| /*- |
| * Copyright (c) 1991, 1993 |
| * The Regents of the University of California. All rights reserved. |
| * Copyright (c) 1997-2005 |
| * Herbert Xu <herbert@gondor.apana.org.au>. 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. |
| * 3. 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. |
| */ |
| |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <sys/stat.h> |
| #include <dirent.h> |
| #include <unistd.h> |
| #ifdef HAVE_GETPWNAM |
| #include <pwd.h> |
| #endif |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <inttypes.h> |
| #include <limits.h> |
| #include <string.h> |
| #include <fnmatch.h> |
| #ifdef HAVE_GLOB |
| #include <glob.h> |
| #endif |
| #include <ctype.h> |
| #include <stdbool.h> |
| |
| /* |
| * 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 "output.h" |
| #include "memalloc.h" |
| #include "error.h" |
| #include "mystring.h" |
| #include "show.h" |
| #include "system.h" |
| |
| /* |
| * _rmescape() flags |
| */ |
| #define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ |
| #define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ |
| #define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ |
| #define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ |
| |
| /* Add CTLESC when necessary. */ |
| #define QUOTES_ESC (EXP_FULL | EXP_CASE | EXP_QPAT) |
| /* Do not skip NUL characters. */ |
| #define QUOTES_KEEPNUL EXP_TILDE |
| |
| /* |
| * 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 nulonly; /* search for nul bytes only */ |
| }; |
| |
| /* output of current string */ |
| static char *expdest; |
| /* list of back quote expressions */ |
| static struct nodelist *argbackq; |
| /* first struct in list of ifs regions */ |
| static struct ifsregion ifsfirst; |
| /* last struct in list */ |
| static struct ifsregion *ifslastp; |
| /* holds expanded arg list */ |
| static struct arglist exparg; |
| |
| STATIC void argstr(char *, int); |
| STATIC char *exptilde(char *, char *, int); |
| STATIC void expbackq(union node *, int); |
| STATIC const char *subevalvar(char *, char *, int, int, int, int, int); |
| STATIC char *evalvar(char *, int); |
| STATIC size_t strtodest(const char *, const char *, int); |
| STATIC void memtodest(const char *, size_t, const char *, int); |
| STATIC ssize_t varvalue(char *, int, int, int *); |
| STATIC void expandmeta(struct strlist *, int); |
| #ifdef HAVE_GLOB |
| STATIC void addglob(const glob_t *); |
| #else |
| STATIC void expmeta(char *, char *); |
| STATIC struct strlist *expsort(struct strlist *); |
| STATIC struct strlist *msort(struct strlist *, int); |
| #endif |
| STATIC void addfname(char *); |
| STATIC int patmatch(char *, const char *); |
| #ifndef HAVE_FNMATCH |
| STATIC int pmatch(const char *, const char *); |
| #else |
| #define pmatch(a, b) !fnmatch((a), (b), 0) |
| #endif |
| STATIC int cvtnum(intmax_t); |
| STATIC size_t esclen(const char *, const char *); |
| STATIC char *scanleft(char *, char *, char *, char *, int, int); |
| STATIC char *scanright(char *, char *, char *, char *, int, int); |
| STATIC void varunset(const char *, const char *, const char *, int) |
| __attribute__((__noreturn__)); |
| |
| |
| /* |
| * Prepare a pattern for a glob(3) call. |
| * |
| * Returns an stalloced string. |
| */ |
| |
| STATIC inline char * |
| preglob(const char *pattern, int flag) { |
| flag |= RMESCAPE_GLOB; |
| return _rmescapes((char *)pattern, flag); |
| } |
| |
| |
| STATIC size_t |
| esclen(const char *start, const char *p) { |
| size_t esc = 0; |
| |
| while (p > start && *--p == (char)CTLESC) { |
| esc++; |
| } |
| return esc; |
| } |
| |
| |
| static inline const char *getpwhome(const char *name) |
| { |
| #ifdef HAVE_GETPWNAM |
| struct passwd *pw = getpwnam(name); |
| return pw ? pw->pw_dir : 0; |
| #else |
| return 0; |
| #endif |
| } |
| |
| |
| /* |
| * Perform variable substitution and command substitution on an argument, |
| * placing the resulting list of arguments in arglist. If EXP_FULL is true, |
| * perform splitting and file name expansion. When arglist is NULL, perform |
| * here document expansion. |
| */ |
| |
| void |
| expandarg(union node *arg, struct arglist *arglist, int flag) |
| { |
| struct strlist *sp; |
| char *p; |
| |
| argbackq = arg->narg.backquote; |
| STARTSTACKSTR(expdest); |
| argstr(arg->narg.text, flag); |
| p = _STPUTC('\0', expdest); |
| expdest = p - 1; |
| if (arglist == NULL) { |
| /* here document expanded */ |
| goto out; |
| } |
| p = grabstackstr(p); |
| exparg.lastp = &exparg.list; |
| /* |
| * TODO - EXP_REDIR |
| */ |
| if (flag & EXP_FULL) { |
| ifsbreakup(p, -1, &exparg); |
| *exparg.lastp = NULL; |
| exparg.lastp = &exparg.list; |
| expandmeta(exparg.list, flag); |
| } else { |
| sp = (struct strlist *)stalloc(sizeof (struct strlist)); |
| sp->text = p; |
| *exparg.lastp = sp; |
| exparg.lastp = &sp->next; |
| } |
| *exparg.lastp = NULL; |
| if (exparg.list) { |
| *arglist->lastp = exparg.list; |
| arglist->lastp = exparg.lastp; |
| } |
| |
| out: |
| ifsfree(); |
| } |
| |
| |
| |
| /* |
| * Perform variable and command substitution. If EXP_FULL is set, output CTLESC |
| * characters to allow for further processing. Otherwise treat |
| * $@ like $* since no splitting will be performed. |
| */ |
| |
| STATIC void |
| argstr(char *p, int flag) |
| { |
| static const char spclchars[] = { |
| '=', |
| ':', |
| CTLQUOTEMARK, |
| CTLENDVAR, |
| CTLESC, |
| CTLVAR, |
| CTLBACKQ, |
| CTLENDARI, |
| 0 |
| }; |
| const char *reject = spclchars; |
| int c; |
| int breakall = (flag & (EXP_WORD | EXP_QUOTED)) == EXP_WORD; |
| int inquotes; |
| size_t length; |
| int startloc; |
| |
| if (!(flag & EXP_VARTILDE)) { |
| reject += 2; |
| } else if (flag & EXP_VARTILDE2) { |
| reject++; |
| } |
| inquotes = 0; |
| length = 0; |
| if (flag & EXP_TILDE) { |
| char *q; |
| |
| flag &= ~EXP_TILDE; |
| tilde: |
| q = p; |
| if (*q == '~') |
| p = exptilde(p, q, flag); |
| } |
| start: |
| startloc = expdest - (char *)stackblock(); |
| for (;;) { |
| length += strcspn(p + length, reject); |
| c = (signed char)p[length]; |
| if (c && (!(c & 0x80) || c == CTLENDARI)) { |
| /* c == '=' || c == ':' || c == CTLENDARI */ |
| length++; |
| } |
| if (length > 0) { |
| int newloc; |
| expdest = stnputs(p, length, expdest); |
| newloc = expdest - (char *)stackblock(); |
| if (breakall && !inquotes && newloc > startloc) { |
| recordregion(startloc, newloc, 0); |
| } |
| startloc = newloc; |
| } |
| p += length + 1; |
| length = 0; |
| |
| switch (c) { |
| case '\0': |
| goto breakloop; |
| case '=': |
| if (flag & EXP_VARTILDE2) { |
| p--; |
| continue; |
| } |
| flag |= EXP_VARTILDE2; |
| reject++; |
| /* fall through */ |
| case ':': |
| /* |
| * sort of a hack - expand tildes in variable |
| * assignments (after the first '=' and after ':'s). |
| */ |
| if (*--p == '~') { |
| goto tilde; |
| } |
| continue; |
| } |
| |
| switch (c) { |
| case CTLENDVAR: /* ??? */ |
| goto breakloop; |
| case CTLQUOTEMARK: |
| inquotes ^= EXP_QUOTED; |
| /* "$@" syntax adherence hack */ |
| if (inquotes && !memcmp(p, dolatstr + 1, |
| DOLATSTRLEN - 1)) { |
| p = evalvar(p + 1, flag | inquotes) + 1; |
| goto start; |
| } |
| addquote: |
| if (flag & QUOTES_ESC) { |
| p--; |
| length++; |
| startloc++; |
| } |
| break; |
| case CTLESC: |
| startloc++; |
| length++; |
| |
| /* |
| * Quoted parameter expansion pattern: remove quote |
| * unless inside inner quotes or we have a literal |
| * backslash. |
| */ |
| if (((flag | inquotes) & (EXP_QPAT | EXP_QUOTED)) == |
| EXP_QPAT && *p != '\\') |
| break; |
| |
| goto addquote; |
| case CTLVAR: |
| p = evalvar(p, flag | inquotes); |
| goto start; |
| case CTLBACKQ: |
| expbackq(argbackq->n, flag | inquotes); |
| argbackq = argbackq->next; |
| goto start; |
| case CTLENDARI: |
| p--; |
| expari(flag | inquotes); |
| goto start; |
| } |
| } |
| breakloop: |
| ; |
| } |
| |
| STATIC char * |
| exptilde(char *startp, char *p, int flag) |
| { |
| signed char c; |
| char *name; |
| const char *home; |
| int quotes = flag & QUOTES_ESC; |
| |
| name = p + 1; |
| |
| while ((c = *++p) != '\0') { |
| switch(c) { |
| case CTLESC: |
| return (startp); |
| case CTLQUOTEMARK: |
| return (startp); |
| case ':': |
| if (flag & EXP_VARTILDE) |
| goto done; |
| break; |
| case '/': |
| case CTLENDVAR: |
| goto done; |
| } |
| } |
| done: |
| *p = '\0'; |
| if (*name == '\0') { |
| home = lookupvar(homestr); |
| } else { |
| home = getpwhome(name); |
| } |
| if (!home || !*home) |
| goto lose; |
| *p = c; |
| strtodest(home, SQSYNTAX, quotes); |
| return (p); |
| lose: |
| *p = c; |
| return (startp); |
| } |
| |
| |
| 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. Backup to start of expression, |
| * evaluate, place result in (backed up) result, adjust string position. |
| */ |
| void |
| expari(int flag) |
| { |
| struct stackmark sm; |
| char *p, *start; |
| int begoff; |
| int len; |
| intmax_t result; |
| |
| /* ifsfree(); */ |
| |
| /* |
| * This routine is slightly over-complicated for |
| * efficiency. Next we scan backwards looking for the |
| * start of arithmetic. |
| */ |
| start = stackblock(); |
| p = expdest; |
| pushstackmark(&sm, p - start); |
| *--p = '\0'; |
| p--; |
| do { |
| int esc; |
| |
| while (*p != (char)CTLARI) { |
| p--; |
| #ifdef DEBUG |
| if (p < start) { |
| sh_error("missing CTLARI (shouldn't happen)"); |
| } |
| #endif |
| } |
| |
| esc = esclen(start, p); |
| if (!(esc % 2)) { |
| break; |
| } |
| |
| p -= esc + 1; |
| } while (1); |
| |
| begoff = p - start; |
| |
| removerecordregions(begoff); |
| |
| expdest = p; |
| |
| if (likely(flag & QUOTES_ESC)) |
| rmescapes(p + 1); |
| |
| result = arith(p + 1); |
| popstackmark(&sm); |
| |
| len = cvtnum(result); |
| |
| if (likely(!(flag & EXP_QUOTED))) |
| recordregion(begoff, begoff + len, 0); |
| } |
| |
| |
| /* |
| * Expand stuff in backwards quotes. |
| */ |
| |
| STATIC void |
| expbackq(union node *cmd, int flag) |
| { |
| struct backcmd in; |
| int i; |
| char buf[128]; |
| char *p; |
| char *dest; |
| int startloc; |
| char const *syntax = flag & EXP_QUOTED ? DQSYNTAX : BASESYNTAX; |
| struct stackmark smark; |
| |
| INTOFF; |
| startloc = expdest - (char *)stackblock(); |
| pushstackmark(&smark, startloc); |
| evalbackcmd(cmd, (struct backcmd *) &in); |
| popstackmark(&smark); |
| |
| p = in.buf; |
| i = in.nleft; |
| if (i == 0) |
| goto read; |
| for (;;) { |
| memtodest(p, i, syntax, flag & QUOTES_ESC); |
| read: |
| if (in.fd < 0) |
| break; |
| do { |
| i = read(in.fd, buf, sizeof buf); |
| } while (i < 0 && errno == EINTR); |
| TRACE(("expbackq: read returns %d\n", i)); |
| if (i <= 0) |
| break; |
| p = buf; |
| } |
| |
| if (in.buf) |
| ckfree(in.buf); |
| if (in.fd >= 0) { |
| close(in.fd); |
| back_exitstatus = waitforjob(in.jp); |
| } |
| INTON; |
| |
| /* Eat all trailing newlines */ |
| dest = expdest; |
| for (; dest > (char *)stackblock() && dest[-1] == '\n';) |
| STUNPUTC(dest); |
| expdest = dest; |
| |
| if (!(flag & EXP_QUOTED)) |
| recordregion(startloc, dest - (char *)stackblock(), 0); |
| TRACE(("evalbackq: size=%d: \"%.*s\"\n", |
| (dest - (char *)stackblock()) - startloc, |
| (dest - (char *)stackblock()) - startloc, |
| stackblock() + startloc)); |
| } |
| |
| |
| STATIC char * |
| scanleft( |
| char *startp, char *rmesc, char *rmescend, char *str, int quotes, |
| int zero |
| ) { |
| char *loc; |
| char *loc2; |
| char c; |
| |
| loc = startp; |
| loc2 = rmesc; |
| do { |
| int match; |
| const char *s = loc2; |
| c = *loc2; |
| if (zero) { |
| *loc2 = '\0'; |
| s = rmesc; |
| } |
| match = pmatch(str, s); |
| *loc2 = c; |
| if (match) |
| return loc; |
| if (quotes && *loc == (char)CTLESC) |
| loc++; |
| loc++; |
| loc2++; |
| } while (c); |
| return 0; |
| } |
| |
| |
| STATIC char * |
| scanright( |
| char *startp, char *rmesc, char *rmescend, char *str, int quotes, |
| int zero |
| ) { |
| int esc = 0; |
| char *loc; |
| char *loc2; |
| |
| for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) { |
| int match; |
| char c = *loc2; |
| const char *s = loc2; |
| if (zero) { |
| *loc2 = '\0'; |
| s = rmesc; |
| } |
| match = pmatch(str, s); |
| *loc2 = c; |
| if (match) |
| return loc; |
| loc--; |
| if (quotes) { |
| if (--esc < 0) { |
| esc = esclen(startp, loc); |
| } |
| if (esc % 2) { |
| esc--; |
| loc--; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| STATIC const char * |
| subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags, int flag) |
| { |
| int quotes = flag & QUOTES_ESC; |
| char *startp; |
| char *loc; |
| struct nodelist *saveargbackq = argbackq; |
| int amount; |
| char *rmesc, *rmescend; |
| int zero; |
| char *(*scan)(char *, char *, char *, char *, int , int); |
| |
| argstr(p, EXP_TILDE | (subtype != VSASSIGN && subtype != VSQUESTION ? |
| (flag & (EXP_QUOTED | EXP_QPAT) ? |
| EXP_QPAT : EXP_CASE) : 0)); |
| STPUTC('\0', expdest); |
| argbackq = saveargbackq; |
| startp = stackblock() + startloc; |
| |
| switch (subtype) { |
| case VSASSIGN: |
| setvar(str, startp, 0); |
| amount = startp - expdest; |
| STADJUST(amount, expdest); |
| return startp; |
| |
| case VSQUESTION: |
| varunset(p, str, startp, varflags); |
| /* NOTREACHED */ |
| } |
| |
| subtype -= VSTRIMRIGHT; |
| #ifdef DEBUG |
| if (subtype < 0 || subtype > 3) |
| abort(); |
| #endif |
| |
| rmesc = startp; |
| rmescend = stackblock() + strloc; |
| if (quotes) { |
| rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW); |
| if (rmesc != startp) { |
| rmescend = expdest; |
| startp = stackblock() + startloc; |
| } |
| } |
| rmescend--; |
| str = stackblock() + strloc; |
| preglob(str, 0); |
| |
| /* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */ |
| zero = subtype >> 1; |
| /* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */ |
| scan = (subtype & 1) ^ zero ? scanleft : scanright; |
| |
| loc = scan(startp, rmesc, rmescend, str, quotes, zero); |
| if (loc) { |
| if (zero) { |
| memmove(startp, loc, str - loc); |
| loc = startp + (str - loc) - 1; |
| } |
| *loc = '\0'; |
| amount = loc - expdest; |
| STADJUST(amount, expdest); |
| } |
| return loc; |
| } |
| |
| |
| /* |
| * 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; |
| int patloc; |
| int c; |
| int startloc; |
| ssize_t varlen; |
| int easy; |
| int quoted; |
| |
| varflags = *p++; |
| subtype = varflags & VSTYPE; |
| |
| if (!subtype) |
| sh_error("Bad substitution"); |
| |
| quoted = flag & EXP_QUOTED; |
| var = p; |
| easy = (!quoted || (*var == '@' && shellparam.nparam)); |
| startloc = expdest - (char *)stackblock(); |
| p = strchr(p, '=') + 1; |
| |
| again: |
| varlen = varvalue(var, varflags, flag, "ed); |
| if (varflags & VSNUL) |
| varlen--; |
| |
| if (subtype == VSPLUS) { |
| varlen = -1 - varlen; |
| goto vsplus; |
| } |
| |
| if (subtype == VSMINUS) { |
| vsplus: |
| if (varlen < 0) { |
| argstr(p, flag | EXP_TILDE | EXP_WORD); |
| goto end; |
| } |
| goto record; |
| } |
| |
| if (subtype == VSASSIGN || subtype == VSQUESTION) { |
| if (varlen >= 0) |
| goto record; |
| |
| subevalvar(p, var, 0, subtype, startloc, varflags, |
| flag & ~QUOTES_ESC); |
| varflags &= ~VSNUL; |
| /* |
| * Remove any recorded regions beyond |
| * start of variable |
| */ |
| removerecordregions(startloc); |
| goto again; |
| } |
| |
| if (varlen < 0 && uflag) |
| varunset(p, var, 0, 0); |
| |
| if (subtype == VSLENGTH) { |
| cvtnum(varlen > 0 ? varlen : 0); |
| goto record; |
| } |
| |
| if (subtype == VSNORMAL) { |
| record: |
| if (!easy) |
| goto end; |
| recordregion(startloc, expdest - (char *)stackblock(), quoted); |
| goto end; |
| } |
| |
| #ifdef DEBUG |
| switch (subtype) { |
| case VSTRIMLEFT: |
| case VSTRIMLEFTMAX: |
| case VSTRIMRIGHT: |
| case VSTRIMRIGHTMAX: |
| break; |
| default: |
| abort(); |
| } |
| #endif |
| |
| if (varlen >= 0) { |
| /* |
| * Terminate the string and start recording the pattern |
| * right after it |
| */ |
| STPUTC('\0', expdest); |
| patloc = expdest - (char *)stackblock(); |
| if (subevalvar(p, NULL, patloc, subtype, |
| startloc, varflags, flag) == 0) { |
| int amount = expdest - ( |
| (char *)stackblock() + patloc - 1 |
| ); |
| STADJUST(-amount, expdest); |
| } |
| /* Remove any recorded regions beyond start of variable */ |
| removerecordregions(startloc); |
| goto record; |
| } |
| |
| end: |
| if (subtype != VSNORMAL) { /* skip to end of alternative */ |
| int nesting = 1; |
| for (;;) { |
| if ((c = (signed char)*p++) == CTLESC) |
| p++; |
| else if (c == CTLBACKQ) { |
| if (varlen >= 0) |
| argbackq = argbackq->next; |
| } else if (c == CTLVAR) { |
| if ((*p++ & VSTYPE) != VSNORMAL) |
| nesting++; |
| } else if (c == CTLENDVAR) { |
| if (--nesting == 0) |
| break; |
| } |
| } |
| } |
| return p; |
| } |
| |
| |
| /* |
| * Put a string on the stack. |
| */ |
| |
| STATIC void |
| memtodest(const char *p, size_t len, const char *syntax, int quotes) { |
| char *q; |
| |
| if (unlikely(!len)) |
| return; |
| |
| q = makestrspace(len * 2, expdest); |
| |
| do { |
| int c = (signed char)*p++; |
| if (c) { |
| if ((quotes & QUOTES_ESC) && |
| ((syntax[c] == CCTL) || |
| (((quotes & EXP_FULL) || syntax != BASESYNTAX) && |
| syntax[c] == CBACK))) |
| USTPUTC(CTLESC, q); |
| } else if (!(quotes & QUOTES_KEEPNUL)) |
| continue; |
| USTPUTC(c, q); |
| } while (--len); |
| |
| expdest = q; |
| } |
| |
| |
| STATIC size_t |
| strtodest(p, syntax, quotes) |
| const char *p; |
| const char *syntax; |
| int quotes; |
| { |
| size_t len = strlen(p); |
| memtodest(p, len, syntax, quotes); |
| return len; |
| } |
| |
| |
| |
| /* |
| * Add the value of a specialized variable to the stack string. |
| */ |
| |
| STATIC ssize_t |
| varvalue(char *name, int varflags, int flags, int *quotedp) |
| { |
| int num; |
| char *p; |
| int i; |
| int sep; |
| char sepc; |
| char **ap; |
| char const *syntax; |
| int quoted = *quotedp; |
| int subtype = varflags & VSTYPE; |
| int discard = subtype == VSPLUS || subtype == VSLENGTH; |
| int quotes = (discard ? 0 : (flags & QUOTES_ESC)) | QUOTES_KEEPNUL; |
| ssize_t len = 0; |
| |
| sep = (flags & EXP_FULL) << CHAR_BIT; |
| syntax = quoted ? DQSYNTAX : BASESYNTAX; |
| |
| switch (*name) { |
| case '$': |
| num = rootpid; |
| goto numvar; |
| case '?': |
| num = exitstatus; |
| goto numvar; |
| case '#': |
| num = shellparam.nparam; |
| goto numvar; |
| case '!': |
| num = backgndpid; |
| if (num == 0) |
| return -1; |
| numvar: |
| len = cvtnum(num); |
| break; |
| case '-': |
| p = makestrspace(NOPTS, expdest); |
| for (i = NOPTS - 1; i >= 0; i--) { |
| if (optlist[i]) { |
| USTPUTC(optletters[i], p); |
| len++; |
| } |
| } |
| expdest = p; |
| break; |
| case '@': |
| if (quoted && sep) |
| goto param; |
| /* fall through */ |
| case '*': |
| if (quoted) |
| sep = 0; |
| sep |= ifsset() ? ifsval()[0] : ' '; |
| param: |
| sepc = sep; |
| *quotedp = !sepc; |
| if (!(ap = shellparam.p)) |
| return -1; |
| while ((p = *ap++)) { |
| len += strtodest(p, syntax, quotes); |
| |
| if (*ap && sep) { |
| len++; |
| memtodest(&sepc, 1, syntax, quotes); |
| } |
| } |
| break; |
| case '0': |
| case '1': |
| case '2': |
| case '3': |
| case '4': |
| case '5': |
| case '6': |
| case '7': |
| case '8': |
| case '9': |
| num = atoi(name); |
| if (num < 0 || num > shellparam.nparam) |
| return -1; |
| p = num ? shellparam.p[num - 1] : arg0; |
| goto value; |
| default: |
| p = lookupvar(name); |
| value: |
| if (!p) |
| return -1; |
| |
| len = strtodest(p, syntax, quotes); |
| break; |
| } |
| |
| if (discard) |
| STADJUST(-len, expdest); |
| return len; |
| } |
| |
| |
| |
| /* |
| * Record the fact that we have to scan this region of the |
| * string for IFS characters. |
| */ |
| |
| void |
| recordregion(int start, int end, int nulonly) |
| { |
| struct ifsregion *ifsp; |
| |
| if (ifslastp == NULL) { |
| ifsp = &ifsfirst; |
| } else { |
| INTOFF; |
| ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); |
| ifsp->next = NULL; |
| ifslastp->next = ifsp; |
| INTON; |
| } |
| ifslastp = ifsp; |
| ifslastp->begoff = start; |
| ifslastp->endoff = end; |
| ifslastp->nulonly = nulonly; |
| } |
| |
| |
| |
| /* |
| * 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. |
| * If maxargs is non-negative, at most maxargs arguments will be created, by |
| * joining together the last arguments. |
| */ |
| void |
| ifsbreakup(char *string, int maxargs, struct arglist *arglist) |
| { |
| struct ifsregion *ifsp; |
| struct strlist *sp; |
| char *start; |
| char *p; |
| char *q; |
| char *r = NULL; |
| const char *ifs, *realifs; |
| int ifsspc; |
| int nulonly; |
| |
| |
| start = string; |
| if (ifslastp != NULL) { |
| ifsspc = 0; |
| nulonly = 0; |
| realifs = ifsset() ? ifsval() : defifs; |
| ifsp = &ifsfirst; |
| do { |
| p = string + ifsp->begoff; |
| nulonly = ifsp->nulonly; |
| ifs = nulonly ? nullstr : realifs; |
| ifsspc = 0; |
| while (p < string + ifsp->endoff) { |
| int c; |
| bool isifs; |
| bool isdefifs; |
| |
| q = p; |
| c = *p++; |
| if (c == (char)CTLESC) |
| c = *p++; |
| |
| isifs = strchr(ifs, c); |
| isdefifs = false; |
| if (isifs) |
| isdefifs = strchr(defifs, c); |
| |
| /* If only reading one more argument: |
| * If we have exactly one field, |
| * read that field without its terminator. |
| * If we have more than one field, |
| * read all fields including their terminators, |
| * except for trailing IFS whitespace. |
| * |
| * This means that if we have only IFS |
| * characters left, and at most one |
| * of them is non-whitespace, we stop |
| * reading here. |
| * Otherwise, we read all the remaining |
| * characters except for trailing |
| * IFS whitespace. |
| * |
| * In any case, r indicates the start |
| * of the characters to remove, or NULL |
| * if no characters should be removed. |
| */ |
| if (!maxargs) { |
| if (isdefifs) { |
| if (!r) |
| r = q; |
| continue; |
| } |
| |
| if (!(isifs && ifsspc)) |
| r = NULL; |
| |
| ifsspc = 0; |
| continue; |
| } |
| |
| if (ifsspc) { |
| if (isifs) |
| q = p; |
| |
| start = q; |
| |
| if (isdefifs) |
| continue; |
| |
| isifs = false; |
| } |
| |
| if (isifs) { |
| if (!nulonly) |
| ifsspc = isdefifs; |
| /* Ignore IFS whitespace at start */ |
| if (q == start && ifsspc) { |
| start = p; |
| ifsspc = 0; |
| continue; |
| } |
| if (maxargs > 0 && !--maxargs) { |
| r = q; |
| continue; |
| } |
| *q = '\0'; |
| sp = (struct strlist *)stalloc(sizeof *sp); |
| sp->text = start; |
| *arglist->lastp = sp; |
| arglist->lastp = &sp->next; |
| start = p; |
| continue; |
| } |
| |
| ifsspc = 0; |
| } |
| } while ((ifsp = ifsp->next) != NULL); |
| if (nulonly) |
| goto add; |
| } |
| |
| if (r) |
| *r = '\0'; |
| |
| if (!*start) |
| return; |
| |
| add: |
| sp = (struct strlist *)stalloc(sizeof *sp); |
| sp->text = start; |
| *arglist->lastp = sp; |
| arglist->lastp = &sp->next; |
| } |
| |
| void ifsfree(void) |
| { |
| struct ifsregion *p = ifsfirst.next; |
| |
| if (!p) |
| goto out; |
| |
| INTOFF; |
| do { |
| struct ifsregion *ifsp; |
| ifsp = p->next; |
| ckfree(p); |
| p = ifsp; |
| } while (p); |
| ifsfirst.next = NULL; |
| INTON; |
| |
| out: |
| ifslastp = NULL; |
| } |
| |
| |
| |
| /* |
| * Expand shell metacharacters. At this point, the only control characters |
| * should be escapes. The results are stored in the list exparg. |
| */ |
| |
| #ifdef HAVE_GLOB |
| STATIC void |
| expandmeta(str, flag) |
| struct strlist *str; |
| int flag; |
| { |
| /* TODO - EXP_REDIR */ |
| |
| while (str) { |
| const char *p; |
| glob_t pglob; |
| int i; |
| |
| if (fflag) |
| goto nometa; |
| INTOFF; |
| p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); |
| i = glob(p, GLOB_NOMAGIC, 0, &pglob); |
| if (p != str->text) |
| ckfree(p); |
| switch (i) { |
| case 0: |
| if (!(pglob.gl_flags & GLOB_MAGCHAR)) |
| goto nometa2; |
| addglob(&pglob); |
| globfree(&pglob); |
| INTON; |
| break; |
| case GLOB_NOMATCH: |
| nometa2: |
| globfree(&pglob); |
| INTON; |
| nometa: |
| *exparg.lastp = str; |
| rmescapes(str->text); |
| exparg.lastp = &str->next; |
| break; |
| default: /* GLOB_NOSPACE */ |
| sh_error("Out of space"); |
| } |
| str = str->next; |
| } |
| } |
| |
| |
| /* |
| * Add the result of glob(3) to the list. |
| */ |
| |
| STATIC void |
| addglob(pglob) |
| const glob_t *pglob; |
| { |
| char **p = pglob->gl_pathv; |
| |
| do { |
| addfname(*p); |
| } while (*++p); |
| } |
| |
| |
| #else /* HAVE_GLOB */ |
| STATIC char *expdir; |
| |
| |
| STATIC void |
| expandmeta(struct strlist *str, int flag) |
| { |
| static const char metachars[] = { |
| '*', '?', '[', 0 |
| }; |
| /* TODO - EXP_REDIR */ |
| |
| while (str) { |
| struct strlist **savelastp; |
| struct strlist *sp; |
| char *p; |
| |
| if (fflag) |
| goto nometa; |
| if (!strpbrk(str->text, metachars)) |
| goto nometa; |
| savelastp = exparg.lastp; |
| |
| INTOFF; |
| p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP); |
| { |
| int i = strlen(str->text); |
| expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ |
| } |
| |
| expmeta(expdir, p); |
| ckfree(expdir); |
| if (p != str->text) |
| ckfree(p); |
| INTON; |
| 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) |
| { |
| char *p; |
| const char *cp; |
| char *start; |
| char *endname; |
| int metaflag; |
| struct stat64 statb; |
| DIR *dirp; |
| struct dirent *dp; |
| int atend; |
| int matchdot; |
| int esc; |
| |
| metaflag = 0; |
| start = name; |
| for (p = name; esc = 0, *p; p += esc + 1) { |
| if (*p == '*' || *p == '?') |
| metaflag = 1; |
| else if (*p == '[') { |
| char *q = p + 1; |
| if (*q == '!') |
| q++; |
| for (;;) { |
| if (*q == '\\') |
| q++; |
| if (*q == '/' || *q == '\0') |
| break; |
| if (*++q == ']') { |
| metaflag = 1; |
| break; |
| } |
| } |
| } else { |
| if (*p == '\\') |
| 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++; |
| p = name; |
| do { |
| if (*p == '\\') |
| p++; |
| *enddir++ = *p; |
| } while (*p++); |
| if (metaflag == 0 || lstat64(expdir, &statb) >= 0) |
| addfname(expdir); |
| return; |
| } |
| endname = p; |
| if (name < start) { |
| p = name; |
| do { |
| if (*p == '\\') |
| p++; |
| *enddir++ = *p++; |
| } while (p < start); |
| } |
| if (enddir == expdir) { |
| cp = "."; |
| } else if (enddir == expdir + 1 && *expdir == '/') { |
| cp = "/"; |
| } else { |
| cp = expdir; |
| enddir[-1] = '\0'; |
| } |
| if ((dirp = opendir(cp)) == NULL) |
| return; |
| if (enddir != expdir) |
| enddir[-1] = '/'; |
| if (*endname == 0) { |
| atend = 1; |
| } else { |
| atend = 0; |
| *endname = '\0'; |
| endname += esc + 1; |
| } |
| matchdot = 0; |
| p = start; |
| if (*p == '\\') |
| p++; |
| if (*p == '.') |
| matchdot++; |
| while (! int_pending() && (dp = readdir(dirp)) != NULL) { |
| if (dp->d_name[0] == '.' && ! matchdot) |
| continue; |
| if (pmatch(start, dp->d_name)) { |
| if (atend) { |
| scopy(dp->d_name, enddir); |
| addfname(expdir); |
| } else { |
| for (p = enddir, cp = dp->d_name; |
| (*p++ = *cp++) != '\0';) |
| continue; |
| p[-1] = '/'; |
| expmeta(p, endname); |
| } |
| } |
| } |
| closedir(dirp); |
| if (! atend) |
| endname[-esc - 1] = esc ? '\\' : '/'; |
| } |
| #endif /* HAVE_GLOB */ |
| |
| |
| /* |
| * Add a file name to the list. |
| */ |
| |
| STATIC void |
| addfname(char *name) |
| { |
| struct strlist *sp; |
| |
| sp = (struct strlist *)stalloc(sizeof *sp); |
| sp->text = sstrdup(name); |
| *exparg.lastp = sp; |
| exparg.lastp = &sp->next; |
| } |
| |
| |
| #ifndef HAVE_GLOB |
| /* |
| * 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; |
| } |
| #endif |
| |
| |
| /* |
| * Returns true if the pattern matches the string. |
| */ |
| |
| STATIC inline int |
| patmatch(char *pattern, const char *string) |
| { |
| return pmatch(preglob(pattern, 0), string); |
| } |
| |
| |
| #ifndef HAVE_FNMATCH |
| STATIC int ccmatch(const char *p, int chr, const char **r) |
| { |
| static const struct class { |
| char name[10]; |
| int (*fn)(int); |
| } classes[] = { |
| { .name = ":alnum:]", .fn = isalnum }, |
| { .name = ":cntrl:]", .fn = iscntrl }, |
| { .name = ":lower:]", .fn = islower }, |
| { .name = ":space:]", .fn = isspace }, |
| { .name = ":alpha:]", .fn = isalpha }, |
| { .name = ":digit:]", .fn = isdigit }, |
| { .name = ":print:]", .fn = isprint }, |
| { .name = ":upper:]", .fn = isupper }, |
| { .name = ":blank:]", .fn = isblank }, |
| { .name = ":graph:]", .fn = isgraph }, |
| { .name = ":punct:]", .fn = ispunct }, |
| { .name = ":xdigit:]", .fn = isxdigit }, |
| }; |
| const struct class *class, *end; |
| |
| end = classes + sizeof(classes) / sizeof(classes[0]); |
| for (class = classes; class < end; class++) { |
| const char *q; |
| |
| q = prefix(p, class->name); |
| if (!q) |
| continue; |
| *r = q; |
| return class->fn(chr); |
| } |
| |
| *r = 0; |
| return 0; |
| } |
| |
| STATIC int |
| pmatch(const char *pattern, const char *string) |
| { |
| const char *p, *q; |
| char c; |
| |
| p = pattern; |
| q = string; |
| for (;;) { |
| switch (c = *p++) { |
| case '\0': |
| goto breakloop; |
| case '\\': |
| if (*p) { |
| c = *p++; |
| } |
| goto dft; |
| case '?': |
| if (*q++ == '\0') |
| return 0; |
| break; |
| case '*': |
| c = *p; |
| while (c == '*') |
| c = *++p; |
| if (c != '\\' && c != '?' && c != '*' && c != '[') { |
| while (*q != c) { |
| if (*q == '\0') |
| return 0; |
| q++; |
| } |
| } |
| do { |
| if (pmatch(p, q)) |
| return 1; |
| } while (*q++ != '\0'); |
| return 0; |
| case '[': { |
| const char *startp; |
| int invert, found; |
| char chr; |
| |
| startp = p; |
| invert = 0; |
| if (*p == '!') { |
| invert++; |
| p++; |
| } |
| found = 0; |
| chr = *q; |
| if (chr == '\0') |
| return 0; |
| c = *p++; |
| do { |
| if (!c) { |
| p = startp; |
| c = '['; |
| goto dft; |
| } |
| if (c == '[') { |
| const char *r; |
| |
| found |= !!ccmatch(p, chr, &r); |
| if (r) { |
| p = r; |
| continue; |
| } |
| } else if (c == '\\') |
| c = *p++; |
| if (*p == '-' && p[1] != ']') { |
| p++; |
| if (*p == '\\') |
| p++; |
| if (chr >= c && chr <= *p) |
| found = 1; |
| p++; |
| } else { |
| if (chr == c) |
| found = 1; |
| } |
| } while ((c = *p++) != ']'); |
| if (found == invert) |
| return 0; |
| q++; |
| break; |
| } |
| dft: default: |
| if (*q++ != c) |
| return 0; |
| break; |
| } |
| } |
| breakloop: |
| if (*q != '\0') |
| return 0; |
| return 1; |
| } |
| #endif |
| |
| |
| |
| /* |
| * Remove any CTLESC characters from a string. |
| */ |
| |
| char * |
| _rmescapes(char *str, int flag) |
| { |
| char *p, *q, *r; |
| unsigned inquotes; |
| int notescaped; |
| int globbing; |
| |
| p = strpbrk(str, qchars); |
| if (!p) { |
| return str; |
| } |
| q = p; |
| r = str; |
| if (flag & RMESCAPE_ALLOC) { |
| size_t len = p - str; |
| size_t fulllen = len + strlen(p) + 1; |
| |
| if (flag & RMESCAPE_GROW) { |
| int strloc = str - (char *)stackblock(); |
| |
| r = makestrspace(fulllen, expdest); |
| str = (char *)stackblock() + strloc; |
| p = str + len; |
| } else if (flag & RMESCAPE_HEAP) { |
| r = ckmalloc(fulllen); |
| } else { |
| r = stalloc(fulllen); |
| } |
| q = r; |
| if (len > 0) { |
| q = mempcpy(q, str, len); |
| } |
| } |
| inquotes = 0; |
| globbing = flag & RMESCAPE_GLOB; |
| notescaped = globbing; |
| while (*p) { |
| if (*p == (char)CTLQUOTEMARK) { |
| inquotes = ~inquotes; |
| p++; |
| notescaped = globbing; |
| continue; |
| } |
| if (*p == (char)CTLESC) { |
| p++; |
| if (notescaped) |
| *q++ = '\\'; |
| } else if (*p == '\\' && !inquotes) { |
| /* naked back slash */ |
| notescaped = 0; |
| goto copy; |
| } |
| notescaped = globbing; |
| copy: |
| *q++ = *p++; |
| } |
| *q = '\0'; |
| if (flag & RMESCAPE_GROW) { |
| expdest = r; |
| STADJUST(q - r + 1, expdest); |
| } |
| return r; |
| } |
| |
| |
| |
| /* |
| * See if a pattern matches in a case statement. |
| */ |
| |
| int |
| casematch(union node *pattern, char *val) |
| { |
| struct stackmark smark; |
| int result; |
| |
| setstackmark(&smark); |
| argbackq = pattern->narg.backquote; |
| STARTSTACKSTR(expdest); |
| argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); |
| STACKSTRNUL(expdest); |
| ifsfree(); |
| result = patmatch(stackblock(), val); |
| popstackmark(&smark); |
| return result; |
| } |
| |
| /* |
| * Our own itoa(). |
| */ |
| |
| STATIC int |
| cvtnum(intmax_t num) |
| { |
| int len = max_int_length(sizeof(num)); |
| |
| expdest = makestrspace(len, expdest); |
| len = fmtstr(expdest, len, "%" PRIdMAX, num); |
| STADJUST(len, expdest); |
| return len; |
| } |
| |
| STATIC void |
| varunset(const char *end, const char *var, const char *umsg, int varflags) |
| { |
| const char *msg; |
| const char *tail; |
| |
| tail = nullstr; |
| msg = "parameter not set"; |
| if (umsg) { |
| if (*end == (char)CTLENDVAR) { |
| if (varflags & VSNUL) |
| tail = " or null"; |
| } else |
| msg = umsg; |
| } |
| sh_error("%.*s: %s%s", end - var - 1, var, msg, tail); |
| } |
| |
| #ifdef mkinit |
| |
| INCLUDE "expand.h" |
| |
| RESET { |
| ifsfree(); |
| } |
| |
| #endif |