diff options
Diffstat (limited to 'src/nspawn/nspawn-mount.c')
-rw-r--r-- | src/nspawn/nspawn-mount.c | 315 |
1 files changed, 255 insertions, 60 deletions
diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index 91cb0861d3..c9d5ac419e 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -47,7 +47,7 @@ CustomMount* custom_mount_add(CustomMount **l, unsigned *n, CustomMountType t) { assert(t >= 0); assert(t < _CUSTOM_MOUNT_TYPE_MAX); - c = realloc(*l, (*n + 1) * sizeof(CustomMount)); + c = realloc_multiply(*l, (*n + 1), sizeof(CustomMount)); if (!c) return NULL; @@ -75,13 +75,18 @@ void custom_mount_free_all(CustomMount *l, unsigned n) { free(m->work_dir); } + if (m->rm_rf_tmpdir) { + (void) rm_rf(m->rm_rf_tmpdir, REMOVE_ROOT|REMOVE_PHYSICAL); + free(m->rm_rf_tmpdir); + } + strv_free(m->lower); } free(l); } -int custom_mount_compare(const void *a, const void *b) { +static int custom_mount_compare(const void *a, const void *b) { const CustomMount *x = a, *y = b; int r; @@ -97,6 +102,109 @@ int custom_mount_compare(const void *a, const void *b) { return 0; } +static bool source_path_is_valid(const char *p) { + assert(p); + + if (*p == '+') + p++; + + return path_is_absolute(p); +} + +static char *resolve_source_path(const char *dest, const char *source) { + + if (!source) + return NULL; + + if (source[0] == '+') + return prefix_root(dest, source + 1); + + return strdup(source); +} + +int custom_mount_prepare_all(const char *dest, CustomMount *l, unsigned n) { + unsigned i; + int r; + + /* Prepare all custom mounts. This will make source we know all temporary directories. This is called in the + * parent process, so that we know the temporary directories to remove on exit before we fork off the + * children. */ + + assert(l || n == 0); + + /* Order the custom mounts, and make sure we have a working directory */ + qsort_safe(l, n, sizeof(CustomMount), custom_mount_compare); + + for (i = 0; i < n; i++) { + CustomMount *m = l + i; + + if (m->source) { + char *s; + + s = resolve_source_path(dest, m->source); + if (!s) + return log_oom(); + + free(m->source); + m->source = s; + } else { + /* No source specified? In that case, use a throw-away temporary directory in /var/tmp */ + + m->rm_rf_tmpdir = strdup("/var/tmp/nspawn-temp-XXXXXX"); + if (!m->rm_rf_tmpdir) + return log_oom(); + + if (!mkdtemp(m->rm_rf_tmpdir)) { + m->rm_rf_tmpdir = mfree(m->rm_rf_tmpdir); + return log_error_errno(errno, "Failed to acquire temporary directory: %m"); + } + + m->source = strjoin(m->rm_rf_tmpdir, "/src"); + if (!m->source) + return log_oom(); + + if (mkdir(m->source, 0755) < 0) + return log_error_errno(errno, "Failed to create %s: %m", m->source); + } + + if (m->type == CUSTOM_MOUNT_OVERLAY) { + char **j; + + STRV_FOREACH(j, m->lower) { + char *s; + + s = resolve_source_path(dest, *j); + if (!s) + return log_oom(); + + free(*j); + *j = s; + } + + if (m->work_dir) { + char *s; + + s = resolve_source_path(dest, m->work_dir); + if (!s) + return log_oom(); + + free(m->work_dir); + m->work_dir = s; + } else { + assert(m->source); + + r = tempfn_random(m->source, NULL, &m->work_dir); + if (r < 0) + return log_error_errno(r, "Failed to acquire working directory: %m"); + } + + (void) mkdir_label(m->work_dir, 0700); + } + } + + return 0; +} + int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) { _cleanup_free_ char *source = NULL, *destination = NULL, *opts = NULL; const char *p = s; @@ -111,20 +219,20 @@ int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only return r; if (r == 0) return -EINVAL; - if (r == 1) { - destination = strdup(source); + destination = strdup(source[0] == '+' ? source+1 : source); if (!destination) return -ENOMEM; } - if (r == 2 && !isempty(p)) { opts = strdup(p); if (!opts) return -ENOMEM; } - if (!path_is_absolute(source)) + if (isempty(source)) + source = NULL; + else if (!source_path_is_valid(source)) return -EINVAL; if (!path_is_absolute(destination)) @@ -132,7 +240,7 @@ int bind_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only m = custom_mount_add(l, n, CUSTOM_MOUNT_BIND); if (!m) - return log_oom(); + return -ENOMEM; m->source = source; m->destination = destination; @@ -180,6 +288,71 @@ int tmpfs_mount_parse(CustomMount **l, unsigned *n, const char *s) { return 0; } +int overlay_mount_parse(CustomMount **l, unsigned *n, const char *s, bool read_only) { + _cleanup_free_ char *upper = NULL, *destination = NULL; + _cleanup_strv_free_ char **lower = NULL; + CustomMount *m; + int k; + + k = strv_split_extract(&lower, s, ":", EXTRACT_DONT_COALESCE_SEPARATORS); + if (k < 0) + return k; + if (k < 2) + return -EADDRNOTAVAIL; + if (k == 2) { + /* If two parameters are specified, the first one is the lower, the second one the upper directory. And + * we'll also define the destination mount point the same as the upper. */ + + if (!source_path_is_valid(lower[0]) || + !source_path_is_valid(lower[1])) + return -EINVAL; + + upper = lower[1]; + lower[1] = NULL; + + destination = strdup(upper[0] == '+' ? upper+1 : upper); /* take the destination without "+" prefix */ + if (!destination) + return -ENOMEM; + } else { + char **i; + + /* If more than two parameters are specified, the last one is the destination, the second to last one + * the "upper", and all before that the "lower" directories. */ + + destination = lower[k - 1]; + upper = lower[k - 2]; + lower[k - 2] = NULL; + + STRV_FOREACH(i, lower) + if (!source_path_is_valid(*i)) + return -EINVAL; + + /* If the upper directory is unspecified, then let's create it automatically as a throw-away directory + * in /var/tmp */ + if (isempty(upper)) + upper = NULL; + else if (!source_path_is_valid(upper)) + return -EINVAL; + + if (!path_is_absolute(destination)) + return -EINVAL; + } + + m = custom_mount_add(l, n, CUSTOM_MOUNT_OVERLAY); + if (!m) + return -ENOMEM; + + m->destination = destination; + m->source = upper; + m->lower = lower; + m->read_only = read_only; + + upper = destination = NULL; + lower = NULL; + + return 0; +} + static int tmpfs_patch_options( const char *options, bool userns, @@ -414,11 +587,11 @@ int mount_all(const char *dest, if (!ro && (bool)(mount_table[k].mount_settings & MOUNT_APPLY_APIVFS_RO)) continue; - where = prefix_root(dest, mount_table[k].where); - if (!where) - return log_oom(); + r = chase_symlinks(mount_table[k].where, dest, CHASE_NONEXISTENT|CHASE_PREFIX_ROOT, &where); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, mount_table[k].where); - r = path_is_mount_point(where, AT_SYMLINK_FOLLOW); + r = path_is_mount_point(where, NULL, 0); if (r < 0 && r != -ENOENT) return log_error_errno(r, "Failed to detect whether %s is a mount point: %m", where); @@ -464,12 +637,14 @@ static int parse_mount_bind_options(const char *options, unsigned long *mount_fl const char *p = options; unsigned long flags = *mount_flags; char *opts = NULL; + int r; assert(options); for (;;) { _cleanup_free_ char *word = NULL; - int r = extract_first_word(&p, &word, ",", 0); + + r = extract_first_word(&p, &word, ",", 0); if (r < 0) return log_error_errno(r, "Failed to extract mount option: %m"); if (r == 0) @@ -493,12 +668,13 @@ static int parse_mount_bind_options(const char *options, unsigned long *mount_fl } static int mount_bind(const char *dest, CustomMount *m) { - struct stat source_st, dest_st; - const char *where; + + _cleanup_free_ char *mount_opts = NULL, *where = NULL; unsigned long mount_flags = MS_BIND | MS_REC; - _cleanup_free_ char *mount_opts = NULL; + struct stat source_st, dest_st; int r; + assert(dest); assert(m); if (m->options) { @@ -510,9 +686,14 @@ static int mount_bind(const char *dest, CustomMount *m) { if (stat(m->source, &source_st) < 0) return log_error_errno(errno, "Failed to stat %s: %m", m->source); - where = prefix_roota(dest, m->destination); + r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination); + if (r > 0) { /* Path exists already? */ + + if (stat(where, &dest_st) < 0) + return log_error_errno(errno, "Failed to stat %s: %m", where); - if (stat(where, &dest_st) >= 0) { if (S_ISDIR(source_st.st_mode) && !S_ISDIR(dest_st.st_mode)) { log_error("Cannot bind mount directory %s on file %s.", m->source, where); return -EINVAL; @@ -523,7 +704,7 @@ static int mount_bind(const char *dest, CustomMount *m) { return -EINVAL; } - } else if (errno == ENOENT) { + } else { /* Path doesn't exist yet? */ r = mkdir_parents_label(where, 0755); if (r < 0) return log_error_errno(r, "Failed to make parents of %s: %m", where); @@ -539,8 +720,7 @@ static int mount_bind(const char *dest, CustomMount *m) { if (r < 0) return log_error_errno(r, "Failed to create mount point %s: %m", where); - } else - return log_error_errno(errno, "Failed to stat %s: %m", where); + } r = mount_verbose(LOG_ERR, m->source, where, NULL, mount_flags, mount_opts); if (r < 0) @@ -561,18 +741,21 @@ static int mount_tmpfs( bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context) { - const char *where, *options; - _cleanup_free_ char *buf = NULL; + const char *options; + _cleanup_free_ char *buf = NULL, *where = NULL; int r; assert(dest); assert(m); - where = prefix_roota(dest, m->destination); - - r = mkdir_p_label(where, 0755); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where); + r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination); + if (r == 0) { /* Doesn't exist yet? */ + r = mkdir_p_label(where, 0755); + if (r < 0) + return log_error_errno(r, "Creating mount point for tmpfs %s failed: %m", where); + } r = tmpfs_patch_options(m->options, userns, uid_shift, uid_range, false, selinux_apifs_context, &buf); if (r < 0) @@ -582,7 +765,7 @@ static int mount_tmpfs( return mount_verbose(LOG_ERR, "tmpfs", where, "tmpfs", MS_NODEV|MS_STRICTATIME, options); } -static char *joined_and_escaped_lower_dirs(char * const *lower) { +static char *joined_and_escaped_lower_dirs(char **lower) { _cleanup_strv_free_ char **sv = NULL; sv = strv_copy(lower); @@ -598,18 +781,22 @@ static char *joined_and_escaped_lower_dirs(char * const *lower) { } static int mount_overlay(const char *dest, CustomMount *m) { - _cleanup_free_ char *lower = NULL; - const char *where, *options; + + _cleanup_free_ char *lower = NULL, *where = NULL, *escaped_source = NULL; + const char *options; int r; assert(dest); assert(m); - where = prefix_roota(dest, m->destination); - - r = mkdir_label(where, 0755); - if (r < 0 && r != -EEXIST) - return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where); + r = chase_symlinks(m->destination, dest, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &where); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", dest, m->destination); + if (r == 0) { /* Doesn't exist yet? */ + r = mkdir_label(where, 0755); + if (r < 0) + return log_error_errno(r, "Creating mount point for overlay %s failed: %m", where); + } (void) mkdir_p_label(m->source, 0755); @@ -617,23 +804,15 @@ static int mount_overlay(const char *dest, CustomMount *m) { if (!lower) return log_oom(); - if (m->read_only) { - _cleanup_free_ char *escaped_source = NULL; - - escaped_source = shell_escape(m->source, ",:"); - if (!escaped_source) - return log_oom(); + escaped_source = shell_escape(m->source, ",:"); + if (!escaped_source) + return log_oom(); + if (m->read_only) options = strjoina("lowerdir=", escaped_source, ":", lower); - } else { - _cleanup_free_ char *escaped_source = NULL, *escaped_work_dir = NULL; + else { + _cleanup_free_ char *escaped_work_dir = NULL; - assert(m->work_dir); - (void) mkdir_label(m->work_dir, 0700); - - escaped_source = shell_escape(m->source, ",:"); - if (!escaped_source) - return log_oom(); escaped_work_dir = shell_escape(m->work_dir, ",:"); if (!escaped_work_dir) return log_oom(); @@ -726,14 +905,19 @@ static int get_controllers(Set *subsystems) { return 0; } -static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controller, const char *hierarchy, - CGroupUnified unified_requested, bool read_only) { +static int mount_legacy_cgroup_hierarchy( + const char *dest, + const char *controller, + const char *hierarchy, + CGroupUnified unified_requested, + bool read_only) { + const char *to, *fstype, *opts; int r; to = strjoina(strempty(dest), "/sys/fs/cgroup/", hierarchy); - r = path_is_mount_point(to, 0); + r = path_is_mount_point(to, dest, 0); if (r < 0 && r != -ENOENT) return log_error_errno(r, "Failed to determine if %s is mounted already: %m", to); if (r > 0) @@ -773,8 +957,13 @@ static int mount_legacy_cgroup_hierarchy(const char *dest, const char *controlle /* Mount a legacy cgroup hierarchy when cgroup namespaces are supported. */ static int mount_legacy_cgns_supported( - CGroupUnified unified_requested, bool userns, uid_t uid_shift, - uid_t uid_range, const char *selinux_apifs_context) { + const char *dest, + CGroupUnified unified_requested, + bool userns, + uid_t uid_shift, + uid_t uid_range, + const char *selinux_apifs_context) { + _cleanup_set_free_free_ Set *controllers = NULL; const char *cgroup_root = "/sys/fs/cgroup", *c; int r; @@ -782,7 +971,7 @@ static int mount_legacy_cgns_supported( (void) mkdir_p(cgroup_root, 0755); /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ - r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW); + r = path_is_mount_point(cgroup_root, dest, AT_SYMLINK_FOLLOW); if (r < 0) return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); if (r == 0) { @@ -871,8 +1060,12 @@ skip_controllers: /* Mount legacy cgroup hierarchy when cgroup namespaces are unsupported. */ static int mount_legacy_cgns_unsupported( const char *dest, - CGroupUnified unified_requested, bool userns, uid_t uid_shift, uid_t uid_range, + CGroupUnified unified_requested, + bool userns, + uid_t uid_shift, + uid_t uid_range, const char *selinux_apifs_context) { + _cleanup_set_free_free_ Set *controllers = NULL; const char *cgroup_root; int r; @@ -882,7 +1075,7 @@ static int mount_legacy_cgns_unsupported( (void) mkdir_p(cgroup_root, 0755); /* Mount a tmpfs to /sys/fs/cgroup if it's not mounted there yet. */ - r = path_is_mount_point(cgroup_root, AT_SYMLINK_FOLLOW); + r = path_is_mount_point(cgroup_root, dest, AT_SYMLINK_FOLLOW); if (r < 0) return log_error_errno(r, "Failed to determine if /sys/fs/cgroup is already mounted: %m"); if (r == 0) { @@ -975,7 +1168,7 @@ static int mount_unified_cgroups(const char *dest) { (void) mkdir_p(p, 0755); - r = path_is_mount_point(p, AT_SYMLINK_FOLLOW); + r = path_is_mount_point(p, dest, AT_SYMLINK_FOLLOW); if (r < 0) return log_error_errno(r, "Failed to determine if %s is mounted already: %m", p); if (r > 0) { @@ -995,14 +1188,16 @@ static int mount_unified_cgroups(const char *dest) { int mount_cgroups( const char *dest, CGroupUnified unified_requested, - bool userns, uid_t uid_shift, uid_t uid_range, + bool userns, + uid_t uid_shift, + uid_t uid_range, const char *selinux_apifs_context, bool use_cgns) { if (unified_requested >= CGROUP_UNIFIED_ALL) return mount_unified_cgroups(dest); else if (use_cgns) - return mount_legacy_cgns_supported(unified_requested, userns, uid_shift, uid_range, selinux_apifs_context); + return mount_legacy_cgns_supported(dest, unified_requested, userns, uid_shift, uid_range, selinux_apifs_context); return mount_legacy_cgns_unsupported(dest, unified_requested, userns, uid_shift, uid_range, selinux_apifs_context); } |