/* ============ 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
union node n;
};
+/*
+ * Free a parse tree.
+ */
+static void
+freefunc(struct funcnode *f)
+{
+ if (f && --f->count < 0)
+ free(f);
+}
+
/* ============ Debugging output */
#endif /* DEBUG */
-/* ============ Parser data
- *
+/* ============ Parser data */
+
+/*
* ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up.
*/
-
struct strlist {
struct strlist *next;
char *text;
}
-/* ============ ... */
+/* ============ nextopt */
static char **argptr; /* argument list for builtin commands */
static char *optionarg; /* set by nextopt (like getopt) */
}
-/* ============ 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
/* 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 */
#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 */
#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 */
/*
#define ALIASINUSE 1
#define ALIASDEAD 2
+#define ATABSIZE 39
+
struct alias {
struct alias *next;
char *name;
}
-/* ============ 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;
}
/*
}
/*
- * 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
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 */
}
/*
optlist[i] ? "on" : "off");
}
-
static void
setoption(int flag, int val)
{
}
/*
- * 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();
}
/*