summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/manager.c2
-rw-r--r--src/shutdownd.c272
-rw-r--r--src/shutdownd.h35
-rw-r--r--src/socket-util.h16
-rw-r--r--src/systemctl.c160
-rw-r--r--src/util.c11
-rw-r--r--src/util.h2
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);