[PATCH v2 2/5] ntfs: preserve timestamps when creating hard links
From: Baolin Liu
Date: Sat Oct 31 2026 - 01:32:10 EST
From: Xuewen Wang <wangxuewen@xxxxxxxxxx>
ntfs_link() resets atime, mtime and ctime on both the source file and
the destination directory after creating a hard link. This overwrites
the source file's atime and mtime and the directory's atime.
Update only the source ctime and the destination directory mtime/ctime.
Use the same timestamp for the new FILE_NAME attribute, its directory
index entry and the source inode, so the new name does not retain the
old ctime. Set the inode's ctime only after the link succeeds and mark
both inodes dirty to queue their timestamps for writeback.
Keep the rename caller's existing timestamp behavior unchanged.
Signed-off-by: Xuewen Wang <wangxuewen@xxxxxxxxxx>
---
Tested the standalone five-patch series in QEMU. The requested unchanged
xfstests generic/004 and generic/389 passed. Hard-link tests checked that
prepared atime/mtime remained unchanged and source ctime advanced.
Offline checks compared STANDARD_INFORMATION, the new FILE_NAME and
the directory index ctime.
Changes in v2:
- Use the same new ctime for the inode, FILE_NAME and directory index,
as requested in review.
- Update inode ctime only after the link succeeds.
fs/ntfs/namei.c | 19 ++++++++++++-------
1 file changed, 12 insertions(+), 7 deletions(-)
diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index 14eaf7204ead..a83a648c8ff8 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -1244,6 +1244,7 @@ static int ntfs_rmdir(struct inode *dir, struct dentry *dentry)
* @dir_ni: ntfs inode for directory in which new link should be placed
* @name: unicode name of the new link
* @name_len: length of the name in unicode characters
+ * @ctime: metadata change time for the new FILE_NAME
*
* Create a new hard link. This involves adding an entry to the directory
* index and adding a new FILE_NAME attribute to the target inode.
@@ -1251,7 +1252,7 @@ static int ntfs_rmdir(struct inode *dir, struct dentry *dentry)
* Return 0 on success and -errno on error.
*/
static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
- __le16 *name, u8 name_len)
+ __le16 *name, u8 name_len, struct timespec64 ctime)
{
struct super_block *sb;
struct inode *vi = VFS_I(ni);
@@ -1313,7 +1314,7 @@ static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
fn->creation_time = utc2ntfs(ni->i_crtime);
fn->last_data_change_time = utc2ntfs(inode_get_mtime(vi));
- fn->last_mft_change_time = utc2ntfs(inode_get_ctime(vi));
+ fn->last_mft_change_time = utc2ntfs(ctime);
fn->last_access_time = utc2ntfs(inode_get_atime(vi));
memcpy(fn->file_name, name, name_len * sizeof(__le16));
@@ -1464,7 +1465,8 @@ static int ntfs_rename(const struct mnt_idmap *idmap, struct inode *old_dir,
goto err_out;
}
- err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len);
+ err = __ntfs_link(old_ni, new_dir_ni, uname_new, new_name_len,
+ inode_get_ctime(old_inode));
if (err)
goto err_out;
@@ -1630,6 +1632,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
int uname_len;
int err;
struct ntfs_inode *ni = NTFS_I(vi), *dir_ni = NTFS_I(dir);
+ struct timespec64 ctime;
if (NVolShutdown(vol))
return -EIO;
@@ -1654,7 +1657,8 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
ihold(vi);
mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
- err = __ntfs_link(NTFS_I(vi), NTFS_I(dir), uname, uname_len);
+ ctime = current_time(vi);
+ err = __ntfs_link(ni, dir_ni, uname, uname_len, ctime);
if (err) {
mutex_unlock(&dir_ni->mrec_lock);
mutex_unlock(&ni->mrec_lock);
@@ -1664,12 +1668,13 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
}
inode_inc_iversion(dir);
- simple_inode_init_ts(dir);
+ inode_set_mtime_to_ts(dir, inode_set_ctime_current(dir));
+ mark_inode_dirty(dir);
inode_inc_iversion(vi);
- simple_inode_init_ts(vi);
+ inode_set_ctime_to_ts(vi, ctime);
+ mark_inode_dirty(vi);
- /* timestamp is already written, so mark_inode_dirty() is unneeded. */
d_instantiate(dentry, vi);
mutex_unlock(&dir_ni->mrec_lock);
mutex_unlock(&ni->mrec_lock);