<feed xmlns='http://www.w3.org/2005/Atom'>
<title>linux-toradex.git/fs/exfat, branch master</title>
<subtitle>Linux kernel for Apalis and Colibri modules</subtitle>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/'/>
<entry>
<title>exfat: fix reading a directory whose FAT chain is longer than its size</title>
<updated>2026-10-20T23:28:32+00:00</updated>
<author>
<name>Hengyu Liang</name>
<email>hengyul@cs.unc.edu</email>
</author>
<published>2026-10-15T14:49:34+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=8534eb11ac056dc2ca08007cf76d3ae7cbe42684'/>
<id>8534eb11ac056dc2ca08007cf76d3ae7cbe42684</id>
<content type='text'>
Commit 08cf4a8181b4 ("exfat: use exfat_chain_advance helper") made the
directory code walk the clusters of a directory with
exfat_chain_advance().

However, exfat_chain_advance() returns -EIO once it has walked as many
clusters as the size of the directory says. The FAT chain of a directory
can be longer than that, because some cameras stop updating the size,
see commit 1e5654de0f51 ("exfat: handle wrong stream entry size in
exfat_readdir()"). As of now, opening such a directory fails with EIO as
soon as the chain is two clusters longer than the size. Reading a
directory that has a FAT chain after a seek past its end fails with EIO
too.

The issue can be reproduced with an image in which the size of a
directory with 200 files and 7 clusters is lowered to 2 clusters:

    mount -o loop exfat.img /mnt
    ls /mnt/d | wc -l

Before commit 08cf4a8181b4 ("exfat: use exfat_chain_advance helper"),
the result is:

    200

After that commit, ls fails with "Input/output error" and the result is:

    0

This patch will make exfat_chain_advance() follow the FAT to its end
when the directory has a FAT chain, as the code did before, and keep the
size check for a directory without one.

Fixes: 08cf4a8181b4 ("exfat: use exfat_chain_advance helper")
Cc: stable@vger.kernel.org
Signed-off-by: Hengyu Liang &lt;hengyul@cs.unc.edu&gt;
Reviewed-by: Chi Zhiling &lt;chizhiling@kylinos.cn&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Commit 08cf4a8181b4 ("exfat: use exfat_chain_advance helper") made the
directory code walk the clusters of a directory with
exfat_chain_advance().

However, exfat_chain_advance() returns -EIO once it has walked as many
clusters as the size of the directory says. The FAT chain of a directory
can be longer than that, because some cameras stop updating the size,
see commit 1e5654de0f51 ("exfat: handle wrong stream entry size in
exfat_readdir()"). As of now, opening such a directory fails with EIO as
soon as the chain is two clusters longer than the size. Reading a
directory that has a FAT chain after a seek past its end fails with EIO
too.

The issue can be reproduced with an image in which the size of a
directory with 200 files and 7 clusters is lowered to 2 clusters:

    mount -o loop exfat.img /mnt
    ls /mnt/d | wc -l

Before commit 08cf4a8181b4 ("exfat: use exfat_chain_advance helper"),
the result is:

    200

After that commit, ls fails with "Input/output error" and the result is:

    0

This patch will make exfat_chain_advance() follow the FAT to its end
when the directory has a FAT chain, as the code did before, and keep the
size check for a directory without one.

Fixes: 08cf4a8181b4 ("exfat: use exfat_chain_advance helper")
Cc: stable@vger.kernel.org
Signed-off-by: Hengyu Liang &lt;hengyul@cs.unc.edu&gt;
Reviewed-by: Chi Zhiling &lt;chizhiling@kylinos.cn&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: free clusters beyond EOF when a write fails or is short</title>
<updated>2026-10-20T23:28:32+00:00</updated>
<author>
<name>Hengyu Liang</name>
<email>hengyul@cs.unc.edu</email>
</author>
<published>2026-10-12T09:40:46+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=b4c282c0d82e8715ea9ed0b35813a3f365e3f952'/>
<id>b4c282c0d82e8715ea9ed0b35813a3f365e3f952</id>
<content type='text'>
Commit 82a81a7352bc ("exfat: add iomap buffered I/O support") and commit
867b9c96dc83 ("exfat: add iomap direct I/O support") made a write
allocate the clusters for its whole range in one step. They also dropped
exfat_write_failed(), which freed the clusters beyond the end of the
file after a failed or short write.

However, a write can stop early, for example when the process is killed
or when a part of the buffer cannot be read. As of now, the clusters for
the part that was not written stay allocated beyond the end of the file.
exfat_cont_expand() does not expect them, so a later write or truncate
that extends the file fails with ENOSPC. The directory entry records
only the size of the file, so the space can be lost for good once the
volume is unmounted.

The issue can be reproduced on a 64M volume made by mkfs.exfat and
mounted on /mnt, with a write of 1M from a buffer that has only its
first 4K mapped:

    #include &lt;fcntl.h&gt;
    #include &lt;stdio.h&gt;
    #include &lt;sys/mman.h&gt;
    #include &lt;sys/stat.h&gt;
    #include &lt;unistd.h&gt;

    int main(void)
    {
            char *p = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
            int fd = open("/mnt/f", O_CREAT | O_WRONLY, 0644);
            struct stat st;

            munmap(p + 4096, 4096);
            printf("write: %zd\n", write(fd, p, 1 &lt;&lt; 20));
            fstat(fd, &amp;st);
            printf("blocks: %ld\n", (long)st.st_blocks);
            printf("ftruncate: %d\n", ftruncate(fd, 1 &lt;&lt; 16));
            return 0;
    }

Before commit 82a81a7352bc ("exfat: add iomap buffered I/O support"),
the result is:

    write: 4096
    blocks: 8
    ftruncate: 0

After that commit, the result is:

    write: 4096
    blocks: 2048
    ftruncate: -1

This patch will bring back exfat_write_failed() and call it when a write
ends before the end of its range, so that the clusters beyond the end of
the file are freed again. It waits for direct I/O in flight first, as
exfat_setattr() does before it frees clusters.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Fixes: 867b9c96dc83 ("exfat: add iomap direct I/O support")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hengyu Liang &lt;hengyul@cs.unc.edu&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Commit 82a81a7352bc ("exfat: add iomap buffered I/O support") and commit
867b9c96dc83 ("exfat: add iomap direct I/O support") made a write
allocate the clusters for its whole range in one step. They also dropped
exfat_write_failed(), which freed the clusters beyond the end of the
file after a failed or short write.

However, a write can stop early, for example when the process is killed
or when a part of the buffer cannot be read. As of now, the clusters for
the part that was not written stay allocated beyond the end of the file.
exfat_cont_expand() does not expect them, so a later write or truncate
that extends the file fails with ENOSPC. The directory entry records
only the size of the file, so the space can be lost for good once the
volume is unmounted.

The issue can be reproduced on a 64M volume made by mkfs.exfat and
mounted on /mnt, with a write of 1M from a buffer that has only its
first 4K mapped:

    #include &lt;fcntl.h&gt;
    #include &lt;stdio.h&gt;
    #include &lt;sys/mman.h&gt;
    #include &lt;sys/stat.h&gt;
    #include &lt;unistd.h&gt;

    int main(void)
    {
            char *p = mmap(NULL, 8192, PROT_READ | PROT_WRITE,
                           MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
            int fd = open("/mnt/f", O_CREAT | O_WRONLY, 0644);
            struct stat st;

            munmap(p + 4096, 4096);
            printf("write: %zd\n", write(fd, p, 1 &lt;&lt; 20));
            fstat(fd, &amp;st);
            printf("blocks: %ld\n", (long)st.st_blocks);
            printf("ftruncate: %d\n", ftruncate(fd, 1 &lt;&lt; 16));
            return 0;
    }

Before commit 82a81a7352bc ("exfat: add iomap buffered I/O support"),
the result is:

    write: 4096
    blocks: 8
    ftruncate: 0

After that commit, the result is:

    write: 4096
    blocks: 2048
    ftruncate: -1

This patch will bring back exfat_write_failed() and call it when a write
ends before the end of its range, so that the clusters beyond the end of
the file are freed again. It waits for direct I/O in flight first, as
exfat_setattr() does before it frees clusters.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Fixes: 867b9c96dc83 ("exfat: add iomap direct I/O support")
Cc: stable@vger.kernel.org
Assisted-by: LLM
Signed-off-by: Hengyu Liang &lt;hengyul@cs.unc.edu&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: clean stale bdev buffers of newly allocated clusters</title>
<updated>2026-10-20T23:28:32+00:00</updated>
<author>
<name>Nicolas Falesy</name>
<email>nicolas@nicolasfalesy.com</email>
</author>
<published>2026-10-10T19:15:13+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=0550ac7f5f577e34e5fcf2bd7a51965f5c1b0194'/>
<id>0550ac7f5f577e34e5fcf2bd7a51965f5c1b0194</id>
<content type='text'>
Directory entries are read and written through buffer_heads of the
block device. Removing the entries of a directory dirties such a
buffer. If the directory is then removed and its cluster is reused for
file data before writeback, the stale buffer stays dirty in the block
device mapping, and the next flush of the block device (fsync, syncfs
or umount) writes it over the new file data. The file silently reads
back with one sector replaced by the deleted directory entries.

This was harmless while file data went through exfat_get_block(), as
__block_write_begin_int() and do_direct_IO() call clean_bdev_aliases()
for newly allocated blocks. Since data I/O moved to iomap, nothing
drops these buffers any more.

Clean the aliases in exfat_alloc_cluster(), once per contiguous run of
allocated clusters. Doing it at allocation time covers every path that
allocates clusters: buffered and direct writes, and also fallocate()
and an extending truncate(), which allocate clusters before the data is
written, so the later iomap mapping does not see them as new.

Tested on the exfat dev branch in a KVM guest with a reproducer that
removes the entries of a directory, removes the directory and writes a
file over its freed cluster: buffered with and without fsync, O_DIRECT,
after fallocate(), after an extending ftruncate(), and with the freed
cluster in the middle of a fragmented allocation. Without this patch
every run corrupted the file (55/55); with it, none did (0/55). A churn
test that creates, removes, preallocates and truncates directories and
files, then runs fsck.exfat, is clean with and without the patch.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Fixes: 867b9c96dc83 ("exfat: add iomap direct I/O support")
Link: https://lore.kernel.org/all/010001a1222f66ba-0c745f29-d4fe-4862-93ff-1db2055b05fc-000000@email.amazonses.com/
Suggested-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
Assisted-by: Claude-Opus-5.5
Signed-off-by: Nicolas Falesy &lt;nicolas@nicolasfalesy.com&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Directory entries are read and written through buffer_heads of the
block device. Removing the entries of a directory dirties such a
buffer. If the directory is then removed and its cluster is reused for
file data before writeback, the stale buffer stays dirty in the block
device mapping, and the next flush of the block device (fsync, syncfs
or umount) writes it over the new file data. The file silently reads
back with one sector replaced by the deleted directory entries.

This was harmless while file data went through exfat_get_block(), as
__block_write_begin_int() and do_direct_IO() call clean_bdev_aliases()
for newly allocated blocks. Since data I/O moved to iomap, nothing
drops these buffers any more.

Clean the aliases in exfat_alloc_cluster(), once per contiguous run of
allocated clusters. Doing it at allocation time covers every path that
allocates clusters: buffered and direct writes, and also fallocate()
and an extending truncate(), which allocate clusters before the data is
written, so the later iomap mapping does not see them as new.

Tested on the exfat dev branch in a KVM guest with a reproducer that
removes the entries of a directory, removes the directory and writes a
file over its freed cluster: buffered with and without fsync, O_DIRECT,
after fallocate(), after an extending ftruncate(), and with the freed
cluster in the middle of a fragmented allocation. Without this patch
every run corrupted the file (55/55); with it, none did (0/55). A churn
test that creates, removes, preallocates and truncates directories and
files, then runs fsck.exfat, is clean with and without the patch.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Fixes: 867b9c96dc83 ("exfat: add iomap direct I/O support")
Link: https://lore.kernel.org/all/010001a1222f66ba-0c745f29-d4fe-4862-93ff-1db2055b05fc-000000@email.amazonses.com/
Suggested-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
Assisted-by: Claude-Opus-5.5
Signed-off-by: Nicolas Falesy &lt;nicolas@nicolasfalesy.com&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: mark straddling folio RO and zero the post-EOF range</title>
<updated>2026-10-20T23:28:32+00:00</updated>
<author>
<name>Yuezhang Mo</name>
<email>Yuezhang.Mo@sony.com</email>
</author>
<published>2026-10-10T10:12:01+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=1043264c3cfa43b0668f7472a749868c1e21749b'/>
<id>1043264c3cfa43b0668f7472a749868c1e21749b</id>
<content type='text'>
When extending a file across a non-page-aligned EOF, the folio
containing the old EOF must be marked RO so that subsequent mmap
writes trigger -&gt;page_mkwrite() and update valid_size.

The straddling folio remains mapped to the same filesystem block
after the file is extended. As a result, the existing writable PTE
may remain in place, allowing mmap writes to bypass -&gt;page_mkwrite()
and leave valid_size unchanged.

Furthermore, writes to the post-EOF range may occur between
truncate_pagecache() and locking the folio. So the post-EOF range
should be zeroed after marking the straddling folio RO, and using
truncate_pagecache() to zero out the post-EOF range is redundant.

Introduce exfat_pagecache_isize_extended() to mark the straddling
folio RO and zero the post-EOF range after clearing writable mapping.
This ensures that data written beyond the old EOF before the extension
is not exposed as valid file data, and that subsequent mmap writes
trigger -&gt;page_mkwrite() to update valid_size.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Yuezhang Mo &lt;Yuezhang.Mo@sony.com&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
When extending a file across a non-page-aligned EOF, the folio
containing the old EOF must be marked RO so that subsequent mmap
writes trigger -&gt;page_mkwrite() and update valid_size.

The straddling folio remains mapped to the same filesystem block
after the file is extended. As a result, the existing writable PTE
may remain in place, allowing mmap writes to bypass -&gt;page_mkwrite()
and leave valid_size unchanged.

Furthermore, writes to the post-EOF range may occur between
truncate_pagecache() and locking the folio. So the post-EOF range
should be zeroed after marking the straddling folio RO, and using
truncate_pagecache() to zero out the post-EOF range is redundant.

Introduce exfat_pagecache_isize_extended() to mark the straddling
folio RO and zero the post-EOF range after clearing writable mapping.
This ensures that data written beyond the old EOF before the extension
is not exposed as valid file data, and that subsequent mmap writes
trigger -&gt;page_mkwrite() to update valid_size.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Yuezhang Mo &lt;Yuezhang.Mo@sony.com&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: fix a large buffered write failing when the volume is nearly full</title>
<updated>2026-10-20T23:28:31+00:00</updated>
<author>
<name>Hengyu Liang</name>
<email>hengyul@cs.unc.edu</email>
</author>
<published>2026-10-10T04:44:20+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=73cababf93cb371e2e9d06da5261ca6ddd2404b6'/>
<id>73cababf93cb371e2e9d06da5261ca6ddd2404b6</id>
<content type='text'>
Commit 82a81a7352bc ("exfat: add iomap buffered I/O support") made a
buffered write map the whole range of the write in one step, so the
clusters for all of it are allocated at once.

However, exfat_alloc_cluster() returns -ENOSPC when fewer clusters are
free than it is asked for. As of now, a write() that is larger than the
free space writes nothing and fails with ENOSPC, while it used to write
as much as fits and return a short count.

The issue can be reproduced with a 16M image made by mkfs.exfat that is
filled except for 256K:

    mount -o loop /img /mnt
    dd if=/dev/zero of=/mnt/hole bs=256k count=1
    dd if=/dev/zero of=/mnt/fill bs=4k
    rm /mnt/hole
    dd if=/dev/zero of=/mnt/f bs=1M count=1
    wc -c &lt; /mnt/f

Before commit 82a81a7352bc ("exfat: add iomap buffered I/O support"),
the result is:

    262144

After that commit, the result is:

    0

This patch will map a single cluster when the clusters for the whole
range cannot be allocated, so that the write goes on until the volume is
full. Direct I/O is not changed, it still fails without writing as it
did before that commit.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Hengyu Liang &lt;hengyul@cs.unc.edu&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Commit 82a81a7352bc ("exfat: add iomap buffered I/O support") made a
buffered write map the whole range of the write in one step, so the
clusters for all of it are allocated at once.

However, exfat_alloc_cluster() returns -ENOSPC when fewer clusters are
free than it is asked for. As of now, a write() that is larger than the
free space writes nothing and fails with ENOSPC, while it used to write
as much as fits and return a short count.

The issue can be reproduced with a 16M image made by mkfs.exfat that is
filled except for 256K:

    mount -o loop /img /mnt
    dd if=/dev/zero of=/mnt/hole bs=256k count=1
    dd if=/dev/zero of=/mnt/fill bs=4k
    rm /mnt/hole
    dd if=/dev/zero of=/mnt/f bs=1M count=1
    wc -c &lt; /mnt/f

Before commit 82a81a7352bc ("exfat: add iomap buffered I/O support"),
the result is:

    262144

After that commit, the result is:

    0

This patch will map a single cluster when the clusters for the whole
range cannot be allocated, so that the write goes on until the volume is
full. Direct I/O is not changed, it still fails without writing as it
did before that commit.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Hengyu Liang &lt;hengyul@cs.unc.edu&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: zero invalid tail on partial overwrites</title>
<updated>2026-10-20T23:28:31+00:00</updated>
<author>
<name>Namjae Jeon</name>
<email>linkinjeon@kernel.org</email>
</author>
<published>2026-10-06T04:06:39+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=748ad4ba13cd14163ed7262c6ac304c380804167'/>
<id>748ad4ba13cd14163ed7262c6ac304c380804167</id>
<content type='text'>
Buffered overwrite that ends before valid_size in the same filesystem
block reads the entire block without IOMAP_F_ZERO_TAIL. If an existing
cluster contains stale bytes beyond valid_size, the synchronous read
loads them into the page cache and marks the block uptodate. Subsequent
buffered reads can return these bytes while i_size exceeds valid_size.

For example, with 512-byte blocks, valid_size of 1000 and i_size of 2048,
a cold-cache overwrite of [900, 950) exposes stale data in [1000, 1024).
Setting ZERO_TAIL for this write would also erase valid data in
[950, 1000).

Use iomap_truncate_page() to zero only the invalid tail at valid_size
before preparing such buffered overwrites. Call it after dropping s_lock
and before iomap locks the write folio. The inode lock held by buffered
writes serializes valid_size against truncation and page_mkwrite.

Skip direct I/O, fault mappings and new allocations. Writes that reach
valid_size continue to use IOMAP_F_ZERO_TAIL.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
Buffered overwrite that ends before valid_size in the same filesystem
block reads the entire block without IOMAP_F_ZERO_TAIL. If an existing
cluster contains stale bytes beyond valid_size, the synchronous read
loads them into the page cache and marks the block uptodate. Subsequent
buffered reads can return these bytes while i_size exceeds valid_size.

For example, with 512-byte blocks, valid_size of 1000 and i_size of 2048,
a cold-cache overwrite of [900, 950) exposes stale data in [1000, 1024).
Setting ZERO_TAIL for this write would also erase valid data in
[950, 1000).

Use iomap_truncate_page() to zero only the invalid tail at valid_size
before preparing such buffered overwrites. Call it after dropping s_lock
and before iomap locks the write folio. The inode lock held by buffered
writes serializes valid_size against truncation and page_mkwrite.

Skip direct I/O, fault mappings and new allocations. Writes that reach
valid_size continue to use IOMAP_F_ZERO_TAIL.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: drain in-flight DIO before buffered writes</title>
<updated>2026-10-20T23:28:31+00:00</updated>
<author>
<name>Jiale Yao</name>
<email>yaojiale02@163.com</email>
</author>
<published>2026-10-03T09:30:35+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=acea17f0f4c7742118375e96d3f876be34e449a5'/>
<id>acea17f0f4c7742118375e96d3f876be34e449a5</id>
<content type='text'>
An asynchronous direct write can remain in flight after the inode lock is
released. If a buffered operation dirties page cache while the direct
write is still pending, the direct write's post-I/O invalidation can find
the dirty pages, report a page cache invalidation failure, and record -EIO
in the mapping error sequence. A later fsync() therefore returns -EIO.

Commit 15cdefd0c0522f9d5e12d947fa04f4c11649b699 ("ext4: drain
in-flight DIO before buffered write fallback") fixed the same race in
ext4. ExFAT does not drain in-flight DIO before a regular buffered write,
the buffered fallback after iomap_dio_rw() returns -ENOTBLK or a short
write, or the page-cache operations used to extend valid_size.

Wait for in-flight DIO before these paths can dirty page cache.

A reproducer using concurrent AIO direct writes and buffered fallback
triggered the following warning and made a subsequent fsync() return
-EIO:

  Page cache invalidation failure on direct I/O. Possible data corruption
  due to collision with buffered I/O!

Fixes: 867b9c96dc83 ("exfat: add iomap direct I/O support")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20260629113827.4074335-3-libaokun@linux.alibaba.com
Signed-off-by: Jiale Yao &lt;yaojiale02@163.com&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
An asynchronous direct write can remain in flight after the inode lock is
released. If a buffered operation dirties page cache while the direct
write is still pending, the direct write's post-I/O invalidation can find
the dirty pages, report a page cache invalidation failure, and record -EIO
in the mapping error sequence. A later fsync() therefore returns -EIO.

Commit 15cdefd0c0522f9d5e12d947fa04f4c11649b699 ("ext4: drain
in-flight DIO before buffered write fallback") fixed the same race in
ext4. ExFAT does not drain in-flight DIO before a regular buffered write,
the buffered fallback after iomap_dio_rw() returns -ENOTBLK or a short
write, or the page-cache operations used to extend valid_size.

Wait for in-flight DIO before these paths can dirty page cache.

A reproducer using concurrent AIO direct writes and buffered fallback
triggered the following warning and made a subsequent fsync() return
-EIO:

  Page cache invalidation failure on direct I/O. Possible data corruption
  due to collision with buffered I/O!

Fixes: 867b9c96dc83 ("exfat: add iomap direct I/O support")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/r/20260629113827.4074335-3-libaokun@linux.alibaba.com
Signed-off-by: Jiale Yao &lt;yaojiale02@163.com&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: make valid_size and zeroed_size atomic</title>
<updated>2026-10-20T23:28:30+00:00</updated>
<author>
<name>Chi Zhiling</name>
<email>chizhiling@kylinos.cn</email>
</author>
<published>2026-10-04T10:19:33+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=6bb112f5d90a78461648e3bd89093ea299196be5'/>
<id>6bb112f5d90a78461648e3bd89093ea299196be5</id>
<content type='text'>
ei-&gt;valid_size and ei-&gt;zeroed_size are 64-bit fields read from bio
completion context (exfat_iomap_read_end_io()) and from -&gt;iomap_begin
without i_rwsem, while writers update them under i_rwsem.  On 32-bit
these loads can tear, and the "if (x &lt; new) x = new" read-modify-write
can lose an update or regress the value when two writers race.

Convert both to atomic64_t and funnel updates through a cmpxchg-based
monotonic advance helper so the value only moves forward; plain reads use
exfat_get_*(), and the few shrink/init paths (truncate, inode fill,
create) use exfat_set_*().  This is a prerequisite for taking
exfat_page_mkwrite() out from under i_rwsem.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Chi Zhiling &lt;chizhiling@kylinos.cn&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
ei-&gt;valid_size and ei-&gt;zeroed_size are 64-bit fields read from bio
completion context (exfat_iomap_read_end_io()) and from -&gt;iomap_begin
without i_rwsem, while writers update them under i_rwsem.  On 32-bit
these loads can tear, and the "if (x &lt; new) x = new" read-modify-write
can lose an update or regress the value when two writers race.

Convert both to atomic64_t and funnel updates through a cmpxchg-based
monotonic advance helper so the value only moves forward; plain reads use
exfat_get_*(), and the few shrink/init paths (truncate, inode fill,
create) use exfat_set_*().  This is a prerequisite for taking
exfat_page_mkwrite() out from under i_rwsem.

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Chi Zhiling &lt;chizhiling@kylinos.cn&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: dirty all new pages when extending valid_size</title>
<updated>2026-10-20T23:28:30+00:00</updated>
<author>
<name>Chi Zhiling</name>
<email>chizhiling@kylinos.cn</email>
</author>
<published>2026-10-04T10:19:32+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=03ac97285b699954fa7bc24f8b6cdc1830c94d28'/>
<id>03ac97285b699954fa7bc24f8b6cdc1830c94d28</id>
<content type='text'>
In the current code, exfat_zero_new_range() does not mark non-uptodate
pages as dirty, which may cause some blocks within a page to not be
written back.

This commit no longer checks each block in a non-uptodate page
individually. Instead, it reads the entire page back into the cache and
then writes it back as a whole. This allows concurrent writes and zeroing
operations to be synchronized using the folio lock.

Fixes: 1135704ed22f ("exfat: fix valid_size extension over a shared writable mapping")
Cc: stable@vger.kernel.org
Signed-off-by: Chi Zhiling &lt;chizhiling@kylinos.cn&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
In the current code, exfat_zero_new_range() does not mark non-uptodate
pages as dirty, which may cause some blocks within a page to not be
written back.

This commit no longer checks each block in a non-uptodate page
individually. Instead, it reads the entire page back into the cache and
then writes it back as a whole. This allows concurrent writes and zeroing
operations to be synchronized using the folio lock.

Fixes: 1135704ed22f ("exfat: fix valid_size extension over a shared writable mapping")
Cc: stable@vger.kernel.org
Signed-off-by: Chi Zhiling &lt;chizhiling@kylinos.cn&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
<entry>
<title>exfat: hold the invalidate lock while truncating</title>
<updated>2026-10-20T23:28:30+00:00</updated>
<author>
<name>Chi Zhiling</name>
<email>chizhiling@kylinos.cn</email>
</author>
<published>2026-10-04T10:19:31+00:00</published>
<link rel='alternate' type='text/html' href='https://git.toradex.cn/cgit/linux-toradex.git/commit/?id=646df471c37483eb31548d76a1b062775db71d60'/>
<id>646df471c37483eb31548d76a1b062775db71d60</id>
<content type='text'>
truncate_setsize() ends in truncate_inode_pages(), which is documented
as being "Called under (and serialised by) inode-&gt;i_rwsem and
mapping-&gt;invalidate_lock".

Hold invalidate_lock while executing truncate_setsize() to ensure that
truncate and mmap writes are mutually exclusive. This can simplify the
handling in exfat_page_mkwrite().

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Chi Zhiling &lt;chizhiling@kylinos.cn&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</content>
<content type='xhtml'>
<div xmlns='http://www.w3.org/1999/xhtml'>
<pre>
truncate_setsize() ends in truncate_inode_pages(), which is documented
as being "Called under (and serialised by) inode-&gt;i_rwsem and
mapping-&gt;invalidate_lock".

Hold invalidate_lock while executing truncate_setsize() to ensure that
truncate and mmap writes are mutually exclusive. This can simplify the
handling in exfat_page_mkwrite().

Fixes: 82a81a7352bc ("exfat: add iomap buffered I/O support")
Cc: stable@vger.kernel.org
Signed-off-by: Chi Zhiling &lt;chizhiling@kylinos.cn&gt;
Signed-off-by: Namjae Jeon &lt;linkinjeon@kernel.org&gt;
</pre>
</div>
</content>
</entry>
</feed>
