diff --git a/fs/proc/generic.c b/fs/proc/generic.c
index ef804cd3ec45..47830d6af255 100644
--- a/fs/proc/generic.c
+++ b/fs/proc/generic.c
@@ -87,6 +87,28 @@ static struct proc_dir_entry *pde_subdir_find(struct proc_dir_entry *dir,
return NULL;
}
+/* The first entry of @dir that sorts after @name. */
+static struct proc_dir_entry *pde_subdir_after(struct proc_dir_entry *dir,
+ const char *name,
+ unsigned int len)
+{
+ struct rb_node *node = dir->subdir.rb_node, *after = NULL;
+
+ while (node) {
+ struct proc_dir_entry *de = rb_entry(node,
+ struct proc_dir_entry,
+ subdir_node);
+
+ if (proc_match(name, de, len) < 0) {
+ after = node;
+ node = node->rb_left;
+ } else {
+ node = node->rb_right;
+ }
+ }
+ return rb_entry_safe(after, struct proc_dir_entry, subdir_node);
+}
+
static bool pde_subdir_insert(struct proc_dir_entry *dir,
struct proc_dir_entry *de)
{@@ -277,6 +299,40 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
return proc_lookup_de(dir, dentry, PDE(dir));
}
+/*
+ * 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.
+ */
+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)
+{
+ struct proc_dir_cursor *cursor = file->private_data;
+
+ if (!cursor) {
+ cursor = kmalloc(sizeof(*cursor), GFP_KERNEL);
+ if (!cursor)
+ return;
+ file->private_data = cursor;
+ }
+ cursor->pos = pos;
+ cursor->len = de->namelen;
+ memcpy(cursor->name, de->name, de->namelen);
+}
+
+int proc_dir_release(struct inode *inode, struct file *file)
+{
+ kfree(file->private_data);
+ return 0;
+}
+
/*
* This returns non-zero if at EOF, so that the /proc
* root directory can use this and check if it should@@ -289,23 +345,26 @@ struct dentry *proc_lookup(struct inode *dir, struct dentry *dentry,
int proc_readdir_de(struct file *file, struct dir_context *ctx,
struct proc_dir_entry *de)
{
+ struct proc_dir_cursor *cursor = file->private_data;
+ struct proc_dir_entry *dir = de;
int i;
if (!dir_emit_dots(file, ctx))
return 0;
- i = ctx->pos - 2;
read_lock(&proc_subdir_lock);
- de = pde_subdir_first(de);
- for (;;) {
- if (!de) {
- read_unlock(&proc_subdir_lock);
- return 0;
- }
- if (!i)
- break;
- de = pde_subdir_next(de);
- i--;
+ if (cursor && cursor->pos == ctx->pos) {
+ /* Resume after the last entry emitted, wherever it is now. */
+ de = pde_subdir_after(dir, cursor->name, cursor->len);
+ } else {
+ /* Moved by a seek, count from the start like always. */
+ de = pde_subdir_first(dir);
+ for (i = ctx->pos - 2; de && i; i--)
+ de = pde_subdir_next(de);
+ }
+ if (!de) {
+ read_unlock(&proc_subdir_lock);
+ return 1;
}
do {@@ -318,8 +377,13 @@ int proc_readdir_de(struct file *file, struct dir_context *ctx,
return 0;
}
ctx->pos++;
+ proc_dir_cursor_set(file, de, ctx->pos);
read_lock(&proc_subdir_lock);
- next = pde_subdir_next(de);
+ /* Removed in the meantime, go on after its name. */
+ if (RB_EMPTY_NODE(&de->subdir_node))
+ next = pde_subdir_after(dir, de->name, de->namelen);
+ else
+ next = pde_subdir_next(de);
pde_put(de);
de = next;
} while (de);
@@ -347,6 +411,7 @@ static const struct file_operations proc_dir_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.iterate_shared = proc_readdir,
+ .release = proc_dir_release,
};
static int proc_net_d_revalidate(struct inode *dir, const struct qstr *name,diff --git a/fs/proc/internal.h b/fs/proc/internal.h
index df873df9afc5..04f7ce2b24ed 100644
--- a/fs/proc/internal.h
+++ b/fs/proc/internal.h
@@ -287,6 +287,7 @@ 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 *);
+int proc_dir_release(struct inode *, struct file *);
static inline void pde_get(struct proc_dir_entry *pde)
{diff --git a/fs/proc/proc_net.c b/fs/proc/proc_net.c
index b1f5eafb069a..dbcf87a257e1 100644
--- a/fs/proc/proc_net.c
+++ b/fs/proc/proc_net.c
@@ -351,6 +351,7 @@ const struct file_operations proc_net_operations = {
.llseek = generic_file_llseek,
.read = generic_read_dir,
.iterate_shared = proc_tgid_net_readdir,
+ .release = proc_dir_release,
};
static __net_init int proc_net_ns_init(struct net *net)diff --git a/fs/proc/root.c b/fs/proc/root.c
index 7fbbe92bf73a..dfee1caa26f7 100644
--- a/fs/proc/root.c
+++ b/fs/proc/root.c
@@ -441,6 +441,7 @@ static const struct file_operations proc_root_operations = {
.read = generic_read_dir,
.iterate_shared = proc_root_readdir,
.llseek = generic_file_llseek,
+ .release = proc_dir_release,
};
/*
--
2.53.0