From 1135704ed22f54873eb0498a232611d9eca30dd4 Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Wed, 22 Jul 2026 14:14:11 +0900 Subject: [PATCH 1/9] exfat: fix valid_size extension over a shared writable mapping When a shared writable mapping has its valid_size extended by a buffered write or a page fault, exfat zeroes the page-cache gap below the new valid_size. A store through the mapping can race with this zeroing and be overwritten. Fix this by zeroing the gap lazily. Drop ->map_pages so that every first write fault goes through exfat_page_mkwrite(), which advances valid_size to cover the faulting page. With fault-around enabled, a store could install a writable PTE, skip ->page_mkwrite(), and land past valid_size without advancing it. Extending valid_size one faulting page at a time also leaves never-written pages in a large mapping alone. The gap is filled with block granularity, zeroing only the not-uptodate blocks and preserving blocks that may hold data stored through the mapping. On the buffered-write path the invalidate lock is held and the gap is unmapped before zeroing, so a racing store re-faults and, under the inode lock, completes only after the gap has been zeroed and valid_size covers it. Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support") Co-developed-by: Yuezhang Mo Signed-off-by: Yuezhang Mo Signed-off-by: Namjae Jeon --- fs/exfat/exfat_fs.h | 2 +- fs/exfat/file.c | 180 +++++++++++++++++++++++++++++++++++--------- fs/exfat/iomap.c | 11 ++- 3 files changed, 153 insertions(+), 40 deletions(-) diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 9be50949ce34..1f020b041a3d 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -294,7 +294,7 @@ struct exfat_inode_info { /* on-disk position of directory entry or 0 */ loff_t i_pos; loff_t valid_size; - /* page-aligned size that has been zeroed out for mmap */ + /* block-aligned size zeroed in the page cache (>= valid_size) */ loff_t zeroed_size; /* hash by i_location */ struct hlist_node i_hash_fat; diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 5fc13378d35f..5e9b47ecc614 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "exfat_raw.h" #include "exfat_fs.h" @@ -654,6 +655,104 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync) return blkdev_issue_flush(inode->i_sb->s_bdev); } +/* + * exfat_zero_new_range - zero [start, end) without overwriting uptodate blocks + * + * Uptodate blocks may contain data written through a shared mapping beyond + * valid_size. + */ +static int exfat_zero_new_range(struct inode *inode, loff_t start, loff_t end) +{ + struct address_space *mapping = inode->i_mapping; + unsigned int blocksize = i_blocksize(inode); + loff_t pos = start; + int err; + + while (pos < end) { + loff_t next = min_t(loff_t, + round_down(pos, PAGE_SIZE) + PAGE_SIZE, end); + struct folio *folio; + loff_t bpos; + + folio = filemap_get_folio(mapping, pos >> PAGE_SHIFT); + if (IS_ERR(folio)) { + err = iomap_zero_range(inode, pos, next - pos, NULL, + &exfat_iomap_ops, NULL, NULL); + if (err < 0) + return err; + pos = next; + continue; + } + + if (folio_test_uptodate(folio)) { + folio_lock(folio); + if (folio->mapping == mapping) + folio_mark_dirty(folio); + folio_unlock(folio); + folio_put(folio); + pos = next; + continue; + } + + /* + * Zero not-uptodate block runs. iomap_zero_range() requires an + * unlocked folio, so recheck ->mapping after each call. + */ + folio_lock(folio); + bpos = pos; + while (bpos < next) { + loff_t rstart, rend; + + if (folio->mapping != mapping) { + folio_unlock(folio); + err = iomap_zero_range(inode, bpos, next - bpos, + NULL, &exfat_iomap_ops, NULL, NULL); + if (err < 0) { + folio_put(folio); + return err; + } + folio_lock(folio); + break; + } + + if (iomap_is_partially_uptodate(folio, + offset_in_folio(folio, bpos), blocksize)) { + bpos += blocksize; + continue; + } + + rstart = bpos; + rend = min_t(loff_t, bpos + blocksize, next); + while (rend < next && + !iomap_is_partially_uptodate(folio, + offset_in_folio(folio, rend), blocksize)) + rend = min_t(loff_t, rend + blocksize, next); + + folio_unlock(folio); + err = iomap_zero_range(inode, rstart, rend - rstart, + NULL, &exfat_iomap_ops, NULL, NULL); + if (err < 0) { + folio_put(folio); + return err; + } + folio_lock(folio); + bpos = rend; + } + + /* + * Dirty only a fully uptodate folio. Dirtying a partial folio could + * write uninitialised cache contents over valid on-disk blocks. + */ + if (folio->mapping == mapping && folio_test_uptodate(folio)) + folio_mark_dirty(folio); + folio_unlock(folio); + folio_put(folio); + pos = next; + } + + return 0; +} + static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size) { struct exfat_inode_info *ei = EXFAT_I(inode); @@ -661,18 +760,41 @@ static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size) int ret = 0; if (old_valid_size < new_valid_size) { + /* Do not re-zero blocks already covered by zeroed_size. */ + loff_t gap_start = max(old_valid_size, ei->zeroed_size); + if (i_size_read(inode) < new_valid_size) { - i_size_write(inode, new_valid_size); - mark_inode_dirty(inode); + /* + * Allocate clusters before increasing i_size. The gap + * may already be zeroed, so the subsequent zeroing + * can be skipped. + */ + ret = exfat_cont_expand(inode, new_valid_size); + if (ret) + return ret; } - ret = iomap_zero_range(inode, old_valid_size, - new_valid_size - old_valid_size, NULL, - &exfat_write_iomap_ops, NULL, NULL); + /* + * Revoke writable PTEs while zeroing the gap. A racing mmap + * store re-faults through exfat_page_mkwrite() after valid_size + * is updated. + */ + filemap_invalidate_lock(inode->i_mapping); + if (gap_start < new_valid_size) + unmap_mapping_range(inode->i_mapping, gap_start, + new_valid_size - gap_start, 0); + ret = exfat_zero_new_range(inode, gap_start, new_valid_size); + filemap_invalidate_unlock(inode->i_mapping); if (ret) { truncate_setsize(inode, old_valid_size); exfat_truncate(inode); + return ret; } + + ei->valid_size = new_valid_size; + if (ei->zeroed_size < round_up(new_valid_size, i_blocksize(inode))) + ei->zeroed_size = round_up(new_valid_size, i_blocksize(inode)); + mark_inode_dirty(inode); } return ret; @@ -825,39 +947,39 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf) struct inode *inode = file_inode(vmf->vma->vm_file); struct exfat_inode_info *ei = EXFAT_I(inode); vm_fault_t ret; - loff_t new_valid_size, mmap_valid_size; + loff_t new_valid_size, mmap_valid_size, fault_page_start; if (!inode_trylock(inode)) return VM_FAULT_RETRY; mmap_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT; + fault_page_start = ((loff_t)vmf->pgoff) << PAGE_SHIFT; new_valid_size = min(mmap_valid_size, i_size_read(inode)); if (ei->valid_size < new_valid_size) { - if (ei->zeroed_size < mmap_valid_size) { + if (ei->zeroed_size < fault_page_start) { int err; /* - * Only zero the range that hasn't been zeroed yet for - * this mmap write path. zeroed_size tracks the largest - * page-aligned offset that has already been zeroed. - * - * This prevents unnecessarily zeroing out the entire - * tail page on every page fault when userspace writes - * data byte-by-byte through mmap (after a small - * fallocate). It fixes data corruption in the tail page - * while preserving the existing valid_size semantics. + * Zero only the gap below the faulting page. The read + * fault populated its folio and iomap_page_mkwrite() + * will dirty it. */ - err = iomap_zero_range(inode, ei->zeroed_size, - mmap_valid_size - ei->zeroed_size, NULL, - &exfat_iomap_ops, NULL, NULL); + err = exfat_zero_new_range(inode, ei->zeroed_size, + fault_page_start); if (err < 0) { inode_unlock(inode); return vmf_fs_error(err); } - ei->zeroed_size = mmap_valid_size; } + /* + * Track zeroed_size by block, not page, because writeback stops + * at i_size recording blocks wholly beyond it could skip a + * later required zeroing. + */ + if (ei->zeroed_size < round_up(new_valid_size, i_blocksize(inode))) + ei->zeroed_size = round_up(new_valid_size, i_blocksize(inode)); ei->valid_size = new_valid_size; mark_inode_dirty(inode); } @@ -866,7 +988,7 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf) file_update_time(vmf->vma->vm_file); filemap_invalidate_lock_shared(inode->i_mapping); - ret = iomap_page_mkwrite(vmf, &exfat_write_iomap_ops, NULL); + ret = iomap_page_mkwrite(vmf, &exfat_iomap_ops, NULL); filemap_invalidate_unlock_shared(inode->i_mapping); sb_end_pagefault(inode->i_sb); inode_unlock(inode); @@ -876,7 +998,6 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf) static const struct vm_operations_struct exfat_file_vm_ops = { .fault = filemap_fault, - .map_pages = filemap_map_pages, .page_mkwrite = exfat_page_mkwrite, }; @@ -887,21 +1008,6 @@ static int exfat_file_mmap_prepare(struct vm_area_desc *desc) if (unlikely(exfat_forced_shutdown(file_inode(desc->file)->i_sb))) return -EIO; - if (vma_desc_test_all(desc, VMA_SHARED_BIT, VMA_MAYWRITE_BIT)) { - struct inode *inode = file_inode(file); - loff_t from, to; - int err; - - from = ((loff_t)desc->pgoff << PAGE_SHIFT); - to = min_t(loff_t, i_size_read(inode), - from + vma_desc_size(desc)); - if (EXFAT_I(inode)->valid_size < to) { - err = exfat_extend_valid_size(inode, to); - if (err) - return err; - } - } - file_accessed(file); desc->vm_ops = &exfat_file_vm_ops; return 0; diff --git a/fs/exfat/iomap.c b/fs/exfat/iomap.c index 1aac38e63fe6..0c805bf6676a 100644 --- a/fs/exfat/iomap.c +++ b/fs/exfat/iomap.c @@ -175,11 +175,18 @@ static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length, if (ei->valid_size < end) { ei->valid_size = end; - if (ei->zeroed_size < end) - ei->zeroed_size = end; dirtied = true; } + /* + * IOMAP_F_ZERO_TAIL zeroes the remainder of the last block. Track that + * block as zeroed so later valid_size extensions do not zero it again. + */ + if (iomap->flags & IOMAP_F_ZERO_TAIL) + end = round_up(end, i_blocksize(inode)); + if (ei->zeroed_size < end) + ei->zeroed_size = end; + if (dirtied || iomap->flags & IOMAP_F_SIZE_CHANGED) mark_inode_dirty(inode); From 4b1fac2bb1bcb2416f5bee66428312d5f9eb8dfb Mon Sep 17 00:00:00 2001 From: Namjae Jeon Date: Tue, 14 Jul 2026 18:44:27 +0900 Subject: [PATCH 2/9] MAINTAINERS: update mailing list address for exfat Add the newly created official mailing list for the exfat file system. This mailing list will be shared and used for both the kernel driver and the exfatprogs utility project. Signed-off-by: Namjae Jeon --- MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MAINTAINERS b/MAINTAINERS index d95d3ef77773..439b4de909af 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -9725,7 +9725,7 @@ EXFAT FILE SYSTEM M: Namjae Jeon M: Sungjong Seo R: Yuezhang Mo -L: linux-fsdevel@vger.kernel.org +L: exfat@lists.linux.dev S: Maintained T: git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat.git F: fs/exfat/ From 896fcc93574fd511ee04a4110e8ea59d6d8d43d7 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Mon, 27 Jul 2026 17:12:14 +0800 Subject: [PATCH 3/9] exfat: write moved entry before removing source exfat_move_file() removes the old directory entry before the new entry has been written. If writing the new entry fails, rename returns an error after the source entry has already been marked deleted. Write the new entry first, then remove the old entry and update the cached inode location. This keeps the source entry intact if creating the destination entry fails. Signed-off-by: Yichong Chen Signed-off-by: Namjae Jeon --- fs/exfat/namei.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index b7d5e44ad38e..8725dfc6467c 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -1116,11 +1116,6 @@ static int exfat_move_file(struct inode *parent_inode, exfat_init_ext_entry(&new_es, num_new_entries, p_uniname, &mov_es, num_extra_entries); - exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false); - - ei->dir = newdir; - ei->entry = newentry; - ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode)); if (ret) { /* Best-effort delete to avoid duplicate entries */ @@ -1134,6 +1129,11 @@ static int exfat_move_file(struct inode *parent_inode, goto put_mov_es; } + exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false); + + ei->dir = newdir; + ei->entry = newentry; + return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode)); put_mov_es: From ce12a39bed483008683438962b3a605374e6f803 Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Tue, 28 Jul 2026 12:29:23 +0800 Subject: [PATCH 4/9] exfat: free new directory cluster if zeroing fails exfat_alloc_new_dir() allocates a cluster for a new directory before zeroing it. If exfat_zeroed_cluster() fails, the function returns the error but leaves the allocated cluster in use. Free the newly allocated cluster before returning the zeroing error. Signed-off-by: Yichong Chen Signed-off-by: Namjae Jeon --- fs/exfat/dir.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index 4fd4ec52b8c0..8d2ae6e33cbf 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -299,7 +299,13 @@ int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu) if (ret) return ret; - return exfat_zeroed_cluster(inode, clu->dir); + ret = exfat_zeroed_cluster(inode, clu->dir); + if (ret) { + exfat_free_cluster(inode, clu); + return ret; + } + + return 0; } int exfat_calc_num_entries(struct exfat_uni_name *p_uniname) From 0901a71e8cff5c60dd464529cba2ed30a2ab0a3f Mon Sep 17 00:00:00 2001 From: Yichong Chen Date: Wed, 29 Jul 2026 10:25:43 +0800 Subject: [PATCH 5/9] exfat: clean up new entry on add entry failure exfat_add_entry() initializes a new directory entry set before writing it with exfat_put_dentry_set(). If the write fails, mkdir/create returns an error but a partially written entry may be left behind. For non-zero-size directories, the failure also happens after a cluster has been allocated for the new directory. Clean up the new entry best-effort, and free the newly allocated directory cluster only when the cleanup writeback succeeds. This avoids freeing a cluster that may still be referenced by an on-disk entry if the cleanup fails. Signed-off-by: Yichong Chen Signed-off-by: Namjae Jeon --- fs/exfat/namei.c | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/fs/exfat/namei.c b/fs/exfat/namei.c index 8725dfc6467c..f26f987a34cf 100644 --- a/fs/exfat/namei.c +++ b/fs/exfat/namei.c @@ -471,6 +471,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, struct exfat_entry_set_cache es; int clu_size = 0; unsigned int start_clu = EXFAT_FREE_CLUSTER; + bool dir_allocated = false; ret = exfat_resolve_path(inode, path, &uniname); if (ret) @@ -497,6 +498,7 @@ static int exfat_add_entry(struct inode *inode, const char *path, } start_clu = clu.dir; clu_size = sbi->cluster_size; + dir_allocated = true; } /* update the directory entry */ @@ -507,8 +509,21 @@ static int exfat_add_entry(struct inode *inode, const char *path, exfat_init_ext_entry(&es, num_entries, &uniname, NULL, 0); ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); - if (ret) + if (ret) { + int cleanup_ret; + + cleanup_ret = exfat_get_dentry_set(&es, sb, &info->dir, + dentry, ES_ALL_ENTRIES); + if (!cleanup_ret) { + exfat_remove_entries(inode, &es, ES_IDX_FILE, false); + cleanup_ret = exfat_put_dentry_set(&es, + IS_DIRSYNC(inode)); + } + + if (!cleanup_ret && dir_allocated) + exfat_free_cluster(inode, &clu); goto out; + } info->entry = dentry; info->flags = ALLOC_NO_FAT_CHAIN; From 0eb24ad05a6a31a3b25c091820ac5cd8ee5e5ae8 Mon Sep 17 00:00:00 2001 From: Yang Wen Date: Tue, 28 Jul 2026 23:07:04 +0800 Subject: [PATCH 6/9] exfat: fix overflow in cluster-to-dentry conversion The cluster-to-dentry calculation is performed as u32 and may overflow and wrap around on large volumes. This can result in an incorrect max_dentries value, causing readdir to stop early and omit directory entries. Cast nr_clusters to u64 before shifting to avoid the overflow. Signed-off-by: Yang Wen Signed-off-by: Namjae Jeon --- fs/exfat/dir.c | 2 +- fs/exfat/exfat_fs.h | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/fs/exfat/dir.c b/fs/exfat/dir.c index 8d2ae6e33cbf..fe73b1380c5d 100644 --- a/fs/exfat/dir.c +++ b/fs/exfat/dir.c @@ -87,7 +87,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent exfat_bytes_to_cluster(sbi, i_size_read(inode)), ei->flags); dentries_per_clu = sbi->dentries_per_clu; - max_dentries = min(MAX_EXFAT_DENTRIES, + max_dentries = (unsigned int)min_t(u64, MAX_EXFAT_DENTRIES, exfat_cluster_to_dentries(sbi, sbi->num_clusters)); clu_offset = exfat_dentries_to_cluster(sbi, dentry); diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index 1f020b041a3d..fa30ab9d8564 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -487,10 +487,10 @@ static inline u32 exfat_dentries_to_bytes(u32 dentry) /* * helpers for cluster size to dentry size conversion. */ -static inline u32 exfat_cluster_to_dentries(struct exfat_sb_info *sbi, +static inline u64 exfat_cluster_to_dentries(struct exfat_sb_info *sbi, u32 nr_clusters) { - return nr_clusters << (sbi->cluster_size_bits - DENTRY_SIZE_BITS); + return (u64)nr_clusters << (sbi->cluster_size_bits - DENTRY_SIZE_BITS); } static inline u32 exfat_dentries_to_cluster(struct exfat_sb_info *sbi, From 27af3392196ddfa4c212f4c883b9964a6dcd5f95 Mon Sep 17 00:00:00 2001 From: Yang Wen Date: Mon, 10 Aug 2026 22:58:14 +0800 Subject: [PATCH 7/9] exfat: fix truncated volume labels returned by FS_IOC_GETFSLABEL exfat_ioctl_get_volume_label() passes uniname.name_len to exfat_utf16_to_nls() as the output buffer length. However, name_len is the number of UTF-16 code units, while exfat_utf16_to_nls() expects the buffer size in bytes. As a result, volume labels that expand during charset conversion are truncated.The destination buffer is FSLABEL_MAX bytes long, so pass its actual size to the conversion helper. Signed-off-by: Yang Wen Signed-off-by: Namjae Jeon --- fs/exfat/file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 5e9b47ecc614..1c1524caaaf7 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -566,7 +566,7 @@ static int exfat_ioctl_get_volume_label(struct super_block *sb, unsigned long ar if (ret < 0) return ret; - ret = exfat_utf16_to_nls(sb, &uniname, label, uniname.name_len); + ret = exfat_utf16_to_nls(sb, &uniname, label, sizeof(label)); if (ret < 0) return ret; From 2de727471b3b13409466a4af4f8824a86073bf4b Mon Sep 17 00:00:00 2001 From: Yang Wen Date: Thu, 13 Aug 2026 22:29:19 +0800 Subject: [PATCH 8/9] exfat: keep FITRIM within the requested range exfat_find_free_bitmap() searches the entire allocation bitmap and may wrap around to its beginning. exfat_trim_fs() does not verify that the returned cluster is still within the requested FITRIM range. As a result, a partial FITRIM operation may discard free clusters outside the user-specified range and report a trimmed length larger than the requested length. Validate each returned cluster against the requested range and stop the search when it wraps around or passes the range end. Signed-off-by: Yang Wen Signed-off-by: Namjae Jeon --- fs/exfat/balloc.c | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/fs/exfat/balloc.c b/fs/exfat/balloc.c index e66ebf899778..c0ddd522c1e1 100644 --- a/fs/exfat/balloc.c +++ b/fs/exfat/balloc.c @@ -340,14 +340,27 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range) mutex_lock(&sbi->bitmap_lock); trim_begin = trim_end = exfat_find_free_bitmap(sb, clu_start); - if (trim_begin == EXFAT_EOF_CLUSTER) + /* + * exfat_find_free_bitmap() may wrap around to the beginning of + * the bitmap. Reject a cluster outside the requested range. + */ + if (trim_begin == EXFAT_EOF_CLUSTER || + trim_begin < clu_start || trim_begin > clu_end) goto unlock; - next_free_clu = exfat_find_free_bitmap(sb, trim_end + 1); - if (next_free_clu == EXFAT_EOF_CLUSTER) - goto unlock; + for (;;) { + if (trim_end >= clu_end) + break; + + next_free_clu = exfat_find_free_bitmap(sb, trim_end + 1); + /* + * Stop if the search wrapped around or moved beyond the requested + * FITRIM range. + */ + if (next_free_clu == EXFAT_EOF_CLUSTER || + next_free_clu <= trim_end || next_free_clu > clu_end) + break; - do { if (next_free_clu == trim_end + 1) { /* extend trim range for continuous free cluster */ trim_end++; @@ -368,17 +381,11 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range) trim_begin = trim_end = next_free_clu; } - if (next_free_clu >= clu_end) - break; - if (fatal_signal_pending(current)) { err = -ERESTARTSYS; goto unlock; } - - next_free_clu = exfat_find_free_bitmap(sb, next_free_clu + 1); - } while (next_free_clu != EXFAT_EOF_CLUSTER && - next_free_clu > trim_end); + } /* try to trim remainder */ count = trim_end - trim_begin + 1; From 490529f668016c401d3da688104d15d3fc2c4fca Mon Sep 17 00:00:00 2001 From: Chi Zhiling Date: Mon, 17 Aug 2026 14:20:33 +0800 Subject: [PATCH 9/9] exfat: replace truncate_lock with inode_lock Remove the per-inode truncate_lock and rely on inode_lock instead. exfat_setattr() truncates under inode_lock (held exclusively by the VFS callers), and exfat_aop_bmap() now takes inode_lock shared to exclude a concurrent truncate, providing the same mutual exclusion with a single lock. Signed-off-by: Chi Zhiling Signed-off-by: Namjae Jeon --- fs/exfat/exfat_fs.h | 2 -- fs/exfat/file.c | 2 -- fs/exfat/inode.c | 5 ++--- fs/exfat/super.c | 1 - 4 files changed, 2 insertions(+), 8 deletions(-) diff --git a/fs/exfat/exfat_fs.h b/fs/exfat/exfat_fs.h index fa30ab9d8564..a9131fe03302 100644 --- a/fs/exfat/exfat_fs.h +++ b/fs/exfat/exfat_fs.h @@ -298,8 +298,6 @@ struct exfat_inode_info { loff_t zeroed_size; /* hash by i_location */ struct hlist_node i_hash_fat; - /* protect bmap against truncate */ - struct rw_semaphore truncate_lock; struct inode vfs_inode; /* File creation time */ struct timespec64 i_crtime; diff --git a/fs/exfat/file.c b/fs/exfat/file.c index 1c1524caaaf7..a2a9ee1a2004 100644 --- a/fs/exfat/file.c +++ b/fs/exfat/file.c @@ -413,7 +413,6 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, * about to be freed. */ inode_dio_wait(inode); - down_write(&EXFAT_I(inode)->truncate_lock); truncate_setsize(inode, attr->ia_size); /* @@ -421,7 +420,6 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, * is already written by it, so mark_inode_dirty() is unneeded. */ exfat_truncate(inode); - up_write(&EXFAT_I(inode)->truncate_lock); } else mark_inode_dirty(inode); diff --git a/fs/exfat/inode.c b/fs/exfat/inode.c index 89826aea5e1e..ccd13630187e 100644 --- a/fs/exfat/inode.c +++ b/fs/exfat/inode.c @@ -291,10 +291,9 @@ static sector_t exfat_aop_bmap(struct address_space *mapping, sector_t block) { sector_t blocknr; - /* exfat_get_cluster() assumes the requested blocknr isn't truncated. */ - down_read(&EXFAT_I(mapping->host)->truncate_lock); + inode_lock_shared(mapping->host); blocknr = iomap_bmap(mapping, block, &exfat_iomap_ops); - up_read(&EXFAT_I(mapping->host)->truncate_lock); + inode_unlock_shared(mapping->host); return blocknr; } diff --git a/fs/exfat/super.c b/fs/exfat/super.c index 388db271c6bf..a9ea36ba2693 100644 --- a/fs/exfat/super.c +++ b/fs/exfat/super.c @@ -195,7 +195,6 @@ static struct inode *exfat_alloc_inode(struct super_block *sb) if (!ei) return NULL; - init_rwsem(&ei->truncate_lock); return &ei->vfs_inode; }