* [PATCH v3 1/5] ntfs: initialize inherited ACLs after allocating the MFT record
2026-11-06 6:30 [PATCH v3 0/5] ntfs: support O_TMPFILE Baolin Liu
@ 2026-11-06 6:30 ` Baolin Liu
2026-11-06 6:30 ` [PATCH v3 2/5] ntfs: preserve timestamps when creating hard links Baolin Liu
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Baolin Liu @ 2026-11-06 6:30 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.
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
Tested in QEMU with four CPUs, NTFS debug and lockdep enabled. Ordinary
files inherited access ACLs; published temporary-file ACLs survived
cache eviction, remount and reboot. Tests used cached default ACLs;
the pre-existing cold-cache ACL defect is left to the follow-up series.
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] 8+ messages in thread* [PATCH v3 2/5] ntfs: preserve timestamps when creating hard links
2026-11-06 6:30 [PATCH v3 0/5] ntfs: support O_TMPFILE Baolin Liu
2026-11-06 6:30 ` [PATCH v3 1/5] ntfs: initialize inherited ACLs after allocating the MFT record Baolin Liu
@ 2026-11-06 6:30 ` Baolin Liu
2026-11-06 6:30 ` [PATCH v3 3/5] ntfs: initialize packed EA size " Baolin Liu
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Baolin Liu @ 2026-11-06 6:30 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.
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Xuewen Wang <wangxuewen@kylinos.cn>
---
Tested in QEMU; generic/004 and generic/389 passed. Hard links preserved
atime/mtime and advanced ctime. Offline checks confirmed matching ctime
in STANDARD_INFORMATION, the new FILE_NAME and the directory index.
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] 8+ messages in thread* [PATCH v3 3/5] ntfs: initialize packed EA size when creating hard links
2026-11-06 6:30 [PATCH v3 0/5] ntfs: support O_TMPFILE Baolin Liu
2026-11-06 6:30 ` [PATCH v3 1/5] ntfs: initialize inherited ACLs after allocating the MFT record Baolin Liu
2026-11-06 6:30 ` [PATCH v3 2/5] ntfs: preserve timestamps when creating hard links Baolin Liu
@ 2026-11-06 6:30 ` Baolin Liu
2026-11-06 6:30 ` [PATCH v3 4/5] ntfs: write unlinked file attributes before dropping their cache Baolin Liu
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Baolin Liu @ 2026-11-06 6:30 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>
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Xuewen Wang <wangxuewen@kylinos.cn>
---
Tested in QEMU. EAs prepared before publication remained readable after
linkat(), cache eviction, remount and reboot.
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] 8+ messages in thread* [PATCH v3 4/5] ntfs: write unlinked file attributes before dropping their cache
2026-11-06 6:30 [PATCH v3 0/5] ntfs: support O_TMPFILE Baolin Liu
` (2 preceding siblings ...)
2026-11-06 6:30 ` [PATCH v3 3/5] ntfs: initialize packed EA size " Baolin Liu
@ 2026-11-06 6:30 ` Baolin Liu
2026-11-06 6:30 ` [PATCH v3 5/5] ntfs: support O_TMPFILE Baolin Liu
2026-11-06 11:37 ` [PATCH v3 0/5] " Namjae Jeon
5 siblings, 0 replies; 8+ messages in thread
From: Baolin Liu @ 2026-11-06 6:30 UTC (permalink / raw)
To: linkinjeon, hyc.lee; +Cc: ntfs, linux-kernel, Xuewen Wang
From: Xuewen Wang <wangxuewen@kylinos.cn>
Attribute inodes initialized for unlinked files have zero links and are
evicted at the final iput(). Explicit writes and resident-to-nonresident
conversion can leave dirty data that eviction discards, losing EAs or a
converted SECURITY_DESCRIPTOR.
Write internal attributes synchronously when the base inode has no
links. During conversion, write the resident value to the new clusters
before changing the record, so write failure can release the clusters
and retain the resident value. Propagate conversion errors to the caller.
Keep named streams on their existing I/O path. This avoids retaining
attribute inodes that would prevent unpublished files from being reclaimed.
Signed-off-by: Xuewen Wang <wangxuewen@kylinos.cn>
---
Changes since v2:
- Write converted attribute data before updating the record; preserve the
resident value and release new clusters on write failure.
- Propagate conversion errors instead of trying another attribute.
Tested in QEMU with four CPUs, NTFS debug and lockdep enabled. With 1 KiB
MFT records, varied EA sizes from 0 to 920 bytes before a 4 KiB write.
Converted security descriptors and EAs matched their expected contents
with 512-byte, 4 KiB and 8 KiB clusters, including after reboot.
Injected EIO preserved resident attributes without leaking clusters;
retry succeeded. The nonresident EA, cleanup and publication tests passed.
fs/ntfs/attrib.c | 58 ++++++++++++++++++++++++++++++++++++++++++++++--
fs/ntfs/inode.c | 8 +++++++
2 files changed, 64 insertions(+), 2 deletions(-)
diff --git a/fs/ntfs/attrib.c b/fs/ntfs/attrib.c
index fce7144d7bef..9e49edebc7d1 100644
--- a/fs/ntfs/attrib.c
+++ b/fs/ntfs/attrib.c
@@ -16,6 +16,7 @@
* Copyright (c) 2010 Erik Larsson
*/
+#include <linux/bio.h>
#include <linux/string_choices.h>
#include <linux/writeback.h>
#include <linux/iomap.h>
@@ -1958,6 +1959,40 @@ int ntfs_resident_attr_value_resize(struct mft_record *m, struct attr_record *a,
return 0;
}
+/* Write the resident value to its new clusters before committing conversion. */
+static int ntfs_attr_write_conversion(struct ntfs_volume *vol, struct folio *folio,
+ const struct runlist_element *rl, u32 size)
+{
+ unsigned int offset = 0;
+
+ size = round_up(size, vol->sector_size);
+ while (size) {
+ struct bio *bio;
+ unsigned int len;
+ int err;
+
+ if (!rl->length || rl->lcn < 0)
+ return -EIO;
+ len = min_t(s64, size, ntfs_cluster_to_bytes(vol, rl->length));
+ bio = bio_alloc(vol->sb->s_bdev, 1, REQ_OP_WRITE | REQ_SYNC,
+ GFP_NOIO);
+ bio->bi_iter.bi_sector =
+ ntfs_bytes_to_bio_sector(ntfs_cluster_to_bytes(vol, rl->lcn));
+ if (!bio_add_folio(bio, folio, len, offset)) {
+ bio_put(bio);
+ return -EIO;
+ }
+ err = submit_bio_wait(bio);
+ bio_put(bio);
+ if (err)
+ return err;
+ offset += len;
+ size -= len;
+ rl++;
+ }
+ return 0;
+}
+
/*
* ntfs_attr_make_non_resident - convert a resident to a non-resident attribute
* @ni: ntfs inode describing the attribute to convert
@@ -1987,6 +2022,7 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
int mp_size, mp_ofs, name_ofs, arec_size, err, err2;
u32 attr_size;
u8 old_res_attr_flags;
+ bool sync;
if (NInoNonResident(ni)) {
ntfs_warning(vol->sb,
@@ -2011,6 +2047,8 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
base_ni = ni;
else
base_ni = ni->ext.base_ntfs_ino;
+ sync = NInoAttr(ni) && !VFS_I(base_ni)->i_nlink &&
+ !ntfs_inode_is_named_stream(ni);
m = map_mft_record(base_ni);
if (IS_ERR(m)) {
err = PTR_ERR(m);
@@ -2120,6 +2158,18 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
folio_mark_uptodate(folio);
}
+ /*
+ * A zero-link attribute inode can be evicted by the caller's iput().
+ * Write its data before changing the record so an I/O failure leaves
+ * the resident value intact. Normal writeback would take mrec_lock,
+ * which callers may already hold.
+ */
+ if (folio && sync) {
+ err = ntfs_attr_write_conversion(vol, folio, rl, attr_size);
+ if (err)
+ goto rl_err_out;
+ }
+
/* Backup the attribute flag. */
old_res_attr_flags = a->data.resident.flags;
/* Resize the resident part of the attribute record. */
@@ -2208,7 +2258,8 @@ int ntfs_attr_make_non_resident(struct ntfs_inode *ni, const u32 data_size)
unmap_mft_record(base_ni);
up_write(&ni->runlist.lock);
if (folio) {
- iomap_dirty_folio(vi->i_mapping, folio);
+ if (!sync)
+ iomap_dirty_folio(vi->i_mapping, folio);
folio_unlock(folio);
folio_put(folio);
}
@@ -4941,8 +4992,11 @@ static int ntfs_resident_attr_resize(struct ntfs_inode *attr_ni, const s64 newsi
continue;
}
- if (ntfs_attr_make_non_resident(NTFS_I(tvi), value_len)) {
+ err = ntfs_attr_make_non_resident(NTFS_I(tvi), value_len);
+ if (err) {
iput(tvi);
+ if (err != -ENOSPC && err != -EPERM)
+ goto put_err_out;
continue;
}
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] 8+ messages in thread* [PATCH v3 5/5] ntfs: support O_TMPFILE
2026-11-06 6:30 [PATCH v3 0/5] ntfs: support O_TMPFILE Baolin Liu
` (3 preceding siblings ...)
2026-11-06 6:30 ` [PATCH v3 4/5] ntfs: write unlinked file attributes before dropping their cache Baolin Liu
@ 2026-11-06 6:30 ` Baolin Liu
2026-11-06 11:37 ` [PATCH v3 0/5] " Namjae Jeon
5 siblings, 0 replies; 8+ messages in thread
From: Baolin Liu @ 2026-11-06 6:30 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.
Reviewed-by: Hyunchul Lee <hyc.lee@gmail.com>
Signed-off-by: Baolin Liu <liubaolin@kylinos.cn>
---
Tested in QEMU with four CPUs, NTFS debug and lockdep enabled. Creation,
publication, cleanup, O_EXCL, permissions, idmapped ownership, named
streams, nonresident EAs, direct I/O, mmap, allocation and concurrent
publication passed. Data and metadata survived cache eviction, remount
and reboot; generic/004 and generic/389 passed.
The basic ENOSPC test passed. Full-volume ACL creation failures remain
subject to the pre-existing rollback bugs described in the cover letter.
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] 8+ messages in thread* Re: [PATCH v3 0/5] ntfs: support O_TMPFILE
2026-11-06 6:30 [PATCH v3 0/5] ntfs: support O_TMPFILE Baolin Liu
` (4 preceding siblings ...)
2026-11-06 6:30 ` [PATCH v3 5/5] ntfs: support O_TMPFILE Baolin Liu
@ 2026-11-06 11:37 ` Namjae Jeon
2026-11-09 1:59 ` liubaolin
5 siblings, 1 reply; 8+ messages in thread
From: Namjae Jeon @ 2026-11-06 11:37 UTC (permalink / raw)
To: Baolin Liu; +Cc: hyc.lee, ntfs, linux-kernel, Baolin Liu
On Fri, Nov 6, 2026 at 3:31 PM Baolin Liu <liubaolin12138@163.com> wrote:
>
> From: Baolin Liu <liubaolin@kylinos.cn>
Hi Baolin,
>
> 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 and v2:
>
> 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 EAs and data copied during resident-to-nonresident
> conversion 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.
>
> In v3, patch 4 also preserves data copied by
> ntfs_attr_make_non_resident(). As reported in review, growing DATA in a
> nearly full MFT record can convert SECURITY_DESCRIPTOR and leave its
> contents only in a dirty folio. Evicting the zero-link attribute inode
> then discards those contents.
>
> Write the resident value to the newly allocated clusters before
> committing the conversion. If that write fails, release the new clusters
> and retain the original resident value. Propagate the error to the caller
> instead of continuing with another attribute. Patches 1-3 and 5 are
> unchanged in code from v2.
>
> 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 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.
>
> The conversion regression uses 1 KiB MFT records and varies EA sizes
> before writing 4 KiB to anonymous and ordinary files. Offline inspection
> compares SECURITY_DESCRIPTOR contents with the original resident value.
> The v2 kernel loses converted security descriptors and some EAs; v3
> preserves them with 512-byte, 4 KiB and 8 KiB clusters, including after a
> fresh boot. An injected block write error returns EIO and leaves the
> resident security descriptor and EA intact. Free space is unchanged after
> that failure, and retrying the write succeeds.
>
> Changes in v3:
> - Extend patch 4 to write converted attribute data before switching the
> record to nonresident form, addressing the v2 review.
> - Preserve the resident value on conversion write failure and propagate
> conversion errors.
> - Add conversion and I/O-error regression tests and rerun generic/004
> and generic/389.
> - No code changes to patches 1-3 and 5; add Hyunchul Lee's Reviewed-by.
>
> 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.
>
> 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
Can you check Sashiko's review comments? I think some of the points
raised in Sashiko’s review are worth looking into.
https://sashiko.dev/#/patchset/20261106063059.2904988-1-liubaolin12138%40163.com
Thanks.
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH v3 0/5] ntfs: support O_TMPFILE
2026-11-06 11:37 ` [PATCH v3 0/5] " Namjae Jeon
@ 2026-11-09 1:59 ` liubaolin
0 siblings, 0 replies; 8+ messages in thread
From: liubaolin @ 2026-11-09 1:59 UTC (permalink / raw)
To: Namjae Jeon; +Cc: hyc.lee, ntfs, linux-kernel, Baolin Liu
在 2026/11/6 19:37, Namjae Jeon 写道:
> On Fri, Nov 6, 2026 at 3:31 PM Baolin Liu <liubaolin12138@163.com> wrote:
>>
>> From: Baolin Liu <liubaolin@kylinos.cn>
> Hi Baolin,
>>
>> 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 and v2:
>>
>> 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 EAs and data copied during resident-to-nonresident
>> conversion 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.
>>
>> In v3, patch 4 also preserves data copied by
>> ntfs_attr_make_non_resident(). As reported in review, growing DATA in a
>> nearly full MFT record can convert SECURITY_DESCRIPTOR and leave its
>> contents only in a dirty folio. Evicting the zero-link attribute inode
>> then discards those contents.
>>
>> Write the resident value to the newly allocated clusters before
>> committing the conversion. If that write fails, release the new clusters
>> and retain the original resident value. Propagate the error to the caller
>> instead of continuing with another attribute. Patches 1-3 and 5 are
>> unchanged in code from v2.
>>
>> 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 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.
>>
>> The conversion regression uses 1 KiB MFT records and varies EA sizes
>> before writing 4 KiB to anonymous and ordinary files. Offline inspection
>> compares SECURITY_DESCRIPTOR contents with the original resident value.
>> The v2 kernel loses converted security descriptors and some EAs; v3
>> preserves them with 512-byte, 4 KiB and 8 KiB clusters, including after a
>> fresh boot. An injected block write error returns EIO and leaves the
>> resident security descriptor and EA intact. Free space is unchanged after
>> that failure, and retrying the write succeeds.
>>
>> Changes in v3:
>> - Extend patch 4 to write converted attribute data before switching the
>> record to nonresident form, addressing the v2 review.
>> - Preserve the resident value on conversion write failure and propagate
>> conversion errors.
>> - Add conversion and I/O-error regression tests and rerun generic/004
>> and generic/389.
>> - No code changes to patches 1-3 and 5; add Hyunchul Lee's Reviewed-by.
>>
>> 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.
>>
>> 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
>
> Can you check Sashiko's review comments? I think some of the points
> raised in Sashiko’s review are worth looking into.
>
> https://sashiko.dev/#/patchset/20261106063059.2904988-1-liubaolin12138%40163.com
>
> Thanks.
Hi Namjae,
Thanks for pointing this out. I'll address the review comments and
send v4 as soon as possible.
Thanks,
Baolin.
^ permalink raw reply [flat|nested] 8+ messages in thread