public inbox for linux-ext4@vger.kernel.org
 help / color / mirror / Atom feed
* [PATCH v2 0/4] show orphan file inode detail info
@ 2026-04-15 10:55 Ye Bin
  2026-04-15 10:55 ` [PATCH v2 1/4] ext4: register 'orphan_list' procfs Ye Bin
                   ` (4 more replies)
  0 siblings, 5 replies; 7+ messages in thread
From: Ye Bin @ 2026-04-15 10:55 UTC (permalink / raw)
  To: tytso, adilger.kernel, linux-ext4; +Cc: jack

From: Ye Bin <yebin10@huawei.com>

Diffs v2 vs v1:
(1) Fix sashiko review issues:
https://sashiko.dev/#/patchset/20260403082507.1882703-1-yebin%40huaweicloud.com
(2) Change "orphan_list" file mode from 0444 to 0400;
(3) The display format of the "orphan_list" file is modified according
    to Andreas' suggestions.
Fault injection tests have been conducted to address the issues raised
in the sashik review. There is no UAF issue in the ext4_seq_orphan_release()
function. The reason for this has already been explained in the code comments.
In addition to the fault injection tests, we also performed a stress test by
observing the /proc/fs/ext4/XX/orphan_list and the concurrent processes of
adding and removing orphan nodes, and no issues were found so far.


In actual production environments, the issue of inconsistency between
df and du is frequently encountered. In many cases, the cause of the
problem can be identified through the use of lsof. However, when
overlayfs is combined with project quota configuration, the issue becomes
more complex and troublesome to diagnose. First, to determine the project
ID, one needs to obtain orphaned nodes using `fsck.ext4 -fn /dev/xx`, and
then retrieve file information through `debugfs`. However, the file names
cannot always be obtained, and it is often unclear which files they are.
To identify which files these are, one would need to use crash for online
debugging or use kprobe to gather information incrementally. However, some
customers in production environments do not agree to upload any tools, and
online debugging might impact the business. There are also scenarios where
files are opened in kernel mode, which do not generate file descriptors(fds),
making it impossible to identify which files were deleted but still have
references through lsof. This patchset adds a procfs interface to query
information about orphaned nodes, which can assist in the analysis and
localization of such issues.

Ye Bin (4):
  ext4: register 'orphan_list' procfs
  ext4: skip cursor node in ext4_orphan_del()
  ext4: show inode orphan list detail information
  ext4: show orphan file inode detail info

 fs/ext4/ext4.h   |   1 +
 fs/ext4/orphan.c | 326 ++++++++++++++++++++++++++++++++++++++++++++++-
 fs/ext4/sysfs.c  |   2 +
 3 files changed, 328 insertions(+), 1 deletion(-)

-- 
2.34.1


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

* [PATCH v2 1/4] ext4: register 'orphan_list' procfs
  2026-04-15 10:55 [PATCH v2 0/4] show orphan file inode detail info Ye Bin
@ 2026-04-15 10:55 ` Ye Bin
  2026-04-15 10:55 ` [PATCH v2 2/4] ext4: skip cursor node in ext4_orphan_del() Ye Bin
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Ye Bin @ 2026-04-15 10:55 UTC (permalink / raw)
  To: tytso, adilger.kernel, linux-ext4; +Cc: jack

From: Ye Bin <yebin10@huawei.com>

This patch register '/proc/fs/ext4/XXX/orphan_list' procfs for show inode
orphan list about EXT4 file system.
In actual production environments, there may be inconsistencies in df/du,
sometimes due to kernel occupation, making it difficult to find such files,
and it is also difficult to operate in the current network environment. So
add "orphan_list" procfs to quickly query files that have been deleted but
are occupied.

Signed-off-by: Ye Bin <yebin10@huawei.com>
---
 fs/ext4/ext4.h   |  1 +
 fs/ext4/orphan.c | 77 ++++++++++++++++++++++++++++++++++++++++++++++++
 fs/ext4/sysfs.c  |  2 ++
 3 files changed, 80 insertions(+)

diff --git a/fs/ext4/ext4.h b/fs/ext4/ext4.h
index 0cf68f85dfd1..ccb0fd1e63e7 100644
--- a/fs/ext4/ext4.h
+++ b/fs/ext4/ext4.h
@@ -3875,6 +3875,7 @@ extern void ext4_stop_mmpd(struct ext4_sb_info *sbi);
 extern const struct fsverity_operations ext4_verityops;
 
 /* orphan.c */
+extern const struct proc_ops ext4_orphan_proc_ops;
 extern int ext4_orphan_add(handle_t *, struct inode *);
 extern int ext4_orphan_del(handle_t *, struct inode *);
 extern void ext4_orphan_cleanup(struct super_block *sb,
diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c
index 64ea47624233..f7e7f77e021e 100644
--- a/fs/ext4/orphan.c
+++ b/fs/ext4/orphan.c
@@ -4,6 +4,8 @@
 #include <linux/fs.h>
 #include <linux/quotaops.h>
 #include <linux/buffer_head.h>
+#include <linux/proc_fs.h>
+#include <linux/seq_file.h>
 
 #include "ext4.h"
 #include "ext4_jbd2.h"
@@ -657,3 +659,78 @@ int ext4_orphan_file_empty(struct super_block *sb)
 			return 0;
 	return 1;
 }
+
+struct ext4_proc_orphan {
+	struct ext4_inode_info cursor;
+};
+
+static void *ext4_orphan_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	return NULL;
+}
+
+static void *ext4_orphan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
+{
+	return NULL;
+}
+
+static int ext4_orphan_seq_show(struct seq_file *seq, void *v)
+{
+	return 0;
+}
+
+static void ext4_orphan_seq_stop(struct seq_file *seq, void *v)
+{
+}
+
+const struct seq_operations ext4_orphan_seq_ops = {
+	.start  = ext4_orphan_seq_start,
+	.next   = ext4_orphan_seq_next,
+	.stop   = ext4_orphan_seq_stop,
+	.show   = ext4_orphan_seq_show,
+};
+
+static int ext4_seq_orphan_open(struct inode *inode, struct file *file)
+{
+	int rc;
+	struct seq_file *m;
+	struct ext4_proc_orphan *private;
+
+	rc = seq_open_private(file, &ext4_orphan_seq_ops,
+			      sizeof(struct ext4_proc_orphan));
+	if (!rc) {
+		m = file->private_data;
+		private = m->private;
+		INIT_LIST_HEAD(&private->cursor.i_orphan);
+		private->cursor.vfs_inode.i_ino = 0;
+	}
+
+	return rc;
+}
+
+static int ext4_seq_orphan_release(struct inode *inode, struct file *file)
+{
+	struct seq_file *seq = file->private_data;
+	struct ext4_proc_orphan *s = seq->private;
+	struct ext4_sb_info *sbi = EXT4_SB(pde_data(inode));
+
+	/*
+	 * The function close_pdeo() is called when deleting the procfs
+	 * in ext4_unregister_sysfs(), and this function is used to remove
+	 * the entry from the 'pde->pde_openers' list. Therefore, when the
+	 * file is closed, proc_reg_release() will not call close_pdeo()
+	 * again because it cannot find the node on the 'pde->pde_openers'
+	 * list. This prevents the UAF issue from occurring.
+	 */
+	mutex_lock(&sbi->s_orphan_lock);
+	list_del(&s->cursor.i_orphan);
+	mutex_unlock(&sbi->s_orphan_lock);
+
+	return seq_release_private(inode, file);
+}
+
+const struct proc_ops ext4_orphan_proc_ops = {
+	.proc_open      = ext4_seq_orphan_open,
+	.proc_read      = seq_read,
+	.proc_release   = ext4_seq_orphan_release,
+};
diff --git a/fs/ext4/sysfs.c b/fs/ext4/sysfs.c
index 923b375e017f..b40a934e30c9 100644
--- a/fs/ext4/sysfs.c
+++ b/fs/ext4/sysfs.c
@@ -639,6 +639,8 @@ int ext4_register_sysfs(struct super_block *sb)
 				ext4_seq_mb_stats_show, sb);
 		proc_create_seq_data("mb_structs_summary", 0444, sbi->s_proc,
 				&ext4_mb_seq_structs_summary_ops, sb);
+		proc_create_data("orphan_list", 0400, sbi->s_proc,
+				 &ext4_orphan_proc_ops, sb);
 	}
 	return 0;
 }
-- 
2.34.1


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

* [PATCH v2 2/4] ext4: skip cursor node in ext4_orphan_del()
  2026-04-15 10:55 [PATCH v2 0/4] show orphan file inode detail info Ye Bin
  2026-04-15 10:55 ` [PATCH v2 1/4] ext4: register 'orphan_list' procfs Ye Bin
@ 2026-04-15 10:55 ` Ye Bin
  2026-04-15 23:56   ` Darrick J. Wong
  2026-04-15 10:55 ` [PATCH v2 3/4] ext4: show inode orphan list detail information Ye Bin
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 7+ messages in thread
From: Ye Bin @ 2026-04-15 10:55 UTC (permalink / raw)
  To: tytso, adilger.kernel, linux-ext4; +Cc: jack

From: Ye Bin <yebin10@huawei.com>

This patch is prepared for displaying orphan_list information. Because
temporary nodes may be inserted when the orphan_list is traversed and
displayed, these temporary nodes need to be skipped.

Signed-off-by: Ye Bin <yebin10@huawei.com>
---
 fs/ext4/orphan.c | 20 +++++++++++++++++++-
 1 file changed, 19 insertions(+), 1 deletion(-)

diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c
index f7e7f77e021e..a6bffe67ef75 100644
--- a/fs/ext4/orphan.c
+++ b/fs/ext4/orphan.c
@@ -220,6 +220,23 @@ static int ext4_orphan_file_del(handle_t *handle, struct inode *inode)
 	return ret;
 }
 
+static inline bool ext4_is_cursor(struct inode *inode)
+{
+	return (inode->i_ino == 0);
+}
+
+static inline struct list_head *ext4_orphan_prev_node(
+					struct ext4_inode_info *pos,
+					struct list_head *head)
+{
+	list_for_each_entry_continue_reverse(pos, head, i_orphan) {
+		if (likely(!ext4_is_cursor(&pos->vfs_inode)))
+			return &pos->i_orphan;
+	}
+
+	return head;
+}
+
 /*
  * ext4_orphan_del() removes an unlinked or truncated inode from the list
  * of such inodes stored on disk, because it is finally being cleaned up.
@@ -253,7 +270,8 @@ int ext4_orphan_del(handle_t *handle, struct inode *inode)
 	mutex_lock(&sbi->s_orphan_lock);
 	ext4_debug("remove inode %llu from orphan list\n", inode->i_ino);
 
-	prev = ei->i_orphan.prev;
+	prev = ext4_orphan_prev_node(ei, &sbi->s_orphan);
+
 	list_del_init(&ei->i_orphan);
 
 	/* If we're on an error path, we may not have a valid
-- 
2.34.1


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

* [PATCH v2 3/4] ext4: show inode orphan list detail information
  2026-04-15 10:55 [PATCH v2 0/4] show orphan file inode detail info Ye Bin
  2026-04-15 10:55 ` [PATCH v2 1/4] ext4: register 'orphan_list' procfs Ye Bin
  2026-04-15 10:55 ` [PATCH v2 2/4] ext4: skip cursor node in ext4_orphan_del() Ye Bin
@ 2026-04-15 10:55 ` Ye Bin
  2026-04-15 10:55 ` [PATCH v2 4/4] ext4: show orphan file inode detail info Ye Bin
  2026-04-15 17:59 ` [PATCH v2 0/4] " Jan Kara
  4 siblings, 0 replies; 7+ messages in thread
From: Ye Bin @ 2026-04-15 10:55 UTC (permalink / raw)
  To: tytso, adilger.kernel, linux-ext4; +Cc: jack

From: Ye Bin <yebin10@huawei.com>

Some inodes added to the orphan list are due to truncation, while others
are due to deletion.Therefore, we printed the information of inode as
follows: inode number/i_nlink/i_size/i_blocks/projid/file path. By using
this information, it is possible to quickly identify files that have been
deleted but are still being referenced.

Signed-off-by: Ye Bin <yebin10@huawei.com>
---
 fs/ext4/orphan.c | 128 ++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 126 insertions(+), 2 deletions(-)

diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c
index a6bffe67ef75..4d6f8c9edaeb 100644
--- a/fs/ext4/orphan.c
+++ b/fs/ext4/orphan.c
@@ -682,23 +682,147 @@ struct ext4_proc_orphan {
 	struct ext4_inode_info cursor;
 };
 
-static void *ext4_orphan_seq_start(struct seq_file *seq, loff_t *pos)
+static struct inode *ext4_list_next(struct ext4_proc_orphan *s,
+				    struct list_head *head,
+				    struct list_head *p)
 {
+	list_for_each_continue(p, head) {
+		struct ext4_inode_info *ei;
+		struct inode *inode;
+
+		ei = list_entry(p, typeof(*ei), i_orphan);
+		inode = &ei->vfs_inode;
+
+		/*
+		 * It is safe to insert a cursor into the orphan list
+		 * because ext4_orphan_del() will skip cursor. When the
+		 * orphan list is processed in ext4_put_super(),
+		 * ext4_seq_orphan_release() must have already been called,
+		 * so the cursor must have already been removed from the
+		 * orphan list.Therefore, there will be no access to a
+		 * stale cursor.
+		 */
+		list_move(&s->cursor.i_orphan, &ei->i_orphan);
+
+		/*
+		 * Because the cursor has moved to the node after the
+		 * current node, the traversal cannot continue from the
+		 * current node. Instead, the traversal should continue
+		 * from the cursor.
+		 */
+		p = &s->cursor.i_orphan;
+
+		if (ext4_is_cursor(inode))
+			continue;
+
+		if (!igrab(inode))
+			continue;
+
+		return inode;
+	}
+
 	return NULL;
 }
 
+static void *ext4_orphan_seq_start(struct seq_file *seq, loff_t *pos)
+{
+	struct ext4_proc_orphan *s = seq->private;
+	struct super_block *sb = pde_data(file_inode(seq->file));
+	struct ext4_sb_info *sbi = EXT4_SB(sb);
+	struct list_head *prev;
+
+	mutex_lock(&sbi->s_orphan_lock);
+
+	if (!*pos) {
+		prev = &sbi->s_orphan;
+	} else {
+		prev = &s->cursor.i_orphan;
+		if (list_empty(prev))
+			return NULL;
+	}
+
+	return ext4_list_next(s, &sbi->s_orphan, prev);
+}
+
 static void *ext4_orphan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 {
-	return NULL;
+	struct super_block *sb = pde_data(file_inode(seq->file));
+	struct ext4_sb_info *sbi = EXT4_SB(sb);
+	struct ext4_proc_orphan *s = seq->private;
+	struct inode *inode = v;
+
+	++*pos;
+
+	/*
+	 * To prevent the deadlock caused by orphan node deletion when the
+	 * last inode reference count is released, the inode reference
+	 * count needs to be released in the unlocked state.
+	 */
+	mutex_unlock(&sbi->s_orphan_lock);
+	iput(inode);
+	mutex_lock(&sbi->s_orphan_lock);
+
+	return ext4_list_next(s, &sbi->s_orphan, &s->cursor.i_orphan);
+}
+
+static void ext4_show_filename(struct seq_file *seq, struct inode *inode)
+{
+	struct dentry *dentry;
+
+	dentry = d_find_alias(inode);
+	if (!dentry)
+		dentry = d_find_any_alias(inode);
+
+	if (dentry)
+		seq_dentry(seq, dentry, " \t\n\\");
+	else
+		seq_puts(seq, "unknown");
+
+	seq_puts(seq, "\"\n");
+
+	/*
+	 * Since igrab() has already been called in ext4_list_next(), the
+	 * inode will not be released here, so there will be no deadlock.
+	 */
+	dput(dentry);
 }
 
 static int ext4_orphan_seq_show(struct seq_file *seq, void *v)
 {
+	struct inode *inode = v;
+
+	/*
+	 * Print the original data without differentiating namespaces.
+	 */
+	seq_printf(seq, "ino: %llu, link: %u, size: %llu, blocks: %llu, proj: %u, path: \"",
+		   inode->i_ino, inode->i_nlink,
+		   i_size_read(inode), inode->i_blocks,
+		   __kprojid_val(EXT4_I(inode)->i_projid));
+
+	ext4_show_filename(seq, inode);
+
 	return 0;
 }
 
 static void ext4_orphan_seq_stop(struct seq_file *seq, void *v)
 {
+	struct super_block *sb = pde_data(file_inode(seq->file));
+	struct ext4_sb_info *sbi = EXT4_SB(sb);
+	struct ext4_proc_orphan *s = seq->private;
+	struct inode *inode = v;
+
+	/*
+	 * stop() is called when the cache is full, so the traversal
+	 * position needs to be moved back to the front of the current
+	 * inode.
+	 */
+	if (v)
+		list_move_tail(&s->cursor.i_orphan,
+			       &EXT4_I(inode)->i_orphan);
+
+	mutex_unlock(&sbi->s_orphan_lock);
+
+	iput(inode);
 }
 
 const struct seq_operations ext4_orphan_seq_ops = {
-- 
2.34.1


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

* [PATCH v2 4/4] ext4: show orphan file inode detail info
  2026-04-15 10:55 [PATCH v2 0/4] show orphan file inode detail info Ye Bin
                   ` (2 preceding siblings ...)
  2026-04-15 10:55 ` [PATCH v2 3/4] ext4: show inode orphan list detail information Ye Bin
@ 2026-04-15 10:55 ` Ye Bin
  2026-04-15 17:59 ` [PATCH v2 0/4] " Jan Kara
  4 siblings, 0 replies; 7+ messages in thread
From: Ye Bin @ 2026-04-15 10:55 UTC (permalink / raw)
  To: tytso, adilger.kernel, linux-ext4; +Cc: jack

From: Ye Bin <yebin10@huawei.com>

Support show inode information in orphan file.

Signed-off-by: Ye Bin <yebin10@huawei.com>
---
 fs/ext4/orphan.c | 179 ++++++++++++++++++++++++++++++++++++++---------
 1 file changed, 146 insertions(+), 33 deletions(-)

diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c
index 4d6f8c9edaeb..715d04e386d0 100644
--- a/fs/ext4/orphan.c
+++ b/fs/ext4/orphan.c
@@ -680,6 +680,11 @@ int ext4_orphan_file_empty(struct super_block *sb)
 
 struct ext4_proc_orphan {
 	struct ext4_inode_info cursor;
+	struct ext4_orphan_info *oi;
+	int inodes_per_ob;
+	int block_idx;
+	int offset;
+	bool orphan_file;
 };
 
 static struct inode *ext4_list_next(struct ext4_proc_orphan *s,
@@ -724,24 +729,94 @@ static struct inode *ext4_list_next(struct ext4_proc_orphan *s,
 	return NULL;
 }
 
+static struct inode *ext4_orphan_file_next(struct ext4_proc_orphan *s,
+					   struct super_block *sb)
+{
+	struct inode *inode = NULL;
+	struct ext4_orphan_info *oi = s->oi;
+
+	for (; s->block_idx < oi->of_blocks; s->block_idx++) {
+		int idx = s->block_idx;
+		struct ext4_orphan_block *binfo = &oi->of_binfo[idx];
+		__le32 *bdata = (__le32 *)(binfo->ob_bh->b_data);
+
+		if (atomic_read(&binfo->ob_free_entries) ==
+				s->inodes_per_ob) {
+			s->offset = 0;
+			continue;
+		}
+		for (; s->offset < s->inodes_per_ob; s->offset++) {
+			u64 ino = le32_to_cpu(bdata[s->offset]);
+
+			if (!ino)
+				continue;
+			/*
+			 * Orphan nodes in the running state are those
+			 * inodes that are still in use, so here we get
+			 * them from the cache if available.
+			 */
+			inode = ilookup(sb, ino);
+			if (!inode)
+				continue;
+
+			if (!ext4_test_inode_state(inode,
+						EXT4_STATE_ORPHAN_FILE)) {
+				iput(inode);
+				continue;
+			}
+
+			s->offset++;
+			if (s->offset == s->inodes_per_ob) {
+				s->offset = 0;
+				s->block_idx++;
+			}
+			return inode;
+		}
+
+		s->offset = 0;
+	}
+
+	return NULL;
+}
+
 static void *ext4_orphan_seq_start(struct seq_file *seq, loff_t *pos)
 {
 	struct ext4_proc_orphan *s = seq->private;
 	struct super_block *sb = pde_data(file_inode(seq->file));
 	struct ext4_sb_info *sbi = EXT4_SB(sb);
 	struct list_head *prev;
+	void *ret;
 
-	mutex_lock(&sbi->s_orphan_lock);
+	if (!s->orphan_file) {
+		mutex_lock(&sbi->s_orphan_lock);
+		if (!*pos)
+			prev = &sbi->s_orphan;
+		else
+			prev = &s->cursor.i_orphan;
 
-	if (!*pos) {
-		prev = &sbi->s_orphan;
-	} else {
-		prev = &s->cursor.i_orphan;
-		if (list_empty(prev))
+		/*
+		 * Here, the code checks whether the linked list is empty
+		 * because when the orphan_file feature is supported, the
+		 * cursor is removed from the linked list after the orphan
+		 * list is traversed. If the orphan_file feature is not
+		 * enabled, calling ext4_orphan_seq_start() again would
+		 * cause an infinite loop.
+		 */
+		if (!list_empty(prev)) {
+			ret = ext4_list_next(s, &sbi->s_orphan, prev);
+			if (ret)
+				return ret;
+		}
+
+		if (!s->oi)
 			return NULL;
+
+		list_del_init(&s->cursor.i_orphan);
+		mutex_unlock(&sbi->s_orphan_lock);
+		s->orphan_file = true;
 	}
 
-	return ext4_list_next(s, &sbi->s_orphan, prev);
+	return ext4_orphan_file_next(s, sb);
 }
 
 static void *ext4_orphan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
@@ -750,19 +825,36 @@ static void *ext4_orphan_seq_next(struct seq_file *seq, void *v, loff_t *pos)
 	struct ext4_sb_info *sbi = EXT4_SB(sb);
 	struct ext4_proc_orphan *s = seq->private;
 	struct inode *inode = v;
+	void *ret;
 
 	++*pos;
 
-	/*
-	 * To prevent the deadlock caused by orphan node deletion when the
-	 * last inode reference count is released, the inode reference
-	 * count needs to be released in the unlocked state.
-	 */
-	mutex_unlock(&sbi->s_orphan_lock);
-	iput(inode);
-	mutex_lock(&sbi->s_orphan_lock);
+	if (!s->orphan_file) {
+		/*
+		 * To prevent the deadlock caused by orphan node deletion
+		 * when the last inode reference count is released, the
+		 * inode reference count needs to be released in the
+		 * unlocked state.
+		 */
+		mutex_unlock(&sbi->s_orphan_lock);
+		iput(inode);
+		mutex_lock(&sbi->s_orphan_lock);
+
+		ret = ext4_list_next(s, &sbi->s_orphan,
+				     &s->cursor.i_orphan);
+		if (ret)
+			return ret;
+		if (!s->oi)
+			return NULL;
+		list_del_init(&s->cursor.i_orphan);
+		mutex_unlock(&sbi->s_orphan_lock);
+		s->orphan_file = true;
+	} else {
+		iput(inode);
+	}
+
 
-	return ext4_list_next(s, &sbi->s_orphan, &s->cursor.i_orphan);
+	return ext4_orphan_file_next(s, sb);
 }
 
 static void ext4_show_filename(struct seq_file *seq, struct inode *inode)
@@ -811,16 +903,28 @@ static void ext4_orphan_seq_stop(struct seq_file *seq, void *v)
 	struct ext4_proc_orphan *s = seq->private;
 	struct inode *inode = v;
 
-	/*
-	 * stop() is called when the cache is full, so the traversal
-	 * position needs to be moved back to the front of the current
-	 * inode.
-	 */
-	if (v)
-		list_move_tail(&s->cursor.i_orphan,
-			       &EXT4_I(inode)->i_orphan);
+	if (!s->orphan_file) {
+		/*
+		 * stop() is called when the cache is full, so the
+		 * traversal position needs to be moved back to the
+		 * front of the current inode. If stop() due to EOF
+		 * then remove cursor from orphan list.
+		 */
+		if (inode)
+			list_move_tail(&s->cursor.i_orphan,
+				       &EXT4_I(inode)->i_orphan);
+		else
+			list_del_init(&s->cursor.i_orphan);
 
-	mutex_unlock(&sbi->s_orphan_lock);
+		mutex_unlock(&sbi->s_orphan_lock);
+	} else if (inode) {
+		if (s->offset) {
+			s->offset--;
+		} else if (s->block_idx) {
+			s->block_idx--;
+			s->offset = s->inodes_per_ob - 1;
+		}
+	}
 
 	iput(inode);
 }
@@ -836,15 +940,21 @@ static int ext4_seq_orphan_open(struct inode *inode, struct file *file)
 {
 	int rc;
 	struct seq_file *m;
-	struct ext4_proc_orphan *private;
+	struct ext4_proc_orphan *s;
 
 	rc = seq_open_private(file, &ext4_orphan_seq_ops,
 			      sizeof(struct ext4_proc_orphan));
 	if (!rc) {
+		struct super_block *sb = pde_data(file_inode(file));
 		m = file->private_data;
-		private = m->private;
-		INIT_LIST_HEAD(&private->cursor.i_orphan);
-		private->cursor.vfs_inode.i_ino = 0;
+		s = m->private;
+		INIT_LIST_HEAD(&s->cursor.i_orphan);
+		s->cursor.vfs_inode.i_ino = 0;
+		s->orphan_file = 0;
+		if (ext4_has_feature_orphan_file(sb)) {
+			s->oi = &EXT4_SB(sb)->s_orphan_info;
+			s->inodes_per_ob = ext4_inodes_per_orphan_block(sb);
+		}
 	}
 
 	return rc;
@@ -862,11 +972,14 @@ static int ext4_seq_orphan_release(struct inode *inode, struct file *file)
 	 * the entry from the 'pde->pde_openers' list. Therefore, when the
 	 * file is closed, proc_reg_release() will not call close_pdeo()
 	 * again because it cannot find the node on the 'pde->pde_openers'
-	 * list. This prevents the UAF issue from occurring.
+	 * list. This prevents the UAF issue from occurring. Maybe, cursor
+	 * already removed in stop().
 	 */
-	mutex_lock(&sbi->s_orphan_lock);
-	list_del(&s->cursor.i_orphan);
-	mutex_unlock(&sbi->s_orphan_lock);
+	if (!s->orphan_file) {
+		mutex_lock(&sbi->s_orphan_lock);
+		list_del(&s->cursor.i_orphan);
+		mutex_unlock(&sbi->s_orphan_lock);
+	}
 
 	return seq_release_private(inode, file);
 }
-- 
2.34.1


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

* Re: [PATCH v2 0/4] show orphan file inode detail info
  2026-04-15 10:55 [PATCH v2 0/4] show orphan file inode detail info Ye Bin
                   ` (3 preceding siblings ...)
  2026-04-15 10:55 ` [PATCH v2 4/4] ext4: show orphan file inode detail info Ye Bin
@ 2026-04-15 17:59 ` Jan Kara
  4 siblings, 0 replies; 7+ messages in thread
From: Jan Kara @ 2026-04-15 17:59 UTC (permalink / raw)
  To: Ye Bin; +Cc: tytso, adilger.kernel, linux-ext4, jack

Hello!

On Wed 15-04-26 18:55:01, Ye Bin wrote:
> From: Ye Bin <yebin10@huawei.com>
> 
> Diffs v2 vs v1:
> (1) Fix sashiko review issues:
> https://sashiko.dev/#/patchset/20260403082507.1882703-1-yebin%40huaweicloud.com
> (2) Change "orphan_list" file mode from 0444 to 0400;
> (3) The display format of the "orphan_list" file is modified according
>     to Andreas' suggestions.
> Fault injection tests have been conducted to address the issues raised
> in the sashik review. There is no UAF issue in the ext4_seq_orphan_release()
> function. The reason for this has already been explained in the code comments.
> In addition to the fault injection tests, we also performed a stress test by
> observing the /proc/fs/ext4/XX/orphan_list and the concurrent processes of
> adding and removing orphan nodes, and no issues were found so far.
> 
> 
> In actual production environments, the issue of inconsistency between
> df and du is frequently encountered. In many cases, the cause of the
> problem can be identified through the use of lsof. However, when
> overlayfs is combined with project quota configuration, the issue becomes
> more complex and troublesome to diagnose. First, to determine the project
> ID, one needs to obtain orphaned nodes using `fsck.ext4 -fn /dev/xx`, and
> then retrieve file information through `debugfs`. However, the file names
> cannot always be obtained, and it is often unclear which files they are.
> To identify which files these are, one would need to use crash for online
> debugging or use kprobe to gather information incrementally. However, some
> customers in production environments do not agree to upload any tools, and
> online debugging might impact the business. There are also scenarios where
> files are opened in kernel mode, which do not generate file descriptors(fds),
> making it impossible to identify which files were deleted but still have
> references through lsof. This patchset adds a procfs interface to query
> information about orphaned nodes, which can assist in the analysis and
> localization of such issues.

Ye, did you read my comments to the v1 of the patchset [1]? I didn't see
any reply from you. I don't think this is a good way how to expose orphan
information for a filesystem for reasons I've outlined in that email.

								Honza

[1] https://lore.kernel.org/all/n4sccudy5avcgnkdhc27rzofzoprxqtwhfrlmsh3yyrj6vbc6d@mmu73gmtawkq/

> 
> Ye Bin (4):
>   ext4: register 'orphan_list' procfs
>   ext4: skip cursor node in ext4_orphan_del()
>   ext4: show inode orphan list detail information
>   ext4: show orphan file inode detail info
> 
>  fs/ext4/ext4.h   |   1 +
>  fs/ext4/orphan.c | 326 ++++++++++++++++++++++++++++++++++++++++++++++-
>  fs/ext4/sysfs.c  |   2 +
>  3 files changed, 328 insertions(+), 1 deletion(-)
> 
> -- 
> 2.34.1
> 
-- 
Jan Kara <jack@suse.com>
SUSE Labs, CR

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

* Re: [PATCH v2 2/4] ext4: skip cursor node in ext4_orphan_del()
  2026-04-15 10:55 ` [PATCH v2 2/4] ext4: skip cursor node in ext4_orphan_del() Ye Bin
@ 2026-04-15 23:56   ` Darrick J. Wong
  0 siblings, 0 replies; 7+ messages in thread
From: Darrick J. Wong @ 2026-04-15 23:56 UTC (permalink / raw)
  To: Ye Bin; +Cc: tytso, adilger.kernel, linux-ext4, jack

On Wed, Apr 15, 2026 at 06:55:03PM +0800, Ye Bin wrote:
> From: Ye Bin <yebin10@huawei.com>
> 
> This patch is prepared for displaying orphan_list information. Because
> temporary nodes may be inserted when the orphan_list is traversed and
> displayed, these temporary nodes need to be skipped.
> 
> Signed-off-by: Ye Bin <yebin10@huawei.com>
> ---
>  fs/ext4/orphan.c | 20 +++++++++++++++++++-
>  1 file changed, 19 insertions(+), 1 deletion(-)
> 
> diff --git a/fs/ext4/orphan.c b/fs/ext4/orphan.c
> index f7e7f77e021e..a6bffe67ef75 100644
> --- a/fs/ext4/orphan.c
> +++ b/fs/ext4/orphan.c
> @@ -220,6 +220,23 @@ static int ext4_orphan_file_del(handle_t *handle, struct inode *inode)
>  	return ret;
>  }
>  
> +static inline bool ext4_is_cursor(struct inode *inode)
> +{
> +	return (inode->i_ino == 0);
> +}
> +
> +static inline struct list_head *ext4_orphan_prev_node(
> +					struct ext4_inode_info *pos,
> +					struct list_head *head)
> +{
> +	list_for_each_entry_continue_reverse(pos, head, i_orphan) {
> +		if (likely(!ext4_is_cursor(&pos->vfs_inode)))
> +			return &pos->i_orphan;
> +	}
> +
> +	return head;

Waitaminute, you inject the procfs file's cursor into the orphan list
with a phony ext4_inode_info??  That sounds like a landmine waiting to
go off the next time someone writes code that traverses the list, or
even wants to check that its non-empty.

--D

> +}
> +
>  /*
>   * ext4_orphan_del() removes an unlinked or truncated inode from the list
>   * of such inodes stored on disk, because it is finally being cleaned up.
> @@ -253,7 +270,8 @@ int ext4_orphan_del(handle_t *handle, struct inode *inode)
>  	mutex_lock(&sbi->s_orphan_lock);
>  	ext4_debug("remove inode %llu from orphan list\n", inode->i_ino);
>  
> -	prev = ei->i_orphan.prev;
> +	prev = ext4_orphan_prev_node(ei, &sbi->s_orphan);
> +
>  	list_del_init(&ei->i_orphan);
>  
>  	/* If we're on an error path, we may not have a valid
> -- 
> 2.34.1
> 
> 

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

end of thread, other threads:[~2026-04-15 23:56 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-04-15 10:55 [PATCH v2 0/4] show orphan file inode detail info Ye Bin
2026-04-15 10:55 ` [PATCH v2 1/4] ext4: register 'orphan_list' procfs Ye Bin
2026-04-15 10:55 ` [PATCH v2 2/4] ext4: skip cursor node in ext4_orphan_del() Ye Bin
2026-04-15 23:56   ` Darrick J. Wong
2026-04-15 10:55 ` [PATCH v2 3/4] ext4: show inode orphan list detail information Ye Bin
2026-04-15 10:55 ` [PATCH v2 4/4] ext4: show orphan file inode detail info Ye Bin
2026-04-15 17:59 ` [PATCH v2 0/4] " Jan Kara

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