diff options
author | Andrzej Bialecki <abial@FreeBSD.org> | 1998-08-27 17:38:45 +0000 |
---|---|---|
committer | Andrzej Bialecki <abial@FreeBSD.org> | 1998-08-27 17:38:45 +0000 |
commit | c9d8fd0a7bb8ae40b9da7c52caec851ca9d0cf3e (patch) | |
tree | 0f9c1dcefca21fb605aaf84b621c2813f01fa5e4 /release/picobsd/tinyware/oinit/oinit.c | |
parent | 0b0c1554a87b20eb1377566bce8833788ec96394 (diff) | |
download | src-c9d8fd0a7bb8ae40b9da7c52caec851ca9d0cf3e.tar.gz src-c9d8fd0a7bb8ae40b9da7c52caec851ca9d0cf3e.zip |
Initial import of PicoBSD v0.4 tree.
Notes
Notes:
svn path=/cvs2svn/branches/PICOBSD/; revision=38589
Diffstat (limited to 'release/picobsd/tinyware/oinit/oinit.c')
-rw-r--r-- | release/picobsd/tinyware/oinit/oinit.c | 924 |
1 files changed, 924 insertions, 0 deletions
diff --git a/release/picobsd/tinyware/oinit/oinit.c b/release/picobsd/tinyware/oinit/oinit.c new file mode 100644 index 000000000000..8cadb872a244 --- /dev/null +++ b/release/picobsd/tinyware/oinit/oinit.c @@ -0,0 +1,924 @@ +/*- + * 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. + * + * $Id: oinit.c,v 1.8 1998/08/11 06:53:47 abial Exp $ + */ + +/* + * 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[11]; /* 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 __P((char *)); +int pwd __P((char *)); +int echo __P((char *)); +int xit __P((char *)); +int set __P((char *)); +int unset __P((char *)); +int env __P((char *)); +int help __P((char *)); +int sourcer __P((char *)); +void do_command __P((int shell, char *cmdline)); +void transition_handler __P((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=cons25",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("%s exited with status %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=cons25"); + 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 +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"; + argv[1]="/etc/rc"; + argv[2]=0; + execvp("/bin/sh",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) +{ + 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","/dev",MNT_NOEXEC|MNT_RDONLY,0); + + /* Fill in the sess structures. */ + /* XXX Really, should be filled basing on config file. */ + for(i=0;i<MAX_CONS;i++) { + sprintf(ttys[i].tty,"/dev/ttyv%c",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); +} |