Commit 172f7c91 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
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
parents f64a72bc c73e680d
Loading
Loading
Loading
Loading
+0 −14
Original line number Diff line number Diff line
@@ -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;
@@ -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;
}

+0 −2
Original line number Diff line number Diff line
@@ -14,8 +14,6 @@

#define EXFAT_ROOT_INO		1

#define EXFAT_CLUSTERS_UNTRACKED (~0u)

/*
 * exfat error flags
 */
+30 −1
Original line number Diff line number Diff line
@@ -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)
{
@@ -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;
@@ -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++;

@@ -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,
+27 −2
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -672,14 +685,26 @@ 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
@@ -687,7 +712,7 @@ const struct file_operations exfat_file_operations = {
#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,
};

+83 −59
Original line number Diff line number Diff line
@@ -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;

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

	/* 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. */
	/* 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);

		if (iblock + max_blocks < valid_blks) {
			/* The range has been written, map it */
		goto done;
		} else if (iblock < valid_blks) {
	}

	/*
			 * The area has been partially written,
			 * map the written part.
	 * The area has just one block partially written.
	 * In that case, we should read and fill the unwritten part of
	 * a block with zero.
	 */
			max_blocks = valid_blks - iblock;
			goto done;
		} else if (iblock == valid_blks &&
	if (bh_result->b_folio && 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;
		loff_t size, pos;
		void *addr;

		max_blocks = 1;

		/*
			 * For direct read, the unwritten part will be zeroed in
			 * exfat_direct_IO()
		 * 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 (!bh_result->b_folio)
		if (!folio_buffers(bh_result->b_folio)) {
			err = -EAGAIN;
			goto done;
		}

		pos = EXFAT_BLK_TO_B(iblock, sb);
		size = ei->valid_size - pos;
			off = pos & (PAGE_SIZE - 1);
		addr = folio_address(bh_result->b_folio) +
			offset_in_folio(bh_result->b_folio, pos);

			folio_set_bh(bh_result, bh_result->b_folio, off);
		/* 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 unlock_ret;
			goto done;

		/* Zero unwritten part of a block */
		memset(bh_result->b_data + size, 0, bh_result->b_size - size);
		err = 0;
		goto done;
	}

			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.
	 * 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;
Loading