/*-*- 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 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 <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 <stddef.h> #include "systemd/sd-daemon.h" #include "systemd/sd-shutdown.h" #include "log.h" #include "macro.h" #include "util.h" #include "utmp-wtmp.h" #include "mkdir.h" #include "fileio.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, union shutdown_buffer *_b) { struct ucred *ucred; ssize_t n; union shutdown_buffer b; /* We maintain our own copy here, in * order not to corrupt the last message */ struct iovec iovec = { .iov_base = &b, .iov_len = sizeof(b) - 1, }; union { struct cmsghdr cmsghdr; uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; } control = {}; struct msghdr msghdr = { .msg_iov = &iovec, .msg_iovlen = 1, .msg_control = &control, .msg_controllen = sizeof(control), }; assert(fd >= 0); assert(_b); n = recvmsg(fd, &msghdr, MSG_DONTWAIT); if (n <= 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 ((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; } 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; _cleanup_free_ 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 if (c->mode == SD_SHUTDOWN_NONE) prefix = "The system shutdown has been cancelled 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) utmp_wall(l, NULL, NULL); else log_error("Failed to allocate wall message"); } _const_ static usec_t when_wall(usec_t n, usec_t elapse) { static const struct { usec_t delay; usec_t interval; } table[] = { { 0, USEC_PER_MINUTE }, { 10 * USEC_PER_MINUTE, 15 * USEC_PER_MINUTE }, { USEC_PER_HOUR, 30 * USEC_PER_MINUTE }, { 3 * USEC_PER_HOUR, USEC_PER_HOUR }, }; usec_t left, sub; unsigned i = ELEMENTSOF(table) - 1; /* If the time is already passed, then don't announce */ if (n >= elapse) return 0; left = elapse - n; while (left < table[i].delay) i--; sub = (left / table[i].interval) * table[i].interval; assert(sub < elapse); return elapse - sub; } 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; _cleanup_fclose_ FILE *f = NULL; _cleanup_free_ char *t = NULL, *temp_path = NULL; assert(c); r = mkdir_safe_label("/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) return log_oom(); 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)); return r; } fchmod(fileno(f), 0644); fprintf(f, "USEC="USEC_FMT"\n" "WARN_WALL=%i\n" "MODE=%s\n", 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); 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"); } 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_WALL_TIMER, FD_NOLOGIN_TIMER, FD_SHUTDOWN_TIMER, _FD_MAX }; 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 EXIT_FAILURE; } if (argc > 1) { log_error("This program does not take arguments."); return EXIT_FAILURE; } log_set_target(LOG_TARGET_AUTO); log_parse_environment(); log_open(); 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 EXIT_FAILURE; } if (n_fds != 1) { log_error("Need exactly one file descriptor."); return EXIT_FAILURE; } pollfd[FD_SOCKET].fd = SD_LISTEN_FDS_START; pollfd[FD_SOCKET].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 "PID_FMT, getpid()); sd_notify(false, "READY=1\n" "STATUS=Processing requests..."); for (;;) { int k; usec_t n; k = poll(pollfd, _FD_MAX, scheduled(&b.command) ? -1 : 0); if (k < 0) { if (errno == EAGAIN || errno == EINTR) continue; log_error("poll(): %m"); goto finish; } /* Exit on idle */ if (k == 0) break; n = now(CLOCK_REALTIME); if (pollfd[FD_SOCKET].revents) { k = read_packet(pollfd[FD_SOCKET].fd, &b); if (k < 0) goto finish; else if (k > 0) { struct itimerspec its; char date[FORMAT_TIMESTAMP_MAX]; if (!scheduled(&b.command)) { log_info("Shutdown canceled."); if (b.command.warn_wall) warn_wall(0, &b.command); break; } 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, 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; } /* Shutdown after the specified time is reached */ zero(its); 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; } update_schedule_file(&b.command); sd_notifyf(false, "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 */ 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].revents) { int e; log_info("Creating /run/nologin, blocking further logins..."); e = write_string_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; flush_fd(pollfd[FD_NOLOGIN_TIMER].fd); } if (pollfd[FD_SHUTDOWN_TIMER].revents) { exec_shutdown = true; goto finish; } } r = EXIT_SUCCESS; log_debug("systemd-shutdownd stopped as pid "PID_FMT, getpid()); finish: for (i = 0; i < _FD_MAX; i++) safe_close(pollfd[i].fd); if (unlink_nologin) unlink("/run/nologin"); unlink("/run/systemd/shutdown/scheduled"); if (exec_shutdown && !b.command.dry_run) { char sw[3]; sw[0] = '-'; sw[1] = b.command.mode; sw[2] = 0; 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"); } sd_notify(false, "STOPPING=\n" "STATUS=Exiting..."); return r; }