summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Maw <richard.maw@codethink.co.uk>2015-05-12 00:57:30 +0000
committerRichard Maw <richard.maw@codethink.co.uk>2015-05-26 16:44:39 +0000
commitd7ab065f9efc8b1d25c3ff761e16b5aa26145ee7 (patch)
tree13a7ed2e3d255d4d15087ba244fe61e650824031
parent8cd552203950026977e896903a912b2bbf96a4c3 (diff)
downloadsystemd-baserock/richardmaw/wip-upgrade-root.tar.gz
WIP: Add systemctl upgrade-rootbaserock/richardmaw/wip-upgrade-root
-rw-r--r--src/core/dbus-manager.c65
-rw-r--r--src/core/main.c32
-rw-r--r--src/core/manager.h1
-rw-r--r--src/systemctl/systemctl.c63
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 },