Re: linux-next: manual merge of the ntfs3 tree with the nfsd, vfs-brauner trees

From: Mark Brown

Date: Fri May 29 2026 - 09:17:53 EST


On Fri, May 29, 2026 at 01:26:10PM +0100, Mark Brown wrote:
> Hi all,
>
> Today's linux-next merge of the ntfs3 tree got conflicts in:
>
> fs/ntfs3/file.c
> fs/ntfs3/namei.c
> fs/ntfs3/ntfs_fs.h
>
> between commit:
>
> eeb7b37b9700f ("ntfs3: Implement fileattr_get for case sensitivity")
>
> from the nfsd, vfs-brauner trees and commit:
>
> 245bbdd2b9d65 ("fs/ntfs3: add fileattr support")
>
> from the ntfs3 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
> with the maintainer of the conflicting tree to minimise any particularly
> complex conflicts.

Sorry, actual diff here taking account of the two additions in different
places with different content of fileattr_get():

diff --combined fs/ntfs3/file.c
index b2dfb46e60c6d,ad9350d7fc3fd..0000000000000
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@@ -89,80 -89,6 +89,91 @@@ static int ntfs_ioctl_fitrim(struct ntf
return 0;
}

+/*
+ * ntfs_fileattr_get - inode_operations::fileattr_get
+ */
+int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct inode *inode = d_inode(dentry);
+ struct ntfs_inode *ni = ntfs_i(inode);
+ u32 flags = 0;
+
+ /* Avoid any operation if inode is bad. */
+ if (unlikely(is_bad_ni(ni)))
+ return -EINVAL;
+
- if (inode->i_flags & S_IMMUTABLE)
- flags |= FS_IMMUTABLE_FL;
++ /*
++ * NTFS preserves case (the default). Case sensitivity depends on
++ * mount options: with "nocase", NTFS is case-insensitive;
++ * otherwise it is case-sensitive.
++ */
++ if (sbi->options->nocase) {
++ fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
++ fa->flags |= FS_CASEFOLD_FL;
++ }
++ if (inode->i_flags & S_IMMUTABLE) {
++ fa->fsx_xflags |= FS_XFLAG_IMMUTABLE;
++ fa->flags |= FS_IMMUTABLE_FL;
++ }
+
+ if (inode->i_flags & S_APPEND)
+ flags |= FS_APPEND_FL;
+
+ if (is_compressed(ni))
+ flags |= FS_COMPR_FL;
+
+ if (is_encrypted(ni))
+ flags |= FS_ENCRYPT_FL;
+
+ if (ni->nodump)
+ flags |= FS_NODUMP_FL;
+
+ fileattr_fill_flags(fa, flags);
+
+ return 0;
+}
+
+/*
+ * ntfs_fileattr_set - inode_operations::fileattr_set
+ */
+int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct file_kattr *fa)
+{
+ struct inode *inode = d_inode(dentry);
+ struct ntfs_inode *ni = ntfs_i(inode);
+ u32 flags = fa->flags;
+ unsigned int new_fl = 0;
+
+ /* Avoid any operation if inode is bad. */
+ if (unlikely(is_bad_ni(ni)))
+ return -EINVAL;
+
+ if (fileattr_has_fsx(fa))
+ return -EOPNOTSUPP;
+
+ if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL))
+ return -EOPNOTSUPP;
+
+ if (flags & FS_IMMUTABLE_FL)
+ new_fl |= S_IMMUTABLE;
+
+ if (flags & FS_APPEND_FL)
+ new_fl |= S_APPEND;
+
+ inode_set_flags(inode, new_fl, S_IMMUTABLE | S_APPEND);
+
+ /* Save nodump flag to return in ntfs_getattr. */
+ if (flags & FS_NODUMP_FL)
+ ni->nodump = 1;
+ else
+ ni->nodump = 0;
+
+ inode_set_ctime_current(inode);
+ mark_inode_dirty(inode);
+
+ return 0;
+}
+
static int ntfs_ioctl_get_volume_label(struct ntfs_sb_info *sbi, u8 __user *buf)
{
if (copy_to_user(buf, sbi->volume.label, FSLABEL_MAX))
@@@ -254,34 -180,34 +265,6 @@@ long ntfs_compat_ioctl(struct file *fil
}
#endif

--/*
-- * ntfs_fileattr_get - inode_operations::fileattr_get
-- */
--int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
--{
-- struct inode *inode = d_inode(dentry);
-- struct ntfs_sb_info *sbi = inode->i_sb->s_fs_info;
--
-- /* Avoid any operation if inode is bad. */
-- if (unlikely(is_bad_ni(ntfs_i(inode))))
-- return -EINVAL;
--
-- /*
-- * NTFS preserves case (the default). Case sensitivity depends on
-- * mount options: with "nocase", NTFS is case-insensitive;
-- * otherwise it is case-sensitive.
-- */
-- if (sbi->options->nocase) {
-- fa->fsx_xflags |= FS_XFLAG_CASEFOLD;
-- fa->flags |= FS_CASEFOLD_FL;
-- }
-- if (inode->i_flags & S_IMMUTABLE) {
-- fa->fsx_xflags |= FS_XFLAG_IMMUTABLE;
-- fa->flags |= FS_IMMUTABLE_FL;
-- }
-- return 0;
--}
--
/*
* ntfs_getattr - inode_operations::getattr
*/
@@@ -305,9 -231,6 +288,9 @@@ int ntfs_getattr(struct mnt_idmap *idma
if (inode->i_flags & S_APPEND)
stat->attributes |= STATX_ATTR_APPEND;

+ if (ni->nodump)
+ stat->attributes |= STATX_ATTR_NODUMP;
+
if (is_compressed(ni))
stat->attributes |= STATX_ATTR_COMPRESSED;

@@@ -351,44 -274,18 +334,44 @@@ static int ntfs_extend_initialized_size
return 0;
}

+/* Zero pagecache after 'from'. */
+static void ntfs_zero_tail(struct address_space *mapping, loff_t from)
+{
+ struct folio_batch fbatch;
+ pgoff_t index = from >> PAGE_SHIFT;
+ unsigned nr, i;
+
+ folio_batch_init(&fbatch);
+
+ nr = filemap_get_folios(mapping, &index, -1, &fbatch);
+
+ for (i = 0; i < nr; i++) {
+ struct folio *folio = fbatch.folios[i];
+ u32 st = folio_pos(folio) < from ?
+ offset_in_folio(folio, from) :
+ 0;
+
+ folio_lock(folio);
+ folio_zero_segment(folio, st, folio_size(folio));
+
+ folio_unlock(folio);
+ }
+ folio_batch_release(&fbatch);
+}
+
static void ntfs_filemap_close(struct vm_area_struct *vma)
{
struct inode *inode = file_inode(vma->vm_file);
struct ntfs_inode *ni = ntfs_i(inode);
+ u64 i_size = i_size_read(inode);
u64 from = (u64)vma->vm_pgoff << PAGE_SHIFT;
- u64 to = min_t(u64, i_size_read(inode),
- from + vma->vm_end - vma->vm_start);
+ u64 to = min(i_size, from + vma->vm_end - vma->vm_start);

if (ni->i_valid < to) {
ni->i_valid = to;
mark_inode_dirty(inode);
}
+ ntfs_zero_tail(inode->i_mapping, ni->i_valid);
}

/* Copy of generic_file_vm_ops. */
@@@ -478,6 -375,93 +461,6 @@@ out
return err;
}

-static int ntfs_extend(struct inode *inode, loff_t pos, size_t count,
- struct file *file)
-{
- struct ntfs_inode *ni = ntfs_i(inode);
- struct address_space *mapping = inode->i_mapping;
- loff_t end = pos + count;
- bool extend_init = file && pos > ni->i_valid;
- int err;
-
- if (end <= inode->i_size && !extend_init)
- return 0;
-
- /* Mark rw ntfs as dirty. It will be cleared at umount. */
- ntfs_set_state(ni->mi.sbi, NTFS_DIRTY_DIRTY);
-
- if (end > inode->i_size) {
- /*
- * Normal files: increase file size, allocate space.
- * Sparse/Compressed: increase file size. No space allocated.
- */
- err = ntfs_set_size(inode, end);
- if (err)
- goto out;
- }
-
- if (extend_init && !is_compressed(ni)) {
- err = ntfs_extend_initialized_size(file, ni, pos);
- if (err)
- goto out;
- } else {
- err = 0;
- }
-
- inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
- mark_inode_dirty(inode);
-
- if (IS_SYNC(inode)) {
- int err2;
-
- err = filemap_fdatawrite_range(mapping, pos, end - 1);
- err2 = write_inode_now(inode, 1);
- if (!err)
- err = err2;
- if (!err)
- err = filemap_fdatawait_range(mapping, pos, end - 1);
- }
-
-out:
- return err;
-}
-
-static int ntfs_truncate(struct inode *inode, loff_t new_size)
-{
- int err;
- struct ntfs_inode *ni = ntfs_i(inode);
- u64 new_valid = min_t(u64, ni->i_valid, new_size);
-
- truncate_setsize(inode, new_size);
-
- ni_lock(ni);
-
- down_write(&ni->file.run_lock);
- err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run, new_size,
- &new_valid, ni->mi.sbi->options->prealloc, NULL,
- false);
- up_write(&ni->file.run_lock);
-
- ni->i_valid = new_valid;
-
- ni_unlock(ni);
-
- if (err)
- return err;
-
- ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
- inode_set_mtime_to_ts(inode, inode_set_ctime_current(inode));
- if (!IS_DIRSYNC(inode)) {
- mark_inode_dirty(inode);
- } else {
- err = ntfs_sync_inode(inode);
- if (err)
- return err;
- }
-
- return 0;
-}
-
/*
* ntfs_fallocate - file_operations::ntfs_fallocate
*
@@@ -684,25 -668,57 +667,25 @@@ static long ntfs_fallocate(struct file
if (is_supported_holes) {
CLST vcn = vbo >> cluster_bits;
CLST cend = bytes_to_cluster(sbi, end);
- CLST cend_v = bytes_to_cluster(sbi, ni->i_valid);
CLST lcn, clen;
bool new;

- if (cend_v > cend)
- cend_v = cend;
-
/*
* Allocate and zero new clusters.
- * Zeroing these clusters may be too long.
- */
- for (; vcn < cend_v; vcn += clen) {
- err = attr_data_get_block(ni, vcn, cend_v - vcn,
- &lcn, &clen, &new,
- true, NULL, false);
- if (err)
- goto out;
- }
-
- /*
- * Moving up 'valid size'.
- */
- err = ntfs_extend_initialized_size(
- file, ni, (u64)cend_v << cluster_bits);
- if (err)
- goto out;
-
- /*
- * Allocate but not zero new clusters.
*/
for (; vcn < cend; vcn += clen) {
err = attr_data_get_block(ni, vcn, cend - vcn,
&lcn, &clen, &new,
- false, NULL, false);
+ true, NULL, false);
if (err)
goto out;
}
}

if (mode & FALLOC_FL_KEEP_SIZE) {
- ni_lock(ni);
- /* True - Keep preallocated. */
- err = attr_set_size(ni, ATTR_DATA, NULL, 0,
- &ni->file.run, i_size, &ni->i_valid,
- true);
- ni_unlock(ni);
+ err = ntfs_set_size(inode, i_size);
if (err)
goto out;
- i_size_write(inode, i_size);
- } else if (new_size > i_size) {
- i_size_write(inode, new_size);
}
}

@@@ -759,20 -775,16 +742,20 @@@ int ntfs_setattr(struct mnt_idmap *idma
oldsize = i_size_read(inode);
newsize = attr->ia_size;

- if (newsize <= oldsize)
- err = ntfs_truncate(inode, newsize);
- else
- err = ntfs_extend(inode, newsize, 0, NULL);
+ if (newsize != oldsize) {
+ truncate_setsize(inode, newsize);

- if (err)
- goto out;
+ err = ntfs_set_size(inode, newsize);
+ if (err) {
+ i_size_write(inode, oldsize);
+ goto out;
+ }

- ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
- i_size_write(inode, newsize);
+ ni->std_fa |= FILE_ATTRIBUTE_ARCHIVE;
+ ni->ni_flags |= NI_FLAG_UPDATE_PARENT;
+ inode_set_mtime_to_ts(inode,
+ inode_set_ctime_current(inode));
+ }
}

setattr_copy(idmap, inode, attr);
@@@ -1036,7 -1048,7 +1019,7 @@@ static ssize_t ntfs_compress_write(stru
CLST lcn, clen;

frame = valid >> frame_bits;
- frame_vbo = valid & ~(frame_size - 1);
+ frame_vbo = valid & ~(u64)(frame_size - 1);
off = valid & (frame_size - 1);

err = attr_data_get_block(ni, frame << NTFS_LZNT_CUNIT, 1, &lcn,
@@@ -1105,7 -1117,7 +1088,7 @@@
if (bytes > count)
bytes = count;

- frame_vbo = pos & ~(frame_size - 1);
+ frame_vbo = pos & ~(u64)(frame_size - 1);
index = frame_vbo >> PAGE_SHIFT;

if (unlikely(fault_in_iov_iter_readable(from, bytes))) {
@@@ -1246,7 -1258,6 +1229,7 @@@ static ssize_t ntfs_file_write_iter(str
struct file *file = iocb->ki_filp;
struct inode *inode = file_inode(file);
struct ntfs_inode *ni = ntfs_i(inode);
+ loff_t vbo, endbyte;
ssize_t ret, err;

if (!inode_trylock(inode)) {
@@@ -1281,30 -1292,15 +1264,30 @@@
goto out;
}

- ret = ntfs_extend(inode, iocb->ki_pos, ret, file);
- if (ret)
- goto out;
+ vbo = iocb->ki_pos;
+ endbyte = vbo + ret;
+
+ if (endbyte > inode->i_size) {
+ /*
+ * Normal files: increase file size, allocate space.
+ * Sparse/Compressed: increase file size. No space allocated.
+ */
+ ret = ntfs_set_size(inode, endbyte);
+ if (ret)
+ goto out;
+ }

if (is_compressed(ni)) {
ret = ntfs_compress_write(iocb, from);
goto out;
}

+ if (vbo > ni->i_valid) {
+ ret = ntfs_extend_initialized_size(file, ni, vbo);
+ if (ret)
+ goto out;
+ }
+
/* Fallback to buffered I/O if the inode does not support direct I/O. */
if (!(iocb->ki_flags & IOCB_DIRECT) ||
!ntfs_should_use_dio(iocb, from)) {
@@@ -1327,8 -1323,7 +1310,8 @@@
goto out;
}

- ret = iomap_dio_rw(iocb, from, &ntfs_iomap_ops, NULL, 0, NULL, 0);
+ ret = iomap_dio_rw(iocb, from, &ntfs_iomap_ops, NULL,
+ IOMAP_DIO_FORCE_WAIT, NULL, 0);

if (ret == -ENOTBLK) {
/* Returns -ENOTBLK in case of a page invalidation failure for writes.*/
@@@ -1337,7 -1332,7 +1320,7 @@@
}

if (ret >= 0 && iov_iter_count(from)) {
- loff_t offset = iocb->ki_pos, endbyte;
+ vbo = iocb->ki_pos;

iocb->ki_flags &= ~IOCB_DIRECT;
err = iomap_file_buffered_write(iocb, from, &ntfs_iomap_ops,
@@@ -1355,15 -1350,15 +1338,15 @@@
* to complete off the I/O request.
*/
ret += err;
- endbyte = offset + err - 1;
- err = filemap_write_and_wait_range(inode->i_mapping, offset,
+ endbyte = vbo + err - 1;
+ err = filemap_write_and_wait_range(inode->i_mapping, vbo,
endbyte);
if (err) {
ret = err;
goto out;
}

- invalidate_mapping_pages(inode->i_mapping, offset >> PAGE_SHIFT,
+ invalidate_mapping_pages(inode->i_mapping, vbo >> PAGE_SHIFT,
endbyte >> PAGE_SHIFT);
}

@@@ -1444,9 -1439,8 +1427,9 @@@ static int ntfs_file_release(struct ino
down_write(&ni->file.run_lock);

/* Deallocate preallocated. */
- err = attr_set_size(ni, ATTR_DATA, NULL, 0, &ni->file.run,
- inode->i_size, &ni->i_valid, false);
+ err = attr_set_size_ex(ni, ATTR_DATA, NULL, 0, &ni->file.run,
+ inode->i_size, &ni->i_valid, false, NULL,
+ true);

up_write(&ni->file.run_lock);
ni_unlock(ni);
@@@ -1558,12 -1552,7 +1541,12 @@@ static loff_t ntfs_llseek(struct file *
loff_t maxbytes = ntfs_get_maxbytes(ni);
loff_t ret;

- if (whence == SEEK_DATA || whence == SEEK_HOLE) {
+ if (whence != SEEK_DATA && whence != SEEK_HOLE) {
+ ret = generic_file_llseek_size(file, offset, whence, maxbytes,
+ i_size_read(inode));
+ } else if ((unsigned long long)offset >= i_size_read(inode)) {
+ ret = -ENXIO;
+ } else {
inode_lock_shared(inode);
/* Scan file for hole or data. */
ret = ni_seek_data_or_hole(ni, offset, whence == SEEK_DATA);
@@@ -1571,6 -1560,9 +1554,6 @@@

if (ret >= 0)
ret = vfs_setpos(file, ret, maxbytes);
- } else {
- ret = generic_file_llseek_size(file, offset, whence, maxbytes,
- i_size_read(inode));
}
return ret;
}
@@@ -1584,7 -1576,6 +1567,7 @@@ const struct inode_operations ntfs_file
.set_acl = ntfs_set_acl,
.fiemap = ntfs_fiemap,
.fileattr_get = ntfs_fileattr_get,
+ .fileattr_set = ntfs_fileattr_set,
};

const struct file_operations ntfs_file_operations = {
diff --combined fs/ntfs3/namei.c
index c59de5f2fa977,e159ba66a34a4..0000000000000
--- a/fs/ntfs3/namei.c
+++ b/fs/ntfs3/namei.c
@@@ -340,7 -340,7 +340,7 @@@ static int ntfs_rename(struct mnt_idma
ntfs_sync_inode(dir);

if (IS_DIRSYNC(new_dir))
- ntfs_sync_inode(inode);
+ ntfs_sync_inode(new_dir);
}

if (dir_ni != new_dir_ni)
@@@ -519,7 -519,6 +519,7 @@@ const struct inode_operations ntfs_dir_
.listxattr = ntfs_listxattr,
.fiemap = ntfs_fiemap,
.fileattr_get = ntfs_fileattr_get,
+ .fileattr_set = ntfs_fileattr_set,
};

const struct inode_operations ntfs_special_inode_operations = {
@@@ -528,8 -527,6 +528,8 @@@
.listxattr = ntfs_listxattr,
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
+ .fileattr_get = ntfs_fileattr_get,
+ .fileattr_set = ntfs_fileattr_set,
};

const struct dentry_operations ntfs_dentry_ops = {
diff --combined fs/ntfs3/ntfs_fs.h
index d98d7e474476c,41db22d652c47..0000000000000
--- a/fs/ntfs3/ntfs_fs.h
+++ b/fs/ntfs3/ntfs_fs.h
@@@ -392,9 -392,6 +392,9 @@@ struct ntfs_inode
*/
u8 ni_bad;

+ /* Keep track of FS_NODUMP_FL. */
+ u8 nodump;
+
union {
struct ntfs_index dir;
struct {
@@@ -533,8 -530,6 +533,8 @@@ extern const struct file_operations ntf

/* Globals from file.c */
int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct file_kattr *fa);
int ntfs_getattr(struct mnt_idmap *idmap, const struct path *path,
struct kstat *stat, u32 request_mask, u32 flags);
int ntfs_setattr(struct mnt_idmap *idmap, struct dentry *dentry,
@@@ -858,9 -853,6 +858,9 @@@ static inline void mi_get_ref(const str
/* Globals from run.c */
bool run_lookup_entry(const struct runs_tree *run, CLST vcn, CLST *lcn,
CLST *len, size_t *index);
+bool run_lookup_entry_da(const struct runs_tree *run,
+ const struct runs_tree *run_da, CLST vcn, CLST *lcn,
+ CLST *len);
void run_truncate(struct runs_tree *run, CLST vcn);
void run_truncate_head(struct runs_tree *run, CLST vcn);
void run_truncate_around(struct runs_tree *run, CLST vcn);
@@@ -886,8 -878,7 +886,8 @@@ int run_unpack_ex(struct runs_tree *run
#else
#define run_unpack_ex run_unpack
#endif
-int run_get_highest_vcn(CLST vcn, const u8 *run_buf, u64 *highest_vcn);
+int run_get_highest_vcn(CLST vcn, const u8 *run_buf, size_t run_buf_size,
+ u64 *highest_vcn);
int run_clone(const struct runs_tree *run, struct runs_tree *new_run);
bool run_remove_range(struct runs_tree *run, CLST vcn, CLST len, CLST *done);
CLST run_len(const struct runs_tree *run);

Attachment: signature.asc
Description: PGP signature