/* simpleinit.c - poe@daimi.aau.dk */ /* Version 1.21 */ /* gerg@snapgear.com -- modified for direct console support DEC/1999 */ /* davidm@snapgear.com -- modified for init.conf SEP/2004 */ /* toby@snapgear.com -- allow the array of commands to grow as needed OCT/2004 */ /* davidm@snapgear.com -- use dynamically allocated tables APR/2005 */ #define _GNU_SOURCE /* For crypt() and termios defines */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef SHADOW_PWD #include #endif #if __GNU_LIBRARY__ > 5 #include #endif #include "pathnames.h" #define BUF_SIZ 100 /* max size of a line in inittab */ #define NUMCMD 30 /* step size when realloc more space for the commands */ #define NUMTOK 20 /* max number of tokens in inittab command */ /* Threshold time for detecting "fast" spawning processes */ static int testtime = 90; /* Number of rapid respawns that counts as too fast */ static int maxspawn = 5; /* Maximum delay between runs */ static int maxdelay = 595; /* Time between successive runs of a process */ static int delaytime = 5; #define MAXTRIES 3 /* number of tries allowed when giving the password */ #define RUN_RC /* Use a console if possible */ #ifndef CTRL #define CTRL(X) ((X)&037) #endif #ifdef INCLUDE_TIMEZONE char tzone[BUF_SIZ]; #endif /* #define DEBUGGING */ /* Define this if you want init to ignore the termcap field in inittab for console ttys. */ /* #define SPECIAL_CONSOLE_TERM */ struct initline { pid_t pid; time_t lastrun; time_t nextrun; char **toks; short delay; char tty[10]; char termcap[30]; char *line; char *fullline; unsigned char xcnt; }; struct initline *inittab; /* How many struct initline's will fit in the memory pointed to by inittab */ int inittab_size = 0; int numcmd; int stopped = 0; /* are we stopped */ int reload = 0; /* are we stopped */ int run_sigint_processing = 0; extern void spawn(int); extern void hup_handler(); extern void reload_inittab(); extern void read_inittab(void); static int read_initfile(const char *); extern void tstp_handler(); extern void int_handler(); extern void sigint_processing(); extern void cont_handler(); extern void set_tz(void); extern void write_wtmp(void); extern void make_ascii_tty(void); extern void make_console(const char *); extern int boot_single(int singlearg, int argc, char *argv[]); #ifdef CONFIG_USER_INIT_CONF extern void load_init_conf(void); #endif /* Keep track of console device, if any... */ #if LINUX_VERSION_CODE < 0x020100 char *console_device = NULL; int console_baud = -1; #else int have_console = 0; #endif static void err(const char *s) { struct iovec output[2]; #if LINUX_VERSION_CODE < 0x020100 int fd; #endif output[0].iov_base = "init: "; output[0].iov_len = 6; output[1].iov_base = (void *)s; output[1].iov_len = strlen(s); #if LINUX_VERSION_CODE < 0x020100 if (console_device == NULL) return; if((fd = open(console_device, O_WRONLY)) < 0) return; writev(fd, output, 2); close(fd); #else if (have_console) writev(1, output, 2); #endif } void add_tok(struct initline *p, char *tok) { int i; for (i = 0; p->toks && p->toks[i]; i++) ; /* allocate space for new entry and terminating NULL */ p->toks = (char **) realloc(p->toks, (i + 2) * sizeof(char *)); if (!p->toks) { err("malloc failed\n"); _exit(1); } p->toks[i++] = tok; p->toks[i] = NULL; } static void enter_single(void) { pid_t pid; char *av[2]; err("Booting to single user mode\n"); av[0] = _PATH_BSHELL; av[1] = NULL; if((pid = vfork()) == 0) { extern char **environ; /* the child */ execve(_PATH_BSHELL, av, environ); err("exec of single user shell failed\n"); _exit(0); } else if(pid > 0) { int i; while(wait(&i) != pid) /* nothing */; } else if(pid < 0) { err("fork of single user shell failed\n"); } unlink(_PATH_SINGLE); } #if LINUX_VERSION_CODE < 0x020100 static void set_console_baud(int baud) { switch (baud) { case 50: console_baud = B50; break; case 75: console_baud = B75; break; case 110: console_baud = B110; break; case 134: console_baud = B134; break; case 150: console_baud = B150; break; case 200: console_baud = B200; break; case 300: console_baud = B300; break; case 600: console_baud = B600; break; case 1200: console_baud = B1200; break; case 1800: console_baud = B1800; break; case 2400: console_baud = B2400; break; case 4800: console_baud = B4800; break; default: case 9600: console_baud = B9600; break; case 19200: console_baud = B19200; break; case 38400: console_baud = B38400; break; case 57600: console_baud = B57600; break; case 115200: console_baud = B115200; break; case 230400: console_baud = B230400; break; case 460800: console_baud = B460800; break; } } #endif static int do_command(const char *path, const char *filename, int dowait) { pid_t pid, wpid; int stat, st; if((pid = vfork()) == 0) { /* the child */ char *argv[3]; #ifdef INCLUDE_TIMEZONE char tz[BUF_SIZ]; #endif char *env[3]; /* Use /dev/null for stdin */ close(0); open("/dev/null", O_RDONLY); argv[0] = (char *)path; argv[1] = (char *)filename; argv[2] = NULL; env[0] = "PATH=/bin:/usr/bin:/etc:/sbin:/usr/sbin"; #ifdef INCLUDE_TIMEZONE strcpy(tz, "TZ="); strcat(tz, tzone); env[1] = tz; env[2] = NULL; #else env[1] = NULL; #endif execve(path, argv, env); err("exec rc failed\n"); _exit(2); } else if(pid > 0) { if (!dowait) stat = 0; else { /* parent, wait till rc process dies before spawning */ while ((wpid = wait(&stat)) != pid) if (wpid == -1 && errno == ECHILD) { /* see wait(2) manpage */ stat = 0; break; } } } else if(pid < 0) { err("fork of rc shell failed\n"); stat = -1; } st = WEXITSTATUS(stat); return st; } /* * run /etc/rc. The environment is passed to the script, so the RC environment * variable can be used to decide what to do. RC may be set from LILO. */ static int do_rc(void) { int rc; rc = do_command(_PATH_BSHELL, _PATH_RC, 1); if (rc) return(rc); #ifdef CONFIG_USER_INIT_RUN_FIREWALL rc = do_command(_PATH_FIREWALL, "-i", 1); if (rc) err(_PATH_FIREWALL " failed!"); #endif #ifdef CONFIG_USER_FLATFSD_FLATFSD rc = do_command(_PATH_BSHELL, _PATH_CONFIGRC, 1); if (rc) err(_PATH_CONFIGRC " failed!"); #endif #ifdef CONFIG_USER_INIT_RUN_FIREWALL rc = do_command(_PATH_FIREWALL, NULL, 0); if (rc) err(_PATH_FIREWALL " failed!"); #endif #ifdef INCLUDE_TIMEZONE /* We read the timezone file here, because the flat file system * has probably been created by now. */ set_tz(); #endif return(0); } void respawn_children(int signo) { int i, delta = -1; time_t now; alarm(0); if ((now = time(NULL)) == 0) now = 1; for(i = 0; i < numcmd; i++) { if(inittab[i].pid < 0) { /* Start jobs */ if(stopped) inittab[i].pid = -1; /* ** Do not spawn child from signal handler ! ** SIGALRM would be blocked for the child */ else if (signo == 0) spawn(i); } /* Check for naughty jobs */ if (inittab[i].nextrun > now) { int d; d = inittab[i].nextrun - now; if (delta < 0 || d < delta) delta = d; } } if (delta > 0) { alarm(delta); } } int main(int argc, char *argv[]) { int i; struct sigaction sa; /* * setup all the signal handlers here */ memset(&sa, 0, sizeof(sa)); /* sa.sa_flags = SA_RESETHAND we want to keep the handlers armed */ sa.sa_handler = tstp_handler; sigaction(SIGTSTP, &sa, NULL); sa.sa_handler = cont_handler; sigaction(SIGCONT, &sa, NULL); sa.sa_handler = int_handler; sigaction(SIGINT, &sa, NULL); sa.sa_handler = respawn_children; sigaction(SIGALRM, &sa, NULL); sa.sa_handler = hup_handler; sigaction(SIGHUP, &sa, NULL); #if defined(CONSOLE_BAUD_RATE) && LINUX_VERSION_CODE < 0x020100 set_console_baud(CONSOLE_BAUD_RATE); #endif /* * start up in single user mode if /etc/singleboot exists or if * argv[1] is "single". */ if(boot_single(0, argc, argv)) enter_single(); #ifdef RUN_RC /* Register console if defined by boot */ #if LINUX_VERSION_CODE < 0x020100 if ((console_device = getenv("CONSOLE"))) { char *sp; unsetenv("CONSOLE"); if ((sp = strchr(console_device, ','))) { *sp++ = 0; set_console_baud(atoi(sp)); } } make_ascii_tty(); #else { struct stat st; if (isatty(1)) { have_console = 1; make_ascii_tty(); } else if (fstat(1, &st) == -1 && errno == EBADF) { /* No stdout, so send everything to /dev/null */ close(0); close(1); close(2); open("/dev/null", O_RDWR); dup(0); dup(0); } } #endif /*If we get a SIGTSTP before multi-user mode, do nothing*/ while(stopped) pause(); if(do_rc() != 0 && boot_single(1, argc, argv) && !stopped) enter_single(); while(stopped) /*Also if /etc/rc fails & we get SIGTSTP*/ pause(); #endif /* initialize the array of commands */ inittab = (struct initline *)malloc(NUMCMD * sizeof(struct initline)); inittab_size = NUMCMD; if (!inittab) { /* failure case - what do you do if init fails? */ err("malloc failed"); _exit(1); } write_wtmp(); /* write boottime record */ read_inittab(); #ifdef DEBUGGING for(i = 0; i < numcmd; i++) { char **p = inittab[i].toks; printf("toks= %s %s %s %s\n",p[0], p[1], p[2], p[3]); printf("tty= %s\n", inittab[i].tty); printf("termcap= %s\n", inittab[i].termcap); } /*exit(0);*/ #endif #if LINUX_VERSION_CODE < 0x020100 for(i = 0; i < getdtablesize(); i++) close(i); #else /* Always leave 0, 1, and 2 connected (to /dev/null) for the child process */ for(i = 3; i < getdtablesize(); i++) close(i); #endif for (;;) { pid_t pid; int vec; if (run_sigint_processing) { run_sigint_processing = 0; sigint_processing(); } respawn_children(0); if (reload) { reload = 0; reload_inittab(); continue; /* process all reloads before waiting */ } pid = wait(&vec); alarm(0); /* clear utmp entry, and append to wtmp if possible */ #if 0 /* DAVIDM */ { struct utmp *ut; int ut_fd; utmpname(_PATH_UTMP); setutent(); while((ut = getutent())) { if(ut->ut_pid == pid) { time(&ut->ut_time); bzero(&ut->ut_user, UT_NAMESIZE); bzero(&ut->ut_host, sizeof(ut->ut_host)); ut->ut_type = DEAD_PROCESS; ut->ut_pid = 0; ut->ut_addr = 0; endutent(); pututline(ut); if((ut_fd = open(_PATH_WTMP, O_APPEND|O_WRONLY)) >= 0) { flock(ut_fd, LOCK_EX|LOCK_NB); write(ut_fd, (const void *)ut, sizeof(struct utmp)); flock(ut_fd, LOCK_UN|LOCK_NB); close(ut_fd); } break; } } endutent(); } #endif for(i = 0; i < numcmd; i++) { if(pid == inittab[i].pid) { inittab[i].pid = -1; } } } } /* * return true if we should boot up in singleuser mode. If argv[i] is * "single" or the file /etc/singleboot exists, then singleuser mode should * be entered. If /etc/securesingle exists ask for root password first. */ int boot_single(int singlearg, int argc, char *argv[]) { char *pass, *rootpass = NULL; struct passwd *pwd; int i; for(i = 1; i < argc; i++) { if(argv[i] && !strcmp(argv[i], "single")) singlearg = 1; } return 0; } void spawn(int i) { pid_t pid; int j; time_t t; struct initline *it; char buf[150]; it = inittab + i; t = time(NULL); /* Check for held process */ if ((unsigned long)(it->nextrun - t - 1) < maxdelay) return; if (it->lastrun + testtime > t) { /* Respawning quickly */ if (it->xcnt < 0xff) it->xcnt++; } else { /* Normal respawning */ it->xcnt = 0; it->lastrun = t; it->delay = delaytime; } if (it->xcnt >= maxspawn) { /* Too many too quickly */ strcpy(buf, it->toks[0]); strcat(buf, " respawning too fast\n"); err(buf); it->pid = -1; if (it->delay >= maxdelay) it->delay = maxdelay; else if (it->delay < delaytime) it->delay = delaytime; else if((it->delay *= 2) > maxdelay) it->delay = maxdelay; it->nextrun = t + it->delay; /* Fiddle with the tracking vars to ensure that only * one attempt is made to run this next time around. */ it->lastrun = it->nextrun; it->xcnt -= 2; return; } it->nextrun = t + delaytime; if((pid = vfork()) < 0) { it->pid = -1; err("fork failed\n"); return; } if(pid) { /* this is the parent */ it->pid = pid; return; } else { /* this is the child */ char term[40]; char *prog; #ifdef INCLUDE_TIMEZONE char tz[BUF_SIZ]; #endif char *env[4]; setsid(); /* Close everything other than 0, 1 and 2 */ for(j = 3; j < getdtablesize(); j++) { close(j); } /* Now set up 0, 1 and 2 */ make_console(it->tty); strcpy(term, "TERM="); strcat(term, it->termcap); env[0] = term; env[1] = "PATH=/bin:/usr/bin:/etc:/sbin:/usr/sbin"; #ifdef INCLUDE_TIMEZONE strcpy(tz, "TZ="); strcat(tz, tzone); env[2] = tz; env[3] = NULL; #else env[2] = NULL; #endif prog = it->toks[0]; if (*prog == '-' && *(prog+1)) prog++; execve(prog, it->toks, env); strcpy(buf, it->toks[0]); strcat(buf, " exec failed\n"); err(buf); _exit(1); } } static void init_itab(struct initline *p) { bzero(p, sizeof(struct initline)); p->pid = -1; p->nextrun = time(NULL); } static void clear_itab(struct initline *p) { if (p->line) free(p->line); if (p->fullline) free(p->fullline); if (p->toks) free(p->toks); init_itab(p); } void read_inittab(void) { int i; /* * free any old data and start again */ for (i = 0; i < numcmd; i++) clear_itab(&inittab[i]); numcmd = 0; /* Fake an inittab entry if boot console defined */ #ifdef CONFIG_USER_INIT_CONSOLE_SH #if LINUX_VERSION_CODE < 0x020100 if (console_device && strcmp(console_device, "/dev/null")) #else if (have_console) #endif { struct initline *p; p = inittab + numcmd++; init_itab(p); p->fullline = strdup("console"); strcpy(p->tty, "console"); strcpy(p->termcap, "linux"); add_tok(p, "-/bin/sh"); } #endif i = 0; if (read_initfile(_PATH_INITTAB) == 0) i++; #ifdef CONFIG_USER_FLATFSD_FLATFSD if (read_initfile(_PATH_CONFIGTAB) == 0) i++; #endif if (i == 0) { err("Failed to open " _PATH_INITTAB #ifdef CONFIG_USER_FLATFSD_FLATFSD " or " _PATH_CONFIGTAB #endif "." ); } #ifdef CONFIG_USER_INIT_CONF load_init_conf(); #endif /* if needed, shrink the array using realloc - * must be done here so that we include the results of all init files * when calculating number of commands */ if ((numcmd + 2) < (inittab_size - NUMCMD)) { /* round up from numcmd to the nearest multiple of NUMCMD */ inittab_size = ((numcmd + 2) / NUMCMD + 1) * NUMCMD; inittab = realloc(inittab, inittab_size * sizeof(struct initline)); if (!inittab) { /* failure case - what do you do if init fails? */ err("malloc failed"); _exit(1); } } if (numcmd == 0) _exit(1); } static int read_initfile(const char *initfile) { struct initline *p; FILE *f; char *buf = NULL; size_t buf_len = 0; int i,j,k; char *ptr, *getty; #ifdef SPECIAL_CONSOLE_TERM char tty[50]; struct stat stb; char *termenv; termenv = getenv("TERM"); /* set by kernel */ #endif i = numcmd; if (!(f = fopen(initfile, "r"))) return 1; while(!feof(f)) { if (i+2 == inittab_size) { /* need to realloc inittab */ inittab_size += NUMCMD; inittab = realloc(inittab, inittab_size * sizeof(struct initline)); if (!inittab) { /* failure case - what do you do if init fails? */ err("malloc failed"); _exit(1); } } if (getline(&buf, &buf_len, f) == -1) break; for(k = 0; k < buf_len && buf[k]; k++) { if(buf[k] == '#') { buf[k] = '\0'; break; } } if(buf[0] == '\0' || buf[0] == '\n') continue; p = inittab + i; init_itab(p); p->line = strdup(buf); p->fullline = strdup(buf); if (!p->line || !p->fullline) { err("Not memory to allocate inittab entry"); clear_itab(p); continue; } ptr = strtok(p->line, ":"); if (!ptr) { err("Missing TTY/ID field in inittab"); clear_itab(p); continue; } strncpy(p->tty, ptr, 9); //p->tty[9] = '\0'; ptr = strtok(NULL, ":"); if (!ptr) { err("Missing TERMTYPE field in inittab"); clear_itab(p); continue; } strncpy(p->termcap, ptr, 29); //p->termcap[29] = '\0'; getty = strtok(NULL, " \t\n"); if (!getty) { err("Missing PROCESS field in inittab"); clear_itab(p); continue; } add_tok(p, getty); j = 1; while((ptr = strtok(NULL, " \t\n"))) add_tok(p, ptr); #ifdef SPECIAL_CONSOLE_TERM /* special-case termcap for the console ttys */ strcpy(tty, "/dev/"); strcat(tty, p->tty); if(!termenv || stat(tty, &stb) < 0) { err("no TERM or cannot stat tty\n"); } else { /* is it a console tty? */ if(major(stb.st_rdev) == 4 && minor(stb.st_rdev) < 64) { strncpy(p->termcap, termenv, 30); p->termcap[29] = 0; } } #endif i++; } if (buf) free(buf); fclose(f); numcmd = i; return 0; } void hup_handler() { reload = 1; } void reload_inittab() { int i; int oldnum; char ** saveline = (char **) malloc(inittab_size * sizeof(char *)); pid_t * savepid = (pid_t*) malloc(inittab_size * sizeof(pid_t)); if (!saveline || !savepid) { /* another failure case - what DO you do if init fails */ err("malloc failed"); _exit(1); } for (i=0; i 1) kill(savepid[i], SIGTERM); free(saveline[i]); } free(saveline); free(savepid); } void tstp_handler() { stopped++; } void cont_handler() { stopped = 0; } void int_handler() { run_sigint_processing = 1; } void sigint_processing() { /* * After Linux 0.96b PL1, we get a SIGINT when * the user presses Ctrl-Alt-Del... */ int pid; sync(); sync(); if((pid = vfork()) == 0) { char *av[2]; extern char **environ; /* reboot properly... */ av[0] = _PATH_REBOOT; av[1] = NULL; execve(_PATH_REBOOT, av, environ); #if __GNU_LIBRARY__ > 5 reboot(0x1234567); #else reboot(0xfee1dead, 672274793, 0x1234567); #endif _exit(2); } else if(pid < 0) { /* fork failed, try the hard way... */ #if __GNU_LIBRARY__ > 5 reboot(0x1234567); #else reboot(0xfee1dead, 672274793, 0x1234567); #endif } } #ifdef INCLUDE_TIMEZONE void set_tz(void) { FILE *f; int len; if((f = fopen("/etc/config/TZ", "r")) == NULL && (f = fopen("/etc/TZ", "r")) == NULL) return; fgets(tzone, BUF_SIZ-2, f); fclose(f); if((len=strlen(tzone)) < 2) return; tzone[len-1] = 0; /* get rid of the '\n' */ setenv("TZ", tzone, 0); } #endif #ifdef CONFIG_USER_INIT_CONF void load_init_conf(void) { char line[BUF_SIZ]; FILE *f; if ((f = fopen("/etc/config/init.conf", "r")) == NULL && (f = fopen("/etc/init.conf", "r")) == NULL) return; while (fgets(line, sizeof(line) - 2, f)) { if (strncasecmp(line, "delaytime=", 10) == 0) delaytime = atoi(line + 10); if (strncasecmp(line, "maxdelay=", 9) == 0) maxdelay = atoi(line + 9); if (strncasecmp(line, "maxspawn=", 9) == 0) maxspawn = atoi(line + 9); if (strncasecmp(line, "testtime=", 9) == 0) testtime = atoi(line + 9); } fclose(f); } #endif void write_wtmp(void) { #if 0 int fd; struct utmp ut; bzero((char *)&ut, sizeof(ut)); strcpy(ut.ut_line, "~"); bzero(ut.ut_name, sizeof(ut.ut_name)); time(&ut.ut_time); ut.ut_type = BOOT_TIME; if((fd = open(_PATH_WTMP, O_WRONLY|O_APPEND)) >= 0) { flock(fd, LOCK_EX|LOCK_NB); /* make sure init won't hang */ write(fd, (char *)&ut, sizeof(ut)); flock(fd, LOCK_UN|LOCK_NB); close(fd); } #endif } void make_ascii_tty(void) { struct termios tty; const char *pt; if (tcgetattr(0, &tty) < 0) return; tty.c_iflag &= ~(INLCR|IGNCR|IUCLC); tty.c_iflag |= ICRNL; tty.c_oflag &= ~(OCRNL|OLCUC|ONOCR|ONLRET|OFILL); tty.c_oflag |= OPOST|ONLCR; tty.c_cflag |= CLOCAL; tty.c_lflag = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE; #ifdef IEXTEN tty.c_lflag |= IEXTEN; #endif #if LINUX_VERSION_CODE < 0x020100 if (console_baud != -1) cfsetospeed(&tty, console_baud); #endif tty.c_cc[VINTR] = CTRL('C'); tty.c_cc[VQUIT] = CTRL('\\'); tty.c_cc[VERASE] = CTRL('H'); /*127*/ tty.c_cc[VKILL] = CTRL('U'); /*Changed from non-standard ^X*/ tty.c_cc[VEOF] = CTRL('D'); tty.c_cc[VTIME] = 0; tty.c_cc[VMIN] = 1; tty.c_cc[VSTART] = CTRL('Q'); tty.c_cc[VSTOP] = CTRL('S'); tty.c_cc[VSUSP] = CTRL('Z'); #ifdef VWERASE tty.c_cc[VWERASE] = CTRL('W'); #endif /* Pick up simple environment setting of VERASE. * Useful for setting on kernel command line. * e.g. TTYERASE=^? */ pt = getenv("TTYERASE"); if (pt && pt[0] == '^' && pt[1]) { tty.c_cc[VERASE] = (pt[1] == '?') ? 127 : CTRL(pt[1]); } tcsetattr(0, TCSANOW, &tty); } void make_console(const char *tty) { char devname[32]; close(0); close(1); close(2); if (tty && *tty) { #if LINUX_VERSION_CODE < 0x020100 /* * until we get proper console support under 2.0 */ if (strcmp(tty, "console") == 0) { strcpy(devname, console_device); } else #endif { strcpy(devname, "/dev/"); strcat(devname, tty); } /* Try to open the specified console */ if (open(devname, O_RDWR|O_NONBLOCK) >= 0) { fcntl(0, F_SETFL, 0); dup(0); dup(0); make_ascii_tty(); ioctl(0, TIOCSCTTY, (char*)0); return; } } /* No go, so send to /dev/null */ open("/dev/null", O_RDWR|O_NONBLOCK); dup(0); dup(0); }