Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 4 Jan 2007 15:32:42 +0100 (CET)
From:      Oliver Fromme <olli@lurza.secnetix.de>
To:        freebsd-hackers@FreeBSD.ORG, imp@bsdimp.com, dougb@FreeBSD.ORG, erik.udo@gmail.com
Subject:   Re: Init.c, making it chroot
Message-ID:  <200701041432.l04EWgXA057039@lurza.secnetix.de>
In-Reply-To: <20061231.005302.174088308.imp@bsdimp.com>

next in thread | previous in thread | raw e-mail | index | archive | help
M. Warner Losh wrote:
 > In message: <45975B7B.7030002@FreeBSD.org>
 >             Doug Barton <dougb@freebsd.org> writes:
 > : Erik Udo wrote:
 > : > That's nice. But NetBSDs init.c executes /etc/rc before calling
 > : > chroot(), and that's what i'm looking for
 > : 
 > : Sorry if I missed your rationale earlier, but could you perhaps
 > : explain a bit more about why you want to do this? I ask because I'm
 > : generally interested in boot-time issues, and this sounds like an
 > : interesting problem.
 > 
 > This allows one to have a 'simple' /etc/rc that arranges things so
 > that a new '/' is ready to 'boot'.

Sorry, I missed that part of the thread because I wasn't
on Cc and didn't look at the list for a while.

I've created (and tested!) a new patch.  I've tested on
RELENG_6, but I think init(8) isn't very different on
HEAD, so it should work there, too.

The patched init does the following:

 - If the kenv variable "init_script" is set, it is
   expected to be the name of a shell script that is
   executed before init(8) enters its usual state
   machine, and before chrooting (if requested, see
   below).  If the script terminates with an exit code
   other than 0, single user mode is enforced.

 - If the kenv variable "init_chroot" is set, init(8)
   performs a chroot(2) operation into that directory.
   That happens after executing the init_script, if
   any, but before entering the usual state machine,
   i.e. before going into single or multi user mode.

 - A check is performed whether /dev is mounted (inside
   the chroot, if any).  If not, it is mounted.

 - Afterwards, init(8) proceeds normally.

It should be noted that the init_script can create or
modify the "init_chroot" variable using the kenv(1)
tool.  So the chroot directory can be specified
dynamically by the init_script; it does not have to
be hardcoded in /boot/loader.conf.

It should also be noted that the init_script requires
a few files and directories to be present _outside_ of
any chroot:

   /bin/sh
   /dev
   /lib/libc.so.6
   /lib/libedit.so.5
   /lib/libncurses.so.6
   /libexec/ld-elf.so.1

Alternatively you can compile a static shell binary,
then you only need /bin/sh and dev (I haven't tested
this, though).  Note that the /dev directory must
exist (outside the chroot), because running the
init_script requires certain devices (/dev/console).
It is mounted by the kernel before starting init(8).

As usual, have prepared an ISO image which tests and
demonstrates the patch.  It's 17.5 MB compressed:

http://www.secnetix.de/tmp/init_chroot/

The /boot/loader.conf file looks like this:

   kernel_options="-C"
   init_path="/ochroot/sbin/init"
   init_script="/ochroot/etc/rc.init"
   init_chroot="/ochroot"

The /ochroot/etc/rc.init just prints a few messages,
so you can see that it actually does something.  It
does not have to be inside the chroot (I put it there
because of convenience only).

Any comments are welcome.  I particularly appreciate
if others test this stuff.

Best regards
   Oliver

PS:  Here's the patch, relatve to rev. 1.60.2.2.

I had to move some parts of the original code around,
so the setup of the signal handlers happen before any
script is run, and the mount of /dev happens afterwards.
That's why the diff grew somewhat, even though my actual
code changes aren't that many.

For executing the init_script, I re-used the runcom()
function which required a few minor modifications to it
so it was a bit more flexible.  In particular I had to
move the _PATH_RUNCOM constant into a variable.

--- src/sbin/init/init.c.orig	Mon Aug  7 15:10:25 2006
+++ src/sbin/init/init.c	Thu Jan  4 11:53:00 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,7 @@
 state_func_t catatonia(void);
 state_func_t death(void);
 
+const char *runcom_script = _PATH_RUNCOM;
 enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT;
 #define FALSE	0
 #define TRUE	1
@@ -187,6 +189,9 @@
 int
 main(int argc, char *argv[])
 {
+	char kenv_value[PATH_MAX];
+	char ichroot_name[] = "init_chroot";
+	char iscript_name[] = "init_script";
 	int c;
 	struct sigaction sa;
 	sigset_t mask;
@@ -275,6 +280,66 @@
 	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, iscript_name, kenv_value, sizeof(kenv_value)) > 0) {
+		runcom_script = kenv_value;
+		runcom();
+		runcom_script = _PATH_RUNCOM;
+		if (requested_transition == 0)
+			requested_transition = runcom;
+	}
+
+	if (kenv(KENV_GET, ichroot_name, 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,34 +377,6 @@
 	}
 
 	/*
-	 * 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);
@@ -733,11 +770,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 *, runcom_script);
 		argv[2] = runcom_mode == AUTOBOOT ? _autoboot : 0;
 		argv[3] = 0;
 
@@ -747,13 +783,13 @@
 		setprocresources(RESOURCE_RC);
 #endif
 		execv(_PATH_BSHELL, argv);
-		stall("can't exec %s for %s: %m", _PATH_BSHELL, _PATH_RUNCOM);
+		stall("can't exec %s for %s: %m", _PATH_BSHELL, runcom_script);
 		_exit(1);	/* force single user mode */
 	}
 
 	if (pid == -1) {
 		emergency("can't fork for %s on %s: %m",
-			_PATH_BSHELL, _PATH_RUNCOM);
+			_PATH_BSHELL, runcom_script);
 		while (waitpid(-1, (int *) 0, WNOHANG) > 0)
 			continue;
 		sleep(STALL_TIMEOUT);
@@ -773,12 +809,12 @@
 			if (errno == EINTR)
 				continue;
 			warning("wait for %s on %s failed: %m; going to single user mode",
-				_PATH_BSHELL, _PATH_RUNCOM);
+				_PATH_BSHELL, runcom_script);
 			return (state_func_t) single_user;
 		}
 		if (wpid == pid && WIFSTOPPED(status)) {
 			warning("init: %s on %s stopped, restarting\n",
-				_PATH_BSHELL, _PATH_RUNCOM);
+				_PATH_BSHELL, runcom_script);
 			kill(pid, SIGCONT);
 			wpid = -1;
 		}
@@ -796,7 +832,7 @@
 
 	if (!WIFEXITED(status)) {
 		warning("%s on %s terminated abnormally, going to single user mode",
-			_PATH_BSHELL, _PATH_RUNCOM);
+			_PATH_BSHELL, runcom_script);
 		return (state_func_t) single_user;
 	}
 



-- 
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.

"The scanf() function is a large and complex beast that often does
something almost but not quite entirely unlike what you desired."
        -- Chris Torek



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