From 8889c700f314bb2d6fc652ff35a5f3d2d72f5edb Mon Sep 17 00:00:00 2001
From: David Nugent <davidn@FreeBSD.org>
Date: Sat, 2 Aug 1997 00:22:52 +0000
Subject: [PATCH] Add /etc/rc.shutdown capability to init. Add sample
 /etc/rc.shutdown (which is just a shell for now). Submitted by:
 Ollivier Robert <roberto@keltia.freenix.fr>

---
 etc/Makefile          |   4 +-
 etc/rc.shutdown       |  26 +++++++
 sbin/init/init.8      |  13 +++-
 sbin/init/init.c      | 175 ++++++++++++++++++++++++++++++++++++++----
 sbin/init/pathnames.h |   1 +
 5 files changed, 199 insertions(+), 20 deletions(-)
 create mode 100644 etc/rc.shutdown

diff --git a/etc/Makefile b/etc/Makefile
index 607099b4fc78..b19a1251a801 100644
--- a/etc/Makefile
+++ b/etc/Makefile
@@ -1,5 +1,5 @@
 #	from: @(#)Makefile	5.11 (Berkeley) 5/21/91
-#	$Id: Makefile,v 1.152 1997/07/05 19:35:22 pst Exp $
+#	$Id: Makefile,v 1.153 1997/07/18 03:49:47 asami Exp $
 
 # -rw-r--r--
 BINOWN= root
@@ -9,7 +9,7 @@ BIN1=   aliases amd.map csh.cshrc csh.login csh.logout dm.conf \
 	inetd.conf login.conf login.access motd modems networks \
 	newsyslog.conf phones pccard.conf.sample printcap profile protocols \
 	rc rc.conf rc.firewall rc.local rc.network rc.pccard rc.serial \
-	etc.${MACHINE}/rc.${MACHINE} \
+	rc.shutdown etc.${MACHINE}/rc.${MACHINE} \
 	remote security services shells \
 	syslog.conf ttys etc.${MACHINE}/disktab rpc make.conf \
 	${.CURDIR}/../gnu/usr.bin/man/manpath/manpath.config \
diff --git a/etc/rc.shutdown b/etc/rc.shutdown
new file mode 100644
index 000000000000..f935834d1898
--- /dev/null
+++ b/etc/rc.shutdown
@@ -0,0 +1,26 @@
+#!/bin/sh
+#	$Id$
+
+# site-specific closing actions for daemons run by init on shutdown
+# or before going single-user from multi-user.
+# Output and errors are directed to console by init, and the
+# console is the controlling terminal.
+
+stty status '^T'
+
+# Set shell to ignore SIGINT (2), but not children;
+# shell catches SIGQUIT (3) and returns to single user after fsck.
+trap : 2
+trap : 3	# shouldn't be needed
+
+HOME=/; export HOME
+PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin
+export PATH
+
+echo -n Shutting down daemon processes: 
+
+# Insert shutdown procedures here
+
+
+echo '.'
+exit 0
diff --git a/sbin/init/init.8 b/sbin/init/init.8
index 0591d9dcfe02..1b5bf5f7c7b2 100644
--- a/sbin/init/init.8
+++ b/sbin/init/init.8
@@ -33,7 +33,7 @@
 .\" SUCH DAMAGE.
 .\"
 .\"     @(#)init.8	8.3 (Berkeley) 4/18/94
-.\"	$Id: init.8,v 1.8 1997/02/22 14:32:34 peter Exp $
+.\"	$Id: init.8,v 1.9 1997/04/01 20:41:04 mpp Exp $
 .\"
 .Dd April 18, 1994
 .Dt INIT 8
@@ -241,6 +241,15 @@ signal, i.e.
 This is useful for shutting the machine down cleanly from inside the kernel
 or from X when the machine appears to be hung.
 .Pp
+When shuting down the machine,
+.Nm init
+will try to run the
+.Pa /etc/rc.shutdown
+script. This script can be used to cleanly terminate specific programs such
+as
+.Nm innd
+(the InterNetNews server).
+.Pp
 The role of
 .Nm init
 is so critical that if it dies, the system will reboot itself
@@ -280,6 +289,8 @@ Record of all logins and logouts.
 The terminal initialization information file.
 .It Pa /etc/rc
 System startup commands.
+.It Pa /etc/rc.shutdown
+System shutdown commands.
 .El
 .Sh SEE ALSO
 .Xr kill 1 ,
diff --git a/sbin/init/init.c b/sbin/init/init.c
index a0b95c99d9be..d549f4a3f6cf 100644
--- a/sbin/init/init.c
+++ b/sbin/init/init.c
@@ -33,7 +33,7 @@
  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
  * SUCH DAMAGE.
  *
- *		$Id: init.c,v 1.22 1997/07/05 19:36:55 ache Exp $
+ *		$Id: init.c,v 1.23 1997/07/08 11:51:11 ache Exp $
  */
 
 #ifndef lint
@@ -47,6 +47,7 @@ static char sccsid[] = "@(#)init.c	8.1 (Berkeley) 7/15/93";
 #endif /* not lint */
 
 #include <sys/param.h>
+#include <sys/ioctl.h>
 #include <sys/mount.h>
 #include <sys/sysctl.h>
 #include <sys/wait.h>
@@ -91,6 +92,7 @@ static char sccsid[] = "@(#)init.c	8.1 (Berkeley) 7/15/93";
 #define	WINDOW_WAIT		 3	/* wait N secs after starting window */
 #define	STALL_TIMEOUT		30	/* wait N secs after warning */
 #define	DEATH_WATCH		10	/* wait N secs for procs to die */
+#define DEATH_SCRIPT		120	/* wait for 2mn for /etc/rc.shutdown */
 #define RESOURCE_RC		"daemon"
 #define RESOURCE_WINDOW 	"default"
 #define RESOURCE_GETTY		"default"
@@ -103,6 +105,7 @@ void warning __P((char *, ...));
 void emergency __P((char *, ...));
 void disaster __P((int));
 void badsys __P((int));
+int  runshutdown __P((void));
 
 /*
  * We really need a recursive typedef...
@@ -309,7 +312,7 @@ handle(va_alist)
 	sa.sa_handler = handler;
 	sigfillset(&mask_everything);
 
-	while (sig = va_arg(ap, int)) {
+	while ((sig = va_arg(ap, int)) != NULL) {
 		sa.sa_mask = mask_everything;
 		/* XXX SA_RESTART? */
 		sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0;
@@ -340,7 +343,7 @@ delset(va_alist)
 	va_start(ap, maskp);
 #endif
 
-	while (sig = va_arg(ap, int))
+	while ((sig = va_arg(ap, int)) != NULL)
 		sigdelset(maskp, sig);
 	va_end(ap);
 }
@@ -451,7 +454,7 @@ disaster(sig)
 	int sig;
 {
 	emergency("fatal signal: %s",
-		sig < (unsigned) NSIG ? sys_siglist[sig] : "unknown signal");
+		(unsigned)sig < NSIG ? sys_siglist[sig] : "unknown signal");
 
 	sleep(STALL_TIMEOUT);
 	_exit(sig);		/* reboot */
@@ -891,7 +894,7 @@ construct_argv(command)
 
 	if ((argv[argc++] = strk(command)) == 0)
 		return 0;
-	while (argv[argc++] = strk((char *) 0))
+	while ((argv[argc++] = strk((char *) 0)) != NULL)
 		continue;
 	return argv;
 }
@@ -1051,8 +1054,8 @@ read_ttys()
 	 * Allocate a session entry for each active port.
 	 * Note that sp starts at 0.
 	 */
-	while (typ = getttyent())
-		if (snext = new_session(sp, ++session_index, typ))
+	while ((typ = getttyent()) != NULL)
+		if ((snext = new_session(sp, ++session_index, typ)) != NULL)
 			sp = snext;
 
 	endttyent();
@@ -1195,11 +1198,11 @@ collect_child(pid)
 	sp->se_process = 0;
 
 	if (sp->se_flags & SE_SHUTDOWN) {
-		if (sprev = sp->se_prev)
+		if ((sprev = sp->se_prev) != NULL)
 			sprev->se_next = sp->se_next;
 		else
 			sessions = sp->se_next;
-		if (snext = sp->se_next)
+		if ((snext = sp->se_next) != NULL)
 			snext->se_prev = sp->se_prev;
 		free_session(sp);
 		return;
@@ -1298,7 +1301,7 @@ clean_ttys()
 		return (state_func_t) multi_user;
 
 	devlen = sizeof(_PATH_DEV) - 1;
-	while (typ = getttyent()) {
+	while ((typ = getttyent()) != NULL) {
 		++session_index;
 
 		for (sprev = 0, sp = sessions; sp; sprev = sp, sp = sp->se_next)
@@ -1329,13 +1332,13 @@ clean_ttys()
 				kill(sp->se_process, SIGHUP);
 			}
 			else if (   !old_getty
-				 || !old_type && sp->se_type
-				 || old_type && !sp->se_type
-				 || !old_window && sp->se_window
-				 || old_window && !sp->se_window
-				 || strcmp(old_getty, sp->se_getty) != 0
-				 || old_window && strcmp(old_window, sp->se_window) != 0
-				 || old_type && strcmp(old_type, sp->se_type) != 0
+				 || (!old_type && sp->se_type)
+				 || (old_type && !sp->se_type)
+				 || (!old_window && sp->se_window)
+				 || (old_window && !sp->se_window)
+				 || (strcmp(old_getty, sp->se_getty) != 0)
+				 || (old_window && strcmp(old_window, sp->se_window) != 0)
+				 || (old_type && strcmp(old_type, sp->se_type) != 0)
 				) {
 				/* Don't set SE_SHUTDOWN here */
 				sp->se_nspace = 0;
@@ -1380,6 +1383,7 @@ void
 alrm_handler(sig)
 	int sig;
 {
+	(void)sig;
 	clang = 1;
 }
 
@@ -1390,6 +1394,7 @@ state_func_t
 death()
 {
 	register session_t *sp;
+	register int rcdown;
 	register int i;
 	pid_t pid;
 	static const int death_sigs[2] = { SIGTERM, SIGKILL };
@@ -1402,6 +1407,9 @@ death()
 		(void) revoke(sp->se_device);
 	}
 
+	/* Try to run the rc.shutdown script within a period of time */
+	rcdown = runshutdown();
+    
 	for (i = 0; i < 2; ++i) {
 		if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH)
 			return (state_func_t) single_user;
@@ -1421,6 +1429,139 @@ death()
 
 	return (state_func_t) single_user;
 }
+
+/*
+ * Run the system shutdown script.
+ *
+ * Exit codes:      XXX I should document more
+ * -2       shutdown script terminated abnormally
+ * -1       fatal error - can't run script
+ * 0        good.
+ * >0       some error (exit code)
+ */
+int
+runshutdown()
+{
+	pid_t pid, wpid;
+	int status;
+	int shutdowntimeout;
+	size_t len;
+	char *argv[3];
+	struct sigaction sa;
+
+	if ((pid = fork()) == 0) {
+		int	fd;
+
+		setsid();
+		sigemptyset(&sa.sa_mask);
+		sa.sa_flags = 0;
+		sa.sa_handler = SIG_IGN;
+		(void) sigaction(SIGTSTP, &sa, (struct sigaction *)0);
+		(void) sigaction(SIGHUP, &sa, (struct sigaction *)0);
+
+		/*
+		 * Clean our descriptor table to be sure of
+		 * getting /dev/console as control terminal.
+		 */
+		for (fd = getdtablesize(); fd-- > 0; )
+		    (void)close(fd);
+
+		if ((fd = open(_PATH_CONSOLE, O_RDWR)) == -1)
+		    warning("can't open %s: %m", _PATH_CONSOLE);
+		else {
+		    if (ioctl(fd, TIOCSCTTY, (char *)NULL) == -1)
+			warning("can't get %s for controlling terminal: %m", _PATH_CONSOLE);
+		    (void) dup2(fd, 1);
+		    (void) dup2(fd, 2);
+		}
+
+		/*
+		 * Run the shutdown script.
+		 */
+		argv[0] = "sh";
+		argv[1] = _PATH_RUNDOWN;
+		argv[2] = 0;
+
+		sigprocmask(SIG_SETMASK, &sa.sa_mask, (sigset_t *) 0);
+
+		execv(_PATH_BSHELL, argv);
+		warning("can't exec %s for %s: %m", _PATH_BSHELL, _PATH_RUNDOWN);
+		_exit(1);	/* force single user mode */
+	}
+
+	if (pid == -1) {
+		emergency("can't fork for %s on %s: %m",
+			_PATH_BSHELL, _PATH_RUNDOWN);
+		while (waitpid(-1, (int *) 0, WNOHANG) > 0)
+			continue;
+		sleep(STALL_TIMEOUT);
+		return -1;
+	}
+
+	len = sizeof(shutdowntimeout);
+	if (sysctlbyname("kern.shutdown_timeout",
+			 &shutdowntimeout,
+			 &len, NULL, 0) == -1 || shutdowntimeout < 2)
+	    shutdowntimeout = DEATH_SCRIPT;
+	alarm(shutdowntimeout);
+	clang = 0;
+	/*
+	 * Copied from single_user().  This is a bit paranoid.
+	 * Use the same ALRM handler.
+	 */
+	do {
+		if ((wpid = waitpid(-1, &status, WUNTRACED)) != -1)
+			collect_child(wpid);
+		if (clang == 1) {
+			/* we were waiting for the sub-shell */
+			kill(wpid, SIGTERM);
+			warning("timeout expired for %s on %s: %m; going to single used mode",
+				_PATH_BSHELL, _PATH_RUNDOWN);
+			return -1;
+		}
+		if (wpid == -1) {
+			if (errno == EINTR)
+				continue;
+			warning("wait for %s on %s failed: %m; going to single user mode",
+				_PATH_BSHELL, _PATH_RUNDOWN);
+			return -1;
+		}
+		if (wpid == pid && WIFSTOPPED(status)) {
+			warning("init: %s on %s stopped, restarting\n",
+				_PATH_BSHELL, _PATH_RUNDOWN);
+			kill(pid, SIGCONT);
+			wpid = -1;
+		}
+	} while (wpid != pid && !clang);
+
+	/* Turn off the alarm */
+	alarm(0);
+
+	if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM &&
+	    requested_transition == catatonia) {
+		/*
+		 * /etc/rc.shutdown executed /sbin/reboot;
+		 * wait for the end quietly
+		 */
+		sigset_t s;
+
+		sigfillset(&s);
+		for (;;)
+			sigsuspend(&s);
+	}
+
+	if (!WIFEXITED(status)) {
+		warning("%s on %s terminated abnormally, going to single user mode",
+			_PATH_BSHELL, _PATH_RUNDOWN);
+		return -2;
+	}
+
+	if ((status = WEXITSTATUS(status)) != 0)
+		warning("%s returned status %d", _PATH_RUNDOWN, status);
+
+	return status;
+}
+
 char *
 strk (char *p)
 {
diff --git a/sbin/init/pathnames.h b/sbin/init/pathnames.h
index abb874a4b988..27837f680b03 100644
--- a/sbin/init/pathnames.h
+++ b/sbin/init/pathnames.h
@@ -40,3 +40,4 @@
 
 #define	_PATH_SLOGGER	"/sbin/session_logger"
 #define	_PATH_RUNCOM	"/etc/rc"
+#define _PATH_RUNDOWN   "/etc/rc.shutdown"