Index: head/bin/sh/expand.c =================================================================== --- head/bin/sh/expand.c (revision 273151) +++ head/bin/sh/expand.c (revision 273152) @@ -1,1677 +1,1675 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * Copyright (c) 1997-2005 * Herbert Xu . All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Routines to expand arguments to commands. We have to deal with * backquotes, shell variables, and file metacharacters. */ #include "shell.h" #include "main.h" #include "nodes.h" #include "eval.h" #include "expand.h" #include "syntax.h" #include "parser.h" #include "jobs.h" #include "options.h" #include "var.h" #include "input.h" #include "output.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "arith.h" #include "show.h" #include "builtins.h" /* * Structure specifying which parts of the string should be searched * for IFS characters. */ struct ifsregion { struct ifsregion *next; /* next region in list */ int begoff; /* offset of start of region */ int endoff; /* offset of end of region */ int inquotes; /* search for nul bytes only */ }; static char *expdest; /* output of current string */ static struct nodelist *argbackq; /* list of back quote expressions */ static struct ifsregion ifsfirst; /* first struct in list of ifs regions */ static struct ifsregion *ifslastp; /* last struct in list */ static struct arglist exparg; /* holds expanded arg list */ static char *argstr(char *, int); static char *exptilde(char *, int); static char *expari(char *); static void expbackq(union node *, int, int); static int subevalvar(char *, char *, int, int, int, int, int); static char *evalvar(char *, int); static int varisset(const char *, int); static void varvalue(const char *, int, int, int); static void recordregion(int, int, int); static void removerecordregions(int); static void ifsbreakup(char *, struct arglist *); static void expandmeta(struct strlist *, int); static void expmeta(char *, char *); static void addfname(char *); static struct strlist *expsort(struct strlist *); static struct strlist *msort(struct strlist *, int); static int patmatch(const char *, const char *, int); static char *cvtnum(int, char *); static int collate_range_cmp(wchar_t, wchar_t); static int collate_range_cmp(wchar_t c1, wchar_t c2) { static wchar_t s1[2], s2[2]; s1[0] = c1; s2[0] = c2; return (wcscoll(s1, s2)); } static char * stputs_quotes(const char *data, const char *syntax, char *p) { while (*data) { CHECKSTRSPACE(2, p); if (syntax[(int)*data] == CCTL) USTPUTC(CTLESC, p); USTPUTC(*data++, p); } return (p); } #define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p) /* * Perform expansions on an argument, placing the resulting list of arguments * in arglist. Parameter expansion, command substitution and arithmetic * expansion are always performed; additional expansions can be requested * via flag (EXP_*). * The result is left in the stack string. * When arglist is NULL, perform here document expansion. * * Caution: this function uses global state and is not reentrant. * However, a new invocation after an interrupted invocation is safe * and will reset the global state for the new call. */ void expandarg(union node *arg, struct arglist *arglist, int flag) { struct strlist *sp; char *p; argbackq = arg->narg.backquote; STARTSTACKSTR(expdest); ifsfirst.next = NULL; ifslastp = NULL; argstr(arg->narg.text, flag); if (arglist == NULL) { STACKSTRNUL(expdest); return; /* here document expanded */ } STPUTC('\0', expdest); p = grabstackstr(expdest); exparg.lastp = &exparg.list; /* * TODO - EXP_REDIR */ if (flag & EXP_FULL) { ifsbreakup(p, &exparg); *exparg.lastp = NULL; exparg.lastp = &exparg.list; expandmeta(exparg.list, flag); } else { if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ rmescapes(p); sp = (struct strlist *)stalloc(sizeof (struct strlist)); sp->text = p; *exparg.lastp = sp; exparg.lastp = &sp->next; } while (ifsfirst.next != NULL) { struct ifsregion *ifsp; INTOFF; ifsp = ifsfirst.next->next; ckfree(ifsfirst.next); ifsfirst.next = ifsp; INTON; } *exparg.lastp = NULL; if (exparg.list) { *arglist->lastp = exparg.list; arglist->lastp = exparg.lastp; } } /* * Perform parameter expansion, command substitution and arithmetic * expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE. * Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'. * This is used to expand word in ${var+word} etc. * If EXP_FULL, EXP_CASE or EXP_REDIR are set, keep and/or generate CTLESC * characters to allow for further processing. * If EXP_FULL is set, also preserve CTLQUOTEMARK characters. */ static char * argstr(char *p, int flag) { char c; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); /* do CTLESC */ int firsteq = 1; int split_lit; int lit_quoted; split_lit = flag & EXP_SPLIT_LIT; lit_quoted = flag & EXP_LIT_QUOTED; flag &= ~(EXP_SPLIT_LIT | EXP_LIT_QUOTED); if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) p = exptilde(p, flag); for (;;) { CHECKSTRSPACE(2, expdest); switch (c = *p++) { case '\0': return (p - 1); case CTLENDVAR: case CTLENDARI: return (p); case CTLQUOTEMARK: lit_quoted = 1; /* "$@" syntax adherence hack */ if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=') break; if ((flag & EXP_FULL) != 0) USTPUTC(c, expdest); break; case CTLQUOTEEND: lit_quoted = 0; break; case CTLESC: if (quotes) USTPUTC(c, expdest); c = *p++; USTPUTC(c, expdest); if (split_lit && !lit_quoted) recordregion(expdest - stackblock() - (quotes ? 2 : 1), expdest - stackblock(), 0); break; case CTLVAR: p = evalvar(p, flag); break; case CTLBACKQ: case CTLBACKQ|CTLQUOTE: expbackq(argbackq->n, c & CTLQUOTE, flag); argbackq = argbackq->next; break; case CTLARI: p = expari(p); break; case ':': case '=': /* * sort of a hack - expand tildes in variable * assignments (after the first '=' and after ':'s). */ USTPUTC(c, expdest); if (split_lit && !lit_quoted) recordregion(expdest - stackblock() - 1, expdest - stackblock(), 0); if (flag & EXP_VARTILDE && *p == '~' && (c != '=' || firsteq)) { if (c == '=') firsteq = 0; p = exptilde(p, flag); } break; default: USTPUTC(c, expdest); if (split_lit && !lit_quoted) recordregion(expdest - stackblock() - 1, expdest - stackblock(), 0); } } } /* * Perform tilde expansion, placing the result in the stack string and * returning the next position in the input string to process. */ static char * exptilde(char *p, int flag) { char c, *startp = p; struct passwd *pw; char *home; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); while ((c = *p) != '\0') { switch(c) { case CTLESC: /* This means CTL* are always considered quoted. */ case CTLVAR: case CTLBACKQ: case CTLBACKQ | CTLQUOTE: case CTLARI: case CTLENDARI: case CTLQUOTEMARK: return (startp); case ':': if (flag & EXP_VARTILDE) goto done; break; case '/': case CTLENDVAR: goto done; } p++; } done: *p = '\0'; if (*(startp+1) == '\0') { home = lookupvar("HOME"); } else { pw = getpwnam(startp+1); home = pw != NULL ? pw->pw_dir : NULL; } *p = c; if (home == NULL || *home == '\0') return (startp); if (quotes) STPUTS_QUOTES(home, SQSYNTAX, expdest); else STPUTS(home, expdest); return (p); } static void removerecordregions(int endoff) { if (ifslastp == NULL) return; if (ifsfirst.endoff > endoff) { while (ifsfirst.next != NULL) { struct ifsregion *ifsp; INTOFF; ifsp = ifsfirst.next->next; ckfree(ifsfirst.next); ifsfirst.next = ifsp; INTON; } if (ifsfirst.begoff > endoff) ifslastp = NULL; else { ifslastp = &ifsfirst; ifsfirst.endoff = endoff; } return; } ifslastp = &ifsfirst; while (ifslastp->next && ifslastp->next->begoff < endoff) ifslastp=ifslastp->next; while (ifslastp->next != NULL) { struct ifsregion *ifsp; INTOFF; ifsp = ifslastp->next->next; ckfree(ifslastp->next); ifslastp->next = ifsp; INTON; } if (ifslastp->endoff > endoff) ifslastp->endoff = endoff; } /* * Expand arithmetic expression. * Note that flag is not required as digits never require CTLESC characters. */ static char * expari(char *p) { char *q, *start; arith_t result; int begoff; int quoted; int adj; quoted = *p++ == '"'; begoff = expdest - stackblock(); p = argstr(p, 0); removerecordregions(begoff); STPUTC('\0', expdest); start = stackblock() + begoff; q = grabstackstr(expdest); result = arith(start); ungrabstackstr(q, expdest); start = stackblock() + begoff; adj = start - expdest; STADJUST(adj, expdest); CHECKSTRSPACE((int)(DIGITS(result) + 1), expdest); fmtstr(expdest, DIGITS(result), ARITH_FORMAT_STR, result); adj = strlen(expdest); STADJUST(adj, expdest); if (!quoted) recordregion(begoff, expdest - stackblock(), 0); return p; } /* * Perform command substitution. */ static void expbackq(union node *cmd, int quoted, int flag) { struct backcmd in; int i; char buf[128]; char *p; char *dest = expdest; struct ifsregion saveifs, *savelastp; struct nodelist *saveargbackq; char lastc; int startloc = dest - stackblock(); char const *syntax = quoted? DQSYNTAX : BASESYNTAX; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); size_t nnl; INTOFF; saveifs = ifsfirst; savelastp = ifslastp; saveargbackq = argbackq; p = grabstackstr(dest); evalbackcmd(cmd, &in); ungrabstackstr(p, dest); ifsfirst = saveifs; ifslastp = savelastp; argbackq = saveargbackq; p = in.buf; lastc = '\0'; nnl = 0; /* Don't copy trailing newlines */ for (;;) { if (--in.nleft < 0) { if (in.fd < 0) break; while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR); TRACE(("expbackq: read returns %d\n", i)); if (i <= 0) break; p = buf; in.nleft = i - 1; } lastc = *p++; if (lastc != '\0') { if (lastc == '\n') { nnl++; } else { CHECKSTRSPACE(nnl + 2, dest); while (nnl > 0) { nnl--; USTPUTC('\n', dest); } if (quotes && syntax[(int)lastc] == CCTL) USTPUTC(CTLESC, dest); USTPUTC(lastc, dest); } } } if (in.fd >= 0) close(in.fd); if (in.buf) ckfree(in.buf); if (in.jp) exitstatus = waitforjob(in.jp, (int *)NULL); if (quoted == 0) recordregion(startloc, dest - stackblock(), 0); TRACE(("expbackq: size=%td: \"%.*s\"\n", ((dest - stackblock()) - startloc), (int)((dest - stackblock()) - startloc), stackblock() + startloc)); expdest = dest; INTON; } static int subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int quotes) { char *startp; char *loc = NULL; char *q; int c = 0; struct nodelist *saveargbackq = argbackq; int amount; argstr(p, (subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX ? EXP_CASE : 0) | EXP_TILDE); STACKSTRNUL(expdest); argbackq = saveargbackq; startp = stackblock() + startloc; if (str == NULL) str = stackblock() + strloc; switch (subtype) { case VSASSIGN: setvar(str, startp, 0); amount = startp - expdest; STADJUST(amount, expdest); varflags &= ~VSNUL; return 1; case VSQUESTION: if (*p != CTLENDVAR) { outfmt(out2, "%s\n", startp); error((char *)NULL); } error("%.*s: parameter %snot set", (int)(p - str - 1), str, (varflags & VSNUL) ? "null or " : nullstr); return 0; case VSTRIMLEFT: for (loc = startp; loc < str; loc++) { c = *loc; *loc = '\0'; if (patmatch(str, startp, quotes)) { *loc = c; goto recordleft; } *loc = c; if (quotes && *loc == CTLESC) loc++; } return 0; case VSTRIMLEFTMAX: for (loc = str - 1; loc >= startp;) { c = *loc; *loc = '\0'; if (patmatch(str, startp, quotes)) { *loc = c; goto recordleft; } *loc = c; loc--; if (quotes && loc > startp && *(loc - 1) == CTLESC) { for (q = startp; q < loc; q++) if (*q == CTLESC) q++; if (q > loc) loc--; } } return 0; case VSTRIMRIGHT: for (loc = str - 1; loc >= startp;) { if (patmatch(str, loc, quotes)) { amount = loc - expdest; STADJUST(amount, expdest); return 1; } loc--; if (quotes && loc > startp && *(loc - 1) == CTLESC) { for (q = startp; q < loc; q++) if (*q == CTLESC) q++; if (q > loc) loc--; } } return 0; case VSTRIMRIGHTMAX: for (loc = startp; loc < str - 1; loc++) { if (patmatch(str, loc, quotes)) { amount = loc - expdest; STADJUST(amount, expdest); return 1; } if (quotes && *loc == CTLESC) loc++; } return 0; default: abort(); } recordleft: amount = ((str - 1) - (loc - startp)) - expdest; STADJUST(amount, expdest); while (loc != str - 1) *startp++ = *loc++; return 1; } /* * Expand a variable, and return a pointer to the next character in the * input string. */ static char * evalvar(char *p, int flag) { int subtype; int varflags; char *var; const char *val; int patloc; int c; int set; int special; int startloc; int varlen; int varlenb; int easy; int quotes = flag & (EXP_FULL | EXP_CASE | EXP_REDIR); varflags = (unsigned char)*p++; subtype = varflags & VSTYPE; var = p; special = 0; if (! is_name(*p)) special = 1; p = strchr(p, '=') + 1; again: /* jump here after setting a variable with ${var=text} */ if (varflags & VSLINENO) { set = 1; special = 1; val = NULL; } else if (special) { set = varisset(var, varflags & VSNUL); val = NULL; } else { val = bltinlookup(var, 1); if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { val = NULL; set = 0; } else set = 1; } varlen = 0; startloc = expdest - stackblock(); if (!set && uflag && *var != '@' && *var != '*') { switch (subtype) { case VSNORMAL: case VSTRIMLEFT: case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: case VSLENGTH: error("%.*s: parameter not set", (int)(p - var - 1), var); } } if (set && subtype != VSPLUS) { /* insert the value of the variable */ if (special) { if (varflags & VSLINENO) STPUTBIN(var, p - var - 1, expdest); else varvalue(var, varflags & VSQUOTE, subtype, flag); if (subtype == VSLENGTH) { varlenb = expdest - stackblock() - startloc; varlen = varlenb; if (localeisutf8) { val = stackblock() + startloc; for (;val != expdest; val++) if ((*val & 0xC0) == 0x80) varlen--; } STADJUST(-varlenb, expdest); } } else { char const *syntax = (varflags & VSQUOTE) ? DQSYNTAX : BASESYNTAX; if (subtype == VSLENGTH) { for (;*val; val++) if (!localeisutf8 || (*val & 0xC0) != 0x80) varlen++; } else { if (quotes) STPUTS_QUOTES(val, syntax, expdest); else STPUTS(val, expdest); } } } if (subtype == VSPLUS) set = ! set; easy = ((varflags & VSQUOTE) == 0 || (*var == '@' && shellparam.nparam != 1)); switch (subtype) { case VSLENGTH: expdest = cvtnum(varlen, expdest); goto record; case VSNORMAL: if (!easy) break; record: recordregion(startloc, expdest - stackblock(), varflags & VSQUOTE || (ifsset() && ifsval()[0] == '\0' && (*var == '@' || *var == '*'))); break; case VSPLUS: case VSMINUS: if (!set) { argstr(p, flag | (flag & EXP_FULL ? EXP_SPLIT_LIT : 0) | (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0)); break; } if (easy) goto record; break; case VSTRIMLEFT: case VSTRIMLEFTMAX: case VSTRIMRIGHT: case VSTRIMRIGHTMAX: if (!set) break; /* * Terminate the string and start recording the pattern * right after it */ STPUTC('\0', expdest); patloc = expdest - stackblock(); if (subevalvar(p, NULL, patloc, subtype, startloc, varflags, quotes) == 0) { int amount = (expdest - stackblock() - patloc) + 1; STADJUST(-amount, expdest); } /* Remove any recorded regions beyond start of variable */ removerecordregions(startloc); goto record; case VSASSIGN: case VSQUESTION: if (!set) { if (subevalvar(p, var, 0, subtype, startloc, varflags, quotes)) { varflags &= ~VSNUL; /* * Remove any recorded regions beyond * start of variable */ removerecordregions(startloc); goto again; } break; } if (easy) goto record; break; case VSERROR: c = p - var - 1; error("${%.*s%s}: Bad substitution", c, var, (c > 0 && *p != CTLENDVAR) ? "..." : ""); default: abort(); } if (subtype != VSNORMAL) { /* skip to end of alternative */ int nesting = 1; for (;;) { if ((c = *p++) == CTLESC) p++; else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { if (set) argbackq = argbackq->next; } else if (c == CTLVAR) { if ((*p++ & VSTYPE) != VSNORMAL) nesting++; } else if (c == CTLENDVAR) { if (--nesting == 0) break; } } } return p; } /* * Test whether a specialized variable is set. */ static int varisset(const char *name, int nulok) { if (*name == '!') return backgndpidset(); else if (*name == '@' || *name == '*') { if (*shellparam.p == NULL) return 0; if (nulok) { char **av; for (av = shellparam.p; *av; av++) if (**av != '\0') return 1; return 0; } } else if (is_digit(*name)) { char *ap; long num; errno = 0; num = strtol(name, NULL, 10); if (errno != 0 || num > shellparam.nparam) return 0; if (num == 0) ap = arg0; else ap = shellparam.p[num - 1]; if (nulok && (ap == NULL || *ap == '\0')) return 0; } return 1; } static void strtodest(const char *p, int flag, int subtype, int quoted) { if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH) STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest); else STPUTS(p, expdest); } /* * Add the value of a specialized variable to the stack string. */ static void varvalue(const char *name, int quoted, int subtype, int flag) { int num; char *p; int i; char sep; char **ap; switch (*name) { case '$': num = rootpid; - goto numvar; + break; case '?': num = oexitstatus; - goto numvar; + break; case '#': num = shellparam.nparam; - goto numvar; + break; case '!': num = backgndpidval(); -numvar: - expdest = cvtnum(num, expdest); break; case '-': for (i = 0 ; i < NOPTS ; i++) { if (optlist[i].val) STPUTC(optlist[i].letter, expdest); } - break; + return; case '@': if (flag & EXP_FULL && quoted) { for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { strtodest(p, flag, subtype, quoted); if (*ap) STPUTC('\0', expdest); } - break; + return; } /* FALLTHROUGH */ case '*': if (ifsset()) sep = ifsval()[0]; else sep = ' '; for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { strtodest(p, flag, subtype, quoted); if (!*ap) break; if (sep || (flag & EXP_FULL && !quoted && **ap != '\0')) STPUTC(sep, expdest); } - break; + return; default: if (is_digit(*name)) { num = atoi(name); if (num == 0) p = arg0; else if (num > 0 && num <= shellparam.nparam) p = shellparam.p[num - 1]; else - break; + return; strtodest(p, flag, subtype, quoted); } - break; + return; } + expdest = cvtnum(num, expdest); } /* * Record the fact that we have to scan this region of the * string for IFS characters. */ static void recordregion(int start, int end, int inquotes) { struct ifsregion *ifsp; INTOFF; if (ifslastp == NULL) { ifsp = &ifsfirst; } else { if (ifslastp->endoff == start && ifslastp->inquotes == inquotes) { /* extend previous area */ ifslastp->endoff = end; INTON; return; } ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); ifslastp->next = ifsp; } ifslastp = ifsp; ifslastp->next = NULL; ifslastp->begoff = start; ifslastp->endoff = end; ifslastp->inquotes = inquotes; INTON; } /* * Break the argument string into pieces based upon IFS and add the * strings to the argument list. The regions of the string to be * searched for IFS characters have been stored by recordregion. * CTLESC characters are preserved but have little effect in this pass * other than escaping CTL* characters. In particular, they do not escape * IFS characters: that should be done with the ifsregion mechanism. * CTLQUOTEMARK characters are used to preserve empty quoted strings. * This pass treats them as a regular character, making the string non-empty. * Later, they are removed along with the other CTL* characters. */ static void ifsbreakup(char *string, struct arglist *arglist) { struct ifsregion *ifsp; struct strlist *sp; char *start; char *p; char *q; const char *ifs; const char *ifsspc; int had_param_ch = 0; start = string; if (ifslastp == NULL) { /* Return entire argument, IFS doesn't apply to any of it */ sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; return; } ifs = ifsset() ? ifsval() : " \t\n"; for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) { p = string + ifsp->begoff; while (p < string + ifsp->endoff) { q = p; if (*p == CTLESC) p++; if (ifsp->inquotes) { /* Only NULs (should be from "$@") end args */ had_param_ch = 1; if (*p != 0) { p++; continue; } ifsspc = NULL; } else { if (!strchr(ifs, *p)) { had_param_ch = 1; p++; continue; } ifsspc = strchr(" \t\n", *p); /* Ignore IFS whitespace at start */ if (q == start && ifsspc != NULL) { p++; start = p; continue; } had_param_ch = 0; } /* Save this argument... */ *q = '\0'; sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; p++; if (ifsspc != NULL) { /* Ignore further trailing IFS whitespace */ for (; p < string + ifsp->endoff; p++) { q = p; if (*p == CTLESC) p++; if (strchr(ifs, *p) == NULL) { p = q; break; } if (strchr(" \t\n", *p) == NULL) { p++; break; } } } start = p; } } /* * Save anything left as an argument. * Traditionally we have treated 'IFS=':'; set -- x$IFS' as * generating 2 arguments, the second of which is empty. * Some recent clarification of the Posix spec say that it * should only generate one.... */ if (had_param_ch || *start != 0) { sp = (struct strlist *)stalloc(sizeof *sp); sp->text = start; *arglist->lastp = sp; arglist->lastp = &sp->next; } } static char expdir[PATH_MAX]; #define expdir_end (expdir + sizeof(expdir)) /* * Perform pathname generation and remove control characters. * At this point, the only control characters should be CTLESC and CTLQUOTEMARK. * The results are stored in the list exparg. */ static void expandmeta(struct strlist *str, int flag __unused) { char *p; struct strlist **savelastp; struct strlist *sp; char c; /* TODO - EXP_REDIR */ while (str) { - if (fflag) - goto nometa; - p = str->text; - for (;;) { /* fast check for meta chars */ - if ((c = *p++) == '\0') - goto nometa; - if (c == '*' || c == '?' || c == '[') - break; - } savelastp = exparg.lastp; - INTOFF; - expmeta(expdir, str->text); - INTON; + if (!fflag) { + p = str->text; + for (; (c = *p) != '\0'; p++) { + /* fast check for meta chars */ + if (c == '*' || c == '?' || c == '[') { + INTOFF; + expmeta(expdir, str->text); + INTON; + break; + } + } + } if (exparg.lastp == savelastp) { /* * no matches */ -nometa: *exparg.lastp = str; rmescapes(str->text); exparg.lastp = &str->next; } else { *exparg.lastp = NULL; *savelastp = sp = expsort(*savelastp); while (sp->next != NULL) sp = sp->next; exparg.lastp = &sp->next; } str = str->next; } } /* * Do metacharacter (i.e. *, ?, [...]) expansion. */ static void expmeta(char *enddir, char *name) { const char *p; const char *q; const char *start; char *endname; int metaflag; struct stat statb; DIR *dirp; struct dirent *dp; int atend; int matchdot; int esc; int namlen; metaflag = 0; start = name; for (p = name; esc = 0, *p; p += esc + 1) { if (*p == '*' || *p == '?') metaflag = 1; else if (*p == '[') { q = p + 1; if (*q == '!' || *q == '^') q++; for (;;) { while (*q == CTLQUOTEMARK) q++; if (*q == CTLESC) q++; if (*q == '/' || *q == '\0') break; if (*++q == ']') { metaflag = 1; break; } } } else if (*p == '\0') break; else if (*p == CTLQUOTEMARK) continue; else { if (*p == CTLESC) esc++; if (p[esc] == '/') { if (metaflag) break; start = p + esc + 1; } } } if (metaflag == 0) { /* we've reached the end of the file name */ if (enddir != expdir) metaflag++; for (p = name ; ; p++) { if (*p == CTLQUOTEMARK) continue; if (*p == CTLESC) p++; *enddir++ = *p; if (*p == '\0') break; if (enddir == expdir_end) return; } if (metaflag == 0 || lstat(expdir, &statb) >= 0) addfname(expdir); return; } endname = name + (p - name); if (start != name) { p = name; while (p < start) { while (*p == CTLQUOTEMARK) p++; if (*p == CTLESC) p++; *enddir++ = *p++; if (enddir == expdir_end) return; } } if (enddir == expdir) { p = "."; } else if (enddir == expdir + 1 && *expdir == '/') { p = "/"; } else { p = expdir; enddir[-1] = '\0'; } if ((dirp = opendir(p)) == NULL) return; if (enddir != expdir) enddir[-1] = '/'; if (*endname == 0) { atend = 1; } else { atend = 0; *endname = '\0'; endname += esc + 1; } matchdot = 0; p = start; while (*p == CTLQUOTEMARK) p++; if (*p == CTLESC) p++; if (*p == '.') matchdot++; while (! int_pending() && (dp = readdir(dirp)) != NULL) { if (dp->d_name[0] == '.' && ! matchdot) continue; if (patmatch(start, dp->d_name, 0)) { namlen = dp->d_namlen; if (enddir + namlen + 1 > expdir_end) continue; memcpy(enddir, dp->d_name, namlen + 1); if (atend) addfname(expdir); else { if (dp->d_type != DT_UNKNOWN && dp->d_type != DT_DIR && dp->d_type != DT_LNK) continue; if (enddir + namlen + 2 > expdir_end) continue; enddir[namlen] = '/'; enddir[namlen + 1] = '\0'; expmeta(enddir + namlen + 1, endname); } } } closedir(dirp); if (! atend) endname[-esc - 1] = esc ? CTLESC : '/'; } /* * Add a file name to the list. */ static void addfname(char *name) { char *p; struct strlist *sp; size_t len; len = strlen(name); p = stalloc(len + 1); memcpy(p, name, len + 1); sp = (struct strlist *)stalloc(sizeof *sp); sp->text = p; *exparg.lastp = sp; exparg.lastp = &sp->next; } /* * Sort the results of file name expansion. It calculates the number of * strings to sort and then calls msort (short for merge sort) to do the * work. */ static struct strlist * expsort(struct strlist *str) { int len; struct strlist *sp; len = 0; for (sp = str ; sp ; sp = sp->next) len++; return msort(str, len); } static struct strlist * msort(struct strlist *list, int len) { struct strlist *p, *q = NULL; struct strlist **lpp; int half; int n; if (len <= 1) return list; half = len >> 1; p = list; for (n = half ; --n >= 0 ; ) { q = p; p = p->next; } q->next = NULL; /* terminate first half of list */ q = msort(list, half); /* sort first half of list */ p = msort(p, len - half); /* sort second half */ lpp = &list; for (;;) { if (strcmp(p->text, q->text) < 0) { *lpp = p; lpp = &p->next; if ((p = *lpp) == NULL) { *lpp = q; break; } } else { *lpp = q; lpp = &q->next; if ((q = *lpp) == NULL) { *lpp = p; break; } } } return list; } static wchar_t get_wc(const char **p) { wchar_t c; int chrlen; chrlen = mbtowc(&c, *p, 4); if (chrlen == 0) return 0; else if (chrlen == -1) c = 0; else *p += chrlen; return c; } /* * See if a character matches a character class, starting at the first colon * of "[:class:]". * If a valid character class is recognized, a pointer to the next character * after the final closing bracket is stored into *end, otherwise a null * pointer is stored into *end. */ static int match_charclass(const char *p, wchar_t chr, const char **end) { char name[20]; const char *nameend; wctype_t cclass; *end = NULL; p++; nameend = strstr(p, ":]"); if (nameend == NULL || (size_t)(nameend - p) >= sizeof(name) || nameend == p) return 0; memcpy(name, p, nameend - p); name[nameend - p] = '\0'; *end = nameend + 2; cclass = wctype(name); /* An unknown class matches nothing but is valid nevertheless. */ if (cclass == 0) return 0; return iswctype(chr, cclass); } /* * Returns true if the pattern matches the string. */ static int patmatch(const char *pattern, const char *string, int squoted) { const char *p, *q, *end; const char *bt_p, *bt_q; char c; wchar_t wc, wc2; p = pattern; q = string; bt_p = NULL; bt_q = NULL; for (;;) { switch (c = *p++) { case '\0': if (*q != '\0') goto backtrack; return 1; case CTLESC: if (squoted && *q == CTLESC) q++; if (*q++ != *p++) goto backtrack; break; case CTLQUOTEMARK: continue; case '?': if (squoted && *q == CTLESC) q++; if (*q == '\0') return 0; if (localeisutf8) { wc = get_wc(&q); /* * A '?' does not match invalid UTF-8 but a * '*' does, so backtrack. */ if (wc == 0) goto backtrack; } else wc = (unsigned char)*q++; break; case '*': c = *p; while (c == CTLQUOTEMARK || c == '*') c = *++p; /* * If the pattern ends here, we know the string * matches without needing to look at the rest of it. */ if (c == '\0') return 1; /* * First try the shortest match for the '*' that * could work. We can forget any earlier '*' since * there is no way having it match more characters * can help us, given that we are already here. */ bt_p = p; bt_q = q; break; case '[': { const char *endp; int invert, found; wchar_t chr; endp = p; if (*endp == '!' || *endp == '^') endp++; for (;;) { while (*endp == CTLQUOTEMARK) endp++; if (*endp == 0) goto dft; /* no matching ] */ if (*endp == CTLESC) endp++; if (*++endp == ']') break; } invert = 0; if (*p == '!' || *p == '^') { invert++; p++; } found = 0; if (squoted && *q == CTLESC) q++; if (*q == '\0') return 0; if (localeisutf8) { chr = get_wc(&q); if (chr == 0) goto backtrack; } else chr = (unsigned char)*q++; c = *p++; do { if (c == CTLQUOTEMARK) continue; if (c == '[' && *p == ':') { found |= match_charclass(p, chr, &end); if (end != NULL) p = end; } if (c == CTLESC) c = *p++; if (localeisutf8 && c & 0x80) { p--; wc = get_wc(&p); if (wc == 0) /* bad utf-8 */ return 0; } else wc = (unsigned char)c; if (*p == '-' && p[1] != ']') { p++; while (*p == CTLQUOTEMARK) p++; if (*p == CTLESC) p++; if (localeisutf8) { wc2 = get_wc(&p); if (wc2 == 0) /* bad utf-8 */ return 0; } else wc2 = (unsigned char)*p++; if ( collate_range_cmp(chr, wc) >= 0 && collate_range_cmp(chr, wc2) <= 0 ) found = 1; } else { if (chr == wc) found = 1; } } while ((c = *p++) != ']'); if (found == invert) goto backtrack; break; } dft: default: if (squoted && *q == CTLESC) q++; if (*q == '\0') return 0; if (*q++ == c) break; backtrack: /* * If we have a mismatch (other than hitting the end * of the string), go back to the last '*' seen and * have it match one additional character. */ if (bt_p == NULL) return 0; if (squoted && *bt_q == CTLESC) bt_q++; if (*bt_q == '\0') return 0; bt_q++; p = bt_p; q = bt_q; break; } } } /* * Remove any CTLESC and CTLQUOTEMARK characters from a string. */ void rmescapes(char *str) { char *p, *q; p = str; while (*p != CTLESC && *p != CTLQUOTEMARK && *p != CTLQUOTEEND) { if (*p++ == '\0') return; } q = p; while (*p) { if (*p == CTLQUOTEMARK || *p == CTLQUOTEEND) { p++; continue; } if (*p == CTLESC) p++; *q++ = *p++; } *q = '\0'; } /* * See if a pattern matches in a case statement. */ int casematch(union node *pattern, const char *val) { struct stackmark smark; int result; char *p; setstackmark(&smark); argbackq = pattern->narg.backquote; STARTSTACKSTR(expdest); ifslastp = NULL; argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); STPUTC('\0', expdest); p = grabstackstr(expdest); result = patmatch(p, val, 0); popstackmark(&smark); return result; } /* * Our own itoa(). */ static char * cvtnum(int num, char *buf) { char temp[32]; int neg = num < 0; char *p = temp + 31; temp[31] = '\0'; do { *--p = num % 10 + '0'; } while ((num /= 10) != 0); if (neg) *--p = '-'; STPUTS(p, buf); return buf; } /* * Do most of the work for wordexp(3). */ int wordexpcmd(int argc, char **argv) { size_t len; int i; out1fmt("%08x", argc - 1); for (i = 1, len = 0; i < argc; i++) len += strlen(argv[i]); out1fmt("%08x", (int)len); for (i = 1; i < argc; i++) outbin(argv[i], strlen(argv[i]) + 1, out1); return (0); } Index: head/bin/sh/jobs.c =================================================================== --- head/bin/sh/jobs.c (revision 273151) +++ head/bin/sh/jobs.c (revision 273152) @@ -1,1497 +1,1515 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)jobs.c 8.5 (Berkeley) 5/4/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include #include #include #include #include #include #include #include #include #include "shell.h" #if JOBS #include #undef CEOF /* syntax.h redefines this */ #endif #include "redir.h" #include "exec.h" #include "show.h" #include "main.h" #include "parser.h" #include "nodes.h" #include "jobs.h" #include "options.h" #include "trap.h" #include "syntax.h" #include "input.h" #include "output.h" #include "memalloc.h" #include "error.h" #include "mystring.h" #include "var.h" #include "builtins.h" static struct job *jobtab; /* array of jobs */ static int njobs; /* size of array */ static pid_t backgndpid = -1; /* pid of last background process */ static struct job *bgjob = NULL; /* last background process */ #if JOBS static struct job *jobmru; /* most recently used job list */ static pid_t initialpgrp; /* pgrp of shell on invocation */ #endif static int ttyfd = -1; /* mode flags for dowait */ #define DOWAIT_BLOCK 0x1 /* wait until a child exits */ #define DOWAIT_SIG 0x2 /* if DOWAIT_BLOCK, abort on SIGINT/SIGQUIT */ #define DOWAIT_SIG_ANY 0x4 /* if DOWAIT_SIG, abort on any signal */ #if JOBS static void restartjob(struct job *); #endif static void freejob(struct job *); static int waitcmdloop(struct job *); static struct job *getjob_nonotfound(const char *); static struct job *getjob(const char *); pid_t killjob(const char *, int); static pid_t dowait(int, struct job *); static void checkzombies(void); static void cmdtxt(union node *); static void cmdputs(const char *); #if JOBS static void setcurjob(struct job *); static void deljob(struct job *); static struct job *getcurjob(struct job *); #endif static void printjobcmd(struct job *); static void showjob(struct job *, int); /* * Turn job control on and off. */ static int jobctl; #if JOBS static void jobctl_notty(void) { if (ttyfd >= 0) { close(ttyfd); ttyfd = -1; } if (!iflag) { setsignal(SIGTSTP); setsignal(SIGTTOU); setsignal(SIGTTIN); jobctl = 1; return; } out2fmt_flush("sh: can't access tty; job control turned off\n"); mflag = 0; } void setjobctl(int on) { int i; if (on == jobctl || rootshell == 0) return; if (on) { if (ttyfd != -1) close(ttyfd); if ((ttyfd = open(_PATH_TTY, O_RDWR | O_CLOEXEC)) < 0) { i = 0; while (i <= 2 && !isatty(i)) i++; if (i > 2 || (ttyfd = fcntl(i, F_DUPFD_CLOEXEC, 10)) < 0) { jobctl_notty(); return; } } if (ttyfd < 10) { /* * Keep our TTY file descriptor out of the way of * the user's redirections. */ if ((i = fcntl(ttyfd, F_DUPFD_CLOEXEC, 10)) < 0) { jobctl_notty(); return; } close(ttyfd); ttyfd = i; } do { /* while we are in the background */ initialpgrp = tcgetpgrp(ttyfd); if (initialpgrp < 0) { jobctl_notty(); return; } if (initialpgrp != getpgrp()) { if (!iflag) { initialpgrp = -1; jobctl_notty(); return; } kill(0, SIGTTIN); continue; } } while (0); setsignal(SIGTSTP); setsignal(SIGTTOU); setsignal(SIGTTIN); setpgid(0, rootpid); tcsetpgrp(ttyfd, rootpid); } else { /* turning job control off */ setpgid(0, initialpgrp); if (ttyfd >= 0) { tcsetpgrp(ttyfd, initialpgrp); close(ttyfd); ttyfd = -1; } setsignal(SIGTSTP); setsignal(SIGTTOU); setsignal(SIGTTIN); } jobctl = on; } #endif #if JOBS int fgcmd(int argc __unused, char **argv __unused) { struct job *jp; pid_t pgrp; int status; nextopt(""); jp = getjob(*argptr); if (jp->jobctl == 0) error("job not created under job control"); printjobcmd(jp); flushout(&output); pgrp = jp->ps[0].pid; if (ttyfd >= 0) tcsetpgrp(ttyfd, pgrp); restartjob(jp); jp->foreground = 1; INTOFF; status = waitforjob(jp, (int *)NULL); INTON; return status; } int bgcmd(int argc, char **argv) { struct job *jp; nextopt(""); do { jp = getjob(*argptr); if (jp->jobctl == 0) error("job not created under job control"); if (jp->state == JOBDONE) continue; restartjob(jp); jp->foreground = 0; out1fmt("[%td] ", jp - jobtab + 1); printjobcmd(jp); } while (*argptr != NULL && *++argptr != NULL); return 0; } static void restartjob(struct job *jp) { struct procstat *ps; int i; if (jp->state == JOBDONE) return; setcurjob(jp); INTOFF; kill(-jp->ps[0].pid, SIGCONT); for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { if (WIFSTOPPED(ps->status)) { ps->status = -1; jp->state = 0; } } INTON; } #endif int jobscmd(int argc __unused, char *argv[] __unused) { char *id; int ch, mode; mode = SHOWJOBS_DEFAULT; while ((ch = nextopt("lps")) != '\0') { switch (ch) { case 'l': mode = SHOWJOBS_VERBOSE; break; case 'p': mode = SHOWJOBS_PGIDS; break; case 's': mode = SHOWJOBS_PIDS; break; } } if (*argptr == NULL) showjobs(0, mode); else while ((id = *argptr++) != NULL) showjob(getjob(id), mode); return (0); } static void printjobcmd(struct job *jp) { struct procstat *ps; int i; for (ps = jp->ps, i = jp->nprocs ; --i >= 0 ; ps++) { out1str(ps->cmd); if (i > 0) out1str(" | "); } out1c('\n'); } static void showjob(struct job *jp, int mode) { char s[64]; char statestr[64]; const char *sigstr; struct procstat *ps; struct job *j; int col, curr, i, jobno, prev, procno; char c; procno = (mode == SHOWJOBS_PGIDS) ? 1 : jp->nprocs; jobno = jp - jobtab + 1; curr = prev = 0; #if JOBS if ((j = getcurjob(NULL)) != NULL) { curr = j - jobtab + 1; if ((j = getcurjob(j)) != NULL) prev = j - jobtab + 1; } #endif ps = jp->ps + jp->nprocs - 1; if (jp->state == 0) { strcpy(statestr, "Running"); #if JOBS } else if (jp->state == JOBSTOPPED) { while (!WIFSTOPPED(ps->status) && ps > jp->ps) ps--; if (WIFSTOPPED(ps->status)) i = WSTOPSIG(ps->status); else i = -1; sigstr = strsignal(i); if (sigstr != NULL) strcpy(statestr, sigstr); else strcpy(statestr, "Suspended"); #endif } else if (WIFEXITED(ps->status)) { if (WEXITSTATUS(ps->status) == 0) strcpy(statestr, "Done"); else fmtstr(statestr, 64, "Done(%d)", WEXITSTATUS(ps->status)); } else { i = WTERMSIG(ps->status); sigstr = strsignal(i); if (sigstr != NULL) strcpy(statestr, sigstr); else strcpy(statestr, "Unknown signal"); if (WCOREDUMP(ps->status)) strcat(statestr, " (core dumped)"); } for (ps = jp->ps ; procno > 0 ; ps++, procno--) { /* for each process */ if (mode == SHOWJOBS_PIDS || mode == SHOWJOBS_PGIDS) { out1fmt("%d\n", (int)ps->pid); continue; } if (mode != SHOWJOBS_VERBOSE && ps != jp->ps) continue; if (jobno == curr && ps == jp->ps) c = '+'; else if (jobno == prev && ps == jp->ps) c = '-'; else c = ' '; if (ps == jp->ps) fmtstr(s, 64, "[%d] %c ", jobno, c); else fmtstr(s, 64, " %c ", c); out1str(s); col = strlen(s); if (mode == SHOWJOBS_VERBOSE) { fmtstr(s, 64, "%d ", (int)ps->pid); out1str(s); col += strlen(s); } if (ps == jp->ps) { out1str(statestr); col += strlen(statestr); } do { out1c(' '); col++; } while (col < 30); if (mode == SHOWJOBS_VERBOSE) { out1str(ps->cmd); out1c('\n'); } else printjobcmd(jp); } } /* * Print a list of jobs. If "change" is nonzero, only print jobs whose * statuses have changed since the last call to showjobs. * * If the shell is interrupted in the process of creating a job, the * result may be a job structure containing zero processes. Such structures * will be freed here. */ void showjobs(int change, int mode) { int jobno; struct job *jp; TRACE(("showjobs(%d) called\n", change)); checkzombies(); for (jobno = 1, jp = jobtab ; jobno <= njobs ; jobno++, jp++) { if (! jp->used) continue; if (jp->nprocs == 0) { freejob(jp); continue; } if (change && ! jp->changed) continue; showjob(jp, mode); if (mode == SHOWJOBS_DEFAULT || mode == SHOWJOBS_VERBOSE) { jp->changed = 0; /* Hack: discard jobs for which $! has not been * referenced in interactive mode when they terminate. */ if (jp->state == JOBDONE && !jp->remembered && (iflag || jp != bgjob)) { freejob(jp); } } } } /* * Mark a job structure as unused. */ static void freejob(struct job *jp) { struct procstat *ps; int i; INTOFF; if (bgjob == jp) bgjob = NULL; for (i = jp->nprocs, ps = jp->ps ; --i >= 0 ; ps++) { if (ps->cmd != nullstr) ckfree(ps->cmd); } if (jp->ps != &jp->ps0) ckfree(jp->ps); jp->used = 0; #if JOBS deljob(jp); #endif INTON; } int waitcmd(int argc __unused, char **argv __unused) { struct job *job; int retval; nextopt(""); if (*argptr == NULL) return (waitcmdloop(NULL)); do { job = getjob_nonotfound(*argptr); if (job == NULL) retval = 127; else retval = waitcmdloop(job); argptr++; } while (*argptr != NULL); return (retval); } static int waitcmdloop(struct job *job) { int status, retval, sig; struct job *jp; /* * Loop until a process is terminated or stopped, or a SIGINT is * received. */ do { if (job != NULL) { if (job->state == JOBDONE) { status = job->ps[job->nprocs - 1].status; if (WIFEXITED(status)) retval = WEXITSTATUS(status); else retval = WTERMSIG(status) + 128; if (! iflag || ! job->changed) freejob(job); else { job->remembered = 0; if (job == bgjob) bgjob = NULL; } return retval; } } else { for (jp = jobtab ; jp < jobtab + njobs; jp++) if (jp->used && jp->state == JOBDONE) { if (! iflag || ! jp->changed) freejob(jp); else { jp->remembered = 0; if (jp == bgjob) bgjob = NULL; } } for (jp = jobtab ; ; jp++) { if (jp >= jobtab + njobs) { /* no running procs */ return 0; } if (jp->used && jp->state == 0) break; } } } while (dowait(DOWAIT_BLOCK | DOWAIT_SIG, (struct job *)NULL) != -1); sig = pendingsig_waitcmd; pendingsig_waitcmd = 0; return sig + 128; } int jobidcmd(int argc __unused, char **argv __unused) { struct job *jp; int i; nextopt(""); jp = getjob(*argptr); for (i = 0 ; i < jp->nprocs ; ) { out1fmt("%d", (int)jp->ps[i].pid); out1c(++i < jp->nprocs? ' ' : '\n'); } return 0; } /* * Convert a job name to a job structure. */ static struct job * getjob_nonotfound(const char *name) { int jobno; struct job *found, *jp; size_t namelen; pid_t pid; int i; if (name == NULL) { #if JOBS -currentjob: if ((jp = getcurjob(NULL)) == NULL) - error("No current job"); - return (jp); + name = "%+"; #else error("No current job"); #endif - } else if (name[0] == '%') { + } + if (name[0] == '%') { if (is_digit(name[1])) { jobno = number(name + 1); if (jobno > 0 && jobno <= njobs && jobtab[jobno - 1].used != 0) return &jobtab[jobno - 1]; #if JOBS - } else if (name[1] == '%' && name[2] == '\0') { - goto currentjob; - } else if (name[1] == '+' && name[2] == '\0') { - goto currentjob; + } else if ((name[1] == '%' || name[1] == '+') && + name[2] == '\0') { + if ((jp = getcurjob(NULL)) == NULL) + error("No current job"); + return (jp); } else if (name[1] == '-' && name[2] == '\0') { if ((jp = getcurjob(NULL)) == NULL || (jp = getcurjob(jp)) == NULL) error("No previous job"); return (jp); #endif } else if (name[1] == '?') { found = NULL; for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && strstr(jp->ps[0].cmd, name + 2) != NULL) { if (found) error("%s: ambiguous", name); found = jp; } } if (found != NULL) return (found); } else { namelen = strlen(name); found = NULL; for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && strncmp(jp->ps[0].cmd, name + 1, namelen - 1) == 0) { if (found) error("%s: ambiguous", name); found = jp; } } if (found) return found; } } else if (is_number(name)) { pid = (pid_t)number(name); for (jp = jobtab, i = njobs ; --i >= 0 ; jp++) { if (jp->used && jp->nprocs > 0 && jp->ps[jp->nprocs - 1].pid == pid) return jp; } } return NULL; } static struct job * getjob(const char *name) { struct job *jp; jp = getjob_nonotfound(name); if (jp == NULL) error("No such job: %s", name); return (jp); } int killjob(const char *name, int sig) { struct job *jp; int i, ret; jp = getjob(name); if (jp->state == JOBDONE) return 0; if (jp->jobctl) return kill(-jp->ps[0].pid, sig); ret = -1; errno = ESRCH; for (i = 0; i < jp->nprocs; i++) if (jp->ps[i].status == -1 || WIFSTOPPED(jp->ps[i].status)) { if (kill(jp->ps[i].pid, sig) == 0) ret = 0; } else ret = 0; return ret; } /* * Return a new job structure, */ struct job * makejob(union node *node __unused, int nprocs) { int i; struct job *jp; for (i = njobs, jp = jobtab ; ; jp++) { if (--i < 0) { INTOFF; if (njobs == 0) { jobtab = ckmalloc(4 * sizeof jobtab[0]); #if JOBS jobmru = NULL; #endif } else { jp = ckmalloc((njobs + 4) * sizeof jobtab[0]); memcpy(jp, jobtab, njobs * sizeof jp[0]); #if JOBS /* Relocate `next' pointers and list head */ if (jobmru != NULL) jobmru = &jp[jobmru - jobtab]; for (i = 0; i < njobs; i++) if (jp[i].next != NULL) jp[i].next = &jp[jp[i].next - jobtab]; #endif if (bgjob != NULL) bgjob = &jp[bgjob - jobtab]; /* Relocate `ps' pointers */ for (i = 0; i < njobs; i++) if (jp[i].ps == &jobtab[i].ps0) jp[i].ps = &jp[i].ps0; ckfree(jobtab); jobtab = jp; } jp = jobtab + njobs; for (i = 4 ; --i >= 0 ; jobtab[njobs++].used = 0) ; INTON; break; } if (jp->used == 0) break; } INTOFF; jp->state = 0; jp->used = 1; jp->changed = 0; jp->nprocs = 0; jp->foreground = 0; jp->remembered = 0; #if JOBS jp->jobctl = jobctl; jp->next = NULL; #endif if (nprocs > 1) { jp->ps = ckmalloc(nprocs * sizeof (struct procstat)); } else { jp->ps = &jp->ps0; } INTON; TRACE(("makejob(%p, %d) returns %%%td\n", (void *)node, nprocs, jp - jobtab + 1)); return jp; } #if JOBS static void setcurjob(struct job *cj) { struct job *jp, *prev; for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { if (jp == cj) { if (prev != NULL) prev->next = jp->next; else jobmru = jp->next; jp->next = jobmru; jobmru = cj; return; } } cj->next = jobmru; jobmru = cj; } static void deljob(struct job *j) { struct job *jp, *prev; for (prev = NULL, jp = jobmru; jp != NULL; prev = jp, jp = jp->next) { if (jp == j) { if (prev != NULL) prev->next = jp->next; else jobmru = jp->next; return; } } } /* * Return the most recently used job that isn't `nj', and preferably one * that is stopped. */ static struct job * getcurjob(struct job *nj) { struct job *jp; /* Try to find a stopped one.. */ for (jp = jobmru; jp != NULL; jp = jp->next) if (jp->used && jp != nj && jp->state == JOBSTOPPED) return (jp); /* Otherwise the most recently used job that isn't `nj' */ for (jp = jobmru; jp != NULL; jp = jp->next) if (jp->used && jp != nj) return (jp); return (NULL); } #endif /* * Fork of a subshell. If we are doing job control, give the subshell its * own process group. Jp is a job structure that the job is to be added to. * N is the command that will be evaluated by the child. Both jp and n may * be NULL. The mode parameter can be one of the following: * FORK_FG - Fork off a foreground process. * FORK_BG - Fork off a background process. * FORK_NOJOB - Like FORK_FG, but don't give the process its own * process group even if job control is on. * * When job control is turned off, background processes have their standard * input redirected to /dev/null (except for the second and later processes * in a pipeline). */ pid_t forkshell(struct job *jp, union node *n, int mode) { pid_t pid; pid_t pgrp; TRACE(("forkshell(%%%td, %p, %d) called\n", jp - jobtab, (void *)n, mode)); INTOFF; if (mode == FORK_BG && (jp == NULL || jp->nprocs == 0)) checkzombies(); flushall(); pid = fork(); if (pid == -1) { TRACE(("Fork failed, errno=%d\n", errno)); INTON; error("Cannot fork: %s", strerror(errno)); } if (pid == 0) { struct job *p; int wasroot; int i; TRACE(("Child shell %d\n", (int)getpid())); wasroot = rootshell; rootshell = 0; handler = &main_handler; closescript(); INTON; forcelocal = 0; clear_traps(); #if JOBS jobctl = 0; /* do job control only in root shell */ if (wasroot && mode != FORK_NOJOB && mflag) { if (jp == NULL || jp->nprocs == 0) pgrp = getpid(); else pgrp = jp->ps[0].pid; if (setpgid(0, pgrp) == 0 && mode == FORK_FG && ttyfd >= 0) { /*** this causes superfluous TIOCSPGRPS ***/ if (tcsetpgrp(ttyfd, pgrp) < 0) error("tcsetpgrp failed, errno=%d", errno); } setsignal(SIGTSTP); setsignal(SIGTTOU); } else if (mode == FORK_BG) { ignoresig(SIGINT); ignoresig(SIGQUIT); if ((jp == NULL || jp->nprocs == 0) && ! fd0_redirected_p ()) { close(0); if (open(_PATH_DEVNULL, O_RDONLY) != 0) error("cannot open %s: %s", _PATH_DEVNULL, strerror(errno)); } } #else if (mode == FORK_BG) { ignoresig(SIGINT); ignoresig(SIGQUIT); if ((jp == NULL || jp->nprocs == 0) && ! fd0_redirected_p ()) { close(0); if (open(_PATH_DEVNULL, O_RDONLY) != 0) error("cannot open %s: %s", _PATH_DEVNULL, strerror(errno)); } } #endif INTOFF; for (i = njobs, p = jobtab ; --i >= 0 ; p++) if (p->used) freejob(p); INTON; if (wasroot && iflag) { setsignal(SIGINT); setsignal(SIGQUIT); setsignal(SIGTERM); } return pid; } if (rootshell && mode != FORK_NOJOB && mflag) { if (jp == NULL || jp->nprocs == 0) pgrp = pid; else pgrp = jp->ps[0].pid; setpgid(pid, pgrp); } if (mode == FORK_BG) { if (bgjob != NULL && bgjob->state == JOBDONE && !bgjob->remembered && !iflag) freejob(bgjob); backgndpid = pid; /* set $! */ bgjob = jp; } if (jp) { struct procstat *ps = &jp->ps[jp->nprocs++]; ps->pid = pid; ps->status = -1; ps->cmd = nullstr; if (iflag && rootshell && n) ps->cmd = commandtext(n); jp->foreground = mode == FORK_FG; #if JOBS setcurjob(jp); #endif } INTON; TRACE(("In parent shell: child = %d\n", (int)pid)); return pid; } pid_t vforkexecshell(struct job *jp, char **argv, char **envp, const char *path, int idx, int pip[2]) { pid_t pid; struct jmploc jmploc; struct jmploc *savehandler; TRACE(("vforkexecshell(%%%td, %s, %p) called\n", jp - jobtab, argv[0], (void *)pip)); INTOFF; flushall(); savehandler = handler; pid = vfork(); if (pid == -1) { TRACE(("Vfork failed, errno=%d\n", errno)); INTON; error("Cannot fork: %s", strerror(errno)); } if (pid == 0) { TRACE(("Child shell %d\n", (int)getpid())); if (setjmp(jmploc.loc)) _exit(exception == EXEXEC ? exerrno : 2); if (pip != NULL) { close(pip[0]); if (pip[1] != 1) { dup2(pip[1], 1); close(pip[1]); } } handler = &jmploc; shellexec(argv, envp, path, idx); } handler = savehandler; if (jp) { struct procstat *ps = &jp->ps[jp->nprocs++]; ps->pid = pid; ps->status = -1; ps->cmd = nullstr; jp->foreground = 1; #if JOBS setcurjob(jp); #endif } INTON; TRACE(("In parent shell: child = %d\n", (int)pid)); return pid; } /* * Wait for job to finish. * * Under job control we have the problem that while a child process is * running interrupts generated by the user are sent to the child but not * to the shell. This means that an infinite loop started by an inter- * active user may be hard to kill. With job control turned off, an * interactive user may place an interactive program inside a loop. If * the interactive program catches interrupts, the user doesn't want * these interrupts to also abort the loop. The approach we take here * is to have the shell ignore interrupt signals while waiting for a * foreground process to terminate, and then send itself an interrupt * signal if the child process was terminated by an interrupt signal. * Unfortunately, some programs want to do a bit of cleanup and then * exit on interrupt; unless these processes terminate themselves by * sending a signal to themselves (instead of calling exit) they will * confuse this approach. */ int waitforjob(struct job *jp, int *origstatus) { #if JOBS int propagate_int = jp->jobctl && jp->foreground; #endif int status; int st; INTOFF; TRACE(("waitforjob(%%%td) called\n", jp - jobtab + 1)); while (jp->state == 0) if (dowait(DOWAIT_BLOCK | (Tflag ? DOWAIT_SIG | DOWAIT_SIG_ANY : 0), jp) == -1) dotrap(); #if JOBS if (jp->jobctl) { if (ttyfd >= 0 && tcsetpgrp(ttyfd, rootpid) < 0) error("tcsetpgrp failed, errno=%d\n", errno); } if (jp->state == JOBSTOPPED) setcurjob(jp); #endif status = jp->ps[jp->nprocs - 1].status; if (origstatus != NULL) *origstatus = status; /* convert to 8 bits */ if (WIFEXITED(status)) st = WEXITSTATUS(status); #if JOBS else if (WIFSTOPPED(status)) st = WSTOPSIG(status) + 128; #endif else st = WTERMSIG(status) + 128; if (! JOBS || jp->state == JOBDONE) freejob(jp); if (int_pending()) { if (!WIFSIGNALED(status) || WTERMSIG(status) != SIGINT) CLEAR_PENDING_INT; } #if JOBS else if (rootshell && iflag && propagate_int && WIFSIGNALED(status) && WTERMSIG(status) == SIGINT) kill(getpid(), SIGINT); #endif INTON; return st; } static void dummy_handler(int sig __unused) { } /* * Wait for a process to terminate. */ static pid_t dowait(int mode, struct job *job) { struct sigaction sa, osa; sigset_t mask, omask; pid_t pid; int status; struct procstat *sp; struct job *jp; struct job *thisjob; const char *sigstr; int done; int stopped; int sig; int coredump; int wflags; int restore_sigchld; TRACE(("dowait(%d, %p) called\n", mode, job)); restore_sigchld = 0; if ((mode & DOWAIT_SIG) != 0) { sigfillset(&mask); sigprocmask(SIG_BLOCK, &mask, &omask); INTOFF; if (!issigchldtrapped()) { restore_sigchld = 1; sa.sa_handler = dummy_handler; sa.sa_flags = 0; sigemptyset(&sa.sa_mask); sigaction(SIGCHLD, &sa, &osa); } } do { #if JOBS if (iflag) wflags = WUNTRACED | WCONTINUED; else #endif wflags = 0; if ((mode & (DOWAIT_BLOCK | DOWAIT_SIG)) != DOWAIT_BLOCK) wflags |= WNOHANG; pid = wait3(&status, wflags, (struct rusage *)NULL); TRACE(("wait returns %d, status=%d\n", (int)pid, status)); if (pid == 0 && (mode & DOWAIT_SIG) != 0) { pid = -1; if (((mode & DOWAIT_SIG_ANY) != 0 ? pendingsig : pendingsig_waitcmd) != 0) { errno = EINTR; break; } sigsuspend(&omask); if (int_pending()) break; } } while (pid == -1 && errno == EINTR); if (pid == -1 && errno == ECHILD && job != NULL) job->state = JOBDONE; if ((mode & DOWAIT_SIG) != 0) { if (restore_sigchld) sigaction(SIGCHLD, &osa, NULL); sigprocmask(SIG_SETMASK, &omask, NULL); INTON; } if (pid <= 0) return pid; INTOFF; thisjob = NULL; for (jp = jobtab ; jp < jobtab + njobs ; jp++) { if (jp->used && jp->nprocs > 0) { done = 1; stopped = 1; for (sp = jp->ps ; sp < jp->ps + jp->nprocs ; sp++) { if (sp->pid == -1) continue; if (sp->pid == pid && (sp->status == -1 || WIFSTOPPED(sp->status))) { TRACE(("Changing status of proc %d from 0x%x to 0x%x\n", (int)pid, sp->status, status)); if (WIFCONTINUED(status)) { sp->status = -1; jp->state = 0; } else sp->status = status; thisjob = jp; } if (sp->status == -1) stopped = 0; else if (WIFSTOPPED(sp->status)) done = 0; } if (stopped) { /* stopped or done */ int state = done? JOBDONE : JOBSTOPPED; if (jp->state != state) { TRACE(("Job %td: changing state from %d to %d\n", jp - jobtab + 1, jp->state, state)); jp->state = state; if (jp != job) { if (done && !jp->remembered && !iflag && jp != bgjob) freejob(jp); #if JOBS else if (done) deljob(jp); #endif } } } } } INTON; if (!thisjob || thisjob->state == 0) ; else if ((!rootshell || !iflag || thisjob == job) && thisjob->foreground && thisjob->state != JOBSTOPPED) { sig = 0; coredump = 0; for (sp = thisjob->ps; sp < thisjob->ps + thisjob->nprocs; sp++) if (WIFSIGNALED(sp->status)) { sig = WTERMSIG(sp->status); coredump = WCOREDUMP(sp->status); } if (sig > 0 && sig != SIGINT && sig != SIGPIPE) { sigstr = strsignal(sig); if (sigstr != NULL) out2str(sigstr); else out2str("Unknown signal"); if (coredump) out2str(" (core dumped)"); out2c('\n'); flushout(out2); } } else { TRACE(("Not printing status, rootshell=%d, job=%p\n", rootshell, job)); thisjob->changed = 1; } return pid; } /* * return 1 if there are stopped jobs, otherwise 0 */ int job_warning = 0; int stoppedjobs(void) { int jobno; struct job *jp; if (job_warning) return (0); for (jobno = 1, jp = jobtab; jobno <= njobs; jobno++, jp++) { if (jp->used == 0) continue; if (jp->state == JOBSTOPPED) { out2fmt_flush("You have stopped jobs.\n"); job_warning = 2; return (1); } } return (0); } static void checkzombies(void) { while (njobs > 0 && dowait(0, NULL) > 0) ; } int backgndpidset(void) { return backgndpid != -1; } pid_t backgndpidval(void) { if (bgjob != NULL && !forcelocal) bgjob->remembered = 1; return backgndpid; } /* * Return a string identifying a command (to be printed by the * jobs command. */ static char *cmdnextc; static int cmdnleft; #define MAXCMDTEXT 200 char * commandtext(union node *n) { char *name; cmdnextc = name = ckmalloc(MAXCMDTEXT); cmdnleft = MAXCMDTEXT - 4; cmdtxt(n); *cmdnextc = '\0'; return name; } static void +cmdtxtdogroup(union node *n) +{ + cmdputs("; do "); + cmdtxt(n); + cmdputs("; done"); +} + + +static void +cmdtxtredir(union node *n, const char *op, int deffd) +{ + char s[2]; + + if (n->nfile.fd != deffd) { + s[0] = n->nfile.fd + '0'; + s[1] = '\0'; + cmdputs(s); + } + cmdputs(op); + if (n->type == NTOFD || n->type == NFROMFD) { + if (n->ndup.dupfd >= 0) + s[0] = n->ndup.dupfd + '0'; + else + s[0] = '-'; + s[1] = '\0'; + cmdputs(s); + } else { + cmdtxt(n->nfile.fname); + } +} + + +static void cmdtxt(union node *n) { union node *np; struct nodelist *lp; - const char *p; - int i; - char s[2]; if (n == NULL) return; switch (n->type) { case NSEMI: cmdtxt(n->nbinary.ch1); cmdputs("; "); cmdtxt(n->nbinary.ch2); break; case NAND: cmdtxt(n->nbinary.ch1); cmdputs(" && "); cmdtxt(n->nbinary.ch2); break; case NOR: cmdtxt(n->nbinary.ch1); cmdputs(" || "); cmdtxt(n->nbinary.ch2); break; case NPIPE: for (lp = n->npipe.cmdlist ; lp ; lp = lp->next) { cmdtxt(lp->n); if (lp->next) cmdputs(" | "); } break; case NSUBSHELL: cmdputs("("); cmdtxt(n->nredir.n); cmdputs(")"); break; case NREDIR: case NBACKGND: cmdtxt(n->nredir.n); break; case NIF: cmdputs("if "); cmdtxt(n->nif.test); cmdputs("; then "); cmdtxt(n->nif.ifpart); cmdputs("..."); break; case NWHILE: cmdputs("while "); - goto until; + cmdtxt(n->nbinary.ch1); + cmdtxtdogroup(n->nbinary.ch2); + break; case NUNTIL: cmdputs("until "); -until: cmdtxt(n->nbinary.ch1); - cmdputs("; do "); - cmdtxt(n->nbinary.ch2); - cmdputs("; done"); + cmdtxtdogroup(n->nbinary.ch2); break; case NFOR: cmdputs("for "); cmdputs(n->nfor.var); cmdputs(" in ..."); break; case NCASE: cmdputs("case "); cmdputs(n->ncase.expr->narg.text); cmdputs(" in ..."); break; case NDEFUN: cmdputs(n->narg.text); cmdputs("() ..."); break; case NNOT: cmdputs("! "); cmdtxt(n->nnot.com); break; case NCMD: for (np = n->ncmd.args ; np ; np = np->narg.next) { cmdtxt(np); if (np->narg.next) cmdputs(" "); } for (np = n->ncmd.redirect ; np ; np = np->nfile.next) { cmdputs(" "); cmdtxt(np); } break; case NARG: cmdputs(n->narg.text); break; case NTO: - p = ">"; i = 1; goto redir; + cmdtxtredir(n, ">", 1); + break; case NAPPEND: - p = ">>"; i = 1; goto redir; + cmdtxtredir(n, ">>", 1); + break; case NTOFD: - p = ">&"; i = 1; goto redir; + cmdtxtredir(n, ">&", 1); + break; case NCLOBBER: - p = ">|"; i = 1; goto redir; + cmdtxtredir(n, ">|", 1); + break; case NFROM: - p = "<"; i = 0; goto redir; + cmdtxtredir(n, "<", 0); + break; case NFROMTO: - p = "<>"; i = 0; goto redir; + cmdtxtredir(n, "<>", 0); + break; case NFROMFD: - p = "<&"; i = 0; goto redir; -redir: - if (n->nfile.fd != i) { - s[0] = n->nfile.fd + '0'; - s[1] = '\0'; - cmdputs(s); - } - cmdputs(p); - if (n->type == NTOFD || n->type == NFROMFD) { - if (n->ndup.dupfd >= 0) - s[0] = n->ndup.dupfd + '0'; - else - s[0] = '-'; - s[1] = '\0'; - cmdputs(s); - } else { - cmdtxt(n->nfile.fname); - } + cmdtxtredir(n, "<&", 0); break; case NHERE: case NXHERE: cmdputs("<<..."); break; default: cmdputs("???"); break; } } static void cmdputs(const char *s) { const char *p; char *q; char c; int subtype = 0; if (cmdnleft <= 0) return; p = s; q = cmdnextc; while ((c = *p++) != '\0') { if (c == CTLESC) *q++ = *p++; else if (c == CTLVAR) { *q++ = '$'; if (--cmdnleft > 0) *q++ = '{'; subtype = *p++; if ((subtype & VSTYPE) == VSLENGTH && --cmdnleft > 0) *q++ = '#'; } else if (c == '=' && subtype != 0) { *q = "}-+?=##%%\0X"[(subtype & VSTYPE) - VSNORMAL]; if (*q) q++; else cmdnleft++; if (((subtype & VSTYPE) == VSTRIMLEFTMAX || (subtype & VSTYPE) == VSTRIMRIGHTMAX) && --cmdnleft > 0) *q = q[-1], q++; subtype = 0; } else if (c == CTLENDVAR) { *q++ = '}'; } else if (c == CTLBACKQ || c == CTLBACKQ+CTLQUOTE) { cmdnleft -= 5; if (cmdnleft > 0) { *q++ = '$'; *q++ = '('; *q++ = '.'; *q++ = '.'; *q++ = '.'; *q++ = ')'; } } else if (c == CTLARI) { cmdnleft -= 2; if (cmdnleft > 0) { *q++ = '$'; *q++ = '('; *q++ = '('; } p++; } else if (c == CTLENDARI) { if (--cmdnleft > 0) { *q++ = ')'; *q++ = ')'; } } else if (c == CTLQUOTEMARK || c == CTLQUOTEEND) cmdnleft++; /* ignore */ else *q++ = c; if (--cmdnleft <= 0) { *q++ = '.'; *q++ = '.'; *q++ = '.'; break; } } cmdnextc = q; } Index: head/bin/sh/parser.c =================================================================== --- head/bin/sh/parser.c (revision 273151) +++ head/bin/sh/parser.c (revision 273152) @@ -1,2063 +1,2060 @@ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. * * This code is derived from software contributed to Berkeley by * Kenneth Almquist. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 4. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #ifndef lint #if 0 static char sccsid[] = "@(#)parser.c 8.7 (Berkeley) 5/16/95"; #endif #endif /* not lint */ #include __FBSDID("$FreeBSD$"); #include #include #include #include "shell.h" #include "parser.h" #include "nodes.h" #include "expand.h" /* defines rmescapes() */ #include "syntax.h" #include "options.h" #include "input.h" #include "output.h" #include "var.h" #include "error.h" #include "memalloc.h" #include "mystring.h" #include "alias.h" #include "show.h" #include "eval.h" #include "exec.h" /* to check for special builtins */ #ifndef NO_HISTORY #include "myhistedit.h" #endif /* * Shell command parser. */ #define PROMPTLEN 128 /* values of checkkwd variable */ #define CHKALIAS 0x1 #define CHKKWD 0x2 #define CHKNL 0x4 /* values returned by readtoken */ #include "token.h" 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 */ }; struct parser_temp { struct parser_temp *next; void *data; }; static struct heredoc *heredoclist; /* list of here documents to read */ 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 int tokpushback; /* last token pushed back */ 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 int startlinno; /* line # where last token started */ static int funclinno; /* line # where the current function started */ static struct parser_temp *parser_temp; static union node *list(int); static union node *andor(void); static union node *pipeline(void); static union node *command(void); static union node *simplecmd(union node **, union node *); static union node *makename(void); static union node *makebinary(int type, union node *n1, union node *n2); static void parsefname(void); static void parseheredoc(void); static int peektoken(void); static int readtoken(void); static int xxreadtoken(void); static int readtoken1(int, const char *, const char *, int); static int noexpand(char *); static void consumetoken(int); static void synexpect(int) __dead2; static void synerror(const char *) __dead2; static void setprompt(int); static void * parser_temp_alloc(size_t len) { struct parser_temp *t; INTOFF; t = ckmalloc(sizeof(*t)); t->data = NULL; t->next = parser_temp; parser_temp = t; t->data = ckmalloc(len); INTON; return t->data; } static void * parser_temp_realloc(void *ptr, size_t len) { struct parser_temp *t; INTOFF; t = parser_temp; if (ptr != t->data) error("bug: parser_temp_realloc misused"); t->data = ckrealloc(t->data, len); INTON; return t->data; } static void parser_temp_free_upto(void *ptr) { struct parser_temp *t; int done = 0; INTOFF; while (parser_temp != NULL && !done) { t = parser_temp; parser_temp = t->next; done = t->data == ptr; ckfree(t->data); ckfree(t); } INTON; if (!done) error("bug: parser_temp_free_upto misused"); } static void parser_temp_free_all(void) { struct parser_temp *t; INTOFF; while (parser_temp != NULL) { t = parser_temp; parser_temp = t->next; ckfree(t->data); ckfree(t); } INTON; } /* * Read and parse a command. Returns NEOF on end of file. (NULL is a * valid parse tree indicating a blank line.) */ union node * parsecmd(int interact) { int t; /* This assumes the parser is not re-entered, * which could happen if we add command substitution on PS1/PS2. */ parser_temp_free_all(); heredoclist = NULL; tokpushback = 0; checkkwd = 0; doprompt = interact; if (doprompt) setprompt(1); else setprompt(0); needprompt = 0; t = readtoken(); if (t == TEOF) return NEOF; if (t == TNL) return NULL; tokpushback++; return list(1); } static union node * list(int nlflag) { union node *ntop, *n1, *n2, *n3; int tok; checkkwd = CHKNL | CHKKWD | CHKALIAS; if (!nlflag && tokendlist[peektoken()]) return NULL; ntop = n1 = NULL; for (;;) { n2 = andor(); tok = readtoken(); if (tok == TBACKGND) { if (n2 != NULL && n2->type == NPIPE) { n2->npipe.backgnd = 1; } else if (n2 != NULL && n2->type == NREDIR) { n2->type = NBACKGND; } else { n3 = (union node *)stalloc(sizeof (struct nredir)); n3->type = NBACKGND; n3->nredir.n = n2; n3->nredir.redirect = NULL; n2 = n3; } } if (ntop == NULL) ntop = n2; else if (n1 == NULL) { n1 = makebinary(NSEMI, ntop, n2); ntop = n1; } else { n3 = makebinary(NSEMI, n1->nbinary.ch2, n2); n1->nbinary.ch2 = n3; n1 = n3; } switch (tok) { case TBACKGND: case TSEMI: tok = readtoken(); /* FALLTHROUGH */ case TNL: if (tok == TNL) { parseheredoc(); if (nlflag) return ntop; } else if (tok == TEOF && nlflag) { parseheredoc(); return ntop; } else { tokpushback++; } checkkwd = CHKNL | CHKKWD | CHKALIAS; if (!nlflag && tokendlist[peektoken()]) return ntop; break; case TEOF: if (heredoclist) parseheredoc(); else pungetc(); /* push back EOF on input */ return ntop; default: if (nlflag) synexpect(-1); tokpushback++; return ntop; } } } static union node * andor(void) { union node *n; int t; n = pipeline(); for (;;) { if ((t = readtoken()) == TAND) { t = NAND; } else if (t == TOR) { t = NOR; } else { tokpushback++; return n; } n = makebinary(t, n, pipeline()); } } static union node * pipeline(void) { union node *n1, *n2, *pipenode; struct nodelist *lp, *prev; int negate, t; negate = 0; checkkwd = CHKNL | CHKKWD | CHKALIAS; TRACE(("pipeline: entered\n")); while (readtoken() == TNOT) negate = !negate; tokpushback++; n1 = command(); if (readtoken() == TPIPE) { pipenode = (union node *)stalloc(sizeof (struct npipe)); pipenode->type = NPIPE; pipenode->npipe.backgnd = 0; lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); pipenode->npipe.cmdlist = lp; lp->n = n1; do { prev = lp; lp = (struct nodelist *)stalloc(sizeof (struct nodelist)); checkkwd = CHKNL | CHKKWD | CHKALIAS; t = readtoken(); tokpushback++; if (t == TNOT) lp->n = pipeline(); else lp->n = command(); prev->next = lp; } while (readtoken() == TPIPE); lp->next = NULL; n1 = pipenode; } tokpushback++; if (negate) { n2 = (union node *)stalloc(sizeof (struct nnot)); n2->type = NNOT; n2->nnot.com = n1; return n2; } else return n1; } static union node * command(void) { union node *n1, *n2; union node *ap, **app; union node *cp, **cpp; union node *redir, **rpp; int t; int is_subshell; checkkwd = CHKNL | CHKKWD | CHKALIAS; is_subshell = 0; redir = NULL; n1 = NULL; rpp = &redir; /* Check for redirection which may precede command */ while (readtoken() == TREDIR) { *rpp = n2 = redirnode; rpp = &n2->nfile.next; parsefname(); } tokpushback++; switch (readtoken()) { case TIF: n1 = (union node *)stalloc(sizeof (struct nif)); n1->type = NIF; if ((n1->nif.test = list(0)) == NULL) synexpect(-1); consumetoken(TTHEN); n1->nif.ifpart = list(0); n2 = n1; while (readtoken() == TELIF) { n2->nif.elsepart = (union node *)stalloc(sizeof (struct nif)); n2 = n2->nif.elsepart; n2->type = NIF; if ((n2->nif.test = list(0)) == NULL) synexpect(-1); consumetoken(TTHEN); n2->nif.ifpart = list(0); } if (lasttoken == TELSE) n2->nif.elsepart = list(0); else { n2->nif.elsepart = NULL; tokpushback++; } consumetoken(TFI); checkkwd = CHKKWD | CHKALIAS; break; case TWHILE: case TUNTIL: t = lasttoken; if ((n1 = list(0)) == NULL) synexpect(-1); consumetoken(TDO); n1 = makebinary((t == TWHILE)? NWHILE : NUNTIL, n1, list(0)); consumetoken(TDONE); checkkwd = CHKKWD | CHKALIAS; break; case TFOR: if (readtoken() != TWORD || quoteflag || ! goodname(wordtext)) synerror("Bad for loop variable"); n1 = (union node *)stalloc(sizeof (struct nfor)); n1->type = NFOR; n1->nfor.var = wordtext; while (readtoken() == TNL) ; if (lasttoken == TWORD && ! quoteflag && equal(wordtext, "in")) { app = ≈ while (readtoken() == TWORD) { n2 = makename(); *app = n2; app = &n2->narg.next; } *app = NULL; n1->nfor.args = ap; if (lasttoken != TNL && lasttoken != TSEMI) synexpect(-1); } else { static char argvars[5] = { CTLVAR, VSNORMAL|VSQUOTE, '@', '=', '\0' }; n2 = (union node *)stalloc(sizeof (struct narg)); n2->type = NARG; n2->narg.text = argvars; 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 ((t = readtoken()) == TDO) t = TDONE; else if (t == TBEGIN) t = TEND; else synexpect(-1); n1->nfor.body = list(0); consumetoken(t); checkkwd = CHKKWD | CHKALIAS; break; case TCASE: n1 = (union node *)stalloc(sizeof (struct ncase)); n1->type = NCASE; consumetoken(TWORD); n1->ncase.expr = makename(); while (readtoken() == TNL); if (lasttoken != TWORD || ! equal(wordtext, "in")) synerror("expecting \"in\""); cpp = &n1->ncase.cases; checkkwd = CHKNL | CHKKWD, readtoken(); while (lasttoken != TESAC) { *cpp = cp = (union node *)stalloc(sizeof (struct nclist)); cp->type = NCLIST; app = &cp->nclist.pattern; if (lasttoken == TLP) readtoken(); for (;;) { *app = ap = makename(); checkkwd = CHKNL | CHKKWD; if (readtoken() != TPIPE) break; app = &ap->narg.next; readtoken(); } ap->narg.next = NULL; if (lasttoken != TRP) synexpect(TRP); cp->nclist.body = list(0); checkkwd = CHKNL | CHKKWD | CHKALIAS; if ((t = readtoken()) != TESAC) { if (t == TENDCASE) ; else if (t == TFALLTHRU) cp->type = NCLISTFALLTHRU; else synexpect(TENDCASE); checkkwd = CHKNL | CHKKWD, readtoken(); } cpp = &cp->nclist.next; } *cpp = NULL; checkkwd = CHKKWD | CHKALIAS; break; case TLP: n1 = (union node *)stalloc(sizeof (struct nredir)); n1->type = NSUBSHELL; n1->nredir.n = list(0); n1->nredir.redirect = NULL; consumetoken(TRP); checkkwd = CHKKWD | CHKALIAS; is_subshell = 1; break; case TBEGIN: n1 = list(0); consumetoken(TEND); checkkwd = CHKKWD | CHKALIAS; break; /* A simple command must have at least one redirection or word. */ case TBACKGND: case TSEMI: case TAND: case TOR: case TPIPE: case TENDCASE: case TFALLTHRU: case TEOF: case TNL: case TRP: if (!redir) synexpect(-1); case TWORD: tokpushback++; n1 = simplecmd(rpp, redir); return n1; default: synexpect(-1); } /* Now check for redirection which may follow command */ while (readtoken() == TREDIR) { *rpp = n2 = redirnode; rpp = &n2->nfile.next; parsefname(); } tokpushback++; *rpp = NULL; if (redir) { if (!is_subshell) { n2 = (union node *)stalloc(sizeof (struct nredir)); n2->type = NREDIR; n2->nredir.n = n1; n1 = n2; } n1->nredir.redirect = redir; } return n1; } static union node * simplecmd(union node **rpp, union node *redir) { union node *args, **app; union node **orig_rpp = rpp; union node *n = NULL; int special; int savecheckkwd; /* If we don't have any redirections already, then we must reset */ /* rpp to be the address of the local redir variable. */ if (redir == 0) rpp = &redir; args = NULL; app = &args; /* * We save the incoming value, because we need this for shell * functions. There can not be a redirect or an argument between * the function name and the open parenthesis. */ orig_rpp = rpp; savecheckkwd = CHKALIAS; for (;;) { checkkwd = savecheckkwd; if (readtoken() == TWORD) { n = makename(); *app = n; app = &n->narg.next; if (savecheckkwd != 0 && !isassignment(wordtext)) savecheckkwd = 0; } else if (lasttoken == TREDIR) { *rpp = n = redirnode; rpp = &n->nfile.next; parsefname(); /* read name of redirection file */ } else if (lasttoken == TLP && app == &args->narg.next && rpp == orig_rpp) { /* We have a function */ consumetoken(TRP); funclinno = plinno; /* * - Require plain text. * - Functions with '/' cannot be called. * - Reject name=(). * - Reject ksh extended glob patterns. */ if (!noexpand(n->narg.text) || quoteflag || strchr(n->narg.text, '/') || strchr("!%*+-=?@}~", n->narg.text[strlen(n->narg.text) - 1])) synerror("Bad function name"); rmescapes(n->narg.text); if (find_builtin(n->narg.text, &special) >= 0 && special) synerror("Cannot override a special builtin with a function"); n->type = NDEFUN; n->narg.next = command(); funclinno = 0; return n; } else { tokpushback++; break; } } *app = NULL; *rpp = NULL; n = (union node *)stalloc(sizeof (struct ncmd)); n->type = NCMD; n->ncmd.args = args; n->ncmd.redirect = redir; return n; } static union node * makename(void) { union node *n; n = (union node *)stalloc(sizeof (struct narg)); n->type = NARG; n->narg.next = NULL; n->narg.text = wordtext; n->narg.backquote = backquotelist; return n; } static union node * makebinary(int type, union node *n1, union node *n2) { union node *n; n = (union node *)stalloc(sizeof (struct nbinary)); n->type = type; n->nbinary.ch1 = n1; n->nbinary.ch2 = n2; return (n); } void forcealias(void) { checkkwd |= CHKALIAS; } 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 (text[0] == '-' && text[1] == '\0') n->ndup.dupfd = -1; else { if (err) synerror("Bad fd number"); else n->ndup.vname = makename(); } } static void parsefname(void) { union node *n = redirnode; consumetoken(TWORD); if (n->type == NHERE) { struct heredoc *here = heredoc; struct heredoc *p; if (quoteflag == 0) n->type = NXHERE; TRACE(("Here document %d\n", n->type)); if (here->striptabs) { while (*wordtext == '\t') wordtext++; } if (! noexpand(wordtext)) synerror("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; while (heredoclist) { here = heredoclist; heredoclist = here->next; if (needprompt) { setprompt(2); needprompt = 0; } readtoken1(pgetc(), here->here->type == NHERE? SQSYNTAX : DQSYNTAX, here->eofmark, here->striptabs); n = makename(); here->here->nhere.doc = n; } } static int peektoken(void) { int t; t = readtoken(); tokpushback++; return (t); } static int readtoken(void) { int t; struct alias *ap; #ifdef DEBUG int alreadyseen = tokpushback; #endif top: t = xxreadtoken(); /* * eat newlines */ if (checkkwd & CHKNL) { while (t == TNL) { parseheredoc(); t = xxreadtoken(); } } /* * check for keywords and aliases */ if (t == TWORD && !quoteflag) { const char * const *pp; if (checkkwd & CHKKWD) for (pp = parsekwd; *pp; pp++) { if (**pp == *wordtext && equal(*pp, wordtext)) { lasttoken = t = pp - parsekwd + KWDOFFSET; TRACE(("keyword %s recognized\n", tokname[t])); goto out; } } if (checkkwd & CHKALIAS && (ap = lookupalias(wordtext, 1)) != NULL) { pushstring(ap->val, strlen(ap->val), ap); goto top; } } out: if (t != TNOT) checkkwd = 0; #ifdef 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 RETURN(token) return lasttoken = token static int xxreadtoken(void) { int c; if (tokpushback) { tokpushback = 0; return lasttoken; } if (needprompt) { setprompt(2); needprompt = 0; } startlinno = plinno; for (;;) { /* until token or start of word found */ c = pgetc_macro(); switch (c) { case ' ': case '\t': continue; case '#': while ((c = pgetc()) != '\n' && c != PEOF); pungetc(); continue; case '\\': if (pgetc() == '\n') { startlinno = ++plinno; if (doprompt) setprompt(2); else setprompt(0); continue; } pungetc(); - goto breakloop; + /* FALLTHROUGH */ + default: + return readtoken1(c, BASESYNTAX, (char *)NULL, 0); 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 ';': c = pgetc(); if (c == ';') RETURN(TENDCASE); else if (c == '&') RETURN(TFALLTHRU); pungetc(); RETURN(TSEMI); case '(': RETURN(TLP); case ')': RETURN(TRP); - default: - goto breakloop; } } -breakloop: - return readtoken1(c, BASESYNTAX, (char *)NULL, 0); #undef RETURN } #define MAXNEST_static 8 struct tokenstate { const char *syntax; /* *SYNTAX */ int parenlevel; /* levels of parentheses in arithmetic */ enum tokenstate_category { TSTATE_TOP, TSTATE_VAR_OLD, /* ${var+-=?}, inherits dquotes */ TSTATE_VAR_NEW, /* other ${var...}, own dquote state */ TSTATE_ARITH } category; }; /* * Check to see whether we are at the end of the here document. When this * is called, c is set to the first character of the next input line. If * we are at the end of the here document, this routine sets the c to PEOF. * The new value of c is returned. */ static int checkend(int c, const char *eofmark, int striptabs) { if (striptabs) { while (c == '\t') c = pgetc(); } if (c == *eofmark) { int c2; const char *q; for (q = eofmark + 1; c2 = pgetc(), *q != '\0' && c2 == *q; q++) ; if ((c2 == PEOF || c2 == '\n') && *q == '\0') { c = PEOF; if (c2 == '\n') { plinno++; needprompt = doprompt; } } else { pungetc(); pushstring(eofmark + 1, q - (eofmark + 1), NULL); } } return (c); } /* * Called to parse command substitutions. */ static char * parsebackq(char *out, struct nodelist **pbqlist, int oldstyle, int dblquote, int quoted) { struct nodelist **nlpp; union node *n; char *volatile str; struct jmploc jmploc; struct jmploc *const savehandler = handler; size_t savelen; int saveprompt; const int bq_startlinno = plinno; char *volatile ostr = NULL; struct parsefile *const savetopfile = getcurrentfile(); struct heredoc *const saveheredoclist = heredoclist; struct heredoc *here; str = NULL; if (setjmp(jmploc.loc)) { popfilesupto(savetopfile); if (str) ckfree(str); if (ostr) ckfree(ostr); heredoclist = saveheredoclist; handler = savehandler; if (exception == EXERROR) { startlinno = bq_startlinno; synerror("Error in command substitution"); } longjmp(handler->loc, 1); } INTOFF; savelen = out - stackblock(); if (savelen > 0) { str = ckmalloc(savelen); memcpy(str, stackblock(), savelen); } handler = &jmploc; heredoclist = NULL; INTON; if (oldstyle) { /* We must read until the closing backquote, giving special treatment to some slashes, and then push the string and reread it as input, interpreting it normally. */ char *oout; int c; int olen; STARTSTACKSTR(oout); for (;;) { if (needprompt) { setprompt(2); needprompt = 0; } CHECKSTRSPACE(2, oout); - switch (c = pgetc()) { - case '`': - goto done; - + c = pgetc(); + if (c == '`') + break; + switch (c) { case '\\': if ((c = pgetc()) == '\n') { plinno++; if (doprompt) setprompt(2); else setprompt(0); /* * If eating a newline, avoid putting * the newline into the new character * stream (via the USTPUTC after the * switch). */ continue; } if (c != '\\' && c != '`' && c != '$' && (!dblquote || c != '"')) USTPUTC('\\', oout); break; case '\n': plinno++; needprompt = doprompt; break; case PEOF: startlinno = plinno; synerror("EOF in backquote substitution"); break; default: break; } USTPUTC(c, oout); } -done: USTPUTC('\0', oout); olen = oout - stackblock(); INTOFF; ostr = ckmalloc(olen); memcpy(ostr, stackblock(), olen); setinputstring(ostr, 1); INTON; } nlpp = pbqlist; while (*nlpp) nlpp = &(*nlpp)->next; *nlpp = (struct nodelist *)stalloc(sizeof (struct nodelist)); (*nlpp)->next = NULL; if (oldstyle) { saveprompt = doprompt; doprompt = 0; } n = list(0); if (oldstyle) { if (peektoken() != TEOF) synexpect(-1); doprompt = saveprompt; } else consumetoken(TRP); (*nlpp)->n = n; if (oldstyle) { /* * Start reading from old file again, ignoring any pushed back * tokens left from the backquote parsing */ popfile(); tokpushback = 0; } STARTSTACKSTR(out); CHECKSTRSPACE(savelen + 1, out); INTOFF; if (str) { memcpy(out, str, savelen); STADJUST(savelen, out); ckfree(str); str = NULL; } if (ostr) { ckfree(ostr); ostr = NULL; } here = saveheredoclist; if (here != NULL) { while (here->next != NULL) here = here->next; here->next = heredoclist; heredoclist = saveheredoclist; } handler = savehandler; INTON; if (quoted) USTPUTC(CTLBACKQ | CTLQUOTE, out); else USTPUTC(CTLBACKQ, out); return out; } /* * Called to parse a backslash escape sequence inside $'...'. * The backslash has already been read. */ static char * readcstyleesc(char *out) { int c, v, i, n; c = pgetc(); switch (c) { case '\0': synerror("Unterminated quoted string"); case '\n': plinno++; if (doprompt) setprompt(2); else setprompt(0); return out; case '\\': case '\'': case '"': v = c; break; case 'a': v = '\a'; break; case 'b': v = '\b'; break; case 'e': v = '\033'; break; case 'f': v = '\f'; break; case 'n': v = '\n'; break; case 'r': v = '\r'; break; case 't': v = '\t'; break; case 'v': v = '\v'; break; case 'x': v = 0; for (;;) { c = pgetc(); if (c >= '0' && c <= '9') v = (v << 4) + c - '0'; else if (c >= 'A' && c <= 'F') v = (v << 4) + c - 'A' + 10; else if (c >= 'a' && c <= 'f') v = (v << 4) + c - 'a' + 10; else break; } pungetc(); break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': v = c - '0'; c = pgetc(); if (c >= '0' && c <= '7') { v <<= 3; v += c - '0'; c = pgetc(); if (c >= '0' && c <= '7') { v <<= 3; v += c - '0'; } else pungetc(); } else pungetc(); break; case 'c': c = pgetc(); if (c < 0x3f || c > 0x7a || c == 0x60) synerror("Bad escape sequence"); if (c == '\\' && pgetc() != '\\') synerror("Bad escape sequence"); if (c == '?') v = 127; else v = c & 0x1f; break; case 'u': case 'U': n = c == 'U' ? 8 : 4; v = 0; for (i = 0; i < n; i++) { c = pgetc(); if (c >= '0' && c <= '9') v = (v << 4) + c - '0'; else if (c >= 'A' && c <= 'F') v = (v << 4) + c - 'A' + 10; else if (c >= 'a' && c <= 'f') v = (v << 4) + c - 'a' + 10; else synerror("Bad escape sequence"); } if (v == 0 || (v >= 0xd800 && v <= 0xdfff)) synerror("Bad escape sequence"); /* We really need iconv here. */ if (initial_localeisutf8 && v > 127) { CHECKSTRSPACE(4, out); /* * We cannot use wctomb() as the locale may have * changed. */ if (v <= 0x7ff) { USTPUTC(0xc0 | v >> 6, out); USTPUTC(0x80 | (v & 0x3f), out); return out; } else if (v <= 0xffff) { USTPUTC(0xe0 | v >> 12, out); USTPUTC(0x80 | ((v >> 6) & 0x3f), out); USTPUTC(0x80 | (v & 0x3f), out); return out; } else if (v <= 0x10ffff) { USTPUTC(0xf0 | v >> 18, out); USTPUTC(0x80 | ((v >> 12) & 0x3f), out); USTPUTC(0x80 | ((v >> 6) & 0x3f), out); USTPUTC(0x80 | (v & 0x3f), out); return out; } } if (v > 127) v = '?'; break; default: synerror("Bad escape sequence"); } v = (char)v; /* * We can't handle NUL bytes. * POSIX says we should skip till the closing quote. */ if (v == '\0') { while ((c = pgetc()) != '\'') { if (c == '\\') c = pgetc(); if (c == PEOF) synerror("Unterminated quoted string"); if (c == '\n') { plinno++; if (doprompt) setprompt(2); else setprompt(0); } } pungetc(); return out; } if (SQSYNTAX[v] == CCTL) USTPUTC(CTLESC, out); USTPUTC(v, out); return out; } /* * 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 * word which marks the end of the document and striptabs is true if * leading tabs should be stripped from the document. The argument firstc * is the first character of the input token or document. * * Because C does not have internal subroutines, I have simulated them * using goto's to implement the subroutine linkage. The following macros * will run code that appears at the end of readtoken1. */ #define PARSEREDIR() {goto parseredir; parseredir_return:;} #define PARSESUB() {goto parsesub; parsesub_return:;} #define PARSEARITH() {goto parsearith; parsearith_return:;} static int readtoken1(int firstc, char const *initialsyntax, const char *eofmark, int striptabs) { int c = firstc; char *out; int len; struct nodelist *bqlist; int quotef; int newvarnest; int level; int synentry; struct tokenstate state_static[MAXNEST_static]; int maxnest = MAXNEST_static; struct tokenstate *state = state_static; int sqiscstyle = 0; startlinno = plinno; quotef = 0; bqlist = NULL; newvarnest = 0; level = 0; state[level].syntax = initialsyntax; state[level].parenlevel = 0; state[level].category = TSTATE_TOP; STARTSTACKSTR(out); loop: { /* for each line, until end of word */ if (eofmark) /* set c to PEOF if at end of here document */ c = checkend(c, eofmark, striptabs); for (;;) { /* until end of line or end of word */ CHECKSTRSPACE(4, out); /* permit 4 calls to USTPUTC */ synentry = state[level].syntax[c]; switch(synentry) { case CNL: /* '\n' */ if (state[level].syntax == BASESYNTAX) goto endword; /* exit outer loop */ USTPUTC(c, out); plinno++; if (doprompt) setprompt(2); else setprompt(0); c = pgetc(); goto loop; /* continue outer loop */ case CSBACK: if (sqiscstyle) { out = readcstyleesc(out); break; } /* FALLTHROUGH */ case CWORD: USTPUTC(c, out); break; case CCTL: if (eofmark == NULL || initialsyntax != SQSYNTAX) USTPUTC(CTLESC, out); USTPUTC(c, out); break; case CBACK: /* backslash */ c = pgetc(); if (c == PEOF) { USTPUTC('\\', out); pungetc(); } else if (c == '\n') { plinno++; if (doprompt) setprompt(2); else setprompt(0); } else { if (state[level].syntax == DQSYNTAX && c != '\\' && c != '`' && c != '$' && (c != '"' || (eofmark != NULL && newvarnest == 0)) && (c != '}' || state[level].category != TSTATE_VAR_OLD)) USTPUTC('\\', out); if ((eofmark == NULL || newvarnest > 0) && state[level].syntax == BASESYNTAX) USTPUTC(CTLQUOTEMARK, out); if (SQSYNTAX[c] == CCTL) USTPUTC(CTLESC, out); USTPUTC(c, out); if ((eofmark == NULL || newvarnest > 0) && state[level].syntax == BASESYNTAX && state[level].category == TSTATE_VAR_OLD) USTPUTC(CTLQUOTEEND, out); quotef++; } break; case CSQUOTE: USTPUTC(CTLQUOTEMARK, out); state[level].syntax = SQSYNTAX; sqiscstyle = 0; break; case CDQUOTE: USTPUTC(CTLQUOTEMARK, out); state[level].syntax = DQSYNTAX; break; case CENDQUOTE: if (eofmark != NULL && newvarnest == 0) USTPUTC(c, out); else { if (state[level].category == TSTATE_VAR_OLD) USTPUTC(CTLQUOTEEND, out); state[level].syntax = BASESYNTAX; quotef++; } break; case CVAR: /* '$' */ PARSESUB(); /* parse substitution */ break; case CENDVAR: /* '}' */ if (level > 0 && ((state[level].category == TSTATE_VAR_OLD && state[level].syntax == state[level - 1].syntax) || (state[level].category == TSTATE_VAR_NEW && state[level].syntax == BASESYNTAX))) { if (state[level].category == TSTATE_VAR_NEW) newvarnest--; level--; USTPUTC(CTLENDVAR, out); } else { USTPUTC(c, out); } break; case CLP: /* '(' in arithmetic */ state[level].parenlevel++; USTPUTC(c, out); break; case CRP: /* ')' in arithmetic */ if (state[level].parenlevel > 0) { USTPUTC(c, out); --state[level].parenlevel; } else { if (pgetc() == ')') { if (level > 0 && state[level].category == TSTATE_ARITH) { level--; USTPUTC(CTLENDARI, out); } else USTPUTC(')', out); } else { /* * unbalanced parens * (don't 2nd guess - no error) */ pungetc(); USTPUTC(')', out); } } break; case CBQUOTE: /* '`' */ out = parsebackq(out, &bqlist, 1, state[level].syntax == DQSYNTAX && (eofmark == NULL || newvarnest > 0), state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX); break; case CEOF: goto endword; /* exit outer loop */ case CIGN: break; default: if (level == 0) goto endword; /* exit outer loop */ USTPUTC(c, out); } c = pgetc_macro(); } } endword: if (state[level].syntax == ARISYNTAX) synerror("Missing '))'"); if (state[level].syntax != BASESYNTAX && eofmark == NULL) synerror("Unterminated quoted string"); if (state[level].category == TSTATE_VAR_OLD || state[level].category == TSTATE_VAR_NEW) { startlinno = plinno; synerror("Missing '}'"); } if (state != state_static) parser_temp_free_upto(state); USTPUTC('\0', out); len = out - stackblock(); out = stackblock(); if (eofmark == NULL) { if ((c == '>' || c == '<') && quotef == 0 && len <= 2 && (*out == '\0' || is_digit(*out))) { PARSEREDIR(); return lasttoken = TREDIR; } else { pungetc(); } } quoteflag = quotef; backquotelist = bqlist; grabstackblock(len); wordtext = out; return lasttoken = TWORD; /* end of readtoken routine */ /* * Parse a redirection operator. The variable "out" points to a string * specifying the fd to be redirected. The variable "c" contains the * first character of the redirection operator. */ parseredir: { char fd = *out; union node *np; np = (union node *)stalloc(sizeof (struct nfile)); if (c == '>') { np->nfile.fd = 1; c = pgetc(); if (c == '>') np->type = NAPPEND; else if (c == '&') np->type = NTOFD; else if (c == '|') np->type = NCLOBBER; else { np->type = NTO; pungetc(); } } else { /* c == '<' */ np->nfile.fd = 0; c = pgetc(); if (c == '<') { if (sizeof (struct nfile) != sizeof (struct nhere)) { np = (union node *)stalloc(sizeof (struct nhere)); np->nfile.fd = 0; } np->type = NHERE; heredoc = (struct heredoc *)stalloc(sizeof (struct heredoc)); heredoc->here = np; if ((c = pgetc()) == '-') { heredoc->striptabs = 1; } else { heredoc->striptabs = 0; pungetc(); } } else if (c == '&') np->type = NFROMFD; else if (c == '>') np->type = NFROMTO; else { np->type = NFROM; pungetc(); } } if (fd != '\0') np->nfile.fd = digit_val(fd); redirnode = np; goto parseredir_return; } /* * Parse a substitution. At this point, we have read the dollar sign * and nothing else. */ parsesub: { char buf[10]; int subtype; int typeloc; int flags; char *p; static const char types[] = "}-+?="; int bracketed_name = 0; /* used to handle ${[0-9]*} variables */ int linno; int length; int c1; c = pgetc(); if (c == '(') { /* $(command) or $((arith)) */ if (pgetc() == '(') { PARSEARITH(); } else { pungetc(); out = parsebackq(out, &bqlist, 0, state[level].syntax == DQSYNTAX && (eofmark == NULL || newvarnest > 0), state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX); } } else if (c == '{' || is_name(c) || is_special(c)) { USTPUTC(CTLVAR, out); typeloc = out - stackblock(); USTPUTC(VSNORMAL, out); subtype = VSNORMAL; flags = 0; if (c == '{') { bracketed_name = 1; c = pgetc(); subtype = 0; } varname: if (!is_eof(c) && is_name(c)) { length = 0; do { STPUTC(c, out); c = pgetc(); length++; } while (!is_eof(c) && is_in_name(c)); if (length == 6 && strncmp(out - length, "LINENO", length) == 0) { /* Replace the variable name with the * current line number. */ linno = plinno; if (funclinno != 0) linno -= funclinno - 1; snprintf(buf, sizeof(buf), "%d", linno); STADJUST(-6, out); STPUTS(buf, out); flags |= VSLINENO; } } else if (is_digit(c)) { if (bracketed_name) { do { STPUTC(c, out); c = pgetc(); } while (is_digit(c)); } else { STPUTC(c, out); c = pgetc(); } } else if (is_special(c)) { c1 = c; c = pgetc(); if (subtype == 0 && c1 == '#') { subtype = VSLENGTH; if (strchr(types, c) == NULL && c != ':' && c != '#' && c != '%') goto varname; c1 = c; c = pgetc(); if (c1 != '}' && c == '}') { pungetc(); c = c1; goto varname; } pungetc(); c = c1; c1 = '#'; subtype = 0; } USTPUTC(c1, out); } else { subtype = VSERROR; if (c == '}') pungetc(); else if (c == '\n' || c == PEOF) synerror("Unexpected end of line in substitution"); else USTPUTC(c, out); } if (subtype == 0) { switch (c) { case ':': flags |= VSNUL; c = pgetc(); /*FALLTHROUGH*/ default: p = strchr(types, c); if (p == NULL) { if (c == '\n' || c == PEOF) synerror("Unexpected end of line in substitution"); if (flags == VSNUL) STPUTC(':', out); STPUTC(c, out); subtype = VSERROR; } else subtype = p - types + VSNORMAL; break; case '%': case '#': { int cc = c; subtype = c == '#' ? VSTRIMLEFT : VSTRIMRIGHT; c = pgetc(); if (c == cc) subtype++; else pungetc(); break; } } } else if (subtype != VSERROR) { if (subtype == VSLENGTH && c != '}') subtype = VSERROR; pungetc(); } STPUTC('=', out); if (state[level].syntax == DQSYNTAX || state[level].syntax == ARISYNTAX) flags |= VSQUOTE; *(stackblock() + typeloc) = subtype | flags; if (subtype != VSNORMAL) { if (level + 1 >= maxnest) { maxnest *= 2; if (state == state_static) { state = parser_temp_alloc( maxnest * sizeof(*state)); memcpy(state, state_static, MAXNEST_static * sizeof(*state)); } else state = parser_temp_realloc(state, maxnest * sizeof(*state)); } level++; state[level].parenlevel = 0; if (subtype == VSMINUS || subtype == VSPLUS || subtype == VSQUESTION || subtype == VSASSIGN) { /* * For operators that were in the Bourne shell, * inherit the double-quote state. */ state[level].syntax = state[level - 1].syntax; state[level].category = TSTATE_VAR_OLD; } else { /* * The other operators take a pattern, * so go to BASESYNTAX. * Also, ' and " are now special, even * in here documents. */ state[level].syntax = BASESYNTAX; state[level].category = TSTATE_VAR_NEW; newvarnest++; } } } else if (c == '\'' && state[level].syntax == BASESYNTAX) { /* $'cstylequotes' */ USTPUTC(CTLQUOTEMARK, out); state[level].syntax = SQSYNTAX; sqiscstyle = 1; } else { USTPUTC('$', out); pungetc(); } goto parsesub_return; } /* * Parse an arithmetic expansion (indicate start of one and set state) */ parsearith: { if (level + 1 >= maxnest) { maxnest *= 2; if (state == state_static) { state = parser_temp_alloc( maxnest * sizeof(*state)); memcpy(state, state_static, MAXNEST_static * sizeof(*state)); } else state = parser_temp_realloc(state, maxnest * sizeof(*state)); } level++; state[level].syntax = ARISYNTAX; state[level].parenlevel = 0; state[level].category = TSTATE_ARITH; USTPUTC(CTLARI, out); if (state[level - 1].syntax == DQSYNTAX) USTPUTC('"',out); else USTPUTC(' ',out); goto parsearith_return; } } /* 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 (BASESYNTAX[(int)c] == CCTL) return 0; } return 1; } /* * Return true if the argument is a legal variable name (a letter or * underscore followed by zero or more letters, underscores, and digits). */ int goodname(const char *name) { const char *p; p = name; if (! is_name(*p)) return 0; while (*++p) { if (! is_in_name(*p)) return 0; } return 1; } int isassignment(const char *p) { if (!is_name(*p)) return 0; p++; for (;;) { if (*p == '=') return 1; else if (!is_in_name(*p)) return 0; p++; } } static void consumetoken(int token) { if (readtoken() != token) synexpect(token); } /* * 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 synexpect(int token) { char msg[64]; if (token >= 0) { fmtstr(msg, 64, "%s unexpected (expecting %s)", tokname[lasttoken], tokname[token]); } else { fmtstr(msg, 64, "%s unexpected", tokname[lasttoken]); } synerror(msg); } static void synerror(const char *msg) { if (commandname) outfmt(out2, "%s: %d: ", commandname, startlinno); outfmt(out2, "Syntax error: %s\n", msg); error((char *)NULL); } static void setprompt(int which) { whichprompt = which; #ifndef NO_HISTORY if (!el) #endif { out2str(getprompt(NULL)); flushout(out2); } } /* * called by editline -- any expansions to the prompt * should be added here. */ char * getprompt(void *unused __unused) { static char ps[PROMPTLEN]; const char *fmt; const char *pwd; int i, trim; static char internal_error[] = "??"; /* * Select prompt format. */ switch (whichprompt) { case 0: fmt = nullstr; break; case 1: fmt = ps1val(); break; case 2: fmt = ps2val(); break; default: return internal_error; } /* * Format prompt string. */ for (i = 0; (i < 127) && (*fmt != '\0'); i++, fmt++) if (*fmt == '\\') switch (*++fmt) { /* * Hostname. * * \h specifies just the local hostname, * \H specifies fully-qualified hostname. */ case 'h': case 'H': ps[i] = '\0'; gethostname(&ps[i], PROMPTLEN - i); /* Skip to end of hostname. */ trim = (*fmt == 'h') ? '.' : '\0'; while ((ps[i+1] != '\0') && (ps[i+1] != trim)) i++; break; /* * Working directory. * * \W specifies just the final component, * \w specifies the entire path. */ case 'W': case 'w': pwd = lookupvar("PWD"); if (pwd == NULL) pwd = "?"; if (*fmt == 'W' && *pwd == '/' && pwd[1] != '\0') strlcpy(&ps[i], strrchr(pwd, '/') + 1, PROMPTLEN - i); else strlcpy(&ps[i], pwd, PROMPTLEN - i); /* Skip to end of path. */ while (ps[i + 1] != '\0') i++; break; /* * Superuser status. * * '$' for normal users, '#' for root. */ case '$': ps[i] = (geteuid() != 0) ? '$' : '#'; break; /* * A literal \. */ case '\\': ps[i] = '\\'; break; /* * Emit unrecognized formats verbatim. */ default: ps[i++] = '\\'; ps[i] = *fmt; break; } else ps[i] = *fmt; ps[i] = '\0'; return (ps); } const char * expandstr(const char *ps) { union node n; struct jmploc jmploc; struct jmploc *const savehandler = handler; const int saveprompt = doprompt; struct parsefile *const savetopfile = getcurrentfile(); struct parser_temp *const saveparser_temp = parser_temp; const char *result = NULL; if (!setjmp(jmploc.loc)) { handler = &jmploc; parser_temp = NULL; setinputstring(ps, 1); doprompt = 0; readtoken1(pgetc(), DQSYNTAX, "", 0); if (backquotelist != NULL) error("Command substitution not allowed here"); n.narg.type = NARG; n.narg.next = NULL; n.narg.text = wordtext; n.narg.backquote = backquotelist; expandarg(&n, NULL, 0); result = stackblock(); INTOFF; } handler = savehandler; doprompt = saveprompt; popfilesupto(savetopfile); if (parser_temp != saveparser_temp) { parser_temp_free_all(); parser_temp = saveparser_temp; } if (result != NULL) { INTON; } else if (exception == EXINT) raise(SIGINT); return result; }