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