From patchwork Wed Nov 27 20:34:54 2019 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: James Bottomley X-Patchwork-Id: 11264905 Return-Path: Received: from mail.kernel.org (pdx-korg-mail-1.web.codeaurora.org [172.30.200.123]) by pdx-korg-patchwork-2.web.codeaurora.org (Postfix) with ESMTP id 8AE20139A for ; Wed, 27 Nov 2019 20:34:58 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 591F32166E for ; Wed, 27 Nov 2019 20:34:58 +0000 (UTC) Authentication-Results: mail.kernel.org; dkim=fail reason="signature verification failed" (1024-bit key) header.d=hansenpartnership.com header.i=@hansenpartnership.com header.b="Bt37d6qM"; dkim=fail reason="signature verification failed" (1024-bit key) header.d=hansenpartnership.com header.i=@hansenpartnership.com header.b="Bt37d6qM" Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1727758AbfK0Ue4 (ORCPT ); Wed, 27 Nov 2019 15:34:56 -0500 Received: from bedivere.hansenpartnership.com ([66.63.167.143]:57188 "EHLO bedivere.hansenpartnership.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1727750AbfK0Uez (ORCPT ); Wed, 27 Nov 2019 15:34:55 -0500 Received: from localhost (localhost [127.0.0.1]) by bedivere.hansenpartnership.com (Postfix) with ESMTP id 8BD908EE133; Wed, 27 Nov 2019 12:34:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=hansenpartnership.com; s=20151216; t=1574886895; bh=Jj1Z0rQKmPwLQovtxxW0PpRdd5UMLYEhGVBpKnn6KAo=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=Bt37d6qMGgLUvz5TqfmxsCKMaoeYOlpEfXf09upZ+HX2oIUROBkW9RL8O0zhc4jJ8 xfxGvUEfjS7Hkiknt+gX1GNJNFCcdp6/91ZT+UUXTfe8IGENPB2HCqHkUy+NSENR6V qIBULvRZDZZseFYDrsjuWL5pexeOiEe/ioXn7RzI= Received: from bedivere.hansenpartnership.com ([127.0.0.1]) by localhost (bedivere.hansenpartnership.com [127.0.0.1]) (amavisd-new, port 10024) with ESMTP id vrNaA4lpdpqA; Wed, 27 Nov 2019 12:34:55 -0800 (PST) Received: from jarvis.lan (unknown [50.35.76.230]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by bedivere.hansenpartnership.com (Postfix) with ESMTPSA id 224658EE130; Wed, 27 Nov 2019 12:34:55 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=simple/simple; d=hansenpartnership.com; s=20151216; t=1574886895; bh=Jj1Z0rQKmPwLQovtxxW0PpRdd5UMLYEhGVBpKnn6KAo=; h=Subject:From:To:Cc:Date:In-Reply-To:References:From; b=Bt37d6qMGgLUvz5TqfmxsCKMaoeYOlpEfXf09upZ+HX2oIUROBkW9RL8O0zhc4jJ8 xfxGvUEfjS7Hkiknt+gX1GNJNFCcdp6/91ZT+UUXTfe8IGENPB2HCqHkUy+NSENR6V qIBULvRZDZZseFYDrsjuWL5pexeOiEe/ioXn7RzI= Message-ID: <1574886894.21593.9.camel@HansenPartnership.com> Subject: [RFC 2/6] configfd: add generic file descriptor based configuration parser From: James Bottomley To: David Howells , Christian Brauner Cc: linux-fsdevel@vger.kernel.org, Al Viro , Miklos Szeredi Date: Wed, 27 Nov 2019 12:34:54 -0800 In-Reply-To: <1574886778.21593.7.camel@HansenPartnership.com> References: <1574295100.17153.25.camel@HansenPartnership.com> <17268.1574323839@warthog.procyon.org.uk> <1574352920.3277.18.camel@HansenPartnership.com> <1574886778.21593.7.camel@HansenPartnership.com> X-Mailer: Evolution 3.26.6 Mime-Version: 1.0 Sender: linux-fsdevel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-fsdevel@vger.kernel.org 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 --- 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 14231b4cf383..569563f6c0d5 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 +#include +#include +#include +#include +#include +#include + + +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 + +#include + +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