1241 lines
33 KiB
C
1241 lines
33 KiB
C
/* 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 <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 = sprintf (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;
|
|
sprintf (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);
|
|
|
|
(void) sprintf(term, "TERM=%s", inittab[i].termcap);
|
|
env[0] = term;
|
|
env[1] = (char *)0;
|
|
#ifdef SET_TZ
|
|
(void) sprintf(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 */
|
|
(void) sprintf(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, COMMAND_SIZE);
|
|
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);
|
|
sprintf (txt, _("error stopping service: \"%s\""),
|
|
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) )
|
|
{
|
|
sprintf (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 )
|
|
{
|
|
if (needer != 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);
|
|
sprintf (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);
|
|
if (needer != NULL) 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 */
|