[RFC PATCH v3 3/5] ntfs3: make crtime user-settable using ptime
From: koraynilay
Date: Sat Oct 31 2026 - 21:59:04 EST
Since NTFS supports user-settable file creation timestamps, map
provenance time (ptime) to NTFS file creation time (crtime) by using the
newly introduced ptime API (in commit "vfs: add provenance time
(ptime)").
To do this, update ntfs_fileattr_[gs]et(), ntfs_getattr() and make it
persist to disk in ni_write_inode().
The timestamp is now retrievable by using file_getattr() or statx(), and
is settable by using file_setattr() with the FS_XFLAG_PTIME flag.
Assisted-by: Gemini:3.8-flash-high antigravity-cli-1.4.7
Signed-off-by: koraynilay <koray.fra@xxxxxxxxx>
---
fs/ntfs3/file.c | 14 ++++++++++++--
fs/ntfs3/frecord.c | 8 +++++++-
2 files changed, 19 insertions(+), 3 deletions(-)
diff --git a/fs/ntfs3/file.c b/fs/ntfs3/file.c
index 1624f9b014e0..b334449e9beb 100644
--- a/fs/ntfs3/file.c
+++ b/fs/ntfs3/file.c
@@ -124,6 +124,10 @@ int ntfs_fileattr_get(struct dentry *dentry, struct file_kattr *fa)
fileattr_fill_flags(fa, flags);
+ fa->fsx_ptime.tv_sec = ni->i_crtime.tv_sec;
+ fa->fsx_ptime.tv_nsec = ni->i_crtime.tv_nsec;
+ fa->fsx_xflags |= FS_XFLAG_PTIME;
+
return 0;
}
@@ -142,7 +146,7 @@ int ntfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
if (unlikely(is_bad_ni(ni)))
return -EINVAL;
- if (fileattr_has_fsx(fa))
+ if (fileattr_has_fsx_mask(fa, FS_XFLAG_PTIME))
return -EOPNOTSUPP;
if (flags & ~(FS_IMMUTABLE_FL | FS_APPEND_FL | FS_NODUMP_FL))
@@ -154,6 +158,11 @@ int ntfs_fileattr_set(const struct mnt_idmap *idmap, struct dentry *dentry,
if (flags & FS_APPEND_FL)
new_fl |= S_APPEND;
+ if (fa->fsx_xflags & FS_XFLAG_PTIME) {
+ ni->i_crtime.tv_sec = fa->fsx_ptime.tv_sec;
+ ni->i_crtime.tv_nsec = fa->fsx_ptime.tv_nsec;
+ }
+
inode_set_flags(inode, new_fl, S_IMMUTABLE | S_APPEND);
/* Save nodump flag to return in ntfs_getattr. */
@@ -275,8 +284,9 @@ int ntfs_getattr(const struct mnt_idmap *idmap, const struct path *path,
if (unlikely(is_bad_ni(ni)))
return -EINVAL;
- stat->result_mask |= STATX_BTIME;
+ stat->result_mask |= (STATX_BTIME | STATX_PTIME);
stat->btime = ni->i_crtime;
+ stat->ptime = ni->i_crtime;
stat->blksize = ni->mi.sbi->cluster_size; /* 512, 1K, ..., 2M */
if (inode->i_flags & S_IMMUTABLE)
diff --git a/fs/ntfs3/frecord.c b/fs/ntfs3/frecord.c
index 16ac847de48d..5e4f743a5930 100644
--- a/fs/ntfs3/frecord.c
+++ b/fs/ntfs3/frecord.c
@@ -3215,6 +3215,13 @@ int ni_write_inode(struct inode *inode, int sync, const char *hint)
}
/* Update the access times if they have changed. */
+ ts = ni->i_crtime;
+ dup.cr_time = kernel2nt(&ts);
+ if (std->cr_time != dup.cr_time) {
+ std->cr_time = dup.cr_time;
+ modified = true;
+ }
+
ts = inode_get_mtime(inode);
dup.m_time = kernel2nt(&ts);
if (std->m_time != dup.m_time) {
@@ -3250,7 +3257,6 @@ int ni_write_inode(struct inode *inode, int sync, const char *hint)
(modified || (ni->ni_flags & NI_FLAG_UPDATE_PARENT))
/* Avoid __wait_on_freeing_inode(inode). */
&& (sb->s_flags & SB_ACTIVE)) {
- dup.cr_time = std->cr_time;
/* Not critical if this function fail. */
re_dirty = ni_update_parent(ni, &dup, sync);
--
2.56.0