]>
Commit | Line | Data |
---|---|---|
db9ecf05 | 1 | /* SPDX-License-Identifier: LGPL-2.1-or-later */ |
daf71ef6 | 2 | |
3c9fbb99 ZJS |
3 | #include <unistd.h> |
4 | ||
daf71ef6 LP |
5 | #include "bootspec.h" |
6 | #include "bus-error.h" | |
7 | #include "bus-locator.h" | |
8 | #include "efivars.h" | |
9 | #include "parse-util.h" | |
10 | #include "path-util.h" | |
11 | #include "process-util.h" | |
12 | #include "reboot-util.h" | |
13 | #include "systemctl-logind.h" | |
14 | #include "systemctl-start-special.h" | |
15 | #include "systemctl-start-unit.h" | |
16 | #include "systemctl-trivial-method.h" | |
17 | #include "systemctl-util.h" | |
18 | #include "systemctl.h" | |
19 | ||
20 | static int load_kexec_kernel(void) { | |
f7a7a5e2 | 21 | _cleanup_(boot_config_free) BootConfig config = BOOT_CONFIG_NULL; |
daf71ef6 LP |
22 | _cleanup_free_ char *kernel = NULL, *initrd = NULL, *options = NULL; |
23 | const BootEntry *e; | |
24 | pid_t pid; | |
25 | int r; | |
26 | ||
27 | if (kexec_loaded()) { | |
28 | log_debug("Kexec kernel already loaded."); | |
29 | return 0; | |
30 | } | |
31 | ||
32 | if (access(KEXEC, X_OK) < 0) | |
33 | return log_error_errno(errno, KEXEC" is not available: %m"); | |
34 | ||
af9ae750 | 35 | r = boot_config_load_auto(&config, NULL, NULL); |
daf71ef6 LP |
36 | if (r == -ENOKEY) |
37 | /* The call doesn't log about ENOKEY, let's do so here. */ | |
38 | return log_error_errno(r, | |
39 | "No kexec kernel loaded and autodetection failed.\n%s", | |
40 | is_efi_boot() | |
250db1bf | 41 | ? "Cannot automatically load kernel: ESP mount point not found." |
daf71ef6 LP |
42 | : "Automatic loading works only on systems booted with EFI."); |
43 | if (r < 0) | |
44 | return r; | |
45 | ||
80a2381d | 46 | r = boot_config_select_special_entries(&config, /* skip_efivars= */ false); |
f7a7a5e2 LP |
47 | if (r < 0) |
48 | return r; | |
49 | ||
daf71ef6 LP |
50 | e = boot_config_default_entry(&config); |
51 | if (!e) | |
52 | return log_error_errno(SYNTHETIC_ERRNO(ENOENT), | |
53 | "No boot loader entry suitable as default, refusing to guess."); | |
54 | ||
55 | log_debug("Found default boot loader entry in file \"%s\"", e->path); | |
56 | ||
57 | if (!e->kernel) | |
58 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
59 | "Boot entry does not refer to Linux kernel, which is not supported currently."); | |
60 | if (strv_length(e->initrd) > 1) | |
61 | return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), | |
62 | "Boot entry specifies multiple initrds, which is not supported currently."); | |
63 | ||
64 | kernel = path_join(e->root, e->kernel); | |
65 | if (!kernel) | |
66 | return log_oom(); | |
67 | ||
68 | if (!strv_isempty(e->initrd)) { | |
69 | initrd = path_join(e->root, e->initrd[0]); | |
70 | if (!initrd) | |
71 | return log_oom(); | |
72 | } | |
73 | ||
74 | options = strv_join(e->options, " "); | |
75 | if (!options) | |
76 | return log_oom(); | |
77 | ||
78 | log_full(arg_quiet ? LOG_DEBUG : LOG_INFO, | |
79 | "%s "KEXEC" --load \"%s\" --append \"%s\"%s%s%s", | |
80 | arg_dry_run ? "Would run" : "Running", | |
81 | kernel, | |
82 | options, | |
83 | initrd ? " --initrd \"" : NULL, strempty(initrd), initrd ? "\"" : ""); | |
84 | if (arg_dry_run) | |
85 | return 0; | |
86 | ||
87 | r = safe_fork("(kexec)", FORK_WAIT|FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_RLIMIT_NOFILE_SAFE|FORK_LOG, &pid); | |
88 | if (r < 0) | |
89 | return r; | |
90 | if (r == 0) { | |
91 | const char* const args[] = { | |
92 | KEXEC, | |
93 | "--load", kernel, | |
94 | "--append", options, | |
95 | initrd ? "--initrd" : NULL, initrd, | |
96 | NULL | |
97 | }; | |
98 | ||
99 | /* Child */ | |
100 | execv(args[0], (char * const *) args); | |
101 | _exit(EXIT_FAILURE); | |
102 | } | |
103 | ||
104 | return 0; | |
105 | } | |
106 | ||
107 | static int set_exit_code(uint8_t code) { | |
108 | _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; | |
109 | sd_bus *bus; | |
110 | int r; | |
111 | ||
112 | r = acquire_bus(BUS_MANAGER, &bus); | |
113 | if (r < 0) | |
114 | return r; | |
115 | ||
116 | r = bus_call_method(bus, bus_systemd_mgr, "SetExitCode", &error, NULL, "y", code); | |
117 | if (r < 0) | |
118 | return log_error_errno(r, "Failed to set exit code: %s", bus_error_message(&error, r)); | |
119 | ||
120 | return 0; | |
121 | } | |
122 | ||
32baf64d | 123 | int verb_start_special(int argc, char *argv[], void *userdata) { |
daf71ef6 LP |
124 | bool termination_action; /* An action that terminates the manager, can be performed also by |
125 | * signal. */ | |
126 | enum action a; | |
127 | int r; | |
128 | ||
129 | assert(argv); | |
130 | ||
131 | a = verb_to_action(argv[0]); | |
132 | ||
133 | r = logind_check_inhibitors(a); | |
134 | if (r < 0) | |
135 | return r; | |
136 | ||
137 | if (arg_force >= 2) { | |
138 | r = must_be_root(); | |
139 | if (r < 0) | |
140 | return r; | |
141 | } | |
142 | ||
143 | r = prepare_firmware_setup(); | |
144 | if (r < 0) | |
145 | return r; | |
146 | ||
147 | r = prepare_boot_loader_menu(); | |
148 | if (r < 0) | |
149 | return r; | |
150 | ||
151 | r = prepare_boot_loader_entry(); | |
152 | if (r < 0) | |
153 | return r; | |
154 | ||
155 | if (a == ACTION_REBOOT) { | |
31853609 MY |
156 | if (arg_reboot_argument) { |
157 | r = update_reboot_parameter_and_warn(arg_reboot_argument, false); | |
daf71ef6 LP |
158 | if (r < 0) |
159 | return r; | |
160 | } | |
161 | ||
162 | } else if (a == ACTION_KEXEC) { | |
163 | r = load_kexec_kernel(); | |
164 | if (r < 0 && arg_force >= 1) | |
165 | log_notice("Failed to load kexec kernel, continuing without."); | |
166 | else if (r < 0) | |
167 | return r; | |
168 | ||
169 | } else if (a == ACTION_EXIT && argc > 1) { | |
170 | uint8_t code; | |
171 | ||
172 | /* If the exit code is not given on the command line, don't reset it to zero: just keep it as | |
173 | * it might have been set previously. */ | |
174 | ||
175 | r = safe_atou8(argv[1], &code); | |
176 | if (r < 0) | |
177 | return log_error_errno(r, "Invalid exit code."); | |
178 | ||
179 | r = set_exit_code(code); | |
180 | if (r < 0) | |
181 | return r; | |
182 | } | |
183 | ||
184 | termination_action = IN_SET(a, | |
185 | ACTION_HALT, | |
186 | ACTION_POWEROFF, | |
187 | ACTION_REBOOT); | |
188 | if (termination_action && arg_force >= 2) | |
189 | return halt_now(a); | |
190 | ||
191 | if (arg_force >= 1 && | |
192 | (termination_action || IN_SET(a, ACTION_KEXEC, ACTION_EXIT))) | |
32baf64d | 193 | r = verb_trivial_method(argc, argv, userdata); |
daf71ef6 LP |
194 | else { |
195 | /* First try logind, to allow authentication with polkit */ | |
4b7fda87 | 196 | switch (a) { |
1433e1f9 | 197 | |
4b7fda87 LP |
198 | case ACTION_POWEROFF: |
199 | case ACTION_REBOOT: | |
200 | case ACTION_KEXEC: | |
201 | case ACTION_HALT: | |
34f21ff6 | 202 | case ACTION_SOFT_REBOOT: |
1433e1f9 MY |
203 | if (arg_when == 0) |
204 | r = logind_reboot(a); | |
205 | else if (arg_when != USEC_INFINITY) | |
206 | r = logind_schedule_shutdown(a); | |
207 | else /* arg_when == USEC_INFINITY */ | |
208 | r = logind_cancel_shutdown(); | |
209 | if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS)) | |
210 | /* The latter indicates that the requested operation requires auth, | |
211 | * is not supported or already in progress, in which cases we ignore the error. */ | |
daf71ef6 LP |
212 | return r; |
213 | ||
214 | /* On all other errors, try low-level operation. In order to minimize the difference | |
215 | * between operation with and without logind, we explicitly enable non-blocking mode | |
216 | * for this, as logind's shutdown operations are always non-blocking. */ | |
1433e1f9 | 217 | arg_no_block = true; |
4b7fda87 | 218 | break; |
1433e1f9 | 219 | |
4b7fda87 LP |
220 | case ACTION_SUSPEND: |
221 | case ACTION_HIBERNATE: | |
222 | case ACTION_HYBRID_SLEEP: | |
223 | case ACTION_SUSPEND_THEN_HIBERNATE: | |
1433e1f9 MY |
224 | |
225 | r = logind_reboot(a); | |
226 | if (r >= 0 || IN_SET(r, -EACCES, -EOPNOTSUPP, -EINPROGRESS)) | |
227 | return r; | |
daf71ef6 LP |
228 | |
229 | arg_no_block = true; | |
4b7fda87 | 230 | break; |
daf71ef6 | 231 | |
4b7fda87 | 232 | case ACTION_EXIT: |
0d96caa5 DR |
233 | /* Since exit is so close in behaviour to power-off/reboot, let's also make |
234 | * it asynchronous, in order to not confuse the user needlessly with unexpected | |
daf71ef6 LP |
235 | * behaviour. */ |
236 | arg_no_block = true; | |
4b7fda87 LP |
237 | break; |
238 | ||
239 | default: | |
240 | ; | |
241 | } | |
daf71ef6 | 242 | |
32baf64d | 243 | r = verb_start(argc, argv, userdata); |
daf71ef6 LP |
244 | } |
245 | ||
246 | if (termination_action && arg_force < 2 && | |
247 | IN_SET(r, -ENOENT, -ETIMEDOUT)) | |
248 | log_notice("It is possible to perform action directly, see discussion of --force --force in man:systemctl(1)."); | |
249 | ||
250 | return r; | |
251 | } | |
252 | ||
32baf64d | 253 | int verb_start_system_special(int argc, char *argv[], void *userdata) { |
daf71ef6 LP |
254 | /* Like start_special above, but raises an error when running in user mode */ |
255 | ||
4870133b | 256 | if (arg_runtime_scope != RUNTIME_SCOPE_SYSTEM) |
daf71ef6 LP |
257 | return log_error_errno(SYNTHETIC_ERRNO(EINVAL), |
258 | "Bad action for %s mode.", | |
40d73340 | 259 | runtime_scope_cmdline_option_to_string(arg_runtime_scope)); |
daf71ef6 | 260 | |
32baf64d | 261 | return verb_start_special(argc, argv, userdata); |
daf71ef6 | 262 | } |