[PATCH v2 01/31] ocfs2: exempt orphaned dinodes from the zero nlink check

From: Joseph Qi

Date: Fri Nov 06 2026 - 06:16:44 EST


An open-unlinked inode legitimately has a zero link count on disk.
ocfs2_unlink() drops the last link and writes it out with
ocfs2_set_links_count(), ocfs2_orphan_add() then sets OCFS2_ORPHANED_FL,
and OCFS2_VALID_FL is only cleared much later by ocfs2_remove_inode().
The zero nlink check added by the commit below rejects such an inode as
-EFSCORRUPTED even though it is perfectly valid.

That commit describes its own change as a check for "(i_mode == 0,
i_nlink == 0, non-orphan)", but the code it added has no orphan exemption
and ORs the two conditions rather than ANDing them, so a zero link count
alone is enough to fail even when i_mode is valid. Add the exemption the
changelog describes.

Orphan recovery hits this on every pass. OCFS2_FI_FLAG_ORPHAN_RECOVERY
clears can_lock in ocfs2_read_locked_inode(), so the block is read with
ocfs2_read_blocks_sync() and then validated by the explicit
ocfs2_validate_inode_block() call in that branch. The read helper having
no validate callback of its own does not mean the path skips validation.
ocfs2_orphan_filldir() then ignores the resulting ocfs2_iget() failure
("Skip bad inodes so that recovery can continue"), so the orphan is never
wiped: its space is leaked permanently and the "Corrupt state" message is
logged again on every mount.

The same applies to an orphaned dinode read through
ocfs2_read_inode_block(), for example from ocfs2_delete_inode() once the
buffer has been evicted.

Note that a zero i_mode is already rejected by the later
ocfs2_valid_inode_mode() check, since fs_umode_to_ftype(0) is FT_UNKNOWN,
so the nlink arm was not what protected against the zombie inode the
original commit was fixing.

DIO orphans need no exemption: ocfs2_add_inode_to_orphan() is the only
caller that passes dio=true to ocfs2_orphan_add(), and it never drops a
link, so those inodes keep a positive link count.

Fixes: 7efb45f9685f ("ocfs2: invalidate inode if i_mode is zero after block read")
Cc: <stable@xxxxxxxxxxxxxxx>
Signed-off-by: Joseph Qi <joseph.qi@xxxxxxxxxxxxxxxxx>
---
fs/ocfs2/inode.c | 10 +++++++++-
1 file changed, 9 insertions(+), 1 deletion(-)

diff --git a/fs/ocfs2/inode.c b/fs/ocfs2/inode.c
index 7005e59519b2..371857673712 100644
--- a/fs/ocfs2/inode.c
+++ b/fs/ocfs2/inode.c
@@ -1482,7 +1482,15 @@ int ocfs2_validate_inode_block(struct super_block *sb,
goto bail;
}

- if ((!di->i_links_count && !di->i_links_count_hi) || !di->i_mode) {
+ /*
+ * An open-unlinked inode legitimately has a zero link count on disk:
+ * ocfs2_unlink() drops the last link and ocfs2_orphan_add() sets
+ * OCFS2_ORPHANED_FL, while OCFS2_VALID_FL is only cleared later by
+ * ocfs2_remove_inode(). DIO orphans keep their link count, so they
+ * need no exemption here.
+ */
+ if (!(le32_to_cpu(di->i_flags) & OCFS2_ORPHANED_FL) &&
+ ((!di->i_links_count && !di->i_links_count_hi) || !di->i_mode)) {
mlog(ML_ERROR, "Invalid dinode #%llu: "
"Corrupt state (nlink = %u or mode = %u) detected!\n",
(unsigned long long)bh->b_blocknr,
--
2.39.3