chiark / gitweb /
main: corrected do_switch_root()
[elogind.git] / src / core / main.c
index 8c25819a120e1870cad9ea84dff23d2316487a11..ea06dc80146ee4a86eeec5151826b4084bd03c59 100644 (file)
@@ -1168,30 +1168,73 @@ static void test_cgroups(void) {
 }
 
 static int do_switch_root(const char *switch_root) {
-        int r;
+        int r=0;
+        /*  Don't try to unmount the old "/", there's no way to do it. */
+        const char *umounts[] = { "/dev", "/proc", "/sys", "/run", NULL };
+        int i;
+        int cfd = -1;
+        struct stat switch_root_stat, sb;
 
         if (path_equal(switch_root, "/"))
                 return 0;
 
-        if (chdir(switch_root) < 0) {
+        if (stat(switch_root, &switch_root_stat) != 0) {
+                r = -errno;
+                log_error("failed to stat directory %s", switch_root);
+                goto fail;
+        }
+
+        for (i = 0; umounts[i] != NULL; i++) {
+                char newmount[PATH_MAX];
+
+                snprintf(newmount, sizeof(newmount), "%s%s", switch_root, umounts[i]);
+
+                if ((stat(newmount, &sb) != 0) || (sb.st_dev != switch_root_stat.st_dev)) {
+                        /* mount point seems to be mounted already or stat failed */
+                        umount2(umounts[i], MNT_DETACH);
+                        continue;
+                }
+
+                if (mount(umounts[i], newmount, NULL, MS_MOVE, NULL) < 0) {
+                        log_error("failed to mount moving %s to %s",
+                                  umounts[i], newmount);
+                        log_error("forcing unmount of %s", umounts[i]);
+                        umount2(umounts[i], MNT_FORCE);
+                }
+        }
+
+        if (chdir(switch_root)) {
                 r = -errno;
+                log_error("failed to change directory to %s", switch_root);
                 goto fail;
         }
 
+        cfd = open("/", O_RDONLY);
+
         if (mount(switch_root, "/", NULL, MS_MOVE, NULL) < 0) {
                 r = -errno;
-                chdir("/");
+                log_error("failed to mount moving %s to /", switch_root);
                 goto fail;
         }
 
-        if (chroot(".") < 0)
-                log_warning("Failed to change root, ignoring: %m");
+        if (chroot(".")) {
+                r = -errno;
+                log_error("failed to change root");
+                goto fail;
+        }
 
-        /* FIXME: remove old root */
+        if (cfd >= 0) {
+                rm_rf_children(cfd, false, false);
+                close(cfd);
+                cfd=-1;
+        }
 
         return 0;
 
 fail:
+        if (cfd >= 0)
+                close(cfd);
+
         log_error("Failed to switch root, ignoring: %s", strerror(-r));
 
         return r;
@@ -1684,22 +1727,36 @@ finish:
                  * getopt() in argv[], and some cleanups in envp[],
                  * but let's hope that doesn't matter.) */
 
-                if (serialization)
+                if (serialization) {
                         fclose(serialization);
+                        serialization = NULL;
+                }
 
-                if (fds)
+                if (fds) {
                         fdset_free(fds);
+                        fds = NULL;
+                }
 
-                i = 0;
-                args[i++] = switch_root_init ? switch_root_init : "/sbin/init";
-                for (j = 1; j < argc; j++)
+                for (j = 1, i = 1; j < argc; j++)
                         args[i++] = argv[j];
                 args[i++] = NULL;
-
                 assert(i <= args_size);
+
+                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)