Existing primitive has several problems: 1) calling conventions are clumsy - it returns a dentry reference that is either identical to its second argument or is an ERR_PTR(-E...); in both cases no refcount changes happen. Inconvenient for users and bug-prone; it would be better to have it return 0 on success and -E... on failure. 2) it allows cross-directory moves; however, no such caller have ever materialized and considering the way debugfs is used, it's unlikely to happen in the future. What's more, any such caller would have fun issues to deal with wrt interplay with recursive removal. It also makes the calling conventions clumsier... 3) tautological rename fails; the callers have no race-free way to deal with that. 4) new name must have been formed by the caller; quite a few callers have it done by sprintf/kasprintf/etc., ending up with considerable boilerplate. Proposed replacement: int debugfs_change_name(dentry, fmt, ...). All callers convert to that easily, and it's simpler internally. IMO debugfs_rename() should go; if we ever get a real-world use case for cross-directory moves in debugfs, we can always look into the right way to handle that. Signed-off-by: Al Viro <viro@zeniv.linux.org.uk> Link: https://lore.kernel.org/r/20250112080705.141166-21-viro@zeniv.linux.org.uk Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
267 lines
5.8 KiB
C
267 lines
5.8 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <linux/idr.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/seq_file.h>
|
|
#include <linux/shrinker.h>
|
|
#include <linux/memcontrol.h>
|
|
|
|
#include "internal.h"
|
|
|
|
/* defined in vmscan.c */
|
|
extern struct mutex shrinker_mutex;
|
|
extern struct list_head shrinker_list;
|
|
|
|
static DEFINE_IDA(shrinker_debugfs_ida);
|
|
static struct dentry *shrinker_debugfs_root;
|
|
|
|
static unsigned long shrinker_count_objects(struct shrinker *shrinker,
|
|
struct mem_cgroup *memcg,
|
|
unsigned long *count_per_node)
|
|
{
|
|
unsigned long nr, total = 0;
|
|
int nid;
|
|
|
|
for_each_node(nid) {
|
|
if (nid == 0 || (shrinker->flags & SHRINKER_NUMA_AWARE)) {
|
|
struct shrink_control sc = {
|
|
.gfp_mask = GFP_KERNEL,
|
|
.nid = nid,
|
|
.memcg = memcg,
|
|
};
|
|
|
|
nr = shrinker->count_objects(shrinker, &sc);
|
|
if (nr == SHRINK_EMPTY)
|
|
nr = 0;
|
|
} else {
|
|
nr = 0;
|
|
}
|
|
|
|
count_per_node[nid] = nr;
|
|
total += nr;
|
|
}
|
|
|
|
return total;
|
|
}
|
|
|
|
static int shrinker_debugfs_count_show(struct seq_file *m, void *v)
|
|
{
|
|
struct shrinker *shrinker = m->private;
|
|
unsigned long *count_per_node;
|
|
struct mem_cgroup *memcg;
|
|
unsigned long total;
|
|
bool memcg_aware;
|
|
int ret = 0, nid;
|
|
|
|
count_per_node = kcalloc(nr_node_ids, sizeof(unsigned long), GFP_KERNEL);
|
|
if (!count_per_node)
|
|
return -ENOMEM;
|
|
|
|
rcu_read_lock();
|
|
|
|
memcg_aware = shrinker->flags & SHRINKER_MEMCG_AWARE;
|
|
|
|
memcg = mem_cgroup_iter(NULL, NULL, NULL);
|
|
do {
|
|
if (memcg && !mem_cgroup_online(memcg))
|
|
continue;
|
|
|
|
total = shrinker_count_objects(shrinker,
|
|
memcg_aware ? memcg : NULL,
|
|
count_per_node);
|
|
if (total) {
|
|
seq_printf(m, "%lu", mem_cgroup_ino(memcg));
|
|
for_each_node(nid)
|
|
seq_printf(m, " %lu", count_per_node[nid]);
|
|
seq_putc(m, '\n');
|
|
}
|
|
|
|
if (!memcg_aware) {
|
|
mem_cgroup_iter_break(NULL, memcg);
|
|
break;
|
|
}
|
|
|
|
if (signal_pending(current)) {
|
|
mem_cgroup_iter_break(NULL, memcg);
|
|
ret = -EINTR;
|
|
break;
|
|
}
|
|
} while ((memcg = mem_cgroup_iter(NULL, memcg, NULL)) != NULL);
|
|
|
|
rcu_read_unlock();
|
|
|
|
kfree(count_per_node);
|
|
return ret;
|
|
}
|
|
DEFINE_SHOW_ATTRIBUTE(shrinker_debugfs_count);
|
|
|
|
static int shrinker_debugfs_scan_open(struct inode *inode, struct file *file)
|
|
{
|
|
file->private_data = inode->i_private;
|
|
return nonseekable_open(inode, file);
|
|
}
|
|
|
|
static ssize_t shrinker_debugfs_scan_write(struct file *file,
|
|
const char __user *buf,
|
|
size_t size, loff_t *pos)
|
|
{
|
|
struct shrinker *shrinker = file->private_data;
|
|
unsigned long nr_to_scan = 0, ino, read_len;
|
|
struct shrink_control sc = {
|
|
.gfp_mask = GFP_KERNEL,
|
|
};
|
|
struct mem_cgroup *memcg = NULL;
|
|
int nid;
|
|
char kbuf[72];
|
|
|
|
read_len = min(size, sizeof(kbuf) - 1);
|
|
if (copy_from_user(kbuf, buf, read_len))
|
|
return -EFAULT;
|
|
kbuf[read_len] = '\0';
|
|
|
|
if (sscanf(kbuf, "%lu %d %lu", &ino, &nid, &nr_to_scan) != 3)
|
|
return -EINVAL;
|
|
|
|
if (nid < 0 || nid >= nr_node_ids)
|
|
return -EINVAL;
|
|
|
|
if (nr_to_scan == 0)
|
|
return size;
|
|
|
|
if (shrinker->flags & SHRINKER_MEMCG_AWARE) {
|
|
memcg = mem_cgroup_get_from_ino(ino);
|
|
if (!memcg || IS_ERR(memcg))
|
|
return -ENOENT;
|
|
|
|
if (!mem_cgroup_online(memcg)) {
|
|
mem_cgroup_put(memcg);
|
|
return -ENOENT;
|
|
}
|
|
} else if (ino != 0) {
|
|
return -EINVAL;
|
|
}
|
|
|
|
sc.nid = nid;
|
|
sc.memcg = memcg;
|
|
sc.nr_to_scan = nr_to_scan;
|
|
sc.nr_scanned = nr_to_scan;
|
|
|
|
shrinker->scan_objects(shrinker, &sc);
|
|
|
|
mem_cgroup_put(memcg);
|
|
|
|
return size;
|
|
}
|
|
|
|
static const struct file_operations shrinker_debugfs_scan_fops = {
|
|
.owner = THIS_MODULE,
|
|
.open = shrinker_debugfs_scan_open,
|
|
.write = shrinker_debugfs_scan_write,
|
|
};
|
|
|
|
int shrinker_debugfs_add(struct shrinker *shrinker)
|
|
{
|
|
struct dentry *entry;
|
|
char buf[128];
|
|
int id;
|
|
|
|
lockdep_assert_held(&shrinker_mutex);
|
|
|
|
/* debugfs isn't initialized yet, add debugfs entries later. */
|
|
if (!shrinker_debugfs_root)
|
|
return 0;
|
|
|
|
id = ida_alloc(&shrinker_debugfs_ida, GFP_KERNEL);
|
|
if (id < 0)
|
|
return id;
|
|
shrinker->debugfs_id = id;
|
|
|
|
snprintf(buf, sizeof(buf), "%s-%d", shrinker->name, id);
|
|
|
|
/* create debugfs entry */
|
|
entry = debugfs_create_dir(buf, shrinker_debugfs_root);
|
|
if (IS_ERR(entry)) {
|
|
ida_free(&shrinker_debugfs_ida, id);
|
|
return PTR_ERR(entry);
|
|
}
|
|
shrinker->debugfs_entry = entry;
|
|
|
|
debugfs_create_file("count", 0440, entry, shrinker,
|
|
&shrinker_debugfs_count_fops);
|
|
debugfs_create_file("scan", 0220, entry, shrinker,
|
|
&shrinker_debugfs_scan_fops);
|
|
return 0;
|
|
}
|
|
|
|
int shrinker_debugfs_rename(struct shrinker *shrinker, const char *fmt, ...)
|
|
{
|
|
const char *new, *old;
|
|
va_list ap;
|
|
int ret = 0;
|
|
|
|
va_start(ap, fmt);
|
|
new = kvasprintf_const(GFP_KERNEL, fmt, ap);
|
|
va_end(ap);
|
|
|
|
if (!new)
|
|
return -ENOMEM;
|
|
|
|
mutex_lock(&shrinker_mutex);
|
|
|
|
old = shrinker->name;
|
|
shrinker->name = new;
|
|
|
|
ret = debugfs_change_name(shrinker->debugfs_entry, "%s-%d",
|
|
shrinker->name, shrinker->debugfs_id);
|
|
|
|
mutex_unlock(&shrinker_mutex);
|
|
|
|
kfree_const(old);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(shrinker_debugfs_rename);
|
|
|
|
struct dentry *shrinker_debugfs_detach(struct shrinker *shrinker,
|
|
int *debugfs_id)
|
|
{
|
|
struct dentry *entry = shrinker->debugfs_entry;
|
|
|
|
lockdep_assert_held(&shrinker_mutex);
|
|
|
|
*debugfs_id = entry ? shrinker->debugfs_id : -1;
|
|
shrinker->debugfs_entry = NULL;
|
|
|
|
return entry;
|
|
}
|
|
|
|
void shrinker_debugfs_remove(struct dentry *debugfs_entry, int debugfs_id)
|
|
{
|
|
debugfs_remove_recursive(debugfs_entry);
|
|
ida_free(&shrinker_debugfs_ida, debugfs_id);
|
|
}
|
|
|
|
static int __init shrinker_debugfs_init(void)
|
|
{
|
|
struct shrinker *shrinker;
|
|
struct dentry *dentry;
|
|
int ret = 0;
|
|
|
|
dentry = debugfs_create_dir("shrinker", NULL);
|
|
if (IS_ERR(dentry))
|
|
return PTR_ERR(dentry);
|
|
shrinker_debugfs_root = dentry;
|
|
|
|
/* Create debugfs entries for shrinkers registered at boot */
|
|
mutex_lock(&shrinker_mutex);
|
|
list_for_each_entry(shrinker, &shrinker_list, list)
|
|
if (!shrinker->debugfs_entry) {
|
|
ret = shrinker_debugfs_add(shrinker);
|
|
if (ret)
|
|
break;
|
|
}
|
|
mutex_unlock(&shrinker_mutex);
|
|
|
|
return ret;
|
|
}
|
|
late_initcall(shrinker_debugfs_init);
|