[RFC] libmount: accept X-mount.{owner,group,mode}=

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Which take an user, group, and mode, respectively, and set them on the
target after mounting

This is vaguely similar to tmpfs(5)'s [ug]id= and mode= options,
but we POSIX-parse the user- and group names

Oft requested in systemd/zram-generator, since a common use-case
is to use it to create /tmp or an equivalent directory that needs
to be a=rwx,o+t (or a user's private temp that needs to be owned
by them) ‒ this is impossible without terrible hacks, cf.
https://github.com/systemd/zram-generator/issues/150,
https://github.com/systemd/zram-generator/issues/146, &c.

This started off as a Set{User,Group,Mode}= systemd mount unit,
but was poetterung into libmount options:
https://github.com/systemd/systemd/pull/22889
---
As a PoC:
$ truncate -s40G /tmp/ext4; /sbin/mkfs.ext4 /tmp/ext4
# ./mount -o \
    X-mount.owner=nabijaczleweli,X-mount.group=1212,X-mount.mode=1234 \
    /tmp/ext4 /tmp/a\ +\ b/
$ l -d /tmp/a\ +\ b/
d-w--wxr-T 3 nabijaczleweli 1212 4.0K Mar 30 20:03 '/tmp/a + b/'

I've marked this RFC because the failures are, well, not pretty
(for example, given a mis-spelled or unset username):
  mount: /tmp/a + b: filesystem was mounted, but any subsequent
  operation failed: Invalid argument.

But I'm not sure how to proceed. I've stuffed the parsing and chowning
stage into mnt_context_finalize_mount() for ease-of-PoC, but should:
  (a) the post-syscall error handling in mnt_context_get_mount_excode()
      be extended to recognise MNT_ERR_MOUNTOPT?
  (b) the parsing/chowning stages be split (parsing in pre-mount prep,
      chowning in post-mount)? with a new MNT_ERR_ flag potentially?
  (c) something else?

Best,
наб

Please keep me in CC, as I'm not subscribed.

 libmount/src/context_mount.c | 136 +++++++++++++++++++++++++++++++++++
 sys-utils/mount.8.adoc       |   6 ++
 2 files changed, 142 insertions(+)

diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c
index 1fc3ff2cc..290f55ea7 100644
--- a/libmount/src/context_mount.c
+++ b/libmount/src/context_mount.c
@@ -1229,6 +1229,138 @@ static int is_source_already_rdonly(struct libmnt_context *cxt)
 	return opts && mnt_optstr_get_option(opts, "ro", NULL, NULL) == 0;
 }
 
+/* Extracted from mnt_optstr_get_uid() */
+static int parse_ugid(const char *value, size_t valsz, unsigned *uid)
+{
+	char buf[sizeof(stringify_value(UINT64_MAX))];
+	int rc;
+	uint64_t num;
+
+	assert(value);
+	assert(uid);
+
+	if (valsz > sizeof(buf) - 1) {
+		rc = -ERANGE;
+		goto fail;
+	}
+	mem2strcpy(buf, value, valsz, sizeof(buf));
+
+	rc = ul_strtou64(buf, &num, 10);
+	if (rc != 0)
+		goto fail;
+	if (num > ULONG_MAX || (unsigned) num != num) {
+		rc = -ERANGE;
+		goto fail;
+	}
+	*uid = (unsigned) num;
+
+	return 0;
+fail:
+	DBG(UTILS, ul_debug("failed to convert '%.*s' to number [rc=%d]", (int) valsz, value, rc));
+	return rc;
+}
+
+static int parse_mode(const char *value, size_t valsz, mode_t *uid)
+{
+	char buf[sizeof(stringify_value(UINT64_MAX))];
+	int rc;
+	uint64_t num;
+
+	assert(value);
+	assert(uid);
+
+	if (valsz > sizeof(buf) - 1) {
+		rc = -ERANGE;
+		goto fail;
+	}
+	mem2strcpy(buf, value, valsz, sizeof(buf));
+
+	rc = ul_strtou64(buf, &num, 8);
+	if (rc != 0)
+		goto fail;
+	if (num > 07777) {
+		rc = -ERANGE;
+		goto fail;
+	}
+	*uid = (mode_t) num;
+
+	return 0;
+fail:
+	DBG(UTILS, ul_debug("failed to convert '%.*s' to mode [rc=%d]", (int) valsz, value, rc));
+	return rc;
+}
+
+/*
+ * Process X-mount.owner=, X-mount.group=, X-mount.mode=.
+ */
+static int set_ownership_mode(struct libmnt_context *cxt)
+{
+	int rc;
+
+	uid_t new_owner = (uid_t) -1;
+	uid_t new_group = (uid_t) -1;
+	mode_t new_mode = (mode_t) -1;
+
+	const char *o = mnt_fs_get_user_options(cxt->fs);
+
+	char *owner;
+	size_t owner_len;
+	if ((rc = mnt_optstr_get_option(o, "X-mount.owner", &owner, &owner_len)) < 0)
+		return rc;
+	if (!rc) {
+		if (!owner_len)
+			return -EINVAL;
+
+		char *owner_tofree = NULL;
+		rc = mnt_get_uid(owner[owner_len] ? (owner_tofree = strndup(owner, owner_len)) : owner, &new_owner);
+		free(owner_tofree);
+		if (new_owner == (uid_t) -1 && isdigit(*owner))
+			rc = parse_ugid(owner, owner_len, &new_owner);
+		if (rc)
+			return rc;
+	}
+
+	char *group;
+	size_t group_len;
+	if ((rc = mnt_optstr_get_option(o, "X-mount.group", &group, &group_len)) < 0)
+		return rc;
+	if (!rc) {
+		if (!group_len)
+			return -EINVAL;
+
+		char *group_tofree = NULL;
+		rc = mnt_get_uid(group[group_len] ? (group_tofree = strndup(group, group_len)) : group, &new_group);
+		free(group_tofree);
+		if (new_group == (uid_t) -1 && isdigit(*group))
+			rc = parse_ugid(group, group_len, &new_group);
+		if (rc)
+			return rc;
+	}
+
+	char *mode;
+	size_t mode_len;
+	if ((rc = mnt_optstr_get_option(o, "X-mount.mode", &mode, &mode_len)) < 0)
+		return rc;
+	if (!rc) {
+		if (!group_len)
+			return -EINVAL;
+		if ((rc = parse_mode(mode, mode_len, &new_mode)))
+			return rc;
+	}
+
+	const char *target = mnt_fs_get_target(cxt->fs);
+
+	if (new_owner != (uid_t) -1 || new_group != (uid_t) -1)
+		if (lchown(target, new_owner, new_group) == -1)
+			return -errno;
+
+	if (new_mode != (mode_t) -1)
+		if (chmod(target, new_mode) == -1)
+			return -errno;
+
+	return 0;
+}
+
 /**
  * mnt_context_finalize_mount:
  * @cxt: context
@@ -1250,6 +1382,8 @@ int mnt_context_finalize_mount(struct libmnt_context *cxt)
 	rc = mnt_context_prepare_update(cxt);
 	if (!rc)
 		rc = mnt_context_update_tabs(cxt);
+	if (!rc)
+		rc = set_ownership_mode(cxt);
 	return rc;
 }
 
@@ -1328,6 +1462,8 @@ again:
 		rc = mnt_context_do_mount(cxt);
 	if (!rc)
 		rc = mnt_context_update_tabs(cxt);
+	if (!rc)
+		rc = set_ownership_mode(cxt);
 
 	/*
 	 * Read-only device or already read-only mounted FS.
diff --git a/sys-utils/mount.8.adoc b/sys-utils/mount.8.adoc
index 343d7e297..53a0bbb34 100644
--- a/sys-utils/mount.8.adoc
+++ b/sys-utils/mount.8.adoc
@@ -633,6 +633,12 @@ Allow to make a target directory (mountpoint) if it does not exist yet. The opti
 **X-mount.subdir=**__directory__::
 Allow mounting sub-directory from a filesystem instead of the root directory. For now, this feature is implemented by temporary filesystem root directory mount in unshared namespace and then bind the sub-directory to the final mount point and umount the root of the filesystem. The sub-directory mount shows up atomically for the rest of the system although it is implemented by multiple *mount*(2) syscalls. This feature is EXPERIMENTAL.
 
+*X-mount.owner*=_username_|_UID_, *X-mount.group*=_group_|_GID_::
+Set _mountpoint_'s ownership after mounting.
+
+*X-mount.mode*=_mode_::
+Set _mountpoint_'s mode after mounting.
+
 *nosymfollow*::
 Do not follow symlinks when resolving paths. Symlinks can still be created, and *readlink*(1), *readlink*(2), *realpath*(1), and *realpath*(3) all still work properly.
 
-- 
2.30.2

Attachment: signature.asc
Description: PGP signature


[Index of Archives]     [Netdev]     [Ethernet Bridging]     [Linux Wireless]     [Kernel Newbies]     [Security]     [Linux for Hams]     [Netfilter]     [Bugtraq]     [Yosemite News]     [MIPS Linux]     [ARM Linux]     [Linux RAID]     [Linux Admin]     [Samba]

  Powered by Linux