From 56a5afdffa559f6d095a24a1f502dea481680dcd Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 21 Jul 2022 15:31:14 +0900 Subject: [PATCH 01/15] exfat: reuse __exfat_write_inode() to update directory entry __exfat_write_inode() is used to update file and stream directory entries, except for file->start_clu and stream->flags. This commit moves update file->start_clu and stream->flags to __exfat_write_inode() and reuse __exfat_write_inode() to update directory entries. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Signed-off-by: Namjae Jeon --- exfat_fs.h | 1 + file.c | 53 ++++------------------------------------------------- inode.c | 37 +++++++++++-------------------------- namei.c | 24 ++++-------------------- 4 files changed, 20 insertions(+), 95 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 6b88270feaed..e419db07b284 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -515,6 +515,7 @@ struct inode *exfat_build_inode(struct super_block *sb, void exfat_hash_inode(struct inode *inode, loff_t i_pos); void exfat_unhash_inode(struct inode *inode); struct inode *exfat_iget(struct super_block *sb, loff_t i_pos); +int __exfat_write_inode(struct inode *inode, int sync); int exfat_write_inode(struct inode *inode, struct writeback_control *wbc); void exfat_evict_inode(struct inode *inode); int exfat_block_truncate_page(struct inode *inode, loff_t from); diff --git a/file.c b/file.c index ebea67c61da9..ad6838a6eb94 100644 --- a/file.c +++ b/file.c @@ -108,7 +108,6 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); - int evict = (ei->dir.dir == DIR_DELETED) ? 1 : 0; /* check if the given file ID is opened */ if (ei->type != TYPE_FILE && ei->type != TYPE_DIR) @@ -157,57 +156,13 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) ei->attr |= ATTR_ARCHIVE; /* update the directory entry */ - if (!evict) { -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) - struct timespec64 ts; -#else - struct timespec ts; -#endif - struct exfat_dentry *ep, *ep2; - struct exfat_entry_set_cache *es; - int err; - - es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, - ES_ALL_ENTRIES); - if (!es) - return -EIO; - ep = exfat_get_dentry_cached(es, 0); - ep2 = exfat_get_dentry_cached(es, 1); - #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - ts = current_time(inode); + inode->i_mtime = current_time(inode); #else - ts = CURRENT_TIME_SEC; + inode->i_mtime = CURRENT_TIME_SEC; #endif - exfat_set_entry_time(sbi, &ts, - &ep->dentry.file.modify_tz, - &ep->dentry.file.modify_time, - &ep->dentry.file.modify_date, - &ep->dentry.file.modify_time_cs); - ep->dentry.file.attr = cpu_to_le16(ei->attr); - - /* File size should be zero if there is no cluster allocated */ - if (ei->start_clu == EXFAT_EOF_CLUSTER) { - ep2->dentry.stream.valid_size = 0; - ep2->dentry.stream.size = 0; - } else { - ep2->dentry.stream.valid_size = cpu_to_le64(new_size); - ep2->dentry.stream.size = ep2->dentry.stream.valid_size; - } - - if (new_size == 0) { - /* Any directory can not be truncated to zero */ - WARN_ON(ei->type != TYPE_FILE); - - ep2->dentry.stream.flags = ALLOC_FAT_CHAIN; - ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; - } - - exfat_update_dir_chksum_with_entry_set(es); - err = exfat_free_dentry_set(es, inode_needs_sync(inode)); - if (err) - return err; - } + if (__exfat_write_inode(inode, inode_needs_sync(inode))) + return -EIO; /* cut off from the FAT chain */ if (ei->flags == ALLOC_FAT_CHAIN && last_clu != EXFAT_FREE_CLUSTER && diff --git a/inode.c b/inode.c index bd04c160848f..3c68a8930ad9 100644 --- a/inode.c +++ b/inode.c @@ -19,7 +19,7 @@ #include "exfat_raw.h" #include "exfat_fs.h" -static int __exfat_write_inode(struct inode *inode, int sync) +int __exfat_write_inode(struct inode *inode, int sync) { unsigned long long on_disk_size; struct exfat_dentry *ep, *ep2; @@ -77,6 +77,13 @@ static int __exfat_write_inode(struct inode *inode, int sync) ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_size); ep2->dentry.stream.size = ep2->dentry.stream.valid_size; + if (on_disk_size) { + ep2->dentry.stream.flags = ei->flags; + ep2->dentry.stream.start_clu = cpu_to_le32(ei->start_clu); + } else { + ep2->dentry.stream.flags = ALLOC_FAT_CHAIN; + ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; + } exfat_update_dir_chksum_with_entry_set(es); return exfat_free_dentry_set(es, sync); @@ -218,32 +225,10 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, num_clusters += num_to_be_allocated; *clu = new_clu.dir; - if (ei->dir.dir != DIR_DELETED && modified) { - struct exfat_dentry *ep; - struct exfat_entry_set_cache *es; - int err; - - es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, - ES_ALL_ENTRIES); - if (!es) + if (modified) { + if (__exfat_write_inode(inode, inode_needs_sync(inode))) return -EIO; - /* get stream entry */ - ep = exfat_get_dentry_cached(es, 1); - - /* update directory entry */ - ep->dentry.stream.flags = ei->flags; - ep->dentry.stream.start_clu = - cpu_to_le32(ei->start_clu); - ep->dentry.stream.valid_size = - cpu_to_le64(i_size_read(inode)); - ep->dentry.stream.size = - ep->dentry.stream.valid_size; - - exfat_update_dir_chksum_with_entry_set(es); - err = exfat_free_dentry_set(es, inode_needs_sync(inode)); - if (err) - return err; - } /* end of if != DIR_DELETED */ + } inode->i_blocks += num_to_be_allocated << sbi->sect_per_clus_bits; diff --git a/namei.c b/namei.c index 523da228ec36..2e99c59637e8 100644 --- a/namei.c +++ b/namei.c @@ -345,7 +345,6 @@ static int exfat_find_empty_entry(struct inode *inode, unsigned int ret, last_clu; loff_t size = 0; struct exfat_chain clu; - struct exfat_dentry *ep = NULL; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); @@ -410,31 +409,16 @@ static int exfat_find_empty_entry(struct inode *inode, p_dir->size++; size = EXFAT_CLU_TO_B(p_dir->size, sbi); - /* update the directory entry */ - if (p_dir->dir != sbi->root_dir) { - struct buffer_head *bh; - - ep = exfat_get_dentry(sb, - &(ei->dir), ei->entry + 1, &bh); - if (!ep) - return -EIO; - - ep->dentry.stream.valid_size = cpu_to_le64(size); - ep->dentry.stream.size = ep->dentry.stream.valid_size; - ep->dentry.stream.flags = p_dir->flags; - exfat_update_bh(bh, IS_DIRSYNC(inode)); - brelse(bh); - if (exfat_update_dir_chksum(inode, &(ei->dir), - ei->entry)) - return -EIO; - } - /* directory inode should be updated in here */ i_size_write(inode, size); ei->i_size_ondisk += sbi->cluster_size; ei->i_size_aligned += sbi->cluster_size; ei->flags = p_dir->flags; inode->i_blocks += 1 << sbi->sect_per_clus_bits; + + /* update the directory entry */ + if (__exfat_write_inode(inode, IS_DIRSYNC(inode))) + return -EIO; } return dentry; From 63e1c4f7c9c657c1628d8a9e164efcbb787dfdc2 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 22 Jul 2022 11:43:04 +0900 Subject: [PATCH 02/15] exfat: remove duplicate write inode for truncating file This commit moves updating file attributes and timestamps before calling __exfat_write_inode(), so that all updates of the inode had been written by __exfat_write_inode(), mark_inode_dirty() is unneeded. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Signed-off-by: Namjae Jeon --- file.c | 59 +++++++++++++++++++++++++++++++-------------------------- inode.c | 5 +++++ 2 files changed, 37 insertions(+), 27 deletions(-) diff --git a/file.c b/file.c index ad6838a6eb94..c03f4267f030 100644 --- a/file.c +++ b/file.c @@ -155,12 +155,17 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) if (ei->type == TYPE_FILE) ei->attr |= ATTR_ARCHIVE; - /* update the directory entry */ -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_mtime = current_time(inode); -#else - inode->i_mtime = CURRENT_TIME_SEC; -#endif + /* + * update the directory entry + * + * If the directory entry is updated by mark_inode_dirty(), the + * directory entry will be written after a writeback cycle of + * updating the bitmap/FAT, which may result in clusters being + * freed but referenced by the directory entry in the event of a + * sudden power failure. + * __exfat_write_inode() is called for directory entry, bitmap + * and FAT to be written in a same writeback. + */ if (__exfat_write_inode(inode, inode_needs_sync(inode))) return -EIO; @@ -217,16 +222,6 @@ void exfat_truncate(struct inode *inode, loff_t size) if (err) goto write_size; -#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) - inode->i_ctime = inode->i_mtime = current_time(inode); -#else - inode->i_ctime = inode->i_mtime = CURRENT_TIME_SEC; -#endif - if (IS_DIRSYNC(inode)) - exfat_sync_inode(inode); - else - mark_inode_dirty(inode); - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; write_size: @@ -342,16 +337,12 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) attr->ia_valid &= ~ATTR_MODE; } - if (attr->ia_valid & ATTR_SIZE) { - error = exfat_block_truncate_page(inode, attr->ia_size); - if (error) - goto out; - - down_write(&EXFAT_I(inode)->truncate_lock); - truncate_setsize(inode, attr->ia_size); - exfat_truncate(inode, attr->ia_size); - up_write(&EXFAT_I(inode)->truncate_lock); - } + if (attr->ia_valid & ATTR_SIZE) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + inode->i_mtime = inode->i_ctime = current_time(inode); +#else + inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) setattr_copy(&init_user_ns, inode, attr); @@ -359,8 +350,22 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) setattr_copy(inode, attr); #endif exfat_truncate_atime(&inode->i_atime); - mark_inode_dirty(inode); + if (attr->ia_valid & ATTR_SIZE) { + error = exfat_block_truncate_page(inode, attr->ia_size); + if (error) + goto out; + + down_write(&EXFAT_I(inode)->truncate_lock); + truncate_setsize(inode, attr->ia_size); + /* + * __exfat_write_inode() is called from exfat_truncate(), inode + * is already written by it, so mark_inode_dirty() is unneeded. + */ + exfat_truncate(inode, attr->ia_size); + up_write(&EXFAT_I(inode)->truncate_lock); + } else + mark_inode_dirty(inode); out: return error; } diff --git a/inode.c b/inode.c index 3c68a8930ad9..8cbc49835902 100644 --- a/inode.c +++ b/inode.c @@ -379,6 +379,11 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) if (to > i_size_read(inode)) { truncate_pagecache(inode, i_size_read(inode)); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0) + inode->i_mtime = inode->i_ctime = current_time(inode); +#else + inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; +#endif exfat_truncate(inode, EXFAT_I(inode)->i_size_aligned); } } From 8d627e30015823501705a9b4e130a22037f2328f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 21 Jul 2022 10:38:21 +0900 Subject: [PATCH 03/15] exfat: remove duplicate write inode for extending dir/file Since the timestamps need to be updated, the directory entries will be updated by mark_inode_dirty() whether or not a new cluster is allocated for the file or directory, so there is no need to use __exfat_write_inode() to update the directory entries when allocating a new cluster for a file or directory. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Daniel Palmer Signed-off-by: Namjae Jeon --- inode.c | 9 +-------- namei.c | 4 ---- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/inode.c b/inode.c index 8cbc49835902..0a1bd5f0baf6 100644 --- a/inode.c +++ b/inode.c @@ -114,7 +114,7 @@ void exfat_sync_inode(struct inode *inode) static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, unsigned int *clu, int create) { - int ret, modified = false; + int ret; unsigned int last_clu; struct exfat_chain new_clu; struct super_block *sb = inode->i_sb; @@ -205,7 +205,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, if (new_clu.flags == ALLOC_FAT_CHAIN) ei->flags = ALLOC_FAT_CHAIN; ei->start_clu = new_clu.dir; - modified = true; } else { if (new_clu.flags != ei->flags) { /* no-fat-chain bit is disabled, @@ -215,7 +214,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, exfat_chain_cont_cluster(sb, ei->start_clu, num_clusters); ei->flags = ALLOC_FAT_CHAIN; - modified = true; } if (new_clu.flags == ALLOC_FAT_CHAIN) if (exfat_ent_set(sb, last_clu, new_clu.dir)) @@ -225,11 +223,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, num_clusters += num_to_be_allocated; *clu = new_clu.dir; - if (modified) { - if (__exfat_write_inode(inode, inode_needs_sync(inode))) - return -EIO; - } - inode->i_blocks += num_to_be_allocated << sbi->sect_per_clus_bits; diff --git a/namei.c b/namei.c index 2e99c59637e8..c9482b8d1548 100644 --- a/namei.c +++ b/namei.c @@ -415,10 +415,6 @@ static int exfat_find_empty_entry(struct inode *inode, ei->i_size_aligned += sbi->cluster_size; ei->flags = p_dir->flags; inode->i_blocks += 1 << sbi->sect_per_clus_bits; - - /* update the directory entry */ - if (__exfat_write_inode(inode, IS_DIRSYNC(inode))) - return -EIO; } return dentry; From dda72dc5365138180ee7fa16b4a31b9fa460f3d0 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 21 Jul 2022 11:28:46 +0900 Subject: [PATCH 04/15] block: remove QUEUE_FLAG_DISCARD MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Just use a non-zero max_discard_sectors as an indicator for discard support, similar to what is done for write zeroes. The only places where needs special attention is the RAID5 driver, which must clear discard support for security reasons by default, even if the default stacking rules would allow for it. Signed-off-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Acked-by: Christoph Böhmwalder [drbd] Acked-by: Jan Höppner [s390] Acked-by: Coly Li [bcache] Acked-by: David Sterba [btrfs] Reviewed-by: Chaitanya Kulkarni Link: https://lore.kernel.org/r/20220415045258.199825-25-hch@lst.de Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- file.c | 4 ++++ super.c | 22 ++++++++++++++++++---- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/file.c b/file.c index c03f4267f030..7ccd3fda1133 100644 --- a/file.c +++ b/file.c @@ -379,7 +379,11 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) if (!capable(CAP_SYS_ADMIN)) return -EPERM; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + if (!bdev_max_discard_sectors(inode->i_sb->s_bdev)) +#else if (!blk_queue_discard(q)) +#endif return -EOPNOTSUPP; if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range))) diff --git a/super.c b/super.c index 6837e042be7f..f672249dde22 100644 --- a/super.c +++ b/super.c @@ -559,14 +559,21 @@ out: if (opts->allow_utime == -1) opts->allow_utime = ~opts->fs_dmask & (0022); +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + if (opts->discard && !bdev_max_discard_sectors(sb->s_bdev)) { + exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); + opts->discard = 0; + } +#else if (opts->discard) { struct request_queue *q = bdev_get_queue(sb->s_bdev); - if (!blk_queue_discard(q)) - exfat_msg(sb, KERN_WARNING, - "mounting with \"discard\" option, but the device does not support discard"); - opts->discard = 0; + if (!blk_queue_discard(q)) { + exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); + opts->discard = 0; + } } +#endif return 0; } @@ -896,6 +903,12 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) if (opts->allow_utime == (unsigned short)-1) opts->allow_utime = ~opts->fs_dmask & 0022; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + if (opts->discard && !bdev_max_discard_sectors(sb->s_bdev)) { + exfat_warn(sb, "mounting with \"discard\" option, but the device does not support discard"); + opts->discard = 0; + } +#else if (opts->discard) { struct request_queue *q = bdev_get_queue(sb->s_bdev); @@ -904,6 +917,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) opts->discard = 0; } } +#endif #else struct exfat_sb_info *sbi; From d196c9a44bb77c3808ca665968ec032f6f8fd6c6 Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Thu, 21 Jul 2022 11:40:30 +0900 Subject: [PATCH 05/15] block: add a bdev_discard_granularity helper MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Abstract away implementation details from file systems by providing a block_device based helper to retrieve the discard granularity. Signed-off-by: Christoph Hellwig Reviewed-by: Martin K. Petersen Acked-by: Christoph Böhmwalder [drbd] Acked-by: Ryusuke Konishi Acked-by: David Sterba [btrfs] Link: https://lore.kernel.org/r/20220415045258.199825-26-hch@lst.de Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- file.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/file.c b/file.c index 7ccd3fda1133..713735143589 100644 --- a/file.c +++ b/file.c @@ -372,7 +372,9 @@ out: static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) { +#if LINUX_VERSION_CODE < KERNEL_VERSION(5, 19, 0) struct request_queue *q = bdev_get_queue(inode->i_sb->s_bdev); +#endif struct fstrim_range range; int ret = 0; @@ -389,8 +391,13 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg) if (copy_from_user(&range, (struct fstrim_range __user *)arg, sizeof(range))) return -EFAULT; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + range.minlen = max_t(unsigned int, range.minlen, + bdev_discard_granularity(inode->i_sb->s_bdev)); +#else range.minlen = max_t(unsigned int, range.minlen, q->limits.discard_granularity); +#endif ret = exfat_trim_fs(inode, &range); if (ret < 0) From ff027ab15c2615ff13876527e35fd08d562f0891 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 21 Jul 2022 11:43:06 +0900 Subject: [PATCH 06/15] fs: Remove aop flags parameter from cont_write_begin() There are no more aop flags left, so remove the parameter. Signed-off-by: Matthew Wilcox (Oracle) Reviewed-by: Christoph Hellwig Signed-off-by: Namjae Jeon --- inode.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/inode.c b/inode.c index 0a1bd5f0baf6..2a50a43e97b7 100644 --- a/inode.c +++ b/inode.c @@ -388,10 +388,15 @@ static int exfat_write_begin(struct file *file, struct address_space *mapping, int ret; *pagep = NULL; +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + ret = cont_write_begin(file, mapping, pos, len, pagep, fsdata, + exfat_get_block, + &EXFAT_I(mapping->host)->i_size_ondisk); +#else ret = cont_write_begin(file, mapping, pos, len, flags, pagep, fsdata, exfat_get_block, &EXFAT_I(mapping->host)->i_size_ondisk); - +#endif if (ret < 0) exfat_write_failed(mapping, pos+len); From 7bcf79cf5a71426ac950df80ab39162d78c44c22 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 21 Jul 2022 11:44:50 +0900 Subject: [PATCH 07/15] fs: Remove flags parameter from aops->write_begin There are no more aop flags left, so remove the parameter. Signed-off-by: Matthew Wilcox (Oracle) Reviewed-by: Christoph Hellwig Signed-off-by: Namjae Jeon --- inode.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/inode.c b/inode.c index 2a50a43e97b7..4acf0aa0c690 100644 --- a/inode.c +++ b/inode.c @@ -381,9 +381,15 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) } } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) +static int exfat_write_begin(struct file *file, struct address_space *mapping, + loff_t pos, unsigned int len, + struct page **pagep, void **fsdata) +#else static int exfat_write_begin(struct file *file, struct address_space *mapping, loff_t pos, unsigned int len, unsigned int flags, struct page **pagep, void **fsdata) +#endif { int ret; From c9d13ea12aecbf43c2d7de8444c882cf55327064 Mon Sep 17 00:00:00 2001 From: "Matthew Wilcox (Oracle)" Date: Thu, 21 Jul 2022 12:00:32 +0900 Subject: [PATCH 08/15] fs: Convert mpage_readpage to mpage_read_folio mpage_readpage still works in terms of pages, and has not been audited for correctness with large folios, so include an assertion that the filesystem is not passing it large folios. Convert all the filesystems to call mpage_read_folio() instead of mpage_readpage(). Signed-off-by: Matthew Wilcox (Oracle) Signed-off-by: Namjae Jeon --- inode.c | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/inode.c b/inode.c index 4acf0aa0c690..28692a095974 100644 --- a/inode.c +++ b/inode.c @@ -337,10 +337,17 @@ unlock_ret: return err; } +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) +static int exfat_read_folio(struct file *file, struct folio *folio) +{ + return mpage_read_folio(folio, exfat_get_block); +} +#else static int exfat_readpage(struct file *file, struct page *page) { return mpage_readpage(page, exfat_get_block); } +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) static void exfat_readahead(struct readahead_control *rac) @@ -516,7 +523,11 @@ static const struct address_space_operations exfat_aops = { #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 18, 0) .invalidate_folio = block_invalidate_folio, #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 19, 0) + .read_folio = exfat_read_folio, +#else .readpage = exfat_readpage, +#endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 8, 0) .readahead = exfat_readahead, #else From 5c189709c987cf505ae2aabdfbfa93d796444c01 Mon Sep 17 00:00:00 2001 From: Christophe Vu-Brugier Date: Thu, 21 Jul 2022 12:08:05 +0900 Subject: [PATCH 09/15] exfat: simplified by using round_up() Signed-off-by: Christophe Vu-Brugier Signed-off-by: Namjae Jeon --- file.c | 4 ++-- inode.c | 4 ++-- super.c | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/file.c b/file.c index 713735143589..6e37277bbd84 100644 --- a/file.c +++ b/file.c @@ -222,8 +222,8 @@ void exfat_truncate(struct inode *inode, loff_t size) if (err) goto write_size; - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & - ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> + inode->i_blkbits; write_size: aligned_size = i_size_read(inode); if (aligned_size & (blocksize - 1)) { diff --git a/inode.c b/inode.c index 28692a095974..f3dbeb300c64 100644 --- a/inode.c +++ b/inode.c @@ -646,8 +646,8 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) exfat_save_attr(inode, info->attr); - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & - ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> + inode->i_blkbits; inode->i_mtime = info->mtime; inode->i_ctime = info->mtime; ei->i_crtime = info->crtime; diff --git a/super.c b/super.c index f672249dde22..cd6a54d0cab4 100644 --- a/super.c +++ b/super.c @@ -632,8 +632,8 @@ static int exfat_read_root(struct inode *inode) inode->i_op = &exfat_dir_inode_operations; inode->i_fop = &exfat_dir_operations; - inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1)) & - ~((loff_t)sbi->cluster_size - 1)) >> inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> + inode->i_blkbits; ei->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff; ei->i_size_aligned = i_size_read(inode); ei->i_size_ondisk = i_size_read(inode); From 3cc661ccd509c9202ebee3781b69c03aef681172 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 10:19:13 +0900 Subject: [PATCH 10/15] exfat: Return ENAMETOOLONG consistently for oversized paths LTP has a test for oversized file path renames and it expects the return value to be ENAMETOOLONG. However, exfat returns EINVAL unexpectedly in some cases, hence LTP test fails. The further investigation indicated that the problem happens only when iocharset isn't set to utf8. The difference comes from that, in the case of utf8, exfat_utf8_to_utf16() returns the error -ENAMETOOLONG directly and it's treated as the final error code. Meanwhile, on other iocharsets, exfat_nls_to_ucs2() returns the max path size but it sets NLS_NAME_OVERLEN to lossy flag instead; the caller side checks only whether lossy flag is set or not, resulting in always -EINVAL unconditionally. This patch aligns the return code for both cases by checking the lossy flag bit and returning ENAMETOOLONG when NLS_NAME_OVERLEN bit is set. BugLink: https://bugzilla.suse.com/show_bug.cgi?id=1201725 Reviewed-by: Petr Vorel Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- namei.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/namei.c b/namei.c index c9482b8d1548..3342f29a7683 100644 --- a/namei.c +++ b/namei.c @@ -469,7 +469,7 @@ static int __exfat_resolve_path(struct inode *inode, const unsigned char *path, return namelen; /* return error value */ if ((lossy && !lookup) || !namelen) - return -EINVAL; + return (lossy & NLS_NAME_OVERLEN) ? -ENAMETOOLONG : -EINVAL; exfat_chain_set(p_dir, ei->start_clu, EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); From 9a903a2731484d34d65d89c593acf27c05761cc3 Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 10:19:53 +0900 Subject: [PATCH 11/15] exfat: Define NLS_NAME_* as bit flags explicitly NLS_NAME_* are bit flags although they are currently defined as enum; it's casually working so far (from 0 to 2), but it's error-prone and may bring a problem when we want to add more flag. This patch changes the definitions of NLS_NAME_* explicitly being bit flags. Reviewed-by: Petr Vorel Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- exfat_fs.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index e419db07b284..90d42a24f3e1 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -36,9 +36,9 @@ enum exfat_error_mode { * exfat nls lossy flag */ enum { - NLS_NAME_NO_LOSSY, /* no lossy */ - NLS_NAME_LOSSY, /* just detected incorrect filename(s) */ - NLS_NAME_OVERLEN, /* the length is over than its limit */ + NLS_NAME_NO_LOSSY = 0, /* no lossy */ + NLS_NAME_LOSSY = 1 << 0, /* just detected incorrect filename(s) */ + NLS_NAME_OVERLEN = 1 << 1, /* the length is over than its limit */ }; #define EXFAT_HASH_BITS 8 From 0d2005c28d581fe8f07af3577015baf9c1da6e1b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 17:24:47 +0900 Subject: [PATCH 12/15] exfat: Expand exfat_err() and co directly to pr_*() macro Currently the error and info messages handled by exfat_err() and co are tossed to exfat_msg() function that does nothing but passes the strings with printk() invocation. Not only that this is more overhead by the indirect calls, but also this makes harder to extend for the debug print usage; because of the direct printk() call, you cannot make it for dynamic debug or without debug like the standard helpers such as pr_debug() or dev_dbg(). For addressing the problem, this patch replaces exfat_*() macro to expand to pr_*() directly. Along with it, add the new exfat_debug() macro that is expanded to pr_debug() (which output can be gracefully suppressed via dyndbg). Reviewed-by: Petr Vorel Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- exfat_fs.h | 12 +++++++----- misc.c | 17 ----------------- super.c | 8 ++++---- 3 files changed, 11 insertions(+), 26 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 90d42a24f3e1..f3b001c2349d 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -541,14 +541,16 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) #define exfat_fs_error_ratelimit(sb, fmt, args...) \ __exfat_fs_error(sb, __ratelimit(&EXFAT_SB(sb)->ratelimit), \ fmt, ## args) -void exfat_msg(struct super_block *sb, const char *lv, const char *fmt, ...) - __printf(3, 4) __cold; + +/* expand to pr_*() with prefix */ #define exfat_err(sb, fmt, ...) \ - exfat_msg(sb, KERN_ERR, fmt, ##__VA_ARGS__) + pr_err("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) #define exfat_warn(sb, fmt, ...) \ - exfat_msg(sb, KERN_WARNING, fmt, ##__VA_ARGS__) + pr_warn("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) #define exfat_info(sb, fmt, ...) \ - exfat_msg(sb, KERN_INFO, fmt, ##__VA_ARGS__) + pr_info("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) +#define exfat_debug(sb, fmt, ...) \ + pr_debug("exFAT-fs (%s): " fmt "\n", (sb)->s_id, ##__VA_ARGS__) #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 19, 0) void exfat_get_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, diff --git a/misc.c b/misc.c index 1b75c8b97f7d..e7ae6f629d6e 100644 --- a/misc.c +++ b/misc.c @@ -52,23 +52,6 @@ void __exfat_fs_error(struct super_block *sb, int report, const char *fmt, ...) } } -/* - * exfat_msg() - print preformated EXFAT specific messages. - * All logs except what uses exfat_fs_error() should be written by exfat_msg() - */ -void exfat_msg(struct super_block *sb, const char *level, const char *fmt, ...) -{ - struct va_format vaf; - va_list args; - - va_start(args, fmt); - vaf.fmt = fmt; - vaf.va = &args; - /* level means KERN_ pacility level */ - printk("%sexFAT-fs (%s): %pV\n", level, sb->s_id, &vaf); - va_end(args); -} - #define SECS_PER_MIN (60) #define TIMEZONE_SEC(x) ((x) * 15 * SECS_PER_MIN) diff --git a/super.c b/super.c index cd6a54d0cab4..f8bce05d2b33 100644 --- a/super.c +++ b/super.c @@ -547,9 +547,9 @@ static int parse_options(struct super_block *sb, char *options, int silent, break; default: if (!silent) { - exfat_msg(sb, KERN_ERR, - "unrecognized mount option \"%s\" or missing value", - p); + exfat_err(sb, + "unrecognized mount option \"%s\" or missing value", + p); } return -EINVAL; } @@ -938,7 +938,7 @@ static int exfat_fill_super(struct super_block *sb, void *data, int silent) DEFAULT_RATELIMIT_BURST); err = parse_options(sb, data, silent, &sbi->options); if (err) { - exfat_msg(sb, KERN_ERR, "failed to parse options"); + exfat_err(sb, "failed to parse options"); goto check_nls_io; } #endif From 33e7a40648bc7c2cbd4e6ba1d773daf8bd5d464c Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 10:20:59 +0900 Subject: [PATCH 13/15] exfat: Downgrade ENAMETOOLONG error message to debug messages The ENAMETOOLONG error message is printed at each time when user tries to operate with a too long name, and this can flood the kernel logs easily, as every user can trigger this. Let's downgrade this error message level to a debug message for suppressing the superfluous logs. BugLink: https://bugzilla.suse.com/show_bug.cgi?id=1201725 Reviewed-by: Petr Vorel Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- nls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nls.c b/nls.c index ef74f1639638..5efd2b103d6d 100644 --- a/nls.c +++ b/nls.c @@ -513,7 +513,7 @@ static int exfat_utf8_to_utf16(struct super_block *sb, } if (unilen > MAX_NAME_LENGTH) { - exfat_err(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", + exfat_debug(sb, "failed to %s (estr:ENAMETOOLONG) nls len : %d, unilen : %d > %d", __func__, len, unilen, MAX_NAME_LENGTH); return -ENAMETOOLONG; } From a129bcc6453a78d2d878096ac26ebc4e86238f0b Mon Sep 17 00:00:00 2001 From: Takashi Iwai Date: Fri, 29 Jul 2022 10:21:39 +0900 Subject: [PATCH 14/15] exfat: Drop superfluous new line for error messages exfat_err() adds the new line at the end of the message by itself, hence the passed string shouldn't contain a new line. Drop the superfluous newline letters in the error messages in a few places that have been put mistakenly. Reported-by: Joe Perches Signed-off-by: Takashi Iwai Signed-off-by: Namjae Jeon --- fatent.c | 2 +- nls.c | 2 +- super.c | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/fatent.c b/fatent.c index b247cbdf987d..8c073c33543c 100644 --- a/fatent.c +++ b/fatent.c @@ -346,7 +346,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, /* find new cluster */ if (hint_clu == EXFAT_EOF_CLUSTER) { if (sbi->clu_srch_ptr < EXFAT_FIRST_CLUSTER) { - exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)\n", + exfat_err(sb, "sbi->clu_srch_ptr is invalid (%u)", sbi->clu_srch_ptr); sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER; } diff --git a/nls.c b/nls.c index 5efd2b103d6d..3b9fd4670aa2 100644 --- a/nls.c +++ b/nls.c @@ -679,7 +679,7 @@ static int exfat_load_upcase_table(struct super_block *sb, bh = sb_bread(sb, sector); if (!bh) { - exfat_err(sb, "failed to read sector(0x%llx)\n", + exfat_err(sb, "failed to read sector(0x%llx)", (unsigned long long)sector); ret = -EIO; goto free_table; diff --git a/super.c b/super.c index f8bce05d2b33..c5d55b0308a9 100644 --- a/super.c +++ b/super.c @@ -728,7 +728,7 @@ static int exfat_read_boot_sector(struct super_block *sb) */ if (p_boot->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS || p_boot->sect_size_bits > EXFAT_MAX_SECT_SIZE_BITS) { - exfat_err(sb, "bogus sector size bits : %u\n", + exfat_err(sb, "bogus sector size bits : %u", p_boot->sect_size_bits); return -EINVAL; } @@ -737,7 +737,7 @@ static int exfat_read_boot_sector(struct super_block *sb) * sect_per_clus_bits could be at least 0 and at most 25 - sect_size_bits. */ if (p_boot->sect_per_clus_bits > EXFAT_MAX_SECT_PER_CLUS_BITS(p_boot)) { - exfat_err(sb, "bogus sectors bits per cluster : %u\n", + exfat_err(sb, "bogus sectors bits per cluster : %u", p_boot->sect_per_clus_bits); return -EINVAL; } From d7cd524f7fb71a99dec757bd00920a4522c39ce0 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 29 Jul 2022 17:29:01 +0900 Subject: [PATCH 15/15] exfat: add auto build-test and simple stability test using travis-CI When backporting mainline exfat patch, there are build issue from low kernel version. This patch add auto build-test for exfat with 4.1 kernel and simple tests. Signed-off-by: Namjae Jeon --- .travis.yml | 96 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000000..04cc4a413626 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,96 @@ +dist: bionic + +language: c + +notifications: + - email: true + +before_script: + # Download the kernel + - sudo apt-get install libelf-dev wget tar gzip python + - wget https://mirrors.edge.kernel.org/pub/linux/kernel/v4.x/linux-4.1.36.tar.gz + - tar xf linux-4.1.36.tar.gz + - mv linux-4.1.36 linux-stable + - ./.travis_get_mainline_kernel + - cp ./.travis_cmd_wrapper.pl ~/travis_cmd_wrapper.pl + # Prerequisite for xfstests testing + - sudo apt-get install linux-headers-$(uname -r) + - sudo apt-get install autoconf libtool pkg-config libnl-3-dev libnl-genl-3-dev + - sudo apt-get install xfslibs-dev uuid-dev libtool-bin xfsprogs libgdbm-dev gawk fio attr libattr1-dev libacl1-dev libaio-dev + - git clone --branch=exfat-next https://github.com/exfat-utils/exfat-utils + - git clone https://github.com/namjaejeon/exfat-testsuites + - export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH + - export PATH=/usr/local/lib:$PATH + - sudo useradd fsgqa + - sudo useradd 123456-fsgqa + +script: + # Copy ksmbd source to kernel + - mv linux-stable ../ + - mv linux ../ + - mkdir ../linux-stable/fs/exfat + - cp -ar * ../linux-stable/fs/exfat/ + - cp -ar * ../linux/fs/exfat/ + + # Compile with 4.1 kernel + - cd ../linux-stable + - yes "" | make oldconfig > /dev/null + - echo 'obj-$(CONFIG_EXFAT_FS) += exfat/' >> fs/Makefile + - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig + - echo 'CONFIG_EXFAT_FS=m' >> .config + - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config + - make -j$((`nproc`+1)) fs/exfat/exfat.ko + + # Compile with latest Torvalds' kernel +# - cd ../linux +# - yes "" | make oldconfig > /dev/null +# - echo 'obj-$(CONFIG_EXFAT) += exfat/' >> fs/Makefile +# - echo 'source "fs/exfat/Kconfig"' >> fs/Kconfig +# - echo 'CONFIG_EXFAT_FS=m' >> .config +# - echo 'CONFIG_EXFAT_DEFAULT_IOCHARSET="utf8"' >> .config +# - make -j$((`nproc`+1)) fs/exfat/exfat.ko + + # Run xfstests testsuite + - cd ../linux-exfat-oot + - make > /dev/null + - sudo make install > /dev/null + - sudo modprobe exfat + - cd exfat-utils + - ./autogen.sh > /dev/null + - ./configure > /dev/null + - make -j$((`nproc`+1)) > /dev/null + - sudo make install > /dev/null + - sudo mkdir -p /mnt/scratch + - sudo mkdir -p /mnt/test + - sudo mkdir -p /mnt/full_test + # create file/director test + - truncate -s 10G full_test.img + - sudo losetup /dev/loop22 full_test.img + - sudo mkfs.exfat /dev/loop22 + - sudo mount -t exfat /dev/loop22 /mnt/full_test/ + - cd /mnt/full_test/ + - i=1;while [ $i -le 10000 ];do sudo touch file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - sync + - sudo fsck.exfat /dev/loop22 + - sudo rm -rf * + - i=1;while [ $i -le 10000 ];do sudo mkdir file$i;if [ $? != 0 ]; then exit 1; fi; i=$(($i + 1));done + - sync + - sudo rm -rf * + - sudo fsck.exfat /dev/loop22 + - cd - + - sudo umount /mnt/full_test/ + - sudo fsck.exfat /dev/loop22 + # run xfstests test + - truncate -s 100G test.img + - truncate -s 100G scratch.img + - sudo losetup /dev/loop20 test.img + - sudo losetup /dev/loop21 scratch.img + - sudo mkfs.exfat /dev/loop20 + - sudo mkfs.exfat /dev/loop21 + - cd .. + - cd exfat-testsuites/ + - tar xzvf xfstests-exfat.tgz > /dev/null + - cd xfstests-exfat + - make -j$((`nproc`+1)) > /dev/null + - sudo ./check generic/001 + - sudo ./check generic/006