Description for this pull request:

- Converts exfat buffered and direct I/O to the iomap infrastructure.
  - Adds the supporting block mapping changes needed for that conversion,
    including multi-cluster allocation, byte-based cluster mapping helpers.
  - Supports SEEK_HOLE/SEEK_DATA and swapfile activation through iomap.
  - Fix damaged upcase-table handling so a zero-sized table does not lead
    to an infinite loop.
  - Fix a potential use-after-free in exfat_find_dir_entry()
  - Bound filename-entry advancement in exfat_find_dir_entry()
  - Preserve benign secondary entries during rename and move
  - Serialize truncate against in-flight direct I/O
  - Simplify exfat_lookup().
  - Replace unsafe arithmetic macros with static inline helpers.
 -----BEGIN PGP SIGNATURE-----
 
 iQJKBAABCgA0FiEE6NzKS6Uv/XAAGHgyZwv7A1FEIQgFAmoz9dYWHGxpbmtpbmpl
 b25Aa2VybmVsLm9yZwAKCRBnC/sDUUQhCOcfEACXzHTGyd58G6oS7o0NsL+W1Z4j
 vUV7CB6liGYPi2jO4FzLoQTImLClTAae0skSiDnR9gmxqrzrv0GGntB0lLbjuD5M
 91qaBpzjSpnaoZLOA3/y5EapH3842I23pHS1MprOdyTr0sG6l2TYP/Uop01oyBv8
 o4NCWwzXykkOBwoGGTZS2ANg59X5hcjIM+v43rmLbeMur1zO9wZOBIjJ7yyz+LxB
 IVD1C5Vul6Oeg9FvWL21kmVhM0qWWUQUWL6d1YiUb41BV6lJ3GIbJOhUl/Veju1/
 AHOYIVMn8fo10F22H/FaIe6k0I9Qc9KB71ewX1wnCrre1cNtyiNIklDuoni+YwBn
 mTdJ+Glh3QlsWC3Bw9MySOnSDuaKta6j59S2Q5K3VarKLE92dONpIMFnMmcITUTH
 WveF0/XdkTzO1z7+C2N1wnEkQUxDH9kbYxRuCPUQxrFchbx/zZv8+d/PloKRlVg3
 h/GMd3N3oRwgAb3d8vo8AUjBErp04BvcfHXbOISHybLyFdsLay3DNPRsd0IPZoLW
 VV0CLRUtWJztCzSA2VJUNC8CUFTXP1hfQtaYpEPNDKtSZ4XnRXUgKFcH7og1GuWx
 cTtDf+MdyqANUOEbdfYspR/q+gMMDZF6UumD2ttII/iozJHMVYjid45oN3Vy6cM/
 QHiKAniPqxOl+70Olw==
 =FP3d
 -----END PGP SIGNATURE-----

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

Pull exfat updates from Namjae Jeon:

 - Convert exfat buffered and direct I/O to the iomap infrastructure

 - Add the supporting block mapping changes needed for that conversion,
   including multi-cluster allocation, byte-based cluster mapping
   helpers

 - Support SEEK_HOLE/SEEK_DATA and swapfile activation through iomap

 - Fix damaged upcase-table handling so a zero-sized table does not lead
   to an infinite loop

 - Fix a potential use-after-free in exfat_find_dir_entry()

 - Bound filename-entry advancement in exfat_find_dir_entry()

 - Preserve benign secondary entries during rename and move

 - Serialize truncate against in-flight direct I/O

 - Simplify exfat_lookup()

 - Replace unsafe arithmetic macros with static inline helpers

* tag 'exfat-for-7.2-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat:
  exfat: bound uniname advance in exfat_find_dir_entry()
  exfat: add swap_activate support
  exfat: preserve benign secondary entries during rename and move
  exfat: serialize truncate against in-flight DIO
  exfat: add support for SEEK_HOLE and SEEK_DATA in llseek
  exfat: add iomap direct I/O support
  exfat: add iomap buffered I/O support
  exfat: fix implicit declaration of brelse()
  exfat: add data_start_bytes and exfat_cluster_to_phys_bytes() helper
  exfat: add support for multi-cluster allocation
  exfat: add exfat_file_open()
  exfat: add balloc parameter to exfat_map_cluster() for iomap support
  exfat: replace unsafe macros with static inline functions
  exfat: simplify exfat_lookup()
  exfat: fix potential use-after-free in exfat_find_dir_entry()
  exfat: fix handling of damaged volume in exfat_create_upcase_table()
This commit is contained in:
Linus Torvalds 2026-06-19 13:43:40 -07:00
commit a975094bf9
13 changed files with 881 additions and 534 deletions

View File

@ -4,7 +4,7 @@ config EXFAT_FS
tristate "exFAT filesystem support" tristate "exFAT filesystem support"
select BUFFER_HEAD select BUFFER_HEAD
select NLS select NLS
select LEGACY_DIRECT_IO select FS_IOMAP
help help
This allows you to mount devices formatted with the exFAT file system. This allows you to mount devices formatted with the exFAT file system.
exFAT is typically used on SD-Cards or USB sticks. exFAT is typically used on SD-Cards or USB sticks.

View File

@ -5,4 +5,4 @@
obj-$(CONFIG_EXFAT_FS) += exfat.o obj-$(CONFIG_EXFAT_FS) += exfat.o
exfat-y := inode.o namei.o dir.o super.o fatent.o cache.o nls.o misc.o \ exfat-y := inode.o namei.o dir.o super.o fatent.o cache.o nls.o misc.o \
file.o balloc.o file.o balloc.o iomap.o

View File

@ -112,7 +112,7 @@ static int exfat_allocate_bitmap(struct super_block *sb,
} }
if (exfat_test_bitmap_range(sb, sbi->map_clu, if (exfat_test_bitmap_range(sb, sbi->map_clu,
EXFAT_B_TO_CLU_ROUND_UP(map_size, sbi)) == false) exfat_bytes_to_cluster_round_up(sbi, map_size)) == false)
goto err_out; goto err_out;
return 0; return 0;

View File

@ -76,7 +76,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
unsigned int dentry = EXFAT_B_TO_DEN(*cpos) & 0xFFFFFFFF; unsigned int dentry = exfat_bytes_to_dentries(*cpos) & 0xFFFFFFFF;
struct buffer_head *bh; struct buffer_head *bh;
/* check if the given file ID is opened */ /* check if the given file ID is opened */
@ -84,13 +84,13 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
return -EPERM; return -EPERM;
exfat_chain_set(&dir, ei->start_clu, exfat_chain_set(&dir, ei->start_clu,
EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); exfat_bytes_to_cluster(sbi, i_size_read(inode)), ei->flags);
dentries_per_clu = sbi->dentries_per_clu; dentries_per_clu = sbi->dentries_per_clu;
max_dentries = (unsigned int)min_t(u64, MAX_EXFAT_DENTRIES, max_dentries = min(MAX_EXFAT_DENTRIES,
(u64)EXFAT_CLU_TO_DEN(sbi->num_clusters, sbi)); exfat_cluster_to_dentries(sbi, sbi->num_clusters));
clu_offset = EXFAT_DEN_TO_CLU(dentry, sbi); clu_offset = exfat_dentries_to_cluster(sbi, dentry);
exfat_chain_dup(&clu, &dir); exfat_chain_dup(&clu, &dir);
if (clu.flags == ALLOC_FAT_CHAIN) { if (clu.flags == ALLOC_FAT_CHAIN) {
@ -147,10 +147,10 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
dir_entry->dir = clu; dir_entry->dir = clu;
brelse(bh); brelse(bh);
ei->hint_bmap.off = EXFAT_DEN_TO_CLU(dentry, sbi); ei->hint_bmap.off = exfat_dentries_to_cluster(sbi, dentry);
ei->hint_bmap.clu = clu.dir; ei->hint_bmap.clu = clu.dir;
*cpos = EXFAT_DEN_TO_B(dentry + 1 + num_ext); *cpos = exfat_dentries_to_bytes(dentry + 1 + num_ext);
return 0; return 0;
} }
@ -160,7 +160,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
out: out:
dir_entry->namebuf.lfn[0] = '\0'; dir_entry->namebuf.lfn[0] = '\0';
*cpos = EXFAT_DEN_TO_B(dentry); *cpos = exfat_dentries_to_bytes(dentry);
return 0; return 0;
} }
@ -295,7 +295,7 @@ int exfat_alloc_new_dir(struct inode *inode, struct exfat_chain *clu)
exfat_chain_set(clu, EXFAT_EOF_CLUSTER, 0, ALLOC_NO_FAT_CHAIN); exfat_chain_set(clu, EXFAT_EOF_CLUSTER, 0, ALLOC_NO_FAT_CHAIN);
ret = exfat_alloc_cluster(inode, 1, clu, IS_DIRSYNC(inode)); ret = exfat_alloc_cluster(inode, 1, clu, IS_DIRSYNC(inode), false);
if (ret) if (ret)
return ret; return ret;
@ -465,37 +465,75 @@ static void exfat_free_benign_secondary_clusters(struct inode *inode,
return; return;
exfat_chain_set(&dir, start_clu, exfat_chain_set(&dir, start_clu,
EXFAT_B_TO_CLU_ROUND_UP(size, EXFAT_SB(sb)), exfat_bytes_to_cluster_round_up(EXFAT_SB(sb), size),
flags); flags);
exfat_free_cluster(inode, &dir); exfat_free_cluster(inode, &dir);
} }
/*
* exfat_init_ext_entry - initialize extension entries in a directory entry set
* @es: target entry set
* @num_entries: number of entries excluding benign secondary entries
* @p_uniname: filename to store
* @old_es: optional source entry set with benign secondary entries, or NULL
* @num_extra: number of benign secondary entries to copy from @old_es
*
* Set up the file, stream extension, and filename entries in @es, optionally
* preserving @num_extra benign secondary entries from @old_es. @es and @old_es
* may refer to the same entry set; excess entries are marked as deleted.
*/
void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
struct exfat_uni_name *p_uniname) struct exfat_uni_name *p_uniname,
struct exfat_entry_set_cache *old_es, int num_extra)
{ {
int i; int i, src_start = 0, old_num;
unsigned short *uniname = p_uniname->name; unsigned short *uniname = p_uniname->name;
struct exfat_dentry *ep; struct exfat_dentry *ep;
es->num_entries = num_entries; if (WARN_ON(num_extra < 0 || (num_extra && (!old_es ||
old_es->num_entries < ES_IDX_FIRST_FILENAME + num_extra))))
num_extra = 0;
/*
* Save old entry count and source position before modifying
* es->num_entries, since old_es and es may point to the same
* entry set.
*/
old_num = es->num_entries;
if (old_es && num_extra > 0)
src_start = old_es->num_entries - num_extra;
es->num_entries = num_entries + num_extra;
ep = exfat_get_dentry_cached(es, ES_IDX_FILE); ep = exfat_get_dentry_cached(es, ES_IDX_FILE);
ep->dentry.file.num_ext = (unsigned char)(num_entries - 1); ep->dentry.file.num_ext = (unsigned char)(num_entries - 1 + num_extra);
ep = exfat_get_dentry_cached(es, ES_IDX_STREAM); ep = exfat_get_dentry_cached(es, ES_IDX_STREAM);
ep->dentry.stream.name_len = p_uniname->name_len; ep->dentry.stream.name_len = p_uniname->name_len;
ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash); ep->dentry.stream.name_hash = cpu_to_le16(p_uniname->name_hash);
if (old_es && num_extra > 0) {
for (i = 0; i < num_extra; i++)
*exfat_get_dentry_cached(es, num_entries + i) =
*exfat_get_dentry_cached(old_es, src_start + i);
}
for (i = ES_IDX_FIRST_FILENAME; i < num_entries; i++) { for (i = ES_IDX_FIRST_FILENAME; i < num_entries; i++) {
ep = exfat_get_dentry_cached(es, i); ep = exfat_get_dentry_cached(es, i);
exfat_init_name_entry(ep, uniname); exfat_init_name_entry(ep, uniname);
uniname += EXFAT_FILE_NAME_LEN; uniname += EXFAT_FILE_NAME_LEN;
} }
/* Mark excess old entries as deleted (in-place shrink) */
for (i = num_entries + num_extra; i < old_num; i++) {
ep = exfat_get_dentry_cached(es, i);
exfat_set_entry_type(ep, TYPE_DELETED);
}
exfat_update_dir_chksum(es); exfat_update_dir_chksum(es);
} }
void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
int order) int order, bool free_benign)
{ {
int i; int i;
struct exfat_dentry *ep; struct exfat_dentry *ep;
@ -503,7 +541,7 @@ void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
for (i = order; i < es->num_entries; i++) { for (i = order; i < es->num_entries; i++) {
ep = exfat_get_dentry_cached(es, i); ep = exfat_get_dentry_cached(es, i);
if (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC) if (free_benign && (exfat_get_entry_type(ep) & TYPE_BENIGN_SEC))
exfat_free_benign_secondary_clusters(inode, ep); exfat_free_benign_secondary_clusters(inode, ep);
exfat_set_entry_type(ep, TYPE_DELETED); exfat_set_entry_type(ep, TYPE_DELETED);
@ -556,10 +594,11 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir
unsigned int off, clu = 0; unsigned int off, clu = 0;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
off = EXFAT_DEN_TO_B(entry); off = exfat_dentries_to_bytes(entry);
clu = p_dir->dir; clu = p_dir->dir;
ret = exfat_cluster_walk(sb, &clu, EXFAT_B_TO_CLU(off, sbi), p_dir->flags); ret = exfat_cluster_walk(sb, &clu, exfat_bytes_to_cluster(sbi, off),
p_dir->flags);
if (ret) if (ret)
return ret; return ret;
@ -567,7 +606,7 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir
exfat_fs_error(sb, exfat_fs_error(sb,
"unexpected early break in cluster chain (clu : %u, len : %d)", "unexpected early break in cluster chain (clu : %u, len : %d)",
p_dir->dir, p_dir->dir,
EXFAT_B_TO_CLU(off, sbi)); exfat_bytes_to_cluster(sbi, off));
return -EIO; return -EIO;
} }
@ -577,13 +616,13 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir
} }
/* byte offset in cluster */ /* byte offset in cluster */
off = EXFAT_CLU_OFFSET(off, sbi); off = exfat_cluster_offset(sbi, off);
/* byte offset in sector */ /* byte offset in sector */
*offset = EXFAT_BLK_OFFSET(off, sb); *offset = exfat_block_offset(sb, off);
/* sector offset in cluster */ /* sector offset in cluster */
*sector = EXFAT_B_TO_BLK(off, sb); *sector = exfat_bytes_to_block(sb, off);
*sector += exfat_cluster_to_sector(sbi, clu); *sector += exfat_cluster_to_sector(sbi, clu);
return 0; return 0;
} }
@ -593,7 +632,7 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb,
{ {
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
unsigned int sect_per_clus = sbi->sect_per_clus; unsigned int sect_per_clus = sbi->sect_per_clus;
unsigned int dentries_per_page = EXFAT_B_TO_DEN(PAGE_SIZE); unsigned int dentries_per_page = exfat_bytes_to_dentries(PAGE_SIZE);
int off; int off;
sector_t sec; sector_t sec;
@ -672,8 +711,8 @@ struct exfat_dentry *exfat_get_dentry_cached(
struct exfat_entry_set_cache *es, int num) struct exfat_entry_set_cache *es, int num)
{ {
int off = es->start_off + num * DENTRY_SIZE; int off = es->start_off + num * DENTRY_SIZE;
struct buffer_head *bh = es->bh[EXFAT_B_TO_BLK(off, es->sb)]; struct buffer_head *bh = es->bh[exfat_bytes_to_block(es->sb, off)];
char *p = bh->b_data + EXFAT_BLK_OFFSET(off, es->sb); char *p = bh->b_data + exfat_block_offset(es->sb, off);
return (struct exfat_dentry *)p; return (struct exfat_dentry *)p;
} }
@ -741,7 +780,7 @@ static int __exfat_get_dentry_set(struct exfat_entry_set_cache *es,
es->num_entries = num_entries; es->num_entries = num_entries;
num_bh = EXFAT_B_TO_BLK_ROUND_UP(off + num_entries * DENTRY_SIZE, sb); num_bh = exfat_bytes_to_block_round_up(sb, off + num_entries * DENTRY_SIZE);
if (num_bh > ARRAY_SIZE(es->__bh)) { if (num_bh > ARRAY_SIZE(es->__bh)) {
es->bh = kmalloc_objs(*es->bh, num_bh, GFP_NOFS); es->bh = kmalloc_objs(*es->bh, num_bh, GFP_NOFS);
if (!es->bh) { if (!es->bh) {
@ -830,7 +869,7 @@ static int exfat_validate_empty_dentry_set(struct exfat_entry_set_cache *es)
err_used_follow_unused: err_used_follow_unused:
off = es->start_off + (i << DENTRY_SIZE_BITS); off = es->start_off + (i << DENTRY_SIZE_BITS);
bh = es->bh[EXFAT_B_TO_BLK(off, es->sb)]; bh = es->bh[exfat_bytes_to_block(es->sb, off)];
exfat_fs_error(es->sb, exfat_fs_error(es->sb,
"in sector %lld, dentry %d should be unused, but 0x%x", "in sector %lld, dentry %d should be unused, but 0x%x",
@ -839,7 +878,8 @@ static int exfat_validate_empty_dentry_set(struct exfat_entry_set_cache *es)
return -EIO; return -EIO;
count_skip_entries: count_skip_entries:
es->num_entries = EXFAT_B_TO_DEN(EXFAT_BLK_TO_B(es->num_bh, es->sb) - es->start_off); es->num_entries =
exfat_bytes_to_dentries(exfat_block_to_bytes(es->sb, es->num_bh) - es->start_off);
for (; i < es->num_entries; i++) { for (; i < es->num_entries; i++) {
ep = exfat_get_dentry_cached(es, i); ep = exfat_get_dentry_cached(es, i);
if (IS_EXFAT_DELETED(ep->type)) if (IS_EXFAT_DELETED(ep->type))
@ -892,7 +932,7 @@ static inline void exfat_set_empty_hint(struct exfat_inode_info *ei,
{ {
if (ei->hint_femp.eidx == EXFAT_HINT_NONE || if (ei->hint_femp.eidx == EXFAT_HINT_NONE ||
ei->hint_femp.eidx > dentry) { ei->hint_femp.eidx > dentry) {
int total_entries = EXFAT_B_TO_DEN(i_size_read(&ei->vfs_inode)); int total_entries = exfat_bytes_to_dentries(i_size_read(&ei->vfs_inode));
if (candi_empty->count == 0) { if (candi_empty->count == 0) {
candi_empty->cur = *clu; candi_empty->cur = *clu;
@ -1027,22 +1067,26 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei,
continue; continue;
} }
brelse(bh);
if (entry_type == TYPE_EXTEND) { if (entry_type == TYPE_EXTEND) {
unsigned short entry_uniname[16], unichar; unsigned short entry_uniname[16], unichar;
unsigned int offset;
if (step != DIRENT_STEP_NAME || if (step != DIRENT_STEP_NAME ||
name_len >= MAX_NAME_LENGTH) { name_len >= MAX_NAME_LENGTH) {
brelse(bh);
step = DIRENT_STEP_FILE; step = DIRENT_STEP_FILE;
continue; continue;
} }
if (++order == 2) offset = (++order - 2) * EXFAT_FILE_NAME_LEN;
uniname = p_uniname->name;
else
uniname += EXFAT_FILE_NAME_LEN;
len = exfat_extract_uni_name(ep, entry_uniname); len = exfat_extract_uni_name(ep, entry_uniname);
brelse(bh);
if (offset > MAX_NAME_LENGTH ||
len > MAX_NAME_LENGTH - offset) {
step = DIRENT_STEP_FILE;
continue;
}
uniname = p_uniname->name + offset;
name_len += len; name_len += len;
unichar = *(uniname+len); unichar = *(uniname+len);
@ -1061,6 +1105,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei,
continue; continue;
} }
brelse(bh);
if (entry_type & if (entry_type &
(TYPE_CRITICAL_SEC | TYPE_BENIGN_SEC)) { (TYPE_CRITICAL_SEC | TYPE_BENIGN_SEC)) {
if (step == DIRENT_STEP_SECD) { if (step == DIRENT_STEP_SECD) {
@ -1215,7 +1260,7 @@ static int exfat_get_volume_label_dentry(struct super_block *sb,
es->bh = es->__bh; es->bh = es->__bh;
es->bh[0] = bh; es->bh[0] = bh;
es->num_bh = 1; es->num_bh = 1;
es->start_off = EXFAT_DEN_TO_B(i) % sb->s_blocksize; es->start_off = exfat_dentries_to_bytes(i) % sb->s_blocksize;
return 0; return 0;
} }

View File

@ -12,6 +12,7 @@
#include <linux/blkdev.h> #include <linux/blkdev.h>
#include <linux/backing-dev.h> #include <linux/backing-dev.h>
#include <uapi/linux/exfat.h> #include <uapi/linux/exfat.h>
#include <linux/buffer_head.h>
#define EXFAT_ROOT_INO 1 #define EXFAT_ROOT_INO 1
@ -84,38 +85,6 @@ enum {
(min_t(blkcnt_t, (sb)->s_bdi->ra_pages, (sb)->s_bdi->io_pages) \ (min_t(blkcnt_t, (sb)->s_bdi->ra_pages, (sb)->s_bdi->io_pages) \
<< (PAGE_SHIFT - (sb)->s_blocksize_bits)) << (PAGE_SHIFT - (sb)->s_blocksize_bits))
/*
* helpers for cluster size to byte conversion.
*/
#define EXFAT_CLU_TO_B(b, sbi) ((b) << (sbi)->cluster_size_bits)
#define EXFAT_B_TO_CLU(b, sbi) ((b) >> (sbi)->cluster_size_bits)
#define EXFAT_B_TO_CLU_ROUND_UP(b, sbi) \
(((b - 1) >> (sbi)->cluster_size_bits) + 1)
#define EXFAT_CLU_OFFSET(off, sbi) ((off) & ((sbi)->cluster_size - 1))
/*
* helpers for block size to byte conversion.
*/
#define EXFAT_BLK_TO_B(b, sb) ((b) << (sb)->s_blocksize_bits)
#define EXFAT_B_TO_BLK(b, sb) ((b) >> (sb)->s_blocksize_bits)
#define EXFAT_B_TO_BLK_ROUND_UP(b, sb) \
(((b - 1) >> (sb)->s_blocksize_bits) + 1)
#define EXFAT_BLK_OFFSET(off, sb) ((off) & ((sb)->s_blocksize - 1))
/*
* helpers for block size to dentry size conversion.
*/
#define EXFAT_B_TO_DEN(b) ((b) >> DENTRY_SIZE_BITS)
#define EXFAT_DEN_TO_B(b) ((b) << DENTRY_SIZE_BITS)
/*
* helpers for cluster size to dentry size conversion.
*/
#define EXFAT_CLU_TO_DEN(clu, sbi) \
((clu) << ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS))
#define EXFAT_DEN_TO_CLU(dentry, sbi) \
((dentry) >> ((sbi)->cluster_size_bits - DENTRY_SIZE_BITS))
/* /*
* helpers for fat entry. * helpers for fat entry.
*/ */
@ -149,7 +118,7 @@ enum {
* The 608 bytes are in 3 sectors at most (even 512 Byte sector). * The 608 bytes are in 3 sectors at most (even 512 Byte sector).
*/ */
#define DIR_CACHE_SIZE \ #define DIR_CACHE_SIZE \
(DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1) (DIV_ROUND_UP(ES_MAX_ENTRY_NUM << DENTRY_SIZE_BITS, SECTOR_SIZE) + 1)
/* Superblock flags */ /* Superblock flags */
#define EXFAT_FLAGS_SHUTDOWN 1 #define EXFAT_FLAGS_SHUTDOWN 1
@ -259,6 +228,7 @@ struct exfat_sb_info {
unsigned long long FAT1_start_sector; /* FAT1 start sector */ unsigned long long FAT1_start_sector; /* FAT1 start sector */
unsigned long long FAT2_start_sector; /* FAT2 start sector */ unsigned long long FAT2_start_sector; /* FAT2 start sector */
unsigned long long data_start_sector; /* data area start sector */ unsigned long long data_start_sector; /* data area start sector */
unsigned long long data_start_bytes;
unsigned int num_FAT_sectors; /* num of FAT sectors */ unsigned int num_FAT_sectors; /* num of FAT sectors */
unsigned int root_dir; /* root dir cluster */ unsigned int root_dir; /* root dir cluster */
unsigned int dentries_per_clu; /* num of dentries per cluster */ unsigned int dentries_per_clu; /* num of dentries per cluster */
@ -324,6 +294,8 @@ struct exfat_inode_info {
/* on-disk position of directory entry or 0 */ /* on-disk position of directory entry or 0 */
loff_t i_pos; loff_t i_pos;
loff_t valid_size; loff_t valid_size;
/* page-aligned size that has been zeroed out for mmap */
loff_t zeroed_size;
/* hash by i_location */ /* hash by i_location */
struct hlist_node i_hash_fat; struct hlist_node i_hash_fat;
/* protect bmap against truncate */ /* protect bmap against truncate */
@ -432,6 +404,101 @@ static inline loff_t exfat_ondisk_size(const struct inode *inode)
return ((loff_t)inode->i_blocks) << 9; return ((loff_t)inode->i_blocks) << 9;
} }
static inline loff_t exfat_cluster_to_phys_bytes(struct exfat_sb_info *sbi,
unsigned int clus)
{
return ((loff_t)(clus - EXFAT_RESERVED_CLUSTERS) << sbi->cluster_size_bits) +
sbi->data_start_bytes;
}
/*
* helpers for cluster size to byte conversion.
*/
static inline loff_t exfat_cluster_to_bytes(struct exfat_sb_info *sbi,
u32 nr_clusters)
{
return (loff_t)nr_clusters << sbi->cluster_size_bits;
}
static inline blkcnt_t exfat_cluster_to_sectors(struct exfat_sb_info *sbi,
u32 nr_clusters)
{
return (blkcnt_t)nr_clusters << (sbi->cluster_size_bits - 9);
}
static inline u32 exfat_bytes_to_cluster(struct exfat_sb_info *sbi, loff_t size)
{
return (u32)(size >> sbi->cluster_size_bits);
}
static inline u32 exfat_bytes_to_cluster_round_up(struct exfat_sb_info *sbi,
loff_t size)
{
if (size <= 0)
return 0;
return (u32)((size - 1) >> sbi->cluster_size_bits) + 1;
}
static inline u32 exfat_cluster_offset(struct exfat_sb_info *sbi, loff_t off)
{
return off & (sbi->cluster_size - 1);
}
/*
* helpers for block size to byte conversion.
*/
static inline loff_t exfat_block_to_bytes(struct super_block *sb,
sector_t block)
{
return (loff_t)block << sb->s_blocksize_bits;
}
static inline sector_t exfat_bytes_to_block(struct super_block *sb, loff_t size)
{
return (sector_t)(size >> sb->s_blocksize_bits);
}
static inline sector_t exfat_bytes_to_block_round_up(struct super_block *sb,
loff_t size)
{
if (size <= 0)
return 0;
return (sector_t)(((size - 1) >> sb->s_blocksize_bits) + 1);
}
static inline u32 exfat_block_offset(struct super_block *sb, loff_t off)
{
return (u32)(off & (sb->s_blocksize - 1));
}
/*
* helpers for block size to dentry size conversion.
*/
static inline u32 exfat_bytes_to_dentries(loff_t b)
{
return (u32)(b >> DENTRY_SIZE_BITS);
}
static inline u32 exfat_dentries_to_bytes(u32 dentry)
{
return dentry << DENTRY_SIZE_BITS;
}
/*
* helpers for cluster size to dentry size conversion.
*/
static inline u32 exfat_cluster_to_dentries(struct exfat_sb_info *sbi,
u32 nr_clusters)
{
return nr_clusters << (sbi->cluster_size_bits - DENTRY_SIZE_BITS);
}
static inline u32 exfat_dentries_to_cluster(struct exfat_sb_info *sbi,
u32 dentry)
{
return dentry >> (sbi->cluster_size_bits - DENTRY_SIZE_BITS);
}
/* super.c */ /* super.c */
int exfat_set_volume_dirty(struct super_block *sb); int exfat_set_volume_dirty(struct super_block *sb);
int exfat_clear_volume_dirty(struct super_block *sb); int exfat_clear_volume_dirty(struct super_block *sb);
@ -441,7 +508,7 @@ int exfat_clear_volume_dirty(struct super_block *sb);
exfat_cluster_walk(sb, (pclu), 1, ALLOC_FAT_CHAIN) exfat_cluster_walk(sb, (pclu), 1, ALLOC_FAT_CHAIN)
int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
struct exfat_chain *p_chain, bool sync_bmap); struct exfat_chain *p_chain, bool sync_bmap, bool contig);
int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain); int exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain);
int exfat_ent_get(struct super_block *sb, unsigned int loc, int exfat_ent_get(struct super_block *sb, unsigned int loc,
unsigned int *content, struct buffer_head **last); unsigned int *content, struct buffer_head **last);
@ -490,7 +557,6 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range);
/* file.c */ /* file.c */
extern const struct file_operations exfat_file_operations; extern const struct file_operations exfat_file_operations;
int __exfat_truncate(struct inode *inode); int __exfat_truncate(struct inode *inode);
void exfat_truncate(struct inode *inode);
int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry, int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr); struct iattr *attr);
int exfat_getattr(struct mnt_idmap *idmap, const struct path *path, int exfat_getattr(struct mnt_idmap *idmap, const struct path *path,
@ -526,9 +592,10 @@ void exfat_init_dir_entry(struct exfat_entry_set_cache *es,
unsigned int type, unsigned int start_clu, unsigned int type, unsigned int start_clu,
unsigned long long size, struct timespec64 *ts); unsigned long long size, struct timespec64 *ts);
void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries, void exfat_init_ext_entry(struct exfat_entry_set_cache *es, int num_entries,
struct exfat_uni_name *p_uniname); struct exfat_uni_name *p_uniname,
struct exfat_entry_set_cache *old_es, int num_extra);
void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es, void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
int order); int order, bool free_benign);
void exfat_update_dir_chksum(struct exfat_entry_set_cache *es); void exfat_update_dir_chksum(struct exfat_entry_set_cache *es);
int exfat_calc_num_entries(struct exfat_uni_name *p_uniname); int exfat_calc_num_entries(struct exfat_uni_name *p_uniname);
int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei, int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei,
@ -586,7 +653,9 @@ struct inode *exfat_iget(struct super_block *sb, loff_t i_pos);
int __exfat_write_inode(struct inode *inode, int sync); int __exfat_write_inode(struct inode *inode, int sync);
int exfat_write_inode(struct inode *inode, struct writeback_control *wbc); int exfat_write_inode(struct inode *inode, struct writeback_control *wbc);
void exfat_evict_inode(struct inode *inode); void exfat_evict_inode(struct inode *inode);
int exfat_block_truncate_page(struct inode *inode, loff_t from); int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
unsigned int *clu, unsigned int *count, int create,
bool *balloc);
/* exfat/nls.c */ /* exfat/nls.c */
unsigned short exfat_toupper(struct super_block *sb, unsigned short a); unsigned short exfat_toupper(struct super_block *sb, unsigned short a);

View File

@ -412,14 +412,14 @@ int exfat_zeroed_cluster(struct inode *dir, unsigned int clu)
if (IS_DIRSYNC(dir)) if (IS_DIRSYNC(dir))
return sync_blockdev_range(sb->s_bdev, return sync_blockdev_range(sb->s_bdev,
EXFAT_BLK_TO_B(blknr, sb), exfat_block_to_bytes(sb, blknr),
EXFAT_BLK_TO_B(last_blknr, sb) - 1); exfat_block_to_bytes(sb, last_blknr) - 1);
return 0; return 0;
} }
int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc, int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
struct exfat_chain *p_chain, bool sync_bmap) struct exfat_chain *p_chain, bool sync_bmap, bool contig)
{ {
int ret = -ENOSPC; int ret = -ENOSPC;
unsigned int total_cnt; unsigned int total_cnt;
@ -470,14 +470,20 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
while ((new_clu = exfat_find_free_bitmap(sb, hint_clu)) != while ((new_clu = exfat_find_free_bitmap(sb, hint_clu)) !=
EXFAT_EOF_CLUSTER) { EXFAT_EOF_CLUSTER) {
if (new_clu != hint_clu && if (new_clu != hint_clu) {
p_chain->flags == ALLOC_NO_FAT_CHAIN) { if (p_chain->flags == ALLOC_NO_FAT_CHAIN) {
if (exfat_chain_cont_cluster(sb, p_chain->dir, if (exfat_chain_cont_cluster(sb, p_chain->dir,
p_chain->size)) { p_chain->size)) {
ret = -EIO; ret = -EIO;
goto free_cluster; goto free_cluster;
}
p_chain->flags = ALLOC_FAT_CHAIN;
}
if (contig && p_chain->size > 0) {
hint_clu = last_clu;
goto done;
} }
p_chain->flags = ALLOC_FAT_CHAIN;
} }
/* update allocation bitmap */ /* update allocation bitmap */
@ -507,9 +513,9 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
last_clu = new_clu; last_clu = new_clu;
if (p_chain->size == num_alloc) { if (p_chain->size == num_alloc) {
done:
sbi->clu_srch_ptr = hint_clu; sbi->clu_srch_ptr = hint_clu;
sbi->used_clusters += num_alloc; sbi->used_clusters += p_chain->size;
mutex_unlock(&sbi->bitmap_lock); mutex_unlock(&sbi->bitmap_lock);
return 0; return 0;
} }

View File

@ -15,9 +15,11 @@
#include <linux/filelock.h> #include <linux/filelock.h>
#include <linux/falloc.h> #include <linux/falloc.h>
#include <linux/fileattr.h> #include <linux/fileattr.h>
#include <linux/iomap.h>
#include "exfat_raw.h" #include "exfat_raw.h"
#include "exfat_fs.h" #include "exfat_fs.h"
#include "iomap.h"
static int exfat_cont_expand(struct inode *inode, loff_t size) static int exfat_cont_expand(struct inode *inode, loff_t size)
{ {
@ -27,16 +29,17 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_chain clu; struct exfat_chain clu;
loff_t oldsize = i_size_read(inode);
truncate_pagecache(inode, i_size_read(inode)); truncate_pagecache(inode, oldsize);
ret = inode_newsize_ok(inode, size); ret = inode_newsize_ok(inode, size);
if (ret) if (ret)
return ret; return ret;
num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi); num_clusters = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
/* integer overflow is already checked in inode_newsize_ok(). */ /* integer overflow is already checked in inode_newsize_ok(). */
new_num_clusters = EXFAT_B_TO_CLU_ROUND_UP(size, sbi); new_num_clusters = exfat_bytes_to_cluster_round_up(sbi, size);
if (new_num_clusters == num_clusters) if (new_num_clusters == num_clusters)
goto out; goto out;
@ -57,7 +60,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
clu.flags = ei->flags; clu.flags = ei->flags;
ret = exfat_alloc_cluster(inode, new_num_clusters - num_clusters, ret = exfat_alloc_cluster(inode, new_num_clusters - num_clusters,
&clu, inode_needs_sync(inode)); &clu, inode_needs_sync(inode), false);
if (ret) if (ret)
return ret; return ret;
@ -79,6 +82,13 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode)); inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
/* Expanded range not zeroed, do not update valid_size */ /* Expanded range not zeroed, do not update valid_size */
i_size_write(inode, 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.
*/
pagecache_isize_extended(inode, oldsize, inode->i_size);
inode->i_blocks = round_up(size, sbi->cluster_size) >> 9; inode->i_blocks = round_up(size, sbi->cluster_size) >> 9;
mark_inode_dirty(inode); mark_inode_dirty(inode);
@ -201,8 +211,8 @@ int __exfat_truncate(struct inode *inode)
exfat_set_volume_dirty(sb); exfat_set_volume_dirty(sb);
num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi); num_clusters_new = exfat_bytes_to_cluster_round_up(sbi, i_size_read(inode));
num_clusters_phys = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi); num_clusters_phys = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags); exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags);
@ -237,7 +247,7 @@ int __exfat_truncate(struct inode *inode)
} }
if (i_size_read(inode) < ei->valid_size) if (i_size_read(inode) < ei->valid_size)
ei->valid_size = i_size_read(inode); ei->valid_size = ei->zeroed_size = i_size_read(inode);
if (ei->type == TYPE_FILE) if (ei->type == TYPE_FILE)
ei->attr |= EXFAT_ATTR_ARCHIVE; ei->attr |= EXFAT_ATTR_ARCHIVE;
@ -283,7 +293,7 @@ int __exfat_truncate(struct inode *inode)
return 0; return 0;
} }
void exfat_truncate(struct inode *inode) static void exfat_truncate(struct inode *inode)
{ {
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
@ -396,10 +406,12 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
exfat_truncate_inode_atime(inode); exfat_truncate_inode_atime(inode);
if (attr->ia_valid & ATTR_SIZE) { if (attr->ia_valid & ATTR_SIZE) {
error = exfat_block_truncate_page(inode, attr->ia_size); /*
if (error) * Wait for any in-flight DIO to finish before truncating to
goto out; * prevent a concurrent DIO from writing to clusters that are
* about to be freed.
*/
inode_dio_wait(inode);
down_write(&EXFAT_I(inode)->truncate_lock); down_write(&EXFAT_I(inode)->truncate_lock);
truncate_setsize(inode, attr->ia_size); truncate_setsize(inode, attr->ia_size);
@ -644,42 +656,76 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size) static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size)
{ {
int err;
loff_t pos;
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
struct address_space *mapping = inode->i_mapping; loff_t old_valid_size = ei->valid_size;
const struct address_space_operations *ops = mapping->a_ops; int ret = 0;
pos = ei->valid_size; if (old_valid_size < new_valid_size) {
while (pos < new_valid_size) { if (i_size_read(inode) < new_valid_size) {
u32 len; i_size_write(inode, new_valid_size);
struct folio *folio; mark_inode_dirty(inode);
unsigned long off; }
len = PAGE_SIZE - (pos & (PAGE_SIZE - 1)); ret = iomap_zero_range(inode, old_valid_size,
if (pos + len > new_valid_size) new_valid_size - old_valid_size, NULL,
len = new_valid_size - pos; &exfat_write_iomap_ops, NULL, NULL);
if (ret) {
err = ops->write_begin(NULL, mapping, pos, len, &folio, NULL); truncate_setsize(inode, old_valid_size);
if (err) exfat_truncate(inode);
goto out; }
off = offset_in_folio(folio, pos);
folio_zero_new_buffers(folio, off, off + len);
err = ops->write_end(NULL, mapping, pos, len, len, folio, NULL);
if (err < 0)
goto out;
pos += len;
balance_dirty_pages_ratelimited(mapping);
cond_resched();
} }
return 0; return ret;
}
out: static ssize_t exfat_fallback_buffered_write(struct kiocb *iocb,
return err; struct iov_iter *from)
{
loff_t offset = iocb->ki_pos, end;
ssize_t written;
int ret;
iocb->ki_flags &= ~IOCB_DIRECT;
written = iomap_file_buffered_write(iocb, from, &exfat_write_iomap_ops,
NULL, NULL);
if (written < 0)
return written;
end = iocb->ki_pos + written - 1;
ret = filemap_write_and_wait_range(iocb->ki_filp->f_mapping,
offset, end);
if (ret)
return -EIO;
invalidate_mapping_pages(iocb->ki_filp->f_mapping,
offset >> PAGE_SHIFT,
end >> PAGE_SHIFT);
return written;
}
static ssize_t exfat_dio_write_iter(struct kiocb *iocb, struct iov_iter *from)
{
ssize_t ret;
ret = iomap_dio_rw(iocb, from, &exfat_write_iomap_ops,
&exfat_write_dio_ops, 0, NULL, 0);
if (ret == -ENOTBLK)
ret = 0;
else if (ret < 0)
return ret;
if (iov_iter_count(from)) {
ssize_t written;
written = exfat_fallback_buffered_write(iocb, from);
if (written < 0)
return written;
ret += written;
}
return ret;
} }
static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter) static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
@ -690,6 +736,7 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t pos = iocb->ki_pos; loff_t pos = iocb->ki_pos;
loff_t valid_size; loff_t valid_size;
int err;
if (unlikely(exfat_forced_shutdown(inode->i_sb))) if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO; return -EIO;
@ -705,14 +752,10 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
if (ret <= 0) if (ret <= 0)
goto unlock; goto unlock;
if (iocb->ki_flags & IOCB_DIRECT) { err = file_modified(iocb->ki_filp);
unsigned long align = pos | iov_iter_alignment(iter); if (err) {
ret = err;
if (!IS_ALIGNED(align, i_blocksize(inode)) && goto unlock;
!IS_ALIGNED(align, bdev_logical_block_size(inode->i_sb->s_bdev))) {
ret = -EINVAL;
goto unlock;
}
} }
if (pos > valid_size) { if (pos > valid_size) {
@ -726,7 +769,11 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
goto unlock; goto unlock;
} }
ret = __generic_file_write_iter(iocb, iter); if (iocb->ki_flags & IOCB_DIRECT)
ret = exfat_dio_write_iter(iocb, iter);
else
ret = iomap_file_buffered_write(iocb, iter,
&exfat_write_iomap_ops, NULL, NULL);
if (ret < 0) if (ret < 0)
goto unlock; goto unlock;
@ -753,37 +800,78 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
static ssize_t exfat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter) static ssize_t exfat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{ {
struct inode *inode = file_inode(iocb->ki_filp); struct inode *inode = file_inode(iocb->ki_filp);
ssize_t ret;
if (unlikely(exfat_forced_shutdown(inode->i_sb))) if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO; return -EIO;
return generic_file_read_iter(iocb, iter); inode_lock_shared(inode);
if (iocb->ki_flags & IOCB_DIRECT) {
file_accessed(iocb->ki_filp);
ret = iomap_dio_rw(iocb, iter, &exfat_iomap_ops, NULL, 0,
NULL, 0);
} else {
ret = generic_file_read_iter(iocb, iter);
}
inode_unlock_shared(inode);
return ret;
} }
static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf) static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
{ {
int err;
struct inode *inode = file_inode(vmf->vma->vm_file); struct inode *inode = file_inode(vmf->vma->vm_file);
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t new_valid_size; vm_fault_t ret;
loff_t new_valid_size, mmap_valid_size;
if (!inode_trylock(inode)) if (!inode_trylock(inode))
return VM_FAULT_RETRY; return VM_FAULT_RETRY;
new_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT; mmap_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT;
new_valid_size = min(new_valid_size, i_size_read(inode)); new_valid_size = min(mmap_valid_size, i_size_read(inode));
if (ei->valid_size < new_valid_size) { if (ei->valid_size < new_valid_size) {
err = exfat_extend_valid_size(inode, new_valid_size); if (ei->zeroed_size < mmap_valid_size) {
if (err < 0) { int err;
inode_unlock(inode);
return vmf_fs_error(err); /*
* Only zero the range that hasn't been zeroed yet for
* this mmap write path. zeroed_size tracks the largest
* page-aligned offset that has already been zeroed.
*
* This prevents unnecessarily zeroing out the entire
* tail page on every page fault when userspace writes
* data byte-by-byte through mmap (after a small
* fallocate). It fixes data corruption in the tail page
* while preserving the existing valid_size semantics.
*/
err = iomap_zero_range(inode, ei->zeroed_size,
mmap_valid_size - ei->zeroed_size, NULL,
&exfat_iomap_ops, NULL, NULL);
if (err < 0) {
inode_unlock(inode);
return vmf_fs_error(err);
}
ei->zeroed_size = mmap_valid_size;
} }
ei->valid_size = new_valid_size;
mark_inode_dirty(inode);
} }
sb_start_pagefault(inode->i_sb);
file_update_time(vmf->vma->vm_file);
filemap_invalidate_lock_shared(inode->i_mapping);
ret = iomap_page_mkwrite(vmf, &exfat_write_iomap_ops, NULL);
filemap_invalidate_unlock_shared(inode->i_mapping);
sb_end_pagefault(inode->i_sb);
inode_unlock(inode); inode_unlock(inode);
return filemap_page_mkwrite(vmf); return ret;
} }
static const struct vm_operations_struct exfat_file_vm_ops = { static const struct vm_operations_struct exfat_file_vm_ops = {
@ -799,6 +887,21 @@ static int exfat_file_mmap_prepare(struct vm_area_desc *desc)
if (unlikely(exfat_forced_shutdown(file_inode(desc->file)->i_sb))) if (unlikely(exfat_forced_shutdown(file_inode(desc->file)->i_sb)))
return -EIO; return -EIO;
if (vma_desc_test_all(desc, VMA_SHARED_BIT, VMA_MAYWRITE_BIT)) {
struct inode *inode = file_inode(file);
loff_t from, to;
int err;
from = ((loff_t)desc->pgoff << PAGE_SHIFT);
to = min_t(loff_t, i_size_read(inode),
from + vma_desc_size(desc));
if (EXFAT_I(inode)->valid_size < to) {
err = exfat_extend_valid_size(inode, to);
if (err)
return err;
}
}
file_accessed(file); file_accessed(file);
desc->vm_ops = &exfat_file_vm_ops; desc->vm_ops = &exfat_file_vm_ops;
return 0; return 0;
@ -813,8 +916,48 @@ static ssize_t exfat_splice_read(struct file *in, loff_t *ppos,
return filemap_splice_read(in, ppos, pipe, len, flags); return filemap_splice_read(in, ppos, pipe, len, flags);
} }
static int exfat_file_open(struct inode *inode, struct file *filp)
{
int err;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
err = generic_file_open(inode, filp);
if (err)
return err;
filp->f_mode |= FMODE_CAN_ODIRECT;
return 0;
}
static loff_t exfat_file_llseek(struct file *file, loff_t offset, int whence)
{
struct inode *inode = file->f_mapping->host;
switch (whence) {
case SEEK_HOLE:
inode_lock_shared(inode);
offset = iomap_seek_hole(inode, offset, &exfat_iomap_ops);
inode_unlock_shared(inode);
break;
case SEEK_DATA:
inode_lock_shared(inode);
offset = iomap_seek_data(inode, offset, &exfat_iomap_ops);
inode_unlock_shared(inode);
break;
default:
return generic_file_llseek(file, offset, whence);
}
if (offset < 0)
return offset;
return vfs_setpos(file, offset, inode->i_sb->s_maxbytes);
}
const struct file_operations exfat_file_operations = { const struct file_operations exfat_file_operations = {
.llseek = generic_file_llseek, .open = exfat_file_open,
.llseek = exfat_file_llseek,
.read_iter = exfat_file_read_iter, .read_iter = exfat_file_read_iter,
.write_iter = exfat_file_write_iter, .write_iter = exfat_file_write_iter,
.unlocked_ioctl = exfat_ioctl, .unlocked_ioctl = exfat_ioctl,

View File

@ -13,13 +13,16 @@
#include <linux/uio.h> #include <linux/uio.h>
#include <linux/random.h> #include <linux/random.h>
#include <linux/iversion.h> #include <linux/iversion.h>
#include <linux/iomap.h>
#include "exfat_raw.h" #include "exfat_raw.h"
#include "exfat_fs.h" #include "exfat_fs.h"
#include "iomap.h"
int __exfat_write_inode(struct inode *inode, int sync) int __exfat_write_inode(struct inode *inode, int sync)
{ {
unsigned long long on_disk_size; unsigned long long on_disk_size;
unsigned long long on_disk_valid_size;
struct exfat_dentry *ep, *ep2; struct exfat_dentry *ep, *ep2;
struct exfat_entry_set_cache es; struct exfat_entry_set_cache es;
struct super_block *sb = inode->i_sb; struct super_block *sb = inode->i_sb;
@ -69,22 +72,30 @@ int __exfat_write_inode(struct inode *inode, int sync)
&ep->dentry.file.access_date, &ep->dentry.file.access_date,
NULL); NULL);
/* File size should be zero if there is no cluster allocated */ /*
on_disk_size = i_size_read(inode); * During a DIO write, valid_size is updated eagerly in iomap_end (so
* that concurrent buffered reads see IOMAP_MAPPED) while i_size is
* updated asynchronously in end_io. The FAT chain was already
* extended to cover ceil(valid_size/cluster_size) clusters. Use the
* maximum so the on-disk size field always covers the FAT chain,
* preventing fsck from reporting "more clusters are allocated".
*/
on_disk_size = max_t(unsigned long long, i_size_read(inode),
ei->valid_size);
if (ei->start_clu == EXFAT_EOF_CLUSTER) if (ei->start_clu == EXFAT_EOF_CLUSTER)
on_disk_size = 0; on_disk_size = 0;
/*
* 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,
i_size_read(inode));
if (ei->start_clu == EXFAT_EOF_CLUSTER)
on_disk_valid_size = 0;
ep2->dentry.stream.size = cpu_to_le64(on_disk_size); ep2->dentry.stream.size = cpu_to_le64(on_disk_size);
/* ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_valid_size);
* mmap write does not use exfat_write_end(), valid_size may be
* extended to the sector-aligned length in exfat_get_block().
* So we need to fixup valid_size to the writren length.
*/
if (on_disk_size < ei->valid_size)
ep2->dentry.stream.valid_size = ep2->dentry.stream.size;
else
ep2->dentry.stream.valid_size = cpu_to_le64(ei->valid_size);
if (on_disk_size) { if (on_disk_size) {
ep2->dentry.stream.flags = ei->flags; ep2->dentry.stream.flags = ei->flags;
@ -123,8 +134,9 @@ void exfat_sync_inode(struct inode *inode)
* Output: errcode, cluster number * Output: errcode, cluster number
* *clu = (~0), if it's unable to allocate a new cluster * *clu = (~0), if it's unable to allocate a new cluster
*/ */
static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset, int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
unsigned int *clu, unsigned int *count, int create) unsigned int *clu, unsigned int *count, int create,
bool *balloc)
{ {
int ret; int ret;
unsigned int last_clu; unsigned int last_clu;
@ -135,10 +147,10 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
unsigned int local_clu_offset = clu_offset; unsigned int local_clu_offset = clu_offset;
unsigned int num_to_be_allocated = 0, num_clusters; unsigned int num_to_be_allocated = 0, num_clusters;
num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi); num_clusters = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
if (clu_offset > num_clusters ||
if (clu_offset >= num_clusters) *count > num_clusters - clu_offset)
num_to_be_allocated = clu_offset - num_clusters + 1; num_to_be_allocated = clu_offset + *count - num_clusters;
if (!create && (num_to_be_allocated > 0)) { if (!create && (num_to_be_allocated > 0)) {
*clu = EXFAT_EOF_CLUSTER; *clu = EXFAT_EOF_CLUSTER;
@ -181,7 +193,7 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
} }
ret = exfat_alloc_cluster(inode, num_to_be_allocated, &new_clu, ret = exfat_alloc_cluster(inode, num_to_be_allocated, &new_clu,
inode_needs_sync(inode)); inode_needs_sync(inode), true);
if (ret) if (ret)
return ret; return ret;
@ -215,19 +227,11 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
} }
*clu = new_clu.dir; *clu = new_clu.dir;
*count = new_clu.size;
inode->i_blocks += EXFAT_CLU_TO_B(num_to_be_allocated, sbi) >> 9; inode->i_blocks += exfat_cluster_to_sectors(sbi, new_clu.size);
if (balloc)
/* *balloc = true;
* Move *clu pointer along FAT chains (hole care) because the
* caller of this function expect *clu to be the last cluster.
* This only works when num_to_be_allocated >= 2,
* *clu = (the first cluster of the allocated chain) =>
* (the last cluster of ...)
*/
if (exfat_cluster_walk(sb, clu, num_to_be_allocated - 1, ei->flags))
return -EIO;
*count = 1;
} }
/* hint information */ /* hint information */
@ -237,154 +241,15 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
return 0; return 0;
} }
static int exfat_get_block(struct inode *inode, sector_t iblock,
struct buffer_head *bh_result, int create)
{
struct exfat_inode_info *ei = EXFAT_I(inode);
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
unsigned long max_blocks = bh_result->b_size >> inode->i_blkbits;
int err = 0;
unsigned long mapped_blocks = 0;
unsigned int cluster, sec_offset, count;
sector_t last_block;
sector_t phys = 0;
sector_t valid_blks;
loff_t i_size;
mutex_lock(&sbi->s_lock);
i_size = i_size_read(inode);
last_block = EXFAT_B_TO_BLK_ROUND_UP(i_size, sb);
if (iblock >= last_block && !create)
goto done;
/* Is this block already allocated? */
count = EXFAT_B_TO_CLU_ROUND_UP(bh_result->b_size, sbi);
err = exfat_map_cluster(inode, iblock >> sbi->sect_per_clus_bits,
&cluster, &count, create);
if (err) {
if (err != -ENOSPC)
exfat_fs_error_ratelimit(sb,
"failed to bmap (inode : %p iblock : %llu, err : %d)",
inode, (unsigned long long)iblock, err);
goto unlock_ret;
}
if (cluster == EXFAT_EOF_CLUSTER)
goto done;
/* sector offset in cluster */
sec_offset = iblock & (sbi->sect_per_clus - 1);
phys = exfat_cluster_to_sector(sbi, cluster) + sec_offset;
mapped_blocks = ((unsigned long)count << sbi->sect_per_clus_bits) - sec_offset;
max_blocks = min(mapped_blocks, max_blocks);
map_bh(bh_result, sb, phys);
if (buffer_delay(bh_result))
clear_buffer_delay(bh_result);
/*
* In most cases, we just need to set bh_result to mapped, unmapped
* or new status as follows:
* 1. i_size == valid_size
* 2. write case (create == 1)
* 3. direct_read (!bh_result->b_folio)
* -> the unwritten part will be zeroed in exfat_direct_IO()
*
* Otherwise, in the case of buffered read, it is necessary to take
* care the last nested block if valid_size is not equal to i_size.
*/
if (i_size == ei->valid_size || create || !bh_result->b_folio)
valid_blks = EXFAT_B_TO_BLK_ROUND_UP(ei->valid_size, sb);
else
valid_blks = EXFAT_B_TO_BLK(ei->valid_size, sb);
/* The range has been fully written, map it */
if (iblock + max_blocks < valid_blks)
goto done;
/* The range has been partially written, map the written part */
if (iblock < valid_blks) {
max_blocks = valid_blks - iblock;
goto done;
}
/* The area has not been written, map and mark as new for create case */
if (create) {
set_buffer_new(bh_result);
ei->valid_size = EXFAT_BLK_TO_B(iblock + max_blocks, sb);
mark_inode_dirty(inode);
goto done;
}
/*
* The area has just one block partially written.
* In that case, we should read and fill the unwritten part of
* a block with zero.
*/
if (bh_result->b_folio && iblock == valid_blks &&
(ei->valid_size & (sb->s_blocksize - 1))) {
loff_t size, pos;
void *addr;
max_blocks = 1;
/*
* No buffer_head is allocated.
* (1) bmap: It's enough to set blocknr without I/O.
* (2) read: The unwritten part should be filled with zero.
* If a folio does not have any buffers,
* let's returns -EAGAIN to fallback to
* block_read_full_folio() for per-bh IO.
*/
if (!folio_buffers(bh_result->b_folio)) {
err = -EAGAIN;
goto done;
}
pos = EXFAT_BLK_TO_B(iblock, sb);
size = ei->valid_size - pos;
addr = folio_address(bh_result->b_folio) +
offset_in_folio(bh_result->b_folio, pos);
/* Check if bh->b_data points to proper addr in folio */
if (bh_result->b_data != addr) {
exfat_fs_error_ratelimit(sb,
"b_data(%p) != folio_addr(%p)",
bh_result->b_data, addr);
err = -EINVAL;
goto done;
}
/* Read a block */
err = bh_read(bh_result, 0);
if (err < 0)
goto done;
/* Zero unwritten part of a block */
memset(bh_result->b_data + size, 0, bh_result->b_size - size);
err = 0;
goto done;
}
/*
* The area has not been written, clear mapped for read/bmap cases.
* If so, it will be filled with zero without reading from disk.
*/
clear_buffer_mapped(bh_result);
done:
bh_result->b_size = EXFAT_BLK_TO_B(max_blocks, sb);
if (err < 0)
clear_buffer_mapped(bh_result);
unlock_ret:
mutex_unlock(&sbi->s_lock);
return err;
}
static int exfat_read_folio(struct file *file, struct folio *folio) static int exfat_read_folio(struct file *file, struct folio *folio)
{ {
return mpage_read_folio(folio, exfat_get_block); struct iomap_read_folio_ctx ctx = {
.cur_folio = folio,
.ops = &exfat_iomap_bio_read_ops,
};
iomap_read_folio(&exfat_iomap_ops, &ctx, NULL);
return 0;
} }
static void exfat_readahead(struct readahead_control *rac) static void exfat_readahead(struct readahead_control *rac)
@ -393,6 +258,10 @@ static void exfat_readahead(struct readahead_control *rac)
struct inode *inode = mapping->host; struct inode *inode = mapping->host;
struct exfat_inode_info *ei = EXFAT_I(inode); struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t pos = readahead_pos(rac); loff_t pos = readahead_pos(rac);
struct iomap_read_folio_ctx ctx = {
.ops = &exfat_iomap_bio_read_ops,
.rac = rac,
};
/* Range cross valid_size, read it page by page. */ /* Range cross valid_size, read it page by page. */
if (ei->valid_size < i_size_read(inode) && if (ei->valid_size < i_size_read(inode) &&
@ -400,115 +269,22 @@ static void exfat_readahead(struct readahead_control *rac)
ei->valid_size < pos + readahead_length(rac)) ei->valid_size < pos + readahead_length(rac))
return; return;
mpage_readahead(rac, exfat_get_block); iomap_readahead(&exfat_iomap_ops, &ctx, NULL);
} }
static int exfat_writepages(struct address_space *mapping, static int exfat_writepages(struct address_space *mapping,
struct writeback_control *wbc) struct writeback_control *wbc)
{ {
if (unlikely(exfat_forced_shutdown(mapping->host->i_sb))) struct iomap_writepage_ctx wpc = {
return -EIO; .inode = mapping->host,
.wbc = wbc,
return mpage_writepages(mapping, wbc, exfat_get_block); .ops = &exfat_writeback_ops,
} };
static void exfat_write_failed(struct address_space *mapping, loff_t to)
{
struct inode *inode = mapping->host;
if (to > i_size_read(inode)) {
truncate_pagecache(inode, i_size_read(inode));
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
exfat_truncate(inode);
}
}
static int exfat_write_begin(const struct kiocb *iocb,
struct address_space *mapping,
loff_t pos, unsigned int len,
struct folio **foliop, void **fsdata)
{
int ret;
if (unlikely(exfat_forced_shutdown(mapping->host->i_sb))) if (unlikely(exfat_forced_shutdown(mapping->host->i_sb)))
return -EIO; return -EIO;
ret = block_write_begin(mapping, pos, len, foliop, exfat_get_block); return iomap_writepages(&wpc);
if (ret < 0)
exfat_write_failed(mapping, pos+len);
return ret;
}
static int exfat_write_end(const struct kiocb *iocb,
struct address_space *mapping,
loff_t pos, unsigned int len, unsigned int copied,
struct folio *folio, void *fsdata)
{
struct inode *inode = mapping->host;
struct exfat_inode_info *ei = EXFAT_I(inode);
int err;
err = generic_write_end(iocb, mapping, pos, len, copied, folio, fsdata);
if (err < len)
exfat_write_failed(mapping, pos+len);
if (!(err < 0) && pos + err > ei->valid_size) {
ei->valid_size = pos + err;
mark_inode_dirty(inode);
}
if (!(err < 0) && !(ei->attr & EXFAT_ATTR_ARCHIVE)) {
inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
ei->attr |= EXFAT_ATTR_ARCHIVE;
mark_inode_dirty(inode);
}
return err;
}
static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
{
struct address_space *mapping = iocb->ki_filp->f_mapping;
struct inode *inode = mapping->host;
struct exfat_inode_info *ei = EXFAT_I(inode);
loff_t pos = iocb->ki_pos;
loff_t size = pos + iov_iter_count(iter);
int rw = iov_iter_rw(iter);
ssize_t ret;
/*
* Need to use the DIO_LOCKING for avoiding the race
* condition of exfat_get_block() and ->truncate().
*/
ret = blockdev_direct_IO(iocb, inode, iter, exfat_get_block);
if (ret < 0) {
if (rw == WRITE && ret != -EIOCBQUEUED)
exfat_write_failed(mapping, size);
return ret;
}
size = pos + ret;
if (rw == WRITE) {
/*
* If the block had been partially written before this write,
* ->valid_size will not be updated in exfat_get_block(),
* update it here.
*/
if (ei->valid_size < size) {
ei->valid_size = size;
mark_inode_dirty(inode);
}
} else if (pos < ei->valid_size && ei->valid_size < size) {
/* zero the unwritten part in the partially written block */
iov_iter_revert(iter, size - ei->valid_size);
iov_iter_zero(size - ei->valid_size, iter);
}
return ret;
} }
static sector_t exfat_aop_bmap(struct address_space *mapping, sector_t block) static sector_t exfat_aop_bmap(struct address_space *mapping, sector_t block)
@ -517,34 +293,23 @@ static sector_t exfat_aop_bmap(struct address_space *mapping, sector_t block)
/* exfat_get_cluster() assumes the requested blocknr isn't truncated. */ /* exfat_get_cluster() assumes the requested blocknr isn't truncated. */
down_read(&EXFAT_I(mapping->host)->truncate_lock); down_read(&EXFAT_I(mapping->host)->truncate_lock);
blocknr = generic_block_bmap(mapping, block, exfat_get_block); blocknr = iomap_bmap(mapping, block, &exfat_iomap_ops);
up_read(&EXFAT_I(mapping->host)->truncate_lock); up_read(&EXFAT_I(mapping->host)->truncate_lock);
return blocknr; return blocknr;
} }
/*
* exfat_block_truncate_page() zeroes out a mapping from file offset `from'
* up to the end of the block which corresponds to `from'.
* This is required during truncate to physically zeroout the tail end
* of that block so it doesn't yield old data if the file is later grown.
* Also, avoid causing failure from fsx for cases of "data past EOF"
*/
int exfat_block_truncate_page(struct inode *inode, loff_t from)
{
return block_truncate_page(inode->i_mapping, from, exfat_get_block);
}
static const struct address_space_operations exfat_aops = { static const struct address_space_operations exfat_aops = {
.dirty_folio = block_dirty_folio, .read_folio = exfat_read_folio,
.invalidate_folio = block_invalidate_folio, .readahead = exfat_readahead,
.read_folio = exfat_read_folio, .writepages = exfat_writepages,
.readahead = exfat_readahead, .dirty_folio = iomap_dirty_folio,
.writepages = exfat_writepages, .bmap = exfat_aop_bmap,
.write_begin = exfat_write_begin, .migrate_folio = filemap_migrate_folio,
.write_end = exfat_write_end, .is_partially_uptodate = iomap_is_partially_uptodate,
.direct_IO = exfat_direct_IO, .error_remove_folio = generic_error_remove_folio,
.bmap = exfat_aop_bmap, .release_folio = iomap_release_folio,
.migrate_folio = buffer_migrate_folio, .invalidate_folio = iomap_invalidate_folio,
.swap_activate = exfat_iomap_swap_activate,
}; };
static inline unsigned long exfat_hash(loff_t i_pos) static inline unsigned long exfat_hash(loff_t i_pos)
@ -608,6 +373,7 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info)
ei->flags = info->flags; ei->flags = info->flags;
ei->type = info->type; ei->type = info->type;
ei->valid_size = info->valid_size; ei->valid_size = info->valid_size;
ei->zeroed_size = info->valid_size;
ei->version = 0; ei->version = 0;
ei->hint_stat.eidx = 0; ei->hint_stat.eidx = 0;

271
fs/exfat/iomap.c Normal file
View File

@ -0,0 +1,271 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* iomap callack functions
*
* Copyright (C) 2026 Namjae Jeon <linkinjeon@kernel.org>
*/
#include <linux/iomap.h>
#include <linux/pagemap.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#include "iomap.h"
/*
* exfat_file_write_dio_end_io - Direct I/O write completion handler
*
* Updates i_size if the write extended the file. Called from the dio layer
* after I/O completion.
*/
static int exfat_file_write_dio_end_io(struct kiocb *iocb, ssize_t size,
int error, unsigned int flags)
{
struct inode *inode = file_inode(iocb->ki_filp);
if (error)
return error;
if (size && i_size_read(inode) < iocb->ki_pos + size) {
i_size_write(inode, iocb->ki_pos + size);
mark_inode_dirty(inode);
}
return 0;
}
const struct iomap_dio_ops exfat_write_dio_ops = {
.end_io = exfat_file_write_dio_end_io,
};
static int __exfat_iomap_begin(struct inode *inode, loff_t offset, loff_t length,
unsigned int flags, struct iomap *iomap, bool may_alloc)
{
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_inode_info *ei = EXFAT_I(inode);
unsigned int cluster, num_clusters;
loff_t cluster_offset, cluster_length;
int err;
bool balloc = false;
if (!may_alloc) {
/* Completely beyond EOF. Treat as hole */
if (i_size_read(inode) <= offset) {
iomap->type = IOMAP_HOLE;
iomap->addr = IOMAP_NULL_ADDR;
iomap->offset = offset;
iomap->length = length;
return 0;
}
/* Clamp length if the requested range goes beyond i_size */
if (offset + length > i_size_read(inode))
length = round_up(i_size_read(inode),
i_blocksize(inode)) - offset;
}
num_clusters = exfat_bytes_to_cluster_round_up(sbi,
offset + length) - exfat_bytes_to_cluster(sbi, offset);
mutex_lock(&sbi->s_lock);
iomap->bdev = inode->i_sb->s_bdev;
iomap->offset = offset;
err = exfat_map_cluster(inode, exfat_bytes_to_cluster(sbi, offset),
&cluster, &num_clusters, may_alloc, &balloc);
if (err)
goto out;
cluster_offset = exfat_cluster_offset(sbi, offset);
cluster_length = exfat_cluster_to_bytes(sbi, num_clusters);
iomap->length = min_t(loff_t, length, cluster_length - cluster_offset);
iomap->addr = exfat_cluster_to_phys_bytes(sbi, cluster) + cluster_offset;
iomap->type = IOMAP_MAPPED;
iomap->flags = IOMAP_F_MERGED;
if (may_alloc || flags & IOMAP_ZERO) {
if (balloc)
iomap->flags |= IOMAP_F_NEW;
else if (iomap->offset + iomap->length >= ei->valid_size) {
/*
* This is a write that starts at or extends beyond
* the current valid_size. The region between the old
* valid_size and the end of this write needs to be
* zeroed in the page cache to prevent stale data
* exposure (see IOMAP_F_ZERO_TAIL handling in
* __iomap_write_begin()).
*/
iomap->flags |= IOMAP_F_ZERO_TAIL;
}
} else {
/*
* valid_size is tracked in byte granularity and
* marks the exact boundary between valid data and
* holes (or unwritten space).
*
* When IOMAP_REPORT is set (used by lseek(SEEK_HOLE)
* and SEEK_DATA), we return IOMAP_HOLE. This allows
* iomap_seek_hole_iter() to directly return the
* precise byte position.
*
* For normal I/O paths (without IOMAP_REPORT) we
* return IOMAP_UNWRITTEN so the write path can
* distinguish it from a real hole.
*/
if (offset >= ei->valid_size) {
iomap->type = flags & IOMAP_REPORT ?
IOMAP_HOLE : IOMAP_UNWRITTEN;
} else if (offset + iomap->length > ei->valid_size) {
if (flags & IOMAP_REPORT) {
/*
* For SEEK_HOLE/SEEK_DATA, clip the length
* to the exact byte boundary (valid_size).
* This ensures the caller gets the precise
* hole position in byte units.
*/
iomap->length = ei->valid_size - iomap->offset;
} else
iomap->length = round_up(ei->valid_size,
i_blocksize(inode)) -
iomap->offset;
}
}
iomap->flags |= IOMAP_F_MERGED;
out:
mutex_unlock(&sbi->s_lock);
return err;
}
static int exfat_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, false);
}
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);
}
const struct iomap_ops exfat_iomap_ops = {
.iomap_begin = exfat_iomap_begin,
};
/*
* exfat_write_iomap_end - Update the state after write
*
* Extends ->valid_size to cover the newly written range.
* Marks the inode dirty if metadata was changed.
*/
static int exfat_write_iomap_end(struct inode *inode, loff_t pos, loff_t length,
ssize_t written, unsigned int flags, struct iomap *iomap)
{
struct exfat_inode_info *ei = EXFAT_I(inode);
bool dirtied = false;
loff_t end;
if (!written)
return 0;
end = pos + written;
if (ei->valid_size < end) {
ei->valid_size = end;
if (ei->zeroed_size < end)
ei->zeroed_size = end;
dirtied = true;
}
if (dirtied || iomap->flags & IOMAP_F_SIZE_CHANGED)
mark_inode_dirty(inode);
return written;
}
const struct iomap_ops exfat_write_iomap_ops = {
.iomap_begin = exfat_write_iomap_begin,
.iomap_end = exfat_write_iomap_end,
};
/*
* exfat_writeback_range - Map folio during writeback
*
* Called for each folio during writeback. If the folio falls outside the
* current iomap, remaps by calling read_iomap_begin.
*/
static ssize_t exfat_writeback_range(struct iomap_writepage_ctx *wpc,
struct folio *folio, u64 offset, unsigned int len, u64 end_pos)
{
if (offset < wpc->iomap.offset ||
offset >= wpc->iomap.offset + wpc->iomap.length) {
int error;
error = __exfat_iomap_begin(wpc->inode, offset, len,
0, &wpc->iomap, false);
if (error)
return error;
}
return iomap_add_to_ioend(wpc, folio, offset, end_pos, len);
}
const struct iomap_writeback_ops exfat_writeback_ops = {
.writeback_range = exfat_writeback_range,
.writeback_submit = iomap_ioend_writeback_submit,
};
/**
* exfat_iomap_read_end_io - iomap read bio completion handler for exFAT
* @bio: bio that has completed reading
*
* exfat_iomap_begin() rounds up MAPPED extents to the block boundary of
* valid_size. This ensures that any subsequent blocks are treated as
* IOMAP_UNWRITTEN, but it also causes the "straddle block" containing
* valid_size to be read from disk. The disk data beyond valid_size in
* this block is stale and must be zeroed to prevent data leakage.
*/
static void exfat_iomap_read_end_io(struct bio *bio)
{
int error = blk_status_to_errno(bio->bi_status);
struct folio_iter iter;
bio_for_each_folio_all(iter, bio) {
struct folio *folio = iter.folio;
struct exfat_inode_info *ei = EXFAT_I(folio->mapping->host);
s64 valid_size;
loff_t pos = folio_pos(folio);
valid_size = ei->valid_size;
if (pos + iter.offset < valid_size &&
pos + iter.offset + iter.length > valid_size)
folio_zero_segment(folio, offset_in_folio(folio, valid_size),
iter.offset + iter.length);
iomap_finish_folio_read(folio, iter.offset, iter.length, error);
}
bio_put(bio);
}
static void exfat_iomap_bio_submit_read(const struct iomap_iter *iter,
struct iomap_read_folio_ctx *ctx)
{
struct bio *bio = ctx->read_ctx;
bio->bi_end_io = exfat_iomap_read_end_io;
submit_bio(bio);
}
const struct iomap_read_ops exfat_iomap_bio_read_ops = {
.read_folio_range = iomap_bio_read_folio_range,
.submit_read = exfat_iomap_bio_submit_read,
};
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);
}

18
fs/exfat/iomap.h Normal file
View File

@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (c) 2026 Namjae Jeon <linkinjeon@kernel.org>
*/
#ifndef _LINUX_EXFAT_IOMAP_H
#define _LINUX_EXFAT_IOMAP_H
extern const struct iomap_dio_ops exfat_write_dio_ops;
extern const struct iomap_ops exfat_iomap_ops;
extern const struct iomap_ops exfat_write_iomap_ops;
extern const struct iomap_writeback_ops exfat_writeback_ops;
extern 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);
#endif /* _LINUX_EXFAT_IOMAP_H */

View File

@ -208,7 +208,7 @@ static int exfat_search_empty_slot(struct super_block *sb,
int dentries_per_clu; int dentries_per_clu;
struct exfat_chain clu; struct exfat_chain clu;
struct exfat_sb_info *sbi = EXFAT_SB(sb); struct exfat_sb_info *sbi = EXFAT_SB(sb);
int total_entries = EXFAT_CLU_TO_DEN(p_dir->size, sbi); unsigned int total_entries = exfat_cluster_to_dentries(sbi, p_dir->size);
dentries_per_clu = sbi->dentries_per_clu; dentries_per_clu = sbi->dentries_per_clu;
@ -266,7 +266,7 @@ static int exfat_search_empty_slot(struct super_block *sb,
static int exfat_check_max_dentries(struct inode *inode) static int exfat_check_max_dentries(struct inode *inode)
{ {
if (EXFAT_B_TO_DEN(i_size_read(inode)) >= MAX_EXFAT_DENTRIES) { if (exfat_bytes_to_dentries(i_size_read(inode)) >= MAX_EXFAT_DENTRIES) {
/* /*
* exFAT spec allows a dir to grow up to 8388608(256MB) * exFAT spec allows a dir to grow up to 8388608(256MB)
* dentries * dentries
@ -314,7 +314,8 @@ int exfat_find_empty_entry(struct inode *inode,
} }
exfat_chain_set(p_dir, ei->start_clu, exfat_chain_set(p_dir, ei->start_clu,
EXFAT_B_TO_CLU(i_size_read(inode), sbi), ei->flags); exfat_bytes_to_cluster(sbi, i_size_read(inode)),
ei->flags);
while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir, while ((dentry = exfat_search_empty_slot(sb, &hint_femp, p_dir,
num_entries, es)) < 0) { num_entries, es)) < 0) {
@ -340,7 +341,7 @@ int exfat_find_empty_entry(struct inode *inode,
} }
/* allocate a cluster */ /* allocate a cluster */
ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode)); ret = exfat_alloc_cluster(inode, 1, &clu, IS_DIRSYNC(inode), false);
if (ret) if (ret)
return ret; return ret;
@ -375,7 +376,7 @@ int exfat_find_empty_entry(struct inode *inode,
hint_femp.cur.size++; hint_femp.cur.size++;
p_dir->size++; p_dir->size++;
size = EXFAT_CLU_TO_B(p_dir->size, sbi); size = exfat_cluster_to_bytes(sbi, p_dir->size);
/* directory inode should be updated in here */ /* directory inode should be updated in here */
i_size_write(inode, size); i_size_write(inode, size);
@ -503,7 +504,7 @@ static int exfat_add_entry(struct inode *inode, const char *path,
* the first cluster is not determined yet. (0) * the first cluster is not determined yet. (0)
*/ */
exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts); exfat_init_dir_entry(&es, type, start_clu, clu_size, &ts);
exfat_init_ext_entry(&es, num_entries, &uniname); exfat_init_ext_entry(&es, num_entries, &uniname, NULL, 0);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); ret = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
if (ret) if (ret)
@ -604,7 +605,7 @@ static int exfat_find(struct inode *dir, const struct qstr *qname,
return ret; return ret;
exfat_chain_set(&cdir, ei->start_clu, exfat_chain_set(&cdir, ei->start_clu,
EXFAT_B_TO_CLU(i_size_read(dir), sbi), ei->flags); exfat_bytes_to_cluster(sbi, i_size_read(dir)), ei->flags);
/* check the validation of hint_stat and initialize it if required */ /* check the validation of hint_stat and initialize it if required */
if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) { if (ei->version != (inode_peek_iversion_raw(dir) & 0xffffffff)) {
@ -681,7 +682,7 @@ static int exfat_find(struct inode *dir, const struct qstr *qname,
return -EIO; return -EIO;
} }
if (unlikely(EXFAT_B_TO_CLU_ROUND_UP(info->size, sbi) > sbi->used_clusters)) { if (unlikely(exfat_bytes_to_cluster_round_up(sbi, info->size) > sbi->used_clusters)) {
exfat_fs_error(sb, "data size is invalid(%lld)", info->size); exfat_fs_error(sb, "data size is invalid(%lld)", info->size);
return -EIO; return -EIO;
} }
@ -695,7 +696,8 @@ static int exfat_find(struct inode *dir, const struct qstr *qname,
if (info->type == TYPE_DIR) { if (info->type == TYPE_DIR) {
exfat_chain_set(&cdir, info->start_clu, exfat_chain_set(&cdir, info->start_clu,
EXFAT_B_TO_CLU(info->size, sbi), info->flags); exfat_bytes_to_cluster(sbi, info->size),
info->flags);
count = exfat_count_dir_entries(sb, &cdir); count = exfat_count_dir_entries(sb, &cdir);
if (count < 0) if (count < 0)
return -EIO; return -EIO;
@ -705,71 +707,44 @@ static int exfat_find(struct inode *dir, const struct qstr *qname,
return 0; return 0;
} }
static int exfat_d_anon_disconn(struct dentry *dentry)
{
return IS_ROOT(dentry) && (dentry->d_flags & DCACHE_DISCONNECTED);
}
static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry, static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags) unsigned int flags)
{ {
struct super_block *sb = dir->i_sb; struct super_block *sb = dir->i_sb;
struct inode *inode; struct inode *inode = NULL;
struct dentry *alias; struct dentry *alias;
struct exfat_dir_entry info; struct exfat_dir_entry info;
int err; int err;
loff_t i_pos; loff_t i_pos;
mode_t i_mode;
mutex_lock(&EXFAT_SB(sb)->s_lock); mutex_lock(&EXFAT_SB(sb)->s_lock);
err = exfat_find(dir, &dentry->d_name, &info); err = exfat_find(dir, &dentry->d_name, &info);
if (err) { if (err) {
if (err == -ENOENT) { if (unlikely(err != -ENOENT))
inode = NULL; inode = ERR_PTR(err);
goto out; goto out;
}
goto unlock;
} }
i_pos = exfat_make_i_pos(&info); i_pos = exfat_make_i_pos(&info);
inode = exfat_build_inode(sb, &info, i_pos); inode = exfat_build_inode(sb, &info, i_pos);
err = PTR_ERR_OR_ZERO(inode); if (IS_ERR(inode) || S_ISDIR(inode->i_mode))
if (err) goto out;
goto unlock;
i_mode = inode->i_mode;
alias = d_find_alias(inode); alias = d_find_alias(inode);
/* /*
* Checking "alias->d_parent == dentry->d_parent" to make sure * Checking "alias->d_parent == dentry->d_parent" to make sure
* FS is not corrupted (especially double linked dir). * FS is not corrupted (especially double linked dir).
*/ */
if (alias && alias->d_parent == dentry->d_parent && if (alias && alias->d_parent == dentry->d_parent) {
!exfat_d_anon_disconn(alias)) {
/* /*
* Unhashed alias is able to exist because of revalidate() * This inode has a hashed alias dentry with different
* called by lookup_fast. You can easily make this status * name. This means, the user did ->lookup() by an
* by calling create and lookup concurrently * another name (longname vs 8.3 alias of it) in past.
* In such case, we reuse an alias instead of new dentry *
* Switch to new one for reason of locality if possible.
*/ */
if (d_unhashed(alias)) { d_move(alias, dentry);
WARN_ON(alias->d_name.hash_len !=
dentry->d_name.hash_len);
exfat_info(sb, "rehashed a dentry(%p) in read lookup",
alias);
d_drop(dentry);
d_rehash(alias);
} else if (!S_ISDIR(i_mode)) {
/*
* This inode has non anonymous-DCACHE_DISCONNECTED
* dentry. This means, the user did ->lookup() by an
* another name (longname vs 8.3 alias of it) in past.
*
* Switch to new one for reason of locality if possible.
*/
d_move(alias, dentry);
}
iput(inode); iput(inode);
mutex_unlock(&EXFAT_SB(sb)->s_lock); mutex_unlock(&EXFAT_SB(sb)->s_lock);
return alias; return alias;
@ -781,9 +756,6 @@ static struct dentry *exfat_lookup(struct inode *dir, struct dentry *dentry,
exfat_d_version_set(dentry, inode_query_iversion(dir)); exfat_d_version_set(dentry, inode_query_iversion(dir));
return d_splice_alias(inode, dentry); return d_splice_alias(inode, dentry);
unlock:
mutex_unlock(&EXFAT_SB(sb)->s_lock);
return ERR_PTR(err);
} }
/* remove an entry, BUT don't truncate */ /* remove an entry, BUT don't truncate */
@ -814,7 +786,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
exfat_set_volume_dirty(sb); exfat_set_volume_dirty(sb);
/* update the directory entry */ /* update the directory entry */
exfat_remove_entries(inode, &es, ES_IDX_FILE); exfat_remove_entries(inode, &es, ES_IDX_FILE, true);
err = exfat_put_dentry_set(&es, IS_DIRSYNC(inode)); err = exfat_put_dentry_set(&es, IS_DIRSYNC(inode));
if (err) if (err)
@ -951,7 +923,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
} }
exfat_chain_set(&clu_to_free, ei->start_clu, exfat_chain_set(&clu_to_free, ei->start_clu,
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi), ei->flags); exfat_bytes_to_cluster_round_up(sbi, i_size_read(inode)), ei->flags);
err = exfat_check_dir_empty(sb, &clu_to_free); err = exfat_check_dir_empty(sb, &clu_to_free);
if (err) { if (err) {
@ -969,7 +941,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
exfat_set_volume_dirty(sb); exfat_set_volume_dirty(sb);
exfat_remove_entries(inode, &es, ES_IDX_FILE); exfat_remove_entries(inode, &es, ES_IDX_FILE, true);
err = exfat_put_dentry_set(&es, IS_DIRSYNC(dir)); err = exfat_put_dentry_set(&es, IS_DIRSYNC(dir));
if (err) if (err)
@ -996,6 +968,23 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
return err; return err;
} }
/*
* Count benign secondary entries beyond the filename entries.
* Returns the count, or -EIO if the entry set is inconsistent.
*/
static int exfat_count_extra_entries(struct exfat_entry_set_cache *es)
{
struct exfat_dentry *stream;
unsigned int name_entries;
int extra;
stream = exfat_get_dentry_cached(es, ES_IDX_STREAM);
name_entries = EXFAT_FILENAME_ENTRY_NUM(stream->dentry.stream.name_len);
extra = es->num_entries - (ES_IDX_FIRST_FILENAME + name_entries);
return extra >= 0 ? extra : -EIO;
}
static int exfat_rename_file(struct inode *parent_inode, static int exfat_rename_file(struct inode *parent_inode,
struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei) struct exfat_uni_name *p_uniname, struct exfat_inode_info *ei)
{ {
@ -1004,6 +993,7 @@ static int exfat_rename_file(struct inode *parent_inode,
struct super_block *sb = parent_inode->i_sb; struct super_block *sb = parent_inode->i_sb;
struct exfat_entry_set_cache old_es, new_es; struct exfat_entry_set_cache old_es, new_es;
int sync = IS_DIRSYNC(parent_inode); int sync = IS_DIRSYNC(parent_inode);
unsigned int num_extra_entries, num_total_entries;
if (unlikely(exfat_forced_shutdown(sb))) if (unlikely(exfat_forced_shutdown(sb)))
return -EIO; return -EIO;
@ -1013,19 +1003,23 @@ static int exfat_rename_file(struct inode *parent_inode,
return num_new_entries; return num_new_entries;
ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei); ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
if (ret) { if (ret)
ret = -EIO; return -EIO;
return ret;
}
epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE); epold = exfat_get_dentry_cached(&old_es, ES_IDX_FILE);
if (old_es.num_entries < num_new_entries) { ret = exfat_count_extra_entries(&old_es);
if (ret < 0)
goto put_old_es;
num_extra_entries = ret;
num_total_entries = num_new_entries + num_extra_entries;
if (old_es.num_entries < num_total_entries) {
int newentry; int newentry;
struct exfat_chain dir; struct exfat_chain dir;
newentry = exfat_find_empty_entry(parent_inode, &dir, newentry = exfat_find_empty_entry(parent_inode, &dir,
num_new_entries, &new_es); num_total_entries, &new_es);
if (newentry < 0) { if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */ ret = newentry; /* -EIO or -ENOSPC */
goto put_old_es; goto put_old_es;
@ -1042,13 +1036,23 @@ static int exfat_rename_file(struct inode *parent_inode,
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM); epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
*epnew = *epold; *epnew = *epold;
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
&old_es, num_extra_entries);
ret = exfat_put_dentry_set(&new_es, sync); ret = exfat_put_dentry_set(&new_es, sync);
if (ret) if (ret) {
/* Best-effort delete to avoid duplicate entries */
if (!exfat_get_dentry_set(&new_es, sb,
&dir, newentry,
ES_ALL_ENTRIES)) {
exfat_remove_entries(parent_inode, &new_es,
ES_IDX_FILE, false);
exfat_put_dentry_set(&new_es, false);
}
goto put_old_es; goto put_old_es;
}
exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE); exfat_remove_entries(parent_inode, &old_es, ES_IDX_FILE, false);
ei->dir = dir; ei->dir = dir;
ei->entry = newentry; ei->entry = newentry;
} else { } else {
@ -1057,8 +1061,8 @@ static int exfat_rename_file(struct inode *parent_inode,
ei->attr |= EXFAT_ATTR_ARCHIVE; ei->attr |= EXFAT_ATTR_ARCHIVE;
} }
exfat_remove_entries(parent_inode, &old_es, ES_IDX_FIRST_FILENAME + 1); exfat_init_ext_entry(&old_es, num_new_entries, p_uniname,
exfat_init_ext_entry(&old_es, num_new_entries, p_uniname); &old_es, num_extra_entries);
} }
return exfat_put_dentry_set(&old_es, sync); return exfat_put_dentry_set(&old_es, sync);
@ -1074,6 +1078,7 @@ static int exfat_move_file(struct inode *parent_inode,
struct exfat_dentry *epmov, *epnew; struct exfat_dentry *epmov, *epnew;
struct exfat_entry_set_cache mov_es, new_es; struct exfat_entry_set_cache mov_es, new_es;
struct exfat_chain newdir; struct exfat_chain newdir;
unsigned int num_extra_entries, num_total_entries;
num_new_entries = exfat_calc_num_entries(p_uniname); num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0) if (num_new_entries < 0)
@ -1083,8 +1088,14 @@ static int exfat_move_file(struct inode *parent_inode,
if (ret) if (ret)
return -EIO; return -EIO;
ret = exfat_count_extra_entries(&mov_es);
if (ret < 0)
goto put_mov_es;
num_extra_entries = ret;
num_total_entries = num_new_entries + num_extra_entries;
newentry = exfat_find_empty_entry(parent_inode, &newdir, newentry = exfat_find_empty_entry(parent_inode, &newdir,
num_new_entries, &new_es); num_total_entries, &new_es);
if (newentry < 0) { if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */ ret = newentry; /* -EIO or -ENOSPC */
goto put_mov_es; goto put_mov_es;
@ -1102,21 +1113,31 @@ static int exfat_move_file(struct inode *parent_inode,
epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM); epnew = exfat_get_dentry_cached(&new_es, ES_IDX_STREAM);
*epnew = *epmov; *epnew = *epmov;
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname); exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE); &mov_es, num_extra_entries);
exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false);
ei->dir = newdir; ei->dir = newdir;
ei->entry = newentry; ei->entry = newentry;
ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode)); ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
if (ret) if (ret) {
/* Best-effort delete to avoid duplicate entries */
if (!exfat_get_dentry_set(&new_es, parent_inode->i_sb,
&newdir, newentry,
ES_ALL_ENTRIES)) {
exfat_remove_entries(parent_inode, &new_es,
ES_IDX_FILE, false);
exfat_put_dentry_set(&new_es, false);
}
goto put_mov_es; goto put_mov_es;
}
return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode)); return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
put_mov_es: put_mov_es:
exfat_put_dentry_set(&mov_es, false); exfat_put_dentry_set(&mov_es, false);
return ret; return ret;
} }
@ -1158,8 +1179,8 @@ static int __exfat_rename(struct inode *old_parent_inode,
new_clu.dir = new_ei->start_clu; new_clu.dir = new_ei->start_clu;
new_clu.size = new_clu.size =
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(new_inode), exfat_bytes_to_cluster_round_up(sbi,
sbi); i_size_read(new_inode));
new_clu.flags = new_ei->flags; new_clu.flags = new_ei->flags;
ret = exfat_check_dir_empty(sb, &new_clu); ret = exfat_check_dir_empty(sb, &new_clu);
@ -1190,7 +1211,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
goto del_out; goto del_out;
} }
exfat_remove_entries(new_inode, &es, ES_IDX_FILE); exfat_remove_entries(new_inode, &es, ES_IDX_FILE, true);
ret = exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode)); ret = exfat_put_dentry_set(&es, IS_DIRSYNC(new_inode));
if (ret) if (ret)
@ -1203,8 +1224,8 @@ static int __exfat_rename(struct inode *old_parent_inode,
struct exfat_chain new_clu_to_free; struct exfat_chain new_clu_to_free;
exfat_chain_set(&new_clu_to_free, new_ei->start_clu, exfat_chain_set(&new_clu_to_free, new_ei->start_clu,
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(new_inode), exfat_bytes_to_cluster_round_up(sbi, i_size_read(new_inode)),
sbi), new_ei->flags); new_ei->flags);
if (exfat_free_cluster(new_inode, &new_clu_to_free)) { if (exfat_free_cluster(new_inode, &new_clu_to_free)) {
/* just set I/O error only */ /* just set I/O error only */

View File

@ -769,13 +769,18 @@ int exfat_create_upcase_table(struct super_block *sb)
tbl_clu = le32_to_cpu(ep->dentry.upcase.start_clu); tbl_clu = le32_to_cpu(ep->dentry.upcase.start_clu);
tbl_size = le64_to_cpu(ep->dentry.upcase.size); tbl_size = le64_to_cpu(ep->dentry.upcase.size);
if (tbl_size) {
sector = exfat_cluster_to_sector(sbi, tbl_clu); sector = exfat_cluster_to_sector(sbi, tbl_clu);
num_sectors = ((tbl_size - 1) >> blksize_bits) + 1; num_sectors = ((tbl_size - 1) >> blksize_bits) + 1;
ret = exfat_load_upcase_table(sb, sector, num_sectors, ret = exfat_load_upcase_table(sb, sector, num_sectors,
le32_to_cpu(ep->dentry.upcase.checksum)); le32_to_cpu(ep->dentry.upcase.checksum));
} else {
exfat_fs_error(sb,
"bad upcase table size (0 bytes). Please run fsck");
ret = -EINVAL;
}
brelse(bh); brelse(bh);
if (ret && ret != -EIO) { if (ret && ret != -EIO) {
/* free memory from exfat_load_upcase_table call */ /* free memory from exfat_load_upcase_table call */
exfat_free_upcase_table(sbi); exfat_free_upcase_table(sbi);
@ -790,6 +795,8 @@ int exfat_create_upcase_table(struct super_block *sb)
return -EIO; return -EIO;
} }
exfat_fs_error(sb, "no upcase table entry. Please run fsck");
load_default: load_default:
/* load default upcase table */ /* load default upcase table */
return exfat_load_default_upcase_table(sb); return exfat_load_default_upcase_table(sb);

View File

@ -369,7 +369,7 @@ static int exfat_read_root(struct inode *inode, struct exfat_chain *root_clu)
ei->hint_stat.clu = sbi->root_dir; ei->hint_stat.clu = sbi->root_dir;
ei->hint_femp.eidx = EXFAT_HINT_NONE; ei->hint_femp.eidx = EXFAT_HINT_NONE;
i_size_write(inode, EXFAT_CLU_TO_B(root_clu->size, sbi)); i_size_write(inode, exfat_cluster_to_bytes(sbi, root_clu->size));
num_subdirs = exfat_count_dir_entries(sb, root_clu); num_subdirs = exfat_count_dir_entries(sb, root_clu);
if (num_subdirs < 0) if (num_subdirs < 0)
@ -499,6 +499,7 @@ static int exfat_read_boot_sector(struct super_block *sb)
if (p_boot->num_fats == 2) if (p_boot->num_fats == 2)
sbi->FAT2_start_sector += sbi->num_FAT_sectors; sbi->FAT2_start_sector += sbi->num_FAT_sectors;
sbi->data_start_sector = le32_to_cpu(p_boot->clu_offset); sbi->data_start_sector = le32_to_cpu(p_boot->clu_offset);
sbi->data_start_bytes = sbi->data_start_sector << p_boot->sect_size_bits;
sbi->num_sectors = le64_to_cpu(p_boot->vol_length); sbi->num_sectors = le64_to_cpu(p_boot->vol_length);
/* because the cluster index starts with 2 */ /* because the cluster index starts with 2 */
sbi->num_clusters = le32_to_cpu(p_boot->clu_count) + sbi->num_clusters = le32_to_cpu(p_boot->clu_count) +
@ -538,7 +539,7 @@ static int exfat_read_boot_sector(struct super_block *sb)
* machines. * machines.
*/ */
sb->s_maxbytes = min(MAX_LFS_FILESIZE, sb->s_maxbytes = min(MAX_LFS_FILESIZE,
EXFAT_CLU_TO_B((loff_t)EXFAT_MAX_NUM_CLUSTER, sbi)); exfat_cluster_to_bytes(sbi, (loff_t)EXFAT_MAX_NUM_CLUSTER));
/* check logical sector size */ /* check logical sector size */
if (exfat_calibrate_blocksize(sb, 1 << p_boot->sect_size_bits)) if (exfat_calibrate_blocksize(sb, 1 << p_boot->sect_size_bits))