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