[PATCH 1/2] ntfs: implement fileattr_get and fileattr_set

From: Baolin Liu

Date: Tue Sep 01 2026 - 05:16:28 EST


From: Baolin Liu <liubaolin@xxxxxxxxxx>

Wire up the fileattr inode operations so that lsattr(1) and chattr(1)
work on ntfs.

fileattr_get reports the compressed and encrypted state of the attribute
and the immutable and append-only flags, the same states ntfs_getattr()
already reports through STATX_ATTR_*.

fileattr_set accepts only FS_IMMUTABLE_FL and FS_APPEND_FL. Compression
and encryption are rejected because changing them would require
rewriting the attribute data. Both accepted flags go to inode->i_flags,
so the VFS enforces them.

Neither flag has an on-disk representation of its own, so a value set
through fileattr_set only lives for the lifetime of the mount. Note also
that S_IMMUTABLE is re-derived on every mount, from FILE_ATTR_SYSTEM when
mounted with sys_immutable and unconditionally for system files, so
clearing it on such a file does not survive a remount either.

Signed-off-by: Baolin Liu <liubaolin@xxxxxxxxxx>
---
fs/ntfs/file.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++
fs/ntfs/ntfs.h | 4 ++++
2 files changed, 60 insertions(+)

diff --git a/fs/ntfs/file.c b/fs/ntfs/file.c
index 1969e4f444f7..b7a0fb2d8229 100644
--- a/fs/ntfs/file.c
+++ b/fs/ntfs/file.c
@@ -15,6 +15,7 @@
#include <linux/posix_acl_xattr.h>
#include <linux/compat.h>
#include <linux/falloc.h>
+#include <linux/fileattr.h>

#include "lcnalloc.h"
#include "ntfs.h"
@@ -135,6 +136,59 @@ static int ntfs_file_release(struct inode *vi, struct file *filp)
return 0;
}

+/*
+ * ntfs_fileattr_get - inode_operations::fileattr_get
+ * @dentry: dentry to report the flags of
+ * @fa: filled in with the flags of @dentry
+ */
+int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
+{
+ struct inode *vi = d_inode(dentry);
+ struct ntfs_inode *ni = NTFS_I(vi);
+ u32 flags = 0;
+
+ if (NInoCompressed(ni) || NInoWofCompressed(ni))
+ flags |= FS_COMPR_FL;
+ if (NInoEncrypted(ni))
+ flags |= FS_ENCRYPT_FL;
+ if (vi->i_flags & S_IMMUTABLE)
+ flags |= FS_IMMUTABLE_FL;
+ if (vi->i_flags & S_APPEND)
+ flags |= FS_APPEND_FL;
+
+ fileattr_fill_flags(fa, flags);
+ return 0;
+}
+
+/*
+ * ntfs_fileattr_set - inode_operations::fileattr_set
+ * @idmap: idmap of the mount @dentry was found from
+ * @dentry: dentry to set the flags of
+ * @fa: flags to set
+ */
+int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct file_kattr *fa)
+{
+ struct inode *vi = d_inode(dentry);
+ unsigned int new_fl = 0;
+
+ if (fileattr_has_fsx(fa))
+ return -EOPNOTSUPP;
+ if (fa->flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL))
+ return -EOPNOTSUPP;
+
+ if (fa->flags & FS_IMMUTABLE_FL)
+ new_fl |= S_IMMUTABLE;
+ if (fa->flags & FS_APPEND_FL)
+ new_fl |= S_APPEND;
+
+ inode_set_flags(vi, new_fl, S_IMMUTABLE | S_APPEND);
+
+ inode_set_ctime_current(vi);
+ mark_inode_dirty(vi);
+ return 0;
+}
+
/*
* ntfs_file_fsync - sync a file to disk
* @filp: file to be synced
@@ -1215,6 +1269,8 @@ const struct file_operations ntfs_file_ops = {
const struct inode_operations ntfs_file_inode_ops = {
.setattr = ntfs_setattr,
.getattr = ntfs_getattr,
+ .fileattr_get = ntfs_fileattr_get,
+ .fileattr_set = ntfs_fileattr_set,
.listxattr = ntfs_listxattr,
.get_acl = ntfs_get_acl,
.set_acl = ntfs_set_acl,
diff --git a/fs/ntfs/ntfs.h b/fs/ntfs/ntfs.h
index 45f77848a9cf..6a8d5c1aeedc 100644
--- a/fs/ntfs/ntfs.h
+++ b/fs/ntfs/ntfs.h
@@ -183,6 +183,10 @@ extern const struct inode_operations ntfs_dir_inode_ops;
extern const struct file_operations ntfs_empty_file_ops;
extern const struct inode_operations ntfs_empty_inode_ops;

+extern int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa);
+extern int ntfs_fileattr_set(struct mnt_idmap *idmap, struct dentry *dentry,
+ struct file_kattr *fa);
+
extern const struct export_operations ntfs_export_ops;

/*
--
2.51.0