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();
DBG(CXT, ul_debugobj(cxt, "----> allocate %s",
cxt->restricted ? "[RESTRICTED]" : ""));
-
return 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.
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;
*
* 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
cxt->syscall_status = 0;
DBG(CXT, ul_debugobj(cxt, "FAKE mount(2) "
- "[source=%s, target=%s, type=%s, "
+ "[source=%s, target=%s, type=%s,"
" mountflags=0x%08lx, mountdata=%s]",
src, target, type,
flags, cxt->mountdata ? "yes" : "<none>"));
}
DBG(CXT, ul_debugobj(cxt, "mount(2) "
- "[source=%s, target=%s, type=%s, "
+ "[source=%s, target=%s, type=%s,"
" mountflags=0x%08lx, mountdata=%s]",
src, target, type,
flags, cxt->mountdata ? "yes" : "<none>"));
return rc;
}
+/*
+ * Process X-mount.owner=, X-mount.group=, X-mount.mode=.
+ */
+static int parse_ownership_mode(struct libmnt_context *cxt)
+{
+ int rc;
+ char *value;
+ size_t valsz;
+
+ const char *o = mnt_fs_get_user_options(cxt->fs);
+ if (!o)
+ return 0;
+
+ if ((rc = mnt_optstr_get_option(o, "X-mount.owner", &value, &valsz)) < 0)
+ return -MNT_ERR_MOUNTOPT;
+ if (!rc) {
+ if (!valsz)
+ return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+
+ if (mnt_parse_uid(value, valsz, &cxt->tgt_owner))
+ return -MNT_ERR_MOUNTOPT;
+ }
+
+ if ((rc = mnt_optstr_get_option(o, "X-mount.group", &value, &valsz)) < 0)
+ return -MNT_ERR_MOUNTOPT;
+ if (!rc) {
+ if (!valsz)
+ return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+
+ if (mnt_parse_gid(value, valsz, &cxt->tgt_group))
+ return -MNT_ERR_MOUNTOPT;
+ }
+
+ if ((rc = mnt_optstr_get_option(o, "X-mount.mode", &value, &valsz)) < 0)
+ return -MNT_ERR_MOUNTOPT;
+ if (!rc) {
+ if (!valsz)
+ return errno = EINVAL, -MNT_ERR_MOUNTOPT;
+
+ if ((rc = mnt_parse_mode(value, valsz, &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
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);
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) {
+ DBG(CXT, ul_debugobj(cxt, "mount: lchown(%s, %u, %u)", target, cxt->tgt_owner, cxt->tgt_group));
+ if (lchown(target, cxt->tgt_owner, cxt->tgt_group) == -1)
+ return -MNT_ERR_CHOWN;
+ }
+
+ if (cxt->tgt_mode != (mode_t) -1) {
+ DBG(CXT, ul_debugobj(cxt, "mount: chmod(%s, %04o)", target, cxt->tgt_mode));
+ if (chmod(target, cxt->tgt_mode) == -1)
+ return -MNT_ERR_CHMOD;
+ }
+
+ return 0;
+}
+
/**
* mnt_context_do_mount
* @cxt: context
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;
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)
* 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
/*
*
* [u]mount(8) exit code: out of memory, cannot fork, ...
*/
-
#define MNT_EX_SYSERR 2
/**
extern char *mnt_get_username(const uid_t uid);
extern int mnt_get_uid(const char *username, uid_t *uid);
extern int mnt_get_gid(const char *groupname, gid_t *gid);
+extern int mnt_parse_uid(const char *user, size_t user_len, uid_t *gid);
+extern int mnt_parse_gid(const char *group, size_t group_len, gid_t *gid);
+extern int mnt_parse_mode(const char *mode, size_t mode_len, mode_t *gid);
extern int mnt_in_group(gid_t gid);
extern int mnt_open_uniq_filename(const char *filename, char **name);
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() */
/*
* Converts value from @optstr addressed by @name to uid.
*
- * Returns: 0 on success, 1 if not found, <0 on error
+ * Returns: 0 on success, <0 on error
*/
int mnt_optstr_get_uid(const char *optstr, const char *name, uid_t *uid)
{
char *value = NULL;
size_t valsz = 0;
- char buf[sizeof(stringify_value(UINT64_MAX))];
int rc;
- uint64_t num;
assert(optstr);
assert(name);
if (rc != 0)
goto fail;
- if (valsz > sizeof(buf) - 1) {
- rc = -ERANGE;
+ rc = mnt_parse_uid(value, valsz, uid);
+ if (rc != 0) {
+ rc = -errno;
goto fail;
}
- mem2strcpy(buf, value, valsz, sizeof(buf));
-
- rc = ul_strtou64(buf, &num, 10);
- if (rc != 0)
- goto fail;
- if (num > ULONG_MAX || (uid_t) num != num) {
- rc = -ERANGE;
- goto fail;
- }
- *uid = (uid_t) num;
return 0;
fail:
return rc;
}
+static int parse_uid_numeric(const char *value, size_t valsz, uid_t *uid)
+{
+ uint64_t num;
+
+ assert(value);
+ assert(uid);
+
+ if (valsz > sizeof(stringify_value(UINT64_MAX)) - 1) {
+ errno = ERANGE;
+ goto fail;
+ }
+
+ if (ul_strtou64(value, &num, 10) != 0) {
+ errno = ENOENT;
+ goto fail;
+ }
+ if (num > ULONG_MAX || (uid_t) num != num) {
+ errno = ERANGE;
+ goto fail;
+ }
+ *uid = (uid_t) num;
+
+ return 0;
+fail:
+ DBG(UTILS, ul_debug("failed to convert '%s' to number [errno=%d]", value, errno));
+ return -1;
+}
+
+/* POSIX-parse user_len-sized user; -1 and errno set, or 0 on success */
+int mnt_parse_uid(const char *user, size_t user_len, uid_t *uid)
+{
+ char *user_tofree = NULL;
+ int rc;
+
+ if (user[user_len]) {
+ user = user_tofree = strndup(user, user_len);
+ if (!user)
+ return -1;
+ }
+
+ rc = mnt_get_uid(user, uid);
+ if (rc != 0 && isdigit(*user))
+ rc = parse_uid_numeric(user, user_len, uid);
+
+ free(user_tofree);
+ return rc;
+}
+
+static int parse_gid_numeric(const char *value, size_t valsz, gid_t *gid)
+{
+ uint64_t num;
+
+ assert(value);
+ assert(gid);
+
+ if (valsz > sizeof(stringify_value(UINT64_MAX)) - 1) {
+ errno = ERANGE;
+ goto fail;
+ }
+
+ if (ul_strtou64(value, &num, 10) != 0) {
+ errno = ENOENT;
+ goto fail;
+ }
+ if (num > ULONG_MAX || (gid_t) num != num) {
+ errno = ERANGE;
+ goto fail;
+ }
+ *gid = (gid_t) num;
+
+ return 0;
+fail:
+ DBG(UTILS, ul_debug("failed to convert '%s' to number [errno=%d]", value, errno));
+ return -1;
+}
+
+/* POSIX-parse group_len-sized group; -1 and errno set, or 0 on success */
+int mnt_parse_gid(const char *group, size_t group_len, gid_t *gid)
+{
+ char *group_tofree = NULL;
+ int rc;
+
+ if (group[group_len]) {
+ group = group_tofree = strndup(group, group_len);
+ if (!group)
+ return -1;
+ }
+
+ rc = mnt_get_gid(group, gid);
+ if (rc != 0 && isdigit(*group))
+ rc = parse_gid_numeric(group, group_len, gid);
+
+ free(group_tofree);
+ return rc;
+}
+
+int mnt_parse_mode(const char *mode, size_t mode_len, mode_t *uid)
+{
+ char buf[sizeof(stringify_value(UINT32_MAX))];
+ uint32_t num;
+
+ assert(mode);
+ assert(uid);
+
+ if (mode_len > sizeof(buf) - 1) {
+ errno = ERANGE;
+ goto fail;
+ }
+ mem2strcpy(buf, mode, mode_len, sizeof(buf));
+
+ if (ul_strtou32(buf, &num, 8) != 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) mode_len, mode, errno));
+ return -1;
+}
+
int mnt_in_group(gid_t gid)
{
int rc = 0, n, i;
**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.
--- /dev/null
+#!/bin/bash
+# SPDX-License-Identifier: 0BSD
+
+
+TS_TOPDIR="${0%/*}/../.."
+TS_DESC="X-mount.{owner,group,mode}="
+
+. $TS_TOPDIR/functions.sh
+ts_init "$*"
+
+ts_check_test_command "$TS_CMD_MOUNT"
+ts_check_test_command "$TS_CMD_UMOUNT"
+
+ts_skip_nonroot
+ts_check_losetup
+ts_check_prog "mkfs.ext2"
+ts_check_prog "id"
+ts_check_prog "ls"
+
+
+do_one() {
+ expected="$1"; shift
+ what="$1"; shift
+ where="$1"; shift
+ $TS_CMD_MOUNT "$@" "$what" "$where" >> $TS_OUTPUT 2>> $TS_ERRLOG
+ read -r m _ o g _ < <(ls -nd "$where")
+ actual="$m $o $g"
+ [ "$actual" = "$expected" ] || echo "$*: $actual != $expected" >> $TS_ERRLOG
+ $TS_CMD_UMOUNT "$where" >> $TS_OUTPUT 2>> $TS_ERRLOG
+}
+
+ts_device_init
+
+mkfs.ext2 "$TS_LODEV" > /dev/null 2>&1 || ts_die "Cannot make ext2 on $TS_LODEV"
+ts_device_has "TYPE" "ext2" "$TS_LODEV" || ts_die "Cannot find ext2 on $TS_LODEV"
+
+user_1="$(id -un 1)"
+group_2="$(id -gn 2)"
+
+
+mkdir -p "$TS_MOUNTPOINT"
+
+do_one "drwxr-xr-x 0 0" "$TS_LODEV" "$TS_MOUNTPOINT"
+do_one "drwxr-xr-x 1 0" "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.owner=$user_1"
+do_one "drwxr-xr-x 1 2" "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.group=$group_2"
+do_one "d-w--wxr-T 132 2" "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.owner=132,X-mount.mode=1234"
+do_one "d-ws-w---x 132 123" "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.mode=4321,X-mount.group=123"
+do_one "d-ws-w---x 1 321" "$TS_LODEV" "$TS_MOUNTPOINT" -o "X-mount.owner=$user_1,X-mount.group=321"
+
+
+> "$TS_MOUNTPOINT/bind"
+> "$TS_MOUNTPOINT/bindsrc"
+
+do_one "-rw-r--r-- 0 0" "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind
+do_one "-rw-r--r-- 1 0" "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.owner=$user_1"
+do_one "-rw-r--r-- 1 2" "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.group=$group_2"
+do_one "--w--wxr-T 132 2" "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.owner=132,X-mount.mode=1234"
+do_one "--ws-w---x 132 123" "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.mode=4321,X-mount.group=123"
+do_one "--wx-w---x 1 321" "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT/bind" --bind -o "X-mount.owner=$user_1,X-mount.group=321"
+
+
+rm -fd "$TS_MOUNTPOINT/bind" "$TS_MOUNTPOINT/bindsrc" "$TS_MOUNTPOINT"
+
+ts_log "Success"
+ts_finalize