[RFC PATCH 1/4] sysctl: API extension for handling sysctl
From: Alexey Gladkov
Date: Wed Jun 01 2022 - 09:21:08 EST
This adds additional optional functions for handling open, read, and
write operations that can be customized for each sysctl file. It also
creates ctl_context that persists from opening to closing the file in
the /proc/sys.
The context allows us to store dynamic information at the time the file
is opened. This eliminates the need to duplicate ctl_table in order to
dynamically change .data, .extra1 or .extra2.
This API extends the existing one and does not require any changes to
already existing sysctl handlers.
Signed-off-by: Alexey Gladkov <legion@xxxxxxxxxx>
---
fs/proc/proc_sysctl.c | 71 +++++++++++++++++++++++++++++++++++-------
include/linux/sysctl.h | 20 ++++++++++--
2 files changed, 77 insertions(+), 14 deletions(-)
diff --git a/fs/proc/proc_sysctl.c b/fs/proc/proc_sysctl.c
index 7d9cfc730bd4..d3d43e738f01 100644
--- a/fs/proc/proc_sysctl.c
+++ b/fs/proc/proc_sysctl.c
@@ -560,6 +560,7 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
struct inode *inode = file_inode(iocb->ki_filp);
struct ctl_table_header *head = grab_header(inode);
struct ctl_table *table = PROC_I(inode)->sysctl_entry;
+ struct ctl_fops *fops = table->ctl_fops;
size_t count = iov_iter_count(iter);
char *kbuf;
ssize_t error;
@@ -577,7 +578,7 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
/* if that can happen at all, it should be -EINVAL, not -EISDIR */
error = -EINVAL;
- if (!table->proc_handler)
+ if (!table->proc_handler && !fops)
goto out;
/* don't even try if the size is too large */
@@ -600,8 +601,20 @@ static ssize_t proc_sys_call_handler(struct kiocb *iocb, struct iov_iter *iter,
if (error)
goto out_free_buf;
- /* careful: calling conventions are nasty here */
- error = table->proc_handler(table, write, kbuf, &count, &iocb->ki_pos);
+ if (fops) {
+ struct ctl_context *ctx = iocb->ki_filp->private_data;
+
+ if (write && fops->write)
+ error = fops->write(ctx, iocb->ki_filp, kbuf, &count, &iocb->ki_pos);
+ else if (!write && fops->read)
+ error = fops->read(ctx, iocb->ki_filp, kbuf, &count, &iocb->ki_pos);
+ else
+ error = -EINVAL;
+ } else {
+ /* careful: calling conventions are nasty here */
+ error = table->proc_handler(table, write, kbuf, &count, &iocb->ki_pos);
+ }
+
if (error)
goto out_free_buf;
@@ -634,17 +647,50 @@ static int proc_sys_open(struct inode *inode, struct file *filp)
{
struct ctl_table_header *head = grab_header(inode);
struct ctl_table *table = PROC_I(inode)->sysctl_entry;
+ struct ctl_context *ctx;
+ int ret = 0;
/* sysctl was unregistered */
if (IS_ERR(head))
return PTR_ERR(head);
- if (table->poll)
- filp->private_data = proc_sys_poll_event(table->poll);
+ ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
+ if (!ctx)
+ return -ENOMEM;
+
+ ctx->table = table;
+ filp->private_data = ctx;
+
+ if (table->ctl_fops && table->ctl_fops->open)
+ ret = table->ctl_fops->open(ctx, inode, filp);
+
+ if (!ret && table->poll)
+ ctx->poll_event = proc_sys_poll_event(table->poll);
sysctl_head_finish(head);
- return 0;
+ return ret;
+}
+
+static int proc_sys_release(struct inode *inode, struct file *filp)
+{
+ struct ctl_table_header *head = grab_header(inode);
+ struct ctl_table *table = PROC_I(inode)->sysctl_entry;
+ struct ctl_context *ctx = filp->private_data;
+ int ret = 0;
+
+ if (IS_ERR(head))
+ return PTR_ERR(head);
+
+ if (table->ctl_fops && table->ctl_fops->release)
+ ret = table->ctl_fops->release(ctx, inode, filp);
+
+ sysctl_head_finish(head);
+
+ kfree(ctx);
+ filp->private_data = NULL;
+
+ return ret;
}
static __poll_t proc_sys_poll(struct file *filp, poll_table *wait)
@@ -653,23 +699,23 @@ static __poll_t proc_sys_poll(struct file *filp, poll_table *wait)
struct ctl_table_header *head = grab_header(inode);
struct ctl_table *table = PROC_I(inode)->sysctl_entry;
__poll_t ret = DEFAULT_POLLMASK;
- unsigned long event;
+ struct ctl_context *ctx;
/* sysctl was unregistered */
if (IS_ERR(head))
return EPOLLERR | EPOLLHUP;
- if (!table->proc_handler)
+ if (!table->proc_handler && !table->ctl_fops)
goto out;
if (!table->poll)
goto out;
- event = (unsigned long)filp->private_data;
+ ctx = filp->private_data;
poll_wait(filp, &table->poll->wait, wait);
- if (event != atomic_read(&table->poll->event)) {
- filp->private_data = proc_sys_poll_event(table->poll);
+ if (ctx->poll_event != atomic_read(&table->poll->event)) {
+ ctx->poll_event = proc_sys_poll_event(table->poll);
ret = EPOLLIN | EPOLLRDNORM | EPOLLERR | EPOLLPRI;
}
@@ -866,6 +912,7 @@ static int proc_sys_getattr(struct user_namespace *mnt_userns,
static const struct file_operations proc_sys_file_operations = {
.open = proc_sys_open,
+ .release = proc_sys_release,
.poll = proc_sys_poll,
.read_iter = proc_sys_read,
.write_iter = proc_sys_write,
@@ -1153,7 +1200,7 @@ static int sysctl_check_table(const char *path, struct ctl_table *table)
else
err |= sysctl_check_table_array(path, table);
}
- if (!table->proc_handler)
+ if (!table->proc_handler && !table->ctl_fops)
err |= sysctl_err(path, table, "No proc_handler");
if ((table->mode & (S_IRUGO|S_IWUGO)) != table->mode)
diff --git a/include/linux/sysctl.h b/include/linux/sysctl.h
index 6353d6db69b2..ca5657c9fcb2 100644
--- a/include/linux/sysctl.h
+++ b/include/linux/sysctl.h
@@ -116,9 +116,9 @@ struct ctl_table_poll {
wait_queue_head_t wait;
};
-static inline void *proc_sys_poll_event(struct ctl_table_poll *poll)
+static inline unsigned long proc_sys_poll_event(struct ctl_table_poll *poll)
{
- return (void *)(unsigned long)atomic_read(&poll->event);
+ return (unsigned long)atomic_read(&poll->event);
}
#define __CTL_TABLE_POLL_INITIALIZER(name) { \
@@ -128,6 +128,21 @@ static inline void *proc_sys_poll_event(struct ctl_table_poll *poll)
#define DEFINE_CTL_TABLE_POLL(name) \
struct ctl_table_poll name = __CTL_TABLE_POLL_INITIALIZER(name)
+struct ctl_context {
+ struct ctl_table *table;
+ unsigned long poll_event;
+ void *ctl_data;
+};
+
+struct inode;
+
+struct ctl_fops {
+ int (*open) (struct ctl_context *, struct inode *, struct file *);
+ int (*release) (struct ctl_context *, struct inode *, struct file *);
+ ssize_t (*read) (struct ctl_context *, struct file *, char *, size_t *, loff_t *);
+ ssize_t (*write) (struct ctl_context *, struct file *, char *, size_t *, loff_t *);
+};
+
/* A sysctl table is an array of struct ctl_table: */
struct ctl_table {
const char *procname; /* Text ID for /proc/sys, or zero */
@@ -139,6 +154,7 @@ struct ctl_table {
struct ctl_table_poll *poll;
void *extra1;
void *extra2;
+ struct ctl_fops *ctl_fops;
} __randomize_layout;
struct ctl_node {
--
2.33.3