chiark / gitweb /
shutdown: in containers, invoke reboot(2), too. Then fallback to exit() if CAP_SYS_BO...
[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 "umount.h"
43 #include "util.h"
44 #include "mkdir.h"
45 #include "virt.h"
46 #include "watchdog.h"
47 #include "killall.h"
48
49 #define FINALIZE_ATTEMPTS 50
50
51 static int prepare_new_root(void) {
52         static const char dirs[] =
53                 "/run/initramfs/oldroot\0"
54                 "/run/initramfs/proc\0"
55                 "/run/initramfs/sys\0"
56                 "/run/initramfs/dev\0"
57                 "/run/initramfs/run\0";
58
59         const char *dir;
60
61         if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0) {
62                 log_error("Failed to mount bind /run/initramfs on /run/initramfs: %m");
63                 return -errno;
64         }
65
66         if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0) {
67                 log_error("Failed to make /run/initramfs private mount: %m");
68                 return -errno;
69         }
70
71         NULSTR_FOREACH(dir, dirs)
72                 if (mkdir_p_label(dir, 0755) < 0 && errno != EEXIST) {
73                         log_error("Failed to mkdir %s: %m", dir);
74                         return -errno;
75                 }
76
77         if (mount("/sys", "/run/initramfs/sys", NULL, MS_BIND, NULL) < 0) {
78                 log_error("Failed to mount bind /sys on /run/initramfs/sys: %m");
79                 return -errno;
80         }
81
82         if (mount("/proc", "/run/initramfs/proc", NULL, MS_BIND, NULL) < 0) {
83                 log_error("Failed to mount bind /proc on /run/initramfs/proc: %m");
84                 return -errno;
85         }
86
87         if (mount("/dev", "/run/initramfs/dev", NULL, MS_BIND, NULL) < 0) {
88                 log_error("Failed to mount bind /dev on /run/initramfs/dev: %m");
89                 return -errno;
90         }
91
92         if (mount("/run", "/run/initramfs/run", NULL, MS_BIND, NULL) < 0) {
93                 log_error("Failed to mount bind /run on /run/initramfs/run: %m");
94                 return -errno;
95         }
96
97         return 0;
98 }
99
100 static int pivot_to_new_root(void) {
101
102         if (chdir("/run/initramfs") < 0) {
103                 log_error("Failed to change directory to /run/initramfs: %m");
104                 return -errno;
105         }
106
107         /* Work-around for a kernel bug: for some reason the kernel
108          * refuses switching root if any file systems are mounted
109          * MS_SHARED. Hence remount them MS_PRIVATE here as a
110          * work-around.
111          *
112          * https://bugzilla.redhat.com/show_bug.cgi?id=847418 */
113         if (mount(NULL, "/", NULL, MS_REC|MS_PRIVATE, NULL) < 0)
114                 log_warning("Failed to make \"/\" private mount: %m");
115
116         if (pivot_root(".", "oldroot") < 0) {
117                 log_error("pivot failed: %m");
118                 /* only chroot if pivot root succeeded */
119                 return -errno;
120         }
121
122         chroot(".");
123
124         setsid();
125         make_console_stdio();
126
127         log_info("Successfully changed into root pivot.");
128
129         return 0;
130 }
131
132 int main(int argc, char *argv[]) {
133         int cmd, r;
134         unsigned retries;
135         bool need_umount = true, need_swapoff = true, need_loop_detach = true, need_dm_detach = true;
136         bool in_container, use_watchdog = false;
137         char *arguments[3];
138
139         log_parse_environment();
140         log_set_target(LOG_TARGET_CONSOLE); /* syslog will die if not gone yet */
141         log_open();
142
143         umask(0022);
144
145         if (getpid() != 1) {
146                 log_error("Not executed by init (pid 1).");
147                 r = -EPERM;
148                 goto error;
149         }
150
151         if (argc != 2) {
152                 log_error("Invalid number of arguments.");
153                 r = -EINVAL;
154                 goto error;
155         }
156
157         in_container = detect_container(NULL) > 0;
158
159         if (streq(argv[1], "reboot"))
160                 cmd = RB_AUTOBOOT;
161         else if (streq(argv[1], "poweroff"))
162                 cmd = RB_POWER_OFF;
163         else if (streq(argv[1], "halt"))
164                 cmd = RB_HALT_SYSTEM;
165         else if (streq(argv[1], "kexec"))
166                 cmd = LINUX_REBOOT_CMD_KEXEC;
167         else {
168                 log_error("Unknown action '%s'.", argv[1]);
169                 r = -EINVAL;
170                 goto error;
171         }
172
173         use_watchdog = !!getenv("WATCHDOG_USEC");
174
175         /* lock us into memory */
176         mlockall(MCL_CURRENT|MCL_FUTURE);
177
178         log_info("Sending SIGTERM to remaining processes...");
179         broadcast_signal(SIGTERM, true);
180
181         log_info("Sending SIGKILL to remaining processes...");
182         broadcast_signal(SIGKILL, true);
183
184         if (in_container) {
185                 need_swapoff = false;
186                 need_dm_detach = false;
187                 need_loop_detach = false;
188         }
189
190         /* Unmount all mountpoints, swaps, and loopback devices */
191         for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) {
192                 bool changed = false;
193
194                 if (use_watchdog)
195                         watchdog_ping();
196
197                 if (need_umount) {
198                         log_info("Unmounting file systems.");
199                         r = umount_all(&changed);
200                         if (r == 0)
201                                 need_umount = false;
202                         else if (r > 0)
203                                 log_info("Not all file systems unmounted, %d left.", r);
204                         else
205                                 log_error("Failed to unmount file systems: %s", strerror(-r));
206                 }
207
208                 if (need_swapoff) {
209                         log_info("Disabling swaps.");
210                         r = swapoff_all(&changed);
211                         if (r == 0)
212                                 need_swapoff = false;
213                         else if (r > 0)
214                                 log_info("Not all swaps are turned off, %d left.", r);
215                         else
216                                 log_error("Failed to turn off swaps: %s", strerror(-r));
217                 }
218
219                 if (need_loop_detach) {
220                         log_info("Detaching loop devices.");
221                         r = loopback_detach_all(&changed);
222                         if (r == 0)
223                                 need_loop_detach = false;
224                         else if (r > 0)
225                                 log_info("Not all loop devices detached, %d left.", r);
226                         else
227                                 log_error("Failed to detach loop devices: %s", strerror(-r));
228                 }
229
230                 if (need_dm_detach) {
231                         log_info("Detaching DM devices.");
232                         r = dm_detach_all(&changed);
233                         if (r == 0)
234                                 need_dm_detach = false;
235                         else if (r > 0)
236                                 log_warning("Not all DM devices detached, %d left.", r);
237                         else
238                                 log_error("Failed to detach DM devices: %s", strerror(-r));
239                 }
240
241                 if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) {
242                         if (retries > 0)
243                                 log_info("All filesystems, swaps, loop devices, DM devices detached.");
244                         /* Yay, done */
245                         break;
246                 }
247
248                 /* If in this iteration we didn't manage to
249                  * unmount/deactivate anything, we simply give up */
250                 if (!changed) {
251                         log_error("Cannot finalize remaining file systems and devices, giving up.");
252                         break;
253                 }
254
255                 log_debug("Couldn't finalize remaining file systems and devices after %u retries, trying again.", retries+1);
256         }
257
258         if (retries >= FINALIZE_ATTEMPTS)
259                 log_error("Too many iterations, giving up.");
260
261         arguments[0] = NULL;
262         arguments[1] = argv[1];
263         arguments[2] = NULL;
264         execute_directory(SYSTEM_SHUTDOWN_PATH, NULL, arguments);
265
266         if (!in_container &&
267             access("/run/initramfs/shutdown", X_OK) == 0) {
268
269                 if (prepare_new_root() >= 0 &&
270                     pivot_to_new_root() >= 0) {
271                         execv("/shutdown", argv);
272                         log_error("Failed to execute shutdown binary: %m");
273                 }
274         }
275
276         if (cmd == LINUX_REBOOT_CMD_KEXEC) {
277
278                 if (!in_container) {
279                         /* We cheat and exec kexec to avoid doing all its work */
280                         pid_t pid = fork();
281
282                         if (pid < 0)
283                                 log_error("Could not fork: %m. Falling back to normal reboot.");
284                         else if (pid > 0) {
285                                 wait_for_terminate_and_warn("kexec", pid);
286                                 log_warning("kexec failed. Falling back to normal reboot.");
287                         } else {
288                                 /* Child */
289                                 const char *args[3] = { "/sbin/kexec", "-e", NULL };
290                                 execv(args[0], (char * const *) args);
291                                 return EXIT_FAILURE;
292                         }
293                 }
294
295                 cmd = RB_AUTOBOOT;
296         }
297
298         reboot(cmd);
299
300         if (errno == EPERM && in_container) {
301                 /* If we are in a container, and we lacked
302                  * CAP_SYS_BOOT just exit, this will kill our
303                  * container for good. */
304                 log_error("Exiting container.");
305                 exit(0);
306         }
307
308         log_error("Failed to invoke reboot(): %m");
309         r = -errno;
310
311   error:
312         log_error("Critical error while doing system shutdown: %s", strerror(-r));
313
314         freeze();
315         return EXIT_FAILURE;
316 }