Init.c, making it chroot

Erik Udo erik.udo at gmail.com
Thu Jan 4 17:34:01 UTC 2007


PERFECT! Just what i needed. Thanks :)

Oliver Fromme wrote:
> M. Warner Losh wrote:
>  > In message: <45975B7B.7030002 at FreeBSD.org>
>  >             Doug Barton <dougb at 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;
>  	}
>  
> 
> 
> 



More information about the freebsd-hackers mailing list