Linux filesystem development
 help / color / mirror / Atom feed
* [PATCH 0/3] ntfs: fix quota out-of-date marking
@ 2026-05-11 16:06 DaeMyung Kang
  2026-05-11 16:06 ` [PATCH 1/3] ntfs: return view index entry data from lookup DaeMyung Kang
                   ` (2 more replies)
  0 siblings, 3 replies; 4+ messages in thread
From: DaeMyung Kang @ 2026-05-11 16:06 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang

This series fixes NTFS quota out-of-date marking in fs/ntfs.

The driver already has quota compatibility code: it loads
FILE_Extend/$Quota, opens the $Q index, and has
ntfs_mark_quotas_out_of_date() to set QUOTA_FLAG_OUT_OF_DATE so Windows
can rescan quota usage.  The failures were not an intentional quota block;
the code could not reach and update the $Quota/$Q defaults entry correctly.

Patch 1 fixes ntfs_index_lookup() so callers looking up view indexes get
the index entry value instead of the key.  Directory $I30 callers keep the
existing key-return behavior.  The patch uses the index root type, not the
collation rule, to choose between $FILE_NAME keys and view-index values. It
also validates the returned value bounds without truncating the minimum
offset, excludes the child VCN tail from INDEX_ENTRY_NODE value ranges, and
clears the returned context pointers before returning an invalid view-index
entry error.  The matched-entry data exposure and validation are moved into
a small helper, ntfs_index_lookup_set_data(), so the done label stays flat
and the bounds check has a single return.

Patch 2 fixes ntfs_mark_quotas_out_of_date() to look up $Quota/$Q by the
$Q index name, not $I30.

Patch 3 calls ntfs_mark_quotas_out_of_date() during initial read-write mount
as well as the existing read-only to read-write remount path.  The init
sequence is wrapped in a small helper, ntfs_init_quota_for_mount(), which
captures each call result in a local variable, derives the failure reason,
and gates the read-only downgrade on policy in one place — keeping
ntfs_load_system_files() flat.

QEMU/virtme testing:

- The original remount-rw reproducer failed with:
  "Lookup of quota defaults entry failed."
- After changing only the quota index name, the same path exposed the second
  bug and failed with:
  "Quota defaults entry size is invalid.  Run chkdsk."
- With this series applied:
  - read-only mount followed by remount,rw succeeds
  - with the $Quota/$Q defaults flags prepared as 0x00000011, remount,rw
    persists 0x00000211
  - direct rw mount with the same prepared image also persists 0x00000211

DaeMyung Kang (3):
  ntfs: return view index entry data from lookup
  ntfs: use $Q when marking quotas out of date
  ntfs: mark quotas out of date on initial rw mount

 fs/ntfs/index.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 fs/ntfs/index.h |  3 +-
 fs/ntfs/quota.c |  3 +-
 fs/ntfs/super.c | 46 ++++++++++++++++++++++++++--------
 4 files changed, 110 insertions(+), 17 deletions(-)

-- 
2.43.0

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

* [PATCH 1/3] ntfs: return view index entry data from lookup
  2026-05-11 16:06 [PATCH 0/3] ntfs: fix quota out-of-date marking DaeMyung Kang
@ 2026-05-11 16:06 ` DaeMyung Kang
  2026-05-11 16:06 ` [PATCH 2/3] ntfs: use $Q when marking quotas out of date DaeMyung Kang
  2026-05-11 16:06 ` [PATCH 3/3] ntfs: mark quotas out of date on initial rw mount DaeMyung Kang
  2 siblings, 0 replies; 4+ messages in thread
From: DaeMyung Kang @ 2026-05-11 16:06 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang

ntfs_index_lookup() always exposes the matched entry key through
icx->data.  That is correct for file name indexes, where $I30 callers
expect the key to be a struct file_name_attr and update it in place.

NTFS view indexes store a separate value in index_entry.data.vi.  For
example, $Quota/$Q uses the owner_id as the key and stores the
quota_control_entry in the entry value.  Returning the key makes quota code
see only the 4-byte owner_id instead of the quota control entry.

Preserve the existing $FILE_NAME index behavior, but return the entry value
for view indexes.  Use the index root type for that layout decision:
indexes with ir->type == AT_FILE_NAME expose the entry key, while view
indexes have type 0 and expose the entry value through data.vi.  Validate
the value offset and length before exposing the pointer, and clear
icx->data when the key is not found because the context then describes an
insertion point rather than a matched value.  Use unsigned arithmetic for
the minimum data offset check, account for the VCN stored at the end of
INDEX_ENTRY_NODE entries, and clear the returned context pointers before
returning an entry-data validation error.

Move the matched-entry data exposure and validation into a small helper,
ntfs_index_lookup_set_data(), so the done label stays flat and the bounds
check has a single return.

Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 fs/ntfs/index.c | 75 ++++++++++++++++++++++++++++++++++++++++++++++++++++---
 fs/ntfs/index.h |  3 +-
 2 files changed, 73 insertions(+), 5 deletions(-)

diff --git a/fs/ntfs/index.c b/fs/ntfs/index.c
index a547bdc..30dfcbb 100644
--- a/fs/ntfs/index.c
+++ b/fs/ntfs/index.c
@@ -696,6 +696,53 @@ static int ntfs_icx_parent_dec(struct ntfs_index_context *icx)
 }

 /*
+ * ntfs_index_lookup_set_data - populate icx->data for the matched entry
+ *
+ * For directory $FILE_NAME indexes the caller updates the key in place, so
+ * expose the key.  For view indexes the value lives at data.vi; validate
+ * its offset and length before exposing the pointer.  Return 0 on success,
+ * or -EIO if the entry value is out of bounds.
+ */
+static int ntfs_index_lookup_set_data(struct ntfs_index_context *icx,
+				      struct index_root *ir,
+				      struct index_entry *ie)
+{
+	u16 data_len, data_off, entry_len, found_key_len;
+	unsigned int data_end, min_data_off;
+
+	if (ie->flags & INDEX_ENTRY_END)
+		return -EIO;
+
+	found_key_len = le16_to_cpu(ie->key_length);
+	if (ir->type == AT_FILE_NAME) {
+		icx->data = (u8 *)ie + offsetof(struct index_entry, key);
+		icx->data_len = found_key_len;
+		return 0;
+	}
+
+	entry_len = le16_to_cpu(ie->length);
+	data_off = le16_to_cpu(ie->data.vi.data_offset);
+	data_len = le16_to_cpu(ie->data.vi.data_length);
+	data_end = entry_len;
+	if (ie->flags & INDEX_ENTRY_NODE) {
+		/* The trailing child VCN is not part of the entry value. */
+		if (data_end < sizeof(s64))
+			return -EIO;
+		data_end -= sizeof(s64);
+	}
+
+	min_data_off = offsetof(struct index_entry, key) + found_key_len;
+	if (data_off < min_data_off || data_off > data_end)
+		return -EIO;
+	if (data_len > data_end - data_off)
+		return -EIO;
+
+	icx->data = (u8 *)ie + data_off;
+	icx->data_len = data_len;
+	return 0;
+}
+
+/*
  * ntfs_index_lookup - find a key in an index and return its index entry
  * @key:	key for which to search in the index
  * @key_len:	length of @key in bytes
@@ -710,7 +757,8 @@ static int ntfs_icx_parent_dec(struct ntfs_index_context *icx)
  * If the @key is found in the index, 0 is returned and @icx is setup to
  * describe the index entry containing the matching @key.  @icx->entry is the
  * index entry and @icx->data and @icx->data_len are the index entry data and
- * its length in bytes, respectively.
+ * its length in bytes, respectively.  For file name indexes @icx->data points
+ * to the entry key; for view indexes @icx->data points to the entry value.
  *
  * If the @key is not found in the index, -ENOENT is returned and
  * @icx is setup to describe the index entry whose key collates immediately
@@ -836,11 +884,30 @@ err_out:
 	return err;
 done:
 	icx->entry = ie;
-	icx->data = (u8 *)ie + offsetof(struct index_entry, key);
-	icx->data_len = le16_to_cpu(ie->key_length);
+	if (err) {
+		/* When the key is not found, ie is an insertion point. */
+		icx->data = NULL;
+		icx->data_len = 0;
+		ntfs_debug("Done.\n");
+		return err;
+	}
+	err = ntfs_index_lookup_set_data(icx, ir, ie);
+	if (err) {
+		ntfs_error(sb,
+			   "Index entry data out of bounds in inode 0x%llx.",
+			   (unsigned long long)ni->mft_no);
+		icx->entry = NULL;
+		icx->data = NULL;
+		icx->data_len = 0;
+		if (!icx->is_in_root) {
+			kvfree(ib);
+			ib = NULL;
+			icx->ib = NULL;
+		}
+		goto err_out;
+	}
 	ntfs_debug("Done.\n");
 	return err;
-
 }

 static struct index_block *ntfs_ib_alloc(s64 ib_vcn, u32 ib_size,
diff --git a/fs/ntfs/index.h b/fs/ntfs/index.h
index e68d6fa..4b8c5c8 100644
--- a/fs/ntfs/index.h
+++ b/fs/ntfs/index.h
@@ -58,7 +58,8 @@
  * To obtain a context call ntfs_index_ctx_get().
  *
  * We use this context to allow ntfs_index_lookup() to return the found index
  * @entry and its @data without having to allocate a buffer and copy the @entry
- * and/or its @data into it.
+ * and/or its @data into it.  For file name indexes @data points to the entry
+ * key; for view indexes @data points to the entry value.
  *
  * When finished with the @entry and its @data, call ntfs_index_ctx_put() to
  * free the context and other associated resources.
--
2.43.0

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

* [PATCH 2/3] ntfs: use $Q when marking quotas out of date
  2026-05-11 16:06 [PATCH 0/3] ntfs: fix quota out-of-date marking DaeMyung Kang
  2026-05-11 16:06 ` [PATCH 1/3] ntfs: return view index entry data from lookup DaeMyung Kang
@ 2026-05-11 16:06 ` DaeMyung Kang
  2026-05-11 16:06 ` [PATCH 3/3] ntfs: mark quotas out of date on initial rw mount DaeMyung Kang
  2 siblings, 0 replies; 4+ messages in thread
From: DaeMyung Kang @ 2026-05-11 16:06 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang

ntfs_mark_quotas_out_of_date() operates on vol->quota_q_ino, which is the
$Quota/$Q index inode opened by load_and_init_quota().  However, it creates
the index context with the $I30 name.

That asks the $Quota file for an $INDEX_ROOT named $I30 and fails before
the quota defaults entry can be looked up.  Use the $Q index name when
marking quotas out of date.

Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 fs/ntfs/quota.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/ntfs/quota.c b/fs/ntfs/quota.c
index b443243..7086951 100644
--- a/fs/ntfs/quota.c
+++ b/fs/ntfs/quota.c
@@ -21,6 +21,7 @@ bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol)
 {
 	struct ntfs_index_context *ictx;
 	struct quota_control_entry *qce;
+	static __le16 Q[3] = { cpu_to_le16('$'), cpu_to_le16('Q'), 0 };
 	const __le32 qid = QUOTA_DEFAULTS_ID;
 	int err;
 
@@ -32,7 +33,7 @@ bool ntfs_mark_quotas_out_of_date(struct ntfs_volume *vol)
 		return false;
 	}
 	inode_lock(vol->quota_q_ino);
-	ictx = ntfs_index_ctx_get(NTFS_I(vol->quota_q_ino), I30, 4);
+	ictx = ntfs_index_ctx_get(NTFS_I(vol->quota_q_ino), Q, 2);
 	if (!ictx) {
 		ntfs_error(vol->sb, "Failed to get index context.");
 		goto err_out;
-- 
2.43.0


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

* [PATCH 3/3] ntfs: mark quotas out of date on initial rw mount
  2026-05-11 16:06 [PATCH 0/3] ntfs: fix quota out-of-date marking DaeMyung Kang
  2026-05-11 16:06 ` [PATCH 1/3] ntfs: return view index entry data from lookup DaeMyung Kang
  2026-05-11 16:06 ` [PATCH 2/3] ntfs: use $Q when marking quotas out of date DaeMyung Kang
@ 2026-05-11 16:06 ` DaeMyung Kang
  2 siblings, 0 replies; 4+ messages in thread
From: DaeMyung Kang @ 2026-05-11 16:06 UTC (permalink / raw)
  To: linkinjeon, hyc.lee; +Cc: linux-fsdevel, linux-kernel, DaeMyung Kang

The remount read-write path marks quotas out of date after emptying the
logfile, but initial read-write mount only loads $Quota and never calls
ntfs_mark_quotas_out_of_date().

That leaves quota tracking metadata looking up to date even though the
driver can modify the volume.  Call the same helper after $Quota is loaded
during initial read-write mount.  If marking quotas out of date fails and
the mount policy is on_errors=remount-ro, convert the mount to read-only
and set the volume error state, matching the nearby $Quota load failure
handling.

This intentionally follows the initial mount error policy rather than the
remount-rw path, where a quota marking failure rejects the remount with
-EROFS.

Move the load/mark/policy handling into a small helper,
ntfs_init_quota_for_mount(), which captures each call result in a local,
derives the failure reason, and gates the read-only downgrade on policy
in one place.  This avoids calling helpers inside compound if conditions
and keeps ntfs_load_system_files() flat.

Signed-off-by: DaeMyung Kang <charsyam@gmail.com>
---
 fs/ntfs/super.c | 46 +++++++++++++++++++++++++++++++++--------------
 1 file changed, 35 insertions(+), 11 deletions(-)

diff --git a/fs/ntfs/super.c b/fs/ntfs/super.c
index 22dc786..8fa298f 100644
--- a/fs/ntfs/super.c
+++ b/fs/ntfs/super.c
@@ -1221,6 +1221,39 @@ static bool load_and_init_quota(struct ntfs_volume *vol)
 	return true;
 }

+/*
+ * ntfs_init_quota_for_mount - finish quota setup at mount time
+ * @sb:		super block of the volume being mounted
+ * @vol:	ntfs volume to set up
+ *
+ * Load $Quota and, on a read-write mount, mark quotas out of date so that
+ * Windows rescans them on next boot.  On failure, downgrade the mount to
+ * read-only when on_errors=remount-ro, matching the volume error policy.
+ */
+static void ntfs_init_quota_for_mount(struct super_block *sb,
+				      struct ntfs_volume *vol)
+{
+	bool quota_loaded, quota_marked = true;
+	const char *reason = NULL;
+
+	quota_loaded = load_and_init_quota(vol);
+	if (quota_loaded && !sb_rdonly(sb))
+		quota_marked = ntfs_mark_quotas_out_of_date(vol);
+
+	if (!quota_loaded)
+		reason = "Failed to load $Quota";
+	else if (!quota_marked)
+		reason = "Failed to mark quotas out of date";
+
+	if (!reason || vol->on_errors != ON_ERRORS_REMOUNT_RO)
+		return;
+
+	sb->s_flags |= SB_RDONLY;
+	ntfs_error(sb, "%s.  Mounting read-only.  Run chkdsk.", reason);
+	/* This will prevent a read-write remount. */
+	NVolSetErrors(vol);
+}
+
 /*
  * load_and_init_attrdef - load the attribute definitions table for a volume
  * @vol:	ntfs super block describing device whose attrdef to load
@@ -1638,16 +1671,7 @@ get_ctx_vol_failed:
 		ntfs_error(sb, "Failed to load $Extend.");
 		goto iput_sec_err_out;
 	}
-	/* Find the quota file, load it if present, and set it up. */
-	if (!load_and_init_quota(vol) &&
-	    vol->on_errors == ON_ERRORS_REMOUNT_RO) {
-		static const char *es1 = "Failed to load $Quota";
-		static const char *es2 = ".  Run chkdsk.";
-
-		sb->s_flags |= SB_RDONLY;
-		ntfs_error(sb, "%s.  Mounting read-only%s", es1, es2);
-		/* This will prevent a read-write remount. */
-		NVolSetErrors(vol);
-	}
+	/* Find the quota file, load it if present, and set it up. */
+	ntfs_init_quota_for_mount(sb, vol);

 	return true;
--
2.43.0


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

end of thread, other threads:[~2026-05-11 16:06 UTC | newest]

Thread overview: 4+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-05-11 16:06 [PATCH 0/3] ntfs: fix quota out-of-date marking DaeMyung Kang
2026-05-11 16:06 ` [PATCH 1/3] ntfs: return view index entry data from lookup DaeMyung Kang
2026-05-11 16:06 ` [PATCH 2/3] ntfs: use $Q when marking quotas out of date DaeMyung Kang
2026-05-11 16:06 ` [PATCH 3/3] ntfs: mark quotas out of date on initial rw mount DaeMyung Kang

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