chiark / gitweb /
main: try a bit harder to find an init process to execute after reexec/switch-root
[elogind.git] / src / core / main.c
index 58780990c8de4a1aa105d3daff0a45f0adf431e3..a51d1be45bc3de8794fa5e1d13cb70f85d085bf4 100644 (file)
@@ -32,6 +32,7 @@
 #include <sys/wait.h>
 #include <fcntl.h>
 #include <sys/prctl.h>
+#include <sys/mount.h>
 
 #include "manager.h"
 #include "log.h"
@@ -47,6 +48,7 @@
 #include "def.h"
 #include "virt.h"
 #include "watchdog.h"
+#include "path-util.h"
 
 #include "mount-setup.h"
 #include "loopback-setup.h"
@@ -1165,6 +1167,36 @@ static void test_cgroups(void) {
         sleep(10);
 }
 
+static int do_switch_root(const char *switch_root) {
+        int r;
+
+        if (path_equal(switch_root, "/"))
+                return 0;
+
+        if (chdir(switch_root) < 0) {
+                r = -errno;
+                goto fail;
+        }
+
+        if (mount(switch_root, "/", NULL, MS_MOVE, NULL) < 0) {
+                r = -errno;
+                chdir("/");
+                goto fail;
+        }
+
+        if (chroot(".") < 0)
+                log_warning("Failed to change root, ignoring: %m");
+
+        /* FIXME: remove old root */
+
+        return 0;
+
+fail:
+        log_error("Failed to switch root, ignoring: %s", strerror(-r));
+
+        return r;
+}
+
 int main(int argc, char *argv[]) {
         Manager *m = NULL;
         int r, retval = EXIT_FAILURE;
@@ -1179,6 +1211,7 @@ int main(int argc, char *argv[]) {
         int j;
         bool loaded_policy = false;
         bool arm_reboot_watchdog = false;
+        char *switch_root = NULL, *switch_root_init = NULL;
 
 #ifdef HAVE_SYSV_COMPAT
         if (getpid() != 1 && strstr(program_invocation_short_name, "init")) {
@@ -1549,6 +1582,7 @@ int main(int argc, char *argv[]) {
                         break;
 
                 case MANAGER_REEXECUTE:
+
                         if (prepare_reexecute(m, &serialization, &fds) < 0)
                                 goto finish;
 
@@ -1556,6 +1590,20 @@ int main(int argc, char *argv[]) {
                         log_notice("Reexecuting.");
                         goto finish;
 
+                case MANAGER_SWITCH_ROOT:
+                        /* Steal the switch root parameters */
+                        switch_root = m->switch_root;
+                        switch_root_init = m->switch_root_init;
+                        m->switch_root = m->switch_root_init = NULL;
+
+                        if (!switch_root_init)
+                                if (prepare_reexecute(m, &serialization, &fds) < 0)
+                                        goto finish;
+
+                        reexecute = true;
+                        log_notice("Switching root.");
+                        goto finish;
+
                 case MANAGER_REBOOT:
                 case MANAGER_POWEROFF:
                 case MANAGER_HALT:
@@ -1588,69 +1636,84 @@ finish:
         free_join_controllers();
 
         dbus_shutdown();
-
         label_finish();
 
         if (reexecute) {
-                const char *args[15];
-                unsigned i = 0;
-                char sfd[16];
+                const char **args;
+                unsigned i, args_size;
 
-                assert(serialization);
-                assert(fds);
+                /* Close and disarm the watchdog, so that the new
+                 * instance can reinitialize it, but doesn't get
+                 * rebooted while we do that */
+                watchdog_close(true);
 
-                args[i++] = SYSTEMD_BINARY_PATH;
+                if (switch_root)
+                        do_switch_root(switch_root);
 
-                args[i++] = "--log-level";
-                args[i++] = log_level_to_string(log_get_max_level());
+                args_size = MAX(5, argc+1);
+                args = newa(const char*, args_size);
 
-                args[i++] = "--log-target";
-                args[i++] = log_target_to_string(log_get_target());
+                if (!switch_root_init) {
+                        char sfd[16];
 
-                if (arg_running_as == MANAGER_SYSTEM)
-                        args[i++] = "--system";
-                else
-                        args[i++] = "--user";
+                        /* 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. */
 
-                if (arg_dump_core)
-                        args[i++] = "--dump-core";
+                        assert(serialization);
+                        assert(fds);
 
-                if (arg_crash_shell)
-                        args[i++] = "--crash-shell";
+                        snprintf(sfd, sizeof(sfd), "%i", fileno(serialization));
+                        char_array_0(sfd);
 
-                if (arg_confirm_spawn)
-                        args[i++] = "--confirm-spawn";
+                        i = 0;
+                        args[i++] = SYSTEMD_BINARY_PATH;
+                        args[i++] = arg_running_as == MANAGER_SYSTEM ? "--system" : "--user";
+                        args[i++] = "--deserialize";
+                        args[i++] = sfd;
+                        args[i++] = NULL;
 
-                if (arg_show_status)
-                        args[i++] = "--show-status=1";
-                else
-                        args[i++] = "--show-status=0";
+                        assert(i <= args_size);
+                        execv(args[0], (char* const*) args);
+                }
 
-#ifdef HAVE_SYSV_COMPAT
-                if (arg_sysv_console)
-                        args[i++] = "--sysv-console=1";
-                else
-                        args[i++] = "--sysv-console=0";
-#endif
+                /* Try the fallback, if there is any, without any
+                 * serialization. We pass the original argv[] and
+                 * envp[]. (Well, modulo the ordering changes due to
+                 * getopt() in argv[], and some cleanups in envp[],
+                 * but let's hope that doesn't matter.) */
 
-                snprintf(sfd, sizeof(sfd), "%i", fileno(serialization));
-                char_array_0(sfd);
+                if (serialization) {
+                        fclose(serialization);
+                        serialization = NULL;
+                }
 
-                args[i++] = "--deserialize";
-                args[i++] = sfd;
+                if (fds) {
+                        fdset_free(fds);
+                        fds = NULL;
+                }
 
+                for (j = 1, i = 1; j < argc; j++)
+                        args[i++] = argv[j];
                 args[i++] = NULL;
+                assert(i <= args_size);
 
-                assert(i <= ELEMENTSOF(args));
-
-                /* Close and disarm the watchdog, so that the new
-                 * instance can reinitialize it, but doesn't get
-                 * rebooted while we do that */
-                watchdog_close(true);
+                if (switch_root_init) {
+                        args[0] = switch_root_init;
+                        execv(args[0], (char* const*) args);
+                        log_warning("Failed to execute configured init, trying fallback: %m");
+                }
 
+                args[0] = "/sbin/init";
                 execv(args[0], (char* const*) args);
 
-                log_error("Failed to reexecute: %m");
+                log_warning("Failed to execute /sbin/init, trying fallback: %m");
+
+                args[0] = "/bin/sh";
+                args[1] = NULL;
+                execv(args[0], (char* const*) args);
+                log_error("Failed to execute /bin/sh, giving up: %m");
         }
 
         if (serialization)