From a4c35b6b4d8457e15caa3fe18ce8050918932d00 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 00:28:05 +0100 Subject: [PATCH 01/46] nspawn: split out VolatileMode definitions This moves the VolatileMode enum and its helper functions to src/shared/. This is useful to then reuse them to implement systemd.volatile= in a later commit. --- Makefile.am | 4 +++- src/nspawn/nspawn-mount.c | 18 ----------------- src/nspawn/nspawn-mount.h | 11 +--------- src/shared/volatile-util.c | 41 ++++++++++++++++++++++++++++++++++++++ src/shared/volatile-util.h | 30 ++++++++++++++++++++++++++++ 5 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 src/shared/volatile-util.c create mode 100644 src/shared/volatile-util.h diff --git a/Makefile.am b/Makefile.am index d75807a9f9..56b8aa3fe8 100644 --- a/Makefile.am +++ b/Makefile.am @@ -1065,7 +1065,9 @@ libshared_la_SOURCES = \ src/shared/nsflags.h \ src/shared/nsflags.c \ src/shared/dissect-image.c \ - src/shared/dissect-image.h + src/shared/dissect-image.h \ + src/shared/volatile-util.c \ + src/shared/volatile-util.h if HAVE_UTMP libshared_la_SOURCES += \ diff --git a/src/nspawn/nspawn-mount.c b/src/nspawn/nspawn-mount.c index aaa64a7ba8..72c007f204 100644 --- a/src/nspawn/nspawn-mount.c +++ b/src/nspawn/nspawn-mount.c @@ -1349,21 +1349,3 @@ fail: (void) rmdir(template); 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; -} diff --git a/src/nspawn/nspawn-mount.h b/src/nspawn/nspawn-mount.h index 467082a737..6b33fbff57 100644 --- a/src/nspawn/nspawn-mount.h +++ b/src/nspawn/nspawn-mount.h @@ -22,6 +22,7 @@ #include #include "cgroup-util.h" +#include "volatile-util.h" typedef enum MountSettingsMask { MOUNT_FATAL = 1 << 0, /* if set, a mount error is considered fatal */ @@ -32,14 +33,6 @@ typedef enum MountSettingsMask { Works only if MOUNT_APPLY_APIVFS_RO is also set. */ } MountSettingsMask; -typedef enum VolatileMode { - VOLATILE_NO, - VOLATILE_YES, - VOLATILE_STATE, - _VOLATILE_MODE_MAX, - _VOLATILE_MODE_INVALID = -1 -} VolatileMode; - typedef enum CustomMountType { CUSTOM_MOUNT_BIND, CUSTOM_MOUNT_TMPFS, @@ -77,5 +70,3 @@ int mount_custom(const char *dest, CustomMount *mounts, unsigned n, bool userns, int setup_volatile(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); int setup_volatile_state(const char *directory, VolatileMode mode, bool userns, uid_t uid_shift, uid_t uid_range, const char *selinux_apifs_context); - -VolatileMode volatile_mode_from_string(const char *s); diff --git a/src/shared/volatile-util.c b/src/shared/volatile-util.c new file mode 100644 index 0000000000..1329b51f4e --- /dev/null +++ b/src/shared/volatile-util.c @@ -0,0 +1,41 @@ +/*** + This file is part of systemd. + + Copyright 2015 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + 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 . +***/ + +#include "macro.h" +#include "parse-util.h" +#include "string-util.h" +#include "volatile-util.h" + +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; +} diff --git a/src/shared/volatile-util.h b/src/shared/volatile-util.h new file mode 100644 index 0000000000..d012940c76 --- /dev/null +++ b/src/shared/volatile-util.h @@ -0,0 +1,30 @@ +#pragma once + +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + 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 . +***/ + +typedef enum VolatileMode { + VOLATILE_NO, + VOLATILE_YES, + VOLATILE_STATE, + _VOLATILE_MODE_MAX, + _VOLATILE_MODE_INVALID = -1 +} VolatileMode; + +VolatileMode volatile_mode_from_string(const char *s); From 289cb4d5cd7462a074ffb5018efa385e82220318 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 10:50:29 +0100 Subject: [PATCH 02/46] shared: use uid_is_valid() for checking validity of UIDs --- src/shared/base-filesystem.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/base-filesystem.c b/src/shared/base-filesystem.c index f1fbce9dca..127cbe44e3 100644 --- a/src/shared/base-filesystem.c +++ b/src/shared/base-filesystem.c @@ -101,7 +101,7 @@ int base_filesystem_create(const char *root, uid_t uid, gid_t gid) { if (r < 0 && errno != EEXIST) return log_error_errno(errno, "Failed to create symlink at %s/%s: %m", root, table[i].dir); - if (uid != UID_INVALID || gid != UID_INVALID) { + if (uid_is_valid(uid) || gid_is_valid(gid)) { if (fchownat(fd, table[i].dir, uid, gid, AT_SYMLINK_NOFOLLOW) < 0) return log_error_errno(errno, "Failed to chown symlink at %s/%s: %m", root, table[i].dir); } From dee22f39707699d3cd8a3bb9cde4ce85486fbebd Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 10:51:32 +0100 Subject: [PATCH 03/46] core: add comment why we don't bother with MS_SHARED remounting of / in containers --- src/core/mount-setup.c | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/src/core/mount-setup.c b/src/core/mount-setup.c index 6338067d7e..9c2bf3a0ef 100644 --- a/src/core/mount-setup.c +++ b/src/core/mount-setup.c @@ -360,7 +360,6 @@ int mount_setup(bool loaded_policy) { int r = 0; r = mount_points_setup(ELEMENTSOF(mount_table), loaded_policy); - if (r < 0) return r; @@ -391,25 +390,24 @@ int mount_setup(bool loaded_policy) { * udevd. */ dev_setup(NULL, UID_INVALID, GID_INVALID); - /* Mark the root directory as shared in regards to mount - * propagation. The kernel defaults to "private", but we think - * it makes more sense to have a default of "shared" so that - * nspawn and the container tools work out of the box. If - * specific setups need other settings they can reset the - * propagation mode to private if needed. */ + /* Mark the root directory as shared in regards to mount propagation. The kernel defaults to "private", but we + * think it makes more sense to have a default of "shared" so that nspawn and the container tools work out of + * the box. If specific setups need other settings they can reset the propagation mode to private if + * needed. Note that we set this only when we are invoked directly by the kernel. If we are invoked by a + * container manager we assume the container manager knows what it is doing (for example, because it set up + * some directories with different propagation modes). */ if (detect_container() <= 0) if (mount(NULL, "/", NULL, MS_REC|MS_SHARED, NULL) < 0) log_warning_errno(errno, "Failed to set up the root directory for shared mount propagation: %m"); - /* Create a few directories we always want around, Note that - * sd_booted() checks for /run/systemd/system, so this mkdir - * really needs to stay for good, otherwise software that - * copied sd-daemon.c into their sources will misdetect - * systemd. */ + /* Create a few directories we always want around, Note that sd_booted() checks for /run/systemd/system, so + * this mkdir really needs to stay for good, otherwise software that copied sd-daemon.c into their sources will + * misdetect systemd. */ (void) mkdir_label("/run/systemd", 0755); (void) mkdir_label("/run/systemd/system", 0755); - (void) mkdir_label("/run/systemd/inaccessible", 0000); + /* Set up inaccessible items */ + (void) mkdir_label("/run/systemd/inaccessible", 0000); (void) mknod("/run/systemd/inaccessible/reg", S_IFREG | 0000, 0); (void) mkdir_label("/run/systemd/inaccessible/dir", 0000); (void) mknod("/run/systemd/inaccessible/chr", S_IFCHR | 0000, makedev(0, 0)); From 245f1d244b3a91e7cc18ed2d21226292251b934d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 17:15:06 +0100 Subject: [PATCH 04/46] util-lib: accept invoking chase_symlinks() with a NULL return parameter Let's permit invoking chase_symlinks() with a NULL return parameter. If so, the resolved name is not returned, and call is useful for checking for existance of a file, without actually returning its ultimate path. --- src/basic/fs-util.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/basic/fs-util.c b/src/basic/fs-util.c index 5b23269109..e31fa2711a 100644 --- a/src/basic/fs-util.c +++ b/src/basic/fs-util.c @@ -799,8 +799,10 @@ int chase_symlinks(const char *path, const char *original_root, unsigned flags, return -ENOMEM; } - *ret = done; - done = NULL; + if (ret) { + *ret = done; + done = NULL; + } return exists; } From 7dcdb24e192c664d1e84db748fa38819f3daa693 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 17:19:27 +0100 Subject: [PATCH 05/46] util-lib: beef path_is_os_tree() up a bit Let's use chase_symlinks() when looking for /etc/os-release and /usr/lib/os-release as these files might be symlinks (and actually are IRL on some distros). --- src/basic/stat-util.c | 26 +++++++++++++++++--------- src/test/test-stat-util.c | 7 +++++++ 2 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 309e84b93d..5663a29cd6 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -28,6 +28,7 @@ #include "dirent-util.h" #include "fd-util.h" +#include "fs-util.h" #include "macro.h" #include "missing.h" #include "stat-util.h" @@ -143,22 +144,29 @@ int path_is_read_only_fs(const char *path) { } int path_is_os_tree(const char *path) { - char *p; int r; assert(path); + /* Does the path exist at all? If not, generate an error immediately. This is useful so that a missing root dir + * always results in -ENOENT, and we can properly distuingish the case where the whole root doesn't exist from + * the case where just the os-release file is missing. */ + if (laccess(path, F_OK) < 0) + return -errno; + /* We use /usr/lib/os-release as flag file if something is an OS */ - p = strjoina(path, "/usr/lib/os-release"); - r = access(p, F_OK); - if (r >= 0) - return 1; + r = chase_symlinks("/usr/lib/os-release", path, CHASE_PREFIX_ROOT, NULL); + if (r == -ENOENT) { - /* Also check for the old location in /etc, just in case. */ - p = strjoina(path, "/etc/os-release"); - r = access(p, F_OK); + /* Also check for the old location in /etc, just in case. */ + r = chase_symlinks("/etc/os-release", path, CHASE_PREFIX_ROOT, NULL); + if (r == -ENOENT) + return 0; /* We got nothing */ + } + if (r < 0) + return r; - return r >= 0; + return 1; } int files_same(const char *filea, const char *fileb) { diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index 6c34250a01..10fc4af79f 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -60,9 +60,16 @@ static void test_is_symlink(void) { unlink(name_link); } +static void test_path_is_os_tree(void) { + assert_se(path_is_os_tree("/") > 0); + assert_se(path_is_os_tree("/etc") == 0); + assert_se(path_is_os_tree("/idontexist") == -ENOENT); +} + int main(int argc, char *argv[]) { test_files_same(); test_is_symlink(); + test_path_is_os_tree(); return 0; } From eee8b7ab2e701a3e9e4b71b80ddf6f0fb77e029b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 17:23:08 +0100 Subject: [PATCH 06/46] core: minor coding style/wording fixes --- src/core/main.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/core/main.c b/src/core/main.c index 02992c7324..6ec996577c 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -49,6 +49,7 @@ #include "cpu-set-util.h" #include "dbus-manager.h" #include "def.h" +#include "emergency-action.h" #include "env-util.h" #include "fd-util.h" #include "fdset.h" @@ -90,7 +91,6 @@ #include "user-util.h" #include "virt.h" #include "watchdog.h" -#include "emergency-action.h" static enum { ACTION_RUN, @@ -1411,7 +1411,7 @@ int main(int argc, char *argv[]) { called 'systemd'. That is confusing, hence let's call us systemd right-away. */ program_invocation_short_name = systemd; - prctl(PR_SET_NAME, systemd); + (void) prctl(PR_SET_NAME, systemd); saved_argv = argv; saved_argc = argc; @@ -1435,9 +1435,10 @@ int main(int argc, char *argv[]) { if (!skip_setup) { r = mount_setup_early(); if (r < 0) { - error_message = "Failed to early mount API filesystems"; + error_message = "Failed to mount early API filesystems"; goto finish; } + dual_timestamp_get(&security_start_timestamp); if (mac_selinux_setup(&loaded_policy) < 0) { error_message = "Failed to load SELinux policy"; @@ -1513,7 +1514,7 @@ int main(int argc, char *argv[]) { log_close_console(); /* force reopen of /dev/console */ log_open(); - /* For the later on, see above... */ + /* For later on, see above... */ log_set_target(LOG_TARGET_JOURNAL); /* clear the kernel timestamp, From 08cea7dfc0aee044c35f234579a9f4fa8edcace5 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 19:35:05 +0100 Subject: [PATCH 07/46] util-lib: make sure fd_check_fstype() opens files with O_CLOEXEC Also, O_NOCTTY is a safer bet, let's add that too. --- src/basic/stat-util.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 5663a29cd6..848572687e 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -204,7 +204,7 @@ int fd_check_fstype(int fd, statfs_f_type_t magic_value) { int path_check_fstype(const char *path, statfs_f_type_t magic_value) { _cleanup_close_ int fd = -1; - fd = open(path, O_RDONLY); + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); if (fd < 0) return -errno; From 9ffcff0e0a3ede3f4c227327cb1a96a436386f4f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 19:36:46 +0100 Subject: [PATCH 08/46] tree-wide: always invoke setmntent() with "re" mode Let's make sure O_CLOEXEC is set for the file descriptor. --- src/cryptsetup/cryptsetup.c | 2 +- src/remount-fs/remount-fs.c | 2 +- src/shared/fstab-util.c | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index c7fec609df..b3abbb8b2f 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -310,7 +310,7 @@ static char *disk_mount_point(const char *label) { if (asprintf(&device, "/dev/mapper/%s", label) < 0) return NULL; - f = setmntent("/etc/fstab", "r"); + f = setmntent("/etc/fstab", "re"); if (!f) return NULL; diff --git a/src/remount-fs/remount-fs.c b/src/remount-fs/remount-fs.c index c3bdcaf1da..0cb9bd9261 100644 --- a/src/remount-fs/remount-fs.c +++ b/src/remount-fs/remount-fs.c @@ -56,7 +56,7 @@ int main(int argc, char *argv[]) { umask(0022); - f = setmntent("/etc/fstab", "r"); + f = setmntent("/etc/fstab", "re"); if (!f) { if (errno == ENOENT) { r = 0; diff --git a/src/shared/fstab-util.c b/src/shared/fstab-util.c index f0bfb30bb5..87b520b540 100644 --- a/src/shared/fstab-util.c +++ b/src/shared/fstab-util.c @@ -38,7 +38,7 @@ bool fstab_is_mount_point(const char *mount) { _cleanup_endmntent_ FILE *f = NULL; struct mntent *m; - f = setmntent("/etc/fstab", "r"); + f = setmntent("/etc/fstab", "re"); if (!f) return false; From ae59114af9b33257e5121a24dfcca3834cf6d46f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 19:37:49 +0100 Subject: [PATCH 09/46] =?UTF-8?q?gpt-auto-generator:=20rename=20add=5Fboot?= =?UTF-8?q?()=20=E2=86=92=20add=5Fesp()?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After all, the call doesn't necessarily mount /boot anymore, but possibly /efi now. --- src/gpt-auto-generator/gpt-auto-generator.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 0f95f0d813..a60905de94 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -449,7 +449,7 @@ static int add_automount( return 0; } -static int add_boot(const char *what) { +static int add_esp(const char *what) { const char *esp; int r; @@ -547,7 +547,7 @@ static int add_boot(const char *what) { 120 * USEC_PER_SEC); } #else -static int add_boot(const char *what) { +static int add_esp(const char *what) { return 0; } #endif @@ -789,7 +789,7 @@ static int enumerate_partitions(dev_t devnum) { } if (boot) { - k = add_boot(boot); + k = add_esp(boot); if (k < 0) r = k; } From ffeb828596ebc9e5b20333c72862569b8b0875ad Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 19:39:50 +0100 Subject: [PATCH 10/46] util-lib: add new path_is_temporary_fs() API As simple wrapper around fd_is_temporary_fs(). --- src/basic/stat-util.c | 10 ++++++++++ src/basic/stat-util.h | 1 + 2 files changed, 11 insertions(+) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index 848572687e..ac5db71022 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -224,3 +224,13 @@ int fd_is_temporary_fs(int fd) { return is_temporary_fs(&s); } + +int path_is_temporary_fs(const char *path) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return -errno; + + return fd_is_temporary_fs(fd); +} diff --git a/src/basic/stat-util.h b/src/basic/stat-util.h index 56d28f791e..5d571efe18 100644 --- a/src/basic/stat-util.h +++ b/src/basic/stat-util.h @@ -61,6 +61,7 @@ int path_check_fstype(const char *path, statfs_f_type_t magic_value); bool is_temporary_fs(const struct statfs *s) _pure_; int fd_is_temporary_fs(int fd); +int path_is_temporary_fs(const char *path); /* Because statfs.t_type can be int on some architectures, we have to cast * the const magic to the type, otherwise the compiler warns about From 710028b473c39446954b72c0d9dadb76cc7f81f8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 8 Dec 2016 19:41:27 +0100 Subject: [PATCH 11/46] core: modernize the SwitchRoot() bus method a bit Let's more verbose error messages when validating the input parameters fails. Also, call path_is_os_tree() properly, as it doesn't return a boolean, but possibly also an error. Finally, check for the existance of the new init process with chase_symlinks() to properly handle possible symlinks on the init binary (which might actually be pretty likely). --- src/core/dbus-manager.c | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index 9af49dd1bc..9876251438 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -35,6 +35,7 @@ #include "fd-util.h" #include "fileio.h" #include "format-util.h" +#include "fs-util.h" #include "install.h" #include "log.h" #include "path-util.h" @@ -1484,25 +1485,36 @@ static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_er if (r < 0) return r; - if (path_equal(root, "/") || !path_is_absolute(root)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid switch root path %s", root); + if (isempty(root)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New root directory may not be the empty string."); + if (!path_is_absolute(root)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New root path '%s' is not absolute.", root); + if (path_equal(root, "/")) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "New root directory cannot be the old root directory."); /* Safety check */ if (isempty(init)) { - if (!path_is_os_tree(root)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified switch root path %s does not seem to be an OS tree. os-release file is missing.", root); + r = path_is_os_tree(root); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Failed to determine whether root path '%s' contains an OS tree: %m", root); + if (r == 0) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified switch root path '%s' does not seem to be an OS tree. os-release file is missing.", root); } else { - _cleanup_free_ char *p = NULL; + _cleanup_free_ char *chased = NULL; if (!path_is_absolute(init)) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid init path %s", init); + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Path to init binary '%s' not absolute.", init); - p = strappend(root, init); - if (!p) - return -ENOMEM; + r = chase_symlinks(init, root, CHASE_PREFIX_ROOT, &chased); + if (r < 0) + return sd_bus_error_set_errnof(error, r, "Could not resolve init executable %s: %m", init); - if (access(p, X_OK) < 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Specified init binary %s does not exist.", p); + if (laccess(chased, X_OK) < 0) { + if (errno == EACCES) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Init binary %s is not executable.", init); + + return sd_bus_error_set_errnof(error, r, "Could not check whether init binary %s is executable: %m", init); + } } rt = strdup(root); From f582cbca0029d3867620dfa7df86534d2a241275 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 9 Dec 2016 01:11:42 +0100 Subject: [PATCH 12/46] firstboot: add kernel cmdline option to disable firstboot wizard If booting with systemd.firstboot=0 the wizard will be skipped. --- man/kernel-command-line.xml | 12 +++++++++++- man/systemd-firstboot.xml | 14 ++++++++++++++ src/firstboot/firstboot.c | 13 +++++++++++++ 3 files changed, 38 insertions(+), 1 deletion(-) diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 1fa31a14b7..c86e45265f 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -353,6 +353,15 @@ systemd-hibernate-resume-generator8. + + + systemd.firstboot= + + Takes a boolean argument, defaults to on. If off, + systemd-firstboot.service8 + will not query the user for basic system settings, even if the system boots up for the first time and the + relevant settings are not initialized yet. + @@ -376,7 +385,8 @@ systemd-modules-load.service8, systemd-backlight@.service8, systemd-rfkill.service8, - systemd-hibernate-resume-generator8 + systemd-hibernate-resume-generator8, + systemd-firstboot.service8 diff --git a/man/systemd-firstboot.xml b/man/systemd-firstboot.xml index b269e48113..4e3b1a3a81 100644 --- a/man/systemd-firstboot.xml +++ b/man/systemd-firstboot.xml @@ -240,6 +240,20 @@ otherwise. + + Kernel Command Line + + + + systemd.firstboot= + + Takes a boolean argument, defaults to on. If off systemd-firstboot.service + won't interactively query the user for basic settings at first boot, even if if the settings are not + initialized yet. + + + + See Also diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index c9e8e54ee3..7c615abbc5 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -33,6 +33,7 @@ #include "mkdir.h" #include "parse-util.h" #include "path-util.h" +#include "proc-cmdline.h" #include "random-util.h" #include "string-util.h" #include "strv.h" @@ -825,6 +826,7 @@ static int parse_argv(int argc, char *argv[]) { } int main(int argc, char *argv[]) { + _cleanup_free_ char *enabled = NULL; int r; r = parse_argv(argc, argv); @@ -837,6 +839,17 @@ int main(int argc, char *argv[]) { umask(0022); + r = get_proc_cmdline_key("systemd.firstboot=", &enabled); + if (r < 0) + return r; + if (r > 0) { + r = parse_boolean(enabled); + if (r == 0) + goto finish; + if (r < 0) + log_warning_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %s", enabled); + } + r = process_locale(); if (r < 0) goto finish; From afe682bc7f1d9a089cb5dfb439929f3791c18d22 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 9 Dec 2016 17:35:48 +0100 Subject: [PATCH 13/46] util-lib: make verbose_mount() grok MS_MOVE Let's print a proper message if we see MS_MOVE. --- src/basic/mount-util.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/basic/mount-util.c b/src/basic/mount-util.c index 840e94a553..f0bc9cac18 100644 --- a/src/basic/mount-util.c +++ b/src/basic/mount-util.c @@ -673,6 +673,9 @@ int mount_verbose( else if ((flags & MS_BIND) && !type) log_debug("Bind-mounting %s on %s (%s \"%s\")...", what, where, strnull(fl), strempty(options)); + else if (flags & MS_MOVE) + log_debug("Moving mount %s → %s (%s \"%s\")...", + what, where, strnull(fl), strempty(options)); else log_debug("Mounting %s on %s (%s \"%s\")...", strna(type), where, strnull(fl), strempty(options)); From 6f4e2f97d738e720ae843cef67cf83e350aa7667 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 9 Dec 2016 17:36:40 +0100 Subject: [PATCH 14/46] units: drop --fail parameter from "systemctl switch-root" invocation This parameter has no effect on switch root hence we shouldn't specify it. --- units/initrd-switch-root.service.in | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/units/initrd-switch-root.service.in b/units/initrd-switch-root.service.in index 82893dafb1..219748d19b 100644 --- a/units/initrd-switch-root.service.in +++ b/units/initrd-switch-root.service.in @@ -15,6 +15,5 @@ AllowIsolate=yes [Service] Type=oneshot -# we have to use "--force" here, otherwise systemd would umount /run -ExecStart=@rootbindir@/systemctl --no-block --force switch-root /sysroot +ExecStart=@rootbindir@/systemctl --no-block switch-root /sysroot KillMode=none From 9b6deb03fcb358d6987ce86fcad08e2e290ee5d0 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 9 Dec 2016 18:39:15 +0100 Subject: [PATCH 15/46] dissect: optionally, only look for GPT partition tables, nothing else This is useful for reusing the dissector logic in the gpt-auto-discovery logic: there we really don't want to use MBR or naked file systems as root device. --- src/dissect/dissect.c | 2 +- src/machine/image-dbus.c | 2 +- src/nspawn/nspawn.c | 2 +- src/shared/dissect-image.c | 97 +++++++++++++++++++---------------- src/shared/dissect-image.h | 3 +- src/test/test-dissect-image.c | 2 +- 6 files changed, 58 insertions(+), 50 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index f2f1e135ec..aa06894037 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -191,7 +191,7 @@ int main(int argc, char *argv[]) { goto finish; } - r = dissect_image(d->fd, arg_root_hash, arg_root_hash_size, &m); + r = dissect_image(d->fd, arg_root_hash, arg_root_hash_size, 0, &m); if (r == -ENOPKG) { log_error_errno(r, "Couldn't identify a suitable partition table or file system in %s.", arg_image); goto finish; diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index e2fb882393..2b168b267b 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -336,7 +336,7 @@ static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *err if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to set up loop block device for %s: %m", image->path); - r = dissect_image(d->fd, NULL, 0, &m); + r = dissect_image(d->fd, NULL, 0, 0, &m); if (r == -ENOPKG) return sd_bus_error_set_errnof(error, r, "Disk image %s not understood: %m", image->path); if (r < 0) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index dcc639f15c..2e879d7d7f 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3743,7 +3743,7 @@ int main(int argc, char *argv[]) { goto finish; } - r = dissect_image(loop->fd, arg_root_hash, arg_root_hash_size, &dissected_image); + r = dissect_image(loop->fd, arg_root_hash, arg_root_hash_size, 0, &dissected_image); if (r == -ENOPKG) { log_error_errno(r, "Could not find a suitable file system or partition table in image: %s", arg_image); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index d3ba9b9dde..10d53eab45 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -84,7 +84,7 @@ not_found: #endif } -int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectedImage **ret) { +int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DissectedImage **ret) { #ifdef HAVE_BLKID sd_id128_t root_uuid = SD_ID128_NULL, verity_uuid = SD_ID128_NULL; @@ -95,7 +95,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte _cleanup_blkid_free_probe_ blkid_probe b = NULL; _cleanup_udev_unref_ struct udev *udev = NULL; _cleanup_free_ char *generic_node = NULL; - const char *pttype = NULL, *usage = NULL; + const char *pttype = NULL; struct udev_list_entry *first, *item; blkid_partlist pl; int r, generic_nr; @@ -147,8 +147,12 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte return -errno; } - blkid_probe_enable_superblocks(b, 1); - blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_USAGE); + if ((flags & DISSECT_IMAGE_GPT_ONLY) == 0) { + /* Look for file system superblocks, unless we only shall look for GPT partition tables */ + blkid_probe_enable_superblocks(b, 1); + blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_USAGE); + } + blkid_probe_enable_partitions(b, 1); blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); @@ -169,40 +173,44 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte if (!m) return -ENOMEM; - (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL); - if (STRPTR_IN_SET(usage, "filesystem", "crypto")) { - _cleanup_free_ char *t = NULL, *n = NULL; - const char *fstype = NULL; + if ((flags & DISSECT_IMAGE_GPT_ONLY) == 0) { + const char *usage = NULL; - /* OK, we have found a file system, that's our root partition then. */ - (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL); + if (STRPTR_IN_SET(usage, "filesystem", "crypto")) { + _cleanup_free_ char *t = NULL, *n = NULL; + const char *fstype = NULL; - if (fstype) { - t = strdup(fstype); - if (!t) + /* OK, we have found a file system, that's our root partition then. */ + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + + if (fstype) { + t = strdup(fstype); + if (!t) + return -ENOMEM; + } + + if (asprintf(&n, "/dev/block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) return -ENOMEM; + + m->partitions[PARTITION_ROOT] = (DissectedPartition) { + .found = true, + .rw = true, + .partno = -1, + .architecture = _ARCHITECTURE_INVALID, + .fstype = t, + .node = n, + }; + + t = n = NULL; + + m->encrypted = streq(fstype, "crypto_LUKS"); + + *ret = m; + m = NULL; + + return 0; } - - if (asprintf(&n, "/dev/block/%u:%u", major(st.st_rdev), minor(st.st_rdev)) < 0) - return -ENOMEM; - - m->partitions[PARTITION_ROOT] = (DissectedPartition) { - .found = true, - .rw = true, - .partno = -1, - .architecture = _ARCHITECTURE_INVALID, - .fstype = t, - .node = n, - }; - - t = n = NULL; - - m->encrypted = streq(fstype, "crypto_LUKS"); - - *ret = m; - m = NULL; - - return 0; } (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); @@ -212,7 +220,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte is_gpt = streq_ptr(pttype, "gpt"); is_mbr = streq_ptr(pttype, "dos"); - if (!is_gpt && !is_mbr) + if (!is_gpt && ((flags & DISSECT_IMAGE_GPT_ONLY) || !is_mbr)) return -ENOPKG; errno = 0; @@ -300,7 +308,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte first = udev_enumerate_get_list_entry(e); udev_list_entry_foreach(item, first) { _cleanup_udev_device_unref_ struct udev_device *q; - unsigned long long flags; + unsigned long long pflags; blkid_partition pp; const char *node; dev_t qn; @@ -325,7 +333,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte if (!pp) continue; - flags = blkid_partition_get_flags(pp); + pflags = blkid_partition_get_flags(pp); nr = blkid_partition_get_partno(pp); if (nr < 0) @@ -337,7 +345,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte sd_id128_t type_id, id; bool rw = true; - if (flags & GPT_FLAG_NO_AUTO) + if (pflags & GPT_FLAG_NO_AUTO) continue; sid = blkid_partition_get_uuid(pp); @@ -354,10 +362,10 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte if (sd_id128_equal(type_id, GPT_HOME)) { designator = PARTITION_HOME; - rw = !(flags & GPT_FLAG_READ_ONLY); + rw = !(pflags & GPT_FLAG_READ_ONLY); } else if (sd_id128_equal(type_id, GPT_SRV)) { designator = PARTITION_SRV; - rw = !(flags & GPT_FLAG_READ_ONLY); + rw = !(pflags & GPT_FLAG_READ_ONLY); } else if (sd_id128_equal(type_id, GPT_ESP)) { designator = PARTITION_ESP; fstype = "vfat"; @@ -371,7 +379,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte designator = PARTITION_ROOT; architecture = native_architecture(); - rw = !(flags & GPT_FLAG_READ_ONLY); + rw = !(pflags & GPT_FLAG_READ_ONLY); } else if (sd_id128_equal(type_id, GPT_ROOT_NATIVE_VERITY)) { m->can_verity = true; @@ -395,9 +403,8 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte designator = PARTITION_ROOT_SECONDARY; architecture = SECONDARY_ARCHITECTURE; - rw = !(flags & GPT_FLAG_READ_ONLY); + rw = !(pflags & GPT_FLAG_READ_ONLY); } else if (sd_id128_equal(type_id, GPT_ROOT_SECONDARY_VERITY)) { - m->can_verity = true; /* Ignore verity unless root has is specified */ @@ -419,7 +426,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte multiple_generic = true; else { generic_nr = nr; - generic_rw = !(flags & GPT_FLAG_READ_ONLY); + generic_rw = !(pflags & GPT_FLAG_READ_ONLY); generic_node = strdup(node); if (!generic_node) return -ENOMEM; @@ -457,7 +464,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, Dissecte } else if (is_mbr) { - if (flags != 0x80) /* Bootable flag */ + if (pflags != 0x80) /* Bootable flag */ continue; if (blkid_partition_get_type(pp) != 0x83) /* Linux partition */ diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 175ddd8ea0..b424dac665 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -67,6 +67,7 @@ typedef enum DissectImageFlags { DISSECT_IMAGE_DISCARD_ANY = DISSECT_IMAGE_DISCARD_ON_LOOP | DISSECT_IMAGE_DISCARD | DISSECT_IMAGE_DISCARD_ON_CRYPTO, + DISSECT_IMAGE_GPT_ONLY = 16, /* Only recognize images with GPT partition tables */ } DissectImageFlags; struct DissectedImage { @@ -76,7 +77,7 @@ struct DissectedImage { DissectedPartition partitions[_PARTITION_DESIGNATOR_MAX]; }; -int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectedImage **ret); +int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectImageFlags flags, DissectedImage **ret); DissectedImage* dissected_image_unref(DissectedImage *m); DEFINE_TRIVIAL_CLEANUP_FUNC(DissectedImage*, dissected_image_unref); diff --git a/src/test/test-dissect-image.c b/src/test/test-dissect-image.c index 0512a15e88..ddaf3a0d8b 100644 --- a/src/test/test-dissect-image.c +++ b/src/test/test-dissect-image.c @@ -43,7 +43,7 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - r = dissect_image(d->fd, NULL, 0, &m); + r = dissect_image(d->fd, NULL, 0, 0, &m); if (r < 0) { log_error_errno(r, "Failed to dissect image: %m"); return EXIT_FAILURE; From 5cfc0a84610949854ae46a5f7f2e3a56077fa92d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Dec 2016 13:40:58 +0100 Subject: [PATCH 16/46] build-sys: don't mke use of "sushell" automatically "sushell" is a Fedora-specific concept, shipped as part of "initscripts". We shouldn't actively search for it if we can avoid it. Hence, lets now default to /bin/sh as debug shell on all systems, and permit Fedora to override that for their RPMs via --with-debug-shell= at configure time. --- NEWS | 5 +++++ configure.ac | 4 ++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/NEWS b/NEWS index 17aa5fb025..aad574ef74 100644 --- a/NEWS +++ b/NEWS @@ -2,6 +2,11 @@ systemd System and Service Manager CHANGES WITH 233 in spe + * The shell invoked by debug-shell.service now defaults to /bin/sh in + all cases. If distributions want to use a different shell for this + purpose (for example Fedora's /sbin/sushell) they need to specify + this explicitly at configure time using --with-debug-shell=. + * The confirmation spawn prompt has been reworked to offer the following choices: diff --git a/configure.ac b/configure.ac index 11bd46cbab..71b330f05d 100644 --- a/configure.ac +++ b/configure.ac @@ -531,8 +531,8 @@ AS_IF([test "x$enable_wheel_group" != "xno"], [ AC_ARG_WITH(debug-shell, AS_HELP_STRING([--with-debug-shell=PATH], [path to debug shell binary]), - [SUSHELL="$withval"],[ - AS_IF([test "x${have_selinux}" != "xno"], [SUSHELL="/sbin/sushell"] , [SUSHELL="/bin/sh"])]) + [SUSHELL="$withval"], + [SUSHELL="/bin/sh"]) AC_SUBST(SUSHELL) From 2467cc55f139cf540dc1ed6da5f3774fcbab4475 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Dec 2016 13:42:06 +0100 Subject: [PATCH 17/46] util-lib: read $SYSTEMD_PROC_CMDLINE if set when looking for the kernel cmdline if we want to parse the kernel command line, let's check the $SYSTEMD_PROC_CMDLINE environment variable first. This is useful for debugging purposes. --- ENVIRONMENT.md | 5 +++++ src/basic/proc-cmdline.c | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/ENVIRONMENT.md b/ENVIRONMENT.md index 4ace45751f..1ad2addfee 100644 --- a/ENVIRONMENT.md +++ b/ENVIRONMENT.md @@ -21,6 +21,11 @@ All tools: * `$SD_EVENT_PROFILE_DELAYS=1` — if set, the sd-event event loop implementation will print latency information at runtime. +* `$SYSTEMD_PROC_CMDLINE` — if set, may contain a string that is used as kernel + command line instead of the actual one readable from /proc/cmdline. This is + useful for debugging, in order to test generators and other code against + specific kernel command lines. + systemctl: * `$SYSTEMCTL_FORCE_BUS=1` — if set, do not connect to PID1's private D-Bus diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 8297a222b7..98c94ed5e2 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -34,8 +34,22 @@ #include "virt.h" int proc_cmdline(char **ret) { + const char *e; assert(ret); + /* For testing purposes it is sometimes useful to be able to override what we consider /proc/cmdline to be */ + e = secure_getenv("SYSTEMD_PROC_CMDLINE"); + if (e) { + char *m; + + m = strdup(e); + if (!m) + return -ENOMEM; + + *ret = m; + return 0; + } + if (detect_container() > 0) return get_process_cmdline(1, 0, false, ret); else From 1d84ad944520fc3e062ef518c4db4e1d3a1866af Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Dec 2016 18:29:15 +0100 Subject: [PATCH 18/46] util-lib: various improvements to kernel command line parsing This improves kernel command line parsing in a number of ways: a) An kernel option "foo_bar=xyz" is now considered equivalent to "foo-bar-xyz", i.e. when comparing kernel command line option names "-" and "_" are now considered equivalent (this only applies to the option names though, not the option values!). Most of our kernel options used "-" as word separator in kernel command line options so far, but some used "_". With this change, which was a source of confusion for users (well, at least of one user: myself, I just couldn't remember that it's systemd.debug-shell, not systemd.debug_shell). Considering both as equivalent is inspired how modern kernel module loading normalizes all kernel module names to use underscores now too. b) All options previously using a dash for separating words in kernel command line options now use an underscore instead, in all documentation and in code. Since a) has been implemented this should not create any compatibility problems, but normalizes our documentation and our code. c) All kernel command line options which take booleans (or are boolean-like) have been reworked so that "foobar" (without argument) is now equivalent to "foobar=1" (but not "foobar=0"), thus normalizing the handling of our boolean arguments. Specifically this means systemd.debug-shell and systemd_debug_shell=1 are now entirely equivalent. d) All kernel command line options which take an argument, and where no argument is specified will now result in a log message. e.g. passing just "systemd.unit" will no result in a complain that it needs an argument. This is implemented in the proc_cmdline_missing_value() function. e) There's now a call proc_cmdline_get_bool() similar to proc_cmdline_get_key() that parses booleans (following the logic explained in c). f) The proc_cmdline_parse() call's boolean argument has been replaced by a new flags argument that takes a common set of bits with proc_cmdline_get_key(). g) All kernel command line APIs now begin with the same "proc_cmdline_" prefix. h) There are now tests for much of this. Yay! --- man/kernel-command-line.xml | 22 +-- man/systemd-debug-generator.xml | 2 +- man/systemd-modules-load.service.xml | 4 +- man/systemd-udevd.service.xml | 16 +-- src/basic/cgroup-util.c | 39 ++--- src/basic/log.c | 25 ++-- src/basic/proc-cmdline.c | 133 ++++++++++++++---- src/basic/proc-cmdline.h | 33 ++++- src/core/main.c | 116 +++++++++------ src/cryptsetup/cryptsetup-generator.c | 46 +++--- src/debug-generator/debug-generator.c | 61 ++++---- src/firstboot/firstboot.c | 19 ++- src/fsck/fsck.c | 12 +- src/fstab-generator/fstab-generator.c | 36 +++-- src/gpt-auto-generator/gpt-auto-generator.c | 11 +- .../hibernate-resume-generator.c | 25 ++-- src/journal/journald-server.c | 56 ++++++-- src/modules-load/modules-load.c | 8 +- src/quotacheck/quotacheck.c | 7 +- src/test/test-proc-cmdline.c | 96 ++++++++++++- src/udev/net/link-config.c | 9 +- src/udev/udevd.c | 41 ++++-- 22 files changed, 561 insertions(+), 256 deletions(-) diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index c86e45265f..78e45e66a9 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -102,7 +102,7 @@ systemd.mask= systemd.wants= - systemd.debug-shell + systemd.debug_shell Additional parameters understood by systemd-debug-generator8, @@ -236,14 +236,14 @@ - udev.log-priority= - rd.udev.log-priority= - udev.children-max= - rd.udev.children-max= - udev.exec-delay= - rd.udev.exec-delay= - udev.event-timeout= - rd.udev.event-timeout= + udev.log_priority= + rd.udev.log_priority= + udev.children_max= + rd.udev.children_max= + udev.exec_delay= + rd.udev.exec_delay= + udev.event_timeout= + rd.udev.event_timeout= net.ifnames= @@ -332,8 +332,8 @@ - modules-load= - rd.modules-load= + modules_load= + rd.modules_load= Load a specific kernel module early at boot. For diff --git a/man/systemd-debug-generator.xml b/man/systemd-debug-generator.xml index 5c5e9fc4a1..0a66b9bbf9 100644 --- a/man/systemd-debug-generator.xml +++ b/man/systemd-debug-generator.xml @@ -70,7 +70,7 @@ the initial transaction. This is useful to start one or more additional units at boot. May be specified more than once. - If the option is + If the option is specified, the debug shell service debug-shell.service is pulled into the boot transaction. It will spawn a debug shell on tty9 during early diff --git a/man/systemd-modules-load.service.xml b/man/systemd-modules-load.service.xml index b25929b2e4..ee097d7f5c 100644 --- a/man/systemd-modules-load.service.xml +++ b/man/systemd-modules-load.service.xml @@ -73,8 +73,8 @@ - modules-load= - rd.modules-load= + modules_load= + rd.modules_load= Takes a comma-separated list of kernel modules to statically load during early boot. The option prefixed with diff --git a/man/systemd-udevd.service.xml b/man/systemd-udevd.service.xml index 243fd06471..81b957b0c8 100644 --- a/man/systemd-udevd.service.xml +++ b/man/systemd-udevd.service.xml @@ -133,22 +133,22 @@ Parameters starting with "rd." will be read when systemd-udevd is used in an initrd. - udev.log-priority= - rd.udev.log-priority= + udev.log_priority= + rd.udev.log_priority= Set the log level. - udev.children-max= - rd.udev.children-max= + udev.children_max= + rd.udev.children_max= Limit the number of events executed in parallel. - udev.exec-delay= - rd.udev.exec-delay= + udev.exec_delay= + rd.udev.exec_delay= Delay the execution of RUN instructions by the given number of seconds. This option might be useful when @@ -157,8 +157,8 @@ - udev.event-timeout= - rd.udev.event-timeout= + udev.event_timeout= + rd.udev.event_timeout= Wait for events to finish up to the given number of seconds. This option might be useful if events are diff --git a/src/basic/cgroup-util.c b/src/basic/cgroup-util.c index dc13025115..d2d18f13f0 100644 --- a/src/basic/cgroup-util.c +++ b/src/basic/cgroup-util.c @@ -2361,6 +2361,7 @@ int cg_enable_everywhere(CGroupMask supported, CGroupMask mask, const char *p) { bool cg_is_unified_wanted(void) { static thread_local int wanted = -1; int r, unified; + bool b; /* If the hierarchy is already mounted, then follow whatever * was chosen for it. */ @@ -2374,20 +2375,11 @@ bool cg_is_unified_wanted(void) { if (wanted >= 0) return wanted; - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy", NULL); - if (r > 0) - return (wanted = true); - else { - _cleanup_free_ char *value = NULL; + r = proc_cmdline_get_bool("systemd.unified_cgroup_hierarchy", &b); + if (r < 0) + return false; - r = get_proc_cmdline_key("systemd.unified_cgroup_hierarchy=", &value); - if (r < 0) - return false; - if (r == 0) - return (wanted = false); - - return (wanted = parse_boolean(value) > 0); - } + return (wanted = r > 0 ? b : false); } bool cg_is_legacy_wanted(void) { @@ -2397,6 +2389,7 @@ bool cg_is_legacy_wanted(void) { bool cg_is_unified_systemd_controller_wanted(void) { static thread_local int wanted = -1; int r, unified; + bool b; /* If the unified hierarchy is requested in full, no need to * bother with this. */ @@ -2415,23 +2408,11 @@ bool cg_is_unified_systemd_controller_wanted(void) { if (wanted >= 0) return wanted; - r = get_proc_cmdline_key("systemd.legacy_systemd_cgroup_controller", NULL); - if (r > 0) - wanted = false; - else { - _cleanup_free_ char *value = NULL; + r = proc_cmdline_get_bool("systemd.legacy_systemd_cgroup_controller", &b); + if (r < 0) + return false; - r = get_proc_cmdline_key("systemd.legacy_systemd_cgroup_controller=", &value); - if (r < 0) - return false; - - if (r == 0) - wanted = false; - else - wanted = parse_boolean(value) <= 0; - } - - return wanted; + return (wanted = r > 0 ? b : false); } bool cg_is_legacy_systemd_controller_wanted(void) { diff --git a/src/basic/log.c b/src/basic/log.c index 557212c022..1362b1c086 100644 --- a/src/basic/log.c +++ b/src/basic/log.c @@ -981,24 +981,30 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (streq(key, "debug") && !value) log_set_max_level(LOG_DEBUG); - else if (streq(key, "systemd.log_target") && value) { + else if (proc_cmdline_key_streq(key, "systemd.log_target")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (log_set_target_from_string(value) < 0) log_warning("Failed to parse log target '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_level") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_level")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (log_set_max_level_from_string(value) < 0) log_warning("Failed to parse log level '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_color") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_color")) { - if (log_show_color_from_string(value) < 0) + if (log_show_color_from_string(value ?: "1") < 0) log_warning("Failed to parse log color setting '%s'. Ignoring.", value); - } else if (streq(key, "systemd.log_location") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.log_location")) { - if (log_show_location_from_string(value) < 0) + if (log_show_location_from_string(value ?: "1") < 0) log_warning("Failed to parse log location setting '%s'. Ignoring.", value); } @@ -1009,10 +1015,9 @@ void log_parse_environment(void) { const char *e; if (get_ctty_devnr(0, NULL) < 0) - /* Only try to read the command line in daemons. - We assume that anything that has a controlling - tty is user stuff. */ - (void) parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + /* Only try to read the command line in daemons. We assume that anything that has a controlling tty is + user stuff. */ + (void) proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); e = secure_getenv("SYSTEMD_LOG_TARGET"); if (e && log_set_target_from_string(e) < 0) diff --git a/src/basic/proc-cmdline.c b/src/basic/proc-cmdline.c index 98c94ed5e2..6ecb6c3f0d 100644 --- a/src/basic/proc-cmdline.c +++ b/src/basic/proc-cmdline.c @@ -56,9 +56,8 @@ int proc_cmdline(char **ret) { return read_one_line_file("/proc/cmdline", ret); } -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, void *data), - void *data, - bool strip_prefix) { +int proc_cmdline_parse(proc_cmdline_parse_t parse_item, void *data, unsigned flags) { + _cleanup_free_ char *line = NULL; const char *p; int r; @@ -72,7 +71,7 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi p = line; for (;;) { _cleanup_free_ char *word = NULL; - char *value = NULL, *unprefixed; + char *value, *key, *q; r = extract_first_word(&p, &word, NULL, EXTRACT_QUOTES|EXTRACT_RELAX); if (r < 0) @@ -80,17 +79,23 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi if (r == 0) break; - /* Filter out arguments that are intended only for the - * initrd */ - unprefixed = startswith(word, "rd."); - if (unprefixed && !in_initrd()) - continue; + key = word; - value = strchr(word, '='); + /* Filter out arguments that are intended only for the initrd */ + q = startswith(word, "rd."); + if (q) { + if (!in_initrd()) + continue; + + if (flags & PROC_CMDLINE_STRIP_RD_PREFIX) + key = q; + } + + value = strchr(key, '='); if (value) *(value++) = 0; - r = parse_item(strip_prefix && unprefixed ? unprefixed : word, value, data); + r = parse_item(key, value, data); if (r < 0) return r; } @@ -98,13 +103,64 @@ int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, voi return 0; } -int get_proc_cmdline_key(const char *key, char **value) { +static bool relaxed_equal_char(char a, char b) { + + return a == b || + (a == '_' && b == '-') || + (a == '-' && b == '_'); +} + +char *proc_cmdline_key_startswith(const char *s, const char *prefix) { + + assert(s); + assert(prefix); + + /* Much like startswith(), but considers "-" and "_" the same */ + + for (; *prefix != 0; s++, prefix++) + if (!relaxed_equal_char(*s, *prefix)) + return NULL; + + return (char*) s; +} + +bool proc_cmdline_key_streq(const char *x, const char *y) { + assert(x); + assert(y); + + /* Much like streq(), but considers "-" and "_" the same */ + + for (; *x != 0 || *y != 0; x++, y++) + if (!relaxed_equal_char(*x, *y)) + return false; + + return true; +} + +int proc_cmdline_get_key(const char *key, unsigned flags, char **value) { _cleanup_free_ char *line = NULL, *ret = NULL; bool found = false; const char *p; int r; - assert(key); + /* Looks for a specific key on the kernel command line. Supports two modes: + * + * a) The "value" parameter is used. In this case a parameter beginning with the "key" string followed by "=" + * is searched, and the value following this is returned in "value". + * + * b) as above, but the PROC_CMDLINE_VALUE_OPTIONAL flag is set. In this case if the the key is found as a + * separate word (i.e. not followed by "=" but instead by whitespace or the end of the command line), then + * this is also accepted, and "value" is returned as NULL. + * + * c) The "value" parameter is NULL. In this case a search for the exact "key" parameter is performed. + * + * In all three cases, > 0 is returned if the key is found, 0 if not.*/ + + if (isempty(key)) + return -EINVAL; + + if ((flags & PROC_CMDLINE_VALUE_OPTIONAL) && !value) + return -EINVAL; r = proc_cmdline(&line); if (r < 0) @@ -121,21 +177,26 @@ int get_proc_cmdline_key(const char *key, char **value) { if (r == 0) break; - /* Filter out arguments that are intended only for the - * initrd */ + /* Automatically filter out arguments that are intended only for the initrd, if we are not in the + * initrd. */ if (!in_initrd() && startswith(word, "rd.")) continue; if (value) { - e = startswith(word, key); + e = proc_cmdline_key_startswith(word, key); if (!e) continue; - r = free_and_strdup(&ret, e); - if (r < 0) - return r; + if (*e == '=') { + r = free_and_strdup(&ret, e+1); + if (r < 0) + return r; + + found = true; + + } else if (*e == 0 && (flags & PROC_CMDLINE_VALUE_OPTIONAL)) + found = true; - found = true; } else { if (streq(word, key)) found = true; @@ -148,20 +209,42 @@ int get_proc_cmdline_key(const char *key, char **value) { } return found; +} +int proc_cmdline_get_bool(const char *key, bool *ret) { + _cleanup_free_ char *v = NULL; + int r; + + assert(ret); + + r = proc_cmdline_get_key(key, PROC_CMDLINE_VALUE_OPTIONAL, &v); + if (r < 0) + return r; + if (r == 0) { + *ret = false; + return 0; + } + + if (v) { /* parameter passed */ + r = parse_boolean(v); + if (r < 0) + return r; + *ret = r; + } else /* no parameter passed */ + *ret = true; + + return 1; } int shall_restore_state(void) { - _cleanup_free_ char *value = NULL; + bool ret; int r; - r = get_proc_cmdline_key("systemd.restore_state=", &value); + r = proc_cmdline_get_bool("systemd.restore_state", &ret); if (r < 0) return r; - if (r == 0) - return true; - return parse_boolean(value); + return r > 0 ? ret : true; } static const char * const rlmap[] = { diff --git a/src/basic/proc-cmdline.h b/src/basic/proc-cmdline.h index 6d6ee95c11..ebfed355e9 100644 --- a/src/basic/proc-cmdline.h +++ b/src/basic/proc-cmdline.h @@ -19,11 +19,36 @@ along with systemd; If not, see . ***/ +#include + +#include "log.h" + +enum { + PROC_CMDLINE_STRIP_RD_PREFIX = 1, + PROC_CMDLINE_VALUE_OPTIONAL = 2, +}; + +typedef int (*proc_cmdline_parse_t)(const char *key, const char *value, void *data); + int proc_cmdline(char **ret); -int parse_proc_cmdline(int (*parse_item)(const char *key, const char *value, void *data), - void *data, - bool strip_prefix); -int get_proc_cmdline_key(const char *parameter, char **value); + +int proc_cmdline_parse(const proc_cmdline_parse_t parse, void *userdata, unsigned flags); + +int proc_cmdline_get_key(const char *parameter, unsigned flags, char **value); +int proc_cmdline_get_bool(const char *key, bool *ret); + +char *proc_cmdline_key_startswith(const char *s, const char *prefix); +bool proc_cmdline_key_streq(const char *x, const char *y); int shall_restore_state(void); const char* runlevel_to_target(const char *rl); + +/* A little helper call, to be used in proc_cmdline_parse_t callbacks */ +static inline bool proc_cmdline_value_missing(const char *key, const char *value) { + if (!value) { + log_warning("Missing argument for %s= kernel command line switch, ignoring.", key); + return true; + } + + return false; +} diff --git a/src/core/main.c b/src/core/main.c index 6ec996577c..56a81ab94a 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -337,60 +337,73 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(key); - if (streq(key, "systemd.unit") && value) { + if (STR_IN_SET(key, "systemd.unit", "rd.systemd.unit")) { - if (!in_initrd()) - return free_and_strdup(&arg_default_unit, value); + if (proc_cmdline_value_missing(key, value)) + return 0; - } else if (streq(key, "rd.systemd.unit") && value) { + if (!unit_name_is_valid(value, UNIT_NAME_PLAIN|UNIT_NAME_INSTANCE)) + log_warning("Unit name specified on %s= is not valid, ignoring: %s", key, value); + else if (in_initrd() == !!startswith(key, "rd.")) { + if (free_and_strdup(&arg_default_unit, value) < 0) + return log_oom(); + } - if (in_initrd()) - return free_and_strdup(&arg_default_unit, value); + } else if (proc_cmdline_key_streq(key, "systemd.dump_core")) { - } else if (streq(key, "systemd.dump_core") && value) { - - r = parse_boolean(value); + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse dump core switch %s. Ignoring.", value); else arg_dump_core = r; - } else if (streq(key, "systemd.crash_chvt") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.crash_chvt")) { - if (parse_crash_chvt(value) < 0) + if (!value) + arg_crash_chvt = 0; /* turn on */ + else if (parse_crash_chvt(value) < 0) log_warning("Failed to parse crash chvt switch %s. Ignoring.", value); - } else if (streq(key, "systemd.crash_shell") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.crash_shell")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse crash shell switch %s. Ignoring.", value); else arg_crash_shell = r; - } else if (streq(key, "systemd.crash_reboot") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.crash_reboot")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse crash reboot switch %s. Ignoring.", value); else arg_crash_reboot = r; - } else if (streq(key, "systemd.confirm_spawn") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.confirm_spawn")) { + char *s; - arg_confirm_spawn = mfree(arg_confirm_spawn); - - r = parse_confirm_spawn(value, &arg_confirm_spawn); + r = parse_confirm_spawn(value, &s); if (r < 0) log_warning_errno(r, "Failed to parse confirm_spawn switch %s. Ignoring.", value); + else { + free(arg_confirm_spawn); + arg_confirm_spawn = s; + } - } else if (streq(key, "systemd.show_status") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.show_status")) { - r = parse_show_status(value, &arg_show_status); - if (r < 0) - log_warning("Failed to parse show status switch %s. Ignoring.", value); + if (value) { + r = parse_show_status(value, &arg_show_status); + if (r < 0) + log_warning("Failed to parse show status switch %s. Ignoring.", value); + } else + arg_show_status = SHOW_STATUS_YES; - } else if (streq(key, "systemd.default_standard_output") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.default_standard_output")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = exec_output_from_string(value); if (r < 0) @@ -398,7 +411,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat else arg_default_std_output = r; - } else if (streq(key, "systemd.default_standard_error") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.default_standard_error")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = exec_output_from_string(value); if (r < 0) @@ -406,24 +422,42 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat else arg_default_std_error = r; - } else if (streq(key, "systemd.setenv") && value) { + } else if (streq(key, "systemd.setenv")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (env_assignment_is_valid(value)) { char **env; env = strv_env_set(arg_default_environment, value); - if (env) - arg_default_environment = env; - else - log_warning_errno(ENOMEM, "Setting environment variable '%s' failed, ignoring: %m", value); + if (!env) + return log_oom(); + + arg_default_environment = env; } else log_warning("Environment variable name '%s' is not valid. Ignoring.", value); - } else if (streq(key, "systemd.machine_id") && value) { + } else if (proc_cmdline_key_streq(key, "systemd.machine_id")) { - r = set_machine_id(value); - if (r < 0) - log_warning("MachineID '%s' is not valid. Ignoring.", value); + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = set_machine_id(value); + if (r < 0) + log_warning("MachineID '%s' is not valid. Ignoring.", value); + + } else if (proc_cmdline_key_streq(key, "systemd.default_timeout_start_sec")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = parse_sec(value, &arg_default_timeout_start_usec); + if (r < 0) + log_warning_errno(r, "Failed to parse default start timeout: %s, ignoring.", value); + + if (arg_default_timeout_start_usec <= 0) + arg_default_timeout_start_usec = USEC_INFINITY; } else if (streq(key, "quiet") && !value) { @@ -445,15 +479,6 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat target = runlevel_to_target(key); if (target) return free_and_strdup(&arg_default_unit, target); - - } else if (streq(key, "systemd.default_timeout_start_sec") && value) { - - r = parse_sec(value, &arg_default_timeout_start_usec); - if (r < 0) - log_warning_errno(r, "Failed to parse default start timeout: %s, ignoring.", value); - - if (arg_default_timeout_start_usec <= 0) - arg_default_timeout_start_usec = USEC_INFINITY; } return 0; @@ -1341,10 +1366,9 @@ static int fixup_environment(void) { * However if TERM was configured through the kernel * command line then leave it alone. */ - r = get_proc_cmdline_key("TERM=", &term); + r = proc_cmdline_get_key("TERM", 0, &term); if (r < 0) return r; - if (r == 0) { term = strdup(default_term_for_tty("/dev/console")); if (!term) @@ -1591,7 +1615,7 @@ int main(int argc, char *argv[]) { } if (arg_system) { - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); } diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index 68029865a0..e2a43f7195 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -278,27 +278,30 @@ static crypto_device *get_crypto_device(const char *uuid) { } static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { - int r; - crypto_device *d; _cleanup_free_ char *uuid = NULL, *uuid_value = NULL; + crypto_device *d; + int r; - if (streq(key, "luks") && value) { + if (streq(key, "luks")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : 1; if (r < 0) - log_warning("Failed to parse luks switch %s. Ignoring.", value); + log_warning("Failed to parse luks= kernel command line switch %s. Ignoring.", value); else arg_enabled = r; - } else if (streq(key, "luks.crypttab") && value) { + } else if (streq(key, "luks.crypttab")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : 1; if (r < 0) - log_warning("Failed to parse luks crypttab switch %s. Ignoring.", value); + log_warning("Failed to parse luks.crypttab= kernel command line switch %s. Ignoring.", value); else arg_read_crypttab = r; - } else if (streq(key, "luks.uuid") && value) { + } else if (streq(key, "luks.uuid")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; d = get_crypto_device(startswith(value, "luks-") ? value+5 : value); if (!d) @@ -306,7 +309,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat d->create = arg_whitelist = true; - } else if (streq(key, "luks.options") && value) { + } else if (streq(key, "luks.options")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -314,13 +320,14 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (!d) return log_oom(); - free(d->options); - d->options = uuid_value; - uuid_value = NULL; + free_and_replace(d->options, uuid_value); } else if (free_and_strdup(&arg_default_options, value) < 0) return log_oom(); - } else if (streq(key, "luks.key") && value) { + } else if (streq(key, "luks.key")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -328,13 +335,14 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (!d) return log_oom(); - free(d->keyfile); - d->keyfile = uuid_value; - uuid_value = NULL; + free_and_replace(d->keyfile, uuid_value); } else if (free_and_strdup(&arg_default_keyfile, value) < 0) return log_oom(); - } else if (streq(key, "luks.name") && value) { + } else if (streq(key, "luks.name")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; r = sscanf(value, "%m[0-9a-fA-F-]=%ms", &uuid, &uuid_value); if (r == 2) { @@ -478,7 +486,7 @@ int main(int argc, char *argv[]) { if (!arg_disks) goto cleanup; - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) { log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); r = EXIT_FAILURE; diff --git a/src/debug-generator/debug-generator.c b/src/debug-generator/debug-generator.c index 28ebe36b38..1d8bc71e57 100644 --- a/src/debug-generator/debug-generator.c +++ b/src/debug-generator/debug-generator.c @@ -39,56 +39,53 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(key); if (streq(key, "systemd.mask")) { + char *n; - if (!value) - log_error("Missing argument for systemd.mask= kernel command line parameter."); - else { - char *n; + if (proc_cmdline_value_missing(key, value)) + return 0; - r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to glob unit name: %m"); + r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to glob unit name: %m"); - r = strv_consume(&arg_mask, n); - if (r < 0) - return log_oom(); - } + r = strv_consume(&arg_mask, n); + if (r < 0) + return log_oom(); } else if (streq(key, "systemd.wants")) { + char *n; - if (!value) - log_error("Missing argument for systemd.want= kernel command line parameter."); - else { - char *n; + if (proc_cmdline_value_missing(key, value)) + return 0; - r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); - if (r < 0) - return log_error_errno(r, "Failed to glob unit name: %m"); + r = unit_name_mangle(value, UNIT_NAME_NOGLOB, &n); + if (r < 0) + return log_error_errno(r, "Failed to glob unit name: %m"); - r = strv_consume(&arg_wants, n); - if (r < 0) - return log_oom(); - } + r = strv_consume(&arg_wants, n); + if (r < 0) + return log_oom(); - } else if (streq(key, "systemd.debug-shell")) { + } else if (proc_cmdline_key_streq(key, "systemd.debug_shell")) { if (value) { r = parse_boolean(value); if (r < 0) - log_error("Failed to parse systemd.debug-shell= argument '%s', ignoring.", value); + log_error("Failed to parse systemd.debug_shell= argument '%s', ignoring.", value); else arg_debug_shell = r; } else arg_debug_shell = true; + } else if (streq(key, "systemd.unit")) { - if (!value) - log_error("Missing argument for systemd.unit= kernel command line parameter."); - else { - r = free_and_strdup(&arg_default_unit, value); - if (r < 0) - return log_error_errno(r, "Failed to set default unit %s: %m", value); - } + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_default_unit, value); + if (r < 0) + return log_error_errno(r, "Failed to set default unit %s: %m", value); + } else if (!value) { const char *target; @@ -173,7 +170,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c index 7c615abbc5..fd7051f21e 100644 --- a/src/firstboot/firstboot.c +++ b/src/firstboot/firstboot.c @@ -826,7 +826,7 @@ static int parse_argv(int argc, char *argv[]) { } int main(int argc, char *argv[]) { - _cleanup_free_ char *enabled = NULL; + bool enabled; int r; r = parse_argv(argc, argv); @@ -839,15 +839,14 @@ int main(int argc, char *argv[]) { umask(0022); - r = get_proc_cmdline_key("systemd.firstboot=", &enabled); - if (r < 0) - return r; - if (r > 0) { - r = parse_boolean(enabled); - if (r == 0) - goto finish; - if (r < 0) - log_warning_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring: %s", enabled); + r = proc_cmdline_get_bool("systemd.firstboot", &enabled); + if (r < 0) { + log_error_errno(r, "Failed to parse systemd.firstboot= kernel command line argument, ignoring."); + goto finish; + } + if (r > 0 && !enabled) { + r = 0; /* disabled */ + goto finish; } r = process_locale(); diff --git a/src/fsck/fsck.c b/src/fsck/fsck.c index be25c6a2b2..2100681e17 100644 --- a/src/fsck/fsck.c +++ b/src/fsck/fsck.c @@ -99,7 +99,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(key); - if (streq(key, "fsck.mode") && value) { + if (streq(key, "fsck.mode")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (streq(value, "auto")) arg_force = arg_skip = false; @@ -110,7 +113,10 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat else log_warning("Invalid fsck.mode= parameter '%s'. Ignoring.", value); - } else if (streq(key, "fsck.repair") && value) { + } else if (streq(key, "fsck.repair")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (streq(value, "preen")) arg_repair = "-a"; @@ -293,7 +299,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index f6a912ae06..84163abbc5 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -612,27 +612,36 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat * instance should take precedence. In the case of multiple rootflags= * or usrflags= the arguments should be concatenated */ - if (STR_IN_SET(key, "fstab", "rd.fstab") && value) { + if (STR_IN_SET(key, "fstab", "rd.fstab")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : 1; if (r < 0) log_warning("Failed to parse fstab switch %s. Ignoring.", value); else arg_fstab_enabled = r; - } else if (streq(key, "root") && value) { + } else if (streq(key, "root")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (free_and_strdup(&arg_root_what, value) < 0) return log_oom(); - } else if (streq(key, "rootfstype") && value) { + } else if (streq(key, "rootfstype")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (free_and_strdup(&arg_root_fstype, value) < 0) return log_oom(); - } else if (streq(key, "rootflags") && value) { + } else if (streq(key, "rootflags")) { char *o; + if (proc_cmdline_value_missing(key, value)) + return 0; + o = arg_root_options ? strjoin(arg_root_options, ",", value) : strdup(value); @@ -642,19 +651,28 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat free(arg_root_options); arg_root_options = o; - } else if (streq(key, "mount.usr") && value) { + } else if (streq(key, "mount.usr")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (free_and_strdup(&arg_usr_what, value) < 0) return log_oom(); - } else if (streq(key, "mount.usrfstype") && value) { + } else if (streq(key, "mount.usrfstype")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (free_and_strdup(&arg_usr_fstype, value) < 0) return log_oom(); - } else if (streq(key, "mount.usrflags") && value) { + } else if (streq(key, "mount.usrflags")) { char *o; + if (proc_cmdline_value_missing(key, value)) + return 0; + o = arg_usr_options ? strjoin(arg_usr_options, ",", value) : strdup(value); @@ -689,7 +707,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index a60905de94..f94f9f7827 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -912,15 +912,18 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(key); - if (STR_IN_SET(key, "systemd.gpt_auto", "rd.systemd.gpt_auto") && value) { + if (STR_IN_SET(key, "systemd.gpt_auto", "rd.systemd.gpt_auto")) { - r = parse_boolean(value); + r = value ? parse_boolean(value) : 1; if (r < 0) log_warning("Failed to parse gpt-auto switch \"%s\". Ignoring.", value); else arg_enabled = r; - } else if (streq(key, "root") && value) { + } else if (streq(key, "root")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; /* Disable root disk logic if there's a root= value * specified (unless it happens to be "gpt-auto") */ @@ -1018,7 +1021,7 @@ int main(int argc, char *argv[]) { return EXIT_SUCCESS; } - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/hibernate-resume/hibernate-resume-generator.c b/src/hibernate-resume/hibernate-resume-generator.c index 9f59c04b26..a97fe668d5 100644 --- a/src/hibernate-resume/hibernate-resume-generator.c +++ b/src/hibernate-resume/hibernate-resume-generator.c @@ -31,15 +31,22 @@ #include "util.h" static const char *arg_dest = "/tmp"; -static char *arg_resume_dev = NULL; +static char *arg_resume_device = NULL; static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { - if (streq(key, "resume") && value) { - free(arg_resume_dev); - arg_resume_dev = fstab_node_to_udev_node(value); - if (!arg_resume_dev) + if (streq(key, "resume")) { + char *s; + + if (proc_cmdline_value_missing(key, value)) + return 0; + + s = fstab_node_to_udev_node(value); + if (!s) return log_oom(); + + free(arg_resume_device); + arg_resume_device = s; } return 0; @@ -49,10 +56,10 @@ static int process_resume(void) { _cleanup_free_ char *name = NULL, *lnk = NULL; int r; - if (!arg_resume_dev) + if (!arg_resume_device) return 0; - r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_dev, ".service", &name); + r = unit_name_from_path_instance("systemd-hibernate-resume", arg_resume_device, ".service", &name); if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); @@ -88,12 +95,12 @@ int main(int argc, char *argv[]) { if (!in_initrd()) return EXIT_SUCCESS; - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); r = process_resume(); - free(arg_resume_dev); + free(arg_resume_device); return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 5c6941ebd6..6bdb375fb7 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -1532,60 +1532,93 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat assert(s); - if (streq(key, "systemd.journald.forward_to_syslog")) { + if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_syslog")) { + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse forward to syslog switch \"%s\". Ignoring.", value); else s->forward_to_syslog = r; - } else if (streq(key, "systemd.journald.forward_to_kmsg")) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_kmsg")) { + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse forward to kmsg switch \"%s\". Ignoring.", value); else s->forward_to_kmsg = r; - } else if (streq(key, "systemd.journald.forward_to_console")) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_console")) { + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse forward to console switch \"%s\". Ignoring.", value); else s->forward_to_console = r; - } else if (streq(key, "systemd.journald.forward_to_wall")) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.forward_to_wall")) { + r = value ? parse_boolean(value) : true; if (r < 0) log_warning("Failed to parse forward to wall switch \"%s\". Ignoring.", value); else s->forward_to_wall = r; - } else if (streq(key, "systemd.journald.max_level_console") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_console")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level console value \"%s\". Ignoring.", value); else s->max_level_console = r; - } else if (streq(key, "systemd.journald.max_level_store") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_store")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level store value \"%s\". Ignoring.", value); else s->max_level_store = r; - } else if (streq(key, "systemd.journald.max_level_syslog") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_syslog")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level syslog value \"%s\". Ignoring.", value); else s->max_level_syslog = r; - } else if (streq(key, "systemd.journald.max_level_kmsg") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_kmsg")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level kmsg value \"%s\". Ignoring.", value); else s->max_level_kmsg = r; - } else if (streq(key, "systemd.journald.max_level_wall") && value) { + + } else if (proc_cmdline_key_streq(key, "systemd.journald.max_level_wall")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = log_level_from_string(value); if (r < 0) log_warning("Failed to parse max level wall value \"%s\". Ignoring.", value); else s->max_level_wall = r; + } else if (startswith(key, "systemd.journald")) log_warning("Unknown journald kernel command line option \"%s\". Ignoring.", key); @@ -1898,7 +1931,10 @@ int server_init(Server *s) { journal_reset_metrics(&s->runtime_storage.metrics); server_parse_config_file(s); - parse_proc_cmdline(parse_proc_cmdline_item, s, true); + + r = proc_cmdline_parse(parse_proc_cmdline_item, s, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) + log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); if (!!s->rate_limit_interval ^ !!s->rate_limit_burst) { log_debug("Setting both rate limit interval and burst from "USEC_FMT",%u to 0,0", diff --git a/src/modules-load/modules-load.c b/src/modules-load/modules-load.c index 0901fea8dc..615998a6f6 100644 --- a/src/modules-load/modules-load.c +++ b/src/modules-load/modules-load.c @@ -62,7 +62,11 @@ static int add_modules(const char *p) { static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; - if (streq(key, "modules-load") && value) { + if (proc_cmdline_key_streq(key, "modules_load")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = add_modules(value); if (r < 0) return r; @@ -226,7 +230,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c index 2714cde5c7..2495bcbefd 100644 --- a/src/quotacheck/quotacheck.c +++ b/src/quotacheck/quotacheck.c @@ -34,7 +34,10 @@ static bool arg_force = false; static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { - if (streq(key, "quotacheck.mode") && value) { + if (streq(key, "quotacheck.mode")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; if (streq(value, "auto")) arg_force = arg_skip = false; @@ -88,7 +91,7 @@ int main(int argc, char *argv[]) { umask(0022); - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, false); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, 0); if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); diff --git a/src/test/test-proc-cmdline.c b/src/test/test-proc-cmdline.c index 4101678f19..12dac8585b 100644 --- a/src/test/test-proc-cmdline.c +++ b/src/test/test-proc-cmdline.c @@ -35,8 +35,8 @@ static int parse_item(const char *key, const char *value, void *data) { return 0; } -static void test_parse_proc_cmdline(void) { - assert_se(parse_proc_cmdline(parse_item, &obj, true) >= 0); +static void test_proc_cmdline_parse(void) { + assert_se(proc_cmdline_parse(parse_item, &obj, true) >= 0); } static void test_runlevel_to_target(void) { @@ -55,11 +55,101 @@ static void test_runlevel_to_target(void) { assert_se(streq_ptr(runlevel_to_target("rd.rescue"), SPECIAL_RESCUE_TARGET)); } +static void test_proc_cmdline_get_key(void) { + _cleanup_free_ char *value = NULL; + + putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar=quux wuff-piep=tuet zumm"); + + assert_se(proc_cmdline_get_key("", 0, &value) == -EINVAL); + assert_se(proc_cmdline_get_key("abc", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("abc", 0, &value) == 0 && value == NULL); + assert_se(proc_cmdline_get_key("abc", PROC_CMDLINE_VALUE_OPTIONAL, &value) == 0 && value == NULL); + + assert_se(proc_cmdline_get_key("foo_bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo_bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo-bar", 0, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo-bar", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "quux")); + value = mfree(value); + assert_se(proc_cmdline_get_key("foo-bar", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("foo-bar", PROC_CMDLINE_VALUE_OPTIONAL, NULL) == -EINVAL); + + assert_se(proc_cmdline_get_key("wuff-piep", 0, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff-piep", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", 0, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && streq_ptr(value, "tuet")); + value = mfree(value); + assert_se(proc_cmdline_get_key("wuff_piep", 0, NULL) == 0); + assert_se(proc_cmdline_get_key("wuff_piep", PROC_CMDLINE_VALUE_OPTIONAL, NULL) == -EINVAL); + + assert_se(proc_cmdline_get_key("zumm", 0, &value) == 0 && value == NULL); + assert_se(proc_cmdline_get_key("zumm", PROC_CMDLINE_VALUE_OPTIONAL, &value) > 0 && value == NULL); + assert_se(proc_cmdline_get_key("zumm", 0, NULL) > 0); +} + +static void test_proc_cmdline_get_bool(void) { + bool value = false; + + putenv((char*) "SYSTEMD_PROC_CMDLINE=foo_bar bar-waldo=1 x_y-z=0 quux=miep"); + + assert_se(proc_cmdline_get_bool("", &value) == -EINVAL); + assert_se(proc_cmdline_get_bool("abc", &value) == 0 && value == false); + assert_se(proc_cmdline_get_bool("foo_bar", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("foo-bar", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("bar-waldo", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("bar_waldo", &value) > 0 && value == true); + assert_se(proc_cmdline_get_bool("x_y-z", &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x-y-z", &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x-y_z", &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("x_y_z", &value) > 0 && value == false); + assert_se(proc_cmdline_get_bool("quux", &value) == -EINVAL && value == false); +} + +static void test_proc_cmdline_key_streq(void) { + + assert_se(proc_cmdline_key_streq("", "")); + assert_se(proc_cmdline_key_streq("a", "a")); + assert_se(!proc_cmdline_key_streq("", "a")); + assert_se(!proc_cmdline_key_streq("a", "")); + assert_se(proc_cmdline_key_streq("a", "a")); + assert_se(!proc_cmdline_key_streq("a", "b")); + assert_se(proc_cmdline_key_streq("x-y-z", "x-y-z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x_y_z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x-y_z")); + assert_se(proc_cmdline_key_streq("x-y-z", "x_y-z")); + assert_se(proc_cmdline_key_streq("x_y-z", "x-y_z")); + assert_se(!proc_cmdline_key_streq("x_y-z", "x-z_z")); +} + +static void test_proc_cmdline_key_startswith(void) { + + assert_se(proc_cmdline_key_startswith("", "")); + assert_se(proc_cmdline_key_startswith("x", "")); + assert_se(!proc_cmdline_key_startswith("", "x")); + assert_se(proc_cmdline_key_startswith("x", "x")); + assert_se(!proc_cmdline_key_startswith("x", "y")); + assert_se(!proc_cmdline_key_startswith("foo-bar", "quux")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo-bar")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo_bar")); + assert_se(proc_cmdline_key_startswith("foo-bar", "foo_")); + assert_se(!proc_cmdline_key_startswith("foo-bar", "foo_xx")); +} + int main(void) { log_parse_environment(); log_open(); - test_parse_proc_cmdline(); + test_proc_cmdline_parse(); + test_proc_cmdline_key_streq(); + test_proc_cmdline_key_startswith(); + test_proc_cmdline_get_key(); + test_proc_cmdline_get_bool(); test_runlevel_to_target(); return 0; diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c index 1dca375279..3af87f1388 100644 --- a/src/udev/net/link-config.c +++ b/src/udev/net/link-config.c @@ -192,14 +192,9 @@ static int load_link(link_config_ctx *ctx, const char *filename) { } static bool enable_name_policy(void) { - _cleanup_free_ char *value = NULL; - int r; + bool b; - r = get_proc_cmdline_key("net.ifnames=", &value); - if (r > 0 && streq(value, "0")) - return false; - - return true; + return proc_cmdline_get_bool("net.ifnames", &b) <= 0 || b; } int link_config_load(link_config_ctx *ctx) { diff --git a/src/udev/udevd.c b/src/udev/udevd.c index 895c6f271b..dd23054b0d 100644 --- a/src/udev/udevd.c +++ b/src/udev/udevd.c @@ -1357,10 +1357,10 @@ static int listen_fds(int *rctrl, int *rnetlink) { /* * read the kernel command line, in case we need to get into debug mode - * udev.log-priority= syslog priority - * udev.children-max= events are fully serialized if set to 1 - * udev.exec-delay= delay execution of every executed program - * udev.event-timeout= seconds to wait before terminating an event + * udev.log_priority= syslog priority + * udev.children_max= events are fully serialized if set to 1 + * udev.exec_delay= delay execution of every executed program + * udev.event_timeout= seconds to wait before terminating an event */ static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r = 0; @@ -1370,25 +1370,46 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat if (!value) return 0; - if (streq(key, "udev.log-priority") && value) { + if (proc_cmdline_key_streq(key, "udev.log_priority")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = util_log_priority(value); if (r >= 0) log_set_max_level(r); - } else if (streq(key, "udev.event-timeout") && value) { + + } else if (proc_cmdline_key_streq(key, "udev.event_timeout")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = safe_atou64(value, &arg_event_timeout_usec); if (r >= 0) { arg_event_timeout_usec *= USEC_PER_SEC; arg_event_timeout_warn_usec = (arg_event_timeout_usec / 3) ? : 1; } - } else if (streq(key, "udev.children-max") && value) + + } else if (proc_cmdline_key_streq(key, "udev.children_max")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = safe_atou(value, &arg_children_max); - else if (streq(key, "udev.exec-delay") && value) + + } else if (proc_cmdline_key_streq(key, "udev.exec_delay")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + r = safe_atoi(value, &arg_exec_delay); - else if (startswith(key, "udev.")) + + } else if (startswith(key, "udev.")) log_warning("Unknown udev kernel command line option \"%s\"", key); if (r < 0) log_warning_errno(r, "Failed to parse \"%s=%s\", ignoring: %m", key, value); + return 0; } @@ -1649,7 +1670,7 @@ int main(int argc, char *argv[]) { if (r <= 0) goto exit; - r = parse_proc_cmdline(parse_proc_cmdline_item, NULL, true); + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) log_warning_errno(r, "failed to parse kernel command line, ignoring: %m"); From 57ab9f893031ec4ae205edc1c32742459a0f1289 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Dec 2016 18:43:38 +0100 Subject: [PATCH 19/46] qutoacheck,gpt-auto-generator: trivial coding style improvements --- src/gpt-auto-generator/gpt-auto-generator.c | 8 +++----- src/quotacheck/quotacheck.c | 8 +++++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index f94f9f7827..f993c5d1e2 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -986,11 +986,11 @@ static int add_mounts(void) { r = get_block_device_harder("/", &devno); if (r < 0) return log_error_errno(r, "Failed to determine block device of root file system: %m"); - else if (r == 0) { + if (r == 0) { r = get_block_device_harder("/usr", &devno); if (r < 0) return log_error_errno(r, "Failed to determine block device of /usr file system: %m"); - else if (r == 0) { + if (r == 0) { log_debug("Neither root nor /usr file system are on a (single) block device."); return 0; } @@ -1000,7 +1000,7 @@ static int add_mounts(void) { } int main(int argc, char *argv[]) { - int r = 0; + int r = 0, k; if (argc > 1 && argc != 4) { log_error("This program takes three or no arguments."); @@ -1034,8 +1034,6 @@ int main(int argc, char *argv[]) { r = add_root_mount(); if (!in_initrd()) { - int k; - k = add_mounts(); if (k < 0) r = k; diff --git a/src/quotacheck/quotacheck.c b/src/quotacheck/quotacheck.c index 2495bcbefd..a42fce377e 100644 --- a/src/quotacheck/quotacheck.c +++ b/src/quotacheck/quotacheck.c @@ -107,9 +107,10 @@ int main(int argc, char *argv[]) { pid = fork(); if (pid < 0) { - log_error_errno(errno, "fork(): %m"); - return EXIT_FAILURE; - } else if (pid == 0) { + r = log_error_errno(errno, "fork(): %m"); + goto finish; + } + if (pid == 0) { /* Child */ @@ -123,5 +124,6 @@ int main(int argc, char *argv[]) { r = wait_for_terminate_and_warn("quotacheck", pid, true); +finish: return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } From 4ad14eff19f644fe632adf6ecc292a8e88540801 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Dec 2016 19:46:56 +0100 Subject: [PATCH 20/46] nspawn: restore --volatile=yes support This was broken by 19caffac75a2590a0c5ebc2a0214960f8188aec7 which remounted the root directory to MS_SHARED before applying the volatile mount logic. This broke things as MS_MOVE is incompatible with MS_SHARED directory trees, and we need MS_MOVE in the volatile mount logic to rearrange the directory tree. Simply swap the order here, apply the volatile logic before we switch to MS_SHARED. --- src/nspawn/nspawn.c | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 2e879d7d7f..01d89df1a4 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2470,20 +2470,6 @@ static int outer_child( if (r < 0) return r; - /* Mark everything as shared so our mounts get propagated down. This is - * required to make new bind mounts available in systemd services - * inside the containter that create a new mount namespace. - * See https://github.com/systemd/systemd/issues/3860 - * Further submounts (such as /dev) done after this will inherit the - * shared propagation mode.*/ - r = mount_verbose(LOG_ERR, NULL, directory, NULL, MS_SHARED|MS_REC, NULL); - if (r < 0) - return r; - - r = recursive_chown(directory, arg_uid_shift, arg_uid_range); - if (r < 0) - return r; - r = setup_volatile( directory, arg_volatile_mode, @@ -2504,6 +2490,20 @@ static int outer_child( if (r < 0) return r; + /* Mark everything as shared so our mounts get propagated down. This is + * required to make new bind mounts available in systemd services + * inside the containter that create a new mount namespace. + * See https://github.com/systemd/systemd/issues/3860 + * Further submounts (such as /dev) done after this will inherit the + * shared propagation mode.*/ + r = mount_verbose(LOG_ERR, NULL, directory, NULL, MS_SHARED|MS_REC, NULL); + if (r < 0) + return r; + + r = recursive_chown(directory, arg_uid_shift, arg_uid_range); + if (r < 0) + return r; + r = base_filesystem_create(directory, arg_uid_shift, (gid_t) arg_uid_shift); if (r < 0) return r; From f78273c8dacf678cc8fd7387f678e6344a99405c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Dec 2016 20:54:45 +0100 Subject: [PATCH 21/46] journald: don't flush to /var/log/journal before we get asked to This changes journald to not write to /var/log/journal until it received SIGUSR1 for the first time, thus having been requested to flush the runtime journal to disk. This makes the journal work nicer with systems which have the root file system writable early, but still need to rearrange /var before journald should start writing and creating files to it, for example because ACLs need to be applied first, or because /var is to be mounted from another file system, NFS or tmpfs (as is the case for systemd.volatile=state). Before this change we required setupts with /var split out to mount the root disk read-only early on, and ship an /etc/fstab that remounted it writable only after having placed /var at the right place. But even that was racy for various preparations as journald might end up accessing the file system before it was entirely set up, as soon as it was writable. With this change we make scheduling when to start writing to /var/log/journal explicit. This means persistent mode now requires systemd-journal-flush.service in the mix to work, as otherwise journald would never write to the directory. See: #1397 --- src/journal/journald-server.c | 21 +++++++++++---------- src/journal/journald-server.h | 2 +- src/journal/journald.c | 2 +- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c index 6bdb375fb7..8b92ea3def 100644 --- a/src/journal/journald-server.c +++ b/src/journal/journald-server.c @@ -283,17 +283,16 @@ static int open_journal( } static bool flushed_flag_is_set(void) { - return (access("/run/systemd/journal/flushed", F_OK) >= 0); + return access("/run/systemd/journal/flushed", F_OK) >= 0; } static int system_journal_open(Server *s, bool flush_requested) { - bool flushed = false; const char *fn; int r = 0; if (!s->system_journal && - (s->storage == STORAGE_PERSISTENT || s->storage == STORAGE_AUTO) && - (flush_requested || (flushed = flushed_flag_is_set()))) { + IN_SET(s->storage, STORAGE_PERSISTENT, STORAGE_AUTO) && + (flush_requested || flushed_flag_is_set())) { /* If in auto mode: first try to create the machine * path, but not the prefix. @@ -326,8 +325,8 @@ static int system_journal_open(Server *s, bool flush_requested) { * Perform an implicit flush to var, leaving the runtime * journal closed, now that the system journal is back. */ - if (s->runtime_journal && flushed) - (void) server_flush_to_var(s); + if (!flush_requested) + (void) server_flush_to_var(s, true); } if (!s->runtime_journal && @@ -1183,7 +1182,7 @@ finish: dispatch_message_real(s, iovec, n, m, ucred, tv, label, label_len, unit_id, priority, object_pid); } -int server_flush_to_var(Server *s) { +int server_flush_to_var(Server *s, bool require_flag_file) { sd_id128_t machine; sd_journal *j = NULL; char ts[FORMAT_TIMESPAN_MAX]; @@ -1193,13 +1192,15 @@ int server_flush_to_var(Server *s) { assert(s); - if (s->storage != STORAGE_AUTO && - s->storage != STORAGE_PERSISTENT) + if (!IN_SET(s->storage, STORAGE_AUTO, STORAGE_PERSISTENT)) return 0; if (!s->runtime_journal) return 0; + if (require_flag_file && !flushed_flag_is_set()) + return 0; + (void) system_journal_open(s, true); if (!s->system_journal) @@ -1411,7 +1412,7 @@ static int dispatch_sigusr1(sd_event_source *es, const struct signalfd_siginfo * log_info("Received request to flush runtime journal from PID " PID_FMT, si->ssi_pid); - (void) server_flush_to_var(s); + (void) server_flush_to_var(s, false); server_sync(s); server_vacuum(s, false); diff --git a/src/journal/journald-server.h b/src/journal/journald-server.h index 99d91496be..de1c48f805 100644 --- a/src/journal/journald-server.h +++ b/src/journal/journald-server.h @@ -197,7 +197,7 @@ void server_sync(Server *s); int server_vacuum(Server *s, bool verbose); void server_rotate(Server *s); int server_schedule_sync(Server *s, int priority); -int server_flush_to_var(Server *s); +int server_flush_to_var(Server *s, bool require_flag_file); void server_maybe_append_tags(Server *s); int server_process_datagram(sd_event_source *es, int fd, uint32_t revents, void *userdata); void server_space_usage_message(Server *s, JournalStorage *storage); diff --git a/src/journal/journald.c b/src/journal/journald.c index fc26ef1785..54fd1f999d 100644 --- a/src/journal/journald.c +++ b/src/journal/journald.c @@ -52,7 +52,7 @@ int main(int argc, char *argv[]) { goto finish; server_vacuum(&server, false); - server_flush_to_var(&server); + server_flush_to_var(&server, true); server_flush_dev_kmsg(&server); log_debug("systemd-journald running as pid "PID_FMT, getpid()); From e5b422034160937712fe7a546f175b883e39e763 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 12 Dec 2016 21:03:34 +0100 Subject: [PATCH 22/46] shared: rework switch_root() code Let's follow symlinks before invoking mount() on arbitrary paths, so that we won't get confused if directories are prepared with absolute symlinks. Use FOREACH_STRING() instead of NULSTR_FOREACH() as it is more readable. Don't use snprintf() for concatenating strings, let chase_symlinks() to that. Replace homegrown mount check with path_is_mount_point(). Also, change the behaviour when we encounter this: instead of unmounting the old mount point, simply leave it around and don't replace it, so that initrds can mount stuff there with different settings than we would apply. This is in-line with how we handle automatic mounts in nspawn for example. Use umount_recursive() instead of a simple umount2() for unmounting the old root, so that we actually cover really all mounts, not just the top-level one. --- src/shared/switch-root.c | 133 +++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 77 deletions(-) diff --git a/src/shared/switch-root.c b/src/shared/switch-root.c index 4eff4f692e..afdf1ab5ad 100644 --- a/src/shared/switch-root.c +++ b/src/shared/switch-root.c @@ -28,123 +28,102 @@ #include "base-filesystem.h" #include "fd-util.h" +#include "fs-util.h" #include "log.h" #include "missing.h" #include "mkdir.h" +#include "mount-util.h" #include "path-util.h" #include "rm-rf.h" #include "stdio-util.h" #include "string-util.h" +#include "strv.h" #include "switch-root.h" #include "user-util.h" #include "util.h" -int switch_root(const char *new_root, const char *oldroot, bool detach_oldroot, unsigned long mountflags) { - - /* Don't try to unmount/move the old "/", there's no way to do it. */ - static const char move_mounts[] = - "/dev\0" - "/proc\0" - "/sys\0" - "/run\0"; +int switch_root(const char *new_root, + const char *old_root_after, /* path below the new root, where to place the old root after the transition */ + bool unmount_old_root, + unsigned long mount_flags) { /* MS_MOVE or MS_BIND */ + _cleanup_free_ char *resolved_old_root_after = NULL; _cleanup_close_ int old_root_fd = -1; - struct stat new_root_stat; bool old_root_remove; - const char *i, *temporary_old_root; + const char *i; + int r; + + assert(new_root); + assert(old_root_after); if (path_equal(new_root, "/")) return 0; - temporary_old_root = strjoina(new_root, oldroot); - mkdir_p_label(temporary_old_root, 0755); - + /* Check if we shall remove the contents of the old root */ old_root_remove = in_initrd(); + if (old_root_remove) { + old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); + if (old_root_fd < 0) + return log_error_errno(errno, "Failed to open root directory: %m"); + } - if (stat(new_root, &new_root_stat) < 0) - return log_error_errno(errno, "Failed to stat directory %s: %m", new_root); + /* Determine where we shall place the old root after the transition */ + r = chase_symlinks(old_root_after, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &resolved_old_root_after); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, old_root_after); + if (r == 0) /* Doesn't exist yet. Let's create it */ + (void) mkdir_p_label(resolved_old_root_after, 0755); - /* Work-around for kernel design: the kernel refuses switching - * root if any file systems are mounted MS_SHARED. Hence + /* Work-around for kernel design: the kernel refuses MS_MOVE if any file systems are mounted MS_SHARED. Hence * remount them MS_PRIVATE here as a work-around. * * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */ if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0) - log_warning_errno(errno, "Failed to make \"/\" private mount: %m"); + return log_error_errno(errno, "Failed to set \"/\" mount propagation to private: %m"); - NULSTR_FOREACH(i, move_mounts) { - char new_mount[PATH_MAX]; - struct stat sb; - size_t n; + FOREACH_STRING(i, "/sys", "/dev", "/run", "/proc") { + _cleanup_free_ char *chased = NULL; - n = snprintf(new_mount, sizeof new_mount, "%s%s", new_root, i); - if (n >= sizeof new_mount) { - bool move = mountflags & MS_MOVE; + r = chase_symlinks(i, new_root, CHASE_PREFIX_ROOT|CHASE_NONEXISTENT, &chased); + if (r < 0) + return log_error_errno(r, "Failed to resolve %s/%s: %m", new_root, i); + if (r > 0) { + /* Already exists. Let's see if it is a mount point already. */ + r = path_is_mount_point(chased, NULL, 0); + if (r < 0) + return log_error_errno(r, "Failed to determine whether %s is a mount point: %m", chased); + if (r > 0) /* If it is already mounted, then do nothing */ + continue; + } else + /* Doesn't exist yet? */ + (void) mkdir_p_label(chased, 0755); - log_warning("New path is too long, %s: %s%s", - move ? "forcing unmount instead" : "ignoring", - new_root, i); - - if (move) - if (umount2(i, MNT_FORCE) < 0) - log_warning_errno(errno, "Failed to unmount %s: %m", i); - continue; - } - - mkdir_p_label(new_mount, 0755); - - if (stat(new_mount, &sb) < 0 || - sb.st_dev != new_root_stat.st_dev) { - - /* Mount point seems to be mounted already or - * stat failed. Unmount the old mount point. */ - if (umount2(i, MNT_DETACH) < 0) - log_warning_errno(errno, "Failed to unmount %s: %m", i); - continue; - } - - if (mount(i, new_mount, NULL, mountflags, NULL) < 0) { - if (mountflags & MS_MOVE) { - log_error_errno(errno, "Failed to move mount %s to %s, forcing unmount: %m", i, new_mount); - - if (umount2(i, MNT_FORCE) < 0) - log_warning_errno(errno, "Failed to unmount %s: %m", i); - - } else if (mountflags & MS_BIND) - log_error_errno(errno, "Failed to bind mount %s to %s: %m", i, new_mount); - } + if (mount(i, chased, NULL, mount_flags, NULL) < 0) + return log_error_errno(r, "Failed to mount %s to %s: %m", i, chased); } - /* Do not fail, if base_filesystem_create() fails. Not all - * switch roots are like base_filesystem_create() wants them - * to look like. They might even boot, if they are RO and - * don't have the FS layout. Just ignore the error and - * switch_root() nevertheless. */ + /* Do not fail if base_filesystem_create() fails. Not all switch roots are like base_filesystem_create() wants + * them to look like. They might even boot, if they are RO and don't have the FS layout. Just ignore the error + * and switch_root() nevertheless. */ (void) base_filesystem_create(new_root, UID_INVALID, GID_INVALID); if (chdir(new_root) < 0) return log_error_errno(errno, "Failed to change directory to %s: %m", new_root); - if (old_root_remove) { - old_root_fd = open("/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_NOCTTY|O_DIRECTORY); - if (old_root_fd < 0) - log_warning_errno(errno, "Failed to open root directory: %m"); - } - - /* We first try a pivot_root() so that we can umount the old - * root dir. In many cases (i.e. where rootfs is /), that's - * not possible however, and hence we simply overmount root */ - if (pivot_root(new_root, temporary_old_root) >= 0) { + /* We first try a pivot_root() so that we can umount the old root dir. In many cases (i.e. where rootfs is /), + * that's not possible however, and hence we simply overmount root */ + if (pivot_root(new_root, resolved_old_root_after) >= 0) { /* Immediately get rid of the old root, if detach_oldroot is set. * Since we are running off it we need to do this lazily. */ - if (detach_oldroot && umount2(oldroot, MNT_DETACH) < 0) - log_error_errno(errno, "Failed to lazily umount old root dir %s, %s: %m", - oldroot, - errno == ENOENT ? "ignoring" : "leaving it around"); + if (unmount_old_root) { + r = umount_recursive(old_root_after, MNT_DETACH); + if (r < 0) + log_warning_errno(r, "Failed to unmount old root directory tree, ignoring: %m"); + } } else if (mount(new_root, "/", NULL, MS_MOVE, NULL) < 0) - return log_error_errno(errno, "Failed to mount moving %s to /: %m", new_root); + return log_error_errno(errno, "Failed to move %s to /: %m", new_root); if (chroot(".") < 0) return log_error_errno(errno, "Failed to change root: %m"); From 91214a37ef4eb8042d2598aa89bae52b410d11a7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Dec 2016 12:45:19 +0100 Subject: [PATCH 23/46] fstab-generator: add support for volatile boots This adds support for a new kernel command line option "systemd.volatile=" that provides the same functionality that systemd-nspawn's --volatile= switch provides, but for host systems (i.e. systems booting with a kernel). It takes the same parameter and has the same effect. In order to implement systemd.volatile=yes a new service systemd-volatile-root.service is introduced that only runs in the initrd and rearranges the root directory as needed to become a tmpfs instance. Note that systemd.volatile=state is implemented different: it simply generates a var.mount unit file that is part of the normal boot and has no effect on the initrd execution. The way this is implemented ensures that other explicit configuration for /var can always override the effect of these options. Specifically, the var.mount unit is generated in the "late" generator directory, so that it only is in effect if nothing else overrides it. --- .gitignore | 1 + Makefile-man.am | 7 ++ Makefile.am | 10 ++ man/kernel-command-line.xml | 23 ++++ man/systemd-fstab-generator.xml | 46 +++++++- man/systemd-nspawn.xml | 15 ++- man/systemd-volatile-root.service.xml | 79 +++++++++++++ src/fstab-generator/fstab-generator.c | 85 +++++++++++-- src/shared/volatile-util.c | 27 +++++ src/shared/volatile-util.h | 2 + src/volatile-root/Makefile | 1 + src/volatile-root/volatile-root.c | 157 +++++++++++++++++++++++++ units/.gitignore | 1 + units/systemd-volatile-root.service.in | 21 ++++ 14 files changed, 456 insertions(+), 19 deletions(-) create mode 100644 man/systemd-volatile-root.service.xml create mode 120000 src/volatile-root/Makefile create mode 100644 src/volatile-root/volatile-root.c create mode 100644 units/systemd-volatile-root.service.in diff --git a/.gitignore b/.gitignore index ec4b7bd672..f246d3e6d5 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ /systemd-update-utmp /systemd-user-sessions /systemd-vconsole-setup +/systemd-volatile-root /tags /test-acd /test-acl-util diff --git a/Makefile-man.am b/Makefile-man.am index 5e6eee5e32..27660ef1c2 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -142,6 +142,7 @@ MANPAGES += \ man/systemd-tty-ask-password-agent.1 \ man/systemd-udevd.service.8 \ man/systemd-update-done.service.8 \ + man/systemd-volatile-root.service.8 \ man/systemd.1 \ man/systemd.automount.5 \ man/systemd.device.5 \ @@ -482,6 +483,7 @@ MANPAGES_ALIAS += \ man/systemd-udevd.8 \ man/systemd-update-done.8 \ man/systemd-user.conf.5 \ + man/systemd-volatile-root.8 \ man/udev_device_get_action.3 \ man/udev_device_get_devlinks_list_entry.3 \ man/udev_device_get_devnode.3 \ @@ -837,6 +839,7 @@ man/systemd-udevd-kernel.socket.8: man/systemd-udevd.service.8 man/systemd-udevd.8: man/systemd-udevd.service.8 man/systemd-update-done.8: man/systemd-update-done.service.8 man/systemd-user.conf.5: man/systemd-system.conf.5 +man/systemd-volatile-root.8: man/systemd-volatile-root.service.8 man/udev_device_get_action.3: man/udev_device_get_syspath.3 man/udev_device_get_devlinks_list_entry.3: man/udev_device_has_tag.3 man/udev_device_get_devnode.3: man/udev_device_get_syspath.3 @@ -1790,6 +1793,9 @@ man/systemd-update-done.html: man/systemd-update-done.service.html man/systemd-user.conf.html: man/systemd-system.conf.html $(html-alias) +man/systemd-volatile-root.html: man/systemd-volatile-root.service.html + $(html-alias) + man/udev_device_get_action.html: man/udev_device_get_syspath.html $(html-alias) @@ -2804,6 +2810,7 @@ EXTRA_DIST += \ man/systemd-update-utmp.service.xml \ man/systemd-user-sessions.service.xml \ man/systemd-vconsole-setup.service.xml \ + man/systemd-volatile-root.service.xml \ man/systemd.automount.xml \ man/systemd.device.xml \ man/systemd.exec.xml \ diff --git a/Makefile.am b/Makefile.am index 56b8aa3fe8..92a3680461 100644 --- a/Makefile.am +++ b/Makefile.am @@ -397,6 +397,7 @@ rootlibexec_PROGRAMS = \ systemd-initctl \ systemd-shutdown \ systemd-remount-fs \ + systemd-volatile-root \ systemd-reply-password \ systemd-fsck \ systemd-ac-power \ @@ -538,6 +539,7 @@ nodist_systemunit_DATA = \ units/system-update-cleanup.service \ units/systemd-initctl.service \ units/systemd-remount-fs.service \ + units/systemd-volatile-root.service \ units/systemd-ask-password-wall.service \ units/systemd-ask-password-console.service \ units/systemd-sysctl.service \ @@ -602,6 +604,7 @@ EXTRA_DIST += \ units/system-update-cleanup.service.in \ units/systemd-initctl.service.in \ units/systemd-remount-fs.service.in \ + units/systemd-volatile-root.service.in \ units/systemd-update-utmp.service.in \ units/systemd-update-utmp-runlevel.service.in \ units/systemd-ask-password-wall.service.in \ @@ -3067,6 +3070,13 @@ systemd_remount_fs_SOURCES = \ systemd_remount_fs_LDADD = \ libsystemd-shared.la +# ------------------------------------------------------------------------------ +systemd_volatile_root_SOURCES = \ + src/volatile-root/volatile-root.c + +systemd_volatile_root_LDADD = \ + libsystemd-shared.la + # ------------------------------------------------------------------------------ systemd_cgroups_agent_SOURCES = \ src/cgroups-agent/cgroups-agent.c diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 78e45e66a9..7e1d408ded 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -124,6 +124,28 @@ + + systemd.volatile= + + This parameter controls whether the system shall boot up in volatile mode. Takes a boolean argument, or + the special value state. If false (the default), normal boot mode is selected, the root + directory and /var are mounted as specified on the kernel command line or + /etc/fstab, or otherwise configured. If true, full state-less boot mode is selected. In + this case the root directory is mounted as volatile memory file system (tmpfs), and only + /usr is mounted from the file system configured as root device, in read-only mode. This + enables fully state-less boots were the vendor-supplied OS is used as shipped, with only default + configuration and no stored state in effect, as /etc and /var (as + well as all other resources shipped in the root file system) are reset at boot and lost on shutdown. If this + setting is set to state the root file system is mounted as usual, however + /var is mounted as a volatile memory file system (tmpfs), so that the + system boots up with the normal configuration applied, but all state reset at boot and lost at shutdown. For details, + see + systemd-volatile-root.service8 + and + systemd-fstab-generator8. + + + quiet @@ -382,6 +404,7 @@ systemd-cryptsetup-generator8, systemd-fstab-generator8, systemd-gpt-auto-generator8, + systemd-volatile-root.service8, systemd-modules-load.service8, systemd-backlight@.service8, systemd-rfkill.service8, diff --git a/man/systemd-fstab-generator.xml b/man/systemd-fstab-generator.xml index a971cb3675..5f37e9193e 100644 --- a/man/systemd-fstab-generator.xml +++ b/man/systemd-fstab-generator.xml @@ -89,12 +89,13 @@ Takes a boolean argument. Defaults to yes. If no, causes the - generator to ignore any mounts or swaps configured in + generator to ignore any mounts or swap devices configured in /etc/fstab. rd.fstab= - is honored only by initial RAM disk (initrd) while + is honored only by the initial RAM disk (initrd) while fstab= is honored by both the main system and the initrd. + root= @@ -102,6 +103,7 @@ initrd. root= is honored by the initrd. + rootfstype= @@ -109,6 +111,7 @@ passed to the mount command. rootfstype= is honored by the initrd. + rootflags= @@ -116,6 +119,7 @@ use. rootflags= is honored by the initrd. + mount.usr= @@ -133,6 +137,7 @@ mount.usr= is honored by the initrd. + mount.usrfstype= @@ -150,6 +155,7 @@ mount.usrfstype= is honored by the initrd. + mount.usrflags= @@ -166,6 +172,39 @@ mount.usrflags= is honored by the initrd. + + + systemd.volatile= + + Controls whether the system shall boot up in volatile mode. Takes a boolean argument or the + special value . + + If false (the default), this generator makes no changes to the mount tree and the system is booted up in + normal mode. + + If true the generator ensures + systemd-volatile-root.service8 + is run as part of the initial RAM disk ("initrd"). This service changes the mount table before transitioning to + the host system, so that a volatile memory file system (tmpfs) is used as root directory, + with only /usr mounted into it from the configured root file system, in read-only + mode. This way the system operates in fully stateless mode, with all configuration and state reset at boot and + lost at shutdown, as /etc and /var will be served from the (initially + unpopulated) volatile memory file system. + + If set to the generator will leave the root + directory mount point unaltered, however will mount a tmpfs file system to + /var. In this mode the normal system configuration (i.e the contents of + /etc) is in effect (and may be modified during system runtime), however the system state + (i.e. the contents of /var) is reset at boot and lost at shutdown. + + Note that in none of these modes the root directory, /etc, /var + or any other resources stored in the root file system are physically removed. It's thus safe to boot a system + that is normally operated in non-volatile mode temporarily into volatile mode, without losing data. + + Note that enabling this setting will only work correctly on operating systems that can boot up with only + /usr mounted, and are able to automatically populate /etc, and also + /var in case of systemd.volatile=yes. + @@ -176,7 +215,8 @@ fstab5, systemd.mount5, systemd.swap5, - systemd-cryptsetup-generator8 + systemd-cryptsetup-generator8, + kernel-command-line7 diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 2bc81ea1aa..f6b3f57fc7 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -939,12 +939,15 @@ (the default), the whole OS tree is made available writable. - Note that setting this to or - will only work correctly with - operating systems in the container that can boot up with only - /usr mounted, and are able to populate - /var automatically, as - needed. + This option provides similar functionality for containers as the systemd.volatile= + kernel command line switch provides for host systems. See + kernel-command-line7 for + details. + + Note that enabling this setting will only work correctly with operating systems in the container that can + boot up with only /usr mounted, and are able to automatically populate + /var, and also /etc in case of + --volatile=yes. diff --git a/man/systemd-volatile-root.service.xml b/man/systemd-volatile-root.service.xml new file mode 100644 index 0000000000..b90a3261fa --- /dev/null +++ b/man/systemd-volatile-root.service.xml @@ -0,0 +1,79 @@ + + + + + + + + systemd-volatile-root.service + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-volatile-root.service + 8 + + + + systemd-volatile-root.service + systemd-volatile-root + Make the root file system volatile + + + + systemd-volatile-root.service + /usr/lib/systemd/systemd-volatile-root + + + + Description + + systemd-volatile-root.service is a service that replaces the root directory with a + volatile memory file system (tmpfs), mounting the original (non-volatile) + /usr inside it read-only. This way, vendor data from /usr is available as + usual, but all configuration data in /etc, all state data in /var and all + other resources stored directly under the root directory are reset on boot and lost at shutdown, enabling fully + stateless systems. + + This service is only enabled if full volatile mode is selected, for example by specifying + systemd.volatile=yes on the kernel command line. This service runs only in the initial RAM disk + ("initrd"), before the system transitions to the host's root directory. Note that this service is not used if + systemd.volatile=state is used, as in that mode the root directory is non-volatile. + + + + See Also + + systemd1, + systemd-fstab-generator8, + kernel-command-line7 + + + + diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index 84163abbc5..f58aa27df2 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -42,8 +42,10 @@ #include "unit-name.h" #include "util.h" #include "virt.h" +#include "volatile-util.h" static const char *arg_dest = "/tmp"; +static const char *arg_dest_late = "/tmp"; static bool arg_fstab_enabled = true; static char *arg_root_what = NULL; static char *arg_root_fstype = NULL; @@ -52,6 +54,7 @@ static int arg_root_rw = -1; static char *arg_usr_what = NULL; static char *arg_usr_fstype = NULL; static char *arg_usr_options = NULL; +static VolatileMode arg_volatile_mode = _VOLATILE_MODE_INVALID; static int add_swap( const char *what, @@ -235,6 +238,7 @@ static int write_requires_mounts_for(FILE *f, const char *opts) { } static int add_mount( + const char *dest, const char *what, const char *where, const char *fstype, @@ -286,7 +290,7 @@ static int add_mount( if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); - unit = strjoin(arg_dest, "/", name); + unit = strjoin(dest, "/", name); if (!unit) return log_oom(); @@ -318,7 +322,7 @@ static int add_mount( } if (passno != 0) { - r = generator_write_fsck_deps(f, arg_dest, what, where, fstype); + r = generator_write_fsck_deps(f, dest, what, where, fstype); if (r < 0) return r; } @@ -334,7 +338,7 @@ static int add_mount( if (!isempty(fstype) && !streq(fstype, "auto")) fprintf(f, "Type=%s\n", fstype); - r = generator_write_timeouts(arg_dest, what, where, opts, &filtered); + r = generator_write_timeouts(dest, what, where, opts, &filtered); if (r < 0) return r; @@ -350,7 +354,7 @@ static int add_mount( return log_error_errno(r, "Failed to write unit file %s: %m", unit); if (!noauto && !automount) { - lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", name); + lnk = strjoin(dest, "/", post, nofail ? ".wants/" : ".requires/", name); if (!lnk) return log_oom(); @@ -364,7 +368,7 @@ static int add_mount( if (r < 0) return log_error_errno(r, "Failed to generate unit name: %m"); - automount_unit = strjoin(arg_dest, "/", automount_name); + automount_unit = strjoin(dest, "/", automount_name); if (!automount_unit) return log_oom(); @@ -406,7 +410,7 @@ static int add_mount( return log_error_errno(r, "Failed to write unit file %s: %m", automount_unit); free(lnk); - lnk = strjoin(arg_dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name); + lnk = strjoin(dest, "/", post, nofail ? ".wants/" : ".requires/", automount_name); if (!lnk) return log_oom(); @@ -479,7 +483,8 @@ static int parse_fstab(bool initrd) { else post = SPECIAL_LOCAL_FS_TARGET; - k = add_mount(what, + k = add_mount(arg_dest, + what, where, me->mnt_type, me->mnt_opts, @@ -540,7 +545,8 @@ static int add_sysroot_mount(void) { return r; } - return add_mount(what, + return add_mount(arg_dest, + what, "/sysroot", arg_root_fstype, opts, @@ -593,7 +599,8 @@ static int add_sysroot_usr_mount(void) { opts = arg_usr_options; log_debug("Found entry what=%s where=/sysroot/usr type=%s", what, strna(arg_usr_fstype)); - return add_mount(what, + return add_mount(arg_dest, + what, "/sysroot/usr", arg_usr_fstype, opts, @@ -605,6 +612,46 @@ static int add_sysroot_usr_mount(void) { "/proc/cmdline"); } +static int add_volatile_root(void) { + const char *from, *to; + + if (arg_volatile_mode != VOLATILE_YES) + return 0; + + /* Let's add in systemd-remount-volatile.service which will remount the root device to tmpfs if this is + * requested, leaving only /usr from the root mount inside. */ + + from = strjoina(SYSTEM_DATA_UNIT_PATH "/systemd-volatile-root.service"); + to = strjoina(arg_dest, "/" SPECIAL_INITRD_ROOT_FS_TARGET, ".requires/systemd-volatile-root.service"); + + (void) mkdir_parents(to, 0755); + + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to hook in volatile remount service: %m"); + + return 0; +} + +static int add_volatile_var(void) { + + if (arg_volatile_mode != VOLATILE_STATE) + return 0; + + /* If requested, mount /var as tmpfs, but do so only if there's nothing else defined for this. */ + + return add_mount(arg_dest_late, + "tmpfs", + "/var", + "tmpfs", + "mode=0755", + 0, + false, + false, + false, + SPECIAL_LOCAL_FS_TARGET, + "/proc/cmdline"); +} + static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { int r; @@ -686,6 +733,18 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat arg_root_rw = true; else if (streq(key, "ro") && !value) arg_root_rw = false; + else if (streq(key, "systemd.volatile")) { + VolatileMode m; + + if (value) { + m = volatile_mode_from_string(value); + if (m < 0) + log_warning("Failed to parse systemd.volatile= argument: %s", value); + else + arg_volatile_mode = m; + } else + arg_volatile_mode = VOLATILE_YES; + } return 0; } @@ -700,6 +759,8 @@ int main(int argc, char *argv[]) { if (argc > 1) arg_dest = argv[1]; + if (argc > 3) + arg_dest_late = argv[3]; log_set_target(LOG_TARGET_SAFE); log_parse_environment(); @@ -720,8 +781,12 @@ int main(int argc, char *argv[]) { k = add_sysroot_usr_mount(); if (k < 0) r = k; + + k = add_volatile_root(); + if (k < 0) + r = k; } else - r = 0; + r = add_volatile_var(); /* Honour /etc/fstab only when that's enabled */ if (arg_fstab_enabled) { diff --git a/src/shared/volatile-util.c b/src/shared/volatile-util.c index 1329b51f4e..e7e9721411 100644 --- a/src/shared/volatile-util.c +++ b/src/shared/volatile-util.c @@ -17,8 +17,10 @@ along with systemd; If not, see . ***/ +#include "alloc-util.h" #include "macro.h" #include "parse-util.h" +#include "proc-cmdline.h" #include "string-util.h" #include "volatile-util.h" @@ -39,3 +41,28 @@ VolatileMode volatile_mode_from_string(const char *s) { return _VOLATILE_MODE_INVALID; } + +int query_volatile_mode(VolatileMode *ret) { + _cleanup_free_ char *mode = NULL; + VolatileMode m = VOLATILE_NO; + int r; + + r = proc_cmdline_get_key("systemd.volatile", PROC_CMDLINE_VALUE_OPTIONAL, &mode); + if (r < 0) + return r; + if (r == 0) + goto finish; + + if (mode) { + m = volatile_mode_from_string(mode); + if (m < 0) + return -EINVAL; + } else + m = VOLATILE_YES; + + r = 1; + +finish: + *ret = m; + return r; +} diff --git a/src/shared/volatile-util.h b/src/shared/volatile-util.h index d012940c76..17930ba6ae 100644 --- a/src/shared/volatile-util.h +++ b/src/shared/volatile-util.h @@ -28,3 +28,5 @@ typedef enum VolatileMode { } VolatileMode; VolatileMode volatile_mode_from_string(const char *s); + +int query_volatile_mode(VolatileMode *ret); diff --git a/src/volatile-root/Makefile b/src/volatile-root/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/volatile-root/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/volatile-root/volatile-root.c b/src/volatile-root/volatile-root.c new file mode 100644 index 0000000000..3c0b6fa1de --- /dev/null +++ b/src/volatile-root/volatile-root.c @@ -0,0 +1,157 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + 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 . +***/ + +#include + +#include "alloc-util.h" +#include "fs-util.h" +#include "mkdir.h" +#include "mount-util.h" +#include "stat-util.h" +#include "volatile-util.h" +#include "string-util.h" +#include "path-util.h" + +static int make_volatile(const char *path) { + _cleanup_free_ char *old_usr = NULL; + int r; + + r = path_is_mount_point(path, NULL, AT_SYMLINK_FOLLOW); + if (r < 0) + return log_error_errno(r, "Couldn't determine whether %s is a mount point: %m", path); + if (r == 0) { + log_error("%s is not a mount point.", path); + return -EINVAL; + } + + r = path_is_temporary_fs(path); + if (r < 0) + return log_error_errno(r, "Couldn't determine whether %s is a temporary file system: %m", path); + if (r > 0) { + log_info("%s already is a temporary file system.", path); + return 0; + } + + r = chase_symlinks("/usr", path, CHASE_PREFIX_ROOT, &old_usr); + if (r < 0) + return log_error_errno(r, "/usr not available in old root: %m"); + + r = mkdir_p("/run/systemd/volatile-sysroot", 0700); + if (r < 0) + return log_error_errno(r, "Couldn't generate volatile sysroot directory: %m"); + + r = mount_verbose(LOG_ERR, "tmpfs", "/run/systemd/volatile-sysroot", "tmpfs", MS_STRICTATIME, "mode=755"); + if (r < 0) + goto finish_rmdir; + + if (mkdir("/run/systemd/volatile-sysroot/usr", 0755) < 0) { + r = -errno; + goto finish_umount; + } + + r = mount_verbose(LOG_ERR, old_usr, "/run/systemd/volatile-sysroot/usr", NULL, MS_BIND|MS_REC, NULL); + if (r < 0) + goto finish_umount; + + r = bind_remount_recursive("/run/systemd/volatile-sysroot/usr", true, NULL); + if (r < 0) + goto finish_umount; + + r = umount_recursive(path, 0); + if (r < 0) { + log_error_errno(r, "Failed to unmount %s: %m", path); + goto finish_umount; + } + + if (mount(NULL, "/", NULL, MS_SLAVE|MS_REC, NULL) < 0) + log_warning_errno(errno, "Failed to remount %s MS_SLAVE|MS_REC: %m", path); + + r = mount_verbose(LOG_ERR, "/run/systemd/volatile-sysroot", path, NULL, MS_MOVE, NULL); + +finish_umount: + (void) umount_recursive("/run/systemd/volatile-sysroot", 0); + +finish_rmdir: + (void) rmdir("/run/systemd/volatile-sysroot"); + + return r; +} + +int main(int argc, char *argv[]) { + VolatileMode m = _VOLATILE_MODE_INVALID; + const char *path; + int r; + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + if (argc > 3) { + log_error("Too many arguments. Expected directory and mode."); + r = -EINVAL; + goto finish; + } + + r = query_volatile_mode(&m); + if (r < 0) { + log_error_errno(r, "Failed to determine volatile mode from kernel command line."); + goto finish; + } + if (r == 0 && argc >= 2) { + /* The kernel command line always wins. However if nothing was set there, the argument passed here wins instead. */ + m = volatile_mode_from_string(argv[1]); + if (m < 0) { + log_error("Couldn't parse volatile mode: %s", argv[1]); + r = -EINVAL; + goto finish; + } + } + + if (argc < 3) + path = "/sysroot"; + else { + path = argv[2]; + + if (isempty(path)) { + log_error("Directory name cannot be empty."); + r = -EINVAL; + goto finish; + } + if (!path_is_absolute(path)) { + log_error("Directory must be specified as absolute path."); + r = -EINVAL; + goto finish; + } + if (path_equal(path, "/")) { + log_error("Directory cannot be the root directory."); + r = -EINVAL; + goto finish; + } + } + + if (m != VOLATILE_YES) { + r = 0; + goto finish; + } + + r = make_volatile(path); + +finish: + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/units/.gitignore b/units/.gitignore index 8fdb6e9ab5..4398a59f91 100644 --- a/units/.gitignore +++ b/units/.gitignore @@ -75,5 +75,6 @@ /systemd-update-utmp.service /systemd-user-sessions.service /systemd-vconsole-setup.service +/systemd-volatile-root.service /tmp.mount /user@.service diff --git a/units/systemd-volatile-root.service.in b/units/systemd-volatile-root.service.in new file mode 100644 index 0000000000..cc4e604e4c --- /dev/null +++ b/units/systemd-volatile-root.service.in @@ -0,0 +1,21 @@ +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# 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. + +[Unit] +Description=Enforce Volatile Root File Systems +Documentation=man:systemd-volatile-root.service(8) +DefaultDependencies=no +Conflicts=shutdown.target +After=sysroot.mount +Before=initrd-root-fs.target shutdown.target +Conflicts=shutdown.target +AssertPathExists=/etc/initrd-release + +[Service] +Type=oneshot +RemainAfterExit=yes +ExecStart=@rootlibexecdir@/systemd-volatile-root yes /sysroot From f7e1577cd49b270e74201dc7213adfa74f710845 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Dec 2016 13:03:03 +0100 Subject: [PATCH 24/46] build-sys: add missing Makefile symlink This way we can just type "make" in the .mkosi/ directory and the right thing happens. --- .mkosi/Makefile | 1 + 1 file changed, 1 insertion(+) create mode 120000 .mkosi/Makefile diff --git a/.mkosi/Makefile b/.mkosi/Makefile new file mode 120000 index 0000000000..bd1047548b --- /dev/null +++ b/.mkosi/Makefile @@ -0,0 +1 @@ +../src/Makefile \ No newline at end of file From 4332edf6b1a1527e14dbdf0ffeb8c6354f59675b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Dec 2016 16:00:28 +0100 Subject: [PATCH 25/46] core: fix typo --- src/core/job.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/job.c b/src/core/job.c index 2ba4c78096..f7c4c59c32 100644 --- a/src/core/job.c +++ b/src/core/job.c @@ -645,7 +645,7 @@ _pure_ static const char *job_get_status_message_format(Unit *u, JobType t, JobR [JOB_DEPENDENCY] = "Dependency failed for %s.", [JOB_ASSERT] = "Assertion failed for %s.", [JOB_UNSUPPORTED] = "Starting of %s not supported.", - [JOB_COLLECTED] = "Unecessary job for %s was removed.", + [JOB_COLLECTED] = "Unnecessary job for %s was removed.", }; static const char *const generic_finished_stop_job[_JOB_RESULT_MAX] = { [JOB_DONE] = "Stopped %s.", From e02c04c0450a753fe9e1594cff1250766349c72f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Dec 2016 16:01:03 +0100 Subject: [PATCH 26/46] mkosi: drop duplicate "diffutils" package listing --- .mkosi/mkosi.fedora | 1 - 1 file changed, 1 deletion(-) diff --git a/.mkosi/mkosi.fedora b/.mkosi/mkosi.fedora index 4f5f084f93..1b8dd47ca4 100644 --- a/.mkosi/mkosi.fedora +++ b/.mkosi/mkosi.fedora @@ -64,7 +64,6 @@ BuildPackages= libxslt lz4-devel make - diffutils pam-devel pkgconfig python3-devel From 01af8c019a33eb3882f17e3b65e30c4a924fed2a Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 13 Dec 2016 18:16:22 +0100 Subject: [PATCH 27/46] gpt-auto-generator: support LUKS encrypted root partitions Previously, we supported GPT auto-discovery for /home and /srv, but not for the root partition. Add that, too. Fixes: #859 --- rules/60-persistent-storage.rules | 3 -- rules/99-systemd.rules.in | 5 ++ src/gpt-auto-generator/gpt-auto-generator.c | 53 ++++++++++++++------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/rules/60-persistent-storage.rules b/rules/60-persistent-storage.rules index 6f60ae9024..28e41ea968 100644 --- a/rules/60-persistent-storage.rules +++ b/rules/60-persistent-storage.rules @@ -88,7 +88,4 @@ ENV{DEVTYPE}=="partition", ENV{ID_WWN_WITH_EXTENSION}=="?*", SYMLINK+="disk/by-i ENV{ID_PART_ENTRY_UUID}=="?*", SYMLINK+="disk/by-partuuid/$env{ID_PART_ENTRY_UUID}" ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_ENTRY_NAME}=="?*", SYMLINK+="disk/by-partlabel/$env{ID_PART_ENTRY_NAME}" -# add symlink to GPT root disk -ENV{ID_PART_ENTRY_SCHEME}=="gpt", ENV{ID_PART_GPT_AUTO_ROOT}=="1", SYMLINK+="gpt-auto-root" - LABEL="persistent_storage_end" diff --git a/rules/99-systemd.rules.in b/rules/99-systemd.rules.in index ca52cf165b..98153bce0f 100644 --- a/rules/99-systemd.rules.in +++ b/rules/99-systemd.rules.in @@ -17,6 +17,11 @@ SUBSYSTEM=="block", ACTION=="add", ENV{DM_UDEV_DISABLE_OTHER_RULES_FLAG}=="1", E # we are probably still calling mke2fs or mkswap on it. SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{ID_PART_TABLE_TYPE}=="", ENV{ID_FS_USAGE}=="", ENV{SYSTEMD_READY}="0" +# add symlink to GPT root disk +SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT}=="1", ENV{ID_FS_TYPE}!="crypto_LUKS", SYMLINK+="gpt-auto-root" +SUBSYSTEM=="block", ENV{ID_PART_GPT_AUTO_ROOT}=="1", ENV{ID_FS_TYPE}=="crypto_LUKS", SYMLINK+="gpt-auto-root-luks" +SUBSYSTEM=="block", ENV{DM_UUID}=="CRYPT-*", ENV{DM_NAME}=="root", SYMLINK+="gpt-auto-root" + # Ignore raid devices that are not yet assembled and started SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", TEST!="md/array_state", ENV{SYSTEMD_READY}="0" SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", KERNEL=="md*", ATTR{md/array_state}=="|clear|inactive", ENV{SYSTEMD_READY}="0" diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index f993c5d1e2..e506f39521 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -54,7 +54,7 @@ static bool arg_enabled = true; static bool arg_root_enabled = true; static bool arg_root_rw = false; -static int add_cryptsetup(const char *id, const char *what, bool rw, char **device) { +static int add_cryptsetup(const char *id, const char *what, bool rw, bool require, char **device) { _cleanup_free_ char *e = NULL, *n = NULL, *p = NULL, *d = NULL, *to = NULL; _cleanup_fclose_ FILE *f = NULL; char *from, *ret; @@ -62,7 +62,6 @@ static int add_cryptsetup(const char *id, const char *what, bool rw, char **devi assert(id); assert(what); - assert(device); r = unit_name_from_path(what, ".device", &d); if (r < 0) @@ -119,23 +118,26 @@ static int add_cryptsetup(const char *id, const char *what, bool rw, char **devi if (symlink(from, to) < 0) return log_error_errno(errno, "Failed to create symlink %s: %m", to); - free(to); - to = strjoin(arg_dest, "/cryptsetup.target.requires/", n); - if (!to) - return log_oom(); + if (require) { + free(to); - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); + to = strjoin(arg_dest, "/cryptsetup.target.requires/", n); + if (!to) + return log_oom(); - free(to); - to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n); - if (!to) - return log_oom(); + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); - mkdir_parents_label(to, 0755); - if (symlink(from, to) < 0) - return log_error_errno(errno, "Failed to create symlink %s: %m", to); + free(to); + to = strjoin(arg_dest, "/dev-mapper-", e, ".device.requires/", n); + if (!to) + return log_oom(); + + mkdir_parents_label(to, 0755); + if (symlink(from, to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + } free(p); p = strjoin(arg_dest, "/dev-mapper-", e, ".device.d/50-job-timeout-sec-0.conf"); @@ -155,7 +157,8 @@ static int add_cryptsetup(const char *id, const char *what, bool rw, char **devi if (!ret) return log_oom(); - *device = ret; + if (device) + *device = ret; return 0; } @@ -182,7 +185,7 @@ static int add_mount( if (streq_ptr(fstype, "crypto_LUKS")) { - r = add_cryptsetup(id, what, rw, &crypto_what); + r = add_cryptsetup(id, what, rw, true, &crypto_what); if (r < 0) return r; @@ -938,6 +941,16 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } +#ifdef ENABLE_EFI +static int add_root_cryptsetup(void) { + + /* If a device /dev/gpt-auto-root-luks appears, then make it pull in systemd-cryptsetup-root.service, which + * sets it up, and causes /dev/gpt-auto-root to appear which is all we are looking for. */ + + return add_cryptsetup("root", "/dev/gpt-auto-root-luks", true, false, NULL); +} +#endif + static int add_root_mount(void) { #ifdef ENABLE_EFI @@ -963,6 +976,10 @@ static int add_root_mount(void) { r = generator_write_initrd_root_device_deps(arg_dest, "/dev/gpt-auto-root"); if (r < 0) return 0; + + r = add_root_cryptsetup(); + if (r < 0) + return r; } return add_mount( From be30ad41ffd796ba40a5026b4963c17f04cc334d Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Dec 2016 17:17:43 +0100 Subject: [PATCH 28/46] dissect: return the GPT partition UUID, too This is useful as we can match up the EFI UUID with the one the firmware supposedly used. --- src/dissect/dissect.c | 3 +++ src/shared/dissect-image.c | 4 ++++ src/shared/dissect-image.h | 1 + 3 files changed, 8 insertions(+) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index aa06894037..78ec88fa35 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -221,6 +221,9 @@ int main(int argc, char *argv[]) { p->rw ? "writable" : "read-only", partition_designator_to_string(i)); + if (!sd_id128_is_null(p->uuid)) + printf(" (UUID " SD_ID128_FORMAT_STR ")", SD_ID128_FORMAT_VAL(p->uuid)); + if (p->fstype) printf(" of type %s", p->fstype); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 10d53eab45..5b6e78dd3d 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -95,6 +95,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectI _cleanup_blkid_free_probe_ blkid_probe b = NULL; _cleanup_udev_unref_ struct udev *udev = NULL; _cleanup_free_ char *generic_node = NULL; + sd_id128_t generic_uuid = SD_ID128_NULL; const char *pttype = NULL; struct udev_list_entry *first, *item; blkid_partlist pl; @@ -427,6 +428,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectI else { generic_nr = nr; generic_rw = !(pflags & GPT_FLAG_READ_ONLY); + generic_uuid = id; generic_node = strdup(node); if (!generic_node) return -ENOMEM; @@ -457,6 +459,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectI .architecture = architecture, .node = n, .fstype = t, + .uuid = id, }; n = t = NULL; @@ -507,6 +510,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectI .partno = generic_nr, .architecture = _ARCHITECTURE_INVALID, .node = generic_node, + .uuid = generic_uuid, }; generic_node = NULL; diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index b424dac665..76104e5780 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -32,6 +32,7 @@ struct DissectedPartition { bool rw:1; int partno; /* -1 if there was no partition and the images contains a file system directly */ int architecture; /* Intended architecture: either native, secondary or unset (-1). */ + sd_id128_t uuid; /* Partition entry UUID as reported by the GPT */ char *fstype; char *node; char *decrypted_node; From e0f9e7bd03eebeb8fdfce7f766c4e254e3586dc8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Dec 2016 17:38:11 +0100 Subject: [PATCH 29/46] dissect: make using a generic partition as root partition optional In preparation for reusing the image dissector in the GPT auto-discovery logic, only optionally fail the dissection when we can't identify a root partition. In the GPT auto-discovery we are completely fine with any kind of root, given that we run when it is already mounted and all we do is find some additional auxiliary partitions on the same disk. --- src/dissect/dissect.c | 4 ++-- src/machine/image-dbus.c | 2 +- src/nspawn/nspawn.c | 6 +++++- src/shared/dissect-image.c | 25 +++++++++++++++++-------- src/shared/dissect-image.h | 1 + src/test/test-dissect-image.c | 2 +- 6 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/dissect/dissect.c b/src/dissect/dissect.c index 78ec88fa35..fd9db5ba87 100644 --- a/src/dissect/dissect.c +++ b/src/dissect/dissect.c @@ -35,7 +35,7 @@ static enum { } arg_action = ACTION_DISSECT; static const char *arg_image = NULL; static const char *arg_path = NULL; -static DissectImageFlags arg_flags = DISSECT_IMAGE_DISCARD_ON_LOOP; +static DissectImageFlags arg_flags = DISSECT_IMAGE_REQUIRE_ROOT|DISSECT_IMAGE_DISCARD_ON_LOOP; static void *arg_root_hash = NULL; static size_t arg_root_hash_size = 0; @@ -191,7 +191,7 @@ int main(int argc, char *argv[]) { goto finish; } - r = dissect_image(d->fd, arg_root_hash, arg_root_hash_size, 0, &m); + r = dissect_image(d->fd, arg_root_hash, arg_root_hash_size, arg_flags, &m); if (r == -ENOPKG) { log_error_errno(r, "Couldn't identify a suitable partition table or file system in %s.", arg_image); goto finish; diff --git a/src/machine/image-dbus.c b/src/machine/image-dbus.c index 2b168b267b..1891f07586 100644 --- a/src/machine/image-dbus.c +++ b/src/machine/image-dbus.c @@ -336,7 +336,7 @@ static int raw_image_get_os_release(Image *image, char ***ret, sd_bus_error *err if (r < 0) return sd_bus_error_set_errnof(error, r, "Failed to set up loop block device for %s: %m", image->path); - r = dissect_image(d->fd, NULL, 0, 0, &m); + r = dissect_image(d->fd, NULL, 0, DISSECT_IMAGE_REQUIRE_ROOT, &m); if (r == -ENOPKG) return sd_bus_error_set_errnof(error, r, "Disk image %s not understood: %m", image->path); if (r < 0) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 01d89df1a4..224d30fca6 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -3743,7 +3743,11 @@ int main(int argc, char *argv[]) { goto finish; } - r = dissect_image(loop->fd, arg_root_hash, arg_root_hash_size, 0, &dissected_image); + r = dissect_image( + loop->fd, + arg_root_hash, arg_root_hash_size, + DISSECT_IMAGE_REQUIRE_ROOT, + &dissected_image); if (r == -ENOPKG) { log_error_errno(r, "Could not find a suitable file system or partition table in image: %s", arg_image); diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c index 5b6e78dd3d..878cb008aa 100644 --- a/src/shared/dissect-image.c +++ b/src/shared/dissect-image.c @@ -174,7 +174,8 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectI if (!m) return -ENOMEM; - if ((flags & DISSECT_IMAGE_GPT_ONLY) == 0) { + if (!(flags & DISSECT_IMAGE_GPT_ONLY) && + (flags & DISSECT_IMAGE_REQUIRE_ROOT)) { const char *usage = NULL; (void) blkid_probe_lookup_value(b, "USAGE", &usage, NULL); @@ -490,7 +491,7 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectI * either, then check if there's a single generic one, and use that. */ if (m->partitions[PARTITION_ROOT_VERITY].found) - return -ENXIO; + return -EADDRNOTAVAIL; if (m->partitions[PARTITION_ROOT_SECONDARY].found) { m->partitions[PARTITION_ROOT] = m->partitions[PARTITION_ROOT_SECONDARY]; @@ -499,8 +500,19 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectI m->partitions[PARTITION_ROOT_VERITY] = m->partitions[PARTITION_ROOT_SECONDARY_VERITY]; zero(m->partitions[PARTITION_ROOT_SECONDARY_VERITY]); - } else if (generic_node && !root_hash) { + } else if (flags & DISSECT_IMAGE_REQUIRE_ROOT) { + /* If the root has was set, then we won't fallback to a generic node, because the root hash + * decides */ + if (root_hash) + return -EADDRNOTAVAIL; + + /* If we didn't find a generic node, then we can't fix this up either */ + if (!generic_node) + return -ENXIO; + + /* If we didn't find a properly marked root partition, but we did find a single suitable + * generic Linux partition, then use this as root partition, if the caller asked for it. */ if (multiple_generic) return -ENOTUNIQ; @@ -514,14 +526,11 @@ int dissect_image(int fd, const void *root_hash, size_t root_hash_size, DissectI }; generic_node = NULL; - } else - return -ENXIO; + } } - assert(m->partitions[PARTITION_ROOT].found); - if (root_hash) { - if (!m->partitions[PARTITION_ROOT_VERITY].found) + if (!m->partitions[PARTITION_ROOT_VERITY].found || !m->partitions[PARTITION_ROOT].found) return -EADDRNOTAVAIL; /* If we found the primary root with the hash, then we definitely want to suppress any secondary root diff --git a/src/shared/dissect-image.h b/src/shared/dissect-image.h index 76104e5780..26319bd8e7 100644 --- a/src/shared/dissect-image.h +++ b/src/shared/dissect-image.h @@ -69,6 +69,7 @@ typedef enum DissectImageFlags { DISSECT_IMAGE_DISCARD | DISSECT_IMAGE_DISCARD_ON_CRYPTO, DISSECT_IMAGE_GPT_ONLY = 16, /* Only recognize images with GPT partition tables */ + DISSECT_IMAGE_REQUIRE_ROOT = 32, /* Don't accept disks without root partition */ } DissectImageFlags; struct DissectedImage { diff --git a/src/test/test-dissect-image.c b/src/test/test-dissect-image.c index ddaf3a0d8b..2bb68be0db 100644 --- a/src/test/test-dissect-image.c +++ b/src/test/test-dissect-image.c @@ -43,7 +43,7 @@ int main(int argc, char *argv[]) { return EXIT_FAILURE; } - r = dissect_image(d->fd, NULL, 0, 0, &m); + r = dissect_image(d->fd, NULL, 0, DISSECT_IMAGE_REQUIRE_ROOT, &m); if (r < 0) { log_error_errno(r, "Failed to dissect image: %m"); return EXIT_FAILURE; From 47f9472950fa51f48cbe610549e00fb05246922c Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Dec 2016 17:46:06 +0100 Subject: [PATCH 30/46] gpt-auto-generator: drop duplicate container check We already check for containers early in main(), no need to do this check again. --- src/gpt-auto-generator/gpt-auto-generator.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index e506f39521..872b654ad8 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -463,11 +463,6 @@ static int add_esp(const char *what) { return 0; } - if (detect_container() > 0) { - log_debug("In a container, ignoring the ESP."); - return 0; - } - /* If /efi exists we'll use that. Otherwise we'll use /boot, as that's usually the better choice */ esp = access("/efi/", F_OK) >= 0 ? "/efi" : "/boot"; From 72e18a98ba5c1a570a2eaadadfdbcb073f04df5b Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Dec 2016 18:18:29 +0100 Subject: [PATCH 31/46] gpt-auto-discovery: port to dissect-image.c dissector Change the gpt auto discovery generator to use the same dissector as nspawn and the rest of the tools. This removes the separate dissector code that the generator previously had and unifies the relevant code. --- src/gpt-auto-generator/gpt-auto-generator.c | 348 ++++---------------- 1 file changed, 69 insertions(+), 279 deletions(-) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 872b654ad8..3126469f60 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -29,6 +29,7 @@ #include "blkid-util.h" #include "btrfs-util.h" #include "dirent-util.h" +#include "dissect-image.h" #include "efivars.h" #include "fd-util.h" #include "fileio.h" @@ -273,61 +274,28 @@ static bool path_is_busy(const char *where) { return false; } -static int probe_and_add_mount( +static int add_partition_mount( + DissectedPartition *p, const char *id, - const char *what, const char *where, - bool rw, - const char *description, - const char *post) { + const char *description) { - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype = NULL; - int r; - - assert(id); - assert(what); - assert(where); - assert(description); + assert(p); if (path_is_busy(where)) { log_debug("%s already populated, ignoring.", where); return 0; } - /* Let's check the partition type here, so that we know - * whether to do LUKS magic. */ - - errno = 0; - b = blkid_new_probe_from_filename(what); - if (!b) { - if (errno == 0) - return log_oom(); - return log_error_errno(errno, "Failed to allocate prober: %m"); - } - - blkid_probe_enable_superblocks(b, 1); - blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) /* no result or uncertain */ - return 0; - else if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); - - /* add_mount is OK with fstype being NULL. */ - (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); - return add_mount( id, - what, + p->node, where, - fstype, - rw, + p->fstype, + p->rw, NULL, description, - post); + SPECIAL_LOCAL_FS_TARGET); } static int add_swap(const char *path) { @@ -452,11 +420,11 @@ static int add_automount( return 0; } -static int add_esp(const char *what) { +static int add_esp(DissectedPartition *p) { const char *esp; int r; - assert(what); + assert(p); if (in_initrd()) { log_debug("In initrd, ignoring the ESP."); @@ -478,9 +446,7 @@ static int add_esp(const char *what) { } if (is_efi_boot()) { - _cleanup_blkid_free_probe_ blkid_probe b = NULL; - const char *fstype = NULL, *uuid_string = NULL; - sd_id128_t loader_uuid, part_uuid; + sd_id128_t loader_uuid; /* If this is an EFI boot, be extra careful, and only mount the ESP if it was the ESP used for booting. */ @@ -492,43 +458,7 @@ static int add_esp(const char *what) { if (r < 0) return log_error_errno(r, "Failed to read ESP partition UUID: %m"); - errno = 0; - b = blkid_new_probe_from_filename(what); - if (!b) { - if (errno == 0) - return log_oom(); - return log_error_errno(errno, "Failed to allocate prober: %m"); - } - - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); - - errno = 0; - r = blkid_do_safeprobe(b); - if (r == -2 || r == 1) /* no result or uncertain */ - return 0; - else if (r != 0) - return log_error_errno(errno ?: EIO, "Failed to probe %s: %m", what); - - (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); - if (!streq_ptr(fstype, "vfat")) { - log_debug("Partition for %s is not a FAT filesystem, ignoring.", esp); - return 0; - } - - errno = 0; - r = blkid_probe_lookup_value(b, "PART_ENTRY_UUID", &uuid_string, NULL); - if (r != 0) { - log_debug_errno(errno, "Partition for %s does not have a UUID, ignoring.", esp); - return 0; - } - - if (sd_id128_from_string(uuid_string, &part_uuid) < 0) { - log_debug("Partition for %s does not have a valid UUID, ignoring.", esp); - return 0; - } - - if (!sd_id128_equal(part_uuid, loader_uuid)) { + if (!sd_id128_equal(p->uuid, loader_uuid)) { log_debug("Partition for %s does not appear to be the partition we are booted from.", esp); return 0; } @@ -536,13 +466,13 @@ static int add_esp(const char *what) { log_debug("Not an EFI boot, skipping ESP check."); return add_automount("boot", - what, - esp, - "vfat", - true, - "umask=0077", - "EFI System Partition Automount", - 120 * USEC_PER_SEC); + p->node, + esp, + p->fstype, + true, + "umask=0077", + "EFI System Partition Automount", + 120 * USEC_PER_SEC); } #else static int add_esp(const char *what) { @@ -550,21 +480,15 @@ static int add_esp(const char *what) { } #endif -static int enumerate_partitions(dev_t devnum) { - - _cleanup_udev_enumerate_unref_ struct udev_enumerate *e = NULL; +static int open_parent(dev_t devnum, int *ret) { _cleanup_udev_device_unref_ struct udev_device *d = NULL; - _cleanup_blkid_free_probe_ blkid_probe b = NULL; _cleanup_udev_unref_ struct udev *udev = NULL; - _cleanup_free_ char *boot = NULL, *home = NULL, *srv = NULL; - struct udev_list_entry *first, *item; - struct udev_device *parent = NULL; - const char *name, *node, *pttype, *devtype; - int boot_nr = -1, home_nr = -1, srv_nr = -1; - bool home_rw = true, srv_rw = true; - blkid_partlist pl; - int r, k; + const char *name, *devtype, *node; + struct udev_device *parent; dev_t pn; + int fd; + + assert(ret); udev = udev_new(); if (!udev) @@ -578,228 +502,94 @@ static int enumerate_partitions(dev_t devnum) { if (!name) name = udev_device_get_syspath(d); if (!name) { - log_debug("Device %u:%u does not have a name, ignoring.", - major(devnum), minor(devnum)); - return 0; + log_debug("Device %u:%u does not have a name, ignoring.", major(devnum), minor(devnum)); + goto not_found; } parent = udev_device_get_parent(d); if (!parent) { log_debug("%s: not a partitioned device, ignoring.", name); - return 0; + goto not_found; } /* Does it have a devtype? */ devtype = udev_device_get_devtype(parent); if (!devtype) { log_debug("%s: parent doesn't have a device type, ignoring.", name); - return 0; + goto not_found; } /* Is this a disk or a partition? We only care for disks... */ if (!streq(devtype, "disk")) { log_debug("%s: parent isn't a raw disk, ignoring.", name); - return 0; + goto not_found; } /* Does it have a device node? */ node = udev_device_get_devnode(parent); if (!node) { log_debug("%s: parent device does not have device node, ignoring.", name); - return 0; + goto not_found; } log_debug("%s: root device %s.", name, node); pn = udev_device_get_devnum(parent); - if (major(pn) == 0) - return 0; - - errno = 0; - b = blkid_new_probe_from_filename(node); - if (!b) { - if (errno == 0) - return log_oom(); - - return log_error_errno(errno, "%s: failed to allocate prober: %m", node); + if (major(pn) == 0) { + log_debug("%s: parent device is not a proper block device, ignoring.", name); + goto not_found; } - blkid_probe_enable_partitions(b, 1); - blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); + fd = open(node, O_RDONLY|O_CLOEXEC|O_NOCTTY); + if (fd < 0) + return log_error_errno(errno, "Failed to open %s: %m", node); - errno = 0; - r = blkid_do_safeprobe(b); - if (r == 1) - return 0; /* no results */ - else if (r == -2) { - log_warning("%s: probe gave ambiguous results, ignoring.", node); - return 0; - } else if (r != 0) - return log_error_errno(errno ?: EIO, "%s: failed to probe: %m", node); + *ret = fd; + return 1; - errno = 0; - r = blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); - if (r != 0) { - if (errno == 0) - return 0; /* No partition table found. */ +not_found: + *ret = -1; + return 0; +} - return log_error_errno(errno, "%s: failed to determine partition table type: %m", node); - } +static int enumerate_partitions(dev_t devnum) { - /* We only do this all for GPT... */ - if (!streq_ptr(pttype, "gpt")) { - log_debug("%s: not a GPT partition table, ignoring.", node); + _cleanup_close_ int fd = -1; + _cleanup_(dissected_image_unrefp) DissectedImage *m = NULL; + int r, k; + + r = open_parent(devnum, &fd); + if (r <= 0) + return r; + + r = dissect_image(fd, NULL, 0, DISSECT_IMAGE_GPT_ONLY, &m); + if (r == -ENOPKG) { + log_debug_errno(r, "No suitable partition table found, ignoring."); return 0; } - - errno = 0; - pl = blkid_probe_get_partitions(b); - if (!pl) { - if (errno == 0) - return log_oom(); - - return log_error_errno(errno, "%s: failed to list partitions: %m", node); - } - - e = udev_enumerate_new(udev); - if (!e) - return log_oom(); - - r = udev_enumerate_add_match_parent(e, parent); if (r < 0) - return log_oom(); + return log_error_errno(r, "Failed to dissect: %m"); - r = udev_enumerate_add_match_subsystem(e, "block"); - if (r < 0) - return log_oom(); - - r = udev_enumerate_scan_devices(e); - if (r < 0) - return log_error_errno(r, "%s: failed to enumerate partitions: %m", node); - - first = udev_enumerate_get_list_entry(e); - udev_list_entry_foreach(item, first) { - _cleanup_udev_device_unref_ struct udev_device *q; - unsigned long long flags; - const char *stype, *subnode; - sd_id128_t type_id; - blkid_partition pp; - dev_t qn; - int nr; - - q = udev_device_new_from_syspath(udev, udev_list_entry_get_name(item)); - if (!q) - continue; - - qn = udev_device_get_devnum(q); - if (major(qn) == 0) - continue; - - if (qn == devnum) - continue; - - if (qn == pn) - continue; - - subnode = udev_device_get_devnode(q); - if (!subnode) - continue; - - pp = blkid_partlist_devno_to_partition(pl, qn); - if (!pp) - continue; - - nr = blkid_partition_get_partno(pp); - if (nr < 0) - continue; - - stype = blkid_partition_get_type_string(pp); - if (!stype) - continue; - - if (sd_id128_from_string(stype, &type_id) < 0) - continue; - - flags = blkid_partition_get_flags(pp); - - if (sd_id128_equal(type_id, GPT_SWAP)) { - - if (flags & GPT_FLAG_NO_AUTO) - continue; - - if (flags & GPT_FLAG_READ_ONLY) { - log_debug("%s marked as read-only swap partition, which is bogus. Ignoring.", subnode); - continue; - } - - k = add_swap(subnode); - if (k < 0) - r = k; - - } else if (sd_id128_equal(type_id, GPT_ESP)) { - - /* We only care for the first /boot partition */ - if (boot && nr >= boot_nr) - continue; - - /* Note that we do not honour the "no-auto" - * flag for the ESP, as it is often unset, to - * hide it from Windows. */ - - boot_nr = nr; - - r = free_and_strdup(&boot, subnode); - if (r < 0) - return log_oom(); - - } else if (sd_id128_equal(type_id, GPT_HOME)) { - - if (flags & GPT_FLAG_NO_AUTO) - continue; - - /* We only care for the first /home partition */ - if (home && nr >= home_nr) - continue; - - home_nr = nr; - home_rw = !(flags & GPT_FLAG_READ_ONLY), - - r = free_and_strdup(&home, subnode); - if (r < 0) - return log_oom(); - - } else if (sd_id128_equal(type_id, GPT_SRV)) { - - if (flags & GPT_FLAG_NO_AUTO) - continue; - - /* We only care for the first /srv partition */ - if (srv && nr >= srv_nr) - continue; - - srv_nr = nr; - srv_rw = !(flags & GPT_FLAG_READ_ONLY), - - r = free_and_strdup(&srv, subnode); - if (r < 0) - return log_oom(); - } - } - - if (boot) { - k = add_esp(boot); + if (m->partitions[PARTITION_SWAP].found) { + k = add_swap(m->partitions[PARTITION_SWAP].node); if (k < 0) r = k; } - if (home) { - k = probe_and_add_mount("home", home, "/home", home_rw, "Home Partition", SPECIAL_LOCAL_FS_TARGET); + if (m->partitions[PARTITION_ESP].found) { + k = add_esp(m->partitions + PARTITION_ESP); if (k < 0) r = k; } - if (srv) { - k = probe_and_add_mount("srv", srv, "/srv", srv_rw, "Server Data Partition", SPECIAL_LOCAL_FS_TARGET); + if (m->partitions[PARTITION_HOME].found) { + k = add_partition_mount(m->partitions + PARTITION_HOME, "home", "/home", "Home Partition"); + if (k < 0) + r = k; + } + + if (m->partitions[PARTITION_SRV].found) { + k = add_partition_mount(m->partitions + PARTITION_SRV, "srv", "/srv", "Server Data Partition"); if (k < 0) r = k; } From 2f3dfc6fb43e13f3999d10c509105d46f3cf5b93 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Dec 2016 12:57:44 +0100 Subject: [PATCH 32/46] verity: add support for setting up verity-protected root disks in the initrd This adds a generator and a small service that will look for "roothash=" on the kernel command line and use it for setting up a very partition for the root device. This provides similar functionality to nspawn's existing --roothash= switch. --- .gitignore | 2 + Makefile-man.am | 13 +- Makefile.am | 23 +- man/kernel-command-line.xml | 14 ++ man/systemd-veritysetup-generator.xml | 122 ++++++++++ man/systemd-veritysetup@.service.xml | 75 ++++++ src/fstab-generator/fstab-generator.c | 29 +++ src/gpt-auto-generator/gpt-auto-generator.c | 9 + src/veritysetup/Makefile | 1 + src/veritysetup/veritysetup-generator.c | 253 ++++++++++++++++++++ src/veritysetup/veritysetup.c | 154 ++++++++++++ 11 files changed, 691 insertions(+), 4 deletions(-) create mode 100644 man/systemd-veritysetup-generator.xml create mode 100644 man/systemd-veritysetup@.service.xml create mode 120000 src/veritysetup/Makefile create mode 100644 src/veritysetup/veritysetup-generator.c create mode 100644 src/veritysetup/veritysetup.c diff --git a/.gitignore b/.gitignore index f246d3e6d5..fe7859c265 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,8 @@ /systemd-update-utmp /systemd-user-sessions /systemd-vconsole-setup +/systemd-veritysetup +/systemd-veritysetup-generator /systemd-volatile-root /tags /test-acd diff --git a/Makefile-man.am b/Makefile-man.am index 27660ef1c2..c47bedd0df 100644 --- a/Makefile-man.am +++ b/Makefile-man.am @@ -2276,13 +2276,20 @@ if HAVE_LIBCRYPTSETUP MANPAGES += \ man/crypttab.5 \ man/systemd-cryptsetup-generator.8 \ - man/systemd-cryptsetup@.service.8 + man/systemd-cryptsetup@.service.8 \ + man/systemd-veritysetup-generator.8 \ + man/systemd-veritysetup@.service.8 MANPAGES_ALIAS += \ - man/systemd-cryptsetup.8 + man/systemd-cryptsetup.8 \ + man/systemd-veritysetup.8 man/systemd-cryptsetup.8: man/systemd-cryptsetup@.service.8 +man/systemd-veritysetup.8: man/systemd-veritysetup@.service.8 man/systemd-cryptsetup.html: man/systemd-cryptsetup@.service.html $(html-alias) +man/systemd-veritysetup.html: man/systemd-veritysetup@.service.html + $(html-alias) + endif if HAVE_MICROHTTPD @@ -2810,6 +2817,8 @@ EXTRA_DIST += \ man/systemd-update-utmp.service.xml \ man/systemd-user-sessions.service.xml \ man/systemd-vconsole-setup.service.xml \ + man/systemd-veritysetup-generator.xml \ + man/systemd-veritysetup@.service.xml \ man/systemd-volatile-root.service.xml \ man/systemd.automount.xml \ man/systemd.device.xml \ diff --git a/Makefile.am b/Makefile.am index 92a3680461..c87c547e05 100644 --- a/Makefile.am +++ b/Makefile.am @@ -4833,10 +4833,12 @@ EXTRA_DIST += \ # ------------------------------------------------------------------------------ if HAVE_LIBCRYPTSETUP rootlibexec_PROGRAMS += \ - systemd-cryptsetup + systemd-cryptsetup \ + systemd-veritysetup systemgenerator_PROGRAMS += \ - systemd-cryptsetup-generator + systemd-cryptsetup-generator \ + systemd-veritysetup-generator dist_systemunit_DATA += \ units/cryptsetup.target \ @@ -4859,6 +4861,23 @@ systemd_cryptsetup_generator_SOURCES = \ systemd_cryptsetup_generator_LDADD = \ libsystemd-shared.la +systemd_veritysetup_SOURCES = \ + src/veritysetup/veritysetup.c + +systemd_veritysetup_CFLAGS = \ + $(AM_CFLAGS) \ + $(LIBCRYPTSETUP_CFLAGS) + +systemd_veritysetup_LDADD = \ + libsystemd-shared.la \ + $(LIBCRYPTSETUP_LIBS) + +systemd_veritysetup_generator_SOURCES = \ + src/veritysetup/veritysetup-generator.c + +systemd_veritysetup_generator_LDADD = \ + libsystemd-shared.la + SYSINIT_TARGET_WANTS += \ cryptsetup.target diff --git a/man/kernel-command-line.xml b/man/kernel-command-line.xml index 7e1d408ded..415b8d3cf9 100644 --- a/man/kernel-command-line.xml +++ b/man/kernel-command-line.xml @@ -333,6 +333,19 @@ + + roothash= + systemd.verity= + rd.systemd.verity= + systemd.verity_root_data= + systemd.verity_root_hash= + + Configures the integrity protection root hash for the root file system, and other related + parameters. For details, see + systemd-veritysetup-generator8. + + + systemd.gpt_auto= rd.systemd.gpt_auto= @@ -402,6 +415,7 @@ systemd-udevd.service8, plymouth8, systemd-cryptsetup-generator8, + systemd-veritysetup-generator8, systemd-fstab-generator8, systemd-gpt-auto-generator8, systemd-volatile-root.service8, diff --git a/man/systemd-veritysetup-generator.xml b/man/systemd-veritysetup-generator.xml new file mode 100644 index 0000000000..87d66e9ee5 --- /dev/null +++ b/man/systemd-veritysetup-generator.xml @@ -0,0 +1,122 @@ + + + + + + + + systemd-veritysetup-generator + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-veritysetup-generator + 8 + + + + systemd-veritysetup-generator + Unit generator for integrity protected block devices + + + + /usr/lib/systemd/system-generators/systemd-veritysetup-generator + + + + Description + + systemd-veritysetup-generator is a generator that translates kernel command line options + configuring integrity protected block devices (verity) into native systemd units early at boot and when + configuration of the system manager is reloaded. This will create + systemd-veritysetup@.service8 + units as necessary. + + Currently, only a single verity device may be se up with this generator, backing the root file system of the + OS. + + systemd-veritysetup-generator implements + systemd.generator7. + + + + Kernel Command Line + + systemd-veritysetup-generator + understands the following kernel command line parameters: + + + + systemd.verity= + rd.systemd.verity= + + Takes a boolean argument. Defaults to yes. If no, + disables the generator entirely. rd.systemd.verity= is honored only by the initial RAM disk + (initrd) while systemd.verity= is honored by both the host system and the + initrd. + + + + roothash= + + Takes a root hash value for the root file system. Expects a hash value formatted in hexadecimal + characters, of the appropriate length (i.e. most likely 256 bit/64 characters, or longer). If not specified via + systemd.verity_root_data= and systemd.verity_root_hash=, the hash and + data devices to use are automatically derived from the specified hash value. Specifically, the data partition + device is looked for under a GPT partition UUID derived from the first 128bit of the root hash, the hash + partition device is looked for under a GPT partition UUID derived from the last 128bit of the root hash. Hence + it is usually sufficient to specify the root hash to boot from an integrity protected root file system, as + device paths are automatically determined from it — as long as the partition table is properly set up. + + + + + systemd.verity_root_data= + systemd.verity_root_hash= + + These two settings take block device paths as arguments, and may be use to explicitly configure + the data partition and hash partition to use for setting up the integrity protection for the root file + system. If not specified, these paths are automatically derived from the roothash= argument + (see above). + + + + + + + See Also + + systemd1, + systemd-veritysetup@.service8, + veritysetup8, + systemd-fstab-generator8 + + + + diff --git a/man/systemd-veritysetup@.service.xml b/man/systemd-veritysetup@.service.xml new file mode 100644 index 0000000000..173e5358e0 --- /dev/null +++ b/man/systemd-veritysetup@.service.xml @@ -0,0 +1,75 @@ + + + + + + + + systemd-veritysetup@.service + systemd + + + + Developer + Lennart + Poettering + lennart@poettering.net + + + + + + systemd-veritysetup@.service + 8 + + + + systemd-veritysetup@.service + systemd-veritysetup + Disk integrity protection logic + + + + systemd-veritysetup@.service + /usr/lib/systemd/systemd-veritysetup + + + + Description + + systemd-veritysetup@.service is a service responsible for setting up integrity + protection (verity) block devices. It should be instantiated for each device that requires integrity + protection. + + At early boot and when the system manager configuration is reloaded kernel command line configuration for + integrity protected block devices is translated into systemd-veritysetup@.service units by + systemd-veritysetup-generator8. + + + + See Also + + systemd1, + systemd-veritysetup-generator8, + veritysetup8 + + + + diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c index f58aa27df2..3c601a63e2 100644 --- a/src/fstab-generator/fstab-generator.c +++ b/src/fstab-generator/fstab-generator.c @@ -50,6 +50,7 @@ static bool arg_fstab_enabled = true; static char *arg_root_what = NULL; static char *arg_root_fstype = NULL; static char *arg_root_options = NULL; +static char *arg_root_hash = NULL; static int arg_root_rw = -1; static char *arg_usr_what = NULL; static char *arg_usr_fstype = NULL; @@ -697,6 +698,13 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat free(arg_root_options); arg_root_options = o; + } else if (streq(key, "roothash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + if (free_and_strdup(&arg_root_hash, value) < 0) + return log_oom(); } else if (streq(key, "mount.usr")) { @@ -749,6 +757,24 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat return 0; } +static int determine_root(void) { + /* If we have a root hash but no root device then Verity is used, and we use the "root" DM device as root. */ + + if (arg_root_what) + return 0; + + if (!arg_root_hash) + return 0; + + arg_root_what = strdup("/dev/mapper/root"); + if (!arg_root_what) + return log_oom(); + + log_info("Using verity root device %s.", arg_root_what); + + return 1; +} + int main(int argc, char *argv[]) { int r = 0; @@ -772,6 +798,8 @@ int main(int argc, char *argv[]) { if (r < 0) log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); + (void) determine_root(); + /* Always honour root= and usr= in the kernel command line if we are in an initrd */ if (in_initrd()) { int k; @@ -812,6 +840,7 @@ int main(int argc, char *argv[]) { free(arg_root_what); free(arg_root_fstype); free(arg_root_options); + free(arg_root_hash); free(arg_usr_what); free(arg_usr_fstype); diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 3126469f60..612a3fe777 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -718,6 +718,15 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat arg_root_enabled = streq(value, "gpt-auto"); + } else if (streq(key, "roothash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + /* Disable root disk logic if there's roothash= defined (i.e. verity enabled) */ + + arg_root_enabled = false; + } else if (streq(key, "rw") && !value) arg_root_rw = true; else if (streq(key, "ro") && !value) diff --git a/src/veritysetup/Makefile b/src/veritysetup/Makefile new file mode 120000 index 0000000000..d0b0e8e008 --- /dev/null +++ b/src/veritysetup/Makefile @@ -0,0 +1 @@ +../Makefile \ No newline at end of file diff --git a/src/veritysetup/veritysetup-generator.c b/src/veritysetup/veritysetup-generator.c new file mode 100644 index 0000000000..519ac050f9 --- /dev/null +++ b/src/veritysetup/veritysetup-generator.c @@ -0,0 +1,253 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + 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 . +***/ + +#include +#include +#include +#include + +#include "alloc-util.h" +#include "fd-util.h" +#include "fileio.h" +#include "fstab-util.h" +#include "hexdecoct.h" +#include "id128-util.h" +#include "mkdir.h" +#include "parse-util.h" +#include "proc-cmdline.h" +#include "string-util.h" +#include "unit-name.h" + +static char *arg_dest = NULL; +static bool arg_enabled = true; +static char *arg_root_hash = NULL; +static char *arg_data_what = NULL; +static char *arg_hash_what = NULL; + +static int create_device(void) { + _cleanup_free_ char *u = NULL, *v = NULL, *d = NULL, *e = NULL; + _cleanup_fclose_ FILE *f = NULL; + const char *p, *to; + int r; + + /* If all three pieces of information are missing, then verity is turned off */ + if (!arg_root_hash && !arg_data_what && !arg_hash_what) + return 0; + + /* if one of them is missing however, the data is simply incomplete and this is an error */ + if (!arg_root_hash) + log_error("Verity information incomplete, root hash unspecified."); + if (!arg_data_what) + log_error("Verity information incomplete, root data device unspecified."); + if (!arg_hash_what) + log_error("Verity information incomplete, root hash device unspecified."); + + if (!arg_root_hash || !arg_data_what || !arg_hash_what) + return -EINVAL; + + log_debug("Using root verity data device %s,\n" + " hash device %s,\n" + " and root hash %s.", arg_data_what, arg_hash_what, arg_root_hash); + + p = strjoina(arg_dest, "/systemd-veritysetup@root.service"); + + u = fstab_node_to_udev_node(arg_data_what); + if (!u) + return log_oom(); + v = fstab_node_to_udev_node(arg_hash_what); + if (!v) + return log_oom(); + + r = unit_name_from_path(u, ".device", &d); + if (r < 0) + return log_error_errno(r, "Failed to to generate unit name: %m"); + r = unit_name_from_path(v, ".device", &e); + if (r < 0) + return log_error_errno(r, "Failed to to generate unit name: %m"); + + f = fopen(p, "wxe"); + if (!f) + return log_error_errno(errno, "Failed to create unit file %s: %m", p); + + fprintf(f, + "# Automatically generated by systemd-veritysetup-generator\n\n" + "[Unit]\n" + "Description=Integrity Protection Setup for %%I\n" + "Documentation=man:systemd-veritysetup-generator(8) man:systemd-veritysetup@.service(8)\n" + "SourcePath=/proc/cmdline\n" + "DefaultDependencies=no\n" + "Conflicts=umount.target\n" + "BindsTo=%s %s\n" + "IgnoreOnIsolate=true\n" + "After=cryptsetup-pre.target %s %s\n" + "Before=cryptsetup.target umount.target\n" + "\n[Service]\n" + "Type=oneshot\n" + "RemainAfterExit=yes\n" + "ExecStart=" ROOTLIBEXECDIR "/systemd-veritysetup attach root '%s' '%s' '%s'\n" + "ExecStop=" ROOTLIBEXECDIR "/systemd-veritysetup detach root\n", + d, e, + d, e, + u, v, arg_root_hash); + + r = fflush_and_check(f); + if (r < 0) + return log_error_errno(r, "Failed to write file %s: %m", p); + + to = strjoina(arg_dest, "/cryptsetup.target.requires/systemd-veritysetup@root.service"); + + (void) mkdir_parents(to, 0755); + if (symlink("../systemd-veritysetup@root.service", to) < 0) + return log_error_errno(errno, "Failed to create symlink %s: %m", to); + + return 0; +} + +static int parse_proc_cmdline_item(const char *key, const char *value, void *data) { + int r; + + if (streq(key, "systemd.verity")) { + + r = value ? parse_boolean(value) : 1; + if (r < 0) + log_warning("Failed to parse verity= kernel command line switch %s. Ignoring.", value); + else + arg_enabled = r; + + } else if (streq(key, "roothash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_root_hash, value); + if (r < 0) + return log_oom(); + + } else if (streq(key, "systemd.verity_root_data")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_data_what, value); + if (r < 0) + return log_oom(); + + } else if (streq(key, "systemd.verity_root_hash")) { + + if (proc_cmdline_value_missing(key, value)) + return 0; + + r = free_and_strdup(&arg_hash_what, value); + if (r < 0) + return log_oom(); + } + + return 0; +} + +static int determine_devices(void) { + _cleanup_free_ void *m = NULL; + sd_id128_t root_uuid, verity_uuid; + char ids[37]; + size_t l; + int r; + + /* Try to automatically derive the root data and hash device paths from the root hash */ + + if (!arg_root_hash) + return 0; + + if (arg_data_what && arg_hash_what) + return 0; + + r = unhexmem(arg_root_hash, strlen(arg_root_hash), &m, &l); + if (r < 0) + return log_error_errno(r, "Failed to parse root hash: %s", arg_root_hash); + if (l < sizeof(sd_id128_t)) { + log_debug("Root hash is shorter than 128 bits (32 characters), ignoring for discovering verity partition."); + return 0; + } + + if (!arg_data_what) { + memcpy(&root_uuid, m, sizeof(root_uuid)); + + arg_data_what = strjoin("/dev/disk/by-partuuid/", id128_to_uuid_string(root_uuid, ids)); + if (!arg_data_what) + return log_oom(); + } + + if (!arg_hash_what) { + memcpy(&verity_uuid, (uint8_t*) m + l - sizeof(verity_uuid), sizeof(verity_uuid)); + + arg_hash_what = strjoin("/dev/disk/by-partuuid/", id128_to_uuid_string(verity_uuid, ids)); + if (!arg_hash_what) + return log_oom(); + } + + return 1; +} + +int main(int argc, char *argv[]) { + int r; + + if (argc > 1 && argc != 4) { + log_error("This program takes three or no arguments."); + return EXIT_FAILURE; + } + + if (argc > 1) + arg_dest = argv[1]; + + log_set_target(LOG_TARGET_SAFE); + log_parse_environment(); + log_open(); + + umask(0022); + + r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); + if (r < 0) { + log_warning_errno(r, "Failed to parse kernel command line: %m"); + goto finish; + } + + /* For now we only support the root device on verity. Later on we might want to add support for /etc/veritytab + * or similar to define additional mappings */ + + if (!arg_enabled) { + r = 0; + goto finish; + } + + r = determine_devices(); + if (r < 0) + goto finish; + + r = create_device(); + if (r < 0) + goto finish; + + r = 0; + +finish: + free(arg_root_hash); + free(arg_data_what); + free(arg_hash_what); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} diff --git a/src/veritysetup/veritysetup.c b/src/veritysetup/veritysetup.c new file mode 100644 index 0000000000..f809d51638 --- /dev/null +++ b/src/veritysetup/veritysetup.c @@ -0,0 +1,154 @@ +/*** + This file is part of systemd. + + Copyright 2016 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + 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 . +***/ + +#include +#include +#include + +#include "log.h" +#include "hexdecoct.h" +#include "string-util.h" +#include "alloc-util.h" + +static char *arg_root_hash = NULL; +static char *arg_data_what = NULL; +static char *arg_hash_what = NULL; + +static int help(void) { + printf("%s attach VOLUME DATADEVICE HASHDEVICE ROOTHASH\n" + "%s detach VOLUME\n\n" + "Attaches or detaches an integrity protected block device.\n", + program_invocation_short_name, + program_invocation_short_name); + + return 0; +} + +static void log_glue(int level, const char *msg, void *usrptr) { + log_debug("%s", msg); +} + +int main(int argc, char *argv[]) { + struct crypt_device *cd = NULL; + int r; + + if (argc <= 1) { + r = help(); + goto finish; + } + + if (argc < 3) { + log_error("This program requires at least two arguments."); + r = -EINVAL; + goto finish; + } + + log_set_target(LOG_TARGET_AUTO); + log_parse_environment(); + log_open(); + + umask(0022); + + if (streq(argv[1], "attach")) { + _cleanup_free_ void *m = NULL; + crypt_status_info status; + size_t l; + + if (argc < 6) { + log_error("attach requires at least two arguments."); + r = -EINVAL; + goto finish; + } + + r = unhexmem(argv[5], strlen(argv[5]), &m, &l); + if (r < 0) { + log_error("Failed to parse root hash."); + goto finish; + } + + r = crypt_init(&cd, argv[4]); + if (r < 0) { + log_error_errno(r, "Failed to open verity device %s: %m", argv[4]); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + status = crypt_status(cd, argv[2]); + if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { + log_info("Volume %s already active.", argv[2]); + r = 0; + goto finish; + } + + r = crypt_load(cd, CRYPT_VERITY, NULL); + if (r < 0) { + log_error_errno(r, "Failed to load verity superblock: %m"); + goto finish; + } + + r = crypt_set_data_device(cd, argv[3]); + if (r < 0) { + log_error_errno(r, "Failed to configure data device: %m"); + goto finish; + } + + r = crypt_activate_by_volume_key(cd, argv[2], m, l, CRYPT_ACTIVATE_READONLY); + if (r < 0) { + log_error_errno(r, "Failed to set up verity device: %m"); + goto finish; + } + + } else if (streq(argv[1], "detach")) { + + r = crypt_init_by_name(&cd, argv[2]); + if (r == -ENODEV) { + log_info("Volume %s already inactive.", argv[2]); + goto finish; + } else if (r < 0) { + log_error_errno(r, "crypt_init_by_name() failed: %m"); + goto finish; + } + + crypt_set_log_callback(cd, log_glue, NULL); + + r = crypt_deactivate(cd, argv[2]); + if (r < 0) { + log_error_errno(r, "Failed to deactivate: %m"); + goto finish; + } + + } else { + log_error("Unknown verb %s.", argv[1]); + r = -EINVAL; + goto finish; + } + + r = 0; + +finish: + if (cd) + crypt_free(cd); + + free(arg_root_hash); + free(arg_data_what); + free(arg_hash_what); + + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; +} From c0cee5f1e851f0a5ba7b7935e0290c6cf5c3aaeb Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Dec 2016 13:01:03 +0100 Subject: [PATCH 33/46] man: drop superfluous 'this' in man page --- man/systemd-cryptsetup@.service.xml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/man/systemd-cryptsetup@.service.xml b/man/systemd-cryptsetup@.service.xml index ea524851eb..fb8b06714d 100644 --- a/man/systemd-cryptsetup@.service.xml +++ b/man/systemd-cryptsetup@.service.xml @@ -66,9 +66,8 @@ password using the right mechanism at boot and during runtime. - At early boot and when the system manager configuration is - reloaded this /etc/crypttab is translated - into systemd-cryptsetup@.service units by + At early boot and when the system manager configuration is reloaded, /etc/crypttab is + translated into systemd-cryptsetup@.service units by systemd-cryptsetup-generator8. From a93c4776decffad08bfaf2b2a195aa162af896e8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Dec 2016 13:08:29 +0100 Subject: [PATCH 34/46] util-lib: be stricter when decoding hex strings If a hex string has an uneven length, generate an error instead of silently assuming a trailing '0' was in place. --- src/basic/hexdecoct.c | 13 +++++++------ src/test/test-hexdecoct.c | 18 +++++------------- 2 files changed, 12 insertions(+), 19 deletions(-) diff --git a/src/basic/hexdecoct.c b/src/basic/hexdecoct.c index c5bda6c4d6..6843aedd0a 100644 --- a/src/basic/hexdecoct.c +++ b/src/basic/hexdecoct.c @@ -97,6 +97,9 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) { assert(len); assert(p); + if (l % 2 != 0) + return -EINVAL; + z = r = malloc((l + 1) / 2 + 1); if (!r) return -ENOMEM; @@ -107,12 +110,10 @@ int unhexmem(const char *p, size_t l, void **mem, size_t *len) { a = unhexchar(x[0]); if (a < 0) return a; - else if (x+1 < p + l) { - b = unhexchar(x[1]); - if (b < 0) - return b; - } else - b = 0; + + b = unhexchar(x[1]); + if (b < 0) + return b; *(z++) = (uint8_t) a << 4 | (uint8_t) b; } diff --git a/src/test/test-hexdecoct.c b/src/test/test-hexdecoct.c index 276f25d091..fcae427e74 100644 --- a/src/test/test-hexdecoct.c +++ b/src/test/test-hexdecoct.c @@ -87,27 +87,19 @@ static void test_undecchar(void) { } static void test_unhexmem(void) { - const char *hex = "efa214921"; + const char *hex = "efa2149213"; const char *hex_invalid = "efa214921o"; _cleanup_free_ char *hex2 = NULL; _cleanup_free_ void *mem = NULL; size_t len; - assert_se(unhexmem(hex, strlen(hex), &mem, &len) == 0); - assert_se(unhexmem(hex, strlen(hex) + 1, &mem, &len) == -EINVAL); assert_se(unhexmem(hex_invalid, strlen(hex_invalid), &mem, &len) == -EINVAL); + assert_se(unhexmem(hex, strlen(hex) + 1, &mem, &len) == -EINVAL); + assert_se(unhexmem(hex, strlen(hex) - 1, &mem, &len) == -EINVAL); + assert_se(unhexmem(hex, strlen(hex), &mem, &len) == 0); assert_se((hex2 = hexmem(mem, len))); - - free(mem); - - assert_se(memcmp(hex, hex2, strlen(hex)) == 0); - - free(hex2); - - assert_se(unhexmem(hex, strlen(hex) - 1, &mem, &len) == 0); - assert_se((hex2 = hexmem(mem, len))); - assert_se(memcmp(hex, hex2, strlen(hex) - 1) == 0); + assert_se(streq(hex, hex2)); } /* https://tools.ietf.org/html/rfc4648#section-10 */ From 5f4bfe56f3a413584d9ee203b3c5a8ea8ca803d2 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Fri, 16 Dec 2016 13:15:31 +0100 Subject: [PATCH 35/46] cryptsetup: various coding style improvements No functional changes. --- src/cryptsetup/cryptsetup-generator.c | 56 ++++++++++++----------- src/cryptsetup/cryptsetup.c | 64 +++++++++++++-------------- 2 files changed, 61 insertions(+), 59 deletions(-) diff --git a/src/cryptsetup/cryptsetup-generator.c b/src/cryptsetup/cryptsetup-generator.c index e2a43f7195..23bf014929 100644 --- a/src/cryptsetup/cryptsetup-generator.c +++ b/src/cryptsetup/cryptsetup-generator.c @@ -102,18 +102,17 @@ static int create_disk( if (!f) return log_error_errno(errno, "Failed to create unit file %s: %m", p); - fputs( - "# Automatically generated by systemd-cryptsetup-generator\n\n" - "[Unit]\n" - "Description=Cryptography Setup for %I\n" - "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n" - "SourcePath=/etc/crypttab\n" - "DefaultDependencies=no\n" - "Conflicts=umount.target\n" - "BindsTo=dev-mapper-%i.device\n" - "IgnoreOnIsolate=true\n" - "After=cryptsetup-pre.target\n", - f); + fputs("# Automatically generated by systemd-cryptsetup-generator\n\n" + "[Unit]\n" + "Description=Cryptography Setup for %I\n" + "Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)\n" + "SourcePath=/etc/crypttab\n" + "DefaultDependencies=no\n" + "Conflicts=umount.target\n" + "BindsTo=dev-mapper-%i.device\n" + "IgnoreOnIsolate=true\n" + "After=cryptsetup-pre.target\n", + f); if (!nofail) fprintf(f, @@ -357,7 +356,6 @@ static int parse_proc_cmdline_item(const char *key, const char *value, void *dat uuid_value = NULL; } else log_warning("Failed to parse luks name switch %s. Ignoring.", value); - } return 0; @@ -466,7 +464,7 @@ static int add_proc_cmdline_devices(void) { } int main(int argc, char *argv[]) { - int r = EXIT_FAILURE; + int r; if (argc > 1 && argc != 4) { log_error("This program takes three or no arguments."); @@ -483,32 +481,36 @@ int main(int argc, char *argv[]) { umask(0022); arg_disks = hashmap_new(&string_hash_ops); - if (!arg_disks) - goto cleanup; + if (!arg_disks) { + r = log_oom(); + goto finish; + } r = proc_cmdline_parse(parse_proc_cmdline_item, NULL, PROC_CMDLINE_STRIP_RD_PREFIX); if (r < 0) { - log_warning_errno(r, "Failed to parse kernel command line, ignoring: %m"); - r = EXIT_FAILURE; + log_warning_errno(r, "Failed to parse kernel command line: %m"); + goto finish; } if (!arg_enabled) { - r = EXIT_SUCCESS; - goto cleanup; + r = 0; + goto finish; } - if (add_crypttab_devices() < 0) - goto cleanup; + r = add_crypttab_devices(); + if (r < 0) + goto finish; - if (add_proc_cmdline_devices() < 0) - goto cleanup; + r = add_proc_cmdline_devices(); + if (r < 0) + goto finish; - r = EXIT_SUCCESS; + r = 0; -cleanup: +finish: free_arg_disks(); free(arg_default_options); free(arg_default_keyfile); - return r; + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c index b3abbb8b2f..bff5664f0f 100644 --- a/src/cryptsetup/cryptsetup.c +++ b/src/cryptsetup/cryptsetup.c @@ -593,17 +593,18 @@ static int help(void) { } int main(int argc, char *argv[]) { - int r = EXIT_FAILURE; struct crypt_device *cd = NULL; + int r; if (argc <= 1) { - help(); - return EXIT_SUCCESS; + r = help(); + goto finish; } if (argc < 3) { log_error("This program requires at least two arguments."); - return EXIT_FAILURE; + r = -EINVAL; + goto finish; } log_set_target(LOG_TARGET_AUTO); @@ -614,7 +615,6 @@ int main(int argc, char *argv[]) { if (streq(argv[1], "attach")) { uint32_t flags = 0; - int k; unsigned tries; usec_t until; crypt_status_info status; @@ -648,11 +648,11 @@ int main(int argc, char *argv[]) { if (arg_header) { log_debug("LUKS header: %s", arg_header); - k = crypt_init(&cd, arg_header); + r = crypt_init(&cd, arg_header); } else - k = crypt_init(&cd, argv[3]); - if (k != 0) { - log_error_errno(k, "crypt_init() failed: %m"); + r = crypt_init(&cd, argv[3]); + if (r < 0) { + log_error_errno(r, "crypt_init() failed: %m"); goto finish; } @@ -661,7 +661,7 @@ int main(int argc, char *argv[]) { status = crypt_status(cd, argv[2]); if (status == CRYPT_ACTIVE || status == CRYPT_BUSY) { log_info("Volume %s already active.", argv[2]); - r = EXIT_SUCCESS; + r = 0; goto finish; } @@ -691,29 +691,30 @@ int main(int argc, char *argv[]) { _cleanup_strv_free_erase_ char **passwords = NULL; if (!key_file) { - k = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); - if (k == -EAGAIN) + r = get_password(argv[2], argv[3], until, tries == 0 && !arg_verify, &passwords); + if (r == -EAGAIN) continue; - else if (k < 0) + if (r < 0) goto finish; } if (streq_ptr(arg_type, CRYPT_TCRYPT)) - k = attach_tcrypt(cd, argv[2], key_file, passwords, flags); + r = attach_tcrypt(cd, argv[2], key_file, passwords, flags); else - k = attach_luks_or_plain(cd, + r = attach_luks_or_plain(cd, argv[2], key_file, arg_header ? argv[3] : NULL, passwords, flags); - if (k >= 0) + if (r >= 0) break; - else if (k == -EAGAIN) { + if (r == -EAGAIN) { key_file = NULL; continue; - } else if (k != -EPERM) { - log_error_errno(k, "Failed to activate: %m"); + } + if (r != -EPERM) { + log_error_errno(r, "Failed to activate: %m"); goto finish; } @@ -722,28 +723,28 @@ int main(int argc, char *argv[]) { if (arg_tries != 0 && tries >= arg_tries) { log_error("Too many attempts; giving up."); - r = EXIT_FAILURE; + r = -EPERM; goto finish; } } else if (streq(argv[1], "detach")) { - int k; - k = crypt_init_by_name(&cd, argv[2]); - if (k == -ENODEV) { + r = crypt_init_by_name(&cd, argv[2]); + if (r == -ENODEV) { log_info("Volume %s already inactive.", argv[2]); - r = EXIT_SUCCESS; + r = 0; goto finish; - } else if (k) { - log_error_errno(k, "crypt_init_by_name() failed: %m"); + } + if (r < 0) { + log_error_errno(r, "crypt_init_by_name() failed: %m"); goto finish; } crypt_set_log_callback(cd, log_glue, NULL); - k = crypt_deactivate(cd, argv[2]); - if (k < 0) { - log_error_errno(k, "Failed to deactivate: %m"); + r = crypt_deactivate(cd, argv[2]); + if (r < 0) { + log_error_errno(r, "Failed to deactivate: %m"); goto finish; } @@ -752,10 +753,9 @@ int main(int argc, char *argv[]) { goto finish; } - r = EXIT_SUCCESS; + r = 0; finish: - if (cd) crypt_free(cd); @@ -764,5 +764,5 @@ finish: free(arg_header); strv_free(arg_tcrypt_keyfiles); - return r; + return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS; } From 78f0243a445882211058f995ee99ea9ae088b802 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 20 Dec 2016 16:04:07 +0100 Subject: [PATCH 36/46] gpt-auto-generator: enable auto-discovery logic also for verity root file systems verity block devices have two backing devices: the data partition and the hash partition. Previously the gpt auto-discovery logic would refuse working on devices with multiple backing devices, losen this up a bit, to permit them as long as the backing devices are all located on the same physical media. --- src/gpt-auto-generator/gpt-auto-generator.c | 36 +++++++++++++++++++-- 1 file changed, 34 insertions(+), 2 deletions(-) diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c index 612a3fe777..f05544f3d4 100644 --- a/src/gpt-auto-generator/gpt-auto-generator.c +++ b/src/gpt-auto-generator/gpt-auto-generator.c @@ -664,8 +664,40 @@ static int get_block_device_harder(const char *path, dev_t *dev) { if (!IN_SET(de->d_type, DT_LNK, DT_UNKNOWN)) continue; - if (found) /* Don't try to support multiple backing block devices */ - goto fallback; + if (found) { + _cleanup_free_ char *u = NULL, *v = NULL, *a = NULL, *b = NULL; + + /* We found a device backed by multiple other devices. We don't really support automatic + * discovery on such setups, with the exception of dm-verity partitions. In this case there are + * two backing devices: the data partitoin and the hash partition. We are fine with such + * setups, however, only if both partitions are on the same physical device. Hence, let's + * verify this. */ + + u = strjoin(p, "/", de->d_name, "/../dev"); + if (!u) + return -ENOMEM; + + v = strjoin(p, "/", found->d_name, "/../dev"); + if (!v) + return -ENOMEM; + + r = read_one_line_file(u, &a); + if (r < 0) { + log_debug_errno(r, "Failed to read %s: %m", u); + goto fallback; + } + + r = read_one_line_file(v, &b); + if (r < 0) { + log_debug_errno(r, "Failed to read %s: %m", v); + goto fallback; + } + + /* Check if the parent device is the same. If not, then the two backing devices are on + * different physical devices, and we don't support that. */ + if (!streq(a, b)) + goto fallback; + } found = de; } From 91359193c359180e6f9453a5afa1e68efdf2b064 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 20 Dec 2016 17:49:35 +0100 Subject: [PATCH 37/46] importd: automatically download .roothash in addition to .nspawn for raw downloads Now that nspawn looks for these files, and mkosi generates them, we should also make sure importd downloads them if it can. --- src/import/pull-common.c | 129 +++++++++--------- src/import/pull-common.h | 4 +- src/import/pull-raw.c | 275 +++++++++++++++++++++++++++------------ src/import/pull-raw.h | 2 +- src/import/pull-tar.c | 86 ++++++------ src/import/pull.c | 14 +- 6 files changed, 322 insertions(+), 188 deletions(-) diff --git a/src/import/pull-common.c b/src/import/pull-common.c index 5ddc0c56f4..62a9195cc4 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -218,37 +218,40 @@ int pull_make_path(const char *url, const char *etag, const char *image_root, co return 0; } -int pull_make_settings_job( +int pull_make_auxiliary_job( PullJob **ret, const char *url, + int (*strip_suffixes)(const char *name, char **ret), + const char *suffix, CurlGlue *glue, PullJobFinished on_finished, void *userdata) { - _cleanup_free_ char *last_component = NULL, *ll = NULL, *settings_url = NULL; + _cleanup_free_ char *last_component = NULL, *ll = NULL, *auxiliary_url = NULL; _cleanup_(pull_job_unrefp) PullJob *job = NULL; const char *q; int r; assert(ret); assert(url); + assert(strip_suffixes); assert(glue); r = import_url_last_component(url, &last_component); if (r < 0) return r; - r = tar_strip_suffixes(last_component, &ll); + r = strip_suffixes(last_component, &ll); if (r < 0) return r; - q = strjoina(ll, ".nspawn"); + q = strjoina(ll, suffix); - r = import_url_change_last_component(url, q, &settings_url); + r = import_url_change_last_component(url, q, &auxiliary_url); if (r < 0) return r; - r = pull_job_new(&job, settings_url, glue, userdata); + r = pull_job_new(&job, auxiliary_url, glue, userdata); if (r < 0) return r; @@ -320,7 +323,56 @@ int pull_make_verification_jobs( return 0; } +static int verify_one(PullJob *checksum_job, PullJob *job) { + _cleanup_free_ char *fn = NULL; + const char *line, *p; + int r; + + assert(checksum_job); + + if (!job) + return 0; + + assert(IN_SET(job->state, PULL_JOB_DONE, PULL_JOB_FAILED)); + + /* Don't verify the checksum if we didn't actually successfully download something new */ + if (job->state != PULL_JOB_DONE) + return 0; + if (job->error != 0) + return 0; + if (job->etag_exists) + return 0; + + assert(job->calc_checksum); + assert(job->checksum); + + r = import_url_last_component(job->url, &fn); + if (r < 0) + return log_oom(); + + if (!filename_is_valid(fn)) { + log_error("Cannot verify checksum, could not determine server-side file name."); + return -EBADMSG; + } + + line = strjoina(job->checksum, " *", fn, "\n"); + + p = memmem(checksum_job->payload, + checksum_job->payload_size, + line, + strlen(line)); + + if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { + log_error("DOWNLOAD INVALID: Checksum of %s file did not checkout, file has been tampered with.", fn); + return -EBADMSG; + } + + log_info("SHA256 checksum of %s is valid.", job->url); + return 1; +} + int pull_verify(PullJob *main_job, + PullJob *roothash_job, PullJob *settings_job, PullJob *checksum_job, PullJob *signature_job) { @@ -328,7 +380,6 @@ int pull_verify(PullJob *main_job, _cleanup_close_pair_ int gpg_pipe[2] = { -1, -1 }; _cleanup_free_ char *fn = NULL; _cleanup_close_ int sig_file = -1; - const char *p, *line; char sig_file_path[] = "/tmp/sigXXXXXX", gpg_home[] = "/tmp/gpghomeXXXXXX"; _cleanup_(sigkill_waitp) pid_t pid = 0; bool gpg_home_created = false; @@ -342,6 +393,7 @@ int pull_verify(PullJob *main_job, assert(main_job->calc_checksum); assert(main_job->checksum); + assert(checksum_job->state == PULL_JOB_DONE); if (!checksum_job->payload || checksum_job->payload_size <= 0) { @@ -349,64 +401,17 @@ int pull_verify(PullJob *main_job, return -EBADMSG; } - r = import_url_last_component(main_job->url, &fn); + r = verify_one(checksum_job, main_job); if (r < 0) - return log_oom(); + return r; - if (!filename_is_valid(fn)) { - log_error("Cannot verify checksum, could not determine valid server-side file name."); - return -EBADMSG; - } + r = verify_one(checksum_job, roothash_job); + if (r < 0) + return r; - line = strjoina(main_job->checksum, " *", fn, "\n"); - - p = memmem(checksum_job->payload, - checksum_job->payload_size, - line, - strlen(line)); - - if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { - log_error("DOWNLOAD INVALID: Checksum did not check out, payload has been tampered with."); - return -EBADMSG; - } - - log_info("SHA256 checksum of %s is valid.", main_job->url); - - assert(!settings_job || IN_SET(settings_job->state, PULL_JOB_DONE, PULL_JOB_FAILED)); - - if (settings_job && - settings_job->state == PULL_JOB_DONE && - settings_job->error == 0 && - !settings_job->etag_exists) { - - _cleanup_free_ char *settings_fn = NULL; - - assert(settings_job->calc_checksum); - assert(settings_job->checksum); - - r = import_url_last_component(settings_job->url, &settings_fn); - if (r < 0) - return log_oom(); - - if (!filename_is_valid(settings_fn)) { - log_error("Cannot verify checksum, could not determine server-side settings file name."); - return -EBADMSG; - } - - line = strjoina(settings_job->checksum, " *", settings_fn, "\n"); - - p = memmem(checksum_job->payload, - checksum_job->payload_size, - line, - strlen(line)); - - if (!p || (p != (char*) checksum_job->payload && p[-1] != '\n')) { - log_error("DOWNLOAD INVALID: Checksum of settings file did not checkout, settings file has been tampered with."); - return -EBADMSG; - } - - log_info("SHA256 checksum of %s is valid.", settings_job->url); - } + r = verify_one(checksum_job, settings_job); + if (r < 0) + return r; if (!signature_job) return 0; diff --git a/src/import/pull-common.h b/src/import/pull-common.h index 929a131c88..f1f1a17fa9 100644 --- a/src/import/pull-common.h +++ b/src/import/pull-common.h @@ -30,7 +30,7 @@ int pull_find_old_etags(const char *url, const char *root, int dt, const char *p int pull_make_path(const char *url, const char *etag, const char *image_root, const char *prefix, const char *suffix, char **ret); -int pull_make_settings_job(PullJob **ret, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata); +int pull_make_auxiliary_job(PullJob **ret, const char *url, int (*strip_suffixes)(const char *name, char **ret), const char *suffix, CurlGlue *glue, PullJobFinished on_finished, void *userdata); int pull_make_verification_jobs(PullJob **ret_checksum_job, PullJob **ret_signature_job, ImportVerify verify, const char *url, CurlGlue *glue, PullJobFinished on_finished, void *userdata); -int pull_verify(PullJob *main_job, PullJob *settings_job, PullJob *checksum_job, PullJob *signature_job); +int pull_verify(PullJob *main_job, PullJob *roothash_job, PullJob *settings_job, PullJob *checksum_job, PullJob *signature_job); diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 0cf410a5d9..ef7fb6ac42 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -63,6 +63,7 @@ struct RawPull { char *image_root; PullJob *raw_job; + PullJob *roothash_job; PullJob *settings_job; PullJob *checksum_job; PullJob *signature_job; @@ -74,6 +75,7 @@ struct RawPull { bool force_local; bool grow_machine_directory; bool settings; + bool roothash; char *final_path; char *temp_path; @@ -81,6 +83,9 @@ struct RawPull { char *settings_path; char *settings_temp_path; + char *roothash_path; + char *roothash_temp_path; + ImportVerify verify; }; @@ -90,6 +95,7 @@ RawPull* raw_pull_unref(RawPull *i) { pull_job_unref(i->raw_job); pull_job_unref(i->settings_job); + pull_job_unref(i->roothash_job); pull_job_unref(i->checksum_job); pull_job_unref(i->signature_job); @@ -101,12 +107,18 @@ RawPull* raw_pull_unref(RawPull *i) { free(i->temp_path); } + if (i->roothash_temp_path) { + (void) unlink(i->roothash_temp_path); + free(i->roothash_temp_path); + } + if (i->settings_temp_path) { (void) unlink(i->settings_temp_path); free(i->settings_temp_path); } free(i->final_path); + free(i->roothash_path); free(i->settings_path); free(i->image_root); free(i->local); @@ -176,6 +188,11 @@ static void raw_pull_report_progress(RawPull *i, RawProgress p) { remain -= 5; } + if (i->roothash_job) { + percent += i->roothash_job->progress_percent * 5 / 100; + remain -= 5; + } + if (i->checksum_job) { percent += i->checksum_job->progress_percent * 5 / 100; remain -= 5; @@ -262,6 +279,55 @@ static int raw_pull_maybe_convert_qcow2(RawPull *i) { return 1; } +static int raw_pull_determine_path(RawPull *i, const char *suffix, char **field) { + int r; + + assert(i); + assert(field); + + if (*field) + return 0; + + assert(i->raw_job); + + r = pull_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", suffix, field); + if (r < 0) + return log_oom(); + + return 1; +} + +static int raw_pull_copy_auxiliary_file( + RawPull *i, + const char *suffix, + char **path) { + + const char *local; + int r; + + assert(i); + assert(suffix); + assert(path); + + r = raw_pull_determine_path(i, suffix, path); + if (r < 0) + return r; + + local = strjoina(i->image_root, "/", i->local, suffix); + + r = copy_file_atomic(*path, local, 0644, i->force_local, 0); + if (r == -EEXIST) + log_warning_errno(r, "File %s already exists, not replacing.", local); + else if (r == -ENOENT) + log_debug_errno(r, "Skipping creation of auxiliary file, since none was found."); + else if (r < 0) + log_warning_errno(r, "Failed to copy file %s, ignoring: %m", local); + else + log_info("Created new file %s.", local); + + return 0; +} + static int raw_pull_make_local_copy(RawPull *i) { _cleanup_free_ char *tp = NULL; _cleanup_close_ int dfd = -1; @@ -274,12 +340,6 @@ static int raw_pull_make_local_copy(RawPull *i) { if (!i->local) return 0; - if (!i->final_path) { - r = pull_make_path(i->raw_job->url, i->raw_job->etag, i->image_root, ".raw-", ".raw", &i->final_path); - if (r < 0) - return log_oom(); - } - if (i->raw_job->etag_exists) { /* We have downloaded this one previously, reopen it */ @@ -338,27 +398,16 @@ static int raw_pull_make_local_copy(RawPull *i) { log_info("Created new local image '%s'.", i->local); + if (i->roothash) { + r = raw_pull_copy_auxiliary_file(i, ".roothash", &i->roothash_path); + if (r < 0) + return r; + } + if (i->settings) { - const char *local_settings; - assert(i->settings_job); - - if (!i->settings_path) { - r = pull_make_path(i->settings_job->url, i->settings_job->etag, i->image_root, ".settings-", NULL, &i->settings_path); - if (r < 0) - return log_oom(); - } - - local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); - - r = copy_file_atomic(i->settings_path, local_settings, 0644, i->force_local, 0); - if (r == -EEXIST) - log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); - else if (r == -ENOENT) - log_debug_errno(r, "Skipping creation of settings file, since none was found."); - else if (r < 0) - log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); - else - log_info("Created new settings file %s.", local_settings); + r = raw_pull_copy_auxiliary_file(i, ".nspawn", &i->settings_path); + if (r < 0) + return r; } return 0; @@ -370,6 +419,8 @@ static bool raw_pull_is_done(RawPull *i) { if (!PULL_JOB_IS_COMPLETE(i->raw_job)) return false; + if (i->roothash_job && !PULL_JOB_IS_COMPLETE(i->roothash_job)) + return false; if (i->settings_job && !PULL_JOB_IS_COMPLETE(i->settings_job)) return false; if (i->checksum_job && !PULL_JOB_IS_COMPLETE(i->checksum_job)) @@ -380,6 +431,39 @@ static bool raw_pull_is_done(RawPull *i) { return true; } +static int raw_pull_rename_auxiliary_file( + RawPull *i, + const char *suffix, + char **temp_path, + char **path) { + + int r; + + assert(i); + assert(temp_path); + assert(suffix); + assert(path); + + /* Regenerate final name for this auxiliary file, we might know the etag of the raw file now, and we shoud + * incorporate it in the file name if we can */ + *path = mfree(*path); + r = raw_pull_determine_path(i, suffix, path); + if (r < 0) + return r; + + r = import_make_read_only(*temp_path); + if (r < 0) + return r; + + r = rename_noreplace(AT_FDCWD, *temp_path, AT_FDCWD, *path); + if (r < 0) + return log_error_errno(r, "Failed to rename file %s to %s: %m", *temp_path, *path); + + *temp_path = mfree(*temp_path); + + return 1; +} + static void raw_pull_job_on_finished(PullJob *j) { RawPull *i; int r; @@ -388,7 +472,10 @@ static void raw_pull_job_on_finished(PullJob *j) { assert(j->userdata); i = j->userdata; - if (j == i->settings_job) { + if (j == i->roothash_job) { + if (j->error != 0) + log_info_errno(j->error, "Root hash file could not be retrieved, proceeding without."); + } else if (j == i->settings_job) { if (j->error != 0) log_info_errno(j->error, "Settings file could not be retrieved, proceeding without."); } else if (j->error != 0) { @@ -413,16 +500,22 @@ static void raw_pull_job_on_finished(PullJob *j) { if (!raw_pull_is_done(i)) return; + if (i->roothash_job) + i->roothash_job->disk_fd = safe_close(i->roothash_job->disk_fd); if (i->settings_job) i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd); + r = raw_pull_determine_path(i, ".raw", &i->final_path); + if (r < 0) + goto finish; + if (!i->raw_job->etag_exists) { /* This is a new download, verify it, and move it into place */ assert(i->raw_job->disk_fd >= 0); raw_pull_report_progress(i, RAW_VERIFYING); - r = pull_verify(i->raw_job, i->settings_job, i->checksum_job, i->signature_job); + r = pull_verify(i->raw_job, i->roothash_job, i->settings_job, i->checksum_job, i->signature_job); if (r < 0) goto finish; @@ -446,24 +539,18 @@ static void raw_pull_job_on_finished(PullJob *j) { i->temp_path = mfree(i->temp_path); - if (i->settings_job && - i->settings_job->error == 0 && - !i->settings_job->etag_exists) { - - assert(i->settings_temp_path); - assert(i->settings_path); - - r = import_make_read_only(i->settings_temp_path); + if (i->roothash_job && + i->roothash_job->error == 0) { + r = raw_pull_rename_auxiliary_file(i, ".roothash", &i->roothash_temp_path, &i->roothash_path); if (r < 0) goto finish; + } - r = rename_noreplace(AT_FDCWD, i->settings_temp_path, AT_FDCWD, i->settings_path); - if (r < 0) { - log_error_errno(r, "Failed to rename settings file: %m"); + if (i->settings_job && + i->settings_job->error == 0) { + r = raw_pull_rename_auxiliary_file(i, ".nspawn", &i->settings_temp_path, &i->settings_path); + if (r < 0) goto finish; - } - - i->settings_temp_path = mfree(i->settings_temp_path); } } @@ -482,6 +569,35 @@ finish: sd_event_exit(i->event, r); } +static int raw_pull_job_on_open_disk_generic( + RawPull *i, + PullJob *j, + const char *extra, + char **temp_path) { + + _cleanup_free_ char *p = NULL; + int r; + + assert(i); + assert(j); + assert(extra); + assert(temp_path); + + if (!*temp_path) { + r = tempfn_random_child(i->image_root, extra, temp_path); + if (r < 0) + return log_oom(); + } + + (void) mkdir_parents_label(*temp_path, 0700); + + j->disk_fd = open(*temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); + if (j->disk_fd < 0) + return log_error_errno(errno, "Failed to create %s: %m", *temp_path); + + return 0; +} + static int raw_pull_job_on_open_disk_raw(PullJob *j) { RawPull *i; int r; @@ -491,57 +607,40 @@ static int raw_pull_job_on_open_disk_raw(PullJob *j) { i = j->userdata; assert(i->raw_job == j); - assert(!i->final_path); - assert(!i->temp_path); - r = pull_make_path(j->url, j->etag, i->image_root, ".raw-", ".raw", &i->final_path); + r = raw_pull_job_on_open_disk_generic(i, j, "raw", &i->temp_path); if (r < 0) - return log_oom(); - - r = tempfn_random(i->final_path, NULL, &i->temp_path); - if (r < 0) - return log_oom(); - - (void) mkdir_parents_label(i->temp_path, 0700); - - j->disk_fd = open(i->temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (j->disk_fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", i->temp_path); + return r; r = chattr_fd(j->disk_fd, FS_NOCOW_FL, FS_NOCOW_FL); if (r < 0) - log_warning_errno(r, "Failed to set file attributes on %s: %m", i->temp_path); + log_warning_errno(r, "Failed to set file attributes on %s, ignoring: %m", i->temp_path); return 0; } +static int raw_pull_job_on_open_disk_roothash(PullJob *j) { + RawPull *i; + + assert(j); + assert(j->userdata); + + i = j->userdata; + assert(i->roothash_job == j); + + return raw_pull_job_on_open_disk_generic(i, j, "roothash", &i->roothash_temp_path); +} + static int raw_pull_job_on_open_disk_settings(PullJob *j) { RawPull *i; - int r; assert(j); assert(j->userdata); i = j->userdata; assert(i->settings_job == j); - assert(!i->settings_path); - assert(!i->settings_temp_path); - r = pull_make_path(j->url, j->etag, i->image_root, ".settings-", NULL, &i->settings_path); - if (r < 0) - return log_oom(); - - r = tempfn_random(i->settings_path, NULL, &i->settings_temp_path); - if (r < 0) - return log_oom(); - - mkdir_parents_label(i->settings_temp_path, 0700); - - j->disk_fd = open(i->settings_temp_path, O_RDWR|O_CREAT|O_EXCL|O_NOCTTY|O_CLOEXEC, 0664); - if (j->disk_fd < 0) - return log_error_errno(errno, "Failed to create %s: %m", i->settings_temp_path); - - return 0; + return raw_pull_job_on_open_disk_generic(i, j, "settings", &i->settings_temp_path); } static void raw_pull_job_on_progress(PullJob *j) { @@ -561,7 +660,8 @@ int raw_pull_start( const char *local, bool force_local, ImportVerify verify, - bool settings) { + bool settings, + bool roothash) { int r; @@ -585,6 +685,7 @@ int raw_pull_start( i->force_local = force_local; i->verify = verify; i->settings = settings; + i->roothash = roothash; /* Queue job for the image itself */ r = pull_job_new(&i->raw_job, url, i->glue, i); @@ -601,18 +702,24 @@ int raw_pull_start( if (r < 0) return r; + if (roothash) { + r = pull_make_auxiliary_job(&i->roothash_job, url, raw_strip_suffixes, ".roothash", i->glue, raw_pull_job_on_finished, i); + if (r < 0) + return r; + + i->roothash_job->on_open_disk = raw_pull_job_on_open_disk_roothash; + i->roothash_job->on_progress = raw_pull_job_on_progress; + i->roothash_job->calc_checksum = verify != IMPORT_VERIFY_NO; + } + if (settings) { - r = pull_make_settings_job(&i->settings_job, url, i->glue, raw_pull_job_on_finished, i); + r = pull_make_auxiliary_job(&i->settings_job, url, raw_strip_suffixes, ".nspawn", i->glue, raw_pull_job_on_finished, i); if (r < 0) return r; i->settings_job->on_open_disk = raw_pull_job_on_open_disk_settings; i->settings_job->on_progress = raw_pull_job_on_progress; i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO; - - r = pull_find_old_etags(i->settings_job->url, i->image_root, DT_REG, ".settings-", NULL, &i->settings_job->old_etags); - if (r < 0) - return r; } r = pull_make_verification_jobs(&i->checksum_job, &i->signature_job, verify, url, i->glue, raw_pull_job_on_finished, i); @@ -623,6 +730,12 @@ int raw_pull_start( if (r < 0) return r; + if (i->roothash_job) { + r = pull_job_begin(i->roothash_job); + if (r < 0) + return r; + } + if (i->settings_job) { r = pull_job_begin(i->settings_job); if (r < 0) diff --git a/src/import/pull-raw.h b/src/import/pull-raw.h index 8f6d16eb3a..6954d98994 100644 --- a/src/import/pull-raw.h +++ b/src/import/pull-raw.h @@ -33,4 +33,4 @@ RawPull* raw_pull_unref(RawPull *pull); DEFINE_TRIVIAL_CLEANUP_FUNC(RawPull*, raw_pull_unref); -int raw_pull_start(RawPull *pull, const char *url, const char *local, bool force_local, ImportVerify verify, bool settings); +int raw_pull_start(RawPull *pull, const char *url, const char *local, bool force_local, ImportVerify verify, bool settings, bool roothash); diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index 68e2397b02..375ee778e2 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -215,6 +215,24 @@ static void tar_pull_report_progress(TarPull *i, TarProgress p) { log_debug("Combined progress %u%%", percent); } +static int tar_pull_determine_path(TarPull *i, const char *suffix, char **field) { + int r; + + assert(i); + assert(field); + + if (*field) + return 0; + + assert(i->tar_job); + + r = pull_make_path(i->tar_job->url, i->tar_job->etag, i->image_root, ".tar-", suffix, field); + if (r < 0) + return log_oom(); + + return 1; +} + static int tar_pull_make_local_copy(TarPull *i) { int r; @@ -224,12 +242,6 @@ static int tar_pull_make_local_copy(TarPull *i) { if (!i->local) return 0; - if (!i->final_path) { - r = pull_make_path(i->tar_job->url, i->tar_job->etag, i->image_root, ".tar-", NULL, &i->final_path); - if (r < 0) - return log_oom(); - } - r = pull_make_local_copy(i->final_path, i->image_root, i->local, i->force_local); if (r < 0) return r; @@ -238,11 +250,9 @@ static int tar_pull_make_local_copy(TarPull *i) { const char *local_settings; assert(i->settings_job); - if (!i->settings_path) { - r = pull_make_path(i->settings_job->url, i->settings_job->etag, i->image_root, ".settings-", NULL, &i->settings_path); - if (r < 0) - return log_oom(); - } + r = tar_pull_determine_path(i, ".nspawn", &i->settings_path); + if (r < 0) + return r; local_settings = strjoina(i->image_root, "/", i->local, ".nspawn"); @@ -311,6 +321,10 @@ static void tar_pull_job_on_finished(PullJob *j) { if (i->settings_job) i->settings_job->disk_fd = safe_close(i->settings_job->disk_fd); + r = tar_pull_determine_path(i, NULL, &i->final_path); + if (r < 0) + goto finish; + if (i->tar_pid > 0) { r = wait_for_terminate_and_warn("tar", i->tar_pid, true); i->tar_pid = 0; @@ -327,7 +341,7 @@ static void tar_pull_job_on_finished(PullJob *j) { tar_pull_report_progress(i, TAR_VERIFYING); - r = pull_verify(i->tar_job, i->settings_job, i->checksum_job, i->signature_job); + r = pull_verify(i->tar_job, NULL, i->settings_job, i->checksum_job, i->signature_job); if (r < 0) goto finish; @@ -346,16 +360,18 @@ static void tar_pull_job_on_finished(PullJob *j) { i->temp_path = mfree(i->temp_path); if (i->settings_job && - i->settings_job->error == 0 && - !i->settings_job->etag_exists) { + i->settings_job->error == 0) { assert(i->settings_temp_path); assert(i->settings_path); - /* Also move the settings file into place, if - * it exist. Note that we do so only if we - * also moved the tar file in place, to keep - * things strictly in sync. */ + /* Also move the settings file into place, if it exist. Note that we do so only if we also + * moved the tar file in place, to keep things strictly in sync. */ + + i->settings_path = mfree(i->settings_path); + r = tar_pull_determine_path(i, ".nspawn", &i->settings_path); + if (r < 0) + goto finish; r = import_make_read_only(i->settings_temp_path); if (r < 0) @@ -395,17 +411,13 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { i = j->userdata; assert(i->tar_job == j); - assert(!i->final_path); - assert(!i->temp_path); assert(i->tar_pid <= 0); - r = pull_make_path(j->url, j->etag, i->image_root, ".tar-", NULL, &i->final_path); - if (r < 0) - return log_oom(); - - r = tempfn_random(i->final_path, NULL, &i->temp_path); - if (r < 0) - return log_oom(); + if (!i->temp_path) { + r = tempfn_random_child(i->image_root, "tar", &i->temp_path); + if (r < 0) + return log_oom(); + } mkdir_parents_label(i->temp_path, 0700); @@ -434,16 +446,12 @@ static int tar_pull_job_on_open_disk_settings(PullJob *j) { i = j->userdata; assert(i->settings_job == j); - assert(!i->settings_path); - assert(!i->settings_temp_path); - r = pull_make_path(j->url, j->etag, i->image_root, ".settings-", NULL, &i->settings_path); - if (r < 0) - return log_oom(); - - r = tempfn_random(i->settings_path, NULL, &i->settings_temp_path); - if (r < 0) - return log_oom(); + if (!i->settings_temp_path) { + r = tempfn_random_child(i->image_root, "settings", &i->settings_temp_path); + if (r < 0) + return log_oom(); + } mkdir_parents_label(i->settings_temp_path, 0700); @@ -513,17 +521,13 @@ int tar_pull_start( /* Set up download job for the settings file (.nspawn) */ if (settings) { - r = pull_make_settings_job(&i->settings_job, url, i->glue, tar_pull_job_on_finished, i); + r = pull_make_auxiliary_job(&i->settings_job, url, tar_strip_suffixes, ".nspawn", i->glue, tar_pull_job_on_finished, i); if (r < 0) return r; i->settings_job->on_open_disk = tar_pull_job_on_open_disk_settings; i->settings_job->on_progress = tar_pull_job_on_progress; i->settings_job->calc_checksum = verify != IMPORT_VERIFY_NO; - - r = pull_find_old_etags(i->settings_job->url, i->image_root, DT_REG, ".settings-", NULL, &i->settings_job->old_etags); - if (r < 0) - return r; } /* Set up download of checksum/signature files */ diff --git a/src/import/pull.c b/src/import/pull.c index 53b1211965..4af5d9c853 100644 --- a/src/import/pull.c +++ b/src/import/pull.c @@ -37,6 +37,7 @@ static bool arg_force = false; static const char *arg_image_root = "/var/lib/machines"; static ImportVerify arg_verify = IMPORT_VERIFY_SIGNATURE; static bool arg_settings = true; +static bool arg_roothash = true; static int interrupt_signal_handler(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { log_notice("Transfer aborted."); @@ -204,7 +205,7 @@ static int pull_raw(int argc, char *argv[], void *userdata) { if (r < 0) return log_error_errno(r, "Failed to allocate puller: %m"); - r = raw_pull_start(pull, url, local, arg_force, arg_verify, arg_settings); + r = raw_pull_start(pull, url, local, arg_force, arg_verify, arg_settings, arg_roothash); if (r < 0) return log_error_errno(r, "Failed to pull image: %m"); @@ -226,6 +227,7 @@ static int help(int argc, char *argv[], void *userdata) { " --verify=MODE Verify downloaded image, one of: 'no',\n" " 'checksum', 'signature'\n" " --settings=BOOL Download settings file with image\n" + " --roothash=BOOL Download root hash file with image\n" " --image-root=PATH Image root directory\n\n" "Commands:\n" " tar URL [NAME] Download a TAR image\n" @@ -243,6 +245,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_IMAGE_ROOT, ARG_VERIFY, ARG_SETTINGS, + ARG_ROOTHASH, }; static const struct option options[] = { @@ -252,6 +255,7 @@ static int parse_argv(int argc, char *argv[]) { { "image-root", required_argument, NULL, ARG_IMAGE_ROOT }, { "verify", required_argument, NULL, ARG_VERIFY }, { "settings", required_argument, NULL, ARG_SETTINGS }, + { "roothash", required_argument, NULL, ARG_ROOTHASH }, {} }; @@ -295,6 +299,14 @@ static int parse_argv(int argc, char *argv[]) { arg_settings = r; break; + case ARG_ROOTHASH: + r = parse_boolean(optarg); + if (r < 0) + return log_error_errno(r, "Failed to parse --roothash= parameter '%s'", optarg); + + arg_roothash = r; + break; + case '?': return -EINVAL; From fc2288f0be9384789c18259b66082c5fb395407f Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 20 Dec 2016 17:50:42 +0100 Subject: [PATCH 38/46] machinectl: minor indentation fix --- src/machine/machinectl.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 36c2607ba9..efb20c3622 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -2670,9 +2670,9 @@ static int help(int argc, char *argv[], void *userdata) { " -o --output=STRING Change journal output mode (short,\n" " short-monotonic, verbose, export, json,\n" " json-pretty, json-sse, cat)\n" - " --verify=MODE Verification mode for downloaded images (no,\n" + " --verify=MODE Verification mode for downloaded images (no,\n" " checksum, signature)\n" - " --force Download image even if already exists\n\n" + " --force Download image even if already exists\n\n" "Machine Commands:\n" " list List running VMs and containers\n" " status NAME... Show VM/container details\n" From 436e916eae03c97837425e157fbf6bb8ce5d5a53 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 20 Dec 2016 19:09:27 +0100 Subject: [PATCH 39/46] util-lib: rework path_check_fstype() and path_is_temporary_fs() to use O_PATH Also, add tests to make sure this actually works as intended. --- src/basic/stat-util.c | 4 ++-- src/test/test-stat-util.c | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/src/basic/stat-util.c b/src/basic/stat-util.c index ac5db71022..7e1914aa14 100644 --- a/src/basic/stat-util.c +++ b/src/basic/stat-util.c @@ -204,7 +204,7 @@ int fd_check_fstype(int fd, statfs_f_type_t magic_value) { int path_check_fstype(const char *path, statfs_f_type_t magic_value) { _cleanup_close_ int fd = -1; - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH); if (fd < 0) return -errno; @@ -228,7 +228,7 @@ int fd_is_temporary_fs(int fd) { int path_is_temporary_fs(const char *path) { _cleanup_close_ int fd = -1; - fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY); + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_PATH); if (fd < 0) return -errno; diff --git a/src/test/test-stat-util.c b/src/test/test-stat-util.c index 10fc4af79f..a48dca99e1 100644 --- a/src/test/test-stat-util.c +++ b/src/test/test-stat-util.c @@ -18,12 +18,14 @@ ***/ #include +#include #include #include "alloc-util.h" #include "fd-util.h" #include "fileio.h" #include "macro.h" +#include "missing.h" #include "stat-util.h" static void test_files_same(void) { @@ -66,10 +68,27 @@ static void test_path_is_os_tree(void) { assert_se(path_is_os_tree("/idontexist") == -ENOENT); } +static void test_path_check_fstype(void) { + assert_se(path_check_fstype("/run", TMPFS_MAGIC) > 0); + assert_se(path_check_fstype("/run", BTRFS_SUPER_MAGIC) == 0); + assert_se(path_check_fstype("/proc", PROC_SUPER_MAGIC) > 0); + assert_se(path_check_fstype("/proc", BTRFS_SUPER_MAGIC) == 0); + assert_se(path_check_fstype("/proc", BTRFS_SUPER_MAGIC) == 0); + assert_se(path_check_fstype("/i-dont-exist", BTRFS_SUPER_MAGIC) == -ENOENT); +} + +static void test_path_is_temporary_fs(void) { + assert_se(path_is_temporary_fs("/run") > 0); + assert_se(path_is_temporary_fs("/proc") == 0); + assert_se(path_is_temporary_fs("/i-dont-exist") == -ENOENT); +} + int main(int argc, char *argv[]) { test_files_same(); test_is_symlink(); test_path_is_os_tree(); + test_path_check_fstype(); + test_path_is_temporary_fs(); return 0; } From 08422e63b2d9ec507ac170ca9c15f50ec37ecca6 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Thu, 15 Dec 2016 16:00:39 +0100 Subject: [PATCH 40/46] update TODO --- TODO | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/TODO b/TODO index 89e0874903..39e4d8bcc4 100644 --- a/TODO +++ b/TODO @@ -24,6 +24,19 @@ Janitorial Clean-ups: Features: +* Maybe add a small tool invoked early at boot, that adds in or resizes + partitions automatically, to be used when the media used is actually larger + than the image written onto it is. + +* change the dependency Set* objects in Unit structures to become Hashmap*, and + then store a bit mask who created a specific dependency: the source unit via + fragment configuration, the destination unit via fragment configuration, or + the source unit via udev rules (in case of .device units), or any combination + there of. This information can then be used to flush out old udev-created + dependencies when the udev properties change, and eventually to implement a + "systemctl refresh" operation for reloading the configuration of individual + units without reloading the whole set. + * Add ExecMonitor= setting. May be used multiple times. Forks off a process in the service cgroup, which is supposed to monitor the service, and when it exits the service is considered failed by its monitor. @@ -55,8 +68,6 @@ Features: partition, that is mounted to / and is writable, and where the actual root's /usr is mounted into. -* add dm-verity boots, and in nspawn (libcryptsetup knows this, should be relatively straight-forward) - * machined: add apis to query /etc/machine-info data of a container * .mount and .swap units: add Format=yes|no option that formats the partition before mounting/enabling it, implicitly @@ -295,7 +306,6 @@ Features: * support empty /etc boots nicely: - nspawn/gpt-generator: introduce new gpt partition type for /usr - - fstab-generator: support systemd.volatile=yes|no|state on the kernel cmdline, too, similar to nspawn's --volatile= * generator that automatically discovers btrfs subvolumes, identifies their purpose based on some xattr on them. @@ -311,7 +321,6 @@ Features: * add bus api to query unit file's X fields. * gpt-auto-generator: - - Support LUKS for root devices - Define new partition type for encrypted swap? Support probed LUKS for encrypted swap? - Make /home automount rather than mount? From 33fc1800d692ab84eeb9e50342ff75204c335319 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 20 Dec 2016 21:15:17 +0100 Subject: [PATCH 41/46] man: improve ShowStatus=/systemd.show_status=/--show-status= documentation Fixes: #4928 --- man/systemd-system.conf.xml | 10 ++++------ man/systemd.xml | 30 +++++++++++++++++------------- 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/man/systemd-system.conf.xml b/man/systemd-system.conf.xml index e4e81f7f2e..336c7a5fd1 100644 --- a/man/systemd-system.conf.xml +++ b/man/systemd-system.conf.xml @@ -97,12 +97,10 @@ DefaultStandardOutput=journal DefaultStandardError=inherit - Configures various parameters of basic manager - operation. These options may be overridden by the respective - command line arguments. See - systemd1 - for details about these command line - arguments. + Configures various parameters of basic manager operation. These options may be overridden by + the respective process and kernel command line arguments. See + systemd1 for + details. diff --git a/man/systemd.xml b/man/systemd.xml index 50398e6259..2964fecd25 100644 --- a/man/systemd.xml +++ b/man/systemd.xml @@ -183,10 +183,14 @@ - Show terse service status information while - booting. This switch has no effect when run as user instance. - Takes a boolean argument which may be omitted which is - interpreted as . + Takes a boolean argument or the special value auto. If on, terse unit + status information is shown on the console during boot-up and shutdown. If off, no such status information is + shown. If set to auto behavior is similar to off, except that it is automatically switched + to on, as soon as the first unit failure or significant boot delay is encountered. This switch has no effect + when invoked as user instance. If specified, overrides both the kernel command line setting + systemd.show_status= (see below) and the configuration file option + , see + systemd-system.conf5. @@ -953,15 +957,15 @@ systemd.show_status= - Takes a boolean argument or the constant - auto. If , the - systemd manager (PID 1) shows terse service status updates on - the console during bootup. auto behaves - like until a service fails or there is - a significant delay in boot. Defaults to - , unless is passed - as kernel command line option, in which case it defaults to - auto. + Takes a boolean argument or the constant auto. If , + the systemd manager (PID 1) shows terse service status updates on the console during bootup. + auto behaves like until a unit fails or there is a significant + delay in boot. Defaults to , unless is passed as kernel command + line option, in which case it defaults to auto. If specified overrides the system manager + configuration file option , see + systemd-system.conf5. However, + the process command line option takes precedence over both this kernel command + line option and the configuration file option. From e4b45b32e59d7481fbbb0756d012bba6556223a8 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Tue, 20 Dec 2016 21:30:05 +0100 Subject: [PATCH 42/46] man: document that services need at least one of ExecStart= or ExecStop= Fixes: #4521 --- man/systemd.service.xml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/man/systemd.service.xml b/man/systemd.service.xml index 684029bdb4..b707961a59 100644 --- a/man/systemd.service.xml +++ b/man/systemd.service.xml @@ -284,7 +284,9 @@ providing multiple command lines in the same directive, or alternatively, this directive may be specified more than once with the same effect. If the empty string is assigned to this option, the list of commands to start is reset, prior assignments of this option will have no effect. If no ExecStart= is - specified, then the service must have RemainAfterExit=yes set. + specified, then the service must have RemainAfterExit=yes and at least one + ExecStop= line set. (Services lacking both ExecStart= and + ExecStop= are not valid.) For each of the specified commands, the first argument must be an absolute path to an executable. Optionally, if this file name is prefixed with @, the second token will be From 8ccf7e9e96ac89f02424c065e39043b5165f8bca Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 21 Dec 2016 00:39:50 +0100 Subject: [PATCH 43/46] nspawn: don't complain when we can't fix the timezone of read-only containers There's nothing we can do about it, hence don't complain. --- src/nspawn/nspawn.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 224d30fca6..5df00df6ed 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1288,15 +1288,18 @@ static int setup_timezone(const char *dest) { return 0; } - r = unlink(where); - if (r < 0 && errno != ENOENT) { - log_error_errno(errno, "Failed to remove existing timezone info %s in container: %m", where); + if (unlink(where) < 0 && errno != ENOENT) { + log_full_errno(IN_SET(errno, EROFS, EACCES, EPERM) ? LOG_DEBUG : LOG_WARNING, /* Don't complain on read-only images */ + errno, + "Failed to remove existing timezone info %s in container, ignoring: %m", where); return 0; } what = strjoina("../usr/share/zoneinfo/", z); if (symlink(what, where) < 0) { - log_error_errno(errno, "Failed to correct timezone of container: %m"); + log_full_errno(IN_SET(errno, EROFS, EACCES, EPERM) ? LOG_DEBUG : LOG_WARNING, + errno, + "Failed to correct timezone of container, ignoring: %m"); return 0; } From 87447ae4598d8de5aad557ec746e27e8271a60a7 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 21 Dec 2016 00:44:00 +0100 Subject: [PATCH 44/46] nspawn: tweaks to /etc/resolv.conf management Handle properly if /etc is a symlink (i.e. make sure we don't follow the symlink outside the image). Also follow /etc/resolv.conf if it is a symlink, and use the resolved path when creating a mount point and mounting (as both of these operations follow symlinks and rally shouldn't). Handle more types of read-only errors as debug-level issues. --- src/nspawn/nspawn.c | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index 5df00df6ed..6246c6d6fa 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -1311,31 +1311,43 @@ static int setup_timezone(const char *dest) { } static int setup_resolv_conf(const char *dest) { - const char *where = NULL; - int r; + _cleanup_free_ char *resolved = NULL, *etc = NULL; + const char *where; + int r, found; assert(dest); if (arg_private_network) return 0; - /* Fix resolv.conf, if possible */ - where = prefix_roota(dest, "/etc/resolv.conf"); + r = chase_symlinks("/etc", dest, CHASE_PREFIX_ROOT, &etc); + if (r < 0) { + log_warning_errno(r, "Failed to resolve /etc path in container, ignoring: %m"); + return 0; + } + + where = strjoina(etc, "/resolv.conf"); + found = chase_symlinks(where, dest, CHASE_NONEXISTENT, &resolved); + if (found < 0) { + log_warning_errno(found, "Failed to resolve /etc/resolv.conf path in container, ignoring: %m"); + return 0; + } if (access("/run/systemd/resolve/resolv.conf", F_OK) >= 0 && - access("/usr/lib/systemd/resolv.conf", F_OK) >= 0) { + access("/usr/lib/systemd/resolv.conf", F_OK) >= 0) { + /* resolved is enabled on the host. In this, case bind mount its static resolv.conf file into the * container, so that the container can use the host's resolver. Given that network namespacing is * disabled it's only natural of the container also uses the host's resolver. It also has the big * advantage that the container will be able to follow the host's DNS server configuration changes * transparently. */ - (void) touch(where); + if (found == 0) /* missing? */ + (void) touch(resolved); - r = mount_verbose(LOG_WARNING, "/usr/lib/systemd/resolv.conf", where, NULL, MS_BIND, NULL); + r = mount_verbose(LOG_DEBUG, "/usr/lib/systemd/resolv.conf", resolved, NULL, MS_BIND, NULL); if (r >= 0) - return mount_verbose(LOG_ERR, NULL, where, NULL, - MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL); + return mount_verbose(LOG_ERR, NULL, resolved, NULL, MS_BIND|MS_REMOUNT|MS_RDONLY|MS_NOSUID|MS_NODEV, NULL); } /* If that didn't work, let's copy the file */ @@ -1346,7 +1358,7 @@ static int setup_resolv_conf(const char *dest) { * * If the disk image is read-only, there's also no point in complaining. */ - log_full_errno(IN_SET(r, -ELOOP, -EROFS) ? LOG_DEBUG : LOG_WARNING, r, + log_full_errno(IN_SET(r, -ELOOP, -EROFS, -EACCES, -EPERM) ? LOG_DEBUG : LOG_WARNING, r, "Failed to copy /etc/resolv.conf to %s, ignoring: %m", where); return 0; } From 34b86909b721e552eff90ca20d2ade754ebe3e04 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 21 Dec 2016 00:58:08 +0100 Subject: [PATCH 45/46] test: make sure test-execute can run in a read-only environment This makes sure we can invoke it safely from out "mkosi.build" script when mkosi is invoked for a read-only image. --- src/test/test-execute.c | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/test/test-execute.c b/src/test/test-execute.c index 4670458ffb..3254f0f231 100644 --- a/src/test/test-execute.c +++ b/src/test/test-execute.c @@ -33,6 +33,7 @@ #ifdef HAVE_SECCOMP #include "seccomp-util.h" #endif +#include "stat-util.h" #include "test-helper.h" #include "unit.h" #include "util.h" @@ -188,15 +189,27 @@ static void test_exec_protectkernelmodules(Manager *m) { } static void test_exec_readonlypaths(Manager *m) { + + if (path_is_read_only_fs("/var") > 0) + return; + test(m, "exec-readonlypaths.service", 0, CLD_EXITED); test(m, "exec-readonlypaths-mount-propagation.service", 0, CLD_EXITED); } static void test_exec_readwritepaths(Manager *m) { + + if (path_is_read_only_fs("/") > 0) + return; + test(m, "exec-readwritepaths-mount-propagation.service", 0, CLD_EXITED); } static void test_exec_inaccessiblepaths(Manager *m) { + + if (path_is_read_only_fs("/") > 0) + return; + test(m, "exec-inaccessiblepaths-mount-propagation.service", 0, CLD_EXITED); } From bafbac4e85a5eefd4b57a5cd0eb61885fb60edc9 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Wed, 21 Dec 2016 12:24:11 +0100 Subject: [PATCH 46/46] machined: when renaming/removing/cloning images, always take care of .roothash file too Since nspawn looks for them, importd now downloads them, and mkosi generates them, let's make sure they also processed correctly on all machined operations. --- src/shared/machine-image.c | 49 ++++++++++++++++++++++++++++++++------ 1 file changed, 42 insertions(+), 7 deletions(-) diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index 712aff65b9..7bc5c0a128 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -99,6 +99,16 @@ static char **image_settings_path(Image *image) { return ret; } +static char *image_roothash_path(Image *image) { + const char *fn; + + assert(image); + + fn = strjoina(image->name, ".roothash"); + + return file_in_same_dir(image->path, fn); +} + static int image_new( ImageType t, const char *pretty, @@ -397,6 +407,7 @@ void image_hashmap_free(Hashmap *map) { int image_remove(Image *i) { _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT; _cleanup_strv_free_ char **settings = NULL; + _cleanup_free_ char *roothash = NULL; char **j; int r; @@ -409,6 +420,10 @@ int image_remove(Image *i) { if (!settings) return -ENOMEM; + roothash = image_roothash_path(i); + if (!roothash) + return -ENOMEM; + /* Make sure we don't interfere with a running nspawn */ r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); if (r < 0) @@ -445,14 +460,17 @@ int image_remove(Image *i) { log_debug_errno(errno, "Failed to unlink %s, ignoring: %m", *j); } + if (unlink(roothash) < 0 && errno != ENOENT) + log_debug_errno(errno, "Failed to unlink %s, ignoring: %m", roothash); + return 0; } -static int rename_settings_file(const char *path, const char *new_name) { +static int rename_auxiliary_file(const char *path, const char *new_name, const char *suffix) { _cleanup_free_ char *rs = NULL; const char *fn; - fn = strjoina(new_name, ".nspawn"); + fn = strjoina(new_name, suffix); rs = file_in_same_dir(path, fn); if (!rs) @@ -463,7 +481,7 @@ static int rename_settings_file(const char *path, const char *new_name) { int image_rename(Image *i, const char *new_name) { _cleanup_release_lock_file_ LockFile global_lock = LOCK_FILE_INIT, local_lock = LOCK_FILE_INIT, name_lock = LOCK_FILE_INIT; - _cleanup_free_ char *new_path = NULL, *nn = NULL; + _cleanup_free_ char *new_path = NULL, *nn = NULL, *roothash = NULL; _cleanup_strv_free_ char **settings = NULL; unsigned file_attr = 0; char **j; @@ -481,6 +499,10 @@ int image_rename(Image *i, const char *new_name) { if (!settings) return -ENOMEM; + roothash = image_roothash_path(i); + if (!roothash) + return -ENOMEM; + /* Make sure we don't interfere with a running nspawn */ r = image_path_lock(i->path, LOCK_EX|LOCK_NB, &global_lock, &local_lock); if (r < 0) @@ -550,19 +572,23 @@ int image_rename(Image *i, const char *new_name) { nn = NULL; STRV_FOREACH(j, settings) { - r = rename_settings_file(*j, new_name); + r = rename_auxiliary_file(*j, new_name, ".nspawn"); if (r < 0 && r != -ENOENT) log_debug_errno(r, "Failed to rename settings file %s, ignoring: %m", *j); } + r = rename_auxiliary_file(roothash, new_name, ".roothash"); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to rename roothash file %s, ignoring: %m", roothash); + return 0; } -static int clone_settings_file(const char *path, const char *new_name) { +static int clone_auxiliary_file(const char *path, const char *new_name, const char *suffix) { _cleanup_free_ char *rs = NULL; const char *fn; - fn = strjoina(new_name, ".nspawn"); + fn = strjoina(new_name, suffix); rs = file_in_same_dir(path, fn); if (!rs) @@ -574,6 +600,7 @@ static int clone_settings_file(const char *path, const char *new_name) { int image_clone(Image *i, const char *new_name, bool read_only) { _cleanup_release_lock_file_ LockFile name_lock = LOCK_FILE_INIT; _cleanup_strv_free_ char **settings = NULL; + _cleanup_free_ char *roothash = NULL; const char *new_path; char **j; int r; @@ -587,6 +614,10 @@ int image_clone(Image *i, const char *new_name, bool read_only) { if (!settings) return -ENOMEM; + roothash = image_roothash_path(i); + if (!roothash) + return -ENOMEM; + /* Make sure nobody takes the new name, between the time we * checked it is currently unused in all search paths, and the * time we take possession of it */ @@ -636,11 +667,15 @@ int image_clone(Image *i, const char *new_name, bool read_only) { return r; STRV_FOREACH(j, settings) { - r = clone_settings_file(*j, new_name); + r = clone_auxiliary_file(*j, new_name, ".nspawn"); if (r < 0 && r != -ENOENT) log_debug_errno(r, "Failed to clone settings %s, ignoring: %m", *j); } + r = clone_auxiliary_file(roothash, new_name, ".roothash"); + if (r < 0 && r != -ENOENT) + log_debug_errno(r, "Failed to clone root hash file %s, ignoring: %m", roothash); + return 0; }