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