Patches to allow one to allow one to specify a directory to chroot to.

This includes support for running a script to setup that directory.
The kenv variables init_chroot and init_script control this behavior,
and are documented in loader(8) that's about to be committed (along
with the other variables like init_path...).

Submitted by: Oliver Fromme
Reviewed by: myself, jhb (earlier versions)
This commit is contained in:
Warner Losh 2007-02-04 06:33:13 +00:00
parent f3b179a4b1
commit 1a7bec91fb
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=166484

View File

@ -55,6 +55,7 @@ static const char rcsid[] =
#include <db.h> #include <db.h>
#include <errno.h> #include <errno.h>
#include <fcntl.h> #include <fcntl.h>
#include <kenv.h>
#include <libutil.h> #include <libutil.h>
#include <paths.h> #include <paths.h>
#include <signal.h> #include <signal.h>
@ -121,6 +122,8 @@ state_func_t clean_ttys(void);
state_func_t catatonia(void); state_func_t catatonia(void);
state_func_t death(void); state_func_t death(void);
state_func_t run_script(const char *);
enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT; enum { AUTOBOOT, FASTBOOT } runcom_mode = AUTOBOOT;
#define FALSE 0 #define FALSE 0
#define TRUE 1 #define TRUE 1
@ -131,9 +134,11 @@ int howto = RB_AUTOBOOT;
int devfs; int devfs;
void transition(state_t); void transition(state_t);
state_t requested_transition = runcom; state_t requested_transition;
void setctty(const char *); void setctty(const char *);
const char *get_shell(void);
void write_stderr(const char *message);
typedef struct init_session { typedef struct init_session {
int se_index; /* index of entry in ttys file */ int se_index; /* index of entry in ttys file */
@ -187,6 +192,8 @@ DB *session_db;
int int
main(int argc, char *argv[]) main(int argc, char *argv[])
{ {
state_t initial_transition = runcom;
char kenv_value[PATH_MAX];
int c; int c;
struct sigaction sa; struct sigaction sa;
sigset_t mask; sigset_t mask;
@ -262,7 +269,7 @@ main(int argc, char *argv[])
devfs = 1; devfs = 1;
break; break;
case 's': case 's':
requested_transition = single_user; initial_transition = single_user;
break; break;
case 'f': case 'f':
runcom_mode = FASTBOOT; runcom_mode = FASTBOOT;
@ -275,6 +282,63 @@ main(int argc, char *argv[])
if (optind != argc) if (optind != argc)
warning("ignoring excess arguments"); 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) { if (devfs) {
struct iovec iov[4]; struct iovec iov[4];
char *s; char *s;
@ -311,38 +375,10 @@ main(int argc, char *argv[])
free(s); free(s);
} }
/*
* 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. * Start the state machine.
*/ */
transition(requested_transition); transition(initial_transition);
/* /*
* Should never reach here. * Should never reach here.
@ -558,6 +594,23 @@ setctty(const char *name)
} }
} }
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. * Bring the system up single user.
*/ */
@ -567,7 +620,7 @@ single_user(void)
pid_t pid, wpid; pid_t pid, wpid;
int status; int status;
sigset_t mask; sigset_t mask;
const char *shell = _PATH_BSHELL; const char *shell;
char *argv[2]; char *argv[2];
#ifdef SECURE #ifdef SECURE
struct ttyent *typ; struct ttyent *typ;
@ -589,6 +642,8 @@ single_user(void)
_exit(0); _exit(0);
} }
shell = get_shell();
if ((pid = fork()) == 0) { if ((pid = fork()) == 0) {
/* /*
* Start the single user session. * Start the single user session.
@ -605,7 +660,7 @@ single_user(void)
pp = getpwnam("root"); pp = getpwnam("root");
if (typ && (typ->ty_status & TTY_SECURE) == 0 && if (typ && (typ->ty_status & TTY_SECURE) == 0 &&
pp && *pp->pw_passwd) { pp && *pp->pw_passwd) {
write(STDERR_FILENO, banner, sizeof banner - 1); write_stderr(banner);
for (;;) { for (;;) {
clear = getpass("Password:"); clear = getpass("Password:");
if (clear == 0 || *clear == '\0') if (clear == 0 || *clear == '\0')
@ -626,10 +681,10 @@ single_user(void)
char *cp = altshell; char *cp = altshell;
int num; int num;
#define SHREQUEST \ #define SHREQUEST "Enter full pathname of shell or RETURN for "
"Enter full pathname of shell or RETURN for " _PATH_BSHELL ": " write_stderr(SHREQUEST);
(void)write(STDERR_FILENO, write_stderr(shell);
SHREQUEST, sizeof(SHREQUEST) - 1); write_stderr(": ");
while ((num = read(STDIN_FILENO, cp, 1)) != -1 && while ((num = read(STDIN_FILENO, cp, 1)) != -1 &&
num != 0 && *cp != '\n' && cp < &altshell[127]) num != 0 && *cp != '\n' && cp < &altshell[127])
cp++; cp++;
@ -717,12 +772,36 @@ single_user(void)
*/ */
state_func_t state_func_t
runcom(void) 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; pid_t pid, wpid;
int status; int status;
char *argv[4]; char *argv[4];
const char *shell;
struct sigaction sa; struct sigaction sa;
shell = get_shell();
if ((pid = fork()) == 0) { if ((pid = fork()) == 0) {
sigemptyset(&sa.sa_mask); sigemptyset(&sa.sa_mask);
sa.sa_flags = 0; sa.sa_flags = 0;
@ -733,11 +812,10 @@ runcom(void)
setctty(_PATH_CONSOLE); setctty(_PATH_CONSOLE);
char _sh[] = "sh"; char _sh[] = "sh";
char _path_runcom[] = _PATH_RUNCOM;
char _autoboot[] = "autoboot"; char _autoboot[] = "autoboot";
argv[0] = _sh; argv[0] = _sh;
argv[1] = _path_runcom; argv[1] = __DECONST(char *, script);
argv[2] = runcom_mode == AUTOBOOT ? _autoboot : 0; argv[2] = runcom_mode == AUTOBOOT ? _autoboot : 0;
argv[3] = 0; argv[3] = 0;
@ -746,14 +824,13 @@ runcom(void)
#ifdef LOGIN_CAP #ifdef LOGIN_CAP
setprocresources(RESOURCE_RC); setprocresources(RESOURCE_RC);
#endif #endif
execv(_PATH_BSHELL, argv); execv(shell, argv);
stall("can't exec %s for %s: %m", _PATH_BSHELL, _PATH_RUNCOM); stall("can't exec %s for %s: %m", shell, script);
_exit(1); /* force single user mode */ _exit(1); /* force single user mode */
} }
if (pid == -1) { if (pid == -1) {
emergency("can't fork for %s on %s: %m", emergency("can't fork for %s on %s: %m", shell, script);
_PATH_BSHELL, _PATH_RUNCOM);
while (waitpid(-1, (int *) 0, WNOHANG) > 0) while (waitpid(-1, (int *) 0, WNOHANG) > 0)
continue; continue;
sleep(STALL_TIMEOUT); sleep(STALL_TIMEOUT);
@ -772,13 +849,13 @@ runcom(void)
return (state_func_t) death; return (state_func_t) death;
if (errno == EINTR) if (errno == EINTR)
continue; continue;
warning("wait for %s on %s failed: %m; going to single user mode", warning("wait for %s on %s failed: %m; going to "
_PATH_BSHELL, _PATH_RUNCOM); "single user mode", shell, script);
return (state_func_t) single_user; return (state_func_t) single_user;
} }
if (wpid == pid && WIFSTOPPED(status)) { if (wpid == pid && WIFSTOPPED(status)) {
warning("init: %s on %s stopped, restarting\n", warning("init: %s on %s stopped, restarting\n",
_PATH_BSHELL, _PATH_RUNCOM); shell, script);
kill(pid, SIGCONT); kill(pid, SIGCONT);
wpid = -1; wpid = -1;
} }
@ -795,18 +872,15 @@ runcom(void)
} }
if (!WIFEXITED(status)) { if (!WIFEXITED(status)) {
warning("%s on %s terminated abnormally, going to single user mode", warning("%s on %s terminated abnormally, going to single "
_PATH_BSHELL, _PATH_RUNCOM); "user mode", shell, script);
return (state_func_t) single_user; return (state_func_t) single_user;
} }
if (WEXITSTATUS(status)) if (WEXITSTATUS(status))
return (state_func_t) single_user; return (state_func_t) single_user;
runcom_mode = AUTOBOOT; /* the default */ return (state_func_t) 0;
/* NB: should send a message to the session logger to avoid blocking. */
logwtmp("~", "reboot", "");
return (state_func_t) read_ttys;
} }
/* /*
@ -1465,6 +1539,7 @@ runshutdown(void)
int shutdowntimeout; int shutdowntimeout;
size_t len; size_t len;
char *argv[4]; char *argv[4];
const char *shell;
struct sigaction sa; struct sigaction sa;
struct stat sb; struct stat sb;
@ -1477,6 +1552,8 @@ runshutdown(void)
if (stat(_PATH_RUNDOWN, &sb) == -1 && errno == ENOENT) if (stat(_PATH_RUNDOWN, &sb) == -1 && errno == ENOENT)
return 0; return 0;
shell = get_shell();
if ((pid = fork()) == 0) { if ((pid = fork()) == 0) {
int fd; int fd;
@ -1517,14 +1594,13 @@ runshutdown(void)
#ifdef LOGIN_CAP #ifdef LOGIN_CAP
setprocresources(RESOURCE_RC); setprocresources(RESOURCE_RC);
#endif #endif
execv(_PATH_BSHELL, argv); execv(shell, argv);
warning("can't exec %s for %s: %m", _PATH_BSHELL, _PATH_RUNDOWN); warning("can't exec %s for %s: %m", shell, _PATH_RUNDOWN);
_exit(1); /* force single user mode */ _exit(1); /* force single user mode */
} }
if (pid == -1) { if (pid == -1) {
emergency("can't fork for %s on %s: %m", emergency("can't fork for %s on %s: %m", shell, _PATH_RUNDOWN);
_PATH_BSHELL, _PATH_RUNDOWN);
while (waitpid(-1, (int *) 0, WNOHANG) > 0) while (waitpid(-1, (int *) 0, WNOHANG) > 0)
continue; continue;
sleep(STALL_TIMEOUT); sleep(STALL_TIMEOUT);
@ -1548,20 +1624,20 @@ runshutdown(void)
if (clang == 1) { if (clang == 1) {
/* we were waiting for the sub-shell */ /* we were waiting for the sub-shell */
kill(wpid, SIGTERM); kill(wpid, SIGTERM);
warning("timeout expired for %s on %s: %m; going to single user mode", warning("timeout expired for %s on %s: %m; going to "
_PATH_BSHELL, _PATH_RUNDOWN); "single user mode", shell, _PATH_RUNDOWN);
return -1; return -1;
} }
if (wpid == -1) { if (wpid == -1) {
if (errno == EINTR) if (errno == EINTR)
continue; continue;
warning("wait for %s on %s failed: %m; going to single user mode", warning("wait for %s on %s failed: %m; going to "
_PATH_BSHELL, _PATH_RUNDOWN); "single user mode", shell, _PATH_RUNDOWN);
return -1; return -1;
} }
if (wpid == pid && WIFSTOPPED(status)) { if (wpid == pid && WIFSTOPPED(status)) {
warning("init: %s on %s stopped, restarting\n", warning("init: %s on %s stopped, restarting\n",
_PATH_BSHELL, _PATH_RUNDOWN); shell, _PATH_RUNDOWN);
kill(pid, SIGCONT); kill(pid, SIGCONT);
wpid = -1; wpid = -1;
} }
@ -1584,8 +1660,8 @@ runshutdown(void)
} }
if (!WIFEXITED(status)) { if (!WIFEXITED(status)) {
warning("%s on %s terminated abnormally, going to single user mode", warning("%s on %s terminated abnormally, going to "
_PATH_BSHELL, _PATH_RUNDOWN); "single user mode", shell, _PATH_RUNDOWN);
return -2; return -2;
} }