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