]>
Commit | Line | Data |
---|---|---|
b1b2a107 FF |
1 | /*** |
2 | This file is part of systemd. | |
3 | ||
4 | Copyright 2010 ProFUSION embedded systems | |
5 | ||
6 | systemd is free software; you can redistribute it and/or modify it | |
5430f7f2 LP |
7 | under the terms of the GNU Lesser General Public License as published by |
8 | the Free Software Foundation; either version 2.1 of the License, or | |
b1b2a107 FF |
9 | (at your option) any later version. |
10 | ||
11 | systemd is distributed in the hope that it will be useful, but | |
12 | WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
5430f7f2 | 14 | Lesser General Public License for more details. |
b1b2a107 | 15 | |
5430f7f2 | 16 | You should have received a copy of the GNU Lesser General Public License |
b1b2a107 FF |
17 | along with systemd; If not, see <http://www.gnu.org/licenses/>. |
18 | ***/ | |
19 | ||
b1b2a107 | 20 | #include <errno.h> |
07630cea LP |
21 | #include <getopt.h> |
22 | #include <linux/reboot.h> | |
b1b2a107 FF |
23 | #include <signal.h> |
24 | #include <stdbool.h> | |
25 | #include <stdlib.h> | |
07630cea LP |
26 | #include <sys/mman.h> |
27 | #include <sys/mount.h> | |
28 | #include <sys/reboot.h> | |
29 | #include <sys/stat.h> | |
30 | #include <unistd.h> | |
b1b2a107 | 31 | |
b5efdb8a | 32 | #include "alloc-util.h" |
07630cea LP |
33 | #include "cgroup-util.h" |
34 | #include "def.h" | |
89711996 | 35 | #include "exec-util.h" |
ec26be51 | 36 | #include "fileio.h" |
07630cea LP |
37 | #include "killall.h" |
38 | #include "log.h" | |
39 | #include "missing.h" | |
6bedfcbb | 40 | #include "parse-util.h" |
07630cea LP |
41 | #include "process-util.h" |
42 | #include "string-util.h" | |
43 | #include "switch-root.h" | |
44 | #include "terminal-util.h" | |
b1b2a107 FF |
45 | #include "umount.h" |
46 | #include "util.h" | |
b52aae1d | 47 | #include "virt.h" |
e96d6be7 | 48 | #include "watchdog.h" |
b1b2a107 | 49 | |
b1b2a107 | 50 | #define FINALIZE_ATTEMPTS 50 |
b1b2a107 | 51 | |
b1e90ec5 | 52 | static char* arg_verb; |
287419c1 | 53 | static uint8_t arg_exit_code; |
b1e90ec5 ZJS |
54 | |
55 | static int parse_argv(int argc, char *argv[]) { | |
56 | enum { | |
57 | ARG_LOG_LEVEL = 0x100, | |
58 | ARG_LOG_TARGET, | |
59 | ARG_LOG_COLOR, | |
60 | ARG_LOG_LOCATION, | |
287419c1 | 61 | ARG_EXIT_CODE, |
b1e90ec5 ZJS |
62 | }; |
63 | ||
64 | static const struct option options[] = { | |
65 | { "log-level", required_argument, NULL, ARG_LOG_LEVEL }, | |
66 | { "log-target", required_argument, NULL, ARG_LOG_TARGET }, | |
67 | { "log-color", optional_argument, NULL, ARG_LOG_COLOR }, | |
68 | { "log-location", optional_argument, NULL, ARG_LOG_LOCATION }, | |
287419c1 | 69 | { "exit-code", required_argument, NULL, ARG_EXIT_CODE }, |
b1e90ec5 ZJS |
70 | {} |
71 | }; | |
72 | ||
73 | int c, r; | |
74 | ||
75 | assert(argc >= 1); | |
76 | assert(argv); | |
77 | ||
4b5d8d0f MS |
78 | /* "-" prevents getopt from permuting argv[] and moving the verb away |
79 | * from argv[1]. Our interface to initrd promises it'll be there. */ | |
80 | while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0) | |
b1e90ec5 ZJS |
81 | switch (c) { |
82 | ||
83 | case ARG_LOG_LEVEL: | |
84 | r = log_set_max_level_from_string(optarg); | |
85 | if (r < 0) | |
86 | log_error("Failed to parse log level %s, ignoring.", optarg); | |
87 | ||
88 | break; | |
89 | ||
90 | case ARG_LOG_TARGET: | |
91 | r = log_set_target_from_string(optarg); | |
92 | if (r < 0) | |
93 | log_error("Failed to parse log target %s, ignoring", optarg); | |
94 | ||
95 | break; | |
96 | ||
97 | case ARG_LOG_COLOR: | |
98 | ||
99 | if (optarg) { | |
100 | r = log_show_color_from_string(optarg); | |
101 | if (r < 0) | |
102 | log_error("Failed to parse log color setting %s, ignoring", optarg); | |
103 | } else | |
104 | log_show_color(true); | |
105 | ||
106 | break; | |
107 | ||
108 | case ARG_LOG_LOCATION: | |
109 | if (optarg) { | |
110 | r = log_show_location_from_string(optarg); | |
111 | if (r < 0) | |
112 | log_error("Failed to parse log location setting %s, ignoring", optarg); | |
113 | } else | |
114 | log_show_location(true); | |
115 | ||
116 | break; | |
117 | ||
287419c1 AC |
118 | case ARG_EXIT_CODE: |
119 | r = safe_atou8(optarg, &arg_exit_code); | |
120 | if (r < 0) | |
121 | log_error("Failed to parse exit code %s, ignoring", optarg); | |
122 | ||
123 | break; | |
124 | ||
4b5d8d0f MS |
125 | case '\001': |
126 | if (!arg_verb) | |
127 | arg_verb = optarg; | |
128 | else | |
129 | log_error("Excess arguments, ignoring"); | |
130 | break; | |
131 | ||
b1e90ec5 | 132 | case '?': |
b1e90ec5 ZJS |
133 | return -EINVAL; |
134 | ||
135 | default: | |
136 | assert_not_reached("Unhandled option code."); | |
137 | } | |
138 | ||
4b5d8d0f | 139 | if (!arg_verb) { |
b1e90ec5 ZJS |
140 | log_error("Verb argument missing."); |
141 | return -EINVAL; | |
142 | } | |
143 | ||
b1e90ec5 ZJS |
144 | return 0; |
145 | } | |
146 | ||
5a4bf02f | 147 | static int switch_root_initramfs(void) { |
4a62c710 MS |
148 | if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) |
149 | return log_error_errno(errno, "Failed to mount bind /run/initramfs on /run/initramfs: %m"); | |
89d471d5 | 150 | |
4a62c710 MS |
151 | if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) |
152 | return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); | |
89d471d5 | 153 | |
f131770b | 154 | /* switch_root with MS_BIND, because there might still be processes lurking around, which have open file descriptors. |
5a4bf02f HH |
155 | * /run/initramfs/shutdown will take care of these. |
156 | * Also do not detach the old root, because /run/initramfs/shutdown needs to access it. | |
157 | */ | |
158 | return switch_root("/run/initramfs", "/oldroot", false, MS_BIND); | |
7cb1094a HH |
159 | } |
160 | ||
b1b2a107 | 161 | int main(int argc, char *argv[]) { |
8c977838 | 162 | bool need_umount, need_swapoff, need_loop_detach, need_dm_detach; |
bd3fa1d2 | 163 | bool in_container, use_watchdog = false; |
06beed6d | 164 | _cleanup_free_ char *cgroup = NULL; |
6edd7d0a | 165 | char *arguments[3]; |
41f85451 LP |
166 | unsigned retries; |
167 | int cmd, r; | |
e801700e | 168 | static const char* const dirs[] = {SYSTEM_SHUTDOWN_PATH, NULL}; |
b1b2a107 | 169 | |
b1e90ec5 ZJS |
170 | log_parse_environment(); |
171 | r = parse_argv(argc, argv); | |
172 | if (r < 0) | |
173 | goto error; | |
ec26be51 | 174 | |
b1e90ec5 | 175 | /* journald will die if not gone yet. The log target defaults |
3f85ef0f | 176 | * to console, but may have been changed by command line options. */ |
ec26be51 | 177 | |
99f09825 | 178 | log_close_console(); /* force reopen of /dev/console */ |
b1b2a107 FF |
179 | log_open(); |
180 | ||
4c12626c LP |
181 | umask(0022); |
182 | ||
b1b2a107 | 183 | if (getpid() != 1) { |
b1e90ec5 | 184 | log_error("Not executed by init (PID 1)."); |
b1b2a107 FF |
185 | r = -EPERM; |
186 | goto error; | |
187 | } | |
188 | ||
b1e90ec5 | 189 | if (streq(arg_verb, "reboot")) |
b1b2a107 | 190 | cmd = RB_AUTOBOOT; |
b1e90ec5 | 191 | else if (streq(arg_verb, "poweroff")) |
b1b2a107 | 192 | cmd = RB_POWER_OFF; |
b1e90ec5 | 193 | else if (streq(arg_verb, "halt")) |
b1b2a107 | 194 | cmd = RB_HALT_SYSTEM; |
b1e90ec5 | 195 | else if (streq(arg_verb, "kexec")) |
b1b2a107 | 196 | cmd = LINUX_REBOOT_CMD_KEXEC; |
287419c1 AC |
197 | else if (streq(arg_verb, "exit")) |
198 | cmd = 0; /* ignored, just checking that arg_verb is valid */ | |
b1b2a107 | 199 | else { |
b1b2a107 | 200 | r = -EINVAL; |
b1e90ec5 | 201 | log_error("Unknown action '%s'.", arg_verb); |
b1b2a107 FF |
202 | goto error; |
203 | } | |
204 | ||
0b9aa270 | 205 | (void) cg_get_root_path(&cgroup); |
2e79d182 | 206 | in_container = detect_container() > 0; |
41f85451 | 207 | |
e96d6be7 LP |
208 | use_watchdog = !!getenv("WATCHDOG_USEC"); |
209 | ||
2e79d182 | 210 | /* Lock us into memory */ |
b55d0651 | 211 | mlockall(MCL_CURRENT|MCL_FUTURE); |
b1b2a107 | 212 | |
2e79d182 LP |
213 | /* Synchronize everything that is not written to disk yet at this point already. This is a good idea so that |
214 | * slow IO is processed here already and the final process killing spree is not impacted by processes | |
215 | * desperately trying to sync IO to disk within their timeout. */ | |
216 | if (!in_container) | |
217 | sync(); | |
218 | ||
ab58e291 | 219 | log_info("Sending SIGTERM to remaining processes..."); |
0bee65f0 | 220 | broadcast_signal(SIGTERM, true, true); |
b1b2a107 | 221 | |
ab58e291 | 222 | log_info("Sending SIGKILL to remaining processes..."); |
0bee65f0 | 223 | broadcast_signal(SIGKILL, true, false); |
40e85d00 | 224 | |
d89b5fed | 225 | need_umount = !in_container; |
8c977838 ZJS |
226 | need_swapoff = !in_container; |
227 | need_loop_detach = !in_container; | |
228 | need_dm_detach = !in_container; | |
b1b2a107 | 229 | |
567ea02a | 230 | /* Unmount all mountpoints, swaps, and loopback devices */ |
12aad1d0 LP |
231 | for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) { |
232 | bool changed = false; | |
233 | ||
e96d6be7 LP |
234 | if (use_watchdog) |
235 | watchdog_ping(); | |
236 | ||
41f85451 LP |
237 | /* Let's trim the cgroup tree on each iteration so |
238 | that we leave an empty cgroup tree around, so that | |
239 | container managers get a nice notify event when we | |
240 | are down */ | |
241 | if (cgroup) | |
242 | cg_trim(SYSTEMD_CGROUP_CONTROLLER, cgroup, false); | |
243 | ||
b1b2a107 | 244 | if (need_umount) { |
ab58e291 | 245 | log_info("Unmounting file systems."); |
12aad1d0 | 246 | r = umount_all(&changed); |
bce93b7a | 247 | if (r == 0) { |
b1b2a107 | 248 | need_umount = false; |
bce93b7a MS |
249 | log_info("All filesystems unmounted."); |
250 | } else if (r > 0) | |
ab58e291 | 251 | log_info("Not all file systems unmounted, %d left.", r); |
b1b2a107 | 252 | else |
da927ba9 | 253 | log_error_errno(r, "Failed to unmount file systems: %m"); |
b1b2a107 FF |
254 | } |
255 | ||
256 | if (need_swapoff) { | |
735e0712 | 257 | log_info("Deactivating swaps."); |
12aad1d0 | 258 | r = swapoff_all(&changed); |
bce93b7a | 259 | if (r == 0) { |
b1b2a107 | 260 | need_swapoff = false; |
735e0712 | 261 | log_info("All swaps deactivated."); |
bce93b7a | 262 | } else if (r > 0) |
735e0712 | 263 | log_info("Not all swaps deactivated, %d left.", r); |
b1b2a107 | 264 | else |
da927ba9 | 265 | log_error_errno(r, "Failed to deactivate swaps: %m"); |
b1b2a107 FF |
266 | } |
267 | ||
268 | if (need_loop_detach) { | |
269 | log_info("Detaching loop devices."); | |
12aad1d0 | 270 | r = loopback_detach_all(&changed); |
bce93b7a | 271 | if (r == 0) { |
b1b2a107 | 272 | need_loop_detach = false; |
bce93b7a MS |
273 | log_info("All loop devices detached."); |
274 | } else if (r > 0) | |
ab58e291 | 275 | log_info("Not all loop devices detached, %d left.", r); |
b1b2a107 | 276 | else |
da927ba9 | 277 | log_error_errno(r, "Failed to detach loop devices: %m"); |
d48141ba | 278 | } |
b1b2a107 | 279 | |
d48141ba LP |
280 | if (need_dm_detach) { |
281 | log_info("Detaching DM devices."); | |
12aad1d0 | 282 | r = dm_detach_all(&changed); |
bce93b7a | 283 | if (r == 0) { |
d48141ba | 284 | need_dm_detach = false; |
bce93b7a MS |
285 | log_info("All DM devices detached."); |
286 | } else if (r > 0) | |
2569a5ce | 287 | log_info("Not all DM devices detached, %d left.", r); |
d48141ba | 288 | else |
da927ba9 | 289 | log_error_errno(r, "Failed to detach DM devices: %m"); |
b1b2a107 FF |
290 | } |
291 | ||
a27d2184 KS |
292 | if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) { |
293 | if (retries > 0) | |
294 | log_info("All filesystems, swaps, loop devices, DM devices detached."); | |
12aad1d0 | 295 | /* Yay, done */ |
8c977838 | 296 | goto initrd_jump; |
a27d2184 | 297 | } |
b1b2a107 | 298 | |
12aad1d0 | 299 | /* If in this iteration we didn't manage to |
bd3fa1d2 | 300 | * unmount/deactivate anything, we simply give up */ |
12aad1d0 | 301 | if (!changed) { |
8c977838 ZJS |
302 | log_info("Cannot finalize remaining%s%s%s%s continuing.", |
303 | need_umount ? " file systems," : "", | |
304 | need_swapoff ? " swap devices," : "", | |
305 | need_loop_detach ? " loop devices," : "", | |
306 | need_dm_detach ? " DM devices," : ""); | |
307 | goto initrd_jump; | |
12aad1d0 LP |
308 | } |
309 | ||
8c977838 ZJS |
310 | log_debug("After %u retries, couldn't finalize remaining %s%s%s%s trying again.", |
311 | retries + 1, | |
312 | need_umount ? " file systems," : "", | |
313 | need_swapoff ? " swap devices," : "", | |
314 | need_loop_detach ? " loop devices," : "", | |
315 | need_dm_detach ? " DM devices," : ""); | |
b1b2a107 FF |
316 | } |
317 | ||
8c977838 ZJS |
318 | log_error("Too many iterations, giving up."); |
319 | ||
320 | initrd_jump: | |
12aad1d0 | 321 | |
6edd7d0a | 322 | arguments[0] = NULL; |
b1e90ec5 | 323 | arguments[1] = arg_verb; |
6edd7d0a | 324 | arguments[2] = NULL; |
e801700e | 325 | execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments); |
83cc030f | 326 | |
02eaa788 | 327 | if (!in_container && !in_initrd() && |
cb7ec564 | 328 | access("/run/initramfs/shutdown", X_OK) == 0) { |
5a4bf02f HH |
329 | r = switch_root_initramfs(); |
330 | if (r >= 0) { | |
a2726e5c | 331 | argv[0] = (char*) "/shutdown"; |
30d743f4 | 332 | |
5a4bf02f HH |
333 | setsid(); |
334 | make_console_stdio(); | |
335 | ||
336 | log_info("Successfully changed into root pivot.\n" | |
337 | "Returning to initrd..."); | |
30d743f4 | 338 | |
a2726e5c | 339 | execv("/shutdown", argv); |
56f64d95 | 340 | log_error_errno(errno, "Failed to execute shutdown binary: %m"); |
5a4bf02f | 341 | } else |
da927ba9 | 342 | log_error_errno(r, "Failed to switch root to \"/run/initramfs\": %m"); |
5a4bf02f | 343 | |
7cb1094a HH |
344 | } |
345 | ||
8c977838 ZJS |
346 | if (need_umount || need_swapoff || need_loop_detach || need_dm_detach) |
347 | log_error("Failed to finalize %s%s%s%s ignoring", | |
348 | need_umount ? " file systems," : "", | |
349 | need_swapoff ? " swap devices," : "", | |
350 | need_loop_detach ? " loop devices," : "", | |
351 | need_dm_detach ? " DM devices," : ""); | |
352 | ||
2e79d182 LP |
353 | /* The kernel will automatically flush ATA disks and suchlike on reboot(), but the file systems need to be |
354 | * sync'ed explicitly in advance. So let's do this here, but not needlessly slow down containers. Note that we | |
355 | * sync'ed things already once above, but we did some more work since then which might have caused IO, hence | |
356 | * let's doit once more. */ | |
0049f05a LP |
357 | if (!in_container) |
358 | sync(); | |
359 | ||
287419c1 AC |
360 | if (streq(arg_verb, "exit")) { |
361 | if (in_container) | |
362 | exit(arg_exit_code); | |
363 | else { | |
364 | /* We cannot exit() on the host, fallback on another | |
365 | * method. */ | |
366 | cmd = RB_POWER_OFF; | |
367 | } | |
368 | } | |
369 | ||
477def80 LP |
370 | switch (cmd) { |
371 | ||
372 | case LINUX_REBOOT_CMD_KEXEC: | |
cb7ec564 LP |
373 | |
374 | if (!in_container) { | |
375 | /* We cheat and exec kexec to avoid doing all its work */ | |
477def80 LP |
376 | pid_t pid; |
377 | ||
378 | log_info("Rebooting with kexec."); | |
cb7ec564 | 379 | |
477def80 | 380 | pid = fork(); |
cb7ec564 | 381 | if (pid < 0) |
56f64d95 | 382 | log_error_errno(errno, "Failed to fork: %m"); |
477def80 LP |
383 | else if (pid == 0) { |
384 | ||
385 | const char * const args[] = { | |
386 | KEXEC, "-e", NULL | |
387 | }; | |
388 | ||
cb7ec564 | 389 | /* Child */ |
477def80 | 390 | |
cb7ec564 | 391 | execv(args[0], (char * const *) args); |
477def80 LP |
392 | _exit(EXIT_FAILURE); |
393 | } else | |
820d3acf | 394 | wait_for_terminate_and_warn("kexec", pid, true); |
b1b2a107 | 395 | } |
e61cd186 LP |
396 | |
397 | cmd = RB_AUTOBOOT; | |
477def80 | 398 | /* Fall through */ |
b1b2a107 | 399 | |
477def80 LP |
400 | case RB_AUTOBOOT: |
401 | ||
402 | if (!in_container) { | |
403 | _cleanup_free_ char *param = NULL; | |
404 | ||
27c06cb5 LP |
405 | r = read_one_line_file("/run/systemd/reboot-param", ¶m); |
406 | if (r < 0) | |
407 | log_warning_errno(r, "Failed to read reboot parameter file: %m"); | |
408 | ||
409 | if (!isempty(param)) { | |
477def80 | 410 | log_info("Rebooting with argument '%s'.", param); |
f07756bf | 411 | syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); |
27c06cb5 | 412 | log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m"); |
477def80 LP |
413 | } |
414 | } | |
415 | ||
416 | log_info("Rebooting."); | |
417 | break; | |
418 | ||
419 | case RB_POWER_OFF: | |
420 | log_info("Powering off."); | |
421 | break; | |
422 | ||
423 | case RB_HALT_SYSTEM: | |
424 | log_info("Halting system."); | |
425 | break; | |
426 | ||
427 | default: | |
428 | assert_not_reached("Unknown magic"); | |
429 | } | |
cb7ec564 | 430 | |
477def80 | 431 | reboot(cmd); |
cb7ec564 LP |
432 | if (errno == EPERM && in_container) { |
433 | /* If we are in a container, and we lacked | |
434 | * CAP_SYS_BOOT just exit, this will kill our | |
435 | * container for good. */ | |
477def80 | 436 | log_info("Exiting container."); |
cb7ec564 LP |
437 | exit(0); |
438 | } | |
439 | ||
76ef789d | 440 | r = log_error_errno(errno, "Failed to invoke reboot(): %m"); |
b1b2a107 FF |
441 | |
442 | error: | |
da927ba9 | 443 | log_emergency_errno(r, "Critical error while doing system shutdown: %m"); |
b1b2a107 | 444 | freeze(); |
b1b2a107 | 445 | } |