| /* $OpenBSD: eval.c,v 1.40 2013/09/14 20:09:30 millert Exp $ */ | |
| /*- | |
| * Copyright (c) 2003, 2004, 2005, 2006, 2007, 2008, 2009, 2010, | |
| * 2011, 2012, 2013, 2014, 2015, 2016, 2017, 2018, | |
| * 2019, 2020 | |
| * mirabilos <m@mirbsd.org> | |
| * | |
| * Provided that these terms and disclaimer and all copyright notices | |
| * are retained or reproduced in an accompanying document, permission | |
| * is granted to deal in this work without restriction, including un- | |
| * limited rights to use, publicly perform, distribute, sell, modify, | |
| * merge, give away, or sublicence. | |
| * | |
| * This work is provided "AS IS" and WITHOUT WARRANTY of any kind, to | |
| * the utmost extent permitted by applicable law, neither express nor | |
| * implied; without malicious intent or gross negligence. In no event | |
| * may a licensor, author or contributor be held liable for indirect, | |
| * direct, other damage, loss, or other issues arising in any way out | |
| * of dealing in the work, even if advised of the possibility of such | |
| * damage or existence of a defect, except proven that it results out | |
| * of said person's immediate fault when using the work as intended. | |
| */ | |
| #include "sh.h" | |
| __RCSID("$MirOS: src/bin/mksh/eval.c,v 1.231 2020/05/05 21:34:27 tg Exp $"); | |
| /* | |
| * string expansion | |
| * | |
| * first pass: quoting, IFS separation, ~, ${}, $() and $(()) substitution. | |
| * second pass: alternation ({,}), filename expansion (*?[]). | |
| */ | |
| /* expansion generator state */ | |
| typedef struct { | |
| /* not including an "int type;" member, see expand() */ | |
| /* string */ | |
| const char *str; | |
| /* source */ | |
| union { | |
| /* string[] */ | |
| const char **strv; | |
| /* file */ | |
| struct shf *shf; | |
| } u; | |
| /* variable in ${var...} */ | |
| struct tbl *var; | |
| /* split "$@" / call waitlast in $() */ | |
| bool split; | |
| } Expand; | |
| #define XBASE 0 /* scanning original string */ | |
| #define XARGSEP 1 /* ifs0 between "$*" */ | |
| #define XARG 2 /* expanding $*, $@ */ | |
| #define XCOM 3 /* expanding $() */ | |
| #define XNULLSUB 4 /* "$@" when $# is 0, so don't generate word */ | |
| #define XSUB 5 /* expanding ${} string */ | |
| #define XSUBMID 6 /* middle of expanding ${}; must be XSUB+1 */ | |
| #define XSUBPAT 7 /* expanding [[ x = ${} ]] string */ | |
| #define XSUBPATMID 8 /* middle, must be XSUBPAT+1 */ | |
| #define isXSUB(t) ((t) == XSUB || (t) == XSUBPAT) | |
| /* States used for field splitting */ | |
| #define IFS_WORD 0 /* word has chars (or quotes except "$@") */ | |
| #define IFS_WS 1 /* have seen IFS white-space */ | |
| #define IFS_NWS 2 /* have seen IFS non-white-space */ | |
| #define IFS_IWS 3 /* beginning of word, ignore IFS WS */ | |
| #define IFS_QUOTE 4 /* beg.w/quote, become IFS_WORD unless "$@" */ | |
| #define STYPE_CHAR 0xFF | |
| #define STYPE_DBL 0x100 | |
| #define STYPE_AT 0x200 | |
| #define STYPE_SINGLE 0x2FF | |
| #define STYPE_MASK 0x300 | |
| static int varsub(Expand *, const char *, const char *, unsigned int *, int *); | |
| static int comsub(Expand *, const char *, int); | |
| static char *valsub(struct op *, Area *); | |
| static char *trimsub(char *, char *, int); | |
| static void glob(char *, XPtrV *, bool); | |
| static void globit(XString *, char **, char *, XPtrV *, int); | |
| static const char *maybe_expand_tilde(const char *, XString *, char **, bool); | |
| #ifndef MKSH_NOPWNAM | |
| static char *homedir(char *); | |
| #endif | |
| static void alt_expand(XPtrV *, char *, char *, char *, int); | |
| static int utflen(const char *) MKSH_A_PURE; | |
| static void utfincptr(const char *, mksh_ari_t *); | |
| /* UTFMODE functions */ | |
| static int | |
| utflen(const char *s) | |
| { | |
| size_t n; | |
| if (UTFMODE) { | |
| n = 0; | |
| while (*s) { | |
| s += utf_ptradj(s); | |
| ++n; | |
| } | |
| } else | |
| n = strlen(s); | |
| if (n > 2147483647) | |
| n = 2147483647; | |
| return ((int)n); | |
| } | |
| static void | |
| utfincptr(const char *s, mksh_ari_t *lp) | |
| { | |
| const char *cp = s; | |
| while ((*lp)--) | |
| cp += utf_ptradj(cp); | |
| *lp = cp - s; | |
| } | |
| /* compile and expand word */ | |
| char * | |
| substitute(const char *cp, int f) | |
| { | |
| struct source *s, *sold; | |
| sold = source; | |
| s = pushs(SWSTR, ATEMP); | |
| s->start = s->str = cp; | |
| source = s; | |
| if (yylex(ONEWORD) != LWORD) | |
| internal_errorf(Tbadsubst); | |
| source = sold; | |
| afree(s, ATEMP); | |
| return (evalstr(yylval.cp, f)); | |
| } | |
| /* | |
| * expand arg-list | |
| */ | |
| char ** | |
| eval(const char **ap, int f) | |
| { | |
| XPtrV w; | |
| if (*ap == NULL) { | |
| union mksh_ccphack vap; | |
| vap.ro = ap; | |
| return (vap.rw); | |
| } | |
| XPinit(w, 32); | |
| /* space for shell name */ | |
| XPput(w, NULL); | |
| while (*ap != NULL) | |
| expand(*ap++, &w, f); | |
| XPput(w, NULL); | |
| return ((char **)XPclose(w) + 1); | |
| } | |
| /* | |
| * expand string | |
| */ | |
| char * | |
| evalstr(const char *cp, int f) | |
| { | |
| XPtrV w; | |
| char *dp = null; | |
| XPinit(w, 1); | |
| expand(cp, &w, f); | |
| if (XPsize(w)) | |
| dp = *XPptrv(w); | |
| XPfree(w); | |
| return (dp); | |
| } | |
| /* | |
| * expand string - return only one component | |
| * used from iosetup to expand redirection files | |
| */ | |
| char * | |
| evalonestr(const char *cp, int f) | |
| { | |
| XPtrV w; | |
| char *rv; | |
| XPinit(w, 1); | |
| expand(cp, &w, f); | |
| switch (XPsize(w)) { | |
| case 0: | |
| rv = null; | |
| break; | |
| case 1: | |
| rv = (char *) *XPptrv(w); | |
| break; | |
| default: | |
| rv = evalstr(cp, f & ~DOGLOB); | |
| break; | |
| } | |
| XPfree(w); | |
| return (rv); | |
| } | |
| /* for nested substitution: ${var:=$var2} */ | |
| typedef struct SubType { | |
| struct tbl *var; /* variable for ${var..} */ | |
| struct SubType *prev; /* old type */ | |
| struct SubType *next; /* poped type (to avoid re-allocating) */ | |
| size_t base; /* start position of expanded word */ | |
| unsigned short stype; /* [=+-?%#] action after expanded word */ | |
| short f; /* saved value of f (DOPAT, etc) */ | |
| uint8_t quotep; /* saved value of quote (for ${..[%#]..}) */ | |
| uint8_t quotew; /* saved value of quote (for ${..[+-=]..}) */ | |
| } SubType; | |
| void | |
| expand( | |
| /* input word */ | |
| const char *ccp, | |
| /* output words */ | |
| XPtrV *wp, | |
| /* DO* flags */ | |
| int f) | |
| { | |
| int c = 0; | |
| /* expansion type */ | |
| int type; | |
| /* quoted */ | |
| int quote = 0; | |
| /* destination string and live pointer */ | |
| XString ds; | |
| char *dp; | |
| /* source */ | |
| const char *sp; | |
| /* second pass flags */ | |
| int fdo; | |
| /* have word */ | |
| int word; | |
| /* field splitting of parameter/command substitution */ | |
| int doblank; | |
| /* expansion variables */ | |
| Expand x = { | |
| NULL, { NULL }, NULL, 0 | |
| }; | |
| SubType st_head, *st; | |
| /* record number of trailing newlines in COMSUB */ | |
| int newlines = 0; | |
| bool saw_eq, make_magic; | |
| unsigned int tilde_ok; | |
| size_t len; | |
| char *cp; | |
| if (ccp == NULL) | |
| internal_errorf("expand(NULL)"); | |
| /* for alias, readonly, set, typeset commands */ | |
| if ((f & DOVACHECK) && is_wdvarassign(ccp)) { | |
| f &= ~(DOVACHECK | DOBLANK | DOGLOB | DOTILDE); | |
| f |= DOASNTILDE | DOSCALAR; | |
| } | |
| if (Flag(FNOGLOB)) | |
| f &= ~DOGLOB; | |
| if (Flag(FMARKDIRS)) | |
| f |= DOMARKDIRS; | |
| if (Flag(FBRACEEXPAND) && (f & DOGLOB)) | |
| f |= DOBRACE; | |
| /* init destination string */ | |
| Xinit(ds, dp, 128, ATEMP); | |
| type = XBASE; | |
| sp = ccp; | |
| fdo = 0; | |
| saw_eq = false; | |
| /* must be 1/0 */ | |
| tilde_ok = (f & (DOTILDE | DOASNTILDE)) ? 1 : 0; | |
| doblank = 0; | |
| make_magic = false; | |
| word = (f&DOBLANK) ? IFS_WS : IFS_WORD; | |
| /* clang doesn't know OSUBST comes before CSUBST */ | |
| memset(&st_head, 0, sizeof(st_head)); | |
| st = &st_head; | |
| while (/* CONSTCOND */ 1) { | |
| Xcheck(ds, dp); | |
| switch (type) { | |
| case XBASE: | |
| /* original prefixed string */ | |
| c = ord(*sp++); | |
| switch (c) { | |
| case EOS: | |
| c = 0; | |
| break; | |
| case CHAR: | |
| c = ord(*sp++); | |
| break; | |
| case QCHAR: | |
| /* temporary quote */ | |
| quote |= 2; | |
| c = ord(*sp++); | |
| break; | |
| case OQUOTE: | |
| if (word != IFS_WORD) | |
| word = IFS_QUOTE; | |
| tilde_ok = 0; | |
| quote = 1; | |
| continue; | |
| case CQUOTE: | |
| if (word == IFS_QUOTE) | |
| word = IFS_WORD; | |
| quote = st->quotew; | |
| continue; | |
| case COMASUB: | |
| case COMSUB: | |
| case FUNASUB: | |
| case FUNSUB: | |
| case VALSUB: | |
| tilde_ok = 0; | |
| if (f & DONTRUNCOMMAND) { | |
| word = IFS_WORD; | |
| *dp++ = '$'; | |
| switch (c) { | |
| case COMASUB: | |
| case COMSUB: | |
| *dp++ = '('; | |
| c = ORD(')'); | |
| break; | |
| case FUNASUB: | |
| case FUNSUB: | |
| case VALSUB: | |
| *dp++ = '{'; | |
| *dp++ = c == VALSUB ? '|' : ' '; | |
| c = ORD('}'); | |
| break; | |
| } | |
| while (*sp != '\0') { | |
| Xcheck(ds, dp); | |
| *dp++ = *sp++; | |
| } | |
| if ((unsigned int)c == ORD(/*{*/'}')) | |
| *dp++ = ';'; | |
| *dp++ = c; | |
| } else { | |
| type = comsub(&x, sp, c); | |
| if (type != XBASE && (f & DOBLANK)) | |
| doblank++; | |
| sp = strnul(sp) + 1; | |
| newlines = 0; | |
| } | |
| continue; | |
| case EXPRSUB: | |
| tilde_ok = 0; | |
| if (f & DONTRUNCOMMAND) { | |
| word = IFS_WORD; | |
| *dp++ = '$'; *dp++ = '('; *dp++ = '('; | |
| while (*sp != '\0') { | |
| Xcheck(ds, dp); | |
| *dp++ = *sp++; | |
| } | |
| *dp++ = ')'; *dp++ = ')'; | |
| } else { | |
| struct tbl v; | |
| v.flag = DEFINED|ISSET|INTEGER; | |
| /* not default */ | |
| v.type = 10; | |
| v.name[0] = '\0'; | |
| v_evaluate(&v, substitute(sp, 0), | |
| KSH_UNWIND_ERROR, true); | |
| sp = strnul(sp) + 1; | |
| x.str = str_val(&v); | |
| type = XSUB; | |
| if (f & DOBLANK) | |
| doblank++; | |
| } | |
| continue; | |
| case OSUBST: { | |
| /* ${{#}var{:}[=+-?#%]word} */ | |
| /*- | |
| * format is: | |
| * OSUBST [{x] plain-variable-part \0 | |
| * compiled-word-part CSUBST [}x] | |
| * This is where all syntax checking gets done... | |
| */ | |
| /* skip the { or x (}) */ | |
| const char *varname = ++sp; | |
| unsigned int stype; | |
| int slen = 0; | |
| /* skip variable */ | |
| sp = cstrchr(sp, '\0') + 1; | |
| type = varsub(&x, varname, sp, &stype, &slen); | |
| if (type < 0) { | |
| char *beg, *end, *str; | |
| unwind_substsyn: | |
| /* restore sp */ | |
| sp = varname - 2; | |
| beg = wdcopy(sp, ATEMP); | |
| end = (wdscan(cstrchr(sp, '\0') + 1, | |
| CSUBST) - sp) + beg; | |
| /* ({) the } or x is already skipped */ | |
| if (end < wdscan(beg, EOS)) | |
| *end = EOS; | |
| str = snptreef(NULL, 64, Tf_S, beg); | |
| afree(beg, ATEMP); | |
| errorf(Tf_sD_s, str, Tbadsubst); | |
| } | |
| if (f & DOBLANK) | |
| doblank++; | |
| tilde_ok = 0; | |
| if (word == IFS_QUOTE && type != XNULLSUB) | |
| word = IFS_WORD; | |
| if (type == XBASE) { | |
| /* expand? */ | |
| if (!st->next) { | |
| SubType *newst; | |
| newst = alloc(sizeof(SubType), ATEMP); | |
| newst->next = NULL; | |
| newst->prev = st; | |
| st->next = newst; | |
| } | |
| st = st->next; | |
| st->stype = stype; | |
| st->base = Xsavepos(ds, dp); | |
| st->f = f; | |
| if (x.var == vtemp) { | |
| st->var = tempvar(vtemp->name); | |
| st->var->flag &= ~INTEGER; | |
| /* can't fail here */ | |
| setstr(st->var, | |
| str_val(x.var), | |
| KSH_RETURN_ERROR | 0x4); | |
| } else | |
| st->var = x.var; | |
| st->quotew = st->quotep = quote; | |
| /* skip qualifier(s) */ | |
| if (stype) | |
| sp += slen; | |
| switch (stype & STYPE_SINGLE) { | |
| case ORD('#') | STYPE_AT: | |
| case ORD('Q') | STYPE_AT: | |
| break; | |
| case ORD('0'): { | |
| char *beg, *mid, *end, *stg; | |
| mksh_ari_t from = 0, num = -1, flen, finc = 0; | |
| beg = wdcopy(sp, ATEMP); | |
| mid = beg + (wdscan(sp, ADELIM) - sp); | |
| stg = beg + (wdscan(sp, CSUBST) - sp); | |
| mid[-2] = EOS; | |
| if (ord(mid[-1]) == ORD(/*{*/ '}')) { | |
| sp += mid - beg - 1; | |
| end = NULL; | |
| } else { | |
| end = mid + | |
| (wdscan(mid, ADELIM) - mid); | |
| if (ord(end[-1]) != ORD(/*{*/ '}')) | |
| /* more than max delimiters */ | |
| goto unwind_substsyn; | |
| end[-2] = EOS; | |
| sp += end - beg - 1; | |
| } | |
| evaluate(substitute(stg = wdstrip(beg, 0), 0), | |
| &from, KSH_UNWIND_ERROR, true); | |
| afree(stg, ATEMP); | |
| if (end) { | |
| evaluate(substitute(stg = wdstrip(mid, 0), 0), | |
| &num, KSH_UNWIND_ERROR, true); | |
| afree(stg, ATEMP); | |
| } | |
| afree(beg, ATEMP); | |
| beg = str_val(st->var); | |
| flen = utflen(beg); | |
| if (from < 0) { | |
| if (-from < flen) | |
| finc = flen + from; | |
| } else | |
| finc = from < flen ? from : flen; | |
| if (UTFMODE) | |
| utfincptr(beg, &finc); | |
| beg += finc; | |
| flen = utflen(beg); | |
| if (num < 0 || num > flen) | |
| num = flen; | |
| if (UTFMODE) | |
| utfincptr(beg, &num); | |
| strndupx(x.str, beg, num, ATEMP); | |
| goto do_CSUBST; | |
| } | |
| case ORD('/') | STYPE_AT: | |
| case ORD('/'): { | |
| char *s, *p, *d, *sbeg; | |
| char *pat = NULL, *rrep; | |
| char fpat = 0, *tpat1, *tpat2; | |
| char *ws, *wpat, *wrep, tch; | |
| size_t rreplen; | |
| s = ws = wdcopy(sp, ATEMP); | |
| p = s + (wdscan(sp, ADELIM) - sp); | |
| d = s + (wdscan(sp, CSUBST) - sp); | |
| p[-2] = EOS; | |
| if (ord(p[-1]) == ORD(/*{*/ '}')) | |
| d = NULL; | |
| else | |
| d[-2] = EOS; | |
| sp += (d ? d : p) - s - 1; | |
| if (!(stype & STYPE_MASK) && | |
| s[0] == CHAR && | |
| ctype(s[1], C_SUB2)) | |
| fpat = s[1]; | |
| wpat = s + (fpat ? 2 : 0); | |
| if (!(wrep = d ? p : NULL)) { | |
| rrep = null; | |
| rreplen = 0; | |
| } else if (!(stype & STYPE_AT)) { | |
| rrep = evalstr(wrep, | |
| DOTILDE | DOSCALAR); | |
| rreplen = strlen(rrep); | |
| } else { | |
| rrep = NULL; | |
| /* shut up GCC */ | |
| rreplen = 0; | |
| } | |
| /* prepare string on which to work */ | |
| strdupx(s, str_val(st->var), ATEMP); | |
| sbeg = s; | |
| again_search: | |
| pat = evalstr(wpat, | |
| DOTILDE | DOSCALAR | DOPAT); | |
| /* check for special cases */ | |
| if (!*pat && !fpat) { | |
| /* | |
| * empty unanchored | |
| * pattern => reject | |
| */ | |
| goto no_repl; | |
| } | |
| if ((stype & STYPE_MASK) && | |
| gmatchx(null, pat, false)) { | |
| /* | |
| * pattern matches empty | |
| * string => don't loop | |
| */ | |
| stype &= ~STYPE_MASK; | |
| } | |
| /* first see if we have any match at all */ | |
| if (ord(fpat) == ORD('#')) { | |
| /* anchor at the beginning */ | |
| tpat1 = shf_smprintf("%s%c*", pat, MAGIC); | |
| tpat2 = tpat1; | |
| } else if (ord(fpat) == ORD('%')) { | |
| /* anchor at the end */ | |
| tpat1 = shf_smprintf("%c*%s", MAGIC, pat); | |
| tpat2 = pat; | |
| } else { | |
| /* float */ | |
| tpat1 = shf_smprintf("%c*%s%c*", MAGIC, pat, MAGIC); | |
| tpat2 = tpat1 + 2; | |
| } | |
| again_repl: | |
| /* | |
| * this would not be necessary if gmatchx would return | |
| * the start and end values of a match found, like re* | |
| */ | |
| if (!gmatchx(sbeg, tpat1, false)) | |
| goto end_repl; | |
| d = strnul(s); | |
| /* now anchor the beginning of the match */ | |
| if (ord(fpat) != ORD('#')) | |
| while (sbeg <= d) { | |
| if (gmatchx(sbeg, tpat2, false)) | |
| break; | |
| else | |
| sbeg++; | |
| } | |
| /* now anchor the end of the match */ | |
| p = d; | |
| if (ord(fpat) != ORD('%')) | |
| while (p >= sbeg) { | |
| bool gotmatch; | |
| c = ord(*p); | |
| *p = '\0'; | |
| gotmatch = tobool(gmatchx(sbeg, pat, false)); | |
| *p = c; | |
| if (gotmatch) | |
| break; | |
| p--; | |
| } | |
| /* record partial string as match */ | |
| tch = *p; | |
| *p = '\0'; | |
| record_match(sbeg); | |
| *p = tch; | |
| /* get replacement string, if necessary */ | |
| if ((stype & STYPE_AT) && | |
| rrep != null) { | |
| afree(rrep, ATEMP); | |
| /* might access match! */ | |
| rrep = evalstr(wrep, | |
| DOTILDE | DOSCALAR); | |
| rreplen = strlen(rrep); | |
| } | |
| /* | |
| * string: | |
| * |--------|---------|-------\0 | |
| * s n1 sbeg n2 p n3 d | |
| * | |
| * replacement: | |
| * |------------| | |
| * rrep rreplen | |
| */ | |
| /* move strings around and replace */ | |
| { | |
| size_t n1 = sbeg - s; | |
| size_t n2 = p - sbeg; | |
| size_t n3 = d - p; | |
| /* move part3 to the front, OR… */ | |
| if (rreplen < n2) | |
| memmove(sbeg + rreplen, | |
| p, n3 + 1); | |
| /* … adjust size, move to back */ | |
| if (rreplen > n2) { | |
| s = aresize(s, | |
| n1 + rreplen + n3 + 1, | |
| ATEMP); | |
| memmove(s + n1 + rreplen, | |
| s + n1 + n2, | |
| n3 + 1); | |
| } | |
| /* insert replacement */ | |
| if (rreplen) | |
| memcpy(s + n1, rrep, rreplen); | |
| /* continue after the place */ | |
| sbeg = s + n1 + rreplen; | |
| } | |
| if (stype & STYPE_AT) { | |
| afree(tpat1, ATEMP); | |
| afree(pat, ATEMP); | |
| goto again_search; | |
| } else if (stype & STYPE_DBL) | |
| goto again_repl; | |
| end_repl: | |
| afree(tpat1, ATEMP); | |
| x.str = s; | |
| no_repl: | |
| afree(pat, ATEMP); | |
| if (rrep != null) | |
| afree(rrep, ATEMP); | |
| afree(ws, ATEMP); | |
| goto do_CSUBST; | |
| } | |
| case ORD('#'): | |
| case ORD('%'): | |
| /* ! DOBLANK,DOBRACE */ | |
| f = (f & DONTRUNCOMMAND) | | |
| DOPAT | DOTILDE | | |
| DOTEMP | DOSCALAR; | |
| tilde_ok = 1; | |
| st->quotew = quote = 0; | |
| /* | |
| * Prepend open pattern (so | | |
| * in a trim will work as | |
| * expected) | |
| */ | |
| if (!Flag(FSH)) { | |
| *dp++ = MAGIC; | |
| *dp++ = ORD(0x80 | '@'); | |
| } | |
| break; | |
| case ORD('='): | |
| /* | |
| * Tilde expansion for string | |
| * variables in POSIX mode is | |
| * governed by Austinbug 351. | |
| * In non-POSIX mode historic | |
| * ksh behaviour (enable it!) | |
| * us followed. | |
| * Not doing tilde expansion | |
| * for integer variables is a | |
| * non-POSIX thing - makes | |
| * sense though, since ~ is | |
| * a arithmetic operator. | |
| */ | |
| if (!(x.var->flag & INTEGER)) | |
| f |= DOASNTILDE | DOTILDE; | |
| f |= DOTEMP | DOSCALAR; | |
| /* | |
| * These will be done after the | |
| * value has been assigned. | |
| */ | |
| f &= ~(DOBLANK|DOGLOB|DOBRACE); | |
| tilde_ok = 1; | |
| break; | |
| case ORD('?'): | |
| if (*sp == CSUBST) | |
| errorf("%s: parameter null or not set", | |
| st->var->name); | |
| f &= ~DOBLANK; | |
| f |= DOTEMP; | |
| /* FALLTHROUGH */ | |
| default: | |
| /* '-' '+' '?' */ | |
| if (quote) | |
| word = IFS_WORD; | |
| else if (dp == Xstring(ds, dp)) | |
| word = IFS_IWS; | |
| /* Enable tilde expansion */ | |
| tilde_ok = 1; | |
| f |= DOTILDE; | |
| } | |
| } else | |
| /* skip word */ | |
| sp += wdscan(sp, CSUBST) - sp; | |
| continue; | |
| } | |
| case CSUBST: | |
| /* only get here if expanding word */ | |
| do_CSUBST: | |
| /* ({) skip the } or x */ | |
| sp++; | |
| /* in case of ${unset:-} */ | |
| tilde_ok = 0; | |
| *dp = '\0'; | |
| quote = st->quotep; | |
| f = st->f; | |
| if (f & DOBLANK) | |
| doblank--; | |
| switch (st->stype & STYPE_SINGLE) { | |
| case ORD('#'): | |
| case ORD('%'): | |
| if (!Flag(FSH)) { | |
| /* Append end-pattern */ | |
| *dp++ = MAGIC; | |
| *dp++ = ')'; | |
| } | |
| *dp = '\0'; | |
| dp = Xrestpos(ds, dp, st->base); | |
| /* | |
| * Must use st->var since calling | |
| * global would break things | |
| * like x[i+=1]. | |
| */ | |
| x.str = trimsub(str_val(st->var), | |
| dp, st->stype); | |
| if (x.str[0] != '\0') { | |
| word = IFS_IWS; | |
| type = XSUB; | |
| } else if (quote) { | |
| word = IFS_WORD; | |
| type = XSUB; | |
| } else { | |
| if (dp == Xstring(ds, dp)) | |
| word = IFS_IWS; | |
| type = XNULLSUB; | |
| } | |
| if (f & DOBLANK) | |
| doblank++; | |
| st = st->prev; | |
| continue; | |
| case ORD('='): | |
| /* | |
| * Restore our position and substitute | |
| * the value of st->var (may not be | |
| * the assigned value in the presence | |
| * of integer/right-adj/etc attributes). | |
| */ | |
| dp = Xrestpos(ds, dp, st->base); | |
| /* | |
| * Must use st->var since calling | |
| * global would cause with things | |
| * like x[i+=1] to be evaluated twice. | |
| */ | |
| /* | |
| * Note: not exported by FEXPORT | |
| * in AT&T ksh. | |
| */ | |
| /* | |
| * XXX POSIX says readonly is only | |
| * fatal for special builtins (setstr | |
| * does readonly check). | |
| */ | |
| len = strlen(dp) + 1; | |
| setstr(st->var, | |
| debunk(alloc(len, ATEMP), | |
| dp, len), KSH_UNWIND_ERROR); | |
| x.str = str_val(st->var); | |
| type = XSUB; | |
| if (f & DOBLANK) | |
| doblank++; | |
| st = st->prev; | |
| word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS; | |
| continue; | |
| case ORD('?'): | |
| dp = Xrestpos(ds, dp, st->base); | |
| errorf(Tf_sD_s, st->var->name, | |
| debunk(dp, dp, strlen(dp) + 1)); | |
| break; | |
| case ORD('#') | STYPE_AT: | |
| x.str = shf_smprintf("%08X", | |
| (unsigned int)hash(str_val(st->var))); | |
| goto common_CSUBST; | |
| case ORD('Q') | STYPE_AT: { | |
| struct shf shf; | |
| shf_sopen(NULL, 0, SHF_WR|SHF_DYNAMIC, &shf); | |
| print_value_quoted(&shf, str_val(st->var)); | |
| x.str = shf_sclose(&shf); | |
| goto common_CSUBST; | |
| } | |
| case ORD('0'): | |
| case ORD('/') | STYPE_AT: | |
| case ORD('/'): | |
| common_CSUBST: | |
| dp = Xrestpos(ds, dp, st->base); | |
| type = XSUB; | |
| word = quote || (!*x.str && (f & DOSCALAR)) ? IFS_WORD : IFS_IWS; | |
| if (f & DOBLANK) | |
| doblank++; | |
| st = st->prev; | |
| continue; | |
| /* default: '-' '+' */ | |
| } | |
| st = st->prev; | |
| type = XBASE; | |
| continue; | |
| case OPAT: | |
| /* open pattern: *(foo|bar) */ | |
| /* Next char is the type of pattern */ | |
| make_magic = true; | |
| c = ord(*sp++) | 0x80U; | |
| break; | |
| case SPAT: | |
| /* pattern separator (|) */ | |
| make_magic = true; | |
| c = ORD('|'); | |
| break; | |
| case CPAT: | |
| /* close pattern */ | |
| make_magic = true; | |
| c = ORD(/*(*/ ')'); | |
| break; | |
| } | |
| break; | |
| case XNULLSUB: | |
| /* | |
| * Special case for "$@" (and "${foo[@]}") - no | |
| * word is generated if $# is 0 (unless there is | |
| * other stuff inside the quotes). | |
| */ | |
| type = XBASE; | |
| if (f & DOBLANK) { | |
| doblank--; | |
| if (dp == Xstring(ds, dp) && word != IFS_WORD) | |
| word = IFS_IWS; | |
| } | |
| continue; | |
| case XSUBPAT: | |
| case XSUBPATMID: | |
| XSUBPAT_beg: | |
| switch ((c = ord(*x.str++))) { | |
| case 0: | |
| goto XSUB_end; | |
| case ORD('\\'): | |
| if ((c = ord(*x.str)) == 0) | |
| /* keep backslash at EOS */ | |
| c = ORD('\\'); | |
| else | |
| ++x.str; | |
| quote |= 2; | |
| break; | |
| /* ctype(c, C_PATMO) */ | |
| case ORD('!'): | |
| case ORD('*'): | |
| case ORD('+'): | |
| case ORD('?'): | |
| case ORD('@'): | |
| if (ord(*x.str) == ORD('('/*)*/)) { | |
| ++x.str; | |
| c |= 0x80U; | |
| make_magic = true; | |
| } | |
| break; | |
| case ORD('('): | |
| c = ORD(' ') | 0x80U; | |
| /* FALLTHROUGH */ | |
| case ORD('|'): | |
| case ORD(')'): | |
| make_magic = true; | |
| break; | |
| } | |
| break; | |
| case XSUB: | |
| if (!quote && (f & DODBMAGIC)) { | |
| const char *cs = x.str; | |
| int level = 0; | |
| while ((c = *cs++)) | |
| switch (c) { | |
| case '\\': | |
| if ((c = *cs)) | |
| ++cs; | |
| break; | |
| case ORD('('): | |
| ++level; | |
| break; | |
| case ORD(')'): | |
| --level; | |
| break; | |
| } | |
| /* balanced parentheses? */ | |
| if (!level) { | |
| type = XSUBPAT; | |
| goto XSUBPAT_beg; | |
| } | |
| } | |
| /* FALLTHROUGH */ | |
| case XSUBMID: | |
| if ((c = ord(*x.str++)) == 0) { | |
| XSUB_end: | |
| type = XBASE; | |
| if (f & DOBLANK) | |
| doblank--; | |
| continue; | |
| } | |
| break; | |
| case XARGSEP: | |
| type = XARG; | |
| quote = 1; | |
| /* FALLTHROUGH */ | |
| case XARG: | |
| if ((c = ord(*x.str++)) == '\0') { | |
| /* | |
| * force null words to be created so | |
| * set -- "" 2 ""; echo "$@" will do | |
| * the right thing | |
| */ | |
| if (quote && x.split) | |
| word = IFS_WORD; | |
| if ((x.str = *x.u.strv++) == NULL) { | |
| type = XBASE; | |
| if (f & DOBLANK) | |
| doblank--; | |
| continue; | |
| } | |
| c = ord(ifs0); | |
| if ((f & DOHEREDOC)) { | |
| /* pseudo-field-split reliably */ | |
| if (c == 0) | |
| c = ORD(' '); | |
| break; | |
| } | |
| if ((f & DOSCALAR)) { | |
| /* do not field-split */ | |
| if (x.split) { | |
| c = ORD(' '); | |
| break; | |
| } | |
| if (c == 0) | |
| continue; | |
| } | |
| if (c == 0) { | |
| if (quote && !x.split) | |
| continue; | |
| if (!quote && word == IFS_WS) | |
| continue; | |
| /* this is so we don't terminate */ | |
| c = ORD(' '); | |
| /* now force-emit a word */ | |
| goto emit_word; | |
| } | |
| if (quote && x.split) { | |
| /* terminate word for "$@" */ | |
| type = XARGSEP; | |
| quote = 0; | |
| } | |
| } | |
| break; | |
| case XCOM: | |
| if (x.u.shf == NULL) { | |
| /* $(<...) failed */ | |
| subst_exstat = 1; | |
| /* fake EOF */ | |
| c = -1; | |
| } else if (newlines) { | |
| /* spit out saved NLs */ | |
| c = ORD('\n'); | |
| --newlines; | |
| } else { | |
| while ((c = shf_getc(x.u.shf)) == 0 || | |
| cinttype(c, C_NL)) { | |
| #ifdef MKSH_WITH_TEXTMODE | |
| if (c == ORD('\r')) { | |
| c = shf_getc(x.u.shf); | |
| switch (c) { | |
| case ORD('\n'): | |
| break; | |
| default: | |
| shf_ungetc(c, x.u.shf); | |
| /* FALLTHROUGH */ | |
| case -1: | |
| c = ORD('\r'); | |
| break; | |
| } | |
| } | |
| #endif | |
| if (c == ORD('\n')) | |
| /* save newlines */ | |
| newlines++; | |
| } | |
| if (newlines && c != -1) { | |
| shf_ungetc(c, x.u.shf); | |
| c = ORD('\n'); | |
| --newlines; | |
| } | |
| } | |
| if (c == -1) { | |
| newlines = 0; | |
| if (x.u.shf) | |
| shf_close(x.u.shf); | |
| if (x.split) | |
| subst_exstat = waitlast(); | |
| type = XBASE; | |
| if (f & DOBLANK) | |
| doblank--; | |
| continue; | |
| } | |
| break; | |
| } | |
| /* check for end of word or IFS separation */ | |
| if (c == 0 || (!quote && (f & DOBLANK) && doblank && | |
| !make_magic && ctype(c, C_IFS))) { | |
| /*- | |
| * How words are broken up: | |
| * | value of c | |
| * word | ws nws 0 | |
| * ----------------------------------- | |
| * IFS_WORD w/WS w/NWS w | |
| * IFS_WS -/WS -/NWS - | |
| * IFS_NWS -/NWS w/NWS - | |
| * IFS_IWS -/WS w/NWS - | |
| * (w means generate a word) | |
| */ | |
| if ((word == IFS_WORD) || (word == IFS_QUOTE) || (c && | |
| (word == IFS_IWS || word == IFS_NWS) && | |
| !ctype(c, C_IFSWS))) { | |
| emit_word: | |
| if (f & DOHERESTR) | |
| *dp++ = '\n'; | |
| *dp++ = '\0'; | |
| cp = Xclose(ds, dp); | |
| if (fdo & DOBRACE) | |
| /* also does globbing */ | |
| alt_expand(wp, cp, cp, | |
| cp + Xlength(ds, (dp - 1)), | |
| fdo | (f & DOMARKDIRS)); | |
| else if (fdo & DOGLOB) | |
| glob(cp, wp, tobool(f & DOMARKDIRS)); | |
| else if ((f & DOPAT) || !(fdo & DOMAGIC)) | |
| XPput(*wp, cp); | |
| else | |
| XPput(*wp, debunk(cp, cp, | |
| strlen(cp) + 1)); | |
| fdo = 0; | |
| saw_eq = false; | |
| /* must be 1/0 */ | |
| tilde_ok = (f & (DOTILDE | DOASNTILDE)) ? 1 : 0; | |
| if (c == 0) | |
| return; | |
| Xinit(ds, dp, 128, ATEMP); | |
| } else if (c == 0) { | |
| return; | |
| } else if (isXSUB(type) && ctype(c, C_IFS) && | |
| !ctype(c, C_IFSWS) && Xlength(ds, dp) == 0) { | |
| *(cp = alloc(1, ATEMP)) = '\0'; | |
| XPput(*wp, cp); | |
| ++type; | |
| } | |
| if (word != IFS_NWS) | |
| word = ctype(c, C_IFSWS) ? IFS_WS : IFS_NWS; | |
| } else { | |
| if (isXSUB(type)) | |
| ++type; | |
| /* age tilde_ok info - ~ code tests second bit */ | |
| tilde_ok <<= 1; | |
| /* mark any special second pass chars */ | |
| if (!quote) | |
| switch (ord(c)) { | |
| case ORD('['): | |
| case ORD('!'): | |
| case ORD('-'): | |
| case ORD(']'): | |
| /* | |
| * For character classes - doesn't hurt | |
| * to have magic !,-,]s outside of | |
| * [...] expressions. | |
| */ | |
| if (f & (DOPAT | DOGLOB)) { | |
| fdo |= DOMAGIC; | |
| if ((unsigned int)c == ORD('[')) | |
| fdo |= f & DOGLOB; | |
| *dp++ = MAGIC; | |
| } | |
| break; | |
| case ORD('*'): | |
| case ORD('?'): | |
| if (f & (DOPAT | DOGLOB)) { | |
| fdo |= DOMAGIC | (f & DOGLOB); | |
| *dp++ = MAGIC; | |
| } | |
| break; | |
| case ORD('{'): | |
| case ORD('}'): | |
| case ORD(','): | |
| if ((f & DOBRACE) && | |
| (ord(c) == ORD('{' /*}*/) || | |
| (fdo & DOBRACE))) { | |
| fdo |= DOBRACE|DOMAGIC; | |
| *dp++ = MAGIC; | |
| } | |
| break; | |
| case ORD('='): | |
| /* Note first unquoted = for ~ */ | |
| if (!(f & DOTEMP) && (!Flag(FPOSIX) || | |
| (f & DOASNTILDE)) && !saw_eq) { | |
| saw_eq = true; | |
| tilde_ok = 1; | |
| } | |
| break; | |
| case ORD(':'): | |
| /* : */ | |
| /* Note unquoted : for ~ */ | |
| if (!(f & DOTEMP) && (f & DOASNTILDE)) | |
| tilde_ok = 1; | |
| break; | |
| case ORD('~'): | |
| /* | |
| * tilde_ok is reset whenever | |
| * any of ' " $( $(( ${ } are seen. | |
| * Note that tilde_ok must be preserved | |
| * through the sequence ${A=a=}~ | |
| */ | |
| if (type == XBASE && | |
| (f & (DOTILDE | DOASNTILDE)) && | |
| (tilde_ok & 2)) { | |
| const char *tcp; | |
| char *tdp = dp; | |
| tcp = maybe_expand_tilde(sp, | |
| &ds, &tdp, | |
| tobool(f & DOASNTILDE)); | |
| if (tcp) { | |
| if (dp != tdp) | |
| word = IFS_WORD; | |
| dp = tdp; | |
| sp = tcp; | |
| continue; | |
| } | |
| } | |
| break; | |
| } | |
| else | |
| /* undo temporary */ | |
| quote &= ~2; | |
| if (make_magic) { | |
| make_magic = false; | |
| fdo |= DOMAGIC | (f & DOGLOB); | |
| *dp++ = MAGIC; | |
| } else if (ISMAGIC(c)) { | |
| fdo |= DOMAGIC; | |
| *dp++ = MAGIC; | |
| } | |
| /* save output char */ | |
| *dp++ = c; | |
| word = IFS_WORD; | |
| } | |
| } | |
| } | |
| static bool | |
| hasnonempty(const char **strv) | |
| { | |
| size_t i = 0; | |
| while (strv[i]) | |
| if (*strv[i++]) | |
| return (true); | |
| return (false); | |
| } | |
| /* | |
| * Prepare to generate the string returned by ${} substitution. | |
| */ | |
| static int | |
| varsub(Expand *xp, const char *sp, const char *word, | |
| /* becomes qualifier type */ | |
| unsigned int *stypep, | |
| /* becomes qualifier type len (=, :=, etc.) valid iff *stypep != 0 */ | |
| int *slenp) | |
| { | |
| unsigned int c; | |
| int state; /* next state: XBASE, XARG, XSUB, XNULLSUB */ | |
| unsigned int stype; /* substitution type */ | |
| int slen = 0; | |
| const char *p; | |
| struct tbl *vp; | |
| bool zero_ok = false; | |
| int sc; | |
| XPtrV wv; | |
| if ((stype = ord(sp[0])) == '\0') | |
| /* Bad variable name */ | |
| return (-1); | |
| xp->var = NULL; | |
| /* entirety of named array? */ | |
| if ((p = cstrchr(sp, '[')) && (sc = ord(p[1])) && | |
| ord(p[2]) == ORD(']')) | |
| /* keep p (for ${!foo[1]} below)! */ | |
| switch (sc) { | |
| case ORD('*'): | |
| sc = 3; | |
| break; | |
| case ORD('@'): | |
| sc = 7; | |
| break; | |
| default: | |
| /* bit2 = @, bit1 = array, bit0 = enabled */ | |
| sc = 0; | |
| } | |
| else | |
| /* $* and $@ checked below */ | |
| sc = 0; | |
| /*- | |
| * ${%var}, string width (-U: screen columns, +U: octets) | |
| * ${#var}, string length (-U: characters, +U: octets) or array size | |
| * ${!var}, variable name | |
| * ${*…} -> set flag for argv | |
| * ${@…} -> set flag for argv | |
| */ | |
| if (ctype(stype, C_SUB2 | CiVAR1)) { | |
| switch (stype) { | |
| case ORD('*'): | |
| if (!sc) | |
| sc = 1; | |
| goto nopfx; | |
| case ORD('@'): | |
| if (!sc) | |
| sc = 5; | |
| goto nopfx; | |
| } | |
| /* varname required */ | |
| if ((c = ord(sp[1])) == '\0') { | |
| if (stype == ORD('%')) | |
| /* $% */ | |
| return (-1); | |
| /* $# or $! */ | |
| goto nopfx; | |
| } | |
| /* can’t have any modifiers for ${#…} or ${%…} or ${!…} */ | |
| if (*word != CSUBST) | |
| return (-1); | |
| /* check for argv past prefix */ | |
| if (!sc) switch (c) { | |
| case ORD('*'): | |
| sc = 1; | |
| break; | |
| case ORD('@'): | |
| sc = 5; | |
| break; | |
| } | |
| /* skip past prefix */ | |
| ++sp; | |
| /* determine result */ | |
| switch (stype) { | |
| case ORD('!'): | |
| if (sc & 2) { | |
| stype = 0; | |
| XPinit(wv, 32); | |
| vp = global(arrayname(sp)); | |
| do { | |
| if (vp->flag & ISSET) | |
| XPput(wv, shf_smprintf(Tf_lu, | |
| arrayindex(vp))); | |
| } while ((vp = vp->u.array)); | |
| goto arraynames; | |
| } | |
| xp->var = global(sp); | |
| /* use saved p from above */ | |
| xp->str = p ? shf_smprintf("%s[%lu]", xp->var->name, | |
| arrayindex(xp->var)) : xp->var->name; | |
| break; | |
| #ifdef DEBUG | |
| default: | |
| internal_errorf("stype mismatch"); | |
| /* NOTREACHED */ | |
| #endif | |
| case ORD('%'): | |
| /* cannot do this on an array */ | |
| if (sc) | |
| return (-1); | |
| p = str_val(global(sp)); | |
| zero_ok = p != null; | |
| /* partial utf_mbswidth reimplementation */ | |
| sc = 0; | |
| while (*p) { | |
| if (!UTFMODE || | |
| (wv.len = utf_mbtowc(&c, p)) == (size_t)-1) | |
| /* not UTFMODE or not UTF-8 */ | |
| c = rtt2asc(*p++); | |
| else | |
| /* UTFMODE and UTF-8 */ | |
| p += wv.len; | |
| /* c == char or wchar at p++ */ | |
| if ((slen = utf_wcwidth(c)) == -1) { | |
| /* 646, 8859-1, 10646 C0/C1 */ | |
| sc = -1; | |
| break; | |
| } | |
| sc += slen; | |
| } | |
| if (0) | |
| /* FALLTHROUGH */ | |
| case ORD('#'): | |
| switch (sc & 3) { | |
| case 3: | |
| vp = global(arrayname(sp)); | |
| if (vp->flag & (ISSET|ARRAY)) | |
| zero_ok = true; | |
| sc = 0; | |
| do { | |
| if (vp->flag & ISSET) | |
| sc++; | |
| } while ((vp = vp->u.array)); | |
| break; | |
| case 1: | |
| sc = e->loc->argc; | |
| break; | |
| default: | |
| p = str_val(global(sp)); | |
| zero_ok = p != null; | |
| sc = utflen(p); | |
| break; | |
| } | |
| /* ${%var} also here */ | |
| if (Flag(FNOUNSET) && sc == 0 && !zero_ok) | |
| errorf(Tf_parm, sp); | |
| xp->str = shf_smprintf(Tf_d, sc); | |
| break; | |
| } | |
| /* unqualified variable/string substitution */ | |
| *stypep = 0; | |
| return (XSUB); | |
| } | |
| nopfx: | |
| /* check for qualifiers in word part */ | |
| stype = 0; | |
| /*slen = 0;*/ | |
| c = word[/*slen +*/ 0] == CHAR ? ord(word[/*slen +*/ 1]) : 0; | |
| if (c == ORD(':')) { | |
| slen += 2; | |
| stype = STYPE_DBL; | |
| c = word[slen + 0] == CHAR ? ord(word[slen + 1]) : 0; | |
| } | |
| if (!stype && c == ORD('/')) { | |
| slen += 2; | |
| stype = c; | |
| if (word[slen] == ADELIM && | |
| ord(word[slen + 1]) == c) { | |
| slen += 2; | |
| stype |= STYPE_DBL; | |
| } | |
| } else if (stype == STYPE_DBL && (c == ORD(' ') || c == ORD('0'))) { | |
| stype |= ORD('0'); | |
| } else if (ctype(c, C_SUB1)) { | |
| slen += 2; | |
| stype |= c; | |
| } else if (ctype(c, C_SUB2)) { | |
| /* Note: ksh88 allows :%, :%%, etc */ | |
| slen += 2; | |
| stype = c; | |
| if (word[slen + 0] == CHAR && ord(word[slen + 1]) == c) { | |
| stype |= STYPE_DBL; | |
| slen += 2; | |
| } | |
| } else if (c == ORD('@')) { | |
| /* @x where x is command char */ | |
| switch (c = ord(word[slen + 2]) == CHAR ? | |
| ord(word[slen + 3]) : 0) { | |
| case ORD('#'): | |
| case ORD('/'): | |
| case ORD('Q'): | |
| break; | |
| default: | |
| return (-1); | |
| } | |
| stype |= STYPE_AT | c; | |
| slen += 4; | |
| } else if (stype) | |
| /* : is not ok */ | |
| return (-1); | |
| if (!stype && *word != CSUBST) | |
| return (-1); | |
| if (!sc) { | |
| xp->var = global(sp); | |
| xp->str = str_val(xp->var); | |
| /* can't assign things like $! or $1 */ | |
| if ((stype & STYPE_SINGLE) == ORD('=') && | |
| !*xp->str && ctype(*sp, C_VAR1 | C_DIGIT)) | |
| return (-1); | |
| state = XSUB; | |
| } else { | |
| /* can’t assign/trim a vector (yet) */ | |
| switch (stype & STYPE_SINGLE) { | |
| case ORD('-'): | |
| case ORD('+'): | |
| /* allowed ops */ | |
| case 0: | |
| /* or no ops */ | |
| break; | |
| /* case ORD('='): | |
| case ORD('?'): | |
| case ORD('#'): | |
| case ORD('%'): | |
| case ORD('/'): | |
| case ORD('/') | STYPE_AT: | |
| case ORD('0'): | |
| case ORD('#') | STYPE_AT: | |
| case ORD('Q') | STYPE_AT: | |
| */ default: | |
| return (-1); | |
| } | |
| /* do what we can */ | |
| if (sc & 2) { | |
| XPinit(wv, 32); | |
| vp = global(arrayname(sp)); | |
| do { | |
| if (vp->flag & ISSET) | |
| XPput(wv, str_val(vp)); | |
| } while ((vp = vp->u.array)); | |
| arraynames: | |
| if ((c = (XPsize(wv) == 0))) | |
| XPfree(wv); | |
| else { | |
| XPput(wv, NULL); | |
| xp->u.strv = (const char **)XPptrv(wv); | |
| } | |
| } else { | |
| if ((c = (e->loc->argc == 0))) | |
| xp->var = global(sp); | |
| else | |
| xp->u.strv = (const char **)e->loc->argv + 1; | |
| /* POSIX 2009? */ | |
| zero_ok = true; | |
| } | |
| /* have we got any elements? */ | |
| if (c) { | |
| /* no */ | |
| xp->str = null; | |
| state = sc & 4 ? XNULLSUB : XSUB; | |
| } else { | |
| /* yes → load first */ | |
| xp->str = *xp->u.strv++; | |
| /* $@ or ${foo[@]} */ | |
| xp->split = tobool(sc & 4); | |
| state = XARG; | |
| } | |
| } | |
| c = stype & STYPE_CHAR; | |
| /* test the compiler's code generator */ | |
| if ((!(stype & STYPE_AT) && (ctype(c, C_SUB2) || | |
| (((stype & STYPE_DBL) ? *xp->str == '\0' : xp->str == null) && | |
| (state != XARG || (ifs0 || xp->split ? | |
| (xp->u.strv[0] == NULL) : !hasnonempty(xp->u.strv))) ? | |
| ctype(c, C_EQUAL | C_MINUS | C_QUEST) : c == ORD('+')))) || | |
| stype == (ORD('0') | STYPE_DBL) || | |
| stype == (ORD('#') | STYPE_AT) || | |
| stype == (ORD('Q') | STYPE_AT) || | |
| (stype & STYPE_CHAR) == ORD('/')) | |
| /* expand word instead of variable value */ | |
| state = XBASE; | |
| if (Flag(FNOUNSET) && xp->str == null && !zero_ok && | |
| (ctype(c, C_SUB2) || (state != XBASE && c != ORD('+')))) | |
| errorf(Tf_parm, sp); | |
| *stypep = stype; | |
| *slenp = slen; | |
| return (state); | |
| } | |
| /* | |
| * Run the command in $(...) and read its output. | |
| */ | |
| static int | |
| comsub(Expand *xp, const char *cp, int fn) | |
| { | |
| Source *s, *sold; | |
| struct op *t; | |
| struct shf *shf; | |
| bool doalias = false; | |
| uint8_t old_utfmode = UTFMODE; | |
| switch (fn) { | |
| case COMASUB: | |
| fn = COMSUB; | |
| if (0) | |
| /* FALLTHROUGH */ | |
| case FUNASUB: | |
| fn = FUNSUB; | |
| doalias = true; | |
| } | |
| s = pushs(SSTRING, ATEMP); | |
| s->start = s->str = cp; | |
| sold = source; | |
| t = compile(s, true, doalias); | |
| afree(s, ATEMP); | |
| source = sold; | |
| UTFMODE = old_utfmode; | |
| if (t == NULL) | |
| return (XBASE); | |
| /* no waitlast() unless specifically enabled later */ | |
| xp->split = false; | |
| if (t->type == TCOM && | |
| *t->args == NULL && *t->vars == NULL && t->ioact != NULL) { | |
| /* $(<file) */ | |
| struct ioword *io = *t->ioact; | |
| char *name; | |
| switch (io->ioflag & IOTYPE) { | |
| case IOREAD: | |
| shf = shf_open(name = evalstr(io->ioname, DOTILDE), | |
| O_RDONLY, 0, SHF_MAPHI | SHF_CLEXEC); | |
| if (shf == NULL) | |
| warningf(!Flag(FTALKING), Tf_sD_s_sD_s, | |
| name, Tcant_open, "$(<...) input", | |
| cstrerror(errno)); | |
| break; | |
| case IOHERE: | |
| if (!herein(io, &name)) { | |
| xp->str = name; | |
| /* as $(…) requires, trim trailing newlines */ | |
| name = strnul(name); | |
| while (name > xp->str && name[-1] == '\n') | |
| --name; | |
| *name = '\0'; | |
| return (XSUB); | |
| } | |
| shf = NULL; | |
| break; | |
| default: | |
| errorf(Tf_sD_s, T_funny_command, | |
| snptreef(NULL, 32, Tft_R, io)); | |
| } | |
| } else if (fn == FUNSUB) { | |
| int ofd1; | |
| struct temp *tf = NULL; | |
| /* | |
| * create a temporary file, open for reading and writing, | |
| * with an shf open for reading (buffered) but yet unused | |
| */ | |
| maketemp(ATEMP, TT_FUNSUB, &tf); | |
| if (!tf->shf) { | |
| errorf(Tf_temp, | |
| Tcreate, tf->tffn, cstrerror(errno)); | |
| } | |
| /* extract shf from temporary file, unlink and free it */ | |
| shf = tf->shf; | |
| unlink(tf->tffn); | |
| afree(tf, ATEMP); | |
| /* save stdout and let it point to the tempfile */ | |
| ofd1 = savefd(1); | |
| ksh_dup2(shf_fileno(shf), 1, false); | |
| /* | |
| * run tree, with output thrown into the tempfile, | |
| * in a new function block | |
| */ | |
| valsub(t, NULL); | |
| subst_exstat = exstat & 0xFF; | |
| /* rewind the tempfile and restore regular stdout */ | |
| lseek(shf_fileno(shf), (off_t)0, SEEK_SET); | |
| restfd(1, ofd1); | |
| } else if (fn == VALSUB) { | |
| xp->str = valsub(t, ATEMP); | |
| subst_exstat = exstat & 0xFF; | |
| return (XSUB); | |
| } else { | |
| int ofd1, pv[2]; | |
| openpipe(pv); | |
| shf = shf_fdopen(pv[0], SHF_RD, NULL); | |
| ofd1 = savefd(1); | |
| if (pv[1] != 1) { | |
| ksh_dup2(pv[1], 1, false); | |
| close(pv[1]); | |
| } | |
| execute(t, XXCOM | XPIPEO | XFORK, NULL); | |
| restfd(1, ofd1); | |
| startlast(); | |
| /* waitlast() */ | |
| xp->split = true; | |
| } | |
| xp->u.shf = shf; | |
| return (XCOM); | |
| } | |
| /* | |
| * perform #pattern and %pattern substitution in ${} | |
| */ | |
| static char * | |
| trimsub(char *str, char *pat, int how) | |
| { | |
| char *end = strnul(str); | |
| char *p, c; | |
| switch (how & (STYPE_CHAR | STYPE_DBL)) { | |
| case ORD('#'): | |
| /* shortest match at beginning */ | |
| for (p = str; p <= end; p += utf_ptradj(p)) { | |
| c = *p; *p = '\0'; | |
| if (gmatchx(str, pat, false)) { | |
| record_match(str); | |
| *p = c; | |
| return (p); | |
| } | |
| *p = c; | |
| } | |
| break; | |
| case ORD('#') | STYPE_DBL: | |
| /* longest match at beginning */ | |
| for (p = end; p >= str; p--) { | |
| c = *p; *p = '\0'; | |
| if (gmatchx(str, pat, false)) { | |
| record_match(str); | |
| *p = c; | |
| return (p); | |
| } | |
| *p = c; | |
| } | |
| break; | |
| case ORD('%'): | |
| /* shortest match at end */ | |
| p = end; | |
| while (p >= str) { | |
| if (gmatchx(p, pat, false)) | |
| goto trimsub_match; | |
| if (UTFMODE) { | |
| char *op = p; | |
| while ((p-- > str) && ((rtt2asc(*p) & 0xC0) == 0x80)) | |
| ; | |
| if ((p < str) || (p + utf_ptradj(p) != op)) | |
| p = op - 1; | |
| } else | |
| --p; | |
| } | |
| break; | |
| case ORD('%') | STYPE_DBL: | |
| /* longest match at end */ | |
| for (p = str; p <= end; p++) | |
| if (gmatchx(p, pat, false)) { | |
| trimsub_match: | |
| record_match(p); | |
| strndupx(end, str, p - str, ATEMP); | |
| return (end); | |
| } | |
| break; | |
| } | |
| /* no match, return string */ | |
| return (str); | |
| } | |
| /* | |
| * glob | |
| * Name derived from V6's /etc/glob, the program that expanded filenames. | |
| */ | |
| /* XXX cp not const 'cause slashes are temporarily replaced with NULs... */ | |
| static void | |
| glob(char *cp, XPtrV *wp, bool markdirs) | |
| { | |
| int oldsize = XPsize(*wp); | |
| if (glob_str(cp, wp, markdirs) == 0) | |
| XPput(*wp, debunk(cp, cp, strlen(cp) + 1)); | |
| else | |
| qsort(XPptrv(*wp) + oldsize, XPsize(*wp) - oldsize, | |
| sizeof(void *), ascpstrcmp); | |
| } | |
| #define GF_NONE 0 | |
| #define GF_EXCHECK BIT(0) /* do existence check on file */ | |
| #define GF_GLOBBED BIT(1) /* some globbing has been done */ | |
| #define GF_MARKDIR BIT(2) /* add trailing / to directories */ | |
| /* | |
| * Apply file globbing to cp and store the matching files in wp. Returns | |
| * the number of matches found. | |
| */ | |
| int | |
| glob_str(char *cp, XPtrV *wp, bool markdirs) | |
| { | |
| int oldsize = XPsize(*wp); | |
| XString xs; | |
| char *xp; | |
| Xinit(xs, xp, 256, ATEMP); | |
| globit(&xs, &xp, cp, wp, markdirs ? GF_MARKDIR : GF_NONE); | |
| Xfree(xs, xp); | |
| return (XPsize(*wp) - oldsize); | |
| } | |
| static void | |
| globit(XString *xs, /* dest string */ | |
| char **xpp, /* ptr to dest end */ | |
| char *sp, /* source path */ | |
| XPtrV *wp, /* output list */ | |
| int check) /* GF_* flags */ | |
| { | |
| char *np; /* next source component */ | |
| char *xp = *xpp; | |
| char *se; | |
| char odirsep; | |
| /* This to allow long expansions to be interrupted */ | |
| intrcheck(); | |
| if (sp == NULL) { | |
| /* end of source path */ | |
| /* | |
| * We only need to check if the file exists if a pattern | |
| * is followed by a non-pattern (eg, foo*x/bar; no check | |
| * is needed for foo* since the match must exist) or if | |
| * any patterns were expanded and the markdirs option is set. | |
| * Symlinks make things a bit tricky... | |
| */ | |
| if ((check & GF_EXCHECK) || | |
| ((check & GF_MARKDIR) && (check & GF_GLOBBED))) { | |
| #define stat_check() (stat_done ? stat_done : (stat_done = \ | |
| stat(Xstring(*xs, xp), &statb) < 0 ? -1 : 1)) | |
| struct stat lstatb, statb; | |
| /* -1: failed, 1 ok, 0 not yet done */ | |
| int stat_done = 0; | |
| if (mksh_lstat(Xstring(*xs, xp), &lstatb) < 0) | |
| return; | |
| /* | |
| * special case for systems which strip trailing | |
| * slashes from regular files (eg, /etc/passwd/). | |
| * SunOS 4.1.3 does this... | |
| */ | |
| if ((check & GF_EXCHECK) && xp > Xstring(*xs, xp) && | |
| mksh_cdirsep(xp[-1]) && !S_ISDIR(lstatb.st_mode) && | |
| (!S_ISLNK(lstatb.st_mode) || | |
| stat_check() < 0 || !S_ISDIR(statb.st_mode))) | |
| return; | |
| /* | |
| * Possibly tack on a trailing / if there isn't already | |
| * one and if the file is a directory or a symlink to a | |
| * directory | |
| */ | |
| if (((check & GF_MARKDIR) && (check & GF_GLOBBED)) && | |
| xp > Xstring(*xs, xp) && !mksh_cdirsep(xp[-1]) && | |
| (S_ISDIR(lstatb.st_mode) || | |
| (S_ISLNK(lstatb.st_mode) && stat_check() > 0 && | |
| S_ISDIR(statb.st_mode)))) { | |
| *xp++ = '/'; | |
| *xp = '\0'; | |
| } | |
| } | |
| strndupx(np, Xstring(*xs, xp), Xlength(*xs, xp), ATEMP); | |
| XPput(*wp, np); | |
| return; | |
| } | |
| if (xp > Xstring(*xs, xp)) | |
| *xp++ = '/'; | |
| while (mksh_cdirsep(*sp)) { | |
| Xcheck(*xs, xp); | |
| *xp++ = *sp++; | |
| } | |
| np = mksh_sdirsep(sp); | |
| if (np != NULL) { | |
| se = np; | |
| /* don't assume '/', can be multiple kinds */ | |
| odirsep = *np; | |
| *np++ = '\0'; | |
| } else { | |
| odirsep = '\0'; /* keep gcc quiet */ | |
| se = strnul(sp); | |
| } | |
| /* | |
| * Check if sp needs globbing - done to avoid pattern checks for strings | |
| * containing MAGIC characters, open [s without the matching close ], | |
| * etc. (otherwise opendir() will be called which may fail because the | |
| * directory isn't readable - if no globbing is needed, only execute | |
| * permission should be required (as per POSIX)). | |
| */ | |
| if (!has_globbing(sp)) { | |
| XcheckN(*xs, xp, se - sp + 1); | |
| debunk(xp, sp, Xnleft(*xs, xp)); | |
| xp = strnul(xp); | |
| *xpp = xp; | |
| globit(xs, xpp, np, wp, check); | |
| } else { | |
| DIR *dirp; | |
| struct dirent *d; | |
| char *name; | |
| size_t len, prefix_len; | |
| /* xp = *xpp; copy_non_glob() may have re-alloc'd xs */ | |
| *xp = '\0'; | |
| prefix_len = Xlength(*xs, xp); | |
| dirp = opendir(prefix_len ? Xstring(*xs, xp) : Tdot); | |
| if (dirp == NULL) | |
| goto Nodir; | |
| while ((d = readdir(dirp)) != NULL) { | |
| name = d->d_name; | |
| if (name[0] == '.' && | |
| (name[1] == 0 || (name[1] == '.' && name[2] == 0))) | |
| /* always ignore . and .. */ | |
| continue; | |
| if ((*name == '.' && *sp != '.') || | |
| !gmatchx(name, sp, true)) | |
| continue; | |
| len = strlen(d->d_name) + 1; | |
| XcheckN(*xs, xp, len); | |
| memcpy(xp, name, len); | |
| *xpp = xp + len - 1; | |
| globit(xs, xpp, np, wp, (check & GF_MARKDIR) | | |
| GF_GLOBBED | (np ? GF_EXCHECK : GF_NONE)); | |
| xp = Xstring(*xs, xp) + prefix_len; | |
| } | |
| closedir(dirp); | |
| Nodir: | |
| ; | |
| } | |
| if (np != NULL) | |
| *--np = odirsep; | |
| } | |
| /* remove MAGIC from string */ | |
| char * | |
| debunk(char *dp, const char *sp, size_t dlen) | |
| { | |
| char *d; | |
| const char *s; | |
| if ((s = cstrchr(sp, MAGIC))) { | |
| if (s - sp >= (ssize_t)dlen) | |
| return (dp); | |
| memmove(dp, sp, s - sp); | |
| for (d = dp + (s - sp); *s && (d - dp < (ssize_t)dlen); s++) | |
| if (!ISMAGIC(*s) || !(*++s & 0x80) || | |
| !ctype(*s & 0x7F, C_PATMO | C_SPC)) | |
| *d++ = *s; | |
| else { | |
| /* extended pattern operators: *+?@! */ | |
| if ((*s & 0x7f) != ' ') | |
| *d++ = *s & 0x7f; | |
| if (d - dp < (ssize_t)dlen) | |
| *d++ = '('; | |
| } | |
| *d = '\0'; | |
| } else if (dp != sp) | |
| strlcpy(dp, sp, dlen); | |
| return (dp); | |
| } | |
| /* | |
| * Check if p is an unquoted name, possibly followed by a / or :. If so | |
| * puts the expanded version in *dcp,dp and returns a pointer in p just | |
| * past the name, otherwise returns 0. | |
| */ | |
| static const char * | |
| maybe_expand_tilde(const char *p, XString *dsp, char **dpp, bool isassign) | |
| { | |
| XString ts; | |
| char *dp = *dpp; | |
| char *tp; | |
| const char *r; | |
| Xinit(ts, tp, 16, ATEMP); | |
| /* : only for DOASNTILDE form */ | |
| while (p[0] == CHAR && /* not cdirsep */ p[1] != '/' && | |
| (!isassign || p[1] != ':')) { | |
| Xcheck(ts, tp); | |
| *tp++ = p[1]; | |
| p += 2; | |
| } | |
| *tp = '\0'; | |
| r = (p[0] == EOS || p[0] == CHAR || p[0] == CSUBST) ? | |
| do_tilde(Xstring(ts, tp)) : NULL; | |
| Xfree(ts, tp); | |
| if (r) { | |
| while (*r) { | |
| Xcheck(*dsp, dp); | |
| if (ISMAGIC(*r)) | |
| *dp++ = MAGIC; | |
| *dp++ = *r++; | |
| } | |
| *dpp = dp; | |
| r = p; | |
| } | |
| return (r); | |
| } | |
| /* | |
| * tilde expansion | |
| * | |
| * based on a version by Arnold Robbins | |
| */ | |
| char * | |
| do_tilde(char *cp) | |
| { | |
| char *dp = null; | |
| #ifndef MKSH_NOPWNAM | |
| bool do_simplify = true; | |
| #endif | |
| if (cp[0] == '\0') | |
| dp = str_val(global("HOME")); | |
| else if (cp[0] == '+' && cp[1] == '\0') | |
| dp = str_val(global(TPWD)); | |
| else if (ksh_isdash(cp)) | |
| dp = str_val(global(TOLDPWD)); | |
| #ifndef MKSH_NOPWNAM | |
| else { | |
| dp = homedir(cp); | |
| do_simplify = false; | |
| } | |
| #endif | |
| /* if parameters aren't set, don't expand ~ */ | |
| if (dp == NULL || dp == null) | |
| return (NULL); | |
| /* simplify parameters as if cwd upon entry */ | |
| #ifndef MKSH_NOPWNAM | |
| if (do_simplify) | |
| #endif | |
| { | |
| strdupx(dp, dp, ATEMP); | |
| simplify_path(dp); | |
| } | |
| return (dp); | |
| } | |
| #ifndef MKSH_NOPWNAM | |
| /* | |
| * map userid to user's home directory. | |
| * note that 4.3's getpw adds more than 6K to the shell, | |
| * and the YP version probably adds much more. | |
| * we might consider our own version of getpwnam() to keep the size down. | |
| */ | |
| static char * | |
| homedir(char *name) | |
| { | |
| struct tbl *ap; | |
| ap = ktenter(&homedirs, name, hash(name)); | |
| if (!(ap->flag & ISSET)) { | |
| struct passwd *pw; | |
| pw = getpwnam(name); | |
| if (pw == NULL) | |
| return (NULL); | |
| strdupx(ap->val.s, pw->pw_dir, APERM); | |
| ap->flag |= DEFINED|ISSET|ALLOC; | |
| } | |
| return (ap->val.s); | |
| } | |
| #endif | |
| static void | |
| alt_expand(XPtrV *wp, char *start, char *exp_start, char *end, int fdo) | |
| { | |
| unsigned int count = 0; | |
| char *brace_start, *brace_end, *comma = NULL; | |
| char *field_start; | |
| char *p = exp_start; | |
| /* search for open brace */ | |
| while ((p = strchr(p, MAGIC)) && ord(p[1]) != ORD('{' /*}*/)) | |
| p += 2; | |
| brace_start = p; | |
| /* find matching close brace, if any */ | |
| if (p) { | |
| comma = NULL; | |
| count = 1; | |
| p += 2; | |
| while (*p && count) { | |
| if (ISMAGIC(*p++)) { | |
| if (ord(*p) == ORD('{' /*}*/)) | |
| ++count; | |
| else if (ord(*p) == ORD(/*{*/ '}')) | |
| --count; | |
| else if (*p == ',' && count == 1) | |
| comma = p; | |
| ++p; | |
| } | |
| } | |
| } | |
| /* no valid expansions... */ | |
| if (!p || count != 0) { | |
| /* | |
| * Note that given a{{b,c} we do not expand anything (this is | |
| * what AT&T ksh does. This may be changed to do the {b,c} | |
| * expansion. } | |
| */ | |
| if (fdo & DOGLOB) | |
| glob(start, wp, tobool(fdo & DOMARKDIRS)); | |
| else | |
| XPput(*wp, debunk(start, start, end - start)); | |
| return; | |
| } | |
| brace_end = p; | |
| if (!comma) { | |
| alt_expand(wp, start, brace_end, end, fdo); | |
| return; | |
| } | |
| /* expand expression */ | |
| field_start = brace_start + 2; | |
| count = 1; | |
| for (p = brace_start + 2; p != brace_end; p++) { | |
| if (ISMAGIC(*p)) { | |
| if (ord(*++p) == ORD('{' /*}*/)) | |
| ++count; | |
| else if ((ord(*p) == ORD(/*{*/ '}') && --count == 0) || | |
| (*p == ',' && count == 1)) { | |
| char *news; | |
| int l1, l2, l3; | |
| /* | |
| * addition safe since these operate on | |
| * one string (separate substrings) | |
| */ | |
| l1 = brace_start - start; | |
| l2 = (p - 1) - field_start; | |
| l3 = end - brace_end; | |
| news = alloc(l1 + l2 + l3 + 1, ATEMP); | |
| memcpy(news, start, l1); | |
| memcpy(news + l1, field_start, l2); | |
| memcpy(news + l1 + l2, brace_end, l3); | |
| news[l1 + l2 + l3] = '\0'; | |
| alt_expand(wp, news, news + l1, | |
| news + l1 + l2 + l3, fdo); | |
| field_start = p + 1; | |
| } | |
| } | |
| } | |
| return; | |
| } | |
| /* helper function due to setjmp/longjmp woes */ | |
| static char * | |
| valsub(struct op *t, Area *ap) | |
| { | |
| char * volatile cp = NULL; | |
| struct tbl * volatile vp = NULL; | |
| newenv(E_FUNC); | |
| newblock(); | |
| if (ap) | |
| vp = local(TREPLY, false); | |
| if (!kshsetjmp(e->jbuf)) | |
| execute(t, XXCOM | XERROK, NULL); | |
| if (vp) | |
| strdupx(cp, str_val(vp), ap); | |
| quitenv(NULL); | |
| return (cp); | |
| } |