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