| /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
| |
| #include "argv-util.h" |
| #include "bus-error.h" |
| #include "bus-locator.h" |
| #include "chase-symlinks.h" |
| #include "parse-util.h" |
| #include "path-util.h" |
| #include "proc-cmdline.h" |
| #include "signal-util.h" |
| #include "stat-util.h" |
| #include "systemctl.h" |
| #include "systemctl-switch-root.h" |
| #include "systemctl-util.h" |
| |
| static int same_file_in_root( |
| const char *root, |
| const char *a, |
| const char *b) { |
| |
| struct stat sta, stb; |
| int r; |
| |
| r = chase_symlinks_and_stat(a, root, CHASE_PREFIX_ROOT, NULL, &sta, NULL); |
| if (r < 0) |
| return r; |
| |
| r = chase_symlinks_and_stat(b, root, CHASE_PREFIX_ROOT, NULL, &stb, NULL); |
| if (r < 0) |
| return r; |
| |
| return stat_inode_same(&sta, &stb); |
| } |
| |
| int verb_switch_root(int argc, char *argv[], void *userdata) { |
| _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; |
| _cleanup_free_ char *cmdline_init = NULL; |
| const char *root, *init; |
| sd_bus *bus; |
| int r; |
| |
| if (arg_transport != BUS_TRANSPORT_LOCAL) |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Cannot switch root remotely."); |
| |
| if (argc < 2 || argc > 3) |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Wrong number of arguments."); |
| |
| root = argv[1]; |
| if (!path_is_valid(root)) |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid root path: %s", root); |
| if (!path_is_absolute(root)) |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Root path is not absolute: %s", root); |
| |
| if (argc >= 3) |
| init = argv[2]; |
| else { |
| r = proc_cmdline_get_key("init", 0, &cmdline_init); |
| if (r < 0) |
| log_debug_errno(r, "Failed to parse /proc/cmdline: %m"); |
| |
| init = cmdline_init; |
| } |
| |
| init = empty_to_null(init); |
| if (init) { |
| if (!path_is_valid(init)) |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid path to init binary: %s", init); |
| if (!path_is_absolute(init)) |
| return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Path to init binary is not absolute: %s", init); |
| |
| /* If the passed init is actually the same as the systemd binary, then let's suppress it. */ |
| if (same_file_in_root(root, SYSTEMD_BINARY_PATH, init) > 0) |
| init = NULL; |
| } |
| |
| /* Instruct PID1 to exclude us from its killing spree applied during the transition. Otherwise we |
| * would exit with a failure status even though the switch to the new root has succeed. */ |
| assert(saved_argv); |
| assert(saved_argv[0]); |
| saved_argv[0][0] = '@'; |
| |
| r = acquire_bus(BUS_MANAGER, &bus); |
| if (r < 0) |
| return r; |
| |
| /* If we are slow to exit after the root switch, the new systemd instance will send us a signal to |
| * terminate. Just ignore it and exit normally. This way the unit does not end up as failed. */ |
| r = ignore_signals(SIGTERM); |
| if (r < 0) |
| log_warning_errno(r, "Failed to change disposition of SIGTERM to ignore: %m"); |
| |
| log_debug("Switching root - root: %s; init: %s", root, strna(init)); |
| |
| r = bus_call_method(bus, bus_systemd_mgr, "SwitchRoot", &error, NULL, "ss", root, init); |
| if (r < 0) { |
| (void) default_signals(SIGTERM); |
| |
| return log_error_errno(r, "Failed to switch root: %s", bus_error_message(&error, r)); |
| } |
| |
| return 0; |
| } |