/***********************************************************************
* *
* This software is part of the ast package *
* Copyright (c) 1982-2010 AT&T Intellectual Property *
* and is licensed under the *
* Common Public License, Version 1.0 *
* by AT&T Intellectual Property *
* *
* A copy of the License is available at *
* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
* *
* Information and Software Systems Research *
* AT&T Research *
* Florham Park NJ *
* *
* David Korn <dgk@research.att.com> *
* *
***********************************************************************/
#pragma prototyped
/*
* UNIX shell parse tree executer
*
* David Korn
* AT&T Labs
*
*/
#include "defs.h"
#include <fcin.h>
#include "variables.h"
#include "path.h"
#include "name.h"
#include "io.h"
#include "shnodes.h"
#include "jobs.h"
#include "test.h"
#include "builtins.h"
#include "streval.h"
#if !_std_malloc
# include <vmalloc.h>
#endif
#if _lib_vfork
# include <ast_vfork.h>
#else
#endif
#if _lib_nice
extern int nice(int);
#endif /* _lib_nice */
#if !_lib_spawnveg
#endif /* !_lib_spawnveg */
#if SHOPT_SPAWN
#endif /* SHOPT_SPAWN */
static int trim_eq(const char*, const char*);
static void *timeout;
static char pipejob;
struct funenv
{
};
/* ======== command execution ========*/
/*
* print time <t> in h:m:s format with precision <p>
*/
{
register int hr;
if(p)
{
}
sec = t%60;
t /= 60;
min = t%60;
if(hr=t/60)
if(p)
else
}
{
const char *first;
double d;
{
if(c!='%')
continue;
n = l = 0;
p = 3;
if((c= *++format) == '%')
{
continue;
}
if(c>='0' && c <='9')
{
p = (c>'3')?3:(c-'0');
c = *++format;
}
else if(c=='P')
{
if(d=tm[0])
p = 2;
goto skip;
}
if(c=='l')
{
l = 1;
c = *++format;
}
if(c=='U')
n = 1;
else if(c=='S')
n = 2;
else if(c!='R')
{
return(0);
}
skip:
if(l)
else
}
return(n);
}
#if SHOPT_OPTIMIZE
/*
* clear argument pointers that point into the stack
*/
{
{
/* call builtin to cleanup state */
}
n++;
return(n);
}
extern void sh_optclear(Shell_t*, void*);
{
int n=0;
if(!t)
return(0);
{
case TTIME:
case TPAR:
case TCOM:
case TSETIO:
case TFORK:
case TIF:
return(n);
case TWH:
return(n);
case TLST:
case TAND:
case TORF:
case TFIL:
case TARITH:
case TFOR:
case TSW:
case TFUN:
case TTST:
else
{
}
}
return(n);
}
{
while(arg)
{
else if(flag==0)
else
}
return(0);
}
{
int n=0;
while(reg)
{
}
return(n);
}
#else
# define OPTIMIZE_FLAG (0)
# define OPTIMIZE (0)
# define sh_tclear(x)
#endif /* SHOPT_OPTIMIZE */
{
register int c;
do
{
switch(c= *cp)
{
case 0:
if(n<0)
return;
c = n;
break;
case '\n':
continue;
case '\\':
if (!(c = *++cp))
c = '\\';
/*FALLTHROUGH*/
case ' ':
case '<': case '>': case ';':
case '$': case '`': case '\t':
break;
}
}
while(*cp++);
}
{
if(quoted)
{
{
return;
}
}
}
struct Level
{
short maxlevel;
};
/*
* this is for a debugger but it hasn't been tested yet
* if a debug script sets .sh.level it should set up the scope
* as if you were executing in that level
*/
{
if(!val)
{
return;
}
{
/* perhaps this should be an error */
return;
}
return;
{
}
}
{
_nv_unset(SH_LEVELNOD,0);
return(lp);
}
/*
* write the current common on the stack and make it available as .sh.command
*/
int sh_debug(Shell_t *shp, const char *trap, const char *name, const char *subscript, char *const argv[], int flags)
{
short level;
return(0);
if(name)
{
if(subscript)
{
}
if(!(flags&ARG_APPEND))
if(!(flags&ARG_ASSIGN))
n -= 2;
}
{
else
}
if(flags&ARG_ASSIGN)
/* now setup .sh.level variable */
if(!SH_LEVELNOD->nvfun || !SH_LEVELNOD->nvfun->disc || nv_isattr(SH_LEVELNOD,NV_INT16|NV_NOFREE)!=(NV_INT16|NV_NOFREE))
else
else
return(n);
}
/*
* Given stream <iop> compile and execute
*/
{
register Shnode_t *t;
int jmpval;
{
}
while(jmpval==0)
{
if(mode&SH_READEVAL)
{
}
{
if(!(mode&SH_READEVAL))
io_save = 0;
mode &= ~SH_FUNEVAL;
}
mode &= ~SH_READEVAL;
if(!sh_isoption(SH_VERBOSE))
{
}
if(!(mode&SH_FUNEVAL))
break;
}
if(traceon)
if(lineno)
if(io_save)
if(jmpval>SH_JMPEVAL)
}
#if SHOPT_FASTPIPE
{
int jmpval;
volatile int r;
{
}
if(jmpval==0)
{
}
return(r);
}
#endif /* SHOPT_FASTPIPE */
/*
* returns 1 when option -<c> is specified
*/
{
char *cp;
{
if(*cp=='+')
continue;
break;
return(1);
break;
}
return(0);
}
{
{
}
}
/*
* set ${.sh.name} and ${.sh.subscript}
* set _ to reference for ${.sh.name}[$.sh.subscript]
*/
{
if(sh.var_tree!=sh.var_base && !nv_open(cp,nr->root,NV_VARNAME|NV_NOREF|NV_NOSCOPE|NV_NOADD|NV_NOFAIL))
if(sp)
{
}
return(0);
}
{
{
}
}
{
sh_sigcheck();
{
register char *com0 = 0;
#if SHOPT_AMP || SHOPT_SPAWN
#else
int ntflag = 0;
#endif
int argn;
int skipexitset = 0;
int was_interactive = 0;
int echeck = 0;
{
if(pipejob==2)
job_unlock();
pipejob = 0;
}
if(was_errexit&flags)
if(was_monitor&flags)
{
case TCOM:
{
char *trap;
echeck = 1;
{
sh_sigcheck();
}
while(np==SYSCOMMAND)
{
if(n==0)
break;
command += n;
np = 0;
break;
}
{
}
else
if(com0)
{
{
#if SHOPT_NAMESPACE
{
}
#endif /* SHOPT_NAMESPACE */
}
}
{
{
if(argn)
{
}
#if SHOPT_BASH
{
if(!nv_getval(SH_FUNNAMENOD))
{
}
}
#else
#endif
{
if(np!=SYSTYPESET)
{
}
#if SHOPT_TYPEDEF
{
}
#endif /* SHOPT_TYPEDEF */
flgs |= NV_NOSCOPE;
}
else
flgs |= NV_VARNAME;
#if 0
if(OPTIMIZE)
#endif
}
}
shp->last_table = 0;
{
tflags |= 2;
if(argn==0)
{
/* fake 'true' built-in */
}
/* set +x doesn't echo */
{
if(!ison)
if(io)
if(!ison)
break;
}
{
{
argn = 1;
com[1] = 0;
io = 0;
argp = 0;
}
else if(n==2)
break;
}
if(io)
{
{
{
if(mp)
}
}
else
{
else
np = 0;
}
}
{
job_unlock();
pipejob = 1;
}
/* check for builtins */
{
volatile void *save_ptr;
volatile void *save_data;
{
/*
* disable editors for built-in
* versions of commands on PATH
*/
}
if(execflg)
if(jmpval == 0)
{
if(io)
{
type=1;
else
}
{
{
path_pwd(0);
}
shp->nextprompt = 0;
}
if(argp)
{
scope++;
}
if(argn)
sh_subtmpfile(0);
{
/* do close-on-exec */
int fd;
}
if(argn)
}
else
{
{
{
else
}
}
/* failure on special built-ins fatal */
jmpval=0;
}
if(execflg && !was_nofork)
{
{
/* restore directory changed */
}
}
if(was_vi)
else if(was_emacs)
else if(was_gmacs)
if(scope)
/* don't restore for subshell exec */
if(jmpval)
#if 0
#endif
goto setexit;
np = 0;
type=0;
}
/* check for functions */
{
volatile int indx;
int jmpval=0;
long mode;
{
if(indx==1)
{
if(indx==1)
{
}
else
{
}
goto setexit;
}
}
/* increase refcnt for unset */
if(nq)
{
}
if(io)
{
}
if(jmpval == 0)
{
if(io)
}
if(io)
{
}
if(nq)
goto setexit;
}
}
else if(!io)
{
exitset();
break;
}
}
case TFORK:
{
{
sh_subtmpfile(1);
else
sh_subfork();
}
));
{
/* disable foreground job monitor */
#if SHOPT_DEVFD
#endif /* SHOPT_DEVFD */
}
if(no_fork)
else
{
#ifdef SHOPT_BGX
int maxjob;
{
{
job_lock();
job_reap(0);
job_unlock();
}
}
#endif /* SHOPT_BGX */
#if SHOPT_AMP
else
if(parent<0)
break;
#else
#if SHOPT_SPAWN
# ifdef _lib_fork
if(com)
else
# else
break;
# endif /* _lib_fork */
if(parent<0)
break;
#else
#endif /* SHOPT_SPAWN */
#endif
}
/* This is the parent branch of fork
* It may or may not wait for the child
*/
{
if(pipejob==2)
{
pipejob = 1;
job_unlock();
}
{
if(!sh_isoption(SH_MONITOR))
{
}
{
}
else
if(!sh_isoption(SH_MONITOR))
{
}
}
{
{
/* print job number */
#ifdef JOBS
#else
#endif /* JOBS */
}
}
break;
}
else
/*
* this is the FORKED branch (child) of execute
*/
{
volatile int jmpval;
if(no_fork)
sh_sigreset(2);
if(jmpval)
goto done;
{
/* default std input for & */
{
if(sh_close(0)>=0)
}
}
/* pipe in or out */
#ifdef _lib_nice
nice(4);
#endif /* _lib_nice */
{
}
{
}
{
job_lock();
if(parent)
{
job_clear();
}
}
{
/* don't clear job table for out
pipes so that jobs comand can
be used in a pipeline
*/
job_clear();
}
else if(com0)
{
}
done:
if(jmpval>SH_JMPEXIT)
}
}
case TSETIO:
{
/*
* don't create a new process, just
* save and restore io-streams
*/
execflg = 0;
{
/*
* if read end of pipe is a simple command
* treat as non-sharable to improve performance
*/
if(simple)
}
else
if(jmpval==0)
{
}
else
{
if(!(type&SH_EXITSIG))
{
/* wait for remainder of pipline */
{
}
else
}
if(simple && was_errexit)
{
echeck = 1;
}
}
break;
}
case TPAR:
echeck = 1;
flags &= ~OPTIMIZE_FLAG;
if(!shp->subshell && !shp->st.trapcom[0] && !shp->st.trap[SH_ERRTRAP] && (flags&sh_state(SH_NOFORK)))
{
char *savsig;
{
nsig += sizeof(char*);
}
sh_sigreset(0);
if(jmpval==0)
if(jmpval > SH_JMPEXIT)
}
else
break;
case TFIL:
{
/*
* This code sets up a pipe.
* All elements of the pipe are started by the parent.
* The last element executes in current environment
*/
{
sh_subtmpfile(0);
else
sh_subfork();
}
if(sh_isoption(SH_PIPEFAIL))
else
job_lock();
do
{
#if SHOPT_FASTPIPE
#else
/* create the pipe */
/* execute out part of pipe no wait */
#endif /* SHOPT_FASTPIPE */
pipejob=1;
/* save the pipe stream-ids */
/* close out-part of pipe */
/* pipeline all in one process group */
}
/* repeat until end of pipeline */
pipejob = 2;
if(type == 0)
{
/*
* execute last element of pipeline
* in the current process
*/
}
else
/* execution failure, close pipe */
if(pipejob==2)
job_unlock();
#ifdef SIGTSTP
#endif /*SIGTSTP */
break;
}
case TLST:
{
/* a list of commands are executed here */
do
{
}
break;
}
case TAND:
skipexitset++;
break;
case TORF:
skipexitset++;
break;
case TFOR: /* for and select */
{
register char **args;
register int nargs;
#if SHOPT_OPTIMIZE
if(jmpval)
goto endfor;
#endif /* SHOPT_OPTIMIZE */
{
}
else
{
}
{
{
char *val;
int save_prompt;
/* reuse register */
if(refresh)
{
refresh = 0;
}
{
break;
}
continue;
else
{
{
refresh++;
goto check;
}
break;
if(type!=0)
else
cp = "";
else
}
}
if(nameref)
if(nameref)
{
av[4] = 0;
}
flag &= ~OPTIMIZE_FLAG;
{
refresh++;
}
else
}
#if SHOPT_OPTIMIZE
if(jmpval)
#endif /*SHOPT_OPTIMIZE */
break;
}
case TWH: /* while and until */
{
volatile int r=0;
#if SHOPT_FILESCAN
#endif /*SHOPT_FILESCAN*/
#if SHOPT_OPTIMIZE
if(jmpval)
goto endwhile;
#endif /* SHOPT_OPTIMIZE */
#if SHOPT_FILESCAN
{
if(fd==0)
close(0);
}
#endif /*SHOPT_FILESCAN */
{
#if SHOPT_FILESCAN
if(iop)
{
break;
}
else
#endif /*SHOPT_FILESCAN */
break;
/* This is for the arithmetic for */
first = 0;
errorflg &= ~OPTIMIZE_FLAG;
#if SHOPT_FILESCAN
#endif /*SHOPT_FILESCAN */
}
#if SHOPT_OPTIMIZE
if(jmpval)
#endif /*SHOPT_OPTIMIZE */
#if SHOPT_FILESCAN
if(iop)
{
close(0);
}
#endif /*SHOPT_FILESCAN */
break;
}
case TARITH: /* (( expression )) */
{
register char *trap;
arg[0] = "((";
else
arg[3] = 0;
if(sh_isoption(SH_XTRACE))
{
}
else
break;
}
case TIF:
else
break;
case TSW:
{
{
av[0] = "case";
av[1] = r;
av[3] = 0;
}
while(t)
{
while(rex)
{
register char *s;
{
while(*s=='\\' && s[1]==0)
s+=2;
}
else
|| trim_eq(r,s))))
{
t=0;
break;
}
else
}
if(t)
}
break;
}
case TTIME:
{
/* time the command */
#ifdef timeofday
#else
#endif /* timeofday */
{
break;
}
{
long timer_on;
#ifdef timeofday
#else
#endif /* timeofday */
if(!timer_on)
}
else
{
#ifndef timeofday
bt = 0;
#endif /* timeofday */
}
#ifdef timeofday
#else
#endif /* timeofday */
{
if(np)
{
}
if(!format)
}
else
break;
}
case TFUN:
{
#if SHOPT_NAMESPACE
{
if(cp)
{
}
if(top)
if(top)
break;
}
#endif /* SHOPT_NAMESPACE */
/* look for discipline functions */
/* Function names cannot be special builtin */
{
{
}
else
{
}
}
#if SHOPT_NAMESPACE
{
}
#endif /* SHOPT_NAMESPACE */
if(npv)
{
{
{
else
}
}
if(!cp)
}
{
{
}
}
{
}
{
{
};
{
}
}
else
else
break;
}
/* new test compound command */
case TTST:
{
register int n;
register char *left;
skipexitset++;
echeck = 1;
{
}
else
{
register int traceon=0;
register char *right;
register char *trap;
if(sh_isoption(SH_XTRACE))
{
}
{
if(traceon)
if(trap)
{
unop[0] = '-';
unop[1] = n;
unop[2] = 0;
argv[4] = 0;
}
}
{
char *op;
int pattern = 0;
if(trap)
{
argv[5] = 0;
}
if(traceon)
{
if(pattern)
else
}
}
if(traceon)
}
if(!skipexitset)
exitset();
break;
}
}
t && echeck)
sh_chktrap();
/* set $_ */
{
/* store last argument here if it fits */
if(sh_isstate(SH_FORKED))
{
}
else
{
}
}
if(!skipexitset)
exitset();
if(!(OPTIMIZE))
{
}
if(was_interactive)
if(was_errexit)
}
}
{
dp = (struct dolnod*)stakalloc((unsigned)sizeof(struct dolnod) + ARG_SPARE*sizeof(char*) + argn*sizeof(char*));
else
return(argn);
}
/*
* test for equality with second argument trimmed
* returns 1 if r == trim(s) otherwise 0
*/
static int trim_eq(register const char *r,register const char *s)
{
register char c;
while(c = *s++)
{
if(c=='\\')
c = *s++;
if(c && c != *r++)
return(0);
}
return(*r==0);
}
/*
* print out the command line if set -x is on
*/
{
register char *cp;
register int bracket = 0;
nl &= ~2;
if(sh_isoption(SH_XTRACE))
{
/* make this trace atomic */
cp = "+ ";
else
{
}
if(*cp)
if(argv)
{
/* don't quote [ and [[ */
{
bracket = 1;
}
{
{
else
}
}
}
return(1);
}
return(0);
}
/*
* This routine creates a subshell by calling fork() or vfork()
* If ((flags&COMASK)==TCOM), then vfork() is permitted
* If fork fails, the shell sleeps for exponentially longer periods
* and tries again until a limit is reached.
* SH_FORKLIM is the max period between forks - power of 2 usually.
* Currently shell tries after 2,4,8,16, and 32 seconds and then quits
* Failures cause the routine to error exit.
* Parent links to here-documents are removed by the child
* Traps are reset by the child
* The process-id of the child is returned to the parent, 0 to the child.
*/
{
timeout = 0;
}
/*
* called by parent and child after fork by sh_fork()
*/
{
if(parent<0)
{
sh_sigcheck();
{
forkcnt=1000L;
}
if(timeout)
{
if(nochild)
pause();
else if(forkcnt>1000L)
forkcnt /= 2;
timeout = 0;
}
return(-1);
}
forkcnt = 1000L;
if(parent)
{
job_clear();
#ifdef JOBS
/* first process defines process group */
if(sh_isstate(SH_MONITOR))
{
/*
* errno==EPERM means that an earlier processes
* completed. Make parent the job group id.
*/
if(postid==0)
{
}
}
#endif /* JOBS */
#ifdef SHOPT_BGX
postid = 1;
if(postid==1)
postid = 0;
#else
#endif /* SHOPT_BGX */
if(jobid)
return(parent);
}
#if !_std_malloc
vmtrace(-1);
#endif
/* This is the child process */
#ifdef JOBS
if(sh_isstate(SH_MONITOR))
{
if(postid==0)
# ifdef SIGTSTP
# endif /* SIGTSTP */
}
# ifdef SIGTSTP
if(job.jobcontrol)
{
}
# endif /* SIGTSTP */
job.jobcontrol = 0;
#endif /* JOBS */
#if SHOPT_ACCT
sh_accsusp();
#endif /* SHOPT_ACCT */
/* Reset remaining signals to parent */
/* except for those `lost' by trap */
sh_sigreset(2);
if(sig>0)
sh_sigcheck();
return(0);
}
{
register int sig;
#if SHOPT_FASTPIPE
{
sh_iostream(0);
}
#endif /* SHOPT_FASTPIPE */
path_get("");
job_fork(-1);
if(sig>0)
return(parent);
}
/*
* add exports from previous scope to the new scope
*/
{
register char *cp;
if(nv_isarray(np))
if((cp = nv_getval(np)) && (mp = nv_search(nv_name(np), sh.var_tree, NV_ADD|HASH_NOSCOPE)) && nv_isnull(mp))
}
/*
* This routine is used to execute the given function <fun> in a new scope
* If <fun> is NULL, then arg points to a structure containing a pointer
* to a function that will be executed in the current environment.
*/
{
register char *trap;
register int nsig;
int jmpval;
volatile int r = 0;
char *savstak;
else
#if 0
#endif
if(!fun)
{
}
{
/* eliminate parent scope */
}
if(!fun)
{
else
#if SHOPT_NAMESPACE
{
}
#endif /* SHOPT_NAMESPACE */
}
/* save trap table */
{
nsig += sizeof(char*);
}
sh_sigreset(0);
if(!fun)
{
}
if(jmpval == 0)
{
{
}
else if(fun)
else
{
}
}
sh_sigreset(1);
if(nsig)
if(nsig)
if(trap)
{
}
{
sh_chktrap();
}
return(r);
}
static void sh_funct(Shell_t *shp,Namval_t *np,int argn, char *argv[],struct argnod *envlist,int execflg)
{
lp = init_level(0);
{
char *save;
argv[-1] = 0;
error_info.errors = 0;
}
else
{
}
{
}
#if 0
#else
#endif
}
/*
* external interface to execute a function without arguments
* <np> is the function node
* If <nq> is not-null, then sh.name and sh.subscript will be set
*/
{
register int offset;
register char *base;
long mode;
int n=0;
base=stakfreeze(0);
if(!argv)
{
argv[1]=0;
}
while(argv[n])
n++;
if(nq)
if(is_abuiltin(np))
{
int jmpval;
if(jmpval == 0)
{
}
}
else
if(nq)
if(offset>0)
}
/*
* This dummy routine is called by built-ins that do recursion
* on the file system (chmod, chgrp, chown). It causes
* the shell to invoke the non-builtin version in this case
*/
{
return(SH_RUNPROG);
}
/*
* set up pipe for cooperating process
*/
{
int outfd;
{
/* first co-process */
{
if(fd>=10)
{
}
}
}
}
#if SHOPT_SPAWN
/*
* print out function definition
*/
{
register char *format;
return;
format="%s()\n{ ";
else
format="function %s\n{ ";
}
/*
* create a shell script consisting of t->fork.forktre and execute it
*/
{
envlist[1] = 0;
if(*arglist[0]=='-')
arglist[0]++;
arglist[2] = 0;
if(trace)
{
/* pass the positional parameters */
while(*argv)
}
for(i=3; i < 10; i++)
{
{
}
}
if(trace)
{
}
if(fd>9)
*cp = 0;
for(i=3; i < 10; i++)
{
}
if(pid <=0)
return(pid);
}
#endif /* !_lib_fork */
{
register char *trap;
while(sig-- > 0)
{
}
}
/*
*/
{
static int savetype;
static int savejobid;
if(flag)
{
savetype=0;
}
if(!argv)
{
int optimize=0;
spawnpid = 0;
# ifndef _lib_fork
{
if(np)
{
np=0;
np=0;
}
optimize=1;
{
else
path = 0;
}
else
optimize=1;
}
# endif
{
{
{
}
}
{
}
{
}
if(optimize==0)
{
#ifdef SIGTSTP
if(job.jobcontrol)
{
}
#endif /* SIGTSTP */
#ifdef JOBS
{
grp = 1;
else
}
#endif /* JOBS */
}
else
{
if(jobid)
}
}
{
}
if(optimize==0)
{
#ifdef SIGTSTP
if(job.jobcontrol)
{
}
#endif /* SIGTSTP */
if(spawnpid>0)
{
}
}
savetype=0;
{
}
return(spawnpid);
}
# endif /* !_lib_fork */
if(jmpval == 0)
{
{
}
spawnpid = -1;
{
scope++;
}
{
{
}
else
{
while(pp)
{
break;
}
if(!pp)
path = 0;
}
}
else if(sh_isoption(SH_RESTRICTED))
if(!path)
{
spawnpid = -1;
goto fail;
}
#ifdef SIGTSTP
if(job.jobcontrol)
{
jobwasset++;
}
#endif /* SIGTSTP */
#ifdef JOBS
{
grp = 1;
else
}
#endif /* JOBS */
sigreset(0); /* set signals to ignore */
sigwasset++;
/* find first path that has a library component */
{
char *devfd;
if(fd>=0)
{
}
if(fd>=0)
}
fail:
{
case ENOENT:
default:
}
}
else
exitset();
#ifdef SIGTSTP
if(jobwasset)
{
}
#endif /* SIGTSTP */
if(sigwasset)
if(scope)
{
if(jmpval==SH_JMPSCRIPT)
}
if(spawnpid>0)
{
#ifdef JOBS
if(grp==1)
# ifdef SIGTSTP
{
}
# endif /* SIGTSTP */
#endif /* JOBS */
if(otype)
return(0);
}
return(spawnpid);
}
# ifdef _was_lib_fork
# endif
# ifndef _lib_fork
{
return(-1);
}
# endif /* _lib_fork */
#endif /* SHOPT_SPAWN */