free(cxt->helper);
free(cxt->orig_user);
+ free(cxt->subdir);
+ free(cxt->tmptgt);
cxt->fs = NULL;
cxt->mtab = NULL;
cxt->mountflags = 0;
cxt->user_mountflags = 0;
cxt->mountdata = NULL;
+ cxt->tmptgt = NULL;
+ cxt->subdir = NULL;
cxt->flags = MNT_FL_DEFAULT;
/* free additional mounts list */
goto failed;
if (strdup_between_structs(n, o, orig_user))
goto failed;
+ if (strdup_between_structs(n, o, subdir))
+ goto failed;
+ if (strdup_between_structs(n, o, tmptgt))
+ goto failed;
n->mountflags = o->mountflags;
n->mountdata = o->mountdata;
return rc;
}
+static int is_subdir_required(struct libmnt_context *cxt, int *rc)
+{
+ char *dir;
+ size_t sz;
+
+ assert(cxt);
+ assert(rc);
+
+ *rc = 0;
+
+ if (!cxt->fs
+ || !cxt->fs->user_optstr
+ || mnt_optstr_get_option(cxt->fs->user_optstr,
+ "X-mount.subdir", &dir, &sz) != 0)
+ return 0;
+
+ cxt->subdir = strndup(dir, sz);
+ if (!cxt->subdir)
+ *rc = -ENOMEM;
+ else if (asprintf(&cxt->tmptgt, "%s/mount.%d", MNT_TMPDIR, getpid()) < 0)
+ *rc = -ENOMEM;
+
+ return *rc == 0;
+}
+
static int is_mkdir_required(const char *tgt, struct libmnt_fs *fs, mode_t *mode, int *rc)
{
char *mstr = NULL;
}
}
+ /* X-mount.subdir= target */
+ if (rc == 0
+ && cxt->action == MNT_ACT_MOUNT
+ && (cxt->user_mountflags & MNT_MS_XFSTABCOMM)
+ && is_subdir_required(cxt, &rc)) {
+
+ DBG(CXT, ul_debugobj(cxt, "subdir %s required, temporary target: %s",
+ cxt->subdir, cxt->tmptgt));
+ }
+
+
if (!mnt_context_switch_ns(cxt, ns_old))
return -MNT_ERR_NAMESPACE;
return 0;
}
+static int do_mount_subdir(struct libmnt_context *cxt,
+ const char *root,
+ const char *subdir,
+ const char *target)
+{
+ char *src = NULL;
+ int rc = 0;
+
+ if (asprintf(&src, "%s/%s", root, subdir) < 0)
+ return -ENOMEM;
+
+ DBG(CXT, ul_debugobj(cxt, "mount subdir %s to %s", src, target));
+ if (mount(src, target, NULL, MS_BIND | MS_REC, NULL) != 0)
+ rc = -MNT_ERR_APPLYFLAGS;
+
+ DBG(CXT, ul_debugobj(cxt, "umount old root %s", root));
+ if (umount(root) != 0)
+ rc = -MNT_ERR_APPLYFLAGS;
+
+ free(src);
+ return rc;
+}
+
/*
* The default is to use fstype from cxt->fs, this could be overwritten by
* @try_type argument. If @try_type is specified then mount with MS_SILENT.
*/
static int do_mount(struct libmnt_context *cxt, const char *try_type)
{
- int rc = 0;
+ int rc = 0, old_ns_fd = -1;
const char *src, *target, *type;
unsigned long flags;
if (try_type)
flags |= MS_SILENT;
- DBG(CXT, ul_debugobj(cxt, "%smount(2) "
- "[source=%s, target=%s, type=%s, "
- " mountflags=0x%08lx, mountdata=%s]",
- mnt_context_is_fake(cxt) ? "(FAKE) " : "",
- src, target, type,
- flags, cxt->mountdata ? "yes" : "<none>"));
if (mnt_context_is_fake(cxt)) {
/*
*/
cxt->syscall_status = 0;
+ DBG(CXT, ul_debugobj(cxt, "FAKE mount(2) "
+ "[source=%s, target=%s, type=%s, "
+ " mountflags=0x%08lx, mountdata=%s]",
+ src, target, type,
+ flags, cxt->mountdata ? "yes" : "<none>"));
+
} else if (mnt_context_propagation_only(cxt)) {
/*
* propagation flags *only*
/*
* regular mount
*/
+
+ /* create unhared temporary target */
+ if (cxt->subdir) {
+ rc = mnt_unshared_mkdir(cxt->tmptgt,
+ S_IRWXU, &old_ns_fd);
+ if (rc)
+ return rc;
+ target = cxt->tmptgt;
+ }
+
+ DBG(CXT, ul_debugobj(cxt, "mount(2) "
+ "[source=%s, target=%s, type=%s, "
+ " mountflags=0x%08lx, mountdata=%s]",
+ src, target, type,
+ flags, cxt->mountdata ? "yes" : "<none>"));
+
if (mount(src, target, type, flags, cxt->mountdata)) {
cxt->syscall_status = -errno;
DBG(CXT, ul_debugobj(cxt, "mount(2) failed [errno=%d %m]",
-cxt->syscall_status));
- return -cxt->syscall_status;
+ rc = -cxt->syscall_status;
+ goto done;
}
- DBG(CXT, ul_debugobj(cxt, " success"));
+ DBG(CXT, ul_debugobj(cxt, " mount(2) success"));
cxt->syscall_status = 0;
/*
&& do_mount_additional(cxt, target, flags, NULL)) {
/* TODO: call umount? */
- return -MNT_ERR_APPLYFLAGS;
+ rc = -MNT_ERR_APPLYFLAGS;
+ goto done;
+ }
+
+ /*
+ * bind subdir to the real target, umount temporary target
+ */
+ if (cxt->subdir) {
+ target = mnt_fs_get_target(cxt->fs);
+ rc = do_mount_subdir(cxt, cxt->tmptgt, cxt->subdir, target);
+ if (rc)
+ goto done;
+ mnt_unshared_rmdir(cxt->tmptgt, old_ns_fd);
+ old_ns_fd = -1;
}
}
rc = mnt_fs_set_fstype(fs, try_type);
}
+done:
+ if (old_ns_fd >= 0)
+ mnt_unshared_rmdir(cxt->tmptgt, old_ns_fd);
+
return rc;
}
#define MNT_MNTTABDIR_EXT ".fstab"
/* library private paths */
+#define MNT_TMPDIR "/tmp/mount"
#define MNT_RUNTIME_TOPDIR "/run"
#define MNT_RUNTIME_TOPDIR_OLD "/dev"
extern int mnt_stat_mountpoint(const char *target, struct stat *st);
extern int mnt_lstat_mountpoint(const char *target, struct stat *st);
+extern int mnt_unshared_mkdir(const char *path, mode_t mode, int *old_ns_fd);
+extern int mnt_unshared_rmdir(const char *path, int old_ns_fd);
+
/* tab.c */
extern int is_mountinfo(struct libmnt_table *tb);
extern int mnt_table_set_parser_fltrcb( struct libmnt_table *tb,
char *fstype_pattern; /* for mnt_match_fstype() */
char *optstr_pattern; /* for mnt_match_options() */
+ char *subdir; /* X-mount.subdir= */
+ char *tmptgt; /* (unshared) private mount target */
+
struct libmnt_fs *fs; /* filesystem description (type, mountpoint, device, ...) */
struct libmnt_fs *fs_template; /* used for @fs on mnt_reset_context() */
return 1;
}
+/*
+ * like ul_mkdir_p(), but create a new namespace and mark (bind mount)
+ * the directory as private.
+ */
+int mnt_unshared_mkdir(const char *path, mode_t mode, int *old_ns_fd)
+{
+ int rc = 0, fd = -1, mounted = 0;
+
+ *old_ns_fd = -1;
+
+ if (!path || !old_ns_fd)
+ return -EINVAL;
+
+ /* create directory */
+ rc = ul_mkdir_p(path, mode);
+ if (rc)
+ goto fail;
+
+ /* remember the current namespace */
+ fd = open("/proc/self/ns/mnt", O_RDONLY | O_CLOEXEC);
+ if (fd < 0)
+ goto fail;
+
+ /* create new namespace */
+ if (unshare(CLONE_NEWNS) != 0)
+ goto fail;
+
+ /* make the directory private */
+ mounted = mount(path, path, "none", MS_BIND, NULL) == 0;
+ if (!mounted)
+ goto fail;
+ if (mount("none", path, NULL, MS_PRIVATE, NULL) != 0)
+ goto fail;
+
+ DBG(UTILS, ul_debug(" %s unshared", path));
+ *old_ns_fd = fd;
+ return 0;
+fail:
+ if (rc == 0)
+ rc = errno ? -errno : -EINVAL;
+ if (mounted)
+ umount(path);
+ if (fd >= 0) {
+ setns(fd, CLONE_NEWNS); /* restore original NS */
+ close(fd);
+ }
+ rmdir(path);
+ DBG(UTILS, ul_debug(" %s unshare failed", path));
+ return rc;
+}
+
+/*
+ * umount, rmdir and switch back to old namespace
+ */
+int mnt_unshared_rmdir(const char *path, int old_ns_fd)
+{
+ if (!path)
+ return -EINVAL;
+
+ umount(path);
+ rmdir(path);
+
+ if (old_ns_fd >= 0) {
+ setns(old_ns_fd, CLONE_NEWNS);
+ close(old_ns_fd);
+ }
+
+ DBG(UTILS, ul_debug(" %s removed", path));
+ return 0;
+}
+
#ifdef TEST_PROGRAM
static int test_match_fstype(struct libmnt_test *ts, int argc, char *argv[])
{
*X-mount.mkdir*[=_mode_]::
Allow to make a target directory (mountpoint) if it does not exit yet. The optional argument _mode_ specifies the filesystem access mode used for *mkdir*(2) in octal notation. The default mode is 0755. This functionality is supported only for root users or when mount executed without suid permissions. The option is also supported as x-mount.mkdir, this notation is deprecated since v2.30.
+**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.
+
*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.