ash: cleanup part 2.4
No binary (build failed)
—
(log yok)
| @@ -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 | |