chiark / gitweb /
shutdown: if now time argument is passed, imply +1 not +0
[elogind.git] / src / shutdownd.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2010 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU General Public License as published by
10   the Free Software Foundation; either version 2 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   General Public License for more details.
17
18   You should have received a copy of the GNU General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <sys/socket.h>
23 #include <sys/poll.h>
24 #include <sys/types.h>
25 #include <sys/timerfd.h>
26 #include <assert.h>
27 #include <string.h>
28 #include <errno.h>
29 #include <unistd.h>
30 #include <fcntl.h>
31
32 #include "shutdownd.h"
33 #include "log.h"
34 #include "macro.h"
35 #include "util.h"
36 #include "sd-daemon.h"
37
38 static int read_packet(int fd, struct shutdownd_command *_c) {
39         struct msghdr msghdr;
40         struct iovec iovec;
41         struct ucred *ucred;
42         union {
43                 struct cmsghdr cmsghdr;
44                 uint8_t buf[CMSG_SPACE(sizeof(struct ucred))];
45         } control;
46         struct shutdownd_command c;
47         ssize_t n;
48
49         assert(fd >= 0);
50         assert(_c);
51
52         zero(iovec);
53         iovec.iov_base = &c;
54         iovec.iov_len = sizeof(c);
55
56         zero(control);
57         zero(msghdr);
58         msghdr.msg_iov = &iovec;
59         msghdr.msg_iovlen = 1;
60         msghdr.msg_control = &control;
61         msghdr.msg_controllen = sizeof(control);
62
63         if ((n = recvmsg(fd, &msghdr, MSG_DONTWAIT)) <= 0) {
64                 if (n >= 0) {
65                         log_error("Short read");
66                         return -EIO;
67                 }
68
69                 if (errno == EAGAIN || errno == EINTR)
70                         return 0;
71
72                 log_error("recvmsg(): %m");
73                 return -errno;
74         }
75
76         if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) ||
77             control.cmsghdr.cmsg_level != SOL_SOCKET ||
78             control.cmsghdr.cmsg_type != SCM_CREDENTIALS ||
79             control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) {
80                 log_warning("Received message without credentials. Ignoring.");
81                 return 0;
82         }
83
84         ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr);
85         if (ucred->uid != 0) {
86                 log_warning("Got request from unprivileged user. Ignoring.");
87                 return 0;
88         }
89
90         if (n != sizeof(c)) {
91                 log_warning("Message has invalid size. Ignoring");
92                 return 0;
93         }
94
95         *_c = c;
96         return 1;
97 }
98
99 int main(int argc, char *argv[]) {
100         enum {
101                 FD_SOCKET,
102                 FD_SHUTDOWN_TIMER,
103                 FD_NOLOGIN_TIMER,
104                 _FD_MAX
105         };
106
107         int r = 4, n;
108         int one = 1;
109         unsigned n_fds = 1;
110         struct shutdownd_command c;
111         struct pollfd pollfd[_FD_MAX];
112         bool exec_shutdown = false, unlink_nologin = false;
113
114         if (getppid() != 1) {
115                 log_error("This program should be invoked by init only.");
116                 return 1;
117         }
118
119         if (argc > 1) {
120                 log_error("This program does not take arguments.");
121                 return 1;
122         }
123
124         log_set_target(LOG_TARGET_SYSLOG_OR_KMSG);
125         log_parse_environment();
126
127         if ((n = sd_listen_fds(true)) < 0) {
128                 log_error("Failed to read listening file descriptors from environment: %s", strerror(-r));
129                 return 1;
130         }
131
132         if (n != 1) {
133                 log_error("Need exactly one file descriptor.");
134                 return 2;
135         }
136
137         if (setsockopt(SD_LISTEN_FDS_START, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) {
138                 log_error("SO_PASSCRED failed: %m");
139                 return 3;
140         }
141
142         zero(c);
143         zero(pollfd);
144
145         pollfd[FD_SOCKET].fd = SD_LISTEN_FDS_START;
146         pollfd[FD_SOCKET].events = POLLIN;
147         pollfd[FD_SHUTDOWN_TIMER].fd = -1;
148         pollfd[FD_SHUTDOWN_TIMER].events = POLLIN;
149         pollfd[FD_NOLOGIN_TIMER].fd = -1;
150         pollfd[FD_NOLOGIN_TIMER].events = POLLIN;
151
152         log_debug("systemd-shutdownd running as pid %lu", (unsigned long) getpid());
153
154         sd_notify(false,
155                   "READY=1\n"
156                   "STATUS=Processing requests...");
157
158         do {
159                 int k;
160
161                 if (poll(pollfd, n_fds, -1) < 0) {
162
163                         if (errno == EAGAIN || errno == EINTR)
164                                 continue;
165
166                         log_error("poll(): %m");
167                         goto finish;
168                 }
169
170                 if (pollfd[FD_SOCKET].revents) {
171
172                         if ((k = read_packet(pollfd[FD_SOCKET].fd, &c)) < 0)
173                                 goto finish;
174                         else if (k > 0 && c.elapse > 0) {
175                                 struct itimerspec its;
176                                 char buf[27];
177
178                                 if (pollfd[FD_SHUTDOWN_TIMER].fd < 0)
179                                         if ((pollfd[FD_SHUTDOWN_TIMER].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) {
180                                                 log_error("timerfd_create(): %m");
181                                                 goto finish;
182                                         }
183
184                                 if (pollfd[FD_NOLOGIN_TIMER].fd < 0)
185                                         if ((pollfd[FD_NOLOGIN_TIMER].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) {
186                                                 log_error("timerfd_create(): %m");
187                                                 goto finish;
188                                         }
189
190                                 /* Disallow logins 5 minutes prior to shutdown */
191                                 zero(its);
192                                 timespec_store(&its.it_value, c.elapse > 5*USEC_PER_MINUTE ? c.elapse - 5*USEC_PER_MINUTE : 0);
193                                 if (timerfd_settime(pollfd[FD_NOLOGIN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
194                                         log_error("timerfd_settime(): %m");
195                                         goto finish;
196                                 }
197
198                                 /* Shutdown after the specified time is reached */
199                                 zero(its);
200                                 timespec_store(&its.it_value, c.elapse);
201                                 if (timerfd_settime(pollfd[FD_SHUTDOWN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) {
202                                         log_error("timerfd_settime(): %m");
203                                         goto finish;
204                                 }
205
206                                 n_fds = 3;
207
208                                 ctime_r(&its.it_value.tv_sec, buf);
209
210                                 sd_notifyf(false,
211                                            "STATUS=Shutting down at %s...",
212                                            strstrip(buf));
213                         }
214                 }
215
216                 if (pollfd[FD_NOLOGIN_TIMER].fd >= 0 &&
217                     pollfd[FD_NOLOGIN_TIMER].revents) {
218                         int e;
219
220                         if ((e = touch("/etc/nologin")) < 0)
221                                 log_error("Failed to create /etc/nologin: %s", strerror(-e));
222                         else
223                                 unlink_nologin = true;
224
225                         /* Disarm nologin timer */
226                         close_nointr_nofail(pollfd[FD_NOLOGIN_TIMER].fd);
227                         pollfd[FD_NOLOGIN_TIMER].fd = -1;
228                         n_fds = 2;
229
230                 }
231
232                 if (pollfd[FD_SHUTDOWN_TIMER].fd >= 0 &&
233                     pollfd[FD_SHUTDOWN_TIMER].revents) {
234                         exec_shutdown = true;
235                         goto finish;
236                 }
237
238         } while (c.elapse > 0);
239
240         r = 0;
241
242         log_debug("systemd-shutdownd stopped as pid %lu", (unsigned long) getpid());
243
244 finish:
245         if (pollfd[FD_SOCKET].fd >= 0)
246                 close_nointr_nofail(pollfd[FD_SOCKET].fd);
247
248         if (pollfd[FD_SHUTDOWN_TIMER].fd >= 0)
249                 close_nointr_nofail(pollfd[FD_SHUTDOWN_TIMER].fd);
250
251         if (pollfd[FD_NOLOGIN_TIMER].fd >= 0)
252                 close_nointr_nofail(pollfd[FD_NOLOGIN_TIMER].fd);
253
254         if (exec_shutdown) {
255                 char sw[3];
256
257                 sw[0] = '-';
258                 sw[1] = c.mode;
259                 sw[2] = 0;
260
261                 execl(SYSTEMCTL_BINARY_PATH, "shutdown", sw, "now", NULL);
262                 log_error("Failed to execute /sbin/shutdown: %m");
263         }
264
265         if (unlink_nologin)
266                 unlink("/etc/nologin");
267
268         sd_notify(false,
269                   "STATUS=Exiting...");
270
271         return r;
272 }