Gentoo Archives: gentoo-commits

From: William Hubbs <williamh@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] proj/openrc:master commit in: src/rc/, man/
Date: Thu, 18 Oct 2018 23:01:53
Message-Id: 1539903396.3f918161aafa61c1c2005709fda0b9bec4c412d8.williamh@OpenRC
1 commit: 3f918161aafa61c1c2005709fda0b9bec4c412d8
2 Author: William Hubbs <w.d.hubbs <AT> gmail <DOT> com>
3 AuthorDate: Fri Oct 5 19:10:59 2018 +0000
4 Commit: William Hubbs <williamh <AT> gentoo <DOT> org>
5 CommitDate: Thu Oct 18 22:56:36 2018 +0000
6 URL: https://gitweb.gentoo.org/proj/openrc.git/commit/?id=3f918161
7
8 openrc-shutdown: Add scheduled shutdown and the ability to cancel a shutdown
9
10 You can now schedule a shutdown for a certain time or a cpecific number
11 of minutes into the future.
12
13 When a shutdown is running, you can now cancel it with ^c from the
14 keyboard or by running "openrc-shutdown -c" from another shell.
15
16 man/openrc-shutdown.8 | 3 +
17 src/rc/Makefile | 4 +-
18 src/rc/broadcast.c | 209 +++++++++++++++++++++++++++++++++++++++++++++++
19 src/rc/broadcast.h | 16 ++++
20 src/rc/openrc-shutdown.c | 179 ++++++++++++++++++++++++++++++++++++++--
21 5 files changed, 402 insertions(+), 9 deletions(-)
22
23 diff --git a/man/openrc-shutdown.8 b/man/openrc-shutdown.8
24 index 5db21334..b09e8c20 100644
25 --- a/man/openrc-shutdown.8
26 +++ b/man/openrc-shutdown.8
27 @@ -16,6 +16,7 @@
28 .Nd bring the system down
29 .Sh SYNOPSIS
30 .Nm
31 +.Op Fl c , -cancel
32 .Op Fl d , -no-write
33 .Op Fl D , -dry-run
34 .Op Fl H , -halt
35 @@ -32,6 +33,8 @@ is the utility that communicates with
36 to bring down the system or instruct openrc-init to re-execute itself.
37 It supports the following options:
38 .Bl -tag -width "poweroff"
39 +.It Fl c , -cancel
40 +Cancel a pending shutdown.
41 .It Fl d , -no-write
42 Do not write the wtmp boot record.
43 .It Fl D , -dry-run
44
45 diff --git a/src/rc/Makefile b/src/rc/Makefile
46 index b09c5058..9ba240fa 100644
47 --- a/src/rc/Makefile
48 +++ b/src/rc/Makefile
49 @@ -14,7 +14,7 @@ SRCS+= rc-selinux.c
50 endif
51
52 ifeq (${OS},Linux)
53 -SRCS+= kill_all.c openrc-init.c openrc-shutdown.c rc-wtmp.c
54 +SRCS+= kill_all.c openrc-init.c openrc-shutdown.c broadcast.c rc-wtmp.c
55 endif
56
57 CLEANFILES= version.h rc-selinux.o
58 @@ -134,7 +134,7 @@ mountinfo: mountinfo.o _usage.o rc-misc.o
59 openrc rc: rc.o rc-logger.o rc-misc.o rc-plugin.o _usage.o
60 ${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ ${LDADD}
61
62 -openrc-shutdown: openrc-shutdown.o _usage.o rc-wtmp.o
63 +openrc-shutdown: openrc-shutdown.o rc-misc.o _usage.o broadcast.o rc-wtmp.o
64 ${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ ${LDADD}
65
66 openrc-run runscript: openrc-run.o _usage.o rc-misc.o rc-plugin.o
67
68 diff --git a/src/rc/broadcast.c b/src/rc/broadcast.c
69 new file mode 100644
70 index 00000000..dbc861d1
71 --- /dev/null
72 +++ b/src/rc/broadcast.c
73 @@ -0,0 +1,209 @@
74 +/*
75 + * broadcast.c
76 + * broadcast a message to every logged in user
77 + */
78 +
79 +/*
80 + * Copyright 2018 Sony Interactive Entertainment Inc.
81 + *
82 + * This file is part of OpenRC. It is subject to the license terms in
83 + * the LICENSE file found in the top-level directory of this
84 + * distribution and at https://github.com/OpenRC/openrc/blob/master/LICENSE
85 + * This file may not be copied, modified, propagated, or distributed
86 + * except according to the terms contained in the LICENSE file.
87 + */
88 +#include <ctype.h>
89 +#include <sys/types.h>
90 +#include <sys/stat.h>
91 +#include <sys/sysmacros.h>
92 +#include <limits.h>
93 +#include <stdlib.h>
94 +#include <string.h>
95 +#include <time.h>
96 +#include <unistd.h>
97 +#include <stdio.h>
98 +#include <utmpx.h>
99 +#include <pwd.h>
100 +#include <fcntl.h>
101 +#include <signal.h>
102 +#include <setjmp.h>
103 +#include <paths.h>
104 +#include <sys/utsname.h>
105 +
106 +#include "broadcast.h"
107 +#include "helpers.h"
108 +
109 +#ifndef _PATH_DEV
110 +# define _PATH_DEV "/dev/"
111 +#endif
112 +
113 +#ifndef UT_LINESIZE
114 +#define UT_LINESIZE __UT_LINESIZE
115 +#endif
116 +
117 +static sigjmp_buf jbuf;
118 +
119 +/*
120 + * Alarm handler
121 + */
122 +/*ARGSUSED*/
123 +# ifdef __GNUC__
124 +static void handler(int arg __attribute__((unused)))
125 +# else
126 +static void handler(int arg)
127 +# endif
128 +{
129 + siglongjmp(jbuf, 1);
130 +}
131 +
132 +static void getuidtty(char **userp, char **ttyp)
133 +{
134 + struct passwd *pwd;
135 + uid_t uid;
136 + char *tty;
137 + static char uidbuf[32];
138 + static char ttynm[UT_LINESIZE + 4];
139 +
140 + uid = getuid();
141 + if ((pwd = getpwuid(uid)) != NULL) {
142 + uidbuf[0] = 0;
143 + strncat(uidbuf, pwd->pw_name, sizeof(uidbuf) - 1);
144 + } else {
145 + if (uid)
146 + sprintf(uidbuf, "uid %d", (int) uid);
147 + else
148 + sprintf(uidbuf, "root");
149 + }
150 +
151 + if ((tty = ttyname(0)) != NULL) {
152 + const size_t plen = strlen(_PATH_DEV);
153 + if (strncmp(tty, _PATH_DEV, plen) == 0) {
154 + tty += plen;
155 + if (tty[0] == '/')
156 + tty++;
157 + }
158 + snprintf(ttynm, sizeof(ttynm), "(%.*s) ",
159 + UT_LINESIZE, tty);
160 + } else
161 + ttynm[0] = 0;
162 +
163 + *userp = uidbuf;
164 + *ttyp = ttynm;
165 +}
166 +
167 +/*
168 + * Check whether the given filename looks like a tty device.
169 + */
170 +static int file_isatty(const char *fname)
171 +{
172 + struct stat st;
173 + int major;
174 +
175 + if (stat(fname, &st) < 0)
176 + return 0;
177 +
178 + if (st.st_nlink != 1 || !S_ISCHR(st.st_mode))
179 + return 0;
180 +
181 + /*
182 + * It would be an impossible task to list all major/minors
183 + * of tty devices here, so we just exclude the obvious
184 + * majors of which just opening has side-effects:
185 + * printers and tapes.
186 + */
187 + major = major(st.st_dev);
188 + if (major == 1 || major == 2 || major == 6 || major == 9 ||
189 + major == 12 || major == 16 || major == 21 || major == 27 ||
190 + major == 37 || major == 96 || major == 97 || major == 206 ||
191 + major == 230)
192 + return 0;
193 + return 1;
194 +}
195 +
196 +/*
197 + * broadcast function.
198 + */
199 +void broadcast(char *text)
200 +{
201 + char *tty;
202 + char *user;
203 + struct utsname name;
204 + time_t t;
205 + char *date;
206 + char *p;
207 + char *line = NULL;
208 + struct sigaction sa;
209 + int fd;
210 + FILE *tp;
211 + int flags;
212 + char *term = NULL;
213 + struct utmpx *utmp;
214 +
215 + getuidtty(&user, &tty);
216 +
217 + /*
218 + * Get and report current hostname, to make it easier to find out
219 + * which machine is being shut down.
220 + */
221 + uname(&name);
222 +
223 + /* Get the time */
224 + time(&t);
225 + date = ctime(&t);
226 + p = strchr(date, '\n');
227 + if (p)
228 + *p = 0;
229 +
230 + xasprintf(&line, "\007\r\nBroadcast message from %s@%s %s(%s):\r\n\r\n",
231 + user, name.nodename, tty, date);
232 +
233 + /*
234 + * Fork to avoid hanging in a write()
235 + */
236 + if (fork() != 0)
237 + return;
238 +
239 + memset(&sa, 0, sizeof(sa));
240 + sa.sa_handler = handler;
241 + sigemptyset(&sa.sa_mask);
242 + sigaction(SIGALRM, &sa, NULL);
243 +
244 + setutxent();
245 +
246 + while ((utmp = getutxent()) != NULL) {
247 + if (utmp->ut_type != USER_PROCESS || utmp->ut_user[0] == 0)
248 + continue;
249 + if (strncmp(utmp->ut_line, _PATH_DEV, strlen(_PATH_DEV)) == 0)
250 + xasprintf(&term, "%s", utmp->ut_line);
251 + else
252 + xasprintf(&term, "%s%s", _PATH_DEV, utmp->ut_line);
253 + if (strstr(term, "/../")) {
254 + free(term);
255 + continue;
256 + }
257 +
258 + /*
259 + * Open it non-delay
260 + */
261 + if (sigsetjmp(jbuf, 1) == 0) {
262 + alarm(2);
263 + flags = O_WRONLY|O_NDELAY|O_NOCTTY;
264 + if (file_isatty(term) && (fd = open(term, flags)) >= 0) {
265 + if (isatty(fd) && (tp = fdopen(fd, "w")) != NULL) {
266 + fputs(line, tp);
267 + fputs(text, tp);
268 + fflush(tp);
269 + }
270 + }
271 + }
272 + alarm(0);
273 + if (fd >= 0)
274 + close(fd);
275 + if (tp != NULL)
276 + fclose(tp);
277 + free(term);
278 + }
279 + endutxent();
280 + free(line);
281 + exit(0);
282 +}
283
284 diff --git a/src/rc/broadcast.h b/src/rc/broadcast.h
285 new file mode 100644
286 index 00000000..5f948272
287 --- /dev/null
288 +++ b/src/rc/broadcast.h
289 @@ -0,0 +1,16 @@
290 +/*
291 + * Copyright 2018 Sony Interactive Entertainment Inc.
292 + *
293 + * This file is part of OpenRC. It is subject to the license terms in
294 + * the LICENSE file found in the top-level directory of this
295 + * distribution and at https://github.com/OpenRC/openrc/blob/master/LICENSE
296 + * This file may not be copied, modified, propagated, or distributed
297 + * except according to the terms contained in the LICENSE file.
298 + */
299 +
300 +#ifndef BROADCAST_H
301 +#define BROADCAST_H
302 +
303 +void broadcast(char *text);
304 +
305 +#endif
306
307 diff --git a/src/rc/openrc-shutdown.c b/src/rc/openrc-shutdown.c
308 index b17a63d8..ab2e7469 100644
309 --- a/src/rc/openrc-shutdown.c
310 +++ b/src/rc/openrc-shutdown.c
311 @@ -25,20 +25,24 @@
312 #include <stdio.h>
313 #include <stdlib.h>
314 #include <string.h>
315 +#include <syslog.h>
316 #include <unistd.h>
317 #include <sys/types.h>
318 #include <sys/utsname.h>
319
320 +#include "broadcast.h"
321 #include "einfo.h"
322 #include "rc.h"
323 #include "helpers.h"
324 +#include "rc-misc.h"
325 #include "_usage.h"
326 #include "rc-wtmp.h"
327
328 const char *applet = NULL;
329 const char *extraopts = NULL;
330 -const char *getoptstring = "dDHKpRrsw" getoptstring_COMMON;
331 +const char *getoptstring = "cdDfFHKpRrsw" getoptstring_COMMON;
332 const struct option longopts[] = {
333 + { "cancel", no_argument, NULL, 'c'},
334 { "no-write", no_argument, NULL, 'd'},
335 { "dry-run", no_argument, NULL, 'D'},
336 { "halt", no_argument, NULL, 'H'},
337 @@ -51,6 +55,7 @@ const struct option longopts[] = {
338 longopts_COMMON
339 };
340 const char * const longopts_help[] = {
341 + "cancel a pending shutdown",
342 "do not write wtmp record",
343 "print actions instead of executing them",
344 "halt the system",
345 @@ -64,8 +69,12 @@ const char * const longopts_help[] = {
346 };
347 const char *usagestring = NULL;
348 const char *exclusive = "Select one of "
349 -"--halt, --kexec, --poweroff, --reexec, --reboot, --single or --write-only";
350 + "--cancel, --halt, --kexec, --poweroff, --reexec, --reboot, --single or \n"
351 + "--write-only";
352 +const char *nologin_file = RC_SYSCONFDIR"/nologin";
353 +const char *shutdown_pid = "/run/openrc-shutdown.pid";
354
355 +static bool do_cancel = false;
356 static bool do_dryrun = false;
357 static bool do_halt = false;
358 static bool do_kexec = false;
359 @@ -76,6 +85,40 @@ static bool do_single = false;
360 static bool do_wtmp = true;
361 static bool do_wtmp_only = false;
362
363 +static void cancel_shutdown(void)
364 +{
365 + pid_t pid;
366 +
367 + pid = get_pid(applet, shutdown_pid);
368 + if (pid <= 0)
369 + eerrorx("%s: Unable to cancel shutdown", applet);
370 +
371 + if (kill(pid, SIGTERM) != -1)
372 + einfo("%s: shutdown canceled", applet);
373 + else
374 + eerrorx("%s: Unable to cancel shutdown", applet);
375 +}
376 +
377 +/*
378 + * Create the nologin file.
379 + */
380 +static void create_nologin(int mins)
381 +{
382 + FILE *fp;
383 + time_t t;
384 +
385 + time(&t);
386 + t += 60 * mins;
387 +
388 + if ((fp = fopen(nologin_file, "w")) != NULL) {
389 + fprintf(fp, "\rThe system is going down on %s\r\n", ctime(&t));
390 + fclose(fp);
391 + }
392 +}
393 +
394 +/*
395 + * Send a command to our init
396 + */
397 static void send_cmd(const char *cmd)
398 {
399 FILE *fifo;
400 @@ -99,16 +142,59 @@ static void send_cmd(const char *cmd)
401 fclose(fifo);
402 }
403
404 +/*
405 + * sleep without being interrupted.
406 + * The idea for this code came from sysvinit.
407 + */
408 +static void sleep_no_interrupt(int seconds)
409 +{
410 + struct timespec duration;
411 + struct timespec remaining;
412 +
413 + duration.tv_sec = seconds;
414 + duration.tv_nsec = 0;
415 +
416 + while(nanosleep(&duration, &remaining) < 0 && errno == EINTR)
417 + duration = remaining;
418 +}
419 +
420 +static void stop_shutdown(int sig)
421 +{
422 + /* use the sig parameter so the compiler will not complain */
423 + if (sig == SIGINT)
424 + ;
425 + unlink(nologin_file);
426 + unlink(shutdown_pid);
427 +einfo("Shutdown canceled");
428 +exit(0);
429 +}
430 +
431 int main(int argc, char **argv)
432 {
433 + char *ch = NULL;
434 int opt;
435 int cmd_count = 0;
436 + int hour = 0;
437 + int min = 0;
438 + int shutdown_delay = 0;
439 + struct sigaction sa;
440 + struct tm *lt;
441 + time_t tv;
442 + bool need_warning = false;
443 + char *msg = NULL;
444 + char *state = NULL;
445 + char *time_arg = NULL;
446 + FILE *fp;
447
448 applet = basename_c(argv[0]);
449 while ((opt = getopt_long(argc, argv, getoptstring,
450 longopts, (int *) 0)) != -1)
451 {
452 switch (opt) {
453 + case 'c':
454 + do_cancel = true;
455 + cmd_count++;
456 + break;
457 case 'd':
458 do_wtmp = false;
459 break;
460 @@ -117,14 +203,17 @@ int main(int argc, char **argv)
461 break;
462 case 'H':
463 do_halt = true;
464 + xasprintf(&state, "%s", "halt");
465 cmd_count++;
466 break;
467 case 'K':
468 do_kexec = true;
469 + xasprintf(&state, "%s", "reboot");
470 cmd_count++;
471 break;
472 case 'p':
473 do_poweroff = true;
474 + xasprintf(&state, "%s", "power off");
475 cmd_count++;
476 break;
477 case 'R':
478 @@ -133,10 +222,12 @@ int main(int argc, char **argv)
479 break;
480 case 'r':
481 do_reboot = true;
482 + xasprintf(&state, "%s", "reboot");
483 cmd_count++;
484 break;
485 case 's':
486 do_single = true;
487 + xasprintf(&state, "%s", "go down for maintenance");
488 cmd_count++;
489 break;
490 case 'w':
491 @@ -146,12 +237,88 @@ int main(int argc, char **argv)
492 case_RC_COMMON_GETOPT
493 }
494 }
495 - if (geteuid() != 0 && ! do_dryrun)
496 + if (geteuid() != 0)
497 eerrorx("%s: you must be root\n", applet);
498 if (cmd_count != 1) {
499 eerror("%s: %s\n", applet, exclusive);
500 usage(EXIT_FAILURE);
501 }
502 +
503 + if (do_cancel) {
504 + cancel_shutdown();
505 + exit(EXIT_SUCCESS);
506 + } else if (do_reexec) {
507 + send_cmd("reexec");
508 + exit(EXIT_SUCCESS);
509 + }
510 +
511 + if (optind >= argc) {
512 + eerror("%s: No shutdown time specified", applet);
513 + usage(EXIT_FAILURE);
514 + }
515 + time_arg = argv[optind];
516 + if (*time_arg == '+')
517 + time_arg++;
518 + if (strcasecmp(time_arg, "now") == 0)
519 + strcpy(time_arg, "0");
520 + for (ch=time_arg; *ch; ch++)
521 + if ((*ch < '0' || *ch > '9') && *ch != ':') {
522 + eerror("%s: invalid time %s", applet, time_arg);
523 + usage(EXIT_FAILURE);
524 + }
525 + if (strchr(time_arg, ':')) {
526 + if ((sscanf(time_arg, "%2d:%2d", &hour, &min) != 2) ||
527 + (hour > 23) || (min > 59)) {
528 + eerror("%s: invalid time %s", applet, time_arg);
529 + usage(EXIT_FAILURE);
530 + }
531 + time(&tv);
532 + lt = localtime(&tv);
533 + shutdown_delay = (hour * 60 + min) - (lt->tm_hour * 60 + lt->tm_min);
534 + if (shutdown_delay < 0)
535 + shutdown_delay += 1440;
536 + } else {
537 + shutdown_delay = atoi(time_arg);
538 + }
539 +
540 + fp = fopen(shutdown_pid, "w");
541 + if (!fp)
542 + eerrorx("%s: fopen `%s': %s", applet, shutdown_pid, strerror(errno));
543 + fprintf(fp, "%d\n", getpid());
544 + fclose(fp);
545 +
546 + openlog(applet, LOG_PID, LOG_DAEMON);
547 + memset(&sa, 0, sizeof(sa));
548 + sa.sa_handler = stop_shutdown;
549 + sigemptyset(&sa.sa_mask);
550 + sigaction(SIGINT, &sa, NULL);
551 + sigaction(SIGTERM, &sa, NULL);
552 + while (shutdown_delay > 0) {
553 + need_warning = false;
554 + if (shutdown_delay > 180)
555 + need_warning = (shutdown_delay % 60 == 0);
556 + else if (shutdown_delay > 60)
557 + need_warning = (shutdown_delay % 30 == 0);
558 + else if (shutdown_delay > 10)
559 + need_warning = (shutdown_delay % 15 == 0);
560 + else
561 + need_warning = true;
562 + if (shutdown_delay <= 5)
563 + create_nologin(shutdown_delay);
564 + if (need_warning) {
565 + xasprintf(&msg, "\rThe system will %s in %d minutes\r\n",
566 + state, shutdown_delay);
567 + broadcast(msg);
568 + free(msg);
569 + }
570 + sleep_no_interrupt(60);
571 + shutdown_delay--;
572 + }
573 + xasprintf(&msg, "\rThe system will %s now\r\n", state);
574 + broadcast(msg);
575 + syslog(LOG_NOTICE, "The system will %s now", state);
576 + unlink(nologin_file);
577 + unlink(shutdown_pid);
578 if (do_halt)
579 send_cmd("halt");
580 else if (do_kexec)
581 @@ -160,11 +327,9 @@ int main(int argc, char **argv)
582 send_cmd("poweroff");
583 else if (do_reboot)
584 send_cmd("reboot");
585 - else if (do_reexec)
586 - send_cmd("reexec");
587 - else if (do_wtmp_only)
588 - log_wtmp("shutdown", "~~", 0, RUN_LVL, "~~");
589 else if (do_single)
590 send_cmd("single");
591 + else if (do_wtmp_only)
592 + log_wtmp("shutdown", "~~", 0, RUN_LVL, "~~");
593 return 0;
594 }