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>