From d7ab065f9efc8b1d25c3ff761e16b5aa26145ee7 Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Tue, 12 May 2015 00:57:30 +0000 Subject: WIP: Add systemctl upgrade-root --- src/core/dbus-manager.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++ src/core/main.c | 32 ++++++++++++++++++++--- src/core/manager.h | 1 + src/systemctl/systemctl.c | 63 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 157 insertions(+), 4 deletions(-) diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c index d8b39bdf5f..5da53768d2 100644 --- a/src/core/dbus-manager.c +++ b/src/core/dbus-manager.c @@ -1354,6 +1354,70 @@ static int method_switch_root(sd_bus_message *message, void *userdata, sd_bus_er return sd_bus_reply_method_return(message, NULL); } +static int method_upgrade_root(sd_bus_message *message, void *userdata, sd_bus_error *error) { + char *ri = NULL, *rt = NULL; + const char *root, *init; + Manager *m = userdata; + int r; + + assert(message); + assert(m); + + r = mac_selinux_access_check(message, "reboot", error); + if (r < 0) + return r; + + if (m->running_as != MANAGER_SYSTEM) + return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Root switching is only supported by system manager."); + + r = sd_bus_message_read(message, "ss", &root, &init); + 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); + + /* 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); + } else { + _cleanup_free_ char *p = NULL; + + if (!path_is_absolute(init)) + return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "Invalid init path %s", init); + + p = strappend(root, init); + if (!p) + return -ENOMEM; + + 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); + } + + rt = strdup(root); + if (!rt) + return -ENOMEM; + + if (!isempty(init)) { + ri = strdup(init); + if (!ri) { + free(rt); + return -ENOMEM; + } + } + + free(m->switch_root); + m->switch_root = rt; + + free(m->switch_root_init); + m->switch_root_init = ri; + + m->exit_code = MANAGER_UPGRADE_ROOT; + + return sd_bus_reply_method_return(message, NULL); +} + static int method_set_environment(sd_bus_message *message, void *userdata, sd_bus_error *error) { _cleanup_strv_free_ char **plus = NULL; Manager *m = userdata; @@ -1990,6 +2054,7 @@ const sd_bus_vtable bus_manager_vtable[] = { SD_BUS_METHOD("Halt", NULL, NULL, method_halt, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), SD_BUS_METHOD("KExec", NULL, NULL, method_kexec, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), SD_BUS_METHOD("SwitchRoot", "ss", NULL, method_switch_root, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), + SD_BUS_METHOD("UpgradeRoot", "ss", NULL, method_upgrade_root, SD_BUS_VTABLE_CAPABILITY(CAP_SYS_BOOT)), SD_BUS_METHOD("SetEnvironment", "as", NULL, method_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("UnsetEnvironment", "as", NULL, method_unset_environment, SD_BUS_VTABLE_UNPRIVILEGED), SD_BUS_METHOD("UnsetAndSetEnvironment", "asas", NULL, method_unset_and_set_environment, SD_BUS_VTABLE_UNPRIVILEGED), diff --git a/src/core/main.c b/src/core/main.c index c39815b106..c27aff9f00 100644 --- a/src/core/main.c +++ b/src/core/main.c @@ -1226,6 +1226,7 @@ int main(int argc, char *argv[]) { bool queue_default_job = false; bool empty_etc = false; char *switch_root_dir = NULL, *switch_root_init = NULL; + bool keep_processes = false; struct rlimit saved_rlimit_nofile = RLIMIT_MAKE_CONST(0); const char *error_message = NULL; @@ -1799,6 +1800,22 @@ int main(int argc, char *argv[]) { log_notice("Switching root."); goto finish; + case MANAGER_UPGRADE_ROOT: + /* Steal the switch root parameters */ + switch_root_dir = m->switch_root; + switch_root_init = m->switch_root_init; + m->switch_root = m->switch_root_init = NULL; + + if (prepare_reexecute(m, &arg_serialization, &fds, true) < 0) { + error_message = "Failed to prepare for reexection"; + goto finish; + } + + reexecute = true; + keep_processes = true; + log_notice("Switching root."); + goto finish; + case MANAGER_REBOOT: case MANAGER_POWEROFF: case MANAGER_HALT: @@ -1862,7 +1879,7 @@ finish: if (saved_rlimit_nofile.rlim_cur > 0) setrlimit(RLIMIT_NOFILE, &saved_rlimit_nofile); - if (switch_root_dir) { + if (switch_root_dir && !keep_processes) { /* Kill all remaining processes from the * initrd, but don't wait for them, so that we * can handle the SIGCHLD for them after @@ -1873,18 +1890,25 @@ finish: r = switch_root(switch_root_dir, "/mnt", true, MS_MOVE); if (r < 0) log_error_errno(r, "Failed to switch root, trying to continue: %m"); + } else if (keep_processes) { + /* And switch root with MS_BIND, because we need to + * leave the old root around for the existing processes + * to run in */ + r = switch_root(switch_root_dir, "/mnt", false, MS_BIND); + if (r < 0) + log_error_errno(r, "Failed to upgrade root, trying to continue: %m"); } args_size = MAX(6, argc+1); args = newa(const char*, args_size); - if (!switch_root_init) { + if (!switch_root_init || keep_processes) { char sfd[DECIMAL_STR_MAX(int) + 1]; /* First try to spawn ourselves with the right * path, and with full serialization. We do * this only if the user didn't specify an - * explicit init to spawn. */ + * explicit init to spawn, or they ran upgrade-root */ assert(arg_serialization); assert(fds); @@ -1892,7 +1916,7 @@ finish: xsprintf(sfd, "%i", fileno(arg_serialization)); i = 0; - args[i++] = SYSTEMD_BINARY_PATH; + args[i++] = switch_root_init?: SYSTEMD_BINARY_PATH; if (switch_root_dir) args[i++] = "--switched-root"; args[i++] = arg_running_as == MANAGER_SYSTEM ? "--system" : "--user"; diff --git a/src/core/manager.h b/src/core/manager.h index 4ef869d14a..4c8b920515 100644 --- a/src/core/manager.h +++ b/src/core/manager.h @@ -58,6 +58,7 @@ typedef enum ManagerExitCode { MANAGER_HALT, MANAGER_KEXEC, MANAGER_SWITCH_ROOT, + MANAGER_UPGRADE_ROOT, _MANAGER_EXIT_CODE_MAX, _MANAGER_EXIT_CODE_INVALID = -1 } ManagerExitCode; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index f8e10a4710..3e9e1e964c 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -4992,6 +4992,67 @@ static int switch_root(sd_bus *bus, char **args) { return 0; } +static int upgrade_root(sd_bus *bus, char **args) { + _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; + _cleanup_free_ char *cmdline_init = NULL; + const char *root, *init; + unsigned l; + int r; + + l = strv_length(args); + if (l < 2 || l > 3) { + log_error("Wrong number of arguments."); + return -EINVAL; + } + + root = args[1]; + + if (l >= 3) + init = args[2]; + else { + r = parse_env_file("/proc/cmdline", WHITESPACE, + "init", &cmdline_init, + NULL); + if (r < 0) + log_debug_errno(r, "Failed to parse /proc/cmdline: %m"); + + init = cmdline_init; + } + + if (isempty(init)) + init = NULL; + + if (init) { + const char *root_systemd_path = NULL, *root_init_path = NULL; + + root_systemd_path = strjoina(root, "/" SYSTEMD_BINARY_PATH); + root_init_path = strjoina(root, "/", init); + + /* If the passed init is actually the same as the + * systemd binary, then let's suppress it. */ + if (files_same(root_init_path, root_systemd_path) > 0) + init = NULL; + } + + log_debug("Switching root - root: %s; init: %s", root, strna(init)); + + r = sd_bus_call_method( + bus, + "org.freedesktop.systemd1", + "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", + "UpgradeRoot", + &error, + NULL, + "ss", root, init); + if (r < 0) { + log_error("Failed to switch root: %s", bus_error_message(&error, r)); + return r; + } + + return 0; +} + static int set_environment(sd_bus *bus, char **args) { _cleanup_bus_error_free_ sd_bus_error error = SD_BUS_ERROR_NULL; _cleanup_bus_message_unref_ sd_bus_message *m = NULL; @@ -6126,6 +6187,7 @@ static void systemctl_help(void) { " kexec Shut down and reboot the system with kexec\n" " exit Request user instance exit\n" " switch-root ROOT [INIT] Change to a different root file system\n" + " upgrade-root ROOT [INIT] Upgrade to a new root file system\n" " suspend Suspend the system\n" " hibernate Hibernate the system\n" " hybrid-sleep Hibernate and suspend the system\n", @@ -7113,6 +7175,7 @@ static int systemctl_main(sd_bus *bus, int argc, char *argv[], int bus_error) { { "unmask", MORE, 2, enable_unit, NOBUS }, { "link", MORE, 2, enable_unit, NOBUS }, { "switch-root", MORE, 2, switch_root }, + { "upgrade-root", MORE, 2, upgrade_root }, { "list-dependencies", LESS, 2, list_dependencies }, { "set-default", EQUAL, 2, set_default, NOBUS }, { "get-default", EQUAL, 1, get_default, NOBUS }, -- cgit v1.2.1