Subject: bin/36551: some major improvements for init(8), with accompanying changes to reboot(8) and shutdown(8)
To: None <gnats-admin@netbsd.org, netbsd-bugs@netbsd.org>
From: Greg A. Woods <woods@planix.com>
List: netbsd-bugs
Date: 06/24/2007 19:10:02
>Number:         36551
>Category:       bin
>Synopsis:       some major improvements for init(8), with accompanying changes to reboot(8) and shutdown(8)
>Confidential:   no
>Severity:       serious
>Priority:       medium
>Responsible:    bin-bug-people
>State:          open
>Class:          sw-bug
>Submitter-Id:   net
>Arrival-Date:   Sun Jun 24 19:10:02 +0000 2007
>Originator:     Greg A. Woods
>Release:        netbsd-4 2007/06/23
>Organization:
Planix, Inc.; Toronto, Ontario; Canada
>Environment:
	
	
System: NetBSD 4.0_STABLE2
>Description:

	I've re-engineered init to work more reliably, and more in hand
	with, shutdown(8), reboot(8), et al.

	Please see the new manual pages and new init/NOTES for more info.

>How-To-Repeat:

>Fix:
	These diffs are against netbsd-4, thus they still have the DEVFS
	creating code.  This is the code I will continue to maintain so
	if it's imported verbatim, then any minimally necessary changes
	are made as separate commits, then I'll be encouraged to
	maintain it in -current too.

	also included are a recommended change to login_tty(3) and a
	somewhat related doc fix for dup(2).

	There's one more minor fix to come for reboot.c, but I haven't
	time to make and test it at the moment.  See the XXX comment
	I've added about SIGINT.


Index: sbin/shutdown/shutdown.c
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/shutdown/shutdown.c,v
retrieving revision 1.46
diff -u -r1.46 shutdown.c
--- sbin/shutdown/shutdown.c	7 Mar 2006 22:19:55 -0000	1.46
+++ sbin/shutdown/shutdown.c	15 Jun 2007 22:50:11 -0000
@@ -50,6 +50,7 @@
 
 #include <ctype.h>
 #include <err.h>
+#include <errno.h>
 #include <fcntl.h>
 #include <pwd.h>
 #include <setjmp.h>
@@ -95,6 +96,7 @@
 
 void badtime(void);
 void die_you_gravy_sucking_pig_dog(void);
+int death_to_all(void);
 void doitfast(void);
 void dorcshutdown(void);
 void finish(int);
@@ -112,10 +114,14 @@
 	struct passwd *pw;
 	int arglen, ch, len;
 
-#ifndef DEBUG
 	if (geteuid())
-		errx(1, "NOT super-user");
-#endif
+		errx(1, "You must be the super-user to shut down the system!");
+
+	/*
+	 * Don't run in the background by default -- getting a shell prompt
+	 * after typing "shutdown" confuses novices to no end!
+	 */
+	nofork = 1;
 	while ((ch = getopt(argc, argv, "b:Ddfhknpr")) != -1)
 		switch (ch) {
 		case 'b':
@@ -125,14 +131,14 @@
 			dodump = 1;
 			break;
 		case 'D':
-			nofork = 1;
+			nofork = 0;
 			break;
 		case 'f':
 			dofast = 1;
 			break;
 		case 'p':
 			dopowerdown = 1;
-			/* FALLTHROUGH */
+			break;
 		case 'h':
 			dohalt = 1;
 			break;
@@ -162,10 +168,10 @@
 		warnx("incompatible switches -f and -n");
 		usage();
 	}
-	if (dohalt && doreboot) {
+	if ((dohalt || dopowerdown) && doreboot) {
 		const char *which_flag = dopowerdown ? "p" : "h";
 
-		warnx("incompatible switches -%s and -r", which_flag);
+		warnx("incompatible options -%s and -r", which_flag);
 		usage();
 	}
 
@@ -228,7 +234,7 @@
 		(void)setsid();
 	}
 #endif
-	openlog("shutdown", LOG_CONS, LOG_AUTH);
+	openlog("shutdown", LOG_PERROR, LOG_AUTH);
 	loop();
 	/* NOTREACHED */
 #ifdef __GNUC__
@@ -341,8 +347,8 @@
 die_you_gravy_sucking_pig_dog(void)
 {
 
-	syslog(LOG_NOTICE, "%s by %s: %s",
-	    doreboot ? "reboot" : dohalt ? "halt" : "shutdown", whom, mbuf);
+	syslog(LOG_ALERT, "%s by %s: %s",
+	    doreboot ? "reboot" : (dohalt ? "halt" : (dopowerdown ? "poweroff" : "shutdown")), whom, mbuf);
 	(void)sleep(2);
 
 	(void)printf("\r\nSystem shutdown time has arrived\007\007\r\n");
@@ -353,7 +359,7 @@
 	if (dofast)
 		doitfast();
 	dorcshutdown();
-	if (doreboot || dohalt) {
+	if (doreboot || dohalt || dopowerdown) {
 		const char *args[16];
 		const char **arg, *path;
 
@@ -361,6 +367,9 @@
 		if (doreboot) {
 			path = _PATH_REBOOT;
 			*arg++ = "reboot";
+		} else if (dopowerdown) {
+			path = _PATH_POWEROFF;
+			*arg++ = "poweroff";
 		} else {
 			path = _PATH_HALT;
 			*arg++ = "halt";
@@ -369,15 +378,13 @@
 			*arg++ = "-d";
 		if (nosync)
 			*arg++ = "-n";
-		if (dopowerdown)
-			*arg++ = "-p";
 		*arg++ = "-l";
 		if (bootstr)
 			*arg++ = bootstr;
 		*arg++ = 0;
 #ifndef DEBUG
 		execve(path, __UNCONST(args), NULL);
-		syslog(LOG_ERR, "shutdown: can't exec %s: %m", path);
+		syslog(LOG_ERR, "can't exec %s: %m", path);
 		perror("shutdown");
 #else
 		printf("%s", path);
@@ -387,14 +394,49 @@
 #endif
 	} else {
 #ifndef DEBUG
-		(void)kill(1, SIGTERM);		/* to single user */
+		(void) kill(1, SIGUSR1);	/* tell init to go_single_user() */
+		(void) death_to_all();
 #else
-		printf("kill 1\n");
+		printf("kill -USR1 1\n");
+		printf("kill -TERM -1\n");
 #endif
 	}
 	finish(0);
 }
 
+/*
+ *	go on a killing spree...
+ *
+ * (guts borrowed from reboot.c)
+ */
+int
+death_to_all(void)
+{
+	/* 
+	 * Ignore signals that we can get as a result of killing
+	 * parents, group leaders, etc.
+	 */
+	(void) signal(SIGHUP,  SIG_IGN);
+	(void) signal(SIGINT,  SIG_IGN);
+	(void) signal(SIGQUIT, SIG_IGN);
+	(void) signal(SIGTERM, SIG_IGN);
+
+	/* Send a SIGTERM first, a chance to save the buffers. */
+	if (kill(-1, SIGTERM) == -1) {
+		/*
+		 * If ESRCH, everything's OK: we're the only non-system
+		 * process!  That can happen e.g. via 'exec reboot' in
+		 * single-user mode.
+		 */
+		if (errno != ESRCH) {
+			warn("sending SIGTERM to all processes failed");
+			return -1;
+		}
+	}
+
+	return 0;
+}
+
 #define	ATOI2(s)	((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0'))
 
 void
@@ -482,7 +524,6 @@
 {
 	(void)printf("\r\nAbout to run shutdown hooks...\r\n");
 	(void)system(". " _PATH_RCSHUTDOWN);
-	(void)sleep(5);		/* Give operator a chance to abort this. */
 	(void)printf("\r\nDone running shutdown hooks.\r\n");
 }
 
Index: sbin/shutdown/shutdown.8
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/shutdown/shutdown.8,v
retrieving revision 1.26
diff -u -r1.26 shutdown.8
--- sbin/shutdown/shutdown.8	7 Aug 2003 10:04:39 -0000	1.26
+++ sbin/shutdown/shutdown.8	14 Jun 2007 06:33:36 -0000
@@ -29,7 +29,7 @@
 .\"
 .\"     @(#)shutdown.8	8.2 (Berkeley) 4/27/95
 .\"
-.Dd October 18, 2002
+.Dd June 14, 2007
 .Dt SHUTDOWN 8
 .Os
 .Sh NAME
@@ -47,9 +47,15 @@
 to nicely notify users when the system is shutting down,
 saving them from system administrators, hackers, and gurus, who
 would otherwise not bother with such niceties.
-.Pp
-Available friendlinesses:
+.\"
+.Ss Command-line parameters:
 .Bl -tag -width bootstr
+.It Fl D
+Cause
+.Nm
+to detach from the TTY and run in the background.  This is useful if you
+want to check things from the same terminal while waiting for the final
+shutdown time to arrive.
 .It Fl b Ar bootstr
 The given
 .Ar bootstr
@@ -65,7 +71,11 @@
 flag to
 .Xr reboot 8
 or
-.Xr halt 8 .
+.Xr halt 8
+to cause them to trigger a system dump (i.e. add
+.Dv RB_DUMP
+to the flags passed to
+.Xr reboot 2 ) .
 If neither the
 .Fl h
 or
@@ -105,7 +115,7 @@
 when
 .Nm
 execs
-.Xr halt 8 .
+.Xr poweroff 8 .
 If the powerdown fails, or the system does not support software powerdown,
 the system will simply halt.
 .It Fl r
@@ -115,12 +125,6 @@
 .Nm
 execs
 .Xr reboot 8 .
-.It Fl D
-Prevents
-.Nm
-from detaching from the tty with
-.Xr fork 2 Ns /
-.Xr exit 3 .
 .It Ar time
 .Ar Time
 is the time at which
@@ -154,6 +158,7 @@
 is supplied as the only argument after the time, the warning message is read
 from the standard input.
 .El
+.Ss Operation
 .Pp
 At intervals, becoming more frequent as apocalypse approaches
 and starting at ten hours before shutdown, warning messages are displayed
@@ -175,15 +180,44 @@
 Then the shutdown hooks in
 .Pa /etc/rc.shutdown
 are run.
-And a message is printed indicating that they have completed.
-After a short delay a terminate
-signal is then sent to
+are run and a message is printed indicating that they have completed.
+.Pp
+If
+.Fl h ,
+.Fl p ,
+or
+.Fl r
+are given
+then
+.Nm
+runs
+.Xr halt 8 ,
+.Xr poweroff 8 ,
+or
+.Xr reboot 8
+as appropriate.
+Otherwise
+.Nm
+sends
+.Dv SIGUSR1
+to
 .Xr init 8
-to bring the system down to single-user state (depending on above options).
+causing it to send
+.Dv SIGTERM
+to all processes and an additional
+.Dv SIGHUP
+to all session leaders.
+Finally
+.Xr init 8
+will bring the system into to its single-user state which normally
+involves resetting the kernel secure level to zero and starting a
+single-user shell on the console terminal.
+.Pp
 The time of the shutdown and the warning message are placed in
 .Pa /etc/nologin
-and should be used to inform the users about when the system will
+and will be used to inform the users about when the system will
 be back up and why it is going down (or anything else).
+.\"
 .Sh FILES
 .Bl -tag -width /etc/rc.shutdown -compact
 .It Pa /etc/nologin
@@ -202,6 +236,7 @@
 .Xr wall 1 ,
 .Xr fastboot 8 ,
 .Xr halt 8 ,
+.Xr init 8 ,
 .Xr reboot 8
 .Sh BACKWARD COMPATIBILITY
 The hours and minutes in the second time format may be separated by
Index: sbin/shutdown/pathnames.h
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/shutdown/pathnames.h,v
retrieving revision 1.9
diff -u -r1.9 pathnames.h
--- sbin/shutdown/pathnames.h	19 Aug 2004 22:30:10 -0000	1.9
+++ sbin/shutdown/pathnames.h	2 Mar 2007 01:11:38 -0000
@@ -37,9 +37,11 @@
 #ifdef RESCUEDIR
 #define	_PATH_HALT	RESCUEDIR "/halt"
 #define	_PATH_REBOOT	RESCUEDIR "/reboot"
+#define	_PATH_POWEROFF	RESCUEDIR "/poweroff"
 #else
 #define	_PATH_HALT	"/sbin/halt"
 #define	_PATH_REBOOT	"/sbin/reboot"
+#define	_PATH_POWEROFF	"/sbin/poweroff"
 #endif
 #define	_PATH_WALL	"/usr/bin/wall"
 #define _PATH_RCSHUTDOWN	"/etc/rc.shutdown"
Index: sbin/reboot/reboot.c
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/reboot/reboot.c,v
retrieving revision 1.33
diff -u -r1.33 reboot.c
--- sbin/reboot/reboot.c	7 Aug 2003 10:04:37 -0000	1.33
+++ sbin/reboot/reboot.c	22 Jun 2007 23:21:33 -0000
@@ -69,7 +69,7 @@
 	const char *progname;
 	int i;
 	struct passwd *pw;
-	int ch, howto, lflag, nflag, qflag, sverrno, len;
+	int ch, howto, lflag, nflag, qflag, len;
 	const char *user;
 	char *bootstr, **av;
 
@@ -130,7 +130,9 @@
 
 	if (qflag) {
 		reboot(howto, bootstr);
-		err(1, "reboot");
+		warn("reboot()");
+		sleep(1);
+		goto single_user;
 	}
 
 	/* Log the reboot. */
@@ -170,13 +172,14 @@
 
 	/* 
 	 * Ignore signals that we can get as a result of killing
-	 * parents, group leaders, etc.
+	 * parents, group leaders, ourself, etc.
 	 */
-	(void)signal(SIGHUP,  SIG_IGN);
-	(void)signal(SIGINT,  SIG_IGN);
-	(void)signal(SIGQUIT, SIG_IGN);
+	(void)signal(SIGHUP,  SIG_IGN);	/* XXX are you sure? */
+	/* XXX we should handle SIGINT by sending SIGINT to init and exiting */
+	(void)signal(SIGINT,  SIG_IGN);	/* XXX we shouldn't ignore this!!! */
+	(void)signal(SIGQUIT, SIG_IGN);	/* XXX ????  why???? */
 	(void)signal(SIGTERM, SIG_IGN);
-	(void)signal(SIGTSTP, SIG_IGN);
+	(void)signal(SIGTSTP, SIG_IGN);	/* XXX ????  why???? */
 
 	/*
 	 * If we're running in a pipeline, we don't want to die
@@ -184,9 +187,16 @@
 	 */
 	(void)signal(SIGPIPE, SIG_IGN);
 
-	/* Just stop init -- if we fail, we'll restart it. */
-	if (kill(1, SIGTSTP) == -1)
-		err(1, "SIGTSTP init");
+	/*
+	 * Tell init to go catatonic right away just in case it handles its
+	 * SIGTERM slightly after some session has already died from SIGTERM --
+	 * we don't want init re-spawning anything just to have to kill it
+	 * again.
+	 *
+	 * Note if we fail, we'll ask it to start a single user shell.
+	 */
+	if (kill(1, SIGUSR2) == -1)
+		err(1, "unable to send SIGTSTP to init process");
 
 	/* Send a SIGTERM first, a chance to save the buffers. */
 	if (kill(-1, SIGTERM) == -1) {
@@ -196,26 +206,34 @@
 		 * single-user mode.
 		 */
 		if (errno != ESRCH) {
-			warn("SIGTERM processes");
-			goto restart;
+			warn("sending SIGTERM to all processes failed");
+			goto single_user;
 		}
 	}
 
 	/*
-	 * After the processes receive the signal, start the rest of the
-	 * buffers on their way.  Wait 5 seconds between the SIGTERM and
-	 * the SIGKILL to pretend to give everybody a chance.
+	 * Wait 5 seconds between the SIGTERM and the SIGKILL to pretend to
+	 * give everybody a chance.
+	 */
+	warnx("Notice: pausing 5 seconds for processes to clean up before the 'kill -KILL -1'");
+	sleep(5);
+
+	/*
+	 * After the processes receive their signals and have a chance to shut
+	 * down clearly, start the rest of the buffers on their way.  This
+	 * isn't necessary but may speed up the final vfs_shutdown() normally
+	 * (!RB_NOSYNC) done by reboot(2) since it may be in parallel with the
+	 * following SIGKILLs.
 	 */
-	sleep(2);
 	if (!nflag)
 		sync();
-	sleep(3);
 
 	for (i = 1;; ++i) {
 		if (kill(-1, SIGKILL) == -1) {
-			if (errno == ESRCH)
+			if (errno == ESRCH)	/* "They're all DEAD Jim!" */
 				break;
-			goto restart;
+			warn("sending SIGKILL to all processes failed");
+			goto single_user;
 		}
 		if (i > 5) {
 			warnx("WARNING: some process(es) wouldn't die");
@@ -225,12 +243,15 @@
 	}
 
 	reboot(howto, bootstr);
+	warn("reboot()");
+	sleep(1);
 	/* FALLTHROUGH */
 
-restart:
-	sverrno = errno;
-	errx(1, "%s%s", kill(1, SIGHUP) == -1 ? "(can't restart init): " : "",
-	    strerror(sverrno));
+  single_user:
+	if (kill(1, SIGINT) == -1)
+		errx(1, "kill(1, INT)"); /* NOTREACHED */
+
+	exit(1);
 	/* NOTREACHED */
 }
 
Index: sbin/reboot/reboot.8
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/reboot/reboot.8,v
retrieving revision 1.21.16.1
diff -u -r1.21.16.1 reboot.8
--- sbin/reboot/reboot.8	30 May 2007 18:27:19 -0000	1.21.16.1
+++ sbin/reboot/reboot.8	31 May 2007 16:06:36 -0000
@@ -51,7 +51,10 @@
 and
 .Nm
 utilities flush the file system cache to disk, send all running processes
-a SIGTERM, wait for several seconds for them to die, send a SIGKILL to the
++.Dv SIGTERM ,
++wait up to 30 seconds for them to die, send a
++.Dv SIGKILL
++to the
 survivors and, respectively, power down, halt or restart the system.
 The action is logged, including entering a shutdown record into the login
 accounting file and sending a message via
@@ -86,11 +89,30 @@
 .Xr shutdown 8
 utility is used when the system needs to be halted or restarted, giving
 users advance warning of their impending doom.
+.Pp
+If
+.Nm
+fails to successfully send the
+.Dv SIGTERM
+or the
+.Dv SIGKILL
+to every process, or if the
+.Fn reboot
+call fails,
+then
+.Nm
+will send
+.Dv SIGUSR1
+to
+.Xr init 
+to tell it to sart a single user shell.
+.\"
 .Sh SEE ALSO
 .Xr reboot 2 ,
 .Xr syslog 3 ,
 .Xr utmp 5 ,
 .Xr boot 8 ,
+.Xr init 8 ,
 .Xr shutdown 8 ,
 .Xr sync 8
 .Sh HISTORY
Index: sbin/init/pathnames.h
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/init/pathnames.h,v
retrieving revision 1.6
diff -u -r1.6 pathnames.h
--- sbin/init/pathnames.h	7 Aug 2003 10:04:25 -0000	1.6
+++ sbin/init/pathnames.h	14 Feb 2007 20:01:59 -0000
@@ -36,5 +36,5 @@
 
 #include <paths.h>
 
-#define	_PATH_SLOGGER	"/sbin/session_logger"
+#define	_PATH_MAKEDEV	"/sbin/MAKEDEV"
 #define	_PATH_RUNCOM	"/etc/rc"
Index: sbin/init/init.c
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/init/init.c,v
retrieving revision 1.81.2.1
diff -u -r1.81.2.1 init.c
--- sbin/init/init.c	16 Feb 2007 20:31:20 -0000	1.81.2.1
+++ sbin/init/init.c	24 Jun 2007 17:37:27 -0000
@@ -78,8 +78,15 @@
 
 #include "pathnames.h"
 
-#define XSTR(x) #x
-#define STR(x) XSTR(x)
+#if defined(LETS_GET_SMALL) && defined(SUPPORT_UTMPX)
+# include "ERROR: SUPPORT_UTMPX not supported with LETS_GET_SMALL"
+#endif
+#if defined(LETS_GET_SMALL) && defined(SUPPORT_UTMP)
+# include "ERROR: SUPPORT_UTMP not supported with LETS_GET_SMALL"
+#endif
+#if defined(LETS_GET_SMALL) && defined(CHROOT)
+# include "ERROR: CHROOT not supported with LETS_GET_SMALL"
+#endif
 
 /*
  * Sleep times; used to prevent thrashing.
@@ -87,8 +94,8 @@
 #define	GETTY_SPACING		 5	/* N secs minimum getty spacing */
 #define	GETTY_SLEEP		30	/* sleep N secs after spacing problem */
 #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	STALL_TIMEOUT		20	/* wait N secs after warning */
+#define	DEATH_WATCH		30	/* wait N secs for procs to die */
 
 const struct timespec dtrtime = {.tv_sec = 0, .tv_nsec = 250000};
 
@@ -113,6 +120,11 @@
     __attribute__((__format__(__printf__,1,2)));
 void emergency(const char *, ...)
     __attribute__((__format__(__printf__,1,2)));
+void notice(const char *, ...)
+    __attribute__((__format__(__printf__,1,2)));
+void info(const char *, ...)
+    __attribute__((__format__(__printf__,1,2)));
+void debug_print(const char *, va_list);
 void disaster(int);
 void badsys(int);
 
@@ -131,19 +143,22 @@
 #define	MULTI_USER	'm'
 #define	CLEAN_TTYS	'T'
 #define	CATATONIA	'c'
+#define	KILL_TTYS	'k'
 
 state_func_t single_user(void);
 state_func_t runcom(void);
 state_func_t read_ttys(void);
 state_func_t multi_user(void);
 state_func_t clean_ttys(void);
-state_func_t catatonia(void);
-state_func_t death(void);
+state_func_t go_catatonic(void);
+state_func_t go_single_user(void);
+state_func_t kill_ttys(void);
+state_func_t wait_for_reboot(void);
 
-enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT;
+enum { AUTOBOOT = 1, MANUALBOOT = 2 } runcom_mode = AUTOBOOT;
 
 void transition(state_t);
-void setctty(const char *);
+int setctty(const char *);
 
 typedef struct init_session {
 	int	se_index;		/* index of entry in ttys file */
@@ -165,16 +180,18 @@
 session_t *new_session(session_t *, int, struct ttyent *);
 session_t *sessions;
 
-char **construct_argv(char *);
 void start_window_system(session_t *);
 void collect_child(pid_t, int);
 pid_t start_getty(session_t *);
 void transition_handler(int);
 void alrm_handler(int);
+int clang;				/* the alarm bell for alrm_handler() */
+
 void setsecuritylevel(int);
 int getsecuritylevel(void);
+
+char **construct_argv(char *);
 int setupargv(session_t *, struct ttyent *);
-int clang;
 
 int start_session_db(void);
 void add_session(session_t *);
@@ -184,27 +201,29 @@
 
 int do_setttyent(void);
 
+static char get_runlevel(const state_t);
+
 #ifndef LETS_GET_SMALL
 state_t requested_transition = runcom;
 
 void clear_session_logs(session_t *, int);
 state_func_t runetcrc(int);
-#ifdef SUPPORT_UTMPX
+
+# ifdef SUPPORT_UTMPX
 static struct timeval boot_time;
-state_t current_state = death;
+state_t current_state = kill_ttys;
 static void session_utmpx(const session_t *, int);
 static void make_utmpx(const char *, const char *, int, pid_t,
     const struct timeval *, int);
-static char get_runlevel(const state_t);
 static void utmpx_set_runlevel(char, char);
-#endif
+# endif
 
-#ifdef CHROOT
+# ifdef CHROOT
 int did_multiuser_chroot = 0;
 char rootdir[PATH_MAX];
 int shouldchroot(void);
 int createsysctlnode(void);
-#endif /* CHROOT */
+# endif /* CHROOT */
 
 #else /* LETS_GET_SMALL */
 state_t requested_transition = single_user;
@@ -212,26 +231,11 @@
 
 #ifdef MFS_DEV_IF_NO_CONSOLE
 
-#define NINODE 1280
-#define FSSIZE ((8192		/* boot area */				\
-	+ 2 * 8192		/* two copies of superblock */		\
-	+ 4096			/* cylinder group info */		\
-	+ NINODE * (128 + 18)	/* inode and directory entry */		\
-	+ mfile[0].len		/* size of MAKEDEV file */		\
-	+ 2 * 4096) / 512)	/* some slack */
-
-struct mappedfile {
-	const char *path;
-	char	*buf;
-	int	len;
-} mfile[] = {
-	{ "/dev/MAKEDEV",	NULL,	0 },
-	{ "/dev/MAKEDEV.local",	NULL,	0 }
-};
+#ifndef FSSIZE
+# define FSSIZE	1024		/* ... in sectors */
+#endif
 
 static int mfs_dev(void);
-static void mapfile(struct mappedfile *);
-static void writefile(struct mappedfile *);
 
 #endif
 
@@ -245,11 +249,29 @@
 	sigset_t mask;
 #ifndef LETS_GET_SMALL
 	int c;
+#endif
 
-#ifdef SUPPORT_UTMPX
-	(void)gettimeofday(&boot_time, NULL);
+#if defined(SUPPORT_UTMPX)
+	(void) gettimeofday(&boot_time, NULL);
 #endif /* SUPPORT_UTMPX */
 
+	/*
+	 * XXX !!! The kernel should pass us the right console FDs as
+	 * stdin/stdout/stderr, just like the shell does. !!!
+	 *
+	 * for now all sys/kern/init_main.c:start_init() does is to check if
+	 * there are /dev/console and /dev/constty devices, and if not then to
+	 * print a warning message on the kernel console.
+	 */
+#define STDOUT_MSG "init: testing stdout\r\n"
+	(void) write(STDOUT_FILENO, STDOUT_MSG, sizeof(STDOUT_MSG) - 1);
+#define STDERR_MSG "init: testing stderr\r\n"
+	(void) write(STDERR_FILENO, STDERR_MSG, sizeof(STDERR_MSG) - 1);
+#define STDIN_MSG "init: stdin is a tty!\r\n"
+	if (isatty(STDIN_FILENO)) {
+		(void) write(STDOUT_FILENO, STDIN_MSG, sizeof(STDIN_MSG) - 1);
+	}
+
 	/* Dispose of random users. */
 	if (getuid() != 0) {
 		errno = EPERM;
@@ -259,20 +281,19 @@
 	/* System V users like to reexec init. */
 	if (getpid() != 1)
 		errx(1, "already running");
-#endif
 
 	/*
 	 * Create an initial session.
 	 */
 	if (setsid() < 0)
-		warn("initial setsid() failed");
+		warning("initial setsid() failed: %m");
 
 	/*
 	 * Establish an initial user so that programs running
 	 * single user do not freak out and die (like passwd).
 	 */
 	if (setlogin("root") < 0)
-		warn("setlogin() failed");
+		warning("initial setlogin('root') failed: %m");
 
 
 #ifdef MFS_DEV_IF_NO_CONSOLE
@@ -282,10 +303,9 @@
 
 #ifndef LETS_GET_SMALL
 	/*
-	 * Note that this does NOT open a file...
 	 * Does 'init' deserve its own facility number?
 	 */
-	openlog("init", LOG_CONS, LOG_AUTH);
+	openlog("init", LOG_PERROR, LOG_AUTH);
 #endif /* LETS_GET_SMALL */
 
 
@@ -300,15 +320,15 @@
 			requested_transition = single_user;
 			break;
 		case 'f':
-			runcom_mode = FASTBOOT;
+			/* fast boot, implementation is handled by /etc/rc */
 			break;
 		default:
-			warning("unrecognized flag `%c'", c);
+			warning("unrecognized command-line flag '-%c'", c);
 			break;
 		}
 
 	if (optind != argc)
-		warning("ignoring excess arguments");
+		warning("ignoring %d excess arguments", argc - optind);
 #else /* LETS_GET_SMALL */
 	requested_transition = single_user;
 #endif /* LETS_GET_SMALL */
@@ -320,39 +340,34 @@
 	handle(badsys, SIGSYS, 0);
 	handle(disaster, SIGABRT, SIGFPE, SIGILL, SIGSEGV,
 	       SIGBUS, SIGXCPU, SIGXFSZ, 0);
-	handle(transition_handler, SIGHUP, SIGTERM, SIGTSTP, 0);
+	handle(transition_handler, SIGHUP, SIGINT, SIGTERM, SIGTSTP, SIGUSR1, SIGUSR2, 0);
 	handle(alrm_handler, SIGALRM, 0);
-	(void)sigfillset(&mask);
+	(void) sigfillset(&mask);
 	delset(&mask, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS,
-	    SIGXCPU, SIGXFSZ, SIGHUP, SIGTERM, SIGTSTP, SIGALRM, 0);
-	(void)sigprocmask(SIG_SETMASK, &mask, NULL);
-	(void)sigemptyset(&sa.sa_mask);
+	    SIGXCPU, SIGXFSZ, SIGHUP, SIGINT, SIGTERM, SIGTSTP, SIGUSR1, SIGUSR2, SIGALRM, 0);
+	(void) sigprocmask(SIG_SETMASK, &mask, (sigset_t *) NULL);
+	(void) sigemptyset(&sa.sa_mask);
 	sa.sa_flags = 0;
 	sa.sa_handler = SIG_IGN;
-	(void)sigaction(SIGTTIN, &sa, NULL);
-	(void)sigaction(SIGTTOU, &sa, NULL);
+	(void) sigaction(SIGTTIN, &sa, (struct sigaction *) NULL);
+	(void) sigaction(SIGTTOU, &sa, (struct sigaction *) NULL);
 
-	/*
-	 * Paranoia.
-	 */
-	(void)close(0);
-	(void)close(1);
-	(void)close(2);
-
-#if !defined(LETS_GET_SMALL) && defined(CHROOT)
+#if defined(CHROOT)
 	/* Create "init.root" sysctl node. */
-	createsysctlnode();
-#endif /* !LETS_GET_SMALL && CHROOT*/
+	(void) createsysctlnode();
+#endif /* CHROOT*/
 
 	/*
 	 * Start the state machine.
 	 */
 	transition(requested_transition);
+	/* NOTREACHED */
 
 	/*
 	 * Should never reach here.
 	 */
-	return 1;
+	_exit(1);		/* avoid atexit() and other sticky goo */
+	/* NOTREACHED */
 }
 
 /*
@@ -369,13 +384,13 @@
 	va_start(ap, handler);
 
 	sa.sa_handler = handler;
-	(void)sigfillset(&mask_everything);
+	(void) sigfillset(&mask_everything);
 
 	while ((sig = va_arg(ap, int)) != 0) {
 		sa.sa_mask = mask_everything;
 		/* XXX SA_RESTART? */
 		sa.sa_flags = sig == SIGCHLD ? SA_NOCLDSTOP : 0;
-		(void)sigaction(sig, &sa, NULL);
+		(void) sigaction(sig, &sa, (struct sigaction *) NULL);
 	}
 	va_end(ap);
 }
@@ -392,14 +407,13 @@
 	va_start(ap, maskp);
 
 	while ((sig = va_arg(ap, int)) != 0)
-		(void)sigdelset(maskp, sig);
+		(void) sigdelset(maskp, sig);
 	va_end(ap);
 }
 
 /*
  * Log a message and sleep for a while (to give someone an opportunity
  * to read it and to save log or hardcopy output if the problem is chronic).
- * NB: should send a message to the session logger to avoid blocking.
  */
 void
 stall(const char *message, ...)
@@ -410,13 +424,13 @@
 	vsyslog(LOG_ALERT, message, ap);
 	va_end(ap);
 	closelog();
-	(void)sleep(STALL_TIMEOUT);
+	debug_print(message, ap);
+	notice("pausing now for %d seconds to prevent thrashing", STALL_TIMEOUT);
+	(void) sleep(STALL_TIMEOUT);
 }
 
 /*
- * Like stall(), but doesn't sleep.
- * If cpp had variadic macros, the two functions could be #defines for another.
- * NB: should send a message to the session logger to avoid blocking.
+ * Log a warning message (like stall(), but doesn't sleep).
  */
 void
 warning(const char *message, ...)
@@ -424,37 +438,14 @@
 	va_list ap;
 
 	va_start(ap, message);
-	vsyslog(LOG_ALERT, message, ap);
+	vsyslog(LOG_EMERG, message, ap);
 	va_end(ap);
 	closelog();
-
-#if 0
-	/*
-	 * XXX: syslog seems to just plain not work in console-only
-	 * XXX: situation... that should be fixed.  Let's leave this
-	 * XXX: note + code here in case someone gets in trouble and
-	 * XXX: wants to debug. -- Jachym Holecek <freza@liberouter.org>
-	 */
-	{
-		char errbuf[1024];
-		int fd, len;
-
-		/* We can't do anything on errors, anyway... */
-		fd = open(_PATH_CONSOLE, O_WRONLY);
-		if (fd == -1)
-			return ;
-
-		/* %m will get lost... */
-		len = vsnprintf(errbuf, sizeof(errbuf), message, ap);
-		(void)write(fd, (void *)errbuf, len);
-		(void)close(fd);
-	}
-#endif
+	debug_print(message, ap);
 }
 
 /*
  * Log an emergency message.
- * NB: should send a message to the session logger to avoid blocking.
  */
 void
 emergency(const char *message, ...)
@@ -465,6 +456,79 @@
 	vsyslog(LOG_EMERG, message, ap);
 	va_end(ap);
 	closelog();
+	debug_print(message, ap);
+}
+
+/*
+ * Log a notice message.
+ */
+void
+notice(const char *message, ...)
+{
+	va_list ap;
+
+	va_start(ap, message);
+	vsyslog(LOG_NOTICE, message, ap);
+	va_end(ap);
+	closelog();
+	debug_print(message, ap);
+}
+
+/*
+ * Log an informational message.
+ */
+void
+info(const char *message, ...)
+{
+	va_list ap;
+
+	va_start(ap, message);
+	vsyslog(LOG_INFO, message, ap);
+	va_end(ap);
+	closelog();
+	debug_print(message, ap);
+}
+
+void
+debug_print(const char *message, va_list ap)
+{
+#if 1
+# define ERRBUF_PREFIX	"init: "
+	char errbuf[BUFSIZ] = ERRBUF_PREFIX;
+	int fd, len;
+
+	/* We can't do anything on errors, anyway... */
+	fd = open(_PATH_CONSOLE, O_WRONLY);
+	if (fd == -1) {
+		syslog(LOG_EMERG, "open(%s) failed: %m", _PATH_CONSOLE);
+		closelog();
+		return;
+	}
+	/* %m will not get expanded, sigh... */
+	len = vsnprintf(&errbuf[sizeof(ERRBUF_PREFIX) - 1], sizeof(errbuf) - 3, message, ap);
+	strcat(errbuf, "\r\n");
+	(void) write(fd, (void *) errbuf, len + sizeof(ERRBUF_PREFIX) - 1 + 2);
+	(void) close(fd);
+#endif
+}
+
+
+/*
+ * return name of signal signum.
+ */
+static char *
+strsigname(int signum)
+{
+	static char signame[32];		/* SIG2STR_MAX */
+
+	if (signum >= sys_nsig || signum < 0)
+		snprintf(signame, sizeof(signame), "#%d", signum);
+	else {
+		strncpy(signame, sys_signame[signum], sizeof(signame));
+		signame[sizeof(signame) - 1] = '\0';
+	}
+
+	return (signame);
 }
 
 /*
@@ -478,8 +542,10 @@
 {
 	static int badcount = 0;
 
-	if (badcount++ < 25)
+	if (badcount++ < 25) {
+ 		warning("signal SIG%s: %s", strsigname(sig), strsignal(sig));
 		return;
+	}
 	disaster(sig);
 }
 
@@ -490,8 +556,7 @@
 disaster(int sig)
 {
 
-	emergency("fatal signal: %s", strsignal(sig));
-	(void)sleep(STALL_TIMEOUT);
+	stall("fatal signal SIG%s: %s", strsigname(sig), strsignal(sig));
 	_exit(sig);		/* reboot */
 }
 
@@ -534,14 +599,14 @@
 	name[1] = KERN_SECURELVL;
 	if (sysctl(name, 2, NULL, NULL, &newlevel, sizeof newlevel) == -1) {
 		emergency("cannot change kernel security level from"
-		    " %d to %d: %m", curlevel, newlevel);
+			  " %d to %d: %m", curlevel, newlevel);
 		return;
 	}
-#ifdef SECURE
+# ifdef SECURE
 	warning("kernel security level changed from %d to %d",
-	    curlevel, newlevel);
-#endif
-#endif
+		curlevel, newlevel);
+# endif
+#endif /* KERN_SECURELVL */
 }
 
 /*
@@ -556,56 +621,66 @@
 		return;
 	for (;;) {
 #ifdef SUPPORT_UTMPX
-#ifndef LETS_GET_SMALL
 		utmpx_set_runlevel(get_runlevel(current_state),
-		    get_runlevel(s));
+				   get_runlevel(s));
 		current_state = s;
 #endif
-#endif
 		s = (state_t)(*s)();
 	}
+	/* NOTREACHED */
 }
 
 #ifndef LETS_GET_SMALL
 /*
  * Close out the accounting files for a login session.
- * NB: should send a message to the session logger to avoid blocking.
  */
 void
 clear_session_logs(session_t *sp, int status)
 {
 	char *line = sp->se_device + sizeof(_PATH_DEV) - 1;
 
-#ifdef SUPPORT_UTMPX
+# ifdef SUPPORT_UTMPX
 	if (logoutx(line, status, DEAD_PROCESS))
 		logwtmpx(line, "", "", status, DEAD_PROCESS);
-#endif
-#ifdef SUPPORT_UTMP
+# endif
+# ifdef SUPPORT_UTMP
 	if (logout(line))
 		logwtmp(line, "", "");
-#endif
+# endif
 }
-#endif
+#endif /* LETS_GET_SMALL */
 
 /*
- * Start a session and allocate a controlling terminal.
- * Only called by children of init after forking.
+ * Start a session and allocate a controlling terminal via the specified name
+ *
+ * Only called by children of init (i.e. for /etc/rc and the single-user shell)
+ * after forking the process that will run them.
+ *
+ * XXX once the kernel is fixed to pass the STDIO file descriptors for the
+ * console to init then calls to this function (which should only be called
+ * from a child process) can probably be replaced with a call to
+ * login_tty(STDIN_FILENO).  /sbin/init should not need to know the pathname to
+ * any console device.
  */
-void
+int
 setctty(const char *name)
 {
 	int fd;
 
 	(void) revoke(name);
 	nanosleep(&dtrtime, NULL);	/* leave DTR low for a bit */
+	(void) close(STDIN_FILENO);
+	(void) close(STDOUT_FILENO);
+	(void) close(STDERR_FILENO);
 	if ((fd = open(name, O_RDWR)) == -1) {
-		stall("can't open %s: %m", name);
-		_exit(1);
+		warning("can't open %s: %m", name);
+		return(-1);
 	}
 	if (login_tty(fd) == -1) {
-		stall("can't get %s for controlling terminal: %m", name);
-		_exit(1);
+		warning("can't set %s as the controlling terminal: %m", name);
+		return(-1);
 	}
+	return(0);
 }
 
 /*
@@ -629,13 +704,16 @@
 	char *clear, *password;
 #endif
 #ifdef ALTSHELL
-	char altshell[128];
+	char altshell_buf[128];
+	char *altshell;
 #endif /* ALTSHELL */
 
-#if !defined(LETS_GET_SMALL) && defined(CHROOT)
+#if defined(CHROOT)
 	/* Clear previous idea, just in case. */
 	did_multiuser_chroot = 0;
-#endif /* !LETS_GET_SMALL && CHROOT */
+#endif /* CHROOT */
+
+	notice("starting single user mode...");
 
 	/*
 	 * If the kernel is in secure mode, downgrade it to insecure mode.
@@ -644,19 +722,22 @@
 	if (from_securitylevel > 0)
 		setsecuritylevel(0);
 
-	(void)sigemptyset(&sa.sa_mask);
+	/* ignore SIGTSTP and SIGHUP so we can't end up here right away */
+	/* XXX or is it really because we want to have them ignored in the child? */
+	(void) sigemptyset(&sa.sa_mask);
 	sa.sa_flags = 0;
 	sa.sa_handler = SIG_IGN;
-	(void)sigaction(SIGTSTP, &sa, &satstp);
-	(void)sigaction(SIGHUP, &sa, &sahup);
+	(void) sigaction(SIGTSTP, &sa, &satstp);
+	(void) sigaction(SIGHUP, &sa, &sahup);
 	if ((pid = fork()) == 0) {
 		/*
 		 * Start the single user session.
 		 */
-		if (access(_PATH_CONSTTY, F_OK) == 0)
-			setctty(_PATH_CONSTTY);
-		else
-			setctty(_PATH_CONSOLE);
+		if (access(_PATH_CONSTTY, F_OK) == 0) {
+			if (setctty(_PATH_CONSTTY) == -1)
+				(void) setctty(_PATH_CONSOLE);
+		} else
+			(void) setctty(_PATH_CONSOLE);
 
 #ifdef SECURE
 		/*
@@ -664,18 +745,26 @@
 		 * We don't care if the console is 'on' by default;
 		 * it's the only tty that can be 'off' and 'secure'.
 		 */
-		typ = getttynam("console");
+		typ = getttynam("constty");
+		if (typ == NULL)
+			typ = getttynam("console");
 		pp = getpwnam("root");
-		if (typ && (from_securitylevel >=2 || (typ->ty_status
-		    & TTY_SECURE) == 0) && pp && *pp->pw_passwd != '\0') {
-			(void)fprintf(stderr,
-			    "Enter root password, or ^D to go multi-user\n");
+		if (typ && (from_securitylevel >= 2 ||
+			    (typ->ty_status & TTY_SECURE) == 0) &&
+		    pp && *pp->pw_passwd != '\0') {
+			(void) fprintf(stderr,
+				       "Enter root password, or ^D to go multi-user\n");
+			/*
+			 * XXX this endless loop is bogus!
+			 */
 			for (;;) {
 				clear = getpass("Password:");
-				if (clear == 0 || *clear == '\0')
+				if (clear == 0 || *clear == '\0') {
+					warning("single-user login terminated");
 					_exit(0);
+				}
 				password = crypt(clear, pp->pw_passwd);
-				(void)memset(clear, 0, _PASSWORD_LEN);
+				(void) memset(clear, 0, _PASSWORD_LEN);
 				if (strcmp(password, pp->pw_passwd) == 0)
 					break;
 				warning("single-user login failed");
@@ -686,17 +775,12 @@
 #endif /* SECURE */
 
 #ifdef ALTSHELL
-		(void)fprintf(stderr,
-		    "Enter pathname of shell or RETURN for %s: ", shell);
+		altshell = &altshell_buf[0];
+		(void) fprintf(stderr, "Enter the pathname of a shell or press RETURN for %s: ", shell);
 		if (fgets(altshell, sizeof(altshell), stdin) == NULL) {
 			altshell[0] = '\0';
-		} else {
-			/* nuke \n */
-			char *p;
-
-			if ((p = strchr(altshell, '\n')) != NULL)
-				*p = '\0';
-		}
+		} else		/* nuke the first newline or CR */
+			altshell = strsep(&altshell, "\r\n");
 
 		if (altshell[0])
 			shell = altshell;
@@ -707,26 +791,25 @@
 		 * We catch all the interesting ones,
 		 * and those are reset to SIG_DFL on exec.
 		 */
-		(void)sigemptyset(&mask);
-		(void)sigprocmask(SIG_SETMASK, &mask, NULL);
+		(void) sigemptyset(&mask);
+		(void) sigprocmask(SIG_SETMASK, &mask, NULL);
 
 		/*
 		 * Fire off a shell.
 		 * If the default one doesn't work, try the Bourne shell.
 		 */
-		argv[0] = "-sh";
+		argv[0] = "-sh";	/* may be reset to altshell */
 		argv[1] = 0;
 		setenv("PATH", INIT_PATH, 1);
 #ifdef ALTSHELL
 		if (altshell[0])
-			argv[0] = altshell;
-		(void)execv(shell, __UNCONST(argv));
-		emergency("can't exec `%s' for single user: %m", shell);
-		argv[0] = "-sh";
+			argv[0] = altshell; /* XXX should prepend a '-' to its basename */
+		(void) execv(shell, __UNCONST(argv));
+		emergency("can't exec specified shell '%s' for single user: %m", shell);
+		argv[0] = "-sh";	/* reset this for INIT_BSHELL */
 #endif /* ALTSHELL */
-		(void)execv(INIT_BSHELL, __UNCONST(argv));
-		emergency("can't exec `%s' for single user: %m", INIT_BSHELL);
-		(void)sleep(STALL_TIMEOUT);
+		(void) execv(INIT_BSHELL, __UNCONST(argv));
+		stall("can't exec standard shell '%s' for single user: %m", INIT_BSHELL);
 		_exit(1);
 	}
 
@@ -737,8 +820,10 @@
 		emergency("can't fork single-user shell: %m, trying again");
 		while (waitpid(-1, NULL, WNOHANG) > 0)
 			continue;
-		(void)sigaction(SIGTSTP, &satstp, NULL);
-		(void)sigaction(SIGHUP, &sahup, NULL);
+		/* remember to restore the signal handlers we set to ignore */
+		(void) sigaction(SIGTSTP, &satstp, NULL);
+		(void) sigaction(SIGHUP, &sahup, NULL);
+
 		return (state_func_t) single_user;
 	}
 
@@ -749,47 +834,63 @@
 		if (wpid == -1) {
 			if (errno == EINTR)
 				continue;
-			warning("wait for single-user shell failed: %m; "
-			    "restarting");
-			return (state_func_t)single_user;
+			warning("waitpid() for single-user shell failed: %m; restarting single-user mode");
+			return (state_func_t) single_user;
 		}
 		if (wpid == pid && WIFSTOPPED(status)) {
-			warning("shell stopped, restarting");
+			warning("single-user shell [pid=%d] stopped -- sending SIGCONT to it\n", pid);
 			kill(pid, SIGCONT);
 			wpid = -1;
 		}
 	} while (wpid != pid && !requested_transition);
 
 	if (requested_transition) {
-		(void)sigaction(SIGTSTP, &satstp, NULL);
-		(void)sigaction(SIGHUP, &sahup, NULL);
-		return (state_func_t)requested_transition;
+		warning("received state transition for '%c' while waiting for single user mode to end!",
+			get_runlevel(requested_transition));
+		/* remember to restore the signal handlers we set to ignore */
+		(void) sigaction(SIGTSTP, &satstp, NULL);
+		(void) sigaction(SIGHUP, &sahup, NULL);
+
+		return (state_func_t) requested_transition;
 	}
 
 	if (WIFSIGNALED(status)) {
 		if (WTERMSIG(status) == SIGKILL) {
-			/* executed /sbin/reboot; wait for the end quietly */
+#if 1
 			sigset_t s;
 	
-			(void)sigfillset(&s);
+			/*
+			 * the single-user shell received SIGKILL, possibly from
+			 * /sbin/reboot; suspend all signals and wait for the end....
+			 */
+			warning("single user shell killed with SIGKILL, waiting for kernel halt or reboot");
+			(void) sigfillset(&s);
 			for (;;)
-				(void)sigsuspend(&s);
-		} else {	
-			warning("single user shell terminated, restarting");
-			(void)sigaction(SIGTSTP, &satstp, NULL);
-			(void)sigaction(SIGHUP, &sahup, NULL);
+				(void) sigsuspend(&s);
+#else
+			warning("single user shell killed with SIGKILL, exiting to trigger panic()");
+			exit(1);
+#endif
+		} else {		/* XXX what about SIGTERM? */
+			warning("single user shell terminated with SIG%s, restarting single user mode",
+				strsigname(WTERMSIG(status)));
+			(void) sigaction(SIGTSTP, &satstp, NULL);
+			(void) sigaction(SIGHUP, &sahup, NULL);
+
 			return (state_func_t) single_user;
 		}
 	}
+	if (WEXITSTATUS(status))
+		notice("single user shell terminated with non-zero exit status (%d)", WEXITSTATUS(status));
 
-	runcom_mode = FASTBOOT;
-	(void)sigaction(SIGTSTP, &satstp, NULL);
-	(void)sigaction(SIGHUP, &sahup, NULL);
+	runcom_mode = MANUALBOOT;
+	(void) sigaction(SIGTSTP, &satstp, NULL);
+	(void) sigaction(SIGHUP, &sahup, NULL);
 #ifndef LETS_GET_SMALL
-	return (state_func_t) runcom;
-#else /* LETS_GET_SMALL */
+	return (state_func_t) runcom;	/* head on up to multi_user mode */
+#else
 	return (state_func_t) single_user;
-#endif /* LETS_GET_SMALL */
+#endif
 }
 
 #ifndef LETS_GET_SMALL
@@ -803,43 +904,58 @@
 	const char *argv[4];
 	struct sigaction sa;
 
+	info("starting %s%s...", _PATH_RUNCOM,
+	     trychroot ?
+#ifdef CHROOT
+	     ", with chroot()" :
+#else
+	     ", impossible internal error!  chroot() enabled without CHROOT!"
+#endif
+	     "");
+
 	switch ((pid = fork())) {
 	case 0:
-		(void)sigemptyset(&sa.sa_mask);
+		(void) sigemptyset(&sa.sa_mask);
 		sa.sa_flags = 0;
 		sa.sa_handler = SIG_IGN;
-		(void)sigaction(SIGTSTP, &sa, NULL);
-		(void)sigaction(SIGHUP, &sa, NULL);
+		(void) sigaction(SIGTSTP, &sa, NULL);
+		(void) sigaction(SIGHUP, &sa, NULL);
 
-		setctty(_PATH_CONSOLE);
+		/*
+		 * try to get constty/console TTY signals to /etc/rc processes
+		 */
+		if (access(_PATH_CONSTTY, F_OK) == 0) {
+			if (setctty(_PATH_CONSTTY) == -1)
+				(void) setctty(_PATH_CONSOLE);
+		} else
+			(void) setctty(_PATH_CONSOLE);
 
 		argv[0] = "sh";
 		argv[1] = _PATH_RUNCOM;
 		argv[2] = (runcom_mode == AUTOBOOT ? "autoboot" : 0);
 		argv[3] = 0;
 
-		(void)sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL);
+		(void) sigprocmask(SIG_SETMASK, &sa.sa_mask, NULL);
 
-#ifdef CHROOT
+# ifdef CHROOT
 		if (trychroot)
 			if (chroot(rootdir) != 0) {
-				warning("failed to chroot to `%s': %m",
-				    rootdir);
-				_exit(1); 	/* force single user mode */
+				warning("failed to chroot to '%s': %m",
+					rootdir);
+				_exit(1);	/* force single user mode */
 			}
-#endif /* CHROOT */
+# endif /* CHROOT */
 
-		(void)execv(INIT_BSHELL, __UNCONST(argv));
-		stall("can't exec `%s' for `%s': %m", INIT_BSHELL, _PATH_RUNCOM);
+		(void) execv(INIT_BSHELL, __UNCONST(argv));
+		stall("can't execv() '%s' for '%s': %m", INIT_BSHELL, _PATH_RUNCOM);
 		_exit(1);	/* force single user mode */
 		/*NOTREACHED*/
 	case -1:
-		emergency("can't fork for `%s' on `%s': %m", INIT_BSHELL,
-		    _PATH_RUNCOM);
+		emergency("can't fork() to run '%s' for '%s': %m", INIT_BSHELL,
+			  _PATH_RUNCOM);
 		while (waitpid(-1, NULL, WNOHANG) > 0)
 			continue;
-		(void)sleep(STALL_TIMEOUT);
-		return (state_func_t)single_user;
+		return (state_func_t) single_user;
 	default:
 		break;
 	}
@@ -853,36 +969,55 @@
 		if (wpid == -1) {
 			if (errno == EINTR)
 				continue;
-			warning("wait for `%s' on `%s' failed: %m; going to "
-			    "single user mode", INIT_BSHELL, _PATH_RUNCOM);
-			return (state_func_t)single_user;
+			warning("wait for '%s  %s' failed: %m; going to "
+				"single user mode", INIT_BSHELL, _PATH_RUNCOM);
+			return (state_func_t) single_user;
 		}
 		if (wpid == pid && WIFSTOPPED(status)) {
-			warning("`%s' on `%s' stopped, restarting",
-			    INIT_BSHELL, _PATH_RUNCOM);
-			(void)kill(pid, SIGCONT);
+			warning("'%s %s' stopped, restarting it",
+				INIT_BSHELL, _PATH_RUNCOM);
+			(void) kill(pid, SIGCONT);
 			wpid = -1;
 		}
 	} while (wpid != pid);
 
 	if (WIFSIGNALED(status) && WTERMSIG(status) == SIGTERM &&
-	    requested_transition == catatonia) {
-		/* /etc/rc executed /sbin/reboot; wait for the end quietly */
+	    requested_transition == go_catatonic) { /* XXX !!! how the heck could this ever be true!?!?!? */
 		sigset_t s;
 
-		(void)sigfillset(&s);
+		/*
+		 * /etc/rc's shell received SIGTERM, possibly from
+		 * /sbin/reboot; suspend all signals and wait for the end....
+		 */
+		warning("'%s %s' killed with SIGTERM, waiting for kernel halt or reboot",
+			_PATH_BSHELL, _PATH_RUNCOM);
+		(void) sigfillset(&s);
 		for (;;)
-			(void)sigsuspend(&s);
+			(void) sigsuspend(&s);
 	}
 
 	if (!WIFEXITED(status)) {
-		warning("`%s' on `%s' terminated abnormally, going to "
-		    "single user mode", INIT_BSHELL, _PATH_RUNCOM);
-		return (state_func_t)single_user;
+		if (WIFSIGNALED(status)) {
+			warning("'%s %s' terminated with SIG%s, going to single user mode",
+				_PATH_BSHELL, _PATH_RUNCOM, strsigname(WTERMSIG(status)));
+		} else
+			warning("'%s %s' terminated abnormally, going to single user mode",
+				INIT_BSHELL, _PATH_RUNCOM);
+		return (state_func_t) single_user;
 	}
 
-	if (WEXITSTATUS(status))
-		return (state_func_t)single_user;
+	if (WEXITSTATUS(status)) {
+		warning("'%s %s' terminated with non-zero exit status (%d), going to single user mode",
+			INIT_BSHELL, _PATH_RUNCOM, WEXITSTATUS(status));
+		return (state_func_t) single_user;
+	}
+
+# ifdef SUPPORT_UTMPX
+	logwtmpx("~", "reboot", "", 0, INIT_PROCESS);
+# endif
+# ifdef SUPPORT_UTMP
+	logwtmp("~", "reboot", "");	/* this really means "boot"! */
+# endif
 
 	return (state_func_t) read_ttys;
 }
@@ -895,12 +1030,14 @@
 {
 	state_func_t next_step;
 
+	info("initiating userlevel boot up to multi-user mode...");
+
 	/* Run /etc/rc and choose next state depending on the result. */
 	next_step = runetcrc(0);
 	if (next_step != (state_func_t) read_ttys)
 		return (state_func_t) next_step;
 
-#ifdef CHROOT
+# ifdef CHROOT
 	/*
 	 * If init.root sysctl does not point to "/", we'll chroot and run
 	 * The Real(tm) /etc/rc now.  Global variable rootdir will tell us
@@ -915,20 +1052,20 @@
 	} else {
 		did_multiuser_chroot = 0;
 	}
-#endif /* CHROOT */
+# endif /* CHROOT */
 
+# ifdef SUPPORT_UTMPX
+	logwtmpx("~", "multi-user", "", 0, INIT_PROCESS);
+# endif
+# ifdef SUPPORT_UTMP
+	logwtmp("~", "multiusr", "");
+# endif
 	/*
 	 * Regardless of whether in chroot or not, we booted successfuly.
 	 * It's time to spawn gettys (ie. next_step's value at this point).
 	 */
-	runcom_mode = AUTOBOOT;		/* the default */
 	/* NB: should send a message to the session logger to avoid blocking. */
-#ifdef SUPPORT_UTMPX
-	logwtmpx("~", "reboot", "", 0, INIT_PROCESS);
-#endif
-#ifdef SUPPORT_UTMP
-	logwtmp("~", "reboot", "");
-#endif
+
 	return (state_func_t) read_ttys;
 }
 
@@ -970,9 +1107,9 @@
 
 	if ((*session_db->put)(session_db, &key, &data, 0))
 		emergency("insert %d: %m", sp->se_process);
-#ifdef SUPPORT_UTMPX
+# ifdef SUPPORT_UTMPX
 	session_utmpx(sp, 1);
-#endif
+# endif
 }
 
 /*
@@ -988,9 +1125,9 @@
 
 	if ((*session_db->del)(session_db, &key, 0))
 		emergency("delete %d: %m", sp->se_process);
-#ifdef SUPPORT_UTMPX
+# ifdef SUPPORT_UTMPX
 	session_utmpx(sp, 0);
-#endif
+# endif
 }
 
 /*
@@ -1004,14 +1141,14 @@
 	session_t *ret;
 
 	if (session_db == NULL)
-		return NULL;
+		return (NULL);
 
 	key.data = &pid;
 	key.size = sizeof pid;
 	if ((*session_db->get)(session_db, &key, &data, 0) != 0)
-		return 0;
-	(void)memmove(&ret, data.data, sizeof(ret));
-	return ret;
+		return (0);
+	(void) memmove(&ret, data.data, sizeof(ret));
+	return (ret);
 }
 
 /*
@@ -1069,15 +1206,15 @@
 
 	sp = malloc(sizeof (session_t));
 	if (sp == NULL)
-		return NULL;
+		return (NULL);
 	memset(sp, 0, sizeof *sp);
 
 	sp->se_flags = SE_PRESENT;
 	sp->se_index = session_index;
 
-	(void)asprintf(&sp->se_device, "%s%s", _PATH_DEV, typ->ty_name);
+	(void) asprintf(&sp->se_device, "%s%s", _PATH_DEV, typ->ty_name);
 	if (!sp->se_device)
-		return NULL;
+		return (NULL);
 
 	if (setupargv(sp, typ) == 0) {
 		free_session(sp);
@@ -1107,12 +1244,12 @@
 		free(sp->se_getty);
 		free(sp->se_getty_argv);
 	}
-	(void)asprintf(&sp->se_getty, "%s %s", typ->ty_getty, typ->ty_name);
+	(void) asprintf(&sp->se_getty, "%s %s", typ->ty_getty, typ->ty_name);
 	if (!sp->se_getty)
 		return (0);
 	sp->se_getty_argv = construct_argv(sp->se_getty);
 	if (sp->se_getty_argv == NULL) {
-		warning("can't parse getty for port `%s'", sp->se_device);
+		warning("can't parse getty for port '%s'", sp->se_device);
 		free(sp->se_getty);
 		sp->se_getty = NULL;
 		return (0);
@@ -1123,8 +1260,8 @@
 		sp->se_window = strdup(typ->ty_window);
 		sp->se_window_argv = construct_argv(sp->se_window);
 		if (sp->se_window_argv == NULL) {
-			warning("can't parse window for port `%s'",
-			    sp->se_device);
+			warning("can't parse window for port '%s'",
+				sp->se_device);
 			free(sp->se_window);
 			sp->se_window = NULL;
 			return (0);
@@ -1143,7 +1280,7 @@
 	session_t *sp, *snext;
 	struct ttyent *typ;
 
-#ifdef SUPPORT_UTMPX
+# ifdef SUPPORT_UTMPX
 	if (sessions == NULL) {
 		struct stat st;
 
@@ -1156,38 +1293,36 @@
 			struct timeval down_time;
 
 			TIMESPEC_TO_TIMEVAL(&down_time, 
-			    st.st_atime > st.st_mtime ?
-			    &st.st_atimespec : &st.st_mtimespec);
+					    st.st_atime > st.st_mtime ?
+					    &st.st_atimespec : &st.st_mtimespec);
 			make_utmpx("", DOWN_MSG, DOWN_TIME, 0, &down_time, 0);
 		}
 	}
-#endif
+# endif
 	/*
 	 * Destroy any previous session state.
 	 * There shouldn't be any, but just in case...
 	 */
 	for (sp = sessions; sp; sp = snext) {
-#ifndef LETS_GET_SMALL
 		if (sp->se_process)
 			clear_session_logs(sp, 0);
-#endif
 		snext = sp->se_next;
 		free_session(sp);
 	}
 	sessions = NULL;
 
 	if (start_session_db()) {
-		warning("start_session_db failed, death");
-#ifdef CHROOT
+		warning("start_session_db failed, going to single-user mode");
+# ifdef CHROOT
 		/* If /etc/rc ran in chroot, we want to kill any survivors. */
 		if (did_multiuser_chroot)
-			return (state_func_t)death;
+			return (state_func_t) kill_ttys;
 		else
-#endif /* CHROOT */
-			return (state_func_t)single_user;
+# endif /* CHROOT */
+			return (state_func_t) single_user;
 	}
 
-	do_setttyent();
+	(void) do_setttyent();
 
 	/*
 	 * Allocate a session entry for each active port.
@@ -1198,7 +1333,7 @@
 			sp = snext;
 	endttyent();
 
-	return (state_func_t)multi_user;
+	return (state_func_t) multi_user;
 }
 
 /*
@@ -1211,8 +1346,8 @@
 	sigset_t mask;
 
 	if ((pid = fork()) == -1) {
-		emergency("can't fork for window system on port `%s': %m",
-		    sp->se_device);
+		emergency("can't fork for window system on port '%s': %m",
+			  sp->se_device);
 		/* hope that getty fails and we can try again */
 		return;
 	}
@@ -1226,9 +1361,9 @@
 	if (setsid() < 0)
 		emergency("setsid failed (window): %m");
 
-	(void)execv(sp->se_window_argv[0], sp->se_window_argv);
-	stall("can't exec window system `%s' for port `%s': %m",
-	    sp->se_window_argv[0], sp->se_device);
+	(void) execv(sp->se_window_argv[0], sp->se_window_argv);
+	stall("can't exec window system '%s' for port '%s': %m",
+	      sp->se_window_argv[0], sp->se_device);
 	_exit(1);
 }
 
@@ -1246,55 +1381,55 @@
 	 * fork(), not vfork() -- we can't afford to block.
 	 */
 	if ((pid = fork()) == -1) {
-		emergency("can't fork for getty on port `%s': %m",
-		    sp->se_device);
-		return -1;
+		emergency("can't fork for getty on port '%s': %m",
+			  sp->se_device);
+		return (-1);
 	}
 
 	if (pid)
-		return pid;
+		return (pid);
 
-#ifdef CHROOT
+# ifdef CHROOT
 	/* If /etc/rc did proceed inside chroot, we have to try as well. */
 	if (did_multiuser_chroot)
 		if (chroot(rootdir) != 0) {
-			stall("can't chroot getty `%s' inside `%s': %m",
-			    sp->se_getty_argv[0], rootdir);
+			stall("can't chroot getty '%s' inside '%s': %m",
+			      sp->se_getty_argv[0], rootdir);
 			_exit(1);
 		}
-#endif /* CHROOT */
+# endif /* CHROOT */
 
 	if (current_time > sp->se_started.tv_sec &&
 	    current_time - sp->se_started.tv_sec < GETTY_SPACING) {
-		warning("getty repeating too quickly on port `%s', sleeping",
-		    sp->se_device);
-		(void)sleep(GETTY_SLEEP);
+		warning("getty repeating too quickly on port '%s', sleeping for %d seconds",
+			sp->se_device, GETTY_SLEEP);
+		(void) sleep(GETTY_SLEEP);
 	}
 
 	if (sp->se_window) {
 		start_window_system(sp);
-		(void)sleep(WINDOW_WAIT);
+		(void) sleep(WINDOW_WAIT);
 	}
 
-	(void)sigemptyset(&mask);
-	(void)sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
+	(void) sigemptyset(&mask);
+	(void) sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
 
-	(void)execv(sp->se_getty_argv[0], sp->se_getty_argv);
-	stall("can't exec getty `%s' for port `%s': %m",
-	    sp->se_getty_argv[0], sp->se_device);
+	(void) execv(sp->se_getty_argv[0], sp->se_getty_argv);
+	stall("can't exec getty '%s' for port '%s': %m",
+	      sp->se_getty_argv[0], sp->se_device);
 	_exit(1);
 	/*NOTREACHED*/
 }
-#ifdef SUPPORT_UTMPX
+# ifdef SUPPORT_UTMPX
 static void
 session_utmpx(const session_t *sp, int add)
 {
 	const char *name = sp->se_getty ? sp->se_getty :
-	    (sp->se_window ? sp->se_window : "");
+			   (sp->se_window ? sp->se_window : "");
 	const char *line = sp->se_device + sizeof(_PATH_DEV) - 1;
 
 	make_utmpx(name, line, add ? LOGIN_PROCESS : DEAD_PROCESS,
-	    sp->se_process, &sp->se_started, sp->se_index);
+		   sp->se_process, &sp->se_started, sp->se_index);
 }
 
 static void
@@ -1304,42 +1439,24 @@
 	struct utmpx ut;
 	const char *eline;
 
-	(void)memset(&ut, 0, sizeof(ut));
-	(void)strlcpy(ut.ut_name, name, sizeof(ut.ut_name));
+	(void) memset(&ut, 0, sizeof(ut));
+	(void) strlcpy(ut.ut_name, name, sizeof(ut.ut_name));
 	ut.ut_type = type;
-	(void)strlcpy(ut.ut_line, line, sizeof(ut.ut_line));
+	(void) strlcpy(ut.ut_line, line, sizeof(ut.ut_line));
 	ut.ut_pid = pid;
 	if (tv)
 		ut.ut_tv = *tv;
 	else
-		(void)gettimeofday(&ut.ut_tv, NULL);
+		(void) gettimeofday(&ut.ut_tv, NULL);
 	ut.ut_session = session;
 
 	eline = line + strlen(line);
 	if (eline - line >= sizeof(ut.ut_id))
 		line = eline - sizeof(ut.ut_id);
-	(void)strncpy(ut.ut_id, line, sizeof(ut.ut_id));
+	(void) strncpy(ut.ut_id, line, sizeof(ut.ut_id));
 
 	if (pututxline(&ut) == NULL)
-		warning("can't add utmpx record for `%s': %m", ut.ut_line);
-}
-
-static char
-get_runlevel(const state_t s)
-{
-	if (s == (state_t)single_user)
-		return SINGLE_USER;
-	if (s == (state_t)runcom)
-		return RUNCOM;
-	if (s == (state_t)read_ttys)
-		return READ_TTYS;
-	if (s == (state_t)multi_user)
-		return MULTI_USER;
-	if (s == (state_t)clean_ttys)
-		return CLEAN_TTYS;
-	if (s == (state_t)catatonia)
-		return CATATONIA;
-	return DEATH;
+		warning("can't add utmpx record for '%s': %m", ut.ut_line);
 }
 
 static void
@@ -1355,19 +1472,41 @@
 	if (sessions == NULL)
 		return;
 
-	(void)memset(&ut, 0, sizeof(ut));
-	(void)snprintf(ut.ut_line, sizeof(ut.ut_line), RUNLVL_MSG, new);
+	(void) memset(&ut, 0, sizeof(ut));
+	(void) snprintf(ut.ut_line, sizeof(ut.ut_line), RUNLVL_MSG, new);
 	ut.ut_type = RUN_LVL;
-	(void)gettimeofday(&ut.ut_tv, NULL);
+	(void) gettimeofday(&ut.ut_tv, NULL);
 	ut.ut_exit.e_exit = old;
 	ut.ut_exit.e_termination = new;
 	if (pututxline(&ut) == NULL)
 		warning("can't add utmpx record for `runlevel': %m");
 }
-#endif /* SUPPORT_UTMPX */
+# endif /* SUPPORT_UTMPX */
 
 #endif /* LETS_GET_SMALL */
 
+static char
+get_runlevel(const state_t s)
+{
+	if (s == (state_t) single_user)
+		return (SINGLE_USER);
+#ifndef LETS_GET_SMALL
+	if (s == (state_t) runcom)
+		return (RUNCOM);
+	if (s == (state_t) read_ttys)
+		return (READ_TTYS);
+	if (s == (state_t) multi_user)
+		return (MULTI_USER);
+	if (s == (state_t) clean_ttys)
+		return (CLEAN_TTYS);
+	if (s == (state_t) go_catatonic)
+		return (CATATONIA);
+	if (s == (state_t) kill_ttys)
+		return (KILL_TTYS);
+#endif
+	return (DEATH);
+}
+
 /*
  * Collect exit status for a child.
  * If an exiting login, start a new login running.
@@ -1406,9 +1545,11 @@
 	}
 
 	sp->se_process = pid;
-	(void)gettimeofday(&sp->se_started, NULL);
+	(void) gettimeofday(&sp->se_started, NULL);
 	add_session(sp);
 #endif /* LETS_GET_SMALL */
+
+	return;
 }
 
 /*
@@ -1424,13 +1565,23 @@
 		requested_transition = clean_ttys;
 		break;
 	case SIGTERM:
-		requested_transition = death;
+		requested_transition = kill_ttys;
 		break;
 	case SIGTSTP:
-		requested_transition = catatonia;
+		requested_transition = go_catatonic;
+		break;
+	case SIGUSR1:
+		requested_transition = go_single_user;
+		break;
+	case SIGUSR2:
+		requested_transition = wait_for_reboot;
+		break;
+	case SIGINT:
+		requested_transition = single_user;
 		break;
 #endif /* LETS_GET_SMALL */
 	default:
+		warning("ignoring unknown state transition [SIG%s]", strsignal(sig));	/* XXX is this safe here? */
 		requested_transition = 0;
 		break;
 	}
@@ -1447,6 +1598,8 @@
 	int status;
 	session_t *sp;
 
+	info("(re)entering multi-user state");
+
 	requested_transition = 0;
 
 	/*
@@ -1467,15 +1620,25 @@
 			break;
 		}
 		sp->se_process = pid;
-		(void)gettimeofday(&sp->se_started, NULL);
+		(void) gettimeofday(&sp->se_started, NULL);
 		add_session(sp);
 	}
 
-	while (!requested_transition)
-		if ((pid = waitpid(-1, &status, 0)) != -1)
+	/*
+	 * the value of requested_transition could be changed either by
+	 * collect_child(), if start_getty() encounters a failure from fork()
+	 * (it'll be clean_ttys); or by transition_handler(), if it is invoked
+	 * by one of the signals it has bee set to handle (in which case the
+	 * waitpid() call here will have been interrupted and will return -1)
+	 */
+	while (!requested_transition) {
+		if ((pid = waitpid(-1, &status, 0)) != -1) {
+			/* may also change requested_transition */
 			collect_child(pid, status);
+		}
+	}
 
-	return (state_func_t)requested_transition;
+	return (state_func_t) requested_transition;
 }
 
 /*
@@ -1489,10 +1652,12 @@
 	int session_index = 0;
 	int devlen;
 
+	notice("(re)loading %s", _PATH_TTYS);
+
 	for (sp = sessions; sp; sp = sp->se_next)
 		sp->se_flags &= ~SE_PRESENT;
 
-	do_setttyent();
+	(void) do_setttyent();
 
 	devlen = sizeof(_PATH_DEV) - 1;
 	while ((typ = getttyent()) != NULL) {
@@ -1505,25 +1670,29 @@
 		if (sp) {
 			sp->se_flags |= SE_PRESENT;
 			if (sp->se_index != session_index) {
-				warning("port `%s' changed utmp index from "
-				    "%d to %d", sp->se_device, sp->se_index,
-				    session_index);
+				warning("port '%s' changed utmp index from "
+					"%d to %d", sp->se_device, sp->se_index,
+					session_index);
 				sp->se_index = session_index;
 			}
 			if ((typ->ty_status & TTY_ON) == 0 ||
 			    typ->ty_getty == 0) {
 				sp->se_flags |= SE_SHUTDOWN;
-				if (sp->se_process != 0)
-					(void)kill(sp->se_process, SIGHUP);
+				if (sp->se_process != 0) {
+					info("kill -HUP %d", sp->se_process);
+					(void) kill(sp->se_process, SIGHUP);
+				}
 				continue;
 			}
 			sp->se_flags &= ~SE_SHUTDOWN;
 			if (setupargv(sp, typ) == 0) {
-				warning("can't parse getty for port `%s'",
-				    sp->se_device);
+				warning("can't parse getty for port '%s'",
+					sp->se_device);
 				sp->se_flags |= SE_SHUTDOWN;
-				if (sp->se_process != 0)
-					(void)kill(sp->se_process, SIGHUP);
+				if (sp->se_process != 0) {
+					info("kill -HUP %d", sp->se_process);
+					(void) kill(sp->se_process, SIGHUP);
+				}
 			}
 			continue;
 		}
@@ -1536,135 +1705,239 @@
 	for (sp = sessions; sp; sp = sp->se_next)
 		if ((sp->se_flags & SE_PRESENT) == 0) {
 			sp->se_flags |= SE_SHUTDOWN;
-			if (sp->se_process != 0)
-				(void)kill(sp->se_process, SIGHUP);
+			if (sp->se_process != 0) {
+				info("kill -HUP %d", sp->se_process);
+				(void) kill(sp->se_process, SIGHUP);
+			}
 		}
 
-	return (state_func_t)multi_user;
+	return (state_func_t) multi_user;
 }
 
 /*
- * Block further logins.
+ * Block further logins and return to multi_user() mode.
+ *
+ * transiting back to clean_ttys, i.e. upon receipt of a SIGHUP, will re-enable
+ * things....
+ *
+ * This state will prevent further logins on TTYs, but of course on a system
+ * offering network services it will not prevent logins via the network.
  */
 state_func_t
-catatonia(void)
+go_catatonic(void)
 {
 	session_t *sp;
 
+	notice("stopping tty respawning...");
+
 	for (sp = sessions; sp; sp = sp->se_next)
 		sp->se_flags |= SE_SHUTDOWN;
 
-	return (state_func_t)multi_user;
+	return (state_func_t) multi_user;
 }
-#endif /* LETS_GET_SMALL */
+
 
 /*
- * Note SIGALRM.
+ * Block further logins and ignore most signals (i.e. ignore most state
+ * transitions).
+ *
+ * transit to single_user() IFF SIGINT....
+ *
+ * This state is here just to avoid having a single user shell start during
+ * shutdown (e.g. when reboot(8) sends SIGTERM to every process including this
+ * one) unless a single user shell is actually requested (e.g. reboot(8) will
+ * send SIGINT if the reboot(2) call fails, for example, and we need an out to
+ * be able to give control back to the human if necessary)
+ *
+ * reboot/halt/powreoff will send init a SIGUSR2 (triggering wait_for_reboot()),
+ * and then it will send everything a kill(-1, SIGTERM) (including init which
+ * will ignore it).
+ *
+ * reboot/halt/poweroff will go on though and wait 5 seconds for everything to
+ * die cleanly, after which it will repeatedly send everything a final kill(-1,
+ * SIGKILL) until everything is good and dead (or until it gives up and warns
+ * that some things just won't die) before finally calling reboot(2).
+ *
+ * If reboot's 5-second pause is interrupted with SIGINT, or if the call to
+ * reboot(2) fails, then it will send init a SIGINT to trigger immediate
+ * creation of a single user shell.
  */
-void
-/*ARGSUSED*/
-alrm_handler(int sig)
+state_func_t
+wait_for_reboot(void)
 {
+	pid_t pid;
+	int status;
+	session_t *sp;
 
-	clang = 1;
+	notice("stopping tty respawning and waiting for halt/reboot/poweroff (or SIGINT)...");
+
+	for (sp = sessions; sp; sp = sp->se_next)
+		sp->se_flags |= SE_SHUTDOWN;
+
+# ifdef SUPPORT_UTMPX
+	logwtmpx("~", "halt", "", 0, INIT_PROCESS);
+# endif
+# ifdef SUPPORT_UTMP
+	logwtmp("~", "halt", "");
+# endif
+
+	while (requested_transition != single_user) { /* i.e. ignore all but SIGINT */
+		if ((pid = waitpid(-1, &status, 0)) != -1)
+			collect_child(pid, status);
+	}
+
+	return (state_func_t) requested_transition;
 }
 
-#ifndef LETS_GET_SMALL
+
 /*
- * Bring the system down to single user.
+ * stop respawns, block SIGTERM and kill everything off
+ *
+ * restore SIGTERM and transit to kill_ttys() when done
  */
 state_func_t
-death(void)
+go_single_user(void)
 {
 	session_t *sp;
-	int i, status;
-	pid_t pid;
-	static const int death_sigs[3] = { SIGHUP, SIGTERM, SIGKILL };
+	struct sigaction sa, saterm;
 
+	notice("single user mode transition initiated...");
+
+	/*
+	 * First we make double sure we don't respawn anything!  (we should
+	 * already have transited through go_catatonic(), but not if someone
+	 * happens to trigger this state directly.
+	 */
 	for (sp = sessions; sp; sp = sp->se_next)
 		sp->se_flags |= SE_SHUTDOWN;
 
-	/* NB: should send a message to the session logger to avoid blocking. */
-#ifdef SUPPORT_UTMPX
+# ifdef SUPPORT_UTMPX
 	logwtmpx("~", "shutdown", "", 0, INIT_PROCESS);
-#endif
-#ifdef SUPPORT_UTMP
+# endif
+# ifdef SUPPORT_UTMP
 	logwtmp("~", "shutdown", "");
-#endif
+# endif
 
-	for (i = 0; i < 3; ++i) {
-		if (kill(-1, death_sigs[i]) == -1 && errno == ESRCH)
-			return (state_func_t)single_user;
-
-		clang = 0;
-		alarm(DEATH_WATCH);
-		do
-			if ((pid = waitpid(-1, &status, 0)) != -1)
-				collect_child(pid, status);
-		while (clang == 0 && errno != ECHILD);
+	/*
+	 * We'll get our own SIGTERM if we don't ignore it, and we don't want
+	 * to go to kill_ttys() prematurely!
+	 */
+	(void) sigemptyset(&sa.sa_mask);
+	sa.sa_flags = 0;
+	sa.sa_handler = SIG_IGN;
+	(void) sigaction(SIGTERM, &sa, &saterm);
 
-		if (errno == ECHILD)
-			return (state_func_t)single_user;
+	/* Send a SIGTERM to everything! */
+	if (kill(-1, SIGTERM) == -1) {
+		/*
+		 * If ESRCH, everything's OK: we're the only non-system
+		 * process left!
+		 */
+		if (errno != ESRCH) {
+			warn("sending SIGTERM to all processes failed, starting single user shell");
+			return (state_func_t) single_user;
+		}
 	}
+	(void) sigaction(SIGTERM, &saterm, NULL);
 
-	warning("some processes would not die; ps axl advised");
-
-	return (state_func_t)single_user;
+	return (state_func_t) kill_ttys;
 }
 #endif /* LETS_GET_SMALL */
 
-#ifdef MFS_DEV_IF_NO_CONSOLE
+/*
+ * Note SIGALRM.
+ */
+void
+/*ARGSUSED*/
+alrm_handler(int sig)
+{
 
-static void
-mapfile(struct mappedfile *mf)
+	clang = 1;
+}
+
+#ifndef LETS_GET_SMALL
+/*
+ * kill all our own directly spawned sessions and go on to single_user().
+ *
+ * This code may help shut down TTY sessions more cleanly (with a proper
+ * SIGHUP) that have for some reason ignored SIGTERM.
+ */
+state_func_t
+kill_ttys(void)
 {
-	int fd;
-	struct stat st;
+	session_t *sp;
 
-	if (lstat(mf->path, &st) == -1)
-		return;
+	/* NB: should send a message to the session logger to avoid blocking. */
+# ifdef SUPPORT_UTMPX
+	logwtmpx("~", "shutdown", "", 0, INIT_PROCESS);
+# endif
+# ifdef SUPPORT_UTMP
+	logwtmp("~", "shutdown", "");
+# endif
 
-	if ((st.st_mode & S_IFMT) == S_IFLNK) {
-		mf->buf = malloc(st.st_size + 1);
-		if (mf->buf == NULL)
-			return;
-		mf->buf[st.st_size] = 0;
-		if (readlink(mf->path, mf->buf, st.st_size) != st.st_size)
-			return;
-		mf->len = -1;
-		return;
+	/*
+	 * First we make double sure we don't respawn anything!  Note that if
+	 * we don't do this first then collect_child() will respawn any entry
+	 * not turned 'off'
+	 *
+	 * This may already have been done by go_catatonic() of course...
+	 */
+	for (sp = sessions; sp; sp = sp->se_next)
+		sp->se_flags |= SE_SHUTDOWN;
+
+	notice("shutting down any remaining sessions, please be patient for %d seconds...",
+	       DEATH_WATCH);
+
+	/*
+	 * try to put them down easy....
+	 *
+	 * NOTE:  These are TTY sessions, not daemons, so SIGHUP is indeed the
+	 * right way to tell them to quit.
+	 */
+	for (sp = sessions; sp; sp = sp->se_next) {
+		if (sp->se_process) {
+			info("kill -HUP %d", sp->se_process);
+			kill(sp->se_process, SIGHUP);
+		}
 	}
+	clang = 0;
+	alarm(DEATH_WATCH);
+	do {
+		int pid;
+		int status;
 
-	if ((fd = open(mf->path, O_RDONLY)) == -1)
-		return;
-	mf->buf = mmap(0, (size_t)st.st_size, PROT_READ,
-			MAP_FILE|MAP_SHARED, fd, (off_t)0);
-	(void)close(fd);
-	if (mf->buf == MAP_FAILED)
-		return;
-	mf->len = st.st_size;
-}
+		errno = 0;
+		if ((pid = waitpid(-1, &status, 0)) != -1)
+			collect_child(pid, status);
+		else if (errno == ECHILD) {
+			/* 
+			 * nobody left but us!!!
+			 */
+			alarm(0);
+			return (state_func_t) single_user;
+		} else if (errno != EINTR)
+			warning("waitpid(-1) failed: %m");
+	} while (clang == 0);
 
-static void
-writefile(struct mappedfile *mf)
-{
-	int fd;
+	info("killing off some remaining sessions...");
 
-	if (mf->len == -1) {
-		symlink(mf->buf, mf->path);
-		free(mf->buf);
-		return;
-	}
+	/* put any remaining down hard.... */
+	for (sp = sessions; sp; sp = sp->se_next) {
+		if (sp->se_process) {
+			info("kill -KILL %d", sp->se_process);
+			kill(sp->se_process, SIGKILL);
+			sp->se_process = 0;
+		}
+		/* don't bother to reap them */
+ 	}
 
-	if (mf->len == 0)
-		return;
-	fd = open(mf->path, O_WRONLY | O_CREAT | O_TRUNC, 0755);
-	if (fd == -1)
-		return;
-	(void)write(fd, mf->buf, mf->len);
-	(void)munmap(mf->buf, mf->len);
-	(void)close(fd);
+	/* ... and keep them down. */
+
+	return (state_func_t) single_user;
 }
+#endif /* LETS_GET_SMALL */
+
+#ifdef MFS_DEV_IF_NO_CONSOLE
 
 static int
 mfs_dev(void)
@@ -1676,32 +1949,34 @@
 	int status;
 	dev_t dev;
 	char *fs_size;
-#ifdef CPU_CONSDEV
+# if defined(CTL_MACHDEP) && defined(CPU_CONSDEV)
 	static int name[2] = { CTL_MACHDEP, CPU_CONSDEV };
 	size_t olen;
-#endif
+# endif
 
 	/* If we have /dev/console, assume all is OK  */
-	if (access(_PATH_CONSOLE, F_OK) != -1)
+	if (access(_PATH_CONSOLE, F_OK) != -1) /* XXX && access(_PATH_CONSTTY, F_OK) != -1 */
 		return(0);
 
-	/* Grab the contents of MAKEDEV */
-	mapfile(&mfile[0]);
-
-	/* Grab the contents of MAKEDEV.local */
-	mapfile(&mfile[1]);
+	warning("Could not find /dev/console, mounting an MFS on /dev and running %s...", _PATH_MAKEDEV);
 
 	/* Mount an mfs over /dev so we can create devices */
 	switch ((pid = fork())) {
 	case 0:
+		(void) dup2(STDERR_FILENO, STDOUT_FILENO); /* Give it stdout, if possible */
+
 		asprintf(&fs_size, "%d", FSSIZE);
-		if (fs_size == NULL)
+		if (fs_size == NULL) {
+			warning("Error formatting blocks string for mount_mfs params: %m");
 			return(-1);
-		(void)execl(INIT_MOUNT_MFS, "mount_mfs",
-		    "-b", "4096", "-f", "512",
-		    "-s", fs_size, "-n", STR(NINODE),
-		    "-p", "0755",
-		    "swap", "/dev", NULL);
+		}
+		info("Creating an MFS /dev (%s blocks)", fs_size);
+		(void) execl(INIT_MOUNT_MFS, "mount_mfs",
+			     "-b", "4096", "-f", "512",
+			     "-s", fs_size,
+			     "-i", "60",
+			     "-p", "0755",
+			     "swap", "/dev", NULL);
 		_exit(1);
 		/*NOTREACHED*/
 
@@ -1716,35 +1991,38 @@
 		break;
 	}
 
-#ifdef CPU_CONSDEV
+# if defined(CTL_MACHDEP) && defined(CPU_CONSDEV)
 	olen = sizeof(dev);
 	if (sysctl(name, sizeof(name) / sizeof(name[0]), &dev, &olen,
 	    NULL, 0) == -1)
-#endif
+# endif
 		dev = makedev(0, 0);
 
-	/* Make a console for us, so we can see things happening */
+	/* Make /dev/console and /dev/constty, so that the user can see what's happening */
 	if (mknod(_PATH_CONSOLE, 0666 | S_IFCHR, dev) == -1)
 		return(-1);
 
-	(void)freopen(_PATH_CONSOLE, "a", stderr);
-
-	warnx("Creating mfs /dev (%d blocks, %d inodes)", FSSIZE, NINODE);
-
-	/* Create a MAKEDEV script in the mfs /dev */
-	writefile(&mfile[0]);
+	dev = makedev(0, 1);		/* /dev/constty */
+	if (mknod(_PATH_CONSTTY, 0666 | S_IFCHR, dev) == -1)
+		return(-1);
 
-	/* Create a MAKEDEV.local script in the mfs /dev */
-	writefile(&mfile[1]);
+	(void) freopen(_PATH_CONSOLE, "a", stderr);
 
 	/* Run the makedev script to create devices */
 	switch ((pid = fork())) {
 	case 0:
-		dup2(2, 1);	/* Give the script stdout */
-		if (chdir("/dev") == 0)
-			(void)execl(INIT_BSHELL, "sh",
-			    mfile[0].len ? "./MAKEDEV" : "/etc/MAKEDEV",
-			    "init", NULL); 
+		(void) dup2(STDERR_FILENO, STDOUT_FILENO); /* Give it stdout */
+		/* XXX what about STDIN too!?!?!?!?!?!?!?!?!?!?!? */
+		/* i.e. call login_tty(STDIN_FILENO); */
+		if (chdir("/dev") != 0) {
+			warning("Unable to 'chdir(/dev)' to run %s: %m", _PATH_MAKEDEV);
+			return (-1);
+		}		
+		info("Running '%s %s all' in the new MFS /dev...", INIT_BSHELL, _PATH_MAKEDEV);
+		(void) execl(INIT_BSHELL, "sh",
+			     _PATH_MAKEDEV,
+			     "all",
+			     (char *) NULL); 
 		_exit(1);
 		/* NOTREACHED */
 
@@ -1758,12 +2036,12 @@
 			errno = EINVAL;
 			break;
 		}
-		return 0;
+		return (0);
 	}
-	warn("Unable to run MAKEDEV");
+	warning("Unable to run '%s %s all': %m", INIT_BSHELL, _PATH_MAKEDEV);
 	return (-1);
 }
-#endif
+#endif /* MFS_DEV_IF_NO_CONSOLE */
 
 int
 do_setttyent(void)
@@ -1775,13 +2053,13 @@
 
 		snprintf(path, sizeof(path), "%s/%s", rootdir, _PATH_TTYS);
 
-		return setttyentpath(path);
+		return (setttyentpath(path));
 	} else
 #endif /* CHROOT */
-		return setttyent();
+		return (setttyent());
 }
 
-#if !defined(LETS_GET_SMALL) && defined(CHROOT)
+#if defined(CHROOT)
 
 int
 createsysctlnode()
@@ -1852,11 +2130,11 @@
 				node.sysctl_num = mib;
 				mib = CTL_DESTROY;
 
-				(void)sysctl(&mib, 1, NULL, NULL, &node,
-				    sizeof(node));
+				(void) sysctl(&mib, 1, NULL, NULL, &node,
+					      sizeof(node));
 			}
 
-			createsysctlnode();
+			(void) createsysctlnode();
 		}
 
 		/* We certainly won't chroot. */
@@ -1874,4 +2152,4 @@
 	return (1);
 }
 
-#endif /* !LETS_GET_SMALL && CHROOT */
+#endif /* CHROOT */
Index: sbin/init/init.8
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/init/init.8,v
retrieving revision 1.42
diff -u -r1.42 init.8
--- sbin/init/init.8	15 Sep 2006 20:20:06 -0000	1.42
+++ sbin/init/init.8	15 Jun 2007 22:18:15 -0000
@@ -32,7 +32,7 @@
 .\"
 .\"     @(#)init.8	8.6 (Berkeley) 5/26/95
 .\"
-.Dd September 15, 2006
+.Dd June 13, 2007
 .Dt INIT 8
 .Os
 .Sh NAME
@@ -43,26 +43,65 @@
 .Sh DESCRIPTION
 The
 .Nm
-program is the last stage of the boot process (after the kernel loads
+program forms the last stage of the boot process (after the kernel loads
 and initializes all the devices).
-It normally begins multi-user operation.
+It is started by the kernel as the first user-level process and always
+has a PID of one (1).
+If it dies unexpectedly the kernel will panic and reboot.
+During a normal administrator-initiated system reboot or halt it is told
+to ignore all future signals and then sit and twiddle its thumbs until
+the kernel reboots or halts or the machine is powered off.
+.Pp
+.Nm
+is essentially a specialized finite state machine.
+The following table describes the state machine it implements:
+.\" Note: do not use -enum -- we need to start at #0 and have explicit references
+.Bl -tag -width 00.
+.It 0.
+Startup
+.Pq Fn main .
+Given the
+.Fl s
+flag, we start at state 1; otherwise we start at state 2.
+.It 1.
+Single-user mode, state
+.Sq s
+.Pq Fn single_user .
+The system is then quiescent for maintenance work with usually only the
+single-user shell process running.
+The system may be made to go to state 2 (normal boot up to multi-user
+mode) by exiting the single-user shell normally.
+If the shell is killed with
+.Dv SIGKILL
+then
+.Nm
+will stop doing anything, ignore further signals, and wait for the
+kernel to halt or reboot.
+If the shell is killed by any other signal it will be restarted by
+re-entering state 1.
 .Pp
-The following table describes the state machine used by
-.Nm :
-.Bl -enum
-.It
-Single user shell.
+If the
+.Sq console
+or
+.Sq constty
+line in the
+.Xr ttys 5
+file is marked
+.Dq insecure ,
+then
 .Nm
-may be passed
-.Fl s
-from the boot program to prevent the system from going multi-user and
-to instead execute a single user shell without starting the normal
-daemons.
-The system is then quiescent for maintenance work and may
-later be made to go to state 2 (multi-user) by exiting the single-user
-shell (with ^D).
-.It
-Multi-user boot (default operation).
+will require that the superuser password be entered before the system
+will start a single-user shell.
+The password check is skipped if the
+.Sq constty
+or
+.Sq console
+line is marked as
+.Dq secure .
+.It 2.
+Normal boot-up to multi-user mode, state
+.Sq r
+.Pq Fn runcom .
 Executes
 .Pa /etc/rc
 (see
@@ -74,9 +113,12 @@
 .Sq autoboot .
 If
 .Pa /etc/rc
-exits with a non-zero (error) exit code, commence single user
-operation by giving the super-user a shell on the console by going
-to state 1 (single user).
+exits with a non-zero (error) exit code, go to state 1 (single user).
+If
+.Pa /etc/rc
+sends
+.Dv SIGTERM
+then turn off signal handlers and wait for the kernel to halt or reboot.
 Otherwise, proceed to state 3.
 .Pp
 If value of the
@@ -87,66 +129,130 @@
 .Pa /etc/rc
 process will be run inside a
 .Xr chroot 2
-indicated by sysctl with the same error handling as above.
-.It
+indicated by
+.Xr sysctl 8
+with the same error handling as above.
+.It 3.
 Set up ttys as specified in
-.Xr ttys 5 .
+.Xr ttys 5 ,
+state
+.Sq t
+.Pq Fn read_ttys .
 See below for more information.
 On completion, continue to state 4.
-If we did chroot in state 2, each
+.Pp
+If we did
+.Xr chroot 2
+in state 2, each
 .Xr getty 8
 process will be run in the same
 .Xr chroot 2
-path as in 2 (that is, the value of
+path as was used in state 2 (that is, the value of
 .Dq init.root
-sysctl is not re-read).
-.It
-Multi-user operation.
-Depending upon the signal received, change state appropriately;
-on
-.Dv SIGTERM ,
-go to state 7;
-on
-.Dv SIGHUP ,
-go to state 5;
-on
-.Dv SIGTSTP ,
-go to state 6.
-.It
-Clean-up mode; re-read
+.Xr sysctl 8
+is not re-read).
+.It 4.
+Multi-user operation, state
+.Sq m
+.Pq Fn multi_user .
+First scan the session table looking for lines without processes and
+attempt to start them.
+Next enter a loop waiting for any process to die or a signal to be
+received.
+.Pp
+When a process dies, collect and and if it was one of the active
+line sessions then record its status then attempt to restart it.
+Certain errors from restart failures will transition to state 5.
+.Pp
+Otherwise, depending upon the signal received, change state
+appropriately:
+.Pp
+. Bl -tag -compact -width SIGTERM
+. It Dv SIGHUP
+go to state 5
+.Pq Fn clean_ttys ;
+. It Dv SIGINT
+go to state 1
+.Pq Fn single_user ;
+. It Dv SIGTSTP
+go to state 6
+.Pq Fn go_catatonic ;
+. It Dv SIGUSR1
+go to state 8
+.Pq Fn go_single_user ;
+. It Dv SIGTERM
+go to state 7
+.Pq Fn kill_ttys ;
+. El
+.It 5.
+Clean-up mode, state
+.Sq T
+.Pq Fn clean_ttys .
+Re-read
 .Xr ttys 5 ,
 killing off the controlling processes on lines that are now
 .Sq off ,
 and starting processes that are newly
 .Sq on .
 On completion, go to state 4.
-.It
+.It 6.
 .Sq Boring
-mode; no new sessions.
+mode, state
+.Sq c
+.Pq Fn go_catatonic .
+Prevent spawning of new sessions, then go (back to) state 4.
 Signals as per state 4.
-.It
-Shutdown mode.
+.Pp
+This is the first stop performed by
+.Xr halt 8 ,
+.Xr poweroff 8 ,
+or
+.Xr reboot 8 .
+The next step will be to send
+.Dv SIGTERM
+to all processes
+.Pq Va pid = -1
+at which point
+.Nm
+will be sent to state 7.
+.It 7.
+Shutdown mode, state
+.Sq k
+.Pq Fn kill_ttys .
+Internally mark all lines as shut down, again just to be sure.
 Send
 .Dv SIGHUP
-to all controlling processes, reap the processes for 30 seconds,
-and then go to state 1 (single user); warning if not all the processes died.
+to all controlling processes, reap the processes for 30 seconds, and
+then go to state 1.
+.It 8.
+Single-user transition mode
+.Pq Fn go_single_user .
+Internally mark all lines as shut down, and log single-user state
+transition in
+.Xr wtmp 5
+and
+.Xr wtmpx 5
+files.
+Then send every process a
+.Dv SIGTERM
+On completion, go to state 7 for final cleanup.
+.It 9.
+Reboot/halt/poweroff transition mode
+.Pq Fn stay_catatonic .
+Internally mark all lines as shut down, and log the shutdown state
+transition in
+.Xr wtmp 5
+and
+.Xr wtmpx 5
+files.
+Then wait for the kernel to halt or reboot, or the machine to be powered
+off (hopefully by the kernel); or in the case of an emergency (e.g. if the
+.Xr reboot 2
+call fails), a
+.Dv SIGINT
+in which case we go to state 1 to start a single user shell.
 .El
 .Pp
-If the
-.Sq console
-entry in the
-.Xr ttys 5
-file is marked
-.Dq insecure ,
-then
-.Nm
-will require that the superuser password be
-entered before the system will start a single-user shell.
-The password check is skipped if the
-.Sq console
-is marked as
-.Dq secure .
-.Pp
 It should be noted that while
 .Nm
 has the ability to start multi-user operation inside a
@@ -187,9 +293,13 @@
 .Nm
 program wakes up, deletes the user from the
 .Xr utmp 5
-file of current users and records the logout in the
+and
+.Xr utmpx 5
+files of current users and records the logout in the
 .Xr wtmp 5
-file.
+and
+.Xr wtmpx 5
+files.
 The cycle is
 then restarted by
 .Nm
@@ -247,39 +357,56 @@
 .Nm
 will terminate multi-user operations and resume single-user mode
 if sent a terminate
-.Pq Dv TERM
+.Pq Dv SIGUSR1
 signal, for example,
-.Dq Li "kill \-s TERM 1" .
+.Dq Li "kill \-s USR1 1" .
 If there are processes outstanding that are deadlocked (because of
 hardware or software failure),
 .Nm
 will not wait for them all to die (which might take forever), but
 will time out after 30 seconds and print a warning message.
+This feature is used by
+.Xr shutdown 8 .
 .Pp
 .Nm
 will cease creating new
 .Xr getty 8 Ns 's
 and allow the system to slowly die away, if it is sent a terminal stop
-.Pq Dv TSTP
+.Pq Dv SIGTSTP
 signal, i.e.
 .Dq Li "kill \-s TSTP 1" .
-A later hangup will resume full
-multi-user operations, or a terminate will start a single user shell.
-This hook is used by
-.Xr reboot 8
+A later
+.Dv SIGHUP
+will resume full multi-user operations; a
+.Dv SIGTERM
+will cause
+.Nm
+to send a
+.Dv SIGHUP
+to the controlling process for each line and then start a single user
+shell; and a
+.Dv SIGUSR1
+will do a full shutdown as above; while a
+.Dv SIGINT
+will immediately start a single user shell.
+This feature is used by
+.Xr halt 8 ,
+.Xr poweroff 8 ,
 and
-.Xr halt 8 .
+.Xr reboot 8 .
 .Pp
 The role of
 .Nm
-is so critical that if it dies, the system will reboot itself
-automatically.
+is so critical that if it dies, the system will panic with the message
+.Dq panic: init died (signal %d, exit %d) .
 If, at bootstrap time, the
 .Nm
-process cannot be located, the system will panic with the message
-.Dq panic: init died (signal %d, exit %d) .
+process cannot be started, the system will panic with the message
+.Dq panic: no init .
 .Pp
 If
+.Pa /dev/constty
+and
 .Pa /dev/console
 does not exist,
 .Nm
@@ -305,15 +432,19 @@
 This creates the standard devices considered necessary to boot the
 system.
 .Sh FILES
-.Bl -tag -width /var/log/wtmp -compact
+.Bl -tag -width /var/log/wtmpx -compact
 .It Pa /dev/console
-System console device.
+System hardware console device.
+.It Pa /dev/constty
+Virtual console device.
 .It Pa /dev/tty*
 Terminal ports found in
 .Xr ttys 5 .
 .It Pa /var/run/utmp
+.It Pa /var/run/utmpx
 Record of Current users on the system.
 .It Pa /var/log/wtmp
+.It Pa /var/log/wtmpx
 Record of all logins and logouts.
 .It Pa /etc/ttys
 The terminal initialization information file.
@@ -322,30 +453,28 @@
 .El
 .Sh DIAGNOSTICS
 .Bl -diag
-.It "getty repeating too quickly on port %s, sleeping"
+.It "getty repeating too quickly on port `%s', sleeping"
 A process being started to service a line is exiting quickly
 each time it is started.
 This is often caused by a ringing or noisy terminal line.
 .Em "Init will sleep for 10 seconds" ,
 .Em "then continue trying to start the process" .
-.Pp
-.It "some processes would not die; ps axl advised."
-A process is hung and could not be killed when the system was
-shutting down.
-This condition is usually caused by a process that is stuck in a
-device driver because of a persistent device error condition.
 .El
 .Sh SEE ALSO
 .Xr config 1 ,
 .Xr kill 1 ,
 .Xr login 1 ,
 .Xr sh 1 ,
+.Xr logwtmp 3 ,
 .Xr options 4 ,
 .Xr ttys 5 ,
+.Xr utmp 5 ,
+.Xr utmpx 5 ,
 .Xr MAKEDEV 8 ,
 .Xr getty 8 ,
 .Xr halt 8 ,
-.Xr mfs 8 ,
+.Xr mount_mfs 8 ,
+.Xr poweroff 8 ,
 .Xr rc 8 ,
 .Xr reboot 8 ,
 .Xr shutdown 8 ,
Index: sbin/init/NOTES
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/init/NOTES,v
retrieving revision 1.3
diff -u -r1.3 NOTES
--- sbin/init/NOTES	18 Apr 2006 11:40:26 -0000	1.3
+++ sbin/init/NOTES	24 Jun 2007 17:17:50 -0000
@@ -9,8 +9,10 @@
 
 	This is part of the extensive 'job control' glossary entry.
 	This specific reference says that 'init' must by default provide
-	protection from job control signals to jobs it starts --
-	it sets SIGTSTP, SIGTTIN and SIGTTOU to SIG_IGN.
+	protection from job control signals to jobs it starts -- it sets
+	SIGTSTP, SIGTTIN and SIGTTOU to SIG_IGN.  We use SIGTSTP as the
+	logical choice (by connotation) to initiate "boring" mode so we
+	don't ignore it.
 
 B.2.2.2, p206 line 889:
 
@@ -82,38 +84,70 @@
 Design notes:
 ------------
 
-your generic finite state machine
-we are fascist about which signals we elect to receive,
-	even signals purportedly generated by hardware
-handle fatal errors gracefully if possible (we reboot if we goof!!)
-	if we get a segmentation fault etc., print a message on the console
-	and spin for a while before rebooting
-	(this at least decreases the amount of paper consumed :-)
-apply hysteresis to rapidly exiting gettys
-check wait status of children we reap
-	don't wait for stopped children
-don't use SIGCHILD, it's too expensive
-	but it may close windows and avoid races, sigh
-look for EINTR in case we need to change state
-init is responsible for utmp and wtmp maintenance (ick)
-	maybe now we can consider replacements?  maintain them in parallel
-	init only removes utmp and closes out wtmp entries...
-
-necessary states and state transitions (gleaned from the man page):
-	1: single user shell (with password checking?); on exit, go to 2
-	2: run rc script, on exit 0 check if init.root sysctl != "/", if it
-           differs then fork + chroot into the value of init.root and run
-           /etc/rc inside the chroot: on exit 0, go to 3; on exit N (error),
-           go to 1 (applies also to /etc/rc when init.root == "/")
-	3: read ttys file: on completion, go to 4.  If we did chroot in
-	   state 2, we chroot after forking each getty to the same dir
-	   (init.root is not re-read)
-	4: multi-user operation: on SIGTERM, go to 7; on SIGHUP, go to 5;
-		on SIGTSTP, go to 6
-	5: clean up mode (re-read ttys file, killing off controlling processes
-		on lines that are now 'off', starting them on lines newly 'on')
-		on completion, go to 4
-	6: boring mode (no new sessions); signals as in 4
-	7: death: send SIGHUP to all controlling processes, reap for 30 seconds,
-		then go to 1 (warn if not all processes died, i.e. wait blocks)
-Given the -s flag, we start at state 1; otherwise state 2
+Init is your generic finite state machine.  See the manual page for
+a complete state table with descriptions of behaviour and of the state
+transition triggers.
+
+In an ideal world init would be started with stdin/stdout/stderr
+attached to whatever the kernel found as the active console device, and
+it would be our controlling terminal so that we could receive SIGINT
+from it.  However in a the current world we must explicitly
+open("/dev/console").  We make sure to first call revoke("/dev/console")
+first though.  (There'd need to be an fdrevoke() to eliminate the hard
+pathname.)
+
+We are fascist about which signals we elect to receive, even signals
+purportedly generated by hardware.  We either catch or block signals
+rather than ignore them, so that they get reset on exec.
+
+We handle fatal errors gracefully if possible (the kernel will reboot if
+we goof!!).  If we receive any fatal error signal etc., or encounter
+errors from bad system calls, we print a message on stderr and then we
+pause for a while before rebooting (this at least decreases the amount
+of paper consumed on a printing console by constant reboots :-) ).
+
+We apply hysteresis controls to quell rapidly exiting gettys.
+
+We check the wait() status of the child processes we reap.
+
+We don't use SIGCHILD, it's too expensive (but it may close windows of
+contention and avoid some kinds of race conditions, sigh)
+
+Look for EINTR from blocking calls in case we need to change state.
+
+init is responsible for utmp[x] and wtmp[x] maintenance
+
+Note that init cannot call reboot(2) directly because there's no safe
+and easy way for 'reboot' to reliably pass the rebootstr to init, so
+most process killing, and calling of reboot(2) is all done by one of
+reboot, halt, or poweroff.
+
+For dropping down to single-user mode shutdown signal init with SIGUSR1
+to trigger a single-user mode transition and init will stop spawning
+children and then send SIGHUP to the TTY sessions that it is responsible
+for starting; while in parallel shutdown will continue to send SIGTERM
+to all processes in order to cleanly shut down all other services and
+daemons.
+
+So shutdown runs /etc/rc.shutdown and then, if it was given '-p|-r|-h',
+it first signals init with SIGUSR2 to wait for a reboot, then it execs
+poweroff/reboot/halt as appropriate.  Reboot goes on a killing spree
+with kill(-1, SIGTERM), pauses, and then kills everything again with
+kill(-1, SIGKILL); and finally it will call reboot(2) as appropriate.
+NOTE: init ignores SIGTERM while waiting for system reboot, but does not
+ignore SIGINT, and if reboot is interrupted during its pause it will
+send a SIGINT to init thus triggering immediate invocation of a
+single-user shell.
+
+If shudown is _not_ given '-p|-r|-h' (i.e. it is just to initiate
+shutdown to single user mode) then it will simply send init a SIGUSR1
+(to trigger it into shutting down and killing off all TTY sessions and
+then create a single-user shell) and then it will send a SIGTERM to all
+processes and finally it will exit itself.
+
+shutdown/poweroff/reboot/halt MUST NOT use SIGHUP at all during their
+killing spree since that signal is often used to signify "config reload"
+to various long-running daemon processes -- SIGHUP is only valid to TTY
+session process groups and only init and the TTY driver should normally
+send SIGHUP to just the processes they control.
+
Index: sbin/init/Makefile
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/sbin/init/Makefile,v
retrieving revision 1.36
diff -u -r1.36 Makefile
--- sbin/init/Makefile	18 Apr 2006 11:40:26 -0000	1.36
+++ sbin/init/Makefile	13 Jun 2007 01:29:31 -0000
@@ -3,16 +3,35 @@
 
 PROG=	init
 MAN=	init.8
+#INSTALLFLAGS=-fschg
 DPADD=	${LIBUTIL}
 LDADD=	-lutil
-CPPFLAGS+=	-DMFS_DEV_IF_NO_CONSOLE -DSUPPORT_UTMP -DSUPPORT_UTMPX
+CPPFLAGS+=	-DMFS_DEV_IF_NO_CONSOLE
 
 .ifdef	SMALLPROG
 CPPFLAGS+=	-DLETS_GET_SMALL
 .else
-CPPFLAGS+=	-DALTSHELL -DSECURE -DCHROOT
+
+# Enable "Enter pathname of shell or RETURN for sh: " prompt in single
+# user mode
+#CPPFLAGS+=	-DALTSHELL
+
+# Require the root password before entering single-user mode if
+# console is "insecure"
+CPPFLAGS+=	-DSECURE
 DPADD+=		${LIBCRYPT}
 LDADD+=		-lcrypt
+
+# support full chroot() environments
+#
+CPPFLAGS+=	-DCHROOT
+
+# support session logging
+#
+CPPFLAGS+=	-DSUPPORT_UTMP -DSUPPORT_UTMPX
+
 .endif
 
 .include <bsd.prog.mk>
+
+LDSTATIC=-static
Index: lib/libutil/login_tty.c
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/lib/libutil/login_tty.c,v
retrieving revision 1.11
diff -u -r1.11 login_tty.c
--- lib/libutil/login_tty.c	7 Aug 2003 16:44:59 -0000	1.11
+++ lib/libutil/login_tty.c	12 Jun 2007 18:07:52 -0000
@@ -53,12 +53,18 @@
 	_DIAGASSERT(fd != -1);
 
 	(void) setsid();
-	if (ioctl(fd, TIOCSCTTY, (char *)NULL) == -1)
+
+	if (ioctl(fd, TIOCSCTTY, (char *) NULL) == -1)
 		return (-1);
-	(void) dup2(fd, 0);
-	(void) dup2(fd, 1);
-	(void) dup2(fd, 2);
+
+	if (fd != STDIN_FILENO)
+		(void) dup2(fd, STDIN_FILENO);
+	if (fd != STDOUT_FILENO)
+		(void) dup2(fd, STDOUT_FILENO);
+	if (fd != STDERR_FILENO)
+		(void) dup2(fd, STDERR_FILENO);
 	if (fd > STDERR_FILENO)
 		(void) close(fd);
+
 	return (0);
 }
Index: lib/libc/sys/dup.2
===================================================================
RCS file: /cvs/master/m-NetBSD/main/src/lib/libc/sys/dup.2,v
retrieving revision 1.18
diff -u -r1.18 dup.2
--- lib/libc/sys/dup.2	13 May 2004 10:20:58 -0000	1.18
+++ lib/libc/sys/dup.2	18 Feb 2007 18:10:27 -0000
@@ -91,10 +91,14 @@
 the value of the new descriptor
 .Fa newd
 is specified.
-If this descriptor is already
-in use, the descriptor is first deallocated as if a
+If the new descriptor number is the same as
+.Fa oldd
+then the call returns without doing anything but a few sanity checks on
+the descriptor.
+If the new descriptor is already in use, it is first deallocated as if
+it had been passed to
 .Xr close 2
-call had been done first.
+first.
 .Sh RETURN VALUES
 The value \-1 is returned if an error occurs in either call.
 The external variable

>Unformatted: