]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - fs/fat/file.c
fat: additions to support fat_fallocate
[karo-tx-linux.git] / fs / fat / file.c
index b0b632e50ddb502fca6fe5ac1533df7bae5c33a5..73264395f705bf0deb9f4315de89c9a9571cb14d 100644 (file)
 #include <linux/blkdev.h>
 #include <linux/fsnotify.h>
 #include <linux/security.h>
+#include <linux/falloc.h>
 #include "fat.h"
 
+static long fat_fallocate(struct file *file, int mode,
+                               loff_t offset, loff_t len);
 static int fat_ioctl_get_attributes(struct inode *inode, u32 __user *user_attr)
 {
        u32 attr;
@@ -140,6 +143,22 @@ static long fat_generic_compat_ioctl(struct file *filp, unsigned int cmd,
 
 static int fat_file_release(struct inode *inode, struct file *filp)
 {
+
+       struct super_block *sb = inode->i_sb;
+       loff_t mmu_private_ideal;
+
+       /*
+        * Release unwritten fallocated blocks on file release.
+        * Do this only when the last open file descriptor is closed.
+        */
+       mutex_lock(&inode->i_mutex);
+       mmu_private_ideal = round_up(inode->i_size, sb->s_blocksize);
+
+       if (mmu_private_ideal < MSDOS_I(inode)->mmu_private &&
+           filp->f_dentry->d_count == 1)
+               fat_truncate_blocks(inode, inode->i_size);
+       mutex_unlock(&inode->i_mutex);
+
        if ((filp->f_mode & FMODE_WRITE) &&
             MSDOS_SB(inode->i_sb)->options.flush) {
                fat_flush_inodes(inode->i_sb, inode, NULL);
@@ -174,6 +193,7 @@ const struct file_operations fat_file_operations = {
 #endif
        .fsync          = fat_file_fsync,
        .splice_read    = generic_file_splice_read,
+       .fallocate      = fat_fallocate,
 };
 
 static int fat_cont_expand(struct inode *inode, loff_t size)
@@ -212,6 +232,88 @@ out:
        return err;
 }
 
+/*
+ * Preallocate space for a file. This implements fat's fallocate file
+ * operation, which gets called from sys_fallocate system call. User
+ * space requests len bytes at offset. If FALLOC_FL_KEEP_SIZE is set
+ * we just allocate clusters without zeroing them out. Otherwise we
+ * allocate and zero out clusters via an expanding truncate. The
+ * allocated clusters are freed in fat_file_release().
+ */
+static long fat_fallocate(struct file *file, int mode,
+                               loff_t offset, loff_t len)
+{
+       int cluster, fclus, dclus;
+       int nr_cluster; /* Number of clusters to be allocated */
+       loff_t nr_bytes; /* Number of bytes to be allocated*/
+       loff_t free_bytes; /* Unused bytes in the last cluster of file*/
+       struct inode *inode = file->f_mapping->host;
+       struct super_block *sb = inode->i_sb;
+       struct msdos_sb_info *sbi = MSDOS_SB(sb);
+       int err = 0;
+
+       /* No support for hole punch or other fallocate flags. */
+       if (mode & ~FALLOC_FL_KEEP_SIZE)
+               return -EOPNOTSUPP;
+
+       mutex_lock(&inode->i_mutex);
+       if ((offset + len) <= MSDOS_I(inode)->mmu_private) {
+               fat_msg(sb, KERN_ERR,
+                       "fat_fallocate(): Blocks already allocated");
+               err = -EINVAL;
+               goto error;
+       }
+
+       if (mode & FALLOC_FL_KEEP_SIZE) {
+               /* First compute the number of clusters to be allocated */
+               if (inode->i_size > 0) {
+                       err = fat_get_cluster(inode, FAT_ENT_EOF,
+                                             &fclus, &dclus);
+                       if (err < 0) {
+                               fat_msg(sb, KERN_ERR,
+                                       "fat_fallocate(): fat_get_cluster() error");
+                               goto error;
+                       }
+                       free_bytes = ((fclus + 1) << sbi->cluster_bits) -
+                                    inode->i_size;
+                       nr_bytes = offset + len - inode->i_size - free_bytes;
+                       MSDOS_I(inode)->mmu_private = (fclus + 1) <<
+                                                     sbi->cluster_bits;
+               } else
+                       nr_bytes = offset + len - inode->i_size;
+
+               nr_cluster = (nr_bytes + (sbi->cluster_size - 1)) >>
+                            sbi->cluster_bits;
+
+               /* Start the allocation.We are not zeroing out the clusters */
+               while (nr_cluster-- > 0) {
+                       err = fat_alloc_clusters(inode, &cluster, 1);
+                       if (err) {
+                               fat_msg(sb, KERN_ERR,
+                                       "fat_fallocate(): fat_alloc_clusters() error");
+                               goto error;
+                       }
+                       err = fat_chain_add(inode, cluster, 1);
+                       if (err) {
+                               fat_free_clusters(inode, cluster);
+                               goto error;
+                       }
+                       MSDOS_I(inode)->mmu_private += sbi->cluster_size;
+               }
+       } else {
+               /* This is just an expanding truncate */
+               err = fat_cont_expand(inode, (offset + len));
+               if (err) {
+                       fat_msg(sb, KERN_ERR,
+                               "fat_fallocate(): fat_cont_expand() error");
+               }
+       }
+
+error:
+       mutex_unlock(&inode->i_mutex);
+       return err;
+}
+
 /* Free all clusters after the skip'th cluster. */
 static int fat_free(struct inode *inode, int skip)
 {
@@ -378,6 +480,9 @@ int fat_setattr(struct dentry *dentry, struct iattr *attr)
        struct inode *inode = dentry->d_inode;
        unsigned int ia_valid;
        int error;
+       loff_t mmu_private_ideal;
+
+       mmu_private_ideal = round_up(inode->i_size, dentry->d_sb->s_blocksize);
 
        /* Check for setting the inode time. */
        ia_valid = attr->ia_valid;
@@ -403,7 +508,8 @@ int fat_setattr(struct dentry *dentry, struct iattr *attr)
        if (attr->ia_valid & ATTR_SIZE) {
                inode_dio_wait(inode);
 
-               if (attr->ia_size > inode->i_size) {
+               if (attr->ia_size > inode->i_size &&
+                   MSDOS_I(inode)->mmu_private <= mmu_private_ideal) {
                        error = fat_cont_expand(inode, attr->ia_size);
                        if (error || attr->ia_valid == ATTR_SIZE)
                                goto out;