939 lines
18 KiB
C
939 lines
18 KiB
C
/*-
|
|
* 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 __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("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=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)
|
|
{
|
|
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","/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,"/dev/console");
|
|
} else {
|
|
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);
|
|
}
|