Date: Sun, 5 Jul 1998 22:52:09 +0200 (CEST) From: Andrzej Bialecki <abial@nask.pl> To: freebsd-small@FreeBSD.ORG Subject: Custom init Message-ID: <Pine.NEB.3.95.980705224839.27775A-200000@korin.warman.org.pl>
next in thread | raw e-mail | index | archive | help
[-- Attachment #1 --]
Hi,
This is my first stab at custom init combined with simple user shell. The
thing is very small (17kB dynamic), and I'm sure it can be substantially
improved... :-) It runs quite well on picobsd floppies (modulo some nits,
which I'm yet unsure of what causes them - most probably wrong signals at
wron time).
Let me know what you think!
Andrzej Bialecki
--------------------+---------------------------------------------------------
abial@nask.pl | if(halt_per_mth > 0) { fetch("http://www.freebsd.org") }
Research & Academic | "Be open-minded, but don't let your brains to fall out."
Network in Poland | All of the above (and more) is just my personal opinion.
--------------------+---------------------------------------------------------
[-- Attachment #2 --]
/*-
* 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:$
*/
/*
* 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 <err.h>
#include <errno.h>
#include <fcntl.h>
#include <libutil.h>
#include <paths.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 SINGLE 1
#define MULTI 2
#define DEATH 3
#define FALSE 0
#define TRUE 1
char cwd[BUFSIZE];
char vty[]="0123456789abcdef";
char *progname;
int Reboot=FALSE;
volatile int transition=MULTI;
volatile int prevtrans=SINGLE;
char *trans[]={ "???!", "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 xit __P((char *));
int set __P((char *));
int unset __P((char *));
int env __P((char *));
int help __P((char *));
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},
{"env","Print all environment variables","env","env",env},
{"?","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 '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);
}
}
}
/*
* 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=" # ";
char *tok,*c,*sep=" ";
char **av;
struct command *x;
int found,len;
int ac,i,res;
pid_t pid,mypid;
getcwd(cwd,BUFSIZE);
mypid=getpid();
printf("\nBuilt-in shell() (enter '?' for short help on commands)\n\n");
while(!feof(stdin)) {
memset(buf,0,BUFSIZE);
printf("(%d)%s%s",mypid,cwd,prompt);
fflush(stdout);
if(fgets(buf,BUFSIZE-1,stdin)==NULL) continue;
len=strlen(buf)-1;
buf[len]='\0';
if(len==0) continue;
tok=strtok(buf,sep);
x=bltins;
found=0;
while(x->cmd!=NULL) {
if(strcmp(x->cmd,tok)==0) {
found++;
break;
}
x++;
}
if(found) {
tok=strtok(NULL,sep);
x->func(tok);
continue;
}
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:
execvp(av[0],av);
/* Something went wrong... */
printf("shell(): %s\n",strerror(errno));
_exit(100);
break;
case -1:
printf("shell(): %s\n",strerror(errno));
break;
default:
while(waitpid(pid,&res,0)!=pid) continue;
if(WEXITSTATUS(res)) {
/* Some error message? */
}
break;
}
free(av);
}
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);
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.)
*/
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;
}
int
run_multi()
{
int i,j;
int ncons=MAX_CONS; /* XXX should be configurable */
pid_t pid;
int found;
syslog(LOG_EMERG,"\n\n\nStarting multi-user mode.\n\n");
/* Run /etc/rc if not in single user */
if(prevtrans==SINGLE) runcom();
if(transition!=MULTI) return(-1);
/* 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;
}
}
}
/* Emulate multi-user */
for(;;) {
/* Wait for any process to exit */
pid=waitpid(-1,(int *)0,WUNTRACED);
if(transition!=MULTI) return(0);
found=0;
j=-1;
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;
}
}
}
}
/*
* Start a shell on ttyv0 (i.e. the console), after killing any other
* process
*/
int
run_single()
{
int i;
pid_t pid;
syslog(LOG_EMERG,"\n\n\nStarting single-user mode.\n\n");
/* Kill all existing sessions */
kill(-1,SIGKILL);
for(i=0;i<MAX_CONS;i++)
ttys[i].pid=0;
/* Wait on them */
while(waitpid(-1,(int *)0,WNOHANG|WUNTRACED)!=-1) continue;
/* 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:
while(waitpid(pid,(int *)0,0)!=pid) continue;
if(transition!=DEATH) {
prevtrans=transition;
transition=MULTI;
}
break;
}
return(0);
}
/*
* Transition handler - installed as signal handler.
*/
void
transition_handler(int sig)
{
syslog(LOG_EMERG,"\npid=%d sig=%s\n",getpid(),sys_siglist[sig]);
switch(sig) {
case SIGHUP:
case SIGTERM:
prevtrans=transition;
transition=SINGLE;
break;
case SIGINT:
Reboot=TRUE;
/* FALLTHROUGH */
case SIGQUIT:
prevtrans=transition;
transition=DEATH;
break;
default:
break;
}
syslog(LOG_EMERG,"\n%s -> %s\n",trans[prevtrans],trans[transition]);
}
/*
* 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;
}
}
/* Kill all sessions */
kill(-1,SIGKILL);
/* Be nice and wait on them */
while(waitpid(-1,(int *)0,WNOHANG|WUNTRACED)>0) continue;
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);
transition_machine(transition);
/* NOTREACHED */
exit(100);
}
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?Pine.NEB.3.95.980705224839.27775A-200000>
