Systemd/src/core/shutdown.c
Zbigniew Jędrzejewski-Szmek c6e47247a7 basic/exec-util: add support for synchronous (ordered) execution
The output of processes can be gathered, and passed back to the callee.
(This commit just implements the basic functionality and tests.)

After the preparation in previous commits, the change in functionality is
relatively simple. For coding convenience, alarm is prepared *before* any
children are executed, and not before. This shouldn't matter usually, since
just forking of the children should be pretty quick. One could also argue that
this is more correct, because we will also catch the case when (for whatever
reason), forking itself is slow.

Three callback functions and three levels of serialization are used:
- from individual generator processes to the generator forker
- from the forker back to the main process
- deserialization in the main process

v2:
- replace an structure with an indexed array of callbacks
2017-02-20 18:49:13 -05:00

446 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 "exec-util.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, NULL, NULL, 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", &param);
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();
}