From aa74445514a71aebfa5d9b22c5ba1c47635b4dba Mon Sep 17 00:00:00 2001 From: Denis Vlasenko Date: Fri, 23 Feb 2007 01:04:22 +0000 Subject: [PATCH] ash: cleanup part 2 --- shell/ash.c | 7492 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 3697 insertions(+), 3795 deletions(-) diff --git a/shell/ash.c b/shell/ash.c index 379e8ab..731b079 100644 --- a/shell/ash.c +++ b/shell/ash.c @@ -65,6 +65,13 @@ #error "Do not even bother, ash will not run on uClinux" #endif +#if DEBUG +#define TRACE(param) trace param +#define TRACEV(param) tracev param +#else +#define TRACE(param) +#define TRACEV(param) +#endif #ifdef __GLIBC__ /* glibc sucks */ @@ -141,6 +148,11 @@ static char optlist[NOPTS]; /* ============ Misc data */ +static char nullstr[1]; /* zero length string */ +static const char homestr[] = "HOME"; +static const char snlfmt[] = "%s\n"; +static const char illnum[] = "Illegal number: %s"; + static int isloginsh; /* pid of main shell */ static int rootpid; @@ -380,6 +392,11 @@ out2str(const char *p) * ash_vmsg() needs parsefile->fd, hence parsefile definition is moved up. */ +struct strlist { + struct strlist *next; + char *text; +}; + struct strpush { struct strpush *prev; /* preceding string on stack */ char *prevstring; @@ -623,6 +640,16 @@ stunalloc(void *p) stacknxt = p; } +/* + * Like strdup but works with the ash stack. + */ +static char * +ststrdup(const char *p) +{ + size_t len = strlen(p) + 1; + return memcpy(stalloc(len), p, len); +} + static void setstackmark(struct stackmark *mark) { @@ -785,584 +812,1602 @@ stack_putstr(const char *s, char *p) return stack_nputstr(s, strlen(s), p); } +static char * +_STPUTC(int c, char *p) +{ + if (p == sstrend) + p = growstackstr(); + *p++ = c; + return p; +} -/* ============ Unsorted yet */ - - -static void setpwd(const char *, int); - -/* expand.h */ +#define STARTSTACKSTR(p) ((p) = stackblock()) +#define STPUTC(c, p) ((p) = _STPUTC((c), (p))) +#define CHECKSTRSPACE(n, p) \ + ({ \ + char *q = (p); \ + size_t l = (n); \ + size_t m = sstrend - q; \ + if (l > m) \ + (p) = makestrspace(l, q); \ + 0; \ + }) +#define USTPUTC(c, p) (*p++ = (c)) +#define STACKSTRNUL(p) ((p) == sstrend ? (p = growstackstr(), *p = '\0') : (*p = '\0')) +#define STUNPUTC(p) (--p) +#define STTOPC(p) p[-1] +#define STADJUST(amount, p) (p += (amount)) -struct strlist { - struct strlist *next; - char *text; -}; +#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock()) +#define ungrabstackstr(s, p) stunalloc((s)) +#define stackstrend() ((void *)sstrend) -struct arglist { - struct strlist *list; - struct strlist **lastp; -}; +/* ============ String helpers */ /* - * expandarg() flags + * prefix -- see if pfx is a prefix of string. */ -#define EXP_FULL 0x1 /* perform word splitting & file globbing */ -#define EXP_TILDE 0x2 /* do normal tilde expansion */ -#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ -#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ -#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ -#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ -#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ -#define EXP_WORD 0x80 /* expand word in parameter expansion */ -#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ - - -union node; -static void expandarg(union node *, struct arglist *, int); -#define rmescapes(p) _rmescapes((p), 0) -static char *_rmescapes(char *, int); -static int casematch(union node *, char *); - -#if ENABLE_ASH_MATH_SUPPORT -static void expari(int); -#endif - -/* eval.h */ - +static char * +prefix(const char *string, const char *pfx) +{ + while (*pfx) { + if (*pfx++ != *string++) + return 0; + } + return (char *) string; +} +/* + * Check for a valid number. This should be elsewhere. + */ +static int +is_number(const char *p) +{ + do { + if (!isdigit(*p)) + return 0; + } while (*++p != '\0'); + return 1; +} -struct backcmd { /* result of evalbackcmd */ - int fd; /* file descriptor to read from */ - char *buf; /* buffer */ - int nleft; /* number of chars in buffer */ - struct job *jp; /* job structure for command */ -}; +/* + * Convert a string of digits to an integer, printing an error message on + * failure. + */ +static int +number(const char *s) +{ + if (!is_number(s)) + ash_msg_and_raise_error(illnum, s); + return atoi(s); +} /* - * This file was generated by the mknodes program. + * Produce a possibly single quoted string suitable as input to the shell. + * The return string is allocated on the stack. */ +static char * +single_quote(const char *s) +{ + char *p; -#define NCMD 0 -#define NPIPE 1 -#define NREDIR 2 -#define NBACKGND 3 -#define NSUBSHELL 4 -#define NAND 5 -#define NOR 6 -#define NSEMI 7 -#define NIF 8 -#define NWHILE 9 -#define NUNTIL 10 -#define NFOR 11 -#define NCASE 12 -#define NCLIST 13 -#define NDEFUN 14 -#define NARG 15 -#define NTO 16 -#define NCLOBBER 17 -#define NFROM 18 -#define NFROMTO 19 -#define NAPPEND 20 -#define NTOFD 21 -#define NFROMFD 22 -#define NHERE 23 -#define NXHERE 24 -#define NNOT 25 + STARTSTACKSTR(p); + do { + char *q; + size_t len; -struct ncmd { - int type; - union node *assign; - union node *args; - union node *redirect; -}; + len = strchrnul(s, '\'') - s; -struct npipe { - int type; - int backgnd; - struct nodelist *cmdlist; -}; + q = p = makestrspace(len + 3, p); -struct nredir { - int type; - union node *n; - union node *redirect; -}; + *q++ = '\''; + q = memcpy(q, s, len) + len; + *q++ = '\''; + s += len; -struct nbinary { - int type; - union node *ch1; - union node *ch2; -}; + STADJUST(q - p, p); -struct nif { - int type; - union node *test; - union node *ifpart; - union node *elsepart; -}; + len = strspn(s, "'"); + if (!len) + break; -struct nfor { - int type; - union node *args; - union node *body; - char *var; -}; + q = p = makestrspace(len + 3, p); -struct ncase { - int type; - union node *expr; - union node *cases; -}; + *q++ = '"'; + q = memcpy(q, s, len) + len; + *q++ = '"'; + s += len; -struct nclist { - int type; - union node *next; - union node *pattern; - union node *body; -}; + STADJUST(q - p, p); + } while (*s); -struct narg { - int type; - union node *next; - char *text; - struct nodelist *backquote; -}; + USTPUTC(0, p); -struct nfile { - int type; - union node *next; - int fd; - union node *fname; - char *expfname; -}; + return stackblock(); +} -struct ndup { - int type; - union node *next; - int fd; - int dupfd; - union node *vname; -}; -struct nhere { - int type; - union node *next; - int fd; - union node *doc; -}; +/* ============ ... */ -struct nnot { - int type; - union node *com; -}; +static char **argptr; /* argument list for builtin commands */ +static char *optionarg; /* set by nextopt (like getopt) */ +static char *optptr; /* used by nextopt */ -union node { - int type; - struct ncmd ncmd; - struct npipe npipe; - struct nredir nredir; - struct nbinary nbinary; - struct nif nif; - struct nfor nfor; - struct ncase ncase; - struct nclist nclist; - struct narg narg; - struct nfile nfile; - struct ndup ndup; - struct nhere nhere; - struct nnot nnot; -}; +/* + * XXX - should get rid of. have all builtins use getopt(3). the + * library getopt must have the BSD extension static variable "optreset" + * otherwise it can't be used within the shell safely. + * + * Standard option processing (a la getopt) for builtin routines. The + * only argument that is passed to nextopt is the option string; the + * other arguments are unnecessary. It return the character, or '\0' on + * end of input. + */ +static int +nextopt(const char *optstring) +{ + char *p; + const char *q; + char c; -struct nodelist { - struct nodelist *next; - union node *n; -}; + p = optptr; + if (p == NULL || *p == '\0') { + p = *argptr; + if (p == NULL || *p != '-' || *++p == '\0') + return '\0'; + argptr++; + if (LONE_DASH(p)) /* check for "--" */ + return '\0'; + } + c = *p++; + for (q = optstring; *q != c; ) { + if (*q == '\0') + ash_msg_and_raise_error("Illegal option -%c", c); + if (*++q == ':') + q++; + } + if (*++q == ':') { + if (*p == '\0' && (p = *argptr++) == NULL) + ash_msg_and_raise_error("No arg for -%c option", c); + optionarg = p; + p = NULL; + } + optptr = p; + return c; +} -struct funcnode { - int count; - union node n; -}; +/* ============ Variables */ -static void freefunc(struct funcnode *); -/* parser.h */ +/* flags */ +#define VEXPORT 0x01 /* variable is exported */ +#define VREADONLY 0x02 /* variable cannot be modified */ +#define VSTRFIXED 0x04 /* variable struct is statically allocated */ +#define VTEXTFIXED 0x08 /* text is statically allocated */ +#define VSTACK 0x10 /* text is allocated on the stack */ +#define VUNSET 0x20 /* the variable is not set */ +#define VNOFUNC 0x40 /* don't call the callback function */ +#define VNOSET 0x80 /* do not set variable - just readonly test */ +#define VNOSAVE 0x100 /* when text is on the heap before setvareq */ +#ifdef DYNAMIC_VAR +# define VDYNAMIC 0x200 /* dynamic variable */ +# else +# define VDYNAMIC 0 +#endif -/* 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 */ +#if ENABLE_LOCALE_SUPPORT +static void change_lc_all(const char *value); +static void change_lc_ctype(const char *value); +#endif -/* 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 */ +static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin"; +#ifdef IFS_BROKEN +static const char defifsvar[] = "IFS= \t\n"; +#define defifs (defifsvar + 4) +#else +static const char defifs[] = " \t\n"; +#endif -/* 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} */ +struct var { + struct var *next; /* next entry in hash list */ + int flags; /* flags are defined above */ + const char *text; /* name=value */ + void (*func)(const char *); /* function to be called when */ + /* the variable gets set/unset */ +}; -/* values of checkkwd variable */ -#define CHKALIAS 0x1 -#define CHKKWD 0x2 -#define CHKNL 0x4 +struct localvar { + struct localvar *next; /* next local variable in list */ + struct var *vp; /* the variable that was made local */ + int flags; /* saved flags */ + const char *text; /* saved text */ +}; -#define IBUFSIZ (BUFSIZ + 1) +/* Forward decls for varinit[] */ +#if ENABLE_ASH_MAIL +static void chkmail(void); +static void changemail(const char *); +#endif +static void changepath(const char *); +#if ENABLE_ASH_GETOPTS +static void getoptsreset(const char *); +#endif +#if ENABLE_ASH_RANDOM_SUPPORT +static void change_random(const char *); +#endif + +static struct var varinit[] = { +#ifdef IFS_BROKEN + { 0, VSTRFIXED|VTEXTFIXED, defifsvar, 0 }, +#else + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0", 0 }, +#endif + +#if ENABLE_ASH_MAIL + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0", changemail }, + { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail }, +#endif + + { 0, VSTRFIXED|VTEXTFIXED, defpathvar, changepath }, + { 0, VSTRFIXED|VTEXTFIXED, "PS1=$ ", 0 }, + { 0, VSTRFIXED|VTEXTFIXED, "PS2=> ", 0 }, + { 0, VSTRFIXED|VTEXTFIXED, "PS4=+ ", 0 }, +#if ENABLE_ASH_GETOPTS + { 0, VSTRFIXED|VTEXTFIXED, "OPTIND=1", getoptsreset }, +#endif +#if ENABLE_ASH_RANDOM_SUPPORT + {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random }, +#endif +#if ENABLE_LOCALE_SUPPORT + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all }, + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype }, +#endif +#if ENABLE_FEATURE_EDITING_SAVEHISTORY + {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL }, +#endif +}; + +#define vifs varinit[0] +#if ENABLE_ASH_MAIL +#define vmail (&vifs)[1] +#define vmpath (&vmail)[1] +#else +#define vmpath vifs +#endif +#define vpath (&vmpath)[1] +#define vps1 (&vpath)[1] +#define vps2 (&vps1)[1] +#define vps4 (&vps2)[1] +#define voptind (&vps4)[1] +#if ENABLE_ASH_GETOPTS +#define vrandom (&voptind)[1] +#else +#define vrandom (&vps4)[1] +#endif +#define defpath (defpathvar + 5) /* - * NEOF is returned by parsecmd when it encounters an end of file. It - * must be distinct from NULL, so we use the address of a variable that - * happens to be handy. + * The following macros access the values of the above variables. + * They have to skip over the name. They return the null string + * for unset variables. */ -static int plinno = 1; /* input line number */ +#define ifsval() (vifs.text + 4) +#define ifsset() ((vifs.flags & VUNSET) == 0) +#define mailval() (vmail.text + 5) +#define mpathval() (vmpath.text + 9) +#define pathval() (vpath.text + 5) +#define ps1val() (vps1.text + 4) +#define ps2val() (vps2.text + 4) +#define ps4val() (vps4.text + 4) +#define optindval() (voptind.text + 7) -/* number of characters left in input buffer */ -static int parsenleft; /* copy of parsefile->nleft */ -static int parselleft; /* copy of parsefile->lleft */ +#define mpathset() ((vmpath.flags & VUNSET) == 0) -/* next character in input buffer */ -static char *parsenextc; /* copy of parsefile->nextc */ +static struct var **hashvar(const char *); -#define basebuf bb_common_bufsiz1 /* buffer for top level input file */ +static int loopnest; /* current loop nesting level */ +/* + * The parsefile structure pointed to by the global variable parsefile + * contains information about the current file being read. + */ +struct redirtab { + struct redirtab *next; + int renamed[10]; + int nullredirs; +}; -static int tokpushback; /* last token pushed back */ -#define NEOF ((union node *)&tokpushback) -static int parsebackquote; /* nonzero if we are inside backquotes */ -static int doprompt; /* if set, prompt the user */ -static int needprompt; /* true if interactive and at start of line */ -static int lasttoken; /* last token read */ -static char *wordtext; /* text of last word returned by readtoken */ -static int checkkwd; -static struct nodelist *backquotelist; -static union node *redirnode; -static struct heredoc *heredoc; -static int quoteflag; /* set if (part of) last token was quoted */ +static struct redirtab *redirlist; +static int nullredirs; -static void fixredir(union node *, const char *, int); -static char *endofname(const char *); +extern char **environ; -/* shell.h */ +static int preverrout_fd; /* save fd2 before print debug if xflag is set. */ -static char nullstr[1]; /* zero length string */ -static const char spcstr[] = " "; -static const char snlfmt[] = "%s\n"; -static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; -static const char illnum[] = "Illegal number: %s"; -static const char homestr[] = "HOME"; - -#if DEBUG -#define TRACE(param) trace param -#define TRACEV(param) tracev param -#else -#define TRACE(param) -#define TRACEV(param) +struct shparam { + int nparam; /* # of positional parameters (without $0) */ + unsigned char malloc; /* if parameter list dynamically allocated */ + char **p; /* parameter list */ +#if ENABLE_ASH_GETOPTS + int optind; /* next parameter to be processed by getopts */ + int optoff; /* used by getopts */ #endif +}; -#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) -#define __builtin_expect(x, expected_value) (x) -#endif +static struct shparam shellparam; /* $@ current positional parameters */ -#define xlikely(x) __builtin_expect((x),1) +#define VTABSIZE 39 +static struct var *vartab[VTABSIZE]; -#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 +#if ENABLE_ASH_GETOPTS +static void +getoptsreset(const char *value) +{ + shellparam.optind = number(value); + shellparam.optoff = -1; +} +#endif -/* 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 is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) +#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) -static const char * -tokname(int tok) +/* + * Return of a legal variable name (a letter or underscore followed by zero or + * more letters, underscores, and digits). + */ +static char * +endofname(const char *name) { - static char buf[16]; + char *p; - if (tok >= TSEMI) - buf[0] = '"'; - sprintf(buf + (tok >= TSEMI), "%s%c", - tokname_array[tok] + 1, (tok >= TSEMI ? '"' : 0)); - return buf; + p = (char *) name; + if (!is_name(*p)) + return p; + while (*++p) { + if (!is_in_name(*p)) + break; + } + return p; } -/* Wrapper around strcmp for qsort/bsearch/... */ +/* + * Compares two strings up to the first = or '\0'. The first + * string must be terminated by '='; the second may be terminated by + * either '=' or '\0'. + */ static int -pstrcmp(const void *a, const void *b) +varcmp(const char *p, const char *q) { - return strcmp((const char *) a, (*(const char *const *) b) + 1); + int c, d; + + while ((c = *p) == (d = *q)) { + if (!c || c == '=') + goto out; + p++; + q++; + } + if (c == '=') + c = 0; + if (d == '=') + d = 0; + out: + return c - d; } -static const char *const * -findkwd(const char *s) +static int +varequal(const char *a, const char *b) { - return bsearch(s, tokname_array + KWDOFFSET, - (sizeof(tokname_array) / sizeof(const char *)) - KWDOFFSET, - sizeof(const char *), pstrcmp); + return !varcmp(a, b); } -/* Syntax classes */ -#define CWORD 0 /* character is nothing special */ -#define CNL 1 /* newline character */ -#define CBACK 2 /* a backslash character */ -#define CSQUOTE 3 /* single quote */ -#define CDQUOTE 4 /* double quote */ -#define CENDQUOTE 5 /* a terminating quote */ -#define CBQUOTE 6 /* backwards single quote */ -#define CVAR 7 /* a dollar sign */ -#define CENDVAR 8 /* a '}' character */ -#define CLP 9 /* a left paren in arithmetic */ -#define CRP 10 /* a right paren in arithmetic */ -#define CENDFILE 11 /* end of file */ -#define CCTL 12 /* like CWORD, except it must be escaped */ -#define CSPCL 13 /* these terminate a word */ -#define CIGN 14 /* character should be ignored */ - -#if ENABLE_ASH_ALIAS -#define SYNBASE 130 -#define PEOF -130 -#define PEOA -129 -#define PEOA_OR_PEOF PEOA -#else -#define SYNBASE 129 -#define PEOF -129 -#define PEOA_OR_PEOF PEOF -#endif +/* + * Find the appropriate entry in the hash table from the name. + */ +static struct var ** +hashvar(const char *p) +{ + unsigned hashval; -#define is_digit(c) ((unsigned)((c) - '0') <= 9) -#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) -#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) + hashval = ((unsigned char) *p) << 4; + while (*p && *p != '=') + hashval += (unsigned char) *p++; + return &vartab[hashval % VTABSIZE]; +} -/* C99 say: "char" declaration may be signed or unsigned default */ -#define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int) +static int +vpcmp(const void *a, const void *b) +{ + return varcmp(*(const char **)a, *(const char **)b); +} /* - * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise - * (assuming ascii char codes, as the original implementation did) + * This routine initializes the builtin variables. */ -#define is_special(c) \ - ( (((unsigned int)c) - 33 < 32) \ - && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1)) +static void +initvar(void) +{ + struct var *vp; + struct var *end; + struct var **vpp; -#define digit_val(c) ((c) - '0') + /* + * PS1 depends on uid + */ +#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT + vps1.text = "PS1=\\w \\$ "; +#else + if (!geteuid()) + vps1.text = "PS1=# "; +#endif + vp = varinit; + end = vp + sizeof(varinit) / sizeof(varinit[0]); + do { + vpp = hashvar(vp->text); + vp->next = *vpp; + *vpp = vp; + } while (++vp < end); +} + +static struct var ** +findvar(struct var **vpp, const char *name) +{ + for (; *vpp; vpp = &(*vpp)->next) { + if (varequal((*vpp)->text, name)) { + break; + } + } + return vpp; +} /* - * This file was generated by the mksyntax program. + * Find the value of a variable. Returns NULL if not set. */ +static char * +lookupvar(const char *name) +{ + struct var *v; -#if ENABLE_ASH_OPTIMIZE_FOR_SIZE -#define USE_SIT_FUNCTION + v = *findvar(hashvar(name), name); + if (v) { +#ifdef DYNAMIC_VAR + /* + * Dynamic variables are implemented roughly the same way they are + * in bash. Namely, they're "special" so long as they aren't unset. + * As soon as they're unset, they're no longer dynamic, and dynamic + * lookup will no longer happen at that point. -- PFM. + */ + if ((v->flags & VDYNAMIC)) + (*v->func)(NULL); #endif + if (!(v->flags & VUNSET)) + return strchrnul(v->text, '=') + 1; + } + return NULL; +} -/* number syntax index */ -#define BASESYNTAX 0 /* not in quotes */ -#define DQSYNTAX 1 /* in double quotes */ -#define SQSYNTAX 2 /* in single quotes */ -#define ARISYNTAX 3 /* in arithmetic */ - -#if ENABLE_ASH_MATH_SUPPORT -static const char S_I_T[][4] = { -#if ENABLE_ASH_ALIAS - {CSPCL, CIGN, CIGN, CIGN}, /* 0, PEOA */ -#endif - {CSPCL, CWORD, CWORD, CWORD}, /* 1, ' ' */ - {CNL, CNL, CNL, CNL}, /* 2, \n */ - {CWORD, CCTL, CCTL, CWORD}, /* 3, !*-/:=?[]~ */ - {CDQUOTE, CENDQUOTE, CWORD, CWORD}, /* 4, '"' */ - {CVAR, CVAR, CWORD, CVAR}, /* 5, $ */ - {CSQUOTE, CWORD, CENDQUOTE, CWORD}, /* 6, "'" */ - {CSPCL, CWORD, CWORD, CLP}, /* 7, ( */ - {CSPCL, CWORD, CWORD, CRP}, /* 8, ) */ - {CBACK, CBACK, CCTL, CBACK}, /* 9, \ */ - {CBQUOTE, CBQUOTE, CWORD, CBQUOTE}, /* 10, ` */ - {CENDVAR, CENDVAR, CWORD, CENDVAR}, /* 11, } */ -#ifndef USE_SIT_FUNCTION - {CENDFILE, CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ - {CWORD, CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ - {CCTL, CCTL, CCTL, CCTL} /* 14, CTLESC ... */ -#endif -}; -#else -static const char S_I_T[][3] = { -#if ENABLE_ASH_ALIAS - {CSPCL, CIGN, CIGN}, /* 0, PEOA */ -#endif - {CSPCL, CWORD, CWORD}, /* 1, ' ' */ - {CNL, CNL, CNL}, /* 2, \n */ - {CWORD, CCTL, CCTL}, /* 3, !*-/:=?[]~ */ - {CDQUOTE, CENDQUOTE, CWORD}, /* 4, '"' */ - {CVAR, CVAR, CWORD}, /* 5, $ */ - {CSQUOTE, CWORD, CENDQUOTE}, /* 6, "'" */ - {CSPCL, CWORD, CWORD}, /* 7, ( */ - {CSPCL, CWORD, CWORD}, /* 8, ) */ - {CBACK, CBACK, CCTL}, /* 9, \ */ - {CBQUOTE, CBQUOTE, CWORD}, /* 10, ` */ - {CENDVAR, CENDVAR, CWORD}, /* 11, } */ -#ifndef USE_SIT_FUNCTION - {CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ - {CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ - {CCTL, CCTL, CCTL} /* 14, CTLESC ... */ -#endif -}; -#endif /* ASH_MATH_SUPPORT */ +/* + * Search the environment of a builtin command. + */ +static char * +bltinlookup(const char *name) +{ + struct strlist *sp; -#ifdef USE_SIT_FUNCTION + for (sp = cmdenviron; sp; sp = sp->next) { + if (varequal(sp->text, name)) + return strchrnul(sp->text, '=') + 1; + } + return lookupvar(name); +} -#define U_C(c) ((unsigned char)(c)) +/* + * Same as setvar except that the variable and value are passed in + * the first argument as name=value. Since the first argument will + * be actually stored in the table, it should not be a string that + * will go away. + * Called with interrupts off. + */ +static void +setvareq(char *s, int flags) +{ + struct var *vp, **vpp; + + vpp = hashvar(s); + flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); + vp = *findvar(vpp, s); + if (vp) { + if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { + const char *n; + + if (flags & VNOSAVE) + free(s); + n = vp->text; + ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n); + } + + if (flags & VNOSET) + return; + + if (vp->func && (flags & VNOFUNC) == 0) + (*vp->func)(strchrnul(s, '=') + 1); + + if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) + free((char*)vp->text); + + flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); + } else { + if (flags & VNOSET) + return; + /* not found */ + vp = ckmalloc(sizeof(*vp)); + vp->next = *vpp; + vp->func = NULL; + *vpp = vp; + } + if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE))) + s = ckstrdup(s); + vp->text = s; + vp->flags = flags; +} + +/* + * Set the value of a variable. The flags argument is ored with the + * flags of the variable. If val is NULL, the variable is unset. + */ +static void +setvar(const char *name, const char *val, int flags) +{ + char *p, *q; + size_t namelen; + char *nameeq; + size_t vallen; + + q = endofname(name); + p = strchrnul(q, '='); + namelen = p - name; + if (!namelen || p != q) + ash_msg_and_raise_error("%.*s: bad variable name", namelen, name); + vallen = 0; + if (val == NULL) { + flags |= VUNSET; + } else { + vallen = strlen(val); + } + INT_OFF; + nameeq = ckmalloc(namelen + vallen + 2); + p = memcpy(nameeq, name, namelen) + namelen; + if (val) { + *p++ = '='; + p = memcpy(p, val, vallen) + vallen; + } + *p = '\0'; + setvareq(nameeq, flags | VNOSAVE); + INT_ON; +} +#if ENABLE_ASH_GETOPTS +/* + * Safe version of setvar, returns 1 on success 0 on failure. + */ static int -SIT(int c, int syntax) +setvarsafe(const char *name, const char *val, int flags) { - static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~"; -#if ENABLE_ASH_ALIAS - static const char syntax_index_table[] = { - 1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */ - 7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */ - 3, 1, 3, 3, 9, 3, 10, 1, /* "=>?[\\]`|" */ - 11, 3 /* "}~" */ - }; -#else - static const char syntax_index_table[] = { - 0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */ - 6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */ - 2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */ - 10, 2 /* "}~" */ - }; -#endif - const char *s; - int indx; + int err; + volatile int saveint; + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; - if (c == PEOF) /* 2^8+2 */ - return CENDFILE; -#if ENABLE_ASH_ALIAS - if (c == PEOA) /* 2^8+1 */ - indx = 0; - else -#endif - if (U_C(c) >= U_C(CTLESC) && U_C(c) <= U_C(CTLQUOTEMARK)) - return CCTL; + SAVE_INT(saveint); + if (setjmp(jmploc.loc)) + err = 1; else { - s = strchr(spec_symbls, c); - if (s == NULL || *s == '\0') - return CWORD; - indx = syntax_index_table[(s - spec_symbls)]; + exception_handler = &jmploc; + setvar(name, val, flags); + err = 0; } - return S_I_T[indx][syntax]; + exception_handler = savehandler; + RESTORE_INT(saveint); + return err; } +#endif -#else /* USE_SIT_FUNCTION */ - -#define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax] +/* + * Unset the specified variable. + */ +static int +unsetvar(const char *s) +{ + struct var **vpp; + struct var *vp; + int retval; -#if ENABLE_ASH_ALIAS -#define CSPCL_CIGN_CIGN_CIGN 0 -#define CSPCL_CWORD_CWORD_CWORD 1 -#define CNL_CNL_CNL_CNL 2 -#define CWORD_CCTL_CCTL_CWORD 3 -#define CDQUOTE_CENDQUOTE_CWORD_CWORD 4 -#define CVAR_CVAR_CWORD_CVAR 5 -#define CSQUOTE_CWORD_CENDQUOTE_CWORD 6 -#define CSPCL_CWORD_CWORD_CLP 7 -#define CSPCL_CWORD_CWORD_CRP 8 -#define CBACK_CBACK_CCTL_CBACK 9 -#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 10 -#define CENDVAR_CENDVAR_CWORD_CENDVAR 11 -#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 12 -#define CWORD_CWORD_CWORD_CWORD 13 -#define CCTL_CCTL_CCTL_CCTL 14 -#else -#define CSPCL_CWORD_CWORD_CWORD 0 -#define CNL_CNL_CNL_CNL 1 -#define CWORD_CCTL_CCTL_CWORD 2 -#define CDQUOTE_CENDQUOTE_CWORD_CWORD 3 -#define CVAR_CVAR_CWORD_CVAR 4 -#define CSQUOTE_CWORD_CENDQUOTE_CWORD 5 -#define CSPCL_CWORD_CWORD_CLP 6 -#define CSPCL_CWORD_CWORD_CRP 7 -#define CBACK_CBACK_CCTL_CBACK 8 -#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 9 -#define CENDVAR_CENDVAR_CWORD_CENDVAR 10 -#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 11 -#define CWORD_CWORD_CWORD_CWORD 12 -#define CCTL_CCTL_CCTL_CCTL 13 -#endif + vpp = findvar(hashvar(s), s); + vp = *vpp; + retval = 2; + if (vp) { + int flags = vp->flags; -static const char syntax_index_table[258] = { - /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */ + retval = 1; + if (flags & VREADONLY) + goto out; +#ifdef DYNAMIC_VAR + vp->flags &= ~VDYNAMIC; +#endif + if (flags & VUNSET) + goto ok; + if ((flags & VSTRFIXED) == 0) { + INT_OFF; + if ((flags & (VTEXTFIXED|VSTACK)) == 0) + free((char*)vp->text); + *vpp = vp->next; + free(vp); + INT_ON; + } else { + setvar(s, 0, 0); + vp->flags &= ~VEXPORT; + } + ok: + retval = 0; + } + out: + return retval; +} + +/* + * Process a linked list of variable assignments. + */ +static void +listsetvar(struct strlist *list_set_var, int flags) +{ + struct strlist *lp = list_set_var; + + if (!lp) + return; + INT_OFF; + do { + setvareq(lp->text, flags); + } while ((lp = lp->next)); + INT_ON; +} + +/* + * Generate a list of variables satisfying the given conditions. + */ +static char ** +listvars(int on, int off, char ***end) +{ + struct var **vpp; + struct var *vp; + char **ep; + int mask; + + STARTSTACKSTR(ep); + vpp = vartab; + mask = on | off; + do { + for (vp = *vpp; vp; vp = vp->next) { + if ((vp->flags & mask) == on) { + if (ep == stackstrend()) + ep = growstackstr(); + *ep++ = (char *) vp->text; + } + } + } while (++vpp < vartab + VTABSIZE); + if (ep == stackstrend()) + ep = growstackstr(); + if (end) + *end = ep; + *ep++ = NULL; + return grabstackstr(ep); +} + + +/* ============ Path search helper + * + * The variable path (passed by reference) should be set to the start + * of the path before the first call; padvance will update + * this value as it proceeds. Successive calls to padvance will return + * the possible path expansions in sequence. If an option (indicated by + * a percent sign) appears in the path entry then the global variable + * pathopt will be set to point to it; otherwise pathopt will be set to + * NULL. + */ +static const char *pathopt; /* set by padvance */ + +static char * +padvance(const char **path, const char *name) +{ + const char *p; + char *q; + const char *start; + size_t len; + + if (*path == NULL) + return NULL; + start = *path; + for (p = start; *p && *p != ':' && *p != '%'; p++); + len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ + while (stackblocksize() < len) + growstackblock(); + q = stackblock(); + if (p != start) { + memcpy(q, start, p - start); + q += p - start; + *q++ = '/'; + } + strcpy(q, name); + pathopt = NULL; + if (*p == '%') { + pathopt = ++p; + while (*p && *p != ':') p++; + } + if (*p == ':') + *path = p + 1; + else + *path = NULL; + return stalloc(len); +} + + +/* ============ Prompt */ + +static int doprompt; /* if set, prompt the user */ +static int needprompt; /* true if interactive and at start of line */ + +#if ENABLE_FEATURE_EDITING +static line_input_t *line_input_state; +static const char *cmdedit_prompt; +static void +putprompt(const char *s) +{ + if (ENABLE_ASH_EXPAND_PRMT) { + free((char*)cmdedit_prompt); + cmdedit_prompt = xstrdup(s); + return; + } + cmdedit_prompt = s; +} +#else +static void +putprompt(const char *s) +{ + out2str(s); +} +#endif + +#if ENABLE_ASH_EXPAND_PRMT +/* expandstr() needs parsing machinery, so it is far away ahead... */ +static const char *expandstr(const char *ps); +#else +#define expandstr(s) s +#endif + +static void +setprompt(int whichprompt) +{ + const char *prompt; +#if ENABLE_ASH_EXPAND_PRMT + struct stackmark smark; +#endif + + needprompt = 0; + + switch (whichprompt) { + case 1: + prompt = ps1val(); + break; + case 2: + prompt = ps2val(); + break; + default: /* 0 */ + prompt = nullstr; + } +#if ENABLE_ASH_EXPAND_PRMT + setstackmark(&smark); + stalloc(stackblocksize()); +#endif + putprompt(expandstr(prompt)); +#if ENABLE_ASH_EXPAND_PRMT + popstackmark(&smark); +#endif +} + + +/* ============ The cd and pwd commands */ + +#define CD_PHYSICAL 1 +#define CD_PRINT 2 + +static int docd(const char *, int); + +static char *curdir = nullstr; /* current working directory */ +static char *physdir = nullstr; /* physical working directory */ + +static int +cdopt(void) +{ + int flags = 0; + int i, j; + + j = 'L'; + while ((i = nextopt("LP"))) { + if (i != j) { + flags ^= CD_PHYSICAL; + j = i; + } + } + + return flags; +} + +/* + * Update curdir (the name of the current directory) in response to a + * cd command. + */ +static const char * +updatepwd(const char *dir) +{ + char *new; + char *p; + char *cdcomppath; + const char *lim; + + cdcomppath = ststrdup(dir); + STARTSTACKSTR(new); + if (*dir != '/') { + if (curdir == nullstr) + return 0; + new = stack_putstr(curdir, new); + } + new = makestrspace(strlen(dir) + 2, new); + lim = stackblock() + 1; + if (*dir != '/') { + if (new[-1] != '/') + USTPUTC('/', new); + if (new > lim && *lim == '/') + lim++; + } else { + USTPUTC('/', new); + cdcomppath++; + if (dir[1] == '/' && dir[2] != '/') { + USTPUTC('/', new); + cdcomppath++; + lim++; + } + } + p = strtok(cdcomppath, "/"); + while (p) { + switch (*p) { + case '.': + if (p[1] == '.' && p[2] == '\0') { + while (new > lim) { + STUNPUTC(new); + if (new[-1] == '/') + break; + } + break; + } else if (p[1] == '\0') + break; + /* fall through */ + default: + new = stack_putstr(p, new); + USTPUTC('/', new); + } + p = strtok(0, "/"); + } + if (new > lim) + STUNPUTC(new); + *new = 0; + return stackblock(); +} + +/* + * Find out what the current directory is. If we already know the current + * directory, this routine returns immediately. + */ +static char * +getpwd(void) +{ + char *dir = getcwd(0, 0); + return dir ? dir : nullstr; +} + +static void +setpwd(const char *val, int setold) +{ + char *oldcur, *dir; + + oldcur = dir = curdir; + + if (setold) { + setvar("OLDPWD", oldcur, VEXPORT); + } + INT_OFF; + if (physdir != nullstr) { + if (physdir != oldcur) + free(physdir); + physdir = nullstr; + } + if (oldcur == val || !val) { + char *s = getpwd(); + physdir = s; + if (!val) + dir = s; + } else + dir = ckstrdup(val); + if (oldcur != dir && oldcur != nullstr) { + free(oldcur); + } + curdir = dir; + INT_ON; + setvar("PWD", dir, VEXPORT); +} + +static void hashcd(void); + +/* + * Actually do the chdir. We also call hashcd to let the routines in exec.c + * know that the current directory has changed. + */ +static int +docd(const char *dest, int flags) +{ + const char *dir = 0; + int err; + + TRACE(("docd(\"%s\", %d) called\n", dest, flags)); + + INT_OFF; + if (!(flags & CD_PHYSICAL)) { + dir = updatepwd(dest); + if (dir) + dest = dir; + } + err = chdir(dest); + if (err) + goto out; + setpwd(dir, 1); + hashcd(); + out: + INT_ON; + return err; +} + +static int +cdcmd(int argc, char **argv) +{ + const char *dest; + const char *path; + const char *p; + char c; + struct stat statb; + int flags; + + flags = cdopt(); + dest = *argptr; + if (!dest) + dest = bltinlookup(homestr); + else if (LONE_DASH(dest)) { + dest = bltinlookup("OLDPWD"); + flags |= CD_PRINT; + } + if (!dest) + dest = nullstr; + if (*dest == '/') + goto step7; + if (*dest == '.') { + c = dest[1]; + dotdot: + switch (c) { + case '\0': + case '/': + goto step6; + case '.': + c = dest[2]; + if (c != '.') + goto dotdot; + } + } + if (!*dest) + dest = "."; + path = bltinlookup("CDPATH"); + if (!path) { + step6: + step7: + p = dest; + goto docd; + } + do { + c = *path; + p = padvance(&path, dest); + if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { + if (c && c != ':') + flags |= CD_PRINT; + docd: + if (!docd(p, flags)) + goto out; + break; + } + } while (path); + ash_msg_and_raise_error("can't cd to %s", dest); + /* NOTREACHED */ + out: + if (flags & CD_PRINT) + out1fmt(snlfmt, curdir); + return 0; +} + +static int +pwdcmd(int argc, char **argv) +{ + int flags; + const char *dir = curdir; + + flags = cdopt(); + if (flags) { + if (physdir == nullstr) + setpwd(dir, 0); + dir = physdir; + } + out1fmt(snlfmt, dir); + return 0; +} + + +/* ============ Unsorted yet */ + + +/* expand.h */ + +struct arglist { + struct strlist *list; + struct strlist **lastp; +}; + +/* + * expandarg() flags + */ +#define EXP_FULL 0x1 /* perform word splitting & file globbing */ +#define EXP_TILDE 0x2 /* do normal tilde expansion */ +#define EXP_VARTILDE 0x4 /* expand tildes in an assignment */ +#define EXP_REDIR 0x8 /* file glob for a redirection (1 match only) */ +#define EXP_CASE 0x10 /* keeps quotes around for CASE pattern */ +#define EXP_RECORD 0x20 /* need to record arguments for ifs breakup */ +#define EXP_VARTILDE2 0x40 /* expand tildes after colons only */ +#define EXP_WORD 0x80 /* expand word in parameter expansion */ +#define EXP_QWORD 0x100 /* expand word in quoted parameter expansion */ + + +union node; +static void expandarg(union node *, struct arglist *, int); +#define rmescapes(p) _rmescapes((p), 0) +static char *_rmescapes(char *, int); +static int casematch(union node *, char *); + +#if ENABLE_ASH_MATH_SUPPORT +static void expari(int); +#endif + +/* eval.h */ + + + +struct backcmd { /* result of evalbackcmd */ + int fd; /* file descriptor to read from */ + char *buf; /* buffer */ + int nleft; /* number of chars in buffer */ + struct job *jp; /* job structure for command */ +}; + +/* + * This file was generated by the mknodes program. + */ + +#define NCMD 0 +#define NPIPE 1 +#define NREDIR 2 +#define NBACKGND 3 +#define NSUBSHELL 4 +#define NAND 5 +#define NOR 6 +#define NSEMI 7 +#define NIF 8 +#define NWHILE 9 +#define NUNTIL 10 +#define NFOR 11 +#define NCASE 12 +#define NCLIST 13 +#define NDEFUN 14 +#define NARG 15 +#define NTO 16 +#define NCLOBBER 17 +#define NFROM 18 +#define NFROMTO 19 +#define NAPPEND 20 +#define NTOFD 21 +#define NFROMFD 22 +#define NHERE 23 +#define NXHERE 24 +#define NNOT 25 + + +struct ncmd { + int type; + union node *assign; + union node *args; + union node *redirect; +}; + +struct npipe { + int type; + int backgnd; + struct nodelist *cmdlist; +}; + +struct nredir { + int type; + union node *n; + union node *redirect; +}; + +struct nbinary { + int type; + union node *ch1; + union node *ch2; +}; + +struct nif { + int type; + union node *test; + union node *ifpart; + union node *elsepart; +}; + +struct nfor { + int type; + union node *args; + union node *body; + char *var; +}; + +struct ncase { + int type; + union node *expr; + union node *cases; +}; + +struct nclist { + int type; + union node *next; + union node *pattern; + union node *body; +}; + +struct narg { + int type; + union node *next; + char *text; + struct nodelist *backquote; +}; + +struct nfile { + int type; + union node *next; + int fd; + union node *fname; + char *expfname; +}; + +struct ndup { + int type; + union node *next; + int fd; + int dupfd; + union node *vname; +}; + +struct nhere { + int type; + union node *next; + int fd; + union node *doc; +}; + +struct nnot { + int type; + union node *com; +}; + +union node { + int type; + struct ncmd ncmd; + struct npipe npipe; + struct nredir nredir; + struct nbinary nbinary; + struct nif nif; + struct nfor nfor; + struct ncase ncase; + struct nclist nclist; + struct narg narg; + struct nfile nfile; + struct ndup ndup; + struct nhere nhere; + struct nnot nnot; +}; + +struct nodelist { + struct nodelist *next; + union node *n; +}; + +struct funcnode { + int count; + union node n; +}; + + +static void freefunc(struct funcnode *); +/* 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 + * must be distinct from NULL, so we use the address of a variable that + * happens to be handy. + */ +static int plinno = 1; /* input line number */ + +/* number of characters left in input buffer */ +static int parsenleft; /* copy of parsefile->nleft */ +static int parselleft; /* copy of parsefile->lleft */ + +/* next character in input buffer */ +static char *parsenextc; /* copy of parsefile->nextc */ + +#define basebuf bb_common_bufsiz1 /* buffer for top level input file */ + + +static int tokpushback; /* last token pushed back */ +#define NEOF ((union node *)&tokpushback) +static int parsebackquote; /* nonzero if we are inside backquotes */ +static int lasttoken; /* last token read */ +static char *wordtext; /* text of last word returned by readtoken */ +static int checkkwd; +static struct nodelist *backquotelist; +static union node *redirnode; +static struct heredoc *heredoc; +static int quoteflag; /* set if (part of) last token was quoted */ + +static void fixredir(union node *, const char *, int); +static char *endofname(const char *); + +/* shell.h */ + +static const char spcstr[] = " "; +static const char dolatstr[] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; + +#if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) +#define __builtin_expect(x, expected_value) (x) +#endif + +#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 */ +#define CBACK 2 /* a backslash character */ +#define CSQUOTE 3 /* single quote */ +#define CDQUOTE 4 /* double quote */ +#define CENDQUOTE 5 /* a terminating quote */ +#define CBQUOTE 6 /* backwards single quote */ +#define CVAR 7 /* a dollar sign */ +#define CENDVAR 8 /* a '}' character */ +#define CLP 9 /* a left paren in arithmetic */ +#define CRP 10 /* a right paren in arithmetic */ +#define CENDFILE 11 /* end of file */ +#define CCTL 12 /* like CWORD, except it must be escaped */ +#define CSPCL 13 /* these terminate a word */ +#define CIGN 14 /* character should be ignored */ + +#if ENABLE_ASH_ALIAS +#define SYNBASE 130 +#define PEOF -130 +#define PEOA -129 +#define PEOA_OR_PEOF PEOA +#else +#define SYNBASE 129 +#define PEOF -129 +#define PEOA_OR_PEOF PEOF +#endif + +/* C99 say: "char" declaration may be signed or unsigned default */ +#define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int) + +/* + * is_special(c) evaluates to 1 for c in "!#$*-0123456789?@"; 0 otherwise + * (assuming ascii char codes, as the original implementation did) + */ +#define is_special(c) \ + ( (((unsigned int)c) - 33 < 32) \ + && ((0xc1ff920dUL >> (((unsigned int)c) - 33)) & 1)) + +#define digit_val(c) ((c) - '0') + +/* + * This file was generated by the mksyntax program. + */ + +#if ENABLE_ASH_OPTIMIZE_FOR_SIZE +#define USE_SIT_FUNCTION +#endif + +/* number syntax index */ +#define BASESYNTAX 0 /* not in quotes */ +#define DQSYNTAX 1 /* in double quotes */ +#define SQSYNTAX 2 /* in single quotes */ +#define ARISYNTAX 3 /* in arithmetic */ + +#if ENABLE_ASH_MATH_SUPPORT +static const char S_I_T[][4] = { +#if ENABLE_ASH_ALIAS + {CSPCL, CIGN, CIGN, CIGN}, /* 0, PEOA */ +#endif + {CSPCL, CWORD, CWORD, CWORD}, /* 1, ' ' */ + {CNL, CNL, CNL, CNL}, /* 2, \n */ + {CWORD, CCTL, CCTL, CWORD}, /* 3, !*-/:=?[]~ */ + {CDQUOTE, CENDQUOTE, CWORD, CWORD}, /* 4, '"' */ + {CVAR, CVAR, CWORD, CVAR}, /* 5, $ */ + {CSQUOTE, CWORD, CENDQUOTE, CWORD}, /* 6, "'" */ + {CSPCL, CWORD, CWORD, CLP}, /* 7, ( */ + {CSPCL, CWORD, CWORD, CRP}, /* 8, ) */ + {CBACK, CBACK, CCTL, CBACK}, /* 9, \ */ + {CBQUOTE, CBQUOTE, CWORD, CBQUOTE}, /* 10, ` */ + {CENDVAR, CENDVAR, CWORD, CENDVAR}, /* 11, } */ +#ifndef USE_SIT_FUNCTION + {CENDFILE, CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ + {CWORD, CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ + {CCTL, CCTL, CCTL, CCTL} /* 14, CTLESC ... */ +#endif +}; +#else +static const char S_I_T[][3] = { +#if ENABLE_ASH_ALIAS + {CSPCL, CIGN, CIGN}, /* 0, PEOA */ +#endif + {CSPCL, CWORD, CWORD}, /* 1, ' ' */ + {CNL, CNL, CNL}, /* 2, \n */ + {CWORD, CCTL, CCTL}, /* 3, !*-/:=?[]~ */ + {CDQUOTE, CENDQUOTE, CWORD}, /* 4, '"' */ + {CVAR, CVAR, CWORD}, /* 5, $ */ + {CSQUOTE, CWORD, CENDQUOTE}, /* 6, "'" */ + {CSPCL, CWORD, CWORD}, /* 7, ( */ + {CSPCL, CWORD, CWORD}, /* 8, ) */ + {CBACK, CBACK, CCTL}, /* 9, \ */ + {CBQUOTE, CBQUOTE, CWORD}, /* 10, ` */ + {CENDVAR, CENDVAR, CWORD}, /* 11, } */ +#ifndef USE_SIT_FUNCTION + {CENDFILE, CENDFILE, CENDFILE}, /* 12, PEOF */ + {CWORD, CWORD, CWORD}, /* 13, 0-9A-Za-z */ + {CCTL, CCTL, CCTL} /* 14, CTLESC ... */ +#endif +}; +#endif /* ASH_MATH_SUPPORT */ + +#ifdef USE_SIT_FUNCTION + +#define U_C(c) ((unsigned char)(c)) + +static int +SIT(int c, int syntax) +{ + static const char spec_symbls[] = "\t\n !\"$&'()*-/:;<=>?[\\]`|}~"; +#if ENABLE_ASH_ALIAS + static const char syntax_index_table[] = { + 1, 2, 1, 3, 4, 5, 1, 6, /* "\t\n !\"$&'" */ + 7, 8, 3, 3, 3, 3, 1, 1, /* "()*-/:;<" */ + 3, 1, 3, 3, 9, 3, 10, 1, /* "=>?[\\]`|" */ + 11, 3 /* "}~" */ + }; +#else + static const char syntax_index_table[] = { + 0, 1, 0, 2, 3, 4, 0, 5, /* "\t\n !\"$&'" */ + 6, 7, 2, 2, 2, 2, 0, 0, /* "()*-/:;<" */ + 2, 0, 2, 2, 8, 2, 9, 0, /* "=>?[\\]`|" */ + 10, 2 /* "}~" */ + }; +#endif + const char *s; + int indx; + + if (c == PEOF) /* 2^8+2 */ + return CENDFILE; +#if ENABLE_ASH_ALIAS + if (c == PEOA) /* 2^8+1 */ + indx = 0; + else +#endif + if (U_C(c) >= U_C(CTLESC) && U_C(c) <= U_C(CTLQUOTEMARK)) + return CCTL; + else { + s = strchr(spec_symbls, c); + if (s == NULL || *s == '\0') + return CWORD; + indx = syntax_index_table[(s - spec_symbls)]; + } + return S_I_T[indx][syntax]; +} + +#else /* USE_SIT_FUNCTION */ + +#define SIT(c, syntax) S_I_T[(int)syntax_index_table[((int)c)+SYNBASE]][syntax] + +#if ENABLE_ASH_ALIAS +#define CSPCL_CIGN_CIGN_CIGN 0 +#define CSPCL_CWORD_CWORD_CWORD 1 +#define CNL_CNL_CNL_CNL 2 +#define CWORD_CCTL_CCTL_CWORD 3 +#define CDQUOTE_CENDQUOTE_CWORD_CWORD 4 +#define CVAR_CVAR_CWORD_CVAR 5 +#define CSQUOTE_CWORD_CENDQUOTE_CWORD 6 +#define CSPCL_CWORD_CWORD_CLP 7 +#define CSPCL_CWORD_CWORD_CRP 8 +#define CBACK_CBACK_CCTL_CBACK 9 +#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 10 +#define CENDVAR_CENDVAR_CWORD_CENDVAR 11 +#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 12 +#define CWORD_CWORD_CWORD_CWORD 13 +#define CCTL_CCTL_CCTL_CCTL 14 +#else +#define CSPCL_CWORD_CWORD_CWORD 0 +#define CNL_CNL_CNL_CNL 1 +#define CWORD_CCTL_CCTL_CWORD 2 +#define CDQUOTE_CENDQUOTE_CWORD_CWORD 3 +#define CVAR_CVAR_CWORD_CVAR 4 +#define CSQUOTE_CWORD_CENDQUOTE_CWORD 5 +#define CSPCL_CWORD_CWORD_CLP 6 +#define CSPCL_CWORD_CWORD_CRP 7 +#define CBACK_CBACK_CCTL_CBACK 8 +#define CBQUOTE_CBQUOTE_CWORD_CBQUOTE 9 +#define CENDVAR_CENDVAR_CWORD_CENDVAR 10 +#define CENDFILE_CENDFILE_CENDFILE_CENDFILE 11 +#define CWORD_CWORD_CWORD_CWORD 12 +#define CCTL_CCTL_CCTL_CCTL 13 +#endif + +static const char syntax_index_table[258] = { + /* BASESYNTAX_DQSYNTAX_SQSYNTAX_ARISYNTAX */ /* 0 PEOF */ CENDFILE_CENDFILE_CENDFILE_CENDFILE, #if ENABLE_ASH_ALIAS /* 1 PEOA */ CSPCL_CIGN_CIGN_CIGN, @@ -1743,13 +2788,6 @@ static int ulimitcmd(int, char **); static int killcmd(int, char **); #endif -/* mail.h */ - -#if ENABLE_ASH_MAIL -static void chkmail(void); -static void changemail(const char *); -#endif - /* exec.h */ /* values of cmdtype */ @@ -1879,14 +2917,10 @@ struct cmdentry { #define DO_ALTPATH 0x08 /* using alternate path */ #define DO_ALTBLTIN 0x20 /* %builtin in alt. path */ -static const char *pathopt; /* set by padvance */ - 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 hashcd(void); -static void changepath(const char *); static void defun(char *, union node *); static void unsetfunc(const char *); @@ -1905,222 +2939,11 @@ static arith_t arith(const char *expr, int *perrcode); #if ENABLE_ASH_RANDOM_SUPPORT static unsigned long rseed; -static void change_random(const char *); # ifndef DYNAMIC_VAR # define DYNAMIC_VAR # endif #endif -/* var.h */ - -/* - * Shell variables. - */ - -#if ENABLE_ASH_GETOPTS -static void getoptsreset(const char *); -#endif - -/* flags */ -#define VEXPORT 0x01 /* variable is exported */ -#define VREADONLY 0x02 /* variable cannot be modified */ -#define VSTRFIXED 0x04 /* variable struct is statically allocated */ -#define VTEXTFIXED 0x08 /* text is statically allocated */ -#define VSTACK 0x10 /* text is allocated on the stack */ -#define VUNSET 0x20 /* the variable is not set */ -#define VNOFUNC 0x40 /* don't call the callback function */ -#define VNOSET 0x80 /* do not set variable - just readonly test */ -#define VNOSAVE 0x100 /* when text is on the heap before setvareq */ -#ifdef DYNAMIC_VAR -# define VDYNAMIC 0x200 /* dynamic variable */ -# else -# define VDYNAMIC 0 -#endif - -struct var { - struct var *next; /* next entry in hash list */ - int flags; /* flags are defined above */ - const char *text; /* name=value */ - void (*func)(const char *); /* function to be called when */ - /* the variable gets set/unset */ -}; - -struct localvar { - struct localvar *next; /* next local variable in list */ - struct var *vp; /* the variable that was made local */ - int flags; /* saved flags */ - const char *text; /* saved text */ -}; - - -static struct localvar *localvars; - -/* - * Shell variables. - */ -#if ENABLE_LOCALE_SUPPORT -static void change_lc_all(const char *value); -static void change_lc_ctype(const char *value); -#endif - - -#define VTABSIZE 39 - -static const char defpathvar[] = "PATH=/usr/local/bin:/usr/bin:/sbin:/bin"; -#ifdef IFS_BROKEN -static const char defifsvar[] = "IFS= \t\n"; -#define defifs (defifsvar + 4) -#else -static const char defifs[] = " \t\n"; -#endif - - -static struct var varinit[] = { -#ifdef IFS_BROKEN - { 0, VSTRFIXED|VTEXTFIXED, defifsvar, 0 }, -#else - { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "IFS\0", 0 }, -#endif - -#if ENABLE_ASH_MAIL - { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAIL\0", changemail }, - { 0, VSTRFIXED|VTEXTFIXED|VUNSET, "MAILPATH\0", changemail }, -#endif - - { 0, VSTRFIXED|VTEXTFIXED, defpathvar, changepath }, - { 0, VSTRFIXED|VTEXTFIXED, "PS1=$ ", 0 }, - { 0, VSTRFIXED|VTEXTFIXED, "PS2=> ", 0 }, - { 0, VSTRFIXED|VTEXTFIXED, "PS4=+ ", 0 }, -#if ENABLE_ASH_GETOPTS - { 0, VSTRFIXED|VTEXTFIXED, "OPTIND=1", getoptsreset }, -#endif -#if ENABLE_ASH_RANDOM_SUPPORT - {0, VSTRFIXED|VTEXTFIXED|VUNSET|VDYNAMIC, "RANDOM\0", change_random }, -#endif -#if ENABLE_LOCALE_SUPPORT - {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_ALL\0", change_lc_all }, - {0, VSTRFIXED | VTEXTFIXED | VUNSET, "LC_CTYPE\0", change_lc_ctype }, -#endif -#if ENABLE_FEATURE_EDITING_SAVEHISTORY - {0, VSTRFIXED | VTEXTFIXED | VUNSET, "HISTFILE\0", NULL }, -#endif -}; - -#define vifs varinit[0] -#if ENABLE_ASH_MAIL -#define vmail (&vifs)[1] -#define vmpath (&vmail)[1] -#else -#define vmpath vifs -#endif -#define vpath (&vmpath)[1] -#define vps1 (&vpath)[1] -#define vps2 (&vps1)[1] -#define vps4 (&vps2)[1] -#define voptind (&vps4)[1] -#if ENABLE_ASH_GETOPTS -#define vrandom (&voptind)[1] -#else -#define vrandom (&vps4)[1] -#endif -#define defpath (defpathvar + 5) - -/* - * The following macros access the values of the above variables. - * They have to skip over the name. They return the null string - * for unset variables. - */ - -#define ifsval() (vifs.text + 4) -#define ifsset() ((vifs.flags & VUNSET) == 0) -#define mailval() (vmail.text + 5) -#define mpathval() (vmpath.text + 9) -#define pathval() (vpath.text + 5) -#define ps1val() (vps1.text + 4) -#define ps2val() (vps2.text + 4) -#define ps4val() (vps4.text + 4) -#define optindval() (voptind.text + 7) - -#define mpathset() ((vmpath.flags & VUNSET) == 0) - -static void setvar(const char *, const char *, int); -static void setvareq(char *, int); -static void listsetvar(struct strlist *, int); -static char *lookupvar(const char *); -static char *bltinlookup(const char *); -static char **listvars(int, int, char ***); -#define environment() listvars(VEXPORT, VUNSET, 0) -static int showvars(const char *, int, int); -static void poplocalvars(void); -static int unsetvar(const char *); -#if ENABLE_ASH_GETOPTS -static int setvarsafe(const char *, const char *, int); -#endif -static int varcmp(const char *, const char *); -static struct var **hashvar(const char *); - - -static int varequal(const char *a, const char *b) -{ - return !varcmp(a, b); -} - - -static int loopnest; /* current loop nesting level */ - -/* - * The parsefile structure pointed to by the global variable parsefile - * contains information about the current file being read. - */ - - -struct redirtab { - struct redirtab *next; - int renamed[10]; - int nullredirs; -}; - -static struct redirtab *redirlist; -static int nullredirs; - -extern char **environ; - - -static int preverrout_fd; /* save fd2 before print debug if xflag is set. */ - - -/* - * Initialization code. - */ - -/* - * This routine initializes the builtin variables. - */ - -static void initvar(void) -{ - struct var *vp; - struct var *end; - struct var **vpp; - - /* - * PS1 depends on uid - */ -#if ENABLE_FEATURE_EDITING && ENABLE_FEATURE_EDITING_FANCY_PROMPT - vps1.text = "PS1=\\w \\$ "; -#else - if (!geteuid()) - vps1.text = "PS1=# "; -#endif - vp = varinit; - end = vp + sizeof(varinit) / sizeof(varinit[0]); - do { - vpp = hashvar(vp->text); - vp->next = *vpp; - *vpp = vp; - } while (++vp < end); -} - /* PEOF (the end of file marker) */ enum { @@ -2133,10 +2956,6 @@ enum { * and restores it when files are pushed and popped. The user of this * package must set its value. */ - -static int pgetc(void); -static int pgetc2(void); -static int preadbuffer(void); static void pungetc(void); static void pushstring(char *, void *); static void popstring(void); @@ -2146,7 +2965,6 @@ static void popfile(void); static void popallfiles(void); static void closescript(void); - /* jobs.h */ @@ -2218,36 +3036,7 @@ static void showjobs(FILE *, int); static void readcmdfile(char *); - - -static char *_STPUTC(int c, char *p) -{ - if (p == sstrend) - p = growstackstr(); - *p++ = c; - return p; -} - -#define STARTSTACKSTR(p) ((p) = stackblock()) -#define STPUTC(c, p) ((p) = _STPUTC((c), (p))) -#define CHECKSTRSPACE(n, p) \ - ({ \ - char *q = (p); \ - size_t l = (n); \ - size_t m = sstrend - q; \ - if (l > m) \ - (p) = makestrspace(l, q); \ - 0; \ - }) -#define USTPUTC(c, p) (*p++ = (c)) -#define STACKSTRNUL(p) ((p) == sstrend? (p = growstackstr(), *p = '\0') : (*p = '\0')) -#define STUNPUTC(p) (--p) -#define STTOPC(p) p[-1] -#define STADJUST(amount, p) (p += (amount)) - -#define grabstackstr(p) stalloc((char *)(p) - (char *)stackblock()) -#define ungrabstackstr(s, p) stunalloc((s)) -#define stackstrend() ((void *)sstrend) + /* mystring.h */ @@ -2258,29 +3047,12 @@ static char *prefix(const char *, const char *); static int number(const char *); static int is_number(const char *); static char *single_quote(const char *); -static char *sstrdup(const char *); #define equal(s1, s2) (strcmp(s1, s2) == 0) #define scopy(s1, s2) ((void)strcpy(s2, s1)) /* options.h */ -struct shparam { - int nparam; /* # of positional parameters (without $0) */ - unsigned char malloc; /* if parameter list dynamically allocated */ - char **p; /* parameter list */ -#if ENABLE_ASH_GETOPTS - int optind; /* next parameter to be processed by getopts */ - int optoff; /* used by getopts */ -#endif -}; - - -static struct shparam shellparam; /* $@ current positional parameters */ -static char **argptr; /* argument list for builtin commands */ -static char *optionarg; /* set by nextopt (like getopt) */ -static char *optptr; /* used by nextopt */ - static char *minusc; /* argument to -c option */ @@ -2550,250 +3322,6 @@ __lookupalias(const char *name) { } #endif /* ASH_ALIAS */ - -/* cd.c */ - -/* - * The cd and pwd commands. - */ - -#define CD_PHYSICAL 1 -#define CD_PRINT 2 - -static int docd(const char *, int); -static int cdopt(void); - -static char *curdir = nullstr; /* current working directory */ -static char *physdir = nullstr; /* physical working directory */ - -static int -cdopt(void) -{ - int flags = 0; - int i, j; - - j = 'L'; - while ((i = nextopt("LP"))) { - if (i != j) { - flags ^= CD_PHYSICAL; - j = i; - } - } - - return flags; -} - -static int -cdcmd(int argc, char **argv) -{ - const char *dest; - const char *path; - const char *p; - char c; - struct stat statb; - int flags; - - flags = cdopt(); - dest = *argptr; - if (!dest) - dest = bltinlookup(homestr); - else if (LONE_DASH(dest)) { - dest = bltinlookup("OLDPWD"); - flags |= CD_PRINT; - } - if (!dest) - dest = nullstr; - if (*dest == '/') - goto step7; - if (*dest == '.') { - c = dest[1]; - dotdot: - switch (c) { - case '\0': - case '/': - goto step6; - case '.': - c = dest[2]; - if (c != '.') - goto dotdot; - } - } - if (!*dest) - dest = "."; - path = bltinlookup("CDPATH"); - if (!path) { - step6: - step7: - p = dest; - goto docd; - } - do { - c = *path; - p = padvance(&path, dest); - if (stat(p, &statb) >= 0 && S_ISDIR(statb.st_mode)) { - if (c && c != ':') - flags |= CD_PRINT; - docd: - if (!docd(p, flags)) - goto out; - break; - } - } while (path); - ash_msg_and_raise_error("can't cd to %s", dest); - /* NOTREACHED */ - out: - if (flags & CD_PRINT) - out1fmt(snlfmt, curdir); - return 0; -} - - -/* - * Update curdir (the name of the current directory) in response to a - * cd command. - */ -static const char * updatepwd(const char *dir) -{ - char *new; - char *p; - char *cdcomppath; - const char *lim; - - cdcomppath = sstrdup(dir); - STARTSTACKSTR(new); - if (*dir != '/') { - if (curdir == nullstr) - return 0; - new = stack_putstr(curdir, new); - } - new = makestrspace(strlen(dir) + 2, new); - lim = stackblock() + 1; - if (*dir != '/') { - if (new[-1] != '/') - USTPUTC('/', new); - if (new > lim && *lim == '/') - lim++; - } else { - USTPUTC('/', new); - cdcomppath++; - if (dir[1] == '/' && dir[2] != '/') { - USTPUTC('/', new); - cdcomppath++; - lim++; - } - } - p = strtok(cdcomppath, "/"); - while (p) { - switch (*p) { - case '.': - if (p[1] == '.' && p[2] == '\0') { - while (new > lim) { - STUNPUTC(new); - if (new[-1] == '/') - break; - } - break; - } else if (p[1] == '\0') - break; - /* fall through */ - default: - new = stack_putstr(p, new); - USTPUTC('/', new); - } - p = strtok(0, "/"); - } - if (new > lim) - STUNPUTC(new); - *new = 0; - return stackblock(); -} - - -/* - * Actually do the chdir. We also call hashcd to let the routines in exec.c - * know that the current directory has changed. - */ -static int -docd(const char *dest, int flags) -{ - const char *dir = 0; - int err; - - TRACE(("docd(\"%s\", %d) called\n", dest, flags)); - - INT_OFF; - if (!(flags & CD_PHYSICAL)) { - dir = updatepwd(dest); - if (dir) - dest = dir; - } - err = chdir(dest); - if (err) - goto out; - setpwd(dir, 1); - hashcd(); - out: - INT_ON; - return err; -} - -/* - * Find out what the current directory is. If we already know the current - * directory, this routine returns immediately. - */ -static char * getpwd(void) -{ - char *dir = getcwd(0, 0); - return dir ? dir : nullstr; -} - -static int -pwdcmd(int argc, char **argv) -{ - int flags; - const char *dir = curdir; - - flags = cdopt(); - if (flags) { - if (physdir == nullstr) - setpwd(dir, 0); - dir = physdir; - } - out1fmt(snlfmt, dir); - return 0; -} - -static void -setpwd(const char *val, int setold) -{ - char *oldcur, *dir; - - oldcur = dir = curdir; - - if (setold) { - setvar("OLDPWD", oldcur, VEXPORT); - } - INT_OFF; - if (physdir != nullstr) { - if (physdir != oldcur) - free(physdir); - physdir = nullstr; - } - if (oldcur == val || !val) { - char *s = getpwd(); - physdir = s; - if (!val) - dir = s; - } else - dir = ckstrdup(val); - if (oldcur != dir && oldcur != nullstr) { - free(oldcur); - } - curdir = dir; - INT_ON; - setvar("PWD", dir, VEXPORT); -} - /* eval.c */ /* @@ -3235,7 +3763,8 @@ evalbackcmd(union node *n, struct backcmd *result) } #if ENABLE_ASH_CMDCMD -static char ** parse_command_args(char **argv, const char **path) +static char ** +parse_command_args(char **argv, const char **path) { char *cp, c; @@ -3275,12 +3804,6 @@ static int isassignment(const char *p) return *q == '='; } -#if ENABLE_ASH_EXPAND_PRMT -static const char *expandstr(const char *ps); -#else -#define expandstr(s) s -#endif - /* * Execute a simple command. */ @@ -3543,6 +4066,40 @@ evalbltin(const struct builtincmd *cmd, int argc, char **argv) return i; } +static struct localvar *localvars; + +/* + * Called after a function returns. + * Interrupts must be off. + */ +static void +poplocalvars(void) +{ + struct localvar *lvp; + struct var *vp; + + while ((lvp = localvars) != NULL) { + localvars = lvp->next; + vp = lvp->vp; + TRACE(("poplocalvar %s", vp ? vp->text : "-")); + if (vp == NULL) { /* $- saved */ + memcpy(optlist, lvp->text, sizeof(optlist)); + free((char*)lvp->text); + optschanged(); + } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { + unsetvar(vp->text); + } else { + 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) { @@ -3588,7 +4145,8 @@ funcdone: } -static int goodname(const char *p) +static int +goodname(const char *p) { return !*endofname(p); } @@ -3738,6 +4296,7 @@ static void delete_cmd_entry(void); * Exec a program. Never returns. If you change this routine, you may * have to change the find_command routine as well. */ +#define environment() listvars(VEXPORT, VUNSET, 0) static void shellexec(char **, const char *, int) ATTRIBUTE_NORETURN; static void shellexec(char **argv, const char *path, int idx) @@ -3845,50 +4404,6 @@ tryexec(char *cmd, char **argv, char **envp) } -/* - * Do a path search. The variable path (passed by reference) should be - * set to the start of the path before the first call; padvance will update - * this value as it proceeds. Successive calls to padvance will return - * the possible path expansions in sequence. If an option (indicated by - * a percent sign) appears in the path entry then the global variable - * pathopt will be set to point to it; otherwise pathopt will be set to - * NULL. - */ -static char * -padvance(const char **path, const char *name) -{ - const char *p; - char *q; - const char *start; - size_t len; - - if (*path == NULL) - return NULL; - start = *path; - for (p = start; *p && *p != ':' && *p != '%'; p++); - len = p - start + strlen(name) + 2; /* "2" is for '/' and '\0' */ - while (stackblocksize() < len) - growstackblock(); - q = stackblock(); - if (p != start) { - memcpy(q, start, p - start); - q += p - start; - *q++ = '/'; - } - strcpy(q, name); - pathopt = NULL; - if (*p == '%') { - pathopt = ++p; - while (*p && *p != ':') p++; - } - if (*p == ':') - *path = p + 1; - else - *path = NULL; - return stalloc(len); -} - - /*** Command hashing code ***/ static void @@ -5652,7 +6167,7 @@ addfname(const char *name) struct strlist *sp; sp = stalloc(sizeof(*sp)); - sp->text = sstrdup(name); + sp->text = ststrdup(name); *exparg.lastp = sp; exparg.lastp = &sp->next; } @@ -5987,8 +6502,7 @@ static void pushfile(void); * Read a character from the script, returning PEOF on end of file. * Nul characters in the input are silently discarded. */ - - +static int preadbuffer(void); #define pgetc_as_macro() (--parsenleft >= 0? SC2INT(*parsenextc++) : preadbuffer()) #if ENABLE_ASH_OPTIMIZE_FOR_SIZE @@ -6007,12 +6521,12 @@ pgetc(void) } #endif - /* * Same as pgetc(), but ignores PEOA. */ #if ENABLE_ASH_ALIAS -static int pgetc2(void) +static int +pgetc2(void) { int c; @@ -6022,7 +6536,8 @@ static int pgetc2(void) return c; } #else -static int pgetc2(void) +static int +pgetc2(void) { return pgetc_macro(); } @@ -6031,8 +6546,8 @@ static int pgetc2(void) /* * Read a line from the script. */ - -static char * pfgets(char *line, int len) +static char * +pfgets(char *line, int len) { char *p = line; int nleft = len; @@ -6053,27 +6568,6 @@ static char * pfgets(char *line, int len) return line; } - -#if ENABLE_FEATURE_EDITING -static line_input_t *line_input_state; -//static SKIP_ASH_EXPAND_PRMT(const) char *cmdedit_prompt; -static const char *cmdedit_prompt; -static void putprompt(const char *s) -{ - if (ENABLE_ASH_EXPAND_PRMT) { - free((char*)cmdedit_prompt); - cmdedit_prompt = xstrdup(s); - return; - } - cmdedit_prompt = s; -} -#else -static void putprompt(const char *s) -{ - out2str(s); -} -#endif - #if ENABLE_FEATURE_EDITING_VI #define setvimode(on) do { \ if (on) line_input_state->flags |= VI_MODE; \ @@ -6083,8 +6577,8 @@ static void putprompt(const char *s) #define setvimode(on) viflag = 0 /* forcibly keep the option off */ #endif - -static int preadfd(void) +static int +preadfd(void) { int nr; char *buf = parsefile->buf; @@ -7838,187 +8332,80 @@ 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 */ -} - -/* mystring.c */ - -/* - * String functions. - * - * number(s) Convert a string of digits to an integer. - * is_number(s) Return true if s is a string of digits. - */ - -/* - * prefix -- see if pfx is a prefix of string. - */ -static char * -prefix(const char *string, const char *pfx) -{ - while (*pfx) { - if (*pfx++ != *string++) - return 0; - } - return (char *) string; -} - - -/* - * Convert a string of digits to an integer, printing an error message on - * failure. - */ -static int -number(const char *s) -{ - if (!is_number(s)) - ash_msg_and_raise_error(illnum, s); - return atoi(s); -} - - -/* - * Check for a valid number. This should be elsewhere. - */ -static int -is_number(const char *p) -{ - do { - if (!is_digit(*p)) - return 0; - } while (*++p != '\0'); - return 1; -} - - -/* - * Produce a possibly single quoted string suitable as input to the shell. - * The return string is allocated on the stack. - */ -static char * -single_quote(const char *s) -{ - char *p; - - STARTSTACKSTR(p); - - do { - char *q; - size_t len; - - len = strchrnul(s, '\'') - s; - - q = p = makestrspace(len + 3, p); - - *q++ = '\''; - q = memcpy(q, s, len) + len; - *q++ = '\''; - s += len; - - STADJUST(q - p, p); + char *q; + time_t *mtp; + struct stackmark smark; + struct stat statb; - len = strspn(s, "'"); - if (!len) + 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); +} - q = p = makestrspace(len + 3, p); - - *q++ = '"'; - q = memcpy(q, s, len) + len; - *q++ = '"'; - s += len; - - STADJUST(q - p, p); - } while (*s); - - USTPUTC(0, p); - return stackblock(); +static void +changemail(const char *val) +{ + mail_var_path_changed++; } +#endif /* ASH_MAIL */ /* - * Like strdup but works with the ash stack. + * 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 * -sstrdup(const char *p) +find_dot_file(char *name) { - size_t len = strlen(p) + 1; - return memcpy(stalloc(len), p, len); -} + 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) @@ -8199,949 +8586,614 @@ copynode(union node *n) return new; } - -static struct nodelist * -copynodelist(struct nodelist *lp) -{ - struct nodelist *start; - struct nodelist **lpp; - - lpp = &start; - while (lp) { - *lpp = funcblock; - funcblock = (char *) funcblock + SHELL_ALIGN(sizeof(struct nodelist)); - (*lpp)->n = copynode(lp->n); - lp = lp->next; - lpp = &(*lpp)->next; - } - *lpp = NULL; - return start; -} - - -static char * -nodeckstrdup(char *s) -{ - char *rtn = funcstring; - - strcpy(funcstring, s); - funcstring += strlen(s) + 1; - return rtn; -} - - -/* - * Free a parse tree. - */ -static void -freefunc(struct funcnode *f) -{ - if (f && --f->count < 0) - free(f); -} - - -static void setoption(int, int); - - -static void -optschanged(void) -{ -#if DEBUG - opentrace(); -#endif - setinteractive(iflag); - setjobctl(mflag); - setvimode(viflag); -} - -static void minus_o(char *name, int val) -{ - int i; - - if (name) { - for (i = 0; i < NOPTS; i++) { - if (equal(name, optnames(i))) { - optlist[i] = val; - return; - } - } - ash_msg_and_raise_error("Illegal option -o %s", name); - } - out1str("Current option settings\n"); - for (i = 0; i < NOPTS; i++) - out1fmt("%-16s%s\n", optnames(i), - optlist[i] ? "on" : "off"); -} - - -/* - * Process shell options. The global variable argptr contains a pointer - * to the argument list; we advance it past the options. - */ -static void -options(int cmdline) -{ - char *p; - int val; - int c; - - if (cmdline) - minusc = NULL; - while ((p = *argptr) != NULL) { - argptr++; - c = *p++; - if (c == '-') { - val = 1; - if (p[0] == '\0' || LONE_DASH(p)) { - if (!cmdline) { - /* "-" means turn off -x and -v */ - if (p[0] == '\0') - xflag = vflag = 0; - /* "--" means reset params */ - else if (*argptr == NULL) - setparam(argptr); - } - break; /* "-" or "--" terminates options */ - } - } else if (c == '+') { - val = 0; - } else { - argptr--; - break; - } - while ((c = *p++) != '\0') { - if (c == 'c' && cmdline) { - minusc = p; /* command is after shell args*/ - } else if (c == 'o') { - minus_o(*argptr, val); - if (*argptr) - argptr++; - } else if (cmdline && (c == '-')) { // long options - if (strcmp(p, "login") == 0) - isloginsh = 1; - break; - } else { - setoption(c, val); - } - } - } -} - - -static void -setoption(int flag, int val) -{ - int i; - - for (i = 0; i < NOPTS; i++) { - if (optletters(i) == flag) { - optlist[i] = val; - return; - } - } - ash_msg_and_raise_error("Illegal option -%c", flag); - /* NOTREACHED */ -} - - -/* - * Set the shell parameters. - */ -static void -setparam(char **argv) -{ - char **newparam; - char **ap; - int nparam; - - for (nparam = 0; argv[nparam]; nparam++); - ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap)); - while (*argv) { - *ap++ = ckstrdup(*argv++); - } - *ap = NULL; - freeparam(&shellparam); - shellparam.malloc = 1; - shellparam.nparam = nparam; - shellparam.p = newparam; -#if ENABLE_ASH_GETOPTS - shellparam.optind = 1; - shellparam.optoff = -1; -#endif -} - - -/* - * Free the list of positional parameters. - */ -static void -freeparam(volatile struct shparam *param) + +static struct nodelist * +copynodelist(struct nodelist *lp) { - char **ap; + struct nodelist *start; + struct nodelist **lpp; - if (param->malloc) { - for (ap = param->p; *ap; ap++) - free(*ap); - free(param->p); + 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; } + *lpp = NULL; + return start; } -/* - * The shift builtin command. - */ -static int -shiftcmd(int argc, char **argv) +static char * +nodeckstrdup(char *s) { - int n; - char **ap1, **ap2; + char *rtn = funcstring; - n = 1; - if (argc > 1) - n = number(argv[1]); - if (n > shellparam.nparam) - ash_msg_and_raise_error("can't shift that many"); - INT_OFF; - shellparam.nparam -= n; - for (ap1 = shellparam.p; --n >= 0; ap1++) { - if (shellparam.malloc) - free(*ap1); - } - ap2 = shellparam.p; - while ((*ap2++ = *ap1++) != NULL); -#if ENABLE_ASH_GETOPTS - shellparam.optind = 1; - shellparam.optoff = -1; -#endif - INT_ON; - return 0; + strcpy(funcstring, s); + funcstring += strlen(s) + 1; + return rtn; } /* - * The set command builtin. + * Free a parse tree. */ -static int -setcmd(int argc, char **argv) +static void +freefunc(struct funcnode *f) { - if (argc == 1) - return showvars(nullstr, 0, VUNSET); - INT_OFF; - options(0); - optschanged(); - if (*argptr != NULL) { - setparam(argptr); - } - INT_ON; - return 0; + if (f && --f->count < 0) + free(f); } -#if ENABLE_ASH_GETOPTS static void -getoptsreset(const char *value) +optschanged(void) { - shellparam.optind = number(value); - shellparam.optoff = -1; -} +#if DEBUG + opentrace(); #endif - -#if ENABLE_LOCALE_SUPPORT -static void change_lc_all(const char *value) -{ - if (value && *value != '\0') - setlocale(LC_ALL, value); -} - -static void change_lc_ctype(const char *value) -{ - if (value && *value != '\0') - setlocale(LC_CTYPE, value); + setinteractive(iflag); + setjobctl(mflag); + setvimode(viflag); } -#endif - -#if ENABLE_ASH_RANDOM_SUPPORT -/* Roughly copied from bash.. */ -static void change_random(const char *value) +static void +minus_o(char *name, int val) { - if (value == NULL) { - /* "get", generate */ - char buf[16]; + int i; - rseed = rseed * 1103515245 + 12345; - sprintf(buf, "%d", (unsigned int)((rseed & 32767))); - /* set without recursion */ - setvar(vrandom.text, buf, VNOFUNC); - vrandom.flags &= ~VNOFUNC; - } else { - /* set/reset */ - rseed = strtoul(value, (char **)NULL, 10); + if (name) { + for (i = 0; i < NOPTS; i++) { + if (equal(name, optnames(i))) { + optlist[i] = val; + return; + } + } + ash_msg_and_raise_error("Illegal option -o %s", name); } + out1str("Current option settings\n"); + for (i = 0; i < NOPTS; i++) + out1fmt("%-16s%s\n", optnames(i), + optlist[i] ? "on" : "off"); } -#endif -#if ENABLE_ASH_GETOPTS -static int -getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff) +static void +setoption(int flag, int val) { - char *p, *q; - char c = '?'; - int done = 0; - int err = 0; - char s[12]; - char **optnext; - - if (*param_optind < 1) - return 1; - optnext = optfirst + *param_optind - 1; - - if (*param_optind <= 1 || *optoff < 0 || strlen(optnext[-1]) < *optoff) - p = NULL; - else - p = optnext[-1] + *optoff; - if (p == NULL || *p == '\0') { - /* Current word is done, advance */ - p = *optnext; - if (p == NULL || *p != '-' || *++p == '\0') { - atend: - p = NULL; - done = 1; - goto out; - } - optnext++; - if (LONE_DASH(p)) /* check for "--" */ - goto atend; - } - - c = *p++; - for (q = optstr; *q != c; ) { - if (*q == '\0') { - if (optstr[0] == ':') { - s[0] = c; - s[1] = '\0'; - err |= setvarsafe("OPTARG", s, 0); - } else { - fprintf(stderr, "Illegal option -%c\n", c); - (void) unsetvar("OPTARG"); - } - c = '?'; - goto out; - } - if (*++q == ':') - q++; - } + int i; - if (*++q == ':') { - if (*p == '\0' && (p = *optnext) == NULL) { - if (optstr[0] == ':') { - s[0] = c; - s[1] = '\0'; - err |= setvarsafe("OPTARG", s, 0); - c = ':'; - } else { - fprintf(stderr, "No arg for -%c option\n", c); - (void) unsetvar("OPTARG"); - c = '?'; - } - goto out; + for (i = 0; i < NOPTS; i++) { + if (optletters(i) == flag) { + optlist[i] = val; + return; } - - if (p == *optnext) - optnext++; - err |= setvarsafe("OPTARG", p, 0); - p = NULL; - } else - err |= setvarsafe("OPTARG", nullstr, 0); - out: - *optoff = p ? p - *(optnext - 1) : -1; - *param_optind = optnext - optfirst + 1; - fmtstr(s, sizeof(s), "%d", *param_optind); - err |= setvarsafe("OPTIND", s, VNOFUNC); - s[0] = c; - s[1] = '\0'; - err |= setvarsafe(optvar, s, 0); - if (err) { - *param_optind = 1; - *optoff = -1; - flush_stdout_stderr(); - raise_exception(EXERROR); } - return done; + ash_msg_and_raise_error("Illegal option -%c", flag); + /* NOTREACHED */ } /* - * The getopts builtin. Shellparam.optnext points to the next argument - * to be processed. Shellparam.optptr points to the next character to - * be processed in the current argument. If shellparam.optnext is NULL, - * then it's the first time getopts has been called. + * Process shell options. The global variable argptr contains a pointer + * to the argument list; we advance it past the options. */ -static int -getoptscmd(int argc, char **argv) +static void +options(int cmdline) { - char **optbase; + char *p; + int val; + int c; - if (argc < 3) - ash_msg_and_raise_error("Usage: getopts optstring var [arg]"); - else if (argc == 3) { - optbase = shellparam.p; - if (shellparam.optind > shellparam.nparam + 1) { - shellparam.optind = 1; - shellparam.optoff = -1; + if (cmdline) + minusc = NULL; + while ((p = *argptr) != NULL) { + argptr++; + c = *p++; + if (c == '-') { + val = 1; + if (p[0] == '\0' || LONE_DASH(p)) { + if (!cmdline) { + /* "-" means turn off -x and -v */ + if (p[0] == '\0') + xflag = vflag = 0; + /* "--" means reset params */ + else if (*argptr == NULL) + setparam(argptr); + } + break; /* "-" or "--" terminates options */ + } + } else if (c == '+') { + val = 0; + } else { + argptr--; + break; } - } else { - optbase = &argv[3]; - if (shellparam.optind > argc - 2) { - shellparam.optind = 1; - shellparam.optoff = -1; + while ((c = *p++) != '\0') { + if (c == 'c' && cmdline) { + minusc = p; /* command is after shell args*/ + } else if (c == 'o') { + minus_o(*argptr, val); + if (*argptr) + argptr++; + } else if (cmdline && (c == '-')) { // long options + if (strcmp(p, "login") == 0) + isloginsh = 1; + break; + } else { + setoption(c, val); + } } } - - return getopts(argv[1], argv[2], optbase, &shellparam.optind, - &shellparam.optoff); } -#endif /* ASH_GETOPTS */ + /* - * XXX - should get rid of. have all builtins use getopt(3). the - * library getopt must have the BSD extension static variable "optreset" - * otherwise it can't be used within the shell safely. - * - * Standard option processing (a la getopt) for builtin routines. The - * only argument that is passed to nextopt is the option string; the - * other arguments are unnecessary. It return the character, or '\0' on - * end of input. + * Set the shell parameters. */ -static int -nextopt(const char *optstring) +static void +setparam(char **argv) { - char *p; - const char *q; - char c; + char **newparam; + char **ap; + int nparam; - p = optptr; - if (p == NULL || *p == '\0') { - p = *argptr; - if (p == NULL || *p != '-' || *++p == '\0') - return '\0'; - argptr++; - if (LONE_DASH(p)) /* check for "--" */ - return '\0'; - } - c = *p++; - for (q = optstring; *q != c; ) { - if (*q == '\0') - ash_msg_and_raise_error("Illegal option -%c", c); - if (*++q == ':') - q++; - } - if (*++q == ':') { - if (*p == '\0' && (p = *argptr++) == NULL) - ash_msg_and_raise_error("No arg for -%c option", c); - optionarg = p; - p = NULL; + for (nparam = 0; argv[nparam]; nparam++); + ap = newparam = ckmalloc((nparam + 1) * sizeof(*ap)); + while (*argv) { + *ap++ = ckstrdup(*argv++); } - optptr = p; - return c; + *ap = NULL; + freeparam(&shellparam); + shellparam.malloc = 1; + shellparam.nparam = nparam; + shellparam.p = newparam; +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif } -/* parser.c */ - - /* - * Shell command parser. + * Free the list of positional parameters. */ - -#define EOFMARKLEN 79 - -struct heredoc { - struct heredoc *next; /* next here document in list */ - union node *here; /* redirection node */ - char *eofmark; /* string indicating end of input */ - int striptabs; /* if set, strip leading tabs */ -}; - -static struct heredoc *heredoclist; /* list of here documents to read */ - -static union node *list(int); -static union node *andor(void); -static union node *pipeline(void); -static union node *command(void); -static union node *simplecmd(void); -static union node *makename(void); -static void parsefname(void); -static void parseheredoc(void); -static char peektoken(void); -static int readtoken(void); -static int xxreadtoken(void); -static int readtoken1(int firstc, int syntax, char *eofmark, int striptabs); -static int noexpand(char *); -static void setprompt(int); - -static void raise_error_syntax(const char *) ATTRIBUTE_NORETURN; static void -raise_error_syntax(const char *msg) +freeparam(volatile struct shparam *param) { - ash_msg_and_raise_error("Syntax error: %s", msg); - /* NOTREACHED */ + char **ap; + + if (param->malloc) { + for (ap = param->p; *ap; ap++) + free(*ap); + free(param->p); + } } + /* - * Called when an unexpected token is read during the parse. The argument - * is the token that is expected, or -1 if more than one type of token can - * occur at this point. + * The shift builtin command. */ -static void raise_error_unexpected_syntax(int) ATTRIBUTE_NORETURN; -static void -raise_error_unexpected_syntax(int token) +static int +shiftcmd(int argc, char **argv) { - char msg[64]; - int l; + int n; + char **ap1, **ap2; - l = sprintf(msg, "%s unexpected", tokname(lasttoken)); - if (token >= 0) - sprintf(msg + l, " (expecting %s)", tokname(token)); - raise_error_syntax(msg); - /* NOTREACHED */ + n = 1; + if (argc > 1) + n = number(argv[1]); + if (n > shellparam.nparam) + ash_msg_and_raise_error("can't shift that many"); + INT_OFF; + shellparam.nparam -= n; + for (ap1 = shellparam.p; --n >= 0; ap1++) { + if (shellparam.malloc) + free(*ap1); + } + ap2 = shellparam.p; + while ((*ap2++ = *ap1++) != NULL); +#if ENABLE_ASH_GETOPTS + shellparam.optind = 1; + shellparam.optoff = -1; +#endif + INT_ON; + return 0; } + /* - * Read and parse a command. Returns NEOF on end of file. (NULL is a - * valid parse tree indicating a blank line.) + * POSIX requires that 'set' (but not export or readonly) output the + * variables in lexicographic order - by the locale's collating order (sigh). + * Maybe we could keep them in an ordered balanced binary tree + * instead of hashed lists. + * For now just roll 'em through qsort for printing... */ -static union node * -parsecmd(int interact) +static int +showvars(const char *sep_prefix, int on, int off) { - int t; + const char *sep; + char **ep, **epend; - tokpushback = 0; - doprompt = interact; - if (doprompt) - setprompt(doprompt); - needprompt = 0; - t = readtoken(); - if (t == TEOF) - return NEOF; - if (t == TNL) - return NULL; - tokpushback++; - return list(1); -} + ep = listvars(on, off, &epend); + qsort(ep, epend - ep, sizeof(char *), vpcmp); + sep = *sep_prefix ? spcstr : sep_prefix; -static union node * -list(int nlflag) -{ - union node *n1, *n2, *n3; - int tok; + for (; ep < epend; ep++) { + const char *p; + const char *q; - checkkwd = CHKNL | CHKKWD | CHKALIAS; - if (nlflag == 2 && peektoken()) - return NULL; - n1 = NULL; - for (;;) { - n2 = andor(); - tok = readtoken(); - if (tok == TBACKGND) { - if (n2->type == NPIPE) { - n2->npipe.backgnd = 1; - } else { - if (n2->type != NREDIR) { - n3 = stalloc(sizeof(struct nredir)); - n3->nredir.n = n2; - n3->nredir.redirect = NULL; - n2 = n3; - } - n2->type = NBACKGND; - } - } - if (n1 == NULL) { - n1 = n2; - } else { - n3 = stalloc(sizeof(struct nbinary)); - n3->type = NSEMI; - n3->nbinary.ch1 = n1; - n3->nbinary.ch2 = n2; - n1 = n3; - } - switch (tok) { - case TBACKGND: - case TSEMI: - tok = readtoken(); - /* fall through */ - case TNL: - if (tok == TNL) { - parseheredoc(); - if (nlflag == 1) - return n1; - } else { - tokpushback++; - } - checkkwd = CHKNL | CHKKWD | CHKALIAS; - if (peektoken()) - return n1; - break; - case TEOF: - if (heredoclist) - parseheredoc(); - else - pungetc(); /* push back EOF on input */ - return n1; - default: - if (nlflag == 1) - raise_error_unexpected_syntax(-1); - tokpushback++; - return n1; - } + p = strchrnul(*ep, '='); + q = nullstr; + if (*p) + q = single_quote(++p); + out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); + } + return 0; +} + +/* + * The set command builtin. + */ +static int +setcmd(int argc, char **argv) +{ + if (argc == 1) + return showvars(nullstr, 0, VUNSET); + INT_OFF; + options(0); + optschanged(); + if (*argptr != NULL) { + setparam(argptr); } + INT_ON; + return 0; } -static union node * -andor(void) +#if ENABLE_LOCALE_SUPPORT +static void change_lc_all(const char *value) { - union node *n1, *n2, *n3; - int t; - - n1 = pipeline(); - for (;;) { - t = readtoken(); - if (t == TAND) { - t = NAND; - } else if (t == TOR) { - t = NOR; - } else { - tokpushback++; - return n1; - } - checkkwd = CHKNL | CHKKWD | CHKALIAS; - n2 = pipeline(); - n3 = stalloc(sizeof(struct nbinary)); - n3->type = t; - n3->nbinary.ch1 = n1; - n3->nbinary.ch2 = n2; - n1 = n3; - } + if (value && *value != '\0') + setlocale(LC_ALL, value); } +static void change_lc_ctype(const char *value) +{ + if (value && *value != '\0') + setlocale(LC_CTYPE, value); +} +#endif -static union node * -pipeline(void) +#if ENABLE_ASH_RANDOM_SUPPORT +/* Roughly copied from bash.. */ +static void change_random(const char *value) { - union node *n1, *n2, *pipenode; - struct nodelist *lp, *prev; - int negate; + if (value == NULL) { + /* "get", generate */ + char buf[16]; - negate = 0; - TRACE(("pipeline: entered\n")); - if (readtoken() == TNOT) { - negate = !negate; - checkkwd = CHKKWD | CHKALIAS; - } else - tokpushback++; - n1 = command(); - if (readtoken() == TPIPE) { - pipenode = stalloc(sizeof(struct npipe)); - pipenode->type = NPIPE; - pipenode->npipe.backgnd = 0; - lp = stalloc(sizeof(struct nodelist)); - pipenode->npipe.cmdlist = lp; - lp->n = n1; - do { - prev = lp; - lp = stalloc(sizeof(struct nodelist)); - checkkwd = CHKNL | CHKKWD | CHKALIAS; - lp->n = command(); - prev->next = lp; - } while (readtoken() == TPIPE); - lp->next = NULL; - n1 = pipenode; - } - tokpushback++; - if (negate) { - n2 = stalloc(sizeof(struct nnot)); - n2->type = NNOT; - n2->nnot.com = n1; - return n2; + rseed = rseed * 1103515245 + 12345; + sprintf(buf, "%d", (unsigned int)((rseed & 32767))); + /* set without recursion */ + setvar(vrandom.text, buf, VNOFUNC); + vrandom.flags &= ~VNOFUNC; + } else { + /* set/reset */ + rseed = strtoul(value, (char **)NULL, 10); } - return n1; } +#endif -static union node * -command(void) +#if ENABLE_ASH_GETOPTS +static int +getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *optoff) { - union node *n1, *n2; - union node *ap, **app; - union node *cp, **cpp; - union node *redir, **rpp; - union node **rpp2; - int t; - - redir = NULL; - rpp2 = &redir; + char *p, *q; + char c = '?'; + int done = 0; + int err = 0; + char s[12]; + char **optnext; - switch (readtoken()) { - default: - raise_error_unexpected_syntax(-1); - /* NOTREACHED */ - case TIF: - n1 = stalloc(sizeof(struct nif)); - n1->type = NIF; - n1->nif.test = list(0); - if (readtoken() != TTHEN) - raise_error_unexpected_syntax(TTHEN); - n1->nif.ifpart = list(0); - n2 = n1; - while (readtoken() == TELIF) { - n2->nif.elsepart = stalloc(sizeof(struct nif)); - n2 = n2->nif.elsepart; - n2->type = NIF; - n2->nif.test = list(0); - if (readtoken() != TTHEN) - raise_error_unexpected_syntax(TTHEN); - n2->nif.ifpart = list(0); - } - if (lasttoken == TELSE) - n2->nif.elsepart = list(0); - else { - n2->nif.elsepart = NULL; - tokpushback++; - } - t = TFI; - break; - case TWHILE: - case TUNTIL: { - int got; - n1 = stalloc(sizeof(struct nbinary)); - n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL; - n1->nbinary.ch1 = list(0); - if ((got=readtoken()) != TDO) { - TRACE(("expecting DO got %s %s\n", tokname(got), - got == TWORD ? wordtext : "")); - raise_error_unexpected_syntax(TDO); - } - n1->nbinary.ch2 = list(0); - t = TDONE; - break; - } - case TFOR: - if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) - raise_error_syntax("Bad for loop variable"); - n1 = stalloc(sizeof(struct nfor)); - n1->type = NFOR; - n1->nfor.var = wordtext; - checkkwd = CHKKWD | CHKALIAS; - if (readtoken() == TIN) { - app = ≈ - while (readtoken() == TWORD) { - n2 = stalloc(sizeof(struct narg)); - n2->type = NARG; - n2->narg.text = wordtext; - n2->narg.backquote = backquotelist; - *app = n2; - app = &n2->narg.next; - } - *app = NULL; - n1->nfor.args = ap; - if (lasttoken != TNL && lasttoken != TSEMI) - raise_error_unexpected_syntax(-1); - } else { - n2 = stalloc(sizeof(struct narg)); - n2->type = NARG; - n2->narg.text = (char *)dolatstr; - n2->narg.backquote = NULL; - n2->narg.next = NULL; - n1->nfor.args = n2; - /* - * Newline or semicolon here is optional (but note - * that the original Bourne shell only allowed NL). - */ - if (lasttoken != TNL && lasttoken != TSEMI) - tokpushback++; - } - checkkwd = CHKNL | CHKKWD | CHKALIAS; - if (readtoken() != TDO) - raise_error_unexpected_syntax(TDO); - n1->nfor.body = list(0); - t = TDONE; - break; - case TCASE: - n1 = stalloc(sizeof(struct ncase)); - n1->type = NCASE; - if (readtoken() != TWORD) - raise_error_unexpected_syntax(TWORD); - n1->ncase.expr = n2 = stalloc(sizeof(struct narg)); - n2->type = NARG; - n2->narg.text = wordtext; - n2->narg.backquote = backquotelist; - n2->narg.next = NULL; - do { - checkkwd = CHKKWD | CHKALIAS; - } while (readtoken() == TNL); - if (lasttoken != TIN) - raise_error_unexpected_syntax(TIN); - cpp = &n1->ncase.cases; - next_case: - checkkwd = CHKNL | CHKKWD; - t = readtoken(); - while (t != TESAC) { - if (lasttoken == TLP) - readtoken(); - *cpp = cp = stalloc(sizeof(struct nclist)); - cp->type = NCLIST; - app = &cp->nclist.pattern; - for (;;) { - *app = ap = stalloc(sizeof(struct narg)); - ap->type = NARG; - ap->narg.text = wordtext; - ap->narg.backquote = backquotelist; - if (readtoken() != TPIPE) - break; - app = &ap->narg.next; - readtoken(); - } - ap->narg.next = NULL; - if (lasttoken != TRP) - raise_error_unexpected_syntax(TRP); - cp->nclist.body = list(2); + if (*param_optind < 1) + return 1; + optnext = optfirst + *param_optind - 1; - cpp = &cp->nclist.next; + if (*param_optind <= 1 || *optoff < 0 || strlen(optnext[-1]) < *optoff) + p = NULL; + else + p = optnext[-1] + *optoff; + if (p == NULL || *p == '\0') { + /* Current word is done, advance */ + p = *optnext; + if (p == NULL || *p != '-' || *++p == '\0') { + atend: + p = NULL; + done = 1; + goto out; + } + optnext++; + if (LONE_DASH(p)) /* check for "--" */ + goto atend; + } - checkkwd = CHKNL | CHKKWD; - t = readtoken(); - if (t != TESAC) { - if (t != TENDCASE) - raise_error_unexpected_syntax(TENDCASE); - goto next_case; + c = *p++; + for (q = optstr; *q != c; ) { + if (*q == '\0') { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + } else { + fprintf(stderr, "Illegal option -%c\n", c); + unsetvar("OPTARG"); } + c = '?'; + goto out; } - *cpp = NULL; - goto redir; - case TLP: - n1 = stalloc(sizeof(struct nredir)); - n1->type = NSUBSHELL; - n1->nredir.n = list(0); - n1->nredir.redirect = NULL; - t = TRP; - break; - case TBEGIN: - n1 = list(0); - t = TEND; - break; - case TWORD: - case TREDIR: - tokpushback++; - return simplecmd(); + if (*++q == ':') + q++; } - if (readtoken() != t) - raise_error_unexpected_syntax(t); + if (*++q == ':') { + if (*p == '\0' && (p = *optnext) == NULL) { + if (optstr[0] == ':') { + s[0] = c; + s[1] = '\0'; + err |= setvarsafe("OPTARG", s, 0); + c = ':'; + } else { + fprintf(stderr, "No arg for -%c option\n", c); + unsetvar("OPTARG"); + c = '?'; + } + goto out; + } - redir: - /* Now check for redirection which may follow command */ - checkkwd = CHKKWD | CHKALIAS; - rpp = rpp2; - while (readtoken() == TREDIR) { - *rpp = n2 = redirnode; - rpp = &n2->nfile.next; - parsefname(); + if (p == *optnext) + optnext++; + err |= setvarsafe("OPTARG", p, 0); + p = NULL; + } else + err |= setvarsafe("OPTARG", nullstr, 0); + out: + *optoff = p ? p - *(optnext - 1) : -1; + *param_optind = optnext - optfirst + 1; + fmtstr(s, sizeof(s), "%d", *param_optind); + err |= setvarsafe("OPTIND", s, VNOFUNC); + s[0] = c; + s[1] = '\0'; + err |= setvarsafe(optvar, s, 0); + if (err) { + *param_optind = 1; + *optoff = -1; + flush_stdout_stderr(); + raise_exception(EXERROR); } - tokpushback++; - *rpp = NULL; - if (redir) { - if (n1->type != NSUBSHELL) { - n2 = stalloc(sizeof(struct nredir)); - n2->type = NREDIR; - n2->nredir.n = n1; - n1 = n2; + return done; +} + + +/* + * The getopts builtin. Shellparam.optnext points to the next argument + * to be processed. Shellparam.optptr points to the next character to + * be processed in the current argument. If shellparam.optnext is NULL, + * then it's the first time getopts has been called. + */ +static int +getoptscmd(int argc, char **argv) +{ + char **optbase; + + if (argc < 3) + ash_msg_and_raise_error("Usage: getopts optstring var [arg]"); + if (argc == 3) { + optbase = shellparam.p; + if (shellparam.optind > shellparam.nparam + 1) { + shellparam.optind = 1; + shellparam.optoff = -1; + } + } else { + optbase = &argv[3]; + if (shellparam.optind > argc - 2) { + shellparam.optind = 1; + shellparam.optoff = -1; } - n1->nredir.redirect = redir; } - return n1; + + return getopts(argv[1], argv[2], optbase, &shellparam.optind, + &shellparam.optoff); } +#endif /* ASH_GETOPTS */ -static union node * -simplecmd(void) +/* ============ Shell parser */ + +static void raise_error_syntax(const char *) ATTRIBUTE_NORETURN; +static void +raise_error_syntax(const char *msg) { - union node *args, **app; - union node *n = NULL; - union node *vars, **vpp; - union node **rpp, *redir; - int savecheckkwd; + ash_msg_and_raise_error("Syntax error: %s", msg); + /* NOTREACHED */ +} - args = NULL; - app = &args; - vars = NULL; - vpp = &vars; - redir = NULL; - rpp = &redir; +/* + * Called when an unexpected token is read during the parse. The argument + * is the token that is expected, or -1 if more than one type of token can + * occur at this point. + */ +static void raise_error_unexpected_syntax(int) ATTRIBUTE_NORETURN; +static void +raise_error_unexpected_syntax(int token) +{ + char msg[64]; + int l; - savecheckkwd = CHKALIAS; + l = sprintf(msg, "%s unexpected", tokname(lasttoken)); + if (token >= 0) + sprintf(msg + l, " (expecting %s)", tokname(token)); + raise_error_syntax(msg); + /* NOTREACHED */ +} + +#define EOFMARKLEN 79 + +struct heredoc { + struct heredoc *next; /* next here document in list */ + union node *here; /* redirection node */ + char *eofmark; /* string indicating end of input */ + int striptabs; /* if set, strip leading tabs */ +}; + +static struct heredoc *heredoclist; /* list of here documents to read */ + +/* parsing is heavily cross-recursive, need these forward decls */ +static union node *andor(void); +static union node *pipeline(void); +static union node *parse_command(void); +static void parseheredoc(void); +static char peektoken(void); +static int readtoken(void); + +static union node * +list(int nlflag) +{ + union node *n1, *n2, *n3; + int tok; + + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (nlflag == 2 && peektoken()) + return NULL; + n1 = NULL; for (;;) { - checkkwd = savecheckkwd; - switch (readtoken()) { - case TWORD: - n = stalloc(sizeof(struct narg)); - n->type = NARG; - n->narg.text = wordtext; - n->narg.backquote = backquotelist; - if (savecheckkwd && isassignment(wordtext)) { - *vpp = n; - vpp = &n->narg.next; + n2 = andor(); + tok = readtoken(); + if (tok == TBACKGND) { + if (n2->type == NPIPE) { + n2->npipe.backgnd = 1; } else { - *app = n; - app = &n->narg.next; - savecheckkwd = 0; - } - break; - case TREDIR: - *rpp = n = redirnode; - rpp = &n->nfile.next; - parsefname(); /* read name of redirection file */ - break; - case TLP: - if (args && app == &args->narg.next - && !vars && !redir - ) { - struct builtincmd *bcmd; - const char *name; - - /* We have a function */ - if (readtoken() != TRP) - raise_error_unexpected_syntax(TRP); - name = n->narg.text; - if (!goodname(name) - || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd)) - ) { - raise_error_syntax("Bad function name"); + if (n2->type != NREDIR) { + n3 = stalloc(sizeof(struct nredir)); + n3->nredir.n = n2; + n3->nredir.redirect = NULL; + n2 = n3; } - n->type = NDEFUN; - checkkwd = CHKNL | CHKKWD | CHKALIAS; - n->narg.next = command(); - return n; + n2->type = NBACKGND; + } + } + if (n1 == NULL) { + n1 = n2; + } else { + n3 = stalloc(sizeof(struct nbinary)); + n3->type = NSEMI; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } + switch (tok) { + case TBACKGND: + case TSEMI: + tok = readtoken(); + /* fall through */ + case TNL: + if (tok == TNL) { + parseheredoc(); + if (nlflag == 1) + return n1; + } else { + tokpushback++; } - /* fall through */ + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (peektoken()) + return n1; + break; + case TEOF: + if (heredoclist) + parseheredoc(); + else + pungetc(); /* push back EOF on input */ + return n1; default: + if (nlflag == 1) + raise_error_unexpected_syntax(-1); tokpushback++; - goto out; + return n1; } } - out: - *app = NULL; - *vpp = NULL; - *rpp = NULL; - n = stalloc(sizeof(struct ncmd)); - n->type = NCMD; - n->ncmd.args = args; - n->ncmd.assign = vars; - n->ncmd.redirect = redir; - return n; +} + +static union node * +andor(void) +{ + union node *n1, *n2, *n3; + int t; + + n1 = pipeline(); + for (;;) { + t = readtoken(); + if (t == TAND) { + t = NAND; + } else if (t == TOR) { + t = NOR; + } else { + tokpushback++; + return n1; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + n2 = pipeline(); + n3 = stalloc(sizeof(struct nbinary)); + n3->type = t; + n3->nbinary.ch1 = n1; + n3->nbinary.ch2 = n2; + n1 = n3; + } +} + +static union node * +pipeline(void) +{ + union node *n1, *n2, *pipenode; + struct nodelist *lp, *prev; + int negate; + + negate = 0; + TRACE(("pipeline: entered\n")); + if (readtoken() == TNOT) { + negate = !negate; + checkkwd = CHKKWD | CHKALIAS; + } else + tokpushback++; + n1 = parse_command(); + if (readtoken() == TPIPE) { + pipenode = stalloc(sizeof(struct npipe)); + pipenode->type = NPIPE; + pipenode->npipe.backgnd = 0; + lp = stalloc(sizeof(struct nodelist)); + pipenode->npipe.cmdlist = lp; + lp->n = n1; + do { + prev = lp; + lp = stalloc(sizeof(struct nodelist)); + checkkwd = CHKNL | CHKKWD | CHKALIAS; + lp->n = parse_command(); + prev->next = lp; + } while (readtoken() == TPIPE); + lp->next = NULL; + n1 = pipenode; + } + tokpushback++; + if (negate) { + n2 = stalloc(sizeof(struct nnot)); + n2->type = NNOT; + n2->nnot.com = n1; + return n2; + } + return n1; } static union node * @@ -9164,7 +9216,7 @@ fixredir(union node *n, const char *text, int err) if (!err) n->ndup.vname = NULL; - if (is_digit(text[0]) && text[1] == '\0') + if (isdigit(text[0]) && text[1] == '\0') n->ndup.dupfd = digit_val(text[0]); else if (LONE_DASH(text)) n->ndup.dupfd = -1; @@ -9175,6 +9227,27 @@ fixredir(union node *n, const char *text, int err) } } +/* + * Returns true if the text contains nothing to expand (no dollar signs + * or backquotes). + */ +static int +noexpand(char *text) +{ + char *p; + char c; + + p = text; + while ((c = *p++) != '\0') { + if (c == CTLQUOTEMARK) + continue; + if (c == CTLESC) + p++; + else if (SIT(c, BASESYNTAX) == CCTL) + return 0; + } + return 1; +} static void parsefname(void) @@ -9209,276 +9282,277 @@ parsefname(void) } } - -/* - * Input any here documents. - */ -static void -parseheredoc(void) -{ - struct heredoc *here; - union node *n; - - here = heredoclist; - heredoclist = 0; - - while (here) { - if (needprompt) { - setprompt(2); - } - readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, - here->eofmark, here->striptabs); - n = stalloc(sizeof(struct narg)); - n->narg.type = NARG; - n->narg.next = NULL; - n->narg.text = wordtext; - n->narg.backquote = backquotelist; - here->here->nhere.doc = n; - here = here->next; - } -} - -static char -peektoken(void) -{ - int t; - - t = readtoken(); - tokpushback++; - return tokname_array[t][0]; -} - -static int -readtoken(void) +static union node * +simplecmd(void) { - int t; -#if DEBUG - int alreadyseen = tokpushback; -#endif - -#if ENABLE_ASH_ALIAS - top: -#endif - - t = xxreadtoken(); - - /* - * eat newlines - */ - if (checkkwd & CHKNL) { - while (t == TNL) { - parseheredoc(); - t = xxreadtoken(); - } - } - - if (t != TWORD || quoteflag) { - goto out; - } + union node *args, **app; + union node *n = NULL; + union node *vars, **vpp; + union node **rpp, *redir; + int savecheckkwd; - /* - * check for keywords - */ - if (checkkwd & CHKKWD) { - const char *const *pp; + args = NULL; + app = &args; + vars = NULL; + vpp = &vars; + redir = NULL; + rpp = &redir; - pp = findkwd(wordtext); - if (pp) { - lasttoken = t = pp - tokname_array; - TRACE(("keyword %s recognized\n", tokname(t))); - goto out; - } - } + savecheckkwd = CHKALIAS; + for (;;) { + checkkwd = savecheckkwd; + switch (readtoken()) { + case TWORD: + n = stalloc(sizeof(struct narg)); + n->type = NARG; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + if (savecheckkwd && isassignment(wordtext)) { + *vpp = n; + vpp = &n->narg.next; + } else { + *app = n; + app = &n->narg.next; + savecheckkwd = 0; + } + break; + case TREDIR: + *rpp = n = redirnode; + rpp = &n->nfile.next; + parsefname(); /* read name of redirection file */ + break; + case TLP: + if (args && app == &args->narg.next + && !vars && !redir + ) { + struct builtincmd *bcmd; + const char *name; - if (checkkwd & CHKALIAS) { -#if ENABLE_ASH_ALIAS - struct alias *ap; - ap = lookupalias(wordtext, 1); - if (ap != NULL) { - if (*ap->val) { - pushstring(ap->val, ap); + /* We have a function */ + if (readtoken() != TRP) + raise_error_unexpected_syntax(TRP); + name = n->narg.text; + if (!goodname(name) + || ((bcmd = find_builtin(name)) && IS_BUILTIN_SPECIAL(bcmd)) + ) { + raise_error_syntax("Bad function name"); + } + n->type = NDEFUN; + checkkwd = CHKNL | CHKKWD | CHKALIAS; + n->narg.next = parse_command(); + return n; } - goto top; + /* fall through */ + default: + tokpushback++; + goto out; } -#endif } out: - checkkwd = 0; -#if DEBUG - if (!alreadyseen) - TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); - else - TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); -#endif - return t; + *app = NULL; + *vpp = NULL; + *rpp = NULL; + n = stalloc(sizeof(struct ncmd)); + n->type = NCMD; + n->ncmd.args = args; + n->ncmd.assign = vars; + n->ncmd.redirect = redir; + return n; } - -/* - * Read the next input token. - * If the token is a word, we set backquotelist to the list of cmds in - * backquotes. We set quoteflag to true if any part of the word was - * quoted. - * If the token is TREDIR, then we set redirnode to a structure containing - * the redirection. - * In all cases, the variable startlinno is set to the number of the line - * on which the token starts. - * - * [Change comment: here documents and internal procedures] - * [Readtoken shouldn't have any arguments. Perhaps we should make the - * word parsing code into a separate routine. In this case, readtoken - * doesn't need to have any internal procedures, but parseword does. - * We could also make parseoperator in essence the main routine, and - * have parseword (readtoken1?) handle both words and redirection.] - */ -#define NEW_xxreadtoken -#ifdef NEW_xxreadtoken -/* singles must be first! */ -static const char xxreadtoken_chars[7] = { '\n', '(', ')', '&', '|', ';', 0 }; - -static const char xxreadtoken_tokens[] = { - TNL, TLP, TRP, /* only single occurrence allowed */ - TBACKGND, TPIPE, TSEMI, /* if single occurrence */ - TEOF, /* corresponds to trailing nul */ - TAND, TOR, TENDCASE, /* if double occurrence */ -}; - -#define xxreadtoken_doubles \ - (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars)) -#define xxreadtoken_singles \ - (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1) - -static int xxreadtoken(void) +static union node * +parse_command(void) { - int c; + union node *n1, *n2; + union node *ap, **app; + union node *cp, **cpp; + union node *redir, **rpp; + union node **rpp2; + int t; - if (tokpushback) { - tokpushback = 0; - return lasttoken; - } - if (needprompt) { - setprompt(2); - } - startlinno = plinno; - for (;;) { /* until token or start of word found */ - c = pgetc_macro(); + redir = NULL; + rpp2 = &redir; - if ((c != ' ') && (c != '\t') -#if ENABLE_ASH_ALIAS - && (c != PEOA) -#endif - ) { - if (c == '#') { - while ((c = pgetc()) != '\n' && c != PEOF); - pungetc(); - } else if (c == '\\') { - if (pgetc() != '\n') { - pungetc(); - goto READTOKEN1; - } - startlinno = ++plinno; - if (doprompt) - setprompt(2); - } else { - const char *p - = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; + switch (readtoken()) { + default: + raise_error_unexpected_syntax(-1); + /* NOTREACHED */ + case TIF: + n1 = stalloc(sizeof(struct nif)); + n1->type = NIF; + n1->nif.test = list(0); + if (readtoken() != TTHEN) + raise_error_unexpected_syntax(TTHEN); + n1->nif.ifpart = list(0); + n2 = n1; + while (readtoken() == TELIF) { + n2->nif.elsepart = stalloc(sizeof(struct nif)); + n2 = n2->nif.elsepart; + n2->type = NIF; + n2->nif.test = list(0); + if (readtoken() != TTHEN) + raise_error_unexpected_syntax(TTHEN); + n2->nif.ifpart = list(0); + } + if (lasttoken == TELSE) + n2->nif.elsepart = list(0); + else { + n2->nif.elsepart = NULL; + tokpushback++; + } + t = TFI; + break; + case TWHILE: + case TUNTIL: { + int got; + n1 = stalloc(sizeof(struct nbinary)); + n1->type = (lasttoken == TWHILE) ? NWHILE : NUNTIL; + n1->nbinary.ch1 = list(0); + got = readtoken(); + if (got != TDO) { + TRACE(("expecting DO got %s %s\n", tokname(got), + got == TWORD ? wordtext : "")); + raise_error_unexpected_syntax(TDO); + } + n1->nbinary.ch2 = list(0); + t = TDONE; + break; + } + case TFOR: + if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) + raise_error_syntax("Bad for loop variable"); + n1 = stalloc(sizeof(struct nfor)); + n1->type = NFOR; + n1->nfor.var = wordtext; + checkkwd = CHKKWD | CHKALIAS; + if (readtoken() == TIN) { + app = ≈ + while (readtoken() == TWORD) { + n2 = stalloc(sizeof(struct narg)); + n2->type = NARG; + n2->narg.text = wordtext; + n2->narg.backquote = backquotelist; + *app = n2; + app = &n2->narg.next; + } + *app = NULL; + n1->nfor.args = ap; + if (lasttoken != TNL && lasttoken != TSEMI) + raise_error_unexpected_syntax(-1); + } else { + n2 = stalloc(sizeof(struct narg)); + n2->type = NARG; + n2->narg.text = (char *)dolatstr; + n2->narg.backquote = NULL; + n2->narg.next = NULL; + n1->nfor.args = n2; + /* + * Newline or semicolon here is optional (but note + * that the original Bourne shell only allowed NL). + */ + if (lasttoken != TNL && lasttoken != TSEMI) + tokpushback++; + } + checkkwd = CHKNL | CHKKWD | CHKALIAS; + if (readtoken() != TDO) + raise_error_unexpected_syntax(TDO); + n1->nfor.body = list(0); + t = TDONE; + break; + case TCASE: + n1 = stalloc(sizeof(struct ncase)); + n1->type = NCASE; + if (readtoken() != TWORD) + raise_error_unexpected_syntax(TWORD); + n1->ncase.expr = n2 = stalloc(sizeof(struct narg)); + n2->type = NARG; + n2->narg.text = wordtext; + n2->narg.backquote = backquotelist; + n2->narg.next = NULL; + do { + checkkwd = CHKKWD | CHKALIAS; + } while (readtoken() == TNL); + if (lasttoken != TIN) + raise_error_unexpected_syntax(TIN); + cpp = &n1->ncase.cases; + next_case: + checkkwd = CHKNL | CHKKWD; + t = readtoken(); + while (t != TESAC) { + if (lasttoken == TLP) + readtoken(); + *cpp = cp = stalloc(sizeof(struct nclist)); + cp->type = NCLIST; + app = &cp->nclist.pattern; + for (;;) { + *app = ap = stalloc(sizeof(struct narg)); + ap->type = NARG; + ap->narg.text = wordtext; + ap->narg.backquote = backquotelist; + if (readtoken() != TPIPE) + break; + app = &ap->narg.next; + readtoken(); + } + ap->narg.next = NULL; + if (lasttoken != TRP) + raise_error_unexpected_syntax(TRP); + cp->nclist.body = list(2); - if (c != PEOF) { - if (c == '\n') { - plinno++; - needprompt = doprompt; - } + cpp = &cp->nclist.next; - p = strchr(xxreadtoken_chars, c); - if (p == NULL) { - READTOKEN1: - return readtoken1(c, BASESYNTAX, (char *) NULL, 0); - } + checkkwd = CHKNL | CHKKWD; + t = readtoken(); + if (t != TESAC) { + if (t != TENDCASE) + raise_error_unexpected_syntax(TENDCASE); + goto next_case; + } + } + *cpp = NULL; + goto redir; + case TLP: + n1 = stalloc(sizeof(struct nredir)); + n1->type = NSUBSHELL; + n1->nredir.n = list(0); + n1->nredir.redirect = NULL; + t = TRP; + break; + case TBEGIN: + n1 = list(0); + t = TEND; + break; + case TWORD: + case TREDIR: + tokpushback++; + return simplecmd(); + } - if (p - xxreadtoken_chars >= xxreadtoken_singles) { - if (pgetc() == *p) { /* double occurrence? */ - p += xxreadtoken_doubles + 1; - } else { - pungetc(); - } - } - } - return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; - } - } - } /* for */ -} -#else -#define RETURN(token) return lasttoken = token -static int -xxreadtoken(void) -{ - int c; + if (readtoken() != t) + raise_error_unexpected_syntax(t); - if (tokpushback) { - tokpushback = 0; - return lasttoken; - } - if (needprompt) { - setprompt(2); + redir: + /* Now check for redirection which may follow command */ + checkkwd = CHKKWD | CHKALIAS; + rpp = rpp2; + while (readtoken() == TREDIR) { + *rpp = n2 = redirnode; + rpp = &n2->nfile.next; + parsefname(); } - startlinno = plinno; - for (;;) { /* until token or start of word found */ - c = pgetc_macro(); - switch (c) { - case ' ': case '\t': -#if ENABLE_ASH_ALIAS - case PEOA: -#endif - continue; - case '#': - while ((c = pgetc()) != '\n' && c != PEOF); - pungetc(); - continue; - case '\\': - if (pgetc() == '\n') { - startlinno = ++plinno; - if (doprompt) - setprompt(2); - continue; - } - pungetc(); - goto breakloop; - case '\n': - plinno++; - needprompt = doprompt; - RETURN(TNL); - case PEOF: - RETURN(TEOF); - case '&': - if (pgetc() == '&') - RETURN(TAND); - pungetc(); - RETURN(TBACKGND); - case '|': - if (pgetc() == '|') - RETURN(TOR); - pungetc(); - RETURN(TPIPE); - case ';': - if (pgetc() == ';') - RETURN(TENDCASE); - pungetc(); - RETURN(TSEMI); - case '(': - RETURN(TLP); - case ')': - RETURN(TRP); - default: - goto breakloop; + tokpushback++; + *rpp = NULL; + if (redir) { + if (n1->type != NSUBSHELL) { + n2 = stalloc(sizeof(struct nredir)); + n2->type = NREDIR; + n2->nredir.n = n1; + n1 = n2; } + n1->nredir.redirect = redir; } - breakloop: - return readtoken1(c, BASESYNTAX, (char *)NULL, 0); -#undef RETURN + return n1; } -#endif /* NEW_xxreadtoken */ - /* * If eofmark is NULL, read a word or a redirection symbol. If eofmark @@ -9696,7 +9770,7 @@ readtoken1(int firstc, int syntax, char *eofmark, int striptabs) if ((c == '>' || c == '<') && quotef == 0 && len <= 2 - && (*out == '\0' || is_digit(*out))) { + && (*out == '\0' || isdigit(*out))) { PARSEREDIR(); return lasttoken = TREDIR; } else { @@ -9864,11 +9938,11 @@ parsesub: { STPUTC(c, out); c = pgetc(); } while (c > PEOA_OR_PEOF && is_in_name(c)); - } else if (is_digit(c)) { + } else if (isdigit(c)) { do { STPUTC(c, out); c = pgetc(); - } while (is_digit(c)); + } while (isdigit(c)); } else if (is_special(c)) { USTPUTC(c, out); c = pgetc(); @@ -10040,10 +10114,8 @@ parsebackq: { if (oldstyle) doprompt = saveprompt; - else { - if (readtoken() != TRP) - raise_error_unexpected_syntax(TRP); - } + else if (readtoken() != TRP) + raise_error_unexpected_syntax(TRP); (*nlpp)->n = n; if (oldstyle) { @@ -10072,1611 +10144,1572 @@ parsebackq: { else USTPUTC(CTLBACKQ, out); if (oldstyle) - goto parsebackq_oldreturn; - goto parsebackq_newreturn; -} - -#if ENABLE_ASH_MATH_SUPPORT -/* - * Parse an arithmetic expansion (indicate start of one and set state) - */ -parsearith: { - if (++arinest == 1) { - prevsyntax = syntax; - syntax = ARISYNTAX; - USTPUTC(CTLARI, out); - if (dblquote) - USTPUTC('"', out); - else - USTPUTC(' ', out); - } else { - /* - * we collapse embedded arithmetic expansion to - * parenthesis, which should be equivalent - */ - USTPUTC('(', out); - } - goto parsearith_return; -} -#endif - -} /* end of readtoken */ - - -/* - * Returns true if the text contains nothing to expand (no dollar signs - * or backquotes). - */ -static int -noexpand(char *text) -{ - char *p; - char c; - - p = text; - while ((c = *p++) != '\0') { - if (c == CTLQUOTEMARK) - continue; - if (c == CTLESC) - p++; - else if (SIT(c, BASESYNTAX) == CCTL) - return 0; - } - return 1; -} - - -/* - * Return of a legal variable name (a letter or underscore followed by zero or - * more letters, underscores, and digits). - */ -static char * -endofname(const char *name) -{ - char *p; - - p = (char *) name; - if (!is_name(*p)) - return p; - while (*++p) { - if (!is_in_name(*p)) - break; - } - return p; -} - - -/* - * called by editline -- any expansions to the prompt - * should be added here. - */ -#if ENABLE_ASH_EXPAND_PRMT -static const char * -expandstr(const char *ps) -{ - union node n; - - /* XXX Fix (char *) cast. */ - setinputstring((char *)ps); - readtoken1(pgetc(), DQSYNTAX, nullstr, 0); - popfile(); - - n.narg.type = NARG; - n.narg.next = NULL; - n.narg.text = wordtext; - n.narg.backquote = backquotelist; - - expandarg(&n, NULL, 0); - return stackblock(); -} -#endif - -static void setprompt(int whichprompt) -{ - const char *prompt; -#if ENABLE_ASH_EXPAND_PRMT - struct stackmark smark; -#endif - - needprompt = 0; - - switch (whichprompt) { - case 1: - prompt = ps1val(); - break; - case 2: - prompt = ps2val(); - break; - default: /* 0 */ - prompt = nullstr; - } -#if ENABLE_ASH_EXPAND_PRMT - setstackmark(&smark); - stalloc(stackblocksize()); -#endif - putprompt(expandstr(prompt)); -#if ENABLE_ASH_EXPAND_PRMT - popstackmark(&smark); -#endif -} - - -/* - * Execute a command or commands contained in a string. - */ -static int -evalstring(char *s, int mask) -{ - union node *n; - struct stackmark smark; - int skip; - - setinputstring(s); - setstackmark(&smark); - - skip = 0; - while ((n = parsecmd(0)) != NEOF) { - evaltree(n, 0); - popstackmark(&smark); - skip = evalskip; - if (skip) - break; - } - popfile(); - - skip &= mask; - evalskip = skip; - return skip; + goto parsebackq_oldreturn; + goto parsebackq_newreturn; } +#if ENABLE_ASH_MATH_SUPPORT /* - * The eval command. + * Parse an arithmetic expansion (indicate start of one and set state) */ -static int -evalcmd(int argc, char **argv) -{ - char *p; - char *concat; - char **ap; - - if (argc > 1) { - p = argv[1]; - if (argc > 2) { - STARTSTACKSTR(concat); - ap = argv + 2; - for (;;) { - concat = stack_putstr(p, concat); - p = *ap++; - if (p == NULL) - break; - STPUTC(' ', concat); - } - STPUTC('\0', concat); - p = grabstackstr(concat); - } - evalstring(p, ~SKIPEVAL); - +parsearith: { + if (++arinest == 1) { + prevsyntax = syntax; + syntax = ARISYNTAX; + USTPUTC(CTLARI, out); + if (dblquote) + USTPUTC('"', out); + else + USTPUTC(' ', out); + } else { + /* + * we collapse embedded arithmetic expansion to + * parenthesis, which should be equivalent + */ + USTPUTC('(', out); } - return exitstatus; + goto parsearith_return; } +#endif + +} /* end of readtoken */ /* - * Read and execute commands. "Top" is nonzero for the top level command - * loop; it turns on prompting if the shell is interactive. + * Read the next input token. + * If the token is a word, we set backquotelist to the list of cmds in + * backquotes. We set quoteflag to true if any part of the word was + * quoted. + * If the token is TREDIR, then we set redirnode to a structure containing + * the redirection. + * In all cases, the variable startlinno is set to the number of the line + * on which the token starts. + * + * [Change comment: here documents and internal procedures] + * [Readtoken shouldn't have any arguments. Perhaps we should make the + * word parsing code into a separate routine. In this case, readtoken + * doesn't need to have any internal procedures, but parseword does. + * We could also make parseoperator in essence the main routine, and + * have parseword (readtoken1?) handle both words and redirection.] */ -static int -cmdloop(int top) -{ - union node *n; - struct stackmark smark; - int inter; - int numeof = 0; - - TRACE(("cmdloop(%d) called\n", top)); - for (;;) { - int skip; +#define NEW_xxreadtoken +#ifdef NEW_xxreadtoken +/* singles must be first! */ +static const char xxreadtoken_chars[7] = { '\n', '(', ')', '&', '|', ';', 0 }; - setstackmark(&smark); -#if JOBS - if (jobctl) - showjobs(stderr, SHOW_CHANGED); -#endif - inter = 0; - if (iflag && top) { - inter++; -#if ENABLE_ASH_MAIL - chkmail(); -#endif - } - n = parsecmd(inter); - /* showtree(n); DEBUG */ - if (n == NEOF) { - if (!top || numeof >= 50) - break; - if (!stoppedjobs()) { - if (!Iflag) - break; - out2str("\nUse \"exit\" to leave shell.\n"); - } - numeof++; - } else if (nflag == 0) { - job_warning = (job_warning == 2) ? 1 : 0; - numeof = 0; - evaltree(n, 0); - } - popstackmark(&smark); - skip = evalskip; +static const char xxreadtoken_tokens[] = { + TNL, TLP, TRP, /* only single occurrence allowed */ + TBACKGND, TPIPE, TSEMI, /* if single occurrence */ + TEOF, /* corresponds to trailing nul */ + TAND, TOR, TENDCASE, /* if double occurrence */ +}; - if (skip) { - evalskip = 0; - return skip & SKIPEVAL; - } - } - return 0; -} +#define xxreadtoken_doubles \ + (sizeof(xxreadtoken_tokens) - sizeof(xxreadtoken_chars)) +#define xxreadtoken_singles \ + (sizeof(xxreadtoken_chars) - xxreadtoken_doubles - 1) static int -dotcmd(int argc, char **argv) +xxreadtoken(void) { - struct strlist *sp; - volatile struct shparam saveparam; - int status = 0; + int c; - for (sp = cmdenviron; sp; sp = sp->next) - setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED); + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); - if (argc >= 2) { /* That's what SVR2 does */ - char *fullname; + if ((c != ' ') && (c != '\t') +#if ENABLE_ASH_ALIAS + && (c != PEOA) +#endif + ) { + if (c == '#') { + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + } else if (c == '\\') { + if (pgetc() != '\n') { + pungetc(); + goto READTOKEN1; + } + startlinno = ++plinno; + if (doprompt) + setprompt(2); + } else { + const char *p + = xxreadtoken_chars + sizeof(xxreadtoken_chars) - 1; - fullname = find_dot_file(argv[1]); + if (c != PEOF) { + if (c == '\n') { + plinno++; + needprompt = doprompt; + } - if (argc > 2) { - saveparam = shellparam; - shellparam.malloc = 0; - shellparam.nparam = argc - 2; - shellparam.p = argv + 2; - }; + p = strchr(xxreadtoken_chars, c); + if (p == NULL) { + READTOKEN1: + return readtoken1(c, BASESYNTAX, (char *) NULL, 0); + } - setinputfile(fullname, INPUT_PUSH_FILE); - commandname = fullname; - cmdloop(0); - popfile(); + if (p - xxreadtoken_chars >= xxreadtoken_singles) { + if (pgetc() == *p) { /* double occurrence? */ + p += xxreadtoken_doubles + 1; + } else { + pungetc(); + } + } + } + return lasttoken = xxreadtoken_tokens[p - xxreadtoken_chars]; + } + } + } /* for */ +} +#else +#define RETURN(token) return lasttoken = token +static int +xxreadtoken(void) +{ + int c; - if (argc > 2) { - freeparam(&shellparam); - shellparam = saveparam; - }; - status = exitstatus; + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + switch (c) { + case ' ': case '\t': +#if ENABLE_ASH_ALIAS + case PEOA: +#endif + continue; + case '#': + while ((c = pgetc()) != '\n' && c != PEOF); + pungetc(); + continue; + case '\\': + if (pgetc() == '\n') { + startlinno = ++plinno; + if (doprompt) + setprompt(2); + continue; + } + pungetc(); + goto breakloop; + case '\n': + plinno++; + needprompt = doprompt; + RETURN(TNL); + case PEOF: + RETURN(TEOF); + case '&': + if (pgetc() == '&') + RETURN(TAND); + pungetc(); + RETURN(TBACKGND); + case '|': + if (pgetc() == '|') + RETURN(TOR); + pungetc(); + RETURN(TPIPE); + case ';': + if (pgetc() == ';') + RETURN(TENDCASE); + pungetc(); + RETURN(TSEMI); + case '(': + RETURN(TLP); + case ')': + RETURN(TRP); + default: + goto breakloop; + } } - return status; -} - -static int -exitcmd(int argc, char **argv) -{ - if (stoppedjobs()) - return 0; - if (argc > 1) - exitstatus = number(argv[1]); - raise_exception(EXEXIT); - /* NOTREACHED */ -} - -#if ENABLE_ASH_BUILTIN_ECHO -static int -echocmd(int argc, char **argv) -{ - return bb_echo(argv); + breakloop: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); +#undef RETURN } -#endif +#endif /* NEW_xxreadtoken */ -#if ENABLE_ASH_BUILTIN_TEST static int -testcmd(int argc, char **argv) +readtoken(void) { - return bb_test(argc, argv); -} + int t; +#if DEBUG + int alreadyseen = tokpushback; #endif -/* - * Read a file containing shell functions. - */ -static void -readcmdfile(char *name) -{ - setinputfile(name, INPUT_PUSH_FILE); - cmdloop(0); - popfile(); -} - - -/* redir.c */ - -/* - * Code for dealing with input/output redirection. - */ - -#define EMPTY -2 /* marks an unused slot in redirtab */ -#ifndef PIPE_BUF -# define PIPESIZE 4096 /* amount of buffering in a pipe */ -#else -# define PIPESIZE PIPE_BUF +#if ENABLE_ASH_ALIAS + top: #endif -/* - * Open a file in noclobber mode. - * The code was copied from bash. - */ -static int -noclobberopen(const char *fname) -{ - int r, fd; - struct stat finfo, finfo2; + t = xxreadtoken(); /* - * If the file exists and is a regular file, return an error - * immediately. + * eat newlines */ - r = stat(fname, &finfo); - if (r == 0 && S_ISREG(finfo.st_mode)) { - errno = EEXIST; - return -1; + if (checkkwd & CHKNL) { + while (t == TNL) { + parseheredoc(); + t = xxreadtoken(); + } } - /* - * If the file was not present (r != 0), make sure we open it - * exclusively so that if it is created before we open it, our open - * will fail. Make sure that we do not truncate an existing file. - * Note that we don't turn on O_EXCL unless the stat failed -- if the - * file was not a regular file, we leave O_EXCL off. - */ - if (r != 0) - return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); - fd = open(fname, O_WRONLY|O_CREAT, 0666); - - /* If the open failed, return the file descriptor right away. */ - if (fd < 0) - return fd; - - /* - * OK, the open succeeded, but the file may have been changed from a - * non-regular file to a regular file between the stat and the open. - * We are assuming that the O_EXCL open handles the case where FILENAME - * did not exist and is symlinked to an existing file between the stat - * and open. - */ + if (t != TWORD || quoteflag) { + goto out; + } /* - * If we can open it and fstat the file descriptor, and neither check - * revealed that it was a regular file, and the file has not been - * replaced, return the file descriptor. + * check for keywords */ - if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) - && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) - return fd; - - /* The file has been replaced. badness. */ - close(fd); - errno = EEXIST; - return -1; -} - - -/* - * Handle here documents. Normally we fork off a process to write the - * data to a pipe. If the document is short, we can stuff the data in - * the pipe without forking. - */ -static int -openhere(union node *redir) -{ - int pip[2]; - size_t len = 0; + if (checkkwd & CHKKWD) { + const char *const *pp; - if (pipe(pip) < 0) - ash_msg_and_raise_error("Pipe call failed"); - if (redir->type == NHERE) { - len = strlen(redir->nhere.doc->narg.text); - if (len <= PIPESIZE) { - full_write(pip[1], redir->nhere.doc->narg.text, len); + pp = findkwd(wordtext); + if (pp) { + lasttoken = t = pp - tokname_array; + TRACE(("keyword %s recognized\n", tokname(t))); goto out; } } - if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { - close(pip[0]); - signal(SIGINT, SIG_IGN); - signal(SIGQUIT, SIG_IGN); - signal(SIGHUP, SIG_IGN); -#ifdef SIGTSTP - signal(SIGTSTP, SIG_IGN); + + if (checkkwd & CHKALIAS) { +#if ENABLE_ASH_ALIAS + struct alias *ap; + ap = lookupalias(wordtext, 1); + if (ap != NULL) { + if (*ap->val) { + pushstring(ap->val, ap); + } + goto top; + } #endif - signal(SIGPIPE, SIG_DFL); - if (redir->type == NHERE) - full_write(pip[1], redir->nhere.doc->narg.text, len); - else - expandhere(redir->nhere.doc, pip[1]); - _exit(0); } out: - close(pip[1]); - return pip[0]; + checkkwd = 0; +#if DEBUG + if (!alreadyseen) + TRACE(("token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); + else + TRACE(("reread token %s %s\n", tokname(t), t == TWORD ? wordtext : "")); +#endif + return t; } -static int -openredirect(union node *redir) +static char +peektoken(void) { - char *fname; - int f; + int t; - switch (redir->nfile.type) { - case NFROM: - fname = redir->nfile.expfname; - f = open(fname, O_RDONLY); - if (f < 0) - goto eopen; - break; - case NFROMTO: - fname = redir->nfile.expfname; - f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666); - if (f < 0) - goto ecreate; - break; - case NTO: - /* Take care of noclobber mode. */ - if (Cflag) { - fname = redir->nfile.expfname; - f = noclobberopen(fname); - if (f < 0) - goto ecreate; - break; - } - /* FALLTHROUGH */ - case NCLOBBER: - fname = redir->nfile.expfname; - f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666); - if (f < 0) - goto ecreate; - break; - case NAPPEND: - fname = redir->nfile.expfname; - f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666); - if (f < 0) - goto ecreate; - break; - default: -#if DEBUG - abort(); -#endif - /* Fall through to eliminate warning. */ - case NTOFD: - case NFROMFD: - f = -1; - break; - case NHERE: - case NXHERE: - f = openhere(redir); - break; - } + t = readtoken(); + tokpushback++; + return tokname_array[t][0]; +} + +/* + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) + */ +static union node * +parsecmd(int interact) +{ + int t; - return f; - ecreate: - ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent")); - eopen: - ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file")); + tokpushback = 0; + doprompt = interact; + if (doprompt) + setprompt(doprompt); + needprompt = 0; + t = readtoken(); + if (t == TEOF) + return NEOF; + if (t == TNL) + return NULL; + tokpushback++; + return list(1); } +/* + * Input any here documents. + */ static void -dupredirect(union node *redir, int f) +parseheredoc(void) { - int fd = redir->nfile.fd; + struct heredoc *here; + union node *n; - if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { - if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ - copyfd(redir->ndup.dupfd, fd); - } - return; - } + here = heredoclist; + heredoclist = 0; - if (f != fd) { - copyfd(f, fd); - close(f); + while (here) { + if (needprompt) { + setprompt(2); + } + readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, + here->eofmark, here->striptabs); + n = stalloc(sizeof(struct narg)); + n->narg.type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + here->here->nhere.doc = n; + here = here->next; } } /* - * Process a list of redirection commands. If the REDIR_PUSH flag is set, - * old file descriptors are stashed away so that the redirection can be - * undone by calling popredir. If the REDIR_BACKQ flag is set, then the - * standard output, and the standard error if it becomes a duplicate of - * stdout, is saved in memory. + * called by editline -- any expansions to the prompt + * should be added here. */ -static void -redirect(union node *redir, int flags) +#if ENABLE_ASH_EXPAND_PRMT +static const char * +expandstr(const char *ps) { - union node *n; - struct redirtab *sv; - int i; - int fd; - int newfd; - int *p; - nullredirs++; - if (!redir) { - return; - } - sv = NULL; - INT_OFF; - if (flags & REDIR_PUSH) { - struct redirtab *q; - q = ckmalloc(sizeof(struct redirtab)); - q->next = redirlist; - redirlist = q; - q->nullredirs = nullredirs - 1; - for (i = 0; i < 10; i++) - q->renamed[i] = EMPTY; - nullredirs = 0; - sv = q; - } - n = redir; - do { - fd = n->nfile.fd; - if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) - && n->ndup.dupfd == fd) - continue; /* redirect from/to same file descriptor */ + union node n; - newfd = openredirect(n); - if (fd == newfd) - continue; - if (sv && *(p = &sv->renamed[fd]) == EMPTY) { - i = fcntl(fd, F_DUPFD, 10); + /* XXX Fix (char *) cast. */ + setinputstring((char *)ps); + readtoken1(pgetc(), DQSYNTAX, nullstr, 0); + popfile(); - if (i == -1) { - i = errno; - if (i != EBADF) { - close(newfd); - errno = i; - ash_msg_and_raise_error("%d: %m", fd); - /* NOTREACHED */ - } - } else { - *p = i; - close(fd); - } - } else { - close(fd); - } - dupredirect(n, newfd); - } while ((n = n->nfile.next)); - INT_ON; - if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0) - preverrout_fd = sv->renamed[2]; + n.narg.type = NARG; + n.narg.next = NULL; + n.narg.text = wordtext; + n.narg.backquote = backquotelist; + + expandarg(&n, NULL, 0); + return stackblock(); } +#endif /* - * Undo the effects of the last redirection. + * Execute a command or commands contained in a string. */ -static void -popredir(int drop) +static int +evalstring(char *s, int mask) { - struct redirtab *rp; - int i; + union node *n; + struct stackmark smark; + int skip; - if (--nullredirs >= 0) - return; - INT_OFF; - rp = redirlist; - for (i = 0; i < 10; i++) { - if (rp->renamed[i] != EMPTY) { - if (!drop) { - close(i); - copyfd(rp->renamed[i], i); - } - close(rp->renamed[i]); - } + setinputstring(s); + setstackmark(&smark); + + skip = 0; + while ((n = parsecmd(0)) != NEOF) { + evaltree(n, 0); + popstackmark(&smark); + skip = evalskip; + if (skip) + break; } - redirlist = rp->next; - nullredirs = rp->nullredirs; - free(rp); - INT_ON; -} + popfile(); -/* - * Undo all redirections. Called on error or interrupt. - */ + skip &= mask; + evalskip = skip; + return skip; +} /* - * Discard all saved file descriptors. + * The eval command. */ -static void -clearredir(int drop) +static int +evalcmd(int argc, char **argv) { - for (;;) { - nullredirs = 0; - if (!redirlist) - break; - popredir(drop); + char *p; + char *concat; + char **ap; + + if (argc > 1) { + p = argv[1]; + if (argc > 2) { + STARTSTACKSTR(concat); + ap = argv + 2; + for (;;) { + concat = stack_putstr(p, concat); + p = *ap++; + if (p == NULL) + break; + STPUTC(' ', concat); + } + STPUTC('\0', concat); + p = grabstackstr(concat); + } + evalstring(p, ~SKIPEVAL); + } + return exitstatus; } - /* - * Copy a file descriptor to be >= to. Returns -1 - * if the source file descriptor is closed, EMPTY if there are no unused - * file descriptors left. + * Read and execute commands. "Top" is nonzero for the top level command + * loop; it turns on prompting if the shell is interactive. */ static int -copyfd(int from, int to) +cmdloop(int top) { - int newfd; + union node *n; + struct stackmark smark; + int inter; + int numeof = 0; + + TRACE(("cmdloop(%d) called\n", top)); + for (;;) { + int skip; + + setstackmark(&smark); +#if JOBS + if (jobctl) + showjobs(stderr, SHOW_CHANGED); +#endif + inter = 0; + if (iflag && top) { + inter++; +#if ENABLE_ASH_MAIL + chkmail(); +#endif + } + n = parsecmd(inter); + /* showtree(n); DEBUG */ + if (n == NEOF) { + if (!top || numeof >= 50) + break; + if (!stoppedjobs()) { + if (!Iflag) + break; + out2str("\nUse \"exit\" to leave shell.\n"); + } + numeof++; + } else if (nflag == 0) { + job_warning = (job_warning == 2) ? 1 : 0; + numeof = 0; + evaltree(n, 0); + } + popstackmark(&smark); + skip = evalskip; - newfd = fcntl(from, F_DUPFD, to); - if (newfd < 0) { - if (errno == EMFILE) - return EMPTY; - ash_msg_and_raise_error("%d: %m", from); + if (skip) { + evalskip = 0; + return skip & SKIPEVAL; + } } - return newfd; + return 0; } - static int -redirectsafe(union node *redir, int flags) +dotcmd(int argc, char **argv) { - int err; - volatile int saveint; - struct jmploc *volatile savehandler = exception_handler; - struct jmploc jmploc; - - SAVE_INT(saveint); - err = setjmp(jmploc.loc) * 2; - if (!err) { - exception_handler = &jmploc; - redirect(redir, flags); - } - exception_handler = savehandler; - if (err && exception != EXERROR) - longjmp(exception_handler->loc, 1); - RESTORE_INT(saveint); - return err; -} - -/* show.c */ - -#if DEBUG -static void shtree(union node *, int, char *, FILE*); -static void shcmd(union node *, FILE *); -static void sharg(union node *, FILE *); -static void indent(int, char *, FILE *); -static void trstring(char *); + struct strlist *sp; + volatile struct shparam saveparam; + int status = 0; + for (sp = cmdenviron; sp; sp = sp->next) + setvareq(xstrdup(sp->text), VSTRFIXED | VTEXTFIXED); -static void -showtree(union node *n) -{ - trputs("showtree called\n"); - shtree(n, 1, NULL, stdout); -} + if (argc >= 2) { /* That's what SVR2 does */ + char *fullname; + fullname = find_dot_file(argv[1]); -static void -shtree(union node *n, int ind, char *pfx, FILE *fp) -{ - struct nodelist *lp; - const char *s; + if (argc > 2) { + saveparam = shellparam; + shellparam.malloc = 0; + shellparam.nparam = argc - 2; + shellparam.p = argv + 2; + }; - if (n == NULL) - return; + setinputfile(fullname, INPUT_PUSH_FILE); + commandname = fullname; + cmdloop(0); + popfile(); - indent(ind, pfx, fp); - switch (n->type) { - case NSEMI: - s = "; "; - goto binop; - case NAND: - s = " && "; - goto binop; - case NOR: - s = " || "; - binop: - shtree(n->nbinary.ch1, ind, NULL, fp); - /* if (ind < 0) */ - fputs(s, fp); - shtree(n->nbinary.ch2, ind, NULL, fp); - break; - case NCMD: - shcmd(n, fp); - if (ind >= 0) - putc('\n', fp); - break; - case NPIPE: - for (lp = n->npipe.cmdlist; lp; lp = lp->next) { - shcmd(lp->n, fp); - if (lp->next) - fputs(" | ", fp); - } - if (n->npipe.backgnd) - fputs(" &", fp); - if (ind >= 0) - putc('\n', fp); - break; - default: - fprintf(fp, "", n->type); - if (ind >= 0) - putc('\n', fp); - break; + if (argc > 2) { + freeparam(&shellparam); + shellparam = saveparam; + }; + status = exitstatus; } + return status; } - -static void -shcmd(union node *cmd, FILE *fp) +static int +exitcmd(int argc, char **argv) { - union node *np; - int first; - const char *s; - int dftfd; - - first = 1; - for (np = cmd->ncmd.args; np; np = np->narg.next) { - if (! first) - putchar(' '); - sharg(np, fp); - first = 0; - } - for (np = cmd->ncmd.redirect; np; np = np->nfile.next) { - if (! first) - putchar(' '); - switch (np->nfile.type) { - case NTO: s = ">"; dftfd = 1; break; - case NCLOBBER: s = ">|"; dftfd = 1; break; - case NAPPEND: s = ">>"; dftfd = 1; break; - case NTOFD: s = ">&"; dftfd = 1; break; - case NFROM: s = "<"; dftfd = 0; break; - case NFROMFD: s = "<&"; dftfd = 0; break; - case NFROMTO: s = "<>"; dftfd = 0; break; - default: s = "*error*"; dftfd = 0; break; - } - if (np->nfile.fd != dftfd) - fprintf(fp, "%d", np->nfile.fd); - fputs(s, fp); - if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { - fprintf(fp, "%d", np->ndup.dupfd); - } else { - sharg(np->nfile.fname, fp); - } - first = 0; - } + if (stoppedjobs()) + return 0; + if (argc > 1) + exitstatus = number(argv[1]); + raise_exception(EXEXIT); + /* NOTREACHED */ } - -static void -sharg(union node *arg, FILE *fp) +#if ENABLE_ASH_BUILTIN_ECHO +static int +echocmd(int argc, char **argv) { - char *p; - struct nodelist *bqlist; - int subtype; - - if (arg->type != NARG) { - out1fmt("\n", arg->type); - abort(); - } - bqlist = arg->narg.backquote; - for (p = arg->narg.text; *p; p++) { - switch (*p) { - case CTLESC: - putc(*++p, fp); - break; - case CTLVAR: - putc('$', fp); - putc('{', fp); - subtype = *++p; - if (subtype == VSLENGTH) - putc('#', fp); - - while (*p != '=') - putc(*p++, fp); - - if (subtype & VSNUL) - putc(':', fp); - - switch (subtype & VSTYPE) { - case VSNORMAL: - putc('}', fp); - break; - case VSMINUS: - putc('-', fp); - break; - case VSPLUS: - putc('+', fp); - break; - case VSQUESTION: - putc('?', fp); - break; - case VSASSIGN: - putc('=', fp); - break; - case VSTRIMLEFT: - putc('#', fp); - break; - case VSTRIMLEFTMAX: - putc('#', fp); - putc('#', fp); - break; - case VSTRIMRIGHT: - putc('%', fp); - break; - case VSTRIMRIGHTMAX: - putc('%', fp); - putc('%', fp); - break; - case VSLENGTH: - break; - default: - out1fmt("", subtype); - } - break; - case CTLENDVAR: - putc('}', fp); - break; - case CTLBACKQ: - case CTLBACKQ|CTLQUOTE: - putc('$', fp); - putc('(', fp); - shtree(bqlist->n, -1, NULL, fp); - putc(')', fp); - break; - default: - putc(*p, fp); - break; - } - } + return bb_echo(argv); } +#endif +#if ENABLE_ASH_BUILTIN_TEST +static int +testcmd(int argc, char **argv) +{ + return bb_test(argc, argv); +} +#endif +/* + * Read a file containing shell functions. + */ static void -indent(int amount, char *pfx, FILE *fp) +readcmdfile(char *name) { - int i; - - for (i = 0; i < amount; i++) { - if (pfx && i == amount - 1) - fputs(pfx, fp); - putc('\t', fp); - } + setinputfile(name, INPUT_PUSH_FILE); + cmdloop(0); + popfile(); } +/* redir.c */ + /* - * Debugging stuff. + * Code for dealing with input/output redirection. */ +#define EMPTY -2 /* marks an unused slot in redirtab */ +#ifndef PIPE_BUF +# define PIPESIZE 4096 /* amount of buffering in a pipe */ +#else +# define PIPESIZE PIPE_BUF +#endif -static FILE *tracefile; - - -static void -trputc(int c) +/* + * Open a file in noclobber mode. + * The code was copied from bash. + */ +static int +noclobberopen(const char *fname) { - if (debug != 1) - return; - putc(c, tracefile); -} + int r, fd; + struct stat finfo, finfo2; -static void -trace(const char *fmt, ...) -{ - va_list va; + /* + * If the file exists and is a regular file, return an error + * immediately. + */ + r = stat(fname, &finfo); + if (r == 0 && S_ISREG(finfo.st_mode)) { + errno = EEXIST; + return -1; + } - if (debug != 1) - return; - va_start(va, fmt); - (void) vfprintf(tracefile, fmt, va); - va_end(va); -} + /* + * If the file was not present (r != 0), make sure we open it + * exclusively so that if it is created before we open it, our open + * will fail. Make sure that we do not truncate an existing file. + * Note that we don't turn on O_EXCL unless the stat failed -- if the + * file was not a regular file, we leave O_EXCL off. + */ + if (r != 0) + return open(fname, O_WRONLY|O_CREAT|O_EXCL, 0666); + fd = open(fname, O_WRONLY|O_CREAT, 0666); -static void -tracev(const char *fmt, va_list va) -{ - if (debug != 1) - return; - (void) vfprintf(tracefile, fmt, va); -} + /* If the open failed, return the file descriptor right away. */ + if (fd < 0) + return fd; + /* + * OK, the open succeeded, but the file may have been changed from a + * non-regular file to a regular file between the stat and the open. + * We are assuming that the O_EXCL open handles the case where FILENAME + * did not exist and is symlinked to an existing file between the stat + * and open. + */ -static void -trputs(const char *s) -{ - if (debug != 1) - return; - fputs(s, tracefile); + /* + * If we can open it and fstat the file descriptor, and neither check + * revealed that it was a regular file, and the file has not been + * replaced, return the file descriptor. + */ + if (fstat(fd, &finfo2) == 0 && !S_ISREG(finfo2.st_mode) + && finfo.st_dev == finfo2.st_dev && finfo.st_ino == finfo2.st_ino) + return fd; + + /* The file has been replaced. badness. */ + close(fd); + errno = EEXIST; + return -1; } -static void -trstring(char *s) +/* + * Handle here documents. Normally we fork off a process to write the + * data to a pipe. If the document is short, we can stuff the data in + * the pipe without forking. + */ +static int +openhere(union node *redir) { - char *p; - char c; + int pip[2]; + size_t len = 0; - if (debug != 1) - return; - putc('"', tracefile); - for (p = s; *p; p++) { - switch (*p) { - case '\n': c = 'n'; goto backslash; - case '\t': c = 't'; goto backslash; - case '\r': c = 'r'; goto backslash; - case '"': c = '"'; goto backslash; - case '\\': c = '\\'; goto backslash; - case CTLESC: c = 'e'; goto backslash; - case CTLVAR: c = 'v'; goto backslash; - case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; - case CTLBACKQ: c = 'q'; goto backslash; - case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; - backslash: - putc('\\', tracefile); - putc(c, tracefile); - break; - default: - if (*p >= ' ' && *p <= '~') - putc(*p, tracefile); - else { - putc('\\', tracefile); - putc(*p >> 6 & 03, tracefile); - putc(*p >> 3 & 07, tracefile); - putc(*p & 07, tracefile); - } - break; + if (pipe(pip) < 0) + ash_msg_and_raise_error("Pipe call failed"); + if (redir->type == NHERE) { + len = strlen(redir->nhere.doc->narg.text); + if (len <= PIPESIZE) { + full_write(pip[1], redir->nhere.doc->narg.text, len); + goto out; } } - putc('"', tracefile); -} - - -static void -trargs(char **ap) -{ - if (debug != 1) - return; - while (*ap) { - trstring(*ap++); - if (*ap) - putc(' ', tracefile); + if (forkshell((struct job *)NULL, (union node *)NULL, FORK_NOJOB) == 0) { + close(pip[0]); + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + signal(SIGHUP, SIG_IGN); +#ifdef SIGTSTP + signal(SIGTSTP, SIG_IGN); +#endif + signal(SIGPIPE, SIG_DFL); + if (redir->type == NHERE) + full_write(pip[1], redir->nhere.doc->narg.text, len); else - putc('\n', tracefile); + expandhere(redir->nhere.doc, pip[1]); + _exit(0); } + out: + close(pip[1]); + return pip[0]; } - -static void -opentrace(void) +static int +openredirect(union node *redir) { - char s[100]; -#ifdef O_APPEND - int flags; -#endif + char *fname; + int f; - if (debug != 1) { - if (tracefile) - fflush(tracefile); - /* leave open because libedit might be using it */ - return; - } - scopy("./trace", s); - if (tracefile) { - if (!freopen(s, "a", tracefile)) { - fprintf(stderr, "Can't re-open %s\n", s); - debug = 0; - return; - } - } else { - tracefile = fopen(s, "a"); - if (tracefile == NULL) { - fprintf(stderr, "Can't open %s\n", s); - debug = 0; - return; + switch (redir->nfile.type) { + case NFROM: + fname = redir->nfile.expfname; + f = open(fname, O_RDONLY); + if (f < 0) + goto eopen; + break; + case NFROMTO: + fname = redir->nfile.expfname; + f = open(fname, O_RDWR|O_CREAT|O_TRUNC, 0666); + if (f < 0) + goto ecreate; + break; + case NTO: + /* Take care of noclobber mode. */ + if (Cflag) { + fname = redir->nfile.expfname; + f = noclobberopen(fname); + if (f < 0) + goto ecreate; + break; } + /* FALLTHROUGH */ + case NCLOBBER: + fname = redir->nfile.expfname; + f = open(fname, O_WRONLY|O_CREAT|O_TRUNC, 0666); + if (f < 0) + goto ecreate; + break; + case NAPPEND: + fname = redir->nfile.expfname; + f = open(fname, O_WRONLY|O_CREAT|O_APPEND, 0666); + if (f < 0) + goto ecreate; + break; + default: +#if DEBUG + abort(); +#endif + /* Fall through to eliminate warning. */ + case NTOFD: + case NFROMFD: + f = -1; + break; + case NHERE: + case NXHERE: + f = openhere(redir); + break; } -#ifdef O_APPEND - flags = fcntl(fileno(tracefile), F_GETFL, 0); - if (flags >= 0) - fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); -#endif - setlinebuf(tracefile); - fputs("\nTracing started.\n", tracefile); -} -#endif /* DEBUG */ - - -/* trap.c */ - -/* - * Sigmode records the current value of the signal handlers for the various - * modes. A value of zero means that the current handler is not known. - * S_HARD_IGN indicates that the signal was ignored on entry to the shell, - */ - -#define S_DFL 1 /* default signal handling (SIG_DFL) */ -#define S_CATCH 2 /* signal is caught */ -#define S_IGN 3 /* signal is ignored (SIG_IGN) */ -#define S_HARD_IGN 4 /* signal is ignored permenantly */ -#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + return f; + ecreate: + ash_msg_and_raise_error("cannot create %s: %s", fname, errmsg(errno, "Directory nonexistent")); + eopen: + ash_msg_and_raise_error("cannot open %s: %s", fname, errmsg(errno, "No such file")); +} -/* - * The trap builtin. - */ -static int -trapcmd(int argc, char **argv) +static void +dupredirect(union node *redir, int f) { - char *action; - char **ap; - int signo; - - nextopt(nullstr); - ap = argptr; - if (!*ap) { - for (signo = 0; signo < NSIG; signo++) { - if (trap[signo] != NULL) { - const char *sn; + int fd = redir->nfile.fd; - sn = get_signame(signo); - out1fmt("trap -- %s %s\n", - single_quote(trap[signo]), sn); - } + if (redir->nfile.type == NTOFD || redir->nfile.type == NFROMFD) { + if (redir->ndup.dupfd >= 0) { /* if not ">&-" */ + copyfd(redir->ndup.dupfd, fd); } - return 0; + return; } - if (!ap[1]) - action = NULL; - else - action = *ap++; - while (*ap) { - signo = get_signum(*ap); - if (signo < 0) - ash_msg_and_raise_error("%s: bad trap", *ap); - INT_OFF; - if (action) { - if (LONE_DASH(action)) - action = NULL; - else - action = ckstrdup(action); - } - if (trap[signo]) - free(trap[signo]); - trap[signo] = action; - if (signo != 0) - setsignal(signo); - INT_ON; - ap++; + + if (f != fd) { + copyfd(f, fd); + close(f); } - return 0; } /* - * Clear traps on a fork. + * Process a list of redirection commands. If the REDIR_PUSH flag is set, + * old file descriptors are stashed away so that the redirection can be + * undone by calling popredir. If the REDIR_BACKQ flag is set, then the + * standard output, and the standard error if it becomes a duplicate of + * stdout, is saved in memory. */ static void -clear_traps(void) +redirect(union node *redir, int flags) { - char **tp; + union node *n; + struct redirtab *sv; + int i; + int fd; + int newfd; + int *p; + nullredirs++; + if (!redir) { + return; + } + sv = NULL; + INT_OFF; + if (flags & REDIR_PUSH) { + struct redirtab *q; + q = ckmalloc(sizeof(struct redirtab)); + q->next = redirlist; + redirlist = q; + q->nullredirs = nullredirs - 1; + for (i = 0; i < 10; i++) + q->renamed[i] = EMPTY; + nullredirs = 0; + sv = q; + } + n = redir; + do { + fd = n->nfile.fd; + if ((n->nfile.type == NTOFD || n->nfile.type == NFROMFD) + && n->ndup.dupfd == fd) + continue; /* redirect from/to same file descriptor */ - for (tp = trap; tp < &trap[NSIG]; tp++) { - if (*tp && **tp) { /* trap not NULL or SIG_IGN */ - INT_OFF; - free(*tp); - *tp = NULL; - if (tp != &trap[0]) - setsignal(tp - trap); - INT_ON; + newfd = openredirect(n); + if (fd == newfd) + continue; + if (sv && *(p = &sv->renamed[fd]) == EMPTY) { + i = fcntl(fd, F_DUPFD, 10); + + if (i == -1) { + i = errno; + if (i != EBADF) { + close(newfd); + errno = i; + ash_msg_and_raise_error("%d: %m", fd); + /* NOTREACHED */ + } + } else { + *p = i; + close(fd); + } + } else { + close(fd); } - } + dupredirect(n, newfd); + } while ((n = n->nfile.next)); + INT_ON; + if (flags & REDIR_SAVEFD2 && sv && sv->renamed[2] >= 0) + preverrout_fd = sv->renamed[2]; } /* - * Set the signal handler for the specified signal. The routine figures - * out what it should be set to. + * Undo the effects of the last redirection. */ static void -setsignal(int signo) +popredir(int drop) { - int action; - char *t, tsig; - struct sigaction act; - - t = trap[signo]; - if (t == NULL) - action = S_DFL; - else if (*t != '\0') - action = S_CATCH; - else - action = S_IGN; - if (rootshell && action == S_DFL) { - switch (signo) { - case SIGINT: - if (iflag || minusc || sflag == 0) - action = S_CATCH; - break; - case SIGQUIT: -#if DEBUG - if (debug) - break; -#endif - /* FALLTHROUGH */ - case SIGTERM: - if (iflag) - action = S_IGN; - break; -#if JOBS - case SIGTSTP: - case SIGTTOU: - if (mflag) - action = S_IGN; - break; -#endif - } - } + struct redirtab *rp; + int i; - t = &sigmode[signo - 1]; - tsig = *t; - if (tsig == 0) { - /* - * current setting unknown - */ - if (sigaction(signo, 0, &act) == -1) { - /* - * Pretend it worked; maybe we should give a warning - * here, but other shells don't. We don't alter - * sigmode, so that we retry every time. - */ - return; - } - if (act.sa_handler == SIG_IGN) { - if (mflag - && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) - ) { - tsig = S_IGN; /* don't hard ignore these */ - } else - tsig = S_HARD_IGN; - } else { - tsig = S_RESET; /* force to be set */ - } - } - if (tsig == S_HARD_IGN || tsig == action) + if (--nullredirs >= 0) return; - switch (action) { - case S_CATCH: - act.sa_handler = onsig; - break; - case S_IGN: - act.sa_handler = SIG_IGN; - break; - default: - act.sa_handler = SIG_DFL; + INT_OFF; + rp = redirlist; + for (i = 0; i < 10; i++) { + if (rp->renamed[i] != EMPTY) { + if (!drop) { + close(i); + copyfd(rp->renamed[i], i); + } + close(rp->renamed[i]); + } } - *t = action; - act.sa_flags = 0; - sigfillset(&act.sa_mask); - sigaction(signo, &act, 0); + redirlist = rp->next; + nullredirs = rp->nullredirs; + free(rp); + INT_ON; } +/* + * Undo all redirections. Called on error or interrupt. + */ /* - * Ignore a signal. + * Discard all saved file descriptors. */ static void -ignoresig(int signo) +clearredir(int drop) { - if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { - signal(signo, SIG_IGN); + for (;;) { + nullredirs = 0; + if (!redirlist) + break; + popredir(drop); } - sigmode[signo - 1] = S_HARD_IGN; } /* - * Signal handler. + * Copy a file descriptor to be >= to. Returns -1 + * if the source file descriptor is closed, EMPTY if there are no unused + * file descriptors left. */ +static int +copyfd(int from, int to) +{ + int newfd; + + newfd = fcntl(from, F_DUPFD, to); + if (newfd < 0) { + if (errno == EMFILE) + return EMPTY; + ash_msg_and_raise_error("%d: %m", from); + } + return newfd; +} + +static int +redirectsafe(union node *redir, int flags) +{ + int err; + volatile int saveint; + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; + + SAVE_INT(saveint); + err = setjmp(jmploc.loc) * 2; + if (!err) { + exception_handler = &jmploc; + redirect(redir, flags); + } + exception_handler = savehandler; + if (err && exception != EXERROR) + longjmp(exception_handler->loc, 1); + RESTORE_INT(saveint); + return err; +} + +/* show.c */ + +#if DEBUG +static void shtree(union node *, int, char *, FILE*); +static void shcmd(union node *, FILE *); +static void sharg(union node *, FILE *); +static void indent(int, char *, FILE *); +static void trstring(char *); + static void -onsig(int signo) +showtree(union node *n) { - gotsig[signo - 1] = 1; - pendingsigs = signo; + trputs("showtree called\n"); + shtree(n, 1, NULL, stdout); +} - if (exsig || (signo == SIGINT && !trap[SIGINT])) { - if (!suppressint) - raise_interrupt(); - intpending = 1; +static void +shtree(union node *n, int ind, char *pfx, FILE *fp) +{ + struct nodelist *lp; + const char *s; + + if (n == NULL) + return; + + indent(ind, pfx, fp); + switch (n->type) { + case NSEMI: + s = "; "; + goto binop; + case NAND: + s = " && "; + goto binop; + case NOR: + s = " || "; + binop: + shtree(n->nbinary.ch1, ind, NULL, fp); + /* if (ind < 0) */ + fputs(s, fp); + shtree(n->nbinary.ch2, ind, NULL, fp); + break; + case NCMD: + shcmd(n, fp); + if (ind >= 0) + putc('\n', fp); + break; + case NPIPE: + for (lp = n->npipe.cmdlist; lp; lp = lp->next) { + shcmd(lp->n, fp); + if (lp->next) + fputs(" | ", fp); + } + if (n->npipe.backgnd) + fputs(" &", fp); + if (ind >= 0) + putc('\n', fp); + break; + default: + fprintf(fp, "", n->type); + if (ind >= 0) + putc('\n', fp); + break; } } +static void +shcmd(union node *cmd, FILE *fp) +{ + union node *np; + int first; + const char *s; + int dftfd; + + first = 1; + for (np = cmd->ncmd.args; np; np = np->narg.next) { + if (! first) + putchar(' '); + sharg(np, fp); + first = 0; + } + for (np = cmd->ncmd.redirect; np; np = np->nfile.next) { + if (! first) + putchar(' '); + switch (np->nfile.type) { + case NTO: s = ">"; dftfd = 1; break; + case NCLOBBER: s = ">|"; dftfd = 1; break; + case NAPPEND: s = ">>"; dftfd = 1; break; + case NTOFD: s = ">&"; dftfd = 1; break; + case NFROM: s = "<"; dftfd = 0; break; + case NFROMFD: s = "<&"; dftfd = 0; break; + case NFROMTO: s = "<>"; dftfd = 0; break; + default: s = "*error*"; dftfd = 0; break; + } + if (np->nfile.fd != dftfd) + fprintf(fp, "%d", np->nfile.fd); + fputs(s, fp); + if (np->nfile.type == NTOFD || np->nfile.type == NFROMFD) { + fprintf(fp, "%d", np->ndup.dupfd); + } else { + sharg(np->nfile.fname, fp); + } + first = 0; + } +} -/* - * Called to execute a trap. Perhaps we should avoid entering new trap - * handlers while we are executing a trap handler. - */ -static int -dotrap(void) +static void +sharg(union node *arg, FILE *fp) { char *p; - char *q; - int i; - int savestatus; - int skip = 0; + struct nodelist *bqlist; + int subtype; - savestatus = exitstatus; - pendingsigs = 0; - xbarrier(); + if (arg->type != NARG) { + out1fmt("\n", arg->type); + abort(); + } + bqlist = arg->narg.backquote; + for (p = arg->narg.text; *p; p++) { + switch (*p) { + case CTLESC: + putc(*++p, fp); + break; + case CTLVAR: + putc('$', fp); + putc('{', fp); + subtype = *++p; + if (subtype == VSLENGTH) + putc('#', fp); - for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { - if (!*q) - continue; - *q = '\0'; + while (*p != '=') + putc(*p++, fp); - p = trap[i + 1]; - if (!p) - continue; - skip = evalstring(p, SKIPEVAL); - exitstatus = savestatus; - if (skip) + if (subtype & VSNUL) + putc(':', fp); + + switch (subtype & VSTYPE) { + case VSNORMAL: + putc('}', fp); + break; + case VSMINUS: + putc('-', fp); + break; + case VSPLUS: + putc('+', fp); + break; + case VSQUESTION: + putc('?', fp); + break; + case VSASSIGN: + putc('=', fp); + break; + case VSTRIMLEFT: + putc('#', fp); + break; + case VSTRIMLEFTMAX: + putc('#', fp); + putc('#', fp); + break; + case VSTRIMRIGHT: + putc('%', fp); + break; + case VSTRIMRIGHTMAX: + putc('%', fp); + putc('%', fp); + break; + case VSLENGTH: + break; + default: + out1fmt("", subtype); + } + break; + case CTLENDVAR: + putc('}', fp); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + putc('$', fp); + putc('(', fp); + shtree(bqlist->n, -1, NULL, fp); + putc(')', fp); + break; + default: + putc(*p, fp); break; + } } +} - return skip; + +static void +indent(int amount, char *pfx, FILE *fp) +{ + int i; + + for (i = 0; i < amount; i++) { + if (pfx && i == amount - 1) + fputs(pfx, fp); + putc('\t', fp); + } } /* - * Controls whether the shell is interactive or not. + * Debugging stuff. */ + + +static FILE *tracefile; + + static void -setinteractive(int on) +trputc(int c) { - static int is_interactive; + if (debug != 1) + return; + putc(c, tracefile); +} - if (++on == is_interactive) +static void +trace(const char *fmt, ...) +{ + va_list va; + + if (debug != 1) return; - is_interactive = on; - setsignal(SIGINT); - setsignal(SIGQUIT); - setsignal(SIGTERM); -#if !ENABLE_FEATURE_SH_EXTRA_QUIET - if (is_interactive > 1) { - /* Looks like they want an interactive shell */ - static int do_banner; + va_start(va, fmt); + (void) vfprintf(tracefile, fmt, va); + va_end(va); +} - if (!do_banner) { - out1fmt( - "\n\n" - "%s Built-in shell (ash)\n" - "Enter 'help' for a list of built-in commands." - "\n\n", - BB_BANNER); - do_banner++; - } - } -#endif +static void +tracev(const char *fmt, va_list va) +{ + if (debug != 1) + return; + (void) vfprintf(tracefile, fmt, va); } -#if !ENABLE_FEATURE_SH_EXTRA_QUIET -/*** List the available builtins ***/ +static void +trputs(const char *s) +{ + if (debug != 1) + return; + fputs(s, tracefile); +} + -static int helpcmd(int argc, char **argv) +static void +trstring(char *s) { - int col, i; + char *p; + char c; - out1fmt("\nBuilt-in commands:\n-------------------\n"); - for (col = 0, i = 0; i < NUMBUILTINS; i++) { - col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), - builtincmd[i].name + 1); - if (col > 60) { - out1fmt("\n"); - col = 0; + if (debug != 1) + return; + putc('"', tracefile); + for (p = s; *p; p++) { + switch (*p) { + case '\n': c = 'n'; goto backslash; + case '\t': c = 't'; goto backslash; + case '\r': c = 'r'; goto backslash; + case '"': c = '"'; goto backslash; + case '\\': c = '\\'; goto backslash; + case CTLESC: c = 'e'; goto backslash; + case CTLVAR: c = 'v'; goto backslash; + case CTLVAR+CTLQUOTE: c = 'V'; goto backslash; + case CTLBACKQ: c = 'q'; goto backslash; + case CTLBACKQ+CTLQUOTE: c = 'Q'; goto backslash; + backslash: + putc('\\', tracefile); + putc(c, tracefile); + break; + default: + if (*p >= ' ' && *p <= '~') + putc(*p, tracefile); + else { + putc('\\', tracefile); + putc(*p >> 6 & 03, tracefile); + putc(*p >> 3 & 07, tracefile); + putc(*p & 07, tracefile); + } + break; } } -#if ENABLE_FEATURE_SH_STANDALONE_SHELL - for (i = 0; i < NUM_APPLETS; i++) { - col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), applets[i].name); - if (col > 60) { - out1fmt("\n"); - col = 0; - } + putc('"', tracefile); +} + + +static void +trargs(char **ap) +{ + if (debug != 1) + return; + while (*ap) { + trstring(*ap++); + if (*ap) + putc(' ', tracefile); + else + putc('\n', tracefile); } -#endif - out1fmt("\n\n"); - return EXIT_SUCCESS; } -#endif /* FEATURE_SH_EXTRA_QUIET */ -/* - * Called to exit the shell. - */ static void -exitshell(void) +opentrace(void) { - struct jmploc loc; - char *p; - int status; + char s[100]; +#ifdef O_APPEND + int flags; +#endif - status = exitstatus; - TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); - if (setjmp(loc.loc)) { - if (exception == EXEXIT) -/* dash bug: it just does _exit(exitstatus) here - * but we have to do setjobctl(0) first! - * (bug is still not fixed in dash-0.5.3 - if you run dash - * under Midnight Commander, on exit from dash MC is backgrounded) */ - status = exitstatus; - goto out; + if (debug != 1) { + if (tracefile) + fflush(tracefile); + /* leave open because libedit might be using it */ + return; } - exception_handler = &loc; - p = trap[0]; - if (p) { - trap[0] = NULL; - evalstring(p, 0); + scopy("./trace", s); + if (tracefile) { + if (!freopen(s, "a", tracefile)) { + fprintf(stderr, "Can't re-open %s\n", s); + debug = 0; + return; + } + } else { + tracefile = fopen(s, "a"); + if (tracefile == NULL) { + fprintf(stderr, "Can't open %s\n", s); + debug = 0; + return; + } } - flush_stdout_stderr(); - out: - setjobctl(0); - _exit(status); - /* NOTREACHED */ +#ifdef O_APPEND + flags = fcntl(fileno(tracefile), F_GETFL, 0); + if (flags >= 0) + fcntl(fileno(tracefile), F_SETFL, flags | O_APPEND); +#endif + setlinebuf(tracefile); + fputs("\nTracing started.\n", tracefile); } +#endif /* DEBUG */ -/* var.c */ - -static struct var *vartab[VTABSIZE]; -static int vpcmp(const void *, const void *); -static struct var **findvar(struct var **, const char *); +/* trap.c */ /* - * Initialize the variable symbol tables and import the environment + * Sigmode records the current value of the signal handlers for the various + * modes. A value of zero means that the current handler is not known. + * S_HARD_IGN indicates that the signal was ignored on entry to the shell, */ +#define S_DFL 1 /* default signal handling (SIG_DFL) */ +#define S_CATCH 2 /* signal is caught */ +#define S_IGN 3 /* signal is ignored (SIG_IGN) */ +#define S_HARD_IGN 4 /* signal is ignored permenantly */ +#define S_RESET 5 /* temporary - to reset a hard ignored sig */ + -#if ENABLE_ASH_GETOPTS /* - * Safe version of setvar, returns 1 on success 0 on failure. + * The trap builtin. */ static int -setvarsafe(const char *name, const char *val, int flags) +trapcmd(int argc, char **argv) { - int err; - volatile int saveint; - struct jmploc *volatile savehandler = exception_handler; - struct jmploc jmploc; + char *action; + char **ap; + int signo; - SAVE_INT(saveint); - if (setjmp(jmploc.loc)) - err = 1; - else { - exception_handler = &jmploc; - setvar(name, val, flags); - err = 0; + nextopt(nullstr); + ap = argptr; + if (!*ap) { + for (signo = 0; signo < NSIG; signo++) { + if (trap[signo] != NULL) { + const char *sn; + + sn = get_signame(signo); + out1fmt("trap -- %s %s\n", + single_quote(trap[signo]), sn); + } + } + return 0; + } + if (!ap[1]) + action = NULL; + else + action = *ap++; + while (*ap) { + signo = get_signum(*ap); + if (signo < 0) + ash_msg_and_raise_error("%s: bad trap", *ap); + INT_OFF; + if (action) { + if (LONE_DASH(action)) + action = NULL; + else + action = ckstrdup(action); + } + if (trap[signo]) + free(trap[signo]); + trap[signo] = action; + if (signo != 0) + setsignal(signo); + INT_ON; + ap++; } - exception_handler = savehandler; - RESTORE_INT(saveint); - return err; + return 0; } -#endif /* - * Set the value of a variable. The flags argument is ored with the - * flags of the variable. If val is NULL, the variable is unset. + * Clear traps on a fork. */ static void -setvar(const char *name, const char *val, int flags) +clear_traps(void) { - char *p, *q; - size_t namelen; - char *nameeq; - size_t vallen; + char **tp; - q = endofname(name); - p = strchrnul(q, '='); - namelen = p - name; - if (!namelen || p != q) - ash_msg_and_raise_error("%.*s: bad variable name", namelen, name); - vallen = 0; - if (val == NULL) { - flags |= VUNSET; - } else { - vallen = strlen(val); - } - INT_OFF; - nameeq = ckmalloc(namelen + vallen + 2); - p = memcpy(nameeq, name, namelen) + namelen; - if (val) { - *p++ = '='; - p = memcpy(p, val, vallen) + vallen; + for (tp = trap; tp < &trap[NSIG]; tp++) { + if (*tp && **tp) { /* trap not NULL or SIG_IGN */ + INT_OFF; + free(*tp); + *tp = NULL; + if (tp != &trap[0]) + setsignal(tp - trap); + INT_ON; + } } - *p = '\0'; - setvareq(nameeq, flags | VNOSAVE); - INT_ON; } /* - * Same as setvar except that the variable and value are passed in - * the first argument as name=value. Since the first argument will - * be actually stored in the table, it should not be a string that - * will go away. - * Called with interrupts off. + * Set the signal handler for the specified signal. The routine figures + * out what it should be set to. */ static void -setvareq(char *s, int flags) +setsignal(int signo) { - struct var *vp, **vpp; - - vpp = hashvar(s); - flags |= (VEXPORT & (((unsigned) (1 - aflag)) - 1)); - vp = *findvar(vpp, s); - if (vp) { - if ((vp->flags & (VREADONLY|VDYNAMIC)) == VREADONLY) { - const char *n; + int action; + char *t, tsig; + struct sigaction act; - if (flags & VNOSAVE) - free(s); - n = vp->text; - ash_msg_and_raise_error("%.*s: is read only", strchrnul(n, '=') - n, n); + t = trap[signo]; + if (t == NULL) + action = S_DFL; + else if (*t != '\0') + action = S_CATCH; + else + action = S_IGN; + if (rootshell && action == S_DFL) { + switch (signo) { + case SIGINT: + if (iflag || minusc || sflag == 0) + action = S_CATCH; + break; + case SIGQUIT: +#if DEBUG + if (debug) + break; +#endif + /* FALLTHROUGH */ + case SIGTERM: + if (iflag) + action = S_IGN; + break; +#if JOBS + case SIGTSTP: + case SIGTTOU: + if (mflag) + action = S_IGN; + break; +#endif } + } - if (flags & VNOSET) + t = &sigmode[signo - 1]; + tsig = *t; + if (tsig == 0) { + /* + * current setting unknown + */ + if (sigaction(signo, 0, &act) == -1) { + /* + * Pretend it worked; maybe we should give a warning + * here, but other shells don't. We don't alter + * sigmode, so that we retry every time. + */ return; + } + if (act.sa_handler == SIG_IGN) { + if (mflag + && (signo == SIGTSTP || signo == SIGTTIN || signo == SIGTTOU) + ) { + tsig = S_IGN; /* don't hard ignore these */ + } else + tsig = S_HARD_IGN; + } else { + tsig = S_RESET; /* force to be set */ + } + } + if (tsig == S_HARD_IGN || tsig == action) + return; + switch (action) { + case S_CATCH: + act.sa_handler = onsig; + break; + case S_IGN: + act.sa_handler = SIG_IGN; + break; + default: + act.sa_handler = SIG_DFL; + } + *t = action; + act.sa_flags = 0; + sigfillset(&act.sa_mask); + sigaction(signo, &act, 0); +} - if (vp->func && (flags & VNOFUNC) == 0) - (*vp->func)(strchrnul(s, '=') + 1); - - if ((vp->flags & (VTEXTFIXED|VSTACK)) == 0) - free((char*)vp->text); - flags |= vp->flags & ~(VTEXTFIXED|VSTACK|VNOSAVE|VUNSET); - } else { - if (flags & VNOSET) - return; - /* not found */ - vp = ckmalloc(sizeof(*vp)); - vp->next = *vpp; - vp->func = NULL; - *vpp = vp; +/* + * Ignore a signal. + */ +static void +ignoresig(int signo) +{ + if (sigmode[signo - 1] != S_IGN && sigmode[signo - 1] != S_HARD_IGN) { + signal(signo, SIG_IGN); } - if (!(flags & (VTEXTFIXED|VSTACK|VNOSAVE))) - s = ckstrdup(s); - vp->text = s; - vp->flags = flags; + sigmode[signo - 1] = S_HARD_IGN; } /* - * Process a linked list of variable assignments. + * Signal handler. */ static void -listsetvar(struct strlist *list_set_var, int flags) +onsig(int signo) { - struct strlist *lp = list_set_var; + gotsig[signo - 1] = 1; + pendingsigs = signo; - if (!lp) - return; - INT_OFF; - do { - setvareq(lp->text, flags); - } while ((lp = lp->next)); - INT_ON; + if (exsig || (signo == SIGINT && !trap[SIGINT])) { + if (!suppressint) + raise_interrupt(); + intpending = 1; + } } /* - * Find the value of a variable. Returns NULL if not set. + * Called to execute a trap. Perhaps we should avoid entering new trap + * handlers while we are executing a trap handler. */ -static char * -lookupvar(const char *name) +static int +dotrap(void) { - struct var *v; + char *p; + char *q; + int i; + int savestatus; + int skip = 0; - v = *findvar(hashvar(name), name); - if (v) { -#ifdef DYNAMIC_VAR - /* - * Dynamic variables are implemented roughly the same way they are - * in bash. Namely, they're "special" so long as they aren't unset. - * As soon as they're unset, they're no longer dynamic, and dynamic - * lookup will no longer happen at that point. -- PFM. - */ - if ((v->flags & VDYNAMIC)) - (*v->func)(NULL); -#endif - if (!(v->flags & VUNSET)) - return strchrnul(v->text, '=') + 1; + savestatus = exitstatus; + pendingsigs = 0; + xbarrier(); + + for (i = 0, q = gotsig; i < NSIG - 1; i++, q++) { + if (!*q) + continue; + *q = '\0'; + + p = trap[i + 1]; + if (!p) + continue; + skip = evalstring(p, SKIPEVAL); + exitstatus = savestatus; + if (skip) + break; } - return NULL; + return skip; } - /* - * Search the environment of a builtin command. + * Controls whether the shell is interactive or not. */ -static char * -bltinlookup(const char *name) +static void +setinteractive(int on) { - struct strlist *sp; + static int is_interactive; + + if (++on == is_interactive) + return; + is_interactive = on; + setsignal(SIGINT); + setsignal(SIGQUIT); + setsignal(SIGTERM); +#if !ENABLE_FEATURE_SH_EXTRA_QUIET + if (is_interactive > 1) { + /* Looks like they want an interactive shell */ + static int do_banner; - for (sp = cmdenviron; sp; sp = sp->next) { - if (varequal(sp->text, name)) - return strchrnul(sp->text, '=') + 1; + if (!do_banner) { + out1fmt( + "\n\n" + "%s Built-in shell (ash)\n" + "Enter 'help' for a list of built-in commands." + "\n\n", + BB_BANNER); + do_banner++; + } } - return lookupvar(name); +#endif } +#if !ENABLE_FEATURE_SH_EXTRA_QUIET +/*** List the available builtins ***/ -/* - * Generate a list of variables satisfying the given conditions. - */ -static char ** -listvars(int on, int off, char ***end) +static int +helpcmd(int argc, char **argv) { - struct var **vpp; - struct var *vp; - char **ep; - int mask; + int col, i; - STARTSTACKSTR(ep); - vpp = vartab; - mask = on | off; - do { - for (vp = *vpp; vp; vp = vp->next) - if ((vp->flags & mask) == on) { - if (ep == stackstrend()) - ep = growstackstr(); - *ep++ = (char *) vp->text; - } - } while (++vpp < vartab + VTABSIZE); - if (ep == stackstrend()) - ep = growstackstr(); - if (end) - *end = ep; - *ep++ = NULL; - return grabstackstr(ep); + out1fmt("\nBuilt-in commands:\n-------------------\n"); + for (col = 0, i = 0; i < NUMBUILTINS; i++) { + col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), + builtincmd[i].name + 1); + if (col > 60) { + out1fmt("\n"); + col = 0; + } + } +#if ENABLE_FEATURE_SH_STANDALONE_SHELL + for (i = 0; i < NUM_APPLETS; i++) { + col += out1fmt("%c%s", ((col == 0) ? '\t' : ' '), applets[i].name); + if (col > 60) { + out1fmt("\n"); + col = 0; + } + } +#endif + out1fmt("\n\n"); + return EXIT_SUCCESS; } - +#endif /* FEATURE_SH_EXTRA_QUIET */ /* - * POSIX requires that 'set' (but not export or readonly) output the - * variables in lexicographic order - by the locale's collating order (sigh). - * Maybe we could keep them in an ordered balanced binary tree - * instead of hashed lists. - * For now just roll 'em through qsort for printing... + * Called to exit the shell. */ -static int -showvars(const char *sep_prefix, int on, int off) +static void +exitshell(void) { - const char *sep; - char **ep, **epend; - - ep = listvars(on, off, &epend); - qsort(ep, epend - ep, sizeof(char *), vpcmp); - - sep = *sep_prefix ? spcstr : sep_prefix; - - for (; ep < epend; ep++) { - const char *p; - const char *q; - - p = strchrnul(*ep, '='); - q = nullstr; - if (*p) - q = single_quote(++p); + struct jmploc loc; + char *p; + int status; - out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); + status = exitstatus; + TRACE(("pid %d, exitshell(%d)\n", getpid(), status)); + if (setjmp(loc.loc)) { + if (exception == EXEXIT) +/* dash bug: it just does _exit(exitstatus) here + * but we have to do setjobctl(0) first! + * (bug is still not fixed in dash-0.5.3 - if you run dash + * under Midnight Commander, on exit from dash MC is backgrounded) */ + status = exitstatus; + goto out; } - - return 0; + exception_handler = &loc; + p = trap[0]; + if (p) { + trap[0] = NULL; + evalstring(p, 0); + } + flush_stdout_stderr(); + out: + setjobctl(0); + _exit(status); + /* NOTREACHED */ } @@ -11781,39 +11814,6 @@ localcmd(int argc, char **argv) /* - * Called after a function returns. - * Interrupts must be off. - */ -static void -poplocalvars(void) -{ - struct localvar *lvp; - struct var *vp; - - while ((lvp = localvars) != NULL) { - localvars = lvp->next; - vp = lvp->vp; - TRACE(("poplocalvar %s", vp ? vp->text : "-")); - if (vp == NULL) { /* $- saved */ - memcpy(optlist, lvp->text, sizeof(optlist)); - free((char*)lvp->text); - optschanged(); - } else if ((lvp->flags & (VUNSET|VSTRFIXED)) == VUNSET) { - unsetvar(vp->text); - } else { - 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); - } -} - - -/* * The unset builtin command. We unset the function before we unset the * variable to allow a function to be unset when there is a readonly variable * with the same name. @@ -11844,104 +11844,6 @@ unsetcmd(int argc, char **argv) } -/* - * Unset the specified variable. - */ -static int -unsetvar(const char *s) -{ - struct var **vpp; - struct var *vp; - int retval; - - vpp = findvar(hashvar(s), s); - vp = *vpp; - retval = 2; - if (vp) { - int flags = vp->flags; - - retval = 1; - if (flags & VREADONLY) - goto out; -#ifdef DYNAMIC_VAR - vp->flags &= ~VDYNAMIC; -#endif - if (flags & VUNSET) - goto ok; - if ((flags & VSTRFIXED) == 0) { - INT_OFF; - if ((flags & (VTEXTFIXED|VSTACK)) == 0) - free((char*)vp->text); - *vpp = vp->next; - free(vp); - INT_ON; - } else { - setvar(s, 0, 0); - vp->flags &= ~VEXPORT; - } - ok: - retval = 0; - } - out: - return retval; -} - - -/* - * Find the appropriate entry in the hash table from the name. - */ -static struct var ** -hashvar(const char *p) -{ - unsigned int hashval; - - hashval = ((unsigned char) *p) << 4; - while (*p && *p != '=') - hashval += (unsigned char) *p++; - return &vartab[hashval % VTABSIZE]; -} - - -/* - * Compares two strings up to the first = or '\0'. The first - * string must be terminated by '='; the second may be terminated by - * either '=' or '\0'. - */ -static int -varcmp(const char *p, const char *q) -{ - int c, d; - - while ((c = *p) == (d = *q)) { - if (!c || c == '=') - goto out; - p++; - q++; - } - if (c == '=') - c = 0; - if (d == '=') - d = 0; - out: - return c - d; -} - -static int -vpcmp(const void *a, const void *b) -{ - return varcmp(*(const char **)a, *(const char **)b); -} - -static struct var ** -findvar(struct var **vpp, const char *name) -{ - for (; *vpp; vpp = &(*vpp)->next) { - if (varequal((*vpp)->text, name)) { - break; - } - } - return vpp; -} /* setmode.c */ #include @@ -12274,7 +12176,7 @@ static int umaskcmd(int argc, char **argv) out1fmt("%.4o\n", mask); } } else { - if (is_digit((unsigned char) *ap)) { + if (isdigit((unsigned char) *ap)) { mask = 0; do { if (*ap >= '8' || *ap < '0') @@ -13012,7 +12914,7 @@ static arith_t arith(const char *expr, int *perrcode) lasttok = TOK_NUM; continue; } - if (is_digit(arithval)) { + if (isdigit(arithval)) { numstackptr->var = NULL; #if ENABLE_ASH_MATH_SUPPORT_64 numstackptr->val = strtoll(expr, (char **) &expr, 0); -- 2.7.4