/* simpleinit.c - poe@daimi.aau.dk */
/* Version 1.21 */

/* gerg@snapgear.com -- modified for direct console support DEC/1999 */

#define _GNU_SOURCE	/* For crypt() and termios defines */

#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <stdio.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/termios.h>
#include <sys/ioctl.h>
#include <sys/uio.h>
#include <linux/version.h>
#include <utmp.h>
#include <errno.h>
#include <termios.h>
#ifdef SHADOW_PWD
#include <shadow.h>
#endif

#include <config/autoconf.h>

#if __GNU_LIBRARY__ > 5
#include <sys/reboot.h>
#endif

#include "pathnames.h"

#define CMDSIZ 80	/* max size of a line in inittab */
#define NUMCMD 16	/* max number of lines in inittab */
#define NUMTOK 10	/* max number of tokens in inittab command */

#define TESTTIME  90	/* Threshold time for detecting "fast" spawning processes */
#define MAXSPAWN  5	/* Number of rapid respawns that counts as too fast */
#define SLEEPTIME 30	/* Fast spawn hold off period */
#define DELAYTIME 5	/* Time between successive runs of a process */

#define MAXTRIES 3	/* number of tries allowed when giving the password */

#define RUN_RC		/* Use a console if possible */

#ifdef INCLUDE_TIMEZONE
char tzone[CMDSIZ];
#endif
/* #define DEBUGGING */

/* Define this if you want init to ignore the termcap field in inittab for
   console ttys. */
/* #define SPECIAL_CONSOLE_TERM */

struct initline {
	pid_t	pid;
	time_t	lastrun;
	time_t	nextrun;
	char	*toks[NUMTOK];
	char	tty[10];
	char	termcap[30];
	char	line[CMDSIZ];
	char	fullline[CMDSIZ];
	unsigned char xcnt;
};

struct initline inittab[NUMCMD];
int numcmd;
int stopped = 0;	/* are we stopped */
int reload = 0;	/* are we stopped */
int run_sigint_processing = 0;

extern void spawn(int);
extern void hup_handler();
extern void reload_inittab();
extern void read_inittab(void);
extern void read_initfile(const char *);
extern void tstp_handler();
extern void int_handler();
extern void sigint_processing();
extern void cont_handler();
extern void set_tz(void);
extern void write_wtmp(void);
extern void make_ascii_tty(void);
extern void make_console(const char *);
extern int boot_single(int singlearg, int argc, char *argv[]);

/* Keep track of console device, if any... */
#if LINUX_VERSION_CODE < 0x020100
char	*console_device = NULL;
int	console_baud = -1;
#else
int have_console = 0;
#endif


static void err(const char *s)
{
	struct iovec output[2];
#if LINUX_VERSION_CODE < 0x020100
	int fd;
#endif
	output[0].iov_base = "init: ";
	output[0].iov_len = 6;
	output[1].iov_base = (void *)s;
	output[1].iov_len = strlen(s);
#if LINUX_VERSION_CODE < 0x020100	
	if (console_device == NULL) return;
	if((fd = open(console_device, O_WRONLY)) < 0) return;
	writev(fd, output, 2);
	close(fd);
#else
	if (have_console)
		writev(1, output, 2);
#endif
}

static void enter_single(void)
{
	pid_t pid;
	char *av[2];

    err("Booting to single user mode\n");
    av[0] = _PATH_BSHELL;
    av[1] = NULL;
    if((pid = vfork()) == 0) {
    extern char **environ;
	/* the child */
	execve(_PATH_BSHELL, av, environ);
	err("exec of single user shell failed\n");
	_exit(0);
    } else if(pid > 0) {
    int i;
	while(wait(&i) != pid) /* nothing */;
    } else if(pid < 0) {
	err("fork of single user shell failed\n");
    }
    unlink(_PATH_SINGLE);
}


#if LINUX_VERSION_CODE < 0x020100
static void
set_console_baud(int baud)
{
	switch (baud) {
	case 50:     console_baud = B50; break;
	case 75:     console_baud = B75; break;
	case 110:    console_baud = B110; break;
	case 134:    console_baud = B134; break;
	case 150:    console_baud = B150; break;
	case 200:    console_baud = B200; break;
	case 300:    console_baud = B300; break;
	case 600:    console_baud = B600; break;
	case 1200:   console_baud = B1200; break;
	case 1800:   console_baud = B1800; break;
	case 2400:   console_baud = B2400; break;
	case 4800:   console_baud = B4800; break;
	default:
	case 9600:   console_baud = B9600; break;
	case 19200:  console_baud = B19200; break;
	case 38400:  console_baud = B38400; break;
	case 57600:  console_baud = B57600; break;
	case 115200: console_baud = B115200; break;
	case 230400: console_baud = B230400; break;
	case 460800: console_baud = B460800; break;
	}
}
#endif

static int do_command(const char *path, const char *filename, int dowait)
{
	pid_t pid, wpid;
	int stat, st;
	
	if((pid = vfork()) == 0) {
		/* the child */
		char *argv[3];
#ifdef INCLUDE_TIMEZONE
		char tz[CMDSIZ];
#endif
		char *env[3];

		close(0);
		argv[0] = (char *)path;
		argv[1] = (char *)filename;
		argv[2] = NULL;

		env[0] = "PATH=/bin:/usr/bin:/etc:/sbin:/usr/sbin";
#ifdef INCLUDE_TIMEZONE
		strcpy(tz, "TZ=");
		strcat(tz, tzone);
		env[1] = tz;
		env[2] = NULL;
#else
		env[1] = NULL;
#endif

		execve(path, argv, env);

		err("exec rc failed\n");
		_exit(2);
	} else if(pid > 0) {
		if (!dowait)
			stat = 0;
		else {
			/* parent, wait till rc process dies before spawning */
			while ((wpid = wait(&stat)) != pid)
				if (wpid == -1 && errno == ECHILD) { /* see wait(2) manpage */
					stat = 0;
					break;
				}
		}
	} else if(pid < 0) {
		err("fork of rc shell failed\n");
		stat = -1;
	}
	st = WEXITSTATUS(stat);
	return st;
}

/*
 * run /etc/rc. The environment is passed to the script, so the RC environment
 * variable can be used to decide what to do. RC may be set from LILO.
 */
static int do_rc(void)
{
	int rc;

	rc = do_command(_PATH_BSHELL, _PATH_RC, 1);
	if (rc)
		return(rc);
#ifdef CONFIG_USER_INIT_RUN_FIREWALL
	rc = do_command(_PATH_FIREWALL, "-i", 1);
	if (rc)
		err(_PATH_FIREWALL " failed!");
#endif
#ifdef CONFIG_USER_FLATFSD_FLATFSD
	rc = do_command(_PATH_BSHELL, _PATH_CONFIGRC, 1);
	if (rc)
		err(_PATH_CONFIGRC " failed!");
#endif
#ifdef CONFIG_USER_INIT_RUN_FIREWALL
	rc = do_command(_PATH_FIREWALL, NULL, 0);
	if (rc)
		err(_PATH_FIREWALL " failed!");
#endif
#ifdef INCLUDE_TIMEZONE
	/* We read the timezone file here, because the flat file system
	 * has probably been created by now.
	 */
	set_tz();
#endif
	return(0);
}

void respawn_children() {
	int i, delta = -1;
	time_t now;
	alarm(0);
	if ((now = time(NULL)) == 0) now = 1;
	for(i = 0; i < numcmd; i++) {
		if(inittab[i].pid < 0) {	/* Start jobs */
			if(stopped)
				inittab[i].pid = -1;
			else
				spawn(i);
		}
		/* Check for naughty jobs */
		if (inittab[i].nextrun > now) {
		int d;
			d = inittab[i].nextrun - now;
			if (delta < 0 || d < delta)
				delta = d;
		}
	}
	if (delta > 0) {
		alarm(delta);
	}
}

int main(int argc, char *argv[])
{
	int 	i;
	struct sigaction sa;

	/*
	 * setup all the signal handlers here
	 */

	memset(&sa, 0, sizeof(sa));
	/* sa.sa_flags = SA_RESETHAND we want to keep the handlers armed */

	sa.sa_handler = tstp_handler;
	sigaction(SIGTSTP, &sa, NULL);

	sa.sa_handler = cont_handler;
	sigaction(SIGCONT, &sa, NULL);

	sa.sa_handler = int_handler;
	sigaction(SIGINT, &sa, NULL);

	sa.sa_handler = respawn_children;
	sigaction(SIGALRM, &sa, NULL);

	sa.sa_handler = hup_handler;
	sigaction(SIGHUP, &sa, NULL);

#if defined(CONSOLE_BAUD_RATE) && LINUX_VERSION_CODE < 0x020100
	sdfs
	set_console_baud(CONSOLE_BAUD_RATE);
#endif

	/* 
	 * start up in single user mode if /etc/singleboot exists or if
	 * argv[1] is "single".
	 */
	if(boot_single(0, argc, argv)) enter_single();

#ifdef RUN_RC
	/* Register console if defined by boot */
#if LINUX_VERSION_CODE < 0x020100
	if ((console_device = getenv("CONSOLE"))) {
	char	*sp;
		unsetenv("CONSOLE");
		if ((sp = strchr(console_device, ','))) {
			*sp++ = 0;
			set_console_baud(atoi(sp));
		}
	}

	make_ascii_tty();
#else
{
	struct stat st;

	if (isatty(1)) {
		have_console = 1;
		make_ascii_tty();
	} else if (fstat(1, &st) == -1 && errno == EBADF) {
		close(0); close(1); close(2);
		open("/dev/null", O_RDWR);
		dup(0);
		dup(0);
	}
}
#endif

	/*If we get a SIGTSTP before multi-user mode, do nothing*/
	while(stopped)	
		pause();
	if(do_rc() != 0 && boot_single(1, argc, argv) && !stopped)
		enter_single();
	while(stopped)	/*Also if /etc/rc fails & we get SIGTSTP*/
		pause();
#endif

	write_wtmp();	/* write boottime record */
	read_inittab();

#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

#if LINUX_VERSION_CODE < 0x020100
	for(i = 0; i < getdtablesize(); i++) close(i);
#else
	i = 0;
	if (have_console)
		i = 3;
	for(; i < getdtablesize(); i++) close(i);
#endif

	for (;;) {
		pid_t	pid;
		int	vec;

		if (run_sigint_processing) {
			run_sigint_processing = 0;
			sigint_processing();
		}

		respawn_children();

		if (reload) {
			reload = 0;
			reload_inittab();
			continue; /* process all reloads before waiting */
		}

		pid = wait(&vec);
		alarm(0);

		/* clear utmp entry, and append to wtmp if possible */
#if 0		/* DAVIDM */
		{
		    struct utmp *ut;
		    int ut_fd;

		    utmpname(_PATH_UTMP);
		    setutent();
		    while((ut = getutent())) {
			if(ut->ut_pid == pid) {
			    time(&ut->ut_time);
			    bzero(&ut->ut_user, UT_NAMESIZE);
			    bzero(&ut->ut_host, sizeof(ut->ut_host));
			    ut->ut_type = DEAD_PROCESS;
			    ut->ut_pid = 0;
			    ut->ut_addr = 0;
			    endutent();
			    pututline(ut);
			    if((ut_fd = open(_PATH_WTMP, O_APPEND|O_WRONLY)) >= 0) {
				flock(ut_fd, LOCK_EX|LOCK_NB);
				write(ut_fd, (const void *)ut, sizeof(struct utmp));
				flock(ut_fd, LOCK_UN|LOCK_NB);
				close(ut_fd);
			    }
			    break;
			}
		    }
		    endutent();
		}
#endif

		for(i = 0; i < numcmd; i++) {
			if(pid == inittab[i].pid) {
				inittab[i].pid = -1;
			}
		}
	}
}	


/*
 * return true if we should boot up in singleuser mode. If argv[i] is 
 * "single" or the file /etc/singleboot exists, then singleuser mode should
 * be entered. If /etc/securesingle exists ask for root password first.
 */
int boot_single(int singlearg, int argc, char *argv[])
{
	char *pass, *rootpass = NULL;
	struct passwd *pwd;
	int i;

	for(i = 1; i < argc; i++) {
	    if(argv[i] && !strcmp(argv[i], "single")) singlearg = 1;
	}

	if(access(_PATH_SINGLE, 04) == 0 || singlearg) {
		if(access(_PATH_SECURE, 04) == 0) {
			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");
			}
		} else return 1;
	}
	return 0;
}

void spawn(int i)
{
	pid_t pid;
	int j;
	time_t t;
	struct initline *it;
	char buf[150];
	
	it = inittab + i;

	t = time(NULL);
	if (it->nextrun > t)			/* Check for held process */
		return;
	if (it->lastrun + TESTTIME > t) {	/* Respawning quickly */
		if (it->xcnt < 0xff)
			it->xcnt++;
	} else {				/* Normal respawning */
		it->xcnt = 0;
		it->lastrun = t;
	}
	if (it->xcnt >= MAXSPAWN) {		/* Too many too quickly */
		strcpy(buf, it->toks[0]);
		strcat(buf, " respawning too fast\n");
		err(buf);
		it->pid = -1;
		it->nextrun = t + SLEEPTIME;
		/* Fiddle with the tracking vars to ensure that only
		 * one attempt is made to run this next time around.
		 */
		it->lastrun = it->nextrun;
		it->xcnt -= 2;
		return;
	}
	it->nextrun = t + DELAYTIME;
	
	if((pid = vfork()) < 0) {
		it->pid = -1;
		err("fork failed\n");
		return;
	}
	if(pid) {
		/* this is the parent */
		it->pid = pid;
		return;
	} else {
		/* this is the child */
		char term[40];
#ifdef INCLUDE_TIMEZONE
		char tz[CMDSIZ];
#endif
		char *env[4];
		
		setsid();

		// for(j = have_console ? 1 : 0; j < getdtablesize(); j++)
		for(j = 0; j < getdtablesize(); j++)
			close(j);
		make_console(it->tty);

		strcpy(term, "TERM=");
		strcat(term, it->termcap);
		env[0] = term;
		env[1] = "PATH=/bin:/usr/bin:/etc:/sbin:/usr/sbin";
#ifdef INCLUDE_TIMEZONE
		strcpy(tz, "TZ=");
		strcat(tz, tzone);
		env[2] = tz;
		env[3] = NULL;
#else
		env[2] = NULL;
#endif

		execve(it->toks[0], it->toks, env);
		strcpy(buf, it->toks[0]);
		strcat(buf, " exec failed\n");
		err(buf);
		_exit(1);
	}
}

static void init_itab(struct initline *p) {
	bzero(p, sizeof(struct initline));
	p->pid = -1;
}

void read_inittab(void)
{
	numcmd = 0;

	/* Fake an inittab entry if boot console defined */
#ifdef CONFIG_USER_INIT_CONSOLE_SH
#if LINUX_VERSION_CODE < 0x020100
	if (console_device && strcmp(console_device, "/dev/null"))
#else
	if (have_console)
#endif
	{
	struct initline *p;
		p = inittab + numcmd++;
		init_itab(p);
		strcpy(p->fullline, "console");
		strcpy(p->tty, "console");
		strcpy(p->termcap, "linux");
		p->toks[0] = "/bin/sh";
	}
#endif

	read_initfile(_PATH_INITTAB);

#ifdef CONFIG_USER_FLATFSD_FLATFSD
	read_initfile(_PATH_CONFIGTAB);
#endif

	if (numcmd == 0)
		_exit(1);
}

void read_initfile(const char *initfile)
{
	struct initline *p;
	FILE *f;
	char buf[CMDSIZ];
	int i,j,k;
	char *ptr, *getty;
#ifdef SPECIAL_CONSOLE_TERM
	char tty[50];
	struct stat stb;
#endif
	char *termenv, *getenv();
	
	termenv = getenv("TERM");	/* set by kernel */
	/* termenv = "vt100"; */
			
	i = numcmd;

	if(!(f = fopen(initfile, "r"))) {
		err("cannot open inittab\n");
		return;
	}

	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] = '\0'; break; 
			}
		}

		if(buf[0] == '\0' || buf[0] == '\n') continue;

		p = inittab + i;
		init_itab(p);
		strcpy(p->line, buf);
		strcpy(p->fullline, buf);
		strtok(p->line, ":");
		strncpy(p->tty, p->line, 9);
		//p->tty[9] = '\0';
		strncpy(p->termcap, strtok(NULL, ":"), 29);
		//p->termcap[29] = '\0';

		getty = strtok(NULL, ":");
		strtok(getty, " \t\n");
		p->toks[0] = getty;
		j = 1;
		while((ptr = strtok(NULL, " \t\n")))
			p->toks[j++] = ptr;

#ifdef SPECIAL_CONSOLE_TERM
		/* special-case termcap for the console ttys */
		strcpy(tty, "/dev/");
		strcat(tty, p->tty);
		if(!termenv || stat(tty, &stb) < 0) {
			err("no TERM or cannot stat tty\n");
		} else {
			/* is it a console tty? */
			if(major(stb.st_rdev) == 4 && minor(stb.st_rdev) < 64) {
				strncpy(p->termcap, termenv, 30);
				p->termcap[29] = 0;
			}
		}
#endif

		i++;
	}
	fclose(f);

	numcmd = i;
}

void hup_handler()
{
	reload = 1;
}

void reload_inittab()
{
	int i;
	int oldnum;
	char saveline[NUMCMD][CMDSIZ];
	pid_t savepid[NUMCMD];

	for (i=0; i<numcmd; i++) {
		savepid[i] = inittab[i].pid;
		strcpy(saveline[i], inittab[i].fullline);
	}
	oldnum = numcmd;		
	read_inittab();

	/* See which ones still exist */
	for(i = 0; i < numcmd; i++) {
	int j;
		for(j = 0; j < oldnum; j++) {
			if(strcmp(saveline[j], inittab[i].fullline) == 0) {
				inittab[i].pid = savepid[j];
				savepid[j] = -1;
				break;
			}
		}
	}

	/* Kill off processes no longer needed */
	for(i = 0; i < oldnum; i++) {
		if (savepid[i] > 1)
			kill(savepid[i], SIGTERM);
	}
}

void tstp_handler()
{
	stopped++;
}

void cont_handler()
{
	stopped = 0;
}

void int_handler()
{
	run_sigint_processing = 1;
}

void sigint_processing()
{
	/*
	 * After Linux 0.96b PL1, we get a SIGINT when
	 * the user presses Ctrl-Alt-Del...
	 */

	int pid;
	
	sync();
	sync();
	if((pid = vfork()) == 0) {
	char *av[2];
	extern char **environ;
		/* reboot properly... */
		av[0] = _PATH_REBOOT;
		av[1] = NULL;
		
		execve(_PATH_REBOOT, av, environ);
#if __GNU_LIBRARY__ > 5
		reboot(0x1234567);
#else
		reboot(0xfee1dead, 672274793, 0x1234567);
#endif
		_exit(2);
	} else if(pid < 0) {
		/* fork failed, try the hard way... */
#if __GNU_LIBRARY__ > 5
		reboot(0x1234567);
#else
		reboot(0xfee1dead, 672274793, 0x1234567);
#endif
	}
}

#ifdef INCLUDE_TIMEZONE
void set_tz(void)
{
	FILE *f;
	int len;

	if((f = fopen("/etc/config/TZ", "r")) == NULL &&
	   (f = fopen("/etc/TZ", "r")) == NULL)
		return;
	fgets(tzone, 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

void write_wtmp(void)
{
#if 0
    int fd;
    struct utmp ut;
    
    bzero((char *)&ut, sizeof(ut));
    strcpy(ut.ut_line, "~");
    bzero(ut.ut_name, sizeof(ut.ut_name));
    time(&ut.ut_time);
    ut.ut_type = BOOT_TIME;
    
    if((fd = open(_PATH_WTMP, O_WRONLY|O_APPEND)) >= 0) {
	flock(fd, LOCK_EX|LOCK_NB); /* make sure init won't hang */
	write(fd, (char *)&ut, sizeof(ut));
	flock(fd, LOCK_UN|LOCK_NB);
	close(fd);
    }
#endif
}     

void make_ascii_tty(void)
{
	struct termios tty;

	tcgetattr(0, &tty);
	tty.c_iflag &= ~(INLCR|IGNCR|IUCLC);
	tty.c_iflag |= ICRNL;
	tty.c_oflag &= ~(OCRNL|OLCUC|ONOCR|ONLRET|OFILL);
	tty.c_oflag |= OPOST|ONLCR;
	tty.c_cflag |= CLOCAL;
	tty.c_lflag  = ISIG|ICANON|ECHO|ECHOE|ECHOK|ECHOCTL|ECHOKE;

#if LINUX_VERSION_CODE < 0x020100
	if (console_baud != -1)
		cfsetospeed(&tty, console_baud);
#endif

	tty.c_cc[VINTR]  = 3;
	tty.c_cc[VQUIT]  = 28;
	tty.c_cc[VERASE] = 8/*127*/;
	tty.c_cc[VKILL]  = 24;
	tty.c_cc[VEOF]   = 4;
	tty.c_cc[VTIME]  = 0;
	tty.c_cc[VMIN]   = 1;
	tty.c_cc[VSTART] = 17;
	tty.c_cc[VSTOP]  = 19;
	tty.c_cc[VSUSP]  = 26;

	tcsetattr(0, TCSANOW, &tty);
}

void make_console(const char *tty)
{
	int j;
	char devname[32];

	close(0); close(1); close(2);
	if (!tty || !*tty) {
		if (open("/dev/null", O_RDWR|O_NONBLOCK) >= 0)
			dup(0), dup(0);
		return;
	}

#if LINUX_VERSION_CODE < 0x020100
/*
 *	until we get proper console support under 2.0
 */
	if (strcmp(tty, "console") == 0) {
		strcpy(devname, console_device);
	} else
#endif
	{
		strcpy(devname, "/dev/");
		strcat(devname, tty);
	}

#if 1 // DAVIDM_FIXME
	if (open(devname, O_RDWR|O_NONBLOCK) == -1) {
#ifdef DEBUGGING
		printf("console '%s' open failed: %d\n", devname, errno);
#endif
		return;
	}
#endif

	fcntl(0, F_SETFL, 0);
	dup(0); 
	dup(0);
	make_ascii_tty();
	j = ioctl(0, TIOCSCTTY, (char*)0);
}