ash: starting second round of cleanups. #1
No binary (build failed)
—
(log yok)
| @@ -408,6 +408,39 @@ out2str(const char *p) | |
| /* ============ Parsing structures */ | |
| + | /* control characters in argument strings */ |
| + | #define CTLESC '\201' /* escape next character */ |
| + | #define CTLVAR '\202' /* variable defn */ |
| + | #define CTLENDVAR '\203' |
| + | #define CTLBACKQ '\204' |
| + | #define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ |
| + | /* CTLBACKQ | CTLQUOTE == '\205' */ |
| + | #define CTLARI '\206' /* arithmetic expression */ |
| + | #define CTLENDARI '\207' |
| + | #define CTLQUOTEMARK '\210' |
| + | |
| + | /* variable substitution byte (follows CTLVAR) */ |
| + | #define VSTYPE 0x0f /* type of variable substitution */ |
| + | #define VSNUL 0x10 /* colon--treat the empty string as unset */ |
| + | #define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ |
| + | |
| + | /* values of VSTYPE field */ |
| + | #define VSNORMAL 0x1 /* normal variable: $var or ${var} */ |
| + | #define VSMINUS 0x2 /* ${var-text} */ |
| + | #define VSPLUS 0x3 /* ${var+text} */ |
| + | #define VSQUESTION 0x4 /* ${var?message} */ |
| + | #define VSASSIGN 0x5 /* ${var=text} */ |
| + | #define VSTRIMRIGHT 0x6 /* ${var%pattern} */ |
| + | #define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ |
| + | #define VSTRIMLEFT 0x8 /* ${var#pattern} */ |
| + | #define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ |
| + | #define VSLENGTH 0xa /* ${#var} */ |
| + | |
| + | /* values of checkkwd variable */ |
| + | #define CHKALIAS 0x1 |
| + | #define CHKKWD 0x2 |
| + | #define CHKNL 0x4 |
| + | |
| #define NCMD 0 | |
| #define NPIPE 1 | |
| #define NREDIR 2 | |
| @@ -551,6 +584,16 @@ struct funcnode { | |
| union node n; | |
| }; | |
| + | /* |
| + | * Free a parse tree. |
| + | */ |
| + | static void |
| + | freefunc(struct funcnode *f) |
| + | { |
| + | if (f && --f->count < 0) |
| + | free(f); |
| + | } |
| + | |
| /* ============ Debugging output */ | |
| @@ -886,11 +929,11 @@ showtree(union node *n) | |
| #endif /* DEBUG */ | |
| - | /* ============ Parser data |
| - | * |
| + | /* ============ Parser data */ |
| + | |
| + | /* |
| * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up. | |
| */ | |
| - | |
| struct strlist { | |
| struct strlist *next; | |
| char *text; | |
| @@ -1431,7 +1474,7 @@ single_quote(const char *s) | |
| } | |
| - | /* ============ ... */ |
| + | /* ============ nextopt */ |
| static char **argptr; /* argument list for builtin commands */ | |
| static char *optionarg; /* set by nextopt (like getopt) */ | |
| @@ -2348,47 +2391,7 @@ pwdcmd(int argc, char **argv) | |
| } | |
| - | /* ============ Unsorted yet */ |
| - | |
| - | |
| - | /* parser.h */ |
| - | |
| - | /* control characters in argument strings */ |
| - | #define CTL_FIRST '\201' /* first 'special' character */ |
| - | #define CTLESC '\201' /* escape next character */ |
| - | #define CTLVAR '\202' /* variable defn */ |
| - | #define CTLENDVAR '\203' |
| - | #define CTLBACKQ '\204' |
| - | #define CTLQUOTE 01 /* ored with CTLBACKQ code if in quotes */ |
| - | /* CTLBACKQ | CTLQUOTE == '\205' */ |
| - | #define CTLARI '\206' /* arithmetic expression */ |
| - | #define CTLENDARI '\207' |
| - | #define CTLQUOTEMARK '\210' |
| - | #define CTL_LAST '\210' /* last 'special' character */ |
| - | |
| - | /* variable substitution byte (follows CTLVAR) */ |
| - | #define VSTYPE 0x0f /* type of variable substitution */ |
| - | #define VSNUL 0x10 /* colon--treat the empty string as unset */ |
| - | #define VSQUOTE 0x80 /* inside double quotes--suppress splitting */ |
| - | |
| - | /* values of VSTYPE field */ |
| - | #define VSNORMAL 0x1 /* normal variable: $var or ${var} */ |
| - | #define VSMINUS 0x2 /* ${var-text} */ |
| - | #define VSPLUS 0x3 /* ${var+text} */ |
| - | #define VSQUESTION 0x4 /* ${var?message} */ |
| - | #define VSASSIGN 0x5 /* ${var=text} */ |
| - | #define VSTRIMRIGHT 0x6 /* ${var%pattern} */ |
| - | #define VSTRIMRIGHTMAX 0x7 /* ${var%%pattern} */ |
| - | #define VSTRIMLEFT 0x8 /* ${var#pattern} */ |
| - | #define VSTRIMLEFTMAX 0x9 /* ${var##pattern} */ |
| - | #define VSLENGTH 0xa /* ${#var} */ |
| - | |
| - | /* values of checkkwd variable */ |
| - | #define CHKALIAS 0x1 |
| - | #define CHKKWD 0x2 |
| - | #define CHKNL 0x4 |
| - | |
| - | #define IBUFSIZ (BUFSIZ + 1) |
| + | /* ============ ... */ |
| /* | |
| * NEOF is returned by parsecmd when it encounters an end of file. It | |
| @@ -2404,6 +2407,7 @@ static int parselleft; /* copy of parsefile->lleft */ | |
| /* next character in input buffer */ | |
| static char *parsenextc; /* copy of parsefile->nextc */ | |
| + | #define IBUFSIZ (BUFSIZ + 1) |
| #define basebuf bb_common_bufsiz1 /* buffer for top level input file */ | |
| static int tokpushback; /* last token pushed back */ | |
| @@ -2431,98 +2435,6 @@ static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; | |
| #define xlikely(x) __builtin_expect((x),1) | |
| - | #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 char buf[16]; |
| - | |
| - | if (tok >= TSEMI) |
| - | buf[0] = '"'; |
| - | sprintf(buf + (tok >= TSEMI), "%s%c", |
| - | tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); |
| - | return buf; |
| - | } |
| - | |
| - | /* 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); |
| - | } |
| - | |
| - | 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); |
| - | } |
| - | |
| /* Syntax classes */ | |
| #define CWORD 0 /* character is nothing special */ | |
| #define CNL 1 /* newline character */ | |
| @@ -2966,131 +2878,40 @@ static const char syntax_index_table[258] = { | |
| #endif /* USE_SIT_FUNCTION */ | |
| - | /* alias.c */ |
| - | |
| - | #define ATABSIZE 39 |
| + | /* exec.h */ |
| - | 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 */ |
| + | #if ENABLE_ASH_MATH_SUPPORT_64 |
| + | typedef int64_t arith_t; |
| + | #define arith_t_type long long |
| + | #else |
| + | typedef long arith_t; |
| + | #define arith_t_type long |
| + | #endif |
| - | 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)), |
| - | }; |
| + | #if ENABLE_ASH_MATH_SUPPORT |
| + | static arith_t dash_arith(const char *); |
| + | static arith_t arith(const char *expr, int *perrcode); |
| + | #endif |
| - | static void calcsize(union node *); |
| - | static void sizenodelist(struct nodelist *); |
| - | static union node *copynode(union node *); |
| - | static struct nodelist *copynodelist(struct nodelist *); |
| - | static char *nodeckstrdup(char *); |
| + | #if ENABLE_ASH_RANDOM_SUPPORT |
| + | static unsigned long rseed; |
| + | # ifndef DYNAMIC_VAR |
| + | # define DYNAMIC_VAR |
| + | # endif |
| + | #endif |
| - | static int evalskip; /* set if we are skipping commands */ |
| - | static int skipcount; /* number of levels to skip */ |
| - | static int funcnest; /* depth of function calls */ |
| - | /* 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) |
| + | /* 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 |
| - | /* exec.h */ |
| - | |
| - | /* 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 */ |
| - | |
| - | struct builtincmd { |
| - | const char *name; |
| - | int (*builtin)(int, char **); |
| - | /* unsigned flags; */ |
| - | }; |
| - | |
| - | struct cmdentry { |
| - | int cmdtype; |
| - | union param { |
| - | int index; |
| - | const struct builtincmd *cmd; |
| - | struct funcnode *func; |
| - | } u; |
| - | }; |
| - | |
| - | /* 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 shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; |
| - | static char *padvance(const char **, const char *); |
| - | static void find_command(char *, struct cmdentry *, int, const char *); |
| - | static struct builtincmd *find_builtin(const char *); |
| - | static void defun(char *, union node *); |
| - | static void unsetfunc(const char *); |
| - | |
| - | #if ENABLE_ASH_MATH_SUPPORT_64 |
| - | typedef int64_t arith_t; |
| - | #define arith_t_type long long |
| - | #else |
| - | typedef long arith_t; |
| - | #define arith_t_type long |
| - | #endif |
| - | |
| - | #if ENABLE_ASH_MATH_SUPPORT |
| - | static arith_t dash_arith(const char *); |
| - | static arith_t arith(const char *expr, int *perrcode); |
| - | #endif |
| - | |
| - | #if ENABLE_ASH_RANDOM_SUPPORT |
| - | static unsigned long rseed; |
| - | # ifndef DYNAMIC_VAR |
| - | # define DYNAMIC_VAR |
| - | # endif |
| - | #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 */ |
| + | /* 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 */ |
| /* | |
| @@ -3222,6 +3043,8 @@ static int is_safe_applet(char *name) | |
| #define ALIASINUSE 1 | |
| #define ALIASDEAD 2 | |
| + | #define ATABSIZE 39 |
| + | |
| struct alias { | |
| struct alias *next; | |
| char *name; | |
| @@ -4899,1314 +4722,1630 @@ casematch(union node *pattern, char *val) | |
| } | |
| - | /* ============ eval.c */ |
| + | /* ============ find_command */ |
| - | /* 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 */ |
| + | 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) |
| - | /* 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 int evalfun(struct funcnode *, int, char **, int); |
| - | static void prehash(union node *); |
| + | 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 */ |
| /* | |
| - | * Evaluate a parse tree. The value is left in the global variable |
| - | * exitstatus. |
| + | * 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. |
| */ | |
| - | 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; |
| - | } |
| - | 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; |
| - | } |
| - | out: |
| - | if ((checkexit & exitstatus)) |
| - | evalskip |= SKIPEVAL; |
| - | else if (pendingsigs && dotrap()) |
| - | goto exexit; |
| - | if (flags & EV_EXIT) { |
| - | exexit: |
| - | raise_exception(EXEXIT); |
| - | } |
| - | } |
| + | #define CMDTABLESIZE 31 /* should be prime */ |
| + | #define ARB 1 /* actual size determined at run time */ |
| - | #if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) |
| - | static |
| - | #endif |
| - | void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); |
| + | 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 int loopnest; /* current loop nesting level */ |
| + | static struct tblentry *cmdtable[CMDTABLESIZE]; |
| + | static int builtinloc = -1; /* index in path of %builtin, or -1 */ |
| static void | |
| - | evalloop(union node *n, int flags) |
| + | tryexec(char *cmd, char **argv, char **envp) |
| { | |
| - | int status; |
| - | |
| - | loopnest++; |
| - | status = 0; |
| - | flags &= EV_TESTED; |
| - | for (;;) { |
| - | int i; |
| + | int repeated = 0; |
| + | struct BB_applet *a; |
| + | int argc = 0; |
| + | char **c; |
| - | 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; |
| + | if (strchr(cmd, '/') == NULL |
| + | && (a = find_applet_by_name(cmd)) != NULL |
| + | && is_safe_applet(cmd) |
| + | ) { |
| + | c = argv; |
| + | while (*c != NULL) { |
| + | c++; argc++; |
| } | |
| - | i = exitstatus; |
| - | if (n->type != NWHILE) |
| - | i = !i; |
| - | if (i != 0) |
| - | break; |
| - | evaltree(n->nbinary.ch2, flags); |
| - | status = exitstatus; |
| - | if (evalskip) |
| - | goto skipping; |
| + | applet_name = cmd; |
| + | exit(a->main(argc, argv)); |
| } | |
| - | 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; |
| + | #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 */ |
| } | |
| - | *arglist.lastp = NULL; |
| + | #endif |
| - | 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; |
| - | } |
| + | 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; |
| + | |
| + | 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; |
| } | |
| - | loopnest--; |
| - | out: |
| - | popstackmark(&smark); |
| } | |
| + | /* |
| + | * 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 | |
| - | evalcase(union node *n, int flags) |
| + | shellexec(char **argv, const char *path, int idx) |
| { | |
| - | union node *cp; |
| - | union node *patp; |
| - | struct arglist arglist; |
| - | struct stackmark smark; |
| + | char *cmdname; |
| + | int e; |
| + | char **envp; |
| + | int exerrno; |
| - | 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; |
| + | 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 |
| + | ) { |
| + | 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); |
| } | |
| } | |
| - | out: |
| - | popstackmark(&smark); |
| + | |
| + | /* 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 */ |
| } | |
| - | /* |
| - | * Kick off a subshell to evaluate a tree. |
| - | */ |
| static void | |
| - | evalsubshell(union node *n, int flags) |
| + | printentry(struct tblentry *cmdp) |
| { | |
| - | struct job *jp; |
| - | int backgnd = (n->type == NBACKGND); |
| - | int status; |
| + | int idx; |
| + | const char *path; |
| + | char *name; |
| - | 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; |
| + | 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)); |
| } | |
| /* | |
| - | * Compute the names of the files in a redirection list. |
| + | * Clear out command entries. The argument specifies the first entry in |
| + | * PATH which has changed. |
| */ | |
| static void | |
| - | expredir(union node *n) |
| + | clearcmdentry(int firstchange) |
| { | |
| - | union node *redir; |
| - | |
| - | for (redir = n; redir; redir = redir->nfile.next) { |
| - | struct arglist fn; |
| + | struct tblentry **tblp; |
| + | struct tblentry **pp; |
| + | struct tblentry *cmdp; |
| - | 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); |
| + | 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; |
| } | |
| - | break; |
| } | |
| } | |
| + | INT_ON; |
| } | |
| /* | |
| - | * 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.) |
| + | * 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 void |
| - | evalpipe(union node *n, int flags) |
| + | static struct tblentry **lastcmdentry; |
| + | |
| + | static struct tblentry * |
| + | cmdlookup(const char *name, int add) |
| { | |
| - | struct job *jp; |
| - | struct nodelist *lp; |
| - | int pipelen; |
| - | int prevfd; |
| - | int pip[2]; |
| + | 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 |
| + | delete_cmd_entry(void) |
| + | { |
| + | struct tblentry *cmdp; |
| - | 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]); |
| + | cmdp = *lastcmdentry; |
| + | *lastcmdentry = cmdp->next; |
| + | if (cmdp->cmdtype == CMDFUNCTION) |
| + | freefunc(cmdp->param.func); |
| + | free(cmdp); |
| + | INT_ON; |
| + | } |
| + | |
| + | /* |
| + | * 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; |
| + | |
| + | cmdp = cmdlookup(name, 1); |
| + | if (cmdp->cmdtype == CMDFUNCTION) { |
| + | freefunc(cmdp->param.func); |
| + | } |
| + | cmdp->cmdtype = entry->cmdtype; |
| + | cmdp->param = entry->u; |
| + | cmdp->rehash = 0; |
| + | } |
| + | |
| + | static int |
| + | hashcmd(int argc, char **argv) |
| + | { |
| + | struct tblentry **pp; |
| + | struct tblentry *cmdp; |
| + | int c; |
| + | struct cmdentry entry; |
| + | char *name; |
| + | |
| + | 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); |
| } | |
| - | evaltreenr(lp->n, flags); |
| - | /* never returns */ |
| } | |
| - | if (prevfd >= 0) |
| - | close(prevfd); |
| - | prevfd = pip[0]; |
| - | close(pip[1]); |
| + | return 0; |
| } | |
| - | if (n->npipe.backgnd == 0) { |
| - | exitstatus = waitforjob(jp); |
| - | TRACE(("evalpipe: job done exit status %d\n", exitstatus)); |
| + | 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++; |
| } | |
| - | INT_ON; |
| + | return c; |
| } | |
| - | #if ENABLE_ASH_CMDCMD |
| - | static char ** |
| - | parse_command_args(char **argv, const char **path) |
| + | /* |
| + | * Called when a cd is done. Marks all commands so the next time they |
| + | * are executed they will be rehashed. |
| + | */ |
| + | static void |
| + | hashcd(void) |
| { | |
| - | char *cp, c; |
| + | struct tblentry **pp; |
| + | struct tblentry *cmdp; |
| + | |
| + | 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; |
| + | } |
| + | } |
| + | } |
| + | |
| + | /* |
| + | * 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; |
| + | old = pathval(); |
| + | new = newval; |
| + | firstchange = 9999; /* assume no change */ |
| + | idx = 0; |
| + | idx_bltin = -1; |
| for (;;) { | |
| - | cp = *++argv; |
| - | if (!cp) |
| - | return 0; |
| - | if (*cp++ != '-') |
| - | break; |
| - | c = *cp++; |
| - | if (!c) |
| - | break; |
| - | if (c == '-' && !*cp) { |
| - | argv++; |
| + | 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++; |
| } | |
| - | do { |
| - | switch (c) { |
| - | case 'p': |
| - | *path = defpath; |
| - | break; |
| - | default: |
| - | /* run 'typecmd' for other options */ |
| - | return 0; |
| - | } |
| - | c = *cp++; |
| - | } while (c); |
| + | new++, old++; |
| } | |
| - | return argv; |
| + | if (builtinloc < 0 && idx_bltin >= 0) |
| + | builtinloc = idx_bltin; /* zap builtins */ |
| + | if (builtinloc >= 0 && idx_bltin < 0) |
| + | firstchange = 0; |
| + | clearcmdentry(firstchange); |
| + | builtinloc = idx_bltin; |
| } | |
| - | #endif |
| - | /* 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 localcmd(int, char **); |
| - | 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" |
| - | |
| - | #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 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 |
| - | /* 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 }, |
| + | /* 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}", |
| }; | |
| - | #define NUMBUILTINS (sizeof(builtintab) / sizeof(builtintab[0])) |
| + | static const char * |
| + | tokname(int tok) |
| + | { |
| + | static char buf[16]; |
| - | #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) |
| + | if (tok >= TSEMI) |
| + | buf[0] = '"'; |
| + | sprintf(buf + (tok >= TSEMI), "%s%c", |
| + | tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); |
| + | return buf; |
| + | } |
| - | /* |
| - | * Execute a simple command. |
| - | */ |
| - | static int back_exitstatus; /* exit status of backquoted command */ |
| + | /* Wrapper around strcmp for qsort/bsearch/... */ |
| static int | |
| - | isassignment(const char *p) |
| + | pstrcmp(const void *a, const void *b) |
| { | |
| - | const char *q = endofname(p); |
| - | if (p == q) |
| - | return 0; |
| - | return *q == '='; |
| + | return strcmp((const char *) a, (*(const char *const *) b) + 1); |
| } | |
| - | static int |
| - | bltincmd(int argc, char **argv) |
| + | |
| + | static const char *const * |
| + | findkwd(const char *s) |
| { | |
| - | /* Preserve exitstatus of a previous possible redirection |
| - | * as POSIX mandates */ |
| - | return back_exitstatus; |
| + | return bsearch(s, tokname_array + KWDOFFSET, |
| + | (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, |
| + | sizeof(const char *), pstrcmp); |
| } | |
| - | 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; |
| - | char *lastarg; |
| - | const char *path; |
| - | int spclbltin; |
| - | int cmd_is_exec; |
| - | int status; |
| - | char **nargv; |
| - | struct builtincmd *bcmd; |
| - | int pseudovarflag = 0; |
| - | /* First expand the arguments. */ |
| - | TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); |
| - | setstackmark(&smark); |
| - | back_exitstatus = 0; |
| - | |
| - | cmdentry.cmdtype = CMDBUILTIN; |
| - | cmdentry.u.cmd = &bltin; |
| - | varlist.lastp = &varlist.list; |
| - | *varlist.lastp = NULL; |
| - | arglist.lastp = &arglist.list; |
| - | *arglist.lastp = NULL; |
| + | /* |
| + | * Locate and print what a word is... |
| + | */ |
| + | #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(); |
| - | argc = 0; |
| - | if (cmd->ncmd.args) { |
| - | bcmd = find_builtin(cmd->ncmd.args->narg.text); |
| - | pseudovarflag = bcmd && IS_BUILTIN_ASSIGN(bcmd); |
| + | if (describe_command_verbose) { |
| + | out1str(command); |
| } | |
| - | 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); |
| + | /* First look at the keywords */ |
| + | if (findkwd(command)) { |
| + | out1str(describe_command_verbose ? " is a shell keyword" : command); |
| + | goto out; |
| + | } |
| - | for (sp = *spp; sp; sp = sp->next) |
| - | argc++; |
| + | #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; |
| + | } |
| + | 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); |
| } | |
| - | 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; |
| + | 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; |
| } | |
| - | *nargv = NULL; |
| - | lastarg = NULL; |
| - | if (iflag && funcnest == 0 && argc > 0) |
| - | lastarg = nargv[-1]; |
| + | case CMDFUNCTION: |
| + | if (describe_command_verbose) { |
| + | out1str(" is a shell function"); |
| + | } else { |
| + | out1str(command); |
| + | } |
| + | break; |
| - | preverrout_fd = 2; |
| - | expredir(cmd->ncmd.redirect); |
| - | status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); |
| + | case CMDBUILTIN: |
| + | if (describe_command_verbose) { |
| + | out1fmt(" is a %sshell builtin", |
| + | IS_BUILTIN_SPECIAL(entry.u.cmd) ? |
| + | "special " : nullstr |
| + | ); |
| + | } else { |
| + | out1str(command); |
| + | } |
| + | break; |
| - | path = vpath.text; |
| - | for (argp = cmd->ncmd.assign; argp; argp = argp->narg.next) { |
| - | struct strlist **spp; |
| - | char *p; |
| + | default: |
| + | if (describe_command_verbose) { |
| + | out1str(": not found\n"); |
| + | } |
| + | return 127; |
| + | } |
| + | out: |
| + | outstr("\n", stdout); |
| + | return 0; |
| + | } |
| - | spp = varlist.lastp; |
| - | expandarg(argp, &varlist, EXP_VARTILDE); |
| + | static int |
| + | typecmd(int argc, char **argv) |
| + | { |
| + | int i; |
| + | int err = 0; |
| - | /* |
| - | * Modify the command lookup path, if a PATH= assignment |
| - | * is present |
| - | */ |
| - | p = (*spp)->text; |
| - | if (varequal(p, path)) |
| - | path = p; |
| + | 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; |
| + | } |
| - | /* Print the command if xflag is set. */ |
| - | if (xflag) { |
| - | int n; |
| - | const char *p = " %s"; |
| + | #if ENABLE_ASH_CMDCMD |
| + | static int |
| + | commandcmd(int argc, char **argv) |
| + | { |
| + | int c; |
| + | enum { |
| + | VERIFY_BRIEF = 1, |
| + | VERIFY_VERBOSE = 2, |
| + | } verify = 0; |
| - | p++; |
| - | dprintf(preverrout_fd, p, expandstr(ps4val())); |
| + | 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); |
| - | 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); |
| - | } |
| + | return 0; |
| + | } |
| + | #endif |
| - | cmd_is_exec = 0; |
| - | spclbltin = -1; |
| - | /* Now locate the command. */ |
| - | if (argc) { |
| - | const char *oldpath; |
| - | int cmd_flag = DO_ERR; |
| + | /* ============ eval.c */ |
| - | path += 5; |
| - | oldpath = path; |
| - | for (;;) { |
| - | find_command(argv[0], &cmdentry, cmd_flag, path); |
| - | if (cmdentry.cmdtype == CMDUNKNOWN) { |
| - | status = 127; |
| - | flush_stderr(); |
| - | goto bail; |
| - | } |
| + | 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 */ |
| - | /* 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; |
| - | } |
| - | } |
| + | /* 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)), |
| + | }; |
| - | if (status) { |
| - | /* We have a redirection error. */ |
| - | if (spclbltin > 0) |
| - | raise_exception(EXERROR); |
| - | bail: |
| - | exitstatus = status; |
| - | goto out; |
| + | static void calcsize(union node *n); |
| + | |
| + | static void |
| + | sizenodelist(struct nodelist *lp) |
| + | { |
| + | while (lp) { |
| + | funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); |
| + | calcsize(lp->n); |
| + | lp = lp->next; |
| } | |
| + | } |
| - | /* 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 */ |
| + | static void |
| + | calcsize(union node *n) |
| + | { |
| + | 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; |
| + | }; |
| + | } |
| - | 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; |
| + | static char * |
| + | nodeckstrdup(char *s) |
| + | { |
| + | char *rtn = funcstring; |
| - | i = exception; |
| - | if (i == EXEXIT) |
| - | goto raise; |
| + | strcpy(funcstring, s); |
| + | funcstring += strlen(s) + 1; |
| + | return rtn; |
| + | } |
| - | exit_status = 2; |
| - | j = 0; |
| - | if (i == EXINT) |
| - | j = SIGINT; |
| - | if (i == EXSIG) |
| - | j = pendingsigs; |
| - | if (j) |
| - | exit_status = j + 128; |
| - | exitstatus = exit_status; |
| + | static union node *copynode(union node *); |
| - | if (i == EXINT || spclbltin > 0) { |
| - | raise: |
| - | longjmp(exception_handler->loc, 1); |
| - | } |
| - | FORCE_INT_ON; |
| - | } |
| - | break; |
| + | static struct nodelist * |
| + | copynodelist(struct nodelist *lp) |
| + | { |
| + | struct nodelist *start; |
| + | struct nodelist **lpp; |
| - | case CMDFUNCTION: |
| - | listsetvar(varlist.list, 0); |
| - | if (evalfun(cmdentry.u.func, argc, argv, flags)) |
| - | goto raise; |
| - | break; |
| + | 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; |
| } | |
| - | |
| - | 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); |
| + | *lpp = NULL; |
| + | return start; |
| } | |
| - | static int |
| - | evalbltin(const struct builtincmd *cmd, int argc, char **argv) |
| + | static union node * |
| + | copynode(union node *n) |
| { | |
| - | char *volatile savecmdname; |
| - | struct jmploc *volatile savehandler; |
| - | struct jmploc jmploc; |
| - | int i; |
| + | union node *new; |
| - | 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 (n == NULL) |
| + | return NULL; |
| + | new = funcblock; |
| + | funcblock = (char *) funcblock + nodesize[n->type]; |
| - | return i; |
| + | 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 struct localvar *localvars; |
| - | |
| /* | |
| - | * Called after a function returns. |
| - | * Interrupts must be off. |
| + | * Make a copy of a parse tree. |
| */ | |
| - | static void |
| - | poplocalvars(void) |
| + | static struct funcnode * |
| + | copyfunc(union node *n) |
| { | |
| - | struct localvar *lvp; |
| - | struct var *vp; |
| + | struct funcnode *f; |
| + | size_t blocksize; |
| - | 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); |
| - | } |
| + | 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; |
| } | |
| /* | |
| - | * Free a parse tree. |
| + | * Define a shell function. |
| */ | |
| static void | |
| - | freefunc(struct funcnode *f) |
| - | { |
| - | if (f && --f->count < 0) |
| - | free(f); |
| - | } |
| - | |
| - | static int |
| - | evalfun(struct funcnode *func, int argc, char **argv, int flags) |
| + | defun(char *name, union node *func) |
| { | |
| - | volatile struct shparam saveparam; |
| - | struct localvar *volatile savelocalvars; |
| - | struct jmploc *volatile savehandler; |
| - | struct jmploc jmploc; |
| - | int e; |
| + | struct cmdentry entry; |
| - | 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 |
| - | evaltree(&func->n, flags & EV_TESTED); |
| - | funcdone: |
| INT_OFF; | |
| - | funcnest--; |
| - | freefunc(func); |
| - | poplocalvars(); |
| - | localvars = savelocalvars; |
| - | freeparam(&shellparam); |
| - | shellparam = saveparam; |
| - | exception_handler = savehandler; |
| + | entry.cmdtype = CMDFUNCTION; |
| + | entry.u.func = copyfunc(func); |
| + | addcmdentry(name, &entry); |
| INT_ON; | |
| - | evalskip &= ~SKIPFUNC; |
| - | return e; |
| } | |
| + | 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 */ |
| - | static int |
| - | goodname(const char *p) |
| - | { |
| - | return !*endofname(p); |
| - | } |
| - | |
| + | /* 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 *); |
| /* | |
| - | * 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. |
| + | * Evaluate a parse tree. The value is left in the global variable |
| + | * exitstatus. |
| */ | |
| static void | |
| - | prehash(union node *n) |
| + | evaltree(union node *n, int flags) |
| { | |
| - | struct cmdentry entry; |
| + | 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; |
| + | } |
| + | 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; |
| + | } |
| + | out: |
| + | if ((checkexit & exitstatus)) |
| + | evalskip |= SKIPEVAL; |
| + | else if (pendingsigs && dotrap()) |
| + | goto exexit; |
| - | if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text)) |
| - | find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); |
| + | if (flags & EV_EXIT) { |
| + | exexit: |
| + | raise_exception(EXEXIT); |
| + | } |
| } | |
| + | #if !defined(__alpha__) || (defined(__GNUC__) && __GNUC__ >= 3) |
| + | static |
| + | #endif |
| + | void evaltreenr(union node *, int) __attribute__ ((alias("evaltree"),__noreturn__)); |
| - | /* |
| - | * Builtin commands. Builtin commands whose functions are closely |
| - | * tied to evaluation are implemented here. |
| - | */ |
| - | |
| - | /* |
| - | * 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 loopnest; /* current loop nesting level */ |
| - | static int |
| - | breakcmd(int argc, char **argv) |
| + | static void |
| + | evalloop(union node *n, int flags) |
| { | |
| - | int n = argc > 1 ? number(argv[1]) : 1; |
| + | int status; |
| - | 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; |
| + | loopnest++; |
| + | status = 0; |
| + | flags &= EV_TESTED; |
| + | for (;;) { |
| + | int i; |
| + | |
| + | 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; |
| } | |
| - | return 0; |
| + | loopnest--; |
| + | exitstatus = status; |
| } | |
| - | /* |
| - | * The return command. |
| - | */ |
| - | static int |
| - | returncmd(int argc, char **argv) |
| + | static void |
| + | evalfor(union node *n, int flags) |
| { | |
| - | /* |
| - | * 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; |
| - | } |
| + | struct arglist arglist; |
| + | union node *argp; |
| + | struct strlist *sp; |
| + | struct stackmark smark; |
| - | static int |
| - | falsecmd(int argc, char **argv) |
| - | { |
| - | return 1; |
| - | } |
| + | 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; |
| - | static int |
| - | truecmd(int argc, char **argv) |
| - | { |
| - | return 0; |
| + | 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); |
| } | |
| - | static int |
| - | execcmd(int argc, char **argv) |
| + | static void |
| + | evalcase(union node *n, int flags) |
| { | |
| - | if (argc > 1) { |
| - | iflag = 0; /* exit on error */ |
| - | mflag = 0; |
| - | optschanged(); |
| - | shellexec(argv + 1, pathval(), 0); |
| + | union node *cp; |
| + | union node *patp; |
| + | struct arglist arglist; |
| + | struct stackmark smark; |
| + | |
| + | 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; |
| + | } |
| + | } |
| } | |
| - | return 0; |
| + | out: |
| + | popstackmark(&smark); |
| } | |
| - | |
| - | /* ============ Executing commands */ |
| - | |
| /* | |
| - | * 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. |
| + | * Kick off a subshell to evaluate a tree. |
| */ | |
| - | |
| - | #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) |
| + | evalsubshell(union node *n, int flags) |
| { | |
| - | int repeated = 0; |
| - | struct BB_applet *a; |
| - | int argc = 0; |
| - | char **c; |
| - | |
| - | if (strchr(cmd, '/') == NULL |
| - | && (a = find_applet_by_name(cmd)) != NULL |
| - | && is_safe_applet(cmd) |
| - | ) { |
| - | c = argv; |
| - | while (*c != NULL) { |
| - | c++; argc++; |
| - | } |
| - | 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 |
| - | |
| - | 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; |
| + | struct job *jp; |
| + | int backgnd = (n->type == NBACKGND); |
| + | int status; |
| - | 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; |
| + | 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; |
| } | |
| /* | |
| - | * Exec a program. Never returns. If you change this routine, you may |
| - | * have to change the find_command routine as well. |
| + | * Compute the names of the files in a redirection list. |
| */ | |
| - | #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) |
| + | expredir(union node *n) |
| { | |
| - | char *cmdname; |
| - | int e; |
| - | char **envp; |
| - | int exerrno; |
| + | union node *redir; |
| - | 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 |
| - | ) { |
| - | 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; |
| + | for (redir = n; redir; redir = redir->nfile.next) { |
| + | struct arglist fn; |
| + | |
| + | 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); |
| } | |
| - | stunalloc(cmdname); |
| + | break; |
| } | |
| } | |
| - | |
| - | /* 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)); |
| } | |
| /* | |
| - | * Clear out command entries. The argument specifies the first entry in |
| - | * PATH which has changed. |
| + | * 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 | |
| - | clearcmdentry(int firstchange) |
| + | evalpipe(union node *n, int flags) |
| { | |
| - | struct tblentry **tblp; |
| - | struct tblentry **pp; |
| - | struct tblentry *cmdp; |
| + | struct job *jp; |
| + | struct nodelist *lp; |
| + | int pipelen; |
| + | int prevfd; |
| + | int pip[2]; |
| + | 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; | |
| - | 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; |
| + | 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; | |
| } | |
| + | static struct localvar *localvars; |
| + | |
| /* | |
| - | * 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. |
| + | * Called after a function returns. |
| + | * Interrupts must be off. |
| */ | |
| - | static struct tblentry **lastcmdentry; |
| + | static void |
| + | poplocalvars(void) |
| + | { |
| + | struct localvar *lvp; |
| + | struct var *vp; |
| - | static struct tblentry * |
| - | cmdlookup(const char *name, int add) |
| + | 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); |
| + | } |
| + | } |
| + | |
| + | static int |
| + | evalfun(struct funcnode *func, int argc, char **argv, int flags) |
| { | |
| - | unsigned int hashval; |
| - | const char *p; |
| - | struct tblentry *cmdp; |
| - | struct tblentry **pp; |
| + | volatile struct shparam saveparam; |
| + | struct localvar *volatile savelocalvars; |
| + | struct jmploc *volatile savehandler; |
| + | struct jmploc jmploc; |
| + | int e; |
| - | 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) |
| + | 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 |
| + | 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) |
| + | { |
| + | char *cp, c; |
| + | |
| + | for (;;) { |
| + | cp = *++argv; |
| + | if (!cp) |
| + | return 0; |
| + | if (*cp++ != '-') |
| 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); |
| + | 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); |
| } | |
| - | lastcmdentry = pp; |
| - | return cmdp; |
| + | return argv; |
| } | |
| + | #endif |
| /* | |
| - | * Delete the command entry returned on the last lookup. |
| + | * 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 void | |
| - | delete_cmd_entry(void) |
| + | mklocal(char *name) |
| { | |
| - | struct tblentry *cmdp; |
| + | struct localvar *lvp; |
| + | struct var **vpp; |
| + | struct var *vp; |
| INT_OFF; | |
| - | cmdp = *lastcmdentry; |
| - | *lastcmdentry = cmdp->next; |
| - | if (cmdp->cmdtype == CMDFUNCTION) |
| - | freefunc(cmdp->param.func); |
| - | free(cmdp); |
| + | 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; |
| + | |
| + | 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; | |
| } | |
| /* | |
| - | * Add a new command entry, replacing any existing command entry for |
| - | * the same name - except special builtins. |
| + | * The "local" command. |
| */ | |
| - | static void |
| - | addcmdentry(char *name, struct cmdentry *entry) |
| + | static int |
| + | localcmd(int argc, char **argv) |
| { | |
| - | struct tblentry *cmdp; |
| + | char *name; |
| - | cmdp = cmdlookup(name, 1); |
| - | if (cmdp->cmdtype == CMDFUNCTION) { |
| - | freefunc(cmdp->param.func); |
| + | argv = argptr; |
| + | while ((name = *argv++) != NULL) { |
| + | mklocal(name); |
| } | |
| - | cmdp->cmdtype = entry->cmdtype; |
| - | cmdp->param = entry->u; |
| - | cmdp->rehash = 0; |
| + | return 0; |
| } | |
| - | static int |
| - | hashcmd(int argc, char **argv) |
| - | { |
| - | struct tblentry **pp; |
| - | struct tblentry *cmdp; |
| - | int c; |
| - | struct cmdentry entry; |
| - | char *name; |
| + | /* 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 |
| - | 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; |
| - | } |
| - | 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++; |
| - | } |
| - | return c; |
| + | #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) |
| + | { |
| + | struct builtincmd *bp; |
| + | |
| + | bp = bsearch( |
| + | name, builtintab, NUMBUILTINS, sizeof(builtintab[0]), |
| + | pstrcmp |
| + | ); |
| + | return bp; |
| } | |
| /* | |
| @@ -6394,283 +6533,377 @@ find_command(char *name, struct cmdentry *entry, int act, const char *path) | |
| } | |
| /* | |
| - | * Search the table of builtin commands. |
| + | * Execute a simple command. |
| */ | |
| - | static struct builtincmd * |
| - | find_builtin(const char *name) |
| + | static int back_exitstatus; /* exit status of backquoted command */ |
| + | static int |
| + | isassignment(const char *p) |
| { | |
| - | struct builtincmd *bp; |
| - | |
| - | bp = bsearch( |
| - | name, builtintab, NUMBUILTINS, sizeof(builtintab[0]), |
| - | pstrcmp |
| - | ); |
| - | return bp; |
| + | const char *q = endofname(p); |
| + | if (p == q) |
| + | return 0; |
| + | return *q == '='; |
| + | } |
| + | static int |
| + | bltincmd(int argc, char **argv) |
| + | { |
| + | /* Preserve exitstatus of a previous possible redirection |
| + | * as POSIX mandates */ |
| + | return back_exitstatus; |
| } | |
| - | |
| - | /* |
| - | * Called when a cd is done. Marks all commands so the next time they |
| - | * are executed they will be rehashed. |
| - | */ |
| static void | |
| - | hashcd(void) |
| + | evalcommand(union node *cmd, int flags) |
| { | |
| - | struct tblentry **pp; |
| - | struct tblentry *cmdp; |
| + | 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; |
| + | |
| + | /* First expand the arguments. */ |
| + | TRACE(("evalcommand(0x%lx, %d) called\n", (long)cmd, flags)); |
| + | setstackmark(&smark); |
| + | back_exitstatus = 0; |
| + | |
| + | cmdentry.cmdtype = CMDBUILTIN; |
| + | cmdentry.u.cmd = &bltin; |
| + | varlist.lastp = &varlist.list; |
| + | *varlist.lastp = NULL; |
| + | arglist.lastp = &arglist.list; |
| + | *arglist.lastp = NULL; |
| + | |
| + | 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 |
| + | expandarg(argp, &arglist, EXP_FULL | EXP_TILDE); |
| + | |
| + | for (sp = *spp; sp; sp = sp->next) |
| + | argc++; |
| + | } |
| + | |
| + | 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; |
| + | |
| + | lastarg = NULL; |
| + | if (iflag && funcnest == 0 && argc > 0) |
| + | lastarg = nargv[-1]; |
| + | |
| + | preverrout_fd = 2; |
| + | expredir(cmd->ncmd.redirect); |
| + | status = redirectsafe(cmd->ncmd.redirect, REDIR_PUSH|REDIR_SAVEFD2); |
| + | |
| + | 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--; |
| + | } |
| + | } |
| + | sp = arglist.list; |
| + | } |
| + | full_write(preverrout_fd, "\n", 1); |
| + | } |
| + | |
| + | cmd_is_exec = 0; |
| + | spclbltin = -1; |
| + | |
| + | /* Now locate the command. */ |
| + | if (argc) { |
| + | const char *oldpath; |
| + | int cmd_flag = DO_ERR; |
| + | |
| + | 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; |
| + | } |
| + | } |
| + | |
| + | if (status) { |
| + | /* We have a redirection error. */ |
| + | if (spclbltin > 0) |
| + | raise_exception(EXERROR); |
| + | bail: |
| + | exitstatus = status; |
| + | 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; |
| + | } |
| + | listsetvar(varlist.list, VEXPORT|VSTACK); |
| + | shellexec(argv, path, cmdentry.u.index); |
| + | /* NOTREACHED */ |
| + | |
| + | 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; |
| - | 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; |
| + | 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; |
| } | |
| + | |
| + | 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); |
| } | |
| - | /* |
| - | * 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) |
| + | static int |
| + | evalbltin(const struct builtincmd *cmd, int argc, char **argv) |
| { | |
| - | const char *old, *new; |
| - | int idx; |
| - | int firstchange; |
| - | int idx_bltin; |
| + | char *volatile savecmdname; |
| + | struct jmploc *volatile savehandler; |
| + | struct jmploc jmploc; |
| + | int i; |
| - | 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; |
| - | } |
| + | 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; |
| + | } |
| - | /* |
| - | * Make a copy of a parse tree. |
| - | */ |
| - | static struct funcnode * |
| - | copyfunc(union node *n) |
| + | static int |
| + | goodname(const char *p) |
| { | |
| - | 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; |
| + | return !*endofname(p); |
| } | |
| + | |
| /* | |
| - | * Define a shell function. |
| + | * 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 | |
| - | defun(char *name, union node *func) |
| + | prehash(union node *n) |
| { | |
| struct cmdentry entry; | |
| - | INT_OFF; |
| - | entry.cmdtype = CMDFUNCTION; |
| - | entry.u.func = copyfunc(func); |
| - | addcmdentry(name, &entry); |
| - | INT_ON; |
| + | if (n->type == NCMD && n->ncmd.args && goodname(n->ncmd.args->narg.text)) |
| + | find_command(n->ncmd.args->narg.text, &entry, 0, pathval()); |
| } | |
| + | |
| /* | |
| - | * Delete a function if it exists. |
| + | * Builtin commands. Builtin commands whose functions are closely |
| + | * tied to evaluation are implemented here. |
| */ | |
| - | static void |
| - | unsetfunc(const char *name) |
| - | { |
| - | struct tblentry *cmdp; |
| - | |
| - | cmdp = cmdlookup(name, 0); |
| - | if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION) |
| - | delete_cmd_entry(); |
| - | } |
| - | |
| /* | |
| - | * Locate and print what a word is... |
| + | * 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. |
| */ | |
| - | #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 |
| + | breakcmd(int argc, char **argv) |
| { | |
| - | 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); |
| - | } |
| - | |
| - | /* First look at the keywords */ |
| - | if (findkwd(command)) { |
| - | out1str(describe_command_verbose ? " is a shell keyword" : command); |
| - | goto out; |
| - | } |
| - | |
| - | #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; |
| - | } |
| - | 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; |
| - | |
| - | case CMDBUILTIN: |
| - | if (describe_command_verbose) { |
| - | out1fmt(" is a %sshell builtin", |
| - | IS_BUILTIN_SPECIAL(entry.u.cmd) ? |
| - | "special " : nullstr |
| - | ); |
| - | } else { |
| - | out1str(command); |
| - | } |
| - | break; |
| + | int n = argc > 1 ? number(argv[1]) : 1; |
| - | default: |
| - | if (describe_command_verbose) { |
| - | out1str(": not found\n"); |
| - | } |
| - | return 127; |
| + | 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; |
| } | |
| - | out: |
| - | outstr("\n", stdout); |
| return 0; | |
| } | |
| + | /* |
| + | * The return command. |
| + | */ |
| static int | |
| - | typecmd(int argc, char **argv) |
| + | returncmd(int argc, char **argv) |
| { | |
| - | int i; |
| - | int err = 0; |
| - | |
| - | 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 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 ENABLE_ASH_CMDCMD |
| static int | |
| - | commandcmd(int argc, char **argv) |
| + | falsecmd(int argc, char **argv) |
| { | |
| - | int c; |
| - | enum { |
| - | VERIFY_BRIEF = 1, |
| - | VERIFY_VERBOSE = 2, |
| - | } verify = 0; |
| + | return 1; |
| + | } |
| - | 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); |
| + | 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; | |
| } | |
| - | #endif |
| /* ============ input.c | |
| @@ -8437,314 +8670,109 @@ stoppedjobs(void) | |
| job_warning = 2; | |
| retval++; | |
| } | |
| - | out: |
| - | return retval; |
| - | } |
| - | |
| - | |
| - | /* ============ mail.c |
| - | * |
| - | * Routines to check for mail. |
| - | */ |
| - | #if ENABLE_ASH_MAIL |
| - | |
| - | #define MAXMBOXES 10 |
| - | |
| - | /* times of mailboxes */ |
| - | static time_t mailtime[MAXMBOXES]; |
| - | /* Set if MAIL or MAILPATH is changed. */ |
| - | static int mail_var_path_changed; |
| - | |
| - | /* |
| - | * Print appropriate message(s) if mail has arrived. |
| - | * If mail_var_path_changed is set, |
| - | * then the value of MAIL has mail_var_path_changed, |
| - | * so we just update the values. |
| - | */ |
| - | static void |
| - | chkmail(void) |
| - | { |
| - | const char *mpath; |
| - | char *p; |
| - | char *q; |
| - | time_t *mtp; |
| - | struct stackmark smark; |
| - | struct stat statb; |
| - | |
| - | setstackmark(&smark); |
| - | mpath = mpathset() ? mpathval() : mailval(); |
| - | for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { |
| - | p = padvance(&mpath, nullstr); |
| - | if (p == NULL) |
| - | break; |
| - | if (*p == '\0') |
| - | continue; |
| - | for (q = p; *q; q++); |
| - | #if DEBUG |
| - | if (q[-1] != '/') |
| - | abort(); |
| - | #endif |
| - | q[-1] = '\0'; /* delete trailing '/' */ |
| - | if (stat(p, &statb) < 0) { |
| - | *mtp = 0; |
| - | continue; |
| - | } |
| - | if (!mail_var_path_changed && statb.st_mtime != *mtp) { |
| - | fprintf( |
| - | stderr, snlfmt, |
| - | pathopt ? pathopt : "you have mail" |
| - | ); |
| - | } |
| - | *mtp = statb.st_mtime; |
| - | } |
| - | mail_var_path_changed = 0; |
| - | popstackmark(&smark); |
| - | } |
| - | |
| - | static void |
| - | changemail(const char *val) |
| - | { |
| - | mail_var_path_changed++; |
| - | } |
| - | #endif /* ASH_MAIL */ |
| - | |
| - | |
| - | /* ============ ??? */ |
| - | |
| - | /* |
| - | * Take commands from a file. To be compatible we should do a path |
| - | * search for the file, which is necessary to find sub-commands. |
| - | */ |
| - | static char * |
| - | find_dot_file(char *name) |
| - | { |
| - | char *fullname; |
| - | const char *path = pathval(); |
| - | struct stat statb; |
| - | |
| - | /* don't try this for absolute or relative paths */ |
| - | if (strchr(name, '/')) |
| - | return name; |
| - | |
| - | while ((fullname = padvance(&path, name)) != NULL) { |
| - | if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { |
| - | /* |
| - | * Don't bother freeing here, since it will |
| - | * be freed by the caller. |
| - | */ |
| - | return fullname; |
| - | } |
| - | stunalloc(fullname); |
| - | } |
| - | |
| - | /* not found in the PATH */ |
| - | ash_msg_and_raise_error("%s: not found", name); |
| - | /* NOTREACHED */ |
| - | } |
| - | |
| - | static void |
| - | calcsize(union node *n) |
| - | { |
| - | 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 void |
| - | sizenodelist(struct nodelist *lp) |
| - | { |
| - | while (lp) { |
| - | funcblocksize += SHELL_ALIGN(sizeof(struct nodelist)); |
| - | calcsize(lp->n); |
| - | lp = lp->next; |
| - | } |
| + | out: |
| + | return retval; |
| } | |
| - | static union node * |
| - | copynode(union node *n) |
| - | { |
| - | union node *new; |
| - | if (n == NULL) |
| - | return NULL; |
| - | new = funcblock; |
| - | funcblock = (char *) funcblock + nodesize[n->type]; |
| + | /* ============ mail.c |
| + | * |
| + | * Routines to check for mail. |
| + | */ |
| + | #if ENABLE_ASH_MAIL |
| - | 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; |
| - | } |
| + | #define MAXMBOXES 10 |
| - | static struct nodelist * |
| - | copynodelist(struct nodelist *lp) |
| + | /* times of mailboxes */ |
| + | static time_t mailtime[MAXMBOXES]; |
| + | /* Set if MAIL or MAILPATH is changed. */ |
| + | static int mail_var_path_changed; |
| + | |
| + | /* |
| + | * Print appropriate message(s) if mail has arrived. |
| + | * If mail_var_path_changed is set, |
| + | * then the value of MAIL has mail_var_path_changed, |
| + | * so we just update the values. |
| + | */ |
| + | static void |
| + | chkmail(void) |
| { | |
| - | struct nodelist *start; |
| - | struct nodelist **lpp; |
| + | const char *mpath; |
| + | char *p; |
| + | char *q; |
| + | time_t *mtp; |
| + | struct stackmark smark; |
| + | struct stat statb; |
| - | 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; |
| + | setstackmark(&smark); |
| + | mpath = mpathset() ? mpathval() : mailval(); |
| + | for (mtp = mailtime; mtp < mailtime + MAXMBOXES; mtp++) { |
| + | p = padvance(&mpath, nullstr); |
| + | if (p == NULL) |
| + | break; |
| + | if (*p == '\0') |
| + | continue; |
| + | for (q = p; *q; q++); |
| + | #if DEBUG |
| + | if (q[-1] != '/') |
| + | abort(); |
| + | #endif |
| + | q[-1] = '\0'; /* delete trailing '/' */ |
| + | if (stat(p, &statb) < 0) { |
| + | *mtp = 0; |
| + | continue; |
| + | } |
| + | if (!mail_var_path_changed && statb.st_mtime != *mtp) { |
| + | fprintf( |
| + | stderr, snlfmt, |
| + | pathopt ? pathopt : "you have mail" |
| + | ); |
| + | } |
| + | *mtp = statb.st_mtime; |
| } | |
| - | *lpp = NULL; |
| - | return start; |
| + | mail_var_path_changed = 0; |
| + | popstackmark(&smark); |
| + | } |
| + | |
| + | static void |
| + | changemail(const char *val) |
| + | { |
| + | mail_var_path_changed++; |
| } | |
| + | #endif /* ASH_MAIL */ |
| + | |
| + | /* ============ ??? */ |
| + | |
| + | /* |
| + | * Take commands from a file. To be compatible we should do a path |
| + | * search for the file, which is necessary to find sub-commands. |
| + | */ |
| static char * | |
| - | nodeckstrdup(char *s) |
| + | find_dot_file(char *name) |
| { | |
| - | char *rtn = funcstring; |
| + | char *fullname; |
| + | const char *path = pathval(); |
| + | struct stat statb; |
| - | strcpy(funcstring, s); |
| - | funcstring += strlen(s) + 1; |
| - | return rtn; |
| + | /* don't try this for absolute or relative paths */ |
| + | if (strchr(name, '/')) |
| + | return name; |
| + | |
| + | while ((fullname = padvance(&path, name)) != NULL) { |
| + | if ((stat(fullname, &statb) == 0) && S_ISREG(statb.st_mode)) { |
| + | /* |
| + | * Don't bother freeing here, since it will |
| + | * be freed by the caller. |
| + | */ |
| + | return fullname; |
| + | } |
| + | stunalloc(fullname); |
| + | } |
| + | |
| + | /* not found in the PATH */ |
| + | ash_msg_and_raise_error("%s: not found", name); |
| + | /* NOTREACHED */ |
| } | |
| /* | |
| @@ -8819,7 +8847,6 @@ minus_o(char *name, int val) | |
| optlist[i] ? "on" : "off"); | |
| } | |
| - | |
| static void | |
| setoption(int flag, int val) | |
| { | |
| @@ -11450,65 +11477,16 @@ exportcmd(int argc, char **argv) | |
| } | |
| /* | |
| - | * 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. |
| + | * Delete a function if it exists. |
| */ | |
| static void | |
| - | mklocal(char *name) |
| - | { |
| - | 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; |
| - | |
| - | 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; |
| - | } |
| - | |
| - | /* |
| - | * The "local" command. |
| - | */ |
| - | static int |
| - | localcmd(int argc, char **argv) |
| + | unsetfunc(const char *name) |
| { | |
| - | char *name; |
| + | struct tblentry *cmdp; |
| - | argv = argptr; |
| - | while ((name = *argv++) != NULL) { |
| - | mklocal(name); |
| - | } |
| - | return 0; |
| + | cmdp = cmdlookup(name, 0); |
| + | if (cmdp!= NULL && cmdp->cmdtype == CMDFUNCTION) |
| + | delete_cmd_entry(); |
| } | |
| /* | |