From c509458c2757e8f3d42be269b9b0f1b236d31572 Mon Sep 17 00:00:00 2001 From: Sungjong Seo Date: Wed, 8 Jun 2022 06:32:03 +0900 Subject: [PATCH 01/60] exfat: use updated exfat_chain directly during renaming In order for a file to access its own directory entry set, exfat_inode_info(ei) has two copied values. One is ei->dir, which is a snapshot of exfat_chain of the parent directory, and the other is ei->entry, which is the offset of the start of the directory entry set in the parent directory. Since the parent directory can be updated after the snapshot point, it should be used only for accessing one's own directory entry set. However, as of now, during renaming, it could try to traverse or to allocate clusters via snapshot values, it does not make sense. This potential problem has been revealed when exfat_update_parent_info() was removed by commit d8dad2588add ("exfat: fix referencing wrong parent directory information after renaming"). However, I don't think it's good idea to bring exfat_update_parent_info() back. Instead, let's use the updated exfat_chain of parent directory diectly. Signed-off-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/namei.c b/namei.c index cae4ec4ac4d0..523da228ec36 100644 --- a/namei.c +++ b/namei.c @@ -1314,7 +1314,9 @@ static int __exfat_rename(struct inode *old_parent_inode, return -ENOENT; } - exfat_chain_dup(&olddir, &ei->dir); + exfat_chain_set(&olddir, EXFAT_I(old_parent_inode)->start_clu, + EXFAT_B_TO_CLU_ROUND_UP(i_size_read(old_parent_inode), sbi), + EXFAT_I(old_parent_inode)->flags); dentry = ei->entry; ep = exfat_get_dentry(sb, &olddir, dentry, &old_bh); From 56a5afdffa559f6d095a24a1f502dea481680dcd Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 21 Jul 2022 15:31:14 +0900 Subject: [PATCH 02/60] 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 03/60] 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 04/60] 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 05/60] 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 06/60] 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 07/60] 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 08/60] 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 09/60] 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 10/60] 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 11/60] 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 12/60] 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 13/60] 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 14/60] 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 15/60] 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 16/60] 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 From 03aa12a9fdba0ba94e6c18386d203471a5f23514 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Wed, 24 Aug 2022 08:20:34 +0900 Subject: [PATCH 17/60] exfat: fix overflow for large capacity partition Using int type for sector index, there will be overflow in a large capacity partition. For example, if storage with sector size of 512 bytes and partition capacity is larger than 2TB, there will be overflow. Fixes: 1b6138385499 ("exfat: reduce block requests when zeroing a cluster") Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Acked-by: Sungjong Seo Signed-off-by: Namjae Jeon --- fatent.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/fatent.c b/fatent.c index 8c073c33543c..ded465bcf84b 100644 --- a/fatent.c +++ b/fatent.c @@ -278,8 +278,7 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu) struct super_block *sb = dir->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct buffer_head *bh; - sector_t blknr, last_blknr; - int i; + sector_t blknr, last_blknr, i; blknr = exfat_cluster_to_sector(sbi, clu); last_blknr = blknr + sbi->sect_per_clus; From bed6b023a135dcecd07cfac21ccfa4ef7b3ef7b6 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Mon, 17 Oct 2022 16:35:31 +0900 Subject: [PATCH 18/60] exfat: release 6.0.0 version This version number is sync with linux mainline. Major changes are: - Use updated exfat_chain directly instead of snapshot values in rename. - fix the error code of rename syscall. - cleanup and suppress the superfluous error messages. - remove duplicate directory entry update. - fix integer overflow on large partition. Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index f3b001c2349d..0db1f254212f 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -17,7 +17,7 @@ #define EXFAT_SUPER_MAGIC 0x2011BAB0UL #endif -#define EXFAT_VERSION "5.19.1" +#define EXFAT_VERSION "6.0.0" #define EXFAT_ROOT_INO 1 From 9466ec40d94f31159710ef0529cef8247853c0f6 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 20 Oct 2022 22:47:32 +0900 Subject: [PATCH 19/60] exfat: remove travis-CI test We replace auto test of travis-CI with github action. Signed-off-by: Namjae Jeon --- .travis.yml | 96 ------------------------------------- .travis_cmd_wrapper.pl | 65 ------------------------- .travis_get_mainline_kernel | 37 -------------- 3 files changed, 198 deletions(-) delete mode 100644 .travis.yml delete mode 100755 .travis_cmd_wrapper.pl delete mode 100755 .travis_get_mainline_kernel diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 04cc4a413626..000000000000 --- a/.travis.yml +++ /dev/null @@ -1,96 +0,0 @@ -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 diff --git a/.travis_cmd_wrapper.pl b/.travis_cmd_wrapper.pl deleted file mode 100755 index 0cbaea440eb9..000000000000 --- a/.travis_cmd_wrapper.pl +++ /dev/null @@ -1,65 +0,0 @@ -#!/usr/bin/perl - -# -# SPDX-License-Identifier: GPL-2.0-or-later -# -# Copyright (C) 2019 Samsung Electronics Co., Ltd. -# - -use strict; - -sub tweak_sysctl() -{ - `sudo sysctl kernel.hardlockup_panic=0`; - `sudo sysctl kernel.hung_task_panic=0`; - `sudo sysctl kernel.panic=128`; - `sudo sysctl kernel.panic_on_io_nmi=0`; - `sudo sysctl kernel.panic_on_oops=0`; - `sudo sysctl kernel.panic_on_rcu_stall=0`; - `sudo sysctl kernel.panic_on_unrecovered_nmi=0`; - `sudo sysctl kernel.panic_on_warn=0`; - `sudo sysctl kernel.softlockup_panic=0`; - `sudo sysctl kernel.unknown_nmi_panic=0`; -} - -sub execute($$) -{ - my $cmd = shift; - my $timeout = shift; - my $output = "Timeout"; - my $status = 1; - - $timeout = 8 * 60 if (!defined $timeout); - - tweak_sysctl(); - - eval { - local $SIG{ALRM} = sub { - print "TIMEOUT:\n"; - system("top -n 1"), print "top\n"; - system("free"), print "free\n"; - system("dmesg"), print "dmesg\n"; - die "Timeout\n"; - }; - - print "Executing $cmd with timeout $timeout\n"; - - alarm $timeout; - $output = `$cmd`; - $status = $?; - alarm 0; - print $output."\n"; - print "Finished: status $status\n"; - }; - - if ($@) { - die unless $@ eq "Timeout\n"; - } -} - -if (! defined $ARGV[0]) { - print "Usage:\n\t./.travis_cmd_wrapper.pl command [timeout seconds]\n"; - exit 1; -} - -execute($ARGV[0], $ARGV[1]); diff --git a/.travis_get_mainline_kernel b/.travis_get_mainline_kernel deleted file mode 100755 index 3356d15da772..000000000000 --- a/.travis_get_mainline_kernel +++ /dev/null @@ -1,37 +0,0 @@ -#!/bin/sh - -# -# A simple script we are using to get the latest mainline kernel -# tar ball -# - -wget https://www.kernel.org/releases.json -if [ $? -ne 0 ]; then - echo "Could not download kernel.org/releases.json" - exit 1 -fi - -VER=$(cat releases.json | python2.7 -c "import sys, json; print json.load(sys.stdin)['latest_stable']['version']") -if [ $? -ne 0 ]; then - echo "Could not parse release.json" - exit 1 -fi - -if [ "z$VER" = "z" ]; then - echo "Could not determine latest release version" - exit 1 -fi - -wget https://cdn.kernel.org/pub/linux/kernel/v5.x/linux-"$VER".tar.gz -if [ $? -ne 0 ]; then - echo "Could not download $VER kernel version" - exit 1 -fi - -tar xf linux-"$VER".tar.gz -if [ $? -ne 0 ]; then - echo "Could not untar kernel tar ball" - exit 1 -fi - -mv linux-"$VER" linux From c39d9e5ed35bbe4d66077adf870b1b65a5a25c42 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 21 Oct 2022 00:09:23 +0900 Subject: [PATCH 20/60] exfat: add auto-test using github action add auto-test using github action. --- .github/workflows/c-cpp.yml | 184 ++++++++++++++++++++++++++++++++++++ 1 file changed, 184 insertions(+) create mode 100644 .github/workflows/c-cpp.yml diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml new file mode 100644 index 000000000000..60eb8403a1a9 --- /dev/null +++ b/.github/workflows/c-cpp.yml @@ -0,0 +1,184 @@ +name: linux-exfat-oot CI + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Download the kernel + run: | + 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 + - name: Prerequisite for xfstests testing + run: | + 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 + - name: Copy exfat source to kernel + run: | + mv linux-stable ../ + mkdir ../linux-stable/fs/exfat + cp -ar * ../linux-stable/fs/exfat/ + - name: Compile with 4.1 kernel + run: | + 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 + - name: Run xfstests testsuite + run: | + cd ../linux-exfat-oot + make > /dev/null + sudo make install > /dev/null + sudo insmod exfat.ko + cd exfat-utils + ./autogen.sh > /dev/null + ./configure > /dev/null + make -j$((`nproc`+1)) > /dev/null + sudo make install > /dev/null + cd .. + sudo mkdir -p /mnt/scratch + sudo mkdir -p /mnt/test + sudo mkdir -p full_test + - name: create file/director test + run: | + truncate -s 10G full_test.img + sudo losetup /dev/loop22 full_test.img + sudo mkfs.exfat /dev/loop22 + sudo mount -t exfat /dev/loop22 ./full_test/ + cd 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 ./full_test/ + sudo fsck.exfat /dev/loop22 + - name: xfstest tests + run: | + 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 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 + sudo ./check generic/007 + sudo ./check generic/011 + sudo ./check generic/013 + sudo ./check generic/014 + sudo ./check generic/028 + sudo ./check generic/029 + sudo ./check generic/030 + sudo ./check generic/034 + sudo ./check generic/035 + sudo ./check generic/036 + sudo ./check generic/069 + sudo ./check generic/073 + sudo ./check generic/074 + sudo ./check generic/075 + sudo ./check generic/076 + sudo ./check generic/080 + sudo ./check generic/084 + sudo ./check generic/091 + sudo ./check generic/095 + sudo ./check generic/098 + sudo ./check generic/100 + sudo ./check generic/112 + sudo ./check generic/113 + sudo ./check generic/114 + sudo ./check generic/120 + sudo ./check generic/123 + sudo ./check generic/124 + sudo ./check generic/127 + sudo ./check generic/129 + sudo ./check generic/130 + sudo ./check generic/131 + sudo ./check generic/132 + sudo ./check generic/133 + sudo ./check generic/135 + sudo ./check generic/141 + sudo ./check generic/169 + sudo ./check generic/198 + sudo ./check generic/207 + sudo ./check generic/208 + sudo ./check generic/209 + sudo ./check generic/210 + sudo ./check generic/211 + sudo ./check generic/212 + sudo ./check generic/215 + sudo ./check generic/221 + sudo ./check generic/239 + sudo ./check generic/240 + sudo ./check generic/241 + sudo ./check generic/245 + sudo ./check generic/246 + sudo ./check generic/247 + sudo ./check generic/248 + sudo ./check generic/249 + sudo ./check generic/257 + sudo ./check generic/260 + sudo ./check generic/263 + sudo ./check generic/285 + sudo ./check generic/286 + sudo ./check generic/288 + sudo ./check generic/308 + sudo ./check generic/309 + sudo ./check generic/310 + sudo ./check generic/313 + sudo ./check generic/323 + sudo ./check generic/325 + sudo ./check generic/338 + sudo ./check generic/339 + sudo ./check generic/340 + sudo ./check generic/344 + sudo ./check generic/345 + sudo ./check generic/346 + sudo ./check generic/354 + sudo ./check generic/376 + sudo ./check generic/393 + sudo ./check generic/394 + sudo ./check generic/405 + sudo ./check generic/406 + sudo ./check generic/409 + sudo ./check generic/410 + sudo ./check generic/411 + sudo ./check generic/412 + sudo ./check generic/418 + sudo ./check generic/428 + sudo ./check generic/437 + sudo ./check generic/438 + sudo ./check generic/441 + sudo ./check generic/443 + sudo ./check generic/448 + sudo ./check generic/450 + sudo ./check generic/451 + sudo ./check generic/452 From 1050b8d94b11679c7eccd731c566a737038904fe Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 4 Nov 2022 08:18:57 +0900 Subject: [PATCH 21/60] exfat: simplify empty entry hint This commit adds exfat_set_empty_hint()/exfat_reset_empty_hint() to reduce code complexity and make code more readable. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 58 +++++++++++++++++++++++++++++++--------------------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/dir.c b/dir.c index 07765a74251a..b56ea407907f 100644 --- a/dir.c +++ b/dir.c @@ -908,6 +908,29 @@ free_es: return NULL; } +static inline void exfat_reset_empty_hint(struct exfat_hint_femp *hint_femp) +{ + hint_femp->eidx = EXFAT_HINT_NONE; + hint_femp->count = 0; +} + +static inline void exfat_set_empty_hint(struct exfat_inode_info *ei, + struct exfat_hint_femp *candi_empty, struct exfat_chain *clu, + int dentry, int num_entries) +{ + if (ei->hint_femp.eidx == EXFAT_HINT_NONE || + ei->hint_femp.eidx > dentry) { + if (candi_empty->count == 0) { + candi_empty->cur = *clu; + candi_empty->eidx = dentry; + } + + candi_empty->count++; + if (candi_empty->count == num_entries) + ei->hint_femp = *candi_empty; + } +} + enum { DIRENT_STEP_FILE, DIRENT_STEP_STRM, @@ -932,7 +955,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, { int i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; int order, step, name_len = 0; - int dentries_per_clu, num_empty = 0; + int dentries_per_clu; unsigned int entry_type; unsigned short *uniname = NULL; struct exfat_chain clu; @@ -950,10 +973,13 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, end_eidx = dentry; } - candi_empty.eidx = EXFAT_HINT_NONE; + exfat_reset_empty_hint(&ei->hint_femp); + rewind: order = 0; step = DIRENT_STEP_FILE; + exfat_reset_empty_hint(&candi_empty); + while (clu.dir != EXFAT_EOF_CLUSTER) { i = dentry & (dentries_per_clu - 1); for (; i < dentries_per_clu; i++, dentry++) { @@ -973,26 +999,8 @@ rewind: entry_type == TYPE_DELETED) { step = DIRENT_STEP_FILE; - num_empty++; - if (candi_empty.eidx == EXFAT_HINT_NONE && - num_empty == 1) { - exfat_chain_set(&candi_empty.cur, - clu.dir, clu.size, clu.flags); - } - - if (candi_empty.eidx == EXFAT_HINT_NONE && - num_empty >= num_entries) { - candi_empty.eidx = - dentry - (num_empty - 1); - WARN_ON(candi_empty.eidx < 0); - candi_empty.count = num_empty; - - if (ei->hint_femp.eidx == - EXFAT_HINT_NONE || - candi_empty.eidx <= - ei->hint_femp.eidx) - ei->hint_femp = candi_empty; - } + exfat_set_empty_hint(ei, &candi_empty, &clu, + dentry, num_entries); brelse(bh); if (entry_type == TYPE_UNUSED) @@ -1000,8 +1008,7 @@ rewind: continue; } - num_empty = 0; - candi_empty.eidx = EXFAT_HINT_NONE; + exfat_reset_empty_hint(&candi_empty); if (entry_type == TYPE_FILE || entry_type == TYPE_DIR) { step = DIRENT_STEP_FILE; @@ -1101,9 +1108,6 @@ not_found: rewind = 1; dentry = 0; clu.dir = p_dir->dir; - /* reset empty hint */ - num_empty = 0; - candi_empty.eidx = EXFAT_HINT_NONE; goto rewind; } From 5c4fd784c142b4b06b2ba1c4a559b249aa2dae81 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 4 Nov 2022 08:20:09 +0900 Subject: [PATCH 22/60] exfat: hint the empty entry which at the end of cluster chain After traversing all directory entries, hint the empty directory entry no matter whether or not there are enough empty directory entries. After this commit, hint the empty directory entries like this: 1. Hint the deleted directory entries if enough; 2. Hint the deleted and unused directory entries which at the end of the cluster chain no matter whether enough or not(Add by this commit); 3. If no any empty directory entries, hint the empty directory entries in the new cluster(Add by this commit). This avoids repeated traversal of directory entries, reduces CPU usage, and improves the performance of creating files and directories(especially on low-performance CPUs). Test create 5000 files in a class 4 SD card on imx6q-sabrelite with: for ((i=0;i<5;i++)); do sync time (for ((j=1;j<=1000;j++)); do touch file$((i*1000+j)); done) done The more files, the more performance improvements. Before After Improvement 1~1000 25.360s 22.168s 14.40% 1001~2000 38.242s 28.72ss 33.15% 2001~3000 49.134s 35.037s 40.23% 3001~4000 62.042s 41.624s 49.05% 4001~5000 73.629s 46.772s 57.42% Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 26 ++++++++++++++++++++++---- namei.c | 33 +++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 16 deletions(-) diff --git a/dir.c b/dir.c index b56ea407907f..beb7f2c1ff59 100644 --- a/dir.c +++ b/dir.c @@ -916,17 +916,24 @@ static inline void exfat_reset_empty_hint(struct exfat_hint_femp *hint_femp) static inline void exfat_set_empty_hint(struct exfat_inode_info *ei, struct exfat_hint_femp *candi_empty, struct exfat_chain *clu, - int dentry, int num_entries) + int dentry, int num_entries, int entry_type) { if (ei->hint_femp.eidx == EXFAT_HINT_NONE || ei->hint_femp.eidx > dentry) { + int total_entries = EXFAT_B_TO_DEN(i_size_read(&ei->vfs_inode)); + if (candi_empty->count == 0) { candi_empty->cur = *clu; candi_empty->eidx = dentry; } - candi_empty->count++; - if (candi_empty->count == num_entries) + if (entry_type == TYPE_UNUSED) + candi_empty->count += total_entries - dentry; + else + candi_empty->count++; + + if (candi_empty->count == num_entries || + candi_empty->count + candi_empty->eidx == total_entries) ei->hint_femp = *candi_empty; } } @@ -1000,7 +1007,8 @@ rewind: step = DIRENT_STEP_FILE; exfat_set_empty_hint(ei, &candi_empty, &clu, - dentry, num_entries); + dentry, num_entries, + entry_type); brelse(bh); if (entry_type == TYPE_UNUSED) @@ -1111,6 +1119,16 @@ not_found: goto rewind; } + /* + * set the EXFAT_EOF_CLUSTER flag to avoid search + * from the beginning again when allocated a new cluster + */ + if (ei->hint_femp.eidx == EXFAT_HINT_NONE) { + ei->hint_femp.cur.dir = EXFAT_EOF_CLUSTER; + ei->hint_femp.eidx = p_dir->size * dentries_per_clu; + ei->hint_femp.count = 0; + } + /* initialized hint_stat */ hint_stat->clu = p_dir->dir; hint_stat->eidx = 0; diff --git a/namei.c b/namei.c index 3342f29a7683..23ff0c983a1b 100644 --- a/namei.c +++ b/namei.c @@ -251,11 +251,18 @@ static int exfat_search_empty_slot(struct super_block *sb, if (hint_femp->eidx != EXFAT_HINT_NONE) { dentry = hint_femp->eidx; - if (num_entries <= hint_femp->count) { - hint_femp->eidx = EXFAT_HINT_NONE; - return dentry; - } + /* + * If hint_femp->count is enough, it is needed to check if + * there are actual empty entries. + * Otherwise, and if "dentry + hint_famp->count" is also equal + * to "p_dir->size * dentries_per_clu", it means ENOSPC. + */ + if (dentry + hint_femp->count == p_dir->size * dentries_per_clu && + num_entries > hint_femp->count) + return -ENOSPC; + + hint_femp->eidx = EXFAT_HINT_NONE; exfat_chain_dup(&clu, &hint_femp->cur); } else { exfat_chain_dup(&clu, p_dir); @@ -320,6 +327,12 @@ static int exfat_search_empty_slot(struct super_block *sb, } } + hint_femp->eidx = p_dir->size * dentries_per_clu - num_empty; + hint_femp->count = num_empty; + if (num_empty == 0) + exfat_chain_set(&hint_femp->cur, EXFAT_EOF_CLUSTER, 0, + clu.flags); + return -ENOSPC; } @@ -396,15 +409,11 @@ static int exfat_find_empty_entry(struct inode *inode, if (exfat_ent_set(sb, last_clu, clu.dir)) return -EIO; - if (hint_femp.eidx == EXFAT_HINT_NONE) { - /* the special case that new dentry - * should be allocated from the start of new cluster - */ - hint_femp.eidx = EXFAT_B_TO_DEN_IDX(p_dir->size, sbi); - hint_femp.count = sbi->dentries_per_clu; - + if (hint_femp.cur.dir == EXFAT_EOF_CLUSTER) exfat_chain_set(&hint_femp.cur, clu.dir, 0, clu.flags); - } + + hint_femp.count += sbi->dentries_per_clu; + hint_femp.cur.size++; p_dir->size++; size = EXFAT_CLU_TO_B(p_dir->size, sbi); From 54b4c1b26020a3b17bca453c269ed2d6208b4ae4 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 19 Nov 2022 15:23:40 +0900 Subject: [PATCH 23/60] exfat: add SECTOR_SIZE macro SECTOR_SIZE macro is not define in low kernel version. This patch add SECTOR_SIZE macro. Signed-off-by: Namjae Jeon --- exfat_fs.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index 0db1f254212f..486132b69c51 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -11,6 +11,10 @@ #include #include +#ifndef SECTOR_SIZE +#define SECTOR_SIZE 512 +#endif + #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 17, 0) #include #else From 48f7c74a4cfa819184af055c90f31f4975341184 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:39:47 +0900 Subject: [PATCH 24/60] exfat: reduce the size of exfat_entry_set_cache In normal, there are 19 directory entries at most for a file or a directory. - A file directory entry - A stream extension directory entry - 1~17 file name directory entry So the directory entries are in 3 sectors at most, it is enough for struct exfat_entry_set_cache to pre-allocate 3 bh. This commit changes the size of struct exfat_entry_set_cache as: Before After 32-bit system 88 32 bytes 64-bit system 168 48 bytes Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- exfat_fs.h | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 486132b69c51..6b035c0d076e 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -10,6 +10,7 @@ #include #include #include +#include #ifndef SECTOR_SIZE #define SECTOR_SIZE 512 @@ -54,6 +55,14 @@ enum { #define ES_2_ENTRIES 2 #define ES_ALL_ENTRIES 0 +#define ES_IDX_FILE 0 +#define ES_IDX_STREAM 1 +#define ES_IDX_FIRST_FILENAME 2 +#define EXFAT_FILENAME_ENTRY_NUM(name_len) \ + DIV_ROUND_UP(name_len, EXFAT_FILE_NAME_LEN) +#define ES_IDX_LAST_FILENAME(name_len) \ + (ES_IDX_FIRST_FILENAME + EXFAT_FILENAME_ENTRY_NUM(name_len) - 1) + #define DIR_DELETED 0xFFFF0321 /* type values */ @@ -81,9 +90,6 @@ enum { #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ #define MAX_VFSNAME_BUF_SIZE ((MAX_NAME_LENGTH + 1) * MAX_CHARSET_SIZE) -/* Enough size to hold 256 dentry (even 512 Byte sector) */ -#define DIR_CACHE_SIZE (256*sizeof(struct exfat_dentry)/512+1) - #define EXFAT_HINT_NONE -1 #define EXFAT_MIN_SUBDIR 2 @@ -138,6 +144,17 @@ enum { #define BITS_PER_BYTE_MASK 0x7 #define IGNORED_BITS_REMAINED(clu, clu_base) ((1 << ((clu) - (clu_base))) - 1) +#define ES_ENTRY_NUM(name_len) (ES_IDX_LAST_FILENAME(name_len) + 1) +/* 19 entries = 1 file entry + 1 stream entry + 17 filename entries */ +#define ES_MAX_ENTRY_NUM ES_ENTRY_NUM(MAX_NAME_LENGTH) + +/* + * 19 entries x 32 bytes/entry = 608 bytes. + * The 608 bytes are in 3 sectors at most (even 512 Byte sector). + */ +#define DIR_CACHE_SIZE \ + (DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) + struct exfat_dentry_namebuf { char *lfn; int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */ @@ -179,11 +196,11 @@ struct exfat_hint { struct exfat_entry_set_cache { struct super_block *sb; - bool modified; unsigned int start_off; int num_bh; struct buffer_head *bh[DIR_CACHE_SIZE]; unsigned int num_entries; + bool modified; }; struct exfat_dir_entry { From 5d4023f0718d36c71a92e0f7709b7d3150aad2a9 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:24:14 +0900 Subject: [PATCH 25/60] exfat: support dynamic allocate bh for exfat_entry_set_cache In special cases, a file or a directory may occupied more than 19 directory entries, pre-allocating 3 bh is not enough. Such as - Support vendor secondary directory entry in the future. - Since file directory entry is damaged, the SecondaryCount field is bigger than 18. So this commit supports dynamic allocation of bh. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- dir.c | 15 +++++++++++++++ exfat_fs.h | 5 ++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/dir.c b/dir.c index beb7f2c1ff59..46df6e611f52 100644 --- a/dir.c +++ b/dir.c @@ -626,6 +626,10 @@ int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) bforget(es->bh[i]); else brelse(es->bh[i]); + + if (IS_DYNAMIC_ES(es)) + kfree(es->bh); + kfree(es); return err; } @@ -858,6 +862,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, /* byte offset in sector */ off = EXFAT_BLK_OFFSET(byte_offset, sb); es->start_off = off; + es->bh = es->__bh; /* sector offset in cluster */ sec = EXFAT_B_TO_BLK(byte_offset, sb); @@ -877,6 +882,16 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, es->num_entries = num_entries; num_bh = EXFAT_B_TO_BLK_ROUND_UP(off + num_entries * DENTRY_SIZE, sb); + if (num_bh > ARRAY_SIZE(es->__bh)) { + es->bh = kmalloc_array(num_bh, sizeof(*es->bh), GFP_KERNEL); + if (!es->bh) { + brelse(bh); + kfree(es); + return NULL; + } + es->bh[0] = bh; + } + for (i = 1; i < num_bh; i++) { /* get the next sector */ if (exfat_is_last_sector_in_cluster(sbi, sec)) { diff --git a/exfat_fs.h b/exfat_fs.h index 6b035c0d076e..e10d005bf400 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -198,11 +198,14 @@ struct exfat_entry_set_cache { struct super_block *sb; unsigned int start_off; int num_bh; - struct buffer_head *bh[DIR_CACHE_SIZE]; + struct buffer_head *__bh[DIR_CACHE_SIZE]; + struct buffer_head **bh; unsigned int num_entries; bool modified; }; +#define IS_DYNAMIC_ES(es) ((es)->__bh != (es)->bh) + struct exfat_dir_entry { struct exfat_chain dir; int entry; From 7771b09c4dc7ef8be49159ff84418558b84caa3e Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:25:14 +0900 Subject: [PATCH 26/60] exfat: move exfat_entry_set_cache from heap to stack The size of struct exfat_entry_set_cache is only 56 bytes on 64-bit system, and allocating from stack is more efficient than allocating from heap. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- dir.c | 35 +++++++++++++++-------------------- exfat_fs.h | 5 +++-- inode.c | 13 ++++++------- namei.c | 11 +++++------ 4 files changed, 29 insertions(+), 35 deletions(-) diff --git a/dir.c b/dir.c index 46df6e611f52..f663cb12688f 100644 --- a/dir.c +++ b/dir.c @@ -34,10 +34,9 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned short *uniname) { int i; - struct exfat_entry_set_cache *es; + struct exfat_entry_set_cache es; - es = exfat_get_dentry_set(sb, p_dir, entry, ES_ALL_ENTRIES); - if (!es) + if (exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES)) return; /* @@ -46,8 +45,8 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, * Third entry : first file-name entry * So, the index of first file-name dentry should start from 2. */ - for (i = 2; i < es->num_entries; i++) { - struct exfat_dentry *ep = exfat_get_dentry_cached(es, i); + for (i = 2; i < es.num_entries; i++) { + struct exfat_dentry *ep = exfat_get_dentry_cached(&es, i); /* end of name entry */ if (exfat_get_entry_type(ep) != TYPE_EXTEND) @@ -57,7 +56,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, uniname += EXFAT_FILE_NAME_LEN; } - exfat_free_dentry_set(es, false); + exfat_free_dentry_set(&es, false); } /* read a directory entry from the opened directory */ @@ -630,7 +629,6 @@ int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) if (IS_DYNAMIC_ES(es)) kfree(es->bh); - kfree(es); return err; } @@ -827,14 +825,14 @@ struct exfat_dentry *exfat_get_dentry_cached( * pointer of entry set on success, * NULL on failure. */ -struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, - struct exfat_chain *p_dir, int entry, unsigned int type) +int exfat_get_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, int entry, + unsigned int type) { int ret, i, num_bh; unsigned int off, byte_offset, clu = 0; sector_t sec; struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct exfat_entry_set_cache *es; struct exfat_dentry *ep; int num_entries; enum exfat_validate_dentry_mode mode = ES_MODE_STARTED; @@ -842,17 +840,15 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, if (p_dir->dir == DIR_DELETED) { exfat_err(sb, "access to deleted dentry"); - return NULL; + return -EIO; } byte_offset = EXFAT_DEN_TO_B(entry); ret = exfat_walk_fat_chain(sb, p_dir, byte_offset, &clu); if (ret) - return NULL; + return ret; - es = kzalloc(sizeof(*es), GFP_KERNEL); - if (!es) - return NULL; + memset(es, 0, sizeof(*es)); es->sb = sb; es->modified = false; @@ -870,7 +866,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, bh = sb_bread(sb, sec); if (!bh) - goto free_es; + return -EIO; es->bh[es->num_bh++] = bh; ep = exfat_get_dentry_cached(es, 0); @@ -886,8 +882,7 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, es->bh = kmalloc_array(num_bh, sizeof(*es->bh), GFP_KERNEL); if (!es->bh) { brelse(bh); - kfree(es); - return NULL; + return -ENOMEM; } es->bh[0] = bh; } @@ -916,11 +911,11 @@ struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) goto free_es; } - return es; + return 0; free_es: exfat_free_dentry_set(es, false); - return NULL; + return -EIO; } static inline void exfat_reset_empty_hint(struct exfat_hint_femp *hint_femp) diff --git a/exfat_fs.h b/exfat_fs.h index e10d005bf400..a64b687279c2 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -526,8 +526,9 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb, struct exfat_chain *p_dir, int entry, struct buffer_head **bh); struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, int num); -struct exfat_entry_set_cache *exfat_get_dentry_set(struct super_block *sb, - struct exfat_chain *p_dir, int entry, unsigned int type); +int exfat_get_dentry_set(struct exfat_entry_set_cache *es, + struct super_block *sb, struct exfat_chain *p_dir, int entry, + unsigned int type); int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); diff --git a/inode.c b/inode.c index f3dbeb300c64..c66276ea423c 100644 --- a/inode.c +++ b/inode.c @@ -23,7 +23,7 @@ int __exfat_write_inode(struct inode *inode, int sync) { unsigned long long on_disk_size; struct exfat_dentry *ep, *ep2; - struct exfat_entry_set_cache *es = NULL; + struct exfat_entry_set_cache es; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(inode); @@ -44,11 +44,10 @@ int __exfat_write_inode(struct inode *inode, int sync) exfat_set_volume_dirty(sb); /* get the directory entry of given file or directory */ - es = exfat_get_dentry_set(sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES); - if (!es) + if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES)) return -EIO; - ep = exfat_get_dentry_cached(es, 0); - ep2 = exfat_get_dentry_cached(es, 1); + ep = exfat_get_dentry_cached(&es, 0); + ep2 = exfat_get_dentry_cached(&es, 1); ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode)); @@ -85,8 +84,8 @@ int __exfat_write_inode(struct inode *inode, int sync) ep2->dentry.stream.start_clu = EXFAT_FREE_CLUSTER; } - exfat_update_dir_chksum_with_entry_set(es); - return exfat_free_dentry_set(es, sync); + exfat_update_dir_chksum_with_entry_set(&es); + return exfat_free_dentry_set(&es, sync); } int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) diff --git a/namei.c b/namei.c index 23ff0c983a1b..2bb79d21e5b0 100644 --- a/namei.c +++ b/namei.c @@ -654,7 +654,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_inode_info *ei = EXFAT_I(dir); struct exfat_dentry *ep, *ep2; - struct exfat_entry_set_cache *es; + struct exfat_entry_set_cache es; /* for optimized dir & entry to prevent long traverse of cluster chain */ struct exfat_hint hint_opt; @@ -702,11 +702,10 @@ static int exfat_find(struct inode *dir, struct qstr *qname, if (cdir.flags & ALLOC_NO_FAT_CHAIN) cdir.size -= dentry / sbi->dentries_per_clu; dentry = hint_opt.eidx; - es = exfat_get_dentry_set(sb, &cdir, dentry, ES_2_ENTRIES); - if (!es) + if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_2_ENTRIES)) return -EIO; - ep = exfat_get_dentry_cached(es, 0); - ep2 = exfat_get_dentry_cached(es, 1); + ep = exfat_get_dentry_cached(&es, 0); + ep2 = exfat_get_dentry_cached(&es, 1); info->type = exfat_get_entry_type(ep); info->attr = le16_to_cpu(ep->dentry.file.attr); @@ -735,7 +734,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, ep->dentry.file.access_time, ep->dentry.file.access_date, 0); - exfat_free_dentry_set(es, false); + exfat_free_dentry_set(&es, false); if (ei->start_clu == EXFAT_FREE_CLUSTER) { exfat_fs_error(sb, From 7cab729caa62089ba5fbb7fee65e54c1fed7175a Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:27:59 +0900 Subject: [PATCH 27/60] exfat: rename exfat_free_dentry_set() to exfat_put_dentry_set() Since struct exfat_entry_set_cache is allocated from stack, no need to free, so rename exfat_free_dentry_set() to exfat_put_dentry_set(). After renaming, the new function pair is exfat_get_dentry_set()/exfat_put_dentry_set(). Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Signed-off-by: Namjae Jeon --- dir.c | 16 ++++++++-------- exfat_fs.h | 2 +- inode.c | 2 +- namei.c | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/dir.c b/dir.c index f663cb12688f..32ebb2a76d91 100644 --- a/dir.c +++ b/dir.c @@ -56,7 +56,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, uniname += EXFAT_FILE_NAME_LEN; } - exfat_free_dentry_set(&es, false); + exfat_put_dentry_set(&es, false); } /* read a directory entry from the opened directory */ @@ -613,7 +613,7 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) es->modified = true; } -int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync) +int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync) { int i, err = 0; @@ -871,7 +871,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, ep = exfat_get_dentry_cached(es, 0); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) - goto free_es; + goto put_es; num_entries = type == ES_ALL_ENTRIES ? ep->dentry.file.num_ext + 1 : type; @@ -893,7 +893,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, if (p_dir->flags == ALLOC_NO_FAT_CHAIN) clu++; else if (exfat_get_next_cluster(sb, &clu)) - goto free_es; + goto put_es; sec = exfat_cluster_to_sector(sbi, clu); } else { sec++; @@ -901,7 +901,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, bh = sb_bread(sb, sec); if (!bh) - goto free_es; + goto put_es; es->bh[es->num_bh++] = bh; } @@ -909,12 +909,12 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, for (i = 1; i < num_entries; i++) { ep = exfat_get_dentry_cached(es, i); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) - goto free_es; + goto put_es; } return 0; -free_es: - exfat_free_dentry_set(es, false); +put_es: + exfat_put_dentry_set(es, false); return -EIO; } diff --git a/exfat_fs.h b/exfat_fs.h index a64b687279c2..b51e72cae6a7 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -529,7 +529,7 @@ struct exfat_dentry *exfat_get_dentry_cached(struct exfat_entry_set_cache *es, int exfat_get_dentry_set(struct exfat_entry_set_cache *es, struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned int type); -int exfat_free_dentry_set(struct exfat_entry_set_cache *es, int sync); +int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync); int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir); /* inode.c */ diff --git a/inode.c b/inode.c index c66276ea423c..87e679d223c3 100644 --- a/inode.c +++ b/inode.c @@ -85,7 +85,7 @@ int __exfat_write_inode(struct inode *inode, int sync) } exfat_update_dir_chksum_with_entry_set(&es); - return exfat_free_dentry_set(&es, sync); + return exfat_put_dentry_set(&es, sync); } int exfat_write_inode(struct inode *inode, struct writeback_control *wbc) diff --git a/namei.c b/namei.c index 2bb79d21e5b0..27251b3f3017 100644 --- a/namei.c +++ b/namei.c @@ -734,7 +734,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, ep->dentry.file.access_time, ep->dentry.file.access_date, 0); - exfat_free_dentry_set(&es, false); + exfat_put_dentry_set(&es, false); if (ei->start_clu == EXFAT_FREE_CLUSTER) { exfat_fs_error(sb, From 15a51d81f19d305be246e715059ef1c00518604d Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Sat, 26 Nov 2022 12:28:34 +0900 Subject: [PATCH 28/60] exfat: replace magic numbers with Macros Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru --- dir.c | 12 ++++++------ inode.c | 4 ++-- namei.c | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/dir.c b/dir.c index 32ebb2a76d91..b359087e3cbc 100644 --- a/dir.c +++ b/dir.c @@ -45,7 +45,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, * Third entry : first file-name entry * So, the index of first file-name dentry should start from 2. */ - for (i = 2; i < es.num_entries; i++) { + for (i = ES_IDX_FIRST_FILENAME; i < es.num_entries; i++) { struct exfat_dentry *ep = exfat_get_dentry_cached(&es, i); /* end of name entry */ @@ -337,7 +337,7 @@ int exfat_calc_num_entries(struct exfat_uni_name *p_uniname) return -EINVAL; /* 1 file entry + 1 stream entry + name entries */ - return ((len - 1) / EXFAT_FILE_NAME_LEN + 3); + return ES_ENTRY_NUM(len); } unsigned int exfat_get_entry_type(struct exfat_dentry *ep) @@ -602,13 +602,13 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es) unsigned short chksum = 0; struct exfat_dentry *ep; - for (i = 0; i < es->num_entries; i++) { + for (i = ES_IDX_FILE; i < es->num_entries; i++) { ep = exfat_get_dentry_cached(es, i); chksum = exfat_calc_chksum16(ep, DENTRY_SIZE, chksum, chksum_type); chksum_type = CS_DEFAULT; } - ep = exfat_get_dentry_cached(es, 0); + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); ep->dentry.file.checksum = cpu_to_le16(chksum); es->modified = true; } @@ -869,7 +869,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, return -EIO; es->bh[es->num_bh++] = bh; - ep = exfat_get_dentry_cached(es, 0); + ep = exfat_get_dentry_cached(es, ES_IDX_FILE); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) goto put_es; @@ -906,7 +906,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, } /* validate cached dentries */ - for (i = 1; i < num_entries; i++) { + for (i = ES_IDX_STREAM; i < num_entries; i++) { ep = exfat_get_dentry_cached(es, i); if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode)) goto put_es; diff --git a/inode.c b/inode.c index 87e679d223c3..93dc313a3a1c 100644 --- a/inode.c +++ b/inode.c @@ -46,8 +46,8 @@ int __exfat_write_inode(struct inode *inode, int sync) /* get the directory entry of given file or directory */ if (exfat_get_dentry_set(&es, sb, &(ei->dir), ei->entry, ES_ALL_ENTRIES)) return -EIO; - ep = exfat_get_dentry_cached(&es, 0); - ep2 = exfat_get_dentry_cached(&es, 1); + ep = exfat_get_dentry_cached(&es, ES_IDX_FILE); + ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM); ep->dentry.file.attr = cpu_to_le16(exfat_make_attr(inode)); diff --git a/namei.c b/namei.c index 27251b3f3017..f88757013f46 100644 --- a/namei.c +++ b/namei.c @@ -704,8 +704,8 @@ static int exfat_find(struct inode *dir, struct qstr *qname, dentry = hint_opt.eidx; if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_2_ENTRIES)) return -EIO; - ep = exfat_get_dentry_cached(&es, 0); - ep2 = exfat_get_dentry_cached(&es, 1); + ep = exfat_get_dentry_cached(&es, ES_IDX_FILE); + ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM); info->type = exfat_get_entry_type(ep); info->attr = le16_to_cpu(ep->dentry.file.attr); From b2d2ce0b02aa1dcc25e8ca5ad2e42610ad5637e8 Mon Sep 17 00:00:00 2001 From: "Jason A. Donenfeld" Date: Sat, 19 Nov 2022 14:58:44 +0900 Subject: [PATCH 29/60] exfat: treewide: use get_random_u32() when possible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prandom_u32() function has been a deprecated inline wrapper around get_random_u32() for several releases now, and compiles down to the exact same code. Replace the deprecated wrapper with a direct call to the real function. The same also applies to get_random_int(), which is just a wrapper around get_random_u32(). This was done as a basic find and replace. Reviewed-by: Greg Kroah-Hartman Reviewed-by: Kees Cook Reviewed-by: Yury Norov Reviewed-by: Jan Kara # for ext4 Acked-by: Toke Høiland-Jørgensen # for sch_cake Acked-by: Chuck Lever # for nfsd Acked-by: Jakub Kicinski Acked-by: Mika Westerberg # for thunderbolt Acked-by: Darrick J. Wong # for xfs Acked-by: Helge Deller # for parisc Acked-by: Heiko Carstens # for s390 Signed-off-by: Jason A. Donenfeld Signed-off-by: Namjae Jeon --- inode.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/inode.c b/inode.c index 93dc313a3a1c..d0bfa26c6584 100644 --- a/inode.c +++ b/inode.c @@ -615,7 +615,11 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) #else inode->i_version++; #endif +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 1, 0) + inode->i_generation = get_random_u32(); +#else inode->i_generation = prandom_u32(); +#endif if (info->attr & ATTR_SUBDIR) { /* directory */ inode->i_generation &= ~1; From 3a977dbbe0fc62137bb037cd82b9a0dd9ea27fd3 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sat, 26 Nov 2022 15:03:32 +0900 Subject: [PATCH 30/60] exfat: github actions: add apt-get update command --- .github/workflows/c-cpp.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 60eb8403a1a9..362f95a3fd7e 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -15,6 +15,7 @@ jobs: - uses: actions/checkout@v3 - name: Download the kernel run: | + sudo apt-get update 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 From 1813cc2e9f486a5ebc2c731d5440d1d2a5ab2bb1 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 9 Dec 2022 22:56:26 +0900 Subject: [PATCH 31/60] exfat: fix python package installation failure --- .github/workflows/c-cpp.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 362f95a3fd7e..0cb70a7650a1 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -16,7 +16,7 @@ jobs: - name: Download the kernel run: | sudo apt-get update - sudo apt-get install libelf-dev wget tar gzip python + sudo apt-get install libelf-dev wget tar gzip python2.7 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 From 2bb461ce26f78b538c856e4cab11733aa3b4022e Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 9 Dec 2022 23:20:49 +0900 Subject: [PATCH 32/60] exfat: remove generic/286 --- .github/workflows/c-cpp.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 0cb70a7650a1..476c746ba715 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -149,7 +149,6 @@ jobs: sudo ./check generic/260 sudo ./check generic/263 sudo ./check generic/285 - sudo ./check generic/286 sudo ./check generic/288 sudo ./check generic/308 sudo ./check generic/309 From e112dbc4664b9bf128729302c8d535eb426cbe19 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:32:02 +0900 Subject: [PATCH 33/60] exfat: remove call ilog2() from exfat_readdir() There is no need to call ilog2() for the conversions between cluster and dentry in exfat_readdir(), because these conversions can be replaced with EXFAT_DEN_TO_CLU()/EXFAT_CLU_TO_DEN(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 9 ++++----- exfat_fs.h | 10 ++++++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/dir.c b/dir.c index b359087e3cbc..546a31cbe724 100644 --- a/dir.c +++ b/dir.c @@ -62,7 +62,7 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, /* read a directory entry from the opened directory */ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_entry *dir_entry) { - int i, dentries_per_clu, dentries_per_clu_bits = 0, num_ext; + int i, dentries_per_clu, num_ext; unsigned int type, clu_offset, max_dentries; struct exfat_chain dir, clu; struct exfat_uni_name uni_name; @@ -84,11 +84,10 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); dentries_per_clu = sbi->dentries_per_clu; - dentries_per_clu_bits = ilog2(dentries_per_clu); max_dentries = (unsigned int)min_t(u64, MAX_EXFAT_DENTRIES, - (u64)sbi->num_clusters << dentries_per_clu_bits); + (u64)EXFAT_CLU_TO_DEN(sbi->num_clusters, sbi)); - clu_offset = dentry >> dentries_per_clu_bits; + clu_offset = EXFAT_DEN_TO_CLU(dentry, sbi); exfat_chain_dup(&clu, &dir); if (clu.flags == ALLOC_NO_FAT_CHAIN) { @@ -163,7 +162,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent dir_entry->entry = dentry; brelse(bh); - ei->hint_bmap.off = dentry >> dentries_per_clu_bits; + ei->hint_bmap.off = EXFAT_DEN_TO_CLU(dentry, sbi); ei->hint_bmap.clu = clu.dir; *cpos = EXFAT_DEN_TO_B(dentry + 1 + num_ext); diff --git a/exfat_fs.h b/exfat_fs.h index b51e72cae6a7..b3d6fe6c621b 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -114,11 +114,17 @@ enum { /* * helpers for block size to dentry size conversion. */ -#define EXFAT_B_TO_DEN_IDX(b, sbi) \ - ((b) << ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS)) #define EXFAT_B_TO_DEN(b) ((b) >> DENTRY_SIZE_BITS) #define EXFAT_DEN_TO_B(b) ((b) << DENTRY_SIZE_BITS) +/* + * helpers for cluster size to dentry size conversion. + */ +#define EXFAT_CLU_TO_DEN(clu, sbi) \ + ((clu) << ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS)) +#define EXFAT_DEN_TO_CLU(dentry, sbi) \ + ((dentry) >> ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS)) + /* * helpers for fat entry. */ From 9ba14ac941cc1861bd638f3f9fa1acefbb0a0dc4 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:32:48 +0900 Subject: [PATCH 34/60] exfat: remove unneeded codes from __exfat_rename() The code gets the dentry, but the dentry is not used, remove the code. Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- namei.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/namei.c b/namei.c index f88757013f46..aef80072fc90 100644 --- a/namei.c +++ b/namei.c @@ -1291,7 +1291,7 @@ static int __exfat_rename(struct inode *old_parent_inode, struct exfat_inode_info *new_ei = NULL; unsigned int new_entry_type = TYPE_UNUSED; int new_entry = 0; - struct buffer_head *old_bh, *new_bh = NULL; + struct buffer_head *new_bh = NULL; /* check the validity of pointer parameters */ if (new_path == NULL || strlen(new_path) == 0) @@ -1307,13 +1307,6 @@ static int __exfat_rename(struct inode *old_parent_inode, EXFAT_I(old_parent_inode)->flags); dentry = ei->entry; - ep = exfat_get_dentry(sb, &olddir, dentry, &old_bh); - if (!ep) { - ret = -EIO; - goto out; - } - brelse(old_bh); - /* check whether new dir is existing directory and empty */ if (new_inode) { ret = -EIO; From 5098d1ebfcdaec9c7b3e6fd44582cc07b2750c21 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:33:36 +0900 Subject: [PATCH 35/60] exfat: remove unnecessary arguments from exfat_find_dir_entry() This commit removes argument 'num_entries' and 'type' from exfat_find_dir_entry(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 12 +++++++----- exfat_fs.h | 3 +-- namei.c | 10 ++-------- 3 files changed, 10 insertions(+), 15 deletions(-) diff --git a/dir.c b/dir.c index 546a31cbe724..8cbff27dbc86 100644 --- a/dir.c +++ b/dir.c @@ -967,7 +967,7 @@ enum { */ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, - int num_entries, unsigned int type, struct exfat_hint *hint_opt) + struct exfat_hint *hint_opt) { int i, rewind = 0, dentry = 0, end_eidx = 0, num_ext = 0, len; int order, step, name_len = 0; @@ -978,6 +978,10 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, struct exfat_hint *hint_stat = &ei->hint_stat; struct exfat_hint_femp candi_empty; struct exfat_sb_info *sbi = EXFAT_SB(sb); + int num_entries = exfat_calc_num_entries(p_uniname); + + if (num_entries < 0) + return num_entries; dentries_per_clu = sbi->dentries_per_clu; @@ -1031,10 +1035,8 @@ rewind: step = DIRENT_STEP_FILE; hint_opt->clu = clu.dir; hint_opt->eidx = i; - if (type == TYPE_ALL || type == entry_type) { - num_ext = ep->dentry.file.num_ext; - step = DIRENT_STEP_STRM; - } + num_ext = ep->dentry.file.num_ext; + step = DIRENT_STEP_STRM; brelse(bh); continue; } diff --git a/exfat_fs.h b/exfat_fs.h index b3d6fe6c621b..10621f18886b 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -84,7 +84,6 @@ enum { #define TYPE_PADDING 0x0402 #define TYPE_ACLTAB 0x0403 #define TYPE_BENIGN_SEC 0x0800 -#define TYPE_ALL 0x0FFF #define MAX_CHARSET_SIZE 6 /* max size of multi-byte character */ #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ @@ -526,7 +525,7 @@ void exfat_update_dir_chksum_with_entry_set(struct exfat_entry_set_cache *es); int exfat_calc_num_entries(struct exfat_uni_name *p_uniname); int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, struct exfat_chain *p_dir, struct exfat_uni_name *p_uniname, - int num_entries, unsigned int type, struct exfat_hint *hint_opt); + struct exfat_hint *hint_opt); int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu); struct exfat_dentry *exfat_get_dentry(struct super_block *sb, struct exfat_chain *p_dir, int entry, struct buffer_head **bh); diff --git a/namei.c b/namei.c index aef80072fc90..8814f762232e 100644 --- a/namei.c +++ b/namei.c @@ -647,7 +647,7 @@ unlock: static int exfat_find(struct inode *dir, struct qstr *qname, struct exfat_dir_entry *info) { - int ret, dentry, num_entries, count; + int ret, dentry, count; struct exfat_chain cdir; struct exfat_uni_name uni_name; struct super_block *sb = dir->i_sb; @@ -666,10 +666,6 @@ static int exfat_find(struct inode *dir, struct qstr *qname, if (ret) return ret; - num_entries = exfat_calc_num_entries(&uni_name); - if (num_entries < 0) - return num_entries; - /* check the validation of hint_stat and initialize it if required */ #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 16, 0) if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) { @@ -687,9 +683,7 @@ static int exfat_find(struct inode *dir, struct qstr *qname, } /* search the file name for directories */ - dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, - num_entries, TYPE_ALL, &hint_opt); - + dentry = exfat_find_dir_entry(sb, ei, &cdir, &uni_name, &hint_opt); if (dentry < 0) return dentry; /* -error value */ From bf6c668d6ed22c7871c1125daf4139c00c8a8c5f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:37:17 +0900 Subject: [PATCH 36/60] exfat: remove argument 'size' from exfat_truncate() argument 'size' is not used in exfat_truncate(), remove it. Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- file.c | 4 ++-- inode.c | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index 10621f18886b..fcfddcd44532 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -472,7 +472,7 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range); /* file.c */ extern const struct file_operations exfat_file_operations; int __exfat_truncate(struct inode *inode, loff_t new_size); -void exfat_truncate(struct inode *inode, loff_t size); +void exfat_truncate(struct inode *inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, diff --git a/file.c b/file.c index 6e37277bbd84..efef7a9caad8 100644 --- a/file.c +++ b/file.c @@ -196,7 +196,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) return 0; } -void exfat_truncate(struct inode *inode, loff_t size) +void exfat_truncate(struct inode *inode) { struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -362,7 +362,7 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) * __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); + exfat_truncate(inode); up_write(&EXFAT_I(inode)->truncate_lock); } else mark_inode_dirty(inode); diff --git a/inode.c b/inode.c index d0bfa26c6584..4bab1e4f5e56 100644 --- a/inode.c +++ b/inode.c @@ -383,7 +383,7 @@ static void exfat_write_failed(struct address_space *mapping, loff_t to) #else inode->i_mtime = inode->i_ctime = CURRENT_TIME_SEC; #endif - exfat_truncate(inode, EXFAT_I(inode)->i_size_aligned); + exfat_truncate(inode); } } From ba9c0dc4c0eadef390d64ae495a2279592d93ec6 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Fri, 9 Dec 2022 22:39:56 +0900 Subject: [PATCH 37/60] exfat: remove i_size_write() from __exfat_truncate() The file/directory size is updated into inode by i_size_write() before __exfat_truncate() is called, so it is redundant to re-update by i_size_write() in __exfat_truncate(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- file.c | 8 +++----- inode.c | 2 +- 3 files changed, 5 insertions(+), 7 deletions(-) diff --git a/exfat_fs.h b/exfat_fs.h index fcfddcd44532..42a9a46d7c69 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -471,7 +471,7 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range); /* file.c */ extern const struct file_operations exfat_file_operations; -int __exfat_truncate(struct inode *inode, loff_t new_size); +int __exfat_truncate(struct inode *inode); void exfat_truncate(struct inode *inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) diff --git a/file.c b/file.c index efef7a9caad8..51de3b3781fa 100644 --- a/file.c +++ b/file.c @@ -100,7 +100,7 @@ static int exfat_sanitize_mode(const struct exfat_sb_info *sbi, } /* resize the file length */ -int __exfat_truncate(struct inode *inode, loff_t new_size) +int __exfat_truncate(struct inode *inode) { unsigned int num_clusters_new, num_clusters_phys; unsigned int last_clu = EXFAT_FREE_CLUSTER; @@ -120,7 +120,7 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags); - if (new_size > 0) { + if (i_size_read(inode) > 0) { /* * Truncate FAT chain num_clusters after the first cluster * num_clusters = min(new, phys); @@ -150,8 +150,6 @@ int __exfat_truncate(struct inode *inode, loff_t new_size) ei->start_clu = EXFAT_EOF_CLUSTER; } - i_size_write(inode, new_size); - if (ei->type == TYPE_FILE) ei->attr |= ATTR_ARCHIVE; @@ -218,7 +216,7 @@ void exfat_truncate(struct inode *inode) goto write_size; } - err = __exfat_truncate(inode, i_size_read(inode)); + err = __exfat_truncate(inode); if (err) goto write_size; diff --git a/inode.c b/inode.c index 4bab1e4f5e56..af305df857df 100644 --- a/inode.c +++ b/inode.c @@ -698,7 +698,7 @@ void exfat_evict_inode(struct inode *inode) if (!inode->i_nlink) { i_size_write(inode, 0); mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock); - __exfat_truncate(inode, 0); + __exfat_truncate(inode); mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock); } From eb7de3a45bed80ba39026f9f28c23557f893887e Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 15 Dec 2022 08:20:23 +0900 Subject: [PATCH 38/60] exfat: fix overflow in sector and cluster conversion According to the exFAT specification, there are at most 2^32-11 clusters in a volume. so using 'int' is not enough for cluster index, the return value type of exfat_sector_to_cluster() should be 'unsigned int'. Signed-off-by: Yuezhang Mo Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index 42a9a46d7c69..02f9529ba543 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -423,7 +423,7 @@ static inline sector_t exfat_cluster_to_sector(struct exfat_sb_info *sbi, sbi->data_start_sector; } -static inline int exfat_sector_to_cluster(struct exfat_sb_info *sbi, +static inline unsigned int exfat_sector_to_cluster(struct exfat_sb_info *sbi, sector_t sec) { return ((sec - sbi->data_start_sector) >> sbi->sect_per_clus_bits) + From c0bbea4d949af7465901bf5c8d8964aaa6ba8479 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 15 Dec 2022 08:21:14 +0900 Subject: [PATCH 39/60] exfat: reuse exfat_find_location() to simplify exfat_get_dentry_set() In exfat_get_dentry_set(), part of the code is the same as exfat_find_location(), reuse exfat_find_location() to simplify exfat_get_dentry_set(). Code refinement, no functional changes. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/dir.c b/dir.c index 8cbff27dbc86..a324d2be6a58 100644 --- a/dir.c +++ b/dir.c @@ -829,7 +829,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, unsigned int type) { int ret, i, num_bh; - unsigned int off, byte_offset, clu = 0; + unsigned int off; sector_t sec; struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_dentry *ep; @@ -842,27 +842,16 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, return -EIO; } - byte_offset = EXFAT_DEN_TO_B(entry); - ret = exfat_walk_fat_chain(sb, p_dir, byte_offset, &clu); + ret = exfat_find_location(sb, p_dir, entry, &sec, &off); if (ret) return ret; memset(es, 0, sizeof(*es)); es->sb = sb; es->modified = false; - - /* byte offset in cluster */ - byte_offset = EXFAT_CLU_OFFSET(byte_offset, sbi); - - /* byte offset in sector */ - off = EXFAT_BLK_OFFSET(byte_offset, sb); es->start_off = off; es->bh = es->__bh; - /* sector offset in cluster */ - sec = EXFAT_B_TO_BLK(byte_offset, sb); - sec += exfat_cluster_to_sector(sbi, clu); - bh = sb_bread(sb, sec); if (!bh) return -EIO; @@ -889,6 +878,8 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es, for (i = 1; i < num_bh; i++) { /* get the next sector */ if (exfat_is_last_sector_in_cluster(sbi, sec)) { + unsigned int clu = exfat_sector_to_cluster(sbi, sec); + if (p_dir->flags == ALLOC_NO_FAT_CHAIN) clu++; else if (exfat_get_next_cluster(sb, &clu)) From bd80b9acb6f240fc046b58defd23f25397fd7651 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 26 Dec 2022 21:24:51 +0900 Subject: [PATCH 40/60] exfat: fix unexpected EOF while reading dir If the position is not aligned with the dentry size, the return value of readdir() will be NULL and errno is 0, which means the end of the directory stream is reached. If the position is aligned with dentry size, but there is no file or directory at the position, exfat_readdir() will continue to get dentry from the next dentry. So the dentry gotten by readdir() may not be at the position. After this commit, if the position is not aligned with the dentry size, round the position up to the dentry size and continue to get the dentry. Cc: stable@vger.kernel.org # v5.7+ Reported-by: Wang Yugui Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/dir.c b/dir.c index a324d2be6a58..e24a0a5bafa4 100644 --- a/dir.c +++ b/dir.c @@ -235,10 +235,7 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) fake_offset = 1; } - if (cpos & (DENTRY_SIZE - 1)) { - err = -ENOENT; - goto unlock; - } + cpos = round_up(cpos, DENTRY_SIZE); /* name buffer should be allocated before use */ err = exfat_alloc_namebuf(nb); From 118b6d539df2e5c46a642583149d43fcc69f996d Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Mon, 26 Dec 2022 21:25:26 +0900 Subject: [PATCH 41/60] exfat: fix reporting fs error when reading dir beyond EOF Since seekdir() does not check whether the position is valid, the position may exceed the size of the directory. We found that for a directory with discontinuous clusters, if the position exceeds the size of the directory and the excess size is greater than or equal to the cluster size, exfat_readdir() will return -EIO, causing a file system error and making the file system unavailable. Reproduce this bug by: seekdir(dir, dir_size + cluster_size); dirent = readdir(dir); The following log will be printed if mount with 'errors=remount-ro'. [11166.712896] exFAT-fs (sdb1): error, invalid access to FAT (entry 0xffffffff) [11166.712905] exFAT-fs (sdb1): Filesystem has been set read-only Fixes: 1e5654de0f51 ("exfat: handle wrong stream entry size in exfat_readdir()") Cc: stable@vger.kernel.org # v5.7+ Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Reviewed-by: Aoyama Wataru Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dir.c b/dir.c index e24a0a5bafa4..a73cb344915f 100644 --- a/dir.c +++ b/dir.c @@ -101,7 +101,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent clu.dir = ei->hint_bmap.clu; } - while (clu_offset > 0) { + while (clu_offset > 0 && clu.dir != EXFAT_EOF_CLUSTER) { if (exfat_get_next_cluster(sb, &(clu.dir))) return -EIO; From 17d8731ce12502b50b468d443ed5fa2094a80132 Mon Sep 17 00:00:00 2001 From: Sungjong Seo Date: Fri, 30 Dec 2022 10:22:05 +0900 Subject: [PATCH 42/60] exfat: redefine DIR_DELETED as the bad cluster number When a file or a directory is deleted, the hint for the cluster of its parent directory in its in-memory inode is set as DIR_DELETED. Therefore, DIR_DELETED must be one of invalid cluster numbers. According to the exFAT specification, a volume can have at most 2^32-11 clusters. However, DIR_DELETED is wrongly defined as 0xFFFF0321, which could be a valid cluster number. To fix it, let's redefine DIR_DELETED as 0xFFFFFFF7, the bad cluster number. Fixes: 1acf1a564b60 ("exfat: add in-memory and on-disk structures and headers") Cc: stable@vger.kernel.org # v5.7+ Reported-by: Yuezhang Mo Signed-off-by: Sungjong Seo Signed-off-by: Namjae Jeon --- exfat_fs.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index 02f9529ba543..1bea841bee12 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -63,7 +63,7 @@ enum { #define ES_IDX_LAST_FILENAME(name_len) \ (ES_IDX_FIRST_FILENAME + EXFAT_FILENAME_ENTRY_NUM(name_len) - 1) -#define DIR_DELETED 0xFFFF0321 +#define DIR_DELETED 0xFFFFFFF7 /* type values */ #define TYPE_UNUSED 0x0000 From 3eef540417547ae7eea6d8bcae97b797e8fb2cbb Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Sun, 8 Jan 2023 18:14:43 +0900 Subject: [PATCH 43/60] exfat: fix inode->i_blocks for non-512 byte sector size device inode->i_blocks is not real number of blocks, but 512 byte ones. Fixes: 98d917047e8b ("exfat: add file operations") Cc: stable@vger.kernel.org # v5.7+ Reported-by: Wang Yugui Tested-by: Wang Yugui Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Signed-off-by: Namjae Jeon --- file.c | 3 +-- inode.c | 6 ++---- namei.c | 2 +- super.c | 3 +-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/file.c b/file.c index 51de3b3781fa..a0388530cffb 100644 --- a/file.c +++ b/file.c @@ -220,8 +220,7 @@ void exfat_truncate(struct inode *inode) if (err) goto write_size; - inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> - inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; write_size: aligned_size = i_size_read(inode); if (aligned_size & (blocksize - 1)) { diff --git a/inode.c b/inode.c index af305df857df..bce152cd386c 100644 --- a/inode.c +++ b/inode.c @@ -222,8 +222,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, num_clusters += num_to_be_allocated; *clu = new_clu.dir; - inode->i_blocks += - num_to_be_allocated << sbi->sect_per_clus_bits; + inode->i_blocks += EXFAT_CLU_TO_B(num_to_be_allocated, sbi) >> 9; /* * Move *clu pointer along FAT chains (hole care) because the @@ -649,8 +648,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info) exfat_save_attr(inode, info->attr); - inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> - inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; inode->i_mtime = info->mtime; inode->i_ctime = info->mtime; ei->i_crtime = info->crtime; diff --git a/namei.c b/namei.c index 8814f762232e..a08278e7a45a 100644 --- a/namei.c +++ b/namei.c @@ -423,7 +423,7 @@ static int exfat_find_empty_entry(struct inode *inode, 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; + inode->i_blocks += sbi->cluster_size >> 9; } return dentry; diff --git a/super.c b/super.c index c5d55b0308a9..665c296e4ad4 100644 --- a/super.c +++ b/super.c @@ -632,8 +632,7 @@ static int exfat_read_root(struct inode *inode) inode->i_op = &exfat_dir_inode_operations; inode->i_fop = &exfat_dir_operations; - inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> - inode->i_blkbits; + inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9; 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 47fb2035ceca46dc4ac79368e030ccb2f5a270cc Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 13 Jan 2023 22:51:20 +0900 Subject: [PATCH 44/60] exfat: handle unreconized benign secondary entries MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sony PXW-Z280 camera add vendor allocation entries to directory of pictures. Currently, linux exfat does not support it and the file is not visible. This patch handle vendor extension and allocation entries as unreconized benign secondary entries. As described in the specification, it is recognized but ignored, and when deleting directory entry set, the associated clusters allocation are removed as well as benign secondary directory entries. Reported-by: Barócsi Dénes Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 83 +++++++++++++++++++++++++++++++++++++---------------- exfat_fs.h | 2 ++ exfat_raw.h | 21 ++++++++++++++ 3 files changed, 81 insertions(+), 25 deletions(-) diff --git a/dir.c b/dir.c index a73cb344915f..f167f68b547d 100644 --- a/dir.c +++ b/dir.c @@ -30,14 +30,15 @@ static int exfat_extract_uni_name(struct exfat_dentry *ep, } -static void exfat_get_uniname_from_ext_entry(struct super_block *sb, +static int exfat_get_uniname_from_ext_entry(struct super_block *sb, struct exfat_chain *p_dir, int entry, unsigned short *uniname) { - int i; + int i, err; struct exfat_entry_set_cache es; - if (exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES)) - return; + err = exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES); + if (err) + return err; /* * First entry : file entry @@ -57,12 +58,13 @@ static void exfat_get_uniname_from_ext_entry(struct super_block *sb, } exfat_put_dentry_set(&es, false); + return 0; } /* read a directory entry from the opened directory */ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_entry *dir_entry) { - int i, dentries_per_clu, num_ext; + int i, dentries_per_clu, num_ext, err; unsigned int type, clu_offset, max_dentries; struct exfat_chain dir, clu; struct exfat_uni_name uni_name; @@ -147,8 +149,12 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent 0); *uni_name.name = 0x0; - exfat_get_uniname_from_ext_entry(sb, &clu, i, + err = exfat_get_uniname_from_ext_entry(sb, &clu, i, uni_name.name); + if (err) { + brelse(bh); + continue; + } exfat_utf16_to_nls(sb, &uni_name, dir_entry->namebuf.lfn, dir_entry->namebuf.lfnbuf_len); @@ -376,6 +382,12 @@ unsigned int exfat_get_entry_type(struct exfat_dentry *ep) return TYPE_ACL; return TYPE_CRITICAL_SEC; } + + if (ep->type == EXFAT_VENDOR_EXT) + return TYPE_VENDOR_EXT; + if (ep->type == EXFAT_VENDOR_ALLOC) + return TYPE_VENDOR_ALLOC; + return TYPE_BENIGN_SEC; } @@ -529,6 +541,25 @@ release_fbh: return ret; } +static void exfat_free_benign_secondary_clusters(struct inode *inode, + struct exfat_dentry *ep) +{ + struct super_block *sb = inode->i_sb; + struct exfat_chain dir; + unsigned int start_clu = + le32_to_cpu(ep->dentry.generic_secondary.start_clu); + u64 size = le64_to_cpu(ep->dentry.generic_secondary.size); + unsigned char flags = ep->dentry.generic_secondary.flags; + + if (!(flags & ALLOC_POSSIBLE) || !start_clu || !size) + return; + + exfat_chain_set(&dir, start_clu, + EXFAT_B_TO_CLU_ROUND_UP(size, EXFAT_SB(sb)), + flags); + exfat_free_cluster(inode, &dir); +} + int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, int entry, int num_entries, struct exfat_uni_name *p_uniname) { @@ -561,6 +592,9 @@ int exfat_init_ext_entry(struct inode *inode, struct exfat_chain *p_dir, if (!ep) return -EIO; + if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) + exfat_free_benign_secondary_clusters(inode, ep); + exfat_init_name_entry(ep, uniname); exfat_update_bh(bh, sync); brelse(bh); @@ -584,6 +618,9 @@ int exfat_remove_entries(struct inode *inode, struct exfat_chain *p_dir, if (!ep) return -EIO; + if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) + exfat_free_benign_secondary_clusters(inode, ep); + exfat_set_entry_type(ep, TYPE_DELETED); exfat_update_bh(bh, IS_DIRSYNC(inode)); brelse(bh); @@ -752,6 +789,7 @@ enum exfat_validate_dentry_mode { ES_MODE_GET_STRM_ENTRY, ES_MODE_GET_NAME_ENTRY, ES_MODE_GET_CRITICAL_SEC_ENTRY, + ES_MODE_GET_BENIGN_SEC_ENTRY, }; static bool exfat_validate_entry(unsigned int type, @@ -765,36 +803,33 @@ static bool exfat_validate_entry(unsigned int type, if (type != TYPE_FILE && type != TYPE_DIR) return false; *mode = ES_MODE_GET_FILE_ENTRY; - return true; + break; case ES_MODE_GET_FILE_ENTRY: if (type != TYPE_STREAM) return false; *mode = ES_MODE_GET_STRM_ENTRY; - return true; + break; case ES_MODE_GET_STRM_ENTRY: if (type != TYPE_EXTEND) return false; *mode = ES_MODE_GET_NAME_ENTRY; - return true; + break; case ES_MODE_GET_NAME_ENTRY: - if (type == TYPE_STREAM) + if (type & TYPE_BENIGN_SEC) + *mode = ES_MODE_GET_BENIGN_SEC_ENTRY; + else if (type != TYPE_EXTEND) return false; - if (type != TYPE_EXTEND) { - if (!(type & TYPE_CRITICAL_SEC)) - return false; - *mode = ES_MODE_GET_CRITICAL_SEC_ENTRY; - } - return true; - case ES_MODE_GET_CRITICAL_SEC_ENTRY: - if (type == TYPE_EXTEND || type == TYPE_STREAM) + break; + case ES_MODE_GET_BENIGN_SEC_ENTRY: + /* Assume unreconized benign secondary entry */ + if (!(type & TYPE_BENIGN_SEC)) return false; - if ((type & TYPE_CRITICAL_SEC) != TYPE_CRITICAL_SEC) - return false; - return true; + break; default: - WARN_ON_ONCE(1); return false; } + + return true; } struct exfat_dentry *exfat_get_dentry_cached( @@ -1175,10 +1210,8 @@ int exfat_count_ext_entries(struct super_block *sb, struct exfat_chain *p_dir, type = exfat_get_entry_type(ext_ep); brelse(bh); - if (type == TYPE_EXTEND || type == TYPE_STREAM) + if (type & TYPE_CRITICAL_SEC || type & TYPE_BENIGN_SEC) count++; - else - break; } return count; } diff --git a/exfat_fs.h b/exfat_fs.h index 1bea841bee12..1b3bfd717a83 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -84,6 +84,8 @@ enum { #define TYPE_PADDING 0x0402 #define TYPE_ACLTAB 0x0403 #define TYPE_BENIGN_SEC 0x0800 +#define TYPE_VENDOR_EXT 0x0801 +#define TYPE_VENDOR_ALLOC 0x0802 #define MAX_CHARSET_SIZE 6 /* max size of multi-byte character */ #define MAX_NAME_LENGTH 255 /* max len of file name excluding NULL */ diff --git a/exfat_raw.h b/exfat_raw.h index 7f39b1c6469c..0ece2e43cf49 100644 --- a/exfat_raw.h +++ b/exfat_raw.h @@ -27,6 +27,7 @@ ((sbi)->num_clusters - EXFAT_RESERVED_CLUSTERS) /* AllocationPossible and NoFatChain field in GeneralSecondaryFlags Field */ +#define ALLOC_POSSIBLE 0x01 #define ALLOC_FAT_CHAIN 0x01 #define ALLOC_NO_FAT_CHAIN 0x03 @@ -50,6 +51,8 @@ #define EXFAT_STREAM 0xC0 /* stream entry */ #define EXFAT_NAME 0xC1 /* file name entry */ #define EXFAT_ACL 0xC2 /* stream entry */ +#define EXFAT_VENDOR_EXT 0xE0 /* vendor extension entry */ +#define EXFAT_VENDOR_ALLOC 0xE1 /* vendor allocation entry */ #define IS_EXFAT_CRITICAL_PRI(x) (x < 0xA0) #define IS_EXFAT_BENIGN_PRI(x) (x < 0xC0) @@ -155,6 +158,24 @@ struct exfat_dentry { __le32 start_clu; __le64 size; } __packed upcase; /* up-case table directory entry */ + struct { + __u8 flags; + __u8 vendor_guid[16]; + __u8 vendor_defined[14]; + } __packed vendor_ext; /* vendor extension directory entry */ + struct { + __u8 flags; + __u8 vendor_guid[16]; + __u8 vendor_defined[2]; + __le32 start_clu; + __le64 size; + } __packed vendor_alloc; /* vendor allocation directory entry */ + struct { + __u8 flags; + __u8 custom_defined[18]; + __le32 start_clu; + __le64 size; + } __packed generic_secondary; /* generic secondary directory entry */ } __packed dentry; } __packed; From 0e44fbe12d5545cef539f30651c11e583786f59b Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Fri, 13 Jan 2023 23:05:15 +0900 Subject: [PATCH 45/60] exfat: remove ->writepage Patch series "start removing writepage instances v2". The VM doesn't need or want ->writepage for writeback and is fine with just having ->writepages as long as ->migrate_folio is implemented. This series removes all ->writepage instances that use block_write_full_page directly and also have a plain mpage_writepages based ->writepages. This patch (of 7): ->writepage is a very inefficient method to write back data, and only used through write_cache_pages or a a fallback when no ->migrate_folio method is present. Set ->migrate_folio to the generic buffer_head based helper, and remove the ->writepage implementation. Link: https://lkml.kernel.org/r/20221202102644.770505-1-hch@lst.de Link: https://lkml.kernel.org/r/20221202102644.770505-2-hch@lst.de Signed-off-by: Christoph Hellwig Acked-by: Namjae Jeon Acked-by: Johannes Weiner Cc: Bob Copeland Cc: Dave Kleikamp Cc: Jan Kara Cc: Mikulas Patocka Cc: OGAWA Hirofumi Cc: Sungjong Seo Signed-off-by: Andrew Morton Signed-off-by: Namjae Jeon --- inode.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/inode.c b/inode.c index bce152cd386c..4d4449e1df3d 100644 --- a/inode.c +++ b/inode.c @@ -360,10 +360,12 @@ static int exfat_readpages(struct file *file, struct address_space *mapping, } #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) static int exfat_writepage(struct page *page, struct writeback_control *wbc) { return block_write_full_page(page, exfat_get_block, wbc); } +#endif static int exfat_writepages(struct address_space *mapping, struct writeback_control *wbc) @@ -531,12 +533,19 @@ static const struct address_space_operations exfat_aops = { #else .readpages = exfat_readpages, #endif +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) .writepage = exfat_writepage, +#endif .writepages = exfat_writepages, .write_begin = exfat_write_begin, .write_end = exfat_write_end, .direct_IO = exfat_direct_IO, +#if LINUX_VERSION_CODE < KERNEL_VERSION(6, 2, 0) .bmap = exfat_aop_bmap +#else + .bmap = exfat_aop_bmap, + .migrate_folio = buffer_migrate_folio, +#endif }; static inline unsigned long exfat_hash(loff_t i_pos) From cd17de5006fa514dff6d25d1de8caff9f0c4723f Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 23 Feb 2023 23:08:08 +0900 Subject: [PATCH 46/60] exfat: remove unneeded code from exfat_alloc_cluster() In the removed code, num_clusters is 0, nothing is done in exfat_chain_cont_cluster(), so it is unneeded, remove it. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Signed-off-by: Namjae Jeon --- fatent.c | 9 +-------- 1 file changed, 1 insertion(+), 8 deletions(-) diff --git a/fatent.c b/fatent.c index ded465bcf84b..e22a77b7d54c 100644 --- a/fatent.c +++ b/fatent.c @@ -362,14 +362,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, exfat_err(sb, "hint_cluster is invalid (%u)", hint_clu); hint_clu = EXFAT_FIRST_CLUSTER; - if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { - if (exfat_chain_cont_cluster(sb, p_chain->dir, - num_clusters)) { - ret = -EIO; - goto unlock; - } - p_chain->flags = ALLOC_FAT_CHAIN; - } + p_chain->flags = ALLOC_FAT_CHAIN; } p_chain->dir = EXFAT_EOF_CLUSTER; From 7a55bf46a5e4d10662cb75ca54b88b59e5621dc9 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Tue, 28 Feb 2023 15:30:06 +0900 Subject: [PATCH 47/60] exfat: don't print error log in normal case When allocating a new cluster, exFAT first allocates from the next cluster of the last cluster of the file. If the last cluster of the file is the last cluster of the volume, allocate from the first cluster. This is a normal case, but the following error log will be printed. It makes users confused, so this commit removes the error log. [1960905.181545] exFAT-fs (sdb1): hint_cluster is invalid (262130) Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Signed-off-by: Namjae Jeon --- fatent.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fatent.c b/fatent.c index e22a77b7d54c..f46bbce781bb 100644 --- a/fatent.c +++ b/fatent.c @@ -359,8 +359,9 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, /* check cluster validation */ if (!is_valid_cluster(sbi, hint_clu)) { - exfat_err(sb, "hint_cluster is invalid (%u)", - hint_clu); + if (hint_clu != sbi->num_clusters) + exfat_err(sb, "hint_cluster is invalid (%u), rewind to the first cluster", + hint_clu); hint_clu = EXFAT_FIRST_CLUSTER; p_chain->flags = ALLOC_FAT_CHAIN; } From 03ab6855b1c9a69df02efdab9df8d0570a933b70 Mon Sep 17 00:00:00 2001 From: Yuezhang Mo Date: Thu, 23 Feb 2023 23:09:50 +0900 Subject: [PATCH 48/60] exfat: fix the newly allocated clusters are not freed in error handling In error handling 'free_cluster', before num_alloc clusters allocated, p_chain->size will not updated and always 0, thus the newly allocated clusters are not freed. Signed-off-by: Yuezhang Mo Reviewed-by: Andy Wu Signed-off-by: Namjae Jeon --- fatent.c | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/fatent.c b/fatent.c index f46bbce781bb..c13a04e94ebc 100644 --- a/fatent.c +++ b/fatent.c @@ -322,7 +322,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, struct exfat_chain *p_chain, bool sync_bmap) { int ret = -ENOSPC; - unsigned int num_clusters = 0, total_cnt; + unsigned int total_cnt; unsigned int hint_clu, new_clu, last_clu = EXFAT_EOF_CLUSTER; struct super_block *sb = inode->i_sb; struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -373,7 +373,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, if (new_clu != hint_clu && p_chain->flags == ALLOC_NO_FAT_CHAIN) { if (exfat_chain_cont_cluster(sb, p_chain->dir, - num_clusters)) { + p_chain->size)) { ret = -EIO; goto free_cluster; } @@ -386,8 +386,6 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, goto free_cluster; } - num_clusters++; - /* update FAT table */ if (p_chain->flags == ALLOC_FAT_CHAIN) { if (exfat_ent_set(sb, new_clu, EXFAT_EOF_CLUSTER)) { @@ -404,13 +402,14 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, goto free_cluster; } } + p_chain->size++; + last_clu = new_clu; - if (--num_alloc == 0) { + if (p_chain->size == num_alloc) { sbi->clu_srch_ptr = hint_clu; - sbi->used_clusters += num_clusters; + sbi->used_clusters += num_alloc; - p_chain->size += num_clusters; mutex_unlock(&sbi->bitmap_lock); return 0; } @@ -421,7 +420,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, if (p_chain->flags == ALLOC_NO_FAT_CHAIN) { if (exfat_chain_cont_cluster(sb, p_chain->dir, - num_clusters)) { + p_chain->size)) { ret = -EIO; goto free_cluster; } @@ -430,8 +429,7 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, } } free_cluster: - if (num_clusters) - __exfat_free_cluster(inode, p_chain); + __exfat_free_cluster(inode, p_chain); unlock: mutex_unlock(&sbi->bitmap_lock); return ret; From f4f94fa7203c929b81767023ba25a7d8c6c3fe51 Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:04:35 +0900 Subject: [PATCH 49/60] exfat: fs: port ->setattr() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- exfat_fs.h | 5 +++++ file.c | 13 +++++++++++++ 2 files changed, 18 insertions(+) diff --git a/exfat_fs.h b/exfat_fs.h index 1b3bfd717a83..54d5314778c0 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -477,8 +477,13 @@ int __exfat_truncate(struct inode *inode); void exfat_truncate(struct inode *inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr); +#else int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, struct iattr *attr); +#endif int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); diff --git a/file.c b/file.c index a0388530cffb..8a04b03f7483 100644 --- a/file.c +++ b/file.c @@ -273,8 +273,13 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, + struct iattr *attr) +#else int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, struct iattr *attr) +#endif #else int exfat_setattr(struct dentry *dentry, struct iattr *attr) #endif @@ -304,7 +309,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 1, 37))) || \ (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)) #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + error = setattr_prepare(&nop_mnt_idmap, dentry, attr); +#else error = setattr_prepare(&init_user_ns, dentry, attr); +#endif #else error = setattr_prepare(dentry, attr); #endif @@ -342,7 +351,11 @@ int exfat_setattr(struct dentry *dentry, struct iattr *attr) #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + setattr_copy(&nop_mnt_idmap, inode, attr); +#else setattr_copy(&init_user_ns, inode, attr); +#endif #else setattr_copy(inode, attr); #endif From eb2bd5c43a370cf581f151a7e838930b53723f79 Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:08:25 +0900 Subject: [PATCH 50/60] exfat: fs: port ->getattr() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- exfat_fs.h | 5 ++++- file.c | 10 ++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/exfat_fs.h b/exfat_fs.h index 54d5314778c0..b79f2fe2bea5 100644 --- a/exfat_fs.h +++ b/exfat_fs.h @@ -480,13 +480,16 @@ void exfat_truncate(struct inode *inode); #if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, struct iattr *attr); +int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags); #else int exfat_setattr(struct user_namespace *mnt_userns, struct dentry *dentry, struct iattr *attr); -#endif int exfat_getattr(struct user_namespace *mnt_userns, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags); +#endif #else int exfat_setattr(struct dentry *dentry, struct iattr *attr); #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) diff --git a/file.c b/file.c index 8a04b03f7483..a08076f44100 100644 --- a/file.c +++ b/file.c @@ -237,9 +237,15 @@ write_size: } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, + struct kstat *stat, unsigned int request_mask, + unsigned int query_flags) +#else int exfat_getattr(struct user_namespace *mnt_uerns, const struct path *path, struct kstat *stat, unsigned int request_mask, unsigned int query_flags) +#endif #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0) int exfat_getattr(const struct path *path, struct kstat *stat, @@ -258,7 +264,11 @@ int exfat_getattr(struct vfsmount *mnt, struct dentry *dentry, #endif #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) + generic_fillattr(&nop_mnt_idmap, inode, stat); +#else generic_fillattr(&init_user_ns, inode, stat); +#endif #else generic_fillattr(inode, stat); #endif From 1610136b5648f95c3d94319206e91fb95117748b Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:10:21 +0900 Subject: [PATCH 51/60] exfat: fs: port ->create() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- namei.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/namei.c b/namei.c index a08278e7a45a..c7662737a9bf 100644 --- a/namei.c +++ b/namei.c @@ -579,8 +579,13 @@ out: } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +static int exfat_create(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode, bool excl) +#else static int exfat_create(struct user_namespace *mnt_userns, struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) +#endif #else static int exfat_create(struct inode *dir, struct dentry *dentry, umode_t mode, bool excl) From 88c55a85d0b9f07266804a02522d0843ef10eed4 Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:12:01 +0900 Subject: [PATCH 52/60] exfat: fs: port ->mkdir() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- namei.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/namei.c b/namei.c index c7662737a9bf..f590de360e4d 100644 --- a/namei.c +++ b/namei.c @@ -918,8 +918,13 @@ unlock: } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +static int exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, + struct dentry *dentry, umode_t mode) +#else static int exfat_mkdir(struct user_namespace *mnt_userns, struct inode *dir, struct dentry *dentry, umode_t mode) +#endif #else static int exfat_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) #endif From d8b0edab69ad7938fa5f664f82228eb0b63ee70c Mon Sep 17 00:00:00 2001 From: "Christian Brauner (Microsoft)" Date: Tue, 11 Jul 2023 22:33:46 +0900 Subject: [PATCH 53/60] exfat: fs: port ->rename() to pass mnt_idmap Convert to struct mnt_idmap. Last cycle we merged the necessary infrastructure in 256c8aed2b42 ("fs: introduce dedicated idmap type for mounts"). This is just the conversion to struct mnt_idmap. Currently we still pass around the plain namespace that was attached to a mount. This is in general pretty convenient but it makes it easy to conflate namespaces that are relevant on the filesystem with namespaces that are relevent on the mount level. Especially for non-vfs developers without detailed knowledge in this area this can be a potential source for bugs. Once the conversion to struct mnt_idmap is done all helpers down to the really low-level helpers will take a struct mnt_idmap argument instead of two namespace arguments. This way it becomes impossible to conflate the two eliminating the possibility of any bugs. All of the vfs and all filesystems only operate on struct mnt_idmap. Acked-by: Dave Chinner Reviewed-by: Christoph Hellwig Signed-off-by: Christian Brauner (Microsoft) Signed-off-by: Namjae Jeon --- namei.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/namei.c b/namei.c index f590de360e4d..e6971c758892 100644 --- a/namei.c +++ b/namei.c @@ -1412,10 +1412,17 @@ out: } #if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 12, 0) +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 3, 0) +static int exfat_rename(struct mnt_idmap *idmap, + struct inode *old_dir, struct dentry *old_dentry, + struct inode *new_dir, struct dentry *new_dentry, + unsigned int flags) +#else static int exfat_rename(struct user_namespace *mnt_userns, struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry, unsigned int flags) +#endif #else #if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0) static int exfat_rename(struct inode *old_dir, struct dentry *old_dentry, From 796bbd07266efa296683a29b31a7f8a2625de00d Mon Sep 17 00:00:00 2001 From: Christoph Hellwig Date: Tue, 11 Jul 2023 22:35:23 +0900 Subject: [PATCH 54/60] exfat: fs: build the legacy direct I/O code conditionally Add a new LEGACY_DIRECT_IO config symbol that is only selected by the file systems that still use the legacy blockdev_direct_IO code, so that kernels without support for those file systems don't need to build the code. Signed-off-by: Christoph Hellwig Reviewed-by: Jan Kara Reviewed-by: Eric Biggers Link: https://lore.kernel.org/r/20230125065839.191256-3-hch@lst.de Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- Kconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/Kconfig b/Kconfig index 2d3636dc5b8c..b3e5e9ebabc8 100644 --- a/Kconfig +++ b/Kconfig @@ -3,6 +3,7 @@ config EXFAT_FS tristate "exFAT filesystem support" select NLS + select LEGACY_DIRECT_IO help This allows you to mount devices formatted with the exFAT file system. exFAT is typically used on SD-Cards or USB sticks. From 331d3f5c425717085a825253f6aeb99af6848099 Mon Sep 17 00:00:00 2001 From: David Howells Date: Tue, 11 Jul 2023 22:27:53 +0900 Subject: [PATCH 55/60] exfat: splice: Use filemap_splice_read() instead of generic_file_splice_read() Replace pointers to generic_file_splice_read() with calls to filemap_splice_read(). Signed-off-by: David Howells Reviewed-by: Christoph Hellwig Reviewed-by: Christian Brauner cc: Jens Axboe cc: Al Viro cc: David Hildenbrand cc: John Hubbard cc: linux-mm@kvack.org cc: linux-block@vger.kernel.org cc: linux-fsdevel@vger.kernel.org Link: https://lore.kernel.org/r/20230522135018.2742245-29-dhowells@redhat.com Signed-off-by: Jens Axboe Signed-off-by: Namjae Jeon --- file.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/file.c b/file.c index a08076f44100..e35998f5aa8c 100644 --- a/file.c +++ b/file.c @@ -483,7 +483,11 @@ const struct file_operations exfat_file_operations = { #endif .mmap = generic_file_mmap, .fsync = exfat_file_fsync, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(6, 5, 0) + .splice_read = filemap_splice_read, +#else .splice_read = generic_file_splice_read, +#endif .splice_write = iter_file_splice_write, }; From 7d8b1148d8d3cfb68eee602f1db4966673d89d20 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 12 Jul 2023 23:31:31 +0900 Subject: [PATCH 56/60] exfat: use kvmalloc_array/kvfree instead of kmalloc_array/kfree The call stack shown below is a scenario in the Linux 4.19 kernel. Allocating memory failed where exfat fs use kmalloc_array due to system memory fragmentation, while the u-disk was inserted without recognition. Devices such as u-disk using the exfat file system are pluggable and may be insert into the system at any time. However, long-term running systems cannot guarantee the continuity of physical memory. Therefore, it's necessary to address this issue. Binder:2632_6: page allocation failure: order:4, mode:0x6040c0(GFP_KERNEL|__GFP_COMP), nodemask=(null) Call trace: [242178.097582] dump_backtrace+0x0/0x4 [242178.097589] dump_stack+0xf4/0x134 [242178.097598] warn_alloc+0xd8/0x144 [242178.097603] __alloc_pages_nodemask+0x1364/0x1384 [242178.097608] kmalloc_order+0x2c/0x510 [242178.097612] kmalloc_order_trace+0x40/0x16c [242178.097618] __kmalloc+0x360/0x408 [242178.097624] load_alloc_bitmap+0x160/0x284 [242178.097628] exfat_fill_super+0xa3c/0xe7c [242178.097635] mount_bdev+0x2e8/0x3a0 [242178.097638] exfat_fs_mount+0x40/0x50 [242178.097643] mount_fs+0x138/0x2e8 [242178.097649] vfs_kern_mount+0x90/0x270 [242178.097655] do_mount+0x798/0x173c [242178.097659] ksys_mount+0x114/0x1ac [242178.097665] __arm64_sys_mount+0x24/0x34 [242178.097671] el0_svc_common+0xb8/0x1b8 [242178.097676] el0_svc_handler+0x74/0x90 [242178.097681] el0_svc+0x8/0x340 By analyzing the exfat code,we found that continuous physical memory is not required here,so kvmalloc_array is used can solve this problem. Signed-off-by: gaoming Signed-off-by: Namjae Jeon --- balloc.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/balloc.c b/balloc.c index 30c2414d859c..21a865e0ac60 100644 --- a/balloc.c +++ b/balloc.c @@ -75,8 +75,12 @@ static int exfat_allocate_bitmap(struct super_block *sb, } sbi->map_sectors = ((need_map_size - 1) >> (sb->s_blocksize_bits)) + 1; - sbi->vol_amap = kmalloc_array(sbi->map_sectors, +#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 12, 0) + sbi->vol_amap = kvmalloc_array(sbi->map_sectors, sizeof(struct buffer_head *), GFP_KERNEL); +#else + sbi->vol_amap = vmalloc(sbi->map_sectors * sizeof(struct buffer_head *)); +#endif if (!sbi->vol_amap) return -ENOMEM; @@ -90,7 +94,7 @@ static int exfat_allocate_bitmap(struct super_block *sb, while (j < i) brelse(sbi->vol_amap[j++]); - kfree(sbi->vol_amap); + kvfree(sbi->vol_amap); sbi->vol_amap = NULL; return -EIO; } @@ -144,7 +148,7 @@ void exfat_free_bitmap(struct exfat_sb_info *sbi) for (i = 0; i < sbi->map_sectors; i++) __brelse(sbi->vol_amap[i]); - kfree(sbi->vol_amap); + kvfree(sbi->vol_amap); } int exfat_set_bitmap(struct inode *inode, unsigned int clu, bool sync) From 17c7881225a35acdea24e3ca72ed94ae2918c7d1 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Fri, 14 Jul 2023 17:08:12 +0900 Subject: [PATCH 57/60] exfat: github action: make space for running xfstests github action seems to decrease disk space of each users. This patch try to remove file creation test and reset test images in the middle of testing xfstests Signed-off-by: Namjae Jeon --- .github/workflows/c-cpp.yml | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/.github/workflows/c-cpp.yml b/.github/workflows/c-cpp.yml index 476c746ba715..375c800ac23c 100644 --- a/.github/workflows/c-cpp.yml +++ b/.github/workflows/c-cpp.yml @@ -78,18 +78,20 @@ jobs: cd .. sudo umount ./full_test/ sudo fsck.exfat /dev/loop22 + sudo losetup -d /dev/loop22 + rm full_test.img - name: xfstest tests run: | + cd exfat-testsuites/ + tar xzvf xfstests-exfat.tgz > /dev/null + cd xfstests-exfat + make -j$((`nproc`+1)) > /dev/null 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 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 sudo ./check generic/007 @@ -136,6 +138,16 @@ jobs: sudo ./check generic/211 sudo ./check generic/212 sudo ./check generic/215 + sudo losetup -d /dev/loop20 + sudo losetup -d /dev/loop21 + rm test.img + rm scratch.img + 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 sudo ./check generic/221 sudo ./check generic/239 sudo ./check generic/240 From 0bf9ccffd2f90dfdd6a26814e77750c68d4598e6 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Thu, 13 Jul 2023 16:40:35 +0900 Subject: [PATCH 58/60] exfat: check if filename entries exceeds max filename length exfat_extract_uni_name copies characters from a given file name entry into the 'uniname' variable. This variable is actually defined on the stack of the exfat_readdir() function. According to the definition of the 'exfat_uni_name' type, the file name should be limited 255 characters (+ null teminator space), but the exfat_get_uniname_from_ext_entry() function can write more characters because there is no check if filename entries exceeds max filename length. This patch add the check not to copy filename characters when exceeding max filename length. Cc: stable@vger.kernel.org Cc: Yuezhang Mo Reported-by: Maxim Suhanov Reviewed-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/dir.c b/dir.c index f167f68b547d..fd721eda2f41 100644 --- a/dir.c +++ b/dir.c @@ -35,6 +35,7 @@ static int exfat_get_uniname_from_ext_entry(struct super_block *sb, { int i, err; struct exfat_entry_set_cache es; + unsigned int uni_len = 0, len; err = exfat_get_dentry_set(&es, sb, p_dir, entry, ES_ALL_ENTRIES); if (err) @@ -53,7 +54,10 @@ static int exfat_get_uniname_from_ext_entry(struct super_block *sb, if (exfat_get_entry_type(ep) != TYPE_EXTEND) break; - exfat_extract_uni_name(ep, uniname); + len = exfat_extract_uni_name(ep, uniname); + uni_len += len; + if (len != EXFAT_FILE_NAME_LEN || uni_len >= MAX_NAME_LENGTH) + break; uniname += EXFAT_FILE_NAME_LEN; } @@ -1090,7 +1094,8 @@ rewind: if (entry_type == TYPE_EXTEND) { unsigned short entry_uniname[16], unichar; - if (step != DIRENT_STEP_NAME) { + if (step != DIRENT_STEP_NAME || + name_len >= MAX_NAME_LENGTH) { step = DIRENT_STEP_FILE; continue; } From 803d7143748976653baf72be77420234d6aba4e4 Mon Sep 17 00:00:00 2001 From: Sungjong Seo Date: Fri, 14 Jul 2023 23:45:15 +0900 Subject: [PATCH 59/60] exfat: release s_lock before calling dir_emit() There is a potential deadlock reported by syzbot as below: ====================================================== WARNING: possible circular locking dependency detected 6.4.0-next-20230707-syzkaller #0 Not tainted ------------------------------------------------------ syz-executor330/5073 is trying to acquire lock: ffff8880218527a0 (&mm->mmap_lock){++++}-{3:3}, at: mmap_read_lock_killable include/linux/mmap_lock.h:151 [inline] ffff8880218527a0 (&mm->mmap_lock){++++}-{3:3}, at: get_mmap_lock_carefully mm/memory.c:5293 [inline] ffff8880218527a0 (&mm->mmap_lock){++++}-{3:3}, at: lock_mm_and_find_vma+0x369/0x510 mm/memory.c:5344 but task is already holding lock: ffff888019f760e0 (&sbi->s_lock){+.+.}-{3:3}, at: exfat_iterate+0x117/0xb50 fs/exfat/dir.c:232 which lock already depends on the new lock. Chain exists of: &mm->mmap_lock --> mapping.invalidate_lock#3 --> &sbi->s_lock Possible unsafe locking scenario: CPU0 CPU1 ---- ---- lock(&sbi->s_lock); lock(mapping.invalidate_lock#3); lock(&sbi->s_lock); rlock(&mm->mmap_lock); Let's try to avoid above potential deadlock condition by moving dir_emit*() out of sbi->s_lock coverage. Fixes: ca06197382bd ("exfat: add directory operations") Cc: stable@vger.kernel.org #v5.7+ Reported-by: syzbot+1741a5d9b79989c10bdc@syzkaller.appspotmail.com Link: https://lore.kernel.org/lkml/00000000000078ee7e060066270b@google.com/T/#u Signed-off-by: Sungjong Seo Signed-off-by: Namjae Jeon --- dir.c | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/dir.c b/dir.c index fd721eda2f41..bf8eb5d1b1c2 100644 --- a/dir.c +++ b/dir.c @@ -219,7 +219,10 @@ static void exfat_free_namebuf(struct exfat_dentry_namebuf *nb) exfat_init_namebuf(nb); } -/* skip iterating emit_dots when dir is empty */ +/* + * Before calling dir_emit*(), sbi->s_lock should be released + * because page fault can occur in dir_emit*(). + */ #define ITER_POS_FILLED_DOTS (2) static int exfat_iterate(struct file *filp, struct dir_context *ctx) { @@ -234,11 +237,10 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) int err = 0, fake_offset = 0; exfat_init_namebuf(nb); - mutex_lock(&EXFAT_SB(sb)->s_lock); cpos = ctx->pos; if (!dir_emit_dots(filp, ctx)) - goto unlock; + goto out; if (ctx->pos == ITER_POS_FILLED_DOTS) { cpos = 0; @@ -250,16 +252,18 @@ static int exfat_iterate(struct file *filp, struct dir_context *ctx) /* name buffer should be allocated before use */ err = exfat_alloc_namebuf(nb); if (err) - goto unlock; + goto out; get_new: + mutex_lock(&EXFAT_SB(sb)->s_lock); + if (ei->flags == ALLOC_NO_FAT_CHAIN && cpos >= i_size_read(inode)) goto end_of_dir; err = exfat_readdir(inode, &cpos, &de); if (err) { /* - * At least we tried to read a sector. Move cpos to next sector - * position (should be aligned). + * At least we tried to read a sector. + * Move cpos to next sector position (should be aligned). */ if (err == -EIO) { cpos += 1 << (sb->s_blocksize_bits); @@ -282,16 +286,10 @@ get_new: inum = iunique(sb, EXFAT_ROOT_INO); } - /* - * Before calling dir_emit(), sb_lock should be released. - * Because page fault can occur in dir_emit() when the size - * of buffer given from user is larger than one page size. - */ mutex_unlock(&EXFAT_SB(sb)->s_lock); if (!dir_emit(ctx, nb->lfn, strlen(nb->lfn), inum, (de.attr & ATTR_SUBDIR) ? DT_DIR : DT_REG)) - goto out_unlocked; - mutex_lock(&EXFAT_SB(sb)->s_lock); + goto out; ctx->pos = cpos; goto get_new; @@ -299,9 +297,8 @@ end_of_dir: if (!cpos && fake_offset) cpos = ITER_POS_FILLED_DOTS; ctx->pos = cpos; -unlock: mutex_unlock(&EXFAT_SB(sb)->s_lock); -out_unlocked: +out: /* * To improve performance, free namebuf after unlock sb_lock. * If namebuf is not allocated, this function do nothing From 2a0023bc28e080f2317a4eb293cf28c3126948de Mon Sep 17 00:00:00 2001 From: HiGarfield Date: Sat, 15 Jul 2023 23:11:12 +0800 Subject: [PATCH 60/60] exfat: add necessary header for vmalloc This fixes building on Linux 4.4. Signed-off-by: HiGarfield Signed-off-by: Namjae Jeon --- balloc.c | 1 + 1 file changed, 1 insertion(+) diff --git a/balloc.c b/balloc.c index 21a865e0ac60..c226d5d1c36b 100644 --- a/balloc.c +++ b/balloc.c @@ -12,6 +12,7 @@ #else #include #endif +#include #include "exfat_raw.h" #include "exfat_fs.h"