linux-next: manual merge of the vfs-brauner tree with the exfat tree

From: Mark Brown

Date: Fri Mar 27 2026 - 13:41:58 EST


Hi all,

Today's linux-next merge of the vfs-brauner tree got a conflict in:

fs/exfat/file.c

between commit:

9b373eacd6e6c4 ("exfat: add iomap buffered I/O support")

from the exfat tree and commit:

5f36c9ca333360 ("fs: Rename generic_file_fsync() to simple_fsync()")

from the vfs-brauner tree.

I fixed it up (see below) and can carry the fix as necessary. This
is now fixed as far as linux-next is concerned, but any non trivial
conflicts should be mentioned to your upstream maintainer when your tree
is submitted for merging. You may also want to consider cooperating

diff --combined fs/exfat/file.c
index 9a30c32b3a0526,4e8d34a75b6638..00000000000000
--- a/fs/exfat/file.c
+++ b/fs/exfat/file.c
@@@ -13,12 -13,9 +13,12 @@@
#include <linux/msdos_fs.h>
#include <linux/writeback.h>
#include <linux/filelock.h>
+#include <linux/falloc.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)
{
@@@ -57,7 -54,7 +57,7 @@@
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;

@@@ -93,45 -90,6 +93,45 @@@ free_clu
return -EIO;
}

+/*
+ * Preallocate space for a file. This implements exfat's fallocate file
+ * operation, which gets called from sys_fallocate system call. User space
+ * requests len bytes at offset. In contrary to fat, we only support
+ * FALLOC_FL_ALLOCATE_RANGE because by leaving the valid data length(VDL)
+ * field, it is unnecessary to zero out the newly allocated clusters.
+ */
+static long exfat_fallocate(struct file *file, int mode,
+ loff_t offset, loff_t len)
+{
+ struct inode *inode = file->f_mapping->host;
+ loff_t newsize = offset + len;
+ int err = 0;
+
+ /* No support for other modes */
+ if (mode != FALLOC_FL_ALLOCATE_RANGE)
+ return -EOPNOTSUPP;
+
+ /* No support for dir */
+ if (!S_ISREG(inode->i_mode))
+ return -EOPNOTSUPP;
+
+ if (unlikely(exfat_forced_shutdown(inode->i_sb)))
+ return -EIO;
+
+ inode_lock(inode);
+
+ if (newsize <= i_size_read(inode))
+ goto error;
+
+ /* This is just an expanding truncate */
+ err = exfat_cont_expand(inode, newsize);
+
+error:
+ inode_unlock(inode);
+
+ return err;
+}
+
static bool exfat_allow_set_time(struct mnt_idmap *idmap,
struct exfat_sb_info *sbi, struct inode *inode)
{
@@@ -337,18 -295,7 +337,18 @@@ int exfat_setattr(struct mnt_idmap *idm

if ((attr->ia_valid & ATTR_SIZE) &&
attr->ia_size > i_size_read(inode)) {
+ loff_t old_size = i_size_read(inode);
+
error = exfat_cont_expand(inode, attr->ia_size);
+ if (!error && attr->ia_size > old_size &&
+ old_size % PAGE_SIZE != 0) {
+ loff_t len = min_t(loff_t,
+ round_up(old_size, PAGE_SIZE) - old_size,
+ attr->ia_size - old_size);
+ error = iomap_zero_range(inode, old_size, len,
+ NULL, &exfat_read_iomap_ops,
+ &exfat_iomap_folio_ops, NULL);
+ }
if (error || attr->ia_valid == ATTR_SIZE)
return error;
attr->ia_valid &= ~ATTR_SIZE;
@@@ -395,10 -342,7 +395,10 @@@
exfat_truncate_inode_atime(inode);

if (attr->ia_valid & ATTR_SIZE) {
- error = exfat_block_truncate_page(inode, attr->ia_size);
+ inode_dio_wait(inode);
+ error = iomap_truncate_page(inode, attr->ia_size, NULL,
+ &exfat_read_iomap_ops,
+ &exfat_iomap_folio_ops, NULL);
if (error)
goto out;

@@@ -633,14 -577,10 +633,14 @@@ int exfat_file_fsync(struct file *filp
if (unlikely(exfat_forced_shutdown(inode->i_sb)))
return -EIO;

- err = simple_fsync_noflush(filp, start, end, datasync);
+ err = file_write_and_wait_range(filp, start, end);
if (err)
return err;

+ if (!datasync)
+ err = __exfat_write_inode(inode, 1);
+ write_inode_now(inode, !datasync);
+
err = sync_blockdev(inode->i_sb->s_bdev);
if (err)
return err;
@@@ -648,72 -588,44 +648,72 @@@
return blkdev_issue_flush(inode->i_sb->s_bdev);
}

-static int exfat_extend_valid_size(struct inode *inode, loff_t new_valid_size)
+int exfat_extend_valid_size(struct inode *inode, loff_t off, bool bsync)
{
- 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;
+ struct exfat_sb_info *sbi = EXFAT_SB(inode->i_sb);
+ loff_t old_valid_size;
+ int ret = 0;

- pos = ei->valid_size;
- while (pos < new_valid_size) {
- u32 len;
- struct folio *folio;
- unsigned long off;
+ mutex_lock(&sbi->s_lock);
+ old_valid_size = ei->valid_size;
+ mutex_unlock(&sbi->s_lock);

- 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();
+ if (old_valid_size < off) {
+ ret = iomap_zero_range(inode, old_valid_size,
+ off - old_valid_size, NULL,
+ &exfat_write_iomap_ops, &exfat_iomap_folio_ops,
+ NULL);
+ if (!ret && bsync)
+ ret = filemap_write_and_wait_range(inode->i_mapping,
+ old_valid_size,
+ off - 1);
}

- return 0;
+ return ret;
+}

+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)
+ goto out;
+
+ if (iov_iter_count(from)) {
+ loff_t offset, end;
+ ssize_t written;
+ int ret2;
+
+ offset = iocb->ki_pos;
+ iocb->ki_flags &= ~IOCB_DIRECT;
+ written = iomap_file_buffered_write(iocb, from,
+ &exfat_write_iomap_ops, &exfat_iomap_folio_ops,
+ NULL);
+ if (written < 0) {
+ ret = written;
+ goto out;
+ }
+
+ ret += written;
+ end = iocb->ki_pos + written - 1;
+ ret2 = filemap_write_and_wait_range(iocb->ki_filp->f_mapping,
+ offset, end);
+ if (ret2) {
+ ret = -EIO;
+ goto out;
+ }
+ if (!ret2)
+ invalidate_mapping_pages(iocb->ki_filp->f_mapping,
+ offset >> PAGE_SHIFT,
+ end >> PAGE_SHIFT);
+ }
out:
- return err;
+ return ret;
}

static ssize_t exfat_file_write_iter(struct kiocb *iocb, struct iov_iter *iter)
@@@ -724,7 -636,6 +724,7 @@@
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;
@@@ -740,18 -651,28 +740,18 @@@
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) {
- ret = exfat_extend_valid_size(inode, pos);
- if (ret < 0 && ret != -ENOSPC) {
- exfat_err(inode->i_sb,
- "write: fail to zero from %llu to %llu(%zd)",
- valid_size, pos, ret);
- }
- if (ret < 0)
- 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, &exfat_iomap_folio_ops,
+ NULL);
if (ret < 0)
goto unlock;

@@@ -778,52 -699,37 +778,52 @@@ unlock
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) {
+ size_t count = iov_iter_count(iter);
+
+ if ((iocb->ki_pos | count) & (inode->i_sb->s_blocksize - 1)) {
+ ret = -EINVAL;
+ goto inode_unlock;
+ }
+
+ file_accessed(iocb->ki_filp);
+ ret = iomap_dio_rw(iocb, iter, &exfat_read_iomap_ops, NULL, 0,
+ NULL, 0);
+ } else {
+ ret = generic_file_read_iter(iocb, iter);
+ }
+
+inode_unlock:
+ 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;

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));
-
- 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);
- }
- }
+ 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_mkwrite_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 = {
@@@ -839,21 -745,6 +839,21 @@@ static int exfat_file_mmap_prepare(stru
if (unlikely(exfat_forced_shutdown(file_inode(desc->file)->i_sb)))
return -EIO;

+ if (vma_desc_test_flags(desc, VMA_WRITE_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, false);
+ if (err)
+ return err;
+ }
+ }
+
file_accessed(file);
desc->vm_ops = &exfat_file_vm_ops;
return 0;
@@@ -868,50 -759,8 +868,50 @@@ static ssize_t exfat_splice_read(struc
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_read_iomap_ops);
+ inode_unlock_shared(inode);
+ break;
+ case SEEK_DATA:
+ inode_lock_shared(inode);
+ offset = iomap_seek_data(inode, offset, &exfat_read_iomap_ops);
+ inode_unlock_shared(inode);
+ break;
+ default:
+ return generic_file_llseek_size(file, offset, whence,
+ inode->i_sb->s_maxbytes,
+ i_size_read(inode));
+ }
+ 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,
@@@ -922,7 -771,6 +922,7 @@@
.fsync = exfat_file_fsync,
.splice_read = exfat_splice_read,
.splice_write = iter_file_splice_write,
+ .fallocate = exfat_fallocate,
.setlease = generic_setlease,
};

with the maintainer of the conflicting tree to minimise any particularly
complex conflicts.

Attachment: signature.asc
Description: PGP signature