mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH 00/30] ocfs2: split dinode validation into shared predicates
@ 2026-11-05  9:48 Joseph Qi
  2026-11-05  9:48 ` [PATCH 01/30] ocfs2: abstract the zero nlink or mode dinode check Joseph Qi
                   ` (30 more replies)
  0 siblings, 31 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:48 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

ocfs2_validate_inode_block() is a flat sequence of about twenty open-coded
on-disk dinode checks.  ocfs2_filecheck_validate_inode_block() duplicates
eight of them, but reports differently: plain mlog() plus an
OCFS2_FILECHECK_ERR_* code instead of ocfs2_error(), because filecheck is
a diagnostic interface an administrator points at a suspect inode and it
must not escalate into ocfs2_handle_error(), which can force the
filesystem read-only or panic.

The duplicated conditions had already drifted apart -- the two
OCFS2_VALID_FL tests were spelled differently -- and filecheck silently
skipped the rest.

This series splits the conditions out and then brings filecheck up to
parity.

Patches 1-20: pure refactor.  Each condition becomes a static bool
ocfs2_dinode_has_*() predicate returning true on bad, following the four
helpers already in inode.c.  Return codes and log messages stay at the
call sites, so the two reporting paths remain distinct and no
ocfs2_error() call site moves.  One check per commit.  No functional
change intended.

Patch 21: a separable fix.  ocfs2_filecheck_repair_inode_block() proceeds
when validation fails but then returns 0 unconditionally, after repairing
only i_blkno, fs_generation and l_next_free_rec.  An inode failing any
other check is reported to userspace as SUCCESS while still corrupt.
This is already true today for the mode, i_rdev and size-without-clusters
checks.  Revalidate after the repairs and return the residual code.  Drop
this patch if you would rather keep the filecheck series purely additive,
but it then widens the set of masked failures considerably.

Patches 22-30: wire the remaining predicates into filecheck.  All reuse
OCFS2_FILECHECK_ERR_INVALIDINO, so no new error code is introduced and the
ocfs2_filecheck_errs[] table with its userspace-visible names is
unchanged.

Points worth a close look:

 - The fast symlink NUL-termination predicate bounds its own strnlen()
   against ocfs2_fast_symlink_chars().  The open-coded version was only
   safe because the preceding i_size check had already bailed; i_size
   comes from disk, so an unbounded strnlen() over i_size + 1 bytes can
   run past the inode block.  A helper should not inherit that ordering
   dependency implicitly, especially once filecheck calls it too.

 - The inline id_count maximum stays a signed int.
   ocfs2_max_inline_data_with_xattr() subtracts the unvalidated
   i_xattr_inline_size and goes negative on small block sizes.  Caching it
   unsigned would turn a negative maximum into a huge bound and silently
   accept the oversized id_count.

 - ocfs2_dinode_is_fast_symlink() is deliberately not shared with the
   similar test inside ocfs2_dinode_has_extents(), which additionally
   requires a non-zero i_size.  A zero-length, zero-cluster symlink
   currently falls through to the extent list checks.

 - The suballoc range predicate keeps its != OCFS2_INVALID_SLOT
   exclusion.  OCFS2_INVALID_SLOT is 65535, so without it every system
   inode mkfs.ocfs2 creates from the global allocator would be rejected.

 - Look-alike tests in suballoc.c are left alone:
   ocfs2_validate_group_descriptor() deliberately permits
   OCFS2_INVALID_SLOT for all inodes, and ocfs2_claim_suballoc_bits() also
   rejects cl_next_free_rec == 0.  Neither is equivalent to the new
   predicates.

The inline xattr check is the one thing filecheck still does not share.
ocfs2_validate_inode_xattr() calls ocfs2_error() through its helpers in
xattr.c, so reusing it would mean threading a reporting flag down into
that file.  Left for a follow-up.

Testing, on top of linux-next 20261103:

Patches 1-20 were checked to be behaviour preserving.  All 27 log
messages, all return codes and all ocfs2_error()/mlog() call sites in
ocfs2_validate_inode_block() are unchanged, each new predicate body was
compared against the condition it replaced, and the disassembly of
ocfs2_filecheck_validate_inode_block() is instruction identical apart
from __LINE__ constants.  The normal validator's disassembly differs by
more than that -- basic blocks are rescheduled and registers reallocated
-- so equivalence there rests on the source level comparison rather than
on an instruction by instruction proof.  The deliberate rewrites are the
ocfs2_read_links_count() reuse in patch 1, the OCFS2_VALID_FL spelling
unification in patch 3, flattening the mutually exclusive suballoc
if/else-if into two tests in patch 5, and the redundant self-bounding
guard added in patch 13.

In a QEMU guest running this series, on 4K blocks with inline-data,
refcount, sparse, usrquota, grpquota, xattr and indexed-dirs enabled:
mount and I/O over seventeen inode types plus all the system inodes
produced no false positive, and filecheck reported SUCCESS for every one
of them.  A separate 512-byte block volume with inline-data, xattr,
sparse and refcount was checked the same way over five inode types, also
with no false positive.

The corruption tests used a third volume formatted with nometaecc, so
that a checksum failure would not mask the field being tested.  Seven
deliberately corrupted dinodes -- zero and oversized extent l_count,
oversized inline id_count, oversized fast symlink i_size, refcount flag
without i_refcount_loc, oversized suballoc bit and oversized
l_next_free_rec -- were each detected with the expected message, with no
ocfs2_error() escalation and the filesystem still read-write afterwards.
fix was then run on two of them: the zero l_count case, which repair
cannot fix, returned INVALIDINO, and the l_next_free_rec case, which it
does fix, returned SUCCESS.

Forcing i_xattr_inline_size up on the 512-byte block volume drove the
inline maximum negative and produced "inline data id_count 312 exceeds
max -88", confirming the signed comparison survived the extraction.

The ocfs2 testsuite and xfstests were not run.

Joseph Qi (30):
  ocfs2: abstract the zero nlink or mode dinode check
  ocfs2: abstract the dinode i_blkno mismatch check
  ocfs2: abstract the dinode OCFS2_VALID_FL check
  ocfs2: abstract the dinode fs_generation mismatch check
  ocfs2: abstract the dinode suballoc slot checks
  ocfs2: abstract the dinode suballoc bit check
  ocfs2: abstract the dinode orphaned slot check
  ocfs2: abstract the dinode DIO orphaned slot check
  ocfs2: abstract the inline data dinode guard and cluster check
  ocfs2: abstract the inline data id_count bound check
  ocfs2: abstract the inline data i_size bound check
  ocfs2: abstract the fast symlink dinode guard and size check
  ocfs2: abstract the fast symlink NUL-termination check
  ocfs2: abstract the chain list dinode guard and count check
  ocfs2: abstract the chain list next free record check
  ocfs2: abstract the chain list bits per cluster check
  ocfs2: abstract the dinode refcount location check
  ocfs2: abstract the zero extent list count check
  ocfs2: abstract the oversized extent list count check
  ocfs2: abstract the extent list next free record check
  ocfs2: report unrepairable corruption from online filecheck fix
  ocfs2: check dinode nlink and mode in online filecheck
  ocfs2: check dinode suballoc slot in online filecheck
  ocfs2: check dinode suballoc bit in online filecheck
  ocfs2: check dinode orphaned slots in online filecheck
  ocfs2: check inline data dinodes in online filecheck
  ocfs2: check fast symlink dinodes in online filecheck
  ocfs2: check chain list dinodes in online filecheck
  ocfs2: check dinode refcount location in online filecheck
  ocfs2: check extent list dinodes in online filecheck

 fs/ocfs2/inode.c | 456 +++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 399 insertions(+), 57 deletions(-)

-- 
2.39.3


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

* [PATCH 01/30] ocfs2: abstract the zero nlink or mode dinode check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
@ 2026-11-05  9:48 ` Joseph Qi
  2026-11-05  9:48 ` [PATCH 02/30] ocfs2: abstract the dinode i_blkno mismatch check Joseph Qi
                   ` (29 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:48 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

ocfs2_validate_inode_block() open-codes the test for a dinode that has
no links or no mode.  Fold the condition into a predicate so it reads
like the neighbouring ocfs2_dinode_has_*() helpers, and so the filecheck
variant can share it.

Reuse ocfs2_read_links_count() rather than testing i_links_count and
i_links_count_hi separately.  OCFS2_LINKS_HI_SHIFT is 16 and both fields
are 16 bits wide, so the combined count is zero exactly when both raw
fields are zero.

The return code and the log message stay at the call site: filecheck
reports the same conditions with a different code and must not call
ocfs2_error().

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 8bbebcafa7a1..bffd64e0a1c0 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -67,6 +67,11 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
 						      struct buffer_head *bh);
 
+static bool ocfs2_dinode_has_zero_nlink_or_mode(struct ocfs2_dinode *di)
+{
+	return !ocfs2_read_links_count(di) || !di->i_mode;
+}
+
 static bool ocfs2_valid_inode_mode(umode_t mode)
 {
 	return fs_umode_to_ftype(mode) != FT_UNKNOWN;
@@ -1481,7 +1486,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
-	if ((!di->i_links_count && !di->i_links_count_hi) || !di->i_mode) {
+	if (ocfs2_dinode_has_zero_nlink_or_mode(di)) {
 		mlog(ML_ERROR, "Invalid dinode #%llu: "
 			"Corrupt state (nlink = %u or mode = %u) detected!\n",
 		        (unsigned long long)bh->b_blocknr,
-- 
2.39.3


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

* [PATCH 02/30] ocfs2: abstract the dinode i_blkno mismatch check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
  2026-11-05  9:48 ` [PATCH 01/30] ocfs2: abstract the zero nlink or mode dinode check Joseph Qi
@ 2026-11-05  9:48 ` Joseph Qi
  2026-11-05  9:48 ` [PATCH 03/30] ocfs2: abstract the dinode OCFS2_VALID_FL check Joseph Qi
                   ` (28 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:48 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

The same i_blkno against b_blocknr comparison is open-coded in
ocfs2_validate_inode_block(), ocfs2_filecheck_validate_inode_block() and
ocfs2_filecheck_repair_inode_block().  Fold it into a shared predicate.

Each call site keeps its own log message and return code: the normal
validator escalates through ocfs2_error(), filecheck reports
OCFS2_FILECHECK_ERR_BLOCKNO, and repair rewrites the field.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index bffd64e0a1c0..b13c7a9b54f3 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -72,6 +72,11 @@ static bool ocfs2_dinode_has_zero_nlink_or_mode(struct ocfs2_dinode *di)
 	return !ocfs2_read_links_count(di) || !di->i_mode;
 }
 
+static bool ocfs2_dinode_has_wrong_blkno(struct ocfs2_dinode *di, u64 blkno)
+{
+	return le64_to_cpu(di->i_blkno) != blkno;
+}
+
 static bool ocfs2_valid_inode_mode(umode_t mode)
 {
 	return fs_umode_to_ftype(mode) != FT_UNKNOWN;
@@ -1507,7 +1512,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
-	if (le64_to_cpu(di->i_blkno) != bh->b_blocknr) {
+	if (ocfs2_dinode_has_wrong_blkno(di, bh->b_blocknr)) {
 		rc = ocfs2_error(sb, "Invalid dinode #%llu: i_blkno is %llu\n",
 				 (unsigned long long)bh->b_blocknr,
 				 (unsigned long long)le64_to_cpu(di->i_blkno));
@@ -1829,7 +1834,7 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 	} else if (rc)
 		goto bail;
 
-	if (le64_to_cpu(di->i_blkno) != bh->b_blocknr) {
+	if (ocfs2_dinode_has_wrong_blkno(di, bh->b_blocknr)) {
 		mlog(ML_ERROR,
 		     "Filecheck: invalid dinode #%llu: i_blkno is %llu\n",
 		     (unsigned long long)bh->b_blocknr,
@@ -1934,7 +1939,7 @@ static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
 		return -OCFS2_FILECHECK_ERR_VALIDFLAG;
 	}
 
-	if (le64_to_cpu(di->i_blkno) != bh->b_blocknr) {
+	if (ocfs2_dinode_has_wrong_blkno(di, bh->b_blocknr)) {
 		di->i_blkno = cpu_to_le64(bh->b_blocknr);
 		changed = 1;
 		mlog(ML_ERROR,
-- 
2.39.3


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

* [PATCH 03/30] ocfs2: abstract the dinode OCFS2_VALID_FL check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
  2026-11-05  9:48 ` [PATCH 01/30] ocfs2: abstract the zero nlink or mode dinode check Joseph Qi
  2026-11-05  9:48 ` [PATCH 02/30] ocfs2: abstract the dinode i_blkno mismatch check Joseph Qi
@ 2026-11-05  9:48 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 04/30] ocfs2: abstract the dinode fs_generation mismatch check Joseph Qi
                   ` (27 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:48 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

The test for a dinode without OCFS2_VALID_FL is open-coded three times,
and the two spellings have already drifted apart: the normal validator
uses le32_to_cpu(di->i_flags) & OCFS2_VALID_FL while filecheck and repair
use di->i_flags & cpu_to_le32(OCFS2_VALID_FL).  Fold them into one
predicate using the le32_to_cpu() form.

Byteswapping is a bit permutation, so both spellings test the same
on-disk bit on either endianness.  The BUG_ON() in ocfs2_populate_inode()
is left alone; it asserts an invariant the validator has already
established rather than validating on-disk state.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 11 ++++++++---
 1 file changed, 8 insertions(+), 3 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index b13c7a9b54f3..9f6808cec22a 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -77,6 +77,11 @@ static bool ocfs2_dinode_has_wrong_blkno(struct ocfs2_dinode *di, u64 blkno)
 	return le64_to_cpu(di->i_blkno) != blkno;
 }
 
+static bool ocfs2_dinode_has_no_valid_flag(struct ocfs2_dinode *di)
+{
+	return !(le32_to_cpu(di->i_flags) & OCFS2_VALID_FL);
+}
+
 static bool ocfs2_valid_inode_mode(umode_t mode)
 {
 	return fs_umode_to_ftype(mode) != FT_UNKNOWN;
@@ -1519,7 +1524,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
-	if (!(le32_to_cpu(di->i_flags) & OCFS2_VALID_FL)) {
+	if (ocfs2_dinode_has_no_valid_flag(di)) {
 		rc = ocfs2_error(sb,
 				 "Invalid dinode #%llu: OCFS2_VALID_FL not set\n",
 				 (unsigned long long)bh->b_blocknr);
@@ -1843,7 +1848,7 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
-	if (!(di->i_flags & cpu_to_le32(OCFS2_VALID_FL))) {
+	if (ocfs2_dinode_has_no_valid_flag(di)) {
 		mlog(ML_ERROR,
 		     "Filecheck: invalid dinode #%llu: OCFS2_VALID_FL "
 		     "not set\n",
@@ -1932,7 +1937,7 @@ static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
 		return -OCFS2_FILECHECK_ERR_INVALIDINO;
 	}
 
-	if (!(di->i_flags & cpu_to_le32(OCFS2_VALID_FL))) {
+	if (ocfs2_dinode_has_no_valid_flag(di)) {
 		/* Cannot just add VALID_FL flag back as a fix,
 		 * need more things to check here.
 		 */
-- 
2.39.3


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

* [PATCH 04/30] ocfs2: abstract the dinode fs_generation mismatch check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (2 preceding siblings ...)
  2026-11-05  9:48 ` [PATCH 03/30] ocfs2: abstract the dinode OCFS2_VALID_FL check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 05/30] ocfs2: abstract the dinode suballoc slot checks Joseph Qi
                   ` (26 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

The comparison against OCFS2_SB(sb)->fs_generation is open-coded in the
normal validator, the filecheck validator and the repair path.  Fold it
into a shared predicate.

Each call site keeps its own log message and return code: the normal
validator escalates through ocfs2_error(), filecheck reports
OCFS2_FILECHECK_ERR_GENERATION, and repair rewrites the field.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 9f6808cec22a..8eefda8fe4a6 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -82,6 +82,12 @@ static bool ocfs2_dinode_has_no_valid_flag(struct ocfs2_dinode *di)
 	return !(le32_to_cpu(di->i_flags) & OCFS2_VALID_FL);
 }
 
+static bool ocfs2_dinode_has_wrong_fs_generation(struct super_block *sb,
+						 struct ocfs2_dinode *di)
+{
+	return le32_to_cpu(di->i_fs_generation) != OCFS2_SB(sb)->fs_generation;
+}
+
 static bool ocfs2_valid_inode_mode(umode_t mode)
 {
 	return fs_umode_to_ftype(mode) != FT_UNKNOWN;
@@ -1531,8 +1537,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
-	if (le32_to_cpu(di->i_fs_generation) !=
-	    OCFS2_SB(sb)->fs_generation) {
+	if (ocfs2_dinode_has_wrong_fs_generation(sb, di)) {
 		rc = ocfs2_error(sb,
 				 "Invalid dinode #%llu: fs_generation is %u\n",
 				 (unsigned long long)bh->b_blocknr,
@@ -1857,8 +1862,7 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
-	if (le32_to_cpu(di->i_fs_generation) !=
-	    OCFS2_SB(sb)->fs_generation) {
+	if (ocfs2_dinode_has_wrong_fs_generation(sb, di)) {
 		mlog(ML_ERROR,
 		     "Filecheck: invalid dinode #%llu: fs_generation is %u\n",
 		     (unsigned long long)bh->b_blocknr,
@@ -1953,8 +1957,7 @@ static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
 		     (unsigned long long)le64_to_cpu(di->i_blkno));
 	}
 
-	if (le32_to_cpu(di->i_fs_generation) !=
-	    OCFS2_SB(sb)->fs_generation) {
+	if (ocfs2_dinode_has_wrong_fs_generation(sb, di)) {
 		di->i_fs_generation = cpu_to_le32(OCFS2_SB(sb)->fs_generation);
 		changed = 1;
 		mlog(ML_ERROR,
-- 
2.39.3


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

* [PATCH 05/30] ocfs2: abstract the dinode suballoc slot checks
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (3 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 04/30] ocfs2: abstract the dinode fs_generation mismatch check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 06/30] ocfs2: abstract the dinode suballoc bit check Joseph Qi
                   ` (25 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

The suballoc slot validation is a nested if/else-if whose two arms carry
different error messages.  Split it into two predicates so each message
stays at its own call site without having to re-test the slot value to
work out which arm fired.

The arms were already mutually exclusive, so two sequential tests are
equivalent to the if/else-if.

Keep the OCFS2_INVALID_SLOT exclusion in the range predicate.
OCFS2_INVALID_SLOT is 65535, which is greater than max_slots - 1, so
without it every system inode mkfs.ocfs2 creates from the global
allocator would be rejected.  ocfs2_validate_group_descriptor() in
suballoc.c open-codes a similar range test that deliberately permits
OCFS2_INVALID_SLOT for all inodes; it is left alone because this
predicate is stricter.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 31 ++++++++++++++++++++++---------
 1 file changed, 22 insertions(+), 9 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 8eefda8fe4a6..412c8149d306 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -88,6 +88,19 @@ static bool ocfs2_dinode_has_wrong_fs_generation(struct super_block *sb,
 	return le32_to_cpu(di->i_fs_generation) != OCFS2_SB(sb)->fs_generation;
 }
 
+static bool ocfs2_dinode_has_invalid_suballoc_slot(struct ocfs2_dinode *di)
+{
+	return le16_to_cpu(di->i_suballoc_slot) == (u16)OCFS2_INVALID_SLOT &&
+		!(le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL);
+}
+
+static bool ocfs2_dinode_has_out_of_range_suballoc_slot(struct super_block *sb,
+							struct ocfs2_dinode *di)
+{
+	return le16_to_cpu(di->i_suballoc_slot) != (u16)OCFS2_INVALID_SLOT &&
+		(u32)le16_to_cpu(di->i_suballoc_slot) > OCFS2_SB(sb)->max_slots - 1;
+}
+
 static bool ocfs2_valid_inode_mode(umode_t mode)
 {
 	return fs_umode_to_ftype(mode) != FT_UNKNOWN;
@@ -1553,15 +1566,15 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 	 * deleting it would pass the slot to get_local_system_inode() via
 	 * ocfs2_remove_inode() and trigger BUG_ON(slot == OCFS2_INVALID_SLOT).
 	 */
-	if (le16_to_cpu(di->i_suballoc_slot) == (u16)OCFS2_INVALID_SLOT) {
-		if (!(le32_to_cpu(di->i_flags) & OCFS2_SYSTEM_FL)) {
-			rc = ocfs2_error(sb,
-					 "Invalid dinode %llu: suballoc slot %u for non-system inode\n",
-					 (unsigned long long)bh->b_blocknr,
-					 le16_to_cpu(di->i_suballoc_slot));
-			goto bail;
-		}
-	} else if ((u32)le16_to_cpu(di->i_suballoc_slot) > OCFS2_SB(sb)->max_slots - 1) {
+	if (ocfs2_dinode_has_invalid_suballoc_slot(di)) {
+		rc = ocfs2_error(sb,
+				 "Invalid dinode %llu: suballoc slot %u for non-system inode\n",
+				 (unsigned long long)bh->b_blocknr,
+				 le16_to_cpu(di->i_suballoc_slot));
+		goto bail;
+	}
+
+	if (ocfs2_dinode_has_out_of_range_suballoc_slot(sb, di)) {
 		rc = ocfs2_error(sb, "Invalid dinode %llu: suballoc slot %u\n",
 				 (unsigned long long)bh->b_blocknr,
 				 le16_to_cpu(di->i_suballoc_slot));
-- 
2.39.3


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

* [PATCH 06/30] ocfs2: abstract the dinode suballoc bit check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (4 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 05/30] ocfs2: abstract the dinode suballoc slot checks Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 07/30] ocfs2: abstract the dinode orphaned slot check Joseph Qi
                   ` (24 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the i_suballoc_bit against ocfs2_suballoc_bits_per_block() test into
a predicate.  The rationale comment stays at the call site, next to the
error it explains.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 412c8149d306..89970378daf4 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -101,6 +101,12 @@ static bool ocfs2_dinode_has_out_of_range_suballoc_slot(struct super_block *sb,
 		(u32)le16_to_cpu(di->i_suballoc_slot) > OCFS2_SB(sb)->max_slots - 1;
 }
 
+static bool ocfs2_dinode_has_bad_suballoc_bit(struct super_block *sb,
+					      struct ocfs2_dinode *di)
+{
+	return le16_to_cpu(di->i_suballoc_bit) >= ocfs2_suballoc_bits_per_block(sb);
+}
+
 static bool ocfs2_valid_inode_mode(umode_t mode)
 {
 	return fs_umode_to_ftype(mode) != FT_UNKNOWN;
@@ -1590,7 +1596,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 	 * BUG_ON((count + start_bit) > ocfs2_bits_per_group(cl)), since any
 	 * group holds at most ocfs2_suballoc_bits_per_block() bits.
 	 */
-	if (le16_to_cpu(di->i_suballoc_bit) >= ocfs2_suballoc_bits_per_block(sb)) {
+	if (ocfs2_dinode_has_bad_suballoc_bit(sb, di)) {
 		rc = ocfs2_error(sb, "Invalid dinode %llu: suballoc bit %u\n",
 				 (unsigned long long)bh->b_blocknr,
 				 le16_to_cpu(di->i_suballoc_bit));
-- 
2.39.3


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

* [PATCH 07/30] ocfs2: abstract the dinode orphaned slot check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (5 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 06/30] ocfs2: abstract the dinode suballoc bit check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 08/30] ocfs2: abstract the dinode DIO " Joseph Qi
                   ` (23 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the OCFS2_ORPHANED_FL guarded i_orphaned_slot range test into a
predicate.  The flag test stays inside it: i_orphaned_slot is only
meaningful when the inode is actually orphaned, so a bare range test
would reject valid inodes whose orphaned slot field is stale.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 89970378daf4..33572a91324d 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -107,6 +107,13 @@ static bool ocfs2_dinode_has_bad_suballoc_bit(struct super_block *sb,
 	return le16_to_cpu(di->i_suballoc_bit) >= ocfs2_suballoc_bits_per_block(sb);
 }
 
+static bool ocfs2_dinode_has_bad_orphaned_slot(struct super_block *sb,
+					       struct ocfs2_dinode *di)
+{
+	return (le32_to_cpu(di->i_flags) & OCFS2_ORPHANED_FL) &&
+		le16_to_cpu(di->i_orphaned_slot) >= OCFS2_SB(sb)->max_slots;
+}
+
 static bool ocfs2_valid_inode_mode(umode_t mode)
 {
 	return fs_umode_to_ftype(mode) != FT_UNKNOWN;
@@ -1603,8 +1610,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
-	if ((le32_to_cpu(di->i_flags) & OCFS2_ORPHANED_FL) &&
-	    le16_to_cpu(di->i_orphaned_slot) >= OCFS2_SB(sb)->max_slots) {
+	if (ocfs2_dinode_has_bad_orphaned_slot(sb, di)) {
 		rc = ocfs2_error(sb, "Invalid dinode %llu: orphaned slot %u\n",
 				 (unsigned long long)bh->b_blocknr,
 				 le16_to_cpu(di->i_orphaned_slot));
-- 
2.39.3


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

* [PATCH 08/30] ocfs2: abstract the dinode DIO orphaned slot check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (6 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 07/30] ocfs2: abstract the dinode orphaned slot check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 09/30] ocfs2: abstract the inline data dinode guard and cluster check Joseph Qi
                   ` (22 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the OCFS2_DIO_ORPHANED_FL guarded i_dio_orphaned_slot range test
into a predicate, mirroring the orphaned slot one.  The flag test stays
inside it for the same reason: the slot field is only meaningful while
the inode is DIO orphaned.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 33572a91324d..8c23170bb595 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -114,6 +114,13 @@ static bool ocfs2_dinode_has_bad_orphaned_slot(struct super_block *sb,
 		le16_to_cpu(di->i_orphaned_slot) >= OCFS2_SB(sb)->max_slots;
 }
 
+static bool ocfs2_dinode_has_bad_dio_orphaned_slot(struct super_block *sb,
+						   struct ocfs2_dinode *di)
+{
+	return (le32_to_cpu(di->i_flags) & OCFS2_DIO_ORPHANED_FL) &&
+		le16_to_cpu(di->i_dio_orphaned_slot) >= OCFS2_SB(sb)->max_slots;
+}
+
 static bool ocfs2_valid_inode_mode(umode_t mode)
 {
 	return fs_umode_to_ftype(mode) != FT_UNKNOWN;
@@ -1617,8 +1624,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
-	if ((le32_to_cpu(di->i_flags) & OCFS2_DIO_ORPHANED_FL) &&
-	    le16_to_cpu(di->i_dio_orphaned_slot) >= OCFS2_SB(sb)->max_slots) {
+	if (ocfs2_dinode_has_bad_dio_orphaned_slot(sb, di)) {
 		rc = ocfs2_error(sb, "Invalid dinode %llu: DIO orphaned slot %u\n",
 				 (unsigned long long)bh->b_blocknr,
 				 le16_to_cpu(di->i_dio_orphaned_slot));
-- 
2.39.3


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

* [PATCH 09/30] ocfs2: abstract the inline data dinode guard and cluster check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (7 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 08/30] ocfs2: abstract the dinode DIO " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 10/30] ocfs2: abstract the inline data id_count bound check Joseph Qi
                   ` (21 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Introduce ocfs2_dinode_has_inline_data() for the OCFS2_INLINE_DATA_FL
test that selects the id2.i_data union arm, and
ocfs2_dinode_has_inline_clusters() for the rule that an inline-data
inode must not also have allocated clusters.

The guard stays above the sub-checks: reading id_count or i_clusters
against the wrong id2 arm would produce false positives on valid inodes.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 14 ++++++++++++--
 1 file changed, 12 insertions(+), 2 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 8c23170bb595..96e4d5c5bfa8 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -154,6 +154,16 @@ static bool ocfs2_dinode_has_size_without_clusters(struct super_block *sb,
 	return !ocfs2_sparse_alloc(OCFS2_SB(sb)) && S_ISREG(mode);
 }
 
+static bool ocfs2_dinode_has_inline_data(struct ocfs2_dinode *di)
+{
+	return le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL;
+}
+
+static bool ocfs2_dinode_has_inline_clusters(struct ocfs2_dinode *di)
+{
+	return le32_to_cpu(di->i_clusters) != 0;
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1715,10 +1725,10 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 	if (rc)
 		goto bail;
 
-	if (le16_to_cpu(di->i_dyn_features) & OCFS2_INLINE_DATA_FL) {
+	if (ocfs2_dinode_has_inline_data(di)) {
 		struct ocfs2_inline_data *data = &di->id2.i_data;
 
-		if (le32_to_cpu(di->i_clusters)) {
+		if (ocfs2_dinode_has_inline_clusters(di)) {
 			rc = ocfs2_error(sb,
 					 "Invalid dinode %llu: %u clusters\n",
 					 (unsigned long long)bh->b_blocknr,
-- 
2.39.3


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

* [PATCH 10/30] ocfs2: abstract the inline data id_count bound check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (8 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 09/30] ocfs2: abstract the inline data dinode guard and cluster check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 11/30] ocfs2: abstract the inline data i_size " Joseph Qi
                   ` (20 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the id_count against ocfs2_max_inline_data_with_xattr() test into a
predicate.

The comparison stays signed.  ocfs2_max_inline_data_with_xattr() returns
int and subtracts the unvalidated on-disk i_xattr_inline_size from the
space available in the inode block, so it can go negative on small block
sizes.  Caching it in an unsigned local would turn a negative maximum
into a huge bound and silently accept the oversized id_count.  The
message keeps its %d for the same reason.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 96e4d5c5bfa8..d2fb111cb193 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -164,6 +164,19 @@ static bool ocfs2_dinode_has_inline_clusters(struct ocfs2_dinode *di)
 	return le32_to_cpu(di->i_clusters) != 0;
 }
 
+static bool ocfs2_dinode_has_oversized_inline_data(struct super_block *sb,
+						   struct ocfs2_dinode *di)
+{
+	/*
+	 * The maximum is a signed int and can go negative, because
+	 * i_xattr_inline_size is itself unvalidated.  Keep the comparison
+	 * signed: an unsigned bound would turn a negative maximum into a
+	 * huge one and let the oversized id_count through.
+	 */
+	return le16_to_cpu(di->id2.i_data.id_count) >
+		ocfs2_max_inline_data_with_xattr(sb, di);
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1736,8 +1749,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 			goto bail;
 		}
 
-		if (le16_to_cpu(data->id_count) >
-		    ocfs2_max_inline_data_with_xattr(sb, di)) {
+		if (ocfs2_dinode_has_oversized_inline_data(sb, di)) {
 			rc = ocfs2_error(sb,
 					 "Invalid dinode #%llu: inline data id_count %u exceeds max %d\n",
 					 (unsigned long long)bh->b_blocknr,
-- 
2.39.3


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

* [PATCH 11/30] ocfs2: abstract the inline data i_size bound check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (9 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 10/30] ocfs2: abstract the inline data id_count bound check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 12/30] ocfs2: abstract the fast symlink dinode guard and size check Joseph Qi
                   ` (19 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the i_size against id_count test into a predicate, completing the
inline-data group.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index d2fb111cb193..db2a67543c7d 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -177,6 +177,11 @@ static bool ocfs2_dinode_has_oversized_inline_data(struct super_block *sb,
 		ocfs2_max_inline_data_with_xattr(sb, di);
 }
 
+static bool ocfs2_dinode_has_inline_size_over_count(struct ocfs2_dinode *di)
+{
+	return le64_to_cpu(di->i_size) > le16_to_cpu(di->id2.i_data.id_count);
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1758,7 +1763,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 			goto bail;
 		}
 
-		if (le64_to_cpu(di->i_size) > le16_to_cpu(data->id_count)) {
+		if (ocfs2_dinode_has_inline_size_over_count(di)) {
 			rc = ocfs2_error(sb,
 					 "Invalid dinode #%llu: inline data i_size %llu exceeds id_count %u\n",
 					 (unsigned long long)bh->b_blocknr,
-- 
2.39.3


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

* [PATCH 12/30] ocfs2: abstract the fast symlink dinode guard and size check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (10 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 11/30] ocfs2: abstract the inline data i_size " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 13/30] ocfs2: abstract the fast symlink NUL-termination check Joseph Qi
                   ` (18 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Introduce ocfs2_dinode_is_fast_symlink() for the S_ISLNK with no
allocated clusters test that selects the id2.i_symlink union arm, and
ocfs2_dinode_has_oversized_fast_symlink() for the i_size bound.

The guard is deliberately not shared with the similar test inside
ocfs2_dinode_has_extents(), which additionally requires a non-zero
i_size.  A zero-length, zero-cluster symlink currently falls through to
the extent list checks, and reusing one predicate for both would change
that.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index db2a67543c7d..51bfa7643ebd 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -182,6 +182,21 @@ static bool ocfs2_dinode_has_inline_size_over_count(struct ocfs2_dinode *di)
 	return le64_to_cpu(di->i_size) > le16_to_cpu(di->id2.i_data.id_count);
 }
 
+/*
+ * Unlike the similar test in ocfs2_dinode_has_extents(), this does not require
+ * a non-zero i_size: a zero-length fast symlink is still validated here.
+ */
+static bool ocfs2_dinode_is_fast_symlink(struct ocfs2_dinode *di)
+{
+	return S_ISLNK(le16_to_cpu(di->i_mode)) && !le32_to_cpu(di->i_clusters);
+}
+
+static bool ocfs2_dinode_has_oversized_fast_symlink(struct super_block *sb,
+						    struct ocfs2_dinode *di)
+{
+	return le64_to_cpu(di->i_size) >= ocfs2_fast_symlink_chars(sb);
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1773,12 +1788,11 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		}
 	}
 
-	if (S_ISLNK(le16_to_cpu(di->i_mode)) &&
-	    !le32_to_cpu(di->i_clusters)) {
+	if (ocfs2_dinode_is_fast_symlink(di)) {
 		int max_inline = ocfs2_fast_symlink_chars(sb);
 		u64 i_size = le64_to_cpu(di->i_size);
 
-		if (i_size >= max_inline) {
+		if (ocfs2_dinode_has_oversized_fast_symlink(sb, di)) {
 			rc = ocfs2_error(sb,
 					 "Invalid dinode #%llu: fast symlink i_size %llu exceeds max %d\n",
 					 (unsigned long long)bh->b_blocknr,
-- 
2.39.3


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

* [PATCH 13/30] ocfs2: abstract the fast symlink NUL-termination check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (11 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 12/30] ocfs2: abstract the fast symlink dinode guard and size check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 14/30] ocfs2: abstract the chain list dinode guard and count check Joseph Qi
                   ` (17 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the strnlen() test into a predicate that bounds the read itself
against ocfs2_fast_symlink_chars(), which is exactly the space available
for id2.i_symlink in the inode block.

The open-coded version was only safe because the preceding i_size check
had already bailed: i_size comes from disk, so an unbounded strnlen() on
i_size + 1 bytes can run past the end of the buffer.  Keeping that
ordering dependency implicit in a helper would be fragile, especially
once the filecheck variant starts calling it, so the predicate returns
false for an oversized i_size and leaves that case to
ocfs2_dinode_has_oversized_fast_symlink().

At the existing call site the size check still runs first, so behaviour
is unchanged.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 21 ++++++++++++++++++++-
 1 file changed, 20 insertions(+), 1 deletion(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 51bfa7643ebd..9cf2541b82ba 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -197,6 +197,25 @@ static bool ocfs2_dinode_has_oversized_fast_symlink(struct super_block *sb,
 	return le64_to_cpu(di->i_size) >= ocfs2_fast_symlink_chars(sb);
 }
 
+static bool ocfs2_dinode_has_unterminated_fast_symlink(struct super_block *sb,
+						       struct ocfs2_dinode *di)
+{
+	u64 i_size = le64_to_cpu(di->i_size);
+
+	/*
+	 * Bound the strnlen() here rather than relying on the caller having
+	 * rejected an oversized i_size first.  i_size comes from disk, and
+	 * ocfs2_fast_symlink_chars() is exactly the space available in the
+	 * inode block, so without this bound the read can run past it.
+	 * An oversized i_size is reported by
+	 * ocfs2_dinode_has_oversized_fast_symlink() instead.
+	 */
+	if (i_size >= ocfs2_fast_symlink_chars(sb))
+		return false;
+
+	return strnlen((char *)di->id2.i_symlink, i_size + 1) != i_size;
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1801,7 +1820,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 			goto bail;
 		}
 
-		if (strnlen((char *)di->id2.i_symlink, i_size + 1) != i_size) {
+		if (ocfs2_dinode_has_unterminated_fast_symlink(sb, di)) {
 			rc = ocfs2_error(sb,
 					 "Invalid dinode #%llu: fast symlink is not NUL-terminated at i_size %llu\n",
 					 (unsigned long long)bh->b_blocknr,
-- 
2.39.3


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

* [PATCH 14/30] ocfs2: abstract the chain list dinode guard and count check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (12 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 13/30] ocfs2: abstract the fast symlink NUL-termination check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 15/30] ocfs2: abstract the chain list next free record check Joseph Qi
                   ` (16 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Introduce ocfs2_dinode_has_chain_list() for the OCFS2_CHAIN_FL test that
selects the id2.i_chain union arm, and ocfs2_dinode_has_bad_chain_count()
for the cl_count against ocfs2_chain_recs_per_inode() test.

This mirrors the guard predicates already used for the inline-data and
fast symlink groups, and ocfs2_dinode_has_extents() for the extent list
group.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 16 ++++++++++++++--
 1 file changed, 14 insertions(+), 2 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 9cf2541b82ba..f3ee6491c141 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -216,6 +216,18 @@ static bool ocfs2_dinode_has_unterminated_fast_symlink(struct super_block *sb,
 	return strnlen((char *)di->id2.i_symlink, i_size + 1) != i_size;
 }
 
+static bool ocfs2_dinode_has_chain_list(struct ocfs2_dinode *di)
+{
+	return le32_to_cpu(di->i_flags) & OCFS2_CHAIN_FL;
+}
+
+static bool ocfs2_dinode_has_bad_chain_count(struct super_block *sb,
+					     struct ocfs2_dinode *di)
+{
+	return le16_to_cpu(di->id2.i_chain.cl_count) !=
+		ocfs2_chain_recs_per_inode(sb);
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1829,12 +1841,12 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		}
 	}
 
-	if (le32_to_cpu(di->i_flags) & OCFS2_CHAIN_FL) {
+	if (ocfs2_dinode_has_chain_list(di)) {
 		struct ocfs2_chain_list *cl = &di->id2.i_chain;
 		u16 bpc = 1 << (OCFS2_SB(sb)->s_clustersize_bits -
 				sb->s_blocksize_bits);
 
-		if (le16_to_cpu(cl->cl_count) != ocfs2_chain_recs_per_inode(sb)) {
+		if (ocfs2_dinode_has_bad_chain_count(sb, di)) {
 			rc = ocfs2_error(sb, "Invalid dinode %llu: chain list count %u\n",
 					 (unsigned long long)bh->b_blocknr,
 					 le16_to_cpu(cl->cl_count));
-- 
2.39.3


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

* [PATCH 15/30] ocfs2: abstract the chain list next free record check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (13 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 14/30] ocfs2: abstract the chain list dinode guard and count check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 16/30] ocfs2: abstract the chain list bits per cluster check Joseph Qi
                   ` (15 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the cl_next_free_rec against cl_count test into a predicate.

ocfs2_claim_suballoc_bits() in suballoc.c open-codes a similar test that
also rejects cl_next_free_rec == 0.  It is left alone because this
predicate is not equivalent to it.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index f3ee6491c141..88284a64cf49 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -228,6 +228,12 @@ static bool ocfs2_dinode_has_bad_chain_count(struct super_block *sb,
 		ocfs2_chain_recs_per_inode(sb);
 }
 
+static bool ocfs2_dinode_has_bad_chain_next_free(struct ocfs2_dinode *di)
+{
+	return le16_to_cpu(di->id2.i_chain.cl_next_free_rec) >
+		le16_to_cpu(di->id2.i_chain.cl_count);
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1852,7 +1858,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 					 le16_to_cpu(cl->cl_count));
 			goto bail;
 		}
-		if (le16_to_cpu(cl->cl_next_free_rec) > le16_to_cpu(cl->cl_count)) {
+		if (ocfs2_dinode_has_bad_chain_next_free(di)) {
 			rc = ocfs2_error(sb, "Invalid dinode %llu: chain list index %u\n",
 					 (unsigned long long)bh->b_blocknr,
 					 le16_to_cpu(cl->cl_next_free_rec));
-- 
2.39.3


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

* [PATCH 16/30] ocfs2: abstract the chain list bits per cluster check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (14 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 15/30] ocfs2: abstract the chain list next free record check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 17/30] ocfs2: abstract the dinode refcount location check Joseph Qi
                   ` (14 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the cl_bpc test into a predicate that computes the expected bits per
cluster itself, and drop the u16 bpc local from
ocfs2_validate_inode_block().  No log message prints it, so keeping it at
the call site would leave an unused variable.

The two bitmap_blkno conditions are preserved: the global bitmap inode
itself is exempt from the bits-per-cluster check.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 16 +++++++++++-----
 1 file changed, 11 insertions(+), 5 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 88284a64cf49..7f3995ca771b 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -234,6 +234,16 @@ static bool ocfs2_dinode_has_bad_chain_next_free(struct ocfs2_dinode *di)
 		le16_to_cpu(di->id2.i_chain.cl_count);
 }
 
+static bool ocfs2_dinode_has_bad_chain_bpc(struct super_block *sb,
+					   struct ocfs2_dinode *di)
+{
+	u16 bpc = 1 << (OCFS2_SB(sb)->s_clustersize_bits - sb->s_blocksize_bits);
+
+	return OCFS2_SB(sb)->bitmap_blkno &&
+		OCFS2_SB(sb)->bitmap_blkno != le64_to_cpu(di->i_blkno) &&
+		le16_to_cpu(di->id2.i_chain.cl_bpc) != bpc;
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1849,8 +1859,6 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 
 	if (ocfs2_dinode_has_chain_list(di)) {
 		struct ocfs2_chain_list *cl = &di->id2.i_chain;
-		u16 bpc = 1 << (OCFS2_SB(sb)->s_clustersize_bits -
-				sb->s_blocksize_bits);
 
 		if (ocfs2_dinode_has_bad_chain_count(sb, di)) {
 			rc = ocfs2_error(sb, "Invalid dinode %llu: chain list count %u\n",
@@ -1864,9 +1872,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 					 le16_to_cpu(cl->cl_next_free_rec));
 			goto bail;
 		}
-		if (OCFS2_SB(sb)->bitmap_blkno &&
-		    OCFS2_SB(sb)->bitmap_blkno != le64_to_cpu(di->i_blkno) &&
-		    le16_to_cpu(cl->cl_bpc) != bpc) {
+		if (ocfs2_dinode_has_bad_chain_bpc(sb, di)) {
 			rc = ocfs2_error(sb, "Invalid dinode %llu: bits per cluster %u\n",
 					 (unsigned long long)bh->b_blocknr,
 					 le16_to_cpu(cl->cl_bpc));
-- 
2.39.3


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

* [PATCH 17/30] ocfs2: abstract the dinode refcount location check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (15 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 16/30] ocfs2: abstract the chain list bits per cluster check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 18/30] ocfs2: abstract the zero extent list count check Joseph Qi
                   ` (13 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the OCFS2_HAS_REFCOUNT_FL without i_refcount_loc test into a
predicate.  The flag test stays inside it, since i_refcount_loc is only
meaningful for a reflink-capable inode.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 9 +++++++--
 1 file changed, 7 insertions(+), 2 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 7f3995ca771b..b4ef4077e09c 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -244,6 +244,12 @@ static bool ocfs2_dinode_has_bad_chain_bpc(struct super_block *sb,
 		le16_to_cpu(di->id2.i_chain.cl_bpc) != bpc;
 }
 
+static bool ocfs2_dinode_has_refcount_without_loc(struct ocfs2_dinode *di)
+{
+	return (le16_to_cpu(di->i_dyn_features) & OCFS2_HAS_REFCOUNT_FL) &&
+		!di->i_refcount_loc;
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1880,8 +1886,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		}
 	}
 
-	if ((le16_to_cpu(di->i_dyn_features) & OCFS2_HAS_REFCOUNT_FL) &&
-	    !di->i_refcount_loc) {
+	if (ocfs2_dinode_has_refcount_without_loc(di)) {
 		rc = ocfs2_error(sb, "Inode #%llu has refcount flag but no i_refcount_loc\n",
 				(unsigned long long)bh->b_blocknr);
 		goto bail;
-- 
2.39.3


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

* [PATCH 18/30] ocfs2: abstract the zero extent list count check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (16 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 17/30] ocfs2: abstract the dinode refcount location check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 19/30] ocfs2: abstract the oversized " Joseph Qi
                   ` (12 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the l_count == 0 test into a predicate, the first of the extent list
group guarded by ocfs2_dinode_has_extents().

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index b4ef4077e09c..70fb032ce891 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -250,6 +250,11 @@ static bool ocfs2_dinode_has_refcount_without_loc(struct ocfs2_dinode *di)
 		!di->i_refcount_loc;
 }
 
+static bool ocfs2_dinode_has_zero_extent_count(struct ocfs2_dinode *di)
+{
+	return le16_to_cpu(di->id2.i_list.l_count) == 0;
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1897,7 +1902,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 		u16 count = le16_to_cpu(el->l_count);
 		u16 next_free = le16_to_cpu(el->l_next_free_rec);
 
-		if (count == 0) {
+		if (ocfs2_dinode_has_zero_extent_count(di)) {
 			rc = ocfs2_error(sb,
 					 "Invalid dinode %llu: extent list l_count is zero\n",
 					 (unsigned long long)bh->b_blocknr);
-- 
2.39.3


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

* [PATCH 19/30] ocfs2: abstract the oversized extent list count check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (17 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 18/30] ocfs2: abstract the zero extent list count check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 20/30] ocfs2: abstract the extent list next free record check Joseph Qi
                   ` (11 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the l_count against ocfs2_extent_recs_per_inode() test into a
predicate.

Move the rationale for using the no-xattr maximum into the predicate,
next to the bound it explains, rather than leaving it at the call site
where the bound is no longer computed.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 21 ++++++++++++++-------
 1 file changed, 14 insertions(+), 7 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 70fb032ce891..f68f8a26e6ab 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -255,6 +255,19 @@ static bool ocfs2_dinode_has_zero_extent_count(struct ocfs2_dinode *di)
 	return le16_to_cpu(di->id2.i_list.l_count) == 0;
 }
 
+static bool ocfs2_dinode_has_oversized_extent_count(struct super_block *sb,
+						    struct ocfs2_dinode *di)
+{
+	/*
+	 * The exact capacity depends on i_xattr_inline_size, another
+	 * unvalidated on-disk field. Inline xattrs only shrink the list, so
+	 * the no-xattr maximum is a safe upper bound that a valid l_count
+	 * never exceeds.
+	 */
+	return le16_to_cpu(di->id2.i_list.l_count) >
+		ocfs2_extent_recs_per_inode(sb);
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1908,13 +1921,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 					 (unsigned long long)bh->b_blocknr);
 			goto bail;
 		}
-		/*
-		 * The exact capacity depends on i_xattr_inline_size, another
-		 * unvalidated on-disk field. Inline xattrs only shrink the
-		 * list, so the no-xattr maximum is a safe upper bound that a
-		 * valid l_count never exceeds.
-		 */
-		if (count > ocfs2_extent_recs_per_inode(sb)) {
+		if (ocfs2_dinode_has_oversized_extent_count(sb, di)) {
 			rc = ocfs2_error(sb,
 					 "Invalid dinode %llu: extent list l_count %u exceeds max %u\n",
 					 (unsigned long long)bh->b_blocknr, count,
-- 
2.39.3


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

* [PATCH 20/30] ocfs2: abstract the extent list next free record check
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (18 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 19/30] ocfs2: abstract the oversized " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 21/30] ocfs2: report unrepairable corruption from online filecheck fix Joseph Qi
                   ` (10 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Fold the l_next_free_rec against l_count test into a predicate and use it
in both ocfs2_validate_inode_block() and
ocfs2_filecheck_repair_inode_block(), which open-coded the same
comparison.  Repair keeps its ocfs2_dinode_has_extents() guard, which
selects the id2.i_list union arm.

This completes the split of ocfs2_validate_inode_block()'s open-coded
conditions into ocfs2_dinode_has_*() predicates.  Every return code and
log message stayed at its call site, so the normal validator still
escalates through ocfs2_error() while filecheck reports its own
OCFS2_FILECHECK_ERR_* codes without touching the error handling state.

No functional change intended.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index f68f8a26e6ab..115aad1b8926 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -268,6 +268,12 @@ static bool ocfs2_dinode_has_oversized_extent_count(struct super_block *sb,
 		ocfs2_extent_recs_per_inode(sb);
 }
 
+static bool ocfs2_dinode_has_bad_extent_next_free(struct ocfs2_dinode *di)
+{
+	return le16_to_cpu(di->id2.i_list.l_next_free_rec) >
+		le16_to_cpu(di->id2.i_list.l_count);
+}
+
 void ocfs2_set_inode_flags(struct inode *inode)
 {
 	unsigned int flags = OCFS2_I(inode)->ip_attr;
@@ -1928,7 +1934,7 @@ int ocfs2_validate_inode_block(struct super_block *sb,
 					 ocfs2_extent_recs_per_inode(sb));
 			goto bail;
 		}
-		if (next_free > count) {
+		if (ocfs2_dinode_has_bad_extent_next_free(di)) {
 			rc = ocfs2_error(sb,
 					 "Invalid dinode %llu: extent list l_next_free_rec %u exceeds l_count %u\n",
 					 (unsigned long long)bh->b_blocknr, next_free, count);
@@ -2099,7 +2105,7 @@ static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
 	}
 
 	if (ocfs2_dinode_has_extents(di) &&
-	    le16_to_cpu(di->id2.i_list.l_next_free_rec) > le16_to_cpu(di->id2.i_list.l_count)) {
+	    ocfs2_dinode_has_bad_extent_next_free(di)) {
 		di->id2.i_list.l_next_free_rec = di->id2.i_list.l_count;
 		changed = 1;
 		mlog(ML_ERROR,
-- 
2.39.3


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

* [PATCH 21/30] ocfs2: report unrepairable corruption from online filecheck fix
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (19 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 20/30] ocfs2: abstract the extent list next free record check Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 22/30] ocfs2: check dinode nlink and mode in online filecheck Joseph Qi
                   ` (9 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

ocfs2_filecheck_repair_inode_block() starts with

	if (!ocfs2_filecheck_validate_inode_block(sb, bh))
		return 0;

so it proceeds when validation fails, but then returns 0 unconditionally
after repairing only i_blkno, fs_generation and l_next_free_rec.  An
inode that fails any other check passes the signature and OCFS2_VALID_FL
gates, leaves changed at 0, and since its ecc was valid the final
recompute is skipped too.  Nothing is written and userspace is told
SUCCESS for an inode that is still corrupt.

This already happens for the mode, i_rdev and size-without-clusters
checks.  Re-validate after the repairs and return the residual code, so
the fix interface reports what it could not fix.
Documentation/filesystems/ocfs2-online-filecheck.rst already describes
this: "the inode must first be checked for errors before performing a
final fix".

Three consequences worth noting:

 - Inodes failing those three existing checks now report INVALIDINO
   instead of SUCCESS.  No new error code is introduced, so the
   ocfs2_filecheck_errs[] table and its userspace-visible names are
   unchanged.

 - A partial repair still marks the buffer dirty and is written back even
   when an error is returned.  The fields that were fixed are real
   improvements and are not rolled back.

 - The revalidation runs ocfs2_validate_meta_ecc() a second time, which
   increments osb_ecc_stats again and emits a second
   ocfs2_filecheck_validate_inode_block trace event per fix request.

Fixes: d56a8f32e4c6 ("ocfs2: check/fix inode block for online file check")
Cc: <stable@vger.kernel.org>
Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 115aad1b8926..cfa545981310 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2122,7 +2122,11 @@ static int ocfs2_filecheck_repair_inode_block(struct super_block *sb,
 		     (unsigned long long)bh->b_blocknr);
 	}
 
-	return 0;
+	/*
+	 * Only i_blkno, fs_generation and l_next_free_rec are repairable here,
+	 * so report whatever is still wrong instead of claiming success.
+	 */
+	return ocfs2_filecheck_validate_inode_block(sb, bh);
 }
 
 static int
-- 
2.39.3


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

* [PATCH 22/30] ocfs2: check dinode nlink and mode in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (20 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 21/30] ocfs2: report unrepairable corruption from online filecheck fix Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 23/30] ocfs2: check dinode suballoc slot " Joseph Qi
                   ` (8 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

The filecheck validator shares most of its conditions with
ocfs2_validate_inode_block(), but skips the zero nlink or mode check.
Wire it in using the predicate the two now share.

Filecheck reports OCFS2_FILECHECK_ERR_INVALIDINO and logs with the
Filecheck prefix rather than calling ocfs2_error(), which would escalate
to ocfs2_handle_error() and could force the filesystem read-only while
an administrator is only asking for a diagnosis.

The check is placed after the ecc and signature gate so that the
existing coupling is preserved: a bad signature still overrides a
checksum failure with INVALIDINO, and every later check still runs only
on an ecc-clean block.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index cfa545981310..07b9445bb538 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2009,6 +2009,15 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	if (ocfs2_dinode_has_zero_nlink_or_mode(di)) {
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: nlink = %u or mode = %u\n",
+		     (unsigned long long)bh->b_blocknr,
+		     ocfs2_read_links_count(di), le16_to_cpu(di->i_mode));
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
 	if (!ocfs2_valid_inode_mode(le16_to_cpu(di->i_mode))) {
 		mlog(ML_ERROR,
 		     "Filecheck: invalid dinode #%llu: mode 0%o has unknown file type\n",
-- 
2.39.3


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

* [PATCH 23/30] ocfs2: check dinode suballoc slot in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (21 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 22/30] ocfs2: check dinode nlink and mode in online filecheck Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 24/30] ocfs2: check dinode suballoc bit " Joseph Qi
                   ` (7 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Wire both suballoc slot predicates into the filecheck validator.  An
OCFS2_INVALID_SLOT on a non-system inode and an out-of-range slot keep
their separate messages, so the log still distinguishes the two.

The range predicate excludes OCFS2_INVALID_SLOT, so system inodes
created by mkfs.ocfs2 from the global allocator are still accepted.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 07b9445bb538..7cde33f0f616 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2018,6 +2018,24 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	if (ocfs2_dinode_has_invalid_suballoc_slot(di)) {
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: suballoc slot %u for non-system inode\n",
+		     (unsigned long long)bh->b_blocknr,
+		     le16_to_cpu(di->i_suballoc_slot));
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
+	if (ocfs2_dinode_has_out_of_range_suballoc_slot(sb, di)) {
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: suballoc slot %u\n",
+		     (unsigned long long)bh->b_blocknr,
+		     le16_to_cpu(di->i_suballoc_slot));
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
 	if (!ocfs2_valid_inode_mode(le16_to_cpu(di->i_mode))) {
 		mlog(ML_ERROR,
 		     "Filecheck: invalid dinode #%llu: mode 0%o has unknown file type\n",
-- 
2.39.3


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

* [PATCH 24/30] ocfs2: check dinode suballoc bit in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (22 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 23/30] ocfs2: check dinode suballoc slot " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 25/30] ocfs2: check dinode orphaned slots " Joseph Qi
                   ` (6 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Wire the suballoc bit range predicate into the filecheck validator.  A
suballocator group bitmap lives in a single block, so a bit at or beyond
ocfs2_suballoc_bits_per_block() can never be valid; accepting it would
later trip the BUG_ON() in _ocfs2_free_suballoc_bits() when the inode is
deleted.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 7cde33f0f616..0c515ba37ea0 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2036,6 +2036,15 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	if (ocfs2_dinode_has_bad_suballoc_bit(sb, di)) {
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: suballoc bit %u\n",
+		     (unsigned long long)bh->b_blocknr,
+		     le16_to_cpu(di->i_suballoc_bit));
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
 	if (!ocfs2_valid_inode_mode(le16_to_cpu(di->i_mode))) {
 		mlog(ML_ERROR,
 		     "Filecheck: invalid dinode #%llu: mode 0%o has unknown file type\n",
-- 
2.39.3


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

* [PATCH 25/30] ocfs2: check dinode orphaned slots in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (23 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 24/30] ocfs2: check dinode suballoc bit " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 26/30] ocfs2: check inline data dinodes " Joseph Qi
                   ` (5 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Wire both orphaned slot predicates into the filecheck validator.  Each
predicate keeps its flag guard, so a stale slot field on an inode that
is not orphaned is still accepted.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 0c515ba37ea0..db8cdf4d8627 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2045,6 +2045,24 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	if (ocfs2_dinode_has_bad_orphaned_slot(sb, di)) {
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: orphaned slot %u\n",
+		     (unsigned long long)bh->b_blocknr,
+		     le16_to_cpu(di->i_orphaned_slot));
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
+	if (ocfs2_dinode_has_bad_dio_orphaned_slot(sb, di)) {
+		mlog(ML_ERROR,
+		     "Filecheck: invalid dinode #%llu: DIO orphaned slot %u\n",
+		     (unsigned long long)bh->b_blocknr,
+		     le16_to_cpu(di->i_dio_orphaned_slot));
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
 	if (!ocfs2_valid_inode_mode(le16_to_cpu(di->i_mode))) {
 		mlog(ML_ERROR,
 		     "Filecheck: invalid dinode #%llu: mode 0%o has unknown file type\n",
-- 
2.39.3


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

* [PATCH 26/30] ocfs2: check inline data dinodes in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (24 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 25/30] ocfs2: check dinode orphaned slots " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 27/30] ocfs2: check fast symlink " Joseph Qi
                   ` (4 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Wire the inline-data guard and its three sub-checks into the filecheck
validator: allocated clusters on an inline inode, an id_count beyond the
space available in the inode block, and an i_size beyond id_count.

The size-without-clusters check now needs an explicit goto bail.  It
previously fell through to the bail label because it was the last check;
with checks following it, a later failure would overwrite its INVALIDINO
code and report the wrong reason.

The id_count maximum stays a signed int in the message.
ocfs2_max_inline_data_with_xattr() subtracts the unvalidated
i_xattr_inline_size and can go negative on small block sizes.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 34 ++++++++++++++++++++++++++++++++++
 1 file changed, 34 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index db8cdf4d8627..06b190b00590 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2094,6 +2094,40 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 			     (unsigned long long)bh->b_blocknr,
 			     (unsigned long long)le64_to_cpu(di->i_size));
 		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
+	if (ocfs2_dinode_has_inline_data(di)) {
+		struct ocfs2_inline_data *data = &di->id2.i_data;
+
+		if (ocfs2_dinode_has_inline_clusters(di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: %u clusters\n",
+			     (unsigned long long)bh->b_blocknr,
+			     le32_to_cpu(di->i_clusters));
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+
+		if (ocfs2_dinode_has_oversized_inline_data(sb, di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: inline data id_count %u exceeds max %d\n",
+			     (unsigned long long)bh->b_blocknr,
+			     le16_to_cpu(data->id_count),
+			     ocfs2_max_inline_data_with_xattr(sb, di));
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+
+		if (ocfs2_dinode_has_inline_size_over_count(di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: inline data i_size %llu exceeds id_count %u\n",
+			     (unsigned long long)bh->b_blocknr,
+			     (unsigned long long)le64_to_cpu(di->i_size),
+			     le16_to_cpu(data->id_count));
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
 	}
 
 bail:
-- 
2.39.3


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

* [PATCH 27/30] ocfs2: check fast symlink dinodes in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (25 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 26/30] ocfs2: check inline data dinodes " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 28/30] ocfs2: check chain list " Joseph Qi
                   ` (3 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Wire the fast symlink guard and its two sub-checks into the filecheck
validator: an i_size beyond the space available for id2.i_symlink, and a
target that is not NUL-terminated at i_size.

ocfs2_dinode_has_unterminated_fast_symlink() bounds its own strnlen()
against ocfs2_fast_symlink_chars(), so this caller does not have to rely
on the size check having run first to stay inside the inode block.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 24 ++++++++++++++++++++++++
 1 file changed, 24 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 06b190b00590..81f1f511d448 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2130,6 +2130,30 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		}
 	}
 
+	if (ocfs2_dinode_is_fast_symlink(di)) {
+		int max_inline = ocfs2_fast_symlink_chars(sb);
+		u64 i_size = le64_to_cpu(di->i_size);
+
+		if (ocfs2_dinode_has_oversized_fast_symlink(sb, di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: fast symlink i_size %llu exceeds max %d\n",
+			     (unsigned long long)bh->b_blocknr,
+			     (unsigned long long)i_size,
+			     max_inline - 1);
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+
+		if (ocfs2_dinode_has_unterminated_fast_symlink(sb, di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: fast symlink is not NUL-terminated at i_size %llu\n",
+			     (unsigned long long)bh->b_blocknr,
+			     (unsigned long long)i_size);
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+	}
+
 bail:
 	return rc;
 }
-- 
2.39.3


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

* [PATCH 28/30] ocfs2: check chain list dinodes in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (26 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 27/30] ocfs2: check fast symlink " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 29/30] ocfs2: check dinode refcount location " Joseph Qi
                   ` (2 subsequent siblings)
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Wire the chain-list guard and its three sub-checks into the filecheck
validator: cl_count against ocfs2_chain_recs_per_inode(),
cl_next_free_rec against cl_count, and cl_bpc against the cluster to
block size ratio.

The bits-per-cluster predicate keeps the two bitmap_blkno conditions, so
the global bitmap inode itself remains exempt.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 81f1f511d448..e0fc68ff997a 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2154,6 +2154,37 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		}
 	}
 
+	if (ocfs2_dinode_has_chain_list(di)) {
+		struct ocfs2_chain_list *cl = &di->id2.i_chain;
+
+		if (ocfs2_dinode_has_bad_chain_count(sb, di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: chain list count %u\n",
+			     (unsigned long long)bh->b_blocknr,
+			     le16_to_cpu(cl->cl_count));
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+
+		if (ocfs2_dinode_has_bad_chain_next_free(di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: chain list index %u\n",
+			     (unsigned long long)bh->b_blocknr,
+			     le16_to_cpu(cl->cl_next_free_rec));
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+
+		if (ocfs2_dinode_has_bad_chain_bpc(sb, di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: bits per cluster %u\n",
+			     (unsigned long long)bh->b_blocknr,
+			     le16_to_cpu(cl->cl_bpc));
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+	}
+
 bail:
 	return rc;
 }
-- 
2.39.3


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

* [PATCH 29/30] ocfs2: check dinode refcount location in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (27 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 28/30] ocfs2: check chain list " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05  9:49 ` [PATCH 30/30] ocfs2: check extent list dinodes " Joseph Qi
  2026-11-05 11:55 ` [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Wire the OCFS2_HAS_REFCOUNT_FL without i_refcount_loc predicate into the
filecheck validator.  A reflink-capable inode with no refcount tree
location cannot be reflinked or have its refcount tree removed cleanly.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 8 ++++++++
 1 file changed, 8 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index e0fc68ff997a..0f7391c9f30b 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2185,6 +2185,14 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		}
 	}
 
+	if (ocfs2_dinode_has_refcount_without_loc(di)) {
+		mlog(ML_ERROR,
+		     "Filecheck: inode #%llu has refcount flag but no i_refcount_loc\n",
+		     (unsigned long long)bh->b_blocknr);
+		rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+		goto bail;
+	}
+
 bail:
 	return rc;
 }
-- 
2.39.3


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

* [PATCH 30/30] ocfs2: check extent list dinodes in online filecheck
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (28 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 29/30] ocfs2: check dinode refcount location " Joseph Qi
@ 2026-11-05  9:49 ` Joseph Qi
  2026-11-05 11:55 ` [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05  9:49 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel, Joseph Qi

Wire the extent list guard and its three sub-checks into the filecheck
validator: a zero l_count, an l_count beyond ocfs2_extent_recs_per_inode(),
and an l_next_free_rec beyond l_count.

This brings the filecheck validator to parity with
ocfs2_validate_inode_block() for every dinode check except the inline
xattr one.  ocfs2_validate_inode_xattr() calls ocfs2_error() through its
helpers in xattr.c, so sharing it would mean threading a reporting flag
down into that file; it is deliberately left out.

The l_next_free_rec predicate is already used by the repair path, which
fixes that field, so a repair of it now revalidates cleanly.

Signed-off-by: Joseph Qi <joseph.qi@linux.alibaba.com>
---
 fs/ocfs2/inode.c | 31 +++++++++++++++++++++++++++++++
 1 file changed, 31 insertions(+)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 0f7391c9f30b..97bc75db71aa 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -2193,6 +2193,37 @@ static int ocfs2_filecheck_validate_inode_block(struct super_block *sb,
 		goto bail;
 	}
 
+	if (ocfs2_dinode_has_extents(di)) {
+		struct ocfs2_extent_list *el = &di->id2.i_list;
+		u16 count = le16_to_cpu(el->l_count);
+		u16 next_free = le16_to_cpu(el->l_next_free_rec);
+
+		if (ocfs2_dinode_has_zero_extent_count(di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: extent list l_count is zero\n",
+			     (unsigned long long)bh->b_blocknr);
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+
+		if (ocfs2_dinode_has_oversized_extent_count(sb, di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: extent list l_count %u exceeds max %u\n",
+			     (unsigned long long)bh->b_blocknr, count,
+			     ocfs2_extent_recs_per_inode(sb));
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+
+		if (ocfs2_dinode_has_bad_extent_next_free(di)) {
+			mlog(ML_ERROR,
+			     "Filecheck: invalid dinode #%llu: extent list l_next_free_rec %u exceeds l_count %u\n",
+			     (unsigned long long)bh->b_blocknr, next_free, count);
+			rc = -OCFS2_FILECHECK_ERR_INVALIDINO;
+			goto bail;
+		}
+	}
+
 bail:
 	return rc;
 }
-- 
2.39.3


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

* Re: [PATCH 00/30] ocfs2: split dinode validation into shared predicates
  2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
                   ` (29 preceding siblings ...)
  2026-11-05  9:49 ` [PATCH 30/30] ocfs2: check extent list dinodes " Joseph Qi
@ 2026-11-05 11:55 ` Joseph Qi
  30 siblings, 0 replies; 32+ messages in thread
From: Joseph Qi @ 2026-11-05 11:55 UTC (permalink / raw)
  To: Andrew Morton, Heming Zhao
  Cc: Mark Fasheh, Joel Becker, ocfs2-devel, linux-kernel

Sashiko has review comments on PATCH 05, 21, 22, 24 and 30.
Will be addressed on v2.

Thanks,
Joseph

On 11/5/26 5:48 PM, Joseph Qi wrote:
> ocfs2_validate_inode_block() is a flat sequence of about twenty open-coded
> on-disk dinode checks.  ocfs2_filecheck_validate_inode_block() duplicates
> eight of them, but reports differently: plain mlog() plus an
> OCFS2_FILECHECK_ERR_* code instead of ocfs2_error(), because filecheck is
> a diagnostic interface an administrator points at a suspect inode and it
> must not escalate into ocfs2_handle_error(), which can force the
> filesystem read-only or panic.
> 
> The duplicated conditions had already drifted apart -- the two
> OCFS2_VALID_FL tests were spelled differently -- and filecheck silently
> skipped the rest.
> 
> This series splits the conditions out and then brings filecheck up to
> parity.
> 
> Patches 1-20: pure refactor.  Each condition becomes a static bool
> ocfs2_dinode_has_*() predicate returning true on bad, following the four
> helpers already in inode.c.  Return codes and log messages stay at the
> call sites, so the two reporting paths remain distinct and no
> ocfs2_error() call site moves.  One check per commit.  No functional
> change intended.
> 
> Patch 21: a separable fix.  ocfs2_filecheck_repair_inode_block() proceeds
> when validation fails but then returns 0 unconditionally, after repairing
> only i_blkno, fs_generation and l_next_free_rec.  An inode failing any
> other check is reported to userspace as SUCCESS while still corrupt.
> This is already true today for the mode, i_rdev and size-without-clusters
> checks.  Revalidate after the repairs and return the residual code.  Drop
> this patch if you would rather keep the filecheck series purely additive,
> but it then widens the set of masked failures considerably.
> 
> Patches 22-30: wire the remaining predicates into filecheck.  All reuse
> OCFS2_FILECHECK_ERR_INVALIDINO, so no new error code is introduced and the
> ocfs2_filecheck_errs[] table with its userspace-visible names is
> unchanged.
> 
> Points worth a close look:
> 
>  - The fast symlink NUL-termination predicate bounds its own strnlen()
>    against ocfs2_fast_symlink_chars().  The open-coded version was only
>    safe because the preceding i_size check had already bailed; i_size
>    comes from disk, so an unbounded strnlen() over i_size + 1 bytes can
>    run past the inode block.  A helper should not inherit that ordering
>    dependency implicitly, especially once filecheck calls it too.
> 
>  - The inline id_count maximum stays a signed int.
>    ocfs2_max_inline_data_with_xattr() subtracts the unvalidated
>    i_xattr_inline_size and goes negative on small block sizes.  Caching it
>    unsigned would turn a negative maximum into a huge bound and silently
>    accept the oversized id_count.
> 
>  - ocfs2_dinode_is_fast_symlink() is deliberately not shared with the
>    similar test inside ocfs2_dinode_has_extents(), which additionally
>    requires a non-zero i_size.  A zero-length, zero-cluster symlink
>    currently falls through to the extent list checks.
> 
>  - The suballoc range predicate keeps its != OCFS2_INVALID_SLOT
>    exclusion.  OCFS2_INVALID_SLOT is 65535, so without it every system
>    inode mkfs.ocfs2 creates from the global allocator would be rejected.
> 
>  - Look-alike tests in suballoc.c are left alone:
>    ocfs2_validate_group_descriptor() deliberately permits
>    OCFS2_INVALID_SLOT for all inodes, and ocfs2_claim_suballoc_bits() also
>    rejects cl_next_free_rec == 0.  Neither is equivalent to the new
>    predicates.
> 
> The inline xattr check is the one thing filecheck still does not share.
> ocfs2_validate_inode_xattr() calls ocfs2_error() through its helpers in
> xattr.c, so reusing it would mean threading a reporting flag down into
> that file.  Left for a follow-up.
> 
> Testing, on top of linux-next 20261103:
> 
> Patches 1-20 were checked to be behaviour preserving.  All 27 log
> messages, all return codes and all ocfs2_error()/mlog() call sites in
> ocfs2_validate_inode_block() are unchanged, each new predicate body was
> compared against the condition it replaced, and the disassembly of
> ocfs2_filecheck_validate_inode_block() is instruction identical apart
> from __LINE__ constants.  The normal validator's disassembly differs by
> more than that -- basic blocks are rescheduled and registers reallocated
> -- so equivalence there rests on the source level comparison rather than
> on an instruction by instruction proof.  The deliberate rewrites are the
> ocfs2_read_links_count() reuse in patch 1, the OCFS2_VALID_FL spelling
> unification in patch 3, flattening the mutually exclusive suballoc
> if/else-if into two tests in patch 5, and the redundant self-bounding
> guard added in patch 13.
> 
> In a QEMU guest running this series, on 4K blocks with inline-data,
> refcount, sparse, usrquota, grpquota, xattr and indexed-dirs enabled:
> mount and I/O over seventeen inode types plus all the system inodes
> produced no false positive, and filecheck reported SUCCESS for every one
> of them.  A separate 512-byte block volume with inline-data, xattr,
> sparse and refcount was checked the same way over five inode types, also
> with no false positive.
> 
> The corruption tests used a third volume formatted with nometaecc, so
> that a checksum failure would not mask the field being tested.  Seven
> deliberately corrupted dinodes -- zero and oversized extent l_count,
> oversized inline id_count, oversized fast symlink i_size, refcount flag
> without i_refcount_loc, oversized suballoc bit and oversized
> l_next_free_rec -- were each detected with the expected message, with no
> ocfs2_error() escalation and the filesystem still read-write afterwards.
> fix was then run on two of them: the zero l_count case, which repair
> cannot fix, returned INVALIDINO, and the l_next_free_rec case, which it
> does fix, returned SUCCESS.
> 
> Forcing i_xattr_inline_size up on the 512-byte block volume drove the
> inline maximum negative and produced "inline data id_count 312 exceeds
> max -88", confirming the signed comparison survived the extraction.
> 
> The ocfs2 testsuite and xfstests were not run.
> 
> Joseph Qi (30):
>   ocfs2: abstract the zero nlink or mode dinode check
>   ocfs2: abstract the dinode i_blkno mismatch check
>   ocfs2: abstract the dinode OCFS2_VALID_FL check
>   ocfs2: abstract the dinode fs_generation mismatch check
>   ocfs2: abstract the dinode suballoc slot checks
>   ocfs2: abstract the dinode suballoc bit check
>   ocfs2: abstract the dinode orphaned slot check
>   ocfs2: abstract the dinode DIO orphaned slot check
>   ocfs2: abstract the inline data dinode guard and cluster check
>   ocfs2: abstract the inline data id_count bound check
>   ocfs2: abstract the inline data i_size bound check
>   ocfs2: abstract the fast symlink dinode guard and size check
>   ocfs2: abstract the fast symlink NUL-termination check
>   ocfs2: abstract the chain list dinode guard and count check
>   ocfs2: abstract the chain list next free record check
>   ocfs2: abstract the chain list bits per cluster check
>   ocfs2: abstract the dinode refcount location check
>   ocfs2: abstract the zero extent list count check
>   ocfs2: abstract the oversized extent list count check
>   ocfs2: abstract the extent list next free record check
>   ocfs2: report unrepairable corruption from online filecheck fix
>   ocfs2: check dinode nlink and mode in online filecheck
>   ocfs2: check dinode suballoc slot in online filecheck
>   ocfs2: check dinode suballoc bit in online filecheck
>   ocfs2: check dinode orphaned slots in online filecheck
>   ocfs2: check inline data dinodes in online filecheck
>   ocfs2: check fast symlink dinodes in online filecheck
>   ocfs2: check chain list dinodes in online filecheck
>   ocfs2: check dinode refcount location in online filecheck
>   ocfs2: check extent list dinodes in online filecheck
> 
>  fs/ocfs2/inode.c | 456 +++++++++++++++++++++++++++++++++++++++++------
>  1 file changed, 399 insertions(+), 57 deletions(-)
> 


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

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

Thread overview: 32+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-05  9:48 [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi
2026-11-05  9:48 ` [PATCH 01/30] ocfs2: abstract the zero nlink or mode dinode check Joseph Qi
2026-11-05  9:48 ` [PATCH 02/30] ocfs2: abstract the dinode i_blkno mismatch check Joseph Qi
2026-11-05  9:48 ` [PATCH 03/30] ocfs2: abstract the dinode OCFS2_VALID_FL check Joseph Qi
2026-11-05  9:49 ` [PATCH 04/30] ocfs2: abstract the dinode fs_generation mismatch check Joseph Qi
2026-11-05  9:49 ` [PATCH 05/30] ocfs2: abstract the dinode suballoc slot checks Joseph Qi
2026-11-05  9:49 ` [PATCH 06/30] ocfs2: abstract the dinode suballoc bit check Joseph Qi
2026-11-05  9:49 ` [PATCH 07/30] ocfs2: abstract the dinode orphaned slot check Joseph Qi
2026-11-05  9:49 ` [PATCH 08/30] ocfs2: abstract the dinode DIO " Joseph Qi
2026-11-05  9:49 ` [PATCH 09/30] ocfs2: abstract the inline data dinode guard and cluster check Joseph Qi
2026-11-05  9:49 ` [PATCH 10/30] ocfs2: abstract the inline data id_count bound check Joseph Qi
2026-11-05  9:49 ` [PATCH 11/30] ocfs2: abstract the inline data i_size " Joseph Qi
2026-11-05  9:49 ` [PATCH 12/30] ocfs2: abstract the fast symlink dinode guard and size check Joseph Qi
2026-11-05  9:49 ` [PATCH 13/30] ocfs2: abstract the fast symlink NUL-termination check Joseph Qi
2026-11-05  9:49 ` [PATCH 14/30] ocfs2: abstract the chain list dinode guard and count check Joseph Qi
2026-11-05  9:49 ` [PATCH 15/30] ocfs2: abstract the chain list next free record check Joseph Qi
2026-11-05  9:49 ` [PATCH 16/30] ocfs2: abstract the chain list bits per cluster check Joseph Qi
2026-11-05  9:49 ` [PATCH 17/30] ocfs2: abstract the dinode refcount location check Joseph Qi
2026-11-05  9:49 ` [PATCH 18/30] ocfs2: abstract the zero extent list count check Joseph Qi
2026-11-05  9:49 ` [PATCH 19/30] ocfs2: abstract the oversized " Joseph Qi
2026-11-05  9:49 ` [PATCH 20/30] ocfs2: abstract the extent list next free record check Joseph Qi
2026-11-05  9:49 ` [PATCH 21/30] ocfs2: report unrepairable corruption from online filecheck fix Joseph Qi
2026-11-05  9:49 ` [PATCH 22/30] ocfs2: check dinode nlink and mode in online filecheck Joseph Qi
2026-11-05  9:49 ` [PATCH 23/30] ocfs2: check dinode suballoc slot " Joseph Qi
2026-11-05  9:49 ` [PATCH 24/30] ocfs2: check dinode suballoc bit " Joseph Qi
2026-11-05  9:49 ` [PATCH 25/30] ocfs2: check dinode orphaned slots " Joseph Qi
2026-11-05  9:49 ` [PATCH 26/30] ocfs2: check inline data dinodes " Joseph Qi
2026-11-05  9:49 ` [PATCH 27/30] ocfs2: check fast symlink " Joseph Qi
2026-11-05  9:49 ` [PATCH 28/30] ocfs2: check chain list " Joseph Qi
2026-11-05  9:49 ` [PATCH 29/30] ocfs2: check dinode refcount location " Joseph Qi
2026-11-05  9:49 ` [PATCH 30/30] ocfs2: check extent list dinodes " Joseph Qi
2026-11-05 11:55 ` [PATCH 00/30] ocfs2: split dinode validation into shared predicates Joseph Qi

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®