| /* simpleinit.c - poe@daimi.aau.dk */ |
| /* Version 2.0.2 */ |
| |
| /* 1999-02-22 Arkadiusz Mi¶kiewicz <misiek@pld.ORG.PL> |
| * - added Native Language Support |
| * 2001-01-25 Richard Gooch <rgooch@atnf.csiro.au> |
| * - fixed bug with failed services so they may be later "reclaimed" |
| * 2001-02-02 Richard Gooch <rgooch@atnf.csiro.au> |
| * - fixed race when reading from pipe and reaping children |
| * 2001-02-18 sam@quux.dropbear.id.au |
| * - fixed bug in <get_path>: multiple INIT_PATH components did not work |
| * 2001-02-21 Richard Gooch <rgooch@atnf.csiro.au> |
| * - block signals in handlers, so that longjmp() doesn't kill context |
| * 2001-02-25 Richard Gooch <rgooch@atnf.csiro.au> |
| * - make default INIT_PATH the boot_prog (if it is a directory) - YECCH |
| * 2002-11-20 patch from SuSE |
| * - refuse initctl_fd if setting FD_CLOEXEC fails |
| */ |
| |
| #include <sys/types.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| #include <stdio.h> |
| #include <ctype.h> |
| #include <fcntl.h> |
| #include <string.h> |
| #include <signal.h> |
| #include <errno.h> |
| #include <time.h> |
| #include <pwd.h> |
| #include <sys/file.h> |
| #include <sys/wait.h> |
| #include <sys/stat.h> |
| #include <sys/sysmacros.h> |
| #include <sys/time.h> |
| #include <sys/ioctl.h> |
| #include <dirent.h> |
| #include <termios.h> |
| #include <utmp.h> |
| #include <setjmp.h> |
| #include <sched.h> |
| #ifdef SHADOW_PWD |
| # include <shadow.h> |
| #endif |
| #include "my_crypt.h" |
| #include "pathnames.h" |
| #include "linux_reboot.h" |
| #include "xstrncpy.h" |
| #include "nls.h" |
| #include "simpleinit.h" |
| |
| #define CMDSIZ 150 /* max size of a line in inittab */ |
| #define NUMCMD 30 /* max number of lines in inittab */ |
| #define NUMTOK 20 /* max number of tokens in inittab command */ |
| #define PATH_SIZE (CMDSIZ+CMDSIZ+1) |
| |
| #define MAX_RESPAWN_RATE 5 /* number of respawns per 100 seconds */ |
| |
| #define TZFILE "/etc/TZ" |
| char tzone[CMDSIZ]; |
| /* #define DEBUGGING */ |
| |
| /* Define this if you want init to ignore the termcap field in inittab for |
| console ttys. */ |
| /* #define SPECIAL_CONSOLE_TERM */ |
| |
| #define ever (;;) |
| |
| struct initline { |
| pid_t pid; |
| char tty[10]; |
| char termcap[30]; |
| char *toks[NUMTOK]; |
| char line[CMDSIZ]; |
| struct timeval last_start; |
| signed long rate; |
| }; |
| |
| struct initline inittab[NUMCMD]; |
| int numcmd; |
| int stopped = 0; /* are we stopped */ |
| static char boot_prog[PATH_SIZE] = _PATH_RC; |
| static char script_prefix[PATH_SIZE] = "\0"; |
| static char final_prog[PATH_SIZE] = "\0"; |
| static char init_path[PATH_SIZE] = "\0"; |
| static int caught_sigint = 0; |
| static int no_reboot = 0; |
| static pid_t rc_child = -1; |
| static const char *initctl_name = "/dev/initctl"; |
| static int initctl_fd = -1; |
| static volatile int do_longjmp = 0; |
| static sigjmp_buf jmp_env; |
| |
| |
| static void do_single (void); |
| static int do_rc_tty (const char *path); |
| static int process_path (const char *path, int (*func) (const char *path), |
| int ignore_dangling_symlink); |
| static int preload_file (const char *path); |
| static int run_file (const char *path); |
| static void spawn (int i), read_inittab (void); |
| static void sighup_handler (int sig); |
| static void sigtstp_handler (int sig); |
| static void sigint_handler (int sig); |
| static void sigchild_handler (int sig); |
| static void sigquit_handler (int sig); |
| static void sigterm_handler (int sig); |
| #ifdef SET_TZ |
| static void set_tz (void); |
| #endif |
| static void write_wtmp (void); |
| static pid_t mywait (int *status); |
| static int run_command (const char *file, const char *name, pid_t pid); |
| |
| |
| static void err (char *s) |
| { |
| int fd; |
| |
| if((fd = open("/dev/console", O_WRONLY)) < 0) return; |
| |
| write(fd, "init: ", 6); |
| write(fd, s, strlen(s)); |
| close(fd); |
| } |
| |
| static void enter_single (void) |
| { |
| pid_t pid; |
| int i; |
| |
| err(_("Booting to single user mode.\n")); |
| if((pid = fork()) == 0) { |
| /* the child */ |
| execl(_PATH_BSHELL, _PATH_BSHELL, NULL); |
| err(_("exec of single user shell failed\n")); |
| } else if(pid > 0) { |
| while (waitpid (pid, &i, 0) != pid) /* Nothing */; |
| } else if(pid < 0) { |
| err(_("fork of single user shell failed\n")); |
| } |
| unlink(_PATH_SINGLE); |
| } |
| |
| int main(int argc, char *argv[]) |
| { |
| int vec, i; |
| int want_single = 0; |
| pid_t pid; |
| struct sigaction sa; |
| |
| |
| #ifdef SET_TZ |
| set_tz(); |
| #endif |
| sigfillset (&sa.sa_mask); /* longjmp and nested signals don't mix */ |
| sa.sa_flags = SA_ONESHOT; |
| sa.sa_handler = sigint_handler; |
| sigaction (SIGINT, &sa, NULL); |
| sa.sa_flags = 0; |
| sa.sa_handler = sigtstp_handler; |
| sigaction (SIGTSTP, &sa, NULL); |
| sa.sa_handler = sigterm_handler; |
| sigaction (SIGTERM, &sa, NULL); |
| sa.sa_handler = sigchild_handler; |
| sigaction (SIGCHLD, &sa, NULL); |
| sa.sa_handler = sigquit_handler; |
| sigaction (SIGQUIT, &sa, NULL); |
| |
| setlocale(LC_ALL, ""); |
| bindtextdomain(PACKAGE, LOCALEDIR); |
| textdomain(PACKAGE); |
| |
| my_reboot (LINUX_REBOOT_CMD_CAD_OFF); |
| /* Find script to run. Command-line overrides config file overrides |
| built-in default */ |
| for (i = 0; i < NUMCMD; i++) inittab[i].pid = -1; |
| read_inittab (); |
| for (i = 1; i < argc; i++) { |
| if (strcmp (argv[i], "single") == 0) |
| want_single = 1; |
| else if (strcmp (argv[i], "-noreboot") == 0) |
| no_reboot = 1; |
| else if (strlen(script_prefix) + strlen(argv[i]) < PATH_SIZE) { |
| char path[PATH_SIZE]; |
| |
| strcpy (path, script_prefix); |
| strcat (path, argv[i]); |
| if (access (path, R_OK | X_OK) == 0) |
| strcpy (boot_prog, path); |
| } |
| } |
| if (init_path[0] == '\0') |
| { |
| struct stat statbuf; |
| |
| if ( (stat (boot_prog, &statbuf) == 0) && S_ISDIR (statbuf.st_mode) ) |
| { |
| strcpy (init_path, boot_prog); |
| i = strlen (init_path); |
| if (init_path[i - 1] == '/') init_path[i - 1] = '\0'; |
| } |
| } |
| |
| if ( ( initctl_fd = open (initctl_name, O_RDWR, 0) ) < 0 ) { |
| mkfifo (initctl_name, S_IRUSR | S_IWUSR); |
| if ( ( initctl_fd = open (initctl_name, O_RDWR, 0) ) < 0 ) |
| err ( _("error opening fifo\n") ); |
| } |
| |
| if (initctl_fd >= 0 && fcntl(initctl_fd, F_SETFD, FD_CLOEXEC) != 0) { |
| err ( _("error setting close-on-exec on /dev/initctl") ); |
| |
| /* Can the fcntl ever fail? If it does, and we leave |
| the descriptor open in child processes, then any |
| process on the system will be able to write to |
| /dev/initctl and have us execute arbitrary commands |
| as root. So let's refuse to use the fifo in this case. */ |
| |
| close(initctl_fd); |
| initctl_fd = -1; |
| } |
| |
| if ( want_single || (access (_PATH_SINGLE, R_OK) == 0) ) do_single (); |
| |
| /*If we get a SIGTSTP before multi-user mode, do nothing*/ |
| while (stopped) |
| pause(); |
| |
| if ( do_rc_tty (boot_prog) ) do_single (); |
| |
| while (stopped) /* Also if /etc/rc fails & we get SIGTSTP */ |
| pause(); |
| |
| write_wtmp(); /* write boottime record */ |
| #ifdef DEBUGGING |
| for(i = 0; i < numcmd; i++) { |
| char **p; |
| 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 |
| signal (SIGHUP, sighup_handler); /* Better semantics with signal(2) */ |
| |
| for (i = 0; i < getdtablesize (); i++) |
| if (i != initctl_fd) close (i); |
| |
| for(i = 0; i < numcmd; i++) |
| spawn(i); |
| |
| if (final_prog[0] != '\0') { |
| switch ( fork () ) |
| { |
| case 0: /* Child */ |
| execl (final_prog, final_prog, "start", NULL); |
| err ( _("error running finalprog\n") ); |
| _exit (1); |
| break; |
| case -1: /* Error */ |
| err ( _("error forking finalprog\n") ); |
| break; |
| default: /* Parent */ |
| break; |
| } |
| } |
| |
| for ever { |
| pid = mywait (&vec); |
| if (pid < 1) continue; |
| |
| /* clear utmp entry, and append to wtmp if possible */ |
| { |
| struct utmp *ut; |
| int ut_fd, lf; |
| |
| utmpname(_PATH_UTMP); |
| setutent(); |
| while((ut = getutent())) { |
| if(ut->ut_pid == pid) { |
| time(&ut->ut_time); |
| memset(&ut->ut_user, 0, UT_NAMESIZE); |
| memset(&ut->ut_host, 0, sizeof(ut->ut_host)); |
| ut->ut_type = DEAD_PROCESS; |
| ut->ut_pid = 0; |
| ut->ut_addr = 0; |
| /*endutent();*/ |
| pututline(ut); |
| |
| if ((lf = open(_PATH_WTMPLOCK, O_CREAT|O_WRONLY, 0660)) >= 0) { |
| flock(lf, LOCK_EX|LOCK_NB); |
| if((ut_fd = open(_PATH_WTMP, O_APPEND|O_WRONLY)) >= 0) { |
| write(ut_fd, ut, sizeof(struct utmp)); |
| close(ut_fd); |
| } |
| flock(lf, LOCK_UN|LOCK_NB); |
| close(lf); |
| } |
| break; |
| } |
| } |
| endutent(); |
| } |
| |
| for(i = 0; i < numcmd; i++) { |
| if(pid == inittab[i].pid || inittab[i].pid < 0) { |
| if (stopped) |
| inittab[i].pid = -1; |
| else |
| spawn(i); |
| if (pid == inittab[i].pid) |
| break; |
| } |
| } |
| } |
| } |
| |
| #define MAXTRIES 3 /* number of tries allowed when giving the password */ |
| |
| /* |
| * return true if singleuser mode is allowed. |
| * If /etc/securesingle exists ask for root password, otherwise always OK. |
| */ |
| static int check_single_ok (void) |
| { |
| char *pass, *rootpass = NULL; |
| struct passwd *pwd; |
| int i; |
| |
| if (access (_PATH_SECURE, R_OK) != 0) return 1; |
| if ( ( pwd = getpwnam ("root") ) || ( pwd = getpwuid (0) ) ) |
| rootpass = pwd->pw_passwd; |
| else |
| return 1; /* a bad /etc/passwd should not lock out */ |
| |
| for (i = 0; i < MAXTRIES; i++) |
| { |
| pass = getpass (_("Password: ")); |
| if (pass == NULL) continue; |
| |
| if ( !strcmp (crypt (pass, rootpass), rootpass) ) return 1; |
| |
| puts (_("\nWrong password.\n")); |
| } |
| return 0; |
| } |
| |
| static void do_single (void) |
| { |
| char path[PATH_SIZE]; |
| |
| if (caught_sigint) return; |
| strcpy (path, script_prefix); |
| strcat (path, "single"); |
| if (access (path, R_OK | X_OK) == 0) |
| if (do_rc_tty (path) == 0) return; |
| if ( check_single_ok () ) enter_single (); |
| } /* End Function do_single */ |
| |
| /* |
| * run boot script(s). The environment is passed to the script(s), so the RC |
| * environment variable can be used to decide what to do. |
| * RC may be set from LILO. |
| * [RETURNS] 0 on success (exit status convention), otherwise error. |
| */ |
| static int do_rc_tty (const char *path) |
| { |
| int status; |
| pid_t pid; |
| sigset_t ss; |
| |
| if (caught_sigint) return 0; |
| process_path (path, preload_file, 0); |
| /* Launch off a subprocess to start a new session (required for frobbing |
| the TTY) and capture control-C */ |
| switch ( rc_child = fork () ) |
| { |
| case 0: /* Child */ |
| for (status = 1; status < NSIG; status++) signal (status, SIG_DFL); |
| sigfillset (&ss); |
| sigprocmask (SIG_UNBLOCK, &ss, NULL); |
| sigdelset (&ss, SIGINT); |
| sigdelset (&ss, SIGQUIT); |
| setsid (); |
| ioctl (0, TIOCSCTTY, 0); /* I want my control-C */ |
| sigsuspend (&ss); /* Should never return, should just be killed */ |
| break; /* No-one else is controlled by this TTY now */ |
| case -1: /* Error */ |
| return (1); |
| /*break;*/ |
| default: /* Parent */ |
| break; |
| } |
| /* Parent */ |
| process_path (path, run_file, 0); |
| while (1) |
| { |
| if ( ( pid = mywait (&status) ) == rc_child ) |
| return (WTERMSIG (status) == SIGINT) ? 0 : 1; |
| if (pid < 0) break; |
| } |
| kill (rc_child, SIGKILL); |
| while (waitpid (rc_child, NULL, 0) != rc_child) /* Nothing */; |
| return 0; |
| } /* End Function do_rc_tty */ |
| |
| static int process_path (const char *path, int (*func) (const char *path), |
| int ignore_dangling_symlink) |
| { |
| struct stat statbuf; |
| DIR *dp; |
| struct dirent *de; |
| |
| if (lstat (path, &statbuf) != 0) |
| { |
| err (_("lstat of path failed\n") ); |
| return 1; |
| } |
| if ( S_ISLNK (statbuf.st_mode) ) |
| { |
| if (stat (path, &statbuf) != 0) |
| { |
| if ( (errno == ENOENT) && ignore_dangling_symlink ) return 0; |
| err (_("stat of path failed\n") ); |
| return 1; |
| } |
| } |
| if ( !( statbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH) ) ) return 0; |
| if ( !S_ISDIR (statbuf.st_mode) ) return (*func) (path); |
| if ( ( dp = opendir (path) ) == NULL ) |
| { |
| err (_("open of directory failed\n") ); |
| return 1; |
| } |
| while ( ( de = readdir (dp) ) != NULL ) |
| { |
| int retval; |
| char newpath[PATH_SIZE]; |
| |
| if (de->d_name[0] == '.') continue; |
| retval = snprintf (newpath, sizeof(newpath), "%s/%s", path, de->d_name); |
| if (newpath[retval - 1] == '~') continue; /* Common mistake */ |
| if ( ( retval = process_path (newpath, func, 1) ) ) return retval; |
| } |
| closedir (dp); |
| return 0; |
| } /* End Function process_path */ |
| |
| static int preload_file (const char *path) |
| { |
| int fd; |
| char ch; |
| |
| if ( ( fd = open (path, O_RDONLY, 0) ) < 0) return 0; |
| while (read (fd, &ch, 1) == 1) lseek (fd, 1024, SEEK_CUR); |
| close (fd); |
| return 0; |
| } /* End Function preload_file */ |
| |
| static int run_file (const char *path) |
| { |
| const char *ptr; |
| |
| if ( ( ptr = strrchr ( (char *) path, '/' ) ) == NULL ) ptr = path; |
| else ++ptr; |
| return (run_command (path, ptr, 0) == SIG_FAILED) ? 1 : 0; |
| } /* End Function run_file */ |
| |
| static void spawn (int i) |
| { |
| pid_t pid; |
| int j; |
| signed long ds_taken; |
| struct timeval ct; |
| |
| if (inittab[i].toks[0] == NULL) return; |
| |
| /* Check if respawning too fast */ |
| gettimeofday (&ct, NULL); |
| ds_taken = ct.tv_sec - inittab[i].last_start.tv_sec; |
| |
| /* On the first iteration last_start==0 and ds_taken |
| may be very large. Avoid overflow. -- Denis Vlasenko */ |
| if (ds_taken > 10000) |
| ds_taken = 10000; |
| |
| ds_taken *= 10; |
| ds_taken += (ct.tv_usec - inittab[i].last_start.tv_usec) / 100000; |
| if (ds_taken < 1) |
| ds_taken = 1; |
| inittab[i].rate = (9 * inittab[i].rate + 1000 / ds_taken) / 10; |
| if (inittab[i].rate > MAX_RESPAWN_RATE) { |
| char txt[256]; |
| |
| inittab[i].toks[0] = NULL; |
| inittab[i].pid = -1; |
| inittab[i].rate = 0; |
| snprintf (txt, sizeof(txt), |
| _("respawning: \"%s\" too fast: quenching entry\n"), |
| inittab[i].tty); |
| err (txt); |
| return; |
| } |
| |
| if((pid = fork()) < 0) { |
| inittab[i].pid = -1; |
| err(_("fork failed\n")); |
| return; |
| } |
| if(pid) { |
| /* this is the parent */ |
| inittab[i].pid = pid; |
| inittab[i].last_start = ct; |
| sched_yield (); |
| return; |
| } else { |
| /* this is the child */ |
| char term[40]; |
| #ifdef SET_TZ |
| char tz[CMDSIZ]; |
| #endif |
| char *env[3]; |
| |
| setsid(); |
| for(j = 0; j < getdtablesize(); j++) |
| (void) close(j); |
| |
| snprintf(term, sizeof(term), "TERM=%s", inittab[i].termcap); |
| env[0] = term; |
| env[1] = (char *)0; |
| #ifdef SET_TZ |
| snprintf(tz, sizeof(tz), "TZ=%s", tzone); |
| env[1] = tz; |
| #endif |
| env[2] = (char *)0; |
| |
| execve(inittab[i].toks[0], inittab[i].toks, env); |
| err(_("exec failed\n")); |
| sleep(5); |
| _exit(1); |
| } |
| } |
| |
| static void read_inittab (void) |
| { |
| FILE *f; |
| char buf[CMDSIZ]; |
| int i,j,k; |
| int has_prog = 0; |
| char *ptr, *getty; |
| char prog[PATH_SIZE]; |
| #ifdef SPECIAL_CONSOLE_TERM |
| char tty[50]; |
| struct stat stb; |
| #endif |
| char *termenv; |
| |
| termenv = getenv("TERM"); /* set by kernel */ |
| /* termenv = "vt100"; */ |
| |
| if(!(f = fopen(_PATH_INITTAB, "r"))) { |
| err(_("cannot open inittab\n")); |
| return; |
| } |
| |
| prog[0] = '\0'; |
| i = 0; |
| while(!feof(f) && i < NUMCMD - 2) { |
| if(fgets(buf, CMDSIZ - 1, f) == 0) break; |
| buf[CMDSIZ-1] = 0; |
| |
| for(k = 0; k < CMDSIZ && buf[k]; k++) { |
| if ((buf[k] == '#') || (buf[k] == '\n')) { |
| buf[k] = 0; break; |
| } |
| } |
| |
| if(buf[0] == 0 || buf[0] == '\n') continue; |
| ptr = strchr (buf, '='); |
| if (ptr) { |
| ptr++; |
| if ( !strncmp (buf, "bootprog", 8) ) { |
| while ( isspace (*ptr) ) ++ptr; |
| strcpy (prog, ptr); |
| has_prog = 1; |
| continue; |
| } |
| if ( !strncmp (buf, "fileprefix", 10) ) { |
| while ( isspace (*ptr) ) ++ptr; |
| strcpy (script_prefix, ptr); |
| continue; |
| } |
| if ( !strncmp (buf, "PATH", 4) ) { |
| while ( isspace (*ptr) ) ++ptr; |
| setenv ("PATH", ptr, 1); |
| continue; |
| } |
| if ( !strncmp (buf, "INIT_PATH", 9) ) { |
| while ( isspace (*ptr) ) ++ptr; |
| strcpy (init_path, ptr); |
| continue; |
| } |
| if ( !strncmp (buf, "finalprog", 8) ) { |
| while ( isspace (*ptr) ) ++ptr; |
| strcpy (final_prog, ptr); |
| continue; |
| } |
| } |
| |
| |
| (void) strcpy(inittab[i].line, buf); |
| |
| (void) strtok(inittab[i].line, ":"); |
| xstrncpy(inittab[i].tty, inittab[i].line, 10); |
| xstrncpy(inittab[i].termcap, strtok((char *)0, ":"), 30); |
| |
| getty = strtok((char *)0, ":"); |
| (void) strtok(getty, " \t\n"); |
| inittab[i].toks[0] = getty; |
| j = 1; |
| while((ptr = strtok((char *)0, " \t\n"))) |
| inittab[i].toks[j++] = ptr; |
| inittab[i].toks[j] = (char *)0; |
| |
| #ifdef SPECIAL_CONSOLE_TERM |
| /* special-case termcap for the console ttys */ |
| snprintf(tty, sizeof(tty), "/dev/%s", inittab[i].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) |
| xstrncpy(inittab[i].termcap, termenv, 30); |
| } |
| #endif |
| |
| i++; |
| } |
| fclose(f); |
| numcmd = i; |
| if (has_prog) { |
| int len; |
| char path[PATH_SIZE]; |
| |
| strcpy (path, script_prefix); |
| strcat (path, prog); |
| len = strlen (path); |
| if (path[len - 1] == '/') path[len - 1] = '\0'; |
| if (access (path, R_OK | X_OK) == 0) |
| strcpy (boot_prog, path); |
| } |
| } /* End Function read_inittab */ |
| |
| static void sighup_handler (int sig) |
| { |
| int i,j; |
| int oldnum; |
| struct initline savetab[NUMCMD]; |
| int had_already; |
| |
| signal (SIGHUP, SIG_IGN); |
| memcpy(savetab, inittab, NUMCMD * sizeof(struct initline)); |
| oldnum = numcmd; |
| read_inittab (); |
| |
| for(i = 0; i < numcmd; i++) { |
| had_already = 0; |
| for(j = 0; j < oldnum; j++) { |
| if(!strcmp(savetab[j].tty, inittab[i].tty)) { |
| had_already = 1; |
| if((inittab[i].pid = savetab[j].pid) < 0) |
| spawn(i); |
| } |
| } |
| if (!had_already) spawn (i); |
| } |
| signal (SIGHUP, sighup_handler); |
| } /* End Function sighup_handler */ |
| |
| static void sigtstp_handler (int sig) |
| { |
| stopped = ~stopped; |
| if (!stopped) sighup_handler (sig); |
| } /* End Function sigtstp_handler */ |
| |
| static void sigterm_handler (int sig) |
| { |
| int i; |
| |
| for (i = 0; i < numcmd; i++) |
| if (inittab[i].pid > 0) kill (inittab[i].pid, SIGTERM); |
| } /* End Function sigterm_handler */ |
| |
| static void sigint_handler (int sig) |
| { |
| pid_t pid; |
| |
| caught_sigint = 1; |
| kill (rc_child, SIGKILL); |
| if (no_reboot) _exit (1) /*kill (0, SIGKILL)*/; |
| sync (); |
| sync (); |
| pid = fork (); |
| if (pid > 0) return; /* Parent */ |
| if (pid == 0) /* Child: reboot properly... */ |
| execl (_PATH_REBOOT, _PATH_REBOOT, (char *) 0); |
| |
| /* fork or exec failed, try the hard way... */ |
| my_reboot (LINUX_REBOOT_CMD_RESTART); |
| } /* End Function sigint_handler */ |
| |
| static void sigchild_handler (int sig) |
| { |
| if (!do_longjmp) return; |
| siglongjmp (jmp_env, 1); |
| } |
| |
| static void sigquit_handler (int sig) |
| { |
| execl (_PATH_REBOOT, _PATH_REBOOT, NULL); /* It knows pid=1 must sleep */ |
| } |
| |
| #ifdef SET_TZ |
| static void set_tz (void) |
| { |
| FILE *f; |
| int len; |
| |
| if((f=fopen(TZFILE, "r")) == (FILE *)NULL) return; |
| fgets(tzone, CMDSIZ-2, f); |
| fclose(f); |
| if((len=strlen(tzone)) < 2) return; |
| tzone[len-1] = 0; /* get rid of the '\n' */ |
| setenv("TZ", tzone, 0); |
| } |
| #endif |
| |
| static void write_wtmp (void) |
| { |
| int fd, lf; |
| struct utmp ut; |
| |
| memset((char *)&ut, 0, sizeof(ut)); |
| strcpy(ut.ut_line, "~"); |
| memset(ut.ut_name, 0, sizeof(ut.ut_name)); |
| time(&ut.ut_time); |
| ut.ut_type = BOOT_TIME; |
| |
| if ((lf = open(_PATH_WTMPLOCK, O_CREAT|O_WRONLY, 0660)) >= 0) { |
| flock(lf, LOCK_EX|LOCK_NB); /* make sure init won't hang */ |
| if((fd = open(_PATH_WTMP, O_WRONLY|O_APPEND)) >= 0) { |
| write(fd, (char *)&ut, sizeof(ut)); |
| close(fd); |
| } |
| flock(lf, LOCK_UN|LOCK_NB); |
| close(lf); |
| } |
| } /* End Function write_wtmp */ |
| |
| |
| struct needer_struct |
| { |
| struct needer_struct *next; |
| pid_t pid; |
| }; |
| |
| struct service_struct |
| { |
| struct service_struct *prev, *next; /* Script services chain */ |
| struct needer_struct *needers; /* Needers waiting for service */ |
| struct script_struct *attempting_providers; |
| int failed; /* TRUE if attempting provider failed badly */ |
| char name[1]; |
| }; |
| |
| struct script_struct |
| { |
| pid_t pid; |
| struct script_struct *prev, *next; /* For the list */ |
| struct service_struct *first_service, *last_service; /*First is true name*/ |
| struct script_struct *next_attempting_provider; /* Provider chain */ |
| }; |
| |
| struct list_head |
| { |
| struct script_struct *first, *last; |
| unsigned int num_entries; |
| }; |
| |
| |
| static struct list_head available_list = {NULL, NULL, 0}; |
| static struct list_head starting_list = {NULL, NULL, 0}; |
| static struct service_struct *unavailable_services = NULL; /* For needers */ |
| static int num_needers = 0; |
| |
| |
| static int process_pidstat (pid_t pid, int status); |
| static void process_command (const struct command_struct *command); |
| static struct service_struct *find_service_in_list (const char *name, |
| struct service_struct *sv); |
| static struct script_struct *find_script_byname |
| (const char *name,struct list_head *head, struct service_struct **service); |
| static struct script_struct *find_script_bypid (pid_t pid, |
| struct list_head *head); |
| static void insert_entry (struct list_head *head, struct script_struct *entry); |
| static void remove_entry (struct list_head *head, struct script_struct *entry); |
| static void signal_needers (struct service_struct *service, int sig); |
| static void handle_nonworking (struct script_struct *script); |
| static int force_progress (void); |
| static void show_scripts (FILE *fp, const struct script_struct *script, |
| const char *type); |
| static const char *get_path (const char *file); |
| |
| |
| static pid_t mywait (int *status) |
| /* [RETURNS] The pid for a process to be reaped, 0 if no process is to be |
| reaped, and less than 0 if the boot scripts appear to have finished. |
| */ |
| { |
| pid_t pid; |
| sigset_t ss; |
| long buffer[COMMAND_SIZE / sizeof (long)]; |
| struct command_struct *command = (struct command_struct *) buffer; |
| |
| if (initctl_fd < 0) return wait (status); |
| /* Some magic to avoid races which can result in lost signals */ |
| command->command = -1; |
| if ( sigsetjmp (jmp_env, 1) ) |
| { /* Jump from signal handler */ |
| do_longjmp = 0; |
| process_command (command); |
| return 0; |
| } |
| sigemptyset (&ss); /* Block SIGCHLD so wait status cannot be lost */ |
| sigaddset (&ss, SIGCHLD); |
| sigprocmask (SIG_BLOCK, &ss, NULL); |
| if ( ( pid = waitpid (-1, status, WNOHANG) ) > 0 ) |
| { |
| sigprocmask (SIG_UNBLOCK, &ss, NULL); |
| return process_pidstat (pid, *status); |
| } |
| do_longjmp = 1; /* After this, SIGCHLD will cause a jump backwards */ |
| sigprocmask (SIG_UNBLOCK, &ss, NULL); |
| read (initctl_fd, buffer, sizeof(buffer)); |
| do_longjmp = 0; |
| process_command (command); |
| return 0; |
| } /* End Function mywait */ |
| |
| static pid_t process_pidstat (pid_t pid, int status) |
| /* [RETURNS] The pid for a process to be reaped, 0 if no process is to be |
| reaped, and less than 0 if the boot scripts appear to have finished. |
| */ |
| { |
| int failed; |
| struct script_struct *script; |
| struct service_struct *service; |
| |
| if ( ( script = find_script_bypid (pid, &starting_list) ) == NULL ) |
| return pid; |
| remove_entry (&starting_list, script); |
| if ( WIFEXITED (status) && (WEXITSTATUS (status) == 0) ) |
| { |
| struct script_struct *provider; |
| |
| /* Notify needers and other providers */ |
| for (service = script->first_service; service != NULL; |
| service = service->next) |
| { |
| signal_needers (service, SIG_PRESENT); |
| for (provider = service->attempting_providers; provider != NULL; |
| provider = provider->next_attempting_provider) |
| kill (provider->pid, SIG_PRESENT); |
| service->attempting_providers = NULL; |
| } |
| insert_entry (&available_list, script); |
| return force_progress (); |
| } |
| failed = ( WIFEXITED (status) && (WEXITSTATUS (status) == 2) ) ? 0 : 1; |
| for (service = script->first_service; service != NULL; |
| service = service->next) |
| service->failed = failed; |
| handle_nonworking (script); |
| return force_progress (); |
| } /* End Function process_pidstat */ |
| |
| static void process_command (const struct command_struct *command) |
| { |
| int ival; |
| struct script_struct *script; |
| struct service_struct *service; |
| |
| switch (command->command) |
| { |
| case COMMAND_TEST: |
| kill (command->pid, |
| (find_script_byname (command->name, &available_list, |
| NULL) == NULL) ? |
| SIG_NOT_PRESENT : SIG_PRESENT); |
| break; |
| case COMMAND_NEED: |
| ival = run_command (command->name, command->name, command->pid); |
| if (ival == 0) |
| { |
| ++num_needers; |
| force_progress (); |
| } |
| else kill (command->pid, ival); |
| break; |
| case COMMAND_ROLLBACK: |
| if (command->name[0] == '\0') script = NULL; |
| else |
| { |
| if ( ( script = find_script_byname (command->name, &available_list, |
| NULL) ) == NULL ) |
| { |
| kill (command->pid, SIG_NOT_PRESENT); |
| break; |
| } |
| } |
| while (script != available_list.first) |
| { |
| pid_t pid; |
| struct script_struct *victim = available_list.first; |
| char txt[256]; |
| |
| if ( ( pid = fork () ) == 0 ) /* Child */ |
| { |
| for (ival = 1; ival < NSIG; ival++) signal (ival, SIG_DFL); |
| open ("/dev/console", O_RDONLY, 0); |
| open ("/dev/console", O_RDWR, 0); |
| dup2 (1, 2); |
| execlp (get_path (victim->first_service->name), |
| victim->first_service->name, "stop", NULL); |
| snprintf (txt, sizeof(txt), |
| _("error stopping service: \"%s\"\n"), |
| victim->first_service->name); |
| err (txt); |
| _exit (SIG_NOT_STOPPED); |
| } |
| else if (pid == -1) break; /* Error */ |
| else /* Parent */ |
| { |
| while (waitpid (pid, &ival, 0) != pid) /* Nothing */; |
| if ( WIFEXITED (ival) && (WEXITSTATUS (ival) == 0) ) |
| { |
| snprintf (txt, sizeof(txt), |
| _("Stopped service: %s\n"), |
| victim->first_service->name); |
| remove_entry (&available_list, victim); |
| free (victim); |
| err (txt); |
| } |
| else break; |
| } |
| } |
| kill (command->pid, |
| (script ==available_list.first) ? SIG_STOPPED : SIG_NOT_STOPPED); |
| break; |
| case COMMAND_DUMP_LIST: |
| if (fork () == 0) /* Do it in a child process so pid=1 doesn't block */ |
| { |
| FILE *fp; |
| |
| if ( ( fp = fopen (command->name, "w") ) == NULL ) _exit (1); |
| show_scripts (fp, available_list.first, "AVAILABLE"); |
| show_scripts (fp, starting_list.first, "STARTING"); |
| fputs ("UNAVAILABLE SERVICES:\n", fp); |
| for (service = unavailable_services; service != NULL; |
| service = service->next) |
| fprintf (fp, "%s (%s)\n", service->name, |
| service->failed ? "FAILED" : "not configured"); |
| fclose (fp); |
| _exit (0); |
| } |
| break; |
| case COMMAND_PROVIDE: |
| /* Sanity check */ |
| if ( ( script = find_script_bypid (command->ppid, &starting_list) ) |
| == NULL ) |
| { |
| kill (command->pid, SIG_NOT_CHILD); |
| break; |
| } |
| if (find_script_byname (command->name, &available_list, NULL) != NULL) |
| { |
| kill (command->pid, SIG_PRESENT); |
| break; |
| } |
| if (find_script_byname (command->name, &starting_list, &service) |
| != NULL) |
| { /* Someone else is trying to provide */ |
| script->next_attempting_provider = service->attempting_providers; |
| service->attempting_providers = script; |
| break; |
| } |
| if ( ( service = find_service_in_list (command->name, |
| unavailable_services) ) |
| == NULL ) |
| { /* We're the first to try and provide: create it */ |
| if ( ( service = |
| calloc (1, strlen (command->name) + sizeof *service) ) |
| == NULL ) |
| { |
| kill (command->pid, SIG_NOT_CHILD); |
| break; |
| } |
| strcpy (service->name, command->name); |
| } |
| else |
| { /* Orphaned service: unhook and grab it */ |
| if (service->prev == NULL) unavailable_services = service->next; |
| else service->prev->next = service->next; |
| if (service->next != NULL) service->next->prev = service->prev; |
| service->next = NULL; |
| } |
| service->prev = script->last_service; |
| script->last_service->next = service; |
| script->last_service = service; |
| kill (command->pid, SIG_NOT_PRESENT); |
| break; |
| case -1: |
| default: |
| break; |
| } |
| } /* End Function process_command */ |
| |
| static int run_command (const char *file, const char *name, pid_t pid) |
| { |
| struct script_struct *script; |
| struct needer_struct *needer = NULL; |
| struct service_struct *service; |
| |
| if (find_script_byname (name, &available_list, NULL) != NULL) |
| return SIG_PRESENT; |
| if (pid != 0) |
| { |
| needer = calloc (1, sizeof *needer); |
| if (needer == NULL) return SIG_FAILED; |
| needer->pid = pid; |
| } |
| script = find_script_byname (name, &starting_list, &service); |
| if (script == NULL) |
| service = find_service_in_list (name, unavailable_services); |
| if (service == NULL) |
| { |
| int i; |
| char txt[1024]; |
| |
| if ( ( script = calloc (1, sizeof *script) ) == NULL ) |
| { |
| free (needer); |
| return SIG_FAILED; |
| } |
| service = calloc (1, strlen (name) + sizeof *service); |
| if (service == NULL) |
| { |
| free (script); |
| return SIG_FAILED; |
| } |
| strcpy (service->name, name); |
| switch ( script->pid = fork () ) |
| { |
| case 0: /* Child */ |
| for (i = 1; i < NSIG; i++) signal (i, SIG_DFL); |
| execlp (get_path (file), service->name, "start", NULL); |
| snprintf (txt, sizeof(txt), |
| _("error running programme: \"%s\"\n"), service->name); |
| err (txt); |
| _exit (SIG_FAILED); |
| break; |
| case -1: /* Error */ |
| service->next = unavailable_services; |
| if (unavailable_services != NULL) |
| unavailable_services->prev = service; |
| unavailable_services = service; |
| free (script); |
| free (needer); |
| return SIG_FAILED; |
| /*break;*/ |
| default: /* Parent */ |
| script->first_service = service; |
| script->last_service = service; |
| insert_entry (&starting_list, script); |
| sched_yield (); |
| break; |
| } |
| } |
| if (needer == NULL) return 0; |
| needer->next = service->needers; |
| service->needers = needer; |
| return 0; |
| } /* End Function run_command */ |
| |
| static struct service_struct *find_service_in_list (const char *name, |
| struct service_struct *sv) |
| { |
| for (; sv != NULL; sv = sv->next) |
| if (strcmp (sv->name, name) == 0) return (sv); |
| return NULL; |
| } /* End Function find_service_in_list */ |
| |
| static struct script_struct *find_script_byname (const char *name, |
| struct list_head *head, |
| struct service_struct **service) |
| { |
| struct script_struct *script; |
| |
| for (script = head->first; script != NULL; script = script->next) |
| { |
| struct service_struct *sv; |
| |
| if ( ( sv = find_service_in_list (name, script->first_service) ) |
| != NULL ) |
| { |
| if (service != NULL) *service = sv; |
| return (script); |
| } |
| } |
| if (service != NULL) *service = NULL; |
| return NULL; |
| } /* End Function find_script_byname */ |
| |
| static struct script_struct *find_script_bypid (pid_t pid, |
| struct list_head *head) |
| { |
| struct script_struct *script; |
| |
| for (script = head->first; script != NULL; script = script->next) |
| if (script->pid == pid) return (script); |
| return NULL; |
| } /* End Function find_script_bypid */ |
| |
| static void insert_entry (struct list_head *head, struct script_struct *entry) |
| { |
| if (entry == NULL) return; |
| entry->prev = NULL; |
| entry->next = head->first; |
| if (head->first != NULL) head->first->prev = entry; |
| head->first = entry; |
| if (head->last == NULL) head->last = entry; |
| ++head->num_entries; |
| } /* End Function insert_entry */ |
| |
| static void remove_entry (struct list_head *head, struct script_struct *entry) |
| { |
| if (entry->prev == NULL) head->first = entry->next; |
| else entry->prev->next = entry->next; |
| if (entry->next == NULL) head->last = entry->prev; |
| else entry->next->prev = entry->prev; |
| --head->num_entries; |
| } /* End Function remove_entry */ |
| |
| static void signal_needers (struct service_struct *service, int sig) |
| { |
| struct needer_struct *needer, *next_needer; |
| |
| for (needer = service->needers; needer != NULL; needer = next_needer) |
| { |
| kill (needer->pid, sig); |
| next_needer = needer->next; |
| free (needer); |
| --num_needers; |
| } |
| service->needers = NULL; |
| } /* End Function signal_needers */ |
| |
| static void handle_nonworking (struct script_struct *script) |
| { |
| struct service_struct *service, *next; |
| |
| for (service = script->first_service; service != NULL; service = next) |
| { |
| struct script_struct *provider = service->attempting_providers; |
| |
| next = service->next; |
| if (provider == NULL) |
| { |
| service->prev = NULL; |
| service->next = unavailable_services; |
| if (unavailable_services != NULL) |
| unavailable_services->prev = service; |
| unavailable_services = service; |
| continue; |
| } |
| service->attempting_providers = provider->next_attempting_provider; |
| provider->last_service->next = service; |
| service->prev = provider->last_service; |
| provider->last_service = service; |
| service->next = NULL; |
| kill (provider->pid, SIG_NOT_PRESENT); |
| } |
| free (script); |
| } /* End Function handle_nonworking */ |
| |
| static int force_progress (void) |
| /* [RETURNS] 0 if boot scripts are still running, else -1. |
| */ |
| { |
| struct service_struct *service; |
| |
| if (starting_list.num_entries > num_needers) return 0; |
| /* No progress can be made: signal needers */ |
| for (service = unavailable_services; service != NULL; |
| service = service->next) |
| signal_needers (service, |
| service->failed ? SIG_FAILED : SIG_NOT_PRESENT); |
| return (starting_list.num_entries < 1) ? -1 : 0; |
| } /* End Function force_progress */ |
| |
| static void show_scripts (FILE *fp, const struct script_struct *script, |
| const char *type) |
| { |
| fprintf (fp, "%s SERVICES:\n", type); |
| for (; script != NULL; script = script->next) |
| { |
| struct service_struct *service = script->first_service; |
| |
| fputs (service->name, fp); |
| for (service = service->next; service != NULL; service = service->next) |
| fprintf (fp, " (%s)", service->name); |
| putc ('\n', fp); |
| } |
| } /* End Function show_scripts */ |
| |
| static const char *get_path (const char *file) |
| { |
| char *p1, *p2; |
| static char path[PATH_SIZE]; |
| |
| if (file[0] == '/') return file; |
| if (init_path[0] == '\0') return file; |
| for (p1 = init_path; *p1 != '\0'; p1 = p2) |
| { |
| if ( ( p2 = strchr (p1, ':') ) == NULL ) |
| p2 = p1 + strlen (p1); |
| strncpy (path, p1, p2 - p1); |
| path[p2 - p1] = '/'; |
| strcpy (path + (p2 - p1) + 1, file); |
| if (*p2 == ':') ++p2; |
| if (access (path, X_OK) == 0) return path; |
| } |
| return file; |
| } /* End Function get_path */ |