Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 5 Jan 2007 22:06:00 +0100 (CET)
From:      Oliver Fromme <olli@lurza.secnetix.de>
To:        jhb@freebsd.org (John Baldwin)
Cc:        erik.udo@gmail.com, freebsd-hackers@freebsd.org, dougb@freebsd.org
Subject:   Re: Init.c, making it chroot
Message-ID:  <200701052106.l05L60a6042599@lurza.secnetix.de>
In-Reply-To: <200701041459.18321.jhb@freebsd.org>

next in thread | previous in thread | raw e-mail | index | archive | help

John Baldwin wrote:
 > Oliver Fromme wrote:
 > > John Baldwin wrote:
 > > >   just pass the string literal to the kenv() function, e.g.
 > > 
 > > In fact that's what I tried first ...  Alas:
 > > warning: passing arg 2 of `kenv' discards qualifiers from pointer target type
 > 
 > It's fixed in HEAD, I'll MFC the prototype fix for kenv() to 6.x.

Thankyou!

That const stuff really drives me crazy.  I still need the
ugly __DECONST macro to assign the script name to argv[2]
for the execv() call.  :-(

 > > Of course it is possible to write an additional function
 > > run_script(char *script) which contains runcom's current
 > > code, and make the runcom() function a wrapper that just
 > > calls run_script(_PATH_RUNCOM).
 > > [...]
 > 
 > That sounds great.

I have updated the patch and included your suggestions.
Also, a bug has been fixed:  The -s option was ignored if
an init_script was executed that terminated successfully
(exit code 0).  That's the one case that I didn't test
earlier.  :-)  To fix it, I needed to introduce a new
variable for the initial state transition.

By the way, I'm wondering why the requested_transition
variable is not declared "volatile".  It is modified
from a signal handler, so it should be declared volatile
in order to prevent the compiler from optimizing
certain access patterns.  Is there a reason why it
isn't declared that way, or have we just been lucky
that it didn't break?

While I was there, I added a third kenv variable called
"init_shell".  If it is set, it is used as the shell
executable for running scripts, instead of the default
/bin/sh.  I think that's useful, because when using the
init_script and init_chroot features, it might make
sense to use a shell binary (maybe statically linked)
that's located at a special place inside the chroot.
After all, the point of allowing chrooted systems is
to share the file system with other systems, so it's
better to keep as few files as possible outside the
chroot.  On my test ISO -- which uses all of the new
features -- the root directory only contains /boot,
/dev and /ochroot.

As usual, the new ISO is here (17.5 MB):
http://www.secnetix.de/tmp/init_chroot/

(BTW, when you boot it to multi-user mode, you can log in
as "root" without password.)

/boot/loader.conf contains these lines:

   init_path="/ochroot/sbin/init"
   init_shell="/ochroot/static/sh"
   init_script="/ochroot/etc/rc.init"
   init_chroot="/ochroot"

That means that init(8) runs /ochroot/etc/rc.init first,
using /ochroot/static/sh as the shell.  The rc.init file
prints a message and changes init_shell to the default
"/bin/sh" (using the kenv(1) utility).  Then init(8)
performs a chroot to /ochroot and runs /etc/rc within it,
now using /bin/sh as the shell (inside chroot), as usual.

A final note:  With my patch, the init binary grows from
535 KB to 537 KB (on RELENG_6), i.e. < 0.5%.

Best regards
   Oliver

--- init.c.orig	Mon Aug  7 15:10:25 2006
+++ init.c	Fri Jan  5 21:16:05 2007
@@ -55,6 +55,7 @@
 #include <db.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <kenv.h>
 #include <libutil.h>
 #include <paths.h>
 #include <signal.h>
@@ -121,6 +122,8 @@
 state_func_t catatonia(void);
 state_func_t death(void);
 
+state_func_t run_script(const char *);
+
 enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT;
 #define FALSE	0
 #define TRUE	1
@@ -131,9 +134,11 @@
 int devfs;
 
 void transition(state_t);
-state_t requested_transition = runcom;
+state_t requested_transition;
 
 void setctty(const char *);
+const char *get_shell(void);
+void write_stderr(const char *message);
 
 typedef struct init_session {
 	int	se_index;		/* index of entry in ttys file */
@@ -187,6 +192,8 @@
 int
 main(int argc, char *argv[])
 {
+	state_t initial_transition = runcom;
+	char kenv_value[PATH_MAX];
 	int c;
 	struct sigaction sa;
 	sigset_t mask;
@@ -262,7 +269,7 @@
 			devfs = 1;
 			break;
 		case 's':
-			requested_transition = single_user;
+			initial_transition = single_user;
 			break;
 		case 'f':
 			runcom_mode = FASTBOOT;
@@ -275,6 +282,65 @@
 	if (optind != argc)
 		warning("ignoring excess arguments");
 
+	/*
+	 * We catch or block signals rather than ignore them,
+	 * so that they get reset on exec.
+	 */
+	handle(badsys, SIGSYS, 0);
+	handle(disaster, SIGABRT, SIGFPE, SIGILL, SIGSEGV,
+	       SIGBUS, SIGXCPU, SIGXFSZ, 0);
+	handle(transition_handler, SIGHUP, SIGINT, SIGTERM, SIGTSTP,
+		SIGUSR1, SIGUSR2, 0);
+	handle(alrm_handler, SIGALRM, 0);
+	sigfillset(&mask);
+	delset(&mask, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS,
+		SIGXCPU, SIGXFSZ, SIGHUP, SIGINT, SIGTERM, SIGTSTP, SIGALRM, 
+		SIGUSR1, SIGUSR2, 0);
+	sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
+	sigemptyset(&sa.sa_mask);
+	sa.sa_flags = 0;
+	sa.sa_handler = SIG_IGN;
+	(void) sigaction(SIGTTIN, &sa, (struct sigaction *)0);
+	(void) sigaction(SIGTTOU, &sa, (struct sigaction *)0);
+
+	/*
+	 * Paranoia.
+	 */
+	close(0);
+	close(1);
+	close(2);
+
+	if (kenv(KENV_GET, "init_script", kenv_value, sizeof(kenv_value)) > 0) {
+		state_func_t next_transition;
+
+		if ((next_transition = run_script(kenv_value)) != 0)
+			initial_transition = (state_t) next_transition;
+	}
+
+	if (kenv(KENV_GET, "init_chroot", kenv_value, sizeof(kenv_value)) > 0) {
+		if (chdir(kenv_value) != 0 || chroot(".") != 0)
+			warning("Can't chroot to %s: %m", kenv_value);
+	}
+
+	/*
+	 * Additional check if devfs needs to be mounted:
+	 * If "/" and "/dev" have the same device number,
+	 * then it hasn't been mounted yet.
+	 */
+	if (!devfs) {
+		struct stat stst;
+		dev_t root_devno;
+
+		stat("/", &stst);
+		root_devno = stst.st_dev;
+		if (stat("/dev", &stst) != 0)
+			warning("Can't stat /dev: %m");
+		else {
+			if (stst.st_dev == root_devno)
+				devfs++;
+		}
+	}
+
 	if (devfs) {
 		struct iovec iov[4];
 		char *s;
@@ -312,37 +378,9 @@
 	}
 
 	/*
-	 * We catch or block signals rather than ignore them,
-	 * so that they get reset on exec.
-	 */
-	handle(badsys, SIGSYS, 0);
-	handle(disaster, SIGABRT, SIGFPE, SIGILL, SIGSEGV,
-	       SIGBUS, SIGXCPU, SIGXFSZ, 0);
-	handle(transition_handler, SIGHUP, SIGINT, SIGTERM, SIGTSTP,
-		SIGUSR1, SIGUSR2, 0);
-	handle(alrm_handler, SIGALRM, 0);
-	sigfillset(&mask);
-	delset(&mask, SIGABRT, SIGFPE, SIGILL, SIGSEGV, SIGBUS, SIGSYS,
-		SIGXCPU, SIGXFSZ, SIGHUP, SIGINT, SIGTERM, SIGTSTP, SIGALRM, 
-		SIGUSR1, SIGUSR2, 0);
-	sigprocmask(SIG_SETMASK, &mask, (sigset_t *) 0);
-	sigemptyset(&sa.sa_mask);
-	sa.sa_flags = 0;
-	sa.sa_handler = SIG_IGN;
-	(void) sigaction(SIGTTIN, &sa, (struct sigaction *)0);
-	(void) sigaction(SIGTTOU, &sa, (struct sigaction *)0);
-
-	/*
-	 * Paranoia.
-	 */
-	close(0);
-	close(1);
-	close(2);
-
-	/*
 	 * Start the state machine.
 	 */
-	transition(requested_transition);
+	transition(initial_transition);
 
 	/*
 	 * Should never reach here.
@@ -558,6 +596,23 @@
 	}
 }
 
+const char *
+get_shell(void)
+{
+	static char kenv_value[PATH_MAX];
+
+	if (kenv(KENV_GET, "init_shell", kenv_value, sizeof(kenv_value)) > 0)
+		return kenv_value;
+	else
+		return _PATH_BSHELL;
+}
+
+void
+write_stderr(const char *message)
+{
+	write(STDERR_FILENO, message, strlen(message));
+}
+
 /*
  * Bring the system up single user.
  */
@@ -567,7 +622,7 @@
 	pid_t pid, wpid;
 	int status;
 	sigset_t mask;
-	const char *shell = _PATH_BSHELL;
+	const char *shell;
 	char *argv[2];
 #ifdef SECURE
 	struct ttyent *typ;
@@ -589,6 +644,8 @@
 		_exit(0);
 	}
 
+	shell = get_shell();
+
 	if ((pid = fork()) == 0) {
 		/*
 		 * Start the single user session.
@@ -605,7 +662,7 @@
 		pp = getpwnam("root");
 		if (typ && (typ->ty_status & TTY_SECURE) == 0 &&
 		    pp && *pp->pw_passwd) {
-			write(STDERR_FILENO, banner, sizeof banner - 1);
+			write_stderr(banner);
 			for (;;) {
 				clear = getpass("Password:");
 				if (clear == 0 || *clear == '\0')
@@ -626,10 +683,10 @@
 			char *cp = altshell;
 			int num;
 
-#define	SHREQUEST \
-	"Enter full pathname of shell or RETURN for " _PATH_BSHELL ": "
-			(void)write(STDERR_FILENO,
-			    SHREQUEST, sizeof(SHREQUEST) - 1);
+#define	SHREQUEST "Enter full pathname of shell or RETURN for "
+			write_stderr(SHREQUEST);
+			write_stderr(shell);
+			write_stderr(": ");
 			while ((num = read(STDIN_FILENO, cp, 1)) != -1 &&
 			    num != 0 && *cp != '\n' && cp < &altshell[127])
 					cp++;
@@ -718,11 +775,35 @@
 state_func_t
 runcom(void)
 {
+	state_func_t next_transition;
+
+	if ((next_transition = run_script(_PATH_RUNCOM)) != 0)
+		return next_transition;
+
+	runcom_mode = AUTOBOOT;		/* the default */
+	/* NB: should send a message to the session logger to avoid blocking. */
+	logwtmp("~", "reboot", "");
+	return (state_func_t) read_ttys;
+}
+
+/*
+ * Run a shell script.
+ * Returns 0 on success, otherwise the next transition to enter:
+ *  - single_user if fork/execv/waitpid failed, or if the script
+ *    terminated with a signal or exit code != 0.
+ *  - death if a SIGTERM was delivered to init(8).
+ */
+state_func_t
+run_script(const char *script)
+{
 	pid_t pid, wpid;
 	int status;
 	char *argv[4];
+	const char *shell;
 	struct sigaction sa;
 
+	shell = get_shell();
+
 	if ((pid = fork()) == 0) {
 		sigemptyset(&sa.sa_mask);
 		sa.sa_flags = 0;
@@ -733,11 +814,10 @@
 		setctty(_PATH_CONSOLE);
 
 		char _sh[]	 	= "sh";
-		char _path_runcom[]	= _PATH_RUNCOM;
 		char _autoboot[]	= "autoboot";
 
 		argv[0] = _sh;
-		argv[1] = _path_runcom;
+		argv[1] = __DECONST(char *, script);
 		argv[2] = runcom_mode == AUTOBOOT ? _autoboot : 0;
 		argv[3] = 0;
 
@@ -746,14 +826,13 @@
 #ifdef LOGIN_CAP
 		setprocresources(RESOURCE_RC);
 #endif
-		execv(_PATH_BSHELL, argv);
-		stall("can't exec %s for %s: %m", _PATH_BSHELL, _PATH_RUNCOM);
+		execv(shell, argv);
+		stall("can't exec %s for %s: %m", shell, script);
 		_exit(1);	/* force single user mode */
 	}
 
 	if (pid == -1) {
-		emergency("can't fork for %s on %s: %m",
-			_PATH_BSHELL, _PATH_RUNCOM);
+		emergency("can't fork for %s on %s: %m", shell, script);
 		while (waitpid(-1, (int *) 0, WNOHANG) > 0)
 			continue;
 		sleep(STALL_TIMEOUT);
@@ -772,13 +851,13 @@
 				return (state_func_t) death;
 			if (errno == EINTR)
 				continue;
-			warning("wait for %s on %s failed: %m; going to single user mode",
-				_PATH_BSHELL, _PATH_RUNCOM);
+			warning("wait for %s on %s failed: %m; going to "
+			    "single user mode", shell, script);
 			return (state_func_t) single_user;
 		}
 		if (wpid == pid && WIFSTOPPED(status)) {
 			warning("init: %s on %s stopped, restarting\n",
-				_PATH_BSHELL, _PATH_RUNCOM);
+				shell, script);
 			kill(pid, SIGCONT);
 			wpid = -1;
 		}
@@ -795,18 +874,15 @@
 	}
 
 	if (!WIFEXITED(status)) {
-		warning("%s on %s terminated abnormally, going to single user mode",
-			_PATH_BSHELL, _PATH_RUNCOM);
+		warning("%s on %s terminated abnormally, going to single "
+		    "user mode", shell, script);
 		return (state_func_t) single_user;
 	}
 
 	if (WEXITSTATUS(status))
 		return (state_func_t) single_user;
 
-	runcom_mode = AUTOBOOT;		/* the default */
-	/* NB: should send a message to the session logger to avoid blocking. */
-	logwtmp("~", "reboot", "");
-	return (state_func_t) read_ttys;
+	return (state_func_t) 0;
 }
 
 /*
@@ -1465,6 +1541,7 @@
 	int shutdowntimeout;
 	size_t len;
 	char *argv[4];
+	const char *shell;
 	struct sigaction sa;
 	struct stat sb;
 
@@ -1477,6 +1554,8 @@
 	if (stat(_PATH_RUNDOWN, &sb) == -1 && errno == ENOENT)
 		return 0;
 
+	shell = get_shell();
+
 	if ((pid = fork()) == 0) {
 		int	fd;
 
@@ -1517,14 +1596,13 @@
 #ifdef LOGIN_CAP
 		setprocresources(RESOURCE_RC);
 #endif
-		execv(_PATH_BSHELL, argv);
-		warning("can't exec %s for %s: %m", _PATH_BSHELL, _PATH_RUNDOWN);
+		execv(shell, argv);
+		warning("can't exec %s for %s: %m", shell, _PATH_RUNDOWN);
 		_exit(1);	/* force single user mode */
 	}
 
 	if (pid == -1) {
-		emergency("can't fork for %s on %s: %m",
-			_PATH_BSHELL, _PATH_RUNDOWN);
+		emergency("can't fork for %s on %s: %m", shell, _PATH_RUNDOWN);
 		while (waitpid(-1, (int *) 0, WNOHANG) > 0)
 			continue;
 		sleep(STALL_TIMEOUT);
@@ -1548,20 +1626,20 @@
 		if (clang == 1) {
 			/* we were waiting for the sub-shell */
 			kill(wpid, SIGTERM);
-			warning("timeout expired for %s on %s: %m; going to single user mode",
-				_PATH_BSHELL, _PATH_RUNDOWN);
+			warning("timeout expired for %s on %s: %m; going to "
+			    "single user mode", shell, _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);
+			warning("wait for %s on %s failed: %m; going to "
+			    "single user mode", shell, _PATH_RUNDOWN);
 			return -1;
 		}
 		if (wpid == pid && WIFSTOPPED(status)) {
 			warning("init: %s on %s stopped, restarting\n",
-				_PATH_BSHELL, _PATH_RUNDOWN);
+				shell, _PATH_RUNDOWN);
 			kill(pid, SIGCONT);
 			wpid = -1;
 		}
@@ -1584,8 +1662,8 @@
 	}
 
 	if (!WIFEXITED(status)) {
-		warning("%s on %s terminated abnormally, going to single user mode",
-			_PATH_BSHELL, _PATH_RUNDOWN);
+		warning("%s on %s terminated abnormally, going to "
+		    "single user mode", shell, _PATH_RUNDOWN);
 		return -2;
 	}
 



-- 
Oliver Fromme,  secnetix GmbH & Co. KG, Marktplatz 29, 85567 Grafing
Dienstleistungen mit Schwerpunkt FreeBSD: http://www.secnetix.de/bsd
Any opinions expressed in this message may be personal to the author
and may not necessarily reflect the opinions of secnetix in any way.

"File names are infinite in length, where infinity is set to 255 characters."
        -- Peter Collinson, "The Unix File System"



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200701052106.l05L60a6042599>