From owner-freebsd-hackers@FreeBSD.ORG Thu Jan 4 14:32:55 2007 Return-Path: X-Original-To: freebsd-hackers@FreeBSD.ORG Delivered-To: freebsd-hackers@FreeBSD.ORG Received: from mx1.freebsd.org (mx1.freebsd.org [69.147.83.52]) by hub.freebsd.org (Postfix) with ESMTP id 6924316A416 for ; Thu, 4 Jan 2007 14:32:55 +0000 (UTC) (envelope-from olli@lurza.secnetix.de) Received: from lurza.secnetix.de (lurza.secnetix.de [83.120.8.8]) by mx1.freebsd.org (Postfix) with ESMTP id BCBE213C44C for ; Thu, 4 Jan 2007 14:32:54 +0000 (UTC) (envelope-from olli@lurza.secnetix.de) Received: from lurza.secnetix.de (jividq@localhost [127.0.0.1]) by lurza.secnetix.de (8.13.4/8.13.4) with ESMTP id l04EWhQh057040; Thu, 4 Jan 2007 15:32:49 +0100 (CET) (envelope-from oliver.fromme@secnetix.de) Received: (from olli@localhost) by lurza.secnetix.de (8.13.4/8.13.1/Submit) id l04EWgXA057039; Thu, 4 Jan 2007 15:32:42 +0100 (CET) (envelope-from olli) Date: Thu, 4 Jan 2007 15:32:42 +0100 (CET) Message-Id: <200701041432.l04EWgXA057039@lurza.secnetix.de> From: Oliver Fromme To: freebsd-hackers@FreeBSD.ORG, imp@bsdimp.com, dougb@FreeBSD.ORG, erik.udo@gmail.com In-Reply-To: <20061231.005302.174088308.imp@bsdimp.com> X-Newsgroups: list.freebsd-hackers User-Agent: tin/1.8.2-20060425 ("Shillay") (UNIX) (FreeBSD/4.11-STABLE (i386)) X-Greylist: Sender IP whitelisted, not delayed by milter-greylist-2.1.2 (lurza.secnetix.de [127.0.0.1]); Thu, 04 Jan 2007 15:32:49 +0100 (CET) X-Mailman-Approved-At: Thu, 04 Jan 2007 14:49:16 +0000 Cc: Subject: Re: Init.c, making it chroot X-BeenThere: freebsd-hackers@freebsd.org X-Mailman-Version: 2.1.5 Precedence: list List-Id: Technical Discussions relating to FreeBSD List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Thu, 04 Jan 2007 14:32:55 -0000 M. Warner Losh wrote: > In message: <45975B7B.7030002@FreeBSD.org> > Doug Barton 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 #include #include +#include #include #include #include @@ -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