From: Denis Vlasenko Date: Fri, 23 Feb 2007 21:10:06 +0000 (-0000) Subject: ash: cleanup part 2.4 X-Git-Tag: 1_5_1~140 X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=a89150733a39d25a916c28e76f530845a56342bb;p=oweals%2Fbusybox.git ash: cleanup part 2.4 --- diff --git a/shell/ash.c b/shell/ash.c index e3a43979a..869ed12fd 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -1583,8 +1583,18 @@ struct localvar { /* Forward decls for varinit[] */ #if ENABLE_LOCALE_SUPPORT -static void change_lc_all(const char *value); -static void change_lc_ctype(const char *value); +static void +change_lc_all(const char *value) +{ + if (value && *value != '\0') + setlocale(LC_ALL, value); +} +static void +change_lc_ctype(const char *value) +{ + if (value && *value != '\0') + setlocale(LC_CTYPE, value); +} #endif #if ENABLE_ASH_MAIL static void chkmail(void); @@ -2863,72 +2873,6 @@ static unsigned long rseed; #endif -/* jobs.h */ - -/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ -#define FORK_FG 0 -#define FORK_BG 1 -#define FORK_NOJOB 2 - -/* mode flags for showjob(s) */ -#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ -#define SHOW_PID 0x04 /* include process pid */ -#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ - - -/* - * A job structure contains information about a job. A job is either a - * single process or a set of processes contained in a pipeline. In the - * latter case, pidlist will be non-NULL, and will point to a -1 terminated - * array of pids. - */ - -struct procstat { - pid_t pid; /* process id */ - int status; /* last process status from wait() */ - char *cmd; /* text of command being run */ -}; - -struct job { - struct procstat ps0; /* status of process */ - struct procstat *ps; /* status or processes when more than one */ -#if JOBS - int stopstatus; /* status of a stopped job */ -#endif - uint32_t - nprocs: 16, /* number of processes */ - state: 8, -#define JOBRUNNING 0 /* at least one proc running */ -#define JOBSTOPPED 1 /* all procs are stopped */ -#define JOBDONE 2 /* all procs are completed */ -#if JOBS - sigint: 1, /* job was killed by SIGINT */ - jobctl: 1, /* job running under job control */ -#endif - waited: 1, /* true if this entry has been waited for */ - used: 1, /* true if this entry is in used */ - changed: 1; /* true if status has changed */ - struct job *prev_job; /* previous job */ -}; - -static pid_t backgndpid; /* pid of last background process */ -static int job_warning; /* user was warned about stopped jobs */ -#if JOBS -static int jobctl; /* true if doing job control */ -#endif - -static struct job *makejob(union node *, int); -static int forkshell(struct job *, union node *, int); -static int waitforjob(struct job *); - -#if ! JOBS -#define setjobctl(on) /* do nothing */ -#else -static void setjobctl(int); -static void showjobs(FILE *, int); -#endif - - /* main.h */ static void readcmdfile(char *); @@ -3159,5641 +3103,5706 @@ unaliascmd(int argc, char **argv) #endif /* ASH_ALIAS */ -/* ============ Routines to expand arguments to commands - * - * We have to deal with backquotes, shell variables, and file metacharacters. - */ +/* ============ jobs.c */ + +/* Mode argument to forkshell. Don't change FORK_FG or FORK_BG. */ +#define FORK_FG 0 +#define FORK_BG 1 +#define FORK_NOJOB 2 + +/* mode flags for showjob(s) */ +#define SHOW_PGID 0x01 /* only show pgid - for jobs -p */ +#define SHOW_PID 0x04 /* include process pid */ +#define SHOW_CHANGED 0x08 /* only jobs whose state has changed */ -/* - * expandarg flags - */ -#define EXP_FULL 0x1 /* perform word splitting & file globbing */ -#define EXP_TILDE 0x2 /* do normal tilde expansion */ -#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ -#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ -#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ -#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ -#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ -#define EXP_WORD 0x80 /* expand word in parameter expansion */ -#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ -/* - * _rmescape() flags - */ -#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ -#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ -#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */ -#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ -#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ /* - * Structure specifying which parts of the string should be searched - * for IFS characters. + * A job structure contains information about a job. A job is either a + * single process or a set of processes contained in a pipeline. In the + * latter case, pidlist will be non-NULL, and will point to a -1 terminated + * array of pids. */ -struct ifsregion { - struct ifsregion *next; /* next region in list */ - int begoff; /* offset of start of region */ - int endoff; /* offset of end of region */ - int nulonly; /* search for nul bytes only */ + +struct procstat { + pid_t pid; /* process id */ + int status; /* last process status from wait() */ + char *cmd; /* text of command being run */ }; -struct arglist { - struct strlist *list; - struct strlist **lastp; +struct job { + struct procstat ps0; /* status of process */ + struct procstat *ps; /* status or processes when more than one */ +#if JOBS + int stopstatus; /* status of a stopped job */ +#endif + uint32_t + nprocs: 16, /* number of processes */ + state: 8, +#define JOBRUNNING 0 /* at least one proc running */ +#define JOBSTOPPED 1 /* all procs are stopped */ +#define JOBDONE 2 /* all procs are completed */ +#if JOBS + sigint: 1, /* job was killed by SIGINT */ + jobctl: 1, /* job running under job control */ +#endif + waited: 1, /* true if this entry has been waited for */ + used: 1, /* true if this entry is in used */ + changed: 1; /* true if status has changed */ + struct job *prev_job; /* previous job */ }; -/* output of current string */ -static char *expdest; -/* list of back quote expressions */ -static struct nodelist *argbackq; -/* first struct in list of ifs regions */ -static struct ifsregion ifsfirst; -/* last struct in list */ -static struct ifsregion *ifslastp; -/* holds expanded arg list */ -static struct arglist exparg; +static pid_t backgndpid; /* pid of last background process */ +static int job_warning; /* user was warned about stopped jobs */ +#if JOBS +static int jobctl; /* true if doing job control */ +#endif -/* - * Our own itoa(). - */ -static int -cvtnum(arith_t num) -{ - int len; +static struct job *makejob(union node *, int); +static int forkshell(struct job *, union node *, int); +static int waitforjob(struct job *); - expdest = makestrspace(32, expdest); -#if ENABLE_ASH_MATH_SUPPORT_64 - len = fmtstr(expdest, 32, "%lld", (long long) num); +#if ! JOBS +#define setjobctl(on) /* do nothing */ #else - len = fmtstr(expdest, 32, "%ld", num); +static void setjobctl(int); +static void showjobs(FILE *, int); #endif - STADJUST(len, expdest); - return len; -} - -static size_t -esclen(const char *start, const char *p) -{ - size_t esc = 0; - - while (p > start && *--p == CTLESC) { - esc++; - } - return esc; -} /* - * Remove any CTLESC characters from a string. + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. */ -static char * -_rmescapes(char *str, int flag) +static void +setsignal(int signo) { - char *p, *q, *r; - static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 }; - unsigned inquotes; - int notescaped; - int globbing; + int action; + char *t, tsig; + struct sigaction act; - p = strpbrk(str, qchars); - if (!p) { - return str; + t = trap[signo]; + if (t == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (rootshell && action == S_DFL) { + switch (signo) { + case SIGINT: + if (iflag || minusc || sflag == 0) + action = S_CATCH; + break; + case SIGQUIT: +#if DEBUG + if (debug) + break; +#endif + /* FALLTHROUGH */ + case SIGTERM: + if (iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (mflag) + action = S_IGN; + break; +#endif + } } - q = p; - r = str; - if (flag & RMESCAPE_ALLOC) { - size_t len = p - str; - size_t fulllen = len + strlen(p) + 1; - if (flag & RMESCAPE_GROW) { - r = makestrspace(fulllen, expdest); - } else if (flag & RMESCAPE_HEAP) { - r = ckmalloc(fulllen); - } else { - r = stalloc(fulllen); - } - q = r; - if (len > 0) { - q = memcpy(q, str, len) + len; - } - } - inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; - globbing = flag & RMESCAPE_GLOB; - notescaped = globbing; - while (*p) { - if (*p == CTLQUOTEMARK) { - inquotes = ~inquotes; - p++; - notescaped = globbing; - continue; - } - if (*p == '\\') { - /* naked back slash */ - notescaped = 0; - goto copy; + t = &sigmode[signo - 1]; + tsig = *t; + if (tsig == 0) { + /* + * current setting unknown + */ + if (sigaction(signo, 0, &act) == -1) { + /* + * Pretend it worked; maybe we should give a warning + * here, but other shells don't. We don't alter + * sigmode, so that we retry every time. + */ + return; } - if (*p == CTLESC) { - p++; - if (notescaped && inquotes && *p != '/') { - *q++ = '\\'; - } + if (act.sa_handler == SIG_IGN) { + if (mflag + && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) + ) { + tsig = S_IGN; /* don't hard ignore these */ + } else + tsig = S_HARD_IGN; + } else { + tsig = S_RESET; /* force to be set */ } - notescaped = globbing; - copy: - *q++ = *p++; } - *q = '\0'; - if (flag & RMESCAPE_GROW) { - expdest = r; - STADJUST(q - r + 1, expdest); + if (tsig == S_HARD_IGN || tsig == action) + return; + switch (action) { + case S_CATCH: + act.sa_handler = onsig; + break; + case S_IGN: + act.sa_handler = SIG_IGN; + break; + default: + act.sa_handler = SIG_DFL; } - return r; + *t = action; + act.sa_flags = 0; + sigfillset(&act.sa_mask); + sigaction(signo, &act, 0); } -#define rmescapes(p) _rmescapes((p), 0) -#define pmatch(a, b) !fnmatch((a), (b), 0) +/* mode flags for set_curjob */ +#define CUR_DELETE 2 +#define CUR_RUNNING 1 +#define CUR_STOPPED 0 -/* - * Prepare a pattern for a expmeta (internal glob(3)) call. - * - * Returns an stalloced string. - */ -static char * -preglob(const char *pattern, int quoted, int flag) -{ - flag |= RMESCAPE_GLOB; - if (quoted) { - flag |= RMESCAPE_QUOTED; - } - return _rmescapes((char *)pattern, flag); -} +/* mode flags for dowait */ +#define DOWAIT_NORMAL 0 +#define DOWAIT_BLOCK 1 + +#if JOBS +/* pgrp of shell on invocation */ +static int initialpgrp; +static int ttyfd = -1; +#endif +/* array of jobs */ +static struct job *jobtab; +/* size of array */ +static unsigned njobs; +/* current job */ +static struct job *curjob; +/* number of presumed living untracked jobs */ +static int jobless; -/* - * Put a string on the stack. - */ static void -memtodest(const char *p, size_t len, int syntax, int quotes) +set_curjob(struct job *jp, unsigned mode) { - char *q = expdest; + struct job *jp1; + struct job **jpp, **curp; - q = makestrspace(len * 2, q); + /* first remove from list */ + jpp = curp = &curjob; + do { + jp1 = *jpp; + if (jp1 == jp) + break; + jpp = &jp1->prev_job; + } while (1); + *jpp = jp1->prev_job; - while (len--) { - int c = SC2INT(*p++); - if (!c) - continue; - if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK)) - USTPUTC(CTLESC, q); - USTPUTC(c, q); + /* Then re-insert in correct position */ + jpp = curp; + switch (mode) { + default: +#if DEBUG + abort(); +#endif + case CUR_DELETE: + /* job being deleted */ + break; + case CUR_RUNNING: + /* newly created job or backgrounded job, + put after all stopped jobs. */ + do { + jp1 = *jpp; +#if JOBS + if (!jp1 || jp1->state != JOBSTOPPED) +#endif + break; + jpp = &jp1->prev_job; + } while (1); + /* FALLTHROUGH */ +#if JOBS + case CUR_STOPPED: +#endif + /* newly stopped job - becomes curjob */ + jp->prev_job = *jpp; + *jpp = jp; + break; } - - expdest = q; } -static void -strtodest(const char *p, int syntax, int quotes) +#if JOBS || DEBUG +static int +jobno(const struct job *jp) { - memtodest(p, strlen(p), syntax, quotes); + return jp - jobtab + 1; } +#endif /* - * Record the fact that we have to scan this region of the - * string for IFS characters. + * Convert a job name to a job structure. */ -static void -recordregion(int start, int end, int nulonly) +static struct job * +getjob(const char *name, int getctl) { - struct ifsregion *ifsp; + struct job *jp; + struct job *found; + const char *err_msg = "No such job: %s"; + unsigned num; + int c; + const char *p; + char *(*match)(const char *, const char *); - if (ifslastp == NULL) { - ifsp = &ifsfirst; - } else { - INT_OFF; - ifsp = ckmalloc(sizeof(*ifsp)); - ifsp->next = NULL; - ifslastp->next = ifsp; - INT_ON; - } - ifslastp = ifsp; - ifslastp->begoff = start; - ifslastp->endoff = end; - ifslastp->nulonly = nulonly; -} + jp = curjob; + p = name; + if (!p) + goto currentjob; -static void -removerecordregions(int endoff) -{ - if (ifslastp == NULL) - return; + if (*p != '%') + goto err; - if (ifsfirst.endoff > endoff) { - while (ifsfirst.next != NULL) { - struct ifsregion *ifsp; - INT_OFF; - ifsp = ifsfirst.next->next; - free(ifsfirst.next); - ifsfirst.next = ifsp; - INT_ON; + c = *++p; + if (!c) + goto currentjob; + + if (!p[1]) { + if (c == '+' || c == '%') { + currentjob: + err_msg = "No current job"; + goto check; } - if (ifsfirst.begoff > endoff) - ifslastp = NULL; - else { - ifslastp = &ifsfirst; - ifsfirst.endoff = endoff; + if (c == '-') { + if (jp) + jp = jp->prev_job; + err_msg = "No previous job"; + check: + if (!jp) + goto err; + goto gotit; } - return; } - ifslastp = &ifsfirst; - while (ifslastp->next && ifslastp->next->begoff < endoff) - ifslastp=ifslastp->next; - while (ifslastp->next != NULL) { - struct ifsregion *ifsp; - INT_OFF; - ifsp = ifslastp->next->next; - free(ifslastp->next); - ifslastp->next = ifsp; - INT_ON; - } - if (ifslastp->endoff > endoff) - ifslastp->endoff = endoff; -} - -static char * -exptilde(char *startp, char *p, int flag) -{ - char c; - char *name; - struct passwd *pw; - const char *home; - int quotes = flag & (EXP_FULL | EXP_CASE); - int startloc; - - name = p + 1; - - while ((c = *++p) != '\0') { - switch (c) { - case CTLESC: - return startp; - case CTLQUOTEMARK: - return startp; - case ':': - if (flag & EXP_VARTILDE) - goto done; - break; - case '/': - case CTLENDVAR: - goto done; + if (is_number(p)) { + num = atoi(p); + if (num < njobs) { + jp = jobtab + num - 1; + if (jp->used) + goto gotit; + goto err; } } - done: - *p = '\0'; - if (*name == '\0') { - home = lookupvar(homestr); - } else { - pw = getpwnam(name); - if (pw == NULL) - goto lose; - home = pw->pw_dir; - } - if (!home || !*home) - goto lose; - *p = c; - startloc = expdest - (char *)stackblock(); - strtodest(home, SQSYNTAX, quotes); - recordregion(startloc, expdest - (char *)stackblock(), 0); - return p; - lose: - *p = c; - return startp; -} - -/* - * Execute a command inside back quotes. If it's a builtin command, we - * want to save its output in a block obtained from malloc. Otherwise - * we fork off a subprocess and get the output of the command via a pipe. - * Should be called with interrupts off. - */ -struct backcmd { /* result of evalbackcmd */ - int fd; /* file descriptor to read from */ - char *buf; /* buffer */ - int nleft; /* number of chars in buffer */ - struct job *jp; /* job structure for command */ -}; - -/* These forward decls are needed to use "eval" code for backticks handling: */ -static int back_exitstatus; /* exit status of backquoted command */ -#define EV_EXIT 01 /* exit after evaluating tree */ -static void evaltree(union node *, int); - -static void -evalbackcmd(union node *n, struct backcmd *result) -{ - int saveherefd; - result->fd = -1; - result->buf = NULL; - result->nleft = 0; - result->jp = NULL; - if (n == NULL) { - goto out; + match = prefix; + if (*p == '?') { + match = strstr; + p++; } - saveherefd = herefd; - herefd = -1; - - { - int pip[2]; - struct job *jp; - - if (pipe(pip) < 0) - ash_msg_and_raise_error("Pipe call failed"); - jp = makejob(n, 1); - if (forkshell(jp, n, FORK_NOJOB) == 0) { - FORCE_INT_ON; - close(pip[0]); - if (pip[1] != 1) { - close(1); - copyfd(pip[1], 1); - close(pip[1]); - } - eflag = 0; - evaltree(n, EV_EXIT); /* actually evaltreenr... */ - /* NOTREACHED */ + found = 0; + while (1) { + if (!jp) + goto err; + if (match(jp->ps[0].cmd, p)) { + if (found) + goto err; + found = jp; + err_msg = "%s: ambiguous"; } - close(pip[1]); - result->fd = pip[0]; - result->jp = jp; + jp = jp->prev_job; } - herefd = saveherefd; - out: - TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", - result->fd, result->buf, result->nleft, result->jp)); + + gotit: +#if JOBS + err_msg = "job %s not created under job control"; + if (getctl && jp->jobctl == 0) + goto err; +#endif + return jp; + err: + ash_msg_and_raise_error(err_msg, name); } /* - * Expand stuff in backwards quotes. + * Mark a job structure as unused. */ static void -expbackq(union node *cmd, int quoted, int quotes) +freejob(struct job *jp) { - struct backcmd in; + struct procstat *ps; int i; - char buf[128]; - char *p; - char *dest; - int startloc; - int syntax = quoted? DQSYNTAX : BASESYNTAX; - struct stackmark smark; INT_OFF; - setstackmark(&smark); - dest = expdest; - startloc = dest - (char *)stackblock(); - grabstackstr(dest); - evalbackcmd(cmd, &in); - popstackmark(&smark); - - p = in.buf; - i = in.nleft; - if (i == 0) - goto read; - for (;;) { - memtodest(p, i, syntax, quotes); - read: - if (in.fd < 0) - break; - i = safe_read(in.fd, buf, sizeof(buf)); - TRACE(("expbackq: read returns %d\n", i)); - if (i <= 0) - break; - p = buf; - } - - if (in.buf) - free(in.buf); - if (in.fd >= 0) { - close(in.fd); - back_exitstatus = waitforjob(in.jp); + for (i = jp->nprocs, ps = jp->ps; --i >= 0; ps++) { + if (ps->cmd != nullstr) + free(ps->cmd); } + if (jp->ps != &jp->ps0) + free(jp->ps); + jp->used = 0; + set_curjob(jp, CUR_DELETE); INT_ON; +} - /* Eat all trailing newlines */ - dest = expdest; - for (; dest > (char *)stackblock() && dest[-1] == '\n';) - STUNPUTC(dest); - expdest = dest; - - if (quoted == 0) - recordregion(startloc, dest - (char *)stackblock(), 0); - TRACE(("evalbackq: size=%d: \"%.*s\"\n", - (dest - (char *)stackblock()) - startloc, - (dest - (char *)stackblock()) - startloc, - stackblock() + startloc)); +#if JOBS +static void +xtcsetpgrp(int fd, pid_t pgrp) +{ + if (tcsetpgrp(fd, pgrp)) + ash_msg_and_raise_error("Cannot set tty process group (%m)"); } -#if ENABLE_ASH_MATH_SUPPORT /* - * Expand arithmetic expression. Backup to start of expression, - * evaluate, place result in (backed up) result, adjust string position. + * Turn job control on and off. + * + * Note: This code assumes that the third arg to ioctl is a character + * pointer, which is true on Berkeley systems but not System V. Since + * System V doesn't have job control yet, this isn't a problem now. + * + * Called with interrupts off. */ static void -expari(int quotes) +setjobctl(int on) { - char *p, *start; - int begoff; - int flag; - int len; - - /* ifsfree(); */ - - /* - * This routine is slightly over-complicated for - * efficiency. Next we scan backwards looking for the - * start of arithmetic. - */ - start = stackblock(); - p = expdest - 1; - *p = '\0'; - p--; - do { - int esc; + int fd; + int pgrp; - while (*p != CTLARI) { - p--; -#if DEBUG - if (p < start) { - ash_msg_and_raise_error("missing CTLARI (shouldn't happen)"); - } -#endif - } - - esc = esclen(start, p); - if (!(esc % 2)) { - break; + if (on == jobctl || rootshell == 0) + return; + if (on) { + int ofd; + ofd = fd = open(_PATH_TTY, O_RDWR); + if (fd < 0) { + /* BTW, bash will try to open(ttyname(0)) if open("/dev/tty") fails. + * That sometimes helps to acquire controlling tty. + * Obviously, a workaround for bugs when someone + * failed to provide a controlling tty to bash! :) */ + fd += 3; + while (!isatty(fd) && --fd >= 0) + ; } + fd = fcntl(fd, F_DUPFD, 10); + close(ofd); + if (fd < 0) + goto out; + fcntl(fd, F_SETFD, FD_CLOEXEC); + do { /* while we are in the background */ + pgrp = tcgetpgrp(fd); + if (pgrp < 0) { + out: + ash_msg("can't access tty; job control turned off"); + mflag = on = 0; + goto close; + } + if (pgrp == getpgrp()) + break; + killpg(0, SIGTTIN); + } while (1); + initialpgrp = pgrp; - p -= esc + 1; - } while (1); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + pgrp = rootpid; + setpgid(0, pgrp); + xtcsetpgrp(fd, pgrp); + } else { + /* turning job control off */ + fd = ttyfd; + pgrp = initialpgrp; + xtcsetpgrp(fd, pgrp); + setpgid(0, pgrp); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + setsignal(SIGTTIN); + close: + close(fd); + fd = -1; + } + ttyfd = fd; + jobctl = on; +} - begoff = p - start; +static int +killcmd(int argc, char **argv) +{ + int signo = -1; + int list = 0; + int i; + pid_t pid; + struct job *jp; - removerecordregions(begoff); + if (argc <= 1) { + usage: + ash_msg_and_raise_error( +"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n" +"kill -l [exitstatus]" + ); + } - flag = p[1]; + if (**++argv == '-') { + signo = get_signum(*argv + 1); + if (signo < 0) { + int c; - expdest = p; + while ((c = nextopt("ls:")) != '\0') { + switch (c) { + default: +#if DEBUG + abort(); +#endif + case 'l': + list = 1; + break; + case 's': + signo = get_signum(optionarg); + if (signo < 0) { + ash_msg_and_raise_error( + "invalid signal number or name: %s", + optionarg + ); + } + break; + } + } + argv = argptr; + } else + argv++; + } - if (quotes) - rmescapes(p + 2); + if (!list && signo < 0) + signo = SIGTERM; - len = cvtnum(dash_arith(p + 2)); + if ((signo < 0 || !*argv) ^ list) { + goto usage; + } - if (flag != '"') - recordregion(begoff, begoff + len, 0); -} -#endif + if (list) { + const char *name; -/* argstr needs it */ -static char *evalvar(char *p, int flag); + if (!*argv) { + for (i = 1; i < NSIG; i++) { + name = get_signame(i); + if (isdigit(*name)) + out1fmt(snlfmt, name); + } + return 0; + } + name = get_signame(signo); + if (!isdigit(*name)) + ash_msg_and_raise_error("invalid signal number or exit status: %s", *argptr); + out1fmt(snlfmt, name); + return 0; + } + + i = 0; + do { + if (**argv == '%') { + jp = getjob(*argv, 0); + pid = -jp->ps[0].pid; + } else { + pid = **argv == '-' ? + -number(*argv + 1) : number(*argv); + } + if (kill(pid, signo) != 0) { + ash_msg("(%d) - %m", pid); + i = 1; + } + } while (*++argv); + + return i; +} -/* - * Perform variable and command substitution. If EXP_FULL is set, output CTLESC - * characters to allow for further processing. Otherwise treat - * $@ like $* since no splitting will be performed. - */ static void -argstr(char *p, int flag) +showpipe(struct job *jp, FILE *out) { - static const char spclchars[] = { - '=', - ':', - CTLQUOTEMARK, - CTLENDVAR, - CTLESC, - CTLVAR, - CTLBACKQ, - CTLBACKQ | CTLQUOTE, -#if ENABLE_ASH_MATH_SUPPORT - CTLENDARI, -#endif - 0 - }; - const char *reject = spclchars; - int c; - int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ - int breakall = flag & EXP_WORD; - int inquotes; - size_t length; - int startloc; + struct procstat *sp; + struct procstat *spend; - if (!(flag & EXP_VARTILDE)) { - reject += 2; - } else if (flag & EXP_VARTILDE2) { - reject++; - } - inquotes = 0; - length = 0; - if (flag & EXP_TILDE) { - char *q; + spend = jp->ps + jp->nprocs; + for (sp = jp->ps + 1; sp < spend; sp++) + fprintf(out, " | %s", sp->cmd); + outcslow('\n', out); + flush_stdout_stderr(); +} - flag &= ~EXP_TILDE; - tilde: - q = p; - if (*q == CTLESC && (flag & EXP_QWORD)) - q++; - if (*q == '~') - p = exptilde(p, q, flag); - } - start: - startloc = expdest - (char *)stackblock(); - for (;;) { - length += strcspn(p + length, reject); - c = p[length]; - if (c && (!(c & 0x80) -#if ENABLE_ASH_MATH_SUPPORT - || c == CTLENDARI -#endif - )) { - /* c == '=' || c == ':' || c == CTLENDARI */ - length++; - } - if (length > 0) { - int newloc; - expdest = stack_nputstr(p, length, expdest); - newloc = expdest - (char *)stackblock(); - if (breakall && !inquotes && newloc > startloc) { - recordregion(startloc, newloc, 0); - } - startloc = newloc; - } - p += length + 1; - length = 0; - switch (c) { - case '\0': - goto breakloop; - case '=': - if (flag & EXP_VARTILDE2) { - p--; - continue; - } - flag |= EXP_VARTILDE2; - reject++; - /* fall through */ - case ':': - /* - * sort of a hack - expand tildes in variable - * assignments (after the first '=' and after ':'s). - */ - if (*--p == '~') { - goto tilde; - } - continue; - } +static int +restartjob(struct job *jp, int mode) +{ + struct procstat *ps; + int i; + int status; + pid_t pgid; - switch (c) { - case CTLENDVAR: /* ??? */ - goto breakloop; - case CTLQUOTEMARK: - /* "$@" syntax adherence hack */ - if ( - !inquotes && - !memcmp(p, dolatstr, 4) && - (p[4] == CTLQUOTEMARK || ( - p[4] == CTLENDVAR && - p[5] == CTLQUOTEMARK - )) - ) { - p = evalvar(p + 1, flag) + 1; - goto start; - } - inquotes = !inquotes; - addquote: - if (quotes) { - p--; - length++; - startloc++; - } - break; - case CTLESC: - startloc++; - length++; - goto addquote; - case CTLVAR: - p = evalvar(p, flag); - goto start; - case CTLBACKQ: - c = 0; - case CTLBACKQ|CTLQUOTE: - expbackq(argbackq->n, c, quotes); - argbackq = argbackq->next; - goto start; -#if ENABLE_ASH_MATH_SUPPORT - case CTLENDARI: - p--; - expari(quotes); - goto start; -#endif + INT_OFF; + if (jp->state == JOBDONE) + goto out; + jp->state = JOBRUNNING; + pgid = jp->ps->pid; + if (mode == FORK_FG) + xtcsetpgrp(ttyfd, pgid); + killpg(pgid, SIGCONT); + ps = jp->ps; + i = jp->nprocs; + do { + if (WIFSTOPPED(ps->status)) { + ps->status = -1; } - } - breakloop: - ; + } while (ps++, --i); + out: + status = (mode == FORK_FG) ? waitforjob(jp) : 0; + INT_ON; + return status; } -static char * -scanleft(char *startp, char *rmesc, char *rmescend, char *str, int quotes, - int zero) +static int +fg_bgcmd(int argc, char **argv) { - char *loc; - char *loc2; - char c; + struct job *jp; + FILE *out; + int mode; + int retval; - loc = startp; - loc2 = rmesc; + mode = (**argv == 'f') ? FORK_FG : FORK_BG; + nextopt(nullstr); + argv = argptr; + out = stdout; do { - int match; - const char *s = loc2; - c = *loc2; - if (zero) { - *loc2 = '\0'; - s = rmesc; + jp = getjob(*argv, 1); + if (mode == FORK_BG) { + set_curjob(jp, CUR_RUNNING); + fprintf(out, "[%d] ", jobno(jp)); } - match = pmatch(str, s); - *loc2 = c; - if (match) - return loc; - if (quotes && *loc == CTLESC) - loc++; - loc++; - loc2++; - } while (c); - return 0; + outstr(jp->ps->cmd, out); + showpipe(jp, out); + retval = restartjob(jp, mode); + } while (*argv && *++argv); + return retval; } +#endif -static char * -scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes, - int zero) +static int +sprint_status(char *s, int status, int sigonly) { - int esc = 0; - char *loc; - char *loc2; + int col; + int st; - for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) { - int match; - char c = *loc2; - const char *s = loc2; - if (zero) { - *loc2 = '\0'; - s = rmesc; + col = 0; + if (!WIFEXITED(status)) { +#if JOBS + if (WIFSTOPPED(status)) + st = WSTOPSIG(status); + else +#endif + st = WTERMSIG(status); + if (sigonly) { + if (st == SIGINT || st == SIGPIPE) + goto out; +#if JOBS + if (WIFSTOPPED(status)) + goto out; +#endif } - match = pmatch(str, s); - *loc2 = c; - if (match) - return loc; - loc--; - if (quotes) { - if (--esc < 0) { - esc = esclen(startp, loc); - } - if (esc % 2) { - esc--; - loc--; - } + st &= 0x7f; + col = fmtstr(s, 32, strsignal(st)); + if (WCOREDUMP(status)) { + col += fmtstr(s + col, 16, " (core dumped)"); } + } else if (!sigonly) { + st = WEXITSTATUS(status); + if (st) + col = fmtstr(s, 16, "Done(%d)", st); + else + col = fmtstr(s, 16, "Done"); } - return 0; + out: + return col; } -static void varunset(const char *, const char *, const char *, int) ATTRIBUTE_NORETURN; -static void -varunset(const char *end, const char *var, const char *umsg, int varflags) +/* + * Do a wait system call. If job control is compiled in, we accept + * stopped processes. If block is zero, we return a value of zero + * rather than blocking. + * + * System V doesn't have a non-blocking wait system call. It does + * have a SIGCLD signal that is sent to a process when one of it's + * children dies. The obvious way to use SIGCLD would be to install + * a handler for SIGCLD which simply bumped a counter when a SIGCLD + * was received, and have waitproc bump another counter when it got + * the status of a process. Waitproc would then know that a wait + * system call would not block if the two counters were different. + * This approach doesn't work because if a process has children that + * have not been waited for, System V will send it a SIGCLD when it + * installs a signal handler for SIGCLD. What this means is that when + * a child exits, the shell will be sent SIGCLD signals continuously + * until is runs out of stack space, unless it does a wait call before + * restoring the signal handler. The code below takes advantage of + * this (mis)feature by installing a signal handler for SIGCLD and + * then checking to see whether it was called. If there are any + * children to be waited for, it will be. + * + * If neither SYSV nor BSD is defined, we don't implement nonblocking + * waits at all. In this case, the user will not be informed when + * a background process until the next time she runs a real program + * (as opposed to running a builtin command or just typing return), + * and the jobs command may give out of date information. + */ +static int +waitproc(int block, int *status) { - const char *msg; - const char *tail; + int flags = 0; - tail = nullstr; - msg = "parameter not set"; - if (umsg) { - if (*end == CTLENDVAR) { - if (varflags & VSNUL) - tail = " or null"; - } else - msg = umsg; - } - ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail); +#if JOBS + if (jobctl) + flags |= WUNTRACED; +#endif + if (block == 0) + flags |= WNOHANG; + return wait3(status, flags, (struct rusage *)NULL); } -static const char * -subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes) +/* + * Wait for a process to terminate. + */ +static int +dowait(int block, struct job *job) { - char *startp; - char *loc; - int saveherefd = herefd; - struct nodelist *saveargbackq = argbackq; - int amount; - char *rmesc, *rmescend; - int zero; - char *(*scan)(char *, char *, char *, char *, int , int); - - herefd = -1; - argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0); - STPUTC('\0', expdest); - herefd = saveherefd; - argbackq = saveargbackq; - startp = stackblock() + startloc; - - switch (subtype) { - case VSASSIGN: - setvar(str, startp, 0); - amount = startp - expdest; - STADJUST(amount, expdest); - return startp; - - case VSQUESTION: - varunset(p, str, startp, varflags); - /* NOTREACHED */ - } - - subtype -= VSTRIMRIGHT; -#if DEBUG - if (subtype < 0 || subtype > 3) - abort(); -#endif + int pid; + int status; + struct job *jp; + struct job *thisjob; + int state; - rmesc = startp; - rmescend = stackblock() + strloc; - if (quotes) { - rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); - if (rmesc != startp) { - rmescend = expdest; - startp = stackblock() + startloc; + TRACE(("dowait(%d) called\n", block)); + pid = waitproc(block, &status); + TRACE(("wait returns pid %d, status=%d\n", pid, status)); + if (pid <= 0) + return pid; + INT_OFF; + thisjob = NULL; + for (jp = curjob; jp; jp = jp->prev_job) { + struct procstat *sp; + struct procstat *spend; + if (jp->state == JOBDONE) + continue; + state = JOBDONE; + spend = jp->ps + jp->nprocs; + sp = jp->ps; + do { + if (sp->pid == pid) { + TRACE(("Job %d: changing status of proc %d " + "from 0x%x to 0x%x\n", + jobno(jp), pid, sp->status, status)); + sp->status = status; + thisjob = jp; + } + if (sp->status == -1) + state = JOBRUNNING; +#if JOBS + if (state == JOBRUNNING) + continue; + if (WIFSTOPPED(sp->status)) { + jp->stopstatus = sp->status; + state = JOBSTOPPED; + } +#endif + } while (++sp < spend); + if (thisjob) + goto gotjob; + } +#if JOBS + if (!WIFSTOPPED(status)) +#endif + + jobless--; + goto out; + + gotjob: + if (state != JOBRUNNING) { + thisjob->changed = 1; + + if (thisjob->state != state) { + TRACE(("Job %d: changing state from %d to %d\n", + jobno(thisjob), thisjob->state, state)); + thisjob->state = state; +#if JOBS + if (state == JOBSTOPPED) { + set_curjob(thisjob, CUR_STOPPED); + } +#endif } } - rmescend--; - str = stackblock() + strloc; - preglob(str, varflags & VSQUOTE, 0); - /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */ - zero = subtype >> 1; - /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */ - scan = (subtype & 1) ^ zero ? scanleft : scanright; + out: + INT_ON; - loc = scan(startp, rmesc, rmescend, str, quotes, zero); - if (loc) { - if (zero) { - memmove(startp, loc, str - loc); - loc = startp + (str - loc) - 1; + if (thisjob && thisjob == job) { + char s[48 + 1]; + int len; + + len = sprint_status(s, status, 1); + if (len) { + s[len] = '\n'; + s[len + 1] = 0; + out2str(s); } - *loc = '\0'; - amount = loc - expdest; - STADJUST(amount, expdest); } - return loc; + return pid; } -/* - * Add the value of a specialized variable to the stack string. - */ -static ssize_t -varvalue(char *name, int varflags, int flags) +#if JOBS +static void +showjob(FILE *out, struct job *jp, int mode) { - int num; - char *p; - int i; - int sep = 0; - int sepq = 0; - ssize_t len = 0; - char **ap; - int syntax; - int quoted = varflags & VSQUOTE; - int subtype = varflags & VSTYPE; - int quotes = flags & (EXP_FULL | EXP_CASE); + struct procstat *ps; + struct procstat *psend; + int col; + int indent; + char s[80]; - if (quoted && (flags & EXP_FULL)) - sep = 1 << CHAR_BIT; + ps = jp->ps; - syntax = quoted ? DQSYNTAX : BASESYNTAX; - switch (*name) { - case '$': - num = rootpid; - goto numvar; - case '?': - num = exitstatus; - goto numvar; - case '#': - num = shellparam.nparam; - goto numvar; - case '!': - num = backgndpid; - if (num == 0) - return -1; - numvar: - len = cvtnum(num); - break; - case '-': - p = makestrspace(NOPTS, expdest); - for (i = NOPTS - 1; i >= 0; i--) { - if (optlist[i]) { - USTPUTC(optletters(i), p); - len++; - } - } - expdest = p; - break; - case '@': - if (sep) - goto param; - /* fall through */ - case '*': - sep = ifsset() ? SC2INT(ifsval()[0]) : ' '; - if (quotes && (SIT(sep, syntax) == CCTL || SIT(sep, syntax) == CBACK)) - sepq = 1; - param: - ap = shellparam.p; - if (!ap) - return -1; - while ((p = *ap++)) { - size_t partlen; + if (mode & SHOW_PGID) { + /* just output process (group) id of pipeline */ + fprintf(out, "%d\n", ps->pid); + return; + } - partlen = strlen(p); - len += partlen; + col = fmtstr(s, 16, "[%d] ", jobno(jp)); + indent = col; - if (!(subtype == VSPLUS || subtype == VSLENGTH)) - memtodest(p, partlen, syntax, quotes); + if (jp == curjob) + s[col - 2] = '+'; + else if (curjob && jp == curjob->prev_job) + s[col - 2] = '-'; - if (*ap && sep) { - char *q; + if (mode & SHOW_PID) + col += fmtstr(s + col, 16, "%d ", ps->pid); - len++; - if (subtype == VSPLUS || subtype == VSLENGTH) { - continue; - } - q = expdest; - if (sepq) - STPUTC(CTLESC, q); - STPUTC(sep, q); - expdest = q; - } - } - return len; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - num = atoi(name); - if (num < 0 || num > shellparam.nparam) - return -1; - p = num ? shellparam.p[num - 1] : arg0; - goto value; - default: - p = lookupvar(name); - value: - if (!p) - return -1; + psend = ps + jp->nprocs; - len = strlen(p); - if (!(subtype == VSPLUS || subtype == VSLENGTH)) - memtodest(p, len, syntax, quotes); - return len; + if (jp->state == JOBRUNNING) { + strcpy(s + col, "Running"); + col += sizeof("Running") - 1; + } else { + int status = psend[-1].status; + if (jp->state == JOBSTOPPED) + status = jp->stopstatus; + col += sprint_status(s + col, status, 0); } - if (subtype == VSPLUS || subtype == VSLENGTH) - STADJUST(-len, expdest); - return len; + goto start; + + do { + /* for each process */ + col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3; + start: + fprintf(out, "%s%*c%s", + s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd + ); + if (!(mode & SHOW_PID)) { + showpipe(jp, out); + break; + } + if (++ps == psend) { + outcslow('\n', out); + break; + } + } while (1); + + jp->changed = 0; + + if (jp->state == JOBDONE) { + TRACE(("showjob: freeing job %d\n", jobno(jp))); + freejob(jp); + } } -/* - * Expand a variable, and return a pointer to the next character in the - * input string. - */ -static char * -evalvar(char *p, int flag) +static int +jobscmd(int argc, char **argv) { - int subtype; - int varflags; - char *var; - int patloc; - int c; - int startloc; - ssize_t varlen; - int easy; - int quotes; - int quoted; + int mode, m; + FILE *out; - quotes = flag & (EXP_FULL | EXP_CASE); - varflags = *p++; - subtype = varflags & VSTYPE; - quoted = varflags & VSQUOTE; - var = p; - easy = (!quoted || (*var == '@' && shellparam.nparam)); - startloc = expdest - (char *)stackblock(); - p = strchr(p, '=') + 1; + mode = 0; + while ((m = nextopt("lp"))) { + if (m == 'l') + mode = SHOW_PID; + else + mode = SHOW_PGID; + } - again: - varlen = varvalue(var, varflags, flag); - if (varflags & VSNUL) - varlen--; + out = stdout; + argv = argptr; + if (*argv) { + do + showjob(out, getjob(*argv,0), mode); + while (*++argv); + } else + showjobs(out, mode); - if (subtype == VSPLUS) { - varlen = -1 - varlen; - goto vsplus; - } + return 0; +} - if (subtype == VSMINUS) { - vsplus: - if (varlen < 0) { - argstr( - p, flag | EXP_TILDE | - (quoted ? EXP_QWORD : EXP_WORD) - ); - goto end; - } - if (easy) - goto record; - goto end; - } +/* + * Print a list of jobs. If "change" is nonzero, only print jobs whose + * statuses have changed since the last call to showjobs. + */ +static void +showjobs(FILE *out, int mode) +{ + struct job *jp; - if (subtype == VSASSIGN || subtype == VSQUESTION) { - if (varlen < 0) { - if (subevalvar(p, var, 0, subtype, startloc, varflags, 0)) { - varflags &= ~VSNUL; - /* - * Remove any recorded regions beyond - * start of variable - */ - removerecordregions(startloc); - goto again; - } - goto end; - } - if (easy) - goto record; - goto end; - } + TRACE(("showjobs(%x) called\n", mode)); - if (varlen < 0 && uflag) - varunset(p, var, 0, 0); + /* If not even one one job changed, there is nothing to do */ + while (dowait(DOWAIT_NORMAL, NULL) > 0) + continue; - if (subtype == VSLENGTH) { - cvtnum(varlen > 0 ? varlen : 0); - goto record; + for (jp = curjob; jp; jp = jp->prev_job) { + if (!(mode & SHOW_CHANGED) || jp->changed) + showjob(out, jp, mode); } +} +#endif /* JOBS */ - if (subtype == VSNORMAL) { - if (!easy) - goto end; - record: - recordregion(startloc, expdest - (char *)stackblock(), quoted); - goto end; - } +static int +getstatus(struct job *job) +{ + int status; + int retval; -#if DEBUG - switch (subtype) { - case VSTRIMLEFT: - case VSTRIMLEFTMAX: - case VSTRIMRIGHT: - case VSTRIMRIGHTMAX: - break; - default: - abort(); - } + status = job->ps[job->nprocs - 1].status; + retval = WEXITSTATUS(status); + if (!WIFEXITED(status)) { +#if JOBS + retval = WSTOPSIG(status); + if (!WIFSTOPPED(status)) +#endif + { + /* XXX: limits number of signals */ + retval = WTERMSIG(status); +#if JOBS + if (retval == SIGINT) + job->sigint = 1; #endif - - if (varlen >= 0) { - /* - * Terminate the string and start recording the pattern - * right after it - */ - STPUTC('\0', expdest); - patloc = expdest - (char *)stackblock(); - if (subevalvar(p, NULL, patloc, subtype, - startloc, varflags, quotes) == 0) { - int amount = expdest - ( - (char *)stackblock() + patloc - 1 - ); - STADJUST(-amount, expdest); } - /* Remove any recorded regions beyond start of variable */ - removerecordregions(startloc); - goto record; + retval += 128; } + TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n", + jobno(job), job->nprocs, status, retval)); + return retval; +} - end: - if (subtype != VSNORMAL) { /* skip to end of alternative */ - int nesting = 1; +static int +waitcmd(int argc, char **argv) +{ + struct job *job; + int retval; + struct job *jp; + + EXSIGON; + + nextopt(nullstr); + retval = 0; + + argv = argptr; + if (!*argv) { + /* wait for all jobs */ for (;;) { - c = *p++; - if (c == CTLESC) - p++; - else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { - if (varlen >= 0) - argbackq = argbackq->next; - } else if (c == CTLVAR) { - if ((*p++ & VSTYPE) != VSNORMAL) - nesting++; - } else if (c == CTLENDVAR) { - if (--nesting == 0) + jp = curjob; + while (1) { + if (!jp) { + /* no running procs */ + goto out; + } + if (jp->state == JOBRUNNING) break; + jp->waited = 1; + jp = jp->prev_job; } + dowait(DOWAIT_BLOCK, 0); } } - return p; + + retval = 127; + do { + if (**argv != '%') { + pid_t pid = number(*argv); + job = curjob; + goto start; + do { + if (job->ps[job->nprocs - 1].pid == pid) + break; + job = job->prev_job; + start: + if (!job) + goto repeat; + } while (1); + } else + job = getjob(*argv, 0); + /* loop until process terminated or stopped */ + while (job->state == JOBRUNNING) + dowait(DOWAIT_BLOCK, 0); + job->waited = 1; + retval = getstatus(job); + repeat: + ; + } while (*++argv); + + out: + return retval; } -/* - * Break the argument string into pieces based upon IFS and add the - * strings to the argument list. The regions of the string to be - * searched for IFS characters have been stored by recordregion. - */ -static void -ifsbreakup(char *string, struct arglist *arglist) +static struct job * +growjobtab(void) { - struct ifsregion *ifsp; - struct strlist *sp; - char *start; - char *p; - char *q; - const char *ifs, *realifs; - int ifsspc; - int nulonly; + size_t len; + ptrdiff_t offset; + struct job *jp, *jq; - start = string; - if (ifslastp != NULL) { - ifsspc = 0; - nulonly = 0; - realifs = ifsset() ? ifsval() : defifs; - ifsp = &ifsfirst; - do { - p = string + ifsp->begoff; - nulonly = ifsp->nulonly; - ifs = nulonly ? nullstr : realifs; - ifsspc = 0; - while (p < string + ifsp->endoff) { - q = p; - if (*p == CTLESC) - p++; - if (!strchr(ifs, *p)) { - p++; - continue; - } - if (!nulonly) - ifsspc = (strchr(defifs, *p) != NULL); - /* Ignore IFS whitespace at start */ - if (q == start && ifsspc) { - p++; - start = p; - continue; - } - *q = '\0'; - sp = stalloc(sizeof(*sp)); - sp->text = start; - *arglist->lastp = sp; - arglist->lastp = &sp->next; - p++; - if (!nulonly) { - for (;;) { - if (p >= string + ifsp->endoff) { - break; - } - q = p; - if (*p == CTLESC) - p++; - if (strchr(ifs, *p) == NULL ) { - p = q; - break; - } else if (strchr(defifs, *p) == NULL) { - if (ifsspc) { - p++; - ifsspc = 0; - } else { - p = q; - break; - } - } else - p++; - } - } - start = p; - } /* while */ - ifsp = ifsp->next; - } while (ifsp != NULL); - if (nulonly) - goto add; - } + len = njobs * sizeof(*jp); + jq = jobtab; + jp = ckrealloc(jq, len + 4 * sizeof(*jp)); - if (!*start) - return; + offset = (char *)jp - (char *)jq; + if (offset) { + /* Relocate pointers */ + size_t l = len; - add: - sp = stalloc(sizeof(*sp)); - sp->text = start; - *arglist->lastp = sp; - arglist->lastp = &sp->next; + jq = (struct job *)((char *)jq + l); + while (l) { + l -= sizeof(*jp); + jq--; +#define joff(p) ((struct job *)((char *)(p) + l)) +#define jmove(p) (p) = (void *)((char *)(p) + offset) + if (joff(jp)->ps == &jq->ps0) + jmove(joff(jp)->ps); + if (joff(jp)->prev_job) + jmove(joff(jp)->prev_job); + } + if (curjob) + jmove(curjob); +#undef joff +#undef jmove + } + + njobs += 4; + jobtab = jp; + jp = (struct job *)((char *)jp + len); + jq = jp + 3; + do { + jq->used = 0; + } while (--jq >= jp); + return jp; } -static void -ifsfree(void) +/* + * Return a new job structure. + * Called with interrupts off. + */ +static struct job * +makejob(union node *node, int nprocs) { - struct ifsregion *p; + int i; + struct job *jp; - INT_OFF; - p = ifsfirst.next; - do { - struct ifsregion *ifsp; - ifsp = p->next; - free(p); - p = ifsp; - } while (p); - ifslastp = NULL; - ifsfirst.next = NULL; - INT_ON; + for (i = njobs, jp = jobtab; ; jp++) { + if (--i < 0) { + jp = growjobtab(); + break; + } + if (jp->used == 0) + break; + if (jp->state != JOBDONE || !jp->waited) + continue; +#if JOBS + if (jobctl) + continue; +#endif + freejob(jp); + break; + } + memset(jp, 0, sizeof(*jp)); +#if JOBS + if (jobctl) + jp->jobctl = 1; +#endif + jp->prev_job = curjob; + curjob = jp; + jp->used = 1; + jp->ps = &jp->ps0; + if (nprocs > 1) { + jp->ps = ckmalloc(nprocs * sizeof(struct procstat)); + } + TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs, + jobno(jp))); + return jp; } +#if JOBS /* - * Add a file name to the list. + * Return a string identifying a command (to be printed by the + * jobs command). */ +static char *cmdnextc; + static void -addfname(const char *name) +cmdputs(const char *s) { - struct strlist *sp; + const char *p, *str; + char c, cc[2] = " "; + char *nextc; + int subtype = 0; + int quoted = 0; + static const char vstype[VSTYPE + 1][4] = { + "", "}", "-", "+", "?", "=", + "%", "%%", "#", "##" + }; - sp = stalloc(sizeof(*sp)); - sp->text = ststrdup(name); - *exparg.lastp = sp; - exparg.lastp = &sp->next; + nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc); + p = s; + while ((c = *p++) != 0) { + str = 0; + switch (c) { + case CTLESC: + c = *p++; + break; + case CTLVAR: + subtype = *p++; + if ((subtype & VSTYPE) == VSLENGTH) + str = "${#"; + else + str = "${"; + if (!(subtype & VSQUOTE) == !(quoted & 1)) + goto dostr; + quoted ^= 1; + c = '"'; + break; + case CTLENDVAR: + str = "\"}" + !(quoted & 1); + quoted >>= 1; + subtype = 0; + goto dostr; + case CTLBACKQ: + str = "$(...)"; + goto dostr; + case CTLBACKQ+CTLQUOTE: + str = "\"$(...)\""; + goto dostr; +#if ENABLE_ASH_MATH_SUPPORT + case CTLARI: + str = "$(("; + goto dostr; + case CTLENDARI: + str = "))"; + goto dostr; +#endif + case CTLQUOTEMARK: + quoted ^= 1; + c = '"'; + break; + case '=': + if (subtype == 0) + break; + if ((subtype & VSTYPE) != VSNORMAL) + quoted <<= 1; + str = vstype[subtype & VSTYPE]; + if (subtype & VSNUL) + c = ':'; + else + goto checkstr; + break; + case '\'': + case '\\': + case '"': + case '$': + /* These can only happen inside quotes */ + cc[0] = c; + str = cc; + c = '\\'; + break; + default: + break; + } + USTPUTC(c, nextc); + checkstr: + if (!str) + continue; + dostr: + while ((c = *str++)) { + USTPUTC(c, nextc); + } + } + if (quoted & 1) { + USTPUTC('"', nextc); + } + *nextc = 0; + cmdnextc = nextc; } -static char *expdir; +/* cmdtxt() and cmdlist() call each other */ +static void cmdtxt(union node *n); -/* - * Do metacharacter (i.e. *, ?, [...]) expansion. - */ static void -expmeta(char *enddir, char *name) +cmdlist(union node *np, int sep) { - char *p; - const char *cp; - char *start; - char *endname; - int metaflag; - struct stat statb; - DIR *dirp; - struct dirent *dp; - int atend; - int matchdot; - - metaflag = 0; - start = name; - for (p = name; *p; p++) { - if (*p == '*' || *p == '?') - metaflag = 1; - else if (*p == '[') { - char *q = p + 1; - if (*q == '!') - q++; - for (;;) { - if (*q == '\\') - q++; - if (*q == '/' || *q == '\0') - break; - if (*++q == ']') { - metaflag = 1; - break; - } - } - } else if (*p == '\\') - p++; - else if (*p == '/') { - if (metaflag) - goto out; - start = p + 1; - } - } - out: - if (metaflag == 0) { /* we've reached the end of the file name */ - if (enddir != expdir) - metaflag++; - p = name; - do { - if (*p == '\\') - p++; - *enddir++ = *p; - } while (*p++); - if (metaflag == 0 || lstat(expdir, &statb) >= 0) - addfname(expdir); - return; - } - endname = p; - if (name < start) { - p = name; - do { - if (*p == '\\') - p++; - *enddir++ = *p++; - } while (p < start); - } - if (enddir == expdir) { - cp = "."; - } else if (enddir == expdir + 1 && *expdir == '/') { - cp = "/"; - } else { - cp = expdir; - enddir[-1] = '\0'; - } - dirp = opendir(cp); - if (dirp == NULL) - return; - if (enddir != expdir) - enddir[-1] = '/'; - if (*endname == 0) { - atend = 1; - } else { - atend = 0; - *endname++ = '\0'; - } - matchdot = 0; - p = start; - if (*p == '\\') - p++; - if (*p == '.') - matchdot++; - while (! intpending && (dp = readdir(dirp)) != NULL) { - if (dp->d_name[0] == '.' && ! matchdot) - continue; - if (pmatch(start, dp->d_name)) { - if (atend) { - strcpy(enddir, dp->d_name); - addfname(expdir); - } else { - for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) - continue; - p[-1] = '/'; - expmeta(p, endname); - } - } + for (; np; np = np->narg.next) { + if (!sep) + cmdputs(spcstr); + cmdtxt(np); + if (sep && np->narg.next) + cmdputs(spcstr); } - closedir(dirp); - if (! atend) - endname[-1] = '/'; } -static struct strlist * -msort(struct strlist *list, int len) +static void +cmdtxt(union node *n) { - struct strlist *p, *q = NULL; - struct strlist **lpp; - int half; - int n; + union node *np; + struct nodelist *lp; + const char *p; + char s[2]; - if (len <= 1) - return list; - half = len >> 1; - p = list; - for (n = half; --n >= 0; ) { - q = p; - p = p->next; - } - q->next = NULL; /* terminate first half of list */ - q = msort(list, half); /* sort first half of list */ - p = msort(p, len - half); /* sort second half */ - lpp = &list; - for (;;) { -#if ENABLE_LOCALE_SUPPORT - if (strcoll(p->text, q->text) < 0) -#else - if (strcmp(p->text, q->text) < 0) + if (!n) + return; + switch (n->type) { + default: +#if DEBUG + abort(); #endif - { - *lpp = p; - lpp = &p->next; - p = *lpp; - if (p == NULL) { - *lpp = q; - break; - } - } else { - *lpp = q; - lpp = &q->next; - q = *lpp; - if (q == NULL) { - *lpp = p; + case NPIPE: + lp = n->npipe.cmdlist; + for (;;) { + cmdtxt(lp->n); + lp = lp->next; + if (!lp) break; - } + cmdputs(" | "); } - } - return list; -} - -/* - * Sort the results of file name expansion. It calculates the number of - * strings to sort and then calls msort (short for merge sort) to do the - * work. - */ -static struct strlist * -expsort(struct strlist *str) -{ - int len; - struct strlist *sp; - - len = 0; - for (sp = str; sp; sp = sp->next) - len++; - return msort(str, len); -} - -static void -expandmeta(struct strlist *str, int flag) -{ - static const char metachars[] = { - '*', '?', '[', 0 - }; - /* TODO - EXP_REDIR */ - - while (str) { - struct strlist **savelastp; - struct strlist *sp; - char *p; - - if (fflag) - goto nometa; - if (!strpbrk(str->text, metachars)) - goto nometa; - savelastp = exparg.lastp; - - INT_OFF; - p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP); - { - int i = strlen(str->text); - expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ + break; + case NSEMI: + p = "; "; + goto binop; + case NAND: + p = " && "; + goto binop; + case NOR: + p = " || "; + binop: + cmdtxt(n->nbinary.ch1); + cmdputs(p); + n = n->nbinary.ch2; + goto donode; + case NREDIR: + case NBACKGND: + n = n->nredir.n; + goto donode; + case NNOT: + cmdputs("!"); + n = n->nnot.com; + donode: + cmdtxt(n); + break; + case NIF: + cmdputs("if "); + cmdtxt(n->nif.test); + cmdputs("; then "); + n = n->nif.ifpart; + if (n->nif.elsepart) { + cmdtxt(n); + cmdputs("; else "); + n = n->nif.elsepart; + } + p = "; fi"; + goto dotail; + case NSUBSHELL: + cmdputs("("); + n = n->nredir.n; + p = ")"; + goto dotail; + case NWHILE: + p = "while "; + goto until; + case NUNTIL: + p = "until "; + until: + cmdputs(p); + cmdtxt(n->nbinary.ch1); + n = n->nbinary.ch2; + p = "; done"; + dodo: + cmdputs("; do "); + dotail: + cmdtxt(n); + goto dotail2; + case NFOR: + cmdputs("for "); + cmdputs(n->nfor.var); + cmdputs(" in "); + cmdlist(n->nfor.args, 1); + n = n->nfor.body; + p = "; done"; + goto dodo; + case NDEFUN: + cmdputs(n->narg.text); + p = "() { ... }"; + goto dotail2; + case NCMD: + cmdlist(n->ncmd.args, 1); + cmdlist(n->ncmd.redirect, 0); + break; + case NARG: + p = n->narg.text; + dotail2: + cmdputs(p); + break; + case NHERE: + case NXHERE: + p = "<<..."; + goto dotail2; + case NCASE: + cmdputs("case "); + cmdputs(n->ncase.expr->narg.text); + cmdputs(" in "); + for (np = n->ncase.cases; np; np = np->nclist.next) { + cmdtxt(np->nclist.pattern); + cmdputs(") "); + cmdtxt(np->nclist.body); + cmdputs(";; "); } - - expmeta(expdir, p); - free(expdir); - if (p != str->text) - free(p); - INT_ON; - if (exparg.lastp == savelastp) { - /* - * no matches - */ - nometa: - *exparg.lastp = str; - rmescapes(str->text); - exparg.lastp = &str->next; - } else { - *exparg.lastp = NULL; - *savelastp = sp = expsort(*savelastp); - while (sp->next != NULL) - sp = sp->next; - exparg.lastp = &sp->next; + p = "esac"; + goto dotail2; + case NTO: + p = ">"; + goto redir; + case NCLOBBER: + p = ">|"; + goto redir; + case NAPPEND: + p = ">>"; + goto redir; + case NTOFD: + p = ">&"; + goto redir; + case NFROM: + p = "<"; + goto redir; + case NFROMFD: + p = "<&"; + goto redir; + case NFROMTO: + p = "<>"; + redir: + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + cmdputs(p); + if (n->type == NTOFD || n->type == NFROMFD) { + s[0] = n->ndup.dupfd + '0'; + p = s; + goto dotail2; } - str = str->next; - } -} - -/* - * Perform variable substitution and command substitution on an argument, - * placing the resulting list of arguments in arglist. If EXP_FULL is true, - * perform splitting and file name expansion. When arglist is NULL, perform - * here document expansion. - */ -static void -expandarg(union node *arg, struct arglist *arglist, int flag) -{ - struct strlist *sp; - char *p; - - argbackq = arg->narg.backquote; - STARTSTACKSTR(expdest); - ifsfirst.next = NULL; - ifslastp = NULL; - argstr(arg->narg.text, flag); - p = _STPUTC('\0', expdest); - expdest = p - 1; - if (arglist == NULL) { - return; /* here document expanded */ - } - p = grabstackstr(p); - exparg.lastp = &exparg.list; - /* - * TODO - EXP_REDIR - */ - if (flag & EXP_FULL) { - ifsbreakup(p, &exparg); - *exparg.lastp = NULL; - exparg.lastp = &exparg.list; - expandmeta(exparg.list, flag); - } else { - if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ - rmescapes(p); - sp = stalloc(sizeof(*sp)); - sp->text = p; - *exparg.lastp = sp; - exparg.lastp = &sp->next; - } - if (ifsfirst.next) - ifsfree(); - *exparg.lastp = NULL; - if (exparg.list) { - *arglist->lastp = exparg.list; - arglist->lastp = exparg.lastp; + n = n->nfile.fname; + goto donode; } } -/* - * Expand shell variables and backquotes inside a here document. - */ -static void -expandhere(union node *arg, int fd) -{ - herefd = fd; - expandarg(arg, (struct arglist *)NULL, 0); - full_write(fd, stackblock(), expdest - (char *)stackblock()); -} - -/* - * Returns true if the pattern matches the string. - */ -static int -patmatch(char *pattern, const char *string) -{ - return pmatch(preglob(pattern, 0, 0), string); -} - -/* - * See if a pattern matches in a case statement. - */ -static int -casematch(union node *pattern, char *val) -{ - struct stackmark smark; - int result; - - setstackmark(&smark); - argbackq = pattern->narg.backquote; - STARTSTACKSTR(expdest); - ifslastp = NULL; - argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); - STACKSTRNUL(expdest); - result = patmatch(stackblock(), val); - popstackmark(&smark); - return result; -} - - -/* ============ find_command */ - -static int is_safe_applet(char *name) +static char * +commandtext(union node *n) { - /* It isn't a bug to have non-existent applet here... */ - /* ...just a waste of space... */ - static const char safe_applets[][8] = { - "[" - USE_AWK (, "awk" ) - USE_CAT (, "cat" ) - USE_CHMOD (, "chmod" ) - USE_CHOWN (, "chown" ) - USE_CP (, "cp" ) - USE_CUT (, "cut" ) - USE_DD (, "dd" ) - USE_ECHO (, "echo" ) - USE_FIND (, "find" ) - USE_HEXDUMP(, "hexdump") - USE_LN (, "ln" ) - USE_LS (, "ls" ) - USE_MKDIR (, "mkdir" ) - USE_RM (, "rm" ) - USE_SORT (, "sort" ) - USE_TEST (, "test" ) - USE_TOUCH (, "touch" ) - USE_XARGS (, "xargs" ) - }; - int n = sizeof(safe_applets) / sizeof(safe_applets[0]); - int i; - for (i = 0; i < n; i++) - if (strcmp(safe_applets[i], name) == 0) - return 1; + char *name; - return 0; + STARTSTACKSTR(cmdnextc); + cmdtxt(n); + name = stackblock(); + TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n", + name, cmdnextc, cmdnextc)); + return ckstrdup(name); } - -struct builtincmd { - const char *name; - int (*builtin)(int, char **); - /* unsigned flags; */ -}; -#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1) -#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2) -#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4) - -struct cmdentry { - int cmdtype; - union param { - int index; - const struct builtincmd *cmd; - struct funcnode *func; - } u; -}; -/* values of cmdtype */ -#define CMDUNKNOWN -1 /* no entry in table for command */ -#define CMDNORMAL 0 /* command is an executable program */ -#define CMDFUNCTION 1 /* command is a shell function */ -#define CMDBUILTIN 2 /* command is a shell builtin */ - -/* action to find_command() */ -#define DO_ERR 0x01 /* prints errors */ -#define DO_ABS 0x02 /* checks absolute paths */ -#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ -#define DO_ALTPATH 0x08 /* using alternate path */ -#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ - -static void find_command(char *, struct cmdentry *, int, const char *); - - -/* ============ Hashing commands */ +#endif /* JOBS */ /* - * When commands are first encountered, they are entered in a hash table. - * This ensures that a full path search will not have to be done for them - * on each invocation. + * Fork off a subshell. If we are doing job control, give the subshell its + * own process group. Jp is a job structure that the job is to be added to. + * N is the command that will be evaluated by the child. Both jp and n may + * be NULL. The mode parameter can be one of the following: + * FORK_FG - Fork off a foreground process. + * FORK_BG - Fork off a background process. + * FORK_NOJOB - Like FORK_FG, but don't give the process its own + * process group even if job control is on. * - * We should investigate converting to a linear search, even though that - * would make the command name "hash" a misnomer. + * When job control is turned off, background processes have their standard + * input redirected to /dev/null (except for the second and later processes + * in a pipeline). + * + * Called with interrupts off. + */ +/* + * Clear traps on a fork. */ - -#define CMDTABLESIZE 31 /* should be prime */ -#define ARB 1 /* actual size determined at run time */ - -struct tblentry { - struct tblentry *next; /* next entry in hash chain */ - union param param; /* definition of builtin function */ - short cmdtype; /* index identifying command */ - char rehash; /* if set, cd done since entry created */ - char cmdname[ARB]; /* name of command */ -}; - -static struct tblentry *cmdtable[CMDTABLESIZE]; -static int builtinloc = -1; /* index in path of %builtin, or -1 */ - static void -tryexec(char *cmd, char **argv, char **envp) +clear_traps(void) { - int repeated = 0; - struct BB_applet *a; - int argc = 0; - char **c; + char **tp; - if (strchr(cmd, '/') == NULL - && (a = find_applet_by_name(cmd)) != NULL - && is_safe_applet(cmd) - ) { - c = argv; - while (*c != NULL) { - c++; argc++; + for (tp = trap; tp < &trap[NSIG]; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INT_OFF; + free(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INT_ON; } - applet_name = cmd; - exit(a->main(argc, argv)); - } -#if ENABLE_FEATURE_SH_STANDALONE_SHELL - if (find_applet_by_name(cmd) != NULL) { - /* re-exec ourselves with the new arguments */ - execve(CONFIG_BUSYBOX_EXEC_PATH, argv, envp); - /* If they called chroot or otherwise made the binary no longer - * executable, fall through */ } -#endif +} +/* lives far away from here, needed for forkchild */ +static void closescript(void); +static void +forkchild(struct job *jp, union node *n, int mode) +{ + int oldlvl; - repeat: -#ifdef SYSV - do { - execve(cmd, argv, envp); - } while (errno == EINTR); -#else - execve(cmd, argv, envp); -#endif - if (repeated++) { - free(argv); - } else if (errno == ENOEXEC) { - char **ap; - char **new; + TRACE(("Child shell %d\n", getpid())); + oldlvl = shlvl; + shlvl++; - for (ap = argv; *ap; ap++) - ; - ap = new = ckmalloc((ap - argv + 2) * sizeof(char *)); - ap[1] = cmd; - *ap = cmd = (char *)DEFAULT_SHELL; - ap += 2; - argv++; - while ((*ap++ = *argv++)) - ; - argv = new; - goto repeat; + closescript(); + clear_traps(); +#if JOBS + /* do job control only in root shell */ + jobctl = 0; + if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) { + pid_t pgrp; + + if (jp->nprocs == 0) + pgrp = getpid(); + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the parent also */ + (void)setpgid(0, pgrp); + if (mode == FORK_FG) + xtcsetpgrp(ttyfd, pgrp); + setsignal(SIGTSTP); + setsignal(SIGTTOU); + } else +#endif + if (mode == FORK_BG) { + ignoresig(SIGINT); + ignoresig(SIGQUIT); + if (jp->nprocs == 0) { + close(0); + if (open(bb_dev_null, O_RDONLY) != 0) + ash_msg_and_raise_error("Can't open %s", bb_dev_null); + } + } + if (!oldlvl && iflag) { + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); } + for (jp = curjob; jp; jp = jp->prev_job) + freejob(jp); + jobless = 0; } -/* - * Exec a program. Never returns. If you change this routine, you may - * have to change the find_command routine as well. - */ -#define environment() listvars(VEXPORT, VUNSET, 0) -static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; static void -shellexec(char **argv, const char *path, int idx) +forkparent(struct job *jp, union node *n, int mode, pid_t pid) { - char *cmdname; - int e; - char **envp; - int exerrno; + TRACE(("In parent shell: child = %d\n", pid)); + if (!jp) { + while (jobless && dowait(DOWAIT_NORMAL, 0) > 0); + jobless++; + return; + } +#if JOBS + if (mode != FORK_NOJOB && jp->jobctl) { + int pgrp; - clearredir(1); - envp = environment(); - if (strchr(argv[0], '/') || is_safe_applet(argv[0]) -#if ENABLE_FEATURE_SH_STANDALONE_SHELL - || find_applet_by_name(argv[0]) + if (jp->nprocs == 0) + pgrp = pid; + else + pgrp = jp->ps[0].pid; + /* This can fail because we are doing it in the child also */ + setpgid(pid, pgrp); + } #endif - ) { - tryexec(argv[0], argv, envp); - e = errno; - } else { - e = ENOENT; - while ((cmdname = padvance(&path, argv[0])) != NULL) { - if (--idx < 0 && pathopt == NULL) { - tryexec(cmdname, argv, envp); - if (errno != ENOENT && errno != ENOTDIR) - e = errno; - } - stunalloc(cmdname); - } + if (mode == FORK_BG) { + backgndpid = pid; /* set $! */ + set_curjob(jp, CUR_RUNNING); } - - /* Map to POSIX errors */ - switch (e) { - case EACCES: - exerrno = 126; - break; - case ENOENT: - exerrno = 127; - break; - default: - exerrno = 2; - break; + if (jp) { + struct procstat *ps = &jp->ps[jp->nprocs++]; + ps->pid = pid; + ps->status = -1; + ps->cmd = nullstr; +#if JOBS + if (jobctl && n) + ps->cmd = commandtext(n); +#endif } - exitstatus = exerrno; - TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", - argv[0], e, suppressint )); - ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found")); - /* NOTREACHED */ } -static void -printentry(struct tblentry *cmdp) +static int +forkshell(struct job *jp, union node *n, int mode) { - int idx; - const char *path; - char *name; + int pid; - idx = cmdp->param.index; - path = pathval(); - do { - name = padvance(&path, cmdp->cmdname); - stunalloc(name); - } while (--idx >= 0); - out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); + TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode)); + pid = fork(); + if (pid < 0) { + TRACE(("Fork failed, errno=%d", errno)); + if (jp) + freejob(jp); + ash_msg_and_raise_error("Cannot fork"); + } + if (pid == 0) + forkchild(jp, n, mode); + else + forkparent(jp, n, mode, pid); + return pid; } /* - * Clear out command entries. The argument specifies the first entry in - * PATH which has changed. + * Wait for job to finish. + * + * Under job control we have the problem that while a child process is + * running interrupts generated by the user are sent to the child but not + * to the shell. This means that an infinite loop started by an inter- + * active user may be hard to kill. With job control turned off, an + * interactive user may place an interactive program inside a loop. If + * the interactive program catches interrupts, the user doesn't want + * these interrupts to also abort the loop. The approach we take here + * is to have the shell ignore interrupt signals while waiting for a + * foreground process to terminate, and then send itself an interrupt + * signal if the child process was terminated by an interrupt signal. + * Unfortunately, some programs want to do a bit of cleanup and then + * exit on interrupt; unless these processes terminate themselves by + * sending a signal to themselves (instead of calling exit) they will + * confuse this approach. + * + * Called with interrupts off. */ -static void -clearcmdentry(int firstchange) +static int +waitforjob(struct job *jp) { - struct tblentry **tblp; - struct tblentry **pp; - struct tblentry *cmdp; + int st; - INT_OFF; - for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { - pp = tblp; - while ((cmdp = *pp) != NULL) { - if ((cmdp->cmdtype == CMDNORMAL && - cmdp->param.index >= firstchange) - || (cmdp->cmdtype == CMDBUILTIN && - builtinloc >= firstchange) - ) { - *pp = cmdp->next; - free(cmdp); - } else { - pp = &cmdp->next; - } - } + TRACE(("waitforjob(%%%d) called\n", jobno(jp))); + while (jp->state == JOBRUNNING) { + dowait(DOWAIT_BLOCK, jp); + } + st = getstatus(jp); +#if JOBS + if (jp->jobctl) { + xtcsetpgrp(ttyfd, rootpid); + /* + * This is truly gross. + * If we're doing job control, then we did a TIOCSPGRP which + * caused us (the shell) to no longer be in the controlling + * session -- so we wouldn't have seen any ^C/SIGINT. So, we + * intuit from the subprocess exit status whether a SIGINT + * occurred, and if so interrupt ourselves. Yuck. - mycroft + */ + if (jp->sigint) + raise(SIGINT); } - INT_ON; + if (jp->state == JOBDONE) +#endif + freejob(jp); + return st; } /* - * Locate a command in the command hash table. If "add" is nonzero, - * add the command to the table if it is not already present. The - * variable "lastcmdentry" is set to point to the address of the link - * pointing to the entry, so that delete_cmd_entry can delete the - * entry. - * - * Interrupts must be off if called with add != 0. + * return 1 if there are stopped jobs, otherwise 0 */ -static struct tblentry **lastcmdentry; - -static struct tblentry * -cmdlookup(const char *name, int add) +static int +stoppedjobs(void) { - unsigned int hashval; - const char *p; - struct tblentry *cmdp; - struct tblentry **pp; + struct job *jp; + int retval; - p = name; - hashval = (unsigned char)*p << 4; - while (*p) - hashval += (unsigned char)*p++; - hashval &= 0x7FFF; - pp = &cmdtable[hashval % CMDTABLESIZE]; - for (cmdp = *pp; cmdp; cmdp = cmdp->next) { - if (strcmp(cmdp->cmdname, name) == 0) - break; - pp = &cmdp->next; - } - if (add && cmdp == NULL) { - cmdp = *pp = ckmalloc(sizeof(struct tblentry) - ARB - + strlen(name) + 1); - cmdp->next = NULL; - cmdp->cmdtype = CMDUNKNOWN; - strcpy(cmdp->cmdname, name); + retval = 0; + if (job_warning) + goto out; + jp = curjob; + if (jp && jp->state == JOBSTOPPED) { + out2str("You have stopped jobs.\n"); + job_warning = 2; + retval++; } - lastcmdentry = pp; - return cmdp; + out: + return retval; } + +/* ============ Routines to expand arguments to commands + * + * We have to deal with backquotes, shell variables, and file metacharacters. + */ + /* - * Delete the command entry returned on the last lookup. + * expandarg flags */ -static void -delete_cmd_entry(void) -{ - struct tblentry *cmdp; +#define EXP_FULL 0x1 /* perform word splitting & file globbing */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ +#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ +#define EXP_WORD 0x80 /* expand word in parameter expansion */ +#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ +/* + * _rmescape() flags + */ +#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ +#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ +#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */ +#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ +#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ - INT_OFF; - cmdp = *lastcmdentry; - *lastcmdentry = cmdp->next; - if (cmdp->cmdtype == CMDFUNCTION) - freefunc(cmdp->param.func); - free(cmdp); - INT_ON; -} +/* + * Structure specifying which parts of the string should be searched + * for IFS characters. + */ +struct ifsregion { + struct ifsregion *next; /* next region in list */ + int begoff; /* offset of start of region */ + int endoff; /* offset of end of region */ + int nulonly; /* search for nul bytes only */ +}; + +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; + +/* output of current string */ +static char *expdest; +/* list of back quote expressions */ +static struct nodelist *argbackq; +/* first struct in list of ifs regions */ +static struct ifsregion ifsfirst; +/* last struct in list */ +static struct ifsregion *ifslastp; +/* holds expanded arg list */ +static struct arglist exparg; /* - * Add a new command entry, replacing any existing command entry for - * the same name - except special builtins. + * Our own itoa(). */ -static void -addcmdentry(char *name, struct cmdentry *entry) +static int +cvtnum(arith_t num) { - struct tblentry *cmdp; + int len; - cmdp = cmdlookup(name, 1); - if (cmdp->cmdtype == CMDFUNCTION) { - freefunc(cmdp->param.func); + expdest = makestrspace(32, expdest); +#if ENABLE_ASH_MATH_SUPPORT_64 + len = fmtstr(expdest, 32, "%lld", (long long) num); +#else + len = fmtstr(expdest, 32, "%ld", num); +#endif + STADJUST(len, expdest); + return len; +} + +static size_t +esclen(const char *start, const char *p) +{ + size_t esc = 0; + + while (p > start && *--p == CTLESC) { + esc++; } - cmdp->cmdtype = entry->cmdtype; - cmdp->param = entry->u; - cmdp->rehash = 0; + return esc; } -static int -hashcmd(int argc, char **argv) +/* + * Remove any CTLESC characters from a string. + */ +static char * +_rmescapes(char *str, int flag) { - struct tblentry **pp; - struct tblentry *cmdp; - int c; - struct cmdentry entry; - char *name; + char *p, *q, *r; + static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 }; + unsigned inquotes; + int notescaped; + int globbing; - while ((c = nextopt("r")) != '\0') { - clearcmdentry(0); - return 0; + p = strpbrk(str, qchars); + if (!p) { + return str; } - if (*argptr == NULL) { - for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { - for (cmdp = *pp; cmdp; cmdp = cmdp->next) { - if (cmdp->cmdtype == CMDNORMAL) - printentry(cmdp); + q = p; + r = str; + if (flag & RMESCAPE_ALLOC) { + size_t len = p - str; + size_t fulllen = len + strlen(p) + 1; + + if (flag & RMESCAPE_GROW) { + r = makestrspace(fulllen, expdest); + } else if (flag & RMESCAPE_HEAP) { + r = ckmalloc(fulllen); + } else { + r = stalloc(fulllen); + } + q = r; + if (len > 0) { + q = memcpy(q, str, len) + len; + } + } + inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; + globbing = flag & RMESCAPE_GLOB; + notescaped = globbing; + while (*p) { + if (*p == CTLQUOTEMARK) { + inquotes = ~inquotes; + p++; + notescaped = globbing; + continue; + } + if (*p == '\\') { + /* naked back slash */ + notescaped = 0; + goto copy; + } + if (*p == CTLESC) { + p++; + if (notescaped && inquotes && *p != '/') { + *q++ = '\\'; } } - return 0; + notescaped = globbing; + copy: + *q++ = *p++; } - c = 0; - while ((name = *argptr) != NULL) { - cmdp = cmdlookup(name, 0); - if (cmdp != NULL - && (cmdp->cmdtype == CMDNORMAL - || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))) - delete_cmd_entry(); - find_command(name, &entry, DO_ERR, pathval()); - if (entry.cmdtype == CMDUNKNOWN) - c = 1; - argptr++; + *q = '\0'; + if (flag & RMESCAPE_GROW) { + expdest = r; + STADJUST(q - r + 1, expdest); + } + return r; +} +#define rmescapes(p) _rmescapes((p), 0) + +#define pmatch(a, b) !fnmatch((a), (b), 0) + +/* + * Prepare a pattern for a expmeta (internal glob(3)) call. + * + * Returns an stalloced string. + */ +static char * +preglob(const char *pattern, int quoted, int flag) +{ + flag |= RMESCAPE_GLOB; + if (quoted) { + flag |= RMESCAPE_QUOTED; } - return c; + return _rmescapes((char *)pattern, flag); } /* - * Called when a cd is done. Marks all commands so the next time they - * are executed they will be rehashed. + * Put a string on the stack. */ static void -hashcd(void) +memtodest(const char *p, size_t len, int syntax, int quotes) { - struct tblentry **pp; - struct tblentry *cmdp; + char *q = expdest; - for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { - for (cmdp = *pp; cmdp; cmdp = cmdp->next) { - if (cmdp->cmdtype == CMDNORMAL || ( - cmdp->cmdtype == CMDBUILTIN && - !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) && - builtinloc > 0 - )) - cmdp->rehash = 1; - } + q = makestrspace(len * 2, q); + + while (len--) { + int c = SC2INT(*p++); + if (!c) + continue; + if (quotes && (SIT(c, syntax) == CCTL || SIT(c, syntax) == CBACK)) + USTPUTC(CTLESC, q); + USTPUTC(c, q); } + + expdest = q; +} + +static void +strtodest(const char *p, int syntax, int quotes) +{ + memtodest(p, strlen(p), syntax, quotes); } /* - * Fix command hash table when PATH changed. - * Called before PATH is changed. The argument is the new value of PATH; - * pathval() still returns the old value at this point. - * Called with interrupts off. + * Record the fact that we have to scan this region of the + * string for IFS characters. */ static void -changepath(const char *newval) +recordregion(int start, int end, int nulonly) { - const char *old, *new; - int idx; - int firstchange; - int idx_bltin; + struct ifsregion *ifsp; - old = pathval(); - new = newval; - firstchange = 9999; /* assume no change */ - idx = 0; - idx_bltin = -1; - for (;;) { - if (*old != *new) { - firstchange = idx; - if ((*old == '\0' && *new == ':') - || (*old == ':' && *new == '\0')) - firstchange++; - old = new; /* ignore subsequent differences */ - } - if (*new == '\0') - break; - if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) - idx_bltin = idx; - if (*new == ':') { - idx++; - } - new++, old++; + if (ifslastp == NULL) { + ifsp = &ifsfirst; + } else { + INT_OFF; + ifsp = ckmalloc(sizeof(*ifsp)); + ifsp->next = NULL; + ifslastp->next = ifsp; + INT_ON; } - if (builtinloc < 0 && idx_bltin >= 0) - builtinloc = idx_bltin; /* zap builtins */ - if (builtinloc >= 0 && idx_bltin < 0) - firstchange = 0; - clearcmdentry(firstchange); - builtinloc = idx_bltin; + ifslastp = ifsp; + ifslastp->begoff = start; + ifslastp->endoff = end; + ifslastp->nulonly = nulonly; } -#define TEOF 0 -#define TNL 1 -#define TREDIR 2 -#define TWORD 3 -#define TSEMI 4 -#define TBACKGND 5 -#define TAND 6 -#define TOR 7 -#define TPIPE 8 -#define TLP 9 -#define TRP 10 -#define TENDCASE 11 -#define TENDBQUOTE 12 -#define TNOT 13 -#define TCASE 14 -#define TDO 15 -#define TDONE 16 -#define TELIF 17 -#define TELSE 18 -#define TESAC 19 -#define TFI 20 -#define TFOR 21 -#define TIF 22 -#define TIN 23 -#define TTHEN 24 -#define TUNTIL 25 -#define TWHILE 26 -#define TBEGIN 27 -#define TEND 28 - -/* first char is indicating which tokens mark the end of a list */ -static const char *const tokname_array[] = { - "\1end of file", - "\0newline", - "\0redirection", - "\0word", - "\0;", - "\0&", - "\0&&", - "\0||", - "\0|", - "\0(", - "\1)", - "\1;;", - "\1`", -#define KWDOFFSET 13 - /* the following are keywords */ - "\0!", - "\0case", - "\1do", - "\1done", - "\1elif", - "\1else", - "\1esac", - "\1fi", - "\0for", - "\0if", - "\0in", - "\1then", - "\0until", - "\0while", - "\0{", - "\1}", -}; - -static const char * -tokname(int tok) +static void +removerecordregions(int endoff) { - static char buf[16]; + if (ifslastp == NULL) + return; - if (tok >= TSEMI) - buf[0] = '"'; - sprintf(buf + (tok >= TSEMI), "%s%c", - tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); - return buf; -} + if (ifsfirst.endoff > endoff) { + while (ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INT_OFF; + ifsp = ifsfirst.next->next; + free(ifsfirst.next); + ifsfirst.next = ifsp; + INT_ON; + } + if (ifsfirst.begoff > endoff) + ifslastp = NULL; + else { + ifslastp = &ifsfirst; + ifsfirst.endoff = endoff; + } + return; + } -/* Wrapper around strcmp for qsort/bsearch/... */ -static int -pstrcmp(const void *a, const void *b) -{ - return strcmp((const char *) a, (*(const char *const *) b) + 1); + ifslastp = &ifsfirst; + while (ifslastp->next && ifslastp->next->begoff < endoff) + ifslastp=ifslastp->next; + while (ifslastp->next != NULL) { + struct ifsregion *ifsp; + INT_OFF; + ifsp = ifslastp->next->next; + free(ifslastp->next); + ifslastp->next = ifsp; + INT_ON; + } + if (ifslastp->endoff > endoff) + ifslastp->endoff = endoff; } -static const char *const * -findkwd(const char *s) +static char * +exptilde(char *startp, char *p, int flag) { - return bsearch(s, tokname_array + KWDOFFSET, - (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, - sizeof(const char *), pstrcmp); + char c; + char *name; + struct passwd *pw; + const char *home; + int quotes = flag & (EXP_FULL | EXP_CASE); + int startloc; + + name = p + 1; + + while ((c = *++p) != '\0') { + switch (c) { + case CTLESC: + return startp; + case CTLQUOTEMARK: + return startp; + case ':': + if (flag & EXP_VARTILDE) + goto done; + break; + case '/': + case CTLENDVAR: + goto done; + } + } + done: + *p = '\0'; + if (*name == '\0') { + home = lookupvar(homestr); + } else { + pw = getpwnam(name); + if (pw == NULL) + goto lose; + home = pw->pw_dir; + } + if (!home || !*home) + goto lose; + *p = c; + startloc = expdest - (char *)stackblock(); + strtodest(home, SQSYNTAX, quotes); + recordregion(startloc, expdest - (char *)stackblock(), 0); + return p; + lose: + *p = c; + return startp; } /* - * Locate and print what a word is... + * Execute a command inside back quotes. If it's a builtin command, we + * want to save its output in a block obtained from malloc. Otherwise + * we fork off a subprocess and get the output of the command via a pipe. + * Should be called with interrupts off. */ -#if ENABLE_ASH_CMDCMD -static int -describe_command(char *command, int describe_command_verbose) -#else -#define describe_command_verbose 1 -static int -describe_command(char *command) -#endif -{ - struct cmdentry entry; - struct tblentry *cmdp; -#if ENABLE_ASH_ALIAS - const struct alias *ap; -#endif - const char *path = pathval(); +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + char *buf; /* buffer */ + int nleft; /* number of chars in buffer */ + struct job *jp; /* job structure for command */ +}; - if (describe_command_verbose) { - out1str(command); - } +/* These forward decls are needed to use "eval" code for backticks handling: */ +static int back_exitstatus; /* exit status of backquoted command */ +#define EV_EXIT 01 /* exit after evaluating tree */ +static void evaltree(union node *, int); - /* First look at the keywords */ - if (findkwd(command)) { - out1str(describe_command_verbose ? " is a shell keyword" : command); - goto out; - } +static void +evalbackcmd(union node *n, struct backcmd *result) +{ + int saveherefd; -#if ENABLE_ASH_ALIAS - /* Then look at the aliases */ - ap = lookupalias(command, 0); - if (ap != NULL) { - if (describe_command_verbose) { - out1fmt(" is an alias for %s", ap->val); - } else { - out1str("alias "); - printalias(ap); - return 0; - } + result->fd = -1; + result->buf = NULL; + result->nleft = 0; + result->jp = NULL; + if (n == NULL) { goto out; } -#endif - /* Then check if it is a tracked alias */ - cmdp = cmdlookup(command, 0); - if (cmdp != NULL) { - entry.cmdtype = cmdp->cmdtype; - entry.u = cmdp->param; - } else { - /* Finally use brute force */ - find_command(command, &entry, DO_ABS, path); - } - - switch (entry.cmdtype) { - case CMDNORMAL: { - int j = entry.u.index; - char *p; - if (j == -1) { - p = command; - } else { - do { - p = padvance(&path, command); - stunalloc(p); - } while (--j >= 0); - } - if (describe_command_verbose) { - out1fmt(" is%s %s", - (cmdp ? " a tracked alias for" : nullstr), p - ); - } else { - out1str(p); - } - break; - } - case CMDFUNCTION: - if (describe_command_verbose) { - out1str(" is a shell function"); - } else { - out1str(command); - } - break; + saveherefd = herefd; + herefd = -1; - case CMDBUILTIN: - if (describe_command_verbose) { - out1fmt(" is a %sshell builtin", - IS_BUILTIN_SPECIAL(entry.u.cmd) ? - "special " : nullstr - ); - } else { - out1str(command); - } - break; + { + int pip[2]; + struct job *jp; - default: - if (describe_command_verbose) { - out1str(": not found\n"); + if (pipe(pip) < 0) + ash_msg_and_raise_error("Pipe call failed"); + jp = makejob(n, 1); + if (forkshell(jp, n, FORK_NOJOB) == 0) { + FORCE_INT_ON; + close(pip[0]); + if (pip[1] != 1) { + close(1); + copyfd(pip[1], 1); + close(pip[1]); + } + eflag = 0; + evaltree(n, EV_EXIT); /* actually evaltreenr... */ + /* NOTREACHED */ } - return 127; + close(pip[1]); + result->fd = pip[0]; + result->jp = jp; } + herefd = saveherefd; out: - outstr("\n", stdout); - return 0; + TRACE(("evalbackcmd done: fd=%d buf=0x%x nleft=%d jp=0x%x\n", + result->fd, result->buf, result->nleft, result->jp)); } -static int -typecmd(int argc, char **argv) +/* + * Expand stuff in backwards quotes. + */ +static void +expbackq(union node *cmd, int quoted, int quotes) { + struct backcmd in; int i; - int err = 0; + char buf[128]; + char *p; + char *dest; + int startloc; + int syntax = quoted? DQSYNTAX : BASESYNTAX; + struct stackmark smark; - for (i = 1; i < argc; i++) { -#if ENABLE_ASH_CMDCMD - err |= describe_command(argv[i], 1); -#else - err |= describe_command(argv[i]); -#endif + INT_OFF; + setstackmark(&smark); + dest = expdest; + startloc = dest - (char *)stackblock(); + grabstackstr(dest); + evalbackcmd(cmd, &in); + popstackmark(&smark); + + p = in.buf; + i = in.nleft; + if (i == 0) + goto read; + for (;;) { + memtodest(p, i, syntax, quotes); + read: + if (in.fd < 0) + break; + i = safe_read(in.fd, buf, sizeof(buf)); + TRACE(("expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; } - return err; + + if (in.buf) + free(in.buf); + if (in.fd >= 0) { + close(in.fd); + back_exitstatus = waitforjob(in.jp); + } + INT_ON; + + /* Eat all trailing newlines */ + dest = expdest; + for (; dest > (char *)stackblock() && dest[-1] == '\n';) + STUNPUTC(dest); + expdest = dest; + + if (quoted == 0) + recordregion(startloc, dest - (char *)stackblock(), 0); + TRACE(("evalbackq: size=%d: \"%.*s\"\n", + (dest - (char *)stackblock()) - startloc, + (dest - (char *)stackblock()) - startloc, + stackblock() + startloc)); } -#if ENABLE_ASH_CMDCMD -static int -commandcmd(int argc, char **argv) +#if ENABLE_ASH_MATH_SUPPORT +/* + * Expand arithmetic expression. Backup to start of expression, + * evaluate, place result in (backed up) result, adjust string position. + */ +static void +expari(int quotes) { - int c; - enum { - VERIFY_BRIEF = 1, - VERIFY_VERBOSE = 2, - } verify = 0; + char *p, *start; + int begoff; + int flag; + int len; - while ((c = nextopt("pvV")) != '\0') - if (c == 'V') - verify |= VERIFY_VERBOSE; - else if (c == 'v') - verify |= VERIFY_BRIEF; -#if DEBUG - else if (c != 'p') - abort(); -#endif - if (verify) - return describe_command(*argptr, verify - VERIFY_BRIEF); + /* ifsfree(); */ - return 0; -} + /* + * This routine is slightly over-complicated for + * efficiency. Next we scan backwards looking for the + * start of arithmetic. + */ + start = stackblock(); + p = expdest - 1; + *p = '\0'; + p--; + do { + int esc; + + while (*p != CTLARI) { + p--; +#if DEBUG + if (p < start) { + ash_msg_and_raise_error("missing CTLARI (shouldn't happen)"); + } #endif + } + esc = esclen(start, p); + if (!(esc % 2)) { + break; + } -/* ============ eval.c */ + p -= esc + 1; + } while (1); -static int funcblocksize; /* size of structures in function */ -static int funcstringsize; /* size of strings in node */ -static void *funcblock; /* block to allocate function from */ -static char *funcstring; /* block to allocate strings from */ + begoff = p - start; -/* flags in argument to evaltree */ -#define EV_EXIT 01 /* exit after evaluating tree */ -#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ -#define EV_BACKCMD 04 /* command executing within back quotes */ + removerecordregions(begoff); -static const short nodesize[26] = { - SHELL_ALIGN(sizeof(struct ncmd)), - SHELL_ALIGN(sizeof(struct npipe)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nredir)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nif)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nbinary)), - SHELL_ALIGN(sizeof(struct nfor)), - SHELL_ALIGN(sizeof(struct ncase)), - SHELL_ALIGN(sizeof(struct nclist)), - SHELL_ALIGN(sizeof(struct narg)), - SHELL_ALIGN(sizeof(struct narg)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct nfile)), - SHELL_ALIGN(sizeof(struct ndup)), - SHELL_ALIGN(sizeof(struct ndup)), - SHELL_ALIGN(sizeof(struct nhere)), - SHELL_ALIGN(sizeof(struct nhere)), - SHELL_ALIGN(sizeof(struct nnot)), -}; + flag = p[1]; -static void calcsize(union node *n); + expdest = p; -static void -sizenodelist(struct nodelist *lp) -{ - while (lp) { - funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); - calcsize(lp->n); - lp = lp->next; - } + if (quotes) + rmescapes(p + 2); + + len = cvtnum(dash_arith(p + 2)); + + if (flag != '"') + recordregion(begoff, begoff + len, 0); } +#endif + +/* argstr needs it */ +static char *evalvar(char *p, int flag); +/* + * Perform variable and command substitution. If EXP_FULL is set, output CTLESC + * characters to allow for further processing. Otherwise treat + * $@ like $* since no splitting will be performed. + */ static void -calcsize(union node *n) +argstr(char *p, int flag) { - if (n == NULL) - return; - funcblocksize += nodesize[n->type]; - switch (n->type) { - case NCMD: - calcsize(n->ncmd.redirect); - calcsize(n->ncmd.args); - calcsize(n->ncmd.assign); - break; - case NPIPE: - sizenodelist(n->npipe.cmdlist); - break; - case NREDIR: - case NBACKGND: - case NSUBSHELL: - calcsize(n->nredir.redirect); - calcsize(n->nredir.n); - break; - case NAND: - case NOR: - case NSEMI: - case NWHILE: - case NUNTIL: - calcsize(n->nbinary.ch2); - calcsize(n->nbinary.ch1); - break; - case NIF: - calcsize(n->nif.elsepart); - calcsize(n->nif.ifpart); - calcsize(n->nif.test); - break; - case NFOR: - funcstringsize += strlen(n->nfor.var) + 1; - calcsize(n->nfor.body); - calcsize(n->nfor.args); - break; - case NCASE: - calcsize(n->ncase.cases); - calcsize(n->ncase.expr); - break; - case NCLIST: - calcsize(n->nclist.body); - calcsize(n->nclist.pattern); - calcsize(n->nclist.next); - break; - case NDEFUN: - case NARG: - sizenodelist(n->narg.backquote); - funcstringsize += strlen(n->narg.text) + 1; - calcsize(n->narg.next); - break; - case NTO: - case NCLOBBER: - case NFROM: - case NFROMTO: - case NAPPEND: - calcsize(n->nfile.fname); - calcsize(n->nfile.next); - break; - case NTOFD: - case NFROMFD: - calcsize(n->ndup.vname); - calcsize(n->ndup.next); - break; - case NHERE: - case NXHERE: - calcsize(n->nhere.doc); - calcsize(n->nhere.next); - break; - case NNOT: - calcsize(n->nnot.com); - break; + static const char spclchars[] = { + '=', + ':', + CTLQUOTEMARK, + CTLENDVAR, + CTLESC, + CTLVAR, + CTLBACKQ, + CTLBACKQ | CTLQUOTE, +#if ENABLE_ASH_MATH_SUPPORT + CTLENDARI, +#endif + 0 }; + const char *reject = spclchars; + int c; + int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ + int breakall = flag & EXP_WORD; + int inquotes; + size_t length; + int startloc; + + if (!(flag & EXP_VARTILDE)) { + reject += 2; + } else if (flag & EXP_VARTILDE2) { + reject++; + } + inquotes = 0; + length = 0; + if (flag & EXP_TILDE) { + char *q; + + flag &= ~EXP_TILDE; + tilde: + q = p; + if (*q == CTLESC && (flag & EXP_QWORD)) + q++; + if (*q == '~') + p = exptilde(p, q, flag); + } + start: + startloc = expdest - (char *)stackblock(); + for (;;) { + length += strcspn(p + length, reject); + c = p[length]; + if (c && (!(c & 0x80) +#if ENABLE_ASH_MATH_SUPPORT + || c == CTLENDARI +#endif + )) { + /* c == '=' || c == ':' || c == CTLENDARI */ + length++; + } + if (length > 0) { + int newloc; + expdest = stack_nputstr(p, length, expdest); + newloc = expdest - (char *)stackblock(); + if (breakall && !inquotes && newloc > startloc) { + recordregion(startloc, newloc, 0); + } + startloc = newloc; + } + p += length + 1; + length = 0; + + switch (c) { + case '\0': + goto breakloop; + case '=': + if (flag & EXP_VARTILDE2) { + p--; + continue; + } + flag |= EXP_VARTILDE2; + reject++; + /* fall through */ + case ':': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + if (*--p == '~') { + goto tilde; + } + continue; + } + + switch (c) { + case CTLENDVAR: /* ??? */ + goto breakloop; + case CTLQUOTEMARK: + /* "$@" syntax adherence hack */ + if ( + !inquotes && + !memcmp(p, dolatstr, 4) && + (p[4] == CTLQUOTEMARK || ( + p[4] == CTLENDVAR && + p[5] == CTLQUOTEMARK + )) + ) { + p = evalvar(p + 1, flag) + 1; + goto start; + } + inquotes = !inquotes; + addquote: + if (quotes) { + p--; + length++; + startloc++; + } + break; + case CTLESC: + startloc++; + length++; + goto addquote; + case CTLVAR: + p = evalvar(p, flag); + goto start; + case CTLBACKQ: + c = 0; + case CTLBACKQ|CTLQUOTE: + expbackq(argbackq->n, c, quotes); + argbackq = argbackq->next; + goto start; +#if ENABLE_ASH_MATH_SUPPORT + case CTLENDARI: + p--; + expari(quotes); + goto start; +#endif + } + } + breakloop: + ; } static char * -nodeckstrdup(char *s) +scanleft(char *startp, char *rmesc, char *rmescend, char *str, int quotes, + int zero) { - char *rtn = funcstring; + char *loc; + char *loc2; + char c; - strcpy(funcstring, s); - funcstring += strlen(s) + 1; - return rtn; + loc = startp; + loc2 = rmesc; + do { + int match; + const char *s = loc2; + c = *loc2; + if (zero) { + *loc2 = '\0'; + s = rmesc; + } + match = pmatch(str, s); + *loc2 = c; + if (match) + return loc; + if (quotes && *loc == CTLESC) + loc++; + loc++; + loc2++; + } while (c); + return 0; } -static union node *copynode(union node *); +static char * +scanright(char *startp, char *rmesc, char *rmescend, char *str, int quotes, + int zero) +{ + int esc = 0; + char *loc; + char *loc2; -static struct nodelist * -copynodelist(struct nodelist *lp) + for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) { + int match; + char c = *loc2; + const char *s = loc2; + if (zero) { + *loc2 = '\0'; + s = rmesc; + } + match = pmatch(str, s); + *loc2 = c; + if (match) + return loc; + loc--; + if (quotes) { + if (--esc < 0) { + esc = esclen(startp, loc); + } + if (esc % 2) { + esc--; + loc--; + } + } + } + return 0; +} + +static void varunset(const char *, const char *, const char *, int) ATTRIBUTE_NORETURN; +static void +varunset(const char *end, const char *var, const char *umsg, int varflags) { - struct nodelist *start; - struct nodelist **lpp; + const char *msg; + const char *tail; - lpp = &start; - while (lp) { - *lpp = funcblock; - funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist)); - (*lpp)->n = copynode(lp->n); - lp = lp->next; - lpp = &(*lpp)->next; + tail = nullstr; + msg = "parameter not set"; + if (umsg) { + if (*end == CTLENDVAR) { + if (varflags & VSNUL) + tail = " or null"; + } else + msg = umsg; } - *lpp = NULL; - return start; + ash_msg_and_raise_error("%.*s: %s%s", end - var - 1, var, msg, tail); } -static union node * -copynode(union node *n) +static const char * +subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes) { - union node *new; + char *startp; + char *loc; + int saveherefd = herefd; + struct nodelist *saveargbackq = argbackq; + int amount; + char *rmesc, *rmescend; + int zero; + char *(*scan)(char *, char *, char *, char *, int , int); - if (n == NULL) - return NULL; - new = funcblock; - funcblock = (char *) funcblock + nodesize[n->type]; + herefd = -1; + argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0); + STPUTC('\0', expdest); + herefd = saveherefd; + argbackq = saveargbackq; + startp = stackblock() + startloc; - switch (n->type) { - case NCMD: - new->ncmd.redirect = copynode(n->ncmd.redirect); - new->ncmd.args = copynode(n->ncmd.args); - new->ncmd.assign = copynode(n->ncmd.assign); - break; - case NPIPE: - new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); - new->npipe.backgnd = n->npipe.backgnd; - break; - case NREDIR: - case NBACKGND: - case NSUBSHELL: - new->nredir.redirect = copynode(n->nredir.redirect); - new->nredir.n = copynode(n->nredir.n); - break; - case NAND: - case NOR: - case NSEMI: - case NWHILE: - case NUNTIL: - new->nbinary.ch2 = copynode(n->nbinary.ch2); - new->nbinary.ch1 = copynode(n->nbinary.ch1); - break; - case NIF: - new->nif.elsepart = copynode(n->nif.elsepart); - new->nif.ifpart = copynode(n->nif.ifpart); - new->nif.test = copynode(n->nif.test); - break; - case NFOR: - new->nfor.var = nodeckstrdup(n->nfor.var); - new->nfor.body = copynode(n->nfor.body); - new->nfor.args = copynode(n->nfor.args); - break; - case NCASE: - new->ncase.cases = copynode(n->ncase.cases); - new->ncase.expr = copynode(n->ncase.expr); - break; - case NCLIST: - new->nclist.body = copynode(n->nclist.body); - new->nclist.pattern = copynode(n->nclist.pattern); - new->nclist.next = copynode(n->nclist.next); - break; - case NDEFUN: - case NARG: - new->narg.backquote = copynodelist(n->narg.backquote); - new->narg.text = nodeckstrdup(n->narg.text); - new->narg.next = copynode(n->narg.next); - break; - case NTO: - case NCLOBBER: - case NFROM: - case NFROMTO: - case NAPPEND: - new->nfile.fname = copynode(n->nfile.fname); - new->nfile.fd = n->nfile.fd; - new->nfile.next = copynode(n->nfile.next); - break; - case NTOFD: - case NFROMFD: - new->ndup.vname = copynode(n->ndup.vname); - new->ndup.dupfd = n->ndup.dupfd; - new->ndup.fd = n->ndup.fd; - new->ndup.next = copynode(n->ndup.next); - break; - case NHERE: - case NXHERE: - new->nhere.doc = copynode(n->nhere.doc); - new->nhere.fd = n->nhere.fd; - new->nhere.next = copynode(n->nhere.next); - break; - case NNOT: - new->nnot.com = copynode(n->nnot.com); - break; - }; - new->type = n->type; - return new; + switch (subtype) { + case VSASSIGN: + setvar(str, startp, 0); + amount = startp - expdest; + STADJUST(amount, expdest); + return startp; + + case VSQUESTION: + varunset(p, str, startp, varflags); + /* NOTREACHED */ + } + + subtype -= VSTRIMRIGHT; +#if DEBUG + if (subtype < 0 || subtype > 3) + abort(); +#endif + + rmesc = startp; + rmescend = stackblock() + strloc; + if (quotes) { + rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); + if (rmesc != startp) { + rmescend = expdest; + startp = stackblock() + startloc; + } + } + rmescend--; + str = stackblock() + strloc; + preglob(str, varflags & VSQUOTE, 0); + + /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */ + zero = subtype >> 1; + /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */ + scan = (subtype & 1) ^ zero ? scanleft : scanright; + + loc = scan(startp, rmesc, rmescend, str, quotes, zero); + if (loc) { + if (zero) { + memmove(startp, loc, str - loc); + loc = startp + (str - loc) - 1; + } + *loc = '\0'; + amount = loc - expdest; + STADJUST(amount, expdest); + } + return loc; } /* - * Make a copy of a parse tree. + * Add the value of a specialized variable to the stack string. */ -static struct funcnode * -copyfunc(union node *n) +static ssize_t +varvalue(char *name, int varflags, int flags) { - struct funcnode *f; - size_t blocksize; + int num; + char *p; + int i; + int sep = 0; + int sepq = 0; + ssize_t len = 0; + char **ap; + int syntax; + int quoted = varflags & VSQUOTE; + int subtype = varflags & VSTYPE; + int quotes = flags & (EXP_FULL | EXP_CASE); - funcblocksize = offsetof(struct funcnode, n); - funcstringsize = 0; - calcsize(n); - blocksize = funcblocksize; - f = ckmalloc(blocksize + funcstringsize); - funcblock = (char *) f + offsetof(struct funcnode, n); - funcstring = (char *) f + blocksize; - copynode(n); - f->count = 0; - return f; -} + if (quoted && (flags & EXP_FULL)) + sep = 1 << CHAR_BIT; -/* - * Define a shell function. - */ -static void -defun(char *name, union node *func) -{ - struct cmdentry entry; + syntax = quoted ? DQSYNTAX : BASESYNTAX; + switch (*name) { + case '$': + num = rootpid; + goto numvar; + case '?': + num = exitstatus; + goto numvar; + case '#': + num = shellparam.nparam; + goto numvar; + case '!': + num = backgndpid; + if (num == 0) + return -1; + numvar: + len = cvtnum(num); + break; + case '-': + p = makestrspace(NOPTS, expdest); + for (i = NOPTS - 1; i >= 0; i--) { + if (optlist[i]) { + USTPUTC(optletters(i), p); + len++; + } + } + expdest = p; + break; + case '@': + if (sep) + goto param; + /* fall through */ + case '*': + sep = ifsset() ? SC2INT(ifsval()[0]) : ' '; + if (quotes && (SIT(sep, syntax) == CCTL || SIT(sep, syntax) == CBACK)) + sepq = 1; + param: + ap = shellparam.p; + if (!ap) + return -1; + while ((p = *ap++)) { + size_t partlen; - INT_OFF; - entry.cmdtype = CMDFUNCTION; - entry.u.func = copyfunc(func); - addcmdentry(name, &entry); - INT_ON; -} + partlen = strlen(p); + len += partlen; -static int evalskip; /* set if we are skipping commands */ -/* reasons for skipping commands (see comment on breakcmd routine) */ -#define SKIPBREAK (1 << 0) -#define SKIPCONT (1 << 1) -#define SKIPFUNC (1 << 2) -#define SKIPFILE (1 << 3) -#define SKIPEVAL (1 << 4) -static int skipcount; /* number of levels to skip */ -static int funcnest; /* depth of function calls */ + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, partlen, syntax, quotes); -/* forward decl way out to parsing code - dotrap needs it */ -static int evalstring(char *s, int mask); + if (*ap && sep) { + char *q; + + len++; + if (subtype == VSPLUS || subtype == VSLENGTH) { + continue; + } + q = expdest; + if (sepq) + STPUTC(CTLESC, q); + STPUTC(sep, q); + expdest = q; + } + } + return len; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + num = atoi(name); + if (num < 0 || num > shellparam.nparam) + return -1; + p = num ? shellparam.p[num - 1] : arg0; + goto value; + default: + p = lookupvar(name); + value: + if (!p) + return -1; + + len = strlen(p); + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, len, syntax, quotes); + return len; + } + + if (subtype == VSPLUS || subtype == VSLENGTH) + STADJUST(-len, expdest); + return len; +} /* - * Called to execute a trap. Perhaps we should avoid entering new trap - * handlers while we are executing a trap handler. + * Expand a variable, and return a pointer to the next character in the + * input string. */ -static int -dotrap(void) +static char * +evalvar(char *p, int flag) { - char *p; - char *q; - int i; - int savestatus; - int skip = 0; + int subtype; + int varflags; + char *var; + int patloc; + int c; + int startloc; + ssize_t varlen; + int easy; + int quotes; + int quoted; - savestatus = exitstatus; - pendingsigs = 0; - xbarrier(); + quotes = flag & (EXP_FULL | EXP_CASE); + varflags = *p++; + subtype = varflags & VSTYPE; + quoted = varflags & VSQUOTE; + var = p; + easy = (!quoted || (*var == '@' && shellparam.nparam)); + startloc = expdest - (char *)stackblock(); + p = strchr(p, '=') + 1; - for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { - if (!*q) - continue; - *q = '\0'; + again: + varlen = varvalue(var, varflags, flag); + if (varflags & VSNUL) + varlen--; - p = trap[i + 1]; - if (!p) - continue; - skip = evalstring(p, SKIPEVAL); - exitstatus = savestatus; - if (skip) - break; + if (subtype == VSPLUS) { + varlen = -1 - varlen; + goto vsplus; } - return skip; -} + if (subtype == VSMINUS) { + vsplus: + if (varlen < 0) { + argstr( + p, flag | EXP_TILDE | + (quoted ? EXP_QWORD : EXP_WORD) + ); + goto end; + } + if (easy) + goto record; + goto end; + } -/* forward declarations - evaluation is fairly recursive business... */ -static void evalloop(union node *, int); -static void evalfor(union node *, int); -static void evalcase(union node *, int); -static void evalsubshell(union node *, int); -static void expredir(union node *); -static void evalpipe(union node *, int); -static void evalcommand(union node *, int); -static int evalbltin(const struct builtincmd *, int, char **); -static void prehash(union node *); + if (subtype == VSASSIGN || subtype == VSQUESTION) { + if (varlen < 0) { + if (subevalvar(p, var, 0, subtype, startloc, varflags, 0)) { + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; + } + goto end; + } + if (easy) + goto record; + goto end; + } + + if (varlen < 0 && uflag) + varunset(p, var, 0, 0); + + if (subtype == VSLENGTH) { + cvtnum(varlen > 0 ? varlen : 0); + goto record; + } -/* - * Evaluate a parse tree. The value is left in the global variable - * exitstatus. - */ -static void -evaltree(union node *n, int flags) -{ - int checkexit = 0; - void (*evalfn)(union node *, int); - unsigned isor; - int status; - if (n == NULL) { - TRACE(("evaltree(NULL) called\n")); - goto out; + if (subtype == VSNORMAL) { + if (!easy) + goto end; + record: + recordregion(startloc, expdest - (char *)stackblock(), quoted); + goto end; } - TRACE(("pid %d, evaltree(%p: %d, %d) called\n", - getpid(), n, n->type, flags)); - switch (n->type) { - default: + #if DEBUG - out1fmt("Node type = %d\n", n->type); - fflush(stdout); + switch (subtype) { + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: break; + default: + abort(); + } #endif - case NNOT: - evaltree(n->nnot.com, EV_TESTED); - status = !exitstatus; - goto setstatus; - case NREDIR: - expredir(n->nredir.redirect); - status = redirectsafe(n->nredir.redirect, REDIR_PUSH); - if (!status) { - evaltree(n->nredir.n, flags & EV_TESTED); - status = exitstatus; - } - popredir(0); - goto setstatus; - case NCMD: - evalfn = evalcommand; - checkexit: - if (eflag && !(flags & EV_TESTED)) - checkexit = ~0; - goto calleval; - case NFOR: - evalfn = evalfor; - goto calleval; - case NWHILE: - case NUNTIL: - evalfn = evalloop; - goto calleval; - case NSUBSHELL: - case NBACKGND: - evalfn = evalsubshell; - goto calleval; - case NPIPE: - evalfn = evalpipe; - goto checkexit; - case NCASE: - evalfn = evalcase; - goto calleval; - case NAND: - case NOR: - case NSEMI: -#if NAND + 1 != NOR -#error NAND + 1 != NOR -#endif -#if NOR + 1 != NSEMI -#error NOR + 1 != NSEMI -#endif - isor = n->type - NAND; - evaltree( - n->nbinary.ch1, - (flags | ((isor >> 1) - 1)) & EV_TESTED - ); - if (!exitstatus == isor) - break; - if (!evalskip) { - n = n->nbinary.ch2; - evaln: - evalfn = evaltree; - calleval: - evalfn(n, flags); - break; - } - break; - case NIF: - evaltree(n->nif.test, EV_TESTED); - if (evalskip) - break; - if (exitstatus == 0) { - n = n->nif.ifpart; - goto evaln; - } else if (n->nif.elsepart) { - n = n->nif.elsepart; - goto evaln; + + if (varlen >= 0) { + /* + * Terminate the string and start recording the pattern + * right after it + */ + STPUTC('\0', expdest); + patloc = expdest - (char *)stackblock(); + if (subevalvar(p, NULL, patloc, subtype, + startloc, varflags, quotes) == 0) { + int amount = expdest - ( + (char *)stackblock() + patloc - 1 + ); + STADJUST(-amount, expdest); } - goto success; - case NDEFUN: - defun(n->narg.text, n->narg.next); - success: - status = 0; - setstatus: - exitstatus = status; - break; + /* Remove any recorded regions beyond start of variable */ + removerecordregions(startloc); + goto record; } - out: - if ((checkexit & exitstatus)) - evalskip |= SKIPEVAL; - else if (pendingsigs && dotrap()) - goto exexit; - if (flags & EV_EXIT) { - exexit: - raise_exception(EXEXIT); + end: + if (subtype != VSNORMAL) { /* skip to end of alternative */ + int nesting = 1; + for (;;) { + c = *p++; + if (c == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { + if (varlen >= 0) + argbackq = argbackq->next; + } else if (c == CTLVAR) { + if ((*p++ & VSTYPE) != VSNORMAL) + nesting++; + } else if (c == CTLENDVAR) { + if (--nesting == 0) + break; + } + } } + return p; } -#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) -static -#endif -void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); - -static int loopnest; /* current loop nesting level */ - +/* + * Break the argument string into pieces based upon IFS and add the + * strings to the argument list. The regions of the string to be + * searched for IFS characters have been stored by recordregion. + */ static void -evalloop(union node *n, int flags) +ifsbreakup(char *string, struct arglist *arglist) { - int status; - - loopnest++; - status = 0; - flags &= EV_TESTED; - for (;;) { - int i; + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + const char *ifs, *realifs; + int ifsspc; + int nulonly; - evaltree(n->nbinary.ch1, EV_TESTED); - if (evalskip) { - skipping: - if (evalskip == SKIPCONT && --skipcount <= 0) { - evalskip = 0; - continue; - } - if (evalskip == SKIPBREAK && --skipcount <= 0) - evalskip = 0; - break; - } - i = exitstatus; - if (n->type != NWHILE) - i = !i; - if (i != 0) - break; - evaltree(n->nbinary.ch2, flags); - status = exitstatus; - if (evalskip) - goto skipping; + start = string; + if (ifslastp != NULL) { + ifsspc = 0; + nulonly = 0; + realifs = ifsset() ? ifsval() : defifs; + ifsp = &ifsfirst; + do { + p = string + ifsp->begoff; + nulonly = ifsp->nulonly; + ifs = nulonly ? nullstr : realifs; + ifsspc = 0; + while (p < string + ifsp->endoff) { + q = p; + if (*p == CTLESC) + p++; + if (!strchr(ifs, *p)) { + p++; + continue; + } + if (!nulonly) + ifsspc = (strchr(defifs, *p) != NULL); + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc) { + p++; + start = p; + continue; + } + *q = '\0'; + sp = stalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + if (!nulonly) { + for (;;) { + if (p >= string + ifsp->endoff) { + break; + } + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL ) { + p = q; + break; + } else if (strchr(defifs, *p) == NULL) { + if (ifsspc) { + p++; + ifsspc = 0; + } else { + p = q; + break; + } + } else + p++; + } + } + start = p; + } /* while */ + ifsp = ifsp->next; + } while (ifsp != NULL); + if (nulonly) + goto add; } - loopnest--; - exitstatus = status; -} - -static void -evalfor(union node *n, int flags) -{ - struct arglist arglist; - union node *argp; - struct strlist *sp; - struct stackmark smark; - setstackmark(&smark); - arglist.lastp = &arglist.list; - for (argp = n->nfor.args; argp; argp = argp->narg.next) { - expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD); - /* XXX */ - if (evalskip) - goto out; - } - *arglist.lastp = NULL; + if (!*start) + return; - exitstatus = 0; - loopnest++; - flags &= EV_TESTED; - for (sp = arglist.list; sp; sp = sp->next) { - setvar(n->nfor.var, sp->text, 0); - evaltree(n->nfor.body, flags); - if (evalskip) { - if (evalskip == SKIPCONT && --skipcount <= 0) { - evalskip = 0; - continue; - } - if (evalskip == SKIPBREAK && --skipcount <= 0) - evalskip = 0; - break; - } - } - loopnest--; - out: - popstackmark(&smark); + add: + sp = stalloc(sizeof(*sp)); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; } static void -evalcase(union node *n, int flags) +ifsfree(void) { - union node *cp; - union node *patp; - struct arglist arglist; - struct stackmark smark; + struct ifsregion *p; - setstackmark(&smark); - arglist.lastp = &arglist.list; - expandarg(n->ncase.expr, &arglist, EXP_TILDE); - exitstatus = 0; - for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) { - for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) { - if (casematch(patp, arglist.list->text)) { - if (evalskip == 0) { - evaltree(cp->nclist.body, flags); - } - goto out; - } - } - } - out: - popstackmark(&smark); + INT_OFF; + p = ifsfirst.next; + do { + struct ifsregion *ifsp; + ifsp = p->next; + free(p); + p = ifsp; + } while (p); + ifslastp = NULL; + ifsfirst.next = NULL; + INT_ON; } /* - * Kick off a subshell to evaluate a tree. + * Add a file name to the list. */ static void -evalsubshell(union node *n, int flags) +addfname(const char *name) { - struct job *jp; - int backgnd = (n->type == NBACKGND); - int status; + struct strlist *sp; - expredir(n->nredir.redirect); - if (!backgnd && flags & EV_EXIT && !trap[0]) - goto nofork; - INT_OFF; - jp = makejob(n, 1); - if (forkshell(jp, n, backgnd) == 0) { - INT_ON; - flags |= EV_EXIT; - if (backgnd) - flags &=~ EV_TESTED; - nofork: - redirect(n->nredir.redirect, 0); - evaltreenr(n->nredir.n, flags); - /* never returns */ - } - status = 0; - if (! backgnd) - status = waitforjob(jp); - exitstatus = status; - INT_ON; + sp = stalloc(sizeof(*sp)); + sp->text = ststrdup(name); + *exparg.lastp = sp; + exparg.lastp = &sp->next; } +static char *expdir; + /* - * Compute the names of the files in a redirection list. + * Do metacharacter (i.e. *, ?, [...]) expansion. */ -static void fixredir(union node *, const char *, int); static void -expredir(union node *n) +expmeta(char *enddir, char *name) { - union node *redir; - - for (redir = n; redir; redir = redir->nfile.next) { - struct arglist fn; + char *p; + const char *cp; + char *start; + char *endname; + int metaflag; + struct stat statb; + DIR *dirp; + struct dirent *dp; + int atend; + int matchdot; - memset(&fn, 0, sizeof(fn)); - fn.lastp = &fn.list; - switch (redir->type) { - case NFROMTO: - case NFROM: - case NTO: - case NCLOBBER: - case NAPPEND: - expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); - redir->nfile.expfname = fn.list->text; - break; - case NFROMFD: - case NTOFD: - if (redir->ndup.vname) { - expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); - if (fn.list == NULL) - ash_msg_and_raise_error("redir error"); - fixredir(redir, fn.list->text, 1); + metaflag = 0; + start = name; + for (p = name; *p; p++) { + if (*p == '*' || *p == '?') + metaflag = 1; + else if (*p == '[') { + char *q = p + 1; + if (*q == '!') + q++; + for (;;) { + if (*q == '\\') + q++; + if (*q == '/' || *q == '\0') + break; + if (*++q == ']') { + metaflag = 1; + break; + } + } + } else if (*p == '\\') + p++; + else if (*p == '/') { + if (metaflag) + goto out; + start = p + 1; + } + } + out: + if (metaflag == 0) { /* we've reached the end of the file name */ + if (enddir != expdir) + metaflag++; + p = name; + do { + if (*p == '\\') + p++; + *enddir++ = *p; + } while (*p++); + if (metaflag == 0 || lstat(expdir, &statb) >= 0) + addfname(expdir); + return; + } + endname = p; + if (name < start) { + p = name; + do { + if (*p == '\\') + p++; + *enddir++ = *p++; + } while (p < start); + } + if (enddir == expdir) { + cp = "."; + } else if (enddir == expdir + 1 && *expdir == '/') { + cp = "/"; + } else { + cp = expdir; + enddir[-1] = '\0'; + } + dirp = opendir(cp); + if (dirp == NULL) + return; + if (enddir != expdir) + enddir[-1] = '/'; + if (*endname == 0) { + atend = 1; + } else { + atend = 0; + *endname++ = '\0'; + } + matchdot = 0; + p = start; + if (*p == '\\') + p++; + if (*p == '.') + matchdot++; + while (! intpending && (dp = readdir(dirp)) != NULL) { + if (dp->d_name[0] == '.' && ! matchdot) + continue; + if (pmatch(start, dp->d_name)) { + if (atend) { + strcpy(enddir, dp->d_name); + addfname(expdir); + } else { + for (p = enddir, cp = dp->d_name; (*p++ = *cp++) != '\0';) + continue; + p[-1] = '/'; + expmeta(p, endname); } - break; } } + closedir(dirp); + if (! atend) + endname[-1] = '/'; } -/* - * Evaluate a pipeline. All the processes in the pipeline are children - * of the process creating the pipeline. (This differs from some versions - * of the shell, which make the last process in a pipeline the parent - * of all the rest.) - */ -static void -evalpipe(union node *n, int flags) +static struct strlist * +msort(struct strlist *list, int len) { - struct job *jp; - struct nodelist *lp; - int pipelen; - int prevfd; - int pip[2]; + struct strlist *p, *q = NULL; + struct strlist **lpp; + int half; + int n; - TRACE(("evalpipe(0x%lx) called\n", (long)n)); - pipelen = 0; - for (lp = n->npipe.cmdlist; lp; lp = lp->next) - pipelen++; - flags |= EV_EXIT; - INT_OFF; - jp = makejob(n, pipelen); - prevfd = -1; - for (lp = n->npipe.cmdlist; lp; lp = lp->next) { - prehash(lp->n); - pip[1] = -1; - if (lp->next) { - if (pipe(pip) < 0) { - close(prevfd); - ash_msg_and_raise_error("Pipe call failed"); - } - } - if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { - INT_ON; - if (pip[1] >= 0) { - close(pip[0]); - } - if (prevfd > 0) { - dup2(prevfd, 0); - close(prevfd); + if (len <= 1) + return list; + half = len >> 1; + p = list; + for (n = half; --n >= 0; ) { + q = p; + p = p->next; + } + q->next = NULL; /* terminate first half of list */ + q = msort(list, half); /* sort first half of list */ + p = msort(p, len - half); /* sort second half */ + lpp = &list; + for (;;) { +#if ENABLE_LOCALE_SUPPORT + if (strcoll(p->text, q->text) < 0) +#else + if (strcmp(p->text, q->text) < 0) +#endif + { + *lpp = p; + lpp = &p->next; + p = *lpp; + if (p == NULL) { + *lpp = q; + break; } - if (pip[1] > 1) { - dup2(pip[1], 1); - close(pip[1]); + } else { + *lpp = q; + lpp = &q->next; + q = *lpp; + if (q == NULL) { + *lpp = p; + break; } - evaltreenr(lp->n, flags); - /* never returns */ } - if (prevfd >= 0) - close(prevfd); - prevfd = pip[0]; - close(pip[1]); - } - if (n->npipe.backgnd == 0) { - exitstatus = waitforjob(jp); - TRACE(("evalpipe: job done exit status %d\n", exitstatus)); } - INT_ON; + return list; } -static struct localvar *localvars; - /* - * Called after a function returns. - * Interrupts must be off. + * Sort the results of file name expansion. It calculates the number of + * strings to sort and then calls msort (short for merge sort) to do the + * work. */ -static void -poplocalvars(void) +static struct strlist * +expsort(struct strlist *str) { - struct localvar *lvp; - struct var *vp; + int len; + struct strlist *sp; - while ((lvp = localvars) != NULL) { - localvars = lvp->next; - vp = lvp->vp; - TRACE(("poplocalvar %s", vp ? vp->text : "-")); - if (vp == NULL) { /* $- saved */ - memcpy(optlist, lvp->text, sizeof(optlist)); - free((char*)lvp->text); - optschanged(); - } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { - unsetvar(vp->text); - } else { - if (vp->func) - (*vp->func)(strchrnul(lvp->text, '=') + 1); - if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) - free((char*)vp->text); - vp->flags = lvp->flags; - vp->text = lvp->text; - } - free(lvp); - } + len = 0; + for (sp = str; sp; sp = sp->next) + len++; + return msort(str, len); } -static int -evalfun(struct funcnode *func, int argc, char **argv, int flags) +static void +expandmeta(struct strlist *str, int flag) { - volatile struct shparam saveparam; - struct localvar *volatile savelocalvars; - struct jmploc *volatile savehandler; - struct jmploc jmploc; - int e; + static const char metachars[] = { + '*', '?', '[', 0 + }; + /* TODO - EXP_REDIR */ - saveparam = shellparam; - savelocalvars = localvars; - e = setjmp(jmploc.loc); - if (e) { - goto funcdone; + while (str) { + struct strlist **savelastp; + struct strlist *sp; + char *p; + + if (fflag) + goto nometa; + if (!strpbrk(str->text, metachars)) + goto nometa; + savelastp = exparg.lastp; + + INT_OFF; + p = preglob(str->text, 0, RMESCAPE_ALLOC | RMESCAPE_HEAP); + { + int i = strlen(str->text); + expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ + } + + expmeta(expdir, p); + free(expdir); + if (p != str->text) + free(p); + INT_ON; + if (exparg.lastp == savelastp) { + /* + * no matches + */ + nometa: + *exparg.lastp = str; + rmescapes(str->text); + exparg.lastp = &str->next; + } else { + *exparg.lastp = NULL; + *savelastp = sp = expsort(*savelastp); + while (sp->next != NULL) + sp = sp->next; + exparg.lastp = &sp->next; + } + str = str->next; } - INT_OFF; - savehandler = exception_handler; - exception_handler = &jmploc; - localvars = NULL; - shellparam.malloc = 0; - func->count++; - funcnest++; - INT_ON; - shellparam.nparam = argc - 1; - shellparam.p = argv + 1; -#if ENABLE_ASH_GETOPTS - shellparam.optind = 1; - shellparam.optoff = -1; -#endif - evaltree(&func->n, flags & EV_TESTED); -funcdone: - INT_OFF; - funcnest--; - freefunc(func); - poplocalvars(); - localvars = savelocalvars; - freeparam(&shellparam); - shellparam = saveparam; - exception_handler = savehandler; - INT_ON; - evalskip &= ~SKIPFUNC; - return e; } -#if ENABLE_ASH_CMDCMD -static char ** -parse_command_args(char **argv, const char **path) +/* + * Perform variable substitution and command substitution on an argument, + * placing the resulting list of arguments in arglist. If EXP_FULL is true, + * perform splitting and file name expansion. When arglist is NULL, perform + * here document expansion. + */ +static void +expandarg(union node *arg, struct arglist *arglist, int flag) { - char *cp, c; + struct strlist *sp; + char *p; - for (;;) { - cp = *++argv; - if (!cp) - return 0; - if (*cp++ != '-') - break; - c = *cp++; - if (!c) - break; - if (c == '-' && !*cp) { - argv++; - break; - } - do { - switch (c) { - case 'p': - *path = defpath; - break; - default: - /* run 'typecmd' for other options */ - return 0; - } - c = *cp++; - } while (c); + argbackq = arg->narg.backquote; + STARTSTACKSTR(expdest); + ifsfirst.next = NULL; + ifslastp = NULL; + argstr(arg->narg.text, flag); + p = _STPUTC('\0', expdest); + expdest = p - 1; + if (arglist == NULL) { + return; /* here document expanded */ + } + p = grabstackstr(p); + exparg.lastp = &exparg.list; + /* + * TODO - EXP_REDIR + */ + if (flag & EXP_FULL) { + ifsbreakup(p, &exparg); + *exparg.lastp = NULL; + exparg.lastp = &exparg.list; + expandmeta(exparg.list, flag); + } else { + if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ + rmescapes(p); + sp = stalloc(sizeof(*sp)); + sp->text = p; + *exparg.lastp = sp; + exparg.lastp = &sp->next; + } + if (ifsfirst.next) + ifsfree(); + *exparg.lastp = NULL; + if (exparg.list) { + *arglist->lastp = exparg.list; + arglist->lastp = exparg.lastp; } - return argv; } -#endif /* - * Make a variable a local variable. When a variable is made local, it's - * value and flags are saved in a localvar structure. The saved values - * will be restored when the shell function returns. We handle the name - * "-" as a special case. + * Expand shell variables and backquotes inside a here document. */ static void -mklocal(char *name) +expandhere(union node *arg, int fd) { - struct localvar *lvp; - struct var **vpp; - struct var *vp; - - INT_OFF; - lvp = ckmalloc(sizeof(struct localvar)); - if (LONE_DASH(name)) { - char *p; - p = ckmalloc(sizeof(optlist)); - lvp->text = memcpy(p, optlist, sizeof(optlist)); - vp = NULL; - } else { - char *eq; + herefd = fd; + expandarg(arg, (struct arglist *)NULL, 0); + full_write(fd, stackblock(), expdest - (char *)stackblock()); +} - vpp = hashvar(name); - vp = *findvar(vpp, name); - eq = strchr(name, '='); - if (vp == NULL) { - if (eq) - setvareq(name, VSTRFIXED); - else - setvar(name, NULL, VSTRFIXED); - vp = *vpp; /* the new variable */ - lvp->flags = VUNSET; - } else { - lvp->text = vp->text; - lvp->flags = vp->flags; - vp->flags |= VSTRFIXED|VTEXTFIXED; - if (eq) - setvareq(name, 0); - } - } - lvp->vp = vp; - lvp->next = localvars; - localvars = lvp; - INT_ON; +/* + * Returns true if the pattern matches the string. + */ +static int +patmatch(char *pattern, const char *string) +{ + return pmatch(preglob(pattern, 0, 0), string); } /* - * The "local" command. + * See if a pattern matches in a case statement. */ static int -localcmd(int argc, char **argv) +casematch(union node *pattern, char *val) { - char *name; + struct stackmark smark; + int result; - argv = argptr; - while ((name = *argv++) != NULL) { - mklocal(name); - } - return 0; + setstackmark(&smark); + argbackq = pattern->narg.backquote; + STARTSTACKSTR(expdest); + ifslastp = NULL; + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); + STACKSTRNUL(expdest); + result = patmatch(stackblock(), val); + popstackmark(&smark); + return result; } -/* Forward declarations for builtintab[] */ -#if JOBS -static int fg_bgcmd(int, char **); -#endif -static int breakcmd(int, char **); -#if ENABLE_ASH_CMDCMD -static int commandcmd(int, char **); -#endif -static int dotcmd(int, char **); -static int evalcmd(int, char **); -#if ENABLE_ASH_BUILTIN_ECHO -static int echocmd(int, char **); -#endif -#if ENABLE_ASH_BUILTIN_TEST -static int testcmd(int, char **); -#endif -static int execcmd(int, char **); -static int exitcmd(int, char **); -static int exportcmd(int, char **); -static int falsecmd(int, char **); -#if ENABLE_ASH_GETOPTS -static int getoptscmd(int, char **); -#endif -static int hashcmd(int, char **); -#if !ENABLE_FEATURE_SH_EXTRA_QUIET -static int helpcmd(int argc, char **argv); -#endif -#if JOBS -static int jobscmd(int, char **); -#endif -#if ENABLE_ASH_MATH_SUPPORT -static int letcmd(int, char **); -#endif -static int pwdcmd(int, char **); -static int readcmd(int, char **); -static int returncmd(int, char **); -static int setcmd(int, char **); -static int shiftcmd(int, char **); -static int timescmd(int, char **); -static int trapcmd(int, char **); -static int truecmd(int, char **); -static int typecmd(int, char **); -static int umaskcmd(int, char **); -static int unsetcmd(int, char **); -static int waitcmd(int, char **); -static int ulimitcmd(int, char **); -#if JOBS -static int killcmd(int, char **); -#endif -#define BUILTIN_NOSPEC "0" -#define BUILTIN_SPECIAL "1" -#define BUILTIN_REGULAR "2" -#define BUILTIN_SPEC_REG "3" -#define BUILTIN_ASSIGN "4" -#define BUILTIN_SPEC_ASSG "5" -#define BUILTIN_REG_ASSG "6" -#define BUILTIN_SPEC_REG_ASSG "7" +/* ============ find_command */ -/* make sure to keep these in proper order since it is searched via bsearch() */ -static const struct builtincmd builtintab[] = { - { BUILTIN_SPEC_REG ".", dotcmd }, - { BUILTIN_SPEC_REG ":", truecmd }, -#if ENABLE_ASH_BUILTIN_TEST - { BUILTIN_REGULAR "[", testcmd }, - { BUILTIN_REGULAR "[[", testcmd }, -#endif -#if ENABLE_ASH_ALIAS - { BUILTIN_REG_ASSG "alias", aliascmd }, -#endif -#if JOBS - { BUILTIN_REGULAR "bg", fg_bgcmd }, -#endif - { BUILTIN_SPEC_REG "break", breakcmd }, - { BUILTIN_REGULAR "cd", cdcmd }, - { BUILTIN_NOSPEC "chdir", cdcmd }, -#if ENABLE_ASH_CMDCMD - { BUILTIN_REGULAR "command", commandcmd }, -#endif - { BUILTIN_SPEC_REG "continue", breakcmd }, -#if ENABLE_ASH_BUILTIN_ECHO - { BUILTIN_REGULAR "echo", echocmd }, -#endif - { BUILTIN_SPEC_REG "eval", evalcmd }, - { BUILTIN_SPEC_REG "exec", execcmd }, - { BUILTIN_SPEC_REG "exit", exitcmd }, - { BUILTIN_SPEC_REG_ASSG "export", exportcmd }, - { BUILTIN_REGULAR "false", falsecmd }, -#if JOBS - { BUILTIN_REGULAR "fg", fg_bgcmd }, -#endif -#if ENABLE_ASH_GETOPTS - { BUILTIN_REGULAR "getopts", getoptscmd }, -#endif - { BUILTIN_NOSPEC "hash", hashcmd }, -#if !ENABLE_FEATURE_SH_EXTRA_QUIET - { BUILTIN_NOSPEC "help", helpcmd }, -#endif -#if JOBS - { BUILTIN_REGULAR "jobs", jobscmd }, - { BUILTIN_REGULAR "kill", killcmd }, -#endif -#if ENABLE_ASH_MATH_SUPPORT - { BUILTIN_NOSPEC "let", letcmd }, -#endif - { BUILTIN_ASSIGN "local", localcmd }, - { BUILTIN_NOSPEC "pwd", pwdcmd }, - { BUILTIN_REGULAR "read", readcmd }, - { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd }, - { BUILTIN_SPEC_REG "return", returncmd }, - { BUILTIN_SPEC_REG "set", setcmd }, - { BUILTIN_SPEC_REG "shift", shiftcmd }, - { BUILTIN_SPEC_REG "source", dotcmd }, -#if ENABLE_ASH_BUILTIN_TEST - { BUILTIN_REGULAR "test", testcmd }, -#endif - { BUILTIN_SPEC_REG "times", timescmd }, - { BUILTIN_SPEC_REG "trap", trapcmd }, - { BUILTIN_REGULAR "true", truecmd }, - { BUILTIN_NOSPEC "type", typecmd }, - { BUILTIN_NOSPEC "ulimit", ulimitcmd }, - { BUILTIN_REGULAR "umask", umaskcmd }, -#if ENABLE_ASH_ALIAS - { BUILTIN_REGULAR "unalias", unaliascmd }, -#endif - { BUILTIN_SPEC_REG "unset", unsetcmd }, - { BUILTIN_REGULAR "wait", waitcmd }, +static int is_safe_applet(char *name) +{ + /* It isn't a bug to have non-existent applet here... */ + /* ...just a waste of space... */ + static const char safe_applets[][8] = { + "[" + USE_AWK (, "awk" ) + USE_CAT (, "cat" ) + USE_CHMOD (, "chmod" ) + USE_CHOWN (, "chown" ) + USE_CP (, "cp" ) + USE_CUT (, "cut" ) + USE_DD (, "dd" ) + USE_ECHO (, "echo" ) + USE_FIND (, "find" ) + USE_HEXDUMP(, "hexdump") + USE_LN (, "ln" ) + USE_LS (, "ls" ) + USE_MKDIR (, "mkdir" ) + USE_RM (, "rm" ) + USE_SORT (, "sort" ) + USE_TEST (, "test" ) + USE_TOUCH (, "touch" ) + USE_XARGS (, "xargs" ) + }; + int n = sizeof(safe_applets) / sizeof(safe_applets[0]); + int i; + for (i = 0; i < n; i++) + if (strcmp(safe_applets[i], name) == 0) + return 1; + + return 0; +} + +struct builtincmd { + const char *name; + int (*builtin)(int, char **); + /* unsigned flags; */ }; +#define IS_BUILTIN_SPECIAL(b) ((b)->name[0] & 1) +#define IS_BUILTIN_REGULAR(b) ((b)->name[0] & 2) +#define IS_BUILTIN_ASSIGN(b) ((b)->name[0] & 4) -#define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0])) +struct cmdentry { + int cmdtype; + union param { + int index; + const struct builtincmd *cmd; + struct funcnode *func; + } u; +}; +/* values of cmdtype */ +#define CMDUNKNOWN -1 /* no entry in table for command */ +#define CMDNORMAL 0 /* command is an executable program */ +#define CMDFUNCTION 1 /* command is a shell function */ +#define CMDBUILTIN 2 /* command is a shell builtin */ -#define COMMANDCMD (builtintab + 5 + \ - 2 * ENABLE_ASH_BUILTIN_TEST + \ - ENABLE_ASH_ALIAS + \ - ENABLE_ASH_JOB_CONTROL) -#define EXECCMD (builtintab + 7 + \ - 2 * ENABLE_ASH_BUILTIN_TEST + \ - ENABLE_ASH_ALIAS + \ - ENABLE_ASH_JOB_CONTROL + \ - ENABLE_ASH_CMDCMD + \ - ENABLE_ASH_BUILTIN_ECHO) +/* action to find_command() */ +#define DO_ERR 0x01 /* prints errors */ +#define DO_ABS 0x02 /* checks absolute paths */ +#define DO_NOFUNC 0x04 /* don't return shell functions, for command */ +#define DO_ALTPATH 0x08 /* using alternate path */ +#define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ -/* - * Search the table of builtin commands. - */ -static struct builtincmd * -find_builtin(const char *name) -{ - struct builtincmd *bp; +static void find_command(char *, struct cmdentry *, int, const char *); - bp = bsearch( - name, builtintab, NUMBUILTINS, sizeof(builtintab[0]), - pstrcmp - ); - return bp; -} + +/* ============ Hashing commands */ /* - * Resolve a command name. If you change this routine, you may have to - * change the shellexec routine as well. + * When commands are first encountered, they are entered in a hash table. + * This ensures that a full path search will not have to be done for them + * on each invocation. + * + * We should investigate converting to a linear search, even though that + * would make the command name "hash" a misnomer. */ + +#define CMDTABLESIZE 31 /* should be prime */ +#define ARB 1 /* actual size determined at run time */ + +struct tblentry { + struct tblentry *next; /* next entry in hash chain */ + union param param; /* definition of builtin function */ + short cmdtype; /* index identifying command */ + char rehash; /* if set, cd done since entry created */ + char cmdname[ARB]; /* name of command */ +}; + +static struct tblentry *cmdtable[CMDTABLESIZE]; +static int builtinloc = -1; /* index in path of %builtin, or -1 */ + static void -find_command(char *name, struct cmdentry *entry, int act, const char *path) +tryexec(char *cmd, char **argv, char **envp) { - struct tblentry *cmdp; - int idx; - int prev; - char *fullname; - struct stat statb; - int e; - int updatetbl; - struct builtincmd *bcmd; + int repeated = 0; + struct BB_applet *a; + int argc = 0; + char **c; - /* If name contains a slash, don't use PATH or hash table */ - if (strchr(name, '/') != NULL) { - entry->u.index = -1; - if (act & DO_ABS) { - while (stat(name, &statb) < 0) { -#ifdef SYSV - if (errno == EINTR) - continue; -#endif - entry->cmdtype = CMDUNKNOWN; - return; - } + if (strchr(cmd, '/') == NULL + && (a = find_applet_by_name(cmd)) != NULL + && is_safe_applet(cmd) + ) { + c = argv; + while (*c != NULL) { + c++; argc++; } - entry->cmdtype = CMDNORMAL; - return; + applet_name = cmd; + exit(a->main(argc, argv)); } - #if ENABLE_FEATURE_SH_STANDALONE_SHELL - if (find_applet_by_name(name)) { - entry->cmdtype = CMDNORMAL; - entry->u.index = -1; - return; + if (find_applet_by_name(cmd) != NULL) { + /* re-exec ourselves with the new arguments */ + execve(CONFIG_BUSYBOX_EXEC_PATH, argv, envp); + /* If they called chroot or otherwise made the binary no longer + * executable, fall through */ } #endif - if (is_safe_applet(name)) { - entry->cmdtype = CMDNORMAL; - entry->u.index = -1; - return; - } + repeat: +#ifdef SYSV + do { + execve(cmd, argv, envp); + } while (errno == EINTR); +#else + execve(cmd, argv, envp); +#endif + if (repeated++) { + free(argv); + } else if (errno == ENOEXEC) { + char **ap; + char **new; - updatetbl = (path == pathval()); - if (!updatetbl) { - act |= DO_ALTPATH; - if (strstr(path, "%builtin") != NULL) - act |= DO_ALTBLTIN; + for (ap = argv; *ap; ap++) + ; + ap = new = ckmalloc((ap - argv + 2) * sizeof(char *)); + ap[1] = cmd; + *ap = cmd = (char *)DEFAULT_SHELL; + ap += 2; + argv++; + while ((*ap++ = *argv++)) + ; + argv = new; + goto repeat; } +} - /* If name is in the table, check answer will be ok */ - cmdp = cmdlookup(name, 0); - if (cmdp != NULL) { - int bit; +/* + * Exec a program. Never returns. If you change this routine, you may + * have to change the find_command routine as well. + */ +#define environment() listvars(VEXPORT, VUNSET, 0) +static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; +static void +shellexec(char **argv, const char *path, int idx) +{ + char *cmdname; + int e; + char **envp; + int exerrno; - switch (cmdp->cmdtype) { - default: -#if DEBUG - abort(); + clearredir(1); + envp = environment(); + if (strchr(argv[0], '/') || is_safe_applet(argv[0]) +#if ENABLE_FEATURE_SH_STANDALONE_SHELL + || find_applet_by_name(argv[0]) #endif - case CMDNORMAL: - bit = DO_ALTPATH; - break; - case CMDFUNCTION: - bit = DO_NOFUNC; - break; - case CMDBUILTIN: - bit = DO_ALTBLTIN; - break; + ) { + tryexec(argv[0], argv, envp); + e = errno; + } else { + e = ENOENT; + while ((cmdname = padvance(&path, argv[0])) != NULL) { + if (--idx < 0 && pathopt == NULL) { + tryexec(cmdname, argv, envp); + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + } + stunalloc(cmdname); } - if (act & bit) { - updatetbl = 0; - cmdp = NULL; - } else if (cmdp->rehash == 0) - /* if not invalidated by cd, we're done */ - goto success; } - /* If %builtin not in path, check for builtin next */ - bcmd = find_builtin(name); - if (bcmd && (IS_BUILTIN_REGULAR(bcmd) || ( - act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc <= 0 - ))) - goto builtin_success; + /* Map to POSIX errors */ + switch (e) { + case EACCES: + exerrno = 126; + break; + case ENOENT: + exerrno = 127; + break; + default: + exerrno = 2; + break; + } + exitstatus = exerrno; + TRACE(("shellexec failed for %s, errno %d, suppressint %d\n", + argv[0], e, suppressint )); + ash_msg_and_raise(EXEXEC, "%s: %s", argv[0], errmsg(e, "not found")); + /* NOTREACHED */ +} + +static void +printentry(struct tblentry *cmdp) +{ + int idx; + const char *path; + char *name; + + idx = cmdp->param.index; + path = pathval(); + do { + name = padvance(&path, cmdp->cmdname); + stunalloc(name); + } while (--idx >= 0); + out1fmt("%s%s\n", name, (cmdp->rehash ? "*" : nullstr)); +} - /* We have to search path. */ - prev = -1; /* where to start */ - if (cmdp && cmdp->rehash) { /* doing a rehash */ - if (cmdp->cmdtype == CMDBUILTIN) - prev = builtinloc; - else - prev = cmdp->param.index; - } +/* + * Clear out command entries. The argument specifies the first entry in + * PATH which has changed. + */ +static void +clearcmdentry(int firstchange) +{ + struct tblentry **tblp; + struct tblentry **pp; + struct tblentry *cmdp; - e = ENOENT; - idx = -1; - loop: - while ((fullname = padvance(&path, name)) != NULL) { - stunalloc(fullname); - idx++; - if (pathopt) { - if (prefix(pathopt, "builtin")) { - if (bcmd) - goto builtin_success; - continue; - } else if (!(act & DO_NOFUNC) && - prefix(pathopt, "func")) { - /* handled below */ + INT_OFF; + for (tblp = cmdtable; tblp < &cmdtable[CMDTABLESIZE]; tblp++) { + pp = tblp; + while ((cmdp = *pp) != NULL) { + if ((cmdp->cmdtype == CMDNORMAL && + cmdp->param.index >= firstchange) + || (cmdp->cmdtype == CMDBUILTIN && + builtinloc >= firstchange) + ) { + *pp = cmdp->next; + free(cmdp); } else { - /* ignore unimplemented options */ - continue; + pp = &cmdp->next; } } - /* if rehash, don't redo absolute path names */ - if (fullname[0] == '/' && idx <= prev) { - if (idx < prev) - continue; - TRACE(("searchexec \"%s\": no change\n", name)); - goto success; - } - while (stat(fullname, &statb) < 0) { -#ifdef SYSV - if (errno == EINTR) - continue; -#endif - if (errno != ENOENT && errno != ENOTDIR) - e = errno; - goto loop; - } - e = EACCES; /* if we fail, this will be the error */ - if (!S_ISREG(statb.st_mode)) - continue; - if (pathopt) { /* this is a %func directory */ - stalloc(strlen(fullname) + 1); - readcmdfile(fullname); - cmdp = cmdlookup(name, 0); - if (cmdp == NULL || cmdp->cmdtype != CMDFUNCTION) - ash_msg_and_raise_error("%s not defined in %s", name, fullname); - stunalloc(fullname); - goto success; - } - TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); - if (!updatetbl) { - entry->cmdtype = CMDNORMAL; - entry->u.index = idx; - return; - } - INT_OFF; - cmdp = cmdlookup(name, 1); - cmdp->cmdtype = CMDNORMAL; - cmdp->param.index = idx; - INT_ON; - goto success; - } - - /* We failed. If there was an entry for this command, delete it */ - if (cmdp && updatetbl) - delete_cmd_entry(); - if (act & DO_ERR) - ash_msg("%s: %s", name, errmsg(e, "not found")); - entry->cmdtype = CMDUNKNOWN; - return; - - builtin_success: - if (!updatetbl) { - entry->cmdtype = CMDBUILTIN; - entry->u.cmd = bcmd; - return; } - INT_OFF; - cmdp = cmdlookup(name, 1); - cmdp->cmdtype = CMDBUILTIN; - cmdp->param.cmd = bcmd; INT_ON; - success: - cmdp->rehash = 0; - entry->cmdtype = cmdp->cmdtype; - entry->u = cmdp->param; } /* - * Execute a simple command. + * Locate a command in the command hash table. If "add" is nonzero, + * add the command to the table if it is not already present. The + * variable "lastcmdentry" is set to point to the address of the link + * pointing to the entry, so that delete_cmd_entry can delete the + * entry. + * + * Interrupts must be off if called with add != 0. */ -static int back_exitstatus; /* exit status of backquoted command */ -static int -isassignment(const char *p) -{ - const char *q = endofname(p); - if (p == q) - return 0; - return *q == '='; -} -static int -bltincmd(int argc, char **argv) +static struct tblentry **lastcmdentry; + +static struct tblentry * +cmdlookup(const char *name, int add) { - /* Preserve exitstatus of a previous possible redirection - * as POSIX mandates */ - return back_exitstatus; + unsigned int hashval; + const char *p; + struct tblentry *cmdp; + struct tblentry **pp; + + p = name; + hashval = (unsigned char)*p << 4; + while (*p) + hashval += (unsigned char)*p++; + hashval &= 0x7FFF; + pp = &cmdtable[hashval % CMDTABLESIZE]; + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (strcmp(cmdp->cmdname, name) == 0) + break; + pp = &cmdp->next; + } + if (add && cmdp == NULL) { + cmdp = *pp = ckmalloc(sizeof(struct tblentry) - ARB + + strlen(name) + 1); + cmdp->next = NULL; + cmdp->cmdtype = CMDUNKNOWN; + strcpy(cmdp->cmdname, name); + } + lastcmdentry = pp; + return cmdp; } + +/* + * Delete the command entry returned on the last lookup. + */ static void -evalcommand(union node *cmd, int flags) +delete_cmd_entry(void) { - static const struct builtincmd bltin = { - "\0\0", bltincmd - }; - struct stackmark smark; - union node *argp; - struct arglist arglist; - struct arglist varlist; - char **argv; - int argc; - const struct strlist *sp; - struct cmdentry cmdentry; - struct job *jp; - char *lastarg; - const char *path; - int spclbltin; - int cmd_is_exec; - int status; - char **nargv; - struct builtincmd *bcmd; - int pseudovarflag = 0; + struct tblentry *cmdp; - /* First expand the arguments. */ - TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); - setstackmark(&smark); - back_exitstatus = 0; + INT_OFF; + cmdp = *lastcmdentry; + *lastcmdentry = cmdp->next; + if (cmdp->cmdtype == CMDFUNCTION) + freefunc(cmdp->param.func); + free(cmdp); + INT_ON; +} - cmdentry.cmdtype = CMDBUILTIN; - cmdentry.u.cmd = &bltin; - varlist.lastp = &varlist.list; - *varlist.lastp = NULL; - arglist.lastp = &arglist.list; - *arglist.lastp = NULL; +/* + * Add a new command entry, replacing any existing command entry for + * the same name - except special builtins. + */ +static void +addcmdentry(char *name, struct cmdentry *entry) +{ + struct tblentry *cmdp; - argc = 0; - if (cmd->ncmd.args) { - bcmd = find_builtin(cmd->ncmd.args->narg.text); - pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); + cmdp = cmdlookup(name, 1); + if (cmdp->cmdtype == CMDFUNCTION) { + freefunc(cmdp->param.func); } + cmdp->cmdtype = entry->cmdtype; + cmdp->param = entry->u; + cmdp->rehash = 0; +} - for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { - struct strlist **spp; - - spp = arglist.lastp; - if (pseudovarflag && isassignment(argp->narg.text)) - expandarg(argp, &arglist, EXP_VARTILDE); - else - expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); +static int +hashcmd(int argc, char **argv) +{ + struct tblentry **pp; + struct tblentry *cmdp; + int c; + struct cmdentry entry; + char *name; - for (sp = *spp; sp; sp = sp->next) - argc++; + while ((c = nextopt("r")) != '\0') { + clearcmdentry(0); + return 0; + } + if (*argptr == NULL) { + for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL) + printentry(cmdp); + } + } + return 0; } - - argv = nargv = stalloc(sizeof(char *) * (argc + 1)); - for (sp = arglist.list; sp; sp = sp->next) { - TRACE(("evalcommand arg: %s\n", sp->text)); - *nargv++ = sp->text; + c = 0; + while ((name = *argptr) != NULL) { + cmdp = cmdlookup(name, 0); + if (cmdp != NULL + && (cmdp->cmdtype == CMDNORMAL + || (cmdp->cmdtype == CMDBUILTIN && builtinloc >= 0))) + delete_cmd_entry(); + find_command(name, &entry, DO_ERR, pathval()); + if (entry.cmdtype == CMDUNKNOWN) + c = 1; + argptr++; } - *nargv = NULL; - - lastarg = NULL; - if (iflag && funcnest == 0 && argc > 0) - lastarg = nargv[-1]; + return c; +} - preverrout_fd = 2; - expredir(cmd->ncmd.redirect); - status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); +/* + * Called when a cd is done. Marks all commands so the next time they + * are executed they will be rehashed. + */ +static void +hashcd(void) +{ + struct tblentry **pp; + struct tblentry *cmdp; - path = vpath.text; - for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { - struct strlist **spp; - char *p; + for (pp = cmdtable; pp < &cmdtable[CMDTABLESIZE]; pp++) { + for (cmdp = *pp; cmdp; cmdp = cmdp->next) { + if (cmdp->cmdtype == CMDNORMAL || ( + cmdp->cmdtype == CMDBUILTIN && + !(IS_BUILTIN_REGULAR(cmdp->param.cmd)) && + builtinloc > 0 + )) + cmdp->rehash = 1; + } + } +} - spp = varlist.lastp; - expandarg(argp, &varlist, EXP_VARTILDE); +/* + * Fix command hash table when PATH changed. + * Called before PATH is changed. The argument is the new value of PATH; + * pathval() still returns the old value at this point. + * Called with interrupts off. + */ +static void +changepath(const char *newval) +{ + const char *old, *new; + int idx; + int firstchange; + int idx_bltin; - /* - * Modify the command lookup path, if a PATH= assignment - * is present - */ - p = (*spp)->text; - if (varequal(p, path)) - path = p; + old = pathval(); + new = newval; + firstchange = 9999; /* assume no change */ + idx = 0; + idx_bltin = -1; + for (;;) { + if (*old != *new) { + firstchange = idx; + if ((*old == '\0' && *new == ':') + || (*old == ':' && *new == '\0')) + firstchange++; + old = new; /* ignore subsequent differences */ + } + if (*new == '\0') + break; + if (*new == '%' && idx_bltin < 0 && prefix(new + 1, "builtin")) + idx_bltin = idx; + if (*new == ':') { + idx++; + } + new++, old++; } + if (builtinloc < 0 && idx_bltin >= 0) + builtinloc = idx_bltin; /* zap builtins */ + if (builtinloc >= 0 && idx_bltin < 0) + firstchange = 0; + clearcmdentry(firstchange); + builtinloc = idx_bltin; +} - /* Print the command if xflag is set. */ - if (xflag) { - int n; - const char *p = " %s"; +#define TEOF 0 +#define TNL 1 +#define TREDIR 2 +#define TWORD 3 +#define TSEMI 4 +#define TBACKGND 5 +#define TAND 6 +#define TOR 7 +#define TPIPE 8 +#define TLP 9 +#define TRP 10 +#define TENDCASE 11 +#define TENDBQUOTE 12 +#define TNOT 13 +#define TCASE 14 +#define TDO 15 +#define TDONE 16 +#define TELIF 17 +#define TELSE 18 +#define TESAC 19 +#define TFI 20 +#define TFOR 21 +#define TIF 22 +#define TIN 23 +#define TTHEN 24 +#define TUNTIL 25 +#define TWHILE 26 +#define TBEGIN 27 +#define TEND 28 - p++; - dprintf(preverrout_fd, p, expandstr(ps4val())); +/* first char is indicating which tokens mark the end of a list */ +static const char *const tokname_array[] = { + "\1end of file", + "\0newline", + "\0redirection", + "\0word", + "\0;", + "\0&", + "\0&&", + "\0||", + "\0|", + "\0(", + "\1)", + "\1;;", + "\1`", +#define KWDOFFSET 13 + /* the following are keywords */ + "\0!", + "\0case", + "\1do", + "\1done", + "\1elif", + "\1else", + "\1esac", + "\1fi", + "\0for", + "\0if", + "\0in", + "\1then", + "\0until", + "\0while", + "\0{", + "\1}", +}; - sp = varlist.list; - for (n = 0; n < 2; n++) { - while (sp) { - dprintf(preverrout_fd, p, sp->text); - sp = sp->next; - if (*p == '%') { - p--; - } - } - sp = arglist.list; - } - full_write(preverrout_fd, "\n", 1); - } +static const char * +tokname(int tok) +{ + static char buf[16]; - cmd_is_exec = 0; - spclbltin = -1; + if (tok >= TSEMI) + buf[0] = '"'; + sprintf(buf + (tok >= TSEMI), "%s%c", + tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); + return buf; +} - /* Now locate the command. */ - if (argc) { - const char *oldpath; - int cmd_flag = DO_ERR; +/* Wrapper around strcmp for qsort/bsearch/... */ +static int +pstrcmp(const void *a, const void *b) +{ + return strcmp((const char *) a, (*(const char *const *) b) + 1); +} - path += 5; - oldpath = path; - for (;;) { - find_command(argv[0], &cmdentry, cmd_flag, path); - if (cmdentry.cmdtype == CMDUNKNOWN) { - status = 127; - flush_stderr(); - goto bail; - } +static const char *const * +findkwd(const char *s) +{ + return bsearch(s, tokname_array + KWDOFFSET, + (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, + sizeof(const char *), pstrcmp); +} - /* implement bltin and command here */ - if (cmdentry.cmdtype != CMDBUILTIN) - break; - if (spclbltin < 0) - spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); - if (cmdentry.u.cmd == EXECCMD) - cmd_is_exec++; +/* + * Locate and print what a word is... + */ #if ENABLE_ASH_CMDCMD - if (cmdentry.u.cmd == COMMANDCMD) { - path = oldpath; - nargv = parse_command_args(argv, &path); - if (!nargv) - break; - argc -= nargv - argv; - argv = nargv; - cmd_flag |= DO_NOFUNC; - } else +static int +describe_command(char *command, int describe_command_verbose) +#else +#define describe_command_verbose 1 +static int +describe_command(char *command) #endif - break; - } +{ + struct cmdentry entry; + struct tblentry *cmdp; +#if ENABLE_ASH_ALIAS + const struct alias *ap; +#endif + const char *path = pathval(); + + if (describe_command_verbose) { + out1str(command); } - if (status) { - /* We have a redirection error. */ - if (spclbltin > 0) - raise_exception(EXERROR); - bail: - exitstatus = status; + /* First look at the keywords */ + if (findkwd(command)) { + out1str(describe_command_verbose ? " is a shell keyword" : command); goto out; } - /* Execute the command. */ - switch (cmdentry.cmdtype) { - default: - /* Fork off a child process if necessary. */ - if (!(flags & EV_EXIT) || trap[0]) { - INT_OFF; - jp = makejob(cmd, 1); - if (forkshell(jp, cmd, FORK_FG) != 0) { - exitstatus = waitforjob(jp); - INT_ON; - break; - } - FORCE_INT_ON; +#if ENABLE_ASH_ALIAS + /* Then look at the aliases */ + ap = lookupalias(command, 0); + if (ap != NULL) { + if (describe_command_verbose) { + out1fmt(" is an alias for %s", ap->val); + } else { + out1str("alias "); + printalias(ap); + return 0; } - listsetvar(varlist.list, VEXPORT|VSTACK); - shellexec(argv, path, cmdentry.u.index); - /* NOTREACHED */ + goto out; + } +#endif + /* Then check if it is a tracked alias */ + cmdp = cmdlookup(command, 0); + if (cmdp != NULL) { + entry.cmdtype = cmdp->cmdtype; + entry.u = cmdp->param; + } else { + /* Finally use brute force */ + find_command(command, &entry, DO_ABS, path); + } - case CMDBUILTIN: - cmdenviron = varlist.list; - if (cmdenviron) { - struct strlist *list = cmdenviron; - int i = VNOSET; - if (spclbltin > 0 || argc == 0) { - i = 0; - if (cmd_is_exec && argc > 1) - i = VEXPORT; - } - listsetvar(list, i); + switch (entry.cmdtype) { + case CMDNORMAL: { + int j = entry.u.index; + char *p; + if (j == -1) { + p = command; + } else { + do { + p = padvance(&path, command); + stunalloc(p); + } while (--j >= 0); } - if (evalbltin(cmdentry.u.cmd, argc, argv)) { - int exit_status; - int i, j; - - i = exception; - if (i == EXEXIT) - goto raise; - - exit_status = 2; - j = 0; - if (i == EXINT) - j = SIGINT; - if (i == EXSIG) - j = pendingsigs; - if (j) - exit_status = j + 128; - exitstatus = exit_status; - - if (i == EXINT || spclbltin > 0) { - raise: - longjmp(exception_handler->loc, 1); - } - FORCE_INT_ON; + if (describe_command_verbose) { + out1fmt(" is%s %s", + (cmdp ? " a tracked alias for" : nullstr), p + ); + } else { + out1str(p); } break; + } case CMDFUNCTION: - listsetvar(varlist.list, 0); - if (evalfun(cmdentry.u.func, argc, argv, flags)) - goto raise; + if (describe_command_verbose) { + out1str(" is a shell function"); + } else { + out1str(command); + } break; - } + case CMDBUILTIN: + if (describe_command_verbose) { + out1fmt(" is a %sshell builtin", + IS_BUILTIN_SPECIAL(entry.u.cmd) ? + "special " : nullstr + ); + } else { + out1str(command); + } + break; + + default: + if (describe_command_verbose) { + out1str(": not found\n"); + } + return 127; + } out: - popredir(cmd_is_exec); - if (lastarg) - /* dsl: I think this is intended to be used to support - * '_' in 'vi' command mode during line editing... - * However I implemented that within libedit itself. - */ - setvar("_", lastarg, 0); - popstackmark(&smark); + outstr("\n", stdout); + return 0; } static int -evalbltin(const struct builtincmd *cmd, int argc, char **argv) +typecmd(int argc, char **argv) { - char *volatile savecmdname; - struct jmploc *volatile savehandler; - struct jmploc jmploc; int i; + int err = 0; - savecmdname = commandname; - i = setjmp(jmploc.loc); - if (i) - goto cmddone; - savehandler = exception_handler; - exception_handler = &jmploc; - commandname = argv[0]; - argptr = argv + 1; - optptr = NULL; /* initialize nextopt */ - exitstatus = (*cmd->builtin)(argc, argv); - flush_stdout_stderr(); - cmddone: - exitstatus |= ferror(stdout); - clearerr(stdout); - commandname = savecmdname; - exsig = 0; - exception_handler = savehandler; - - return i; + for (i = 1; i < argc; i++) { +#if ENABLE_ASH_CMDCMD + err |= describe_command(argv[i], 1); +#else + err |= describe_command(argv[i]); +#endif + } + return err; } +#if ENABLE_ASH_CMDCMD static int -goodname(const char *p) +commandcmd(int argc, char **argv) { - return !*endofname(p); -} - + int c; + enum { + VERIFY_BRIEF = 1, + VERIFY_VERBOSE = 2, + } verify = 0; -/* - * Search for a command. This is called before we fork so that the - * location of the command will be available in the parent as well as - * the child. The check for "goodname" is an overly conservative - * check that the name will not be subject to expansion. - */ -static void -prehash(union node *n) -{ - struct cmdentry entry; + while ((c = nextopt("pvV")) != '\0') + if (c == 'V') + verify |= VERIFY_VERBOSE; + else if (c == 'v') + verify |= VERIFY_BRIEF; +#if DEBUG + else if (c != 'p') + abort(); +#endif + if (verify) + return describe_command(*argptr, verify - VERIFY_BRIEF); - if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text)) - find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); + return 0; } +#endif -/* - * Builtin commands. Builtin commands whose functions are closely - * tied to evaluation are implemented here. - */ +/* ============ eval.c */ -/* - * Handle break and continue commands. Break, continue, and return are - * all handled by setting the evalskip flag. The evaluation routines - * above all check this flag, and if it is set they start skipping - * commands rather than executing them. The variable skipcount is - * the number of loops to break/continue, or the number of function - * levels to return. (The latter is always 1.) It should probably - * be an error to break out of more loops than exist, but it isn't - * in the standard shell so we don't make it one here. - */ +static int funcblocksize; /* size of structures in function */ +static int funcstringsize; /* size of strings in node */ +static void *funcblock; /* block to allocate function from */ +static char *funcstring; /* block to allocate strings from */ + +/* flags in argument to evaltree */ +#define EV_EXIT 01 /* exit after evaluating tree */ +#define EV_TESTED 02 /* exit status is checked; ignore -e flag */ +#define EV_BACKCMD 04 /* command executing within back quotes */ + +static const short nodesize[26] = { + SHELL_ALIGN(sizeof(struct ncmd)), + SHELL_ALIGN(sizeof(struct npipe)), + SHELL_ALIGN(sizeof(struct nredir)), + SHELL_ALIGN(sizeof(struct nredir)), + SHELL_ALIGN(sizeof(struct nredir)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nif)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nbinary)), + SHELL_ALIGN(sizeof(struct nfor)), + SHELL_ALIGN(sizeof(struct ncase)), + SHELL_ALIGN(sizeof(struct nclist)), + SHELL_ALIGN(sizeof(struct narg)), + SHELL_ALIGN(sizeof(struct narg)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct nfile)), + SHELL_ALIGN(sizeof(struct ndup)), + SHELL_ALIGN(sizeof(struct ndup)), + SHELL_ALIGN(sizeof(struct nhere)), + SHELL_ALIGN(sizeof(struct nhere)), + SHELL_ALIGN(sizeof(struct nnot)), +}; -static int -breakcmd(int argc, char **argv) -{ - int n = argc > 1 ? number(argv[1]) : 1; +static void calcsize(union node *n); - if (n <= 0) - ash_msg_and_raise_error(illnum, argv[1]); - if (n > loopnest) - n = loopnest; - if (n > 0) { - evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; - skipcount = n; +static void +sizenodelist(struct nodelist *lp) +{ + while (lp) { + funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); + calcsize(lp->n); + lp = lp->next; } - return 0; } -/* - * The return command. - */ -static int -returncmd(int argc, char **argv) +static void +calcsize(union node *n) { - /* - * If called outside a function, do what ksh does; - * skip the rest of the file. - */ - evalskip = funcnest ? SKIPFUNC : SKIPFILE; - return argv[1] ? number(argv[1]) : exitstatus; + if (n == NULL) + return; + funcblocksize += nodesize[n->type]; + switch (n->type) { + case NCMD: + calcsize(n->ncmd.redirect); + calcsize(n->ncmd.args); + calcsize(n->ncmd.assign); + break; + case NPIPE: + sizenodelist(n->npipe.cmdlist); + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + calcsize(n->nredir.redirect); + calcsize(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + calcsize(n->nbinary.ch2); + calcsize(n->nbinary.ch1); + break; + case NIF: + calcsize(n->nif.elsepart); + calcsize(n->nif.ifpart); + calcsize(n->nif.test); + break; + case NFOR: + funcstringsize += strlen(n->nfor.var) + 1; + calcsize(n->nfor.body); + calcsize(n->nfor.args); + break; + case NCASE: + calcsize(n->ncase.cases); + calcsize(n->ncase.expr); + break; + case NCLIST: + calcsize(n->nclist.body); + calcsize(n->nclist.pattern); + calcsize(n->nclist.next); + break; + case NDEFUN: + case NARG: + sizenodelist(n->narg.backquote); + funcstringsize += strlen(n->narg.text) + 1; + calcsize(n->narg.next); + break; + case NTO: + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + calcsize(n->nfile.fname); + calcsize(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + calcsize(n->ndup.vname); + calcsize(n->ndup.next); + break; + case NHERE: + case NXHERE: + calcsize(n->nhere.doc); + calcsize(n->nhere.next); + break; + case NNOT: + calcsize(n->nnot.com); + break; + }; } -static int -falsecmd(int argc, char **argv) +static char * +nodeckstrdup(char *s) { - return 1; -} + char *rtn = funcstring; -static int -truecmd(int argc, char **argv) -{ - return 0; + strcpy(funcstring, s); + funcstring += strlen(s) + 1; + return rtn; } -static int -execcmd(int argc, char **argv) +static union node *copynode(union node *); + +static struct nodelist * +copynodelist(struct nodelist *lp) { - if (argc > 1) { - iflag = 0; /* exit on error */ - mflag = 0; - optschanged(); - shellexec(argv + 1, pathval(), 0); + struct nodelist *start; + struct nodelist **lpp; + + lpp = &start; + while (lp) { + *lpp = funcblock; + funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist)); + (*lpp)->n = copynode(lp->n); + lp = lp->next; + lpp = &(*lpp)->next; } - return 0; + *lpp = NULL; + return start; } +static union node * +copynode(union node *n) +{ + union node *new; -/* ============ input.c - * - * This implements the input routines used by the parser. - */ - -#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ - -enum { - INPUT_PUSH_FILE = 1, - INPUT_NOFILE_OK = 2, -}; + if (n == NULL) + return NULL; + new = funcblock; + funcblock = (char *) funcblock + nodesize[n->type]; -/* - * NEOF is returned by parsecmd when it encounters an end of file. It - * must be distinct from NULL, so we use the address of a variable that - * happens to be handy. - */ -static int plinno = 1; /* input line number */ -/* number of characters left in input buffer */ -static int parsenleft; /* copy of parsefile->nleft */ -static int parselleft; /* copy of parsefile->lleft */ -/* next character in input buffer */ -static char *parsenextc; /* copy of parsefile->nextc */ + switch (n->type) { + case NCMD: + new->ncmd.redirect = copynode(n->ncmd.redirect); + new->ncmd.args = copynode(n->ncmd.args); + new->ncmd.assign = copynode(n->ncmd.assign); + break; + case NPIPE: + new->npipe.cmdlist = copynodelist(n->npipe.cmdlist); + new->npipe.backgnd = n->npipe.backgnd; + break; + case NREDIR: + case NBACKGND: + case NSUBSHELL: + new->nredir.redirect = copynode(n->nredir.redirect); + new->nredir.n = copynode(n->nredir.n); + break; + case NAND: + case NOR: + case NSEMI: + case NWHILE: + case NUNTIL: + new->nbinary.ch2 = copynode(n->nbinary.ch2); + new->nbinary.ch1 = copynode(n->nbinary.ch1); + break; + case NIF: + new->nif.elsepart = copynode(n->nif.elsepart); + new->nif.ifpart = copynode(n->nif.ifpart); + new->nif.test = copynode(n->nif.test); + break; + case NFOR: + new->nfor.var = nodeckstrdup(n->nfor.var); + new->nfor.body = copynode(n->nfor.body); + new->nfor.args = copynode(n->nfor.args); + break; + case NCASE: + new->ncase.cases = copynode(n->ncase.cases); + new->ncase.expr = copynode(n->ncase.expr); + break; + case NCLIST: + new->nclist.body = copynode(n->nclist.body); + new->nclist.pattern = copynode(n->nclist.pattern); + new->nclist.next = copynode(n->nclist.next); + break; + case NDEFUN: + case NARG: + new->narg.backquote = copynodelist(n->narg.backquote); + new->narg.text = nodeckstrdup(n->narg.text); + new->narg.next = copynode(n->narg.next); + break; + case NTO: + case NCLOBBER: + case NFROM: + case NFROMTO: + case NAPPEND: + new->nfile.fname = copynode(n->nfile.fname); + new->nfile.fd = n->nfile.fd; + new->nfile.next = copynode(n->nfile.next); + break; + case NTOFD: + case NFROMFD: + new->ndup.vname = copynode(n->ndup.vname); + new->ndup.dupfd = n->ndup.dupfd; + new->ndup.fd = n->ndup.fd; + new->ndup.next = copynode(n->ndup.next); + break; + case NHERE: + case NXHERE: + new->nhere.doc = copynode(n->nhere.doc); + new->nhere.fd = n->nhere.fd; + new->nhere.next = copynode(n->nhere.next); + break; + case NNOT: + new->nnot.com = copynode(n->nnot.com); + break; + }; + new->type = n->type; + return new; +} -static int checkkwd; -/* values of checkkwd variable */ -#define CHKALIAS 0x1 -#define CHKKWD 0x2 -#define CHKNL 0x4 +/* + * Make a copy of a parse tree. + */ +static struct funcnode * +copyfunc(union node *n) +{ + struct funcnode *f; + size_t blocksize; + funcblocksize = offsetof(struct funcnode, n); + funcstringsize = 0; + calcsize(n); + blocksize = funcblocksize; + f = ckmalloc(blocksize + funcstringsize); + funcblock = (char *) f + offsetof(struct funcnode, n); + funcstring = (char *) f + blocksize; + copynode(n); + f->count = 0; + return f; +} +/* + * Define a shell function. + */ static void -popstring(void) +defun(char *name, union node *func) { - struct strpush *sp = parsefile->strpush; + struct cmdentry entry; INT_OFF; -#if ENABLE_ASH_ALIAS - if (sp->ap) { - if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') { - checkkwd |= CHKALIAS; - } - if (sp->string != sp->ap->val) { - free(sp->string); - } - sp->ap->flag &= ~ALIASINUSE; - if (sp->ap->flag & ALIASDEAD) { - unalias(sp->ap->name); - } - } -#endif - parsenextc = sp->prevstring; - parsenleft = sp->prevnleft; -/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/ - parsefile->strpush = sp->prev; - if (sp != &(parsefile->basestrpush)) - free(sp); + entry.cmdtype = CMDFUNCTION; + entry.u.func = copyfunc(func); + addcmdentry(name, &entry); INT_ON; } -static int -preadfd(void) -{ - int nr; - char *buf = parsefile->buf; - parsenextc = buf; - - retry: -#if ENABLE_FEATURE_EDITING - if (!iflag || parsefile->fd) - nr = safe_read(parsefile->fd, buf, BUFSIZ - 1); - else { -#if ENABLE_FEATURE_TAB_COMPLETION - line_input_state->path_lookup = pathval(); -#endif - nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state); - if (nr == 0) { - /* Ctrl+C pressed */ - if (trap[SIGINT]) { - buf[0] = '\n'; - buf[1] = '\0'; - raise(SIGINT); - return 1; - } - goto retry; - } - if (nr < 0 && errno == 0) { - /* Ctrl+D presend */ - nr = 0; - } - } -#else - nr = safe_read(parsefile->fd, buf, BUFSIZ - 1); -#endif +static int evalskip; /* set if we are skipping commands */ +/* reasons for skipping commands (see comment on breakcmd routine) */ +#define SKIPBREAK (1 << 0) +#define SKIPCONT (1 << 1) +#define SKIPFUNC (1 << 2) +#define SKIPFILE (1 << 3) +#define SKIPEVAL (1 << 4) +static int skipcount; /* number of levels to skip */ +static int funcnest; /* depth of function calls */ - if (nr < 0) { - if (parsefile->fd == 0 && errno == EWOULDBLOCK) { - int flags = fcntl(0, F_GETFL, 0); - if (flags >= 0 && flags & O_NONBLOCK) { - flags &=~ O_NONBLOCK; - if (fcntl(0, F_SETFL, flags) >= 0) { - out2str("sh: turning off NDELAY mode\n"); - goto retry; - } - } - } - } - return nr; -} +/* forward decl way out to parsing code - dotrap needs it */ +static int evalstring(char *s, int mask); /* - * Refill the input buffer and return the next input character: - * - * 1) If a string was pushed back on the input, pop it; - * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading - * from a string so we can't refill the buffer, return EOF. - * 3) If the is more stuff in this buffer, use it else call read to fill it. - * 4) Process input up to the next newline, deleting nul characters. + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. */ static int -preadbuffer(void) +dotrap(void) { + char *p; char *q; - int more; - char savec; + int i; + int savestatus; + int skip = 0; - while (parsefile->strpush) { -#if ENABLE_ASH_ALIAS - if (parsenleft == -1 && parsefile->strpush->ap && - parsenextc[-1] != ' ' && parsenextc[-1] != '\t') { - return PEOA; - } -#endif - popstring(); - if (--parsenleft >= 0) - return SC2INT(*parsenextc++); - } - if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) - return PEOF; - flush_stdout_stderr(); + savestatus = exitstatus; + pendingsigs = 0; + xbarrier(); - more = parselleft; - if (more <= 0) { - again: - more = preadfd(); - if (more <= 0) { - parselleft = parsenleft = EOF_NLEFT; - return PEOF; - } - } + for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { + if (!*q) + continue; + *q = '\0'; - q = parsenextc; + p = trap[i + 1]; + if (!p) + continue; + skip = evalstring(p, SKIPEVAL); + exitstatus = savestatus; + if (skip) + break; + } - /* delete nul characters */ - for (;;) { - int c; + return skip; +} - more--; - c = *q; +/* forward declarations - evaluation is fairly recursive business... */ +static void evalloop(union node *, int); +static void evalfor(union node *, int); +static void evalcase(union node *, int); +static void evalsubshell(union node *, int); +static void expredir(union node *); +static void evalpipe(union node *, int); +static void evalcommand(union node *, int); +static int evalbltin(const struct builtincmd *, int, char **); +static void prehash(union node *); - if (!c) - memmove(q, q + 1, more); - else { - q++; - if (c == '\n') { - parsenleft = q - parsenextc - 1; - break; - } +/* + * Evaluate a parse tree. The value is left in the global variable + * exitstatus. + */ +static void +evaltree(union node *n, int flags) +{ + int checkexit = 0; + void (*evalfn)(union node *, int); + unsigned isor; + int status; + if (n == NULL) { + TRACE(("evaltree(NULL) called\n")); + goto out; + } + TRACE(("pid %d, evaltree(%p: %d, %d) called\n", + getpid(), n, n->type, flags)); + switch (n->type) { + default: +#if DEBUG + out1fmt("Node type = %d\n", n->type); + fflush(stdout); + break; +#endif + case NNOT: + evaltree(n->nnot.com, EV_TESTED); + status = !exitstatus; + goto setstatus; + case NREDIR: + expredir(n->nredir.redirect); + status = redirectsafe(n->nredir.redirect, REDIR_PUSH); + if (!status) { + evaltree(n->nredir.n, flags & EV_TESTED); + status = exitstatus; + } + popredir(0); + goto setstatus; + case NCMD: + evalfn = evalcommand; + checkexit: + if (eflag && !(flags & EV_TESTED)) + checkexit = ~0; + goto calleval; + case NFOR: + evalfn = evalfor; + goto calleval; + case NWHILE: + case NUNTIL: + evalfn = evalloop; + goto calleval; + case NSUBSHELL: + case NBACKGND: + evalfn = evalsubshell; + goto calleval; + case NPIPE: + evalfn = evalpipe; + goto checkexit; + case NCASE: + evalfn = evalcase; + goto calleval; + case NAND: + case NOR: + case NSEMI: +#if NAND + 1 != NOR +#error NAND + 1 != NOR +#endif +#if NOR + 1 != NSEMI +#error NOR + 1 != NSEMI +#endif + isor = n->type - NAND; + evaltree( + n->nbinary.ch1, + (flags | ((isor >> 1) - 1)) & EV_TESTED + ); + if (!exitstatus == isor) + break; + if (!evalskip) { + n = n->nbinary.ch2; + evaln: + evalfn = evaltree; + calleval: + evalfn(n, flags); + break; } - - if (more <= 0) { - parsenleft = q - parsenextc - 1; - if (parsenleft < 0) - goto again; + break; + case NIF: + evaltree(n->nif.test, EV_TESTED); + if (evalskip) break; + if (exitstatus == 0) { + n = n->nif.ifpart; + goto evaln; + } else if (n->nif.elsepart) { + n = n->nif.elsepart; + goto evaln; } + goto success; + case NDEFUN: + defun(n->narg.text, n->narg.next); + success: + status = 0; + setstatus: + exitstatus = status; + break; } - parselleft = more; - - savec = *q; - *q = '\0'; + out: + if ((checkexit & exitstatus)) + evalskip |= SKIPEVAL; + else if (pendingsigs && dotrap()) + goto exexit; - if (vflag) { - out2str(parsenextc); + if (flags & EV_EXIT) { + exexit: + raise_exception(EXEXIT); } - - *q = savec; - - return SC2INT(*parsenextc++); } -#define pgetc_as_macro() (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer()) - -#if ENABLE_ASH_OPTIMIZE_FOR_SIZE -#define pgetc_macro() pgetc() -static int -pgetc(void) -{ - return pgetc_as_macro(); -} -#else -#define pgetc_macro() pgetc_as_macro() -static int -pgetc(void) -{ - return pgetc_macro(); -} +#if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) +static #endif +void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); -/* - * Same as pgetc(), but ignores PEOA. - */ -#if ENABLE_ASH_ALIAS -static int -pgetc2(void) -{ - int c; +static int loopnest; /* current loop nesting level */ - do { - c = pgetc_macro(); - } while (c == PEOA); - return c; -} -#else -static int -pgetc2(void) +static void +evalloop(union node *n, int flags) { - return pgetc_macro(); -} -#endif + int status; -/* - * Read a line from the script. - */ -static char * -pfgets(char *line, int len) -{ - char *p = line; - int nleft = len; - int c; + loopnest++; + status = 0; + flags &= EV_TESTED; + for (;;) { + int i; - while (--nleft > 0) { - c = pgetc2(); - if (c == PEOF) { - if (p == line) - return NULL; + evaltree(n->nbinary.ch1, EV_TESTED); + if (evalskip) { + skipping: + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; break; } - *p++ = c; - if (c == '\n') + i = exitstatus; + if (n->type != NWHILE) + i = !i; + if (i != 0) break; + evaltree(n->nbinary.ch2, flags); + status = exitstatus; + if (evalskip) + goto skipping; } - *p = '\0'; - return line; -} - -/* - * Undo the last call to pgetc. Only one character may be pushed back. - * PEOF may be pushed back. - */ -static void -pungetc(void) -{ - parsenleft++; - parsenextc--; + loopnest--; + exitstatus = status; } -/* - * Push a string back onto the input at this current parsefile level. - * We handle aliases this way. - */ static void -pushstring(char *s, void *ap) +evalfor(union node *n, int flags) { - struct strpush *sp; - size_t len; + struct arglist arglist; + union node *argp; + struct strlist *sp; + struct stackmark smark; - len = strlen(s); - INT_OFF; -/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/ - if (parsefile->strpush) { - sp = ckmalloc(sizeof(struct strpush)); - sp->prev = parsefile->strpush; - parsefile->strpush = sp; - } else - sp = parsefile->strpush = &(parsefile->basestrpush); - sp->prevstring = parsenextc; - sp->prevnleft = parsenleft; -#if ENABLE_ASH_ALIAS - sp->ap = (struct alias *)ap; - if (ap) { - ((struct alias *)ap)->flag |= ALIASINUSE; - sp->string = s; + setstackmark(&smark); + arglist.lastp = &arglist.list; + for (argp = n->nfor.args; argp; argp = argp->narg.next) { + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE | EXP_RECORD); + /* XXX */ + if (evalskip) + goto out; } -#endif - parsenextc = s; - parsenleft = len; - INT_ON; -} - -/* - * To handle the "." command, a stack of input files is used. Pushfile - * adds a new entry to the stack and popfile restores the previous level. - */ -static void -pushfile(void) -{ - struct parsefile *pf; - - parsefile->nleft = parsenleft; - parsefile->lleft = parselleft; - parsefile->nextc = parsenextc; - parsefile->linno = plinno; - pf = ckmalloc(sizeof(*pf)); - pf->prev = parsefile; - pf->fd = -1; - pf->strpush = NULL; - pf->basestrpush.prev = NULL; - parsefile = pf; -} - -static void -popfile(void) -{ - struct parsefile *pf = parsefile; + *arglist.lastp = NULL; - INT_OFF; - if (pf->fd >= 0) - close(pf->fd); - if (pf->buf) - free(pf->buf); - while (pf->strpush) - popstring(); - parsefile = pf->prev; - free(pf); - parsenleft = parsefile->nleft; - parselleft = parsefile->lleft; - parsenextc = parsefile->nextc; - plinno = parsefile->linno; - INT_ON; + exitstatus = 0; + loopnest++; + flags &= EV_TESTED; + for (sp = arglist.list; sp; sp = sp->next) { + setvar(n->nfor.var, sp->text, 0); + evaltree(n->nfor.body, flags); + if (evalskip) { + if (evalskip == SKIPCONT && --skipcount <= 0) { + evalskip = 0; + continue; + } + if (evalskip == SKIPBREAK && --skipcount <= 0) + evalskip = 0; + break; + } + } + loopnest--; + out: + popstackmark(&smark); } -/* - * Return to top level. - */ static void -popallfiles(void) +evalcase(union node *n, int flags) { - while (parsefile != &basepf) - popfile(); -} + union node *cp; + union node *patp; + struct arglist arglist; + struct stackmark smark; -/* - * Close the file(s) that the shell is reading commands from. Called - * after a fork is done. - */ -static void -closescript(void) -{ - popallfiles(); - if (parsefile->fd > 0) { - close(parsefile->fd); - parsefile->fd = 0; + setstackmark(&smark); + arglist.lastp = &arglist.list; + expandarg(n->ncase.expr, &arglist, EXP_TILDE); + exitstatus = 0; + for (cp = n->ncase.cases; cp && evalskip == 0; cp = cp->nclist.next) { + for (patp = cp->nclist.pattern; patp; patp = patp->narg.next) { + if (casematch(patp, arglist.list->text)) { + if (evalskip == 0) { + evaltree(cp->nclist.body, flags); + } + goto out; + } + } } + out: + popstackmark(&smark); } /* - * Like setinputfile, but takes an open file descriptor. Call this with - * interrupts off. + * Kick off a subshell to evaluate a tree. */ static void -setinputfd(int fd, int push) -{ - fcntl(fd, F_SETFD, FD_CLOEXEC); - if (push) { - pushfile(); - parsefile->buf = 0; - } - parsefile->fd = fd; - if (parsefile->buf == NULL) - parsefile->buf = ckmalloc(IBUFSIZ); - parselleft = parsenleft = 0; - plinno = 1; -} - -/* - * Set the input to take input from a file. If push is set, push the - * old input onto the stack first. - */ -static int -setinputfile(const char *fname, int flags) +evalsubshell(union node *n, int flags) { - int fd; - int fd2; + struct job *jp; + int backgnd = (n->type == NBACKGND); + int status; + expredir(n->nredir.redirect); + if (!backgnd && flags & EV_EXIT && !trap[0]) + goto nofork; INT_OFF; - fd = open(fname, O_RDONLY); - if (fd < 0) { - if (flags & INPUT_NOFILE_OK) - goto out; - ash_msg_and_raise_error("Can't open %s", fname); - } - if (fd < 10) { - fd2 = copyfd(fd, 10); - close(fd); - if (fd2 < 0) - ash_msg_and_raise_error("Out of file descriptors"); - fd = fd2; + jp = makejob(n, 1); + if (forkshell(jp, n, backgnd) == 0) { + INT_ON; + flags |= EV_EXIT; + if (backgnd) + flags &=~ EV_TESTED; + nofork: + redirect(n->nredir.redirect, 0); + evaltreenr(n->nredir.n, flags); + /* never returns */ } - setinputfd(fd, flags & INPUT_PUSH_FILE); - out: + status = 0; + if (! backgnd) + status = waitforjob(jp); + exitstatus = status; INT_ON; - return fd; } /* - * Like setinputfile, but takes input from a string. + * Compute the names of the files in a redirection list. */ +static void fixredir(union node *, const char *, int); static void -setinputstring(char *string) +expredir(union node *n) { - INT_OFF; - pushfile(); - parsenextc = string; - parsenleft = strlen(string); - parsefile->buf = NULL; - plinno = 1; - INT_ON; -} + union node *redir; + for (redir = n; redir; redir = redir->nfile.next) { + struct arglist fn; -/* ============ jobs.c */ + memset(&fn, 0, sizeof(fn)); + fn.lastp = &fn.list; + switch (redir->type) { + case NFROMTO: + case NFROM: + case NTO: + case NCLOBBER: + case NAPPEND: + expandarg(redir->nfile.fname, &fn, EXP_TILDE | EXP_REDIR); + redir->nfile.expfname = fn.list->text; + break; + case NFROMFD: + case NTOFD: + if (redir->ndup.vname) { + expandarg(redir->ndup.vname, &fn, EXP_FULL | EXP_TILDE); + if (fn.list == NULL) + ash_msg_and_raise_error("redir error"); + fixredir(redir, fn.list->text, 1); + } + break; + } + } +} /* - * Set the signal handler for the specified signal. The routine figures - * out what it should be set to. + * Evaluate a pipeline. All the processes in the pipeline are children + * of the process creating the pipeline. (This differs from some versions + * of the shell, which make the last process in a pipeline the parent + * of all the rest.) */ static void -setsignal(int signo) +evalpipe(union node *n, int flags) { - int action; - char *t, tsig; - struct sigaction act; + struct job *jp; + struct nodelist *lp; + int pipelen; + int prevfd; + int pip[2]; - t = trap[signo]; - if (t == NULL) - action = S_DFL; - else if (*t != '\0') - action = S_CATCH; - else - action = S_IGN; - if (rootshell && action == S_DFL) { - switch (signo) { - case SIGINT: - if (iflag || minusc || sflag == 0) - action = S_CATCH; - break; - case SIGQUIT: -#if DEBUG - if (debug) - break; -#endif - /* FALLTHROUGH */ - case SIGTERM: - if (iflag) - action = S_IGN; - break; -#if JOBS - case SIGTSTP: - case SIGTTOU: - if (mflag) - action = S_IGN; - break; -#endif + TRACE(("evalpipe(0x%lx) called\n", (long)n)); + pipelen = 0; + for (lp = n->npipe.cmdlist; lp; lp = lp->next) + pipelen++; + flags |= EV_EXIT; + INT_OFF; + jp = makejob(n, pipelen); + prevfd = -1; + for (lp = n->npipe.cmdlist; lp; lp = lp->next) { + prehash(lp->n); + pip[1] = -1; + if (lp->next) { + if (pipe(pip) < 0) { + close(prevfd); + ash_msg_and_raise_error("Pipe call failed"); + } + } + if (forkshell(jp, lp->n, n->npipe.backgnd) == 0) { + INT_ON; + if (pip[1] >= 0) { + close(pip[0]); + } + if (prevfd > 0) { + dup2(prevfd, 0); + close(prevfd); + } + if (pip[1] > 1) { + dup2(pip[1], 1); + close(pip[1]); + } + evaltreenr(lp->n, flags); + /* never returns */ } + if (prevfd >= 0) + close(prevfd); + prevfd = pip[0]; + close(pip[1]); + } + if (n->npipe.backgnd == 0) { + exitstatus = waitforjob(jp); + TRACE(("evalpipe: job done exit status %d\n", exitstatus)); } + INT_ON; +} - t = &sigmode[signo - 1]; - tsig = *t; - if (tsig == 0) { - /* - * current setting unknown - */ - if (sigaction(signo, 0, &act) == -1) { - /* - * Pretend it worked; maybe we should give a warning - * here, but other shells don't. We don't alter - * sigmode, so that we retry every time. - */ - return; - } - if (act.sa_handler == SIG_IGN) { - if (mflag - && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) - ) { - tsig = S_IGN; /* don't hard ignore these */ - } else - tsig = S_HARD_IGN; +static struct localvar *localvars; + +/* + * Called after a function returns. + * Interrupts must be off. + */ +static void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + TRACE(("poplocalvar %s", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof(optlist)); + free((char*)lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + unsetvar(vp->text); } else { - tsig = S_RESET; /* force to be set */ + if (vp->func) + (*vp->func)(strchrnul(lvp->text, '=') + 1); + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free((char*)vp->text); + vp->flags = lvp->flags; + vp->text = lvp->text; } + free(lvp); } - if (tsig == S_HARD_IGN || tsig == action) - return; - switch (action) { - case S_CATCH: - act.sa_handler = onsig; - break; - case S_IGN: - act.sa_handler = SIG_IGN; - break; - default: - act.sa_handler = SIG_DFL; - } - *t = action; - act.sa_flags = 0; - sigfillset(&act.sa_mask); - sigaction(signo, &act, 0); } -/* mode flags for set_curjob */ -#define CUR_DELETE 2 -#define CUR_RUNNING 1 -#define CUR_STOPPED 0 - -/* mode flags for dowait */ -#define DOWAIT_NORMAL 0 -#define DOWAIT_BLOCK 1 +static int +evalfun(struct funcnode *func, int argc, char **argv, int flags) +{ + volatile struct shparam saveparam; + struct localvar *volatile savelocalvars; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int e; -#if JOBS -/* pgrp of shell on invocation */ -static int initialpgrp; -static int ttyfd = -1; + saveparam = shellparam; + savelocalvars = localvars; + e = setjmp(jmploc.loc); + if (e) { + goto funcdone; + } + INT_OFF; + savehandler = exception_handler; + exception_handler = &jmploc; + localvars = NULL; + shellparam.malloc = 0; + func->count++; + funcnest++; + INT_ON; + shellparam.nparam = argc - 1; + shellparam.p = argv + 1; +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; #endif -/* array of jobs */ -static struct job *jobtab; -/* size of array */ -static unsigned njobs; -/* current job */ -static struct job *curjob; -/* number of presumed living untracked jobs */ -static int jobless; + evaltree(&func->n, flags & EV_TESTED); +funcdone: + INT_OFF; + funcnest--; + freefunc(func); + poplocalvars(); + localvars = savelocalvars; + freeparam(&shellparam); + shellparam = saveparam; + exception_handler = savehandler; + INT_ON; + evalskip &= ~SKIPFUNC; + return e; +} -static void -set_curjob(struct job *jp, unsigned mode) +#if ENABLE_ASH_CMDCMD +static char ** +parse_command_args(char **argv, const char **path) { - struct job *jp1; - struct job **jpp, **curp; + char *cp, c; - /* first remove from list */ - jpp = curp = &curjob; - do { - jp1 = *jpp; - if (jp1 == jp) + for (;;) { + cp = *++argv; + if (!cp) + return 0; + if (*cp++ != '-') break; - jpp = &jp1->prev_job; - } while (1); - *jpp = jp1->prev_job; - - /* Then re-insert in correct position */ - jpp = curp; - switch (mode) { - default: -#if DEBUG - abort(); -#endif - case CUR_DELETE: - /* job being deleted */ - break; - case CUR_RUNNING: - /* newly created job or backgrounded job, - put after all stopped jobs. */ + c = *cp++; + if (!c) + break; + if (c == '-' && !*cp) { + argv++; + break; + } do { - jp1 = *jpp; -#if JOBS - if (!jp1 || jp1->state != JOBSTOPPED) -#endif + switch (c) { + case 'p': + *path = defpath; break; - jpp = &jp1->prev_job; - } while (1); - /* FALLTHROUGH */ -#if JOBS - case CUR_STOPPED: -#endif - /* newly stopped job - becomes curjob */ - jp->prev_job = *jpp; - *jpp = jp; - break; + default: + /* run 'typecmd' for other options */ + return 0; + } + c = *cp++; + } while (c); } -} - -#if JOBS || DEBUG -static int -jobno(const struct job *jp) -{ - return jp - jobtab + 1; + return argv; } #endif /* - * Convert a job name to a job structure. + * Make a variable a local variable. When a variable is made local, it's + * value and flags are saved in a localvar structure. The saved values + * will be restored when the shell function returns. We handle the name + * "-" as a special case. */ -static struct job * -getjob(const char *name, int getctl) +static void +mklocal(char *name) { - struct job *jp; - struct job *found; - const char *err_msg = "No such job: %s"; - unsigned num; - int c; - const char *p; - char *(*match)(const char *, const char *); - - jp = curjob; - p = name; - if (!p) - goto currentjob; - - if (*p != '%') - goto err; - - c = *++p; - if (!c) - goto currentjob; - - if (!p[1]) { - if (c == '+' || c == '%') { - currentjob: - err_msg = "No current job"; - goto check; - } - if (c == '-') { - if (jp) - jp = jp->prev_job; - err_msg = "No previous job"; - check: - if (!jp) - goto err; - goto gotit; - } - } - - if (is_number(p)) { - num = atoi(p); - if (num < njobs) { - jp = jobtab + num - 1; - if (jp->used) - goto gotit; - goto err; - } - } + struct localvar *lvp; + struct var **vpp; + struct var *vp; - match = prefix; - if (*p == '?') { - match = strstr; - p++; - } + INT_OFF; + lvp = ckmalloc(sizeof(struct localvar)); + if (LONE_DASH(name)) { + char *p; + p = ckmalloc(sizeof(optlist)); + lvp->text = memcpy(p, optlist, sizeof(optlist)); + vp = NULL; + } else { + char *eq; - found = 0; - while (1) { - if (!jp) - goto err; - if (match(jp->ps[0].cmd, p)) { - if (found) - goto err; - found = jp; - err_msg = "%s: ambiguous"; + vpp = hashvar(name); + vp = *findvar(vpp, name); + eq = strchr(name, '='); + if (vp == NULL) { + if (eq) + setvareq(name, VSTRFIXED); + else + setvar(name, NULL, VSTRFIXED); + vp = *vpp; /* the new variable */ + lvp->flags = VUNSET; + } else { + lvp->text = vp->text; + lvp->flags = vp->flags; + vp->flags |= VSTRFIXED|VTEXTFIXED; + if (eq) + setvareq(name, 0); } - jp = jp->prev_job; } - - gotit: -#if JOBS - err_msg = "job %s not created under job control"; - if (getctl && jp->jobctl == 0) - goto err; -#endif - return jp; - err: - ash_msg_and_raise_error(err_msg, name); + lvp->vp = vp; + lvp->next = localvars; + localvars = lvp; + INT_ON; } /* - * Mark a job structure as unused. + * The "local" command. */ -static void -freejob(struct job *jp) +static int +localcmd(int argc, char **argv) { - struct procstat *ps; - int i; + char *name; - INT_OFF; - for (i = jp->nprocs, ps = jp->ps; --i >= 0; ps++) { - if (ps->cmd != nullstr) - free(ps->cmd); + argv = argptr; + while ((name = *argv++) != NULL) { + mklocal(name); } - if (jp->ps != &jp->ps0) - free(jp->ps); - jp->used = 0; - set_curjob(jp, CUR_DELETE); - INT_ON; + return 0; } +/* Forward declarations for builtintab[] */ #if JOBS -static void -xtcsetpgrp(int fd, pid_t pgrp) +static int fg_bgcmd(int, char **); +#endif +static int breakcmd(int, char **); +#if ENABLE_ASH_CMDCMD +static int commandcmd(int, char **); +#endif +static int dotcmd(int, char **); +static int evalcmd(int, char **); +#if ENABLE_ASH_BUILTIN_ECHO +static int echocmd(int, char **); +#endif +#if ENABLE_ASH_BUILTIN_TEST +static int testcmd(int, char **); +#endif +static int execcmd(int, char **); +static int exitcmd(int, char **); +static int exportcmd(int, char **); +static int falsecmd(int, char **); +#if ENABLE_ASH_GETOPTS +static int getoptscmd(int, char **); +#endif +static int hashcmd(int, char **); +#if !ENABLE_FEATURE_SH_EXTRA_QUIET +static int helpcmd(int argc, char **argv); +#endif +#if JOBS +static int jobscmd(int, char **); +#endif +#if ENABLE_ASH_MATH_SUPPORT +static int letcmd(int, char **); +#endif +static int pwdcmd(int, char **); +static int readcmd(int, char **); +static int returncmd(int, char **); +static int setcmd(int, char **); +static int shiftcmd(int, char **); +static int timescmd(int, char **); +static int trapcmd(int, char **); +static int truecmd(int, char **); +static int typecmd(int, char **); +static int umaskcmd(int, char **); +static int unsetcmd(int, char **); +static int waitcmd(int, char **); +static int ulimitcmd(int, char **); +#if JOBS +static int killcmd(int, char **); +#endif + +#define BUILTIN_NOSPEC "0" +#define BUILTIN_SPECIAL "1" +#define BUILTIN_REGULAR "2" +#define BUILTIN_SPEC_REG "3" +#define BUILTIN_ASSIGN "4" +#define BUILTIN_SPEC_ASSG "5" +#define BUILTIN_REG_ASSG "6" +#define BUILTIN_SPEC_REG_ASSG "7" + +/* make sure to keep these in proper order since it is searched via bsearch() */ +static const struct builtincmd builtintab[] = { + { BUILTIN_SPEC_REG ".", dotcmd }, + { BUILTIN_SPEC_REG ":", truecmd }, +#if ENABLE_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "[", testcmd }, + { BUILTIN_REGULAR "[[", testcmd }, +#endif +#if ENABLE_ASH_ALIAS + { BUILTIN_REG_ASSG "alias", aliascmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "bg", fg_bgcmd }, +#endif + { BUILTIN_SPEC_REG "break", breakcmd }, + { BUILTIN_REGULAR "cd", cdcmd }, + { BUILTIN_NOSPEC "chdir", cdcmd }, +#if ENABLE_ASH_CMDCMD + { BUILTIN_REGULAR "command", commandcmd }, +#endif + { BUILTIN_SPEC_REG "continue", breakcmd }, +#if ENABLE_ASH_BUILTIN_ECHO + { BUILTIN_REGULAR "echo", echocmd }, +#endif + { BUILTIN_SPEC_REG "eval", evalcmd }, + { BUILTIN_SPEC_REG "exec", execcmd }, + { BUILTIN_SPEC_REG "exit", exitcmd }, + { BUILTIN_SPEC_REG_ASSG "export", exportcmd }, + { BUILTIN_REGULAR "false", falsecmd }, +#if JOBS + { BUILTIN_REGULAR "fg", fg_bgcmd }, +#endif +#if ENABLE_ASH_GETOPTS + { BUILTIN_REGULAR "getopts", getoptscmd }, +#endif + { BUILTIN_NOSPEC "hash", hashcmd }, +#if !ENABLE_FEATURE_SH_EXTRA_QUIET + { BUILTIN_NOSPEC "help", helpcmd }, +#endif +#if JOBS + { BUILTIN_REGULAR "jobs", jobscmd }, + { BUILTIN_REGULAR "kill", killcmd }, +#endif +#if ENABLE_ASH_MATH_SUPPORT + { BUILTIN_NOSPEC "let", letcmd }, +#endif + { BUILTIN_ASSIGN "local", localcmd }, + { BUILTIN_NOSPEC "pwd", pwdcmd }, + { BUILTIN_REGULAR "read", readcmd }, + { BUILTIN_SPEC_REG_ASSG "readonly", exportcmd }, + { BUILTIN_SPEC_REG "return", returncmd }, + { BUILTIN_SPEC_REG "set", setcmd }, + { BUILTIN_SPEC_REG "shift", shiftcmd }, + { BUILTIN_SPEC_REG "source", dotcmd }, +#if ENABLE_ASH_BUILTIN_TEST + { BUILTIN_REGULAR "test", testcmd }, +#endif + { BUILTIN_SPEC_REG "times", timescmd }, + { BUILTIN_SPEC_REG "trap", trapcmd }, + { BUILTIN_REGULAR "true", truecmd }, + { BUILTIN_NOSPEC "type", typecmd }, + { BUILTIN_NOSPEC "ulimit", ulimitcmd }, + { BUILTIN_REGULAR "umask", umaskcmd }, +#if ENABLE_ASH_ALIAS + { BUILTIN_REGULAR "unalias", unaliascmd }, +#endif + { BUILTIN_SPEC_REG "unset", unsetcmd }, + { BUILTIN_REGULAR "wait", waitcmd }, +}; + +#define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0])) + +#define COMMANDCMD (builtintab + 5 + \ + 2 * ENABLE_ASH_BUILTIN_TEST + \ + ENABLE_ASH_ALIAS + \ + ENABLE_ASH_JOB_CONTROL) +#define EXECCMD (builtintab + 7 + \ + 2 * ENABLE_ASH_BUILTIN_TEST + \ + ENABLE_ASH_ALIAS + \ + ENABLE_ASH_JOB_CONTROL + \ + ENABLE_ASH_CMDCMD + \ + ENABLE_ASH_BUILTIN_ECHO) + +/* + * Search the table of builtin commands. + */ +static struct builtincmd * +find_builtin(const char *name) { - if (tcsetpgrp(fd, pgrp)) - ash_msg_and_raise_error("Cannot set tty process group (%m)"); + struct builtincmd *bp; + + bp = bsearch( + name, builtintab, NUMBUILTINS, sizeof(builtintab[0]), + pstrcmp + ); + return bp; } /* - * Turn job control on and off. - * - * Note: This code assumes that the third arg to ioctl is a character - * pointer, which is true on Berkeley systems but not System V. Since - * System V doesn't have job control yet, this isn't a problem now. - * - * Called with interrupts off. + * Resolve a command name. If you change this routine, you may have to + * change the shellexec routine as well. */ static void -setjobctl(int on) +find_command(char *name, struct cmdentry *entry, int act, const char *path) { - int fd; - int pgrp; - - if (on == jobctl || rootshell == 0) - return; - if (on) { - int ofd; - ofd = fd = open(_PATH_TTY, O_RDWR); - if (fd < 0) { - /* BTW, bash will try to open(ttyname(0)) if open("/dev/tty") fails. - * That sometimes helps to acquire controlling tty. - * Obviously, a workaround for bugs when someone - * failed to provide a controlling tty to bash! :) */ - fd += 3; - while (!isatty(fd) && --fd >= 0) - ; - } - fd = fcntl(fd, F_DUPFD, 10); - close(ofd); - if (fd < 0) - goto out; - fcntl(fd, F_SETFD, FD_CLOEXEC); - do { /* while we are in the background */ - pgrp = tcgetpgrp(fd); - if (pgrp < 0) { - out: - ash_msg("can't access tty; job control turned off"); - mflag = on = 0; - goto close; + struct tblentry *cmdp; + int idx; + int prev; + char *fullname; + struct stat statb; + int e; + int updatetbl; + struct builtincmd *bcmd; + + /* If name contains a slash, don't use PATH or hash table */ + if (strchr(name, '/') != NULL) { + entry->u.index = -1; + if (act & DO_ABS) { + while (stat(name, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + entry->cmdtype = CMDUNKNOWN; + return; } - if (pgrp == getpgrp()) - break; - killpg(0, SIGTTIN); - } while (1); - initialpgrp = pgrp; + } + entry->cmdtype = CMDNORMAL; + return; + } - setsignal(SIGTSTP); - setsignal(SIGTTOU); - setsignal(SIGTTIN); - pgrp = rootpid; - setpgid(0, pgrp); - xtcsetpgrp(fd, pgrp); - } else { - /* turning job control off */ - fd = ttyfd; - pgrp = initialpgrp; - xtcsetpgrp(fd, pgrp); - setpgid(0, pgrp); - setsignal(SIGTSTP); - setsignal(SIGTTOU); - setsignal(SIGTTIN); - close: - close(fd); - fd = -1; +#if ENABLE_FEATURE_SH_STANDALONE_SHELL + if (find_applet_by_name(name)) { + entry->cmdtype = CMDNORMAL; + entry->u.index = -1; + return; } - ttyfd = fd; - jobctl = on; -} +#endif -static int -killcmd(int argc, char **argv) -{ - int signo = -1; - int list = 0; - int i; - pid_t pid; - struct job *jp; + if (is_safe_applet(name)) { + entry->cmdtype = CMDNORMAL; + entry->u.index = -1; + return; + } - if (argc <= 1) { - usage: - ash_msg_and_raise_error( -"Usage: kill [-s sigspec | -signum | -sigspec] [pid | job]... or\n" -"kill -l [exitstatus]" - ); + updatetbl = (path == pathval()); + if (!updatetbl) { + act |= DO_ALTPATH; + if (strstr(path, "%builtin") != NULL) + act |= DO_ALTBLTIN; } - if (**++argv == '-') { - signo = get_signum(*argv + 1); - if (signo < 0) { - int c; + /* If name is in the table, check answer will be ok */ + cmdp = cmdlookup(name, 0); + if (cmdp != NULL) { + int bit; - while ((c = nextopt("ls:")) != '\0') { - switch (c) { - default: + switch (cmdp->cmdtype) { + default: #if DEBUG - abort(); + abort(); #endif - case 'l': - list = 1; - break; - case 's': - signo = get_signum(optionarg); - if (signo < 0) { - ash_msg_and_raise_error( - "invalid signal number or name: %s", - optionarg - ); - } - break; - } - } - argv = argptr; - } else - argv++; + case CMDNORMAL: + bit = DO_ALTPATH; + break; + case CMDFUNCTION: + bit = DO_NOFUNC; + break; + case CMDBUILTIN: + bit = DO_ALTBLTIN; + break; + } + if (act & bit) { + updatetbl = 0; + cmdp = NULL; + } else if (cmdp->rehash == 0) + /* if not invalidated by cd, we're done */ + goto success; } - if (!list && signo < 0) - signo = SIGTERM; + /* If %builtin not in path, check for builtin next */ + bcmd = find_builtin(name); + if (bcmd && (IS_BUILTIN_REGULAR(bcmd) || ( + act & DO_ALTPATH ? !(act & DO_ALTBLTIN) : builtinloc <= 0 + ))) + goto builtin_success; - if ((signo < 0 || !*argv) ^ list) { - goto usage; + /* We have to search path. */ + prev = -1; /* where to start */ + if (cmdp && cmdp->rehash) { /* doing a rehash */ + if (cmdp->cmdtype == CMDBUILTIN) + prev = builtinloc; + else + prev = cmdp->param.index; } - if (list) { - const char *name; - - if (!*argv) { - for (i = 1; i < NSIG; i++) { - name = get_signame(i); - if (isdigit(*name)) - out1fmt(snlfmt, name); + e = ENOENT; + idx = -1; + loop: + while ((fullname = padvance(&path, name)) != NULL) { + stunalloc(fullname); + idx++; + if (pathopt) { + if (prefix(pathopt, "builtin")) { + if (bcmd) + goto builtin_success; + continue; + } else if (!(act & DO_NOFUNC) && + prefix(pathopt, "func")) { + /* handled below */ + } else { + /* ignore unimplemented options */ + continue; } - return 0; } - name = get_signame(signo); - if (!isdigit(*name)) - ash_msg_and_raise_error("invalid signal number or exit status: %s", *argptr); - out1fmt(snlfmt, name); - return 0; - } - - i = 0; - do { - if (**argv == '%') { - jp = getjob(*argv, 0); - pid = -jp->ps[0].pid; - } else { - pid = **argv == '-' ? - -number(*argv + 1) : number(*argv); + /* if rehash, don't redo absolute path names */ + if (fullname[0] == '/' && idx <= prev) { + if (idx < prev) + continue; + TRACE(("searchexec \"%s\": no change\n", name)); + goto success; } - if (kill(pid, signo) != 0) { - ash_msg("(%d) - %m", pid); - i = 1; + while (stat(fullname, &statb) < 0) { +#ifdef SYSV + if (errno == EINTR) + continue; +#endif + if (errno != ENOENT && errno != ENOTDIR) + e = errno; + goto loop; } - } while (*++argv); - - return i; -} + e = EACCES; /* if we fail, this will be the error */ + if (!S_ISREG(statb.st_mode)) + continue; + if (pathopt) { /* this is a %func directory */ + stalloc(strlen(fullname) + 1); + readcmdfile(fullname); + cmdp = cmdlookup(name, 0); + if (cmdp == NULL || cmdp->cmdtype != CMDFUNCTION) + ash_msg_and_raise_error("%s not defined in %s", name, fullname); + stunalloc(fullname); + goto success; + } + TRACE(("searchexec \"%s\" returns \"%s\"\n", name, fullname)); + if (!updatetbl) { + entry->cmdtype = CMDNORMAL; + entry->u.index = idx; + return; + } + INT_OFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDNORMAL; + cmdp->param.index = idx; + INT_ON; + goto success; + } -static void -showpipe(struct job *jp, FILE *out) -{ - struct procstat *sp; - struct procstat *spend; + /* We failed. If there was an entry for this command, delete it */ + if (cmdp && updatetbl) + delete_cmd_entry(); + if (act & DO_ERR) + ash_msg("%s: %s", name, errmsg(e, "not found")); + entry->cmdtype = CMDUNKNOWN; + return; - spend = jp->ps + jp->nprocs; - for (sp = jp->ps + 1; sp < spend; sp++) - fprintf(out, " | %s", sp->cmd); - outcslow('\n', out); - flush_stdout_stderr(); + builtin_success: + if (!updatetbl) { + entry->cmdtype = CMDBUILTIN; + entry->u.cmd = bcmd; + return; + } + INT_OFF; + cmdp = cmdlookup(name, 1); + cmdp->cmdtype = CMDBUILTIN; + cmdp->param.cmd = bcmd; + INT_ON; + success: + cmdp->rehash = 0; + entry->cmdtype = cmdp->cmdtype; + entry->u = cmdp->param; } - +/* + * Execute a simple command. + */ +static int back_exitstatus; /* exit status of backquoted command */ static int -restartjob(struct job *jp, int mode) +isassignment(const char *p) { - struct procstat *ps; - int i; - int status; - pid_t pgid; - - INT_OFF; - if (jp->state == JOBDONE) - goto out; - jp->state = JOBRUNNING; - pgid = jp->ps->pid; - if (mode == FORK_FG) - xtcsetpgrp(ttyfd, pgid); - killpg(pgid, SIGCONT); - ps = jp->ps; - i = jp->nprocs; - do { - if (WIFSTOPPED(ps->status)) { - ps->status = -1; - } - } while (ps++, --i); - out: - status = (mode == FORK_FG) ? waitforjob(jp) : 0; - INT_ON; - return status; + const char *q = endofname(p); + if (p == q) + return 0; + return *q == '='; } - static int -fg_bgcmd(int argc, char **argv) +bltincmd(int argc, char **argv) +{ + /* Preserve exitstatus of a previous possible redirection + * as POSIX mandates */ + return back_exitstatus; +} +static void +evalcommand(union node *cmd, int flags) { + static const struct builtincmd bltin = { + "\0\0", bltincmd + }; + struct stackmark smark; + union node *argp; + struct arglist arglist; + struct arglist varlist; + char **argv; + int argc; + const struct strlist *sp; + struct cmdentry cmdentry; struct job *jp; - FILE *out; - int mode; - int retval; + char *lastarg; + const char *path; + int spclbltin; + int cmd_is_exec; + int status; + char **nargv; + struct builtincmd *bcmd; + int pseudovarflag = 0; - mode = (**argv == 'f') ? FORK_FG : FORK_BG; - nextopt(nullstr); - argv = argptr; - out = stdout; - do { - jp = getjob(*argv, 1); - if (mode == FORK_BG) { - set_curjob(jp, CUR_RUNNING); - fprintf(out, "[%d] ", jobno(jp)); - } - outstr(jp->ps->cmd, out); - showpipe(jp, out); - retval = restartjob(jp, mode); - } while (*argv && *++argv); - return retval; -} -#endif + /* First expand the arguments. */ + TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); + setstackmark(&smark); + back_exitstatus = 0; -static int -sprint_status(char *s, int status, int sigonly) -{ - int col; - int st; + cmdentry.cmdtype = CMDBUILTIN; + cmdentry.u.cmd = &bltin; + varlist.lastp = &varlist.list; + *varlist.lastp = NULL; + arglist.lastp = &arglist.list; + *arglist.lastp = NULL; - col = 0; - if (!WIFEXITED(status)) { -#if JOBS - if (WIFSTOPPED(status)) - st = WSTOPSIG(status); - else -#endif - st = WTERMSIG(status); - if (sigonly) { - if (st == SIGINT || st == SIGPIPE) - goto out; -#if JOBS - if (WIFSTOPPED(status)) - goto out; -#endif - } - st &= 0x7f; - col = fmtstr(s, 32, strsignal(st)); - if (WCOREDUMP(status)) { - col += fmtstr(s + col, 16, " (core dumped)"); - } - } else if (!sigonly) { - st = WEXITSTATUS(status); - if (st) - col = fmtstr(s, 16, "Done(%d)", st); + argc = 0; + if (cmd->ncmd.args) { + bcmd = find_builtin(cmd->ncmd.args->narg.text); + pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); + } + + for (argp = cmd->ncmd.args; argp; argp = argp->narg.next) { + struct strlist **spp; + + spp = arglist.lastp; + if (pseudovarflag && isassignment(argp->narg.text)) + expandarg(argp, &arglist, EXP_VARTILDE); else - col = fmtstr(s, 16, "Done"); + expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); + + for (sp = *spp; sp; sp = sp->next) + argc++; } - out: - return col; -} -/* - * Do a wait system call. If job control is compiled in, we accept - * stopped processes. If block is zero, we return a value of zero - * rather than blocking. - * - * System V doesn't have a non-blocking wait system call. It does - * have a SIGCLD signal that is sent to a process when one of it's - * children dies. The obvious way to use SIGCLD would be to install - * a handler for SIGCLD which simply bumped a counter when a SIGCLD - * was received, and have waitproc bump another counter when it got - * the status of a process. Waitproc would then know that a wait - * system call would not block if the two counters were different. - * This approach doesn't work because if a process has children that - * have not been waited for, System V will send it a SIGCLD when it - * installs a signal handler for SIGCLD. What this means is that when - * a child exits, the shell will be sent SIGCLD signals continuously - * until is runs out of stack space, unless it does a wait call before - * restoring the signal handler. The code below takes advantage of - * this (mis)feature by installing a signal handler for SIGCLD and - * then checking to see whether it was called. If there are any - * children to be waited for, it will be. - * - * If neither SYSV nor BSD is defined, we don't implement nonblocking - * waits at all. In this case, the user will not be informed when - * a background process until the next time she runs a real program - * (as opposed to running a builtin command or just typing return), - * and the jobs command may give out of date information. - */ -static int -waitproc(int block, int *status) -{ - int flags = 0; + argv = nargv = stalloc(sizeof(char *) * (argc + 1)); + for (sp = arglist.list; sp; sp = sp->next) { + TRACE(("evalcommand arg: %s\n", sp->text)); + *nargv++ = sp->text; + } + *nargv = NULL; -#if JOBS - if (jobctl) - flags |= WUNTRACED; -#endif - if (block == 0) - flags |= WNOHANG; - return wait3(status, flags, (struct rusage *)NULL); -} + lastarg = NULL; + if (iflag && funcnest == 0 && argc > 0) + lastarg = nargv[-1]; -/* - * Wait for a process to terminate. - */ -static int -dowait(int block, struct job *job) -{ - int pid; - int status; - struct job *jp; - struct job *thisjob; - int state; + preverrout_fd = 2; + expredir(cmd->ncmd.redirect); + status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); - TRACE(("dowait(%d) called\n", block)); - pid = waitproc(block, &status); - TRACE(("wait returns pid %d, status=%d\n", pid, status)); - if (pid <= 0) - return pid; - INT_OFF; - thisjob = NULL; - for (jp = curjob; jp; jp = jp->prev_job) { - struct procstat *sp; - struct procstat *spend; - if (jp->state == JOBDONE) - continue; - state = JOBDONE; - spend = jp->ps + jp->nprocs; - sp = jp->ps; - do { - if (sp->pid == pid) { - TRACE(("Job %d: changing status of proc %d " - "from 0x%x to 0x%x\n", - jobno(jp), pid, sp->status, status)); - sp->status = status; - thisjob = jp; - } - if (sp->status == -1) - state = JOBRUNNING; -#if JOBS - if (state == JOBRUNNING) - continue; - if (WIFSTOPPED(sp->status)) { - jp->stopstatus = sp->status; - state = JOBSTOPPED; + path = vpath.text; + for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { + struct strlist **spp; + char *p; + + spp = varlist.lastp; + expandarg(argp, &varlist, EXP_VARTILDE); + + /* + * Modify the command lookup path, if a PATH= assignment + * is present + */ + p = (*spp)->text; + if (varequal(p, path)) + path = p; + } + + /* Print the command if xflag is set. */ + if (xflag) { + int n; + const char *p = " %s"; + + p++; + dprintf(preverrout_fd, p, expandstr(ps4val())); + + sp = varlist.list; + for (n = 0; n < 2; n++) { + while (sp) { + dprintf(preverrout_fd, p, sp->text); + sp = sp->next; + if (*p == '%') { + p--; + } } -#endif - } while (++sp < spend); - if (thisjob) - goto gotjob; + sp = arglist.list; + } + full_write(preverrout_fd, "\n", 1); } -#if JOBS - if (!WIFSTOPPED(status)) -#endif - jobless--; - goto out; + cmd_is_exec = 0; + spclbltin = -1; - gotjob: - if (state != JOBRUNNING) { - thisjob->changed = 1; + /* Now locate the command. */ + if (argc) { + const char *oldpath; + int cmd_flag = DO_ERR; - if (thisjob->state != state) { - TRACE(("Job %d: changing state from %d to %d\n", - jobno(thisjob), thisjob->state, state)); - thisjob->state = state; -#if JOBS - if (state == JOBSTOPPED) { - set_curjob(thisjob, CUR_STOPPED); + path += 5; + oldpath = path; + for (;;) { + find_command(argv[0], &cmdentry, cmd_flag, path); + if (cmdentry.cmdtype == CMDUNKNOWN) { + status = 127; + flush_stderr(); + goto bail; } + + /* implement bltin and command here */ + if (cmdentry.cmdtype != CMDBUILTIN) + break; + if (spclbltin < 0) + spclbltin = IS_BUILTIN_SPECIAL(cmdentry.u.cmd); + if (cmdentry.u.cmd == EXECCMD) + cmd_is_exec++; +#if ENABLE_ASH_CMDCMD + if (cmdentry.u.cmd == COMMANDCMD) { + path = oldpath; + nargv = parse_command_args(argv, &path); + if (!nargv) + break; + argc -= nargv - argv; + argv = nargv; + cmd_flag |= DO_NOFUNC; + } else #endif + break; } } - out: - INT_ON; + if (status) { + /* We have a redirection error. */ + if (spclbltin > 0) + raise_exception(EXERROR); + bail: + exitstatus = status; + goto out; + } - if (thisjob && thisjob == job) { - char s[48 + 1]; - int len; + /* Execute the command. */ + switch (cmdentry.cmdtype) { + default: + /* Fork off a child process if necessary. */ + if (!(flags & EV_EXIT) || trap[0]) { + INT_OFF; + jp = makejob(cmd, 1); + if (forkshell(jp, cmd, FORK_FG) != 0) { + exitstatus = waitforjob(jp); + INT_ON; + break; + } + FORCE_INT_ON; + } + listsetvar(varlist.list, VEXPORT|VSTACK); + shellexec(argv, path, cmdentry.u.index); + /* NOTREACHED */ - len = sprint_status(s, status, 1); - if (len) { - s[len] = '\n'; - s[len + 1] = 0; - out2str(s); + case CMDBUILTIN: + cmdenviron = varlist.list; + if (cmdenviron) { + struct strlist *list = cmdenviron; + int i = VNOSET; + if (spclbltin > 0 || argc == 0) { + i = 0; + if (cmd_is_exec && argc > 1) + i = VEXPORT; + } + listsetvar(list, i); + } + if (evalbltin(cmdentry.u.cmd, argc, argv)) { + int exit_status; + int i, j; + + i = exception; + if (i == EXEXIT) + goto raise; + + exit_status = 2; + j = 0; + if (i == EXINT) + j = SIGINT; + if (i == EXSIG) + j = pendingsigs; + if (j) + exit_status = j + 128; + exitstatus = exit_status; + + if (i == EXINT || spclbltin > 0) { + raise: + longjmp(exception_handler->loc, 1); + } + FORCE_INT_ON; } + break; + + case CMDFUNCTION: + listsetvar(varlist.list, 0); + if (evalfun(cmdentry.u.func, argc, argv, flags)) + goto raise; + break; } - return pid; + + out: + popredir(cmd_is_exec); + if (lastarg) + /* dsl: I think this is intended to be used to support + * '_' in 'vi' command mode during line editing... + * However I implemented that within libedit itself. + */ + setvar("_", lastarg, 0); + popstackmark(&smark); } -#if JOBS -static void -showjob(FILE *out, struct job *jp, int mode) +static int +evalbltin(const struct builtincmd *cmd, int argc, char **argv) { - struct procstat *ps; - struct procstat *psend; - int col; - int indent; - char s[80]; + char *volatile savecmdname; + struct jmploc *volatile savehandler; + struct jmploc jmploc; + int i; - ps = jp->ps; + savecmdname = commandname; + i = setjmp(jmploc.loc); + if (i) + goto cmddone; + savehandler = exception_handler; + exception_handler = &jmploc; + commandname = argv[0]; + argptr = argv + 1; + optptr = NULL; /* initialize nextopt */ + exitstatus = (*cmd->builtin)(argc, argv); + flush_stdout_stderr(); + cmddone: + exitstatus |= ferror(stdout); + clearerr(stdout); + commandname = savecmdname; + exsig = 0; + exception_handler = savehandler; - if (mode & SHOW_PGID) { - /* just output process (group) id of pipeline */ - fprintf(out, "%d\n", ps->pid); - return; - } + return i; +} - col = fmtstr(s, 16, "[%d] ", jobno(jp)); - indent = col; +static int +goodname(const char *p) +{ + return !*endofname(p); +} - if (jp == curjob) - s[col - 2] = '+'; - else if (curjob && jp == curjob->prev_job) - s[col - 2] = '-'; - if (mode & SHOW_PID) - col += fmtstr(s + col, 16, "%d ", ps->pid); +/* + * Search for a command. This is called before we fork so that the + * location of the command will be available in the parent as well as + * the child. The check for "goodname" is an overly conservative + * check that the name will not be subject to expansion. + */ +static void +prehash(union node *n) +{ + struct cmdentry entry; - psend = ps + jp->nprocs; + if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text)) + find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); +} - if (jp->state == JOBRUNNING) { - strcpy(s + col, "Running"); - col += sizeof("Running") - 1; - } else { - int status = psend[-1].status; - if (jp->state == JOBSTOPPED) - status = jp->stopstatus; - col += sprint_status(s + col, status, 0); - } - goto start; +/* + * Builtin commands. Builtin commands whose functions are closely + * tied to evaluation are implemented here. + */ - do { - /* for each process */ - col = fmtstr(s, 48, " |\n%*c%d ", indent, ' ', ps->pid) - 3; - start: - fprintf(out, "%s%*c%s", - s, 33 - col >= 0 ? 33 - col : 0, ' ', ps->cmd - ); - if (!(mode & SHOW_PID)) { - showpipe(jp, out); - break; - } - if (++ps == psend) { - outcslow('\n', out); - break; - } - } while (1); +/* + * Handle break and continue commands. Break, continue, and return are + * all handled by setting the evalskip flag. The evaluation routines + * above all check this flag, and if it is set they start skipping + * commands rather than executing them. The variable skipcount is + * the number of loops to break/continue, or the number of function + * levels to return. (The latter is always 1.) It should probably + * be an error to break out of more loops than exist, but it isn't + * in the standard shell so we don't make it one here. + */ - jp->changed = 0; +static int +breakcmd(int argc, char **argv) +{ + int n = argc > 1 ? number(argv[1]) : 1; - if (jp->state == JOBDONE) { - TRACE(("showjob: freeing job %d\n", jobno(jp))); - freejob(jp); + if (n <= 0) + ash_msg_and_raise_error(illnum, argv[1]); + if (n > loopnest) + n = loopnest; + if (n > 0) { + evalskip = (**argv == 'c')? SKIPCONT : SKIPBREAK; + skipcount = n; } + return 0; } +/* + * The return command. + */ static int -jobscmd(int argc, char **argv) +returncmd(int argc, char **argv) { - int mode, m; - FILE *out; + /* + * If called outside a function, do what ksh does; + * skip the rest of the file. + */ + evalskip = funcnest ? SKIPFUNC : SKIPFILE; + return argv[1] ? number(argv[1]) : exitstatus; +} - mode = 0; - while ((m = nextopt("lp"))) { - if (m == 'l') - mode = SHOW_PID; - else - mode = SHOW_PGID; - } +static int +falsecmd(int argc, char **argv) +{ + return 1; +} - out = stdout; - argv = argptr; - if (*argv) { - do - showjob(out, getjob(*argv,0), mode); - while (*++argv); - } else - showjobs(out, mode); +static int +truecmd(int argc, char **argv) +{ + return 0; +} +static int +execcmd(int argc, char **argv) +{ + if (argc > 1) { + iflag = 0; /* exit on error */ + mflag = 0; + optschanged(); + shellexec(argv + 1, pathval(), 0); + } return 0; } + +/* ============ input.c + * + * This implements the input routines used by the parser. + */ + +#define EOF_NLEFT -99 /* value of parsenleft when EOF pushed back */ + +enum { + INPUT_PUSH_FILE = 1, + INPUT_NOFILE_OK = 2, +}; + /* - * Print a list of jobs. If "change" is nonzero, only print jobs whose - * statuses have changed since the last call to showjobs. + * NEOF is returned by parsecmd when it encounters an end of file. It + * must be distinct from NULL, so we use the address of a variable that + * happens to be handy. */ -static void -showjobs(FILE *out, int mode) -{ - struct job *jp; +static int plinno = 1; /* input line number */ +/* number of characters left in input buffer */ +static int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ +/* next character in input buffer */ +static char *parsenextc; /* copy of parsefile->nextc */ - TRACE(("showjobs(%x) called\n", mode)); +static int checkkwd; +/* values of checkkwd variable */ +#define CHKALIAS 0x1 +#define CHKKWD 0x2 +#define CHKNL 0x4 - /* If not even one one job changed, there is nothing to do */ - while (dowait(DOWAIT_NORMAL, NULL) > 0) - continue; - for (jp = curjob; jp; jp = jp->prev_job) { - if (!(mode & SHOW_CHANGED) || jp->changed) - showjob(out, jp, mode); +static void +popstring(void) +{ + struct strpush *sp = parsefile->strpush; + + INT_OFF; +#if ENABLE_ASH_ALIAS + if (sp->ap) { + if (parsenextc[-1] == ' ' || parsenextc[-1] == '\t') { + checkkwd |= CHKALIAS; + } + if (sp->string != sp->ap->val) { + free(sp->string); + } + sp->ap->flag &= ~ALIASINUSE; + if (sp->ap->flag & ALIASDEAD) { + unalias(sp->ap->name); + } } +#endif + parsenextc = sp->prevstring; + parsenleft = sp->prevnleft; +/*dprintf("*** calling popstring: restoring to '%s'\n", parsenextc);*/ + parsefile->strpush = sp->prev; + if (sp != &(parsefile->basestrpush)) + free(sp); + INT_ON; } -#endif /* JOBS */ static int -getstatus(struct job *job) +preadfd(void) { - int status; - int retval; + int nr; + char *buf = parsefile->buf; + parsenextc = buf; - status = job->ps[job->nprocs - 1].status; - retval = WEXITSTATUS(status); - if (!WIFEXITED(status)) { -#if JOBS - retval = WSTOPSIG(status); - if (!WIFSTOPPED(status)) + retry: +#if ENABLE_FEATURE_EDITING + if (!iflag || parsefile->fd) + nr = safe_read(parsefile->fd, buf, BUFSIZ - 1); + else { +#if ENABLE_FEATURE_TAB_COMPLETION + line_input_state->path_lookup = pathval(); #endif - { - /* XXX: limits number of signals */ - retval = WTERMSIG(status); -#if JOBS - if (retval == SIGINT) - job->sigint = 1; + nr = read_line_input(cmdedit_prompt, buf, BUFSIZ, line_input_state); + if (nr == 0) { + /* Ctrl+C pressed */ + if (trap[SIGINT]) { + buf[0] = '\n'; + buf[1] = '\0'; + raise(SIGINT); + return 1; + } + goto retry; + } + if (nr < 0 && errno == 0) { + /* Ctrl+D presend */ + nr = 0; + } + } +#else + nr = safe_read(parsefile->fd, buf, BUFSIZ - 1); #endif + + if (nr < 0) { + if (parsefile->fd == 0 && errno == EWOULDBLOCK) { + int flags = fcntl(0, F_GETFL, 0); + if (flags >= 0 && flags & O_NONBLOCK) { + flags &=~ O_NONBLOCK; + if (fcntl(0, F_SETFL, flags) >= 0) { + out2str("sh: turning off NDELAY mode\n"); + goto retry; + } + } } - retval += 128; } - TRACE(("getstatus: job %d, nproc %d, status %x, retval %x\n", - jobno(job), job->nprocs, status, retval)); - return retval; + return nr; } +/* + * Refill the input buffer and return the next input character: + * + * 1) If a string was pushed back on the input, pop it; + * 2) If an EOF was pushed back (parsenleft == EOF_NLEFT) or we are reading + * from a string so we can't refill the buffer, return EOF. + * 3) If the is more stuff in this buffer, use it else call read to fill it. + * 4) Process input up to the next newline, deleting nul characters. + */ static int -waitcmd(int argc, char **argv) +preadbuffer(void) { - struct job *job; - int retval; - struct job *jp; - - EXSIGON; + char *q; + int more; + char savec; - nextopt(nullstr); - retval = 0; + while (parsefile->strpush) { +#if ENABLE_ASH_ALIAS + if (parsenleft == -1 && parsefile->strpush->ap && + parsenextc[-1] != ' ' && parsenextc[-1] != '\t') { + return PEOA; + } +#endif + popstring(); + if (--parsenleft >= 0) + return SC2INT(*parsenextc++); + } + if (parsenleft == EOF_NLEFT || parsefile->buf == NULL) + return PEOF; + flush_stdout_stderr(); - argv = argptr; - if (!*argv) { - /* wait for all jobs */ - for (;;) { - jp = curjob; - while (1) { - if (!jp) { - /* no running procs */ - goto out; - } - if (jp->state == JOBRUNNING) - break; - jp->waited = 1; - jp = jp->prev_job; - } - dowait(DOWAIT_BLOCK, 0); + more = parselleft; + if (more <= 0) { + again: + more = preadfd(); + if (more <= 0) { + parselleft = parsenleft = EOF_NLEFT; + return PEOF; } } - retval = 127; - do { - if (**argv != '%') { - pid_t pid = number(*argv); - job = curjob; - goto start; - do { - if (job->ps[job->nprocs - 1].pid == pid) - break; - job = job->prev_job; - start: - if (!job) - goto repeat; - } while (1); - } else - job = getjob(*argv, 0); - /* loop until process terminated or stopped */ - while (job->state == JOBRUNNING) - dowait(DOWAIT_BLOCK, 0); - job->waited = 1; - retval = getstatus(job); - repeat: - ; - } while (*++argv); - - out: - return retval; -} + q = parsenextc; -static struct job * -growjobtab(void) -{ - size_t len; - ptrdiff_t offset; - struct job *jp, *jq; + /* delete nul characters */ + for (;;) { + int c; - len = njobs * sizeof(*jp); - jq = jobtab; - jp = ckrealloc(jq, len + 4 * sizeof(*jp)); + more--; + c = *q; - offset = (char *)jp - (char *)jq; - if (offset) { - /* Relocate pointers */ - size_t l = len; + if (!c) + memmove(q, q + 1, more); + else { + q++; + if (c == '\n') { + parsenleft = q - parsenextc - 1; + break; + } + } - jq = (struct job *)((char *)jq + l); - while (l) { - l -= sizeof(*jp); - jq--; -#define joff(p) ((struct job *)((char *)(p) + l)) -#define jmove(p) (p) = (void *)((char *)(p) + offset) - if (joff(jp)->ps == &jq->ps0) - jmove(joff(jp)->ps); - if (joff(jp)->prev_job) - jmove(joff(jp)->prev_job); + if (more <= 0) { + parsenleft = q - parsenextc - 1; + if (parsenleft < 0) + goto again; + break; } - if (curjob) - jmove(curjob); -#undef joff -#undef jmove } + parselleft = more; - njobs += 4; - jobtab = jp; - jp = (struct job *)((char *)jp + len); - jq = jp + 3; - do { - jq->used = 0; - } while (--jq >= jp); - return jp; + savec = *q; + *q = '\0'; + + if (vflag) { + out2str(parsenextc); + } + + *q = savec; + + return SC2INT(*parsenextc++); } +#define pgetc_as_macro() (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer()) + +#if ENABLE_ASH_OPTIMIZE_FOR_SIZE +#define pgetc_macro() pgetc() +static int +pgetc(void) +{ + return pgetc_as_macro(); +} +#else +#define pgetc_macro() pgetc_as_macro() +static int +pgetc(void) +{ + return pgetc_macro(); +} +#endif + /* - * Return a new job structure. - * Called with interrupts off. + * Same as pgetc(), but ignores PEOA. */ -static struct job * -makejob(union node *node, int nprocs) +#if ENABLE_ASH_ALIAS +static int +pgetc2(void) { - int i; - struct job *jp; + int c; - for (i = njobs, jp = jobtab; ; jp++) { - if (--i < 0) { - jp = growjobtab(); - break; - } - if (jp->used == 0) - break; - if (jp->state != JOBDONE || !jp->waited) - continue; -#if JOBS - if (jobctl) - continue; -#endif - freejob(jp); - break; - } - memset(jp, 0, sizeof(*jp)); -#if JOBS - if (jobctl) - jp->jobctl = 1; -#endif - jp->prev_job = curjob; - curjob = jp; - jp->used = 1; - jp->ps = &jp->ps0; - if (nprocs > 1) { - jp->ps = ckmalloc(nprocs * sizeof(struct procstat)); - } - TRACE(("makejob(0x%lx, %d) returns %%%d\n", (long)node, nprocs, - jobno(jp))); - return jp; + do { + c = pgetc_macro(); + } while (c == PEOA); + return c; } +#else +static int +pgetc2(void) +{ + return pgetc_macro(); +} +#endif -#if JOBS /* - * Return a string identifying a command (to be printed by the - * jobs command). + * Read a line from the script. */ -static char *cmdnextc; - -static void -cmdputs(const char *s) +static char * +pfgets(char *line, int len) { - const char *p, *str; - char c, cc[2] = " "; - char *nextc; - int subtype = 0; - int quoted = 0; - static const char vstype[VSTYPE + 1][4] = { - "", "}", "-", "+", "?", "=", - "%", "%%", "#", "##" - }; + char *p = line; + int nleft = len; + int c; - nextc = makestrspace((strlen(s) + 1) * 8, cmdnextc); - p = s; - while ((c = *p++) != 0) { - str = 0; - switch (c) { - case CTLESC: - c = *p++; - break; - case CTLVAR: - subtype = *p++; - if ((subtype & VSTYPE) == VSLENGTH) - str = "${#"; - else - str = "${"; - if (!(subtype & VSQUOTE) == !(quoted & 1)) - goto dostr; - quoted ^= 1; - c = '"'; - break; - case CTLENDVAR: - str = "\"}" + !(quoted & 1); - quoted >>= 1; - subtype = 0; - goto dostr; - case CTLBACKQ: - str = "$(...)"; - goto dostr; - case CTLBACKQ+CTLQUOTE: - str = "\"$(...)\""; - goto dostr; -#if ENABLE_ASH_MATH_SUPPORT - case CTLARI: - str = "$(("; - goto dostr; - case CTLENDARI: - str = "))"; - goto dostr; -#endif - case CTLQUOTEMARK: - quoted ^= 1; - c = '"'; - break; - case '=': - if (subtype == 0) - break; - if ((subtype & VSTYPE) != VSNORMAL) - quoted <<= 1; - str = vstype[subtype & VSTYPE]; - if (subtype & VSNUL) - c = ':'; - else - goto checkstr; - break; - case '\'': - case '\\': - case '"': - case '$': - /* These can only happen inside quotes */ - cc[0] = c; - str = cc; - c = '\\'; - break; - default: + while (--nleft > 0) { + c = pgetc2(); + if (c == PEOF) { + if (p == line) + return NULL; break; } - USTPUTC(c, nextc); - checkstr: - if (!str) - continue; - dostr: - while ((c = *str++)) { - USTPUTC(c, nextc); - } - } - if (quoted & 1) { - USTPUTC('"', nextc); + *p++ = c; + if (c == '\n') + break; } - *nextc = 0; - cmdnextc = nextc; + *p = '\0'; + return line; } -/* cmdtxt() and cmdlist() call each other */ -static void cmdtxt(union node *n); - +/* + * Undo the last call to pgetc. Only one character may be pushed back. + * PEOF may be pushed back. + */ static void -cmdlist(union node *np, int sep) +pungetc(void) { - for (; np; np = np->narg.next) { - if (!sep) - cmdputs(spcstr); - cmdtxt(np); - if (sep && np->narg.next) - cmdputs(spcstr); - } + parsenleft++; + parsenextc--; } +/* + * Push a string back onto the input at this current parsefile level. + * We handle aliases this way. + */ static void -cmdtxt(union node *n) +pushstring(char *s, void *ap) { - union node *np; - struct nodelist *lp; - const char *p; - char s[2]; + struct strpush *sp; + size_t len; - if (!n) - return; - switch (n->type) { - default: -#if DEBUG - abort(); -#endif - case NPIPE: - lp = n->npipe.cmdlist; - for (;;) { - cmdtxt(lp->n); - lp = lp->next; - if (!lp) - break; - cmdputs(" | "); - } - break; - case NSEMI: - p = "; "; - goto binop; - case NAND: - p = " && "; - goto binop; - case NOR: - p = " || "; - binop: - cmdtxt(n->nbinary.ch1); - cmdputs(p); - n = n->nbinary.ch2; - goto donode; - case NREDIR: - case NBACKGND: - n = n->nredir.n; - goto donode; - case NNOT: - cmdputs("!"); - n = n->nnot.com; - donode: - cmdtxt(n); - break; - case NIF: - cmdputs("if "); - cmdtxt(n->nif.test); - cmdputs("; then "); - n = n->nif.ifpart; - if (n->nif.elsepart) { - cmdtxt(n); - cmdputs("; else "); - n = n->nif.elsepart; - } - p = "; fi"; - goto dotail; - case NSUBSHELL: - cmdputs("("); - n = n->nredir.n; - p = ")"; - goto dotail; - case NWHILE: - p = "while "; - goto until; - case NUNTIL: - p = "until "; - until: - cmdputs(p); - cmdtxt(n->nbinary.ch1); - n = n->nbinary.ch2; - p = "; done"; - dodo: - cmdputs("; do "); - dotail: - cmdtxt(n); - goto dotail2; - case NFOR: - cmdputs("for "); - cmdputs(n->nfor.var); - cmdputs(" in "); - cmdlist(n->nfor.args, 1); - n = n->nfor.body; - p = "; done"; - goto dodo; - case NDEFUN: - cmdputs(n->narg.text); - p = "() { ... }"; - goto dotail2; - case NCMD: - cmdlist(n->ncmd.args, 1); - cmdlist(n->ncmd.redirect, 0); - break; - case NARG: - p = n->narg.text; - dotail2: - cmdputs(p); - break; - case NHERE: - case NXHERE: - p = "<<..."; - goto dotail2; - case NCASE: - cmdputs("case "); - cmdputs(n->ncase.expr->narg.text); - cmdputs(" in "); - for (np = n->ncase.cases; np; np = np->nclist.next) { - cmdtxt(np->nclist.pattern); - cmdputs(") "); - cmdtxt(np->nclist.body); - cmdputs(";; "); - } - p = "esac"; - goto dotail2; - case NTO: - p = ">"; - goto redir; - case NCLOBBER: - p = ">|"; - goto redir; - case NAPPEND: - p = ">>"; - goto redir; - case NTOFD: - p = ">&"; - goto redir; - case NFROM: - p = "<"; - goto redir; - case NFROMFD: - p = "<&"; - goto redir; - case NFROMTO: - p = "<>"; - redir: - s[0] = n->nfile.fd + '0'; - s[1] = '\0'; - cmdputs(s); - cmdputs(p); - if (n->type == NTOFD || n->type == NFROMFD) { - s[0] = n->ndup.dupfd + '0'; - p = s; - goto dotail2; - } - n = n->nfile.fname; - goto donode; + len = strlen(s); + INT_OFF; +/*dprintf("*** calling pushstring: %s, %d\n", s, len);*/ + if (parsefile->strpush) { + sp = ckmalloc(sizeof(struct strpush)); + sp->prev = parsefile->strpush; + parsefile->strpush = sp; + } else + sp = parsefile->strpush = &(parsefile->basestrpush); + sp->prevstring = parsenextc; + sp->prevnleft = parsenleft; +#if ENABLE_ASH_ALIAS + sp->ap = (struct alias *)ap; + if (ap) { + ((struct alias *)ap)->flag |= ALIASINUSE; + sp->string = s; } +#endif + parsenextc = s; + parsenleft = len; + INT_ON; } -static char * -commandtext(union node *n) +/* + * To handle the "." command, a stack of input files is used. Pushfile + * adds a new entry to the stack and popfile restores the previous level. + */ +static void +pushfile(void) { - char *name; + struct parsefile *pf; - STARTSTACKSTR(cmdnextc); - cmdtxt(n); - name = stackblock(); - TRACE(("commandtext: name %p, end %p\n\t\"%s\"\n", - name, cmdnextc, cmdnextc)); - return ckstrdup(name); + parsefile->nleft = parsenleft; + parsefile->lleft = parselleft; + parsefile->nextc = parsenextc; + parsefile->linno = plinno; + pf = ckmalloc(sizeof(*pf)); + pf->prev = parsefile; + pf->fd = -1; + pf->strpush = NULL; + pf->basestrpush.prev = NULL; + parsefile = pf; } -#endif /* JOBS */ -/* - * Fork off a subshell. If we are doing job control, give the subshell its - * own process group. Jp is a job structure that the job is to be added to. - * N is the command that will be evaluated by the child. Both jp and n may - * be NULL. The mode parameter can be one of the following: - * FORK_FG - Fork off a foreground process. - * FORK_BG - Fork off a background process. - * FORK_NOJOB - Like FORK_FG, but don't give the process its own - * process group even if job control is on. - * - * When job control is turned off, background processes have their standard - * input redirected to /dev/null (except for the second and later processes - * in a pipeline). - * - * Called with interrupts off. - */ -/* - * Clear traps on a fork. - */ static void -clear_traps(void) +popfile(void) { - char **tp; + struct parsefile *pf = parsefile; - for (tp = trap; tp < &trap[NSIG]; tp++) { - if (*tp && **tp) { /* trap not NULL or SIG_IGN */ - INT_OFF; - free(*tp); - *tp = NULL; - if (tp != &trap[0]) - setsignal(tp - trap); - INT_ON; - } - } + INT_OFF; + if (pf->fd >= 0) + close(pf->fd); + if (pf->buf) + free(pf->buf); + while (pf->strpush) + popstring(); + parsefile = pf->prev; + free(pf); + parsenleft = parsefile->nleft; + parselleft = parsefile->lleft; + parsenextc = parsefile->nextc; + plinno = parsefile->linno; + INT_ON; } + +/* + * Return to top level. + */ static void -forkchild(struct job *jp, union node *n, int mode) +popallfiles(void) { - int oldlvl; - - TRACE(("Child shell %d\n", getpid())); - oldlvl = shlvl; - shlvl++; - - closescript(); - clear_traps(); -#if JOBS - /* do job control only in root shell */ - jobctl = 0; - if (mode != FORK_NOJOB && jp->jobctl && !oldlvl) { - pid_t pgrp; - - if (jp->nprocs == 0) - pgrp = getpid(); - else - pgrp = jp->ps[0].pid; - /* This can fail because we are doing it in the parent also */ - (void)setpgid(0, pgrp); - if (mode == FORK_FG) - xtcsetpgrp(ttyfd, pgrp); - setsignal(SIGTSTP); - setsignal(SIGTTOU); - } else -#endif - if (mode == FORK_BG) { - ignoresig(SIGINT); - ignoresig(SIGQUIT); - if (jp->nprocs == 0) { - close(0); - if (open(bb_dev_null, O_RDONLY) != 0) - ash_msg_and_raise_error("Can't open %s", bb_dev_null); - } - } - if (!oldlvl && iflag) { - setsignal(SIGINT); - setsignal(SIGQUIT); - setsignal(SIGTERM); - } - for (jp = curjob; jp; jp = jp->prev_job) - freejob(jp); - jobless = 0; + while (parsefile != &basepf) + popfile(); } +/* + * Close the file(s) that the shell is reading commands from. Called + * after a fork is done. + */ static void -forkparent(struct job *jp, union node *n, int mode, pid_t pid) +closescript(void) { - TRACE(("In parent shell: child = %d\n", pid)); - if (!jp) { - while (jobless && dowait(DOWAIT_NORMAL, 0) > 0); - jobless++; - return; - } -#if JOBS - if (mode != FORK_NOJOB && jp->jobctl) { - int pgrp; - - if (jp->nprocs == 0) - pgrp = pid; - else - pgrp = jp->ps[0].pid; - /* This can fail because we are doing it in the child also */ - setpgid(pid, pgrp); - } -#endif - if (mode == FORK_BG) { - backgndpid = pid; /* set $! */ - set_curjob(jp, CUR_RUNNING); - } - if (jp) { - struct procstat *ps = &jp->ps[jp->nprocs++]; - ps->pid = pid; - ps->status = -1; - ps->cmd = nullstr; -#if JOBS - if (jobctl && n) - ps->cmd = commandtext(n); -#endif + popallfiles(); + if (parsefile->fd > 0) { + close(parsefile->fd); + parsefile->fd = 0; } } -static int -forkshell(struct job *jp, union node *n, int mode) +/* + * Like setinputfile, but takes an open file descriptor. Call this with + * interrupts off. + */ +static void +setinputfd(int fd, int push) { - int pid; - - TRACE(("forkshell(%%%d, %p, %d) called\n", jobno(jp), n, mode)); - pid = fork(); - if (pid < 0) { - TRACE(("Fork failed, errno=%d", errno)); - if (jp) - freejob(jp); - ash_msg_and_raise_error("Cannot fork"); + fcntl(fd, F_SETFD, FD_CLOEXEC); + if (push) { + pushfile(); + parsefile->buf = 0; } - if (pid == 0) - forkchild(jp, n, mode); - else - forkparent(jp, n, mode, pid); - return pid; + parsefile->fd = fd; + if (parsefile->buf == NULL) + parsefile->buf = ckmalloc(IBUFSIZ); + parselleft = parsenleft = 0; + plinno = 1; } /* - * Wait for job to finish. - * - * Under job control we have the problem that while a child process is - * running interrupts generated by the user are sent to the child but not - * to the shell. This means that an infinite loop started by an inter- - * active user may be hard to kill. With job control turned off, an - * interactive user may place an interactive program inside a loop. If - * the interactive program catches interrupts, the user doesn't want - * these interrupts to also abort the loop. The approach we take here - * is to have the shell ignore interrupt signals while waiting for a - * foreground process to terminate, and then send itself an interrupt - * signal if the child process was terminated by an interrupt signal. - * Unfortunately, some programs want to do a bit of cleanup and then - * exit on interrupt; unless these processes terminate themselves by - * sending a signal to themselves (instead of calling exit) they will - * confuse this approach. - * - * Called with interrupts off. + * Set the input to take input from a file. If push is set, push the + * old input onto the stack first. */ static int -waitforjob(struct job *jp) +setinputfile(const char *fname, int flags) { - int st; + int fd; + int fd2; - TRACE(("waitforjob(%%%d) called\n", jobno(jp))); - while (jp->state == JOBRUNNING) { - dowait(DOWAIT_BLOCK, jp); + INT_OFF; + fd = open(fname, O_RDONLY); + if (fd < 0) { + if (flags & INPUT_NOFILE_OK) + goto out; + ash_msg_and_raise_error("Can't open %s", fname); } - st = getstatus(jp); -#if JOBS - if (jp->jobctl) { - xtcsetpgrp(ttyfd, rootpid); - /* - * This is truly gross. - * If we're doing job control, then we did a TIOCSPGRP which - * caused us (the shell) to no longer be in the controlling - * session -- so we wouldn't have seen any ^C/SIGINT. So, we - * intuit from the subprocess exit status whether a SIGINT - * occurred, and if so interrupt ourselves. Yuck. - mycroft - */ - if (jp->sigint) - raise(SIGINT); + if (fd < 10) { + fd2 = copyfd(fd, 10); + close(fd); + if (fd2 < 0) + ash_msg_and_raise_error("Out of file descriptors"); + fd = fd2; } - if (jp->state == JOBDONE) -#endif - freejob(jp); - return st; + setinputfd(fd, flags & INPUT_PUSH_FILE); + out: + INT_ON; + return fd; } /* - * return 1 if there are stopped jobs, otherwise 0 + * Like setinputfile, but takes input from a string. */ -static int -stoppedjobs(void) +static void +setinputstring(char *string) { - struct job *jp; - int retval; - - retval = 0; - if (job_warning) - goto out; - jp = curjob; - if (jp && jp->state == JOBSTOPPED) { - out2str("You have stopped jobs.\n"); - job_warning = 2; - retval++; - } - out: - return retval; + INT_OFF; + pushfile(); + parsenextc = string; + parsenleft = strlen(string); + parsefile->buf = NULL; + plinno = 1; + INT_ON; } @@ -9157,22 +9166,6 @@ setcmd(int argc, char **argv) return 0; } -#if ENABLE_LOCALE_SUPPORT -static void -change_lc_all(const char *value) -{ - if (value && *value != '\0') - setlocale(LC_ALL, value); -} - -static void -change_lc_ctype(const char *value) -{ - if (value && *value != '\0') - setlocale(LC_CTYPE, value); -} -#endif - #if ENABLE_ASH_RANDOM_SUPPORT /* Roughly copied from bash.. */ static void