mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
- 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:
commit
42023dd0a2
117
Documentation/filesystems/exfat.rst
Normal file
117
Documentation/filesystems/exfat.rst
Normal 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.
|
||||
|
|
@ -86,6 +86,7 @@ Documentation for filesystem implementations.
|
|||
ecryptfs
|
||||
efivarfs
|
||||
erofs
|
||||
exfat
|
||||
ext2
|
||||
ext3
|
||||
ext4/index
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
258
fs/exfat/file.c
258
fs/exfat/file.c
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user