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