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