]>
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 | |
5430f7f2 LP |
9 | under the terms of the GNU Lesser General Public License as published by |
10 | the Free Software Foundation; either version 2.1 of the License, or | |
f6144808 LP |
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 | |
5430f7f2 | 16 | Lesser General Public License for more details. |
f6144808 | 17 | |
5430f7f2 | 18 | You should have received a copy of the GNU Lesser General Public License |
f6144808 LP |
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> | |
04ebb595 | 31 | #include <stddef.h> |
f6144808 | 32 | |
81527be1 | 33 | #include <systemd/sd-daemon.h> |
04ebb595 | 34 | #include <systemd/sd-shutdown.h> |
81527be1 | 35 | |
f6144808 LP |
36 | #include "log.h" |
37 | #include "macro.h" | |
38 | #include "util.h" | |
9be9828c | 39 | #include "utmp-wtmp.h" |
04ebb595 | 40 | #include "mkdir.h" |
a5c32cff | 41 | #include "fileio.h" |
f6144808 | 42 | |
04ebb595 LP |
43 | union shutdown_buffer { |
44 | struct sd_shutdown_command command; | |
45 | char space[offsetof(struct sd_shutdown_command, wall_message) + LINE_MAX]; | |
46 | }; | |
47 | ||
48 | static int read_packet(int fd, union shutdown_buffer *_b) { | |
f6144808 LP |
49 | struct msghdr msghdr; |
50 | struct iovec iovec; | |
51 | struct ucred *ucred; | |
52 | union { | |
53 | struct cmsghdr cmsghdr; | |
54 | uint8_t buf[CMSG_SPACE(sizeof(struct ucred))]; | |
55 | } control; | |
f6144808 | 56 | ssize_t n; |
04ebb595 | 57 | union shutdown_buffer b; /* We maintain our own copy here, in order not to corrupt the last message */ |
f6144808 LP |
58 | |
59 | assert(fd >= 0); | |
04ebb595 | 60 | assert(_b); |
f6144808 LP |
61 | |
62 | zero(iovec); | |
04ebb595 LP |
63 | iovec.iov_base = &b; |
64 | iovec.iov_len = sizeof(b) - 1; | |
f6144808 LP |
65 | |
66 | zero(control); | |
67 | zero(msghdr); | |
68 | msghdr.msg_iov = &iovec; | |
69 | msghdr.msg_iovlen = 1; | |
70 | msghdr.msg_control = &control; | |
71 | msghdr.msg_controllen = sizeof(control); | |
72 | ||
04ebb595 LP |
73 | n = recvmsg(fd, &msghdr, MSG_DONTWAIT); |
74 | if (n <= 0) { | |
75 | if (n == 0) { | |
f6144808 LP |
76 | log_error("Short read"); |
77 | return -EIO; | |
78 | } | |
79 | ||
80 | if (errno == EAGAIN || errno == EINTR) | |
81 | return 0; | |
82 | ||
83 | log_error("recvmsg(): %m"); | |
84 | return -errno; | |
85 | } | |
86 | ||
87 | if (msghdr.msg_controllen < CMSG_LEN(sizeof(struct ucred)) || | |
88 | control.cmsghdr.cmsg_level != SOL_SOCKET || | |
89 | control.cmsghdr.cmsg_type != SCM_CREDENTIALS || | |
90 | control.cmsghdr.cmsg_len != CMSG_LEN(sizeof(struct ucred))) { | |
91 | log_warning("Received message without credentials. Ignoring."); | |
92 | return 0; | |
93 | } | |
94 | ||
95 | ucred = (struct ucred*) CMSG_DATA(&control.cmsghdr); | |
96 | if (ucred->uid != 0) { | |
97 | log_warning("Got request from unprivileged user. Ignoring."); | |
98 | return 0; | |
99 | } | |
100 | ||
04ebb595 LP |
101 | if ((size_t) n < offsetof(struct sd_shutdown_command, wall_message)) { |
102 | log_warning("Message has invalid size. Ignoring."); | |
103 | return 0; | |
104 | } | |
105 | ||
106 | if (b.command.mode != SD_SHUTDOWN_NONE && | |
107 | b.command.mode != SD_SHUTDOWN_REBOOT && | |
108 | b.command.mode != SD_SHUTDOWN_POWEROFF && | |
109 | b.command.mode != SD_SHUTDOWN_HALT && | |
110 | b.command.mode != SD_SHUTDOWN_KEXEC) { | |
111 | log_warning("Message has invalid mode. Ignoring."); | |
f6144808 LP |
112 | return 0; |
113 | } | |
114 | ||
04ebb595 | 115 | b.space[n] = 0; |
9be9828c | 116 | |
04ebb595 | 117 | *_b = b; |
f6144808 LP |
118 | return 1; |
119 | } | |
120 | ||
04ebb595 | 121 | static void warn_wall(usec_t n, struct sd_shutdown_command *c) { |
30923233 MS |
122 | char date[FORMAT_TIMESTAMP_MAX]; |
123 | const char *prefix; | |
124 | char *l = NULL; | |
9be9828c LP |
125 | |
126 | assert(c); | |
127 | assert(c->warn_wall); | |
128 | ||
04ebb595 | 129 | if (n >= c->usec) |
08e4b1c5 LP |
130 | return; |
131 | ||
04ebb595 | 132 | if (c->mode == SD_SHUTDOWN_HALT) |
30923233 | 133 | prefix = "The system is going down for system halt at "; |
04ebb595 | 134 | else if (c->mode == SD_SHUTDOWN_POWEROFF) |
30923233 | 135 | prefix = "The system is going down for power-off at "; |
04ebb595 | 136 | else if (c->mode == SD_SHUTDOWN_REBOOT) |
30923233 | 137 | prefix = "The system is going down for reboot at "; |
04ebb595 LP |
138 | else if (c->mode == SD_SHUTDOWN_KEXEC) |
139 | prefix = "The system is going down for kexec reboot at "; | |
dfcc5c33 MS |
140 | else if (c->mode == SD_SHUTDOWN_NONE) |
141 | prefix = "The system shutdown has been cancelled at "; | |
30923233 MS |
142 | else |
143 | assert_not_reached("Unknown mode!"); | |
144 | ||
145 | if (asprintf(&l, "%s%s%s%s!", c->wall_message, c->wall_message[0] ? "\n" : "", | |
04ebb595 | 146 | prefix, format_timestamp(date, sizeof(date), c->usec)) < 0) |
30923233 | 147 | log_error("Failed to allocate wall message"); |
9be9828c | 148 | else { |
30923233 MS |
149 | utmp_wall(l, NULL); |
150 | free(l); | |
9be9828c LP |
151 | } |
152 | } | |
153 | ||
154 | static usec_t when_wall(usec_t n, usec_t elapse) { | |
155 | ||
156 | static const struct { | |
157 | usec_t delay; | |
158 | usec_t interval; | |
159 | } table[] = { | |
7a4b2eab ZJS |
160 | { 0, USEC_PER_MINUTE }, |
161 | { 10 * USEC_PER_MINUTE, 15 * USEC_PER_MINUTE }, | |
162 | { USEC_PER_HOUR, 30 * USEC_PER_MINUTE }, | |
163 | { 3 * USEC_PER_HOUR, USEC_PER_HOUR }, | |
9be9828c LP |
164 | }; |
165 | ||
166 | usec_t left, sub; | |
7a4b2eab | 167 | unsigned i = ELEMENTSOF(table) - 1; |
9be9828c LP |
168 | |
169 | /* If the time is already passed, then don't announce */ | |
170 | if (n >= elapse) | |
171 | return 0; | |
172 | ||
173 | left = elapse - n; | |
7a4b2eab ZJS |
174 | while (left < table[i].delay) |
175 | i--; | |
176 | sub = (left / table[i].interval) * table[i].interval; | |
9be9828c | 177 | |
7a4b2eab ZJS |
178 | assert(sub < elapse); |
179 | return elapse - sub; | |
9be9828c LP |
180 | } |
181 | ||
182 | static usec_t when_nologin(usec_t elapse) { | |
183 | return elapse > 5*USEC_PER_MINUTE ? elapse - 5*USEC_PER_MINUTE : 1; | |
184 | } | |
185 | ||
04ebb595 LP |
186 | static const char *mode_to_string(enum sd_shutdown_mode m) { |
187 | switch (m) { | |
188 | case SD_SHUTDOWN_REBOOT: | |
189 | return "reboot"; | |
190 | case SD_SHUTDOWN_POWEROFF: | |
191 | return "poweroff"; | |
192 | case SD_SHUTDOWN_HALT: | |
193 | return "halt"; | |
194 | case SD_SHUTDOWN_KEXEC: | |
195 | return "kexec"; | |
196 | default: | |
197 | return NULL; | |
198 | } | |
199 | } | |
200 | ||
201 | static int update_schedule_file(struct sd_shutdown_command *c) { | |
202 | int r; | |
203 | FILE *f; | |
204 | char *temp_path, *t; | |
205 | ||
206 | assert(c); | |
207 | ||
d2e54fae | 208 | r = mkdir_safe_label("/run/systemd/shutdown", 0755, 0, 0); |
04ebb595 LP |
209 | if (r < 0) { |
210 | log_error("Failed to create shutdown subdirectory: %s", strerror(-r)); | |
211 | return r; | |
212 | } | |
213 | ||
214 | t = cescape(c->wall_message); | |
0d0f0c50 SL |
215 | if (!t) |
216 | return log_oom(); | |
04ebb595 LP |
217 | |
218 | r = fopen_temporary("/run/systemd/shutdown/scheduled", &f, &temp_path); | |
219 | if (r < 0) { | |
220 | log_error("Failed to save information about scheduled shutdowns: %s", strerror(-r)); | |
221 | free(t); | |
222 | return r; | |
223 | } | |
224 | ||
225 | fchmod(fileno(f), 0644); | |
226 | ||
227 | fprintf(f, | |
228 | "USEC=%llu\n" | |
229 | "WARN_WALL=%i\n" | |
230 | "MODE=%s\n", | |
231 | (unsigned long long) c->usec, | |
232 | c->warn_wall, | |
233 | mode_to_string(c->mode)); | |
234 | ||
235 | if (c->dry_run) | |
236 | fputs("DRY_RUN=1\n", f); | |
237 | ||
238 | if (!isempty(t)) | |
239 | fprintf(f, "WALL_MESSAGE=%s\n", t); | |
240 | ||
241 | free(t); | |
242 | ||
243 | fflush(f); | |
244 | ||
245 | if (ferror(f) || rename(temp_path, "/run/systemd/shutdown/scheduled") < 0) { | |
246 | log_error("Failed to write information about scheduled shutdowns: %m"); | |
247 | r = -errno; | |
248 | ||
249 | unlink(temp_path); | |
250 | unlink("/run/systemd/shutdown/scheduled"); | |
251 | } | |
252 | ||
253 | fclose(f); | |
254 | free(temp_path); | |
255 | ||
256 | return r; | |
257 | } | |
258 | ||
259 | static bool scheduled(struct sd_shutdown_command *c) { | |
260 | return c->usec > 0 && c->mode != SD_SHUTDOWN_NONE; | |
261 | } | |
262 | ||
f6144808 LP |
263 | int main(int argc, char *argv[]) { |
264 | enum { | |
265 | FD_SOCKET, | |
9be9828c | 266 | FD_WALL_TIMER, |
f6144808 | 267 | FD_NOLOGIN_TIMER, |
9be9828c | 268 | FD_SHUTDOWN_TIMER, |
f6144808 LP |
269 | _FD_MAX |
270 | }; | |
271 | ||
22f4096c | 272 | int r = EXIT_FAILURE, n_fds; |
04ebb595 | 273 | union shutdown_buffer b; |
f6144808 | 274 | struct pollfd pollfd[_FD_MAX]; |
04ebb595 | 275 | bool exec_shutdown = false, unlink_nologin = false; |
9be9828c | 276 | unsigned i; |
f6144808 LP |
277 | |
278 | if (getppid() != 1) { | |
279 | log_error("This program should be invoked by init only."); | |
22f4096c | 280 | return EXIT_FAILURE; |
f6144808 LP |
281 | } |
282 | ||
283 | if (argc > 1) { | |
284 | log_error("This program does not take arguments."); | |
22f4096c | 285 | return EXIT_FAILURE; |
f6144808 LP |
286 | } |
287 | ||
4cfa2c99 | 288 | log_set_target(LOG_TARGET_AUTO); |
f6144808 | 289 | log_parse_environment(); |
2396fb04 | 290 | log_open(); |
f6144808 | 291 | |
4c12626c LP |
292 | umask(0022); |
293 | ||
04ebb595 LP |
294 | n_fds = sd_listen_fds(true); |
295 | if (n_fds < 0) { | |
f6144808 | 296 | log_error("Failed to read listening file descriptors from environment: %s", strerror(-r)); |
22f4096c | 297 | return EXIT_FAILURE; |
f6144808 LP |
298 | } |
299 | ||
9be9828c | 300 | if (n_fds != 1) { |
f6144808 | 301 | log_error("Need exactly one file descriptor."); |
22f4096c | 302 | return EXIT_FAILURE; |
f6144808 LP |
303 | } |
304 | ||
04ebb595 | 305 | zero(b); |
f6144808 LP |
306 | zero(pollfd); |
307 | ||
308 | pollfd[FD_SOCKET].fd = SD_LISTEN_FDS_START; | |
309 | pollfd[FD_SOCKET].events = POLLIN; | |
9be9828c | 310 | |
04ebb595 | 311 | for (i = FD_WALL_TIMER; i < _FD_MAX; i++) { |
9be9828c | 312 | pollfd[i].events = POLLIN; |
04ebb595 LP |
313 | pollfd[i].fd = timerfd_create(CLOCK_REALTIME, TFD_NONBLOCK|TFD_CLOEXEC); |
314 | if (pollfd[i].fd < 0) { | |
9be9828c | 315 | log_error("timerfd_create(): %m"); |
04ebb595 | 316 | goto finish; |
9be9828c LP |
317 | } |
318 | } | |
319 | ||
f6144808 LP |
320 | log_debug("systemd-shutdownd running as pid %lu", (unsigned long) getpid()); |
321 | ||
322 | sd_notify(false, | |
323 | "READY=1\n" | |
324 | "STATUS=Processing requests..."); | |
325 | ||
04ebb595 | 326 | for (;;) { |
f6144808 | 327 | int k; |
9be9828c | 328 | usec_t n; |
f6144808 | 329 | |
04ebb595 LP |
330 | k = poll(pollfd, _FD_MAX, scheduled(&b.command) ? -1 : 0); |
331 | if (k < 0) { | |
f6144808 LP |
332 | |
333 | if (errno == EAGAIN || errno == EINTR) | |
334 | continue; | |
335 | ||
336 | log_error("poll(): %m"); | |
337 | goto finish; | |
338 | } | |
339 | ||
04ebb595 LP |
340 | /* Exit on idle */ |
341 | if (k == 0) | |
342 | break; | |
343 | ||
9be9828c LP |
344 | n = now(CLOCK_REALTIME); |
345 | ||
f6144808 LP |
346 | if (pollfd[FD_SOCKET].revents) { |
347 | ||
04ebb595 LP |
348 | k = read_packet(pollfd[FD_SOCKET].fd, &b); |
349 | if (k < 0) | |
f6144808 | 350 | goto finish; |
04ebb595 | 351 | else if (k > 0) { |
f6144808 | 352 | struct itimerspec its; |
11620592 | 353 | char date[FORMAT_TIMESTAMP_MAX]; |
f6144808 | 354 | |
04ebb595 LP |
355 | if (!scheduled(&b.command)) { |
356 | log_info("Shutdown canceled."); | |
dfcc5c33 MS |
357 | if (b.command.warn_wall) |
358 | warn_wall(0, &b.command); | |
04ebb595 LP |
359 | break; |
360 | } | |
361 | ||
362 | zero(its); | |
363 | if (b.command.warn_wall) { | |
9be9828c | 364 | /* Send wall messages every so often */ |
04ebb595 | 365 | timespec_store(&its.it_value, when_wall(n, b.command.usec)); |
f6144808 | 366 | |
9be9828c | 367 | /* Warn immediately if less than 15 minutes are left */ |
04ebb595 LP |
368 | if (n < b.command.usec && |
369 | n + 15*USEC_PER_MINUTE >= b.command.usec) | |
370 | warn_wall(n, &b.command); | |
371 | } | |
372 | if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { | |
373 | log_error("timerfd_settime(): %m"); | |
374 | goto finish; | |
9be9828c LP |
375 | } |
376 | ||
f6144808 LP |
377 | /* Disallow logins 5 minutes prior to shutdown */ |
378 | zero(its); | |
04ebb595 | 379 | timespec_store(&its.it_value, when_nologin(b.command.usec)); |
f6144808 LP |
380 | if (timerfd_settime(pollfd[FD_NOLOGIN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { |
381 | log_error("timerfd_settime(): %m"); | |
382 | goto finish; | |
383 | } | |
384 | ||
385 | /* Shutdown after the specified time is reached */ | |
386 | zero(its); | |
04ebb595 | 387 | timespec_store(&its.it_value, b.command.usec); |
f6144808 LP |
388 | if (timerfd_settime(pollfd[FD_SHUTDOWN_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { |
389 | log_error("timerfd_settime(): %m"); | |
390 | goto finish; | |
391 | } | |
392 | ||
04ebb595 LP |
393 | update_schedule_file(&b.command); |
394 | ||
f6144808 | 395 | sd_notifyf(false, |
04ebb595 LP |
396 | "STATUS=Shutting down at %s (%s)...", |
397 | format_timestamp(date, sizeof(date), b.command.usec), | |
398 | mode_to_string(b.command.mode)); | |
399 | ||
400 | log_info("Shutting down at %s (%s)...", date, mode_to_string(b.command.mode)); | |
f6144808 LP |
401 | } |
402 | } | |
403 | ||
9be9828c LP |
404 | if (pollfd[FD_WALL_TIMER].revents) { |
405 | struct itimerspec its; | |
406 | ||
04ebb595 | 407 | warn_wall(n, &b.command); |
9be9828c LP |
408 | flush_fd(pollfd[FD_WALL_TIMER].fd); |
409 | ||
410 | /* Restart timer */ | |
411 | zero(its); | |
04ebb595 | 412 | timespec_store(&its.it_value, when_wall(n, b.command.usec)); |
9be9828c LP |
413 | if (timerfd_settime(pollfd[FD_WALL_TIMER].fd, TFD_TIMER_ABSTIME, &its, NULL) < 0) { |
414 | log_error("timerfd_settime(): %m"); | |
415 | goto finish; | |
416 | } | |
417 | } | |
418 | ||
419 | if (pollfd[FD_NOLOGIN_TIMER].revents) { | |
f6144808 LP |
420 | int e; |
421 | ||
db019b8d | 422 | log_info("Creating /run/nologin, blocking further logins..."); |
08e4b1c5 | 423 | |
574d5f2d | 424 | e = write_string_file_atomic("/run/nologin", "System is going down."); |
04ebb595 | 425 | if (e < 0) |
db019b8d | 426 | log_error("Failed to create /run/nologin: %s", strerror(-e)); |
f6144808 LP |
427 | else |
428 | unlink_nologin = true; | |
429 | ||
9be9828c | 430 | flush_fd(pollfd[FD_NOLOGIN_TIMER].fd); |
f6144808 LP |
431 | } |
432 | ||
9be9828c | 433 | if (pollfd[FD_SHUTDOWN_TIMER].revents) { |
f6144808 LP |
434 | exec_shutdown = true; |
435 | goto finish; | |
436 | } | |
04ebb595 | 437 | } |
f6144808 | 438 | |
22f4096c | 439 | r = EXIT_SUCCESS; |
f6144808 LP |
440 | |
441 | log_debug("systemd-shutdownd stopped as pid %lu", (unsigned long) getpid()); | |
442 | ||
443 | finish: | |
f6144808 | 444 | |
9be9828c LP |
445 | for (i = 0; i < _FD_MAX; i++) |
446 | if (pollfd[i].fd >= 0) | |
447 | close_nointr_nofail(pollfd[i].fd); | |
f6144808 | 448 | |
16061c20 | 449 | if (unlink_nologin) |
db019b8d | 450 | unlink("/run/nologin"); |
16061c20 | 451 | |
04ebb595 LP |
452 | unlink("/run/systemd/shutdown/scheduled"); |
453 | ||
454 | if (exec_shutdown && !b.command.dry_run) { | |
f6144808 LP |
455 | char sw[3]; |
456 | ||
457 | sw[0] = '-'; | |
04ebb595 | 458 | sw[1] = b.command.mode; |
f6144808 LP |
459 | sw[2] = 0; |
460 | ||
08e4b1c5 LP |
461 | execl(SYSTEMCTL_BINARY_PATH, |
462 | "shutdown", | |
463 | sw, | |
464 | "now", | |
04ebb595 LP |
465 | (b.command.warn_wall && b.command.wall_message[0]) ? b.command.wall_message : |
466 | (b.command.warn_wall ? NULL : "--no-wall"), | |
08e4b1c5 LP |
467 | NULL); |
468 | ||
f6144808 LP |
469 | log_error("Failed to execute /sbin/shutdown: %m"); |
470 | } | |
471 | ||
f6144808 LP |
472 | sd_notify(false, |
473 | "STATUS=Exiting..."); | |
474 | ||
475 | return r; | |
476 | } |