* Copyright (c) 1991 The Regents of the University of California.
* This code is derived from software contributed to Berkeley by
* Donn Seeley at UUNET Technologies, Inc.
* %sccs.include.redist.c%
"@(#) Copyright (c) 1991 The Regents of the University of California.\n\
static char sccsid
[] = "@(#)init.c 6.5 (Berkeley) %G%";
* 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 10 /* fork getty on a port every N secs */
#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 *));
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 */
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((int));
pid_t start_getty
__P((session_t
*));
void transition_handler
__P((int));
void alrm_handler
__P((int));
int start_logger
__P((void));
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.
* Silently dispose of random users running this program.
* 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.
syslog(LOG_ERR
, "setsid failed (initial) %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(disaster
, SIGABRT
, SIGFPE
, SIGILL
, SIGSEGV
,
SIGBUS
, SIGSYS
, 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
);
* Catch an unexpected signal.
emergency("fatal signal: %s",
sig
< (unsigned) NSIG
? sys_siglist
[sig
] : "unknown signal");
* Change states in the finite state machine.
* The initial state is passed as an argument.
* We send requests for session logging to another process for two reasons.
* First, we don't want to block if the log files go away (e.g. because
* one or more are on hard-mounted NFS systems whose server crashes).
* Second, despite all the crud already contained in init, it still isn't
* right that init should care about session logging record formats and files.
* We could use explicit 'Unix' IPC for this, but let's try to be POSIX...
static char *argv
[] = { _PATH_SLOGGER
, 0 };
warning("session logging disabled: can't make pipe to %s: %m",
if ((pid
= fork()) == -1) {
emergency("session logging disabled: can't fork for %s: %m",
if ((fd
= open(_PATH_DEVNULL
, O_WRONLY
)) != -1) {
sigprocmask(SIG_SETMASK
, &mask
, (sigset_t
*) 0);
stall("can't exec %s: %m", argv
[0]);
fcntl(pfd
[1], F_SETFD
, FD_CLOEXEC
);
fcntl(pfd
[1], F_SETFL
, O_NONBLOCK
);
* Close out the accounting files for a login session.
* NB: should send a message to the session logger to avoid blocking.
if (logout(sp
->se_device
))
logwtmp(sp
->se_device
, "", "");
* Start a session and allocate a controlling terminal.
* Only called by children of init after forking.
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 ((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);
password
= getpass("Password:");
if (password
== 0 || *password
== '\0')
if (strcmp(password
, pp
->pw_passwd
) == 0)
* Make the single-user shell be root's standard shell?
* 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 (!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
= hash_open(NULL
, O_RDWR
, 0, 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
, R_PUT
))
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)
return *(session_t
**)data
.data
;
* 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
));
sp
->se_index
= session_index
;
sp
->se_device
= malloc(6 + strlen(typ
->ty_name
));
memcpy(sp
->se_device
, _PATH_DEV
, 5);
strcpy(sp
->se_device
+ 5, typ
->ty_name
);
sp
->se_getty
= strdup(typ
->ty_getty
);
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_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_SPACING
-
(current_time
- sp
->se_started
));
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;
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
;
while (typ
= getttyent()) {
for (sp
= sessions
; sp
; sprev
= sp
, sp
= sp
->se_next
)
if (strcmp(typ
->ty_name
, sp
->se_device
) == 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
)
sp
->se_flags
&= ~SE_SHUTDOWN
;
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 wouldn't die");
return (state_func_t
) single_user
;