- Use the recalculated append position when advancing valid_size, dirty

all pages involved in valid-data extension, and zero invalid tails on
   partial overwrites. Write-protect the folio spanning the old EOF and
   zero its post-EOF range when extending a file, ensuring subsequent mmap
   writes advance valid_size.
 
 - Make valid_size and zeroed_size atomic to prevent torn reads and lost
   updates. Hold the invalidate lock during truncation, and drain in-flight
   direct I/O before buffered writes, buffered fallback, and page-cache
   operations that extend valid_size.
 
 - Fall back to single-cluster allocation when a buffered write cannot
   allocate its entire range, allowing writes to consume the remaining
   free space and return a short count.
 
 - Free clusters allocated beyond EOF after failed or short writes to
   prevent leaked space and spurious ENOSPC errors on later writes or
   file extensions.
 
 - Clean block-device buffer aliases when allocating clusters so delayed
   directory metadata writeback cannot overwrite file data after cluster
   reuse.
 
 - Update used_clusters as each cluster is allocated so partial-allocation
   cleanup keeps free-space accounting consistent. Acquire bitmap_lock
   before checking available space to serialize the check with allocation.
 
 - Use swap-specific iomap operations to report allocated extents beyond
   valid_size as unwritten during swap activation, retaining byte-accurate
   SEEK_HOLE and SEEK_DATA reporting in regular I/O.
 
 - Check allocation flags, vendor GUIDs, cluster numbers, data lengths,
   and contiguous extents in Vendor Allocation directory entries. Read
   complete entry sets during lookup so malformed trailing entries cannot
   bypass validation.
 
 - Follow directory FAT chains to their actual end when the recorded size
   is too small, supporting devices that leave directory sizes stale.
   Handle reads after seeking past the directory end.
 
 - Clear the volume dirty flag during remount only when switching to
   read-only without SB_FORCE. Update percent_in_use when changing volume
   flags, reporting unknown usage while the volume is dirty or persistent
   flags prevent marking it clean.
 
 - Replace runtime division by powers of two with bit operations.
 
 - Document supported mount options, and exfatprogs, and add
   the documentation path to MAINTAINERS.
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmra8/8WHGxpbmtpbmpl
 b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCP26D/918CGh5K97YrakCEnEkpIW4Rms
 uZk0Q7BW85lGojBq0KPtxjUFcrQa4MnGkt04BvvMFk5OfHZnBjDxIYl3P3ccZ5rZ
 ONDmC9jeQ5BcjOXWmHSwEiL/8if6MypfnAaSQHtlM7jtS9HFCRO3QsttfT4WBrBp
 Ju5p5+1kN/8mYcQc+0rpNnv/O0BdBezzv7BgeIjGfpjBHB2/QJaEqzw5MxfFkDCr
 nB4tlpDVmLvmH34hS2JdAJoXQVnVmauCxIVxx5Rjd/SV7VptK4k8E3SA2aTPALdk
 lFbg9ShDYjLHNZ2mWUwDKwjwHs2eoc/tiTbmnxS1a7xEYd5p5IEAlPPrXnvg8z8z
 iLyXpCN9WokRUtI0AE56AS0qa+ozRFvRv3aL7ZKgjNGo+5eW45pM/gQT8Pwo9f1t
 XJDbfxpy7ZwC/zg+ipO6cy30Y26aEHuGkSkDrOVKG9lplLrmp/F1mZH80APwQ/8G
 EftzkZORs+Vr+Z/I88EJq0bHrmddISyOv6ujbAZ0h0NbOET6etxvZmh8ZH6ws98R
 DDFKPlJQxRwZOk0fbAbOx+fhTRqKBsy3Pz/BJnbfg0VrRiO9F02YIpc9rNV8Ip+h
 B661abgORVwjvVTGbMwNvmHT49bQkw9pykliJ5b5m03ar4wa+RXQVjnnDypz+wzZ
 jeeNzt4cdkY1Gz0THQ==
 =QzbL
 -----END PGP SIGNATURE-----

Merge tag 'exfat-for-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat

Pull exfat updates from Namjae Jeon:

 - Use the recalculated append position when advancing valid_size, dirty
   all pages involved in valid-data extension, and zero invalid tails on
   partial overwrites. Write-protect the folio spanning the old EOF and
   zero its post-EOF range when extending a file, ensuring subsequent
   mmap writes advance valid_size.

 - Make valid_size and zeroed_size atomic to prevent torn reads and lost
   updates. Hold the invalidate lock during truncation, and drain
   in-flight direct I/O before buffered writes, buffered fallback, and
   page-cache operations that extend valid_size.

 - Fall back to single-cluster allocation when a buffered write cannot
   allocate its entire range, allowing writes to consume the remaining
   free space and return a short count.

 - Free clusters allocated beyond EOF after failed or short writes to
   prevent leaked space and spurious ENOSPC errors on later writes or
   file extensions.

 - Clean block-device buffer aliases when allocating clusters so delayed
   directory metadata writeback cannot overwrite file data after cluster
   reuse.

 - Update used_clusters as each cluster is allocated so
   partial-allocation cleanup keeps free-space accounting consistent.
   Acquire bitmap_lock before checking available space to serialize the
   check with allocation.

 - Use swap-specific iomap operations to report allocated extents beyond
   valid_size as unwritten during swap activation, retaining
   byte-accurate SEEK_HOLE and SEEK_DATA reporting in regular I/O.

 - Check allocation flags, vendor GUIDs, cluster numbers, data lengths,
   and contiguous extents in Vendor Allocation directory entries. Read
   complete entry sets during lookup so malformed trailing entries
   cannot bypass validation.

 - Follow directory FAT chains to their actual end when the recorded
   size is too small, supporting devices that leave directory sizes
   stale. Handle reads after seeking past the directory end.

 - Clear the volume dirty flag during remount only when switching to
   read-only without SB_FORCE. Update percent_in_use when changing
   volume flags, reporting unknown usage while the volume is dirty or
   persistent flags prevent marking it clean.

 - Replace runtime division by powers of two with bit operations.

 - Document supported mount options, and exfatprogs, and add the
   documentation path to MAINTAINERS.

* tag 'exfat-for-7.4-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat:
  exfat: fix reading a directory whose FAT chain is longer than its size
  exfat: free clusters beyond EOF when a write fails or is short
  exfat: clean stale bdev buffers of newly allocated clusters
  exfat: mark straddling folio RO and zero the post-EOF range
  exfat: fix a large buffered write failing when the volume is nearly full
  exfat: zero invalid tail on partial overwrites
  exfat: drain in-flight DIO before buffered writes
  exfat: make valid_size and zeroed_size atomic
  exfat: dirty all new pages when extending valid_size
  exfat: hold the invalidate lock while truncating
  exfat: advance valid_size to EOF for append writes
  MAINTAINERS: add exFAT documentation
  exfat: remove runtime integer division
  exfat: update percent_in_use field when updating volume flags
  exfat: clear the volume dirty flag only when remounting read-only
  exfat: doc: add documentation
  exfat: validate vendor allocation directory entries
  exfat: take bitmap_lock at the start of exfat_alloc_cluster()
  exfat: fix used_clusters accounting during cluster allocation
  exfat: map allocated extents for swap activation
This commit is contained in:
Linus Torvalds 2026-10-25 15:54:05 -10:00
commit 42023dd0a2
11 changed files with 534 additions and 168 deletions

View File

@ -0,0 +1,117 @@
.. SPDX-License-Identifier: GPL-2.0
==================================
The Linux exFAT filesystem driver
==================================
.. Table of contents
- Overview
- Utilities support
- Supported mount options
Overview
========
exFAT is a filesystem designed for removable storage and other devices that
need to store large files. The Linux exFAT filesystem driver provides read
and write support for exFAT volumes.
To mount an exFAT volume, use the ``exfat`` filesystem type::
mount -t exfat /dev/sdX1 /mnt
Utilities support
=================
The exfatprogs project provides userspace utilities for creating, checking,
repairing, inspecting, and tuning exFAT filesystems. Use exfatprogs when
creating or checking an exFAT filesystem. For example, use ``mkfs.exfat``
to create a filesystem and ``fsck.exfat`` to check or repair one.
The project is available at:
https://github.com/exfatprogs/exfatprogs
Supported mount options
=======================
The exFAT driver supports the following mount options:
======================= ====================================================
uid=
gid= Set the owner and group of all files and
directories. The default is the uid and gid of
the process mounting the filesystem.
umask= Set the permission mask for files and directories.
The default is the umask of the process mounting
the filesystem.
dmask= Set the permission mask for directories.
fmask= Set the permission mask for files.
allow_utime= Control the permission check for changing file
timestamps. Only permission bits 0022 are used.
Permission bit 0020 allows members of the file's
group to change timestamps, and permission bit 0002
allows other users to change timestamps. The
default is derived from dmask (``~dmask & 0022``).
iocharset=name Character set used to convert between user-visible
filenames and the UTF-16 character encoding used by
exFAT. The default is
CONFIG_EXFAT_DEFAULT_IOCHARSET, which is ``utf8``
unless changed at kernel configuration time. Use
``iocharset=utf8`` for UTF-8 filename handling.
errors= Specify exFAT behavior on filesystem errors. The
value must be ``panic``, ``continue``, or
``remount-ro``. These respectively panic, continue
without changing the filesystem, or remount the
filesystem read-only. The default is
``remount-ro``.
discard Issue discard/TRIM requests to the block device
when clusters are freed. This is disabled by
default. ``nodiscard`` disables it explicitly.
keep_last_dots Keep trailing periods in path components during
lookup. Without this option, trailing periods are
stripped. Existing entries with trailing periods
can be accessed when this option is enabled, but
creating new entries with trailing periods is
rejected.
sys_tz Use the system timezone as the UTC offset when an
exFAT timestamp does not contain a valid timezone
offset. This takes precedence over time_offset.
time_offset=minutes Set the UTC offset, in minutes, used when an exFAT
timestamp does not contain a valid timezone offset.
Values from -1440 to 1440 are accepted. The default
is 0. This option is ignored when sys_tz is set.
zero_size_dir Create directories with zero size and without
allocating a cluster. This is disabled by default;
the default behavior allocates a cluster for a new
directory. ``nozero_size_dir`` disables it
explicitly.
======================= ====================================================
Deprecated mount options
------------------------
The following options are accepted for compatibility but should not be used:
``utf8``
Deprecated. Use ``iocharset=utf8`` instead.
``debug``, ``namecase=``, ``codepage=``
Deprecated and ignored by the exFAT driver.

View File

@ -86,6 +86,7 @@ Documentation for filesystem implementations.
ecryptfs
efivarfs
erofs
exfat
ext2
ext3
ext4/index

View File

@ -10031,6 +10031,7 @@ R: Yuezhang Mo <yuezhang.mo@sony.com>
L: exfat@lists.linux.dev
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat.git
F: Documentation/filesystems/exfat.rst
F: fs/exfat/
EXPRESSWIRE PROTOCOL LIBRARY

View File

@ -678,11 +678,54 @@ enum exfat_validate_dentry_mode {
ES_MODE_GET_BENIGN_SEC_ENTRY,
};
static bool exfat_validate_entry(unsigned int type,
enum exfat_validate_dentry_mode *mode)
static bool exfat_validate_vendor_alloc(struct super_block *sb,
struct exfat_dentry *ep)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
u8 flags = ep->dentry.vendor_alloc.flags;
u32 start_clu = le32_to_cpu(ep->dentry.vendor_alloc.start_clu);
u64 size = le64_to_cpu(ep->dentry.vendor_alloc.size);
u64 max_size = exfat_cluster_to_bytes(sbi,
(u64)EXFAT_DATA_CLUSTER_COUNT(sbi));
u64 num_clusters;
/* AllocationPossible is required for Vendor Allocation entries. */
if (!(flags & ALLOC_POSSIBLE))
return false;
/* The null GUID does not identify a valid vendor allocation. */
if (!memchr_inv(ep->dentry.vendor_alloc.vendor_guid, 0,
sizeof(ep->dentry.vendor_alloc.vendor_guid)))
return false;
if (!start_clu)
return !size && !(flags & (ALLOC_NO_FAT_CHAIN ^ ALLOC_FAT_CHAIN));
if (!is_valid_cluster(sbi, start_clu) || size > max_size)
return false;
if ((flags & ALLOC_NO_FAT_CHAIN) == ALLOC_NO_FAT_CHAIN) {
if (!size)
return false;
num_clusters = DIV_ROUND_UP_ULL(size, sbi->cluster_size);
if (num_clusters > sbi->num_clusters - start_clu)
return false;
}
return true;
}
static bool exfat_validate_entry(struct super_block *sb,
struct exfat_dentry *ep, enum exfat_validate_dentry_mode *mode)
{
unsigned int type = exfat_get_entry_type(ep);
if (type == TYPE_UNUSED || type == TYPE_DELETED)
return false;
if (type == TYPE_VENDOR_ALLOC &&
!exfat_validate_vendor_alloc(sb, ep))
return false;
switch (*mode) {
case ES_MODE_GET_FILE_ENTRY:
@ -836,7 +879,7 @@ int exfat_get_dentry_set(struct exfat_entry_set_cache *es,
/* validate cached dentries */
for (i = ES_IDX_STREAM; i < es->num_entries; i++) {
ep = exfat_get_dentry_cached(es, i);
if (!exfat_validate_entry(exfat_get_entry_type(ep), &mode))
if (!exfat_validate_entry(sb, ep, &mode))
goto put_es;
}
return 0;
@ -1266,7 +1309,8 @@ static int exfat_get_volume_label_dentry(struct super_block *sb,
es->bh = es->__bh;
es->bh[0] = bh;
es->num_bh = 1;
es->start_off = exfat_dentries_to_bytes(i) % sb->s_blocksize;
es->start_off = exfat_dentries_to_bytes(i) &
((u32)sb->s_blocksize - 1);
return 0;
}

View File

@ -7,6 +7,7 @@
#define _EXFAT_FS_H
#include <linux/fs.h>
#include <linux/atomic.h>
#include <linux/ratelimit.h>
#include <linux/nls.h>
#include <linux/blkdev.h>
@ -232,6 +233,7 @@ struct exfat_sb_info {
unsigned int num_FAT_sectors; /* num of FAT sectors */
unsigned int root_dir; /* root dir cluster */
unsigned int dentries_per_clu; /* num of dentries per cluster */
unsigned int dentries_per_clu_bits;
unsigned int vol_flags; /* volume flags */
unsigned int vol_flags_persistent; /* volume flags to retain */
struct buffer_head *boot_bh; /* buffer_head of BOOT sector */
@ -293,9 +295,16 @@ struct exfat_inode_info {
/* on-disk position of directory entry or 0 */
loff_t i_pos;
loff_t valid_size;
/*
* valid_size and zeroed_size are updated from multiple contexts that
* are not serialised against each other (page_mkwrite runs without
* i_rwsem, while buffered/DIO writes advance them under i_rwsem). Keep
* them atomic and only ever advance them with a cmpxchg loop so a
* concurrent update can never regress the value or tear on 32-bit.
*/
atomic64_t valid_size;
/* block-aligned size zeroed in the page cache (>= valid_size) */
loff_t zeroed_size;
atomic64_t zeroed_size;
/* hash by i_location */
struct hlist_node i_hash_fat;
struct inode vfs_inode;
@ -313,6 +322,54 @@ static inline struct exfat_inode_info *EXFAT_I(struct inode *inode)
return container_of(inode, struct exfat_inode_info, vfs_inode);
}
static inline loff_t exfat_get_valid_size(struct exfat_inode_info *ei)
{
return atomic64_read(&ei->valid_size);
}
static inline loff_t exfat_get_zeroed_size(struct exfat_inode_info *ei)
{
return atomic64_read(&ei->zeroed_size);
}
static inline void exfat_set_valid_size(struct exfat_inode_info *ei, loff_t v)
{
atomic64_set(&ei->valid_size, v);
}
static inline void exfat_set_zeroed_size(struct exfat_inode_info *ei, loff_t v)
{
atomic64_set(&ei->zeroed_size, v);
}
/*
* Monotonically advance @v to at least @new. Returns true if the value was
* actually raised, so callers can decide whether to mark the inode dirty.
*/
static inline bool exfat_size_advance(atomic64_t *v, loff_t new)
{
loff_t old = atomic64_read(v);
do {
if (old >= new)
return false;
} while (!atomic64_try_cmpxchg(v, &old, new));
return true;
}
static inline bool exfat_advance_valid_size(struct exfat_inode_info *ei,
loff_t new)
{
return exfat_size_advance(&ei->valid_size, new);
}
static inline bool exfat_advance_zeroed_size(struct exfat_inode_info *ei,
loff_t new)
{
return exfat_size_advance(&ei->zeroed_size, new);
}
static inline int exfat_forced_shutdown(struct super_block *sb)
{
return test_bit(EXFAT_FLAGS_SHUTDOWN, &EXFAT_SB(sb)->s_exfat_flags);
@ -623,19 +680,29 @@ static inline int exfat_chain_advance(struct super_block *sb,
struct exfat_chain *chain, unsigned int step)
{
unsigned int clu = chain->dir;
unsigned int i;
if (unlikely(chain->size < step))
return -EIO;
if (chain->flags == ALLOC_NO_FAT_CHAIN) {
if (unlikely(chain->size < step))
return -EIO;
if (exfat_cluster_walk(sb, &clu, step, chain->flags))
return -EIO;
chain->size -= step;
chain->dir = chain->size ? clu + step : EXFAT_EOF_CLUSTER;
return 0;
}
chain->size -= step;
/*
* Only the FAT tells where a chain of this kind ends. The size can be
* smaller than the chain, see commit 1e5654de0f51 ("exfat: handle
* wrong stream entry size in exfat_readdir()").
*/
for (i = 0; i < step && clu != EXFAT_EOF_CLUSTER; i++) {
if (exfat_get_next_cluster(sb, &clu))
return -EIO;
}
if (chain->size == 0 && chain->flags == ALLOC_NO_FAT_CHAIN)
chain->dir = EXFAT_EOF_CLUSTER;
else
chain->dir = clu;
chain->size -= min(chain->size, step);
chain->dir = clu;
return 0;
}

View File

@ -418,28 +418,47 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu)
return 0;
}
/*
* Newly allocated clusters may still have dirty buffer_heads in the block
* device mapping, e.g. the dentries of a directory that was just removed.
* File data does not go through those buffers, so drop them; otherwise a
* later flush of the block device writes them over the new data.
*/
static void exfat_clean_bdev_aliases(struct super_block *sb,
unsigned int clu, unsigned int count)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
clean_bdev_aliases(sb->s_bdev, exfat_cluster_to_sector(sbi, clu),
(sector_t)count << sbi->sect_per_clus_bits);
}
int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
struct exfat_chain *p_chain, bool sync_bmap, bool contig)
{
int ret = -ENOSPC;
unsigned int total_cnt;
unsigned int hint_clu, new_clu, last_clu = EXFAT_EOF_CLUSTER;
unsigned int run_clu = EXFAT_EOF_CLUSTER, run_len = 0;
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
mutex_lock(&sbi->bitmap_lock);
total_cnt = EXFAT_DATA_CLUSTER_COUNT(sbi);
if (unlikely(total_cnt < sbi->used_clusters)) {
exfat_fs_error_ratelimit(sb,
"%s: invalid used clusters(t:%u,u:%u)\n",
__func__, total_cnt, sbi->used_clusters);
return -EIO;
ret = -EIO;
goto unlock;
}
if (num_alloc > total_cnt - sbi->used_clusters)
return -ENOSPC;
mutex_lock(&sbi->bitmap_lock);
if (num_alloc > total_cnt - sbi->used_clusters) {
ret = -ENOSPC;
goto unlock;
}
hint_clu = p_chain->dir;
/* find new cluster */
@ -509,15 +528,26 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
}
}
p_chain->size++;
sbi->used_clusters++;
/* clean stale buffers once per contiguous run of clusters */
if (run_len && new_clu == run_clu + run_len) {
run_len++;
} else {
if (run_len)
exfat_clean_bdev_aliases(sb, run_clu, run_len);
run_clu = new_clu;
run_len = 1;
}
last_clu = new_clu;
if (p_chain->size == num_alloc) {
done:
exfat_clean_bdev_aliases(sb, run_clu, run_len);
sbi->clu_srch_ptr = hint_clu;
sbi->used_clusters += p_chain->size;
mutex_unlock(&sbi->bitmap_lock);
return 0;
ret = 0;
goto unlock;
}
hint_clu = new_clu + 1;

View File

@ -17,11 +17,70 @@
#include <linux/fileattr.h>
#include <linux/iomap.h>
#include <linux/pagemap.h>
#include <linux/rmap.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#include "iomap.h"
/*
* Unlike pagecache_isize_extended(), this function updates i_size while
* holding the straddling folio lock, and does not skip the straddling
* folio when the filesystem block size is >= PAGE_SIZE or when 'from'
* and 'to' fall within the same filesystem block.
*
* This ensures that the folio is marked RO and the post-EOF range is
* zeroed before readers or writeback can observe the updated i_size.
*/
static void exfat_pagecache_isize_extended(struct inode *inode, loff_t from,
loff_t to)
{
struct folio *folio;
if (!(from & (PAGE_SIZE - 1))) {
i_size_write(inode, to);
return;
}
folio = filemap_lock_folio(inode->i_mapping, from >> PAGE_SHIFT);
/* Folio not cached? Nothing to do */
if (IS_ERR(folio)) {
i_size_write(inode, to);
return;
}
/*
* See folio_clear_dirty_for_io() for details why folio_mark_dirty()
* is needed.
*/
if (folio_mkclean(folio))
folio_mark_dirty(folio);
/*
* Zero the post-EOF range before publishing the new size. Writeback
* normally does this, but the extension would expose the tail to
* readers before writeback gets a chance to zero it.
*/
if (folio_test_dirty(folio)) {
loff_t offset, end;
offset = from - folio_pos(folio);
end = min_t(loff_t, to - folio_pos(folio),
folio_size(folio));
folio_zero_segment(folio, offset, end);
}
/*
* Publish the new size after zeroing the tail. Uptodate folios can
* be read without taking the folio lock.
*/
i_size_write(inode, to);
folio_unlock(folio);
folio_put(folio);
}
static int exfat_cont_expand(struct inode *inode, loff_t size)
{
int ret;
@ -32,8 +91,6 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
struct exfat_chain clu;
loff_t oldsize = i_size_read(inode);
truncate_pagecache(inode, oldsize);
ret = inode_newsize_ok(inode, size);
if (ret)
return ret;
@ -81,15 +138,12 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
out:
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
/* Expanded range not zeroed, do not update valid_size */
i_size_write(inode, size);
/*
* When extending file size, call truncate_pagecache() first,
* then update i_size, and call pagecache_isize_extended()
* to ensures the straddling folio is properly marked RO so
* page_mkwrite() is called and post-EOF area is zeroed.
* When extending file size, call exfat_pagecache_isize_extended()
* to update i_size and ensure the straddling folio is properly
* marked RO so page_mkwrite() is called and post-EOF area is zeroed.
*/
pagecache_isize_extended(inode, oldsize, inode->i_size);
exfat_pagecache_isize_extended(inode, oldsize, size);
inode->i_blocks = round_up(size, sbi->cluster_size) >> 9;
mark_inode_dirty(inode);
@ -247,8 +301,10 @@ int __exfat_truncate(struct inode *inode)
ei->start_clu = EXFAT_EOF_CLUSTER;
}
if (i_size_read(inode) < ei->valid_size)
ei->valid_size = ei->zeroed_size = i_size_read(inode);
if (i_size_read(inode) < exfat_get_valid_size(ei)) {
exfat_set_valid_size(ei, i_size_read(inode));
exfat_set_zeroed_size(ei, i_size_read(inode));
}
if (ei->type == TYPE_FILE)
ei->attr |= EXFAT_ATTR_ARCHIVE;
@ -413,6 +469,7 @@ int exfat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
* about to be freed.
*/
inode_dio_wait(inode);
filemap_invalidate_lock(inode->i_mapping);
truncate_setsize(inode, attr->ia_size);
/*
@ -420,6 +477,7 @@ int exfat_setattr(const struct mnt_idmap *idmap, struct dentry *dentry,
* is already written by it, so mark_inode_dirty() is unneeded.
*/
exfat_truncate(inode);
filemap_invalidate_unlock(inode->i_mapping);
} else
mark_inode_dirty(inode);
@ -654,98 +712,42 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
}
/*
* exfat_zero_new_range - zero [start, end) without overwriting uptodate blocks
* exfat_prepare_valid_range - populate and dirty folios covering [start, end)
*
* Uptodate blocks may contain data written through a shared mapping beyond
* valid_size.
* Populate missing blocks via the read path, which zero-fills data
* beyond the current valid_size and skips uptodate blocks.
* Mark entire folios dirty so the newly valid range is written back.
*
* Call before advancing valid_size.
*/
static int exfat_zero_new_range(struct inode *inode, loff_t start, loff_t end)
static int exfat_prepare_valid_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;
loff_t next, pos = start;
struct folio *folio;
pgoff_t index;
if (pos >= end)
return 0;
while (pos < end) {
loff_t next = min_t(loff_t,
round_down(pos, PAGE_SIZE) + PAGE_SIZE, end);
struct folio *folio;
loff_t bpos;
index = pos >> PAGE_SHIFT;
next = min(((loff_t)index + 1) << PAGE_SHIFT, end);
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;
}
balance_dirty_pages_ratelimited(mapping);
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;
}
folio = read_mapping_folio(mapping, index, NULL);
if (IS_ERR(folio))
return PTR_ERR(folio);
/*
* 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))
if (folio->mapping == mapping) {
folio_mark_dirty(folio);
pos = next;
}
folio_unlock(folio);
folio_put(folio);
pos = next;
}
return 0;
@ -754,12 +756,14 @@ static int exfat_zero_new_range(struct inode *inode, loff_t start, loff_t end)
static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size)
{
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t old_valid_size = ei->valid_size;
loff_t old_valid_size = exfat_get_valid_size(ei);
int ret = 0;
if (old_valid_size < new_valid_size) {
inode_dio_wait(inode);
/* Do not re-zero blocks already covered by zeroed_size. */
loff_t gap_start = max(old_valid_size, ei->zeroed_size);
loff_t gap_start = max(old_valid_size, exfat_get_zeroed_size(ei));
if (i_size_read(inode) < new_valid_size) {
/*
@ -781,23 +785,38 @@ static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size)
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);
ret = exfat_prepare_valid_range(inode, gap_start, new_valid_size);
filemap_invalidate_unlock(inode->i_mapping);
if (ret) {
filemap_invalidate_lock(inode->i_mapping);
truncate_setsize(inode, old_valid_size);
exfat_truncate(inode);
filemap_invalidate_unlock(inode->i_mapping);
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));
exfat_set_valid_size(ei, new_valid_size);
exfat_advance_zeroed_size(ei,
round_up(new_valid_size, i_blocksize(inode)));
mark_inode_dirty(inode);
}
return ret;
}
/*
* The clusters for a write are allocated before the data is copied. Free the
* ones that a failed or short write has left beyond the end of the file.
*/
static void exfat_write_failed(struct inode *inode)
{
inode_dio_wait(inode);
filemap_invalidate_lock(inode->i_mapping);
truncate_pagecache(inode, i_size_read(inode));
exfat_truncate(inode);
filemap_invalidate_unlock(inode->i_mapping);
}
static ssize_t exfat_fallback_buffered_write(struct kiocb *iocb,
struct iov_iter *from)
{
@ -807,6 +826,8 @@ static ssize_t exfat_fallback_buffered_write(struct kiocb *iocb,
iocb->ki_flags &= ~IOCB_DIRECT;
inode_dio_wait(file_inode(iocb->ki_filp));
written = iomap_file_buffered_write(iocb, from, &exfat_write_iomap_ops,
NULL, NULL);
if (written < 0)
@ -854,8 +875,7 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t pos = iocb->ki_pos;
loff_t valid_size;
loff_t pos, valid_size, end;
int err;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
@ -863,11 +883,6 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
inode_lock(inode);
if (pos > i_size_read(inode))
truncate_pagecache(inode, i_size_read(inode));
valid_size = ei->valid_size;
ret = generic_write_checks(iocb, iter);
if (ret <= 0)
goto unlock;
@ -878,6 +893,12 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
goto unlock;
}
pos = iocb->ki_pos;
end = pos + iov_iter_count(iter);
if (pos > i_size_read(inode))
truncate_pagecache(inode, i_size_read(inode));
valid_size = exfat_get_valid_size(ei);
if (pos > valid_size) {
ret = exfat_extend_valid_size(inode, pos);
if (ret < 0 && ret != -ENOSPC) {
@ -887,21 +908,28 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
}
if (ret < 0)
goto unlock;
pos = valid_size;
}
if (iocb->ki_flags & IOCB_DIRECT)
if (iocb->ki_flags & IOCB_DIRECT) {
ret = exfat_dio_write_iter(iocb, iter);
else
} else {
/*
* Prevent concurrent direct I/O and buffered I/O to the same file
* range. Wait for in-flight DIO to finish before dirtying pages.
*/
inode_dio_wait(inode);
ret = iomap_file_buffered_write(iocb, iter,
&exfat_write_iomap_ops, NULL, NULL);
}
if (ret != -EIOCBQUEUED && end > i_size_read(inode))
exfat_write_failed(inode);
if (ret < 0)
goto unlock;
inode_unlock(inode);
if (pos > valid_size)
pos = valid_size;
if (iocb->ki_pos > pos) {
ssize_t err = generic_write_sync(iocb, iocb->ki_pos - pos);
@ -954,8 +982,10 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
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 < fault_page_start) {
if (exfat_get_valid_size(ei) < new_valid_size) {
loff_t zeroed_size = exfat_get_zeroed_size(ei);
if (zeroed_size < fault_page_start) {
int err;
/*
@ -963,7 +993,7 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
* fault populated its folio and iomap_page_mkwrite()
* will dirty it.
*/
err = exfat_zero_new_range(inode, ei->zeroed_size,
err = exfat_prepare_valid_range(inode, zeroed_size,
fault_page_start);
if (err < 0) {
inode_unlock(inode);
@ -976,9 +1006,9 @@ static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
* 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;
exfat_advance_zeroed_size(ei,
round_up(new_valid_size, i_blocksize(inode)));
exfat_set_valid_size(ei, new_valid_size);
mark_inode_dirty(inode);
}

View File

@ -29,6 +29,7 @@ int __exfat_write_inode(struct inode *inode, int sync)
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
bool is_dir = (ei->type == TYPE_DIR);
loff_t valid_size = exfat_get_valid_size(ei);
struct timespec64 ts;
if (inode->i_ino == EXFAT_ROOT_INO)
@ -81,7 +82,7 @@ int __exfat_write_inode(struct inode *inode, int sync)
* preventing fsck from reporting "more clusters are allocated".
*/
on_disk_size = max_t(unsigned long long, i_size_read(inode),
ei->valid_size);
valid_size);
if (ei->start_clu == EXFAT_EOF_CLUSTER)
on_disk_size = 0;
@ -89,7 +90,7 @@ int __exfat_write_inode(struct inode *inode, int sync)
* valid_size on disk must reflect only confirmed data (up to i_size)
* and must not exceed on_disk_size.
*/
on_disk_valid_size = min_t(unsigned long long, ei->valid_size,
on_disk_valid_size = min_t(unsigned long long, valid_size,
i_size_read(inode));
if (ei->start_clu == EXFAT_EOF_CLUSTER)
on_disk_valid_size = 0;
@ -258,15 +259,16 @@ static void exfat_readahead(struct readahead_control *rac)
struct inode *inode = mapping->host;
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t pos = readahead_pos(rac);
loff_t valid_size = exfat_get_valid_size(ei);
struct iomap_read_folio_ctx ctx = {
.ops = &exfat_iomap_bio_read_ops,
.rac = rac,
};
/* Range cross valid_size, read it page by page. */
if (ei->valid_size < i_size_read(inode) &&
pos <= ei->valid_size &&
ei->valid_size < pos + readahead_length(rac))
if (valid_size < i_size_read(inode) &&
pos <= valid_size &&
valid_size < pos + readahead_length(rac))
return;
iomap_readahead(&exfat_iomap_ops, &ctx, NULL);
@ -371,8 +373,8 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info)
ei->start_clu = info->start_clu;
ei->flags = info->flags;
ei->type = info->type;
ei->valid_size = info->valid_size;
ei->zeroed_size = info->valid_size;
exfat_set_valid_size(ei, info->valid_size);
exfat_set_zeroed_size(ei, info->valid_size);
ei->version = 0;
ei->hint_stat.eidx = 0;

View File

@ -46,6 +46,7 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
struct exfat_inode_info *ei = EXFAT_I(inode);
unsigned int cluster, num_clusters;
loff_t cluster_offset, cluster_length;
loff_t valid_size = exfat_get_valid_size(ei);
int err;
bool balloc = false;
@ -74,6 +75,17 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
err = exfat_map_cluster(inode, exfat_bytes_to_cluster(sbi, offset),
&cluster, &num_clusters, may_alloc, &balloc);
if (err == -ENOSPC && !(flags & IOMAP_DIRECT)) {
/*
* There are not enough free clusters for the whole range. A
* buffered write can be short, so map what still fits.
*/
num_clusters = 1;
err = exfat_map_cluster(inode,
exfat_bytes_to_cluster(sbi, offset),
&cluster, &num_clusters, may_alloc,
&balloc);
}
if (err)
goto out;
@ -88,7 +100,7 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
if (may_alloc || flags & IOMAP_ZERO) {
if (balloc)
iomap->flags |= IOMAP_F_NEW;
else if (iomap->offset + iomap->length >= ei->valid_size) {
else if (iomap->offset + iomap->length >= valid_size) {
/*
* This is a write that starts at or extends beyond
* the current valid_size. The region between the old
@ -114,10 +126,10 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
* return IOMAP_UNWRITTEN so the write path can
* distinguish it from a real hole.
*/
if (offset >= ei->valid_size) {
if (offset >= valid_size) {
iomap->type = flags & IOMAP_REPORT ?
IOMAP_HOLE : IOMAP_UNWRITTEN;
} else if (offset + iomap->length > ei->valid_size) {
} else if (offset + iomap->length > valid_size) {
if (flags & IOMAP_REPORT) {
/*
* For SEEK_HOLE/SEEK_DATA, clip the length
@ -125,9 +137,9 @@ static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length
* This ensures the caller gets the precise
* hole position in byte units.
*/
iomap->length = ei->valid_size - iomap->offset;
iomap->length = valid_size - iomap->offset;
} else
iomap->length = round_up(ei->valid_size,
iomap->length = round_up(valid_size,
i_blocksize(inode)) -
iomap->offset;
}
@ -148,7 +160,27 @@ static int exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
static int exfat_write_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap, struct iomap *srcmap)
{
return __exfat_iomap_begin(inode, offset, length, flags, iomap, true);
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t valid_size = exfat_get_valid_size(ei);
loff_t end;
int err;
err = __exfat_iomap_begin(inode, offset, length, flags, iomap, true);
if (err || flags & (IOMAP_DIRECT | IOMAP_FAULT) ||
iomap->flags & IOMAP_F_NEW)
return err;
end = iomap->offset + iomap->length;
if (end >= valid_size ||
round_up(end, i_blocksize(inode)) <= valid_size)
return 0;
/*
* Zero the invalid bytes from valid_size to the end of the block
* to prevent stale data from being exposed through the page cache.
*/
return iomap_truncate_page(inode, valid_size, NULL, &exfat_iomap_ops,
NULL, NULL);
}
static DEFINE_IOMAP_ITER_NEXT(exfat_iomap_next, exfat_iomap_begin);
@ -157,6 +189,27 @@ const struct iomap_ops exfat_iomap_ops = {
.iomap_next = exfat_iomap_next,
};
#ifdef CONFIG_SWAP
static int exfat_swap_iomap_begin(struct inode *inode, loff_t offset,
loff_t length, unsigned int flags, struct iomap *iomap,
struct iomap *srcmap)
{
/*
* Swap activation needs the physical mappings of preallocated
* ranges. Do not report the VDL tail as a hole.
*/
return __exfat_iomap_begin(inode, offset, length,
flags & ~IOMAP_REPORT, iomap, false);
}
static DEFINE_IOMAP_ITER_NEXT(exfat_swap_iomap_next,
exfat_swap_iomap_begin);
static const struct iomap_ops exfat_swap_iomap_ops = {
.iomap_next = exfat_swap_iomap_next,
};
#endif
/*
* exfat_write_iomap_end - Update the state after write
*
@ -175,10 +228,8 @@ static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
end = pos + written;
if (ei->valid_size < end) {
ei->valid_size = end;
if (exfat_advance_valid_size(ei, end))
dirtied = true;
}
/*
* IOMAP_F_ZERO_TAIL zeroes the remainder of the last block. Track that
@ -186,8 +237,7 @@ static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
*/
if (iomap->flags & IOMAP_F_ZERO_TAIL)
end = round_up(end, i_blocksize(inode));
if (ei->zeroed_size < end)
ei->zeroed_size = end;
exfat_advance_zeroed_size(ei, end);
if (dirtied || iomap->flags & IOMAP_F_SIZE_CHANGED)
mark_inode_dirty(inode);
@ -250,7 +300,7 @@ static void exfat_iomap_read_end_io(struct bio *bio)
s64 valid_size;
loff_t pos = folio_pos(folio);
valid_size = ei->valid_size;
valid_size = exfat_get_valid_size(ei);
if (pos + iter.offset < valid_size &&
pos + iter.offset + iter.length > valid_size)
folio_zero_segment(folio, offset_in_folio(folio, valid_size),
@ -275,5 +325,10 @@ const struct iomap_read_ops exfat_iomap_bio_read_ops = {
int exfat_iomap_swap_activate(struct swap_info_struct *sis,
struct file *file, sector_t *span)
{
return iomap_swapfile_activate(sis, file, span, &exfat_iomap_ops);
#ifdef CONFIG_SWAP
return iomap_swapfile_activate(sis, file, span,
&exfat_swap_iomap_ops);
#else
return -EIO;
#endif
}

View File

@ -380,13 +380,13 @@ int exfat_find_empty_entry(struct inode *inode,
/* directory inode should be updated in here */
i_size_write(inode, size);
ei->valid_size += sbi->cluster_size;
atomic64_add(sbi->cluster_size, &ei->valid_size);
ei->flags = p_dir->flags;
inode->i_blocks += sbi->cluster_size >> 9;
}
p_dir->dir = exfat_sector_to_cluster(sbi, es->bh[0]->b_blocknr);
p_dir->size -= dentry / sbi->dentries_per_clu;
p_dir->size -= dentry >> sbi->dentries_per_clu_bits;
return dentry & (sbi->dentries_per_clu - 1);
}
@ -638,14 +638,15 @@ static int exfat_find(struct inode *dir, const struct qstr *qname,
/* adjust cdir to the optimized value */
cdir.dir = hint_opt.clu;
if (cdir.flags & ALLOC_NO_FAT_CHAIN)
cdir.size -= dentry / sbi->dentries_per_clu;
cdir.size -= dentry >> sbi->dentries_per_clu_bits;
dentry = hint_opt.eidx;
info->dir = cdir;
info->entry = dentry;
info->num_subdirs = 0;
if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_2_ENTRIES))
/* Validate the complete set, including recognized benign entries. */
if (exfat_get_dentry_set(&es, sb, &cdir, dentry, ES_ALL_ENTRIES))
return -EIO;
ep = exfat_get_dentry_cached(&es, ES_IDX_FILE);
ep2 = exfat_get_dentry_cached(&es, ES_IDX_STREAM);
@ -1248,7 +1249,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
}
i_size_write(new_inode, 0);
new_ei->valid_size = 0;
exfat_set_valid_size(new_ei, 0);
new_ei->start_clu = EXFAT_EOF_CLUSTER;
new_ei->flags = ALLOC_NO_FAT_CHAIN;
}

View File

@ -18,6 +18,7 @@
#include <linux/nls.h>
#include <linux/buffer_head.h>
#include <linux/magic.h>
#include <linux/math64.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
@ -69,16 +70,28 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf)
return 0;
}
static inline __u8 exfat_calc_perc_in_use(const struct exfat_sb_info *sbi)
{
return (__u8)mul_u64_u32_div(sbi->used_clusters, 100,
EXFAT_DATA_CLUSTER_COUNT(sbi));
}
static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags)
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct boot_sector *p_boot = (struct boot_sector *)sbi->boot_bh->b_data;
__u8 new_piu;
/* retain persistent-flags */
new_flags |= sbi->vol_flags_persistent;
if (new_flags & VOLUME_DIRTY)
new_piu = 0xFF;
else
new_piu = exfat_calc_perc_in_use(sbi);
/* flags are not changed */
if (sbi->vol_flags == new_flags)
if (sbi->vol_flags == new_flags && new_piu == p_boot->percent_in_use)
return 0;
sbi->vol_flags = new_flags;
@ -90,6 +103,7 @@ static int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flags)
return 0;
p_boot->vol_flags = cpu_to_le16(new_flags);
p_boot->percent_in_use = new_piu;
set_buffer_uptodate(sbi->boot_bh);
mark_buffer_dirty(sbi->boot_bh);
@ -505,8 +519,8 @@ static int exfat_read_boot_sector(struct super_block *sb)
EXFAT_RESERVED_CLUSTERS;
sbi->root_dir = le32_to_cpu(p_boot->root_cluster);
sbi->dentries_per_clu = 1 <<
(sbi->cluster_size_bits - DENTRY_SIZE_BITS);
sbi->dentries_per_clu_bits = sbi->cluster_size_bits - DENTRY_SIZE_BITS;
sbi->dentries_per_clu = 1 << sbi->dentries_per_clu_bits;
sbi->vol_flags = le16_to_cpu(p_boot->vol_flags);
sbi->vol_flags_persistent = sbi->vol_flags & (VOLUME_DIRTY | MEDIA_FAILURE);
@ -775,9 +789,13 @@ static int exfat_reconfigure(struct fs_context *fc)
fc->sb_flags |= SB_NODIRATIME;
sync_filesystem(sb);
mutex_lock(&sbi->s_lock);
exfat_clear_volume_dirty(sb);
mutex_unlock(&sbi->s_lock);
if ((fc->sb_flags & (SB_FORCE | SB_RDONLY)) == SB_RDONLY &&
!sb_rdonly(sb)) {
mutex_lock(&sbi->s_lock);
exfat_clear_volume_dirty(sb);
mutex_unlock(&sbi->s_lock);
}
if (new_opts->allow_utime == (unsigned short)-1)
new_opts->allow_utime = ~new_opts->fs_dmask & 0022;