From 9be9828c00449e3cab77aaea8359de61e793cc28 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Mon, 16 Aug 2010 19:26:27 +0200 Subject: [PATCH] systemctl: warn in intervals before going down when delayed shutdown is used --- Makefile.am | 1 + src/shutdownd.c | 170 +++++++++++++++++++++++++++++++++++++----------- src/shutdownd.h | 10 +++ src/systemctl.c | 24 +++++-- 4 files changed, 161 insertions(+), 44 deletions(-) diff --git a/Makefile.am b/Makefile.am index e3cef517c..013a603fc 100644 --- a/Makefile.am +++ b/Makefile.am @@ -535,6 +535,7 @@ systemd_random_seed_LDADD = \ libsystemd-basic.la systemd_shutdownd_SOURCES = \ + src/utmp-wtmp.c \ src/sd-daemon.c \ src/shutdownd.c diff --git a/src/shutdownd.c b/src/shutdownd.c index 751a1a59b..7f5e66dbe 100644 --- a/src/shutdownd.c +++ b/src/shutdownd.c @@ -34,6 +34,7 @@ #include "macro.h" #include "util.h" #include "sd-daemon.h" +#include "utmp-wtmp.h" static int read_packet(int fd, struct shutdownd_command *_c) { struct msghdr msghdr; @@ -92,24 +93,95 @@ static int read_packet(int fd, struct shutdownd_command *_c) { return 0; } + char_array_0(c.wall_message); + *_c = c; return 1; } +static void warn_wall(struct shutdownd_command *c) { + + assert(c); + assert(c->warn_wall); + + if (c->wall_message[0]) + utmp_wall(c->wall_message); + else { + time_t s; + char buf[27]; + const char* prefix; + char *l; + + s = c->elapse / USEC_PER_SEC; + ctime_r(&s, buf); + + + if (c->mode == 'H') + prefix = "The system is going down for system halt at"; + else if (c->mode == 'P') + prefix = "The system is going down for power-off at"; + else if (c->mode == 'r') + prefix = "The system is going down for reboot at"; + else + assert_not_reached("Unknown mode!"); + + if (asprintf(&l, "%s %s!", prefix, strstrip(buf)) < 0) + log_error("Failed to allocate wall message"); + else { + utmp_wall(l); + free(l); + } + } +} + +static usec_t when_wall(usec_t n, usec_t elapse) { + + static const struct { + usec_t delay; + usec_t interval; + } table[] = { + { 10 * USEC_PER_MINUTE, USEC_PER_MINUTE }, + { USEC_PER_HOUR, 15 * USEC_PER_MINUTE }, + { 3 * USEC_PER_HOUR, 30 * USEC_PER_MINUTE } + }; + + usec_t left, sub; + unsigned i; + + /* If the time is already passed, then don't announce */ + if (n >= elapse) + return 0; + + left = elapse - n; + for (i = 0; i < ELEMENTSOF(table); i++) + if (n + table[i].delay >= elapse) + sub = ((left / table[i].interval) * table[i].interval); + + if (i >= ELEMENTSOF(table)) + sub = ((left / USEC_PER_HOUR) * USEC_PER_HOUR); + + return elapse > sub ? elapse - sub : 1; +} + +static usec_t when_nologin(usec_t elapse) { + return elapse > 5*USEC_PER_MINUTE ? elapse - 5*USEC_PER_MINUTE : 1; +} + int main(int argc, char *argv[]) { enum { FD_SOCKET, - FD_SHUTDOWN_TIMER, + FD_WALL_TIMER, FD_NOLOGIN_TIMER, + FD_SHUTDOWN_TIMER, _FD_MAX }; - int r = 4, n; + int r = 4, n_fds; int one = 1; - unsigned n_fds = 1; struct shutdownd_command c; struct pollfd pollfd[_FD_MAX]; - bool exec_shutdown = false, unlink_nologin = false; + bool exec_shutdown = false, unlink_nologin = false, failed = false; + unsigned i; if (getppid() != 1) { log_error("This program should be invoked by init only."); @@ -124,12 +196,12 @@ int main(int argc, char *argv[]) { log_set_target(LOG_TARGET_SYSLOG_OR_KMSG); log_parse_environment(); - if ((n = sd_listen_fds(true)) < 0) { + if ((n_fds = sd_listen_fds(true)) < 0) { log_error("Failed to read listening file descriptors from environment: %s", strerror(-r)); return 1; } - if (n != 1) { + if (n_fds != 1) { log_error("Need exactly one file descriptor."); return 2; } @@ -144,10 +216,22 @@ int main(int argc, char *argv[]) { 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; + + for (i = 0; i < _FD_MAX; i++) { + + if (i == FD_SOCKET) + continue; + + pollfd[i].events = POLLIN; + + if ((pollfd[i].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { + log_error("timerfd_create(): %m"); + failed = false; + } + } + + if (failed) + goto finish; log_debug("systemd-shutdownd running as pid %lu", (unsigned long) getpid()); @@ -157,8 +241,9 @@ int main(int argc, char *argv[]) { do { int k; + usec_t n; - if (poll(pollfd, n_fds, -1) < 0) { + if (poll(pollfd, _FD_MAX, -1) < 0) { if (errno == EAGAIN || errno == EINTR) continue; @@ -167,6 +252,8 @@ int main(int argc, char *argv[]) { goto finish; } + n = now(CLOCK_REALTIME); + if (pollfd[FD_SOCKET].revents) { if ((k = read_packet(pollfd[FD_SOCKET].fd, &c)) < 0) @@ -175,21 +262,25 @@ int main(int argc, char *argv[]) { 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"); + + if (c.warn_wall) { + /* Send wall messages every so often */ + zero(its); + timespec_store(&its.it_value, when_wall(n, c.elapse)); + if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { + log_error("timerfd_settime(): %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; - } + /* Warn immediately if less than 15 minutes are left */ + if (n < c.elapse && + n + 15*USEC_PER_MINUTE >= c.elapse) + warn_wall(&c); + } /* 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); + timespec_store(&its.it_value, when_nologin(c.elapse)); if (timerfd_settime(pollfd[FD_NOLOGIN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { log_error("timerfd_settime(): %m"); goto finish; @@ -203,8 +294,6 @@ int main(int argc, char *argv[]) { goto finish; } - n_fds = 3; - ctime_r(&its.it_value.tv_sec, buf); sd_notifyf(false, @@ -213,8 +302,22 @@ int main(int argc, char *argv[]) { } } - if (pollfd[FD_NOLOGIN_TIMER].fd >= 0 && - pollfd[FD_NOLOGIN_TIMER].revents) { + if (pollfd[FD_WALL_TIMER].revents) { + struct itimerspec its; + + warn_wall(&c); + flush_fd(pollfd[FD_WALL_TIMER].fd); + + /* Restart timer */ + zero(its); + timespec_store(&its.it_value, when_wall(n, c.elapse)); + if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { + log_error("timerfd_settime(): %m"); + goto finish; + } + } + + if (pollfd[FD_NOLOGIN_TIMER].revents) { int e; if ((e = touch("/etc/nologin")) < 0) @@ -222,15 +325,10 @@ int main(int argc, char *argv[]) { else unlink_nologin = true; - /* Disarm nologin timer */ - close_nointr_nofail(pollfd[FD_NOLOGIN_TIMER].fd); - pollfd[FD_NOLOGIN_TIMER].fd = -1; - n_fds = 2; - + flush_fd(pollfd[FD_NOLOGIN_TIMER].fd); } - if (pollfd[FD_SHUTDOWN_TIMER].fd >= 0 && - pollfd[FD_SHUTDOWN_TIMER].revents) { + if (pollfd[FD_SHUTDOWN_TIMER].revents) { exec_shutdown = true; goto finish; } @@ -242,14 +340,10 @@ int main(int argc, char *argv[]) { 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); + for (i = 0; i < _FD_MAX; i++) + if (pollfd[i].fd >= 0) + close_nointr_nofail(pollfd[i].fd); if (exec_shutdown) { char sw[3]; diff --git a/src/shutdownd.h b/src/shutdownd.h index d298b01c9..ed8a704b1 100644 --- a/src/shutdownd.h +++ b/src/shutdownd.h @@ -25,11 +25,21 @@ #include "util.h" #include "macro.h" +/* This is a private message, we don't care much about ABI + * stability. */ + _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 */ + bool warn_wall; + + /* Yepp, sometimes we are lazy and use fixed-size strings like + * this one. Shame on us. But then again, we'd have to + * pre-allocate the receive buffer anyway, so there's nothing + * too bad here. */ + char wall_message[4096]; }; #endif diff --git a/src/systemctl.c b/src/systemctl.c index 2b34798d0..ca676f9da 100644 --- a/src/systemctl.c +++ b/src/systemctl.c @@ -4696,7 +4696,7 @@ 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) { +static int send_shutdownd(usec_t t, char mode, bool warn, const char *message) { int fd = -1; struct msghdr msghdr; struct iovec iovec; @@ -4711,6 +4711,10 @@ static int send_shutdownd(usec_t t, char mode) { zero(c); c.elapse = t; c.mode = mode; + c.warn_wall = warn; + + if (message) + strncpy(c.wall_message, message, sizeof(c.wall_message)); if ((fd = socket(AF_UNIX, SOCK_DGRAM|SOCK_CLOEXEC, 0)) < 0) return -errno; @@ -4814,10 +4818,18 @@ static int halt_main(DBusConnection *bus) { } if (arg_when > 0) { - if ((r = send_shutdownd(arg_when, - arg_action == ACTION_HALT ? 'H' : - arg_action == ACTION_POWEROFF ? 'P' : - 'r')) < 0) + char *m; + + m = strv_join(arg_wall, " "); + r = send_shutdownd(arg_when, + arg_action == ACTION_HALT ? 'H' : + arg_action == ACTION_POWEROFF ? 'P' : + 'r', + !arg_no_wall, + m); + free(m); + + if (r < 0) log_warning("Failed to talk to shutdownd, proceeding with immediate shutdown: %s", strerror(-r)); else return 0; @@ -4937,7 +4949,7 @@ int main(int argc, char*argv[]) { break; case ACTION_CANCEL_SHUTDOWN: - retval = send_shutdownd(0, 0) < 0; + retval = send_shutdownd(0, 0, false, NULL) < 0; break; case ACTION_INVALID: