* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
* This code is derived from software contributed to Berkeley by
* Donn Seeley at Berkeley Software Design, Inc.
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This product includes software developed by the University of
* California, Berkeley and its contributors.
* 4. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
static char copyright
[] =
"@(#) Copyright (c) 1991, 1993\n\
The Regents of the University of California. All rights reserved.\n";
static char sccsid
[] = "@(#)init.c 8.2 (Berkeley) 4/28/95";
* Until the mythical util.h arrives...
extern int login_tty
__P((int));
extern int logout
__P((const char *));
extern void logwtmp
__P((const char *, const char *, const char *));
* Sleep times; used to prevent thrashing.
#define GETTY_SPACING 5 /* N secs minimum getty spacing */
#define GETTY_SLEEP 30 /* sleep N secs after spacing problem */
#define WINDOW_WAIT 3 /* wait N secs after starting window */
#define STALL_TIMEOUT 30 /* wait N secs after warning */
#define DEATH_WATCH 10 /* wait N secs for procs to die */
void handle
__P((sig_t
, ...));
void delset
__P((sigset_t
*, ...));
void stall
__P((char *, ...));
void warning
__P((char *, ...));
void emergency
__P((char *, ...));
void disaster
__P((int));
* We really need a recursive typedef...
* The following at least guarantees that the return type of (*state_t)()
* is sufficiently wide to hold a function pointer.
typedef long (*state_func_t
) __P((void));
typedef state_func_t (*state_t
) __P((void));
state_func_t single_user
__P((void));
state_func_t runcom
__P((void));
state_func_t read_ttys
__P((void));
state_func_t multi_user
__P((void));
state_func_t clean_ttys
__P((void));
state_func_t catatonia
__P((void));
state_func_t death
__P((void));
enum { AUTOBOOT
, FASTBOOT
} runcom_mode
= AUTOBOOT
;
void transition
__P((state_t
));
state_t requested_transition
= runcom
;
void setctty
__P((char *));
typedef struct init_session
{
int se_index
; /* index of entry in ttys file */
pid_t se_process
; /* controlling process */
time_t se_started
; /* used to avoid thrashing */
int se_flags
; /* status of session */
#define SE_SHUTDOWN 0x1 /* session won't be restarted */
char *se_device
; /* filename of port */
char *se_getty
; /* what to run on that port */
char **se_getty_argv
; /* pre-parsed argument array */
char *se_window
; /* window system (started only once) */
char **se_window_argv
; /* pre-parsed argument array */
struct init_session
*se_prev
;
struct init_session
*se_next
;
void free_session
__P((session_t
*));
session_t
*new_session
__P((session_t
*, int, struct ttyent
*));
char **construct_argv
__P((char *));
void start_window_system
__P((session_t
*));
void collect_child
__P((pid_t
));
pid_t start_getty
__P((session_t
*));
void transition_handler
__P((int));
void alrm_handler
__P((int));
void setsecuritylevel
__P((int));
int getsecuritylevel
__P((void));
int setupargv
__P((session_t
*, struct ttyent
*));
void clear_session_logs
__P((session_t
*));
int start_session_db
__P((void));
void add_session
__P((session_t
*));
void del_session
__P((session_t
*));
session_t
*find_session
__P((pid_t
));
* The mother of all processes.
/* Dispose of random users. */
(void)fprintf(stderr
, "init: %s\n", strerror(EPERM
));
/* System V users like to reexec init. */
(void)fprintf(stderr
, "init: already running\n");
* Note that this does NOT open a file...
* Does 'init' deserve its own facility number?
openlog("init", LOG_CONS
|LOG_ODELAY
, LOG_AUTH
);
* Create an initial session.
warning("initial setsid() failed: %m");
* Establish an initial user so that programs running
* single user do not freak out and die (like passwd).
if (setlogin("root") < 0)
warning("setlogin() failed: %m");
* This code assumes that we always get arguments through flags,
* never through bits set in some random machine register.
while ((c
= getopt(argc
, argv
, "sf")) != -1)
requested_transition
= single_user
;
warning("unrecognized flag '-%c'", c
);
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
, SIGTERM
, SIGTSTP
, 0);
handle(alrm_handler
, SIGALRM
, 0);
delset(&mask
, SIGABRT
, SIGFPE
, SIGILL
, SIGSEGV
, SIGBUS
, SIGSYS
,
SIGXCPU
, SIGXFSZ
, SIGHUP
, SIGTERM
, SIGTSTP
, SIGALRM
, 0);
sigprocmask(SIG_SETMASK
, &mask
, (sigset_t
*) 0);
sigemptyset(&sa
.sa_mask
);
(void) sigaction(SIGTTIN
, &sa
, (struct sigaction
*)0);
(void) sigaction(SIGTTOU
, &sa
, (struct sigaction
*)0);
* Start the state machine.
transition(requested_transition
);
* Should never reach here.
* Associate a function with a signal handler.
handle(sig_t handler
, ...)
handler
= va_arg(ap
, sig_t
);
sigfillset(&mask_everything
);
while (sig
= va_arg(ap
, int)) {
sa
.sa_mask
= mask_everything
;
sa
.sa_flags
= sig
== SIGCHLD
? SA_NOCLDSTOP
: 0;
sigaction(sig
, &sa
, (struct sigaction
*) 0);
* Delete a set of signals from a mask.
delset(sigset_t
*maskp
, ...)
maskp
= va_arg(ap
, sigset_t
*);
while (sig
= va_arg(ap
, int))
* Log a message and sleep for a while (to give someone an opportunity
* to read it and to save log or hardcopy output if the problem is chronic).
* NB: should send a message to the session logger to avoid blocking.
stall(char *message
, ...)
message
= va_arg(ap
, char *);
vsyslog(LOG_ALERT
, message
, ap
);
* Like stall(), but doesn't sleep.
* If cpp had variadic macros, the two functions could be #defines for another.
* NB: should send a message to the session logger to avoid blocking.
warning(char *message
, ...)
message
= va_arg(ap
, char *);
vsyslog(LOG_ALERT
, message
, ap
);
* Log an emergency message.
* NB: should send a message to the session logger to avoid blocking.
emergency(char *message
, ...)
message
= va_arg(ap
, char *);
vsyslog(LOG_EMERG
, message
, ap
);
* These may arise if a system does not support sysctl.
* We tolerate up to 25 of these, then throw in the towel.
* Catch an unexpected signal.
emergency("fatal signal: %s",
sig
< (unsigned) NSIG
? sys_siglist
[sig
] : "unknown signal");
* Get the security level of the kernel.
name
[1] = KERN_SECURELVL
;
if (sysctl(name
, 2, &curlevel
, &len
, NULL
, 0) == -1) {
emergency("cannot get kernel security level: %s",
* Set the security level of the kernel.
setsecuritylevel(newlevel
)
curlevel
= getsecuritylevel();
if (newlevel
== curlevel
)
name
[1] = KERN_SECURELVL
;
if (sysctl(name
, 2, NULL
, NULL
, &newlevel
, sizeof newlevel
) == -1) {
"cannot change kernel security level from %d to %d: %s",
curlevel
, newlevel
, strerror(errno
));
warning("kernel security level changed from %d to %d",
* Change states in the finite state machine.
* The initial state is passed as an argument.
* Close out the accounting files for a login session.
* NB: should send a message to the session logger to avoid blocking.
char *line
= sp
->se_device
+ sizeof(_PATH_DEV
) - 1;
* Start a session and allocate a controlling terminal.
* Only called by children of init after forking.
sleep (2); /* leave DTR low */
if ((fd
= open(name
, O_RDWR
)) == -1) {
stall("can't open %s: %m", name
);
if (login_tty(fd
) == -1) {
stall("can't get %s for controlling terminal: %m", name
);
* Bring the system up single user.
char *shell
= _PATH_BSHELL
;
static const char banner
[] =
"Enter root password, or ^D to go multi-user\n";
* If the kernel is in secure mode, downgrade it to insecure mode.
if (getsecuritylevel() > 0)
if ((pid
= fork()) == 0) {
* Start the single user session.
* Check the root password.
* We don't care if the console is 'on' by default;
* it's the only tty that can be 'off' and 'secure'.
typ
= getttynam("console");
if (typ
&& (typ
->ty_status
& TTY_SECURE
) == 0 && pp
) {
write(2, banner
, sizeof banner
- 1);
clear
= getpass("Password:");
if (clear
== 0 || *clear
== '\0')
password
= crypt(clear
, pp
->pw_passwd
);
memset(clear
, 0, _PASSWORD_LEN
);
if (strcmp(password
, pp
->pw_passwd
) == 0)
warning("single-user login failed\n");
char altshell
[128], *cp
= altshell
;
"Enter pathname of shell or RETURN for sh: "
(void)write(STDERR_FILENO
,
SHREQUEST
, sizeof(SHREQUEST
) - 1);
while ((num
= read(STDIN_FILENO
, cp
, 1)) != -1 &&
num
!= 0 && *cp
!= '\n' && cp
< &altshell
[127])
* We catch all the interesting ones,
* and those are reset to SIG_DFL on exec.
sigprocmask(SIG_SETMASK
, &mask
, (sigset_t
*) 0);
* If the default one doesn't work, try the Bourne shell.
emergency("can't exec %s for single user: %m", shell
);
execv(_PATH_BSHELL
, argv
);
emergency("can't exec %s for single user: %m", _PATH_BSHELL
);
* We are seriously hosed. Do our best.
emergency("can't fork single-user shell, trying again");
while (waitpid(-1, (int *) 0, WNOHANG
) > 0)
return (state_func_t
) single_user
;
requested_transition
= 0;
if ((wpid
= waitpid(-1, &status
, WUNTRACED
)) != -1)
warning("wait for single-user shell failed: %m; restarting");
return (state_func_t
) single_user
;
if (wpid
== pid
&& WIFSTOPPED(status
)) {
warning("init: shell stopped, restarting\n");
} while (wpid
!= pid
&& !requested_transition
);
if (requested_transition
)
return (state_func_t
) requested_transition
;
if (!WIFEXITED(status
)) {
if (WTERMSIG(status
) == SIGKILL
) {
* reboot(8) killed shell?
warning("single user shell terminated.");
warning("single user shell terminated, restarting");
return (state_func_t
) single_user
;
return (state_func_t
) runcom
;
* Run the system startup script.
if ((pid
= fork()) == 0) {
sigemptyset(&sa
.sa_mask
);
(void) sigaction(SIGTSTP
, &sa
, (struct sigaction
*)0);
(void) sigaction(SIGHUP
, &sa
, (struct sigaction
*)0);
argv
[2] = runcom_mode
== AUTOBOOT
? "autoboot" : 0;
sigprocmask(SIG_SETMASK
, &sa
.sa_mask
, (sigset_t
*) 0);
execv(_PATH_BSHELL
, argv
);
stall("can't exec %s for %s: %m", _PATH_BSHELL
, _PATH_RUNCOM
);
_exit(1); /* force single user mode */
emergency("can't fork for %s on %s: %m",
_PATH_BSHELL
, _PATH_RUNCOM
);
while (waitpid(-1, (int *) 0, WNOHANG
) > 0)
return (state_func_t
) single_user
;
* Copied from single_user(). This is a bit paranoid.
if ((wpid
= waitpid(-1, &status
, WUNTRACED
)) != -1)
warning("wait for %s on %s failed: %m; going to single user mode",
_PATH_BSHELL
, _PATH_RUNCOM
);
return (state_func_t
) single_user
;
if (wpid
== pid
&& WIFSTOPPED(status
)) {
warning("init: %s on %s stopped, restarting\n",
_PATH_BSHELL
, _PATH_RUNCOM
);
if (WIFSIGNALED(status
) && WTERMSIG(status
) == SIGTERM
&&
requested_transition
== catatonia
) {
/* /etc/rc executed /sbin/reboot; wait for the end quietly */
if (!WIFEXITED(status
)) {
warning("%s on %s terminated abnormally, going to single user mode",
_PATH_BSHELL
, _PATH_RUNCOM
);
return (state_func_t
) single_user
;
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
;
* Open the session database.
* NB: We could pass in the size here; is it necessary?
if (session_db
&& (*session_db
->close
)(session_db
))
emergency("session database close: %s", strerror(errno
));
if ((session_db
= dbopen(NULL
, O_RDWR
, 0, DB_HASH
, NULL
)) == 0) {
emergency("session database open: %s", strerror(errno
));
* Add a new login session.
key
.data
= &sp
->se_process
;
key
.size
= sizeof sp
->se_process
;
if ((*session_db
->put
)(session_db
, &key
, &data
, 0))
emergency("insert %d: %s", sp
->se_process
, strerror(errno
));
* Delete an old login session.
key
.data
= &sp
->se_process
;
key
.size
= sizeof sp
->se_process
;
if ((*session_db
->del
)(session_db
, &key
, 0))
emergency("delete %d: %s", sp
->se_process
, strerror(errno
));
* Look up a login session by pid.
if ((*session_db
->get
)(session_db
, &key
, &data
, 0) != 0)
memmove(&ret
, data
.data
, sizeof(ret
));
* Construct an argument vector from a command line.
register char **argv
= (char **) malloc(((strlen(command
) + 1) / 2 + 1)
static const char separators
[] = " \t";
if ((argv
[argc
++] = strtok(command
, separators
)) == 0)
while (argv
[argc
++] = strtok((char *) 0, separators
))
* Deallocate a session descriptor.
free(sp
->se_window_argv
);
* Allocate a new session descriptor.
new_session(sprev
, session_index
, typ
)
register struct ttyent
*typ
;
if ((typ
->ty_status
& TTY_ON
) == 0 ||
sp
= (session_t
*) malloc(sizeof (session_t
));
memset(sp
, 0, sizeof *sp
);
sp
->se_index
= session_index
;
sp
->se_device
= malloc(sizeof(_PATH_DEV
) + strlen(typ
->ty_name
));
(void) sprintf(sp
->se_device
, "%s%s", _PATH_DEV
, typ
->ty_name
);
if (setupargv(sp
, typ
) == 0) {
* Calculate getty and if useful window argv vectors.
sp
->se_getty
= malloc(strlen(typ
->ty_getty
) + strlen(typ
->ty_name
) + 2);
(void) sprintf(sp
->se_getty
, "%s %s", typ
->ty_getty
, typ
->ty_name
);
sp
->se_getty_argv
= construct_argv(sp
->se_getty
);
if (sp
->se_getty_argv
== 0) {
warning("can't parse getty for port %s", sp
->se_device
);
sp
->se_window
= strdup(typ
->ty_window
);
sp
->se_window_argv
= construct_argv(sp
->se_window
);
if (sp
->se_window_argv
== 0) {
warning("can't parse window for port %s",
* Walk the list of ttys and create sessions for each active line.
register session_t
*sp
, *snext
;
register struct ttyent
*typ
;
* Destroy any previous session state.
* There shouldn't be any, but just in case...
for (sp
= sessions
; sp
; sp
= snext
) {
return (state_func_t
) single_user
;
* Allocate a session entry for each active port.
* Note that sp starts at 0.
while (typ
= getttyent())
if (snext
= new_session(sp
, ++session_index
, typ
))
return (state_func_t
) multi_user
;
* Start a window system running.
if ((pid
= fork()) == -1) {
emergency("can't fork for window system on port %s: %m",
/* hope that getty fails and we can try again */
sigprocmask(SIG_SETMASK
, &mask
, (sigset_t
*) 0);
emergency("setsid failed (window) %m");
execv(sp
->se_window_argv
[0], sp
->se_window_argv
);
stall("can't exec window system '%s' for port %s: %m",
sp
->se_window_argv
[0], sp
->se_device
);
* Start a login session running.
time_t current_time
= time((time_t *) 0);
* fork(), not vfork() -- we can't afford to block.
if ((pid
= fork()) == -1) {
emergency("can't fork for getty on port %s: %m", sp
->se_device
);
if (current_time
> sp
->se_started
&&
current_time
- sp
->se_started
< GETTY_SPACING
) {
warning("getty repeating too quickly on port %s, sleeping",
sleep((unsigned) GETTY_SLEEP
);
sigprocmask(SIG_SETMASK
, &mask
, (sigset_t
*) 0);
execv(sp
->se_getty_argv
[0], sp
->se_getty_argv
);
stall("can't exec getty '%s' for port %s: %m",
sp
->se_getty_argv
[0], sp
->se_device
);
* Collect exit status for a child.
* If an exiting login, start a new login running.
register session_t
*sp
, *sprev
, *snext
;
if (! (sp
= find_session(pid
)))
if (sp
->se_flags
& SE_SHUTDOWN
) {
sprev
->se_next
= sp
->se_next
;
snext
->se_prev
= sp
->se_prev
;
if ((pid
= start_getty(sp
)) == -1) {
requested_transition
= clean_ttys
;
sp
->se_started
= time((time_t *) 0);
* Catch a signal and request a state transition.
requested_transition
= clean_ttys
;
requested_transition
= death
;
requested_transition
= catatonia
;
requested_transition
= 0;
* Take the system multiuser.
requested_transition
= 0;
* If the administrator has not set the security level to -1
* to indicate that the kernel should not run multiuser in secure
* mode, and the run script has not set a higher level of security
* than level 1, then put the kernel into secure mode.
if (getsecuritylevel() == 0)
for (sp
= sessions
; sp
; sp
= sp
->se_next
) {
if ((pid
= start_getty(sp
)) == -1) {
requested_transition
= clean_ttys
;
sp
->se_started
= time((time_t *) 0);
while (!requested_transition
)
if ((pid
= waitpid(-1, (int *) 0, 0)) != -1)
return (state_func_t
) requested_transition
;
* This is an n-squared algorithm. We hope it isn't run often...
register session_t
*sp
, *sprev
;
register struct ttyent
*typ
;
register int session_index
= 0;
return (state_func_t
) multi_user
;
devlen
= sizeof(_PATH_DEV
) - 1;
while (typ
= getttyent()) {
for (sprev
= 0, sp
= sessions
; sp
; sprev
= sp
, sp
= sp
->se_next
)
if (strcmp(typ
->ty_name
, sp
->se_device
+ devlen
) == 0)
if (sp
->se_index
!= session_index
) {
warning("port %s changed utmp index from %d to %d",
sp
->se_device
, sp
->se_index
,
sp
->se_index
= session_index
;
if ((typ
->ty_status
& TTY_ON
) == 0 ||
sp
->se_flags
|= SE_SHUTDOWN
;
kill(sp
->se_process
, SIGHUP
);
sp
->se_flags
&= ~SE_SHUTDOWN
;
if (setupargv(sp
, typ
) == 0) {
warning("can't parse getty for port %s",
sp
->se_flags
|= SE_SHUTDOWN
;
kill(sp
->se_process
, SIGHUP
);
new_session(sprev
, session_index
, typ
);
return (state_func_t
) multi_user
;
for (sp
= sessions
; sp
; sp
= sp
->se_next
)
sp
->se_flags
|= SE_SHUTDOWN
;
return (state_func_t
) multi_user
;
* Bring the system down to single user.
static const int death_sigs
[3] = { SIGHUP
, SIGTERM
, SIGKILL
};
for (sp
= sessions
; sp
; sp
= sp
->se_next
)
sp
->se_flags
|= SE_SHUTDOWN
;
/* NB: should send a message to the session logger to avoid blocking. */
logwtmp("~", "shutdown", "");
for (i
= 0; i
< 3; ++i
) {
if (kill(-1, death_sigs
[i
]) == -1 && errno
== ESRCH
)
return (state_func_t
) single_user
;
if ((pid
= waitpid(-1, (int *)0, 0)) != -1)
while (clang
== 0 && errno
!= ECHILD
);
return (state_func_t
) single_user
;
warning("some processes would not die; ps axl advised");
return (state_func_t
) single_user
;