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

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

 



Thanks for the pointers!

Please see below for updated scissor-patch, which
  * splits parsing from setting
  * integrates parsing into _prepare_mount()
  * integrates setting into _do_mount()
  * has tests
  * returns MNT_ERR_MOUNTOPT (with errno set to an unambiguous value)
    on parsing errors
  * returns MNT_ERR_CHOWN/MNT_ERR_CHMOD on setting errors
  * explicitly mentions that username/group parsing happens in the
    target namespace, which I assume is what you meant by
	"before mnt_context_switch_ns()", because that's literal there
	(I also don't know if that's, uh, valid?);
	if you meant the transitive namespace switch via
	mnt_context_switch_target_ns() then it's trivial to hoist it up
	above it in _prepare() ‒ this is why I marked this RFC

Best,
наб

-- >8 --
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
---
 .gitignore                   |   1 +
 libmount/src/context.c       |  12 ++-
 libmount/src/context_mount.c | 157 +++++++++++++++++++++++++++++++++++
 libmount/src/libmount.h.in   |  13 ++-
 libmount/src/mountP.h        |   4 +
 sys-utils/mount.8.adoc       |   6 ++
 6 files changed, 189 insertions(+), 4 deletions(-)

diff --git a/.gitignore b/.gitignore
index 840f64615..51a019307 100644
--- a/.gitignore
+++ b/.gitignore
@@ -71,6 +71,7 @@ ylwrap
 /blkdiscard
 /blkzone
 /blkid
+/blkpr
 /blockdev
 /cal
 /cfdisk
diff --git a/libmount/src/context.c b/libmount/src/context.c
index 99ca58b9f..7927012d2 100644
--- a/libmount/src/context.c
+++ b/libmount/src/context.c
@@ -57,6 +57,10 @@ struct libmnt_context *mnt_new_context(void)
 	if (!cxt)
 		return NULL;
 
+	cxt->tgt_owner = (uid_t) -1;
+	cxt->tgt_group = (gid_t) -1;
+	cxt->tgt_mode = (mode_t) -1;
+
 	INIT_LIST_HEAD(&cxt->addmounts);
 
 	ruid = getuid();
@@ -76,7 +80,6 @@ struct libmnt_context *mnt_new_context(void)
 	DBG(CXT, ul_debugobj(cxt, "----> allocate %s",
 				cxt->restricted ? "[RESTRICTED]" : ""));
 
-
 	return cxt;
 }
 
@@ -130,7 +133,6 @@ void mnt_free_context(struct libmnt_context *cxt)
  *	mnt_context_set_options_pattern(cxt, NULL);
  *	mnt_context_set_target_ns(cxt, NULL);
  *
- *
  * to reset this stuff.
  *
  * Returns: 0 on success, negative number in case of error.
@@ -155,6 +157,10 @@ int mnt_reset_context(struct libmnt_context *cxt)
 	free(cxt->orig_user);
 	free(cxt->subdir);
 
+	cxt->tgt_owner = (uid_t) -1;
+	cxt->tgt_group = (gid_t) -1;
+	cxt->tgt_mode = (mode_t) -1;
+
 	cxt->fs = NULL;
 	cxt->mtab = NULL;
 	cxt->utab = NULL;
@@ -3108,7 +3114,7 @@ static void close_ns(struct libmnt_ns *ns)
  *
  * This function sets errno to ENOSYS and returns error if libmount is
  * compiled without namespaces support.
-*
+ *
  * Returns: 0 on success, negative number in case of error.
  *
  * Since: 2.33
diff --git a/libmount/src/context_mount.c b/libmount/src/context_mount.c
index f09e68860..13209d861 100644
--- a/libmount/src/context_mount.c
+++ b/libmount/src/context_mount.c
@@ -1020,6 +1020,132 @@ static int do_mount_by_pattern(struct libmnt_context *cxt, const char *pattern)
 	return rc;
 }
 
+/* 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) {
+		errno = ENOENT;
+		goto fail;
+	}
+	if (num > ULONG_MAX || (unsigned) num != num) {
+		errno = ERANGE;
+		goto fail;
+	}
+	*uid = (unsigned) num;
+
+	return 0;
+fail:
+	DBG(UTILS, ul_debug("failed to convert '%.*s' to number [errno=%d]", (int) valsz, value, errno));
+	return -1;
+}
+
+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) {
+		errno = EINVAL;
+		goto fail;
+	}
+	if (num > 07777) {
+		errno = ERANGE;
+		goto fail;
+	}
+	*uid = (mode_t) num;
+
+	return 0;
+fail:
+	DBG(UTILS, ul_debug("failed to convert '%.*s' to mode [errno=%d]", (int) valsz, value, errno));
+	return -1;
+}
+
+/*
+ * Process X-mount.owner=, X-mount.group=, X-mount.mode=.
+ */
+static int parse_ownership_mode(struct libmnt_context *cxt)
+{
+	int rc;
+
+	const char *o = mnt_fs_get_user_options(cxt->fs);
+	if (!o)
+		return 0;
+
+	char *owner;
+	size_t owner_len;
+	if ((rc = mnt_optstr_get_option(o, "X-mount.owner", &owner, &owner_len)) < 0) {
+		return -MNT_ERR_MOUNTOPT;}
+	if (!rc) {
+		if (!owner_len)
+			return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+
+		char *owner_tofree = NULL;
+		rc = mnt_get_uid(owner[owner_len] ? (owner_tofree = strndup(owner, owner_len)) : owner, &cxt->tgt_owner);
+		free(owner_tofree);
+		if (cxt->tgt_owner == (uid_t) -1 && isdigit(*owner))
+			rc = parse_ugid(owner, owner_len, &cxt->tgt_owner);
+		if (rc)
+			return -MNT_ERR_MOUNTOPT;
+	}
+
+	char *group;
+	size_t group_len;
+	if ((rc = mnt_optstr_get_option(o, "X-mount.group", &group, &group_len)) < 0)
+		return -MNT_ERR_MOUNTOPT;
+	if (!rc) {
+		if (!group_len)
+			return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+
+		char *group_tofree = NULL;
+		rc = mnt_get_uid(group[group_len] ? (group_tofree = strndup(group, group_len)) : group, &cxt->tgt_group);
+		free(group_tofree);
+		if (cxt->tgt_group == (uid_t) -1 && isdigit(*group))
+			rc = parse_ugid(group, group_len, &cxt->tgt_group);
+		if (rc)
+			return errno = ENOENT, -MNT_ERR_MOUNTOPT;
+	}
+
+	char *mode;
+	size_t mode_len;
+	if ((rc = mnt_optstr_get_option(o, "X-mount.mode", &mode, &mode_len)) < 0)
+		return -MNT_ERR_MOUNTOPT;
+	if (!rc) {
+		if (!mode_len)
+			return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+		if ((rc = parse_mode(mode, mode_len, &cxt->tgt_mode)))
+			return -MNT_ERR_MOUNTOPT;
+	}
+
+	DBG(CXT, ul_debugobj(cxt, "ownership %d:%d, mode %04o", cxt->tgt_owner, cxt->tgt_group, cxt->tgt_mode));
+
+	return 0;
+}
+
 /**
  * mnt_context_prepare_mount:
  * @cxt: context
@@ -1064,6 +1190,8 @@ int mnt_context_prepare_mount(struct libmnt_context *cxt)
 		rc = mnt_context_guess_fstype(cxt);
 	if (!rc)
 		rc = mnt_context_prepare_target(cxt);
+	if (!rc)
+		rc = parse_ownership_mode(cxt);
 	if (!rc)
 		rc = mnt_context_prepare_helper(cxt, "mount", NULL);
 
@@ -1089,6 +1217,21 @@ end:
 	return rc;
 }
 
+static int set_ownership_mode(struct libmnt_context *cxt)
+{
+	const char *target = mnt_fs_get_target(cxt->fs);
+
+	if (cxt->tgt_owner != (uid_t) -1 || cxt->tgt_group != (uid_t) -1)
+		if (lchown(target, cxt->tgt_owner, cxt->tgt_group) == -1)
+			return -MNT_ERR_CHOWN;
+
+	if (cxt->tgt_mode != (mode_t) -1)
+		if (chmod(target, cxt->tgt_mode) == -1)
+			return -MNT_ERR_CHMOD;
+
+	return 0;
+}
+
 /**
  * mnt_context_do_mount
  * @cxt: context
@@ -1191,6 +1334,9 @@ int mnt_context_do_mount(struct libmnt_context *cxt)
 	if (mnt_context_is_veritydev(cxt))
 		mnt_context_deferred_delete_veritydev(cxt);
 
+	if (!res)
+		res = set_ownership_mode(cxt);
+
 	if (!mnt_context_switch_ns(cxt, ns_old))
 		return -MNT_ERR_NAMESPACE;
 
@@ -1841,7 +1987,18 @@ int mnt_context_get_mount_excode(
 			if (buf)
 				snprintf(buf, bufsz, _("filesystem was mounted, but failed to switch namespace back"));
 			return MNT_EX_SYSERR;
+		}
 
+		if (rc == -MNT_ERR_CHOWN) {
+			if (buf)
+				snprintf(buf, bufsz, _("filesystem was mounted, but failed to change ownership to %u:%u: %m"), cxt->tgt_owner, cxt->tgt_group);
+			return MNT_EX_SYSERR;
+		}
+
+		if (rc == -MNT_ERR_CHMOD) {
+			if (buf)
+				snprintf(buf, bufsz, _("filesystem was mounted, but failed to change mode to %04o: %m"), cxt->tgt_mode);
+			return MNT_EX_SYSERR;
 		}
 
 		if (rc < 0)
diff --git a/libmount/src/libmount.h.in b/libmount/src/libmount.h.in
index 43d8e44ce..0ab1d6ece 100644
--- a/libmount/src/libmount.h.in
+++ b/libmount/src/libmount.h.in
@@ -220,6 +220,18 @@ enum {
  * filesystem mounted, but --onlyonce specified
  */
 #define MNT_ERR_ONLYONCE    5010
+/**
+ * MNT_ERR_CHOWN:
+ *
+ * filesystem mounted, but subsequent X-mount.owner=/X-mount.group= lchown(2) failed
+ */
+#define MNT_ERR_CHOWN    5011
+/**
+ * MNT_ERR_CHMOD:
+ *
+ * filesystem mounted, but subsequent X-mount.mode= chmod(2) failed
+ */
+#define MNT_ERR_CHMOD    5012
 
 
 /*
@@ -246,7 +258,6 @@ enum {
  *
  * [u]mount(8) exit code: out of memory, cannot fork, ...
  */
-
 #define MNT_EX_SYSERR	2
 
 /**
diff --git a/libmount/src/mountP.h b/libmount/src/mountP.h
index 561ddcd8c..c63eb1029 100644
--- a/libmount/src/mountP.h
+++ b/libmount/src/mountP.h
@@ -294,6 +294,10 @@ struct libmnt_context
 
 	char	*subdir;		/* X-mount.subdir= */
 
+	uid_t	tgt_owner;		/* X-mount.owner= */
+	gid_t	tgt_group;		/* X-mount.group= */
+	mode_t	tgt_mode;		/* X-mount.mode= */
+
 	struct libmnt_fs *fs;		/* filesystem description (type, mountpoint, device, ...) */
 	struct libmnt_fs *fs_template;	/* used for @fs on mnt_reset_context() */
 
diff --git a/sys-utils/mount.8.adoc b/sys-utils/mount.8.adoc
index 7465fcd0d..5f0ddd05b 100644
--- a/sys-utils/mount.8.adoc
+++ b/sys-utils/mount.8.adoc
@@ -636,6 +636,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. Names resolved in the target mount namespace, see *-N*.
+
+*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