public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "William Hubbs" <williamh@gentoo.org>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/openrc:master commit in: src/rc/, man/
Date: Thu, 18 Oct 2018 23:01:42 +0000 (UTC)	[thread overview]
Message-ID: <1539903396.3f918161aafa61c1c2005709fda0b9bec4c412d8.williamh@OpenRC> (raw)

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	other threads:[~2018-10-18 23:01 UTC|newest]

Thread overview: 4+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-10-18 23:01 William Hubbs [this message]
  -- strict thread matches above, loose matches on Subject: below --
2018-05-16 18:27 [gentoo-commits] proj/openrc:master commit in: src/rc/, man/ William Hubbs
2017-12-04 23:31 William Hubbs
2016-07-18 19:12 William Hubbs

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1539903396.3f918161aafa61c1c2005709fda0b9bec4c412d8.williamh@OpenRC \
    --to=williamh@gentoo.org \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox