chiark / gitweb /
Support additional argument in reboot
[elogind.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 <sys/mman.h>
23 #include <sys/types.h>
24 #include <sys/reboot.h>
25 #include <linux/reboot.h>
26 #include <sys/wait.h>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <sys/mount.h>
30 #include <sys/syscall.h>
31 #include <fcntl.h>
32 #include <dirent.h>
33 #include <errno.h>
34 #include <unistd.h>
35 #include <signal.h>
36 #include <stdbool.h>
37 #include <stdlib.h>
38 #include <string.h>
39
40 #include "missing.h"
41 #include "log.h"
42 #include "fileio.h"
43 #include "umount.h"
44 #include "util.h"
45 #include "mkdir.h"
46 #include "virt.h"
47 #include "watchdog.h"
48 #include "killall.h"
49 #include "cgroup-util.h"
50 #include "def.h"
51
52 #define FINALIZE_ATTEMPTS 50
53
54 static int prepare_new_root(void) {
55         static const char dirs[] =
56                 "/run/initramfs/oldroot\0"
57                 "/run/initramfs/proc\0"
58                 "/run/initramfs/sys\0"
59                 "/run/initramfs/dev\0"
60                 "/run/initramfs/run\0";
61
62         const char *dir;
63
64         if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) {
65                 log_error("Failed to mount bind /run/initramfs on /run/initramfs: %m");
66                 return -errno;
67         }
68
69         if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) {
70                 log_error("Failed to make /run/initramfs private mount: %m");
71                 return -errno;
72         }
73
74         NULSTR_FOREACH(dir, dirs)
75                 if (mkdir_p_label(dir, 0755) < 0 && errno != EEXIST) {
76                         log_error("Failed to mkdir %s: %m", dir);
77                         return -errno;
78                 }
79
80         if (mount("/sys", "/run/initramfs/sys", NULL, MS_BIND, NULL) < 0) {
81                 log_error("Failed to mount bind /sys on /run/initramfs/sys: %m");
82                 return -errno;
83         }
84
85         if (mount("/proc", "/run/initramfs/proc", NULL, MS_BIND, NULL) < 0) {
86                 log_error("Failed to mount bind /proc on /run/initramfs/proc: %m");
87                 return -errno;
88         }
89
90         if (mount("/dev", "/run/initramfs/dev", NULL, MS_BIND, NULL) < 0) {
91                 log_error("Failed to mount bind /dev on /run/initramfs/dev: %m");
92                 return -errno;
93         }
94
95         if (mount("/run", "/run/initramfs/run", NULL, MS_BIND, NULL) < 0) {
96                 log_error("Failed to mount bind /run on /run/initramfs/run: %m");
97                 return -errno;
98         }
99
100         return 0;
101 }
102
103 static int pivot_to_new_root(void) {
104
105         if (chdir("/run/initramfs") < 0) {
106                 log_error("Failed to change directory to /run/initramfs: %m");
107                 return -errno;
108         }
109
110         /* Work-around for a kernel bug: for some reason the kernel
111          * refuses switching root if any file systems are mounted
112          * MS_SHARED. Hence remount them MS_PRIVATE here as a
113          * work-around.
114          *
115          * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
116         if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
117                 log_warning("Failed to make \"/\" private mount: %m");
118
119         if (pivot_root(".", "oldroot") < 0) {
120                 log_error("pivot failed: %m");
121                 /* only chroot if pivot root succeeded */
122                 return -errno;
123         }
124
125         chroot(".");
126
127         setsid();
128         make_console_stdio();
129
130         log_info("Successfully changed into root pivot.");
131
132         return 0;
133 }
134
135 int main(int argc, char *argv[]) {
136         bool need_umount = true, need_swapoff = true, need_loop_detach = true, need_dm_detach = true;
137         bool in_container, use_watchdog = false;
138         _cleanup_free_ char *line = NULL, *cgroup = NULL, *param = NULL;
139         char *arguments[3];
140         unsigned retries;
141         int cmd, r;
142
143         /* suppress shutdown status output if 'quiet' is used  */
144         r = proc_cmdline(&line);
145         if (r > 0) {
146                 char *w, *state;
147                 size_t l;
148
149                 FOREACH_WORD_QUOTED(w, l, line, state) {
150                         if (l == 5 && memcmp(w, "quiet", 5) == 0) {
151                                 log_set_max_level(LOG_WARNING);
152                                 break;
153                         }
154                 }
155         }
156
157         log_parse_environment();
158         log_set_target(LOG_TARGET_CONSOLE); /* syslog will die if not gone yet */
159         log_open();
160
161         umask(0022);
162
163         if (getpid() != 1) {
164                 log_error("Not executed by init (pid 1).");
165                 r = -EPERM;
166                 goto error;
167         }
168
169         if (argc != 2) {
170                 log_error("Invalid number of arguments.");
171                 r = -EINVAL;
172                 goto error;
173         }
174
175         in_container = detect_container(NULL) > 0;
176
177         if (streq(argv[1], "reboot")) {
178                 cmd = RB_AUTOBOOT;
179                 /* if this fails, that's OK */
180                 read_one_line_file(REBOOT_PARAM_FILE, &param);
181         } else if (streq(argv[1], "poweroff"))
182                 cmd = RB_POWER_OFF;
183         else if (streq(argv[1], "halt"))
184                 cmd = RB_HALT_SYSTEM;
185         else if (streq(argv[1], "kexec"))
186                 cmd = LINUX_REBOOT_CMD_KEXEC;
187         else {
188                 log_error("Unknown action '%s'.", argv[1]);
189                 r = -EINVAL;
190                 goto error;
191         }
192
193         cg_get_root_path(&cgroup);
194
195         use_watchdog = !!getenv("WATCHDOG_USEC");
196
197         /* lock us into memory */
198         mlockall(MCL_CURRENT|MCL_FUTURE);
199
200         log_info("Sending SIGTERM to remaining processes...");
201         broadcast_signal(SIGTERM, true);
202
203         log_info("Sending SIGKILL to remaining processes...");
204         broadcast_signal(SIGKILL, true);
205
206         if (in_container) {
207                 need_swapoff = false;
208                 need_dm_detach = false;
209                 need_loop_detach = false;
210         }
211
212         /* Unmount all mountpoints, swaps, and loopback devices */
213         for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) {
214                 bool changed = false;
215
216                 if (use_watchdog)
217                         watchdog_ping();
218
219                 /* Let's trim the cgroup tree on each iteration so
220                    that we leave an empty cgroup tree around, so that
221                    container managers get a nice notify event when we
222                    are down */
223                 if (cgroup)
224                         cg_trim(SYSTEMD_CGROUP_CONTROLLER, cgroup, false);
225
226                 if (need_umount) {
227                         log_info("Unmounting file systems.");
228                         r = umount_all(&changed);
229                         if (r == 0) {
230                                 need_umount = false;
231                                 log_info("All filesystems unmounted.");
232                         } else if (r > 0)
233                                 log_info("Not all file systems unmounted, %d left.", r);
234                         else
235                                 log_error("Failed to unmount file systems: %s", strerror(-r));
236                 }
237
238                 if (need_swapoff) {
239                         log_info("Deactivating swaps.");
240                         r = swapoff_all(&changed);
241                         if (r == 0) {
242                                 need_swapoff = false;
243                                 log_info("All swaps deactivated.");
244                         } else if (r > 0)
245                                 log_info("Not all swaps deactivated, %d left.", r);
246                         else
247                                 log_error("Failed to deactivate swaps: %s", strerror(-r));
248                 }
249
250                 if (need_loop_detach) {
251                         log_info("Detaching loop devices.");
252                         r = loopback_detach_all(&changed);
253                         if (r == 0) {
254                                 need_loop_detach = false;
255                                 log_info("All loop devices detached.");
256                         } else if (r > 0)
257                                 log_info("Not all loop devices detached, %d left.", r);
258                         else
259                                 log_error("Failed to detach loop devices: %s", strerror(-r));
260                 }
261
262                 if (need_dm_detach) {
263                         log_info("Detaching DM devices.");
264                         r = dm_detach_all(&changed);
265                         if (r == 0) {
266                                 need_dm_detach = false;
267                                 log_info("All DM devices detached.");
268                         } else if (r > 0)
269                                 log_info("Not all DM devices detached, %d left.", r);
270                         else
271                                 log_error("Failed to detach DM devices: %s", strerror(-r));
272                 }
273
274                 if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) {
275                         if (retries > 0)
276                                 log_info("All filesystems, swaps, loop devices, DM devices detached.");
277                         /* Yay, done */
278                         break;
279                 }
280
281                 /* If in this iteration we didn't manage to
282                  * unmount/deactivate anything, we simply give up */
283                 if (!changed) {
284                         log_error("Cannot finalize remaining file systems and devices, giving up.");
285                         break;
286                 }
287
288                 log_debug("Couldn't finalize remaining file systems and devices after %u retries, trying again.", retries+1);
289         }
290
291         if (retries >= FINALIZE_ATTEMPTS)
292                 log_error("Too many iterations, giving up.");
293         else
294                 log_info("Storage is finalized.");
295
296         arguments[0] = NULL;
297         arguments[1] = argv[1];
298         arguments[2] = NULL;
299         execute_directory(SYSTEM_SHUTDOWN_PATH, NULL, arguments);
300
301         if (!in_container && !in_initrd() &&
302             access("/run/initramfs/shutdown", X_OK) == 0) {
303
304                 if (prepare_new_root() >= 0 &&
305                     pivot_to_new_root() >= 0) {
306
307                         log_info("Returning to initrd...");
308
309                         execv("/shutdown", argv);
310                         log_error("Failed to execute shutdown binary: %m");
311                 }
312         }
313
314         /* The kernel will automaticall flush ATA disks and suchlike
315          * on reboot(), but the file systems need to be synce'd
316          * explicitly in advance. So let's do this here, but not
317          * needlessly slow down containers. */
318         if (!in_container)
319                 sync();
320
321         if (cmd == LINUX_REBOOT_CMD_KEXEC) {
322
323                 if (!in_container) {
324                         /* We cheat and exec kexec to avoid doing all its work */
325                         pid_t pid = fork();
326
327                         if (pid < 0)
328                                 log_error("Could not fork: %m. Falling back to normal reboot.");
329                         else if (pid > 0) {
330                                 wait_for_terminate_and_warn("kexec", pid);
331                                 log_warning("kexec failed. Falling back to normal reboot.");
332                         } else {
333                                 /* Child */
334                                 const char *args[3] = { KEXEC, "-e", NULL };
335                                 execv(args[0], (char * const *) args);
336                                 return EXIT_FAILURE;
337                         }
338                 }
339
340                 cmd = RB_AUTOBOOT;
341         }
342
343         if (param)
344                 syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
345                         LINUX_REBOOT_CMD_RESTART2, param);
346         else
347                 reboot(cmd);
348
349         if (errno == EPERM && in_container) {
350                 /* If we are in a container, and we lacked
351                  * CAP_SYS_BOOT just exit, this will kill our
352                  * container for good. */
353                 log_error("Exiting container.");
354                 exit(0);
355         }
356
357         log_error("Failed to invoke reboot(): %m");
358         r = -errno;
359
360   error:
361         log_error("Critical error while doing system shutdown: %s", strerror(-r));
362
363         freeze();
364         return EXIT_FAILURE;
365 }