/* simpleinit.c - poe@daimi.aau.dk */ /* Version 2.0.2 */ /* 1999-02-22 Arkadiusz Mi¶kiewicz * - added Native Language Support * 2001-01-25 Richard Gooch * - fixed bug with failed services so they may be later "reclaimed" * 2001-02-02 Richard Gooch * - fixed race when reading from pipe and reaping children * 2001-02-18 sam@quux.dropbear.id.au * - fixed bug in : multiple INIT_PATH components did not work * 2001-02-21 Richard Gooch * - block signals in handlers, so that longjmp() doesn't kill context * 2001-02-25 Richard Gooch * - 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 #include #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 #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 */