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