Files
systemd/src/systemctl/systemctl-start-special.c
T
2025-05-21 23:59:03 +09:00

276 lines
10 KiB
C

/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include <unistd.h>
#include "sd-bus.h"
#include "bootspec.h"
#include "bus-error.h"
#include "bus-locator.h"
#include "efivars.h"
#include "log.h"
#include "parse-util.h"
#include "path-util.h"
#include "process-util.h"
#include "reboot-util.h"
#include "runtime-scope.h"
#include "strv.h"
#include "systemctl.h"
#include "systemctl-logind.h"
#include "systemctl-start-special.h"
#include "systemctl-start-unit.h"
#include "systemctl-trivial-method.h"
#include "systemctl-util.h"
static int load_kexec_kernel(void) {
_cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL;
_cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL;
const BootEntry *e;
pid_t pid;
int r;
if (kexec_loaded()) {
log_debug("Kexec kernel already loaded.");
return 0;
}
if (access(KEXEC, X_OK) < 0)
return log_error_errno(errno, KEXEC" is not available: %m");
r = boot_config_load_auto(&config, NULL, NULL);
if (r == -ENOKEY)
/* The call doesn't log about ENOKEY, let's do so here. */
return log_error_errno(r,
"No kexec kernel loaded and autodetection failed.\n%s",
is_efi_boot()
? "Cannot automatically load kernel: ESP mount point not found."
: "Automatic loading works only on systems booted with EFI.");
if (r < 0)
return r;
r = boot_config_select_special_entries(&config, /* skip_efivars= */ false);
if (r < 0)
return r;
e = boot_config_default_entry(&config);
if (!e)
return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
"No boot loader entry suitable as default, refusing to guess.");
log_debug("Found default boot loader entry in file \"%s\"", e->path);
if (!e->kernel)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Boot entry does not refer to Linux kernel, which is not supported currently.");
if (strv_length(e->initrd) > 1)
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
"Boot entry specifies multiple initrds, which is not supported currently.");
kernel = path_join(e->root, e->kernel);
if (!kernel)
return log_oom();
if (!strv_isempty(e->initrd)) {
initrd = path_join(e->root, e->initrd[0]);
if (!initrd)
return log_oom();
}
options = strv_join(e->options, " ");
if (!options)
return log_oom();
log_full(arg_quiet ? LOG_DEBUG : LOG_INFO,
"%s "KEXEC" --load \"%s\" --append \"%s\"%s%s%s",
arg_dry_run ? "Would run" : "Running",
kernel,
options,
initrd ? " --initrd \"" : NULL, strempty(initrd), initrd ? "\"" : "");
if (arg_dry_run)
return 0;
r = safe_fork("(kexec)", FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG_SIGTERM|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid);
if (r < 0)
return r;
if (r == 0) {
const char* const args[] = {
KEXEC,
"--load", kernel,
"--append", options,
initrd ? "--initrd" : NULL, initrd,
NULL
};
/* Child */
execv(args[0], (char * const *) args);
_exit(EXIT_FAILURE);
}
return 0;
}
static int set_exit_code(uint8_t code) {
_cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL;
sd_bus *bus;
int r;
r = acquire_bus(BUS_MANAGER, &bus);
if (r < 0)
return r;
r = bus_call_method(bus, bus_systemd_mgr, "SetExitCode", &error, NULL, "y", code);
if (r < 0)
return log_error_errno(r, "Failed to set exit code: %s", bus_error_message(&error, r));
return 0;
}
int verb_start_special(int argc, char *argv[], void *userdata) {
bool termination_action; /* An action that terminates the system, can be performed also by signal. */
enum action a;
int r;
assert(argv);
a = verb_to_action(argv[0]);
r = logind_check_inhibitors(a);
if (r < 0)
return r;
if (arg_force >= 2) {
r = must_be_root();
if (r < 0)
return r;
}
termination_action = IN_SET(a, ACTION_HALT, ACTION_POWEROFF, ACTION_REBOOT);
if (termination_action) {
r = prepare_firmware_setup();
if (r < 0)
return r;
r = prepare_boot_loader_menu();
if (r < 0)
return r;
r = prepare_boot_loader_entry();
if (r < 0)
return r;
}
if (arg_reboot_argument && IN_SET(a, ACTION_HALT, ACTION_POWEROFF, ACTION_REBOOT, ACTION_KEXEC)) {
/* If we are going through an action that involves systemd-shutdown, let's set the reboot
* parameter, even if it's not a regular reboot. After all we nowadays send the string to
* our supervisor via sd_notify() too. */
r = update_reboot_parameter_and_warn(arg_reboot_argument, /* keep= */ false);
if (r < 0)
return r;
}
if (a == ACTION_KEXEC) {
r = load_kexec_kernel();
if (r < 0 && arg_force >= 1)
log_notice("Failed to load kexec kernel, continuing without.");
else if (r < 0)
return r;
} else if (a == ACTION_EXIT && argc > 1) {
uint8_t code;
/* If the exit code is not given on the command line, don't reset it to zero: just keep it as
* it might have been set previously. */
r = safe_atou8(argv[1], &code);
if (r < 0)
return log_error_errno(r, "Invalid exit code.");
r = set_exit_code(code);
if (r < 0)
return r;
}
if (termination_action && arg_force >= 2)
return halt_now(a);
if (arg_force >= 1 &&
(termination_action || IN_SET(a, ACTION_KEXEC, ACTION_EXIT)))
r = verb_trivial_method(argc, argv, userdata);
else {
/* First try logind, to allow authentication with polkit */
switch (a) {
case ACTION_POWEROFF:
case ACTION_REBOOT:
case ACTION_KEXEC:
case ACTION_HALT:
case ACTION_SOFT_REBOOT:
if (arg_when == 0) {
r = logind_reboot(a);
if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS))
/* The latter indicates that the requested operation requires auth,
* is not supported or already in progress, in which cases we ignore the error. */
return r;
} else {
r = logind_schedule_shutdown(a);
if (r != -ENOSYS)
return r;
}
/* On all other errors, try low-level operation. In order to minimize the difference
* between operation with and without logind, we explicitly enable non-blocking mode
* for this, as logind's shutdown operations are always non-blocking. */
arg_no_block = true;
break;
case ACTION_SUSPEND:
case ACTION_HIBERNATE:
case ACTION_HYBRID_SLEEP:
case ACTION_SUSPEND_THEN_HIBERNATE:
/* For sleep operations, do not automatically fall back to low-level operation for
* errors other than logind not available. There's a high chance that logind did
* some extra sanity check and that didn't pass. */
r = logind_reboot(a);
if (r >= 0 || (r != -ENOSYS && arg_force == 0))
return r;
arg_no_block = true;
break;
case ACTION_SLEEP:
return logind_reboot(a);
case ACTION_EXIT:
/* Since exit is so close in behaviour to power-off/reboot, let's also make
* it asynchronous, in order to not confuse the user needlessly with unexpected
* behaviour. */
arg_no_block = true;
break;
default:
;
}
r = verb_start(argc, argv, userdata);
}
if (termination_action && arg_force < 2 &&
IN_SET(r, -ENOENT, -ETIMEDOUT))
log_notice("It is possible to perform action directly, see discussion of --force --force in man:systemctl(1).");
return r;
}
int verb_start_system_special(int argc, char *argv[], void *userdata) {
/* Like start_special above, but raises an error when running in user mode */
if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"Bad action for %s mode.",
runtime_scope_cmdline_option_to_string(arg_runtime_scope));
return verb_start_special(argc, argv, userdata);
}