Skip to content

Commit

Permalink
Merge tag 'exfat-for-6.15-rc1' of git://git.kernel.org/pub/scm/linux/…
Browse files Browse the repository at this point in the history
…kernel/git/linkinjeon/exfat

Pull exfat updates from Namjae Jeon:

 - Fix random stack corruption and incorrect error returns in
   exfat_get_block()

 - Optimize exfat_get_block() by improving checking corner cases

 - Fix an endless loop by self-linked chain in exfat_find_last_cluster

 - Remove dead EXFAT_CLUSTERS_UNTRACKED codes

 - Add missing shutdown check

 - Improve the delete performance with discard mount option

* tag 'exfat-for-6.15-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat:
  exfat: call bh_read in get_block only when necessary
  exfat: fix potential wrong error return from get_block
  exfat: fix missing shutdown check
  exfat: fix the infinite loop in exfat_find_last_cluster()
  exfat: fix random stack corruption after get_block
  exfat: remove count used cluster from exfat_statfs()
  exfat: support batch discard of clusters when freeing clusters
  • Loading branch information
Linus Torvalds committed Apr 1, 2025
2 parents f64a72b + c73e680 commit 172f7c9
Show file tree
Hide file tree
Showing 6 changed files with 140 additions and 88 deletions.
14 changes: 0 additions & 14 deletions fs/exfat/balloc.c
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,6 @@ int exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync)
unsigned int ent_idx;
struct super_block *sb = inode->i_sb;
struct exfat_sb_info *sbi = EXFAT_SB(sb);
struct exfat_mount_options *opts = &sbi->options;

if (!is_valid_cluster(sbi, clu))
return -EIO;
Expand All @@ -163,19 +162,6 @@ int exfat_clear_bitmap(struct inode *inode, unsigned int clu, bool sync)

exfat_update_bh(sbi->vol_amap[i], sync);

if (opts->discard) {
int ret_discard;

ret_discard = sb_issue_discard(sb,
exfat_cluster_to_sector(sbi, clu),
(1 << sbi->sect_per_clus_bits), GFP_NOFS, 0);

if (ret_discard == -EOPNOTSUPP) {
exfat_err(sb, "discard not supported by device, disabling");
opts->discard = 0;
}
}

return 0;
}

Expand Down
2 changes: 0 additions & 2 deletions fs/exfat/exfat_fs.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@

#define EXFAT_ROOT_INO 1

#define EXFAT_CLUSTERS_UNTRACKED (~0u)

/*
* exfat error flags
*/
Expand Down
31 changes: 30 additions & 1 deletion fs/exfat/fatent.c
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,20 @@ int exfat_chain_cont_cluster(struct super_block *sb, unsigned int chain,
return 0;
}

static inline void exfat_discard_cluster(struct super_block *sb,
unsigned int clu, unsigned int num_clusters)
{
int ret;
struct exfat_sb_info *sbi = EXFAT_SB(sb);

ret = sb_issue_discard(sb, exfat_cluster_to_sector(sbi, clu),
sbi->sect_per_clus * num_clusters, GFP_NOFS, 0);
if (ret == -EOPNOTSUPP) {
exfat_err(sb, "discard not supported by device, disabling");
sbi->options.discard = 0;
}
}

/* This function must be called with bitmap_lock held */
static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain)
{
Expand Down Expand Up @@ -196,7 +210,12 @@ static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain
clu++;
num_clusters++;
} while (num_clusters < p_chain->size);

if (sbi->options.discard)
exfat_discard_cluster(sb, p_chain->dir, p_chain->size);
} else {
unsigned int nr_clu = 1;

do {
bool sync = false;
unsigned int n_clu = clu;
Expand All @@ -215,6 +234,16 @@ static int __exfat_free_cluster(struct inode *inode, struct exfat_chain *p_chain

if (exfat_clear_bitmap(inode, clu, (sync && IS_DIRSYNC(inode))))
break;

if (sbi->options.discard) {
if (n_clu == clu + 1)
nr_clu++;
else {
exfat_discard_cluster(sb, clu - nr_clu + 1, nr_clu);
nr_clu = 1;
}
}

clu = n_clu;
num_clusters++;

Expand Down Expand Up @@ -265,7 +294,7 @@ int exfat_find_last_cluster(struct super_block *sb, struct exfat_chain *p_chain,
clu = next;
if (exfat_ent_get(sb, clu, &next))
return -EIO;
} while (next != EXFAT_EOF_CLUSTER);
} while (next != EXFAT_EOF_CLUSTER && count <= p_chain->size);

if (p_chain->size != count) {
exfat_fs_error(sb,
Expand Down
29 changes: 27 additions & 2 deletions fs/exfat/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -582,6 +582,9 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
loff_t pos = iocb->ki_pos;
loff_t valid_size;

if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;

inode_lock(inode);

valid_size = ei->valid_size;
Expand Down Expand Up @@ -635,6 +638,16 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
return ret;
}

static ssize_t exfat_file_read_iter(struct kiocb *iocb, struct iov_iter *iter)
{
struct inode *inode = file_inode(iocb->ki_filp);

if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;

return generic_file_read_iter(iocb, iter);
}

static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
{
int err;
Expand Down Expand Up @@ -672,22 +685,34 @@ static const struct vm_operations_struct exfat_file_vm_ops = {

static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma)
{
if (unlikely(exfat_forced_shutdown(file_inode(file)->i_sb)))
return -EIO;

file_accessed(file);
vma->vm_ops = &exfat_file_vm_ops;
return 0;
}

static ssize_t exfat_splice_read(struct file *in, loff_t *ppos,
struct pipe_inode_info *pipe, size_t len, unsigned int flags)
{
if (unlikely(exfat_forced_shutdown(file_inode(in)->i_sb)))
return -EIO;

return filemap_splice_read(in, ppos, pipe, len, flags);
}

const struct file_operations exfat_file_operations = {
.llseek = generic_file_llseek,
.read_iter = generic_file_read_iter,
.read_iter = exfat_file_read_iter,
.write_iter = exfat_file_write_iter,
.unlocked_ioctl = exfat_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = exfat_compat_ioctl,
#endif
.mmap = exfat_file_mmap,
.fsync = exfat_file_fsync,
.splice_read = filemap_splice_read,
.splice_read = exfat_splice_read,
.splice_write = iter_file_splice_write,
};

Expand Down
142 changes: 83 additions & 59 deletions fs/exfat/inode.c
Original file line number Diff line number Diff line change
Expand Up @@ -274,9 +274,11 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
sector_t last_block;
sector_t phys = 0;
sector_t valid_blks;
loff_t i_size;

mutex_lock(&sbi->s_lock);
last_block = EXFAT_B_TO_BLK_ROUND_UP(i_size_read(inode), sb);
i_size = i_size_read(inode);
last_block = EXFAT_B_TO_BLK_ROUND_UP(i_size, sb);
if (iblock >= last_block && !create)
goto done;

Expand Down Expand Up @@ -305,77 +307,99 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
if (buffer_delay(bh_result))
clear_buffer_delay(bh_result);

if (create) {
/*
* 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);

if (iblock + max_blocks < valid_blks) {
/* The range has been written, map it */
goto done;
} else if (iblock < valid_blks) {
/*
* The range has been partially written,
* map the written part.
*/
max_blocks = valid_blks - iblock;
goto done;
}
/* The range has been fully written, map it */
if (iblock + max_blocks < valid_blks)
goto done;

/* The area has not been written, map and mark as new. */
set_buffer_new(bh_result);
/* 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);
} else {
valid_blks = EXFAT_B_TO_BLK(ei->valid_size, sb);
goto done;
}

if (iblock + max_blocks < valid_blks) {
/* The range has been written, map it */
/*
* 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;
} else if (iblock < valid_blks) {
/*
* The area has been partially written,
* map the written part.
*/
max_blocks = valid_blks - iblock;
}

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;
} else if (iblock == valid_blks &&
(ei->valid_size & (sb->s_blocksize - 1))) {
/*
* The block has been partially written,
* zero the unwritten part and map the block.
*/
loff_t size, off, pos;

max_blocks = 1;

/*
* For direct read, the unwritten part will be zeroed in
* exfat_direct_IO()
*/
if (!bh_result->b_folio)
goto done;

pos = EXFAT_BLK_TO_B(iblock, sb);
size = ei->valid_size - pos;
off = pos & (PAGE_SIZE - 1);

folio_set_bh(bh_result, bh_result->b_folio, off);
err = bh_read(bh_result, 0);
if (err < 0)
goto unlock_ret;

folio_zero_segment(bh_result->b_folio, off + size,
off + sb->s_blocksize);
} else {
/*
* The range has not been written, clear the mapped flag
* to only zero the cache and do not read from disk.
*/
clear_buffer_mapped(bh_result);
}

/* 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;
Expand Down
10 changes: 0 additions & 10 deletions fs/exfat/super.c
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,6 @@ static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf)
struct exfat_sb_info *sbi = EXFAT_SB(sb);
unsigned long long id = huge_encode_dev(sb->s_bdev->bd_dev);

if (sbi->used_clusters == EXFAT_CLUSTERS_UNTRACKED) {
mutex_lock(&sbi->s_lock);
if (exfat_count_used_clusters(sb, &sbi->used_clusters)) {
mutex_unlock(&sbi->s_lock);
return -EIO;
}
mutex_unlock(&sbi->s_lock);
}

buf->f_type = sb->s_magic;
buf->f_bsize = sbi->cluster_size;
buf->f_blocks = sbi->num_clusters - 2; /* clu 0 & 1 */
Expand Down Expand Up @@ -531,7 +522,6 @@ static int exfat_read_boot_sector(struct super_block *sb)
sbi->vol_flags = le16_to_cpu(p_boot->vol_flags);
sbi->vol_flags_persistent = sbi->vol_flags & (VOLUME_DIRTY | MEDIA_FAILURE);
sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER;
sbi->used_clusters = EXFAT_CLUSTERS_UNTRACKED;

/* check consistencies */
if ((u64)sbi->num_FAT_sectors << p_boot->sect_size_bits <
Expand Down

0 comments on commit 172f7c9

Please sign in to comment.