]>
Commit | Line | Data |
---|---|---|
663996b3 MS |
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 | |
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 | |
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 | |
16 | Lesser General Public License for more details. | |
17 | ||
18 | You should have received a copy of the GNU Lesser General Public License | |
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> | |
663996b3 MS |
27 | #include <sys/stat.h> |
28 | #include <sys/mount.h> | |
29 | #include <sys/syscall.h> | |
30 | #include <fcntl.h> | |
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> | |
60f067b4 | 38 | #include <getopt.h> |
663996b3 MS |
39 | |
40 | #include "missing.h" | |
41 | #include "log.h" | |
14228c0d | 42 | #include "fileio.h" |
663996b3 MS |
43 | #include "umount.h" |
44 | #include "util.h" | |
45 | #include "mkdir.h" | |
46 | #include "virt.h" | |
47 | #include "watchdog.h" | |
48 | #include "killall.h" | |
60f067b4 JS |
49 | #include "cgroup-util.h" |
50 | #include "def.h" | |
5eef597e | 51 | #include "switch-root.h" |
663996b3 MS |
52 | |
53 | #define FINALIZE_ATTEMPTS 50 | |
54 | ||
60f067b4 JS |
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 | ||
f47781d8 MP |
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) | |
60f067b4 JS |
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 | ||
f47781d8 MP |
118 | case '\001': |
119 | if (!arg_verb) | |
120 | arg_verb = optarg; | |
121 | else | |
122 | log_error("Excess arguments, ignoring"); | |
123 | break; | |
124 | ||
60f067b4 | 125 | case '?': |
60f067b4 JS |
126 | return -EINVAL; |
127 | ||
128 | default: | |
129 | assert_not_reached("Unhandled option code."); | |
130 | } | |
131 | ||
f47781d8 | 132 | if (!arg_verb) { |
60f067b4 JS |
133 | log_error("Verb argument missing."); |
134 | return -EINVAL; | |
135 | } | |
136 | ||
60f067b4 JS |
137 | return 0; |
138 | } | |
139 | ||
5eef597e | 140 | static int switch_root_initramfs(void) { |
f47781d8 MP |
141 | if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) |
142 | return log_error_errno(errno, "Failed to mount bind /run/initramfs on /run/initramfs: %m"); | |
663996b3 | 143 | |
f47781d8 MP |
144 | if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) |
145 | return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m"); | |
663996b3 | 146 | |
5eef597e MP |
147 | /* switch_root with MS_BIND, because there might still be processes lurking around, which have open file desriptors. |
148 | * /run/initramfs/shutdown will take care of these. | |
149 | * Also do not detach the old root, because /run/initramfs/shutdown needs to access it. | |
150 | */ | |
151 | return switch_root("/run/initramfs", "/oldroot", false, MS_BIND); | |
663996b3 MS |
152 | } |
153 | ||
663996b3 MS |
154 | |
155 | int main(int argc, char *argv[]) { | |
e842803a | 156 | bool need_umount, need_swapoff, need_loop_detach, need_dm_detach; |
663996b3 | 157 | bool in_container, use_watchdog = false; |
60f067b4 | 158 | _cleanup_free_ char *cgroup = NULL; |
663996b3 | 159 | char *arguments[3]; |
60f067b4 JS |
160 | unsigned retries; |
161 | int cmd, r; | |
663996b3 | 162 | |
60f067b4 JS |
163 | log_parse_environment(); |
164 | r = parse_argv(argc, argv); | |
165 | if (r < 0) | |
166 | goto error; | |
14228c0d | 167 | |
60f067b4 | 168 | /* journald will die if not gone yet. The log target defaults |
f47781d8 | 169 | * to console, but may have been changed by command line options. */ |
14228c0d | 170 | |
60f067b4 | 171 | log_close_console(); /* force reopen of /dev/console */ |
663996b3 MS |
172 | log_open(); |
173 | ||
174 | umask(0022); | |
175 | ||
176 | if (getpid() != 1) { | |
60f067b4 | 177 | log_error("Not executed by init (PID 1)."); |
663996b3 MS |
178 | r = -EPERM; |
179 | goto error; | |
180 | } | |
181 | ||
60f067b4 | 182 | if (streq(arg_verb, "reboot")) |
663996b3 | 183 | cmd = RB_AUTOBOOT; |
60f067b4 | 184 | else if (streq(arg_verb, "poweroff")) |
663996b3 | 185 | cmd = RB_POWER_OFF; |
60f067b4 | 186 | else if (streq(arg_verb, "halt")) |
663996b3 | 187 | cmd = RB_HALT_SYSTEM; |
60f067b4 | 188 | else if (streq(arg_verb, "kexec")) |
663996b3 MS |
189 | cmd = LINUX_REBOOT_CMD_KEXEC; |
190 | else { | |
663996b3 | 191 | r = -EINVAL; |
60f067b4 | 192 | log_error("Unknown action '%s'.", arg_verb); |
663996b3 MS |
193 | goto error; |
194 | } | |
195 | ||
60f067b4 JS |
196 | cg_get_root_path(&cgroup); |
197 | ||
663996b3 MS |
198 | use_watchdog = !!getenv("WATCHDOG_USEC"); |
199 | ||
200 | /* lock us into memory */ | |
201 | mlockall(MCL_CURRENT|MCL_FUTURE); | |
202 | ||
203 | log_info("Sending SIGTERM to remaining processes..."); | |
60f067b4 | 204 | broadcast_signal(SIGTERM, true, true); |
663996b3 MS |
205 | |
206 | log_info("Sending SIGKILL to remaining processes..."); | |
60f067b4 | 207 | broadcast_signal(SIGKILL, true, false); |
663996b3 | 208 | |
e842803a MB |
209 | in_container = detect_container(NULL) > 0; |
210 | ||
5eef597e | 211 | need_umount = !in_container; |
e842803a MB |
212 | need_swapoff = !in_container; |
213 | need_loop_detach = !in_container; | |
214 | need_dm_detach = !in_container; | |
663996b3 MS |
215 | |
216 | /* Unmount all mountpoints, swaps, and loopback devices */ | |
217 | for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) { | |
218 | bool changed = false; | |
219 | ||
220 | if (use_watchdog) | |
221 | watchdog_ping(); | |
222 | ||
60f067b4 JS |
223 | /* Let's trim the cgroup tree on each iteration so |
224 | that we leave an empty cgroup tree around, so that | |
225 | container managers get a nice notify event when we | |
226 | are down */ | |
227 | if (cgroup) | |
228 | cg_trim(SYSTEMD_CGROUP_CONTROLLER, cgroup, false); | |
229 | ||
663996b3 MS |
230 | if (need_umount) { |
231 | log_info("Unmounting file systems."); | |
232 | r = umount_all(&changed); | |
233 | if (r == 0) { | |
234 | need_umount = false; | |
235 | log_info("All filesystems unmounted."); | |
236 | } else if (r > 0) | |
237 | log_info("Not all file systems unmounted, %d left.", r); | |
238 | else | |
f47781d8 | 239 | log_error_errno(r, "Failed to unmount file systems: %m"); |
663996b3 MS |
240 | } |
241 | ||
242 | if (need_swapoff) { | |
243 | log_info("Deactivating swaps."); | |
244 | r = swapoff_all(&changed); | |
245 | if (r == 0) { | |
246 | need_swapoff = false; | |
247 | log_info("All swaps deactivated."); | |
248 | } else if (r > 0) | |
249 | log_info("Not all swaps deactivated, %d left.", r); | |
250 | else | |
f47781d8 | 251 | log_error_errno(r, "Failed to deactivate swaps: %m"); |
663996b3 MS |
252 | } |
253 | ||
254 | if (need_loop_detach) { | |
255 | log_info("Detaching loop devices."); | |
256 | r = loopback_detach_all(&changed); | |
257 | if (r == 0) { | |
258 | need_loop_detach = false; | |
259 | log_info("All loop devices detached."); | |
260 | } else if (r > 0) | |
261 | log_info("Not all loop devices detached, %d left.", r); | |
262 | else | |
f47781d8 | 263 | log_error_errno(r, "Failed to detach loop devices: %m"); |
663996b3 MS |
264 | } |
265 | ||
266 | if (need_dm_detach) { | |
267 | log_info("Detaching DM devices."); | |
268 | r = dm_detach_all(&changed); | |
269 | if (r == 0) { | |
270 | need_dm_detach = false; | |
271 | log_info("All DM devices detached."); | |
272 | } else if (r > 0) | |
273 | log_info("Not all DM devices detached, %d left.", r); | |
274 | else | |
f47781d8 | 275 | log_error_errno(r, "Failed to detach DM devices: %m"); |
663996b3 MS |
276 | } |
277 | ||
278 | if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) { | |
279 | if (retries > 0) | |
280 | log_info("All filesystems, swaps, loop devices, DM devices detached."); | |
281 | /* Yay, done */ | |
e842803a | 282 | goto initrd_jump; |
663996b3 MS |
283 | } |
284 | ||
285 | /* If in this iteration we didn't manage to | |
286 | * unmount/deactivate anything, we simply give up */ | |
287 | if (!changed) { | |
e842803a MB |
288 | log_info("Cannot finalize remaining%s%s%s%s continuing.", |
289 | need_umount ? " file systems," : "", | |
290 | need_swapoff ? " swap devices," : "", | |
291 | need_loop_detach ? " loop devices," : "", | |
292 | need_dm_detach ? " DM devices," : ""); | |
293 | goto initrd_jump; | |
663996b3 MS |
294 | } |
295 | ||
e842803a MB |
296 | log_debug("After %u retries, couldn't finalize remaining %s%s%s%s trying again.", |
297 | retries + 1, | |
298 | need_umount ? " file systems," : "", | |
299 | need_swapoff ? " swap devices," : "", | |
300 | need_loop_detach ? " loop devices," : "", | |
301 | need_dm_detach ? " DM devices," : ""); | |
663996b3 MS |
302 | } |
303 | ||
e842803a MB |
304 | log_error("Too many iterations, giving up."); |
305 | ||
306 | initrd_jump: | |
663996b3 MS |
307 | |
308 | arguments[0] = NULL; | |
60f067b4 | 309 | arguments[1] = arg_verb; |
663996b3 | 310 | arguments[2] = NULL; |
60f067b4 | 311 | execute_directory(SYSTEM_SHUTDOWN_PATH, NULL, DEFAULT_TIMEOUT_USEC, arguments); |
663996b3 MS |
312 | |
313 | if (!in_container && !in_initrd() && | |
314 | access("/run/initramfs/shutdown", X_OK) == 0) { | |
5eef597e MP |
315 | r = switch_root_initramfs(); |
316 | if (r >= 0) { | |
317 | argv[0] = (char*) "/shutdown"; | |
663996b3 | 318 | |
5eef597e MP |
319 | setsid(); |
320 | make_console_stdio(); | |
663996b3 | 321 | |
5eef597e MP |
322 | log_info("Successfully changed into root pivot.\n" |
323 | "Returning to initrd..."); | |
663996b3 | 324 | |
5eef597e | 325 | execv("/shutdown", argv); |
f47781d8 | 326 | log_error_errno(errno, "Failed to execute shutdown binary: %m"); |
5eef597e | 327 | } else |
f47781d8 | 328 | log_error_errno(r, "Failed to switch root to \"/run/initramfs\": %m"); |
5eef597e | 329 | |
663996b3 MS |
330 | } |
331 | ||
e842803a MB |
332 | if (need_umount || need_swapoff || need_loop_detach || need_dm_detach) |
333 | log_error("Failed to finalize %s%s%s%s ignoring", | |
334 | need_umount ? " file systems," : "", | |
335 | need_swapoff ? " swap devices," : "", | |
336 | need_loop_detach ? " loop devices," : "", | |
337 | need_dm_detach ? " DM devices," : ""); | |
338 | ||
663996b3 MS |
339 | /* The kernel will automaticall flush ATA disks and suchlike |
340 | * on reboot(), but the file systems need to be synce'd | |
341 | * explicitly in advance. So let's do this here, but not | |
342 | * needlessly slow down containers. */ | |
343 | if (!in_container) | |
344 | sync(); | |
345 | ||
60f067b4 JS |
346 | switch (cmd) { |
347 | ||
348 | case LINUX_REBOOT_CMD_KEXEC: | |
663996b3 MS |
349 | |
350 | if (!in_container) { | |
351 | /* We cheat and exec kexec to avoid doing all its work */ | |
60f067b4 | 352 | pid_t pid; |
663996b3 | 353 | |
60f067b4 JS |
354 | log_info("Rebooting with kexec."); |
355 | ||
356 | pid = fork(); | |
663996b3 | 357 | if (pid < 0) |
f47781d8 | 358 | log_error_errno(errno, "Failed to fork: %m"); |
60f067b4 JS |
359 | else if (pid == 0) { |
360 | ||
361 | const char * const args[] = { | |
362 | KEXEC, "-e", NULL | |
363 | }; | |
364 | ||
663996b3 | 365 | /* Child */ |
60f067b4 | 366 | |
663996b3 | 367 | execv(args[0], (char * const *) args); |
60f067b4 JS |
368 | _exit(EXIT_FAILURE); |
369 | } else | |
f47781d8 | 370 | wait_for_terminate_and_warn("kexec", pid, true); |
663996b3 MS |
371 | } |
372 | ||
373 | cmd = RB_AUTOBOOT; | |
60f067b4 JS |
374 | /* Fall through */ |
375 | ||
376 | case RB_AUTOBOOT: | |
377 | ||
378 | if (!in_container) { | |
379 | _cleanup_free_ char *param = NULL; | |
380 | ||
381 | if (read_one_line_file(REBOOT_PARAM_FILE, ¶m) >= 0) { | |
382 | log_info("Rebooting with argument '%s'.", param); | |
5eef597e | 383 | syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param); |
60f067b4 JS |
384 | } |
385 | } | |
386 | ||
387 | log_info("Rebooting."); | |
388 | break; | |
389 | ||
390 | case RB_POWER_OFF: | |
391 | log_info("Powering off."); | |
392 | break; | |
393 | ||
394 | case RB_HALT_SYSTEM: | |
395 | log_info("Halting system."); | |
396 | break; | |
397 | ||
398 | default: | |
399 | assert_not_reached("Unknown magic"); | |
663996b3 MS |
400 | } |
401 | ||
402 | reboot(cmd); | |
663996b3 MS |
403 | if (errno == EPERM && in_container) { |
404 | /* If we are in a container, and we lacked | |
405 | * CAP_SYS_BOOT just exit, this will kill our | |
406 | * container for good. */ | |
60f067b4 | 407 | log_info("Exiting container."); |
663996b3 MS |
408 | exit(0); |
409 | } | |
410 | ||
f47781d8 | 411 | log_error_errno(errno, "Failed to invoke reboot(): %m"); |
663996b3 MS |
412 | r = -errno; |
413 | ||
414 | error: | |
f47781d8 | 415 | log_emergency_errno(r, "Critical error while doing system shutdown: %m"); |
663996b3 MS |
416 | |
417 | freeze(); | |
663996b3 | 418 | } |