chiark / gitweb /
relicense to LGPLv2.1 (with exceptions)
[elogind.git] / src / shutdownd.c
index 241c4327a628c4413d1df943b2e4686b6e205fc7..0497cd41a05ee8ed90c6042e22da3528dde3778a 100644 (file)
@@ -6,16 +6,16 @@
   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
+  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
-  General Public License for more details.
+  Lesser General Public License for more details.
 
-  You should have received a copy of the GNU General Public License
+  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 <unistd.h>
 #include <fcntl.h>
+#include <stddef.h>
+
+#include <systemd/sd-daemon.h>
+#include <systemd/sd-shutdown.h>
 
-#include "shutdownd.h"
 #include "log.h"
 #include "macro.h"
 #include "util.h"
-#include "sd-daemon.h"
+#include "utmp-wtmp.h"
+#include "mkdir.h"
+
+union shutdown_buffer {
+        struct sd_shutdown_command command;
+        char space[offsetof(struct sd_shutdown_command, wall_message) + LINE_MAX];
+};
 
-static int read_packet(int fd, struct shutdownd_command *_c) {
+static int read_packet(int fd, union shutdown_buffer *_b) {
         struct msghdr msghdr;
         struct iovec iovec;
         struct ucred *ucred;
@@ -43,15 +52,15 @@ static int read_packet(int fd, struct shutdownd_command *_c) {
                 struct cmsghdr cmsghdr;
                 uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
         } control;
-        struct shutdownd_command c;
         ssize_t n;
+        union shutdown_buffer b; /* We maintain our own copy here, in order not to corrupt the last message */
 
         assert(fd >= 0);
-        assert(_c);
+        assert(_b);
 
         zero(iovec);
-        iovec.iov_base = &c;
-        iovec.iov_len = sizeof(c);
+        iovec.iov_base = &b;
+        iovec.iov_len = sizeof(b) - 1;
 
         zero(control);
         zero(msghdr);
@@ -60,8 +69,9 @@ static int read_packet(int fd, struct shutdownd_command *_c) {
         msghdr.msg_control = &control;
         msghdr.msg_controllen = sizeof(control);
 
-        if ((n = recvmsg(fd, &msghdr, MSG_DONTWAIT)) <= 0) {
-                if (n >= 0) {
+        n = recvmsg(fd, &msghdr, MSG_DONTWAIT);
+        if (n <= 0) {
+                if (n == 0) {
                         log_error("Short read");
                         return -EIO;
                 }
@@ -87,67 +97,227 @@ static int read_packet(int fd, struct shutdownd_command *_c) {
                 return 0;
         }
 
-        if (n != sizeof(c)) {
-                log_warning("Message has invaliud size. Ignoring");
+        if ((size_t) n < offsetof(struct sd_shutdown_command, wall_message)) {
+                log_warning("Message has invalid size. Ignoring.");
+                return 0;
+        }
+
+        if (b.command.mode != SD_SHUTDOWN_NONE &&
+            b.command.mode != SD_SHUTDOWN_REBOOT &&
+            b.command.mode != SD_SHUTDOWN_POWEROFF &&
+            b.command.mode != SD_SHUTDOWN_HALT &&
+            b.command.mode != SD_SHUTDOWN_KEXEC) {
+                log_warning("Message has invalid mode. Ignoring.");
                 return 0;
         }
 
-        *_c = c;
+        b.space[n] = 0;
+
+        *_b = b;
         return 1;
 }
 
+static void warn_wall(usec_t n, struct sd_shutdown_command *c) {
+        char date[FORMAT_TIMESTAMP_MAX];
+        const char *prefix;
+        char *l = NULL;
+
+        assert(c);
+        assert(c->warn_wall);
+
+        if (n >= c->usec)
+                return;
+
+        if (c->mode == SD_SHUTDOWN_HALT)
+                prefix = "The system is going down for system halt at ";
+        else if (c->mode == SD_SHUTDOWN_POWEROFF)
+                prefix = "The system is going down for power-off at ";
+        else if (c->mode == SD_SHUTDOWN_REBOOT)
+                prefix = "The system is going down for reboot at ";
+        else if (c->mode == SD_SHUTDOWN_KEXEC)
+                prefix = "The system is going down for kexec reboot at ";
+        else
+                assert_not_reached("Unknown mode!");
+
+        if (asprintf(&l, "%s%s%s%s!", c->wall_message, c->wall_message[0] ? "\n" : "",
+                     prefix, format_timestamp(date, sizeof(date), c->usec)) < 0)
+                log_error("Failed to allocate wall message");
+        else {
+                utmp_wall(l, NULL);
+                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);
+                        break;
+                }
+
+        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;
+}
+
+static const char *mode_to_string(enum sd_shutdown_mode m) {
+        switch (m) {
+        case SD_SHUTDOWN_REBOOT:
+                return "reboot";
+        case SD_SHUTDOWN_POWEROFF:
+                return "poweroff";
+        case SD_SHUTDOWN_HALT:
+                return "halt";
+        case SD_SHUTDOWN_KEXEC:
+                return "kexec";
+        default:
+                return NULL;
+        }
+}
+
+static int update_schedule_file(struct sd_shutdown_command *c) {
+        int r;
+        FILE *f;
+        char *temp_path, *t;
+
+        assert(c);
+
+        r = safe_mkdir("/run/systemd/shutdown", 0755, 0, 0);
+        if (r < 0) {
+                log_error("Failed to create shutdown subdirectory: %s", strerror(-r));
+                return r;
+        }
+
+        t = cescape(c->wall_message);
+        if (!t) {
+                log_error("Out of memory");
+                return -ENOMEM;
+        }
+
+        r = fopen_temporary("/run/systemd/shutdown/scheduled", &f, &temp_path);
+        if (r < 0) {
+                log_error("Failed to save information about scheduled shutdowns: %s", strerror(-r));
+                free(t);
+                return r;
+        }
+
+        fchmod(fileno(f), 0644);
+
+        fprintf(f,
+                "USEC=%llu\n"
+                "WARN_WALL=%i\n"
+                "MODE=%s\n",
+                (unsigned long long) c->usec,
+                c->warn_wall,
+                mode_to_string(c->mode));
+
+        if (c->dry_run)
+                fputs("DRY_RUN=1\n", f);
+
+        if (!isempty(t))
+                fprintf(f, "WALL_MESSAGE=%s\n", t);
+
+        free(t);
+
+        fflush(f);
+
+        if (ferror(f) || rename(temp_path, "/run/systemd/shutdown/scheduled") < 0) {
+                log_error("Failed to write information about scheduled shutdowns: %m");
+                r = -errno;
+
+                unlink(temp_path);
+                unlink("/run/systemd/shutdown/scheduled");
+        }
+
+        fclose(f);
+        free(temp_path);
+
+        return r;
+}
+
+static bool scheduled(struct sd_shutdown_command *c) {
+        return c->usec > 0 && c->mode != SD_SHUTDOWN_NONE;
+}
+
 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 one = 1;
-        unsigned n_fds = 1;
-        struct shutdownd_command c;
+        int r = EXIT_FAILURE, n_fds;
+        union shutdown_buffer b;
         struct pollfd pollfd[_FD_MAX];
         bool exec_shutdown = false, unlink_nologin = false;
+        unsigned i;
 
         if (getppid() != 1) {
                 log_error("This program should be invoked by init only.");
-                return 1;
+                return EXIT_FAILURE;
         }
 
         if (argc > 1) {
                 log_error("This program does not take arguments.");
-                return 1;
+                return EXIT_FAILURE;
         }
 
-        log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
+        log_set_target(LOG_TARGET_AUTO);
         log_parse_environment();
+        log_open();
 
-        if ((n = sd_listen_fds(true)) < 0) {
+        umask(0022);
+
+        n_fds = sd_listen_fds(true);
+        if (n_fds < 0) {
                 log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
-                return 1;
+                return EXIT_FAILURE;
         }
 
-        if (n != 1) {
+        if (n_fds != 1) {
                 log_error("Need exactly one file descriptor.");
-                return 2;
+                return EXIT_FAILURE;
         }
 
-        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(b);
         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;
+
+        for (i = FD_WALL_TIMER; i < _FD_MAX; i++) {
+                pollfd[i].events = POLLIN;
+                pollfd[i].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC);
+                if (pollfd[i].fd < 0) {
+                        log_error("timerfd_create(): %m");
+                        goto finish;
+                }
+        }
 
         log_debug("systemd-shutdownd running as pid %lu", (unsigned long) getpid());
 
@@ -155,10 +325,12 @@ int main(int argc, char *argv[]) {
                   "READY=1\n"
                   "STATUS=Processing requests...");
 
-        do {
+        for (;;) {
                 int k;
+                usec_t n;
 
-                if (poll(pollfd, n_fds, -1) < 0) {
+                k = poll(pollfd, _FD_MAX, scheduled(&b.command) ? -1 : 0);
+                if (k < 0) {
 
                         if (errno == EAGAIN || errno == EINTR)
                                 continue;
@@ -167,29 +339,44 @@ int main(int argc, char *argv[]) {
                         goto finish;
                 }
 
+                /* Exit on idle */
+                if (k == 0)
+                        break;
+
+                n = now(CLOCK_REALTIME);
+
                 if (pollfd[FD_SOCKET].revents) {
 
-                        if ((k = read_packet(pollfd[FD_SOCKET].fd, &c)) < 0)
+                        k = read_packet(pollfd[FD_SOCKET].fd, &b);
+                        if (k < 0)
                                 goto finish;
-                        else if (k > 0 && c.elapse > 0) {
+                        else if (k > 0) {
                                 struct itimerspec its;
-                                char buf[27];
+                                char date[FORMAT_TIMESTAMP_MAX];
 
-                                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 (!scheduled(&b.command)) {
+                                        log_info("Shutdown canceled.");
+                                        break;
+                                }
 
-                                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;
-                                        }
+                                zero(its);
+                                if (b.command.warn_wall) {
+                                        /* Send wall messages every so often */
+                                        timespec_store(&its.it_value, when_wall(n, b.command.usec));
+
+                                        /* Warn immediately if less than 15 minutes are left */
+                                        if (n < b.command.usec &&
+                                            n + 15*USEC_PER_MINUTE >= b.command.usec)
+                                                warn_wall(n, &b.command);
+                                }
+                                if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
+                                        log_error("timerfd_settime(): %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);
+                                timespec_store(&its.it_value, when_nologin(b.command.usec));
                                 if (timerfd_settime(pollfd[FD_NOLOGIN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
                                         log_error("timerfd_settime(): %m");
                                         goto finish;
@@ -197,74 +384,91 @@ int main(int argc, char *argv[]) {
 
                                 /* Shutdown after the specified time is reached */
                                 zero(its);
-                                timespec_store(&its.it_value, c.elapse);
+                                timespec_store(&its.it_value, b.command.usec);
                                 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);
+                                update_schedule_file(&b.command);
 
                                 sd_notifyf(false,
-                                           "STATUS=Shutting down at %s...",
-                                           strstrip(buf));
+                                           "STATUS=Shutting down at %s (%s)...",
+                                           format_timestamp(date, sizeof(date), b.command.usec),
+                                           mode_to_string(b.command.mode));
+
+                                log_info("Shutting down at %s (%s)...", date, mode_to_string(b.command.mode));
+                        }
+                }
+
+                if (pollfd[FD_WALL_TIMER].revents) {
+                        struct itimerspec its;
+
+                        warn_wall(n, &b.command);
+                        flush_fd(pollfd[FD_WALL_TIMER].fd);
+
+                        /* Restart timer */
+                        zero(its);
+                        timespec_store(&its.it_value, when_wall(n, b.command.usec));
+                        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 &&
-                    pollfd[FD_NOLOGIN_TIMER].revents) {
+                if (pollfd[FD_NOLOGIN_TIMER].revents) {
                         int e;
 
-                        if ((e = touch("/etc/nologin")) < 0)
-                                log_error("Failed to create /etc/nologin: %s", strerror(-e));
+                        log_info("Creating /run/nologin, blocking further logins...");
+
+                        e = write_one_line_file_atomic("/run/nologin", "System is going down.");
+                        if (e < 0)
+                                log_error("Failed to create /run/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;
-
+                        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;
                 }
+        }
 
-        } while (c.elapse > 0);
-
-        r = 0;
+        r = EXIT_SUCCESS;
 
         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);
+        for (i = 0; i < _FD_MAX; i++)
+                if (pollfd[i].fd >= 0)
+                        close_nointr_nofail(pollfd[i].fd);
 
-        if (pollfd[FD_NOLOGIN_TIMER].fd >= 0)
-                close_nointr_nofail(pollfd[FD_NOLOGIN_TIMER].fd);
+        if (unlink_nologin)
+                unlink("/run/nologin");
+
+        unlink("/run/systemd/shutdown/scheduled");
 
-        if (exec_shutdown) {
+        if (exec_shutdown && !b.command.dry_run) {
                 char sw[3];
 
                 sw[0] = '-';
-                sw[1] = c.mode;
+                sw[1] = b.command.mode;
                 sw[2] = 0;
 
-                execl(SYSTEMCTL_BINARY_PATH, "shutdown", sw, "now", NULL);
+                execl(SYSTEMCTL_BINARY_PATH,
+                      "shutdown",
+                      sw,
+                      "now",
+                      (b.command.warn_wall && b.command.wall_message[0]) ? b.command.wall_message :
+                      (b.command.warn_wall ? NULL : "--no-wall"),
+                      NULL);
+
                 log_error("Failed to execute /sbin/shutdown: %m");
         }
 
-        if (unlink_nologin)
-                unlink("/etc/nologin");
-
         sd_notify(false,
                   "STATUS=Exiting...");