/* * Init A System-V Init Clone. * * Usage: /sbin/init * init [0123456SsQqAaBbCc] * telinit [0123456SsQqAaBbCc] * * Version: @(#)init.c 2.86 30-Jul-2004 miquels@cistron.nl */ #define VERSION "2.89" #define DATE "26-Mar-2010" /* * This file is part of the sysvinit suite, * Copyright (C) 1991-2004 Miquel van Smoorenburg. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #ifdef __linux__ #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef WITH_SELINUX # include #endif #ifdef __i386__ # ifdef __GLIBC__ /* GNU libc 2.x */ # define STACK_DEBUG 1 # if (__GLIBC__ == 2 && __GLIBC_MINOR__ == 0) /* Only glibc 2.0 needs this */ # include # elif ( __GLIBC__ > 2) && ((__GLIBC__ == 2) && (__GLIBC_MINOR__ >= 1)) # include # endif # endif #endif #include "init.h" #include "initreq.h" #include "paths.h" #include "reboot.h" #include "set.h" #ifndef SIGPWR # define SIGPWR SIGUSR2 #endif #ifndef CBAUD # define CBAUD 0 #endif #ifndef CBAUDEX # define CBAUDEX 0 #endif /* Set a signal handler. */ #define SETSIG(sa, sig, fun, flags) \ do { \ memset(&sa, 0, sizeof(sa)); \ sa.sa_handler = fun; \ sa.sa_flags = flags; \ sigemptyset(&sa.sa_mask); \ sigaction(sig, &sa, NULL); \ } while(0) /* Version information */ char *Version = "@(#) init " VERSION " " DATE " miquels@cistron.nl"; char *bootmsg = "version " VERSION " %s"; #define E_VERSION "INIT_VERSION=sysvinit-" VERSION CHILD *family = NULL; /* The linked list of all entries */ CHILD *newFamily = NULL; /* The list after inittab re-read */ CHILD ch_emerg = { /* Emergency shell */ WAITING, 0, 0, 0, 0, "~~", "S", 3, "/sbin/sulogin", NULL, NULL }; char runlevel = 'S'; /* The current run level */ char thislevel = 'S'; /* The current runlevel */ char prevlevel = 'N'; /* Previous runlevel */ int dfl_level = 0; /* Default runlevel */ sig_atomic_t got_cont = 0; /* Set if we received the SIGCONT signal */ sig_atomic_t got_signals; /* Set if we received a signal. */ int emerg_shell = 0; /* Start emergency shell? */ int wrote_wtmp_reboot = 1; /* Set when we wrote the reboot record */ int wrote_utmp_reboot = 1; /* Set when we wrote the reboot record */ int wrote_wtmp_rlevel = 1; /* Set when we wrote the runlevel record */ int wrote_utmp_rlevel = 1; /* Set when we wrote the runlevel record */ int sltime = 5; /* Sleep time between TERM and KILL */ char *argv0; /* First arguments; show up in ps listing */ int maxproclen; /* Maximal length of argv[0] with \0 */ struct utmp utproto; /* Only used for sizeof(utproto.ut_id) */ char *console_dev; /* Console device. */ int pipe_fd = -1; /* /run/initctl */ int did_boot = 0; /* Did we already do BOOT* stuff? */ int main(int, char **); /* Used by re-exec part */ int reload = 0; /* Should we do initialization stuff? */ char *myname="/sbin/init"; /* What should we exec */ int oops_error; /* Used by some of the re-exec code. */ const char *Signature = "12567362"; /* Signature for re-exec fd */ /* Macro to see if this is a special action */ #define ISPOWER(i) ((i) == POWERWAIT || (i) == POWERFAIL || \ (i) == POWEROKWAIT || (i) == POWERFAILNOW || \ (i) == CTRLALTDEL) /* ascii values for the `action' field. */ struct actions { char *name; int act; } actions[] = { { "respawn", RESPAWN }, { "wait", WAIT }, { "once", ONCE }, { "boot", BOOT }, { "bootwait", BOOTWAIT }, { "powerfail", POWERFAIL }, { "powerfailnow",POWERFAILNOW }, { "powerwait", POWERWAIT }, { "powerokwait", POWEROKWAIT }, { "ctrlaltdel", CTRLALTDEL }, { "off", OFF }, { "ondemand", ONDEMAND }, { "initdefault", INITDEFAULT }, { "sysinit", SYSINIT }, { "kbrequest", KBREQUEST }, { NULL, 0 }, }; /* * State parser token table (see receive_state) */ struct { char name[4]; int cmd; } cmds[] = { { "VER", C_VER }, { "END", C_END }, { "REC", C_REC }, { "EOR", C_EOR }, { "LEV", C_LEV }, { "FL ", C_FLAG }, { "AC ", C_ACTION }, { "CMD", C_PROCESS }, { "PID", C_PID }, { "EXS", C_EXS }, { "-RL", D_RUNLEVEL }, { "-TL", D_THISLEVEL }, { "-PL", D_PREVLEVEL }, { "-SI", D_GOTSIGN }, { "-WR", D_WROTE_WTMP_REBOOT}, { "-WU", D_WROTE_UTMP_REBOOT}, { "-ST", D_SLTIME }, { "-DB", D_DIDBOOT }, { "-LW", D_WROTE_WTMP_RLEVEL}, { "-LU", D_WROTE_UTMP_RLEVEL}, { "", 0 } }; struct { char *name; int mask; } flags[]={ {"RU",RUNNING}, {"DE",DEMAND}, {"XD",XECUTED}, {"WT",WAITING}, {NULL,0} }; #define NR_EXTRA_ENV 16 char *extra_env[NR_EXTRA_ENV]; /* * Sleep a number of seconds. * * This only works correctly because the linux select updates * the elapsed time in the struct timeval passed to select! */ static void do_sleep(int sec) { struct timeval tv; tv.tv_sec = sec; tv.tv_usec = 0; while(select(0, NULL, NULL, NULL, &tv) < 0 && errno == EINTR) ; } /* * Non-failing allocation routines (init cannot fail). */ static void *imalloc(size_t size) { void *m; while ((m = malloc(size)) == NULL) { initlog(L_VB, "out of memory"); do_sleep(5); } memset(m, 0, size); return m; } static char *istrdup(const char *s) { char *m; int l; l = strlen(s) + 1; m = imalloc(l); memcpy(m, s, l); return m; } /* * Send the state info of the previous running init to * the new one, in a version-independant way. */ static void send_state(int fd) { FILE *fp; CHILD *p; int i,val; fp = fdopen(fd,"w"); fprintf(fp, "VER%s\n", Version); fprintf(fp, "-RL%c\n", runlevel); fprintf(fp, "-TL%c\n", thislevel); fprintf(fp, "-PL%c\n", prevlevel); fprintf(fp, "-SI%u\n", got_signals); fprintf(fp, "-WR%d\n", wrote_wtmp_reboot); fprintf(fp, "-WU%d\n", wrote_utmp_reboot); fprintf(fp, "-ST%d\n", sltime); fprintf(fp, "-DB%d\n", did_boot); for (p = family; p; p = p->next) { fprintf(fp, "REC%s\n", p->id); fprintf(fp, "LEV%s\n", p->rlevel); for (i = 0, val = p->flags; flags[i].mask; i++) if (val & flags[i].mask) { val &= ~flags[i].mask; fprintf(fp, "FL %s\n",flags[i].name); } fprintf(fp, "PID%d\n",p->pid); fprintf(fp, "EXS%u\n",p->exstat); for(i = 0; actions[i].act; i++) if (actions[i].act == p->action) { fprintf(fp, "AC %s\n", actions[i].name); break; } fprintf(fp, "CMD%s\n", p->process); fprintf(fp, "EOR\n"); } fprintf(fp, "END\n"); fclose(fp); } /* * Read a string from a file descriptor. * FIXME: why not use fgets() ? */ static int get_string(char *p, int size, FILE *f) { int c; while ((c = getc(f)) != EOF && c != '\n') { if (--size > 0) *p++ = c; } *p = '\0'; return (c != EOF) && (size > 0); } /* * Read trailing data from the state pipe until we see a newline. */ static int get_void(FILE *f) { int c; while ((c = getc(f)) != EOF && c != '\n') ; return (c != EOF); } /* * Read the next "command" from the state pipe. */ static int get_cmd(FILE *f) { char cmd[4] = " "; int i; if (fread(cmd, 1, sizeof(cmd) - 1, f) != sizeof(cmd) - 1) return C_EOF; for(i = 0; cmds[i].cmd && strcmp(cmds[i].name, cmd) != 0; i++) ; return cmds[i].cmd; } /* * Read a CHILD * from the state pipe. */ static CHILD *get_record(FILE *f) { int cmd; char s[32]; int i; CHILD *p; do { errno = 0; switch (cmd = get_cmd(f)) { case C_END: get_void(f); return NULL; case 0: get_void(f); break; case C_REC: break; case D_RUNLEVEL: if (fscanf(f, "%c\n", &runlevel) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_THISLEVEL: if (fscanf(f, "%c\n", &thislevel) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_PREVLEVEL: if (fscanf(f, "%c\n", &prevlevel) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_GOTSIGN: if (fscanf(f, "%u\n", &got_signals) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_WROTE_WTMP_REBOOT: if (fscanf(f, "%d\n", &wrote_wtmp_reboot) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_WROTE_UTMP_REBOOT: if (fscanf(f, "%d\n", &wrote_utmp_reboot) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_SLTIME: if (fscanf(f, "%d\n", &sltime) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_DIDBOOT: if (fscanf(f, "%d\n", &did_boot) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_WROTE_WTMP_RLEVEL: if (fscanf(f, "%d\n", &wrote_wtmp_rlevel) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case D_WROTE_UTMP_RLEVEL: if (fscanf(f, "%d\n", &wrote_utmp_rlevel) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; default: if (cmd > 0 || cmd == C_EOF) { oops_error = -1; return NULL; } } } while (cmd != C_REC); p = imalloc(sizeof(CHILD)); get_string(p->id, sizeof(p->id), f); do switch(cmd = get_cmd(f)) { case 0: case C_EOR: get_void(f); break; case C_PID: if (fscanf(f, "%d\n", &(p->pid)) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case C_EXS: if (fscanf(f, "%u\n", &(p->exstat)) == EOF && errno != 0) { fprintf(stderr, "%s (%d): %s\n", __FILE__, __LINE__, strerror(errno)); } break; case C_LEV: get_string(p->rlevel, sizeof(p->rlevel), f); break; case C_PROCESS: get_string(p->process, sizeof(p->process), f); break; case C_FLAG: get_string(s, sizeof(s), f); for(i = 0; flags[i].name; i++) { if (strcmp(flags[i].name,s) == 0) break; } p->flags |= flags[i].mask; break; case C_ACTION: get_string(s, sizeof(s), f); for(i = 0; actions[i].name; i++) { if (strcmp(actions[i].name, s) == 0) break; } p->action = actions[i].act ? actions[i].act : OFF; break; default: free(p); oops_error = -1; return NULL; } while( cmd != C_EOR); return p; } /* * Read the complete state info from the state pipe. * Returns 0 on success */ static int receive_state(int fd) { FILE *f; char old_version[256]; CHILD **pp; f = fdopen(fd, "r"); if (get_cmd(f) != C_VER) { fclose(f); return -1; } get_string(old_version, sizeof(old_version), f); oops_error = 0; for (pp = &family; (*pp = get_record(f)) != NULL; pp = &((*pp)->next)) ; fclose(f); return oops_error; } /* * Set the process title. */ #ifdef __GNUC__ __attribute__ ((format (printf, 1, 2))) #endif static int setproctitle(char *fmt, ...) { va_list ap; int len; char buf[256]; buf[0] = 0; va_start(ap, fmt); len = vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); if (maxproclen > 1) { memset(argv0, 0, maxproclen); strncpy(argv0, buf, maxproclen - 1); } return len; } /* * Set console_dev to a working console. */ static void console_init(void) { int fd; int tried_devcons = 0; int tried_vtmaster = 0; char *s; if ((s = getenv("CONSOLE")) != NULL) console_dev = s; else { console_dev = CONSOLE; tried_devcons++; } while ((fd = open(console_dev, O_RDONLY|O_NONBLOCK)) < 0) { if (!tried_devcons) { tried_devcons++; console_dev = CONSOLE; continue; } if (!tried_vtmaster) { tried_vtmaster++; console_dev = VT_MASTER; continue; } break; } if (fd < 0) console_dev = "/dev/null"; else close(fd); } /* * Open the console with retries. */ static int console_open(int mode) { int f, fd = -1; int m; /* * Open device in nonblocking mode. */ m = mode | O_NONBLOCK; /* * Retry the open five times. */ for(f = 0; f < 5; f++) { if ((fd = open(console_dev, m)) >= 0) break; usleep(10000); } if (fd < 0) return fd; /* * Set original flags. */ if (m != mode) fcntl(fd, F_SETFL, mode); return fd; } /* * We got a signal (HUP PWR WINCH ALRM INT) */ static void signal_handler(int sig) { ADDSET(got_signals, sig); } /* * SIGCHLD: one of our children has died. */ static # ifdef __GNUC__ void chld_handler(int sig __attribute__((unused))) # else void chld_handler(int sig) # endif { CHILD *ch; int pid, st; int saved_errno = errno; /* * Find out which process(es) this was (were) */ while((pid = waitpid(-1, &st, WNOHANG)) != 0) { if (errno == ECHILD) break; for( ch = family; ch; ch = ch->next ) if ( ch->pid == pid && (ch->flags & RUNNING) ) { INITDBG(L_VB, "chld_handler: marked %d as zombie", ch->pid); ADDSET(got_signals, SIGCHLD); ch->exstat = st; ch->flags |= ZOMBIE; if (ch->new) { ch->new->exstat = st; ch->new->flags |= ZOMBIE; } break; } if (ch == NULL) { INITDBG(L_VB, "chld_handler: unknown child %d exited.", pid); } } errno = saved_errno; } /* * Linux ignores all signals sent to init when the * SIG_DFL handler is installed. Therefore we must catch SIGTSTP * and SIGCONT, or else they won't work.... * * The SIGCONT handler */ static # ifdef __GNUC__ void cont_handler(int sig __attribute__((unused))) # else void cont_handler(int sig) # endif { got_cont = 1; } /* * Fork and dump core in /. */ static void coredump(void) { static int dumped = 0; struct rlimit rlim; sigset_t mask; if (dumped) return; dumped = 1; if (fork() != 0) return; sigfillset(&mask); sigprocmask(SIG_SETMASK, &mask, NULL); rlim.rlim_cur = RLIM_INFINITY; rlim.rlim_max = RLIM_INFINITY; setrlimit(RLIMIT_CORE, &rlim); if (0 != chdir("/")) initlog(L_VB, "unable to chdir to /: %s", strerror(errno)); signal(SIGSEGV, SIG_DFL); raise(SIGSEGV); sigdelset(&mask, SIGSEGV); sigprocmask(SIG_SETMASK, &mask, NULL); do_sleep(5); exit(0); } /* * OOPS: segmentation violation! * If we have the info, print where it occured. * Then sleep 30 seconds and try to continue. */ static #if defined(STACK_DEBUG) && defined(__linux__) # ifdef __GNUC__ void segv_handler(int sig __attribute__((unused)), struct sigcontext ctx) # else void segv_handler(int sig, struct sigcontext ctx) # endif { char *p = ""; int saved_errno = errno; if ((void *)ctx.eip >= (void *)do_sleep && (void *)ctx.eip < (void *)main) p = " (code)"; initlog(L_VB, "PANIC: segmentation violation at %p%s! " "sleeping for 30 seconds.", (void *)ctx.eip, p); coredump(); do_sleep(30); errno = saved_errno; } #else # ifdef __GNUC__ void segv_handler(int sig __attribute__((unused))) # else void segv_handler(int sig) # endif { int saved_errno = errno; initlog(L_VB, "PANIC: segmentation violation! sleeping for 30 seconds."); coredump(); do_sleep(30); errno = saved_errno; } #endif /* * The SIGSTOP & SIGTSTP handler */ static # ifdef __GNUC__ void stop_handler(int sig __attribute__((unused))) # else void stop_handler(int sig) # endif { int saved_errno = errno; got_cont = 0; while(!got_cont) pause(); got_cont = 0; errno = saved_errno; } /* * Set terminal settings to reasonable defaults */ static void console_stty(void) { struct termios tty; int fd; if ((fd = console_open(O_RDWR|O_NOCTTY)) < 0) { initlog(L_VB, "can't open %s", console_dev); return; } #ifdef __FreeBSD_kernel__ /* * The kernel of FreeBSD expects userland to set TERM. Usually, we want * "xterm". Later, gettys might disagree on this (i.e. we're not using * syscons) but some boot scripts, like /etc/init.d/xserver-xorg, still * need a non-dumb terminal. */ putenv ("TERM=xterm"); #endif (void) tcgetattr(fd, &tty); tty.c_cflag &= CBAUD|CBAUDEX|CSIZE|CSTOPB|PARENB|PARODD; tty.c_cflag |= HUPCL|CLOCAL|CREAD; tty.c_cc[VINTR] = CINTR; tty.c_cc[VQUIT] = CQUIT; tty.c_cc[VERASE] = CERASE; /* ASCII DEL (0177) */ tty.c_cc[VKILL] = CKILL; tty.c_cc[VEOF] = CEOF; tty.c_cc[VTIME] = 0; tty.c_cc[VMIN] = 1; #ifdef VSWTC /* not defined on FreeBSD */ tty.c_cc[VSWTC] = _POSIX_VDISABLE; #endif /* VSWTC */ tty.c_cc[VSTART] = CSTART; tty.c_cc[VSTOP] = CSTOP; tty.c_cc[VSUSP] = CSUSP; tty.c_cc[VEOL] = _POSIX_VDISABLE; tty.c_cc[VREPRINT] = CREPRINT; tty.c_cc[VDISCARD] = CDISCARD; tty.c_cc[VWERASE] = CWERASE; tty.c_cc[VLNEXT] = CLNEXT; tty.c_cc[VEOL2] = _POSIX_VDISABLE; /* * Set pre and post processing */ tty.c_iflag = IGNPAR|ICRNL|IXON|IXANY #ifdef IUTF8 /* Not defined on FreeBSD */ | (tty.c_iflag & IUTF8) #endif /* IUTF8 */ ; tty.c_oflag = OPOST|ONLCR; tty.c_lflag = ISIG|ICANON|ECHO|ECHOCTL|ECHOPRT|ECHOKE; #if defined(SANE_TIO) && (SANE_TIO == 1) /* * Disable flow control (-ixon), ignore break (ignbrk), * and make nl/cr more usable (sane). */ tty.c_iflag |= IGNBRK; tty.c_iflag &= ~(BRKINT|INLCR|IGNCR|IXON); tty.c_oflag &= ~(OCRNL|ONLRET); #endif /* * Now set the terminal line. * We don't care about non-transmitted output data * and non-read input data. */ (void) tcsetattr(fd, TCSANOW, &tty); (void) tcflush(fd, TCIOFLUSH); (void) close(fd); } static ssize_t safe_write(int fd, const char *buffer, size_t count) { ssize_t offset = 0; while (count > 0) { ssize_t block = write(fd, &buffer[offset], count); if (block < 0 && errno == EINTR) continue; if (block <= 0) return offset ? offset : block; offset += block; count -= block; } return offset; } /* * Print to the system console */ void print(char *s) { int fd; if ((fd = console_open(O_WRONLY|O_NOCTTY|O_NDELAY)) >= 0) { safe_write(fd, s, strlen(s)); close(fd); } } /* * Log something to a logfile and the console. */ #ifdef __GNUC__ __attribute__ ((format (printf, 2, 3))) #endif void initlog(int loglevel, char *s, ...) { va_list va_alist; char buf[256]; sigset_t nmask, omask; va_start(va_alist, s); vsnprintf(buf, sizeof(buf), s, va_alist); va_end(va_alist); if (loglevel & L_SY) { /* * Re-establish connection with syslogd every time. * Block signals while talking to syslog. */ sigfillset(&nmask); sigprocmask(SIG_BLOCK, &nmask, &omask); openlog("init", 0, LOG_DAEMON); syslog(LOG_INFO, "%s", buf); closelog(); sigprocmask(SIG_SETMASK, &omask, NULL); } /* * And log to the console. */ if (loglevel & L_CO) { print("\rINIT: "); print(buf); print("\r\n"); } } /* * Add or replace specific environment value */ int addnewenv(const char *new, char **curr, int n) { size_t nlen = strcspn(new, "="); int i; for (i = 0; i < n; i++) { if (nlen != strcspn(curr[i], "=")) continue; if (strncmp (new, curr[i], nlen) == 0) break; } if (i >= n) curr[n++] = istrdup(new); else { free(curr[i]); curr[i] = istrdup(new); } return n; } /* * Build a new environment for execve(). */ char **init_buildenv(int child) { char i_lvl[] = "RUNLEVEL=x"; char i_prev[] = "PREVLEVEL=x"; char i_cons[128]; char i_shell[] = "SHELL=" SHELL; char **e; int n, i; for (n = 0; environ[n]; n++) ; n += NR_EXTRA_ENV + 1; /* Also room for last NULL */ if (child) n += 8; while ((e = (char**)calloc(n, sizeof(char *))) == NULL) { initlog(L_VB, "out of memory"); do_sleep(5); } for (n = 0; environ[n]; n++) e[n] = istrdup(environ[n]); for (i = 0; i < NR_EXTRA_ENV; i++) { if (extra_env[i] == NULL || *extra_env[i] == '\0') continue; n = addnewenv(extra_env[i], e, n); } if (child) { snprintf(i_cons, sizeof(i_cons), "CONSOLE=%s", console_dev); i_lvl[9] = thislevel; i_prev[10] = prevlevel; n = addnewenv(i_shell, e, n); n = addnewenv(i_lvl, e, n); n = addnewenv(i_prev, e, n); n = addnewenv(i_cons, e, n); n = addnewenv(E_VERSION, e, n); } e[n++] = NULL; return e; } void init_freeenv(char **e) { int n; for (n = 0; e[n]; n++) free(e[n]); free(e); } /* * Fork and execute. * * This function is too long and indents too deep. * */ static pid_t spawn(CHILD *ch, int *res) { char *args[16]; /* Argv array */ char buf[136]; /* Line buffer */ int f, st; /* Scratch variables */ char *ptr; /* Ditto */ time_t t; /* System time */ int oldAlarm; /* Previous alarm value */ char *proc = ch->process; /* Command line */ pid_t pid, pgrp; /* child, console process group. */ sigset_t nmask, omask; /* For blocking SIGCHLD */ struct sigaction sa; *res = -1; buf[sizeof(buf) - 1] = 0; /* Skip '+' if it's there */ if (proc[0] == '+') proc++; ch->flags |= XECUTED; if (ch->action == RESPAWN || ch->action == ONDEMAND) { /* Is the date stamp from less than 2 minutes ago? */ time(&t); if (ch->tm + TESTTIME > t) { ch->count++; } else { ch->count = 0; ch->tm = t; } /* Do we try to respawn too fast? */ if (ch->count >= MAXSPAWN) { initlog(L_VB, "Id \"%s\" respawning too fast: disabled for %d minutes", ch->id, SLEEPTIME / 60); ch->flags &= ~RUNNING; ch->flags |= FAILING; /* Remember the time we stopped */ ch->tm = t; /* Try again in 5 minutes */ oldAlarm = alarm(0); if (oldAlarm > SLEEPTIME || oldAlarm <= 0) oldAlarm = SLEEPTIME; alarm(oldAlarm); return(-1); } } /* See if there is an "initscript" (except in single user mode). */ if (access(INITSCRIPT, R_OK) == 0 && runlevel != 'S') { /* Build command line using "initscript" */ args[1] = SHELL; args[2] = INITSCRIPT; args[3] = ch->id; args[4] = ch->rlevel; args[5] = "unknown"; for(f = 0; actions[f].name; f++) { if (ch->action == actions[f].act) { args[5] = actions[f].name; break; } } args[6] = proc; args[7] = NULL; } else if (strpbrk(proc, "~`!$^&*()=|\\{}[];\"'<>?")) { /* See if we need to fire off a shell for this command */ /* Give command line to shell */ args[1] = SHELL; args[2] = "-c"; strcpy(buf, "exec "); strncat(buf, proc, sizeof(buf) - strlen(buf) - 1); args[3] = buf; args[4] = NULL; } else { /* Split up command line arguments */ buf[0] = 0; strncat(buf, proc, sizeof(buf) - 1); ptr = buf; for(f = 1; f < 15; f++) { /* Skip white space */ while(*ptr == ' ' || *ptr == '\t') ptr++; args[f] = ptr; /* May be trailing space.. */ if (*ptr == 0) break; /* Skip this `word' */ while(*ptr && *ptr != ' ' && *ptr != '\t' && *ptr != '#') ptr++; /* If end-of-line, break */ if (*ptr == '#' || *ptr == 0) { f++; *ptr = 0; break; } /* End word with \0 and continue */ *ptr++ = 0; } args[f] = NULL; } args[0] = args[1]; while(1) { /* * Block sigchild while forking. */ sigemptyset(&nmask); sigaddset(&nmask, SIGCHLD); sigprocmask(SIG_BLOCK, &nmask, &omask); if ((pid = fork()) == 0) { close(0); close(1); close(2); if (pipe_fd >= 0) close(pipe_fd); sigprocmask(SIG_SETMASK, &omask, NULL); /* * In sysinit, boot, bootwait or single user mode: * for any wait-type subprocess we _force_ the console * to be its controlling tty. */ if (strchr("*#sS", runlevel) && ch->flags & WAITING) { int ftty; /* Handler for tty controlling */ /* * We fork once extra. This is so that we can * wait and change the process group and session * of the console after exit of the leader. */ setsid(); if ((ftty = console_open(O_RDWR|O_NOCTTY)) >= 0) { /* Take over controlling tty by force */ (void)ioctl(ftty, TIOCSCTTY, 1); if(dup(ftty) < 0){ initlog(L_VB, "cannot duplicate console fd"); } if(dup(ftty) < 0){ initlog(L_VB, "cannot duplicate console fd"); } } /* * 4 Sep 2001, Andrea Arcangeli: * Fix a race in spawn() that is used to deadlock init in a * waitpid() loop: must set the childhandler as default before forking * off the child or the chld_handler could run before the waitpid loop * has a chance to find its zombie-child. */ SETSIG(sa, SIGCHLD, SIG_DFL, SA_RESTART); if ((pid = fork()) < 0) { initlog(L_VB, "cannot fork: %s", strerror(errno)); exit(1); } if (pid > 0) { pid_t rc; /* * Ignore keyboard signals etc. * Then wait for child to exit. */ SETSIG(sa, SIGINT, SIG_IGN, SA_RESTART); SETSIG(sa, SIGTSTP, SIG_IGN, SA_RESTART); SETSIG(sa, SIGQUIT, SIG_IGN, SA_RESTART); while ((rc = waitpid(pid, &st, 0)) != pid) if (rc < 0 && errno == ECHILD) break; /* * Small optimization. See if stealing * controlling tty back is needed. */ pgrp = tcgetpgrp(ftty); if (pgrp != getpid()) exit(0); /* * Steal controlling tty away. We do * this with a temporary process. */ if ((pid = fork()) < 0) { initlog(L_VB, "cannot fork: %s", strerror(errno)); exit(1); } if (pid == 0) { setsid(); (void)ioctl(ftty, TIOCSCTTY, 1); exit(0); } while((rc = waitpid(pid, &st, 0)) != pid) if (rc < 0 && errno == ECHILD) break; exit(0); } /* Set ioctl settings to default ones */ console_stty(); } else { /* parent */ int fd; setsid(); if ((fd = console_open(O_RDWR|O_NOCTTY)) < 0) { initlog(L_VB, "open(%s): %s", console_dev, strerror(errno)); fd = open("/dev/null", O_RDWR); } if(dup(fd) < 0) { initlog(L_VB, "cannot duplicate /dev/null fd"); } if(dup(fd) < 0) { initlog(L_VB, "cannot duplicate /dev/null fd"); } } /* * Update utmp/wtmp file prior to starting * any child. This MUST be done right here in * the child process in order to prevent a race * condition that occurs when the child * process' time slice executes before the * parent (can and does happen in a uniprocessor * environment). If the child is a getty and * the race condition happens, then init's utmp * update will happen AFTER the getty runs * and expects utmp to be updated already! * * Do NOT log if process field starts with '+' * FIXME: that's for compatibility with *very* * old getties - probably it can be taken out. */ if (ch->process[0] != '+') write_utmp_wtmp("", ch->id, getpid(), INIT_PROCESS, ""); /* Reset all the signals, set up environment */ for(f = 1; f < NSIG; f++) SETSIG(sa, f, SIG_DFL, SA_RESTART); environ = init_buildenv(1); /* * Execute prog. In case of ENOEXEC try again * as a shell script. */ execvp(args[1], args + 1); if (errno == ENOEXEC) { args[1] = SHELL; args[2] = "-c"; strcpy(buf, "exec "); strncat(buf, proc, sizeof(buf) - strlen(buf) - 1); args[3] = buf; args[4] = NULL; execvp(args[1], args + 1); } initlog(L_VB, "cannot execute \"%s\"", args[1]); if (ch->process[0] != '+') write_utmp_wtmp("", ch->id, getpid(), DEAD_PROCESS, NULL); exit(1); } *res = pid; sigprocmask(SIG_SETMASK, &omask, NULL); INITDBG(L_VB, "Started id %s (pid %d)", ch->id, pid); if (pid == -1) { initlog(L_VB, "cannot fork, retry.."); do_sleep(5); continue; } return(pid); } } /* * Start a child running! */ static void startup(CHILD *ch) { /* * See if it's disabled */ if (ch->flags & FAILING) return; switch(ch->action) { case SYSINIT: case BOOTWAIT: case WAIT: case POWERWAIT: case POWERFAILNOW: case POWEROKWAIT: case CTRLALTDEL: if (!(ch->flags & XECUTED)) ch->flags |= WAITING; /* Fall through */ case KBREQUEST: case BOOT: case POWERFAIL: case ONCE: if (ch->flags & XECUTED) break; case ONDEMAND: case RESPAWN: ch->flags |= RUNNING; (void)spawn(ch, &(ch->pid)); break; } } #ifdef __linux__ static void check_kernel_console() { FILE* fp; char buf[4096]; if ((fp = fopen("/proc/cmdline", "r")) == 0) { return; } if (fgets(buf, sizeof(buf), fp)) { char* p = buf; while ((p = strstr(p, "console="))) { char* e; p += strlen("console="); for (e = p; *e; ++e) { switch (*e) { case '-' ... '9': case 'A' ... 'Z': case '_': case 'a' ... 'z': continue; } break; } if (p != e) { CHILD* old; int dup = 0; char id[8] = {0}; char dev[32] = {0}; strncpy(dev, p, MIN(sizeof(dev), (unsigned)(e-p))); if (!strncmp(dev, "tty", 3)) strncpy(id, dev+3, sizeof(id)); else strncpy(id, dev, sizeof(id)); for(old = newFamily; old; old = old->next) { if (!strcmp(old->id, id)) { dup = 1; } } if (!dup) { CHILD* ch = imalloc(sizeof(CHILD)); ch->action = RESPAWN; strcpy(ch->id, id); strcpy(ch->rlevel, "2345"); sprintf(ch->process, "/sbin/agetty -L -s 115200,38400,9600 %s vt102", dev); ch->next = NULL; for(old = family; old; old = old->next) { if (strcmp(old->id, ch->id) == 0) { old->new = ch; break; } } /* add to end */ for(old = newFamily; old; old = old->next) { if (!old->next) { old->next = ch; break; } } initlog(L_VB, "added agetty on %s with id %s", dev, id); } } } } fclose(fp); return; } #endif /* * Read the inittab file. */ static void read_inittab(void) { FILE *fp; /* The INITTAB file */ CHILD *ch, *old, *i; /* Pointers to CHILD structure */ CHILD *head = NULL; /* Head of linked list */ #ifdef INITLVL struct stat st; /* To stat INITLVL */ #endif sigset_t nmask, omask; /* For blocking SIGCHLD. */ char buf[256]; /* Line buffer */ char err[64]; /* Error message. */ char *id, *rlevel, *action, *process; /* Fields of a line */ char *p; int lineNo = 0; /* Line number in INITTAB file */ int actionNo; /* Decoded action field */ int f; /* Counter */ int round; /* round 0 for SIGTERM, 1 for SIGKILL */ int foundOne = 0; /* No killing no sleep */ int talk; /* Talk to the user */ int done = 0; /* Ready yet? */ #if DEBUG if (newFamily != NULL) { INITDBG(L_VB, "PANIC newFamily != NULL"); exit(1); } INITDBG(L_VB, "Reading inittab"); #endif /* * Open INITTAB and read line by line. */ if ((fp = fopen(INITTAB, "r")) == NULL) initlog(L_VB, "No inittab file found"); while(!done) { /* * Add single user shell entry at the end. */ if (fp == NULL || fgets(buf, sizeof(buf), fp) == NULL) { done = 1; /* * See if we have a single user entry. */ for(old = newFamily; old; old = old->next) if (strpbrk(old->rlevel, "S")) break; if (old == NULL) snprintf(buf, sizeof(buf), "~~:S:wait:%s\n", SULOGIN); else continue; } lineNo++; /* * Skip comments and empty lines */ for(p = buf; *p == ' ' || *p == '\t'; p++) ; if (*p == '#' || *p == '\n') continue; /* * Decode the fields */ id = strsep(&p, ":"); rlevel = strsep(&p, ":"); action = strsep(&p, ":"); process = strsep(&p, "\n"); /* * Check if syntax is OK. Be very verbose here, to * avoid newbie postings on comp.os.linux.setup :) */ err[0] = 0; if (!id || !*id) strcpy(err, "missing id field"); if (!rlevel) strcpy(err, "missing runlevel field"); if (!process) strcpy(err, "missing process field"); if (!action || !*action) strcpy(err, "missing action field"); if (id && strlen(id) > sizeof(utproto.ut_id)) sprintf(err, "id field too long (max %d characters)", (int)sizeof(utproto.ut_id)); if (rlevel && strlen(rlevel) > 11) strcpy(err, "rlevel field too long (max 11 characters)"); if (process && strlen(process) > 127) strcpy(err, "process field too long (max 127 characters)"); if (action && strlen(action) > 32) strcpy(err, "action field too long"); if (err[0] != 0) { initlog(L_VB, "%s[%d]: %s", INITTAB, lineNo, err); INITDBG(L_VB, "%s:%s:%s:%s", id, rlevel, action, process); continue; } /* * Decode the "action" field */ actionNo = -1; for(f = 0; actions[f].name; f++) if (strcasecmp(action, actions[f].name) == 0) { actionNo = actions[f].act; break; } if (actionNo == -1) { initlog(L_VB, "%s[%d]: %s: unknown action field", INITTAB, lineNo, action); continue; } /* * See if the id field is unique */ for(old = newFamily; old; old = old->next) { if(strcmp(old->id, id) == 0 && strcmp(id, "~~")) { initlog(L_VB, "%s[%d]: duplicate ID field \"%s\"", INITTAB, lineNo, id); break; } } if (old) continue; /* * Allocate a CHILD structure */ ch = imalloc(sizeof(CHILD)); /* * And fill it in. */ ch->action = actionNo; strncpy(ch->id, id, sizeof(utproto.ut_id) + 1); /* Hack for different libs. */ strncpy(ch->process, process, sizeof(ch->process) - 1); if (rlevel[0]) { for(f = 0; f < (int)sizeof(rlevel) - 1 && rlevel[f]; f++) { ch->rlevel[f] = rlevel[f]; if (ch->rlevel[f] == 's') ch->rlevel[f] = 'S'; } strncpy(ch->rlevel, rlevel, sizeof(ch->rlevel) - 1); } else { strcpy(ch->rlevel, "0123456789"); if (ISPOWER(ch->action)) strcpy(ch->rlevel, "S0123456789"); } /* * We have the fake runlevel '#' for SYSINIT and * '*' for BOOT and BOOTWAIT. */ if (ch->action == SYSINIT) strcpy(ch->rlevel, "#"); if (ch->action == BOOT || ch->action == BOOTWAIT) strcpy(ch->rlevel, "*"); /* * Now add it to the linked list. Special for powerfail. */ if (ISPOWER(ch->action)) { /* * Disable by default */ ch->flags |= XECUTED; /* * Tricky: insert at the front of the list.. */ old = NULL; for(i = newFamily; i; i = i->next) { if (!ISPOWER(i->action)) break; old = i; } /* * Now add after entry "old" */ if (old) { ch->next = i; old->next = ch; if (i == NULL) head = ch; } else { ch->next = newFamily; newFamily = ch; if (ch->next == NULL) head = ch; } } else { /* * Just add at end of the list */ if (ch->action == KBREQUEST) ch->flags |= XECUTED; ch->next = NULL; if (head) head->next = ch; else newFamily = ch; head = ch; } /* * Walk through the old list comparing id fields */ for(old = family; old; old = old->next) if (strcmp(old->id, ch->id) == 0) { old->new = ch; break; } } /* * We're done. */ if (fp) fclose(fp); #ifdef __linux__ check_kernel_console(); #endif /* * Loop through the list of children, and see if they need to * be killed. */ INITDBG(L_VB, "Checking for children to kill"); for(round = 0; round < 2; round++) { talk = 1; for(ch = family; ch; ch = ch->next) { ch->flags &= ~KILLME; /* * Is this line deleted? */ if (ch->new == NULL) ch->flags |= KILLME; /* * If the entry has changed, kill it anyway. Note that * we do not check ch->process, only the "action" field. * This way, you can turn an entry "off" immediately, but * changes in the command line will only become effective * after the running version has exited. */ if (ch->new && ch->action != ch->new->action) ch->flags |= KILLME; /* * Only BOOT processes may live in all levels */ if (ch->action != BOOT && strchr(ch->rlevel, runlevel) == NULL) { /* * Ondemand procedures live always, * except in single user */ if (runlevel == 'S' || !(ch->flags & DEMAND)) ch->flags |= KILLME; } /* * Now, if this process may live note so in the new list */ if ((ch->flags & KILLME) == 0) { ch->new->flags = ch->flags; ch->new->pid = ch->pid; ch->new->exstat = ch->exstat; continue; } /* * Is this process still around? */ if ((ch->flags & RUNNING) == 0) { ch->flags &= ~KILLME; continue; } INITDBG(L_VB, "Killing \"%s\"", ch->process); switch(round) { case 0: /* Send TERM signal */ if (talk) initlog(L_CO, "Sending processes configured via /etc/inittab the TERM signal"); kill(-(ch->pid), SIGTERM); foundOne = 1; break; case 1: /* Send KILL signal and collect status */ if (talk) initlog(L_CO, "Sending processes configured via /etc/inittab the KILL signal"); kill(-(ch->pid), SIGKILL); break; } talk = 0; } /* * See if we have to wait 5 seconds */ if (foundOne && round == 0) { /* * Yup, but check every second if we still have children. */ for(f = 0; f < sltime; f++) { for(ch = family; ch; ch = ch->next) { if (!(ch->flags & KILLME)) continue; if ((ch->flags & RUNNING) && !(ch->flags & ZOMBIE)) break; } if (ch == NULL) { /* * No running children, skip SIGKILL */ round = 1; foundOne = 0; /* Skip the sleep below. */ break; } do_sleep(1); } } } /* * Now give all processes the chance to die and collect exit statuses. */ if (foundOne) do_sleep(1); for(ch = family; ch; ch = ch->next) if (ch->flags & KILLME) { if (!(ch->flags & ZOMBIE)) initlog(L_CO, "Pid %d [id %s] seems to hang", ch->pid, ch->id); else { INITDBG(L_VB, "Updating utmp for pid %d [id %s]", ch->pid, ch->id); ch->flags &= ~RUNNING; if (ch->process[0] != '+') write_utmp_wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL); } } /* * Both rounds done; clean up the list. */ sigemptyset(&nmask); sigaddset(&nmask, SIGCHLD); sigprocmask(SIG_BLOCK, &nmask, &omask); for(ch = family; ch; ch = old) { old = ch->next; free(ch); } family = newFamily; for(ch = family; ch; ch = ch->next) ch->new = NULL; newFamily = NULL; sigprocmask(SIG_SETMASK, &omask, NULL); #ifdef INITLVL /* * Dispose of INITLVL file. */ if (lstat(INITLVL, &st) >= 0 && S_ISLNK(st.st_mode)) { /* * INITLVL is a symbolic link, so just truncate the file. */ close(open(INITLVL, O_WRONLY|O_TRUNC)); } else { /* * Delete INITLVL file. */ unlink(INITLVL); } #endif #ifdef INITLVL2 /* * Dispose of INITLVL2 file. */ if (lstat(INITLVL2, &st) >= 0 && S_ISLNK(st.st_mode)) { /* * INITLVL2 is a symbolic link, so just truncate the file. */ close(open(INITLVL2, O_WRONLY|O_TRUNC)); } else { /* * Delete INITLVL2 file. */ unlink(INITLVL2); } #endif } /* * Walk through the family list and start up children. * The entries that do not belong here at all are removed * from the list. */ static void start_if_needed(void) { CHILD *ch; /* Pointer to child */ int delete; /* Delete this entry from list? */ INITDBG(L_VB, "Checking for children to start"); for(ch = family; ch; ch = ch->next) { #if DEBUG if (ch->rlevel[0] == 'C') { INITDBG(L_VB, "%s: flags %d", ch->process, ch->flags); } #endif /* Are we waiting for this process? Then quit here. */ if (ch->flags & WAITING) break; /* Already running? OK, don't touch it */ if (ch->flags & RUNNING) continue; /* See if we have to start it up */ delete = 1; if (strchr(ch->rlevel, runlevel) || ((ch->flags & DEMAND) && !strchr("#*Ss", runlevel))) { startup(ch); delete = 0; } if (delete) { /* FIXME: is this OK? */ ch->flags &= ~(RUNNING|WAITING); if (!ISPOWER(ch->action) && ch->action != KBREQUEST) ch->flags &= ~XECUTED; ch->pid = 0; } else /* Do we have to wait for this process? */ if (ch->flags & WAITING) break; } /* Done. */ } /* * Ask the user on the console for a runlevel */ static int ask_runlevel(void) { const char prompt[] = "\nEnter runlevel: "; char buf[8]; int lvl = -1; int fd; console_stty(); fd = console_open(O_RDWR|O_NOCTTY); if (fd < 0) return('S'); while(!strchr("0123456789S", lvl)) { safe_write(fd, prompt, sizeof(prompt) - 1); if (read(fd, buf, sizeof(buf)) <= 0) buf[0] = 0; if (buf[0] != 0 && (buf[1] == '\r' || buf[1] == '\n')) lvl = buf[0]; if (islower(lvl)) lvl = toupper(lvl); } close(fd); return lvl; } /* * Search the INITTAB file for the 'initdefault' field, with the default * runlevel. If this fails, ask the user to supply a runlevel. */ static int get_init_default(void) { CHILD *ch; int lvl = -1; char *p; /* * Look for initdefault. */ for(ch = family; ch; ch = ch->next) if (ch->action == INITDEFAULT) { p = ch->rlevel; while(*p) { if (*p > lvl) lvl = *p; p++; } break; } /* * See if level is valid */ if (lvl > 0) { if (islower(lvl)) lvl = toupper(lvl); if (strchr("0123456789S", lvl) == NULL) { initlog(L_VB, "Initdefault level '%c' is invalid", lvl); lvl = 0; } } /* * Ask for runlevel on console if needed. */ if (lvl <= 0) lvl = ask_runlevel(); /* * Log the fact that we have a runlevel now. */ return lvl; } /* * We got signaled. * * Do actions for the new level. If we are compatible with * the "old" INITLVL and arg == 0, try to read the new * runlevel from that file first. */ static int read_level(int arg) { CHILD *ch; /* Walk through list */ unsigned char foo = 'X'; /* Contents of INITLVL */ int ok = 1; #ifdef INITLVL FILE *fp; struct stat stt; int st; #endif if (arg) foo = arg; #ifdef INITLVL ok = 0; if (arg == 0) { fp = NULL; if (stat(INITLVL, &stt) != 0 || stt.st_size != 0L) fp = fopen(INITLVL, "r"); #ifdef INITLVL2 if (fp == NULL && (stat(INITLVL2, &stt) != 0 || stt.st_size != 0L)) fp = fopen(INITLVL2, "r"); #endif if (fp == NULL) { /* INITLVL file empty or not there - act as 'init q' */ initlog(L_SY, "Re-reading inittab"); return(runlevel); } ok = fscanf(fp, "%c %d", &foo, &st); fclose(fp); } else { /* We go to the new runlevel passed as an argument. */ foo = arg; ok = 1; } if (ok == 2) sltime = st; #endif /* INITLVL */ if (islower(foo)) foo = toupper(foo); if (ok < 1 || ok > 2 || strchr("QS0123456789ABCU", foo) == NULL) { initlog(L_VB, "bad runlevel: %c", foo); return runlevel; } /* Log this action */ switch(foo) { case 'S': initlog(L_VB, "Going single user"); break; case 'Q': initlog(L_SY, "Re-reading inittab"); break; case 'A': case 'B': case 'C': initlog(L_SY, "Activating demand-procedures for '%c'", foo); break; case 'U': initlog(L_SY, "Trying to re-exec init"); return 'U'; default: initlog(L_VB, "Switching to runlevel: %c", foo); } if (foo == 'Q') { #if defined(SIGINT_ONLYONCE) && (SIGINT_ONLYONCE == 1) /* Re-enable signal from keyboard */ struct sigaction sa; SETSIG(sa, SIGINT, signal_handler, 0); #endif return runlevel; } /* Check if this is a runlevel a, b or c */ if (strchr("ABC", foo)) { if (runlevel == 'S') return(runlevel); /* Read inittab again first! */ read_inittab(); /* Mark those special tasks */ for(ch = family; ch; ch = ch->next) if (strchr(ch->rlevel, foo) != NULL || strchr(ch->rlevel, tolower(foo)) != NULL) { ch->flags |= DEMAND; ch->flags &= ~XECUTED; INITDBG(L_VB, "Marking (%s) as ondemand, flags %d", ch->id, ch->flags); } return runlevel; } /* Store both the old and the new runlevel. */ wrote_utmp_rlevel = 0; wrote_wtmp_rlevel = 0; write_utmp_wtmp("runlevel", "~~", foo + 256*runlevel, RUN_LVL, "~"); thislevel = foo; prevlevel = runlevel; return foo; } /* * This procedure is called after every signal (SIGHUP, SIGALRM..) * * Only clear the 'failing' flag if the process is sleeping * longer than 5 minutes, or inittab was read again due * to user interaction. */ static void fail_check(void) { CHILD *ch; /* Pointer to child structure */ time_t t; /* System time */ time_t next_alarm = 0; /* When to set next alarm */ time(&t); for(ch = family; ch; ch = ch->next) { if (ch->flags & FAILING) { /* Can we free this sucker? */ if (ch->tm + SLEEPTIME < t) { ch->flags &= ~FAILING; ch->count = 0; ch->tm = 0; } else { /* No, we'll look again later */ if (next_alarm == 0 || ch->tm + SLEEPTIME > next_alarm) next_alarm = ch->tm + SLEEPTIME; } } } if (next_alarm) { next_alarm -= t; if (next_alarm < 1) next_alarm = 1; alarm(next_alarm); } } /* Set all 'Fail' timers to 0 */ static void fail_cancel(void) { CHILD *ch; for(ch = family; ch; ch = ch->next) { ch->count = 0; ch->tm = 0; ch->flags &= ~FAILING; } } /* * Start up powerfail entries. */ static void do_power_fail(int pwrstat) { CHILD *ch; /* * Tell powerwait & powerfail entries to start up */ for (ch = family; ch; ch = ch->next) { if (pwrstat == 'O') { /* * The power is OK again. */ if (ch->action == POWEROKWAIT) ch->flags &= ~XECUTED; } else if (pwrstat == 'L') { /* * Low battery, shut down now. */ if (ch->action == POWERFAILNOW) ch->flags &= ~XECUTED; } else { /* * Power is failing, shutdown imminent */ if (ch->action == POWERFAIL || ch->action == POWERWAIT) ch->flags &= ~XECUTED; } } } /* * Check for state-pipe presence */ static int check_pipe(int fd) { struct timeval t; fd_set s; char signature[8]; FD_ZERO(&s); FD_SET(fd, &s); t.tv_sec = t.tv_usec = 0; if (select(fd+1, &s, NULL, NULL, &t) != 1) return 0; if (read(fd, signature, 8) != 8) return 0; return strncmp(Signature, signature, 8) == 0; } /* * Make a state-pipe. */ static int make_pipe(int fd) { int fds[2]; if (pipe(fds)) { initlog(L_VB, "pipe: %m"); return -1; } dup2(fds[0], fd); close(fds[0]); fcntl(fds[1], F_SETFD, 1); fcntl(fd, F_SETFD, 0); safe_write(fds[1], Signature, 8); return fds[1]; } /* * Attempt to re-exec. */ static void re_exec(void) { CHILD *ch; sigset_t mask, oldset; pid_t pid; char **env; int fd; if (strchr("S0123456",runlevel) == NULL) return; /* * Reset the alarm, and block all signals. */ alarm(0); sigfillset(&mask); sigprocmask(SIG_BLOCK, &mask, &oldset); /* * construct a pipe fd --> STATE_PIPE and write a signature */ if ((fd = make_pipe(STATE_PIPE)) < 0) { sigprocmask(SIG_SETMASK, &oldset, NULL); initlog(L_CO, "Attempt to re-exec failed"); } /* * It's a backup day today, so I'm pissed off. Being a BOFH, however, * does have it's advantages... */ fail_cancel(); close(pipe_fd); pipe_fd = -1; DELSET(got_signals, SIGCHLD); DELSET(got_signals, SIGHUP); DELSET(got_signals, SIGUSR1); /* * That should be cleaned. */ for(ch = family; ch; ch = ch->next) if (ch->flags & ZOMBIE) { INITDBG(L_VB, "Child died, PID= %d", ch->pid); ch->flags &= ~(RUNNING|ZOMBIE|WAITING); if (ch->process[0] != '+') write_utmp_wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL); } if ((pid = fork()) == 0) { /* * Child sends state information to the parent. */ send_state(fd); exit(0); } /* * The existing init process execs a new init binary. */ env = init_buildenv(0); execle(myname, myname, "--init", NULL, env); /* * We shouldn't be here, something failed. * Bitch, close the state pipe, unblock signals and return. */ init_freeenv(env); close(fd); close(STATE_PIPE); sigprocmask(SIG_SETMASK, &oldset, NULL); initlog(L_CO, "Attempt to re-exec failed"); } /* * Redo utmp/wtmp entries if required or requested * Check for written records and size of utmp */ static void redo_utmp_wtmp(void) { struct stat ustat; const int ret = stat(UTMP_FILE, &ustat); if ((ret < 0) || (ustat.st_size == 0)) wrote_utmp_rlevel = wrote_utmp_reboot = 0; if ((wrote_wtmp_reboot == 0) || (wrote_utmp_reboot == 0)) write_utmp_wtmp("reboot", "~~", 0, BOOT_TIME, "~"); if ((wrote_wtmp_rlevel == 0) || (wrote_wtmp_rlevel == 0)) write_utmp_wtmp("runlevel", "~~", thislevel + 256 * prevlevel, RUN_LVL, "~"); } /* * We got a change runlevel request through the * init.fifo. Process it. */ static void fifo_new_level(int level) { #if CHANGE_WAIT CHILD *ch; #endif int oldlevel; if (level == runlevel) return; #if CHANGE_WAIT /* Are we waiting for a child? */ for(ch = family; ch; ch = ch->next) if (ch->flags & WAITING) break; if (ch == NULL) #endif { /* We need to go into a new runlevel */ oldlevel = runlevel; runlevel = read_level(level); if (runlevel == 'U') { runlevel = oldlevel; re_exec(); } else { if (oldlevel != 'S' && runlevel == 'S') console_stty(); if (runlevel == '6' || runlevel == '0' || runlevel == '1') console_stty(); if (runlevel > '1' && runlevel < '6') redo_utmp_wtmp(); read_inittab(); fail_cancel(); setproctitle("init [%c]", (int)runlevel); } } } /* * Set/unset environment variables. The variables are * encoded as KEY=VAL\0KEY=VAL\0\0. With "=VAL" it means * setenv, without it means unsetenv. */ static void initcmd_setenv(char *data, int size) { char *env, *p, *e; size_t sz; int i, eq; e = data + size; while (*data && data < e) { for (p = data; *p && p < e; p++) ; if (*p) break; env = data; data = ++p; /* * We only allow INIT_* to be set. */ if (strncmp(env, "INIT_", 5) != 0) continue; sz = strcspn(env, "="); eq = (env[sz] == '='); /*initlog(L_SY, "init_setenv: %s, %d, %d", env, eq, sz);*/ /* Free existing vars. */ for (i = 0; i < NR_EXTRA_ENV; i++) { if (extra_env[i] == NULL) continue; if (sz != strcspn(extra_env[i], "=")) continue; if (strncmp(extra_env[i], env, sz) == 0) { free(extra_env[i]); extra_env[i] = NULL; } } if (eq == 0) continue; /* Set new vars if needed. */ for (i = 0; i < NR_EXTRA_ENV; i++) { if (extra_env[i] == NULL) { extra_env[i] = istrdup(env); break; } } } } /* * Read from the init FIFO. Processes like telnetd and rlogind can * ask us to create login processes on their behalf. * * FIXME: this needs to be finished. NOT that it is buggy, but we need * to add the telnetd/rlogind stuff so people can start using it. * Maybe move to using an AF_UNIX socket so we can use * the 2.2 kernel credential stuff to see who we're talking to. * */ static void check_init_fifo(void) { struct init_request request; struct timeval tv; struct stat st, st2; fd_set fds; int n; int quit = 0; /* * First, try to create /run/initctl if not present. */ if (stat(INIT_FIFO, &st2) < 0 && errno == ENOENT) (void)mkfifo(INIT_FIFO, 0600); /* * If /run/initctl is open, stat the file to see if it * is still the _same_ inode. */ if (pipe_fd >= 0) { fstat(pipe_fd, &st); if (stat(INIT_FIFO, &st2) < 0 || st.st_dev != st2.st_dev || st.st_ino != st2.st_ino) { close(pipe_fd); pipe_fd = -1; } } /* * Now finally try to open /run/initctl */ if (pipe_fd < 0) { if ((pipe_fd = open(INIT_FIFO, O_RDWR|O_NONBLOCK)) >= 0) { fstat(pipe_fd, &st); if (!S_ISFIFO(st.st_mode)) { initlog(L_VB, "%s is not a fifo", INIT_FIFO); close(pipe_fd); pipe_fd = -1; } } if (pipe_fd >= 0) { /* * Don't use fd's 0, 1 or 2. */ (void) dup2(pipe_fd, PIPE_FD); close(pipe_fd); pipe_fd = PIPE_FD; /* * Return to caller - we'll be back later. */ } } /* Wait for data to appear, _if_ the pipe was opened. */ if (pipe_fd >= 0) while(!quit) { /* Do select, return on EINTR. */ FD_ZERO(&fds); FD_SET(pipe_fd, &fds); tv.tv_sec = 5; tv.tv_usec = 0; n = select(pipe_fd + 1, &fds, NULL, NULL, &tv); if (n <= 0) { if (n == 0 || errno == EINTR) return; continue; } /* Read the data, return on EINTR. */ n = read(pipe_fd, &request, sizeof(request)); if (n == 0) { /* * End of file. This can't happen under Linux (because * the pipe is opened O_RDWR - see select() in the * kernel) but you never know... */ close(pipe_fd); pipe_fd = -1; return; } if (n <= 0) { if (errno == EINTR) return; initlog(L_VB, "error reading initrequest"); continue; } /* * This is a convenient point to also try to * find the console device or check if it changed. */ console_init(); /* * Process request. */ if (request.magic != INIT_MAGIC || n != sizeof(request)) { initlog(L_VB, "got bogus initrequest"); continue; } switch(request.cmd) { case INIT_CMD_RUNLVL: sltime = request.sleeptime; fifo_new_level(request.runlevel); quit = 1; break; case INIT_CMD_POWERFAIL: sltime = request.sleeptime; do_power_fail('F'); quit = 1; break; case INIT_CMD_POWERFAILNOW: sltime = request.sleeptime; do_power_fail('L'); quit = 1; break; case INIT_CMD_POWEROK: sltime = request.sleeptime; do_power_fail('O'); quit = 1; break; case INIT_CMD_SETENV: initcmd_setenv(request.i.data, sizeof(request.i.data)); break; default: initlog(L_VB, "got unimplemented initrequest."); break; } } /* * We come here if the pipe couldn't be opened. */ if (pipe_fd < 0) pause(); } /* * This function is used in the transition * sysinit (-> single user) boot -> multi-user. */ static void boot_transitions() { CHILD *ch; static int newlevel = 0; static int warn = 1; int loglevel; int oldlevel; /* Check if there is something to wait for! */ for( ch = family; ch; ch = ch->next ) if ((ch->flags & RUNNING) && ch->action != BOOT) break; if (ch == NULL) { /* No processes left in this level, proceed to next level. */ loglevel = -1; oldlevel = 'N'; switch(runlevel) { case '#': /* SYSINIT -> BOOT */ INITDBG(L_VB, "SYSINIT -> BOOT"); /* Write a boot record. */ wrote_utmp_reboot = 0; wrote_wtmp_reboot = 0; write_utmp_wtmp("reboot", "~~", 0, BOOT_TIME, "~"); /* Get our run level */ newlevel = dfl_level ? dfl_level : get_init_default(); if (newlevel == 'S') { runlevel = newlevel; /* Not really 'S' but show anyway. */ setproctitle("init [S]"); } else runlevel = '*'; break; case '*': /* BOOT -> NORMAL */ INITDBG(L_VB, "BOOT -> NORMAL"); if (runlevel != newlevel) loglevel = newlevel; runlevel = newlevel; did_boot = 1; warn = 1; break; case 'S': /* Ended SU mode */ case 's': INITDBG(L_VB, "END SU MODE"); newlevel = get_init_default(); if (!did_boot && newlevel != 'S') runlevel = '*'; else { if (runlevel != newlevel) loglevel = newlevel; runlevel = newlevel; oldlevel = 'S'; } warn = 1; for(ch = family; ch; ch = ch->next) if (strcmp(ch->rlevel, "S") == 0) ch->flags &= ~(FAILING|WAITING|XECUTED); break; default: if (warn) initlog(L_VB, "no more processes left in this runlevel"); warn = 0; loglevel = -1; if (got_signals == 0) check_init_fifo(); break; } if (loglevel > 0) { initlog(L_VB, "Entering runlevel: %c", runlevel); wrote_utmp_rlevel = 0; wrote_wtmp_rlevel = 0; write_utmp_wtmp("runlevel", "~~", runlevel + 256 * oldlevel, RUN_LVL, "~"); thislevel = runlevel; prevlevel = oldlevel; setproctitle("init [%c]", (int)runlevel); } } } /* * Init got hit by a signal. See which signal it is, * and act accordingly. */ static void process_signals() { CHILD *ch; int pwrstat; int oldlevel; int fd; char c; if (ISMEMBER(got_signals, SIGPWR)) { INITDBG(L_VB, "got SIGPWR"); /* See _what_ kind of SIGPWR this is. */ pwrstat = 0; if ((fd = open(PWRSTAT, O_RDONLY)) >= 0) { if (read(fd, &c, 1) != 1) c = 0; pwrstat = c; close(fd); unlink(PWRSTAT); } else if ((fd = open(PWRSTAT_OLD, O_RDONLY)) >= 0) { /* Path changed 2010-03-20. Look for the old path for a while. */ initlog(L_VB, "warning: found obsolete path %s, use %s instead", PWRSTAT_OLD, PWRSTAT); if (read(fd, &c, 1) != 1) c = 0; pwrstat = c; close(fd); unlink(PWRSTAT_OLD); } do_power_fail(pwrstat); DELSET(got_signals, SIGPWR); } if (ISMEMBER(got_signals, SIGINT)) { #if defined(SIGINT_ONLYONCE) && (SIGINT_ONLYONCE == 1) /* Ignore any further signal from keyboard */ struct sigaction sa; SETSIG(sa, SIGINT, SIG_IGN, SA_RESTART); #endif INITDBG(L_VB, "got SIGINT"); /* Tell ctrlaltdel entry to start up */ for(ch = family; ch; ch = ch->next) if (ch->action == CTRLALTDEL) ch->flags &= ~XECUTED; DELSET(got_signals, SIGINT); } if (ISMEMBER(got_signals, SIGWINCH)) { INITDBG(L_VB, "got SIGWINCH"); /* Tell kbrequest entry to start up */ for(ch = family; ch; ch = ch->next) if (ch->action == KBREQUEST) ch->flags &= ~XECUTED; DELSET(got_signals, SIGWINCH); } if (ISMEMBER(got_signals, SIGALRM)) { INITDBG(L_VB, "got SIGALRM"); /* The timer went off: check it out */ DELSET(got_signals, SIGALRM); } if (ISMEMBER(got_signals, SIGCHLD)) { INITDBG(L_VB, "got SIGCHLD"); /* First set flag to 0 */ DELSET(got_signals, SIGCHLD); /* See which child this was */ for(ch = family; ch; ch = ch->next) if (ch->flags & ZOMBIE) { INITDBG(L_VB, "Child died, PID= %d", ch->pid); ch->flags &= ~(RUNNING|ZOMBIE|WAITING); if (ch->process[0] != '+') write_utmp_wtmp("", ch->id, ch->pid, DEAD_PROCESS, NULL); } } if (ISMEMBER(got_signals, SIGHUP)) { INITDBG(L_VB, "got SIGHUP"); #if CHANGE_WAIT /* Are we waiting for a child? */ for(ch = family; ch; ch = ch->next) if (ch->flags & WAITING) break; if (ch == NULL) #endif { /* We need to go into a new runlevel */ oldlevel = runlevel; #ifdef INITLVL runlevel = read_level(0); #endif if (runlevel == 'U') { runlevel = oldlevel; re_exec(); } else { if (oldlevel != 'S' && runlevel == 'S') console_stty(); if (runlevel == '6' || runlevel == '0' || runlevel == '1') console_stty(); read_inittab(); fail_cancel(); setproctitle("init [%c]", (int)runlevel); DELSET(got_signals, SIGHUP); } } } if (ISMEMBER(got_signals, SIGUSR1)) { /* * SIGUSR1 means close and reopen /run/initctl */ INITDBG(L_VB, "got SIGUSR1"); close(pipe_fd); pipe_fd = -1; DELSET(got_signals, SIGUSR1); } } /* * The main loop */ static void init_main(void) { CHILD *ch; struct sigaction sa; sigset_t sgt; int f, st; if (!reload) { #if INITDEBUG /* * Fork so we can debug the init process. */ if ((f = fork()) > 0) { static const char killmsg[] = "PRNT: init killed.\r\n"; pid_t rc; while((rc = wait(&st)) != f) if (rc < 0 && errno == ECHILD) break; safe_write(1, killmsg, sizeof(killmsg) - 1); while(1) pause(); } #endif #ifdef __linux__ /* * Tell the kernel to send us SIGINT when CTRL-ALT-DEL * is pressed, and that we want to handle keyboard signals. */ init_reboot(BMAGIC_SOFT); if ((f = open(VT_MASTER, O_RDWR | O_NOCTTY)) >= 0) { (void) ioctl(f, KDSIGACCEPT, SIGWINCH); close(f); } else (void) ioctl(0, KDSIGACCEPT, SIGWINCH); #endif /* * Ignore all signals. */ for(f = 1; f <= NSIG; f++) SETSIG(sa, f, SIG_IGN, SA_RESTART); } SETSIG(sa, SIGALRM, signal_handler, 0); SETSIG(sa, SIGHUP, signal_handler, 0); SETSIG(sa, SIGINT, signal_handler, 0); SETSIG(sa, SIGCHLD, chld_handler, SA_RESTART); SETSIG(sa, SIGPWR, signal_handler, 0); SETSIG(sa, SIGWINCH, signal_handler, 0); SETSIG(sa, SIGUSR1, signal_handler, 0); SETSIG(sa, SIGSTOP, stop_handler, SA_RESTART); SETSIG(sa, SIGTSTP, stop_handler, SA_RESTART); SETSIG(sa, SIGCONT, cont_handler, SA_RESTART); SETSIG(sa, SIGSEGV, (void (*)(int))segv_handler, SA_RESTART); console_init(); if (!reload) { int fd; /* Close whatever files are open, and reset the console. */ close(0); close(1); close(2); console_stty(); setsid(); /* * Set default PATH variable. */ setenv("PATH", PATH_DEFAULT, 1 /* Overwrite */); /* * Initialize /var/run/utmp (only works if /var is on * root and mounted rw) */ if ((fd = open(UTMP_FILE, O_WRONLY|O_CREAT|O_TRUNC, 0644)) >= 0) close(fd); /* * Say hello to the world */ initlog(L_CO, bootmsg, "booting"); /* * See if we have to start an emergency shell. */ if (emerg_shell) { pid_t rc; SETSIG(sa, SIGCHLD, SIG_DFL, SA_RESTART); if (spawn(&ch_emerg, &f) > 0) { while((rc = wait(&st)) != f) if (rc < 0 && errno == ECHILD) break; } SETSIG(sa, SIGCHLD, chld_handler, SA_RESTART); } /* * Start normal boot procedure. */ runlevel = '#'; read_inittab(); } else { /* * Restart: unblock signals and let the show go on */ initlog(L_CO, bootmsg, "reloading"); sigfillset(&sgt); sigprocmask(SIG_UNBLOCK, &sgt, NULL); /* * Set default PATH variable. */ setenv("PATH", PATH_DEFAULT, 0 /* Don't overwrite */); } start_if_needed(); while(1) { /* See if we need to make the boot transitions. */ boot_transitions(); INITDBG(L_VB, "init_main: waiting.."); /* Check if there are processes to be waited on. */ for(ch = family; ch; ch = ch->next) if ((ch->flags & RUNNING) && ch->action != BOOT) break; #if CHANGE_WAIT /* Wait until we get hit by some signal. */ while (ch != NULL && got_signals == 0) { if (ISMEMBER(got_signals, SIGHUP)) { /* See if there are processes to be waited on. */ for(ch = family; ch; ch = ch->next) if (ch->flags & WAITING) break; } if (ch != NULL) check_init_fifo(); } #else /* CHANGE_WAIT */ if (ch != NULL && got_signals == 0) check_init_fifo(); #endif /* CHANGE_WAIT */ /* Check the 'failing' flags */ fail_check(); /* Process any signals. */ process_signals(); /* See what we need to start up (again) */ start_if_needed(); } /*NOTREACHED*/ } /* * Tell the user about the syntax we expect. */ static void usage(char *s) { fprintf(stderr, "Usage: %s {-e VAR[=VAL] | [-t SECONDS] {0|1|2|3|4|5|6|S|s|Q|q|A|a|B|b|C|c|U|u}}\n", s); exit(1); } static int telinit(char *progname, int argc, char **argv) { #ifdef TELINIT_USES_INITLVL FILE *fp; #endif struct init_request request; struct sigaction sa; int f, fd, l; char *env = NULL; memset(&request, 0, sizeof(request)); request.magic = INIT_MAGIC; while ((f = getopt(argc, argv, "t:e:")) != EOF) switch(f) { case 't': sltime = atoi(optarg); break; case 'e': if (env == NULL) env = request.i.data; l = strlen(optarg); if (env + l + 2 > request.i.data + sizeof(request.i.data)) { fprintf(stderr, "%s: -e option data " "too large\n", progname); exit(1); } memcpy(env, optarg, l); env += l; *env++ = 0; break; default: usage(progname); break; } if (env) *env++ = 0; if (env) { if (argc != optind) usage(progname); request.cmd = INIT_CMD_SETENV; } else { if (argc - optind != 1 || strlen(argv[optind]) != 1) usage(progname); if (!strchr("0123456789SsQqAaBbCcUu", argv[optind][0])) usage(progname); request.cmd = INIT_CMD_RUNLVL; request.runlevel = argv[optind][0]; request.sleeptime = sltime; } /* Change to the root directory. */ if (0 != chdir("/")) initlog(L_VB, "unable to chdir to /: %s", strerror(errno)); /* Open the fifo and write a command. */ /* Make sure we don't hang on opening /run/initctl */ SETSIG(sa, SIGALRM, signal_handler, 0); alarm(3); if ((fd = open(INIT_FIFO, O_WRONLY)) >= 0) { ssize_t p = 0; size_t s = sizeof(request); void *ptr = &request; while (s > 0) { p = write(fd, ptr, s); if (p < 0) { if (errno == EINTR || errno == EAGAIN) continue; break; } ptr += p; s -= p; } close(fd); alarm(0); return 0; } #ifdef TELINIT_USES_INITLVL if (request.cmd == INIT_CMD_RUNLVL) { /* Fallthrough to the old method. */ /* Now write the new runlevel. */ if ((fp = fopen(INITLVL, "w")) == NULL) { fprintf(stderr, "%s: cannot create %s\n", progname, INITLVL); exit(1); } fprintf(fp, "%s %d", argv[optind], sltime); fclose(fp); /* And tell init about the pending runlevel change. */ if (kill(INITPID, SIGHUP) < 0) perror(progname); return 0; } #endif fprintf(stderr, "%s: ", progname); if (ISMEMBER(got_signals, SIGALRM)) { fprintf(stderr, "timeout opening/writing control channel %s\n", INIT_FIFO); } else { perror(INIT_FIFO); } return 1; } /* * Main entry for init and telinit. */ int main(int argc, char **argv) { char *p; int f; int isinit; #ifdef WITH_SELINUX int enforce = 0; #endif /* Get my own name */ if ((p = strrchr(argv[0], '/')) != NULL) p++; else p = argv[0]; /* Common umask */ umask(umask(077) | 022); /* Quick check */ if (geteuid() != 0) { fprintf(stderr, "%s: must be superuser.\n", p); exit(1); } /* * Is this telinit or init ? */ isinit = (getpid() == 1); for (f = 1; f < argc; f++) { if (!strcmp(argv[f], "-i") || !strcmp(argv[f], "--init")) { isinit = 1; break; } } if (!isinit) exit(telinit(p, argc, argv)); /* * Check for re-exec */ if (check_pipe(STATE_PIPE)) { receive_state(STATE_PIPE); myname = istrdup(argv[0]); argv0 = argv[0]; maxproclen = 0; for (f = 0; f < argc; f++) maxproclen += strlen(argv[f]) + 1; reload = 1; setproctitle("init [%c]", (int)runlevel); init_main(); } /* Check command line arguments */ maxproclen = strlen(argv[0]) + 1; for(f = 1; f < argc; f++) { if (!strcmp(argv[f], "single") || !strcmp(argv[f], "-s")) dfl_level = 'S'; else if (!strcmp(argv[f], "-a") || !strcmp(argv[f], "auto")) putenv("AUTOBOOT=YES"); else if (!strcmp(argv[f], "-b") || !strcmp(argv[f],"emergency")) emerg_shell = 1; else if (!strcmp(argv[f], "-z")) { /* Ignore -z xxx */ if (argv[f + 1]) f++; } else if (strchr("0123456789sS", argv[f][0]) && strlen(argv[f]) == 1) dfl_level = argv[f][0]; /* "init u" in the very beginning makes no sense */ if (dfl_level == 's') dfl_level = 'S'; maxproclen += strlen(argv[f]) + 1; } #ifdef WITH_SELINUX if (getenv("SELINUX_INIT") == NULL) { if (is_selinux_enabled() != 1) { if (selinux_init_load_policy(&enforce) == 0) { putenv("SELINUX_INIT=YES"); execv(myname, argv); } else { if (enforce > 0) { /* SELinux in enforcing mode but load_policy failed */ /* At this point, we probably can't open /dev/console, so log() won't work */ fprintf(stderr,"Unable to load SELinux Policy. Machine is in enforcing mode. Halting now.\n"); exit(1); } } } } #endif /* Start booting. */ argv0 = argv[0]; argv[1] = NULL; setproctitle("init boot"); init_main(); /*NOTREACHED*/ return 0; }