plan9fox/sys/src/cmd/sh.C
2011-03-30 19:35:09 +03:00

510 lines
10 KiB
C

/* sh - simple shell - great for early stages of porting */
#include "u.h"
#include "libc.h"
#define MAXLINE 200 /* maximum line length */
#define WORD 256 /* token code for words */
#define EOF -1 /* token code for end of file */
#define ispunct(c) (c=='|' || c=='&' || c==';' || c=='<' || \
c=='>' || c=='(' || c==')' || c=='\n')
#define isspace(c) (c==' ' || c=='\t')
#define execute(np) (ignored = (np? (*(np)->op)(np) : 0))
typedef struct Node Node;
struct Node{ /* parse tree node */
int (*op)(Node *); /* operator function */
Node *args[2]; /* argument nodes */
char *argv[100]; /* argument pointers */
char *io[3]; /* i/o redirection */
};
Node nodes[25]; /* node pool */
Node *nfree; /* next available node */
char strspace[10*MAXLINE]; /* string storage */
char *sfree; /* next free character in strspace */
int t; /* current token code */
char *token; /* current token text (in strspace) */
int putback = 0; /* lookahead */
char status[256]; /* exit status of most recent command */
int cflag = 0; /* command is argument to sh */
int tflag = 0; /* read only one line */
int interactive = 0; /* prompt */
char *cflagp; /* command line for cflag */
char *path[] ={"/bin", 0};
int ignored;
Node *alloc(int (*op)(Node *));
int builtin(Node *np);
Node *command(void);
int getch(void);
int gettoken(void);
Node *list(void);
void error(char *s, char *t);
Node *pipeline(void);
void redirect(Node *np);
int setio(Node *np);
Node *simple(void);
int xpipeline(Node *np);
int xsimple(Node *np);
int xsubshell(Node *np);
int xnowait(Node *np);
int xwait(Node *np);
void
main(int argc, char *argv[])
{
Node *np;
if(argc>1 && strcmp(argv[1], "-t")==0)
tflag++;
else if(argc>2 && strcmp(argv[1], "-c")==0){
cflag++;
cflagp = argv[2];
}else if(argc>1){
close(0);
if(open(argv[1], 0) != 0){
error(": can't open", argv[1]);
exits("argument");
}
}else
interactive = 1;
for(;;){
if(interactive)
fprint(2, "%d$ ", getpid());
nfree = nodes;
sfree = strspace;
if((t=gettoken()) == EOF)
break;
if(t != '\n')
if(np = list())
execute(np);
else
error("syntax error", "");
while(t!=EOF && t!='\n') /* flush syntax errors */
t = gettoken();
}
exits(status);
}
/* alloc - allocate for op and return a node */
Node*
alloc(int (*op)(Node *))
{
if(nfree < nodes+sizeof(nodes)){
nfree->op = op;
nfree->args[0] = nfree->args[1] = 0;
nfree->argv[0] = nfree->argv[1] = 0;
nfree->io[0] = nfree->io[1] = nfree->io[2] = 0;
return nfree++;
}
error("node storage overflow", "");
exits("node storage overflow");
return nil;
}
/* builtin - check np for builtin command and, if found, execute it */
int
builtin(Node *np)
{
int n = 0;
char name[MAXLINE];
Waitmsg *wmsg;
if(np->argv[1])
n = strtoul(np->argv[1], 0, 0);
if(strcmp(np->argv[0], "cd") == 0){
if(chdir(np->argv[1]? np->argv[1] : "/") == -1)
error(": bad directory", np->argv[0]);
return 1;
}else if(strcmp(np->argv[0], "exit") == 0)
exits(np->argv[1]? np->argv[1] : status);
else if(strcmp(np->argv[0], "bind") == 0){
if(np->argv[1]==0 || np->argv[2]==0)
error("usage: bind new old", "");
else if(bind(np->argv[1], np->argv[2], 0)==-1)
error("bind failed", "");
return 1;
#ifdef asdf
}else if(strcmp(np->argv[0], "unmount") == 0){
if(np->argv[1] == 0)
error("usage: unmount [new] old", "");
else if(np->argv[2] == 0){
if(unmount((char *)0, np->argv[1]) == -1)
error("unmount:", "");
}else if(unmount(np->argv[1], np->argv[2]) == -1)
error("unmount", "");
return 1;
#endif
}else if(strcmp(np->argv[0], "wait") == 0){
while((wmsg = wait()) != nil){
strncpy(status, wmsg->msg, sizeof(status)-1);
if(n && wmsg->pid==n){
n = 0;
free(wmsg);
break;
}
free(wmsg);
}
if(n)
error("wait error", "");
return 1;
}else if(strcmp(np->argv[0], "rfork") == 0){
char *p;
int mask;
p = np->argv[1];
if(p == 0 || *p == 0)
p = "ens";
mask = 0;
while(*p)
switch(*p++){
case 'n': mask |= RFNAMEG; break;
case 'N': mask |= RFCNAMEG; break;
case 'e': mask |= RFENVG; break;
case 'E': mask |= RFCENVG; break;
case 's': mask |= RFNOTEG; break;
case 'f': mask |= RFFDG; break;
case 'F': mask |= RFCFDG; break;
case 'm': mask |= RFNOMNT; break;
default: error(np->argv[1], "bad rfork flag");
}
rfork(mask);
return 1;
}else if(strcmp(np->argv[0], "exec") == 0){
redirect(np);
if(np->argv[1] == (char *) 0)
return 1;
exec(np->argv[1], &np->argv[1]);
n = np->argv[1][0];
if(n!='/' && n!='#' && (n!='.' || np->argv[1][1]!='/'))
for(n = 0; path[n]; n++){
sprint(name, "%s/%s", path[n], np->argv[1]);
exec(name, &np->argv[1]);
}
error(": not found", np->argv[1]);
return 1;
}
return 0;
}
/* command - ( list ) [ ( < | > | >> ) word ]* | simple */
Node*
command(void)
{
Node *np;
if(t != '(')
return simple();
np = alloc(xsubshell);
t = gettoken();
if((np->args[0]=list())==0 || t!=')')
return 0;
while((t=gettoken())=='<' || t=='>')
if(!setio(np))
return 0;
return np;
}
/* getch - get next, possibly pushed back, input character */
int
getch(void)
{
unsigned char c;
static done=0;
if(putback){
c = putback;
putback = 0;
}else if(tflag){
if(done || read(0, &c, 1)!=1){
done = 1;
return EOF;
}
if(c == '\n')
done = 1;
}else if(cflag){
if(done)
return EOF;
if((c=*cflagp++) == 0){
done = 1;
c = '\n';
}
}else if(read(0, &c, 1) != 1)
return EOF;
return c;
}
/* gettoken - get next token into string space, return token code */
int
gettoken(void)
{
int c;
while((c = getch()) != EOF)
if(!isspace(c))
break;
if(c==EOF || ispunct(c))
return c;
token = sfree;
do{
if(sfree >= strspace+sizeof(strspace) - 1){
error("string storage overflow", "");
exits("string storage overflow");
}
*sfree++ = c;
}while((c=getch()) != EOF && !ispunct(c) && !isspace(c));
*sfree++ = 0;
putback = c;
return WORD;
}
/* list - pipeline ( ( ; | & ) pipeline )* [ ; | & ] (not LL(1), but ok) */
Node*
list(void)
{
Node *np, *np1;
np = alloc(0);
if((np->args[1]=pipeline()) == 0)
return 0;
while(t==';' || t=='&'){
np->op = (t==';')? xwait : xnowait;
t = gettoken();
if(t==')' || t=='\n') /* tests ~first(pipeline) */
break;
np1 = alloc(0);
np1->args[0] = np;
if((np1->args[1]=pipeline()) == 0)
return 0;
np = np1;
}
if(np->op == 0)
np->op = xwait;
return np;
}
/* error - print error message s, prefixed by t */
void
error(char *s, char *t)
{
char buf[256];
fprint(2, "%s%s", t, s);
errstr(buf, sizeof buf);
fprint(2, ": %s\n", buf);
}
/* pipeline - command ( | command )* */
Node*
pipeline(void)
{
Node *np, *np1;
if((np=command()) == 0)
return 0;
while(t == '|'){
np1 = alloc(xpipeline);
np1->args[0] = np;
t = gettoken();
if((np1->args[1]=command()) == 0)
return 0;
np = np1;
}
return np;
}
/* redirect - redirect i/o according to np->io[] values */
void
redirect(Node *np)
{
int fd;
if(np->io[0]){
if((fd = open(np->io[0], 0)) < 0){
error(": can't open", np->io[0]);
exits("open");
}
dup(fd, 0);
close(fd);
}
if(np->io[1]){
if((fd = create(np->io[1], 1, 0666L)) < 0){
error(": can't create", np->io[1]);
exits("create");
}
dup(fd, 1);
close(fd);
}
if(np->io[2]){
if((fd = open(np->io[2], 1)) < 0 && (fd = create(np->io[2], 1, 0666L)) < 0){
error(": can't write", np->io[2]);
exits("write");
}
dup(fd, 1);
close(fd);
seek(1, 0, 2);
}
}
/* setio - ( < | > | >> ) word; fill in np->io[] */
int
setio(Node *np)
{
if(t == '<'){
t = gettoken();
np->io[0] = token;
}else if(t == '>'){
t = gettoken();
if(t == '>'){
t = gettoken();
np->io[2] = token;
}else
np->io[1] = token;
}else
return 0;
if(t != WORD)
return 0;
return 1;
}
/* simple - word ( [ < | > | >> ] word )* */
Node*
simple(void)
{
Node *np;
int n = 1;
if(t != WORD)
return 0;
np = alloc(xsimple);
np->argv[0] = token;
while((t = gettoken())==WORD || t=='<' || t=='>')
if(t == WORD)
np->argv[n++] = token;
else if(!setio(np))
return 0;
np->argv[n] = 0;
return np;
}
/* xpipeline - execute cmd | cmd */
int
xpipeline(Node *np)
{
int pid, fd[2];
if(pipe(fd) < 0){
error("can't create pipe", "");
return 0;
}
if((pid=fork()) == 0){ /* left side; redirect stdout */
dup(fd[1], 1);
close(fd[0]);
close(fd[1]);
execute(np->args[0]);
exits(status);
}else if(pid == -1){
error("can't create process", "");
return 0;
}
if((pid=fork()) == 0){ /* right side; redirect stdin */
dup(fd[0], 0);
close(fd[0]);
close(fd[1]);
pid = execute(np->args[1]); /*BUG: this is wrong sometimes*/
if(pid > 0)
while(waitpid()!=pid)
;
exits(0);
}else if(pid == -1){
error("can't create process", "");
return 0;
}
close(fd[0]); /* avoid using up fd's */
close(fd[1]);
return pid;
}
/* xsimple - execute a simple command */
int
xsimple(Node *np)
{
char name[MAXLINE];
int pid, i;
if(builtin(np))
return 0;
if(pid = fork()){
if(pid == -1)
error(": can't create process", np->argv[0]);
return pid;
}
redirect(np); /* child process */
exec(np->argv[0], &np->argv[0]);
i = np->argv[0][0];
if(i!='/' && i!='#' && (i!='.' || np->argv[0][1]!='/'))
for(i = 0; path[i]; i++){
sprint(name, "%s/%s", path[i], np->argv[0]);
exec(name, &np->argv[0]);
}
error(": not found", np->argv[0]);
exits("not found");
return -1; // suppress compiler warnings
}
/* xsubshell - execute (cmd) */
int
xsubshell(Node *np)
{
int pid;
if(pid = fork()){
if(pid == -1)
error("can't create process", "");
return pid;
}
redirect(np); /* child process */
execute(np->args[0]);
exits(status);
return -1; // suppress compiler warnings
}
/* xnowait - execute cmd & */
int
xnowait(Node *np)
{
int pid;
execute(np->args[0]);
pid = execute(np->args[1]);
if(interactive)
fprint(2, "%d\n", pid);
return 0;
}
/* xwait - execute cmd ; */
int xwait(Node *np)
{
int pid;
Waitmsg *wmsg;
execute(np->args[0]);
pid = execute(np->args[1]);
if(pid > 0){
while((wmsg = wait()) != nil){
if(wmsg->pid == pid)
break;
free(wmsg);
}
if(wmsg == nil)
error("wait error", "");
else {
strncpy(status, wmsg->msg, sizeof(status)-1);
free(wmsg);
}
}
return 0;
}