Commit 4165cee7 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull exfat updates from Namjae Jeon:

 - Clean-up unnecessary codes as ->valid_size is supported

 - buffered-IO fallback is no longer needed when using direct-IO

 - Move ->valid_size extension from mmap to ->page_mkwrite. This
   improves the overhead caused by unnecessary zero-out during mmap.

 - Fix memleaks from exfat_load_bitmap() and exfat_create_upcase_table()

 - Add sops->shutdown and ioctl

 - Add Yuezhang Mo as a reviwer

* tag 'exfat-for-6.12-rc1' of git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat:
  MAINTAINERS: exfat: add myself as reviewer
  exfat: resolve memory leak from exfat_create_upcase_table()
  exfat: move extend valid_size into ->page_mkwrite()
  exfat: fix memory leak in exfat_load_bitmap()
  exfat: Implement sops->shutdown and ioctl
  exfat: do not fallback to buffered write
  exfat: drop ->i_size_ondisk
parents 79952bdc cb7d8501
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -8466,6 +8466,7 @@ N: binfmt
EXFAT FILE SYSTEM
M:	Namjae Jeon <linkinjeon@kernel.org>
M:	Sungjong Seo <sj1557.seo@samsung.com>
R:	Yuezhang Mo <yuezhang.mo@sony.com>
L:	linux-fsdevel@vger.kernel.org
S:	Maintained
T:	git git://git.kernel.org/pub/scm/linux/kernel/git/linkinjeon/exfat.git
+5 −5
Original line number Diff line number Diff line
@@ -91,11 +91,8 @@ int exfat_load_bitmap(struct super_block *sb)
				return -EIO;

			type = exfat_get_entry_type(ep);
			if (type == TYPE_UNUSED)
				break;
			if (type != TYPE_BITMAP)
				continue;
			if (ep->dentry.bitmap.flags == 0x0) {
			if (type == TYPE_BITMAP &&
			    ep->dentry.bitmap.flags == 0x0) {
				int err;

				err = exfat_allocate_bitmap(sb, ep);
@@ -103,6 +100,9 @@ int exfat_load_bitmap(struct super_block *sb)
				return err;
			}
			brelse(bh);

			if (type == TYPE_UNUSED)
				return -EINVAL;
		}

		if (exfat_get_next_cluster(sb, &clu.dir))
+17 −7
Original line number Diff line number Diff line
@@ -10,6 +10,7 @@
#include <linux/ratelimit.h>
#include <linux/nls.h>
#include <linux/blkdev.h>
#include <uapi/linux/exfat.h>

#define EXFAT_ROOT_INO		1

@@ -148,6 +149,9 @@ enum {
#define DIR_CACHE_SIZE		\
	(DIV_ROUND_UP(EXFAT_DEN_TO_B(ES_MAX_ENTRY_NUM), SECTOR_SIZE) + 1)

/* Superblock flags */
#define EXFAT_FLAGS_SHUTDOWN	1

struct exfat_dentry_namebuf {
	char *lfn;
	int lfnbuf_len; /* usually MAX_UNINAME_BUF_SIZE */
@@ -267,6 +271,8 @@ struct exfat_sb_info {
	unsigned int clu_srch_ptr; /* cluster search pointer */
	unsigned int used_clusters; /* number of used clusters */

	unsigned long s_exfat_flags; /* Exfat superblock flags */

	struct mutex s_lock; /* superblock lock */
	struct mutex bitmap_lock; /* bitmap lock */
	struct exfat_mount_options options;
@@ -309,13 +315,6 @@ struct exfat_inode_info {
	/* for avoiding the race between alloc and free */
	unsigned int cache_valid_id;

	/*
	 * NOTE: i_size_ondisk is 64bits, so must hold ->inode_lock to access.
	 * physically allocated size.
	 */
	loff_t i_size_ondisk;
	/* block-aligned i_size (used in cont_write_begin) */
	loff_t i_size_aligned;
	/* on-disk position of directory entry or 0 */
	loff_t i_pos;
	loff_t valid_size;
@@ -338,6 +337,11 @@ static inline struct exfat_inode_info *EXFAT_I(struct inode *inode)
	return container_of(inode, struct exfat_inode_info, vfs_inode);
}

static inline int exfat_forced_shutdown(struct super_block *sb)
{
	return test_bit(EXFAT_FLAGS_SHUTDOWN, &EXFAT_SB(sb)->s_exfat_flags);
}

/*
 * If ->i_mode can't hold 0222 (i.e. ATTR_RO), we use ->i_attrs to
 * save ATTR_RO instead of ->i_mode.
@@ -417,6 +421,11 @@ static inline bool is_valid_cluster(struct exfat_sb_info *sbi,
	return clus >= EXFAT_FIRST_CLUSTER && clus < sbi->num_clusters;
}

static inline loff_t exfat_ondisk_size(const struct inode *inode)
{
	return ((loff_t)inode->i_blocks) << 9;
}

/* super.c */
int exfat_set_volume_dirty(struct super_block *sb);
int exfat_clear_volume_dirty(struct super_block *sb);
@@ -461,6 +470,7 @@ int exfat_file_fsync(struct file *file, loff_t start, loff_t end, int datasync);
long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
long exfat_compat_ioctl(struct file *filp, unsigned int cmd,
				unsigned long arg);
int exfat_force_shutdown(struct super_block *sb, u32 flags);

/* namei.c */
extern const struct dentry_operations exfat_dentry_ops;
+68 −42
Original line number Diff line number Diff line
@@ -29,7 +29,7 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
	if (ret)
		return ret;

	num_clusters = EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi);
	num_clusters = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi);
	new_num_clusters = EXFAT_B_TO_CLU_ROUND_UP(size, sbi);

	if (new_num_clusters == num_clusters)
@@ -74,8 +74,6 @@ static int exfat_cont_expand(struct inode *inode, loff_t size)
	/* Expanded range not zeroed, do not update valid_size */
	i_size_write(inode, size);

	ei->i_size_aligned = round_up(size, sb->s_blocksize);
	ei->i_size_ondisk = ei->i_size_aligned;
	inode->i_blocks = round_up(size, sbi->cluster_size) >> 9;
	mark_inode_dirty(inode);

@@ -159,7 +157,7 @@ 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_ROUND_UP(ei->i_size_ondisk, sbi);
	num_clusters_phys = EXFAT_B_TO_CLU(exfat_ondisk_size(inode), sbi);

	exfat_chain_set(&clu, ei->start_clu, num_clusters_phys, ei->flags);

@@ -245,8 +243,6 @@ void exfat_truncate(struct inode *inode)
	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 blocksize = i_blocksize(inode);
	loff_t aligned_size;
	int err;

	mutex_lock(&sbi->s_lock);
@@ -264,17 +260,6 @@ void exfat_truncate(struct inode *inode)

	inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
write_size:
	aligned_size = i_size_read(inode);
	if (aligned_size & (blocksize - 1)) {
		aligned_size |= (blocksize - 1);
		aligned_size++;
	}

	if (ei->i_size_ondisk > i_size_read(inode))
		ei->i_size_ondisk = aligned_size;

	if (ei->i_size_aligned > i_size_read(inode))
		ei->i_size_aligned = aligned_size;
	mutex_unlock(&sbi->s_lock);
}

@@ -302,6 +287,9 @@ int exfat_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
	unsigned int ia_valid;
	int error;

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

	if ((attr->ia_valid & ATTR_SIZE) &&
	    attr->ia_size > i_size_read(inode)) {
		error = exfat_cont_expand(inode, attr->ia_size);
@@ -485,6 +473,19 @@ static int exfat_ioctl_fitrim(struct inode *inode, unsigned long arg)
	return 0;
}

static int exfat_ioctl_shutdown(struct super_block *sb, unsigned long arg)
{
	u32 flags;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	if (get_user(flags, (__u32 __user *)arg))
		return -EFAULT;

	return exfat_force_shutdown(sb, flags);
}

long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
	struct inode *inode = file_inode(filp);
@@ -495,6 +496,8 @@ long exfat_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
		return exfat_ioctl_get_attributes(inode, user_attr);
	case FAT_IOCTL_SET_ATTRIBUTES:
		return exfat_ioctl_set_attributes(filp, user_attr);
	case EXFAT_IOC_SHUTDOWN:
		return exfat_ioctl_shutdown(inode->i_sb, arg);
	case FITRIM:
		return exfat_ioctl_fitrim(inode, arg);
	default:
@@ -515,6 +518,9 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
	struct inode *inode = filp->f_mapping->host;
	int err;

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

	err = __generic_file_fsync(filp, start, end, datasync);
	if (err)
		return err;
@@ -526,32 +532,32 @@ int exfat_file_fsync(struct file *filp, loff_t start, loff_t end, int datasync)
	return blkdev_issue_flush(inode->i_sb->s_bdev);
}

static int exfat_file_zeroed_range(struct file *file, loff_t start, loff_t end)
static int exfat_extend_valid_size(struct file *file, loff_t new_valid_size)
{
	int err;
	loff_t pos;
	struct inode *inode = file_inode(file);
	struct exfat_inode_info *ei = EXFAT_I(inode);
	struct address_space *mapping = inode->i_mapping;
	const struct address_space_operations *ops = mapping->a_ops;

	while (start < end) {
		u32 zerofrom, len;
	pos = ei->valid_size;
	while (pos < new_valid_size) {
		u32 len;
		struct folio *folio;

		zerofrom = start & (PAGE_SIZE - 1);
		len = PAGE_SIZE - zerofrom;
		if (start + len > end)
			len = end - start;
		len = PAGE_SIZE - (pos & (PAGE_SIZE - 1));
		if (pos + len > new_valid_size)
			len = new_valid_size - pos;

		err = ops->write_begin(file, mapping, start, len, &folio, NULL);
		err = ops->write_begin(file, mapping, pos, len, &folio, NULL);
		if (err)
			goto out;

		folio_zero_range(folio, offset_in_folio(folio, start), len);

		err = ops->write_end(file, mapping, start, len, len, folio, NULL);
		err = ops->write_end(file, mapping, pos, len, len, folio, NULL);
		if (err < 0)
			goto out;
		start += len;
		pos += len;

		balance_dirty_pages_ratelimited(mapping);
		cond_resched();
@@ -579,7 +585,7 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
		goto unlock;

	if (pos > valid_size) {
		ret = exfat_file_zeroed_range(file, valid_size, pos);
		ret = exfat_extend_valid_size(file, pos);
		if (ret < 0 && ret != -ENOSPC) {
			exfat_err(inode->i_sb,
				"write: fail to zero from %llu to %llu(%zd)",
@@ -613,26 +619,46 @@ static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
	return ret;
}

static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma)
static vm_fault_t exfat_page_mkwrite(struct vm_fault *vmf)
{
	int ret;
	int err;
	struct vm_area_struct *vma = vmf->vma;
	struct file *file = vma->vm_file;
	struct inode *inode = file_inode(file);
	struct exfat_inode_info *ei = EXFAT_I(inode);
	loff_t start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
	loff_t end = min_t(loff_t, i_size_read(inode),
	loff_t start, end;

	if (!inode_trylock(inode))
		return VM_FAULT_RETRY;

	start = ((loff_t)vma->vm_pgoff << PAGE_SHIFT);
	end = min_t(loff_t, i_size_read(inode),
			start + vma->vm_end - vma->vm_start);

	if ((vma->vm_flags & VM_WRITE) && ei->valid_size < end) {
		ret = exfat_file_zeroed_range(file, ei->valid_size, end);
		if (ret < 0) {
			exfat_err(inode->i_sb,
				  "mmap: fail to zero from %llu to %llu(%d)",
				  start, end, ret);
			return ret;
	if (ei->valid_size < end) {
		err = exfat_extend_valid_size(file, end);
		if (err < 0) {
			inode_unlock(inode);
			return vmf_fs_error(err);
		}
	}

	return generic_file_mmap(file, vma);
	inode_unlock(inode);

	return filemap_page_mkwrite(vmf);
}

static const struct vm_operations_struct exfat_file_vm_ops = {
	.fault		= filemap_fault,
	.map_pages	= filemap_map_pages,
	.page_mkwrite	= exfat_page_mkwrite,
};

static int exfat_file_mmap(struct file *file, struct vm_area_struct *vma)
{
	file_accessed(file);
	vma->vm_ops = &exfat_file_vm_ops;
	return 0;
}

const struct file_operations exfat_file_operations = {
+26 −68
Original line number Diff line number Diff line
@@ -102,6 +102,9 @@ int exfat_write_inode(struct inode *inode, struct writeback_control *wbc)
{
	int ret;

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

	mutex_lock(&EXFAT_SB(inode->i_sb)->s_lock);
	ret = __exfat_write_inode(inode, wbc->sync_mode == WB_SYNC_ALL);
	mutex_unlock(&EXFAT_SB(inode->i_sb)->s_lock);
@@ -130,11 +133,9 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
	struct exfat_sb_info *sbi = EXFAT_SB(sb);
	struct exfat_inode_info *ei = EXFAT_I(inode);
	unsigned int local_clu_offset = clu_offset;
	unsigned int num_to_be_allocated = 0, num_clusters = 0;
	unsigned int num_to_be_allocated = 0, num_clusters;

	if (ei->i_size_ondisk > 0)
		num_clusters =
			EXFAT_B_TO_CLU_ROUND_UP(ei->i_size_ondisk, sbi);
	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;
@@ -260,21 +261,6 @@ static int exfat_map_cluster(struct inode *inode, unsigned int clu_offset,
	return 0;
}

static int exfat_map_new_buffer(struct exfat_inode_info *ei,
		struct buffer_head *bh, loff_t pos)
{
	if (buffer_delay(bh) && pos > ei->i_size_aligned)
		return -EIO;
	set_buffer_new(bh);

	/*
	 * Adjust i_size_aligned if i_size_ondisk is bigger than it.
	 */
	if (ei->i_size_ondisk > ei->i_size_aligned)
		ei->i_size_aligned = ei->i_size_ondisk;
	return 0;
}

static int exfat_get_block(struct inode *inode, sector_t iblock,
		struct buffer_head *bh_result, int create)
{
@@ -288,7 +274,6 @@ 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 pos;

	mutex_lock(&sbi->s_lock);
	last_block = EXFAT_B_TO_BLK_ROUND_UP(i_size_read(inode), sb);
@@ -316,12 +301,6 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
	mapped_blocks = sbi->sect_per_clus - sec_offset;
	max_blocks = min(mapped_blocks, max_blocks);

	pos = EXFAT_BLK_TO_B((iblock + 1), sb);
	if ((create && iblock >= last_block) || buffer_delay(bh_result)) {
		if (ei->i_size_ondisk < pos)
			ei->i_size_ondisk = pos;
	}

	map_bh(bh_result, sb, phys);
	if (buffer_delay(bh_result))
		clear_buffer_delay(bh_result);
@@ -342,13 +321,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
		}

		/* The area has not been written, map and mark as new. */
		err = exfat_map_new_buffer(ei, bh_result, pos);
		if (err) {
			exfat_fs_error(sb,
					"requested for bmap out of range(pos : (%llu) > i_size_aligned(%llu)\n",
					pos, ei->i_size_aligned);
			goto unlock_ret;
		}
		set_buffer_new(bh_result);

		ei->valid_size = EXFAT_BLK_TO_B(iblock + max_blocks, sb);
		mark_inode_dirty(inode);
@@ -371,7 +344,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
			 * The block has been partially written,
			 * zero the unwritten part and map the block.
			 */
			loff_t size, off;
			loff_t size, off, pos;

			max_blocks = 1;

@@ -382,7 +355,7 @@ static int exfat_get_block(struct inode *inode, sector_t iblock,
			if (!bh_result->b_folio)
				goto done;

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

@@ -432,6 +405,9 @@ static void exfat_readahead(struct readahead_control *rac)
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);
}

@@ -452,6 +428,9 @@ static int exfat_write_begin(struct file *file, struct address_space *mapping,
{
	int ret;

	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)
@@ -469,14 +448,6 @@ static int exfat_write_end(struct file *file, struct address_space *mapping,
	int err;

	err = generic_write_end(file, mapping, pos, len, copied, folio, fsdata);

	if (ei->i_size_aligned < i_size_read(inode)) {
		exfat_fs_error(inode->i_sb,
			"invalid size(size(%llu) > aligned(%llu)\n",
			i_size_read(inode), ei->i_size_aligned);
		return -EIO;
	}

	if (err < len)
		exfat_write_failed(mapping, pos+len);

@@ -504,20 +475,6 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
	int rw = iov_iter_rw(iter);
	ssize_t ret;

	if (rw == WRITE) {
		/*
		 * FIXME: blockdev_direct_IO() doesn't use ->write_begin(),
		 * so we need to update the ->i_size_aligned to block boundary.
		 *
		 * But we must fill the remaining area or hole by nul for
		 * updating ->i_size_aligned
		 *
		 * Return 0, and fallback to normal buffered write.
		 */
		if (EXFAT_I(inode)->i_size_aligned < size)
			return 0;
	}

	/*
	 * Need to use the DIO_LOCKING for avoiding the race
	 * condition of exfat_get_block() and ->truncate().
@@ -531,8 +488,18 @@ static ssize_t exfat_direct_IO(struct kiocb *iocb, struct iov_iter *iter)
	} else
		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 */
	if (rw == READ && pos < ei->valid_size && ei->valid_size < size) {
		iov_iter_revert(iter, size - ei->valid_size);
		iov_iter_zero(size - ei->valid_size, iter);
	}
@@ -667,15 +634,6 @@ static int exfat_fill_inode(struct inode *inode, struct exfat_dir_entry *info)

	i_size_write(inode, size);

	/* ondisk and aligned size should be aligned with block size */
	if (size & (inode->i_sb->s_blocksize - 1)) {
		size |= (inode->i_sb->s_blocksize - 1);
		size++;
	}

	ei->i_size_aligned = size;
	ei->i_size_ondisk = size;

	exfat_save_attr(inode, info->attr);

	inode->i_blocks = round_up(i_size_read(inode), sbi->cluster_size) >> 9;
Loading