commit:     3f918161aafa61c1c2005709fda0b9bec4c412d8
Author:     William Hubbs <w.d.hubbs <AT> gmail <DOT> com>
AuthorDate: Fri Oct  5 19:10:59 2018 +0000
Commit:     William Hubbs <williamh <AT> gentoo <DOT> org>
CommitDate: Thu Oct 18 22:56:36 2018 +0000
URL:        https://gitweb.gentoo.org/proj/openrc.git/commit/?id=3f918161

openrc-shutdown: Add scheduled shutdown and the ability to cancel a shutdown

You can now schedule a shutdown for a certain time or a cpecific number
of minutes into the future.

When a shutdown is running, you can now cancel it with ^c from the
keyboard or by running "openrc-shutdown -c" from another shell.

 man/openrc-shutdown.8    |   3 +
 src/rc/Makefile          |   4 +-
 src/rc/broadcast.c       | 209 +++++++++++++++++++++++++++++++++++++++++++++++
 src/rc/broadcast.h       |  16 ++++
 src/rc/openrc-shutdown.c | 179 ++++++++++++++++++++++++++++++++++++++--
 5 files changed, 402 insertions(+), 9 deletions(-)

diff --git a/man/openrc-shutdown.8 b/man/openrc-shutdown.8
index 5db21334..b09e8c20 100644
--- a/man/openrc-shutdown.8
+++ b/man/openrc-shutdown.8
@@ -16,6 +16,7 @@
 .Nd bring the system down
 .Sh SYNOPSIS
 .Nm
+.Op Fl c , -cancel
 .Op Fl d , -no-write
 .Op Fl D , -dry-run
 .Op Fl H , -halt
@@ -32,6 +33,8 @@ is the utility that communicates with
 to bring down the system or instruct openrc-init to re-execute itself.
 It supports the following options:
 .Bl -tag -width "poweroff"
+.It Fl c , -cancel
+Cancel a pending shutdown.
 .It Fl d , -no-write
 Do not write the wtmp boot record.
 .It Fl D , -dry-run

diff --git a/src/rc/Makefile b/src/rc/Makefile
index b09c5058..9ba240fa 100644
--- a/src/rc/Makefile
+++ b/src/rc/Makefile
@@ -14,7 +14,7 @@ SRCS+=                rc-selinux.c
 endif
 
 ifeq (${OS},Linux)
-SRCS+=         kill_all.c openrc-init.c openrc-shutdown.c rc-wtmp.c
+SRCS+=         kill_all.c openrc-init.c openrc-shutdown.c broadcast.c rc-wtmp.c
 endif
 
 CLEANFILES=    version.h rc-selinux.o
@@ -134,7 +134,7 @@ mountinfo: mountinfo.o _usage.o rc-misc.o
 openrc rc: rc.o rc-logger.o rc-misc.o rc-plugin.o _usage.o
        ${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ 
${LDADD}
 
-openrc-shutdown: openrc-shutdown.o _usage.o rc-wtmp.o
+openrc-shutdown: openrc-shutdown.o rc-misc.o _usage.o broadcast.o rc-wtmp.o
        ${CC} ${LOCAL_CFLAGS} ${LOCAL_LDFLAGS} ${CFLAGS} ${LDFLAGS} -o $@ $^ 
${LDADD}
 
 openrc-run runscript: openrc-run.o _usage.o rc-misc.o rc-plugin.o

diff --git a/src/rc/broadcast.c b/src/rc/broadcast.c
new file mode 100644
index 00000000..dbc861d1
--- /dev/null
+++ b/src/rc/broadcast.c
@@ -0,0 +1,209 @@
+/*
+ * broadcast.c
+ * broadcast a message to every logged in user
+ */
+
+/*
+ * Copyright 2018 Sony Interactive Entertainment Inc. 
+ *
+ * This file is part of OpenRC. It is subject to the license terms in
+ * the LICENSE file found in the top-level directory of this
+ * distribution and at https://github.com/OpenRC/openrc/blob/master/LICENSE
+ * This file may not be copied, modified, propagated, or distributed
+ *    except according to the terms contained in the LICENSE file.
+ */
+#include <ctype.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <utmpx.h>
+#include <pwd.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <setjmp.h>
+#include <paths.h>
+#include <sys/utsname.h>
+
+#include "broadcast.h"
+#include "helpers.h"
+
+#ifndef _PATH_DEV
+# define _PATH_DEV     "/dev/"
+#endif
+
+#ifndef UT_LINESIZE
+#define UT_LINESIZE __UT_LINESIZE
+#endif
+
+static sigjmp_buf jbuf;
+
+/*
+ *     Alarm handler
+ */
+/*ARGSUSED*/
+# ifdef __GNUC__
+static void handler(int arg __attribute__((unused)))
+# else
+static void handler(int arg)
+# endif
+{
+       siglongjmp(jbuf, 1);
+}
+
+static void getuidtty(char **userp, char **ttyp)
+{
+       struct passwd           *pwd;
+       uid_t                   uid;
+       char                    *tty;
+       static char             uidbuf[32];
+       static char             ttynm[UT_LINESIZE + 4];
+
+       uid = getuid();
+       if ((pwd = getpwuid(uid)) != NULL) {
+               uidbuf[0] = 0;
+               strncat(uidbuf, pwd->pw_name, sizeof(uidbuf) - 1);
+       } else {
+               if (uid)
+                       sprintf(uidbuf, "uid %d", (int) uid);
+               else
+                       sprintf(uidbuf, "root");
+       }
+
+       if ((tty = ttyname(0)) != NULL) {
+               const size_t plen = strlen(_PATH_DEV);
+               if (strncmp(tty, _PATH_DEV, plen) == 0) {
+                       tty += plen;
+                       if (tty[0] == '/')
+                               tty++;
+               }
+               snprintf(ttynm, sizeof(ttynm), "(%.*s) ",
+                                UT_LINESIZE, tty);
+       } else
+               ttynm[0] = 0;
+
+       *userp = uidbuf;
+       *ttyp  = ttynm;
+}
+
+/*
+ *     Check whether the given filename looks like a tty device.
+ */
+static int file_isatty(const char *fname)
+{
+       struct stat             st;
+       int                     major;
+
+       if (stat(fname, &st) < 0)
+               return 0;
+
+       if (st.st_nlink != 1 || !S_ISCHR(st.st_mode))
+               return 0;
+
+       /*
+        *      It would be an impossible task to list all major/minors
+        *      of tty devices here, so we just exclude the obvious
+        *      majors of which just opening has side-effects:
+        *      printers and tapes.
+        */
+       major = major(st.st_dev);
+       if (major == 1 || major == 2 || major == 6 || major == 9 ||
+           major == 12 || major == 16 || major == 21 || major == 27 ||
+           major == 37 || major == 96 || major == 97 || major == 206 ||
+           major == 230)
+               return 0;
+       return 1;
+}
+
+/*
+ *     broadcast function.
+ */
+void broadcast(char *text)
+{
+       char *tty;
+       char *user;
+       struct utsname name;
+       time_t t;
+       char    *date;
+       char *p;
+       char *line = NULL;
+       struct sigaction sa;
+       int fd;
+       FILE *tp;
+       int     flags;
+       char *term = NULL;
+       struct utmpx *utmp;
+
+       getuidtty(&user, &tty);
+
+       /*
+        * Get and report current hostname, to make it easier to find out
+        * which machine is being shut down.
+        */
+       uname(&name);
+
+       /* Get the time */
+       time(&t);
+       date = ctime(&t);
+       p = strchr(date, '\n');
+       if (p)
+               *p = 0;
+       
+       xasprintf(&line, "\007\r\nBroadcast message from %s@%s %s(%s):\r\n\r\n",
+                       user, name.nodename, tty, date);
+
+       /*
+        *      Fork to avoid hanging in a write()
+        */
+       if (fork() != 0)
+               return;
+       
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = handler;
+       sigemptyset(&sa.sa_mask);
+       sigaction(SIGALRM, &sa, NULL);
+
+       setutxent();
+
+       while ((utmp = getutxent()) != NULL) {
+               if (utmp->ut_type != USER_PROCESS || utmp->ut_user[0] == 0)
+                       continue;
+               if (strncmp(utmp->ut_line, _PATH_DEV, strlen(_PATH_DEV)) == 0)
+                       xasprintf(&term, "%s", utmp->ut_line);
+               else
+                       xasprintf(&term, "%s%s", _PATH_DEV, utmp->ut_line);
+               if (strstr(term, "/../")) {
+                       free(term);
+                       continue;
+               }
+
+               /*
+                *      Open it non-delay
+                */
+               if (sigsetjmp(jbuf, 1) == 0) {
+                       alarm(2);
+                       flags = O_WRONLY|O_NDELAY|O_NOCTTY;
+                       if (file_isatty(term) && (fd = open(term, flags)) >= 0) 
{
+                               if (isatty(fd) && (tp = fdopen(fd, "w")) != 
NULL) {
+                                       fputs(line, tp);
+                                       fputs(text, tp);
+                                       fflush(tp);
+                               }
+                       }
+               }
+               alarm(0);
+               if (fd >= 0)
+                       close(fd);
+               if (tp != NULL)
+                       fclose(tp);
+               free(term);
+       }
+       endutxent();
+       free(line);
+       exit(0);
+}

diff --git a/src/rc/broadcast.h b/src/rc/broadcast.h
new file mode 100644
index 00000000..5f948272
--- /dev/null
+++ b/src/rc/broadcast.h
@@ -0,0 +1,16 @@
+/*
+ * Copyright 2018 Sony Interactive Entertainment Inc. 
+ *
+ * This file is part of OpenRC. It is subject to the license terms in
+ * the LICENSE file found in the top-level directory of this
+ * distribution and at https://github.com/OpenRC/openrc/blob/master/LICENSE
+ * This file may not be copied, modified, propagated, or distributed
+ *    except according to the terms contained in the LICENSE file.
+ */
+
+#ifndef BROADCAST_H
+#define BROADCAST_H
+
+void broadcast(char *text);
+
+#endif

diff --git a/src/rc/openrc-shutdown.c b/src/rc/openrc-shutdown.c
index b17a63d8..ab2e7469 100644
--- a/src/rc/openrc-shutdown.c
+++ b/src/rc/openrc-shutdown.c
@@ -25,20 +25,24 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <syslog.h>
 #include <unistd.h>
 #include <sys/types.h>
 #include <sys/utsname.h>
 
+#include "broadcast.h"
 #include "einfo.h"
 #include "rc.h"
 #include "helpers.h"
+#include "rc-misc.h"
 #include "_usage.h"
 #include "rc-wtmp.h"
 
 const char *applet = NULL;
 const char *extraopts = NULL;
-const char *getoptstring = "dDHKpRrsw" getoptstring_COMMON;
+const char *getoptstring = "cdDfFHKpRrsw" getoptstring_COMMON;
 const struct option longopts[] = {
+       { "cancel",        no_argument, NULL, 'c'},
        { "no-write",        no_argument, NULL, 'd'},
        { "dry-run",        no_argument, NULL, 'D'},
        { "halt",        no_argument, NULL, 'H'},
@@ -51,6 +55,7 @@ const struct option longopts[] = {
        longopts_COMMON
 };
 const char * const longopts_help[] = {
+       "cancel a pending shutdown",
        "do not write wtmp record",
        "print actions instead of executing them",
        "halt the system",
@@ -64,8 +69,12 @@ const char * const longopts_help[] = {
 };
 const char *usagestring = NULL;
 const char *exclusive = "Select one of "
-"--halt, --kexec, --poweroff, --reexec, --reboot, --single or --write-only";
+       "--cancel, --halt, --kexec, --poweroff, --reexec, --reboot, --single or 
\n"
+       "--write-only";
+const char *nologin_file = RC_SYSCONFDIR"/nologin";
+const char *shutdown_pid = "/run/openrc-shutdown.pid";
 
+static bool do_cancel = false;
 static bool do_dryrun = false;
 static bool do_halt = false;
 static bool do_kexec = false;
@@ -76,6 +85,40 @@ static bool do_single = false;
 static bool do_wtmp = true;
 static bool do_wtmp_only = false;
 
+static void cancel_shutdown(void)
+{
+       pid_t pid;
+
+       pid = get_pid(applet, shutdown_pid);
+       if (pid <= 0)
+               eerrorx("%s: Unable to cancel shutdown", applet);
+
+       if (kill(pid, SIGTERM) != -1)
+               einfo("%s: shutdown canceled", applet);
+       else
+               eerrorx("%s: Unable to cancel shutdown", applet);
+}
+
+/*
+ *     Create the nologin file.
+ */
+static void create_nologin(int mins)
+{
+       FILE *fp;
+       time_t t;
+
+       time(&t);
+       t += 60 * mins;
+
+       if ((fp = fopen(nologin_file, "w")) != NULL) {
+               fprintf(fp, "\rThe system is going down on %s\r\n", ctime(&t));
+               fclose(fp);
+       }
+}
+
+/*
+ * Send a command to our init
+ */
 static void send_cmd(const char *cmd)
 {
        FILE *fifo;
@@ -99,16 +142,59 @@ static void send_cmd(const char *cmd)
        fclose(fifo);
 }
 
+/*
+ * sleep without being interrupted.
+ * The idea for this code came from sysvinit.
+ */
+static void sleep_no_interrupt(int seconds)
+{
+       struct timespec duration;
+       struct timespec remaining;
+
+       duration.tv_sec = seconds;
+       duration.tv_nsec = 0;
+
+       while(nanosleep(&duration, &remaining) < 0 && errno == EINTR)
+               duration = remaining;
+}
+
+static void stop_shutdown(int sig)
+{
+       /* use the sig parameter so the compiler will not complain */
+       if (sig == SIGINT)
+               ;
+       unlink(nologin_file);
+       unlink(shutdown_pid);
+einfo("Shutdown canceled");
+exit(0);
+}
+
 int main(int argc, char **argv)
 {
+       char *ch = NULL;
        int opt;
        int cmd_count = 0;
+       int hour = 0;
+       int min = 0;
+       int shutdown_delay = 0;
+       struct sigaction sa;
+       struct tm *lt;
+       time_t tv;
+       bool need_warning = false;
+       char *msg = NULL;
+       char *state = NULL;
+       char *time_arg = NULL;
+       FILE *fp;
 
        applet = basename_c(argv[0]);
        while ((opt = getopt_long(argc, argv, getoptstring,
                    longopts, (int *) 0)) != -1)
        {
                switch (opt) {
+                       case 'c':
+                               do_cancel = true;
+                       cmd_count++;
+                               break;
                        case 'd':
                                do_wtmp = false;
                                break;
@@ -117,14 +203,17 @@ int main(int argc, char **argv)
                        break;
                case 'H':
                        do_halt = true;
+                       xasprintf(&state, "%s", "halt");
                        cmd_count++;
                        break;
                case 'K':
                        do_kexec = true;
+                       xasprintf(&state, "%s", "reboot");
                        cmd_count++;
                        break;
                case 'p':
                        do_poweroff = true;
+                       xasprintf(&state, "%s", "power off");
                        cmd_count++;
                        break;
                case 'R':
@@ -133,10 +222,12 @@ int main(int argc, char **argv)
                        break;
                case 'r':
                        do_reboot = true;
+                       xasprintf(&state, "%s", "reboot");
                        cmd_count++;
                        break;
                case 's':
                        do_single = true;
+                       xasprintf(&state, "%s", "go down for maintenance");
                        cmd_count++;
                        break;
                case 'w':
@@ -146,12 +237,88 @@ int main(int argc, char **argv)
                case_RC_COMMON_GETOPT
                }
        }
-       if (geteuid() != 0 && ! do_dryrun)
+       if (geteuid() != 0)
                eerrorx("%s: you must be root\n", applet);
        if (cmd_count != 1) {
                eerror("%s: %s\n", applet, exclusive);
                usage(EXIT_FAILURE);
        }
+
+       if (do_cancel) {
+               cancel_shutdown();
+               exit(EXIT_SUCCESS);
+       } else if (do_reexec) {
+               send_cmd("reexec");
+               exit(EXIT_SUCCESS);
+       }
+
+       if (optind >= argc) {
+               eerror("%s: No shutdown time specified", applet);
+               usage(EXIT_FAILURE);
+       }
+       time_arg = argv[optind];
+       if (*time_arg == '+')
+               time_arg++;
+       if (strcasecmp(time_arg, "now") == 0)
+               strcpy(time_arg, "0");
+       for (ch=time_arg; *ch; ch++)
+               if ((*ch < '0' || *ch > '9') && *ch != ':') {
+                       eerror("%s: invalid time %s", applet, time_arg);
+                       usage(EXIT_FAILURE);
+               }
+       if (strchr(time_arg, ':')) {
+               if ((sscanf(time_arg, "%2d:%2d", &hour, &min) != 2) ||
+                               (hour > 23) || (min > 59)) {
+                       eerror("%s: invalid time %s", applet, time_arg);
+                       usage(EXIT_FAILURE);
+               }
+               time(&tv);
+               lt = localtime(&tv);
+               shutdown_delay = (hour * 60 + min) - (lt->tm_hour * 60 + 
lt->tm_min);
+               if (shutdown_delay < 0)
+                       shutdown_delay += 1440;
+       } else {
+               shutdown_delay = atoi(time_arg);
+       }
+
+       fp = fopen(shutdown_pid, "w");
+       if (!fp)
+               eerrorx("%s: fopen `%s': %s", applet, shutdown_pid, 
strerror(errno));
+       fprintf(fp, "%d\n", getpid());
+       fclose(fp);
+
+       openlog(applet, LOG_PID, LOG_DAEMON);
+       memset(&sa, 0, sizeof(sa));
+       sa.sa_handler = stop_shutdown;
+       sigemptyset(&sa.sa_mask);
+       sigaction(SIGINT, &sa, NULL);
+       sigaction(SIGTERM, &sa, NULL);
+       while (shutdown_delay > 0) {
+               need_warning = false;
+               if (shutdown_delay > 180)
+                       need_warning = (shutdown_delay % 60 == 0);
+               else if (shutdown_delay > 60)
+                       need_warning = (shutdown_delay % 30 == 0);
+               else if (shutdown_delay > 10)
+                       need_warning = (shutdown_delay % 15 == 0);
+               else
+                       need_warning = true;
+               if (shutdown_delay <= 5)
+                       create_nologin(shutdown_delay);
+               if (need_warning) {
+               xasprintf(&msg, "\rThe system will %s in %d minutes\r\n",
+                         state, shutdown_delay);
+                       broadcast(msg);
+                       free(msg);
+               }
+               sleep_no_interrupt(60);
+               shutdown_delay--;
+       }
+       xasprintf(&msg, "\rThe system will %s now\r\n", state);
+       broadcast(msg);
+       syslog(LOG_NOTICE, "The system will %s now", state);
+       unlink(nologin_file);
+       unlink(shutdown_pid);
        if (do_halt)
                send_cmd("halt");
        else if (do_kexec)
@@ -160,11 +327,9 @@ int main(int argc, char **argv)
                send_cmd("poweroff");
        else if (do_reboot)
                send_cmd("reboot");
-       else if (do_reexec)
-               send_cmd("reexec");
-       else if (do_wtmp_only)
-               log_wtmp("shutdown", "~~", 0, RUN_LVL, "~~");
        else if (do_single)
                send_cmd("single");
+               else if (do_wtmp_only)
+               log_wtmp("shutdown", "~~", 0, RUN_LVL, "~~");
        return 0;
 }

Reply via email to