aboutsummaryrefslogblamecommitdiff
path: root/release/picobsd/tinyware/oinit/oinit.c
blob: 7715f3e67a5bd5a85e5e1a628fb9b1848e1a84e1 (plain) (tree)
























                                                                             
            


























































                                                                     
                                             














                                                                            










                                          





                                                                  
                                                                                  














































































































































































































































































































































































                                                                                                           
                                                                






















































































                                                                                    
                             

























                                                                           








                            

















                                              

                                
                          
                                     
























                                                                              









                                      




























































































































































































































































                                                                                                 
                                                                 

                                          
                                                                  
                                 
                          
                                                           
                        
                                                                      
                 
















                                           
/*-
 * Copyright (c) 1998 Andrzej Bialecki
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * $FreeBSD$
 */

/*
 * A primitive version of init(8) with simplistic user interface
 */

#include <sys/types.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <sys/reboot.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <ctype.h>
#include <err.h>

#ifdef USE_HISTORY
#error "Not yet. But it's quite simple to add - patches are welcome!"
#endif

#include <errno.h>
#include <fcntl.h>
#include <libutil.h>
#include <paths.h>
#include <setjmp.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <syslog.h>
#include <unistd.h>
#include <varargs.h>

#define BUFSIZE 1024
#define MAX_CONS 12

#define	NONE	0
#define	SINGLE	1
#define	MULTI	2
#define	DEATH	3

#define	FALSE	0
#define TRUE	1

char cwd[BUFSIZE];
char vty[]="0123456789abcdef";
char *progname;
char *motd=NULL;
int ncons=MAX_CONS;
int Reboot=FALSE;
int transition=MULTI;
int prevtrans=SINGLE;
jmp_buf machine;

char *trans[]={ "NONE", "SINGLE", "MULTI", "DEATH" };

extern char **environ;

/* Struct for holding session state */
struct sess {
	char tty[16];	/* vty device path */
	pid_t pid;	/* pid of process running on it */
	int (*func)(int argc, char **argv);
			/* internal function to run on it (after forking) */
} ttys[MAX_CONS];

/* Struct for built-in command */
struct command {
	char *cmd;		/* command name */
	char *descr;		/* command description */
	char *usage;		/* usage */
	char *example;		/* example of usage */
	int (*func)(char *);	/* callback function */
};

/* Prototypes */
int cd(char *);
int pwd(char *);
int echo(char *);
int xit(char *);
int set(char *);
int unset(char *);
int env(char *);
int help(char *);
int sourcer(char *);
void do_command(int shell, char *cmdline);
void transition_handler(int);

/* Table of built-in functions */
struct command bltins[]={
	{"cd","Change working directory","cd [dir]","cd /etc",cd},
	{"pwd","Print current directory","pwd","pwd",pwd},
	{"exit","Exit from shell()","exit","exit",xit},
	{"set","Set environment variable","set [VAR=value]","set TERM=xterm",set},
	{"unset","Unset environment variable","unset VAR","unset EDITOR",unset},
	{"echo","Echo arguments on stdout","echo arg1 arg2 ...","echo Hello World!",echo},
	{"env","Print all environment variables","env","env",env},
	{".","Source-in a file with commands",". filename",". /etc/rc",sourcer},
	{"?","Print this help :-)","? [command]","? set",help},
	{NULL,NULL,NULL,NULL,NULL}
};

/*
 * Built-in 'cd <path>' handler
 */
int
cd(char *path)
{
	if(chdir(path)) return(-1);
	getcwd(cwd,BUFSIZE);
	return(0);
}

/*
 * Built-in 'pwd' handler
 */
int
pwd(char *dummy)
{

	if(getcwd(cwd,BUFSIZE)==NULL) return(-1);
	printf("%s\n",cwd);
	return(0);
}

/*
 * Built-in 'exit' handler
 */
int
xit(char *dummy)
{
	_exit(0);
}

/*
 * Built-in 'echo' handler
 */
int
echo(char *args)
{
	int i=0,j;
	int len;
	char c;
	int s_quote=0,d_quote=0;
	int sep=0,no_lf=0;

	if(args==NULL) {
		printf("\n");
		return;
	}
	len=strlen(args);
	if(len>=2) {
		if(args[0]=='-' && args[1]=='n') {
			no_lf++;
			i=2;
			while(i<len && (args[i]==' ' || args[i]=='\t')) i++;
		}
	}
	while(i<len) {
		c=args[i];
		switch(c) {
		case ' ':
		case '\t':
			if(s_quote||d_quote) {
				putchar(c);
			} else if(!sep) {
				putchar(' ');
				sep=1;
			}
			break;
		case '\\':
			i++;
			c=args[i];
			switch(c) {
			case 'n':
				putchar('\n');
				break;
			case 'b':
				putchar('\b');
				break;
			case 't':
				putchar('\t');
				break;
			case 'r':
				putchar('\r');
				break;
			default:
				putchar(c);
				break;
			}
			break;
		case '"':
			if(!d_quote) {
				d_quote=1;
				for(j=i+1;j<len;j++) {
					if(args[j]=='\\') {
						j++;
						continue;
					}
					if(args[j]=='"') {
						d_quote=2;
						break;
					}
				}
				if(d_quote!=2) {
					printf("\necho(): unmatched \"\n");
					return;
				}
			} else d_quote=0;
			break;
		case '\'':
			if(!s_quote) {
				s_quote=1;
				for(j=i+1;j<len;j++) {
					if(args[j]=='\\') {
						j++;
						continue;
					}
					if(args[j]=='\'') {
						s_quote=2;
						break;
					}
				}
				if(s_quote!=2) {
					printf("\necho(): unmatched '\n");
					return;
				}
			} else s_quote=0;
			break;
		case '`':
			printf("echo(): backquote not implemented yet!\n");
			break;
		default:
			sep=0;
			putchar(c);
			break;
		}
		i++;
	}
	if(!no_lf) putchar('\n');
	fflush(stdout);
}

/*
 * Built-in 'set VAR=value' handler
 */
int
set(char *var)
{
	int res;

	if(var==NULL) return(env(NULL));
	res=putenv(var);
	if(res) printf("set: %s\n",strerror(errno));
	return(res);
}

/*
 * Built-in 'env' handler
 */
int
env(char *dummy)
{
	char **e;

	e=environ;
	while(*e!=NULL) {
		printf("%s\n",*e++);
	}
	return(0);
}

/*
 * Built-in 'unset VAR' handler
 */
int
unset(char *var)
{
	if(var==NULL) {
		printf("%s: parameter required.\n",progname);
		return(-1);
	}
	return(unsetenv(var));
}

/*
 * Built-in '?' handler
 */
int
help(char *cmd)
{
	struct command *x;
	int found=0;

	if(cmd==NULL) {
		printf("\nBuilt-in commands:\n");
		printf("-------------------\n");
		x=bltins;
		while(x->cmd!=NULL) {
			printf("%s\t%s\n",x->cmd,x->descr);
			x++;
		}
		printf("\nEnter '? <cmd>' for details.\n\n");
		return(0);
	} else {
		x=bltins;
		while(x->cmd!=NULL) {
			if(strcmp(x->cmd,cmd)==0) {
				found++;
				break;
			}
			x++;
		}
		if(found) {
			printf("\n%s\t%s:\n",x->cmd,x->descr);
			printf("\tUsage:\n\t\t%s\n",x->usage);
			printf("\te.g:\n\t\t%s\n\n",x->example);
			return(0);
		} else {
			printf("\n%s: no such command.\n\n",cmd);
			return(-1);
		}
	}
}

/*
 * Signal handler for shell()
 */
void
shell_sig(int sig)
{
	switch(sig) {
	case SIGINT:
	case SIGQUIT:
	case SIGTERM:
		/* ignore ? */
		break;
	default:
		break;
	}
}

/*
 * Built-in '.' handler (read-in and execute commands from file)
 */
int
sourcer(char *fname)
{
	FILE *fd;
	char buf[512],*tok,*arg,**av;
	int ac,len,f,res,i;
	pid_t pid;
	char *sep=" \t";

	fd=fopen(fname,"r");
	if(fd==NULL) {
		printf("Couldn't open file '%s'\n",fname);
		return(-1);
	}
	while(!feof(fd)) {
		memset(buf,0,512);
		if(fgets(buf,512,fd)==NULL) continue;
		if((*buf=='#') || (*buf=='\n')) continue;
		len=strlen(buf);
		buf[len-1]='\0';
		if(strncmp(buf,"ncons",5)==0) {
			tok=strtok(buf,sep);
			tok=strtok(NULL,sep);
			ncons=atoi(tok);
			if((ncons<1)||(ncons>MAX_CONS)) {
				syslog(LOG_EMERG,"%s: bad ncons value; defaulting to %d\n",fname,MAX_CONS);
				ncons=MAX_CONS;
			}
			continue;
		} else if(strncmp(buf,"motd",4)==0) {
			tok=strtok(buf,sep);
			motd=strdup(strtok(NULL,sep));
			continue;
		} else {
			do_command(0,buf);
		}
		/* Next command, please. */
	}
	fclose(fd);
	syslog(LOG_EMERG,"Done with %s",fname);
}

void
do_command(int shell, char *cmdline)
{
	char *tok,*c,*sep=" \t";
	char **av;
	struct command *x;
	int found,len;
	int ac,i,f,res;
	int bg=0;
	pid_t pid;

	len=strlen(cmdline);
	if(cmdline[len-1]=='&') {
		bg++;
		cmdline[len-1]='\0';
		len--;
	} else bg=0;
	tok=strtok(cmdline,sep);
	x=bltins;
	found=0;
	while(x->cmd!=NULL) {
		if(strcmp(x->cmd,tok)==0) {
			found++;
			break;
		}
		x++;
	}
	if(found) {
		tok=cmdline+strlen(x->cmd)+1;
		while(*tok && isblank(*tok) && (tok<(cmdline+len))) tok++;
		if(*tok==NULL) tok=NULL;
		x->func(tok);
		return;
	}
	ac=0;
	av=(char **)calloc(((len+1)/2+1),sizeof(char *));
	av[ac++]=tok;
	while((av[ac++]=strtok(NULL,sep))!=NULL)
		continue;
	switch((pid=fork())) {
	case 0:
		if(shell) {
			signal(SIGINT,SIG_DFL);
			signal(SIGQUIT,SIG_DFL);
			signal(SIGTERM,SIG_DFL);
			signal(SIGHUP,SIG_DFL);
		} else {
			close(0);
			close(1);
			close(2);
			f=open(_PATH_CONSOLE,O_RDWR);
			dup2(f,0);
			dup2(f,1);
			dup2(f,2);
			if(f>2) close(f);
		}
		if(bg) {
			if(daemon(0,0)) {
				printf("do_command(%s): failed to run bg: %s\n",
				av[0],strerror(errno));
				_exit(100);
			}
		}
		execvp(av[0],av);
		/* Something went wrong... */
		printf("do_command(%s): %s\n",av[0],strerror(errno));
		_exit(100);
		break;
	case -1:
		printf("do_command(): %s\n",strerror(errno));
		break;
	default:
		while(waitpid(pid,&res,0)!=pid) continue;
		if(WEXITSTATUS(res)) {
			printf("do_command(%s): exit code=%d\n",
				av[0],WEXITSTATUS(res));
		}
		break;
	}
	free(av);
	return;
}

/*
 * This is the user interface. This routine gets executed on each
 * virtual console serviced by init.
 *
 * It works as normal shell does - for each external command it forks
 * and execs, for each internal command just executes a function.
 */

int
shell(int argc, char **argv)
{
	char buf[BUFSIZE];
	char *prompt=" # ";
	int fd;
	int res;
	pid_t mypid;

	if(motd!=NULL) {
		if((fd=open(motd,O_RDONLY))!=-1) {
			do {
				res=read(fd,buf,BUFSIZE);
				res=write(1,buf,res);
			} while(res>0);
			close(fd);
		}
	}

	printf("\n\n+=========================================================+\n");
	printf("| Built-in shell() (enter '?' for short help on commands) |\n");
	printf("+=========================================================+\n\n");
	getcwd(cwd,BUFSIZE);
	mypid=getpid();
	signal(SIGINT,shell_sig);
	signal(SIGQUIT,shell_sig);
	signal(SIGTERM,shell_sig);
	while(!feof(stdin)) {
		memset(buf,0,BUFSIZE);
		printf("(%d)%s%s",mypid,cwd,prompt);
		fflush(stdout);
		if(fgets(buf,BUFSIZE-1,stdin)==NULL) continue;
		buf[strlen(buf)-1]='\0';
		if(strlen(buf)==0) continue;
		do_command(1,buf);
	}
	return(0);
}

/*
 * Stub for executing some external program on a console. This is called
 * from previously forked copy of our process, so that exec is ok.
 */
int
external_cmd(int argc, char **argv)
{
	execvp(argv[0],argv);
}

/*
 * Acquire vty and properly attach ourselves to it.
 * Also, build basic environment for running user interface.
 */

int
start_session(int vty, int argc, char **argv)
{
	int fd;
	char *t;

	close(0);
	close(1);
	close(2);
	revoke(ttys[vty].tty);
	fd=open(ttys[vty].tty,O_RDWR);
	dup2(fd,0);
	dup2(fd,1);
	dup2(fd,2);
	if(fd>2) close(fd);
	login_tty(fd);
	setpgid(0,getpid());
	putenv("TERM=xterm");
	putenv("HOME=/");
	putenv("PATH=/stand:/bin:/usr/bin:/sbin:.");
	signal(SIGHUP,SIG_DFL);
	signal(SIGINT,SIG_DFL);
	signal(SIGQUIT,SIG_DFL);
	signal(SIGTERM,SIG_DFL);
	chdir("/");
	t=(char *)(rindex(ttys[vty].tty,'/')+1);
	printf("\n\n\nStarting session on %s.\n",t);
	ttys[vty].func(argc,argv);
	_exit(0);
}

/*
 * Execute system startup script /etc/rc
 *
 * (Of course if you don't like it - I don't - you can run anything you
 * want here. Perhaps it would be useful just to read some config DB and
 * do these things ourselves, avoiding forking lots of shells and scripts.)
 */

/* If OINIT_RC is defined, oinit will use it's own configuration file, 
 * /etc/oinit.rc. It's format is described below. Otherwise, it will use
 * normal /etc/rc interpreted by Bourne shell.
 */
#ifndef OINIT_RC
#ifndef SH_NAME
#define SH_NAME	"-sh"
#endif
#ifndef SH_PATH
#define SH_PATH	_PATH_BSHELL
#endif
#ifndef SH_ARG
#define SH_ARG	"/etc/rc"
#endif
void
runcom()
{
	char *argv[3];
	pid_t pid;
	int st;
	int fd;

	if((pid=fork())==0) {
		/* child */
		close(0);
		close(1);
		close(2);
		fd=open(_PATH_CONSOLE,O_RDWR);
		dup2(fd,0);
		dup2(fd,1);
		dup2(fd,2);
		if(fd>2) close(fd);
		argv[0]=SH_NAME;
		argv[1]=SH_ARG;
		argv[2]=0;
		execvp(SH_PATH,argv);
		printf("runcom(): %s\n",strerror(errno));
		_exit(1);
	}
	/* Wait for child to exit */
	while(pid!=waitpid(pid,(int *)0,0)) continue;
	return;
}
#else
/* Alternative /etc/rc - default is /etc/oinit.rc. Its format is as follows:
 * - each empty line or line beginning with a '#' is discarded
 * - any other line must contain a keyword, or a (nonblocking) command to run.
 *
 * Thus far, the following keywords are defined:
 * ncons <number>	number of virtual consoles to open
 * motd <pathname>	full path to motd file
 *
 * Examples of commands to run:
 *
 * ifconfig lo0 inet 127.0.0.1 netmask 255.0.0.0
 * ifconfig ed0 inet 148.81.168.10 netmask 255.255.255.0
 * kbdcontrol -l /usr/share/syscons/my_map.kbd
 */
void
runcom(char *fname)
{
	int fd;

	close(0);
	close(1);
	close(2);
	fd=open(_PATH_CONSOLE,O_RDWR);
	dup2(fd,0);
	dup2(fd,1);
	dup2(fd,2);
	if(fd>2) close(fd);
	sourcer(fname);
}
#endif

int
run_multi()
{
	int i,j;
	pid_t pid;
	int found;

	/* Run /etc/rc if not in single user */
#ifndef OINIT_RC
	if(prevtrans==SINGLE) runcom();
#else
	if(prevtrans==SINGLE) runcom(OINIT_RC);
#endif
	if(transition!=MULTI) return(-1);

	syslog(LOG_EMERG,"*** Starting multi-user mode ***");

	/* Fork shell interface for each console */
	for(i=0;i<ncons;i++) {
		if(ttys[i].pid==0) {
			switch(pid=fork()) {
			case 0:
				start_session(i,0,NULL);
				break;
			case -1:
				printf("%s: %s\n",progname,strerror(errno));
				break;
			default:
				ttys[i].pid=pid;
				break;
			}
		}
	}
	/* Initialize any other services we'll use - most probably this will
	 * be a 'telnet' service (some day...).
	 */
	/* */

	/* Emulate multi-user */
	while(transition==MULTI) {
		/* XXX Modify this to allow for checking for the input on
		 * XXX listening sockets, and forking a 'telnet' service.
		 */
		/* */

		/* Wait for any process to exit */
		pid=waitpid(-1,(int *)0,0);
		if(pid==-1) continue;
		found=0;
		j=-1;
		/* search if it's one of our sessions */
		for(i=0;i<ncons;i++) {
			if(ttys[i].pid==pid) {
				found++;
				j=i;
				ttys[j].pid=0;
				break;
			}
		}
		if(!found) {
			/* Just collect the process's status */
			continue;
		} else {
			/* restart shell() on a console, if it died */
			if(transition!=MULTI) return(0);
			switch(pid=fork()) {
			case 0:
				sleep(1);
				start_session(j,0,NULL);
				break;
			case -1:
				printf("%s: %s\n",progname,strerror(errno));
				break;
			default:
				ttys[j].pid=pid;
				break;
			}
		}
	}
}

int clang;

void
kill_timer(int sig)
{
	clang=1;
}

kill_ttys()
{
}

/*
 * Start a shell on ttyv0 (i.e. the console).
 */

int
run_single()
{
	int i;
	pid_t pid,wpid;
	static int sigs[2]={SIGTERM,SIGKILL};

	syslog(LOG_EMERG,"*** Starting single-user mode ***");
	/* Kill all existing sessions */
	syslog(LOG_EMERG,"Killing all existing sessions...");
	for(i=0;i<MAX_CONS;i++) {
		kill(ttys[i].pid,SIGHUP);
		ttys[i].pid=0;
	}
	for(i=0;i<2;i++) {
		if(kill(-1,sigs[i])==-1 && errno==ESRCH) break;
		clang=0;
		alarm(10);
		do {
			pid=waitpid(-1,(int *)0,WUNTRACED);
			if(errno==EINTR) continue;
			else break;
		} while (clang==0);
	}
	if(errno!=ECHILD) {
		syslog(LOG_EMERG,"Some processes would not die; ps -axl advised");
	}
	/* Single-user */
	switch(pid=fork()) {
	case 0:
		start_session(0,0,NULL);
		break;
	case -1:
		printf("%s: %s\n",progname,strerror(errno));
		printf("The system is seriously hosed. I'm dying...\n");
		transition=DEATH;
		return(-1);
		break;
	default:
		do {
			wpid=waitpid(pid,(int *)0,WUNTRACED);
		} while(wpid!=pid && transition==SINGLE);
		if(transition!=DEATH) {
			prevtrans=transition;
			transition=MULTI;
		}
		break;
	}
	return(0);
}

/*
 * Transition handler - installed as signal handler.
 */

void
transition_handler(int sig)
{

	switch(sig) {
	case SIGHUP:
	case SIGTERM:
		prevtrans=transition;
		transition=SINGLE;
		syslog(LOG_EMERG,"*** Going from %s -> %s\n",trans[prevtrans],trans[transition]);
		if(prevtrans!=transition) longjmp(machine,sig);
		break;
	case SIGINT:
	case SIGQUIT:
		prevtrans=transition;
		transition=DEATH;
		syslog(LOG_EMERG,"*** Going from %s -> %s\n",trans[prevtrans],trans[transition]);
		if(prevtrans!=transition) longjmp(machine,sig);
		break;
	default:
		syslog(LOG_EMERG,"pid=%d sig=%s (ignored)\n",getpid(),sys_siglist[sig]);
		break;
	}
}

/*
 * Change system state appropriately to the signals
 */

int
transition_machine()
{
	int i;

	while(transition!=DEATH) {
		switch(transition) {
		case MULTI:
			run_multi();
			break;
		case SINGLE:
			run_single();
			break;
		}
	}
	syslog(LOG_EMERG,"Killing all existing sessions...");
	/* Kill all sessions */
	kill(-1,SIGKILL);
	/* Be nice and wait for them */
	while(waitpid(-1,(int *)0,WNOHANG|WUNTRACED)>0) continue;
	unmount("/",0);
	reboot(RB_AUTOBOOT);
	/* NOTREACHED */
}

int
main(int argc, char **argv)
{
	int devfs=0,c,i;

	/* These are copied from real init(8) */
	if(getuid()!=0)
		errx(1,"%s",strerror(EPERM));
	openlog("init",LOG_CONS|LOG_ODELAY,LOG_AUTH);
	if(setsid()<0)
		warn("initial setsid() failed");
	if(setlogin("root")<0)
		warn("setlogin() failed");

	close(0);
	close(1);
	close(2);
	chdir("/");

	progname=rindex(argv[0],'/');
	if(progname==NULL) {
		progname=argv[0];
	} else progname++;

	transition=MULTI;

	/* We must recognize the same options as real init does */
	while((c=getopt(argc,argv,"dsf"))!=-1) {
		switch(c) {
		case 'd':
			devfs=1;
			break;
		case 's':
			transition=SINGLE;
			break;
		case 'f':
			break;
		default:
			printf("%s: unrecognized flag '-%c'\n",progname,c);
			break;
		}
	}
	if(devfs)
		mount("devfs",_PATH_DEV,MNT_NOEXEC|MNT_RDONLY,0);

	/* Fill in the sess structures. */
	/* XXX Really, should be filled based upon config file. */
	for(i=0;i<MAX_CONS;i++) {
		if(i==0) {
			sprintf(ttys[i].tty,_PATH_CONSOLE);
		} else {
			sprintf(ttys[i].tty,"%sv%c",_PATH_TTY,vty[i]);
		}
		ttys[i].pid=0;
		ttys[i].func=shell;
	}

	getcwd(cwd,BUFSIZE);

	signal(SIGINT,transition_handler);
	signal(SIGQUIT,transition_handler);
	signal(SIGTERM,transition_handler);
	signal(SIGHUP,transition_handler);
	signal(SIGALRM,kill_timer);

	setjmp(machine);
	transition_machine(transition);
	/* NOTREACHED */
	exit(100);
}