From 3c217a5688a5a76e81061e064a25cdc598b8581e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 16 Oct 2014 14:44:50 +1100 Subject: fat: add i_disksize to represent uninitialized size This patchset provides support for doing fallocate operation on FAT filesystem. This patch (of 5): Add i_disksize to represent uninitialized allocated size. And mmu_private represent initialized allocated size. i_disksize - is always kept cluster size aligned mmu_private - is normally block size aligned Signed-off-by: Namjae Jeon Signed-off-by: Amit Sahrawat Cc: OGAWA Hirofumi Signed-off-by: Andrew Morton --- fs/fat/fat.h | 3 ++- fs/fat/file.c | 5 ++++- fs/fat/inode.c | 11 +++++++++-- 3 files changed, 15 insertions(+), 4 deletions(-) (limited to 'fs') diff --git a/fs/fat/fat.h b/fs/fat/fat.h index e0c4ba39a377..f6bcaa7f1a10 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -119,7 +119,8 @@ struct msdos_inode_info { unsigned int cache_valid_id; /* NOTE: mmu_private is 64bits, so must hold ->i_mutex to access */ - loff_t mmu_private; /* physically allocated size */ + loff_t mmu_private; /* physically allocated size (initialized) */ + loff_t i_disksize; /* physically allocated size (uninitialized) */ int i_start; /* first cluster or 0 */ int i_logstart; /* logical first cluster */ diff --git a/fs/fat/file.c b/fs/fat/file.c index 85f79a89e747..288cf1ffae0d 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -300,8 +300,11 @@ void fat_truncate_blocks(struct inode *inode, loff_t offset) * This protects against truncating a file bigger than it was then * trying to write into the hole. */ - if (MSDOS_I(inode)->mmu_private > offset) + if (MSDOS_I(inode)->mmu_private > offset) { MSDOS_I(inode)->mmu_private = offset; + MSDOS_I(inode)->i_disksize = round_up(offset, + sbi->cluster_size); + } nr_clusters = (offset + (cluster_size - 1)) >> sbi->cluster_bits; diff --git a/fs/fat/inode.c b/fs/fat/inode.c index 756aead10d96..21c244289d2f 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -104,6 +104,7 @@ static struct fat_floppy_defaults { static int fat_add_cluster(struct inode *inode) { int err, cluster; + struct msdos_sb_info *sbi = MSDOS_SB(inode->i_sb); err = fat_alloc_clusters(inode, &cluster, 1); if (err) @@ -113,6 +114,9 @@ static int fat_add_cluster(struct inode *inode) err = fat_chain_add(inode, cluster, 1); if (err) fat_free_clusters(inode, cluster); + else + MSDOS_I(inode)->i_disksize += sbi->cluster_size; + return err; } @@ -469,7 +473,6 @@ int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de) error = fat_calc_dir_size(inode); if (error < 0) return error; - MSDOS_I(inode)->mmu_private = inode->i_size; set_nlink(inode, fat_subdirs(inode)); } else { /* not a directory */ @@ -484,8 +487,11 @@ int fat_fill_inode(struct inode *inode, struct msdos_dir_entry *de) inode->i_op = &fat_file_inode_operations; inode->i_fop = &fat_file_operations; inode->i_mapping->a_ops = &fat_aops; - MSDOS_I(inode)->mmu_private = inode->i_size; } + + MSDOS_I(inode)->mmu_private = inode->i_size; + MSDOS_I(inode)->i_disksize = round_up(inode->i_size, sbi->cluster_size); + if (de->attr & ATTR_SYS) { if (sbi->options.sys_immutable) inode->i_flags |= S_IMMUTABLE; @@ -1293,6 +1299,7 @@ static int fat_read_root(struct inode *inode) & ~((loff_t)sbi->cluster_size - 1)) >> 9; MSDOS_I(inode)->i_logstart = 0; MSDOS_I(inode)->mmu_private = inode->i_size; + MSDOS_I(inode)->i_disksize = round_up(inode->i_size, sbi->cluster_size); fat_save_attrs(inode, ATTR_DIR); inode->i_mtime.tv_sec = inode->i_atime.tv_sec = inode->i_ctime.tv_sec = 0; -- cgit v1.2.3 From 05acfcaa5666af45f838575311c5706680f5dd36 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 16 Oct 2014 14:44:50 +1100 Subject: fat: add fat_fallocate operation Implement preallocation via the fallocate syscall on VFAT partitions. This patch is based on an earlier patch of the same name which had some issues detailed below and did not get accepted. Refer https://lkml.org/lkml/2007/12/22/130. a) The preallocated space was not persistent when the FALLOC_FL_KEEP_SIZE flag was set. It will deallocate cluster at evict time. b) There was no need to zero out the clusters when the flag was set Instead of doing an expanding truncate, just allocate clusters and add them to the fat chain. This reduces preallocation time. Compatibility with windows: There are no issues when FALLOC_FL_KEEP_SIZE is not set because it just does an expanding truncate. Thus reading from the preallocated area on windows returns null until data is written to it. When a file with preallocated area using the FALLOC_FL_KEEP_SIZE was written to on windows, the windows driver freed-up the preallocated clusters and allocated new clusters for the new data. The freed up clusters gets reflected in the free space available for the partition which can be seen from the Volume properties. The windows chkdsk tool also does not report any errors on a disk containing files with preallocated space. And there is also no issue using linux fat fsck. because discard preallocated clusters at repair time. Signed-off-by: Namjae Jeon Signed-off-by: Amit Sahrawat Cc: OGAWA Hirofumi Signed-off-by: Andrew Morton --- fs/fat/file.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ fs/fat/inode.c | 23 ++++++++++++++++++ 2 files changed, 100 insertions(+) (limited to 'fs') diff --git a/fs/fat/file.c b/fs/fat/file.c index 288cf1ffae0d..f2c73aea7e80 100644 --- a/fs/fat/file.c +++ b/fs/fat/file.c @@ -17,8 +17,12 @@ #include #include #include +#include #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; @@ -182,6 +186,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) @@ -220,6 +225,78 @@ 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. + */ +static long fat_fallocate(struct file *file, int mode, + loff_t offset, loff_t len) +{ + int cluster; + int nr_cluster; /* Number of clusters to be allocated */ + loff_t mm_bytes; /* Number of bytes to be allocated for 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; + + /* No support for dir */ + if (!S_ISREG(inode->i_mode)) + return -EOPNOTSUPP; + + mutex_lock(&inode->i_mutex); + + if (mode & FALLOC_FL_KEEP_SIZE) { + if ((offset + len) <= MSDOS_I(inode)->i_disksize) + goto error; + + /* First compute the number of clusters to be allocated */ + mm_bytes = offset + len - MSDOS_I(inode)->i_disksize; + nr_cluster = (mm_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)->i_disksize += sbi->cluster_size; + } + } else { + if ((offset + len) <= MSDOS_I(inode)->i_disksize) + goto error; + + err = inode_newsize_ok(inode, (len + offset)); + if (err) + goto error; + + /* 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) { diff --git a/fs/fat/inode.c b/fs/fat/inode.c index 21c244289d2f..b6a2da242ccb 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -556,12 +556,35 @@ out: EXPORT_SYMBOL_GPL(fat_build_inode); +static int __fat_write_inode(struct inode *inode, int wait); static void fat_evict_inode(struct inode *inode) { truncate_inode_pages_final(&inode->i_data); if (!inode->i_nlink) { inode->i_size = 0; fat_truncate_blocks(inode, 0); + } else { + /* Release unwritten fallocated blocks on inode eviction. */ + if (MSDOS_I(inode)->i_disksize > + round_up(MSDOS_I(inode)->mmu_private, + inode->i_sb->s_blocksize)) { + int err; + + fat_truncate_blocks(inode, MSDOS_I(inode)->mmu_private); + /* Fallocate results in updating the i_start/iogstart + * for the zero byte file. So, make it return to + * original state during evict and commit it to avoid + * any corruption on the next access to the cluster + * chain for the file. + */ + err = __fat_write_inode(inode, inode_needs_sync(inode)); + if (err) { + fat_msg(inode->i_sb, KERN_WARNING, "Failed to " + "update on disk inode for unused fallocated " + "blocks, inode could be corrupted. Please run " + "fsck"); + } + } } invalidate_inode_buffers(inode); clear_inode(inode); -- cgit v1.2.3 From 122ebcacd9ea0382ecdb01f760f3f321463b39c0 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 16 Oct 2014 14:44:50 +1100 Subject: fat: skip cluster allocation on fallocated region Skip new cluster allocation after checking i_disksize in _fat_get_block. because the blocks are already allocated in fallocated region. Signed-off-by: Namjae Jeon Signed-off-by: Amit Sahrawat Cc: OGAWA Hirofumi Signed-off-by: Andrew Morton --- fs/fat/inode.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) (limited to 'fs') diff --git a/fs/fat/inode.c b/fs/fat/inode.c index b6a2da242ccb..ecd455c5870e 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -120,6 +120,14 @@ static int fat_add_cluster(struct inode *inode) return err; } +static inline int check_fallocated_region(struct inode *inode, sector_t iblock) +{ + struct super_block *sb = inode->i_sb; + sector_t last_disk_block = MSDOS_I(inode)->i_disksize >> + sb->s_blocksize_bits; + return iblock < last_disk_block; +} + static inline int __fat_get_block(struct inode *inode, sector_t iblock, unsigned long *max_blocks, struct buffer_head *bh_result, int create) @@ -148,7 +156,7 @@ static inline int __fat_get_block(struct inode *inode, sector_t iblock, } offset = (unsigned long)iblock & (sbi->sec_per_clus - 1); - if (!offset) { + if (!offset && !check_fallocated_region(inode, iblock)) { /* TODO: multiple cluster allocation would be desirable. */ err = fat_add_cluster(inode); if (err) -- cgit v1.2.3 From 02ffde1d6c4388ffe41de7f14fcb1fcc1e622776 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 16 Oct 2014 14:44:50 +1100 Subject: fat: permit to return phy block number by fibmap in fallocated region Make the fibmap call the return the proper physical block number for any offset request in the fallocated range. Signed-off-by: Namjae Jeon Signed-off-by: Amit Sahrawat Cc: OGAWA Hirofumi Signed-off-by: Andrew Morton --- fs/fat/cache.c | 40 +++++++++++++++++++++++++++------------- fs/fat/fat.h | 3 +++ fs/fat/inode.c | 35 ++++++++++++++++++++++++++++++++++- 3 files changed, 64 insertions(+), 14 deletions(-) (limited to 'fs') diff --git a/fs/fat/cache.c b/fs/fat/cache.c index 91ad9e1c9441..7010061e8ca5 100644 --- a/fs/fat/cache.c +++ b/fs/fat/cache.c @@ -303,6 +303,31 @@ static int fat_bmap_cluster(struct inode *inode, int cluster) return dclus; } +int fat_get_mapped_cluster(struct inode *inode, sector_t sector, + sector_t last_block, + unsigned long *mapped_blocks, sector_t *bmap) +{ + struct super_block *sb = inode->i_sb; + struct msdos_sb_info *sbi = MSDOS_SB(sb); + int cluster, offset; + + cluster = sector >> (sbi->cluster_bits - sb->s_blocksize_bits); + offset = sector & (sbi->sec_per_clus - 1); + cluster = fat_bmap_cluster(inode, cluster); + + if (cluster < 0) + return cluster; + + else if (cluster) { + *bmap = fat_clus_to_blknr(sbi, cluster) + offset; + *mapped_blocks = sbi->sec_per_clus - offset; + if (*mapped_blocks > last_block - sector) + *mapped_blocks = last_block - sector; + } + + return 0; +} + int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys, unsigned long *mapped_blocks, int create) { @@ -311,7 +336,6 @@ int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys, const unsigned long blocksize = sb->s_blocksize; const unsigned char blocksize_bits = sb->s_blocksize_bits; sector_t last_block; - int cluster, offset; *phys = 0; *mapped_blocks = 0; @@ -338,16 +362,6 @@ int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys, return 0; } - cluster = sector >> (sbi->cluster_bits - sb->s_blocksize_bits); - offset = sector & (sbi->sec_per_clus - 1); - cluster = fat_bmap_cluster(inode, cluster); - if (cluster < 0) - return cluster; - else if (cluster) { - *phys = fat_clus_to_blknr(sbi, cluster) + offset; - *mapped_blocks = sbi->sec_per_clus - offset; - if (*mapped_blocks > last_block - sector) - *mapped_blocks = last_block - sector; - } - return 0; + return fat_get_mapped_cluster(inode, sector, last_block, mapped_blocks, + phys); } diff --git a/fs/fat/fat.h b/fs/fat/fat.h index f6bcaa7f1a10..eff027d275f3 100644 --- a/fs/fat/fat.h +++ b/fs/fat/fat.h @@ -289,6 +289,9 @@ static inline void fatwchar_to16(__u8 *dst, const wchar_t *src, size_t len) extern void fat_cache_inval_inode(struct inode *inode); extern int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus); +extern int fat_get_mapped_cluster(struct inode *inode, sector_t sector, + sector_t last_block, + unsigned long *mapped_blocks, sector_t *bmap); extern int fat_bmap(struct inode *inode, sector_t sector, sector_t *phys, unsigned long *mapped_blocks, int create); diff --git a/fs/fat/inode.c b/fs/fat/inode.c index ecd455c5870e..f36279688a75 100644 --- a/fs/fat/inode.c +++ b/fs/fat/inode.c @@ -294,13 +294,46 @@ static ssize_t fat_direct_IO(int rw, struct kiocb *iocb, return ret; } +static int fat_get_block_bmap(struct inode *inode, sector_t iblock, + struct buffer_head *bh_result, int create) +{ + struct super_block *sb = inode->i_sb; + unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits; + int err; + sector_t bmap, last_block; + unsigned long mapped_blocks; + const unsigned long blocksize = sb->s_blocksize; + const unsigned char blocksize_bits = sb->s_blocksize_bits; + + BUG_ON(create != 0); + + last_block = (MSDOS_I(inode)->i_disksize + (blocksize - 1)) + >> blocksize_bits; + + if (iblock >= last_block) + return 0; + + err = fat_get_mapped_cluster(inode, iblock, last_block, &mapped_blocks, + &bmap); + if (err) + return err; + + if (bmap) { + map_bh(bh_result, sb, bmap); + max_blocks = min(mapped_blocks, max_blocks); + } + + bh_result->b_size = max_blocks << sb->s_blocksize_bits; + return 0; +} + static sector_t _fat_bmap(struct address_space *mapping, sector_t block) { sector_t blocknr; /* fat_get_cluster() assumes the requested blocknr isn't truncated. */ down_read(&MSDOS_I(mapping->host)->truncate_lock); - blocknr = generic_block_bmap(mapping, block, fat_get_block); + blocknr = generic_block_bmap(mapping, block, fat_get_block_bmap); up_read(&MSDOS_I(mapping->host)->truncate_lock); return blocknr; -- cgit v1.2.3