nspawn-mount.c revision e83bebeff7d9d734e17c3e38ac13daabc09518e1
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2015 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include "util.h"
#include "rm-rf.h"
#include "strv.h"
#include "path-util.h"
#include "mkdir.h"
#include "label.h"
#include "set.h"
#include "cgroup-util.h"
#include "nspawn.h"
#include "nspawn-mount.h"
CustomMount *c, *ret;
assert(l);
assert(n);
assert(t >= 0);
assert(t < _CUSTOM_MOUNT_TYPE_MAX);
if (!c)
return NULL;
*l = c;
ret = *l + *n;
(*n)++;
return ret;
}
void custom_mount_free_all(CustomMount *l, unsigned n) {
unsigned i;
for (i = 0; i < n; i++) {
CustomMount *m = l + i;
free(m->destination);
if (m->work_dir) {
}
}
free(l);
}
int custom_mount_compare(const void *a, const void *b) {
const CustomMount *x = a, *y = b;
int r;
if (r != 0)
return r;
return -1;
return 1;
return 0;
}
const char *p = s;
CustomMount *m;
int r;
assert(l);
assert(n);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
if (r == 1) {
if (!destination)
return -ENOMEM;
}
if (r == 2 && !isempty(p)) {
if (!opts)
return -ENOMEM;
}
if (!path_is_absolute(source))
return -EINVAL;
if (!path_is_absolute(destination))
return -EINVAL;
m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND);
if (!m)
return log_oom();
m->destination = destination;
return 0;
}
int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) {
const char *p = s;
CustomMount *m;
int r;
assert(l);
assert(n);
assert(s);
if (r < 0)
return r;
if (r == 0)
return -EINVAL;
if (isempty(p))
else
if (!opts)
return -ENOMEM;
if (!path_is_absolute(path))
return -EINVAL;
m = custom_mount_add(l, n, CUSTOM_MOUNT_TMPFS);
if (!m)
return -ENOMEM;
m->destination = path;
return 0;
}
static int tmpfs_patch_options(
const char *options,
const char *selinux_apifs_context,
char **ret) {
if (options)
else
if (!buf)
return -ENOMEM;
}
#ifdef HAVE_SELINUX
if (selinux_apifs_context) {
char *t;
if (options)
else
if (!t) {
return -ENOMEM;
}
buf = t;
}
#endif
return !!buf;
}
const char *selinux_apifs_context) {
typedef struct MountPoint {
const char *what;
const char *where;
const char *type;
const char *options;
unsigned long flags;
bool fatal;
bool userns;
} MountPoint;
static const MountPoint mount_table[] = {
{ NULL, "/proc/sys", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, true, true }, /* Then, make it r/o */
#ifdef HAVE_SELINUX
{ NULL, "/sys/fs/selinux", NULL, NULL, MS_BIND|MS_RDONLY|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_REMOUNT, false, false }, /* Then, make it r/o */
#endif
};
unsigned k;
int r;
for (k = 0; k < ELEMENTSOF(mount_table); k++) {
const char *o;
continue;
if (!where)
return log_oom();
if (r < 0 && r != -ENOENT)
/* Skip this entry if it is not a remount. */
if (mount_table[k].what && r > 0)
continue;
if (r < 0) {
if (mount_table[k].fatal)
continue;
}
o = mount_table[k].options;
if (r < 0)
return log_oom();
if (r > 0)
o = options;
}
mount_table[k].type,
mount_table[k].flags,
o) < 0) {
if (mount_table[k].fatal)
}
}
return 0;
}
static int parse_mount_bind_options(const char *options, unsigned long *mount_flags, char **mount_opts) {
const char *p = options;
unsigned long flags = *mount_flags;
for (;;) {
if (r < 0)
return log_error_errno(r, "Failed to extract mount option: %m");
if (r == 0)
break;
else {
return -EINVAL;
}
}
*mount_flags = flags;
/* in the future mount_opts will hold string options for mount(2) */
*mount_opts = opts;
return 0;
}
const char *where;
int r;
assert(m);
if (m->options) {
if (r < 0)
return r;
}
return -EINVAL;
}
return -EINVAL;
}
if (r < 0)
} else {
return -errno;
}
/* Create the mount point. Any non-directory file can be
* mounted on any non-directory file (regular, fifo, socket,
* char, block).
*/
else
if (r < 0 && r != -EEXIST)
if (m->read_only) {
r = bind_remount_recursive(where, true);
if (r < 0)
return log_error_errno(r, "Read-only bind mount failed: %m");
}
return 0;
}
static int mount_tmpfs(
const char *dest,
CustomMount *m,
const char *selinux_apifs_context) {
int r;
assert(m);
if (r < 0 && r != -EEXIST)
if (r < 0)
return log_oom();
return 0;
}
static char *joined_and_escaped_lower_dirs(char * const *lower) {
if (!sv)
return NULL;
return NULL;
}
int r;
assert(m);
if (r < 0 && r != -EEXIST)
if (!lower)
return log_oom();
if (m->read_only) {
if (!escaped_source)
return log_oom();
} else {
if (!escaped_source)
return log_oom();
if (!escaped_work_dir)
return log_oom();
options = strjoina("lowerdir=", lower, ",upperdir=", escaped_source, ",workdir=", escaped_work_dir);
}
return 0;
}
int mount_custom(
const char *dest,
CustomMount *mounts, unsigned n,
const char *selinux_apifs_context) {
unsigned i;
int r;
for (i = 0; i < n; i++) {
CustomMount *m = mounts + i;
switch (m->type) {
case CUSTOM_MOUNT_BIND:
r = mount_bind(dest, m);
break;
case CUSTOM_MOUNT_TMPFS:
break;
case CUSTOM_MOUNT_OVERLAY:
r = mount_overlay(dest, m);
break;
default:
assert_not_reached("Unknown custom mount type");
}
if (r < 0)
return r;
}
return 0;
}
static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, bool read_only) {
char *to;
int r;
r = path_is_mount_point(to, 0);
if (r < 0 && r != -ENOENT)
if (r > 0)
return 0;
/* The superblock mount options of the mount point need to be
* identical to the hosts', and hence writable... */
/* ... hence let's only make the bind mount read-only, not the
* superblock. */
if (read_only) {
}
return 1;
}
static int mount_legacy_cgroups(
const char *dest,
const char *selinux_apifs_context) {
const char *cgroup_root;
int r;
if (r < 0)
return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m");
if (r == 0) {
if (r < 0)
return log_oom();
}
if (cg_unified() > 0)
goto skip_controllers;
if (!controllers)
return log_oom();
if (r < 0)
return log_error_errno(r, "Failed to determine cgroup controllers: %m");
for (;;) {
if (!controller)
break;
if (!origin)
return log_oom();
if (r == -EINVAL) {
/* Not a symbolic link, but directly a single cgroup hierarchy */
if (r < 0)
return r;
} else if (r < 0)
else {
if (!target)
return log_oom();
/* A symbolic link, a combination of controllers in one hierarchy */
if (!filename_is_valid(combined)) {
continue;
}
if (r < 0)
return r;
if (r == -EINVAL) {
log_error("Invalid existing symlink for combined hierarchy");
return r;
}
if (r < 0)
return log_error_errno(r, "Failed to create symlink for combined hierarchy: %m");
}
}
if (r < 0)
return r;
if (mount(NULL, cgroup_root, NULL, MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_STRICTATIME|MS_RDONLY, "mode=755") < 0)
return 0;
}
static int mount_unified_cgroups(const char *dest) {
const char *p;
int r;
r = path_is_mount_point(p, AT_SYMLINK_FOLLOW);
if (r < 0)
return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p);
if (r > 0) {
return 0;
return log_error_errno(errno, "Failed to determine if mount point %s contains the unified cgroup hierarchy: %m", p);
log_error("%s is already mounted but not a unified cgroup hierarchy. Refusing.", p);
return -EINVAL;
}
return 0;
}
int mount_cgroups(
const char *dest,
bool unified_requested,
const char *selinux_apifs_context) {
if (unified_requested)
return mount_unified_cgroups(dest);
else
}
const char *dest,
bool unified_requested) {
const char *systemd_root, *systemd_own;
int r;
if (r < 0)
return log_error_errno(r, "Failed to determine our own cgroup path: %m");
/* If we are living in the top-level, then there's nothing to do... */
return 0;
if (unified_requested) {
} else {
}
/* Make our own cgroup a (writable) bind mount */
/* And then remount the systemd cgroup root read-only */
if (mount(NULL, systemd_root, NULL, MS_BIND|MS_REMOUNT|MS_NOSUID|MS_NOEXEC|MS_NODEV|MS_RDONLY, NULL) < 0)
return 0;
}
int setup_volatile_state(
const char *directory,
const char *selinux_apifs_context) {
const char *p, *options;
int r;
if (mode != VOLATILE_STATE)
return 0;
/* --volatile=state means we simply overmount /var
with a tmpfs, and the rest read-only. */
r = bind_remount_recursive(directory, true);
if (r < 0)
r = mkdir(p, 0755);
options = "mode=755";
if (r < 0)
return log_oom();
if (r > 0)
return 0;
}
int setup_volatile(
const char *directory,
const char *selinux_apifs_context) {
bool tmpfs_mounted = false, bind_mounted = false;
char template[] = "/tmp/nspawn-volatile-XXXXXX";
const char *f, *t, *options;
int r;
if (mode != VOLATILE_YES)
return 0;
/* --volatile=yes means we mount a tmpfs to the root dir, and
the original /usr to use inside it, and that read-only. */
options = "mode=755";
if (r < 0)
return log_oom();
if (r > 0)
goto fail;
}
tmpfs_mounted = true;
r = mkdir(t, 0755);
goto fail;
}
goto fail;
}
bind_mounted = true;
r = bind_remount_recursive(t, true);
if (r < 0) {
log_error_errno(r, "Failed to remount %s read-only: %m", t);
goto fail;
}
goto fail;
}
return 0;
fail:
if (bind_mounted)
(void) umount(t);
if (tmpfs_mounted)
return r;
}
VolatileMode volatile_mode_from_string(const char *s) {
int b;
if (isempty(s))
return _VOLATILE_MODE_INVALID;
b = parse_boolean(s);
if (b > 0)
return VOLATILE_YES;
if (b == 0)
return VOLATILE_NO;
if (streq(s, "state"))
return VOLATILE_STATE;
return _VOLATILE_MODE_INVALID;
}