diff options
Diffstat (limited to 'shell/ash.c')
-rw-r--r-- | shell/ash.c | 3378 |
1 files changed, 1640 insertions, 1738 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,19 +812,1055 @@ 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 */ +#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) -static void setpwd(const char *, int); -/* expand.h */ +/* ============ String helpers */ -struct strlist { - struct strlist *next; - char *text; +/* + * 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; +} + +/* + * 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; +} + +/* + * 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); +} + +/* + * 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); + + len = strspn(s, "'"); + if (!len) + break; + + 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 char **argptr; /* argument list for builtin commands */ +static char *optionarg; /* set by nextopt (like getopt) */ +static char *optptr; /* used by nextopt */ + +/* + * 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; + + 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; +} + + +/* ============ Variables */ + +/* 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 + +#if ENABLE_LOCALE_SUPPORT +static void change_lc_all(const char *value); +static void change_lc_ctype(const char *value); +#endif + +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 + +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 */ +}; + +/* 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) + +/* + * 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 struct var **hashvar(const char *); + +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. */ + +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 */ + +#define VTABSIZE 39 + +static struct var *vartab[VTABSIZE]; + +#if ENABLE_ASH_GETOPTS +static void +getoptsreset(const char *value) +{ + shellparam.optind = number(value); + shellparam.optoff = -1; +} +#endif + +#define is_name(c) ((c) == '_' || isalpha((unsigned char)(c))) +#define is_in_name(c) ((c) == '_' || isalnum((unsigned char)(c))) + +/* + * 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; +} + +/* + * 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 +varequal(const char *a, const char *b) +{ + return !varcmp(a, b); +} + +/* + * Find the appropriate entry in the hash table from the name. + */ +static struct var ** +hashvar(const char *p) +{ + unsigned hashval; + + hashval = ((unsigned char) *p) << 4; + while (*p && *p != '=') + hashval += (unsigned char) *p++; + return &vartab[hashval % VTABSIZE]; +} + +static int +vpcmp(const void *a, const void *b) +{ + return varcmp(*(const char **)a, *(const char **)b); +} + +/* + * 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); +} + +static struct var ** +findvar(struct var **vpp, const char *name) +{ + for (; *vpp; vpp = &(*vpp)->next) { + if (varequal((*vpp)->text, name)) { + break; + } + } + return vpp; +} + +/* + * Find the value of a variable. Returns NULL if not set. + */ +static char * +lookupvar(const char *name) +{ + struct var *v; + + 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; +} + +/* + * Search the environment of a builtin command. + */ +static char * +bltinlookup(const char *name) +{ + struct strlist *sp; + + for (sp = cmdenviron; sp; sp = sp->next) { + if (varequal(sp->text, name)) + return strchrnul(sp->text, '=') + 1; + } + return lookupvar(name); +} + +/* + * 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 +setvarsafe(const char *name, const char *val, int flags) +{ + int err; + volatile int saveint; + struct jmploc *volatile savehandler = exception_handler; + struct jmploc jmploc; + + SAVE_INT(saveint); + if (setjmp(jmploc.loc)) + err = 1; + else { + exception_handler = &jmploc; + setvar(name, val, flags); + err = 0; + } + exception_handler = savehandler; + RESTORE_INT(saveint); + return err; +} +#endif + +/* + * 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; +} + +/* + * 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; @@ -1046,8 +2109,6 @@ static char *parsenextc; /* copy of parsefile->nextc */ 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; @@ -1061,20 +2122,8 @@ static char *endofname(const char *); /* shell.h */ -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) -#endif #if !defined(__GNUC__) || (__GNUC__ == 2 && __GNUC_MINOR__ < 96) #define __builtin_expect(x, expected_value) (x) @@ -1203,10 +2252,6 @@ findkwd(const char *s) #define PEOA_OR_PEOF PEOF #endif -#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))) - /* C99 say: "char" declaration may be signed or unsigned default */ #define SC2INT(chr2may_be_negative_int) (int)((signed char)chr2may_be_negative_int) @@ -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 */ @@ -2220,35 +3038,6 @@ 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; @@ -7913,113 +8407,6 @@ find_dot_file(char *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); - - len = strspn(s, "'"); - if (!len) - break; - - 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(); -} - - -/* - * Like strdup but works with the ash stack. - */ -static char * -sstrdup(const char *p) -{ - size_t len = strlen(p) + 1; - return memcpy(stalloc(len), p, len); -} - - static void calcsize(union node *n) { @@ -8241,9 +8628,6 @@ freefunc(struct funcnode *f) } -static void setoption(int, int); - - static void optschanged(void) { @@ -8255,7 +8639,8 @@ optschanged(void) setvimode(viflag); } -static void minus_o(char *name, int val) +static void +minus_o(char *name, int val) { int i; @@ -8275,6 +8660,22 @@ static void minus_o(char *name, int 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 */ +} + + /* * Process shell options. The global variable argptr contains a pointer * to the argument list; we advance it past the options. @@ -8329,22 +8730,6 @@ options(int cmdline) } -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. */ @@ -8420,6 +8805,37 @@ shiftcmd(int argc, char **argv) /* + * 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 int +showvars(const char *sep_prefix, int on, int off) +{ + 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); + out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); + } + return 0; +} + +/* * The set command builtin. */ static int @@ -8438,15 +8854,6 @@ setcmd(int argc, char **argv) } -#if ENABLE_ASH_GETOPTS -static void -getoptsreset(const char *value) -{ - shellparam.optind = number(value); - shellparam.optoff = -1; -} -#endif - #if ENABLE_LOCALE_SUPPORT static void change_lc_all(const char *value) { @@ -8459,7 +8866,6 @@ static void change_lc_ctype(const char *value) if (value && *value != '\0') setlocale(LC_CTYPE, value); } - #endif #if ENABLE_ASH_RANDOM_SUPPORT @@ -8525,7 +8931,7 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt err |= setvarsafe("OPTARG", s, 0); } else { fprintf(stderr, "Illegal option -%c\n", c); - (void) unsetvar("OPTARG"); + unsetvar("OPTARG"); } c = '?'; goto out; @@ -8543,7 +8949,7 @@ getopts(char *optstr, char *optvar, char **optfirst, int *param_optind, int *opt c = ':'; } else { fprintf(stderr, "No arg for -%c option\n", c); - (void) unsetvar("OPTARG"); + unsetvar("OPTARG"); c = '?'; } goto out; @@ -8586,7 +8992,7 @@ getoptscmd(int argc, char **argv) if (argc < 3) ash_msg_and_raise_error("Usage: getopts optstring var [arg]"); - else if (argc == 3) { + if (argc == 3) { optbase = shellparam.p; if (shellparam.optind > shellparam.nparam + 1) { shellparam.optind = 1; @@ -8605,82 +9011,8 @@ getoptscmd(int argc, char **argv) } #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. - */ -static int -nextopt(const char *optstring) -{ - char *p; - const char *q; - char c; - 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; -} - - -/* parser.c */ - - -/* - * Shell command parser. - */ - -#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); +/* ============ Shell parser */ static void raise_error_syntax(const char *) ATTRIBUTE_NORETURN; static void @@ -8709,29 +9041,24 @@ raise_error_unexpected_syntax(int token) /* NOTREACHED */ } -/* - * 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; +#define EOFMARKLEN 79 - 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); -} +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) @@ -8800,7 +9127,6 @@ list(int nlflag) } } - static union node * andor(void) { @@ -8828,7 +9154,6 @@ andor(void) } } - static union node * pipeline(void) { @@ -8843,7 +9168,7 @@ pipeline(void) checkkwd = CHKKWD | CHKALIAS; } else tokpushback++; - n1 = command(); + n1 = parse_command(); if (readtoken() == TPIPE) { pipenode = stalloc(sizeof(struct npipe)); pipenode->type = NPIPE; @@ -8855,7 +9180,7 @@ pipeline(void) prev = lp; lp = stalloc(sizeof(struct nodelist)); checkkwd = CHKNL | CHKKWD | CHKALIAS; - lp->n = command(); + lp->n = parse_command(); prev->next = lp; } while (readtoken() == TPIPE); lp->next = NULL; @@ -8871,9 +9196,172 @@ pipeline(void) return n1; } +static union node * +makename(void) +{ + union node *n; + + n = stalloc(sizeof(struct narg)); + n->type = NARG; + n->narg.next = NULL; + n->narg.text = wordtext; + n->narg.backquote = backquotelist; + return n; +} + +static void +fixredir(union node *n, const char *text, int err) +{ + TRACE(("Fix redir %s %d\n", text, err)); + if (!err) + n->ndup.vname = NULL; + + if (isdigit(text[0]) && text[1] == '\0') + n->ndup.dupfd = digit_val(text[0]); + else if (LONE_DASH(text)) + n->ndup.dupfd = -1; + else { + if (err) + raise_error_syntax("Bad fd number"); + n->ndup.vname = makename(); + } +} + +/* + * 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) +{ + union node *n = redirnode; + + if (readtoken() != TWORD) + raise_error_unexpected_syntax(-1); + if (n->type == NHERE) { + struct heredoc *here = heredoc; + struct heredoc *p; + int i; + + if (quoteflag == 0) + n->type = NXHERE; + TRACE(("Here document %d\n", n->type)); + if (!noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN) + raise_error_syntax("Illegal eof marker for << redirection"); + rmescapes(wordtext); + here->eofmark = wordtext; + here->next = NULL; + if (heredoclist == NULL) + heredoclist = here; + else { + for (p = heredoclist; p->next; p = p->next); + p->next = here; + } + } else if (n->type == NTOFD || n->type == NFROMFD) { + fixredir(n, wordtext, 0); + } else { + n->nfile.fname = makename(); + } +} + +static union node * +simplecmd(void) +{ + union node *args, **app; + union node *n = NULL; + union node *vars, **vpp; + union node **rpp, *redir; + int savecheckkwd; + + args = NULL; + app = &args; + vars = NULL; + vpp = &vars; + redir = NULL; + rpp = &redir; + + 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; + + /* 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; + } + /* fall through */ + default: + tokpushback++; + goto out; + } + } + 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 * -command(void) +parse_command(void) { union node *n1, *n2; union node *ap, **app; @@ -8918,9 +9406,10 @@ command(void) case TUNTIL: { int got; n1 = stalloc(sizeof(struct nbinary)); - n1->type = (lasttoken == TWHILE)? NWHILE : NUNTIL; + n1->type = (lasttoken == TWHILE) ? NWHILE : NUNTIL; n1->nbinary.ch1 = list(0); - if ((got=readtoken()) != TDO) { + got = readtoken(); + if (got != TDO) { TRACE(("expecting DO got %s %s\n", tokname(got), got == TWORD ? wordtext : "")); raise_error_unexpected_syntax(TDO); @@ -9065,421 +9554,6 @@ command(void) return n1; } - -static union node * -simplecmd(void) -{ - union node *args, **app; - union node *n = NULL; - union node *vars, **vpp; - union node **rpp, *redir; - int savecheckkwd; - - args = NULL; - app = &args; - vars = NULL; - vpp = &vars; - redir = NULL; - rpp = &redir; - - 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; - - /* 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 = command(); - return n; - } - /* fall through */ - default: - tokpushback++; - goto out; - } - } - 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 * -makename(void) -{ - union node *n; - - n = stalloc(sizeof(struct narg)); - n->type = NARG; - n->narg.next = NULL; - n->narg.text = wordtext; - n->narg.backquote = backquotelist; - return n; -} - -static void -fixredir(union node *n, const char *text, int err) -{ - TRACE(("Fix redir %s %d\n", text, err)); - if (!err) - n->ndup.vname = NULL; - - if (is_digit(text[0]) && text[1] == '\0') - n->ndup.dupfd = digit_val(text[0]); - else if (LONE_DASH(text)) - n->ndup.dupfd = -1; - else { - if (err) - raise_error_syntax("Bad fd number"); - n->ndup.vname = makename(); - } -} - - -static void -parsefname(void) -{ - union node *n = redirnode; - - if (readtoken() != TWORD) - raise_error_unexpected_syntax(-1); - if (n->type == NHERE) { - struct heredoc *here = heredoc; - struct heredoc *p; - int i; - - if (quoteflag == 0) - n->type = NXHERE; - TRACE(("Here document %d\n", n->type)); - if (!noexpand(wordtext) || (i = strlen(wordtext)) == 0 || i > EOFMARKLEN) - raise_error_syntax("Illegal eof marker for << redirection"); - rmescapes(wordtext); - here->eofmark = wordtext; - here->next = NULL; - if (heredoclist == NULL) - heredoclist = here; - else { - for (p = heredoclist; p->next; p = p->next); - p->next = here; - } - } else if (n->type == NTOFD || n->type == NFROMFD) { - fixredir(n, wordtext, 0); - } else { - n->nfile.fname = makename(); - } -} - - -/* - * 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) -{ - 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; - } - - /* - * check for keywords - */ - if (checkkwd & CHKKWD) { - const char *const *pp; - - pp = findkwd(wordtext); - if (pp) { - lasttoken = t = pp - tokname_array; - TRACE(("keyword %s recognized\n", tokname(t))); - goto out; - } - } - - 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 - } - 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; -} - - -/* - * 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) -{ - int c; - - if (tokpushback) { - tokpushback = 0; - return lasttoken; - } - if (needprompt) { - setprompt(2); - } - startlinno = plinno; - for (;;) { /* until token or start of word found */ - c = pgetc_macro(); - - 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; - - if (c != PEOF) { - if (c == '\n') { - plinno++; - needprompt = doprompt; - } - - p = strchr(xxreadtoken_chars, c); - if (p == NULL) { - READTOKEN1: - return readtoken1(c, BASESYNTAX, (char *) NULL, 0); - } - - 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 (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; - } - } - breakloop: - return readtoken1(c, BASESYNTAX, (char *)NULL, 0); -#undef RETURN -} -#endif /* NEW_xxreadtoken */ - - /* * If eofmark is NULL, read a word or a redirection symbol. If eofmark * is not NULL, read a here document. In the latter case, eofmark is the @@ -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) { @@ -10102,47 +10174,296 @@ parsearith: { } /* end of readtoken */ - /* - * Returns true if the text contains nothing to expand (no dollar signs - * or backquotes). + * 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 -noexpand(char *text) +xxreadtoken(void) { - char *p; - char c; + int c; - p = text; - while ((c = *p++) != '\0') { - if (c == CTLQUOTEMARK) + if (tokpushback) { + tokpushback = 0; + return lasttoken; + } + if (needprompt) { + setprompt(2); + } + startlinno = plinno; + for (;;) { /* until token or start of word found */ + c = pgetc_macro(); + + 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; + + if (c != PEOF) { + if (c == '\n') { + plinno++; + needprompt = doprompt; + } + + p = strchr(xxreadtoken_chars, c); + if (p == NULL) { + READTOKEN1: + return readtoken1(c, BASESYNTAX, (char *) NULL, 0); + } + + 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 (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; - if (c == CTLESC) - p++; - else if (SIT(c, BASESYNTAX) == CCTL) - return 0; + 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 1; + breakloop: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); +#undef RETURN } +#endif /* NEW_xxreadtoken */ + +static int +readtoken(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; + } + + /* + * check for keywords + */ + if (checkkwd & CHKKWD) { + const char *const *pp; + + pp = findkwd(wordtext); + if (pp) { + lasttoken = t = pp - tokname_array; + TRACE(("keyword %s recognized\n", tokname(t))); + goto out; + } + } + + 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 + } + 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; +} + +static char +peektoken(void) +{ + int t; + + t = readtoken(); + tokpushback++; + return tokname_array[t][0]; +} /* - * Return of a legal variable name (a letter or underscore followed by zero or - * more letters, underscores, and digits). + * Read and parse a command. Returns NEOF on end of file. (NULL is a + * valid parse tree indicating a blank line.) */ -static char * -endofname(const char *name) +static union node * +parsecmd(int interact) { - char *p; + int t; - p = (char *) name; - if (!is_name(*p)) - return p; - while (*++p) { - if (!is_in_name(*p)) - break; + 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 +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; } - return p; } @@ -10171,35 +10492,6 @@ expandstr(const char *ps) } #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. @@ -10719,7 +11011,6 @@ copyfd(int from, int to) return newfd; } - static int redirectsafe(union node *redir, int flags) { @@ -10750,7 +11041,6 @@ static void sharg(union node *, FILE *); static void indent(int, char *, FILE *); static void trstring(char *); - static void showtree(union node *n) { @@ -10758,7 +11048,6 @@ showtree(union node *n) shtree(n, 1, NULL, stdout); } - static void shtree(union node *n, int ind, char *pfx, FILE *fp) { @@ -10808,7 +11097,6 @@ shtree(union node *n, int ind, char *pfx, FILE *fp) } } - static void shcmd(union node *cmd, FILE *fp) { @@ -10849,7 +11137,6 @@ shcmd(union node *cmd, FILE *fp) } } - static void sharg(union node *arg, FILE *fp) { @@ -11328,7 +11615,6 @@ dotrap(void) return skip; } - /* * Controls whether the shell is interactive or not. */ @@ -11361,11 +11647,11 @@ setinteractive(int on) #endif } - #if !ENABLE_FEATURE_SH_EXTRA_QUIET /*** List the available builtins ***/ -static int helpcmd(int argc, char **argv) +static int +helpcmd(int argc, char **argv) { int col, i; @@ -11392,7 +11678,6 @@ static int helpcmd(int argc, char **argv) } #endif /* FEATURE_SH_EXTRA_QUIET */ - /* * Called to exit the shell. */ @@ -11427,258 +11712,6 @@ exitshell(void) /* NOTREACHED */ } -/* var.c */ - -static struct var *vartab[VTABSIZE]; - -static int vpcmp(const void *, const void *); -static struct var **findvar(struct var **, const char *); - -/* - * Initialize the variable symbol tables and import the environment - */ - - -#if ENABLE_ASH_GETOPTS -/* - * Safe version of setvar, returns 1 on success 0 on failure. - */ -static int -setvarsafe(const char *name, const char *val, int flags) -{ - int err; - volatile int saveint; - struct jmploc *volatile savehandler = exception_handler; - struct jmploc jmploc; - - SAVE_INT(saveint); - if (setjmp(jmploc.loc)) - err = 1; - else { - exception_handler = &jmploc; - setvar(name, val, flags); - err = 0; - } - exception_handler = savehandler; - RESTORE_INT(saveint); - return err; -} -#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. - */ -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; -} - - -/* - * 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; -} - - -/* - * 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; -} - - -/* - * Find the value of a variable. Returns NULL if not set. - */ -static char * -lookupvar(const char *name) -{ - struct var *v; - - 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; -} - - -/* - * Search the environment of a builtin command. - */ -static char * -bltinlookup(const char *name) -{ - struct strlist *sp; - - for (sp = cmdenviron; sp; sp = sp->next) { - if (varequal(sp->text, name)) - return strchrnul(sp->text, '=') + 1; - } - return lookupvar(name); -} - - -/* - * 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); -} - - -/* - * 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 int -showvars(const char *sep_prefix, int on, int off) -{ - 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); - - out1fmt("%s%s%.*s%s\n", sep_prefix, sep, (int)(p - *ep), *ep, q); - } - - return 0; -} - /* * The export and readonly commands. @@ -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 <sys/times.h> @@ -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); |