From: James Bottomley <James.Bottomley@HansenPartnership.com>
To: linux-fsdevel@vger.kernel.org
Cc: David Howells <dhowells@redhat.com>,
Christian Brauner <christian@brauner.io>,
Al Viro <viro@ZenIV.linux.org.uk>,
Miklos Szeredi <miklos@szeredi.hu>
Subject: [PATCH v2 2/6] configfd: add generic file descriptor based configuration parser
Date: Sat, 4 Jan 2020 12:14:28 -0800 [thread overview]
Message-ID: <20200104201432.27320-3-James.Bottomley@HansenPartnership.com> (raw)
In-Reply-To: <20200104201432.27320-1-James.Bottomley@HansenPartnership.com>
This code is based on the original filesystem context based
configuration parser by David Howells, but lifted up so it can apply
to anything rather than only filesystem contexts.
Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
fs/Makefile | 3 +-
fs/configfd.c | 450 ++++++++++++++++++++++++++++++++++++++++++
include/linux/configfd.h | 61 ++++++
include/uapi/linux/configfd.h | 20 ++
4 files changed, 533 insertions(+), 1 deletion(-)
create mode 100644 fs/configfd.c
create mode 100644 include/linux/configfd.h
create mode 100644 include/uapi/linux/configfd.h
diff --git a/fs/Makefile b/fs/Makefile
index 1148c555c4d3..de83671ce80d 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -13,7 +13,8 @@ obj-y := open.o read_write.o file_table.o super.o \
seq_file.o xattr.o libfs.o fs-writeback.o \
pnode.o splice.o sync.o utimes.o d_path.o \
stack.o fs_struct.o statfs.o fs_pin.o nsfs.o \
- fs_types.o fs_context.o fs_parser.o fsopen.o
+ fs_types.o fs_context.o fs_parser.o fsopen.o \
+ configfd.o
ifeq ($(CONFIG_BLOCK),y)
obj-y += buffer.o block_dev.o direct-io.o mpage.o
diff --git a/fs/configfd.c b/fs/configfd.c
new file mode 100644
index 000000000000..7f2c750ac7e3
--- /dev/null
+++ b/fs/configfd.c
@@ -0,0 +1,450 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Generic configuration file descriptor handling
+ *
+ * Copyright (C) 2019 James.Bottomley@HansenPartnership.com
+ */
+
+#include <linux/anon_inodes.h>
+#include <linux/configfd.h>
+#include <linux/namei.h>
+#include <linux/slab.h>
+#include <linux/syscalls.h>
+#include <linux/rwlock.h>
+#include <linux/uaccess.h>
+
+
+static struct configfd_type *configfds;
+static DEFINE_RWLOCK(configfds_lock);
+
+static ssize_t configfd_read(struct file *file,
+ char __user *buf, size_t len, loff_t *pos)
+{
+ struct configfd_context *cfc = file->private_data;
+
+ return logger_read(cfc->log, buf, len);
+}
+
+static void configfd_type_put(const struct configfd_type *cft)
+{
+ module_put(cft->owner);
+}
+
+static void configfd_context_free(struct configfd_context *cfc)
+{
+ if (cfc->cft->ops->free)
+ cfc->cft->ops->free(cfc);
+ logger_put(&cfc->log);
+ configfd_type_put(cfc->cft);
+ kfree(cfc);
+}
+
+static int configfd_release(struct inode *inode, struct file *file)
+{
+ struct configfd_context *cfc = file->private_data;
+
+ if (cfc) {
+ file->private_data = NULL;
+ configfd_context_free(cfc);
+ }
+ return 0;
+}
+
+const struct file_operations configfd_context_fops = {
+ .read = configfd_read,
+ .release = configfd_release,
+ .llseek = no_llseek,
+};
+
+static int configfd_create_fd(struct configfd_context *cfc,
+ unsigned int flags)
+{
+ int fd;
+
+ fd = anon_inode_getfd("[configfd]", &configfd_context_fops, cfc,
+ O_RDWR | flags);
+ if (fd < 0)
+ configfd_context_free(cfc);
+ return fd;
+}
+
+static struct configfd_type **configfd_type_find(const char *name)
+{
+ struct configfd_type **c;
+
+ for (c = &configfds; *c; c = &(*c)->next) {
+ if (strcmp((*c)->name, name) == 0)
+ break;
+ }
+
+ return c;
+}
+
+static struct configfd_type *configfd_type_get(const char *name)
+{
+ struct configfd_type *cft;
+
+ read_lock(&configfds_lock);
+ cft = *(configfd_type_find(name));
+ if (cft && !try_module_get(cft->owner))
+ cft = NULL;
+ read_unlock(&configfds_lock);
+
+ return cft;
+}
+
+struct configfd_context *configfd_context_alloc(const struct configfd_type *cft,
+ unsigned int op)
+{
+ struct configfd_context *cfc;
+ struct logger *log;
+ int ret;
+
+ cfc = kzalloc(sizeof(*cfc) + cft->data_size, GFP_KERNEL);
+ if (!cfc)
+ return ERR_PTR(-ENOMEM);
+
+ if (cft->data_size)
+ cfc->data = &cfc[1];
+
+ cfc->cft = cft;
+ cfc->op = op;
+ log = logger_alloc(cft->owner);
+ if (IS_ERR(log)) {
+ ret = PTR_ERR(log);
+ goto out_free;
+ }
+
+ cfc->log = log;
+ if (cft->ops->alloc) {
+ ret = cft->ops->alloc(cfc);
+ if (ret)
+ goto out_put;
+ }
+
+ return cfc;
+ out_put:
+ logger_put(&cfc->log);
+ out_free:
+ kfree(cfc);
+ return ERR_PTR(ret);
+}
+
+int kern_configfd_open(const char *config_name, unsigned int flags,
+ unsigned int op)
+{
+ const struct configfd_type *cft;
+ struct configfd_context *cfc;
+
+ if (flags & ~O_CLOEXEC)
+ return -EINVAL;
+ if (op != CONFIGFD_CMD_CREATE && op != CONFIGFD_CMD_RECONFIGURE)
+ return -EINVAL;
+
+ cft = configfd_type_get(config_name);
+ if (!cft)
+ return -ENODEV;
+ cfc = configfd_context_alloc(cft, op);
+ if (IS_ERR(cfc)) {
+ configfd_type_put(cft);
+ return PTR_ERR(cfc);
+ }
+
+ return configfd_create_fd(cfc, flags);
+}
+
+long ksys_configfd_open(const char __user *_config_name, unsigned int flags,
+ unsigned int op)
+{
+ const char *config_name = strndup_user(_config_name, PAGE_SIZE);
+ int ret;
+
+ if (IS_ERR(config_name))
+ return PTR_ERR(config_name);
+ ret = kern_configfd_open(config_name, flags, op);
+ kfree(config_name);
+
+ return ret;
+}
+
+SYSCALL_DEFINE3(configfd_open,
+ const char __user *, _config_name,
+ unsigned int, flags,
+ unsigned int, op)
+{
+ return ksys_configfd_open(_config_name, flags, op);
+}
+
+int kern_configfd_action(int fd, struct configfd_param *p)
+{
+ struct fd f = fdget(fd);
+ struct configfd_context *cfc;
+ const struct configfd_ops *ops;
+ int ret = -EINVAL;
+ /* upper 24 bits are available to consumers */
+ u8 our_cmd = p->cmd & 0xff;
+
+ if (!f.file)
+ return -EBADF;
+ if (f.file->f_op != &configfd_context_fops)
+ goto out_f;
+ cfc = f.file->private_data;
+
+ ops = cfc->cft->ops;
+
+ /* check allowability */
+ ret = -EOPNOTSUPP;
+ switch (our_cmd) {
+ case CONFIGFD_SET_FLAG:
+ case CONFIGFD_SET_STRING:
+ case CONFIGFD_SET_BINARY:
+ case CONFIGFD_SET_PATH:
+ case CONFIGFD_SET_PATH_EMPTY:
+ case CONFIGFD_SET_INT:
+ if (!ops->set)
+ goto out_f;
+ break;
+ case CONFIGFD_GET_FD:
+ if (!ops->get)
+ goto out_f;
+ break;
+ case CONFIGFD_CMD_CREATE:
+ case CONFIGFD_CMD_RECONFIGURE:
+ if (our_cmd != cfc->op) {
+ logger_err(cfc->log, "Wrong operation, we were opened for %d", cfc->op);
+ goto out_f;
+ }
+ if (!ops->act)
+ goto out_f;
+ break;
+ default:
+ break;
+ }
+
+ /*
+ * Execute
+ */
+ switch (our_cmd) {
+ case CONFIGFD_SET_STRING:
+ case CONFIGFD_SET_BINARY:
+ case CONFIGFD_SET_PATH:
+ case CONFIGFD_SET_PATH_EMPTY:
+ case CONFIGFD_SET_FD:
+ case CONFIGFD_SET_FLAG:
+ case CONFIGFD_SET_INT:
+ ret = ops->set(cfc, p);
+ break;
+ case CONFIGFD_GET_FD:
+ ret = ops->get(cfc, p);
+ if (ret == 0) {
+ int fd;
+
+ fd = get_unused_fd_flags(p->aux & O_CLOEXEC);
+ if (fd < 0) {
+ ret = fd;
+ break;
+ }
+ fd_install(fd, p->file);
+ p->file = NULL; /* consume the file */
+ p->aux = fd;
+ }
+ break;
+ case CONFIGFD_CMD_RECONFIGURE:
+ case CONFIGFD_CMD_CREATE:
+ ret = ops->act(cfc, p->cmd);
+ break;
+ default:
+ break;
+ }
+out_f:
+ fdput(f);
+ return ret;
+}
+
+long ksys_configfd_action(int fd, unsigned int cmd, const char __user *_key,
+ void __user *_value, int aux)
+{
+ struct configfd_param param = {
+ .cmd = cmd,
+ };
+ u8 our_cmd = cmd & 0xff;
+ int ret;
+
+ /* check parameters required for action */
+ switch (our_cmd) {
+ case CONFIGFD_SET_FLAG:
+ if (!_key || _value || aux)
+ return -EINVAL;
+ break;
+ case CONFIGFD_SET_STRING:
+ case CONFIGFD_GET_FD:
+ if (!_key || !_value)
+ return -EINVAL;
+ break;
+ case CONFIGFD_SET_BINARY:
+ if (!_key || !_value || aux <= 0 || aux > 1024 * 1024)
+ return -EINVAL;
+ break;
+ case CONFIGFD_SET_PATH:
+ case CONFIGFD_SET_PATH_EMPTY:
+ if (!_key || !_value || (aux != AT_FDCWD && aux < 0))
+ return -EINVAL;
+ break;
+ case CONFIGFD_SET_FD:
+ if (!_key || _value || aux < 0)
+ return -EINVAL;
+ break;
+ case CONFIGFD_SET_INT:
+ if (!_key)
+ return -EINVAL;
+ break;
+ case CONFIGFD_CMD_CREATE:
+ case CONFIGFD_CMD_RECONFIGURE:
+ if (_key || _value || aux)
+ return -EINVAL;
+ break;
+ default:
+ return -EOPNOTSUPP;
+ }
+
+ if (_key) {
+ param.key = strndup_user(_key, 256);
+ if (IS_ERR(param.key))
+ return PTR_ERR(param.key);
+ }
+
+ /* now pull the parameters into the kernel */
+ switch (our_cmd) {
+ case CONFIGFD_SET_STRING:
+ param.string = strndup_user(_value, 256);
+ if (IS_ERR(param.string)) {
+ ret = PTR_ERR(param.string);
+ goto out_key;
+ }
+ param.size = strlen(param.string);
+ break;
+ case CONFIGFD_SET_BINARY:
+ param.size = aux;
+ param.blob = memdup_user_nul(_value, aux);
+ if (IS_ERR(param.blob)) {
+ ret = PTR_ERR(param.blob);
+ goto out_key;
+ }
+ break;
+ case CONFIGFD_SET_PATH:
+ param.name = getname_flags(_value, 0, NULL);
+ if (IS_ERR(param.name)) {
+ ret = PTR_ERR(param.name);
+ goto out_key;
+ }
+ param.aux = aux;
+ param.size = strlen(param.name->name);
+ break;
+ case CONFIGFD_SET_PATH_EMPTY:
+ param.name = getname_flags(_value, LOOKUP_EMPTY, NULL);
+ if (IS_ERR(param.name)) {
+ ret = PTR_ERR(param.name);
+ goto out_key;
+ }
+ param.aux = aux;
+ param.size = strlen(param.name->name);
+ break;
+ case CONFIGFD_SET_FD:
+ ret = -EBADF;
+ param.file = fget_raw(aux);
+ if (!param.file)
+ goto out_key;
+ break;
+ case CONFIGFD_SET_INT:
+ param.aux = aux;
+ break;
+ default:
+ break;
+ }
+ ret = kern_configfd_action(fd, ¶m);
+ /* clean up unconsumed parameters */
+ switch (our_cmd) {
+ case CONFIGFD_SET_STRING:
+ case CONFIGFD_SET_BINARY:
+ kfree(param.string);
+ break;
+ case CONFIGFD_SET_PATH:
+ case CONFIGFD_SET_PATH_EMPTY:
+ if (param.name)
+ putname(param.name);
+ break;
+ case CONFIGFD_GET_FD:
+ if (!ret)
+ ret = put_user(param.aux, (int __user *)_value);
+ /* FALL THROUGH */
+ case CONFIGFD_SET_FD:
+ if (param.file)
+ fput(param.file);
+ break;
+ default:
+ break;
+ }
+ out_key:
+ kfree(param.key);
+
+ return ret;
+}
+
+
+SYSCALL_DEFINE5(configfd_action,
+ int, fd, unsigned int, cmd,
+ const char __user *, _key,
+ void __user *, _value,
+ int, aux)
+{
+ return ksys_configfd_action(fd, cmd, _key, _value, aux);
+}
+
+int configfd_type_register(struct configfd_type *cft)
+{
+ int ret = 0;
+ struct configfd_type **c;
+
+ if (WARN(cft->next,
+ "BUG: registering already registered configfd_type: %s",
+ cft->name))
+ return -EBUSY;
+
+ if (WARN(cft->ops == NULL,
+ "BUG: configfd_type has no ops set: %s", cft->name))
+ return -EINVAL;
+
+ if (WARN(cft->ops->alloc && (!cft->ops->free),
+ "BUG: if configfd ops alloc is set, free must also be"))
+ return -EINVAL;
+
+ if (WARN(cft->name == NULL || cft->name[0] == '\0',
+ "BUG: configfd_type has no name"))
+ return -EINVAL;
+
+ write_lock(&configfds_lock);
+ c = configfd_type_find(cft->name);
+ if (WARN(*c, "BUG: configfd_type name already exists: %s",
+ cft->name))
+ ret = -EBUSY;
+ else
+ *c = cft;
+ write_unlock(&configfds_lock);
+
+ return ret;
+}
+
+void configfd_type_unregister(struct configfd_type *cft)
+{
+ struct configfd_type **c;
+
+ write_lock(&configfds_lock);
+ c = configfd_type_find(cft->name);
+ if (WARN(*c != cft, "BUG: trying to register %s from wrong structure",
+ cft->name))
+ goto out;
+ *c = cft->next;
+ cft->next = NULL;
+ out:
+ write_unlock(&configfds_lock);
+}
diff --git a/include/linux/configfd.h b/include/linux/configfd.h
new file mode 100644
index 000000000000..2a2efd7eabe2
--- /dev/null
+++ b/include/linux/configfd.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+
+#ifndef _LINUX_CONFIGFD_H
+#define _LINUX_CONFIGFD_H
+
+#include <linux/logger.h>
+
+#include <uapi/linux/configfd.h>
+
+struct configfd_context;
+
+struct configfd_param {
+ const char *key;
+ union {
+ char *string;
+ void *blob;
+ struct filename *name;
+ struct file *file;
+ };
+ int aux;
+ unsigned int cmd;
+ size_t size;
+};
+
+struct configfd_ops {
+ int (*alloc)(struct configfd_context *cfc);
+ void (*free)(const struct configfd_context *cfc);
+ int (*set)(const struct configfd_context *cfc,
+ struct configfd_param *p);
+ int (*get)(const struct configfd_context *cfc,
+ struct configfd_param *p);
+ int (*act)(const struct configfd_context *cfc, unsigned int cmd);
+};
+
+struct configfd_type {
+ const char *name;
+ size_t data_size;
+ struct module *owner;
+ struct configfd_ops *ops;
+ struct configfd_type *next;
+};
+
+struct configfd_context {
+ const struct configfd_type *cft;
+ struct logger *log;
+ void *data;
+ unsigned int op;
+};
+
+int configfd_type_register(struct configfd_type *cft);
+void configfd_type_unregister(struct configfd_type *cft);
+
+long ksys_configfd_open(const char __user *config_name, unsigned int flags,
+ unsigned int op);
+long ksys_configfd_action(int fd, unsigned int cmd, const char __user *key,
+ void __user *value, int aux);
+int kern_configfd_action(int fd, struct configfd_param *p);
+int kern_configfd_open(const char *name, unsigned int flags,
+ unsigned int op);
+
+#endif
diff --git a/include/uapi/linux/configfd.h b/include/uapi/linux/configfd.h
new file mode 100644
index 000000000000..3e54cfef0182
--- /dev/null
+++ b/include/uapi/linux/configfd.h
@@ -0,0 +1,20 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later WITH Linux-syscall-note */
+
+#ifndef _UAPI_LINUX_CONFIGFD_H
+#define _UAPI_LINUX_CONFIGFD_H
+
+enum configfd_cmd {
+ CONFIGFD_SET_FLAG = 0,
+ CONFIGFD_SET_STRING = 1,
+ CONFIGFD_SET_BINARY = 2,
+ CONFIGFD_SET_PATH = 3,
+ CONFIGFD_SET_PATH_EMPTY = 4,
+ CONFIGFD_SET_FD = 5,
+ CONFIGFD_CMD_CREATE = 6,
+ CONFIGFD_CMD_RECONFIGURE = 7,
+ /* gap for 30 other commands */
+ CONFIGFD_SET_INT = 38,
+ CONFIGFD_GET_FD = 39,
+};
+
+#endif
--
2.16.4
next prev parent reply other threads:[~2020-01-04 20:15 UTC|newest]
Thread overview: 19+ messages / expand[flat|nested] mbox.gz Atom feed top
2020-01-04 20:14 [PATCH v2 0/6] introduce configfd as generalisation of fsconfig James Bottomley
2020-01-04 20:14 ` [PATCH v2 1/6] logger: add a limited buffer logging facility James Bottomley
2020-01-04 20:14 ` James Bottomley [this message]
2020-01-06 3:58 ` [PATCH v2 2/6] configfd: add generic file descriptor based configuration parser kbuild test robot
2020-01-06 3:58 ` [RFC PATCH] configfd: configfd_context_fops can be static kbuild test robot
2020-01-04 20:14 ` [PATCH v2 3/6] configfd: syscall: wire up configfd syscalls James Bottomley
2020-01-04 20:14 ` [PATCH v2 4/6] fs: implement fsconfig via configfd James Bottomley
2020-01-05 1:12 ` kbuild test robot
2020-01-05 2:56 ` kbuild test robot
2020-01-11 19:58 ` kbuild test robot
2020-01-04 20:14 ` [PATCH v2 5/6] fs: expose internal interfaces open_detached_copy and do_reconfigure_mount James Bottomley
2020-01-04 20:14 ` [PATCH v2 6/6] fs: bind: add configfs type for bind mounts James Bottomley
2020-01-12 10:35 ` kbuild test robot
2020-01-12 10:35 ` [RFC PATCH] fs: bind: to_bind_data() can be static kbuild test robot
2020-01-05 16:23 ` [PATCH v2 0/6] introduce configfd as generalisation of fsconfig Christian Brauner
2020-01-05 17:51 ` James Bottomley
2020-01-05 18:02 ` James Bottomley
2020-01-08 17:07 ` Christian Brauner
2020-01-08 18:42 ` James Bottomley
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20200104201432.27320-3-James.Bottomley@HansenPartnership.com \
--to=james.bottomley@hansenpartnership.com \
--cc=christian@brauner.io \
--cc=dhowells@redhat.com \
--cc=linux-fsdevel@vger.kernel.org \
--cc=miklos@szeredi.hu \
--cc=viro@ZenIV.linux.org.uk \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).