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