minix/servers/init/init.c

471 lines
11 KiB
C
Raw Normal View History

2005-04-21 16:53:53 +02:00
/* This process is the father (mother) of all Minix user processes. When
* Minix comes up, this is process number 2, and has a pid of 1. It
* executes the /etc/rc shell file, and then reads the /etc/ttytab file to
* determine which terminals need a login process.
*
* If the files /usr/adm/wtmp and /etc/utmp exist and are writable, init
* (with help from login) will maintain login accounting. Sending a
* signal 1 (SIGHUP) to init will cause it to rescan /etc/ttytab and start
* up new shell processes if necessary. It will not, however, kill off
* login processes for lines that have been turned off; do this manually.
* Signal 15 (SIGTERM) makes init stop spawning new processes, this is
* used by shutdown and friends when they are about to close the system
* down.
*/
#include <minix/type.h>
2005-04-21 16:53:53 +02:00
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/svrctl.h>
#include <ttyent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <string.h>
#include <time.h>
#include <stdlib.h>
#include <unistd.h>
#include <utmp.h>
/* Command to execute as a response to the three finger salute. */
char *REBOOT_CMD[] = { "shutdown", "-d", "now", "CTRL-ALT-DEL", NULL };
2005-04-21 16:53:53 +02:00
/* Associated fake ttytab entry. */
struct ttyent TT_REBOOT = { "console", "-", REBOOT_CMD, NULL };
char PATH_UTMP[] = "/etc/utmp"; /* current logins */
char PATH_WTMP[] = "/usr/adm/wtmp"; /* login/logout history */
#define PIDSLOTS 32 /* first this many ttys can be on */
struct slotent {
int errct; /* error count */
pid_t pid; /* pid of login process for this tty line */
};
#define ERRCT_DISABLE 10 /* disable after this many errors */
#define NO_PID 0 /* pid value indicating no process */
struct slotent slots[PIDSLOTS]; /* init table of ttys and pids */
int gothup = 0; /* flag, showing signal 1 was received */
int gotabrt = 0; /* flag, showing signal 6 was received */
int spawn = 1; /* flag, spawn processes only when set */
void tell(int fd, char *s);
void report(int fd, char *label);
void wtmp(int type, int linenr, char *line, pid_t pid);
void startup(int linenr, struct ttyent *ttyp);
int execute(char **cmd);
void onhup(int sig);
void onterm(int sig);
void onabrt(int sig);
int main(void)
{
pid_t pid; /* pid of child process */
int fd; /* generally useful */
int linenr; /* loop variable */
int check; /* check if a new process must be spawned */
Rewrite of boot process KERNEL CHANGES: - The kernel only knows about privileges of kernel tasks and the root system process (now RS). - Kernel tasks and the root system process are the only processes that are made schedulable by the kernel at startup. All the other processes in the boot image don't get their privileges set at startup and are inhibited from running by the RTS_NO_PRIV flag. - Removed the assumption on the ordering of processes in the boot image table. System processes can now appear in any order in the boot image table. - Privilege ids can now be assigned both statically or dynamically. The kernel assigns static privilege ids to kernel tasks and the root system process. Each id is directly derived from the process number. - User processes now all share the static privilege id of the root user process (now INIT). - sys_privctl split: we have more calls now to let RS set privileges for system processes. SYS_PRIV_ALLOW / SYS_PRIV_DISALLOW are only used to flip the RTS_NO_PRIV flag and allow / disallow a process from running. SYS_PRIV_SET_SYS / SYS_PRIV_SET_USER are used to set privileges for a system / user process. - boot image table flags split: PROC_FULLVM is the only flag that has been moved out of the privilege flags and is still maintained in the boot image table. All the other privilege flags are out of the kernel now. RS CHANGES: - RS is the only user-space process who gets to run right after in-kernel startup. - RS uses the boot image table from the kernel and three additional boot image info table (priv table, sys table, dev table) to complete the initialization of the system. - RS checks that the entries in the priv table match the entries in the boot image table to make sure that every process in the boot image gets schedulable. - RS only uses static privilege ids to set privileges for system services in the boot image. - RS includes basic memory management support to allocate the boot image buffer dynamically during initialization. The buffer shall contain the executable image of all the system services we would like to restart after a crash. - First step towards decoupling between resource provisioning and resource requirements in RS: RS must know what resources it needs to restart a process and what resources it has currently available. This is useful to tradeoff reliability and resource consumption. When required resources are missing, the process cannot be restarted. In that case, in the future, a system flag will tell RS what to do. For example, if CORE_PROC is set, RS should trigger a system-wide panic because the system can no longer function correctly without a core system process. PM CHANGES: - The process tree built at initialization time is changed to have INIT as root with pid 0, RS child of INIT and all the system services children of RS. This is required to make RS in control of all the system services. - PM no longer registers labels for system services in the boot image. This is now part of RS's initialization process.
2009-12-11 01:08:19 +01:00
int sn; /* signal number */
2005-04-21 16:53:53 +02:00
struct slotent *slotp; /* slots[] pointer */
struct ttyent *ttyp; /* ttytab entry */
struct sigaction sa;
struct stat stb;
#define OPENFDS \
if (fstat(0, &stb) < 0) { \
/* Open standard input, output & error. */ \
(void) open("/dev/null", O_RDONLY); \
(void) open("/dev/log", O_WRONLY); \
dup(1); \
2005-04-21 16:53:53 +02:00
}
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
Rewrite of boot process KERNEL CHANGES: - The kernel only knows about privileges of kernel tasks and the root system process (now RS). - Kernel tasks and the root system process are the only processes that are made schedulable by the kernel at startup. All the other processes in the boot image don't get their privileges set at startup and are inhibited from running by the RTS_NO_PRIV flag. - Removed the assumption on the ordering of processes in the boot image table. System processes can now appear in any order in the boot image table. - Privilege ids can now be assigned both statically or dynamically. The kernel assigns static privilege ids to kernel tasks and the root system process. Each id is directly derived from the process number. - User processes now all share the static privilege id of the root user process (now INIT). - sys_privctl split: we have more calls now to let RS set privileges for system processes. SYS_PRIV_ALLOW / SYS_PRIV_DISALLOW are only used to flip the RTS_NO_PRIV flag and allow / disallow a process from running. SYS_PRIV_SET_SYS / SYS_PRIV_SET_USER are used to set privileges for a system / user process. - boot image table flags split: PROC_FULLVM is the only flag that has been moved out of the privilege flags and is still maintained in the boot image table. All the other privilege flags are out of the kernel now. RS CHANGES: - RS is the only user-space process who gets to run right after in-kernel startup. - RS uses the boot image table from the kernel and three additional boot image info table (priv table, sys table, dev table) to complete the initialization of the system. - RS checks that the entries in the priv table match the entries in the boot image table to make sure that every process in the boot image gets schedulable. - RS only uses static privilege ids to set privileges for system services in the boot image. - RS includes basic memory management support to allocate the boot image buffer dynamically during initialization. The buffer shall contain the executable image of all the system services we would like to restart after a crash. - First step towards decoupling between resource provisioning and resource requirements in RS: RS must know what resources it needs to restart a process and what resources it has currently available. This is useful to tradeoff reliability and resource consumption. When required resources are missing, the process cannot be restarted. In that case, in the future, a system flag will tell RS what to do. For example, if CORE_PROC is set, RS should trigger a system-wide panic because the system can no longer function correctly without a core system process. PM CHANGES: - The process tree built at initialization time is changed to have INIT as root with pid 0, RS child of INIT and all the system services children of RS. This is required to make RS in control of all the system services. - PM no longer registers labels for system services in the boot image. This is now part of RS's initialization process.
2009-12-11 01:08:19 +01:00
/* Default: Ignore every signal (except those that follow). */
sa.sa_handler = SIG_IGN;
for (sn = 1; sn < _NSIG; sn++) {
sigaction(sn, &sa, NULL);
}
2005-04-21 16:53:53 +02:00
/* Hangup: Reexamine /etc/ttytab for newly enabled terminal lines. */
sa.sa_handler = onhup;
sigaction(SIGHUP, &sa, NULL);
/* Terminate: Stop spawning login processes, shutdown is near. */
sa.sa_handler = onterm;
sigaction(SIGTERM, &sa, NULL);
/* Abort: Sent by the kernel on CTRL-ALT-DEL; shut the system down. */
sa.sa_handler = onabrt;
sigaction(SIGABRT, &sa, NULL);
/* Execute the /etc/rc file. */
if ((pid = fork()) != 0) {
/* Parent just waits. */
while (wait(NULL) != pid) {
if (gotabrt) reboot(RBT_HALT);
}
} else {
#if ! SYS_GETKENV
struct sysgetenv sysgetenv;
#endif
char bootopts[16];
static char *rc_command[] = { "sh", "/etc/rc", NULL, NULL, NULL };
char **rcp = rc_command + 2;
/* Get the boot options from the boot environment. */
sysgetenv.key = "bootopts";
sysgetenv.keylen = 8+1;
sysgetenv.val = bootopts;
sysgetenv.vallen = sizeof(bootopts);
if (svrctl(PMGETPARAM, &sysgetenv) == 0) *rcp++ = bootopts;
2005-04-21 16:53:53 +02:00
*rcp = "start";
execute(rc_command);
report(2, "sh /etc/rc");
_exit(1); /* impossible, we hope */
}
OPENFDS;
2005-04-21 16:53:53 +02:00
/* Clear /etc/utmp if it exists. */
if ((fd = open(PATH_UTMP, O_WRONLY | O_TRUNC)) >= 0) close(fd);
/* Log system reboot. */
wtmp(BOOT_TIME, 0, NULL, 0);
/* Main loop. If login processes have already been started up, wait for one
* to terminate, or for a HUP signal to arrive. Start up new login processes
* for all ttys which don't have them. Note that wait() also returns when
* somebody's orphan dies, in which case ignore it. If the TERM signal is
* sent then stop spawning processes, shutdown time is near.
*/
check = 1;
while (1) {
while ((pid = waitpid(-1, NULL, check ? WNOHANG : 0)) > 0) {
/* Search to see which line terminated. */
for (linenr = 0; linenr < PIDSLOTS; linenr++) {
slotp = &slots[linenr];
if (slotp->pid == pid) {
/* Record process exiting. */
wtmp(DEAD_PROCESS, linenr, NULL, pid);
slotp->pid = NO_PID;
check = 1;
}
}
}
/* If a signal 1 (SIGHUP) is received, simply reset error counts. */
if (gothup) {
gothup = 0;
for (linenr = 0; linenr < PIDSLOTS; linenr++) {
slots[linenr].errct = 0;
}
check = 1;
}
/* Shut down on signal 6 (SIGABRT). */
if (gotabrt) {
gotabrt = 0;
startup(0, &TT_REBOOT);
}
if (spawn && check) {
/* See which lines need a login process started up. */
for (linenr = 0; linenr < PIDSLOTS; linenr++) {
slotp = &slots[linenr];
if ((ttyp = getttyent()) == NULL) break;
if (ttyp->ty_getty != NULL
&& ttyp->ty_getty[0] != NULL
&& slotp->pid == NO_PID
&& slotp->errct < ERRCT_DISABLE)
{
startup(linenr, ttyp);
}
}
endttyent();
}
check = 0;
}
}
void onhup(int sig)
{
gothup = 1;
spawn = 1;
}
void onterm(int sig)
{
spawn = 0;
}
void onabrt(int sig)
{
static int count = 0;
2005-04-21 16:53:53 +02:00
if (++count == 2) reboot(RBT_HALT);
gotabrt = 1;
}
void startup(int linenr, struct ttyent *ttyp)
{
/* Fork off a process for the indicated line. */
struct slotent *slotp; /* pointer to ttyslot */
pid_t pid; /* new pid */
int err[2]; /* error reporting pipe */
char line[32]; /* tty device name */
int status;
slotp = &slots[linenr];
/* Error channel for between fork and exec. */
if (pipe(err) < 0) err[0] = err[1] = -1;
if ((pid = fork()) == -1 ) {
report(2, "fork()");
sleep(10);
return;
}
if (pid == 0) {
/* Child */
close(err[0]);
fcntl(err[1], F_SETFD, fcntl(err[1], F_GETFD) | FD_CLOEXEC);
/* A new session. */
setsid();
/* Construct device name. */
strcpy(line, "/dev/");
strncat(line, ttyp->ty_name, sizeof(line) - 6);
/* Open the line for standard input and output. */
close(0);
close(1);
if (open(line, O_RDWR) < 0 || dup(0) < 0) {
write(err[1], &errno, sizeof(errno));
_exit(1);
}
if (ttyp->ty_init != NULL && ttyp->ty_init[0] != NULL) {
/* Execute a command to initialize the terminal line. */
if ((pid = fork()) == -1) {
report(2, "fork()");
errno= 0;
write(err[1], &errno, sizeof(errno));
_exit(1);
}
if (pid == 0) {
alarm(10);
execute(ttyp->ty_init);
report(2, ttyp->ty_init[0]);
_exit(1);
}
while (waitpid(pid, &status, 0) != pid) {}
if (status != 0) {
tell(2, "init: ");
tell(2, ttyp->ty_name);
tell(2, ": ");
tell(2, ttyp->ty_init[0]);
tell(2, ": bad exit status\n");
errno = 0;
write(err[1], &errno, sizeof(errno));
_exit(1);
}
}
/* Redirect standard error too. */
dup2(0, 2);
/* Execute the getty process. */
execute(ttyp->ty_getty);
/* Oops, disaster strikes. */
fcntl(2, F_SETFL, fcntl(2, F_GETFL) | O_NONBLOCK);
if (linenr != 0) report(2, ttyp->ty_getty[0]);
write(err[1], &errno, sizeof(errno));
_exit(1);
}
/* Parent */
if (ttyp != &TT_REBOOT) slotp->pid = pid;
close(err[1]);
if (read(err[0], &errno, sizeof(errno)) != 0) {
/* If an errno value goes down the error pipe: Problems. */
switch (errno) {
case ENOENT:
case ENODEV:
case ENXIO:
/* Device nonexistent, no driver, or no minor device. */
slotp->errct = ERRCT_DISABLE;
close(err[0]);
return;
case 0:
/* Error already reported. */
break;
default:
/* Any other error on the line. */
report(2, ttyp->ty_name);
}
close(err[0]);
if (++slotp->errct >= ERRCT_DISABLE) {
tell(2, "init: ");
tell(2, ttyp->ty_name);
tell(2, ": excessive errors, shutting down\n");
} else {
sleep(5);
}
return;
}
close(err[0]);
if (ttyp != &TT_REBOOT) wtmp(LOGIN_PROCESS, linenr, ttyp->ty_name, pid);
slotp->errct = 0;
}
int execute(char **cmd)
{
/* Execute a command with a path search along /sbin:/bin:/usr/sbin:/usr/bin.
*/
static char *nullenv[] = { NULL };
char command[128];
char *path[] = { "/sbin", "/bin", "/usr/sbin", "/usr/bin" };
int i;
if (cmd[0][0] == '/') {
/* A full path. */
return execve(cmd[0], cmd, nullenv);
}
/* Path search. */
for (i = 0; i < 4; i++) {
if (strlen(path[i]) + 1 + strlen(cmd[0]) + 1 > sizeof(command)) {
errno= ENAMETOOLONG;
return -1;
}
strcpy(command, path[i]);
strcat(command, "/");
strcat(command, cmd[0]);
execve(command, cmd, nullenv);
if (errno != ENOENT) break;
}
return -1;
}
void wtmp(type, linenr, line, pid)
int type; /* type of entry */
int linenr; /* line number in ttytab */
char *line; /* tty name (only good on login) */
pid_t pid; /* pid of process */
{
/* Log an event into the UTMP and WTMP files. */
struct utmp utmp; /* UTMP/WTMP User Accounting */
int fd;
/* Clear the utmp record. */
memset((void *) &utmp, 0, sizeof(utmp));
/* Fill in utmp. */
switch (type) {
case BOOT_TIME:
/* Make a special reboot record. */
strcpy(utmp.ut_name, "reboot");
strcpy(utmp.ut_line, "~");
break;
case LOGIN_PROCESS:
/* A new login, fill in line name. */
strncpy(utmp.ut_line, line, sizeof(utmp.ut_line));
break;
case DEAD_PROCESS:
/* A logout. Use the current utmp entry, but make sure it is a
* user process exiting, and not getty or login giving up.
*/
if ((fd = open(PATH_UTMP, O_RDONLY)) < 0) {
if (errno != ENOENT) report(2, PATH_UTMP);
return;
}
if (lseek(fd, (off_t) (linenr+1) * sizeof(utmp), SEEK_SET) == -1
|| read(fd, &utmp, sizeof(utmp)) == -1
) {
report(2, PATH_UTMP);
close(fd);
return;
}
close(fd);
if (utmp.ut_type != USER_PROCESS) return;
strncpy(utmp.ut_name, "", sizeof(utmp.ut_name));
break;
}
/* Finish new utmp entry. */
utmp.ut_pid = pid;
utmp.ut_type = type;
utmp.ut_time = time((time_t *) 0);
switch (type) {
case LOGIN_PROCESS:
case DEAD_PROCESS:
/* Write new entry to utmp. */
if ((fd = open(PATH_UTMP, O_WRONLY)) < 0
|| lseek(fd, (off_t) (linenr+1) * sizeof(utmp), SEEK_SET) == -1
|| write(fd, &utmp, sizeof(utmp)) == -1
) {
if (errno != ENOENT) report(2, PATH_UTMP);
}
if (fd != -1) close(fd);
break;
}
switch (type) {
case BOOT_TIME:
case DEAD_PROCESS:
/* Add new wtmp entry. */
if ((fd = open(PATH_WTMP, O_WRONLY | O_APPEND)) < 0
|| write(fd, &utmp, sizeof(utmp)) == -1
) {
if (errno != ENOENT) report(2, PATH_WTMP);
}
if (fd != -1) close(fd);
break;
}
}
void tell(fd, s)
int fd;
char *s;
{
write(fd, s, strlen(s));
}
void report(fd, label)
int fd;
char *label;
{
int err = errno;
tell(fd, "init: ");
tell(fd, label);
tell(fd, ": ");
tell(fd, strerror(err));
tell(fd, "\n");
errno= err;
}