diff mbox series

[RFC,6/6] fs: bind: add configfs type for bind mounts

Message ID 1574887085.21593.13.camel@HansenPartnership.com (mailing list archive)
State New, archived
Headers show
Series [RFC,1/6] logger: add a limited buffer logging facility | expand

Commit Message

James Bottomley Nov. 27, 2019, 8:38 p.m. UTC
This can do the equivalent of open_tree and also do bind mount
reconfiguration from ro to rw and vice versa.

To get the equvalent of open tree you need to do

   mnt = open("/path/to/tree", O_PATH);
   fd = configfs_open(bind, O_CLOEXEC);
   configfs_action(fd, CONFIGFD_SET_FD, "pathfd", NULL, mnt);
   configfs_action(fd, CONFIGFD_SET_FLAG, "detached", NULL, 0);
   configfs_action(fd, CONFIGFD_SET_FLAG, "recursive", NULL, 0);
   configfs_action(fd, CONFIGFD_CMD_CREATE, NULL, NULL, 0);
   configfs_action(fd, CONFIGFD_GET_FD, "bindfd", &bfd, NULL, O_CLOEXEC);

And bfd will now contain the file descriptor to pass to move_tree.
There is a deficiency over the original implementation in that the
open system call has no way of clearing the LOOKUP_AUTOMOUNT path, but
that's fixable.

To do a mount reconfigure to change the bind mount to readonly do

   mnt = open("/path/to/tree", O_PATH);
   fd = configfs_open(bind, O_CLOEXEC);
   configfs_action(fd, CONFIGFD_SET_FD, "pathfd", NULL, mnt);
   configfs_action(fd, CONFIGFD_SET_FLAG, "ro", NULL, 0);
   configfs_action(fd, CONFIGFD_CMD_RECONFIGURE, NULL, NULL, 0);

And the bind properties will be changed.  You can also pass the "rw"
flag to reset the read only.

Signed-off-by: James Bottomley <James.Bottomley@HansenPartnership.com>
---
 fs/Makefile |   2 +-
 fs/bind.c   | 193 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 194 insertions(+), 1 deletion(-)
 create mode 100644 fs/bind.c
diff mbox series

Patch

diff --git a/fs/Makefile b/fs/Makefile
index 569563f6c0d5..c676ca15c644 100644
--- a/fs/Makefile
+++ b/fs/Makefile
@@ -14,7 +14,7 @@  obj-y :=	open.o read_write.o file_table.o super.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 \
-		configfd.o
+		configfd.o bind.o
 
 ifeq ($(CONFIG_BLOCK),y)
 obj-y +=	buffer.o block_dev.o direct-io.o mpage.o
diff --git a/fs/bind.c b/fs/bind.c
new file mode 100644
index 000000000000..905572c70260
--- /dev/null
+++ b/fs/bind.c
@@ -0,0 +1,193 @@ 
+// SPDX-License-Identifier: GPL-2.0-only
+/* Dummy configfd handler for doing context based configuration
+ * on bind mounts
+ *
+ * Copyright (C) James.Bottomley@HansenPartnership.com
+ */
+
+#include <linux/configfd.h>
+#include <linux/file.h>
+#include <linux/fs.h>
+#include <linux/mount.h>
+#include <linux/nsproxy.h>
+
+#include "internal.h"
+#include "mount.h"
+
+struct bind_data {
+	bool		ro:1;
+	bool		detached:1;
+	bool		recursive:1;
+	struct file	*file;
+	struct file	*retfile;
+};
+
+struct bind_data *to_bind_data(const struct configfd_context *cfc)
+{
+	return cfc->data;
+}
+
+static int bind_set_fd(const struct configfd_context *cfc,
+		       struct configfd_param *p)
+{
+	struct bind_data *bd = to_bind_data(cfc);
+	struct path *path;
+
+	if (strcmp(p->key, "pathfd") != 0)
+		return -EINVAL;
+
+	path = &p->file->f_path;
+
+	if (cfc->op == CONFIGFD_CMD_RECONFIGURE &&
+	    path->mnt->mnt_root != path->dentry) {
+		logger_err(cfc->log, "pathfd must be a bind mount");
+		return -EINVAL;
+	}
+	bd->file = p->file;
+	p->file = NULL;	/* we now own */
+	return 0;
+}
+
+static int bind_set_flag(const struct configfd_context *cfc,
+			 struct configfd_param *p)
+{
+	struct bind_data *bd = to_bind_data(cfc);
+
+	if (strcmp(p->key, "ro") == 0 &&
+	    cfc->op == CONFIGFD_CMD_RECONFIGURE) {
+		bd->ro = true;
+	} else if (strcmp(p->key, "rw") == 0 &&
+		   cfc->op == CONFIGFD_CMD_RECONFIGURE) {
+		bd->ro = false;
+	} else if (strcmp(p->key, "recursive") == 0 &&
+		   cfc->op == CONFIGFD_CMD_CREATE) {
+		bd->recursive = true;
+	} else if (strcmp(p->key, "detached") == 0 &&
+		   cfc->op == CONFIGFD_CMD_CREATE) {
+		if (!ns_capable(current->nsproxy->mnt_ns->user_ns,
+				CAP_SYS_ADMIN)) {
+			logger_err(cfc->log, "bind set: insufficient permission for detached tree");
+			return -EPERM;
+		}
+		bd->detached = true;
+	} else {
+		logger_err(cfc->log, "bind set: invalid flag %s", p->key);
+		return -EINVAL;
+	}
+	return 0;
+}
+static int bind_set(const struct configfd_context *cfc,
+		    struct configfd_param *p)
+{
+	switch (p->cmd) {
+	case CONFIGFD_SET_FLAG:
+		return bind_set_flag(cfc, p);
+	case CONFIGFD_SET_FD:
+		return bind_set_fd(cfc, p);
+	default:
+		logger_err(cfc->log, "bind only takes a flag or fd argument");
+		return -EINVAL;
+	}
+}
+
+static int bind_get(const struct configfd_context *cfc,
+		    struct configfd_param *p)
+{
+	struct bind_data *bd = to_bind_data(cfc);
+
+	if (strcmp(p->key, "bindfd") != 0 || p->cmd != CONFIGFD_GET_FD)
+		return -EINVAL;
+
+	if (!bd->retfile)
+		return -EINVAL;
+
+	p->file = bd->retfile;
+	bd->retfile = NULL;
+
+	return 0;
+}
+
+static int bind_reconfigure(const struct configfd_context *cfc)
+{
+	struct bind_data *bd = to_bind_data(cfc);
+	unsigned int mnt_flags = 0;
+
+	if (!bd->file) {
+		logger_err(cfc->log, "bind reconfigure: fd must be set");
+		return -EINVAL;
+	}
+	if (bd->ro)
+		mnt_flags |= MNT_READONLY;
+
+	return do_reconfigure_mnt(&bd->file->f_path, mnt_flags);
+}
+
+static int bind_create(const struct configfd_context *cfc)
+{
+	struct bind_data *bd = to_bind_data(cfc);
+	struct path *p;
+	struct file *f;
+
+	if (!bd->file) {
+		logger_err(cfc->log, "bind create: fd must be set");
+		return -EINVAL;
+	}
+	if (bd->recursive && !bd->detached) {
+		logger_err(cfc->log, "bind create: recursive cannot be set without detached");
+		return -EINVAL;
+	}
+
+	p = &bd->file->f_path;
+
+	if (bd->detached)
+		f = open_detached_copy(p, bd->recursive);
+	else
+		f = dentry_open(p, O_PATH, current_cred());
+	if (IS_ERR(f))
+		return PTR_ERR(f);
+
+	bd->retfile = f;
+	return 0;
+}
+
+static int bind_act(const struct configfd_context *cfc, unsigned int cmd)
+{
+	switch (cmd) {
+	case CONFIGFD_CMD_RECONFIGURE:
+		return bind_reconfigure(cfc);
+	case CONFIGFD_CMD_CREATE:
+		return bind_create(cfc);
+	default:
+		logger_err(cfc->log, "bind only responds to reconfigure or create actions");
+		return -EINVAL;
+	}
+}
+
+static void bind_free(const struct configfd_context *cfc)
+{
+	struct bind_data *bd = to_bind_data(cfc);
+
+	if (bd->file)
+		fput(bd->file);
+}
+
+static struct configfd_ops bind_type_ops = {
+	.free = bind_free,
+	.get = bind_get,
+	.set = bind_set,
+	.act = bind_act,
+};
+
+static struct configfd_type bind_type = {
+	.name		= "bind",
+	.ops		= &bind_type_ops,
+	.data_size	= sizeof(struct bind_data),
+};
+
+static int __init bind_setup(void)
+{
+	configfd_type_register(&bind_type);
+
+	return 0;
+}
+fs_initcall(bind_setup);