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
13 changed files with 881 additions and 534 deletions

View File

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

View File

@@ -5,4 +5,4 @@
obj-$(CONFIG_EXFAT_FS) += exfat.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,
EXFAT_B_TO_CLU_ROUND_UP(map_size, sbi)) == false)
exfat_bytes_to_cluster_round_up(sbi, map_size)) == false)
goto err_out;
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 exfat_sb_info *sbi = EXFAT_SB(sb);
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;
/* 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;
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;
max_dentries = (unsigned int)min_t(u64, MAX_EXFAT_DENTRIES,
(u64)EXFAT_CLU_TO_DEN(sbi->num_clusters, sbi));
max_dentries = min(MAX_EXFAT_DENTRIES,
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);
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;
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;
*cpos = EXFAT_DEN_TO_B(dentry + 1 + num_ext);
*cpos = exfat_dentries_to_bytes(dentry + 1 + num_ext);
return 0;
}
@@ -160,7 +160,7 @@ static int exfat_readdir(struct inode *inode, loff_t *cpos, struct exfat_dir_ent
out:
dir_entry->namebuf.lfn[0] = '\0';
*cpos = EXFAT_DEN_TO_B(dentry);
*cpos = exfat_dentries_to_bytes(dentry);
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);
ret = exfat_alloc_cluster(inode, 1, clu, IS_DIRSYNC(inode));
ret = exfat_alloc_cluster(inode, 1, clu, IS_DIRSYNC(inode), false);
if (ret)
return ret;
@@ -465,37 +465,75 @@ static void exfat_free_benign_secondary_clusters(struct inode *inode,
return;
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);
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,
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;
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->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->dentry.stream.name_len = p_uniname->name_len;
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++) {
ep = exfat_get_dentry_cached(es, i);
exfat_init_name_entry(ep, uniname);
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);
}
void exfat_remove_entries(struct inode *inode, struct exfat_entry_set_cache *es,
int order)
int order, bool free_benign)
{
int i;
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++) {
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_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;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
off = EXFAT_DEN_TO_B(entry);
off = exfat_dentries_to_bytes(entry);
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)
return ret;
@@ -567,7 +606,7 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir
exfat_fs_error(sb,
"unexpected early break in cluster chain (clu : %u, len : %d)",
p_dir->dir,
EXFAT_B_TO_CLU(off, sbi));
exfat_bytes_to_cluster(sbi, off));
return -EIO;
}
@@ -577,13 +616,13 @@ static int exfat_find_location(struct super_block *sb, struct exfat_chain *p_dir
}
/* byte offset in cluster */
off = EXFAT_CLU_OFFSET(off, sbi);
off = exfat_cluster_offset(sbi, off);
/* byte offset in sector */
*offset = EXFAT_BLK_OFFSET(off, sb);
*offset = exfat_block_offset(sb, off);
/* 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);
return 0;
}
@@ -593,7 +632,7 @@ struct exfat_dentry *exfat_get_dentry(struct super_block *sb,
{
struct exfat_sb_info *sbi = EXFAT_SB(sb);
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;
sector_t sec;
@@ -672,8 +711,8 @@ struct exfat_dentry *exfat_get_dentry_cached(
struct exfat_entry_set_cache *es, int num)
{
int off = es->start_off + num * DENTRY_SIZE;
struct buffer_head *bh = es->bh[EXFAT_B_TO_BLK(off, es->sb)];
char *p = bh->b_data + EXFAT_BLK_OFFSET(off, es->sb);
struct buffer_head *bh = es->bh[exfat_bytes_to_block(es->sb, off)];
char *p = bh->b_data + exfat_block_offset(es->sb, off);
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;
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)) {
es->bh = kmalloc_objs(*es->bh, num_bh, GFP_NOFS);
if (!es->bh) {
@@ -830,7 +869,7 @@ static int exfat_validate_empty_dentry_set(struct exfat_entry_set_cache *es)
err_used_follow_unused:
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,
"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;
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++) {
ep = exfat_get_dentry_cached(es, i);
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 ||
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) {
candi_empty->cur = *clu;
@@ -1027,22 +1067,26 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei,
continue;
}
brelse(bh);
if (entry_type == TYPE_EXTEND) {
unsigned short entry_uniname[16], unichar;
unsigned int offset;
if (step != DIRENT_STEP_NAME ||
name_len >= MAX_NAME_LENGTH) {
brelse(bh);
step = DIRENT_STEP_FILE;
continue;
}
if (++order == 2)
uniname = p_uniname->name;
else
uniname += EXFAT_FILE_NAME_LEN;
offset = (++order - 2) * EXFAT_FILE_NAME_LEN;
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;
unichar = *(uniname+len);
@@ -1061,6 +1105,7 @@ int exfat_find_dir_entry(struct super_block *sb, struct exfat_inode_info *ei,
continue;
}
brelse(bh);
if (entry_type &
(TYPE_CRITICAL_SEC | TYPE_BENIGN_SEC)) {
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[0] = bh;
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;
}

View File

@@ -12,6 +12,7 @@
#include <linux/blkdev.h>
#include <linux/backing-dev.h>
#include <uapi/linux/exfat.h>
#include <linux/buffer_head.h>
#define EXFAT_ROOT_INO 1
@@ -84,38 +85,6 @@ enum {
(min_t(blkcnt_t, (sb)->s_bdi->ra_pages, (sb)->s_bdi->io_pages) \
<< (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.
*/
@@ -149,7 +118,7 @@ enum {
* The 608 bytes are in 3 sectors at most (even 512 Byte sector).
*/
#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 */
#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 FAT2_start_sector; /* FAT2 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 root_dir; /* root dir 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 */
loff_t i_pos;
loff_t valid_size;
/* page-aligned size that has been zeroed out for mmap */
loff_t zeroed_size;
/* hash by i_location */
struct hlist_node i_hash_fat;
/* 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;
}
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 */
int exfat_set_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)
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_ent_get(struct super_block *sb, unsigned int loc,
unsigned int *content, struct buffer_head **last);
@@ -490,7 +557,6 @@ int exfat_trim_fs(struct inode *inode, struct fstrim_range *range);
/* file.c */
extern const struct file_operations exfat_file_operations;
int __exfat_truncate(struct inode *inode);
void exfat_truncate(struct inode *inode);
int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
struct iattr *attr);
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 long long size, struct timespec64 *ts);
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,
int order);
int order, bool free_benign);
void exfat_update_dir_chksum(struct exfat_entry_set_cache *es);
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,
@@ -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, struct writeback_control *wbc);
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 */
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))
return sync_blockdev_range(sb->s_bdev,
EXFAT_BLK_TO_B(blknr, sb),
EXFAT_BLK_TO_B(last_blknr, sb) - 1);
exfat_block_to_bytes(sb, blknr),
exfat_block_to_bytes(sb, last_blknr) - 1);
return 0;
}
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;
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)) !=
EXFAT_EOF_CLUSTER) {
if (new_clu != hint_clu &&
p_chain->flags == ALLOC_NO_FAT_CHAIN) {
if (exfat_chain_cont_cluster(sb, p_chain->dir,
p_chain->size)) {
ret = -EIO;
goto free_cluster;
if (new_clu != hint_clu) {
if (p_chain->flags == ALLOC_NO_FAT_CHAIN) {
if (exfat_chain_cont_cluster(sb, p_chain->dir,
p_chain->size)) {
ret = -EIO;
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 */
@@ -507,9 +513,9 @@ int exfat_alloc_cluster(struct inode *inode, unsigned int num_alloc,
last_clu = new_clu;
if (p_chain->size == num_alloc) {
done:
sbi->clu_srch_ptr = hint_clu;
sbi->used_clusters += num_alloc;
sbi->used_clusters += p_chain->size;
mutex_unlock(&sbi->bitmap_lock);
return 0;
}

View File

@@ -15,9 +15,11 @@
#include <linux/filelock.h>
#include <linux/falloc.h>
#include <linux/fileattr.h>
#include <linux/iomap.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#include "iomap.h"
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 exfat_sb_info *sbi = EXFAT_SB(sb);
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);
if (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(). */
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)
goto out;
@@ -57,7 +60,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
clu.flags = ei->flags;
ret = exfat_alloc_cluster(inode, new_num_clusters - num_clusters,
&clu, inode_needs_sync(inode));
&clu, inode_needs_sync(inode), false);
if (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));
/* 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.
*/
pagecache_isize_extended(inode, oldsize, inode->i_size);
inode->i_blocks = round_up(size, sbi->cluster_size) >> 9;
mark_inode_dirty(inode);
@@ -201,8 +211,8 @@ int __exfat_truncate(struct inode *inode)
exfat_set_volume_dirty(sb);
num_clusters_new = EXFAT_B_TO_CLU_ROUND_UP(i_size_read(inode), sbi);
num_clusters_phys = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi);
num_clusters_new = exfat_bytes_to_cluster_round_up(sbi, i_size_read(inode));
num_clusters_phys = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
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)
ei->valid_size = i_size_read(inode);
ei->valid_size = ei->zeroed_size = i_size_read(inode);
if (ei->type == TYPE_FILE)
ei->attr |= EXFAT_ATTR_ARCHIVE;
@@ -283,7 +293,7 @@ int __exfat_truncate(struct inode *inode)
return 0;
}
void exfat_truncate(struct inode *inode)
static void exfat_truncate(struct inode *inode)
{
struct super_block *sb = inode->i_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);
if (attr->ia_valid & ATTR_SIZE) {
error = exfat_block_truncate_page(inode, attr->ia_size);
if (error)
goto out;
/*
* Wait for any in-flight DIO to finish before truncating to
* 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);
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)
{
int err;
loff_t pos;
struct exfat_inode_info *ei = EXFAT_I(inode);
struct address_space *mapping = inode->i_mapping;
const struct address_space_operations *ops = mapping->a_ops;
loff_t old_valid_size = ei->valid_size;
int ret = 0;
pos = ei->valid_size;
while (pos < new_valid_size) {
u32 len;
struct folio *folio;
unsigned long off;
if (old_valid_size < new_valid_size) {
if (i_size_read(inode) < new_valid_size) {
i_size_write(inode, new_valid_size);
mark_inode_dirty(inode);
}
len = PAGE_SIZE - (pos & (PAGE_SIZE - 1));
if (pos + len > new_valid_size)
len = new_valid_size - pos;
err = ops->write_begin(NULL, mapping, pos, len, &folio, NULL);
if (err)
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();
ret = iomap_zero_range(inode, old_valid_size,
new_valid_size - old_valid_size, NULL,
&exfat_write_iomap_ops, NULL, NULL);
if (ret) {
truncate_setsize(inode, old_valid_size);
exfat_truncate(inode);
}
}
return 0;
return ret;
}
out:
return err;
static ssize_t exfat_fallback_buffered_write(struct kiocb *iocb,
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)
@@ -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);
loff_t pos = iocb->ki_pos;
loff_t valid_size;
int err;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;
@@ -705,14 +752,10 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
if (ret <= 0)
goto unlock;
if (iocb->ki_flags & IOCB_DIRECT) {
unsigned long align = pos | iov_iter_alignment(iter);
if (!IS_ALIGNED(align, i_blocksize(inode)) &&
!IS_ALIGNED(align, bdev_logical_block_size(inode->i_sb->s_bdev))) {
ret = -EINVAL;
goto unlock;
}
err = file_modified(iocb->ki_filp);
if (err) {
ret = err;
goto unlock;
}
if (pos > valid_size) {
@@ -726,7 +769,11 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
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)
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)
{
struct inode *inode = file_inode(iocb->ki_filp);
ssize_t ret;
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
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)
{
int err;
struct inode *inode = file_inode(vmf->vma->vm_file);
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))
return VM_FAULT_RETRY;
new_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT;
new_valid_size = min(new_valid_size, i_size_read(inode));
mmap_valid_size = ((loff_t)vmf->pgoff + 1) << PAGE_SHIFT;
new_valid_size = min(mmap_valid_size, i_size_read(inode));
if (ei->valid_size < new_valid_size) {
err = exfat_extend_valid_size(inode, new_valid_size);
if (err < 0) {
inode_unlock(inode);
return vmf_fs_error(err);
if (ei->zeroed_size < mmap_valid_size) {
int err;
/*
* Only zero the range that hasn't been zeroed yet for
* this mmap write path. zeroed_size tracks the largest
* page-aligned offset that has already been zeroed.
*
* This prevents unnecessarily zeroing out the entire
* tail page on every page fault when userspace writes
* data byte-by-byte through mmap (after a small
* fallocate). It fixes data corruption in the tail page
* while preserving the existing valid_size semantics.
*/
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);
return filemap_page_mkwrite(vmf);
return ret;
}
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)))
return -EIO;
if (vma_desc_test_all(desc, VMA_SHARED_BIT, VMA_MAYWRITE_BIT)) {
struct inode *inode = file_inode(file);
loff_t from, to;
int err;
from = ((loff_t)desc->pgoff << PAGE_SHIFT);
to = min_t(loff_t, i_size_read(inode),
from + vma_desc_size(desc));
if (EXFAT_I(inode)->valid_size < to) {
err = exfat_extend_valid_size(inode, to);
if (err)
return err;
}
}
file_accessed(file);
desc->vm_ops = &exfat_file_vm_ops;
return 0;
@@ -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);
}
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 = {
.llseek = generic_file_llseek,
.open = exfat_file_open,
.llseek = exfat_file_llseek,
.read_iter = exfat_file_read_iter,
.write_iter = exfat_file_write_iter,
.unlocked_ioctl = exfat_ioctl,

View File

@@ -13,13 +13,16 @@
#include <linux/uio.h>
#include <linux/random.h>
#include <linux/iversion.h>
#include <linux/iomap.h>
#include "exfat_raw.h"
#include "exfat_fs.h"
#include "iomap.h"
int __exfat_write_inode(struct inode *inode, int sync)
{
unsigned long long on_disk_size;
unsigned long long on_disk_valid_size;
struct exfat_dentry *ep, *ep2;
struct exfat_entry_set_cache es;
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,
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)
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);
/*
* 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);
ep2->dentry.stream.valid_size = cpu_to_le64(on_disk_valid_size);
if (on_disk_size) {
ep2->dentry.stream.flags = ei->flags;
@@ -123,8 +134,9 @@ void exfat_sync_inode(struct inode *inode)
* Output: errcode, cluster number
* *clu = (~0), if it's unable to allocate a new cluster
*/
static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
unsigned int *clu, unsigned int *count, int create)
int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
unsigned int *clu, unsigned int *count, int create,
bool *balloc)
{
int ret;
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 num_to_be_allocated = 0, num_clusters;
num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi);
if (clu_offset >= num_clusters)
num_to_be_allocated = clu_offset - num_clusters + 1;
num_clusters = exfat_bytes_to_cluster(sbi, exfat_ondisk_size(inode));
if (clu_offset > num_clusters ||
*count > num_clusters - clu_offset)
num_to_be_allocated = clu_offset + *count - num_clusters;
if (!create && (num_to_be_allocated > 0)) {
*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,
inode_needs_sync(inode));
inode_needs_sync(inode), true);
if (ret)
return ret;
@@ -215,19 +227,11 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
}
*clu = new_clu.dir;
*count = new_clu.size;
inode->i_blocks += EXFAT_CLU_TO_B(num_to_be_allocated, sbi) >> 9;
/*
* 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;
inode->i_blocks += exfat_cluster_to_sectors(sbi, new_clu.size);
if (balloc)
*balloc = true;
}
/* hint information */
@@ -237,154 +241,15 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
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)
{
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)
@@ -393,6 +258,10 @@ 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);
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) &&
@@ -400,115 +269,22 @@ static void exfat_readahead(struct readahead_control *rac)
ei->valid_size < pos + readahead_length(rac))
return;
mpage_readahead(rac, exfat_get_block);
iomap_readahead(&exfat_iomap_ops, &ctx, NULL);
}
static int exfat_writepages(struct address_space *mapping,
struct writeback_control *wbc)
{
if (unlikely(exfat_forced_shutdown(mapping->host->i_sb)))
return -EIO;
return mpage_writepages(mapping, wbc, exfat_get_block);
}
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;
struct iomap_writepage_ctx wpc = {
.inode = mapping->host,
.wbc = wbc,
.ops = &exfat_writeback_ops,
};
if (unlikely(exfat_forced_shutdown(mapping->host->i_sb)))
return -EIO;
ret = block_write_begin(mapping, pos, len, foliop, exfat_get_block);
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;
return iomap_writepages(&wpc);
}
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. */
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);
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 = {
.dirty_folio = block_dirty_folio,
.invalidate_folio = block_invalidate_folio,
.read_folio = exfat_read_folio,
.readahead = exfat_readahead,
.writepages = exfat_writepages,
.write_begin = exfat_write_begin,
.write_end = exfat_write_end,
.direct_IO = exfat_direct_IO,
.bmap = exfat_aop_bmap,
.migrate_folio = buffer_migrate_folio,
.read_folio = exfat_read_folio,
.readahead = exfat_readahead,
.writepages = exfat_writepages,
.dirty_folio = iomap_dirty_folio,
.bmap = exfat_aop_bmap,
.migrate_folio = filemap_migrate_folio,
.is_partially_uptodate = iomap_is_partially_uptodate,
.error_remove_folio = generic_error_remove_folio,
.release_folio = iomap_release_folio,
.invalidate_folio = iomap_invalidate_folio,
.swap_activate = exfat_iomap_swap_activate,
};
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->type = info->type;
ei->valid_size = info->valid_size;
ei->zeroed_size = info->valid_size;
ei->version = 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;
struct exfat_chain clu;
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;
@@ -266,7 +266,7 @@ static int exfat_search_empty_slot(struct super_block *sb,
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)
* dentries
@@ -314,7 +314,8 @@ int exfat_find_empty_entry(struct inode *inode,
}
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,
num_entries, es)) < 0) {
@@ -340,7 +341,7 @@ int exfat_find_empty_entry(struct inode *inode,
}
/* 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)
return ret;
@@ -375,7 +376,7 @@ int exfat_find_empty_entry(struct inode *inode,
hint_femp.cur.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 */
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)
*/
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));
if (ret)
@@ -604,7 +605,7 @@ static int exfat_find(struct inode *dir, const struct qstr *qname,
return ret;
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 */
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;
}
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);
return -EIO;
}
@@ -695,7 +696,8 @@ static int exfat_find(struct inode *dir, const struct qstr *qname,
if (info->type == TYPE_DIR) {
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);
if (count < 0)
return -EIO;
@@ -705,71 +707,44 @@ static int exfat_find(struct inode *dir, const struct qstr *qname,
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,
unsigned int flags)
{
struct super_block *sb = dir->i_sb;
struct inode *inode;
struct inode *inode = NULL;
struct dentry *alias;
struct exfat_dir_entry info;
int err;
loff_t i_pos;
mode_t i_mode;
mutex_lock(&EXFAT_SB(sb)->s_lock);
err = exfat_find(dir, &dentry->d_name, &info);
if (err) {
if (err == -ENOENT) {
inode = NULL;
goto out;
}
goto unlock;
if (unlikely(err != -ENOENT))
inode = ERR_PTR(err);
goto out;
}
i_pos = exfat_make_i_pos(&info);
inode = exfat_build_inode(sb, &info, i_pos);
err = PTR_ERR_OR_ZERO(inode);
if (err)
goto unlock;
if (IS_ERR(inode) || S_ISDIR(inode->i_mode))
goto out;
i_mode = inode->i_mode;
alias = d_find_alias(inode);
/*
* Checking "alias->d_parent == dentry->d_parent" to make sure
* FS is not corrupted (especially double linked dir).
*/
if (alias && alias->d_parent == dentry->d_parent &&
!exfat_d_anon_disconn(alias)) {
if (alias && alias->d_parent == dentry->d_parent) {
/*
* Unhashed alias is able to exist because of revalidate()
* called by lookup_fast. You can easily make this status
* by calling create and lookup concurrently
* In such case, we reuse an alias instead of new dentry
* This inode has a hashed alias dentry with different
* name. 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.
*/
if (d_unhashed(alias)) {
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);
}
d_move(alias, dentry);
iput(inode);
mutex_unlock(&EXFAT_SB(sb)->s_lock);
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));
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 */
@@ -814,7 +786,7 @@ static int exfat_unlink(struct inode *dir, struct dentry *dentry)
exfat_set_volume_dirty(sb);
/* 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));
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_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);
if (err) {
@@ -969,7 +941,7 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
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));
if (err)
@@ -996,6 +968,23 @@ static int exfat_rmdir(struct inode *dir, struct dentry *dentry)
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,
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 exfat_entry_set_cache old_es, new_es;
int sync = IS_DIRSYNC(parent_inode);
unsigned int num_extra_entries, num_total_entries;
if (unlikely(exfat_forced_shutdown(sb)))
return -EIO;
@@ -1013,19 +1003,23 @@ static int exfat_rename_file(struct inode *parent_inode,
return num_new_entries;
ret = exfat_get_dentry_set_by_ei(&old_es, sb, ei);
if (ret) {
ret = -EIO;
return ret;
}
if (ret)
return -EIO;
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;
struct exfat_chain dir;
newentry = exfat_find_empty_entry(parent_inode, &dir,
num_new_entries, &new_es);
num_total_entries, &new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
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 = *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);
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;
}
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->entry = newentry;
} else {
@@ -1057,8 +1061,8 @@ static int exfat_rename_file(struct inode *parent_inode,
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);
@@ -1074,6 +1078,7 @@ static int exfat_move_file(struct inode *parent_inode,
struct exfat_dentry *epmov, *epnew;
struct exfat_entry_set_cache mov_es, new_es;
struct exfat_chain newdir;
unsigned int num_extra_entries, num_total_entries;
num_new_entries = exfat_calc_num_entries(p_uniname);
if (num_new_entries < 0)
@@ -1083,8 +1088,14 @@ static int exfat_move_file(struct inode *parent_inode,
if (ret)
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,
num_new_entries, &new_es);
num_total_entries, &new_es);
if (newentry < 0) {
ret = newentry; /* -EIO or -ENOSPC */
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 = *epmov;
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname);
exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE);
exfat_init_ext_entry(&new_es, num_new_entries, p_uniname,
&mov_es, num_extra_entries);
exfat_remove_entries(parent_inode, &mov_es, ES_IDX_FILE, false);
ei->dir = newdir;
ei->entry = newentry;
ret = exfat_put_dentry_set(&new_es, IS_DIRSYNC(parent_inode));
if (ret)
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;
}
return exfat_put_dentry_set(&mov_es, IS_DIRSYNC(parent_inode));
put_mov_es:
exfat_put_dentry_set(&mov_es, false);
return ret;
}
@@ -1158,8 +1179,8 @@ static int __exfat_rename(struct inode *old_parent_inode,
new_clu.dir = new_ei->start_clu;
new_clu.size =
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(new_inode),
sbi);
exfat_bytes_to_cluster_round_up(sbi,
i_size_read(new_inode));
new_clu.flags = new_ei->flags;
ret = exfat_check_dir_empty(sb, &new_clu);
@@ -1190,7 +1211,7 @@ static int __exfat_rename(struct inode *old_parent_inode,
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));
if (ret)
@@ -1203,8 +1224,8 @@ static int __exfat_rename(struct inode *old_parent_inode,
struct exfat_chain new_clu_to_free;
exfat_chain_set(&new_clu_to_free, new_ei->start_clu,
EXFAT_B_TO_CLU_ROUND_UP(i_size_read(new_inode),
sbi), new_ei->flags);
exfat_bytes_to_cluster_round_up(sbi, i_size_read(new_inode)),
new_ei->flags);
if (exfat_free_cluster(new_inode, &new_clu_to_free)) {
/* 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_size = le64_to_cpu(ep->dentry.upcase.size);
sector = exfat_cluster_to_sector(sbi, tbl_clu);
num_sectors = ((tbl_size - 1) >> blksize_bits) + 1;
ret = exfat_load_upcase_table(sb, sector, num_sectors,
le32_to_cpu(ep->dentry.upcase.checksum));
if (tbl_size) {
sector = exfat_cluster_to_sector(sbi, tbl_clu);
num_sectors = ((tbl_size - 1) >> blksize_bits) + 1;
ret = exfat_load_upcase_table(sb, sector, num_sectors,
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);
if (ret && ret != -EIO) {
/* free memory from exfat_load_upcase_table call */
exfat_free_upcase_table(sbi);
@@ -790,6 +795,8 @@ int exfat_create_upcase_table(struct super_block *sb)
return -EIO;
}
exfat_fs_error(sb, "no upcase table entry. Please run fsck");
load_default:
/* load default upcase table */
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_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);
if (num_subdirs < 0)
@@ -499,6 +499,7 @@ static int exfat_read_boot_sector(struct super_block *sb)
if (p_boot->num_fats == 2)
sbi->FAT2_start_sector += sbi->num_FAT_sectors;
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);
/* because the cluster index starts with 2 */
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.
*/
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 */
if (exfat_calibrate_blocksize(sb, 1 << p_boot->sect_size_bits))