]>
Commit | Line | Data |
---|---|---|
f6144808 LP |
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" | |
9be9828c | 37 | #include "utmp-wtmp.h" |
f6144808 LP |
38 | |
39 | static int read_packet(int fd, struct shutdownd_command *_c) { | |
40 | struct msghdr msghdr; | |
41 | struct iovec iovec; | |
42 | struct ucred *ucred; | |
43 | union { | |
44 | struct cmsghdr cmsghdr; | |
45 | uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; | |
46 | } control; | |
47 | struct shutdownd_command c; | |
48 | ssize_t n; | |
49 | ||
50 | assert(fd >= 0); | |
51 | assert(_c); | |
52 | ||
53 | zero(iovec); | |
54 | iovec.iov_base = &c; | |
55 | iovec.iov_len = sizeof(c); | |
56 | ||
57 | zero(control); | |
58 | zero(msghdr); | |
59 | msghdr.msg_iov = &iovec; | |
60 | msghdr.msg_iovlen = 1; | |
61 | msghdr.msg_control = &control; | |
62 | msghdr.msg_controllen = sizeof(control); | |
63 | ||
64 | if ((n = recvmsg(fd, &msghdr, MSG_DONTWAIT)) <= 0) { | |
65 | if (n >= 0) { | |
66 | log_error("Short read"); | |
67 | return -EIO; | |
68 | } | |
69 | ||
70 | if (errno == EAGAIN || errno == EINTR) | |
71 | return 0; | |
72 | ||
73 | log_error("recvmsg(): %m"); | |
74 | return -errno; | |
75 | } | |
76 | ||
77 | if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || | |
78 | control.cmsghdr.cmsg_level != SOL_SOCKET || | |
79 | control.cmsghdr.cmsg_type != SCM_CREDENTIALS || | |
80 | control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { | |
81 | log_warning("Received message without credentials. Ignoring."); | |
82 | return 0; | |
83 | } | |
84 | ||
85 | ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); | |
86 | if (ucred->uid != 0) { | |
87 | log_warning("Got request from unprivileged user. Ignoring."); | |
88 | return 0; | |
89 | } | |
90 | ||
91 | if (n != sizeof(c)) { | |
6b5ad000 | 92 | log_warning("Message has invalid size. Ignoring"); |
f6144808 LP |
93 | return 0; |
94 | } | |
95 | ||
9be9828c LP |
96 | char_array_0(c.wall_message); |
97 | ||
f6144808 LP |
98 | *_c = c; |
99 | return 1; | |
100 | } | |
101 | ||
08e4b1c5 | 102 | static void warn_wall(usec_t n, struct shutdownd_command *c) { |
9be9828c LP |
103 | |
104 | assert(c); | |
105 | assert(c->warn_wall); | |
106 | ||
08e4b1c5 LP |
107 | if (n >= c->elapse) |
108 | return; | |
109 | ||
9be9828c | 110 | if (c->wall_message[0]) |
7af53310 | 111 | utmp_wall(c->wall_message, NULL); |
9be9828c | 112 | else { |
11620592 | 113 | char date[FORMAT_TIMESTAMP_MAX]; |
9be9828c | 114 | const char* prefix; |
08e4b1c5 | 115 | char *l = NULL; |
9be9828c | 116 | |
9be9828c | 117 | if (c->mode == 'H') |
08e4b1c5 | 118 | prefix = "The system is going down for system halt at "; |
9be9828c | 119 | else if (c->mode == 'P') |
08e4b1c5 | 120 | prefix = "The system is going down for power-off at "; |
9be9828c | 121 | else if (c->mode == 'r') |
08e4b1c5 | 122 | prefix = "The system is going down for reboot at "; |
9be9828c LP |
123 | else |
124 | assert_not_reached("Unknown mode!"); | |
125 | ||
08e4b1c5 | 126 | if (asprintf(&l, "%s%s!", prefix, format_timestamp(date, sizeof(date), c->elapse)) < 0) |
9be9828c LP |
127 | log_error("Failed to allocate wall message"); |
128 | else { | |
7af53310 | 129 | utmp_wall(l, NULL); |
9be9828c LP |
130 | free(l); |
131 | } | |
132 | } | |
133 | } | |
134 | ||
135 | static usec_t when_wall(usec_t n, usec_t elapse) { | |
136 | ||
137 | static const struct { | |
138 | usec_t delay; | |
139 | usec_t interval; | |
140 | } table[] = { | |
141 | { 10 * USEC_PER_MINUTE, USEC_PER_MINUTE }, | |
142 | { USEC_PER_HOUR, 15 * USEC_PER_MINUTE }, | |
143 | { 3 * USEC_PER_HOUR, 30 * USEC_PER_MINUTE } | |
144 | }; | |
145 | ||
146 | usec_t left, sub; | |
147 | unsigned i; | |
148 | ||
149 | /* If the time is already passed, then don't announce */ | |
150 | if (n >= elapse) | |
151 | return 0; | |
152 | ||
153 | left = elapse - n; | |
154 | for (i = 0; i < ELEMENTSOF(table); i++) | |
08e4b1c5 | 155 | if (n + table[i].delay >= elapse) { |
9be9828c | 156 | sub = ((left / table[i].interval) * table[i].interval); |
08e4b1c5 LP |
157 | break; |
158 | } | |
9be9828c LP |
159 | |
160 | if (i >= ELEMENTSOF(table)) | |
161 | sub = ((left / USEC_PER_HOUR) * USEC_PER_HOUR); | |
162 | ||
163 | return elapse > sub ? elapse - sub : 1; | |
164 | } | |
165 | ||
166 | static usec_t when_nologin(usec_t elapse) { | |
167 | return elapse > 5*USEC_PER_MINUTE ? elapse - 5*USEC_PER_MINUTE : 1; | |
168 | } | |
169 | ||
f6144808 LP |
170 | int main(int argc, char *argv[]) { |
171 | enum { | |
172 | FD_SOCKET, | |
9be9828c | 173 | FD_WALL_TIMER, |
f6144808 | 174 | FD_NOLOGIN_TIMER, |
9be9828c | 175 | FD_SHUTDOWN_TIMER, |
f6144808 LP |
176 | _FD_MAX |
177 | }; | |
178 | ||
22f4096c | 179 | int r = EXIT_FAILURE, n_fds; |
f6144808 | 180 | int one = 1; |
f6144808 LP |
181 | struct shutdownd_command c; |
182 | struct pollfd pollfd[_FD_MAX]; | |
9be9828c LP |
183 | bool exec_shutdown = false, unlink_nologin = false, failed = false; |
184 | unsigned i; | |
f6144808 LP |
185 | |
186 | if (getppid() != 1) { | |
187 | log_error("This program should be invoked by init only."); | |
22f4096c | 188 | return EXIT_FAILURE; |
f6144808 LP |
189 | } |
190 | ||
191 | if (argc > 1) { | |
192 | log_error("This program does not take arguments."); | |
22f4096c | 193 | return EXIT_FAILURE; |
f6144808 LP |
194 | } |
195 | ||
196 | log_set_target(LOG_TARGET_SYSLOG_OR_KMSG); | |
197 | log_parse_environment(); | |
2396fb04 | 198 | log_open(); |
f6144808 | 199 | |
9be9828c | 200 | if ((n_fds = sd_listen_fds(true)) < 0) { |
f6144808 | 201 | log_error("Failed to read listening file descriptors from environment: %s", strerror(-r)); |
22f4096c | 202 | return EXIT_FAILURE; |
f6144808 LP |
203 | } |
204 | ||
9be9828c | 205 | if (n_fds != 1) { |
f6144808 | 206 | log_error("Need exactly one file descriptor."); |
22f4096c | 207 | return EXIT_FAILURE; |
f6144808 LP |
208 | } |
209 | ||
210 | if (setsockopt(SD_LISTEN_FDS_START, SOL_SOCKET, SO_PASSCRED, &one, sizeof(one)) < 0) { | |
211 | log_error("SO_PASSCRED failed: %m"); | |
22f4096c | 212 | return EXIT_FAILURE; |
f6144808 LP |
213 | } |
214 | ||
215 | zero(c); | |
216 | zero(pollfd); | |
217 | ||
218 | pollfd[FD_SOCKET].fd = SD_LISTEN_FDS_START; | |
219 | pollfd[FD_SOCKET].events = POLLIN; | |
9be9828c LP |
220 | |
221 | for (i = 0; i < _FD_MAX; i++) { | |
222 | ||
223 | if (i == FD_SOCKET) | |
224 | continue; | |
225 | ||
226 | pollfd[i].events = POLLIN; | |
227 | ||
228 | if ((pollfd[i].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC)) < 0) { | |
229 | log_error("timerfd_create(): %m"); | |
da19d5c1 | 230 | failed = true; |
9be9828c LP |
231 | } |
232 | } | |
233 | ||
234 | if (failed) | |
235 | goto finish; | |
f6144808 LP |
236 | |
237 | log_debug("systemd-shutdownd running as pid %lu", (unsigned long) getpid()); | |
238 | ||
239 | sd_notify(false, | |
240 | "READY=1\n" | |
241 | "STATUS=Processing requests..."); | |
242 | ||
243 | do { | |
244 | int k; | |
9be9828c | 245 | usec_t n; |
f6144808 | 246 | |
9be9828c | 247 | if (poll(pollfd, _FD_MAX, -1) < 0) { |
f6144808 LP |
248 | |
249 | if (errno == EAGAIN || errno == EINTR) | |
250 | continue; | |
251 | ||
252 | log_error("poll(): %m"); | |
253 | goto finish; | |
254 | } | |
255 | ||
9be9828c LP |
256 | n = now(CLOCK_REALTIME); |
257 | ||
f6144808 LP |
258 | if (pollfd[FD_SOCKET].revents) { |
259 | ||
260 | if ((k = read_packet(pollfd[FD_SOCKET].fd, &c)) < 0) | |
261 | goto finish; | |
262 | else if (k > 0 && c.elapse > 0) { | |
263 | struct itimerspec its; | |
11620592 | 264 | char date[FORMAT_TIMESTAMP_MAX]; |
f6144808 | 265 | |
9be9828c LP |
266 | if (c.warn_wall) { |
267 | /* Send wall messages every so often */ | |
268 | zero(its); | |
269 | timespec_store(&its.it_value, when_wall(n, c.elapse)); | |
270 | if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { | |
271 | log_error("timerfd_settime(): %m"); | |
f6144808 LP |
272 | goto finish; |
273 | } | |
274 | ||
9be9828c LP |
275 | /* Warn immediately if less than 15 minutes are left */ |
276 | if (n < c.elapse && | |
277 | n + 15*USEC_PER_MINUTE >= c.elapse) | |
08e4b1c5 | 278 | warn_wall(n, &c); |
9be9828c LP |
279 | } |
280 | ||
f6144808 LP |
281 | /* Disallow logins 5 minutes prior to shutdown */ |
282 | zero(its); | |
9be9828c | 283 | timespec_store(&its.it_value, when_nologin(c.elapse)); |
f6144808 LP |
284 | if (timerfd_settime(pollfd[FD_NOLOGIN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { |
285 | log_error("timerfd_settime(): %m"); | |
286 | goto finish; | |
287 | } | |
288 | ||
289 | /* Shutdown after the specified time is reached */ | |
290 | zero(its); | |
291 | timespec_store(&its.it_value, c.elapse); | |
292 | if (timerfd_settime(pollfd[FD_SHUTDOWN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { | |
293 | log_error("timerfd_settime(): %m"); | |
294 | goto finish; | |
295 | } | |
296 | ||
f6144808 LP |
297 | sd_notifyf(false, |
298 | "STATUS=Shutting down at %s...", | |
11620592 | 299 | format_timestamp(date, sizeof(date), c.elapse)); |
f6144808 LP |
300 | } |
301 | } | |
302 | ||
9be9828c LP |
303 | if (pollfd[FD_WALL_TIMER].revents) { |
304 | struct itimerspec its; | |
305 | ||
08e4b1c5 | 306 | warn_wall(n, &c); |
9be9828c LP |
307 | flush_fd(pollfd[FD_WALL_TIMER].fd); |
308 | ||
309 | /* Restart timer */ | |
310 | zero(its); | |
311 | timespec_store(&its.it_value, when_wall(n, c.elapse)); | |
312 | if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { | |
313 | log_error("timerfd_settime(): %m"); | |
314 | goto finish; | |
315 | } | |
316 | } | |
317 | ||
318 | if (pollfd[FD_NOLOGIN_TIMER].revents) { | |
f6144808 LP |
319 | int e; |
320 | ||
ef9ffbd8 | 321 | log_info("Creating /var/run/nologin, blocking further logins..."); |
08e4b1c5 | 322 | |
ef9ffbd8 LP |
323 | if ((e = write_one_line_file("/var/run/nologin", "System is going down.")) < 0) |
324 | log_error("Failed to create /var/run/nologin: %s", strerror(-e)); | |
f6144808 LP |
325 | else |
326 | unlink_nologin = true; | |
327 | ||
9be9828c | 328 | flush_fd(pollfd[FD_NOLOGIN_TIMER].fd); |
f6144808 LP |
329 | } |
330 | ||
9be9828c | 331 | if (pollfd[FD_SHUTDOWN_TIMER].revents) { |
f6144808 LP |
332 | exec_shutdown = true; |
333 | goto finish; | |
334 | } | |
335 | ||
336 | } while (c.elapse > 0); | |
337 | ||
22f4096c | 338 | r = EXIT_SUCCESS; |
f6144808 LP |
339 | |
340 | log_debug("systemd-shutdownd stopped as pid %lu", (unsigned long) getpid()); | |
341 | ||
342 | finish: | |
f6144808 | 343 | |
9be9828c LP |
344 | for (i = 0; i < _FD_MAX; i++) |
345 | if (pollfd[i].fd >= 0) | |
346 | close_nointr_nofail(pollfd[i].fd); | |
f6144808 | 347 | |
16061c20 | 348 | if (unlink_nologin) |
ef9ffbd8 | 349 | unlink("/var/run/nologin"); |
16061c20 | 350 | |
f6144808 LP |
351 | if (exec_shutdown) { |
352 | char sw[3]; | |
353 | ||
354 | sw[0] = '-'; | |
355 | sw[1] = c.mode; | |
356 | sw[2] = 0; | |
357 | ||
08e4b1c5 LP |
358 | execl(SYSTEMCTL_BINARY_PATH, |
359 | "shutdown", | |
360 | sw, | |
361 | "now", | |
362 | (c.warn_wall && c.wall_message[0]) ? c.wall_message : | |
363 | (c.warn_wall ? NULL : "--no-wall"), | |
364 | NULL); | |
365 | ||
f6144808 LP |
366 | log_error("Failed to execute /sbin/shutdown: %m"); |
367 | } | |
368 | ||
f6144808 LP |
369 | sd_notify(false, |
370 | "STATUS=Exiting..."); | |
371 | ||
372 | return r; | |
373 | } |