mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v2 0/5] ntfs: support O_TMPFILE
@ 2026-10-31  5:28 Baolin Liu
  2026-10-31  5:28 ` [PATCH v2 1/5] ntfs: initialize inherited ACLs after allocating the MFT record Baolin Liu
                   ` (5 more replies)
  0 siblings, 6 replies; 9+ messages in thread
From: Baolin Liu @ 2026-10-31  5:28 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

This series adds O_TMPFILE support to fs/ntfs. Applications can prepare a
regular file's contents and metadata before publishing it with linkat().
Unpublished files are reclaimed when their last reference is released.
O_EXCL prevents publication.

The series keeps the same five changes and ordering as v1:

  1. Initialize inherited ACLs after allocating the new MFT record.
     Anonymous creation needs its inherited ACL stored before returning.
  2. Preserve prepared atime and mtime when publishing the inode through
     the hard-link path.
  3. Initialize the new FILE_NAME's packed EA size, since a temporary
     file may already have EAs when it is published.
  4. Complete unlinked-file attribute writes before dropping the cache,
     including nonresident EAs prepared before publication.
  5. Add O_TMPFILE creation and publication support.

As requested in review, patch 2 gives the new FILE_NAME and directory
index entry the same ctime as the inode. The inode ctime is updated only
after the link succeeds. Patch 1 also initializes a newly created
FILE_NAME's ctime after inherited ACL setup, fixing a timestamp mismatch
caused by moving ACL initialization later in v1.

While implementing and testing O_TMPFILE, we also found pre-existing
NTFS bugs in attribute rollback and ACL lookup. These include stale
attribute inodes left cached after failed creation, which can cause
subsequent creation to return EIO even after space is freed, incorrect
ACL inheritance when the default ACL is not cached, and a fixed-size
read buffer that cannot handle larger ACL values. The underlying bugs
predate this series.

To keep this series focused on O_TMPFILE support and the v1 review
feedback, we are leaving these existing bugs to a separate bug-fix
series. We plan to submit those fixes after this series is accepted and
merged. These issues remain present with this series applied.

Tested this five-patch series in QEMU with four CPUs, NTFS debug and
lockdep enabled. The requested unchanged xfstests generic/004 and
generic/389 passed. Tests covered creation, publication, last-reference
cleanup, O_EXCL, normal idmapped ownership, named streams, nonresident
EAs, direct I/O, mmap, allocation and concurrent publication. Published
data and metadata were checked after cache eviction, remount and reboot.
Offline inspection compared hard-link ctime in STANDARD_INFORMATION,
the new FILE_NAME and the directory index.

Changes in v2:
- Update FILE_NAME and index ctime as requested in review.
- Set the source inode ctime only after successful hard-link creation.
- Initialize creation FILE_NAME ctime after inherited ACL setup.
- Run generic/004 and generic/389 on the standalone five-patch series.
- Rebase onto ntfs-next commit
  564c7753647731d19170914c1c637d3b33d88d3b, including the const mnt_idmap
  tmpfile callback signature and documentation context.
- Resolve patch 1 against the updated early ACL error handling in
  __ntfs_create(); the relocated ACL failure still uses MFT/EA rollback.
- Keep the original five-patch scope; submit fixes for the pre-existing
  attribute rollback and ACL lookup bugs separately after this series
  is merged.
- No functional changes to patches 3 and 4.

Thanks,
Baolin

Baolin Liu (2):
  ntfs: initialize inherited ACLs after allocating the MFT record
  ntfs: support O_TMPFILE

Xuewen Wang (3):
  ntfs: preserve timestamps when creating hard links
  ntfs: initialize packed EA size when creating hard links
  ntfs: write unlinked file attributes before dropping their cache

 Documentation/filesystems/ntfs.rst |  15 ++++
 fs/ntfs/inode.c                    |   8 ++
 fs/ntfs/namei.c                    | 140 +++++++++++++++++++++++------
 3 files changed, 136 insertions(+), 27 deletions(-)


base-commit: 564c7753647731d19170914c1c637d3b33d88d3b


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2 1/5] ntfs: initialize inherited ACLs after allocating the MFT record
  2026-10-31  5:28 [PATCH v2 0/5] ntfs: support O_TMPFILE Baolin Liu
@ 2026-10-31  5:28 ` Baolin Liu
  2026-10-31  5:28 ` [PATCH v2 2/5] ntfs: preserve timestamps when creating hard links Baolin Liu
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Baolin Liu @ 2026-10-31  5:28 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

ntfs_init_acl() can write EAs, but __ntfs_create() calls it before
allocating the new inode's MFT record. An inherited access ACL can appear
correct in the inode cache and disappear after eviction.

Initialize the ACL after allocating the MFT record and adding the standard
attributes. Release the parent and child record locks around the ACL
helpers to avoid recursive locking, then refresh the local mode from the
inode. On creation failure, remove any EA and EA_INFORMATION attributes
before freeing the MFT record.

Initialize the new FILE_NAME ctime from the inode after ACL setup, since
writing the inherited ACL may advance the inode ctime. This also keeps
the directory index copy consistent with STANDARD_INFORMATION.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
Tested the standalone five-patch series in QEMU with four CPUs, NTFS
debug and lockdep enabled. Ordinary files inherited an access ACL, and
published temporary-file ACLs remained readable after cache eviction,
remount and reboot. These checks used the existing cached default ACL
path; the separate cold-cache ACL defect is not fixed here.

Changes in v2:
- Initialize FILE_NAME ctime after ACL setup to avoid retaining the
  pre-ACL timestamp.
- Refresh context for ntfs-next, including the updated early ACL
  error path. The relocated ACL failure still uses MFT/EA rollback.
  Existing attribute rollback defects are handled separately.

 fs/ntfs/namei.c | 49 +++++++++++++++++++++++++++++++++++--------------
 1 file changed, 35 insertions(+), 14 deletions(-)

diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index b46fe44b63b5..14eaf7204ead 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -422,6 +422,7 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	struct ntfs_volume *vol = dir_ni->vol;
 	struct ntfs_inode *ni;
 	bool rollback_data = false, rollback_sd = false, rollback_reparse = false;
+	bool rollback_ea = false;
 	struct file_name_attr *fn = NULL;
 	struct standard_information *si = NULL;
 	int err = 0, fn_len, si_len;
@@ -429,6 +430,7 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	struct mft_record *ni_mrec, *dni_mrec;
 	struct super_block *sb = dir_ni->vol->sb;
 	struct inode *rollback_data_vi = NULL, *rollback_sd_vi = NULL;
+	struct inode *rollback_ea_vi = NULL, *rollback_eai_vi = NULL;
 	__le64 parent_mft_ref;
 	u64 child_mft_ref;
 	__le16 ea_size;
@@ -469,19 +471,6 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 
 	mode = vi->i_mode;
 
-#ifdef CONFIG_NTFS_FS_POSIX_ACL
-	if (!S_ISLNK(mode) && (sb->s_flags & SB_POSIXACL)) {
-		err = ntfs_init_acl(idmap, vi, dir);
-		if (err) {
-			iput(vi);
-			return ERR_PTR(err);
-		}
-	} else
-#endif
-	{
-		vi->i_flags |= S_NOSEC;
-	}
-
 	if (uid_valid(vol->uid))
 		vi->i_uid = vol->uid;
 
@@ -646,6 +635,24 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 			goto err_out;
 	}
 
+	/* ACL EAs need the allocated MFT record and initialized attributes. */
+	rollback_ea = true;
+#ifdef CONFIG_NTFS_FS_POSIX_ACL
+	if (!S_ISLNK(mode) && (sb->s_flags & SB_POSIXACL)) {
+		/* The ACL helpers acquire both the parent's and child's locks. */
+		mutex_unlock(&dir_ni->mrec_lock);
+		mutex_unlock(&ni->mrec_lock);
+		err = ntfs_init_acl(idmap, vi, dir);
+		mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
+		mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
+		if (err)
+			goto err_out;
+		mode = vi->i_mode;
+	}
+#endif
+	if (S_ISLNK(mode) || !(sb->s_flags & SB_POSIXACL))
+		vi->i_flags |= S_NOSEC;
+
 	err = ntfs_ea_set_wsl_inode(vi, dev, &ea_size,
 			NTFS_EA_UID | NTFS_EA_GID | NTFS_EA_MODE);
 	if (err)
@@ -679,7 +686,8 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	if (NVolHideDotFiles(vol) && name_len > 0 && name[0] == cpu_to_le16('.'))
 		fn->file_attributes |= FILE_ATTR_HIDDEN;
 	fn->creation_time = fn->last_data_change_time = utc2ntfs(ni->i_crtime);
-	fn->last_mft_change_time = fn->last_access_time = fn->creation_time;
+	fn->last_mft_change_time = utc2ntfs(inode_get_ctime(vi));
+	fn->last_access_time = fn->creation_time;
 	memcpy(fn->file_name, name, name_len * sizeof(__le16));
 
 	/* Add FILE_NAME attribute to inode. */
@@ -720,6 +728,15 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	return ni;
 
 err_out:
+	if (rollback_ea) {
+		if (ntfs_attr_exist(ni, AT_EA, AT_UNNAMED, 0) &&
+		    ntfs_attr_remove(ni, AT_EA, AT_UNNAMED, 0, &rollback_ea_vi))
+			ntfs_error(sb, "Failed to roll back EA attribute.");
+		if (ntfs_attr_exist(ni, AT_EA_INFORMATION, AT_UNNAMED, 0) &&
+		    ntfs_attr_remove(ni, AT_EA_INFORMATION, AT_UNNAMED, 0,
+				     &rollback_eai_vi))
+			ntfs_error(sb, "Failed to roll back EA_INFORMATION attribute.");
+	}
 	if (rollback_sd) {
 		int rollback_err;
 
@@ -766,6 +783,10 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	mutex_unlock(&dir_ni->mrec_lock);
 	mutex_unlock(&ni->mrec_lock);
 
+	if (rollback_ea_vi)
+		iput(rollback_ea_vi);
+	if (rollback_eai_vi)
+		iput(rollback_eai_vi);
 	if (rollback_data_vi)
 		iput(rollback_data_vi);
 	if (rollback_sd_vi)


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2 2/5] ntfs: preserve timestamps when creating hard links
  2026-10-31  5:28 [PATCH v2 0/5] ntfs: support O_TMPFILE Baolin Liu
  2026-10-31  5:28 ` [PATCH v2 1/5] ntfs: initialize inherited ACLs after allocating the MFT record Baolin Liu
@ 2026-10-31  5:28 ` Baolin Liu
  2026-10-31  5:28 ` [PATCH v2 3/5] ntfs: initialize packed EA size " Baolin Liu
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Baolin Liu @ 2026-10-31  5:28 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Xuewen Wang

From: Xuewen Wang <wangxuewen@kylinos.cn>

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@kylinos.cn>
---
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);


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2 3/5] ntfs: initialize packed EA size when creating hard links
  2026-10-31  5:28 [PATCH v2 0/5] ntfs: support O_TMPFILE Baolin Liu
  2026-10-31  5:28 ` [PATCH v2 1/5] ntfs: initialize inherited ACLs after allocating the MFT record Baolin Liu
  2026-10-31  5:28 ` [PATCH v2 2/5] ntfs: preserve timestamps when creating hard links Baolin Liu
@ 2026-10-31  5:28 ` Baolin Liu
  2026-10-31  5:28 ` [PATCH v2 4/5] ntfs: write unlinked file attributes before dropping their cache Baolin Liu
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 9+ messages in thread
From: Baolin Liu @ 2026-10-31  5:28 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Xuewen Wang, Baolin Liu

From: Xuewen Wang <wangxuewen@kylinos.cn>

A new hard link has a zero packed EA size in its FILE_NAME even when
the source file already has extended attributes.

For files with EAs and no reparse point, read EA_INFORMATION and check
its attribute length before adding the name. Copy its ea_length into
the new FILE_NAME's packed_ea_size. Skip this assignment for reparse
points, where the same storage holds the reparse-point tag.

Suggested-by: Baolin Liu <liubaolin@kylinos.cn>
Signed-off-by: Xuewen Wang <wangxuewen@kylinos.cn>
---
Tested the standalone five-patch series in QEMU. Prepared EAs remained
readable after publishing the temporary file and after cache eviction,
remount and reboot.

No functional change from v1.

 fs/ntfs/namei.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index a83a648c8ff8..5124e7a3e124 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -1318,6 +1318,24 @@ static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
 	fn->last_access_time = utc2ntfs(inode_get_atime(vi));
 	memcpy(fn->file_name, name, name_len * sizeof(__le16));
 
+	if (NInoHasEA(ni) && !(ni->flags & FILE_ATTR_REPARSE_POINT)) {
+		struct ea_information *info;
+		s64 size;
+
+		info = ntfs_attr_readall(ni, AT_EA_INFORMATION, AT_UNNAMED, 0, &size);
+		if (IS_ERR(info)) {
+			err = PTR_ERR(info);
+			goto err_out;
+		}
+		if (size != sizeof(*info))
+			err = -EUCLEAN;
+		else
+			fn->type.ea.packed_ea_size = info->ea_length;
+		kvfree(info);
+		if (err)
+			goto err_out;
+	}
+
 	/* Add FILE_NAME attribute to index. */
 	err = ntfs_index_add_filename(dir_ni, fn, MK_MREF(ni->mft_no,
 					le16_to_cpu(ni_mrec->sequence_number)));


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2 4/5] ntfs: write unlinked file attributes before dropping their cache
  2026-10-31  5:28 [PATCH v2 0/5] ntfs: support O_TMPFILE Baolin Liu
                   ` (2 preceding siblings ...)
  2026-10-31  5:28 ` [PATCH v2 3/5] ntfs: initialize packed EA size " Baolin Liu
@ 2026-10-31  5:28 ` Baolin Liu
  2026-11-04  9:06   ` Namjae Jeon
  2026-10-31  5:28 ` [PATCH v2 5/5] ntfs: support O_TMPFILE Baolin Liu
  2026-11-04  7:03 ` [PATCH v2 0/5] " Hyunchul Lee
  5 siblings, 1 reply; 9+ messages in thread
From: Baolin Liu @ 2026-10-31  5:28 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Xuewen Wang

From: Xuewen Wang <wangxuewen@kylinos.cn>

Attribute inodes copy the base inode's link count when initialized.
Those initialized after the base file is unlinked have zero links and
are evicted at the final iput(). Nonresident attribute writes can leave
dirty pages in that cache, so an immediately following getxattr can fail
to find a successfully written EA.

Use the synchronous nonresident attribute write path when the base inode
has no links. Keep named data streams on their existing I/O path. This
allows anonymous files to prepare nonresident EAs without retaining
attribute inodes that would pin the base inode and prevent its
reclamation.

Signed-off-by: Xuewen Wang <wangxuewen@kylinos.cn>
---
Tested the standalone five-patch series in QEMU with a 12000-byte EA on
an unlinked file. Attribute writeback and cleanup completed; published
EA contents also survived cache eviction, remount and reboot.

No functional change from v1.

 fs/ntfs/inode.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/ntfs/inode.c b/fs/ntfs/inode.c
index 62dd5bd5bb81..2b1388de7bb2 100644
--- a/fs/ntfs/inode.c
+++ b/fs/ntfs/inode.c
@@ -4052,6 +4052,14 @@ s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, bool s
 
 	WARN_ON(!NInoAttr(ni));
 
+	/*
+	 * Attribute inodes of an unlinked file have no links either and are
+	 * evicted at the final iput(). Do not leave their data in dirty pages
+	 * that eviction would discard while the base file is still open.
+	 */
+	if (!VFS_I(base_ni)->i_nlink && !ntfs_inode_is_named_stream(ni))
+		sync = true;
+
 	if (ntfs_inode_is_named_stream(ni)) {
 		if (NInoEncrypted(ni))
 			return -EACCES;


^ permalink raw reply	[flat|nested] 9+ messages in thread

* [PATCH v2 5/5] ntfs: support O_TMPFILE
  2026-10-31  5:28 [PATCH v2 0/5] ntfs: support O_TMPFILE Baolin Liu
                   ` (3 preceding siblings ...)
  2026-10-31  5:28 ` [PATCH v2 4/5] ntfs: write unlinked file attributes before dropping their cache Baolin Liu
@ 2026-10-31  5:28 ` Baolin Liu
  2026-11-04  7:03 ` [PATCH v2 0/5] " Hyunchul Lee
  5 siblings, 0 replies; 9+ messages in thread
From: Baolin Liu @ 2026-10-31  5:28 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Baolin Liu

From: Baolin Liu <liubaolin@kylinos.cn>

Allow regular files to be created without a FILE_NAME attribute or
a directory index entry. Keep their on-disk link count at zero and use
d_tmpfile() to attach the inode to the anonymous dentry. Clear the base
inode's in-memory link count before initializing EAs so that attribute
inodes inherit zero links and can be evicted rather than pinning an
unpublished file in cache.

Allow the first hard link of a zero-link inode only when the VFS marks
it I_LINKABLE. Existing deletion checks remain in place, and the VFS
controls O_EXCL and clears I_LINKABLE after publication. Reuse the
zero-link eviction path for unpublished files.

Document the interface and the named-stream restriction before
publication.

Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
Tested the standalone five-patch series in QEMU with four CPUs, NTFS
debug and lockdep enabled. Checked creation, last-reference cleanup,
O_EXCL, linkat publication, permissions, normal idmapped ownership,
nonresident EAs, direct I/O, mmap, allocation and named streams.
Published data and metadata survived cache eviction, remount and reboot.
Concurrent publication and the basic ENOSPC probe passed. The requested
unchanged xfstests generic/004 and generic/389 passed.

The basic ENOSPC probe does not cover all ACL-backed creation failures.
Full-volume ACL creation retries still failed after freeing space on
this five-patch series. Attribute rollback and cold-cache ACL fixes are
prepared as a separate follow-up series.

Changes in v2:
- Rebase onto ntfs-next and use the const mnt_idmap callback signature.
- Run the requested fstests and keep the original five-patch scope.

 Documentation/filesystems/ntfs.rst | 15 +++++++++
 fs/ntfs/namei.c                    | 54 ++++++++++++++++++++++++++----
 2 files changed, 63 insertions(+), 6 deletions(-)

diff --git a/Documentation/filesystems/ntfs.rst b/Documentation/filesystems/ntfs.rst
index d5819da6ab53..635301117807 100644
--- a/Documentation/filesystems/ntfs.rst
+++ b/Documentation/filesystems/ntfs.rst
@@ -10,6 +10,7 @@ The Linux NTFS filesystem driver
    - Overview
    - Utilities support
    - Linux file attributes
+   - Temporary files
    - Supported mount options
 
 
@@ -63,6 +64,20 @@ These Linux flags are enforced by Linux. Storing them in an NTFS EA does
 not make Windows enforce immutable, append-only, nodump or noatime semantics.
 
 
+Temporary files
+===============
+
+The driver supports ``O_TMPFILE`` for regular files. The file has no
+directory entry until it is linked with linkat(2); ``O_EXCL`` prevents
+this publication. Ownership and mode follow normal file creation rules.
+With ACLs enabled, the file's access ACL is derived from the creation
+directory's default ACL. Unpublished files are reclaimed when their last
+reference is released.
+
+Named stream ioctls cannot create or open streams on an unpublished
+temporary file. They can be used after the file has been linked.
+
+
 Supported mount options
 =======================
 
diff --git a/fs/ntfs/namei.c b/fs/ntfs/namei.c
index 5124e7a3e124..a8bfc732b558 100644
--- a/fs/ntfs/namei.c
+++ b/fs/ntfs/namei.c
@@ -489,9 +489,11 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	simple_inode_init_ts(vi);
 	ni->i_crtime = inode_get_ctime(vi);
 
-	inode_set_mtime_to_ts(dir, ni->i_crtime);
-	inode_set_ctime_to_ts(dir, ni->i_crtime);
-	mark_inode_dirty(dir);
+	if (name) {
+		inode_set_mtime_to_ts(dir, ni->i_crtime);
+		inode_set_ctime_to_ts(dir, ni->i_crtime);
+		mark_inode_dirty(dir);
+	}
 
 	err = ntfs_mft_record_alloc(dir_ni->vol, mode, &ni, NULL,
 			&ni_mrec, -1);
@@ -513,6 +515,10 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	inode_set_iversion(vi, 1);
 	insert_inode_hash(vi);
 
+	/* Attribute inodes must not pin an unpublished file in cache. */
+	if (!name)
+		clear_nlink(vi);
+
 	mutex_lock_nested(&ni->mrec_lock, NTFS_INODE_MUTEX_NORMAL);
 	mutex_lock_nested(&dir_ni->mrec_lock, NTFS_INODE_MUTEX_PARENT);
 	if (NInoBeingDeleted(dir_ni)) {
@@ -658,6 +664,12 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	if (err)
 		goto err_out;
 
+	if (!name) {
+		ni_mrec->link_count = 0;
+		mark_mft_record_dirty(ni);
+		goto done;
+	}
+
 	/* Create FILE_NAME attribute. */
 	fn_len = sizeof(struct file_name_attr) + name_len * sizeof(__le16);
 	fn = kzalloc(fn_len, GFP_NOFS);
@@ -710,12 +722,13 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 		goto err_out;
 	}
 
+	ni->flags = fn->file_attributes |
+		    (ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN);
+done:
 	unmap_mft_record(ni);
 	mutex_unlock(&dir_ni->mrec_lock);
 	mutex_unlock(&ni->mrec_lock);
 
-	ni->flags = fn->file_attributes |
-		    (ni->flags & FILE_ATTRIBUTE_RECALL_ON_OPEN);
 	/* Set the sequence number. */
 	vi->i_generation = ni->seq_no;
 	set_nlink(vi, 1);
@@ -783,6 +796,8 @@ static struct ntfs_inode *__ntfs_create(const struct mnt_idmap *idmap, struct in
 	mutex_unlock(&dir_ni->mrec_lock);
 	mutex_unlock(&ni->mrec_lock);
 
+	/* The MFT record has already been freed above. */
+	set_nlink(vi, 1);
 	if (rollback_ea_vi)
 		iput(rollback_ea_vi);
 	if (rollback_eai_vi)
@@ -857,6 +872,31 @@ static int ntfs_create(const struct mnt_idmap *idmap, struct inode *dir,
 	return 0;
 }
 
+static int ntfs_tmpfile(const struct mnt_idmap *idmap, struct inode *dir,
+			struct file *file, umode_t mode)
+{
+	struct ntfs_volume *vol = NTFS_SB(dir->i_sb);
+	struct ntfs_inode *ni;
+	struct inode *inode;
+	int err;
+
+	if (NVolShutdown(vol))
+		return -EIO;
+
+	err = ntfs_set_volume_flags(vol, VOLUME_IS_DIRTY);
+	if (err)
+		return err;
+
+	ni = __ntfs_create(idmap, dir, NULL, 0, mode, 0, NULL, 0);
+	if (IS_ERR(ni))
+		return PTR_ERR(ni);
+
+	inode = VFS_I(ni);
+	d_tmpfile(file, inode);
+	unlock_new_inode(inode);
+	return finish_open_simple(file, 0);
+}
+
 static int ntfs_check_unlinkable_dir(struct ntfs_attr_search_ctx *ctx, struct file_name_attr *fn)
 {
 	int link_count;
@@ -1272,7 +1312,8 @@ static int __ntfs_link(struct ntfs_inode *ni, struct ntfs_inode *dir_ni,
 		goto err_out;
 	}
 
-	if (le16_to_cpu(ni_mrec->link_count) == 0) {
+	if (!ni_mrec->link_count &&
+	    !(inode_state_read_once(vi) & I_LINKABLE)) {
 		err = -ENOENT;
 		goto err_out;
 	}
@@ -1708,6 +1749,7 @@ static int ntfs_link(struct dentry *old_dentry, struct inode *dir,
 const struct inode_operations ntfs_dir_inode_ops = {
 	.lookup		= ntfs_lookup,	/* VFS: Lookup directory. */
 	.create		= ntfs_create,
+	.tmpfile	= ntfs_tmpfile,
 	.unlink		= ntfs_unlink,
 	.mkdir		= ntfs_mkdir,
 	.rmdir		= ntfs_rmdir,


^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 0/5] ntfs: support O_TMPFILE
  2026-10-31  5:28 [PATCH v2 0/5] ntfs: support O_TMPFILE Baolin Liu
                   ` (4 preceding siblings ...)
  2026-10-31  5:28 ` [PATCH v2 5/5] ntfs: support O_TMPFILE Baolin Liu
@ 2026-11-04  7:03 ` Hyunchul Lee
  5 siblings, 0 replies; 9+ messages in thread
From: Hyunchul Lee @ 2026-11-04  7:03 UTC (permalink / raw)
  To: Baolin Liu; +Cc: linkinjeon, ntfs, linux-kernel, Baolin Liu

This series look good to me.

Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>

On Sat, Oct 31, 2026 at 01:28:52PM +0800, Baolin Liu wrote:
> From: Baolin Liu <liubaolin@kylinos.cn>
> 
> This series adds O_TMPFILE support to fs/ntfs. Applications can prepare a
> regular file's contents and metadata before publishing it with linkat().
> Unpublished files are reclaimed when their last reference is released.
> O_EXCL prevents publication.
> 
> The series keeps the same five changes and ordering as v1:
> 
>   1. Initialize inherited ACLs after allocating the new MFT record.
>      Anonymous creation needs its inherited ACL stored before returning.
>   2. Preserve prepared atime and mtime when publishing the inode through
>      the hard-link path.
>   3. Initialize the new FILE_NAME's packed EA size, since a temporary
>      file may already have EAs when it is published.
>   4. Complete unlinked-file attribute writes before dropping the cache,
>      including nonresident EAs prepared before publication.
>   5. Add O_TMPFILE creation and publication support.
> 
> As requested in review, patch 2 gives the new FILE_NAME and directory
> index entry the same ctime as the inode. The inode ctime is updated only
> after the link succeeds. Patch 1 also initializes a newly created
> FILE_NAME's ctime after inherited ACL setup, fixing a timestamp mismatch
> caused by moving ACL initialization later in v1.
> 
> While implementing and testing O_TMPFILE, we also found pre-existing
> NTFS bugs in attribute rollback and ACL lookup. These include stale
> attribute inodes left cached after failed creation, which can cause
> subsequent creation to return EIO even after space is freed, incorrect
> ACL inheritance when the default ACL is not cached, and a fixed-size
> read buffer that cannot handle larger ACL values. The underlying bugs
> predate this series.
> 
> To keep this series focused on O_TMPFILE support and the v1 review
> feedback, we are leaving these existing bugs to a separate bug-fix
> series. We plan to submit those fixes after this series is accepted and
> merged. These issues remain present with this series applied.
> 
> Tested this five-patch series in QEMU with four CPUs, NTFS debug and
> lockdep enabled. The requested unchanged xfstests generic/004 and
> generic/389 passed. Tests covered creation, publication, last-reference
> cleanup, O_EXCL, normal idmapped ownership, named streams, nonresident
> EAs, direct I/O, mmap, allocation and concurrent publication. Published
> data and metadata were checked after cache eviction, remount and reboot.
> Offline inspection compared hard-link ctime in STANDARD_INFORMATION,
> the new FILE_NAME and the directory index.
> 
> Changes in v2:
> - Update FILE_NAME and index ctime as requested in review.
> - Set the source inode ctime only after successful hard-link creation.
> - Initialize creation FILE_NAME ctime after inherited ACL setup.
> - Run generic/004 and generic/389 on the standalone five-patch series.
> - Rebase onto ntfs-next commit
>   564c7753647731d19170914c1c637d3b33d88d3b, including the const mnt_idmap
>   tmpfile callback signature and documentation context.
> - Resolve patch 1 against the updated early ACL error handling in
>   __ntfs_create(); the relocated ACL failure still uses MFT/EA rollback.
> - Keep the original five-patch scope; submit fixes for the pre-existing
>   attribute rollback and ACL lookup bugs separately after this series
>   is merged.
> - No functional changes to patches 3 and 4.
> 
> Thanks,
> Baolin
> 
> Baolin Liu (2):
>   ntfs: initialize inherited ACLs after allocating the MFT record
>   ntfs: support O_TMPFILE
> 
> Xuewen Wang (3):
>   ntfs: preserve timestamps when creating hard links
>   ntfs: initialize packed EA size when creating hard links
>   ntfs: write unlinked file attributes before dropping their cache
> 
>  Documentation/filesystems/ntfs.rst |  15 ++++
>  fs/ntfs/inode.c                    |   8 ++
>  fs/ntfs/namei.c                    | 140 +++++++++++++++++++++++------
>  3 files changed, 136 insertions(+), 27 deletions(-)
> 
> 
> base-commit: 564c7753647731d19170914c1c637d3b33d88d3b
> 
> 

-- 
Thanks,
Hyunchul

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 4/5] ntfs: write unlinked file attributes before dropping their cache
  2026-10-31  5:28 ` [PATCH v2 4/5] ntfs: write unlinked file attributes before dropping their cache Baolin Liu
@ 2026-11-04  9:06   ` Namjae Jeon
  2026-11-04  9:32     ` liubaolin
  0 siblings, 1 reply; 9+ messages in thread
From: Namjae Jeon @ 2026-11-04  9:06 UTC (permalink / raw)
  To: Baolin Liu; +Cc: hyc.lee, ntfs, linux-kernel, Xuewen Wang

> @@ -4052,6 +4052,14 @@ s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, bool s
>
>         WARN_ON(!NInoAttr(ni));
>
> +       /*
> +        * Attribute inodes of an unlinked file have no links either and are
> +        * evicted at the final iput(). Do not leave their data in dirty pages
> +        * that eviction would discard while the base file is still open.
> +        */
> +       if (!VFS_I(base_ni)->i_nlink && !ntfs_inode_is_named_stream(ni))
> +               sync = true;
This misses data dirtied by ntfs_attr_make_non_resident(). For
example, with 1 KiB MFT records, create an O_TMPFILE, nearly fill
its MFT record with a resident EA, then write 4 KiB to the file.
Growing $DATA can force ntfs_resident_attr_resize() to convert
SECURITY_DESCRIPTOR to nonresident form to make room. the conversion
only dirties the folio. With patch 5, the subsequent
iput(tvi) can evict the zero-link attribute inode and discard the SD
contents before writeback. If the file is later linked with linkat(),
it can retain invalid SD data after close. Please also ensure that the
converted data is written back before dropping the last
attribute-inode reference.

Thanks!

^ permalink raw reply	[flat|nested] 9+ messages in thread

* Re: [PATCH v2 4/5] ntfs: write unlinked file attributes before dropping their cache
  2026-11-04  9:06   ` Namjae Jeon
@ 2026-11-04  9:32     ` liubaolin
  0 siblings, 0 replies; 9+ messages in thread
From: liubaolin @ 2026-11-04  9:32 UTC (permalink / raw)
  To: Namjae Jeon; +Cc: hyc.lee, ntfs, linux-kernel, Xuewen Wang



在 2026/11/4 17:06, Namjae Jeon 写道:
>> @@ -4052,6 +4052,14 @@ s64 ntfs_inode_attr_pwrite(struct inode *vi, s64 pos, s64 count, u8 *buf, bool s
>>
>>          WARN_ON(!NInoAttr(ni));
>>
>> +       /*
>> +        * Attribute inodes of an unlinked file have no links either and are
>> +        * evicted at the final iput(). Do not leave their data in dirty pages
>> +        * that eviction would discard while the base file is still open.
>> +        */
>> +       if (!VFS_I(base_ni)->i_nlink && !ntfs_inode_is_named_stream(ni))
>> +               sync = true;
> This misses data dirtied by ntfs_attr_make_non_resident(). For
> example, with 1 KiB MFT records, create an O_TMPFILE, nearly fill
> its MFT record with a resident EA, then write 4 KiB to the file.
> Growing $DATA can force ntfs_resident_attr_resize() to convert
> SECURITY_DESCRIPTOR to nonresident form to make room. the conversion
> only dirties the folio. With patch 5, the subsequent
> iput(tvi) can evict the zero-link attribute inode and discard the SD
> contents before writeback. If the file is later linked with linkat(),
> it can retain invalid SD data after close. Please also ensure that the
> converted data is written back before dropping the last
> attribute-inode reference.
> 
> Thanks!

Hi Namjae,
    Thank you for the review. I will fix this issue and send v3 soon.

Thanks,
Baolin.


^ permalink raw reply	[flat|nested] 9+ messages in thread

end of thread, other threads:[~2026-11-04  9:33 UTC | newest]

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-10-31  5:28 [PATCH v2 0/5] ntfs: support O_TMPFILE Baolin Liu
2026-10-31  5:28 ` [PATCH v2 1/5] ntfs: initialize inherited ACLs after allocating the MFT record Baolin Liu
2026-10-31  5:28 ` [PATCH v2 2/5] ntfs: preserve timestamps when creating hard links Baolin Liu
2026-10-31  5:28 ` [PATCH v2 3/5] ntfs: initialize packed EA size " Baolin Liu
2026-10-31  5:28 ` [PATCH v2 4/5] ntfs: write unlinked file attributes before dropping their cache Baolin Liu
2026-11-04  9:06   ` Namjae Jeon
2026-11-04  9:32     ` liubaolin
2026-10-31  5:28 ` [PATCH v2 5/5] ntfs: support O_TMPFILE Baolin Liu
2026-11-04  7:03 ` [PATCH v2 0/5] " Hyunchul Lee

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®