diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/manager.c | 2 | ||||
-rw-r--r-- | src/shutdownd.c | 272 | ||||
-rw-r--r-- | src/shutdownd.h | 35 | ||||
-rw-r--r-- | src/socket-util.h | 16 | ||||
-rw-r--r-- | src/systemctl.c | 160 | ||||
-rw-r--r-- | src/util.c | 11 | ||||
-rw-r--r-- | src/util.h | 2 |
7 files changed, 484 insertions, 14 deletions
diff --git a/src/manager.c b/src/manager.c index 4e8ddfb235..c8fdbb5dee 100644 --- a/src/manager.c +++ b/src/manager.c @@ -1786,7 +1786,7 @@ static int manager_process_notify_fd(Manager *m) { if (n >= 0) return -EIO; - if (errno == EAGAIN) + if (errno == EAGAIN || errno == EINTR) break; return -errno; diff --git a/src/shutdownd.c b/src/shutdownd.c new file mode 100644 index 0000000000..241c4327a6 --- /dev/null +++ b/src/shutdownd.c @@ -0,0 +1,272 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include <sys/socket.h> +#include <sys/poll.h> +#include <sys/types.h> +#include <sys/timerfd.h> +#include <assert.h> +#include <string.h> +#include <errno.h> +#include <unistd.h> +#include <fcntl.h> + +#include "shutdownd.h" +#include "log.h" +#include "macro.h" +#include "util.h" +#include "sd-daemon.h" + +static int read_packet(int fd, struct shutdownd_command *_c) { + struct msghdr msghdr; + struct iovec iovec; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + struct shutdownd_command c; + ssize_t n; + + assert(fd >= 0); + assert(_c); + + zero(iovec); + iovec.iov_base = &c; + iovec.iov_len = sizeof(c); + + zero(control); + zero(msghdr); + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = sizeof(control); + + if ((n = recvmsg(fd, &msghdr, MSG_DONTWAIT)) <= 0) { + if (n >= 0) { + log_error("Short read"); + return -EIO; + } + + if (errno == EAGAIN || errno == EINTR) + return 0; + + log_error("recvmsg(): %m"); + return -errno; + } + + if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || + control.cmsghdr.cmsg_level != SOL_SOCKET || + control.cmsghdr.cmsg_type != SCM_CREDENTIALS || + control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { + log_warning("Received message without credentials. Ignoring."); + return 0; + } + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + if (ucred->uid != 0) { + log_warning("Got request from unprivileged user. Ignoring."); + return 0; + } + + if (n != sizeof(c)) { + log_warning("Message has invaliud size. Ignoring"); + return 0; + } + + *_c = c; + return 1; +} + +int main(int argc, char *argv[]) { + enum { + FD_SOCKET, + FD_SHUTDOWN_TIMER, + FD_NOLOGIN_TIMER, + _FD_MAX + }; + + int r = 4, n; + int one = 1; + unsigned n_fds = 1; + struct shutdownd_command c; + struct pollfd pollfd[_FD_MAX]; + bool exec_shutdown = false, unlink_nologin = false; + + if (getppid() != 1) { + log_error("This program should be invoked by init only."); + return 1; + } + + if (argc > 1) { + log_error("This program does not take arguments."); + return 1; + } + + log_set_target(LOG_TARGET_SYSLOG_OR_KMSG); + log_parse_environment(); + + if ((n = sd_listen_fds(true)) < 0) { + log_error("Failed to read listening file descriptors from environment: %s", strerror(-r)); + return 1; + } + + if (n != 1) { + log_error("Need exactly one file descriptor."); + return 2; + } + + if (setsockopt(SD_LISTEN_FDS_START, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { + log_error("SO_PASSCRED failed: %m"); + return 3; + } + + zero(c); + zero(pollfd); + + pollfd[FD_SOCKET].fd = SD_LISTEN_FDS_START; + pollfd[FD_SOCKET].events = POLLIN; + pollfd[FD_SHUTDOWN_TIMER].fd = -1; + pollfd[FD_SHUTDOWN_TIMER].events = POLLIN; + pollfd[FD_NOLOGIN_TIMER].fd = -1; + pollfd[FD_NOLOGIN_TIMER].events = POLLIN; + + log_debug("systemd-shutdownd running as pid %lu", (unsigned long) getpid()); + + sd_notify(false, + "READY=1\n" + "STATUS=Processing requests..."); + + do { + int k; + + if (poll(pollfd, n_fds, -1) < 0) { + + if (errno == EAGAIN || errno == EINTR) + continue; + + log_error("poll(): %m"); + goto finish; + } + + if (pollfd[FD_SOCKET].revents) { + + if ((k = read_packet(pollfd[FD_SOCKET].fd, &c)) < 0) + goto finish; + else if (k > 0 && c.elapse > 0) { + struct itimerspec its; + char buf[27]; + + if (pollfd[FD_SHUTDOWN_TIMER].fd < 0) + if ((pollfd[FD_SHUTDOWN_TIMER].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { + log_error("timerfd_create(): %m"); + goto finish; + } + + if (pollfd[FD_NOLOGIN_TIMER].fd < 0) + if ((pollfd[FD_NOLOGIN_TIMER].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { + log_error("timerfd_create(): %m"); + goto finish; + } + + /* Disallow logins 5 minutes prior to shutdown */ + zero(its); + timespec_store(&its.it_value, c.elapse > 5*USEC_PER_MINUTE ? c.elapse - 5*USEC_PER_MINUTE : 0); + if (timerfd_settime(pollfd[FD_NOLOGIN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { + log_error("timerfd_settime(): %m"); + goto finish; + } + + /* Shutdown after the specified time is reached */ + zero(its); + timespec_store(&its.it_value, c.elapse); + if (timerfd_settime(pollfd[FD_SHUTDOWN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { + log_error("timerfd_settime(): %m"); + goto finish; + } + + n_fds = 3; + + ctime_r(&its.it_value.tv_sec, buf); + + sd_notifyf(false, + "STATUS=Shutting down at %s...", + strstrip(buf)); + } + } + + if (pollfd[FD_NOLOGIN_TIMER].fd >= 0 && + pollfd[FD_NOLOGIN_TIMER].revents) { + int e; + + if ((e = touch("/etc/nologin")) < 0) + log_error("Failed to create /etc/nologin: %s", strerror(-e)); + else + unlink_nologin = true; + + /* Disarm nologin timer */ + close_nointr_nofail(pollfd[FD_NOLOGIN_TIMER].fd); + pollfd[FD_NOLOGIN_TIMER].fd = -1; + n_fds = 2; + + } + + if (pollfd[FD_SHUTDOWN_TIMER].fd >= 0 && + pollfd[FD_SHUTDOWN_TIMER].revents) { + exec_shutdown = true; + goto finish; + } + + } while (c.elapse > 0); + + r = 0; + + log_debug("systemd-shutdownd stopped as pid %lu", (unsigned long) getpid()); + +finish: + if (pollfd[FD_SOCKET].fd >= 0) + close_nointr_nofail(pollfd[FD_SOCKET].fd); + + if (pollfd[FD_SHUTDOWN_TIMER].fd >= 0) + close_nointr_nofail(pollfd[FD_SHUTDOWN_TIMER].fd); + + if (pollfd[FD_NOLOGIN_TIMER].fd >= 0) + close_nointr_nofail(pollfd[FD_NOLOGIN_TIMER].fd); + + if (exec_shutdown) { + char sw[3]; + + sw[0] = '-'; + sw[1] = c.mode; + sw[2] = 0; + + execl(SYSTEMCTL_BINARY_PATH, "shutdown", sw, "now", NULL); + log_error("Failed to execute /sbin/shutdown: %m"); + } + + if (unlink_nologin) + unlink("/etc/nologin"); + + sd_notify(false, + "STATUS=Exiting..."); + + return r; +} diff --git a/src/shutdownd.h b/src/shutdownd.h new file mode 100644 index 0000000000..d298b01c92 --- /dev/null +++ b/src/shutdownd.h @@ -0,0 +1,35 @@ +/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/ + +#ifndef fooshutdowndhfoo +#define fooshutdowndhfoo + +/*** + This file is part of systemd. + + Copyright 2010 Lennart Poettering + + systemd is free software; you can redistribute it and/or modify it + under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 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 + General Public License for more details. + + You should have received a copy of the GNU General Public License + along with systemd; If not, see <http://www.gnu.org/licenses/>. +***/ + +#include "util.h" +#include "macro.h" + +_packed_ struct shutdownd_command { + usec_t elapse; + char mode; /* H, P, r, i.e. the switches usually passed to + * shutdown to select whether to halt, power-off or + * reboot the machine */ +}; + +#endif diff --git a/src/socket-util.h b/src/socket-util.h index 86c9e47809..b5cb2a844d 100644 --- a/src/socket-util.h +++ b/src/socket-util.h @@ -30,14 +30,16 @@ #include "macro.h" #include "util.h" +union sockaddr_union { + struct sockaddr sa; + struct sockaddr_in in4; + struct sockaddr_in6 in6; + struct sockaddr_un un; + struct sockaddr_storage storage; +}; + typedef struct SocketAddress { - union { - struct sockaddr sa; - struct sockaddr_in in4; - struct sockaddr_in6 in6; - struct sockaddr_un un; - struct sockaddr_storage storage; - } sockaddr; + union sockaddr_union sockaddr; /* We store the size here explicitly due to the weird * sockaddr_un semantics for abstract sockets */ diff --git a/src/systemctl.c b/src/systemctl.c index baae019e0f..e517031cea 100644 --- a/src/systemctl.c +++ b/src/systemctl.c @@ -49,6 +49,7 @@ #include "path-lookup.h" #include "conf-parser.h" #include "sd-daemon.h" +#include "shutdownd.h" static const char *arg_type = NULL; static char **arg_property = NULL; @@ -68,6 +69,9 @@ static bool arg_full = false; static bool arg_force = false; static bool arg_defaults = false; static char **arg_wall = NULL; +static usec_t arg_when = 0; +static bool arg_skip_fsck = false; +static bool arg_force_fsck = false; static enum action { ACTION_INVALID, ACTION_SYSTEMCTL, @@ -84,6 +88,7 @@ static enum action { ACTION_RELOAD, ACTION_REEXEC, ACTION_RUNLEVEL, + ACTION_CANCEL_SHUTDOWN, _ACTION_MAX } arg_action = ACTION_SYSTEMCTL; static enum dot { @@ -3832,7 +3837,10 @@ static int shutdown_help(void) { " -r --reboot Reboot the machine\n" " -h Equivalent to --poweroff, overriden by --halt\n" " -k Don't halt/power-off/reboot, just send warnings\n" - " --no-wall Don't send wall message before halt/power-off/reboot\n", + " --no-wall Don't send wall message before halt/power-off/reboot\n" + " -f Skip fsck on reboot\n" + " -F Force fsck on reboot\n" + " -c Cancel a pending shutdown\n", program_invocation_short_name); return 0; @@ -4099,6 +4107,53 @@ static int halt_parse_argv(int argc, char *argv[]) { return 1; } +static int parse_time_spec(const char *t, usec_t *_u) { + assert(t); + assert(_u); + + if (streq(t, "now")) + *_u = 0; + else if (t[0] == '+') { + uint64_t u; + + if (safe_atou64(t + 1, &u) < 0) + return -EINVAL; + + *_u = now(CLOCK_REALTIME) + USEC_PER_MINUTE * u; + } else { + char *e = NULL; + long hour, minute; + struct tm tm; + time_t s; + usec_t n; + + errno = 0; + hour = strtol(t, &e, 10); + if (errno != 0 || *e != ':' || hour < 0 || hour > 23) + return -EINVAL; + + minute = strtol(e+1, &e, 10); + if (errno != 0 || *e != 0 || minute < 0 || minute > 59) + return -EINVAL; + + n = now(CLOCK_REALTIME); + s = (time_t) n / USEC_PER_SEC; + assert_se(localtime_r(&s, &tm)); + + tm.tm_hour = (int) hour; + tm.tm_min = (int) minute; + + assert_se(s = mktime(&tm)); + + *_u = (usec_t) s * USEC_PER_SEC; + + while (*_u <= n) + *_u += USEC_PER_DAY; + } + + return 0; +} + static int shutdown_parse_argv(int argc, char *argv[]) { enum { @@ -4115,12 +4170,12 @@ static int shutdown_parse_argv(int argc, char *argv[]) { { NULL, 0, NULL, 0 } }; - int c; + int c, r; assert(argc >= 0); assert(argv); - while ((c = getopt_long(argc, argv, "HPrhkt:a", options, NULL)) >= 0) { + while ((c = getopt_long(argc, argv, "HPrhkt:afFc", options, NULL)) >= 0) { switch (c) { case ARG_HELP: @@ -4157,6 +4212,18 @@ static int shutdown_parse_argv(int argc, char *argv[]) { /* Compatibility nops */ break; + case 'f': + arg_skip_fsck = true; + break; + + case 'F': + arg_force_fsck = true; + break; + + case 'c': + arg_action = ACTION_CANCEL_SHUTDOWN; + break; + case '?': return -EINVAL; @@ -4166,10 +4233,13 @@ static int shutdown_parse_argv(int argc, char *argv[]) { } } - if (argc > optind && !streq(argv[optind], "now")) - log_warning("First argument '%s' isn't 'now'. Ignoring.", argv[optind]); + if (argc > optind) + if ((r = parse_time_spec(argv[optind], &arg_when)) < 0) { + log_error("Failed to parse time specification: %s", argv[optind]); + return r; + } - /* We ignore the time argument */ + /* We skip the time argument */ if (argc > optind + 1) arg_wall = argv + optind + 1; @@ -4624,6 +4694,62 @@ static int systemctl_main(DBusConnection *bus, int argc, char *argv[], DBusError return verbs[i].dispatch(bus, argv + optind, left); } +static int send_shutdownd(usec_t t, char mode) { + int fd = -1; + struct msghdr msghdr; + struct iovec iovec; + union sockaddr_union sockaddr; + struct ucred *ucred; + union { + struct cmsghdr cmsghdr; + uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; + } control; + struct shutdownd_command c; + + zero(c); + c.elapse = t; + c.mode = mode; + + if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) + return -errno; + + zero(sockaddr); + sockaddr.sa.sa_family = AF_UNIX; + sockaddr.un.sun_path[0] = 0; + strncpy(sockaddr.un.sun_path+1, "/org/freedesktop/systemd1/shutdownd", sizeof(sockaddr.un.sun_path)-1); + + zero(iovec); + iovec.iov_base = (char*) &c; + iovec.iov_len = sizeof(c); + + zero(control); + control.cmsghdr.cmsg_level = SOL_SOCKET; + control.cmsghdr.cmsg_type = SCM_CREDENTIALS; + control.cmsghdr.cmsg_len = CMSG_LEN(sizeof(struct ucred)); + + ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); + ucred->pid = getpid(); + ucred->uid = getuid(); + ucred->gid = getgid(); + + zero(msghdr); + msghdr.msg_name = &sockaddr; + msghdr.msg_namelen = sizeof(sa_family_t) + 1 + sizeof("/org/freedesktop/systemd1/shutdownd") - 1; + + msghdr.msg_iov = &iovec; + msghdr.msg_iovlen = 1; + msghdr.msg_control = &control; + msghdr.msg_controllen = control.cmsghdr.cmsg_len; + + if (sendmsg(fd, &msghdr, MSG_NOSIGNAL) < 0) { + close_nointr_nofail(fd); + return -errno; + } + + close_nointr_nofail(fd); + return 0; +} + static int reload_with_fallback(DBusConnection *bus) { if (bus) { @@ -4677,6 +4803,24 @@ static int halt_main(DBusConnection *bus) { return -EPERM; } + if (arg_force_fsck) { + if ((r = touch("/forcefsck")) < 0) + log_warning("Failed to create /forcefsck: %s", strerror(-r)); + } else if (arg_skip_fsck) { + if ((r = touch("/fastboot")) < 0) + log_warning("Failed to create /fastboot: %s", strerror(-r)); + } + + if (arg_when > 0) { + if ((r = send_shutdownd(arg_when, + arg_action == ACTION_HALT ? 'H' : + arg_action == ACTION_POWEROFF ? 'P' : + 'r')) < 0) + log_warning("Failed to talk to shutdownd, proceeding with immediate shutdown: %s", strerror(-r)); + else + return 0; + } + if (!arg_dry && !arg_immediate) return start_with_fallback(bus); @@ -4790,6 +4934,10 @@ int main(int argc, char*argv[]) { retval = reload_with_fallback(bus) < 0; break; + case ACTION_CANCEL_SHUTDOWN: + retval = send_shutdownd(0, 0) < 0; + break; + case ACTION_INVALID: case ACTION_RUNLEVEL: default: diff --git a/src/util.c b/src/util.c index c0b63dd574..bc227f52d5 100644 --- a/src/util.c +++ b/src/util.c @@ -2997,6 +2997,17 @@ void nss_disable_nscd(void) { log_debug("Cannot disable nscd."); } +int touch(const char *path) { + int fd; + + assert(path); + + if ((fd = open(path, O_WRONLY|O_CREAT|O_CLOEXEC|O_NOCTTY, 0666)) < 0) + return -errno; + + close_nointr_nofail(fd); + return 0; +} static const char *const ioprio_class_table[] = { [IOPRIO_CLASS_NONE] = "none", diff --git a/src/util.h b/src/util.h index 66ebb46dbf..e9126c04fa 100644 --- a/src/util.h +++ b/src/util.h @@ -338,6 +338,8 @@ char *ellipsize(const char *s, unsigned length, unsigned percent); void nss_disable_nscd(void); +int touch(const char *path); + const char *ioprio_class_to_string(int i); int ioprio_class_from_string(const char *s); |