mirror of
https://github.com/systemd/systemd.git
synced 2024-11-06 16:59:03 +03:00
2e79d1828a
This way, slow IO journald has to wait for can't cause it to reach the killing spree timeout and is hit by SIGKILL in addition to SIGTERM.
445 lines
16 KiB
C
445 lines
16 KiB
C
/***
|
|
This file is part of systemd.
|
|
|
|
Copyright 2010 ProFUSION embedded systems
|
|
|
|
systemd is free software; you can redistribute it and/or modify it
|
|
under the terms of the GNU Lesser General Public License as published by
|
|
the Free Software Foundation; either version 2.1 of the License, or
|
|
(at your option) any later version.
|
|
|
|
systemd is distributed in the hope that it will be useful, but
|
|
WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
Lesser General Public License for more details.
|
|
|
|
You should have received a copy of the GNU Lesser General Public License
|
|
along with systemd; If not, see <http://www.gnu.org/licenses/>.
|
|
***/
|
|
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <linux/reboot.h>
|
|
#include <signal.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <sys/mman.h>
|
|
#include <sys/mount.h>
|
|
#include <sys/reboot.h>
|
|
#include <sys/stat.h>
|
|
#include <unistd.h>
|
|
|
|
#include "alloc-util.h"
|
|
#include "cgroup-util.h"
|
|
#include "def.h"
|
|
#include "fileio.h"
|
|
#include "killall.h"
|
|
#include "log.h"
|
|
#include "missing.h"
|
|
#include "parse-util.h"
|
|
#include "process-util.h"
|
|
#include "string-util.h"
|
|
#include "switch-root.h"
|
|
#include "terminal-util.h"
|
|
#include "umount.h"
|
|
#include "util.h"
|
|
#include "virt.h"
|
|
#include "watchdog.h"
|
|
|
|
#define FINALIZE_ATTEMPTS 50
|
|
|
|
static char* arg_verb;
|
|
static uint8_t arg_exit_code;
|
|
|
|
static int parse_argv(int argc, char *argv[]) {
|
|
enum {
|
|
ARG_LOG_LEVEL = 0x100,
|
|
ARG_LOG_TARGET,
|
|
ARG_LOG_COLOR,
|
|
ARG_LOG_LOCATION,
|
|
ARG_EXIT_CODE,
|
|
};
|
|
|
|
static const struct option options[] = {
|
|
{ "log-level", required_argument, NULL, ARG_LOG_LEVEL },
|
|
{ "log-target", required_argument, NULL, ARG_LOG_TARGET },
|
|
{ "log-color", optional_argument, NULL, ARG_LOG_COLOR },
|
|
{ "log-location", optional_argument, NULL, ARG_LOG_LOCATION },
|
|
{ "exit-code", required_argument, NULL, ARG_EXIT_CODE },
|
|
{}
|
|
};
|
|
|
|
int c, r;
|
|
|
|
assert(argc >= 1);
|
|
assert(argv);
|
|
|
|
/* "-" prevents getopt from permuting argv[] and moving the verb away
|
|
* from argv[1]. Our interface to initrd promises it'll be there. */
|
|
while ((c = getopt_long(argc, argv, "-", options, NULL)) >= 0)
|
|
switch (c) {
|
|
|
|
case ARG_LOG_LEVEL:
|
|
r = log_set_max_level_from_string(optarg);
|
|
if (r < 0)
|
|
log_error("Failed to parse log level %s, ignoring.", optarg);
|
|
|
|
break;
|
|
|
|
case ARG_LOG_TARGET:
|
|
r = log_set_target_from_string(optarg);
|
|
if (r < 0)
|
|
log_error("Failed to parse log target %s, ignoring", optarg);
|
|
|
|
break;
|
|
|
|
case ARG_LOG_COLOR:
|
|
|
|
if (optarg) {
|
|
r = log_show_color_from_string(optarg);
|
|
if (r < 0)
|
|
log_error("Failed to parse log color setting %s, ignoring", optarg);
|
|
} else
|
|
log_show_color(true);
|
|
|
|
break;
|
|
|
|
case ARG_LOG_LOCATION:
|
|
if (optarg) {
|
|
r = log_show_location_from_string(optarg);
|
|
if (r < 0)
|
|
log_error("Failed to parse log location setting %s, ignoring", optarg);
|
|
} else
|
|
log_show_location(true);
|
|
|
|
break;
|
|
|
|
case ARG_EXIT_CODE:
|
|
r = safe_atou8(optarg, &arg_exit_code);
|
|
if (r < 0)
|
|
log_error("Failed to parse exit code %s, ignoring", optarg);
|
|
|
|
break;
|
|
|
|
case '\001':
|
|
if (!arg_verb)
|
|
arg_verb = optarg;
|
|
else
|
|
log_error("Excess arguments, ignoring");
|
|
break;
|
|
|
|
case '?':
|
|
return -EINVAL;
|
|
|
|
default:
|
|
assert_not_reached("Unhandled option code.");
|
|
}
|
|
|
|
if (!arg_verb) {
|
|
log_error("Verb argument missing.");
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int switch_root_initramfs(void) {
|
|
if (mount("/run/initramfs", "/run/initramfs", NULL, MS_BIND, NULL) < 0)
|
|
return log_error_errno(errno, "Failed to mount bind /run/initramfs on /run/initramfs: %m");
|
|
|
|
if (mount(NULL, "/run/initramfs", NULL, MS_PRIVATE, NULL) < 0)
|
|
return log_error_errno(errno, "Failed to make /run/initramfs private mount: %m");
|
|
|
|
/* switch_root with MS_BIND, because there might still be processes lurking around, which have open file descriptors.
|
|
* /run/initramfs/shutdown will take care of these.
|
|
* Also do not detach the old root, because /run/initramfs/shutdown needs to access it.
|
|
*/
|
|
return switch_root("/run/initramfs", "/oldroot", false, MS_BIND);
|
|
}
|
|
|
|
int main(int argc, char *argv[]) {
|
|
bool need_umount, need_swapoff, need_loop_detach, need_dm_detach;
|
|
bool in_container, use_watchdog = false;
|
|
_cleanup_free_ char *cgroup = NULL;
|
|
char *arguments[3];
|
|
unsigned retries;
|
|
int cmd, r;
|
|
static const char* const dirs[] = {SYSTEM_SHUTDOWN_PATH, NULL};
|
|
|
|
log_parse_environment();
|
|
r = parse_argv(argc, argv);
|
|
if (r < 0)
|
|
goto error;
|
|
|
|
/* journald will die if not gone yet. The log target defaults
|
|
* to console, but may have been changed by command line options. */
|
|
|
|
log_close_console(); /* force reopen of /dev/console */
|
|
log_open();
|
|
|
|
umask(0022);
|
|
|
|
if (getpid() != 1) {
|
|
log_error("Not executed by init (PID 1).");
|
|
r = -EPERM;
|
|
goto error;
|
|
}
|
|
|
|
if (streq(arg_verb, "reboot"))
|
|
cmd = RB_AUTOBOOT;
|
|
else if (streq(arg_verb, "poweroff"))
|
|
cmd = RB_POWER_OFF;
|
|
else if (streq(arg_verb, "halt"))
|
|
cmd = RB_HALT_SYSTEM;
|
|
else if (streq(arg_verb, "kexec"))
|
|
cmd = LINUX_REBOOT_CMD_KEXEC;
|
|
else if (streq(arg_verb, "exit"))
|
|
cmd = 0; /* ignored, just checking that arg_verb is valid */
|
|
else {
|
|
r = -EINVAL;
|
|
log_error("Unknown action '%s'.", arg_verb);
|
|
goto error;
|
|
}
|
|
|
|
(void) cg_get_root_path(&cgroup);
|
|
in_container = detect_container() > 0;
|
|
|
|
use_watchdog = !!getenv("WATCHDOG_USEC");
|
|
|
|
/* Lock us into memory */
|
|
mlockall(MCL_CURRENT|MCL_FUTURE);
|
|
|
|
/* Synchronize everything that is not written to disk yet at this point already. This is a good idea so that
|
|
* slow IO is processed here already and the final process killing spree is not impacted by processes
|
|
* desperately trying to sync IO to disk within their timeout. */
|
|
if (!in_container)
|
|
sync();
|
|
|
|
log_info("Sending SIGTERM to remaining processes...");
|
|
broadcast_signal(SIGTERM, true, true);
|
|
|
|
log_info("Sending SIGKILL to remaining processes...");
|
|
broadcast_signal(SIGKILL, true, false);
|
|
|
|
need_umount = !in_container;
|
|
need_swapoff = !in_container;
|
|
need_loop_detach = !in_container;
|
|
need_dm_detach = !in_container;
|
|
|
|
/* Unmount all mountpoints, swaps, and loopback devices */
|
|
for (retries = 0; retries < FINALIZE_ATTEMPTS; retries++) {
|
|
bool changed = false;
|
|
|
|
if (use_watchdog)
|
|
watchdog_ping();
|
|
|
|
/* Let's trim the cgroup tree on each iteration so
|
|
that we leave an empty cgroup tree around, so that
|
|
container managers get a nice notify event when we
|
|
are down */
|
|
if (cgroup)
|
|
cg_trim(SYSTEMD_CGROUP_CONTROLLER, cgroup, false);
|
|
|
|
if (need_umount) {
|
|
log_info("Unmounting file systems.");
|
|
r = umount_all(&changed);
|
|
if (r == 0) {
|
|
need_umount = false;
|
|
log_info("All filesystems unmounted.");
|
|
} else if (r > 0)
|
|
log_info("Not all file systems unmounted, %d left.", r);
|
|
else
|
|
log_error_errno(r, "Failed to unmount file systems: %m");
|
|
}
|
|
|
|
if (need_swapoff) {
|
|
log_info("Deactivating swaps.");
|
|
r = swapoff_all(&changed);
|
|
if (r == 0) {
|
|
need_swapoff = false;
|
|
log_info("All swaps deactivated.");
|
|
} else if (r > 0)
|
|
log_info("Not all swaps deactivated, %d left.", r);
|
|
else
|
|
log_error_errno(r, "Failed to deactivate swaps: %m");
|
|
}
|
|
|
|
if (need_loop_detach) {
|
|
log_info("Detaching loop devices.");
|
|
r = loopback_detach_all(&changed);
|
|
if (r == 0) {
|
|
need_loop_detach = false;
|
|
log_info("All loop devices detached.");
|
|
} else if (r > 0)
|
|
log_info("Not all loop devices detached, %d left.", r);
|
|
else
|
|
log_error_errno(r, "Failed to detach loop devices: %m");
|
|
}
|
|
|
|
if (need_dm_detach) {
|
|
log_info("Detaching DM devices.");
|
|
r = dm_detach_all(&changed);
|
|
if (r == 0) {
|
|
need_dm_detach = false;
|
|
log_info("All DM devices detached.");
|
|
} else if (r > 0)
|
|
log_info("Not all DM devices detached, %d left.", r);
|
|
else
|
|
log_error_errno(r, "Failed to detach DM devices: %m");
|
|
}
|
|
|
|
if (!need_umount && !need_swapoff && !need_loop_detach && !need_dm_detach) {
|
|
if (retries > 0)
|
|
log_info("All filesystems, swaps, loop devices, DM devices detached.");
|
|
/* Yay, done */
|
|
goto initrd_jump;
|
|
}
|
|
|
|
/* If in this iteration we didn't manage to
|
|
* unmount/deactivate anything, we simply give up */
|
|
if (!changed) {
|
|
log_info("Cannot finalize remaining%s%s%s%s continuing.",
|
|
need_umount ? " file systems," : "",
|
|
need_swapoff ? " swap devices," : "",
|
|
need_loop_detach ? " loop devices," : "",
|
|
need_dm_detach ? " DM devices," : "");
|
|
goto initrd_jump;
|
|
}
|
|
|
|
log_debug("After %u retries, couldn't finalize remaining %s%s%s%s trying again.",
|
|
retries + 1,
|
|
need_umount ? " file systems," : "",
|
|
need_swapoff ? " swap devices," : "",
|
|
need_loop_detach ? " loop devices," : "",
|
|
need_dm_detach ? " DM devices," : "");
|
|
}
|
|
|
|
log_error("Too many iterations, giving up.");
|
|
|
|
initrd_jump:
|
|
|
|
arguments[0] = NULL;
|
|
arguments[1] = arg_verb;
|
|
arguments[2] = NULL;
|
|
execute_directories(dirs, DEFAULT_TIMEOUT_USEC, arguments);
|
|
|
|
if (!in_container && !in_initrd() &&
|
|
access("/run/initramfs/shutdown", X_OK) == 0) {
|
|
r = switch_root_initramfs();
|
|
if (r >= 0) {
|
|
argv[0] = (char*) "/shutdown";
|
|
|
|
setsid();
|
|
make_console_stdio();
|
|
|
|
log_info("Successfully changed into root pivot.\n"
|
|
"Returning to initrd...");
|
|
|
|
execv("/shutdown", argv);
|
|
log_error_errno(errno, "Failed to execute shutdown binary: %m");
|
|
} else
|
|
log_error_errno(r, "Failed to switch root to \"/run/initramfs\": %m");
|
|
|
|
}
|
|
|
|
if (need_umount || need_swapoff || need_loop_detach || need_dm_detach)
|
|
log_error("Failed to finalize %s%s%s%s ignoring",
|
|
need_umount ? " file systems," : "",
|
|
need_swapoff ? " swap devices," : "",
|
|
need_loop_detach ? " loop devices," : "",
|
|
need_dm_detach ? " DM devices," : "");
|
|
|
|
/* The kernel will automatically flush ATA disks and suchlike on reboot(), but the file systems need to be
|
|
* sync'ed explicitly in advance. So let's do this here, but not needlessly slow down containers. Note that we
|
|
* sync'ed things already once above, but we did some more work since then which might have caused IO, hence
|
|
* let's doit once more. */
|
|
if (!in_container)
|
|
sync();
|
|
|
|
if (streq(arg_verb, "exit")) {
|
|
if (in_container)
|
|
exit(arg_exit_code);
|
|
else {
|
|
/* We cannot exit() on the host, fallback on another
|
|
* method. */
|
|
cmd = RB_POWER_OFF;
|
|
}
|
|
}
|
|
|
|
switch (cmd) {
|
|
|
|
case LINUX_REBOOT_CMD_KEXEC:
|
|
|
|
if (!in_container) {
|
|
/* We cheat and exec kexec to avoid doing all its work */
|
|
pid_t pid;
|
|
|
|
log_info("Rebooting with kexec.");
|
|
|
|
pid = fork();
|
|
if (pid < 0)
|
|
log_error_errno(errno, "Failed to fork: %m");
|
|
else if (pid == 0) {
|
|
|
|
const char * const args[] = {
|
|
KEXEC, "-e", NULL
|
|
};
|
|
|
|
/* Child */
|
|
|
|
execv(args[0], (char * const *) args);
|
|
_exit(EXIT_FAILURE);
|
|
} else
|
|
wait_for_terminate_and_warn("kexec", pid, true);
|
|
}
|
|
|
|
cmd = RB_AUTOBOOT;
|
|
/* Fall through */
|
|
|
|
case RB_AUTOBOOT:
|
|
|
|
if (!in_container) {
|
|
_cleanup_free_ char *param = NULL;
|
|
|
|
r = read_one_line_file("/run/systemd/reboot-param", ¶m);
|
|
if (r < 0)
|
|
log_warning_errno(r, "Failed to read reboot parameter file: %m");
|
|
|
|
if (!isempty(param)) {
|
|
log_info("Rebooting with argument '%s'.", param);
|
|
syscall(SYS_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2, LINUX_REBOOT_CMD_RESTART2, param);
|
|
log_warning_errno(errno, "Failed to reboot with parameter, retrying without: %m");
|
|
}
|
|
}
|
|
|
|
log_info("Rebooting.");
|
|
break;
|
|
|
|
case RB_POWER_OFF:
|
|
log_info("Powering off.");
|
|
break;
|
|
|
|
case RB_HALT_SYSTEM:
|
|
log_info("Halting system.");
|
|
break;
|
|
|
|
default:
|
|
assert_not_reached("Unknown magic");
|
|
}
|
|
|
|
reboot(cmd);
|
|
if (errno == EPERM && in_container) {
|
|
/* If we are in a container, and we lacked
|
|
* CAP_SYS_BOOT just exit, this will kill our
|
|
* container for good. */
|
|
log_info("Exiting container.");
|
|
exit(0);
|
|
}
|
|
|
|
r = log_error_errno(errno, "Failed to invoke reboot(): %m");
|
|
|
|
error:
|
|
log_emergency_errno(r, "Critical error while doing system shutdown: %m");
|
|
freeze();
|
|
}
|