From 4637b4cdd7aebfa2e38fa39f4db91fa089b809c5 Mon Sep 17 00:00:00 2001 From: Yang Wen Date: Wed, 25 Feb 2026 22:59:42 +0800 Subject: [PATCH 01/18] exfat: use truncate_inode_pages_final() at evict_inode() Currently, exfat uses truncate_inode_pages() in exfat_evict_inode(). However, truncate_inode_pages() does not mark the mapping as exiting, so reclaim may still install shadow entries for the mapping until the inode teardown completes. In older kernels like Linux 5.10, if shadow entries are present at that point,clear_inode() can hit BUG_ON(inode->i_data.nrexceptional); To align with VFS eviction semantics and prevent this situation, switch to truncate_inode_pages_final() in ->evict_inode(). Other filesystems were updated to use truncate_inode_pages_final() in ->evict_inode() by commit 91b0abe36a7b ("mm + fs: store shadow entries in page cache")'. Signed-off-by: Yang Wen Signed-off-by: Namjae Jeon --- fs/exfat/inode.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index 2fb2d2d5d503..567308aff726 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -686,7 +686,7 @@ struct inode *exfat_build_inode(struct super_block *sb, void exfat_evict_inode(struct inode *inode) { - truncate_inode_pages(&inode->i_data, 0); + truncate_inode_pages_final(&inode->i_data); if (!inode->i_nlink) { i_size_write(inode, 0); From 81440a740d385a992b0652fbd4a5c71edd6f27d2 Mon Sep 17 00:00:00 2001 From: Philipp Hahn Date: Tue, 3 Mar 2026 11:59:15 +0100 Subject: [PATCH 02/18] exfat: Drop dead assignment of num_clusters num_clusters is not used anywhere afterwards. Remove assignment. Found by static code analysis using Klocwork. Signed-off-by: Philipp Hahn Signed-off-by: Namjae Jeon --- fs/exfat/inode.c | 1 - 1 file changed, 1 deletion(-) diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index 567308aff726..d5f6dbab4720 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -213,7 +213,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, return -EIO; } - num_clusters += num_to_be_allocated; *clu = new_clu.dir; inode->i_blocks += EXFAT_CLU_TO_B(num_to_be_allocated, sbi) >> 9; From 3dce5bb82c97fc2ac28d80d496120a6525ce3fb7 Mon Sep 17 00:00:00 2001 From: Philipp Hahn Date: Tue, 3 Mar 2026 11:59:14 +0100 Subject: [PATCH 03/18] exfat: Fix bitwise operation having different size cpos has type loff_t (long long), while s_blocksize has type u32. The inversion wil happen on u32, the coercion to s64 happens afterwards and will do 0-left-paddding, resulting in the upper bits getting masked out. Cast s_blocksize to loff_t before negating it. Found by static code analysis using Klocwork. Signed-off-by: Philipp Hahn Signed-off-by: Namjae Jeon --- fs/exfat/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index 3a4853693d8b..e710dd196e2f 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -249,7 +249,7 @@ static int exfat_iterate(struct file *file, struct dir_context *ctx) */ if (err == -EIO) { cpos += 1 << (sb->s_blocksize_bits); - cpos &= ~(sb->s_blocksize - 1); + cpos &= ~(loff_t)(sb->s_blocksize - 1); } err = -EIO; From bf1797960c20f3d4dc4e8b6f560ca39692abac56 Mon Sep 17 00:00:00 2001 From: David Timber Date: Sat, 28 Feb 2026 17:44:14 +0900 Subject: [PATCH 04/18] exfat: add fallocate FALLOC_FL_ALLOCATE_RANGE support Currently, the Linux (ex)FAT drivers do not employ any cluster allocation strategy to keep fragmentation at bay. As a result, when multiple processes are competing for new clusters to expand files in exfat filesystem on Linux simultaneously, the files end up heavily fragmented. HDDs are most impacted, but this could also have some negative impact on various forms of flash memory depending on the type of underlying technology. For instance, modern digital cameras produce multiple media files for a single video stream. If the application does not take the fragmentation issue into account or the system is under memory pressure, the kernel end up allocating clusters in said files in a interleaved manner. Demo script: for (( i = 0; i < 4; i += 1 )); do dd if=/dev/urandom iflag=fullblock bs=1M count=64 of=frag-$i & done for (( i = 0; i < 4; i += 1 )); do wait done filefrag frag-* Result - Linux kernel native exfat, async mount: 780 extents found 740 extents found 809 extents found 712 extents found Result - Linux kernel native exfat, sync mount: 1852 extents found 1836 extents found 1846 extents found 1881 extents found Result - Windows XP: 3 extents found 3 extents found 3 extents found 2 extents found Windows kernel, on the other hand, regardless of the underlying storage interface or the medium, seems to space out clusters for each file. Similar strategy has to be employed by Linux fat filesystems for efficient utilisation of storage backend. In the meantime, userspace applications like rsync may use fallocate to combat this issue. This patch may introduce a regression-like behaviour to some niche filesystem-agnostic applications that use fallocate and proceed to non-sequentially write to the file. Examples: - libtorrent's use of posix_fallocate() and the first fragment from a peer is near the end of the file - "Download accelerators" that do partial content requests(HTTP 206) in multiple threads writing to the same file The delay incurred in such use cases is documented in WinAPI. Patches that add the ioctl equivalents to the WinAPI function SetFileValidData() and `fsutil file queryvaliddata ...` will follow. Signed-off-by: David Timber Signed-off-by: Namjae Jeon --- fs/exfat/file.c | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 90cd540afeaa..2daf0dbabb24 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -13,6 +13,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -90,6 +91,45 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) return -EIO; } +/* + * Preallocate space for a file. This implements exfat's fallocate file + * operation, which gets called from sys_fallocate system call. User space + * requests len bytes at offset. In contrary to fat, we only support + * FALLOC_FL_ALLOCATE_RANGE because by leaving the valid data length(VDL) + * field, it is unnecessary to zero out the newly allocated clusters. + */ +static long exfat_fallocate(struct file *file, int mode, + loff_t offset, loff_t len) +{ + struct inode *inode = file->f_mapping->host; + loff_t newsize = offset + len; + int err = 0; + + /* No support for other modes */ + if (mode != FALLOC_FL_ALLOCATE_RANGE) + return -EOPNOTSUPP; + + /* No support for dir */ + if (!S_ISREG(inode->i_mode)) + return -EOPNOTSUPP; + + if (unlikely(exfat_forced_shutdown(inode->i_sb))) + return -EIO; + + inode_lock(inode); + + if (newsize <= i_size_read(inode)) + goto error; + + /* This is just an expanding truncate */ + err = exfat_cont_expand(inode, newsize); + +error: + inode_unlock(inode); + + return err; +} + static bool exfat_allow_set_time(struct mnt_idmap *idmap, struct exfat_sb_info *sbi, struct inode *inode) { @@ -771,6 +811,7 @@ const struct file_operations exfat_file_operations = { .fsync = exfat_file_fsync, .splice_read = exfat_splice_read, .splice_write = iter_file_splice_write, + .fallocate = exfat_fallocate, .setlease = generic_setlease, }; From 6ed88c9491d7693c87dd8bfb850c56770310828b Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Tue, 3 Mar 2026 11:14:04 +0800 Subject: [PATCH 05/18] exfat: add block readahead in exfat_chain_cont_cluster When a file cannot allocate contiguous clusters, exfat converts the file from NO_FAT_CHAIN to FAT_CHAIN format. For large files, this conversion process can take a significant amount of time. Add simple readahead to read all the FAT blocks in advance, as these blocks are consecutive, significantly improving the conversion performance. Test in an empty exfat filesystem: dd if=/dev/zero of=/mnt/file bs=1M count=30k dd if=/dev/zero of=/mnt/file2 bs=1M count=1 time cat /mnt/file2 >> /mnt/file | cluster size | before patch | after patch | | ------------ | ------------ | ----------- | | 512 | 47.667s | 4.316s | | 4k | 6.436s | 0.541s | | 32k | 0.758s | 0.071s | | 256k | 0.117s | 0.011s | Signed-off-by: Chi Zhiling Signed-off-by: Namjae Jeon --- fs/exfat/exfat_fs.h | 11 +++++++++-- fs/exfat/fatent.c | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 2dbed5f8ec26..090f25d1a418 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -10,6 +10,7 @@ #include #include #include +#include #include #define EXFAT_ROOT_INO 1 @@ -79,6 +80,10 @@ enum { #define EXFAT_HINT_NONE -1 #define EXFAT_MIN_SUBDIR 2 +#define EXFAT_BLK_RA_SIZE(sb) \ + (min_t(blkcnt_t, (sb)->s_bdi->ra_pages, (sb)->s_bdi->io_pages) \ + << (PAGE_SHIFT - (sb)->s_blocksize_bits)) + /* * helpers for cluster size to byte conversion. */ @@ -117,9 +122,9 @@ enum { #define FAT_ENT_SIZE (4) #define FAT_ENT_SIZE_BITS (2) #define FAT_ENT_OFFSET_SECTOR(sb, loc) (EXFAT_SB(sb)->FAT1_start_sector + \ - (((u64)loc << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits)) + (((u64)(loc) << FAT_ENT_SIZE_BITS) >> sb->s_blocksize_bits)) #define FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc) \ - ((loc << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1)) + (((loc) << FAT_ENT_SIZE_BITS) & (sb->s_blocksize - 1)) /* * helpers for bitmap. @@ -448,6 +453,8 @@ int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain, unsigned int *ret_clu); int exfat_count_num_clusters(struct super_block *sb, struct exfat_chain *p_chain, unsigned int *ret_count); +int exfat_blk_readahead(struct super_block *sb, sector_t sec, + sector_t *ra, blkcnt_t *ra_cnt, sector_t end); /* balloc.c */ int exfat_load_bitmap(struct super_block *sb); diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index f87576ca7032..9a4143f3fc0c 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -142,13 +142,50 @@ int exfat_ent_get(struct super_block *sb, unsigned int loc, return -EIO; } +int exfat_blk_readahead(struct super_block *sb, sector_t sec, + sector_t *ra, blkcnt_t *ra_cnt, sector_t end) +{ + struct blk_plug plug; + + if (sec < *ra) + return 0; + + *ra += *ra_cnt; + + /* No blocks left (or only the last block), skip readahead. */ + if (*ra >= end) + return 0; + + *ra_cnt = min(end - *ra + 1, EXFAT_BLK_RA_SIZE(sb)); + if (*ra_cnt == 0) { + /* Move 'ra' to the end to disable readahead. */ + *ra = end; + return 0; + } + + blk_start_plug(&plug); + for (unsigned int i = 0; i < *ra_cnt; i++) + sb_breadahead(sb, *ra + i); + blk_finish_plug(&plug); + return 0; +} + int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, unsigned int len) { + sector_t sec, end, ra; + blkcnt_t ra_cnt = 0; + if (!len) return 0; + ra = FAT_ENT_OFFSET_SECTOR(sb, chain); + end = FAT_ENT_OFFSET_SECTOR(sb, chain + len - 1); + while (len > 1) { + sec = FAT_ENT_OFFSET_SECTOR(sb, chain); + exfat_blk_readahead(sb, sec, &ra, &ra_cnt, end); + if (exfat_ent_set(sb, chain, chain + 1)) return -EIO; chain++; From a299900144da9276a66d3d8c5112605b273bacd6 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Tue, 3 Mar 2026 11:14:05 +0800 Subject: [PATCH 06/18] exfat: use readahead helper in exfat_allocate_bitmap Use the newly added exfat_blk_readahead() helper in exfat_allocate_bitmap() to simplify the code. This eliminates the duplicate inline readahead logic and uses the unified readahead interface. Signed-off-by: Chi Zhiling Signed-off-by: Namjae Jeon --- fs/exfat/balloc.c | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/fs/exfat/balloc.c b/fs/exfat/balloc.c index 3a32f49f9dbd..625f2f14d4fe 100644 --- a/fs/exfat/balloc.c +++ b/fs/exfat/balloc.c @@ -74,11 +74,10 @@ static int exfat_allocate_bitmap(struct super_block *sb, struct exfat_dentry *ep) { struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct blk_plug plug; long long map_size; unsigned int i, j, need_map_size; - sector_t sector; - unsigned int max_ra_count; + sector_t sector, end, ra; + blkcnt_t ra_cnt = 0; sbi->map_clu = le32_to_cpu(ep->dentry.bitmap.start_clu); map_size = le64_to_cpu(ep->dentry.bitmap.size); @@ -100,17 +99,12 @@ static int exfat_allocate_bitmap(struct super_block *sb, if (!sbi->vol_amap) return -ENOMEM; - sector = exfat_cluster_to_sector(sbi, sbi->map_clu); - max_ra_count = min(sb->s_bdi->ra_pages, sb->s_bdi->io_pages) << - (PAGE_SHIFT - sb->s_blocksize_bits); + sector = ra = exfat_cluster_to_sector(sbi, sbi->map_clu); + end = sector + sbi->map_sectors - 1; + for (i = 0; i < sbi->map_sectors; i++) { /* Trigger the next readahead in advance. */ - if (max_ra_count && 0 == (i % max_ra_count)) { - blk_start_plug(&plug); - for (j = i; j < min(max_ra_count, sbi->map_sectors - i) + i; j++) - sb_breadahead(sb, sector + j); - blk_finish_plug(&plug); - } + exfat_blk_readahead(sb, sector + i, &ra, &ra_cnt, end); sbi->vol_amap[i] = sb_bread(sb, sector + i); if (!sbi->vol_amap[i]) From 7094b09ea713d537f801a48853681c1b4205df2e Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Tue, 3 Mar 2026 11:14:06 +0800 Subject: [PATCH 07/18] exfat: use readahead helper in exfat_get_dentry Replace the custom exfat_dir_readahead() function with the unified exfat_blk_readahead() helper in exfat_get_dentry(). This removes the duplicate readahead implementation and uses the common interface, also reducing code complexity. Signed-off-by: Chi Zhiling Signed-off-by: Namjae Jeon --- fs/exfat/dir.c | 52 ++++++++++++++------------------------------------ 1 file changed, 14 insertions(+), 38 deletions(-) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index e710dd196e2f..a2c2b998808c 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -623,44 +623,11 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir return 0; } -#define EXFAT_MAX_RA_SIZE (128*1024) -static int exfat_dir_readahead(struct super_block *sb, sector_t sec) -{ - struct exfat_sb_info *sbi = EXFAT_SB(sb); - struct buffer_head *bh; - unsigned int max_ra_count = EXFAT_MAX_RA_SIZE >> sb->s_blocksize_bits; - unsigned int page_ra_count = PAGE_SIZE >> sb->s_blocksize_bits; - unsigned int adj_ra_count = max(sbi->sect_per_clus, page_ra_count); - unsigned int ra_count = min(adj_ra_count, max_ra_count); - - /* Read-ahead is not required */ - if (sbi->sect_per_clus == 1) - return 0; - - if (sec < sbi->data_start_sector) { - exfat_err(sb, "requested sector is invalid(sect:%llu, root:%llu)", - (unsigned long long)sec, sbi->data_start_sector); - return -EIO; - } - - /* Not sector aligned with ra_count, resize ra_count to page size */ - if ((sec - sbi->data_start_sector) & (ra_count - 1)) - ra_count = page_ra_count; - - bh = sb_find_get_block(sb, sec); - if (!bh || !buffer_uptodate(bh)) { - unsigned int i; - - for (i = 0; i < ra_count; i++) - sb_breadahead(sb, (sector_t)(sec + i)); - } - brelse(bh); - return 0; -} - struct exfat_dentry *exfat_get_dentry(struct super_block *sb, struct exfat_chain *p_dir, int entry, struct buffer_head **bh) { + struct exfat_sb_info *sbi = EXFAT_SB(sb); + unsigned int sect_per_clus = sbi->sect_per_clus; unsigned int dentries_per_page = EXFAT_B_TO_DEN(PAGE_SIZE); int off; sector_t sec; @@ -673,9 +640,18 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb, if (exfat_find_location(sb, p_dir, entry, &sec, &off)) return NULL; - if (p_dir->dir != EXFAT_FREE_CLUSTER && - !(entry & (dentries_per_page - 1))) - exfat_dir_readahead(sb, sec); + if (sect_per_clus > 1 && + (entry & (dentries_per_page - 1)) == 0) { + sector_t ra = sec; + blkcnt_t cnt = 0; + unsigned int ra_count = sect_per_clus; + + /* Not sector aligned with ra_count, resize ra_count to page size */ + if ((sec - sbi->data_start_sector) & (ra_count - 1)) + ra_count = PAGE_SIZE >> sb->s_blocksize_bits; + + exfat_blk_readahead(sb, sec, &ra, &cnt, sec + ra_count - 1); + } *bh = sb_bread(sb, sec); if (!*bh) From 63193eb4452d3bf5b2b71042c536cb51c7d786cb Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Tue, 3 Mar 2026 11:14:07 +0800 Subject: [PATCH 08/18] exfat: drop redundant sec parameter from exfat_mirror_bh The sector offset can be obtained from bh->b_blocknr, so drop the redundant sec parameter from exfat_mirror_bh(). Also clean up the function to use exfat_update_bh() helper. No functional changes. Signed-off-by: Chi Zhiling Signed-off-by: Namjae Jeon --- fs/exfat/fatent.c | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index 9a4143f3fc0c..4177a933e0be 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -11,11 +11,11 @@ #include "exfat_raw.h" #include "exfat_fs.h" -static int exfat_mirror_bh(struct super_block *sb, sector_t sec, - struct buffer_head *bh) +static int exfat_mirror_bh(struct super_block *sb, struct buffer_head *bh) { struct buffer_head *c_bh; struct exfat_sb_info *sbi = EXFAT_SB(sb); + sector_t sec = bh->b_blocknr; sector_t sec2; int err = 0; @@ -25,10 +25,7 @@ static int exfat_mirror_bh(struct super_block *sb, sector_t sec, if (!c_bh) return -ENOMEM; memcpy(c_bh->b_data, bh->b_data, sb->s_blocksize); - set_buffer_uptodate(c_bh); - mark_buffer_dirty(c_bh); - if (sb->s_flags & SB_SYNCHRONOUS) - err = sync_dirty_buffer(c_bh); + exfat_update_bh(c_bh, sb->s_flags & SB_SYNCHRONOUS); brelse(c_bh); } @@ -83,7 +80,7 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, fat_entry = (__le32 *)&(bh->b_data[off]); *fat_entry = cpu_to_le32(content); exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS); - exfat_mirror_bh(sb, sec, bh); + exfat_mirror_bh(sb, bh); brelse(bh); return 0; } From 636bd62299aea24684086d126ea88b23a6466f8e Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Tue, 3 Mar 2026 11:14:08 +0800 Subject: [PATCH 09/18] exfat: optimize exfat_chain_cont_cluster with cached buffer heads When converting files from NO_FAT_CHAIN to FAT_CHAIN format, profiling reveals significant time spent in mark_buffer_dirty() and exfat_mirror_bh() operations. This overhead occurs because each FAT entry modification triggers a full block dirty marking and mirroring operation. For consecutive clusters that reside in the same block, optimize by caching the buffer head and performing dirty marking only once at the end of the block's modifications. Performance improvements for converting a 30GB file: | Cluster Size | Before Patch | After Patch | Speedup | |--------------|--------------|-------------|---------| | 512 bytes | 4.243s | 1.866s | 2.27x | | 4KB | 0.863s | 0.236s | 3.66x | | 32KB | 0.069s | 0.034s | 2.03x | | 256KB | 0.012s | 0.006s | 2.00x | Signed-off-by: Chi Zhiling Signed-off-by: Namjae Jeon --- fs/exfat/fatent.c | 49 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index 4177a933e0be..a973aa4de57b 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -32,6 +32,17 @@ static int exfat_mirror_bh(struct super_block *sb, struct buffer_head *bh) return err; } +static int exfat_end_bh(struct super_block *sb, struct buffer_head *bh) +{ + int err; + + exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS); + err = exfat_mirror_bh(sb, bh); + brelse(bh); + + return err; +} + static int __exfat_ent_get(struct super_block *sb, unsigned int loc, unsigned int *content, struct buffer_head **last) { @@ -62,29 +73,40 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc, return 0; } -int exfat_ent_set(struct super_block *sb, unsigned int loc, - unsigned int content) +static int __exfat_ent_set(struct super_block *sb, unsigned int loc, + unsigned int content, struct buffer_head **cache) { - unsigned int off; sector_t sec; __le32 *fat_entry; - struct buffer_head *bh; + struct buffer_head *bh = cache ? *cache : NULL; + unsigned int off; sec = FAT_ENT_OFFSET_SECTOR(sb, loc); off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc); - bh = sb_bread(sb, sec); - if (!bh) - return -EIO; + if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) { + if (bh) + exfat_end_bh(sb, bh); + bh = sb_bread(sb, sec); + if (cache) + *cache = bh; + if (unlikely(!bh)) + return -EIO; + } fat_entry = (__le32 *)&(bh->b_data[off]); *fat_entry = cpu_to_le32(content); - exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS); - exfat_mirror_bh(sb, bh); - brelse(bh); + if (!cache) + exfat_end_bh(sb, bh); return 0; } +int exfat_ent_set(struct super_block *sb, unsigned int loc, + unsigned int content) +{ + return __exfat_ent_set(sb, loc, content, NULL); +} + /* * Caller must release the buffer_head if no error return. */ @@ -170,6 +192,7 @@ int exfat_blk_readahead(struct super_block *sb, sector_t sec, int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, unsigned int len) { + struct buffer_head *bh = NULL; sector_t sec, end, ra; blkcnt_t ra_cnt = 0; @@ -183,14 +206,16 @@ int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain, sec = FAT_ENT_OFFSET_SECTOR(sb, chain); exfat_blk_readahead(sb, sec, &ra, &ra_cnt, end); - if (exfat_ent_set(sb, chain, chain + 1)) + if (__exfat_ent_set(sb, chain, chain + 1, &bh)) return -EIO; chain++; len--; } - if (exfat_ent_set(sb, chain, EXFAT_EOF_CLUSTER)) + if (__exfat_ent_set(sb, chain, EXFAT_EOF_CLUSTER, &bh)) return -EIO; + + exfat_end_bh(sb, bh); return 0; } From d1d75eaf01abceb3d5cb50253375b5f254b6be54 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Tue, 3 Mar 2026 11:14:09 +0800 Subject: [PATCH 10/18] exfat: fix error handling for FAT table operations Fix three error handling issues in FAT table operations: 1. Fix exfat_update_bh() to properly return errors from sync_dirty_buffer 2. Fix exfat_end_bh() to properly return errors from exfat_update_bh() and exfat_mirror_bh() 3. Fix ignored return values from exfat_chain_cont_cluster() in inode.c and namei.c These fixes ensure that FAT table write errors are properly propagated to the caller instead of being silently ignored. Signed-off-by: Chi Zhiling Signed-off-by: Namjae Jeon --- fs/exfat/exfat_fs.h | 2 +- fs/exfat/fatent.c | 8 ++++---- fs/exfat/inode.c | 5 +++-- fs/exfat/misc.c | 8 ++++++-- fs/exfat/namei.c | 3 ++- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 090f25d1a418..9fed9fb33cae 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -584,7 +584,7 @@ void exfat_set_entry_time(struct exfat_sb_info *sbi, struct timespec64 *ts, u8 *tz, __le16 *time, __le16 *date, u8 *time_cs); u16 exfat_calc_chksum16(void *data, int len, u16 chksum, int type); u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type); -void exfat_update_bh(struct buffer_head *bh, int sync); +int exfat_update_bh(struct buffer_head *bh, int sync); int exfat_update_bhs(struct buffer_head **bhs, int nr_bhs, int sync); void exfat_chain_set(struct exfat_chain *ec, unsigned int dir, unsigned int size, unsigned char flags); diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index a973aa4de57b..f2e5d5dde393 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -25,7 +25,7 @@ static int exfat_mirror_bh(struct super_block *sb, struct buffer_head *bh) if (!c_bh) return -ENOMEM; memcpy(c_bh->b_data, bh->b_data, sb->s_blocksize); - exfat_update_bh(c_bh, sb->s_flags & SB_SYNCHRONOUS); + err = exfat_update_bh(c_bh, sb->s_flags & SB_SYNCHRONOUS); brelse(c_bh); } @@ -36,10 +36,10 @@ static int exfat_end_bh(struct super_block *sb, struct buffer_head *bh) { int err; - exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS); - err = exfat_mirror_bh(sb, bh); + err = exfat_update_bh(bh, sb->s_flags & SB_SYNCHRONOUS); + if (!err) + err = exfat_mirror_bh(sb, bh); brelse(bh); - return err; } diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index d5f6dbab4720..beb9ea7cca9f 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -204,8 +204,9 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, * so fat-chain should be synced with * alloc-bitmap */ - exfat_chain_cont_cluster(sb, ei->start_clu, - num_clusters); + if (exfat_chain_cont_cluster(sb, ei->start_clu, + num_clusters)) + return -EIO; ei->flags = ALLOC_FAT_CHAIN; } if (new_clu.flags == ALLOC_FAT_CHAIN) diff --git a/fs/exfat/misc.c b/fs/exfat/misc.c index fa8459828046..6f11a96a4ffa 100644 --- a/fs/exfat/misc.c +++ b/fs/exfat/misc.c @@ -161,13 +161,17 @@ u32 exfat_calc_chksum32(void *data, int len, u32 chksum, int type) return chksum; } -void exfat_update_bh(struct buffer_head *bh, int sync) +int exfat_update_bh(struct buffer_head *bh, int sync) { + int err = 0; + set_buffer_uptodate(bh); mark_buffer_dirty(bh); if (sync) - sync_dirty_buffer(bh); + err = sync_dirty_buffer(bh); + + return err; } int exfat_update_bhs(struct buffer_head **bhs, int nr_bhs, int sync) diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index 670116ae9ec8..ef2a3488c1b3 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -365,7 +365,8 @@ int exfat_find_empty_entry(struct inode *inode, /* no-fat-chain bit is disabled, * so fat-chain should be synced with alloc-bitmap */ - exfat_chain_cont_cluster(sb, p_dir->dir, p_dir->size); + if (exfat_chain_cont_cluster(sb, p_dir->dir, p_dir->size)) + return -EIO; p_dir->flags = ALLOC_FAT_CHAIN; hint_femp.cur.flags = ALLOC_FAT_CHAIN; } From 73f01258002fe35d6c6b20f92c75b891e7215b06 Mon Sep 17 00:00:00 2001 From: Yang Wen Date: Sun, 22 Mar 2026 23:32:06 +0800 Subject: [PATCH 11/18] exfat: fix passing zero to ERR_PTR() in exfat_mkdir() Detected by Smatch. namei.c:890 exfat_mkdir() warn: passing zero to 'ERR_PTR' Signed-off-by: Yang Wen Signed-off-by: Namjae Jeon --- fs/exfat/namei.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index ef2a3488c1b3..d0ea1ff81c09 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -874,9 +874,10 @@ static struct dentry *exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, i_pos = exfat_make_i_pos(&info); inode = exfat_build_inode(sb, &info, i_pos); - err = PTR_ERR_OR_ZERO(inode); - if (err) + if (IS_ERR(inode)) { + err = PTR_ERR(inode); goto unlock; + } inode_inc_iversion(inode); EXFAT_I(inode)->i_crtime = simple_inode_init_ts(inode); @@ -887,7 +888,7 @@ static struct dentry *exfat_mkdir(struct mnt_idmap *idmap, struct inode *dir, unlock: mutex_unlock(&EXFAT_SB(sb)->s_lock); - return ERR_PTR(err); + return err ? ERR_PTR(err) : NULL; } static int exfat_check_dir_empty(struct super_block *sb, From 4129a3a2751cba8511cee5d13145223662a8e019 Mon Sep 17 00:00:00 2001 From: David Timber Date: Tue, 17 Mar 2026 06:41:37 +0900 Subject: [PATCH 12/18] exfat: fix s_maxbytes With fallocate support, xfstest unit generic/213 fails with QA output created by 213 We should get: fallocate: No space left on device Strangely, xfs_io sometimes says "Success" when something went wrong -fallocate: No space left on device +fallocate: File too large because sb->s_maxbytes is set to the volume size. To be in line with other non-extent-based filesystems, set to max volume size possible with the cluster size of the volume. Signed-off-by: David Timber Signed-off-by: Namjae Jeon --- fs/exfat/exfat_raw.h | 1 + fs/exfat/file.c | 1 + fs/exfat/super.c | 11 ++++++++--- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/fs/exfat/exfat_raw.h b/fs/exfat/exfat_raw.h index 4082fa7b8c14..ec70cd35bba0 100644 --- a/fs/exfat/exfat_raw.h +++ b/fs/exfat/exfat_raw.h @@ -25,6 +25,7 @@ #define EXFAT_FIRST_CLUSTER 2 #define EXFAT_DATA_CLUSTER_COUNT(sbi) \ ((sbi)->num_clusters - EXFAT_RESERVED_CLUSTERS) +#define EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5) /* AllocationPossible and NoFatChain field in GeneralSecondaryFlags Field */ #define ALLOC_POSSIBLE 0x01 diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 2daf0dbabb24..6fa720e99103 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -34,6 +34,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size) return ret; num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi); + /* integer overflow is already checked in inode_newsize_ok(). */ new_num_clusters = EXFAT_B_TO_CLU_ROUND_UP(size, sbi); if (new_num_clusters == num_clusters) diff --git a/fs/exfat/super.c b/fs/exfat/super.c index 83396fd265cd..95d87e2d7717 100644 --- a/fs/exfat/super.c +++ b/fs/exfat/super.c @@ -531,9 +531,14 @@ static int exfat_read_boot_sector(struct super_block *sb) if (sbi->vol_flags & MEDIA_FAILURE) exfat_warn(sb, "Medium has reported failures. Some data may be lost."); - /* exFAT file size is limited by a disk volume size */ - sb->s_maxbytes = (u64)(sbi->num_clusters - EXFAT_RESERVED_CLUSTERS) << - sbi->cluster_size_bits; + /* + * Set to the max possible volume size for this volume's cluster size so + * that any integer overflow from bytes to cluster size conversion is + * checked in inode_newsize_ok(). Clamped to MAX_LFS_FILESIZE for 32-bit + * machines. + */ + sb->s_maxbytes = min(MAX_LFS_FILESIZE, + EXFAT_CLU_TO_B((loff_t)EXFAT_MAX_NUM_CLUSTER, sbi)); /* check logical sector size */ if (exfat_calibrate_blocksize(sb, 1 << p_boot->sect_size_bits)) From ff37797badd831797b8a27830fe5046d7e23fdc3 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Fri, 3 Apr 2026 16:05:33 +0800 Subject: [PATCH 13/18] exfat: fix incorrect directory checksum after rename to shorter name When renaming a file in-place to a shorter name, exfat_remove_entries marks excess entries as DELETED, but es->num_entries is not updated accordingly. As a result, exfat_update_dir_chksum iterates over the deleted entries and computes an incorrect checksum. This does not lead to persistent corruption because mark_inode_dirty() is called afterward, and __exfat_write_inode later recomputes the checksum using the correct num_entries value. Fix by setting es->num_entries = num_entries in exfat_init_ext_entry. Signed-off-by: Chi Zhiling Reviewed-by: Sungjong Seo Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/dir.c | 1 + 1 file changed, 1 insertion(+) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index a2c2b998808c..7619410d668e 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -490,6 +490,7 @@ void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, unsigned short *uniname = p_uniname->name; struct exfat_dentry *ep; + es->num_entries = num_entries; ep = exfat_get_dentry_cached(es, ES_IDX_FILE); ep->dentry.file.num_ext = (unsigned char)(num_entries - 1); From f5e5177fd751529a3c951a0f4f7a05fdfadab775 Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Fri, 3 Apr 2026 16:05:34 +0800 Subject: [PATCH 14/18] exfat: introduce exfat_cluster_walk helper Introduce exfat_cluster_walk() to walk the FAT chain by a given step, handling both ALLOC_NO_FAT_CHAIN and ALLOC_FAT_CHAIN modes. Also redefine exfat_get_next_cluster as a thin wrapper around it for backward compatibility. Signed-off-by: Chi Zhiling Reviewed-by: Sungjong Seo Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/exfat_fs.h | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 9fed9fb33cae..7f9d0cfa252b 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -437,7 +437,8 @@ int exfat_set_volume_dirty(struct super_block *sb); int exfat_clear_volume_dirty(struct super_block *sb); /* fatent.c */ -#define exfat_get_next_cluster(sb, pclu) exfat_ent_get(sb, *(pclu), pclu, NULL) +#define exfat_get_next_cluster(sb, pclu) \ + exfat_cluster_walk(sb, (pclu), 1, ALLOC_FAT_CHAIN) int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, struct exfat_chain *p_chain, bool sync_bmap); @@ -456,6 +457,26 @@ int exfat_count_num_clusters(struct super_block *sb, int exfat_blk_readahead(struct super_block *sb, sector_t sec, sector_t *ra, blkcnt_t *ra_cnt, sector_t end); +static inline int +exfat_cluster_walk(struct super_block *sb, unsigned int *clu, + unsigned int step, int flags) +{ + struct buffer_head *bh = NULL; + + if (flags == ALLOC_NO_FAT_CHAIN) { + (*clu) += step; + return 0; + } + + while (step--) { + if (exfat_ent_get(sb, *clu, clu, &bh)) + return -EIO; + } + brelse(bh); + + return 0; +} + /* balloc.c */ int exfat_load_bitmap(struct super_block *sb); void exfat_free_bitmap(struct exfat_sb_info *sbi); From 6f2cbe45c675d21e104856fc5917048e772a0dcf Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Fri, 3 Apr 2026 16:05:35 +0800 Subject: [PATCH 15/18] exfat: use exfat_cluster_walk helper Replace the custom exfat_walk_fat_chain() function and open-coded FAT chain walking logic with the exfat_cluster_walk() helper across exfat_find_location, __exfat_get_dentry_set, and exfat_map_cluster. Suggested-by: Sungjong Seo Signed-off-by: Chi Zhiling Reviewed-by: Sungjong Seo Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/dir.c | 47 +++++++++++------------------------------------ fs/exfat/inode.c | 11 ++--------- 2 files changed, 13 insertions(+), 45 deletions(-) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index 7619410d668e..ca5827046a1f 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -562,38 +562,6 @@ int exfat_put_dentry_set(struct exfat_entry_set_cache *es, int sync) return err; } -static int exfat_walk_fat_chain(struct super_block *sb, - struct exfat_chain *p_dir, unsigned int byte_offset, - unsigned int *clu) -{ - struct exfat_sb_info *sbi = EXFAT_SB(sb); - unsigned int clu_offset; - unsigned int cur_clu; - - clu_offset = EXFAT_B_TO_CLU(byte_offset, sbi); - cur_clu = p_dir->dir; - - if (p_dir->flags == ALLOC_NO_FAT_CHAIN) { - cur_clu += clu_offset; - } else { - while (clu_offset > 0) { - if (exfat_get_next_cluster(sb, &cur_clu)) - return -EIO; - if (cur_clu == EXFAT_EOF_CLUSTER) { - exfat_fs_error(sb, - "invalid dentry access beyond EOF (clu : %u, eidx : %d)", - p_dir->dir, - EXFAT_B_TO_DEN(byte_offset)); - return -EIO; - } - clu_offset--; - } - } - - *clu = cur_clu; - return 0; -} - static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir, int entry, sector_t *sector, int *offset) { @@ -603,10 +571,19 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir off = EXFAT_DEN_TO_B(entry); - ret = exfat_walk_fat_chain(sb, p_dir, off, &clu); + clu = p_dir->dir; + ret = exfat_cluster_walk(sb, &clu, EXFAT_B_TO_CLU(off, sbi), p_dir->flags); if (ret) return ret; + if (clu == EXFAT_EOF_CLUSTER) { + exfat_fs_error(sb, + "unexpected early break in cluster chain (clu : %u, len : %d)", + p_dir->dir, + EXFAT_B_TO_CLU(off, sbi)); + return -EIO; + } + if (!exfat_test_bitmap(sb, clu)) { exfat_err(sb, "failed to test cluster bit(%u)", clu); return -EIO; @@ -792,9 +769,7 @@ static int __exfat_get_dentry_set(struct exfat_entry_set_cache *es, 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)) + if (exfat_cluster_walk(sb, &clu, 1, p_dir->flags)) goto put_es; sec = exfat_cluster_to_sector(sbi, clu); } else { diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index beb9ea7cca9f..cde2eb0f31ad 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -225,15 +225,8 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, * *clu = (the first cluster of the allocated chain) => * (the last cluster of ...) */ - if (ei->flags == ALLOC_NO_FAT_CHAIN) { - *clu += num_to_be_allocated - 1; - } else { - while (num_to_be_allocated > 1) { - if (exfat_get_next_cluster(sb, clu)) - return -EIO; - num_to_be_allocated--; - } - } + if (exfat_cluster_walk(sb, clu, num_to_be_allocated - 1, ei->flags)) + return -EIO; *count = 1; } From f764c5897fc2d2eccce05dba86f32aac94bcd28c Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Fri, 3 Apr 2026 16:05:36 +0800 Subject: [PATCH 16/18] exfat: remove NULL cache pointer case in exfat_ent_get Since exfat_get_next_cluster has been updated, no callers pass a NULL pointer to exfat_ent_get, so remove the handling logic for this case. Signed-off-by: Chi Zhiling Reviewed-by: Sungjong Seo Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/fatent.c | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/fs/exfat/fatent.c b/fs/exfat/fatent.c index f2e5d5dde393..dce0955e689a 100644 --- a/fs/exfat/fatent.c +++ b/fs/exfat/fatent.c @@ -44,11 +44,11 @@ static int exfat_end_bh(struct super_block *sb, struct buffer_head *bh) } static int __exfat_ent_get(struct super_block *sb, unsigned int loc, - unsigned int *content, struct buffer_head **last) + unsigned int *content, struct buffer_head **cache) { unsigned int off; sector_t sec; - struct buffer_head *bh = last ? *last : NULL; + struct buffer_head *bh = *cache; sec = FAT_ENT_OFFSET_SECTOR(sb, loc); off = FAT_ENT_OFFSET_BYTE_IN_SECTOR(sb, loc); @@ -56,8 +56,7 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc, if (!bh || bh->b_blocknr != sec || !buffer_uptodate(bh)) { brelse(bh); bh = sb_bread(sb, sec); - if (last) - *last = bh; + *cache = bh; if (unlikely(!bh)) return -EIO; } @@ -68,8 +67,6 @@ static int __exfat_ent_get(struct super_block *sb, unsigned int loc, if (*content > EXFAT_BAD_CLUSTER) *content = EXFAT_EOF_CLUSTER; - if (!last) - brelse(bh); return 0; } @@ -111,7 +108,7 @@ int exfat_ent_set(struct super_block *sb, unsigned int loc, * Caller must release the buffer_head if no error return. */ int exfat_ent_get(struct super_block *sb, unsigned int loc, - unsigned int *content, struct buffer_head **last) + unsigned int *content, struct buffer_head **cache) { struct exfat_sb_info *sbi = EXFAT_SB(sb); @@ -122,7 +119,7 @@ int exfat_ent_get(struct super_block *sb, unsigned int loc, goto err; } - if (unlikely(__exfat_ent_get(sb, loc, content, last))) { + if (unlikely(__exfat_ent_get(sb, loc, content, cache))) { exfat_fs_error_ratelimit(sb, "failed to access to FAT (entry 0x%08x)", loc); @@ -151,13 +148,11 @@ int exfat_ent_get(struct super_block *sb, unsigned int loc, } return 0; -err: - if (last) { - brelse(*last); - /* Avoid double release */ - *last = NULL; - } +err: + /* Avoid double release */ + brelse(*cache); + *cache = NULL; return -EIO; } From 227468fc82e4ce43b6ccc1111aa479948f3dd38c Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Fri, 3 Apr 2026 16:05:37 +0800 Subject: [PATCH 17/18] exfat: introduce exfat_chain_advance helper Introduce exfat_chain_advance() to walk a exfat_chain structure by a given step, updating both ->dir and ->size fields atomically. This helper handles both ALLOC_NO_FAT_CHAIN and ALLOC_FAT_CHAIN modes with proper boundary checking. Suggested-by: Yuezhang Mo Signed-off-by: Chi Zhiling Reviewed-by: Sungjong Seo Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/exfat_fs.h | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 7f9d0cfa252b..89ef5368277f 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -552,6 +552,27 @@ int exfat_read_volume_label(struct super_block *sb, int exfat_write_volume_label(struct super_block *sb, struct exfat_uni_name *label); +static inline int exfat_chain_advance(struct super_block *sb, + struct exfat_chain *chain, unsigned int step) +{ + unsigned int clu = chain->dir; + + if (unlikely(chain->size < step)) + return -EIO; + + if (exfat_cluster_walk(sb, &clu, step, chain->flags)) + return -EIO; + + chain->size -= step; + + if (chain->size == 0 && chain->flags == ALLOC_NO_FAT_CHAIN) + chain->dir = EXFAT_EOF_CLUSTER; + else + chain->dir = clu; + + return 0; +} + /* inode.c */ extern const struct inode_operations exfat_file_inode_operations; void exfat_sync_inode(struct inode *inode); From 08cf4a8181b4378a0a50ea370391f30032c4e4ec Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Fri, 3 Apr 2026 16:05:38 +0800 Subject: [PATCH 18/18] exfat: use exfat_chain_advance helper Replace open-coded cluster chain walking logic with exfat_chain_advance() across exfat_readdir, exfat_find_dir_entry, exfat_count_dir_entries, exfat_search_empty_slot and exfat_check_dir_empty. Signed-off-by: Chi Zhiling Reviewed-by: Sungjong Seo Reviewed-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/dir.c | 71 ++++++++++++------------------------------------ fs/exfat/namei.c | 28 +++++-------------- 2 files changed, 25 insertions(+), 74 deletions(-) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index ca5827046a1f..ac008ccaa97d 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -93,25 +93,19 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent clu_offset = EXFAT_DEN_TO_CLU(dentry, sbi); exfat_chain_dup(&clu, &dir); - if (clu.flags == ALLOC_NO_FAT_CHAIN) { - clu.dir += clu_offset; - clu.size -= clu_offset; - } else { + if (clu.flags == ALLOC_FAT_CHAIN) { /* hint_information */ if (clu_offset > 0 && ei->hint_bmap.off != EXFAT_EOF_CLUSTER && ei->hint_bmap.off > 0 && clu_offset >= ei->hint_bmap.off) { clu_offset -= ei->hint_bmap.off; clu.dir = ei->hint_bmap.clu; - } - - while (clu_offset > 0 && clu.dir != EXFAT_EOF_CLUSTER) { - if (exfat_get_next_cluster(sb, &(clu.dir))) - return -EIO; - - clu_offset--; + clu.size -= ei->hint_bmap.off; } } + if (exfat_chain_advance(sb, &clu, clu_offset)) + return -EIO; + while (clu.dir != EXFAT_EOF_CLUSTER && dentry < max_dentries) { i = dentry & (dentries_per_clu - 1); @@ -160,15 +154,8 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent return 0; } - if (clu.flags == ALLOC_NO_FAT_CHAIN) { - if (--clu.size > 0) - clu.dir++; - else - clu.dir = EXFAT_EOF_CLUSTER; - } else { - if (exfat_get_next_cluster(sb, &(clu.dir))) - return -EIO; - } + if (exfat_chain_advance(sb, &clu, 1)) + return -EIO; } out: @@ -1085,19 +1072,12 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, step = DIRENT_STEP_FILE; } - if (clu.flags == ALLOC_NO_FAT_CHAIN) { - if (--clu.size > 0) - clu.dir++; - else - clu.dir = EXFAT_EOF_CLUSTER; - } else { - if (exfat_get_next_cluster(sb, &clu.dir)) - return -EIO; + if (exfat_chain_advance(sb, &clu, 1)) + return -EIO; - /* break if the cluster chain includes a loop */ - if (unlikely(++clu_count > EXFAT_DATA_CLUSTER_COUNT(sbi))) - goto not_found; - } + /* break if the cluster chain includes a loop */ + if (unlikely(++clu_count > EXFAT_DATA_CLUSTER_COUNT(sbi))) + goto not_found; } not_found: @@ -1132,14 +1112,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, if (!((dentry + 1) & (dentries_per_clu - 1))) { int ret = 0; - if (clu.flags == ALLOC_NO_FAT_CHAIN) { - if (--clu.size > 0) - clu.dir++; - else - clu.dir = EXFAT_EOF_CLUSTER; - } else { - ret = exfat_get_next_cluster(sb, &clu.dir); - } + ret = exfat_chain_advance(sb, &clu, 1); if (ret || clu.dir == EXFAT_EOF_CLUSTER) { /* just initialized hint_stat */ @@ -1184,20 +1157,12 @@ int exfat_count_dir_entries(struct super_block *sb, struct exfat_chain *p_dir) count++; } - if (clu.flags == ALLOC_NO_FAT_CHAIN) { - if (--clu.size > 0) - clu.dir++; - else - clu.dir = EXFAT_EOF_CLUSTER; - } else { - if (exfat_get_next_cluster(sb, &(clu.dir))) - return -EIO; - - if (unlikely(++clu_count > sbi->used_clusters)) { - exfat_fs_error(sb, "FAT or bitmap is corrupted"); - return -EIO; - } + if (exfat_chain_advance(sb, &clu, 1)) + return -EIO; + if (unlikely(++clu_count > sbi->used_clusters)) { + exfat_fs_error(sb, "FAT or bitmap is corrupted"); + return -EIO; } } diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index d0ea1ff81c09..2c5636634b4a 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -246,15 +246,8 @@ static int exfat_search_empty_slot(struct super_block *sb, i += ret; while (i >= dentries_per_clu) { - if (clu.flags == ALLOC_NO_FAT_CHAIN) { - if (--clu.size > 0) - clu.dir++; - else - clu.dir = EXFAT_EOF_CLUSTER; - } else { - if (exfat_get_next_cluster(sb, &clu.dir)) - return -EIO; - } + if (exfat_chain_advance(sb, &clu, 1)) + return -EIO; i -= dentries_per_clu; } @@ -925,19 +918,12 @@ static int exfat_check_dir_empty(struct super_block *sb, return -ENOTEMPTY; } - if (clu.flags == ALLOC_NO_FAT_CHAIN) { - if (--clu.size > 0) - clu.dir++; - else - clu.dir = EXFAT_EOF_CLUSTER; - } else { - if (exfat_get_next_cluster(sb, &(clu.dir))) - return -EIO; + if (exfat_chain_advance(sb, &clu, 1)) + return -EIO; - /* break if the cluster chain includes a loop */ - if (unlikely(++clu_count > EXFAT_DATA_CLUSTER_COUNT(sbi))) - break; - } + /* break if the cluster chain includes a loop */ + if (unlikely(++clu_count > EXFAT_DATA_CLUSTER_COUNT(sbi))) + break; } return 0;