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