Commit f6144808 authored by Lennart Poettering's avatar Lennart Poettering

systemctl: add support for delayed shutdown, similar to sysv in style

parent 6e200d55
systemd-shutdownd
systemd-random-seed
systemd-update-utmp
test-env-replace
......
......@@ -71,7 +71,8 @@ rootlibexec_PROGRAMS = \
systemd-cgroups-agent \
systemd-initctl \
systemd-update-utmp \
systemd-random-seed
systemd-random-seed \
systemd-shutdownd
noinst_PROGRAMS = \
test-engine \
......@@ -137,6 +138,7 @@ dist_systemunit_DATA = \
units/dbus.target \
units/systemd-initctl.socket \
units/systemd-logger.socket \
units/systemd-shutdownd.socket \
units/dev-hugepages.automount \
units/dev-hugepages.mount \
units/dev-mqueue.automount \
......@@ -161,6 +163,7 @@ nodist_systemunit_DATA = \
units/multi-user.target \
units/systemd-initctl.service \
units/systemd-logger.service \
units/systemd-shutdownd.service \
units/systemd-update-utmp-runlevel.service \
units/systemd-update-utmp-shutdown.service \
units/systemd-random-seed-save.service \
......@@ -182,6 +185,7 @@ EXTRA_DIST = \
units/remote-fs.target.m4 \
units/systemd-initctl.service.in \
units/systemd-logger.service.in \
units/systemd-shutdownd.service.in \
units/systemd-update-utmp-runlevel.service.in \
units/systemd-update-utmp-shutdown.service.in \
units/systemd-random-seed-save.service.in \
......@@ -346,7 +350,8 @@ EXTRA_DIST += \
src/bus-errors.h \
src/cgroup-show.h \
src/utmp-wtmp.h \
src/build.h
src/build.h \
src/shutdownd.h
MANPAGES = \
man/systemd.1 \
......@@ -529,6 +534,16 @@ systemd_random_seed_CFLAGS = \
systemd_random_seed_LDADD = \
libsystemd-basic.la
systemd_shutdownd_SOURCES = \
src/sd-daemon.c \
src/shutdownd.c
systemd_shutdownd_CFLAGS = \
$(AM_CFLAGS)
systemd_shutdownd_LDADD = \
libsystemd-basic.la
systemd_cgroups_agent_SOURCES = \
src/cgroups-agent.c \
src/dbus-common.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;
......
/*-*- 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;
}
/*-*- 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
......@@ -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 */
......
......@@ -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:
......
......@@ -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;
}