[PATCH RESEND v2 03/23] proc: share the readdir cursor with sysctl

Christian Brauner brauner at kernel.org
Wed Nov 4 11:30:01 PST 2026


proc_readdir_de() and proc_sys_readdir() both resume a listing after
the last entry it emitted and each remembers that entry in the open
file with a cursor of its own: the same struct, the same way of
setting it, the same release. Keep one in generic.c and let sysctl
use it.

No functional changes.

Signed-off-by: Christian Brauner (Amutable) <brauner at kernel.org>
---
 fs/proc/generic.c     | 29 ++++++++++++-----------------
 fs/proc/internal.h    | 25 +++++++++++++++++++++++++
 fs/proc/proc_sysctl.c | 45 +++++----------------------------------------
 3 files changed, 42 insertions(+), 57 deletions(-)

diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index e7264f0cf5ab..f6d620a5c3d2 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -301,22 +301,16 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
 }
 
 /*
- * Where the next readdir resumes. The entries are counted, so one that is
- * added or removed in front of the position would shift what the next call
- * returns. The name of the last entry emitted doesn't move.
+ * Remember @name as the entry the listing is behind at @pos. If this can't
+ * the next call counts from the start.
  */
-struct proc_dir_cursor {
-	loff_t pos;			/* the position behind the entry */
-	unsigned int len;
-	char name[NAME_MAX + 1];
-};
-
-/* If this can't remember the entry the next call counts from the start. */
-static void proc_dir_cursor_set(struct file *file, struct proc_dir_entry *de,
-				loff_t pos)
+void proc_dir_cursor_set(struct file *file, const char *name, unsigned int len,
+			 loff_t pos)
 {
 	struct proc_dir_cursor *cursor = file->private_data;
 
+	if (len > NAME_MAX)
+		return;
 	if (!cursor) {
 		cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
 		if (!cursor)
@@ -324,8 +318,8 @@ static void proc_dir_cursor_set(struct file *file, struct proc_dir_entry *de,
 		file->private_data = cursor;
 	}
 	cursor->pos = pos;
-	cursor->len = de->namelen;
-	memcpy(cursor->name, de->name, de->namelen);
+	cursor->len = len;
+	memcpy(cursor->name, name, len);
 }
 
 int proc_dir_release(struct inode *inode, struct file *file)
@@ -346,7 +340,7 @@ int proc_dir_release(struct inode *inode, struct file *file)
 int proc_readdir_de(struct file *file, struct dir_context *ctx,
 		    struct proc_dir_entry *de)
 {
-	struct proc_dir_cursor *cursor = file->private_data;
+	const struct proc_dir_cursor *cursor;
 	struct proc_dir_entry *dir = de;
 	int i;
 
@@ -354,7 +348,8 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,
 		return 0;
 
 	read_lock(&proc_subdir_lock);
-	if (cursor && cursor->pos == ctx->pos) {
+	cursor = proc_dir_cursor_at(file, ctx->pos);
+	if (cursor) {
 		/* Resume after the last entry emitted, wherever it is now. */
 		de = pde_subdir_after(dir, cursor->name, cursor->len);
 	} else {
@@ -378,7 +373,7 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,
 			return 0;
 		}
 		ctx->pos++;
-		proc_dir_cursor_set(file, de, ctx->pos);
+		proc_dir_cursor_set(file, de->name, de->namelen, ctx->pos);
 		read_lock(&proc_subdir_lock);
 		/* Removed in the meantime, go on after its name. */
 		if (RB_EMPTY_NODE(&de->subdir_node))
diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index 268a0523be12..6b61d09ac5e5 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -278,6 +278,31 @@ extern struct dentry *proc_lookup(struct inode *, struct dentry *, unsigned int)
 struct dentry *proc_lookup_de(struct inode *, struct dentry *, struct proc_dir_entry *);
 extern int proc_readdir(struct file *, struct dir_context *);
 int proc_readdir_de(struct file *, struct dir_context *, struct proc_dir_entry *);
+
+/*
+ * Where the next readdir resumes. The entries are counted, so one that is
+ * added or removed in front of the position would shift what the next call
+ * returns. The name of the last entry emitted doesn't move. The directory's
+ * file keeps it, see proc_dir_release().
+ */
+struct proc_dir_cursor {
+	loff_t pos;			/* the position behind the entry */
+	unsigned int len;
+	char name[NAME_MAX + 1];
+};
+
+/* The entry to resume after, if the position is still the one behind it. */
+static inline const struct proc_dir_cursor *
+proc_dir_cursor_at(struct file *file, loff_t pos)
+{
+	const struct proc_dir_cursor *cursor = file->private_data;
+
+	if (cursor && cursor->pos == pos)
+		return cursor;
+	return NULL;
+}
+void proc_dir_cursor_set(struct file *file, const char *name, unsigned int len,
+			 loff_t pos);
 int proc_dir_release(struct inode *, struct file *);
 
 static inline void pde_get(struct proc_dir_entry *pde)
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 6a92c33db9b5..c1e41a3f8f6b 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -787,36 +787,6 @@ out:
 	return ret;
 }
 
-/*
- * Where the next readdir resumes. The entries are counted, so one that is
- * registered or unregistered in front of the position would shift what
- * the next call returns. The name of the last entry emitted doesn't move.
- */
-struct sysctl_cursor {
-	loff_t pos;			/* the position behind the entry */
-	int len;
-	char name[NAME_MAX + 1];
-};
-
-/* If this can't remember the entry the next call counts from the start. */
-static void sysctl_cursor_set(struct file *file, const char *name, int len,
-			      loff_t pos)
-{
-	struct sysctl_cursor *cursor = file->private_data;
-
-	if (len > NAME_MAX)
-		return;
-	if (!cursor) {
-		cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
-		if (!cursor)
-			return;
-		file->private_data = cursor;
-	}
-	cursor->pos = pos;
-	cursor->len = len;
-	memcpy(cursor->name, name, len);
-}
-
 /*
  * The inode number and type of the entry @d holds, instantiating its dentry
  * if there is none. Needs the header pinned, proc_sys_make_inode() checks
@@ -871,7 +841,7 @@ static int proc_sys_fill_dirent(struct file *file, struct sysctl_dirent *d)
 static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 {
 	struct ctl_table_header *head = PROC_I(file_inode(file))->sysctl;
-	struct sysctl_cursor *cursor = file->private_data;
+	const struct proc_dir_cursor *cursor;
 	struct sysctl_dirent d = {};
 	struct ctl_dir *ctl_dir;
 	int err;
@@ -888,7 +858,8 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 	 * that moved the position away from it counts from the start, like
 	 * seeking to an arbitrary position always did.
 	 */
-	if (cursor && cursor->pos == ctx->pos) {
+	cursor = proc_dir_cursor_at(file, ctx->pos);
+	if (cursor) {
 		err = sysctl_dir_next(ctl_dir, &d, cursor->name, cursor->len);
 	} else {
 		loff_t pos = 2;
@@ -910,19 +881,13 @@ static int proc_sys_readdir(struct file *file, struct dir_context *ctx)
 				break;
 		}
 		ctx->pos++;
-		sysctl_cursor_set(file, d.name, d.len, ctx->pos);
+		proc_dir_cursor_set(file, d.name, d.len, ctx->pos);
 		err = sysctl_dir_next(ctl_dir, &d, d.name, d.len);
 	}
 	sysctl_dir_done(&d);
 	return err;
 }
 
-static int proc_sys_dir_release(struct inode *inode, struct file *file)
-{
-	kfree(file->private_data);
-	return 0;
-}
-
 static int proc_sys_permission(const struct mnt_idmap *idmap,
 			       struct inode *inode, int mask)
 {
@@ -1002,7 +967,7 @@ static const struct file_operations proc_sys_dir_file_operations = {
 	.read		= generic_read_dir,
 	.iterate_shared	= proc_sys_readdir,
 	.llseek		= generic_file_llseek,
-	.release	= proc_sys_dir_release,
+	.release	= proc_dir_release,
 };
 
 static const struct inode_operations proc_sys_inode_operations = {

-- 
2.53.0




More information about the linux-um mailing list