diff options
Diffstat (limited to 'src/expand.c')
-rw-r--r-- | src/expand.c | 1759 |
1 files changed, 1759 insertions, 0 deletions
diff --git a/src/expand.c b/src/expand.c new file mode 100644 index 0000000..800011e --- /dev/null +++ b/src/expand.c @@ -0,0 +1,1759 @@ +/* $NetBSD: expand.c,v 1.56 2002/11/24 22:35:39 christos Exp $ */ + +/*- + * 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. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 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. + */ + +#include <sys/cdefs.h> +#ifndef lint +#if 0 +static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#else +__RCSID("$NetBSD: expand.c,v 1.56 2002/11/24 22:35:39 christos Exp $"); +#endif +#endif /* not lint */ + +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> +#include <dirent.h> +#include <unistd.h> +#include <pwd.h> +#include <stdlib.h> +#include <stdio.h> +#include <limits.h> +#include <string.h> +#if defined(__GLIBC__) +#if !defined(FNMATCH_BROKEN) +#include <fnmatch.h> +#if !defined(GLOB_BROKEN) +#include <glob.h> +#endif +#else +#include <ctype.h> +#endif +#endif + +/* + * 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 "show.h" + +/* + * _rmescape() flags + */ +#define RMESCAPE_ALLOC 0x1 /* Allocate a new string */ +#define RMESCAPE_GLOB 0x2 /* Add backslashes for glob */ +#define RMESCAPE_QUOTED 0x4 /* Remove CTLESC unless in quotes */ +#define RMESCAPE_GROW 0x8 /* Grow strings instead of stalloc */ +#define RMESCAPE_HEAP 0x10 /* Malloc strings instead of stalloc */ + +/* + * 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, int); +STATIC const char *subevalvar(char *, char *, int, int, int, int, int); +STATIC char *evalvar(char *, int); +STATIC void strtodest(const char *, const char *, int); +STATIC void memtodest(const char *, size_t, const char *, int); +STATIC ssize_t varvalue(char *, int, int); +STATIC void recordregion(int, int, int); +STATIC void removerecordregions(int); +STATIC void ifsbreakup(char *, struct arglist *); +STATIC void ifsfree(void); +STATIC void expandmeta(struct strlist *, int); +#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) +STATIC void addglob(const glob_t *); +#else +STATIC void expmeta(char *, char *); +#endif +STATIC void addfname(char *); +#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)) +STATIC struct strlist *expsort(struct strlist *); +STATIC struct strlist *msort(struct strlist *, int); +#endif +STATIC int patmatch(char *, const char *); +#if !defined(__GLIBC__) || defined(FNMATCH_BROKEN) +STATIC int pmatch(const char *, const char *); +#else +#define pmatch(a, b) !fnmatch((a), (b), 0) +#endif +STATIC int cvtnum(long); +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 quoted, int flag) { + flag |= RMESCAPE_GLOB; + if (quoted) { + flag |= RMESCAPE_QUOTED; + } + return _rmescapes((char *)pattern, flag); +} + + +STATIC size_t +esclen(const char *start, const char *p) { + size_t esc = 0; + + while (p > start && *--p == CTLESC) { + esc++; + } + return esc; +} + + +/* + * Expand shell variables and backquotes inside a here document. + */ + +void +expandhere(union node *arg, int fd) +{ + herefd = fd; + expandarg(arg, (struct arglist *)NULL, 0); + xwrite(fd, stackblock(), expdest - (char *)stackblock()); +} + + +/* + * 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); + ifsfirst.next = NULL; + ifslastp = NULL; + argstr(arg->narg.text, flag); + p = _STPUTC('\0', expdest); + expdest = p - 1; + if (arglist == NULL) { + return; /* here document expanded */ + } + p = grabstackstr(p); + 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; + } + if (ifsfirst.next) + ifsfree(); + *exparg.lastp = NULL; + if (exparg.list) { + *arglist->lastp = exparg.list; + arglist->lastp = exparg.lastp; + } +} + + + +/* + * 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, + CTLBACKQ | CTLQUOTE, + CTLENDARI, + 0 + }; + const char *reject = spclchars; + int c; + int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ + int breakall = flag & 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 == CTLESC && (flag & EXP_QWORD)) + q++; + if (*q == '~') + p = exptilde(p, q, flag); + } +start: + startloc = expdest - (char *)stackblock(); + for (;;) { + length += strcspn(p + length, reject); + c = 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: + /* "$@" syntax adherence hack */ + if ( + !inquotes && + !memcmp(p, dolatstr, DOLATSTRLEN) && + (p[4] == CTLQUOTEMARK || ( + p[4] == CTLENDVAR && + p[5] == CTLQUOTEMARK + )) + ) { + p = evalvar(p + 1, flag) + 1; + goto start; + } + inquotes = !inquotes; +addquote: + if (quotes) { + p--; + length++; + startloc++; + } + break; + case CTLESC: + startloc++; + length++; + goto addquote; + case CTLVAR: + p = evalvar(p, flag); + goto start; + case CTLBACKQ: + c = 0; + case CTLBACKQ|CTLQUOTE: + expbackq(argbackq->n, c, quotes); + argbackq = argbackq->next; + goto start; + case CTLENDARI: + p--; + expari(quotes); + goto start; + } + } +breakloop: + ; +} + +STATIC char * +exptilde(char *startp, char *p, int flag) +{ + char c; + char *name; + struct passwd *pw; + const char *home; + int quotes = flag & (EXP_FULL | EXP_CASE); + int startloc; + + 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') { + if ((home = lookupvar(homestr)) == NULL) + goto lose; + } else { + if ((pw = getpwnam(name)) == NULL) + goto lose; + home = pw->pw_dir; + } + if (*home == '\0') + goto lose; + *p = c; + startloc = expdest - (char *)stackblock(); + strtodest(home, SQSYNTAX, quotes); + recordregion(startloc, expdest - (char *)stackblock(), 0); + return (p); +lose: + *p = c; + return (startp); +} + + +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. Backup to start of expression, + * evaluate, place result in (backed up) result, adjust string position. + */ +void +expari(int quotes) +{ + char *p, *start; + int begoff; + int flag; + int len; + + /* ifsfree(); */ + + /* + * This routine is slightly over-complicated for + * efficiency. Next we scan backwards looking for the + * start of arithmetic. + */ + start = stackblock(); + p = expdest - 1; + *p = '\0'; + p--; + do { + int esc; + + while (*p != CTLARI) { + p--; +#ifdef DEBUG + if (p < start) { + 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); + + flag = p[1]; + + expdest = p; + + if (quotes) + rmescapes(p + 2); + + len = cvtnum(arith(p + 2)); + + if (flag != '"') + recordregion(begoff, begoff + len, 0); +} + + +/* + * Expand stuff in backwards quotes. + */ + +STATIC void +expbackq(union node *cmd, int quoted, int quotes) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest; + int startloc; + char const *syntax = quoted? DQSYNTAX : BASESYNTAX; + struct stackmark smark; + + INTOFF; + setstackmark(&smark); + dest = expdest; + startloc = dest - (char *)stackblock(); + grabstackstr(dest); + evalbackcmd(cmd, (struct backcmd *) &in); + popstackmark(&smark); + + p = in.buf; + i = in.nleft; + if (i == 0) + goto read; + for (;;) { + memtodest(p, i, syntax, quotes); +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 (quoted == 0) + 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 == 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 quotes) +{ + char *startp; + char *loc; + int saveherefd = herefd; + struct nodelist *saveargbackq = argbackq; + int amount; + char *rmesc, *rmescend; + int zero; + char *(*scan)(char *, char *, char *, char *, int , int); + + herefd = -1; + argstr(p, subtype != VSASSIGN && subtype != VSQUESTION ? EXP_CASE : 0); + STPUTC('\0', expdest); + herefd = saveherefd; + 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, varflags & VSQUOTE, 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 quotes; + int quoted; + + quotes = flag & (EXP_FULL | EXP_CASE); + varflags = *p++; + subtype = varflags & VSTYPE; + quoted = varflags & VSQUOTE; + var = p; + easy = (!quoted || (*var == '@' && shellparam.nparam)); + startloc = expdest - (char *)stackblock(); + p = strchr(p, '=') + 1; + +again: + varlen = varvalue(var, varflags, flag); + if (varflags & VSNUL) + varlen--; + + if (subtype == VSPLUS) { + varlen = -1 - varlen; + goto vsplus; + } + + if (subtype == VSMINUS) { +vsplus: + if (varlen < 0) { + argstr( + p, flag | EXP_TILDE | + (quoted ? EXP_QWORD : EXP_WORD) + ); + goto end; + } + if (easy) + goto record; + goto end; + } + + if (subtype == VSASSIGN || subtype == VSQUESTION) { + if (varlen < 0) { + if (subevalvar(p, var, 0, subtype, startloc, + varflags, 0)) { + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(startloc); + goto again; + } + goto end; + } + if (easy) + goto record; + goto end; + } + + if (varlen < 0 && uflag) + varunset(p, var, 0, 0); + + if (subtype == VSLENGTH) { + cvtnum(varlen > 0 ? varlen : 0); + goto record; + } + + if (subtype == VSNORMAL) { + if (!easy) + goto end; +record: + 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, quotes) == 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 = *p++) == CTLESC) + p++; + else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { + 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 = expdest; + + q = makestrspace(len * 2, q); + + while (len--) { + int c = *p++; + if (!c) + continue; + if (quotes && (syntax[c] == CCTL || syntax[c] == CBACK)) + USTPUTC(CTLESC, q); + USTPUTC(c, q); + } + + expdest = q; +} + + +STATIC void +strtodest(p, syntax, quotes) + const char *p; + const char *syntax; + int quotes; +{ + memtodest(p, strlen(p), syntax, quotes); +} + + + +/* + * Add the value of a specialized variable to the stack string. + */ + +STATIC ssize_t +varvalue(char *name, int varflags, int flags) +{ + int num; + char *p; + int i; + int sep = 0; + int sepq = 0; + ssize_t len = 0; + char **ap; + char const *syntax; + int quoted = varflags & VSQUOTE; + int subtype = varflags & VSTYPE; + int quotes = flags & (EXP_FULL | EXP_CASE); + + if (quoted && (flags & EXP_FULL)) + sep = 1 << 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 (sep) + goto param; + /* fall through */ + case '*': + sep = ifsset() ? ifsval()[0] : ' '; + if (quotes && (syntax[sep] == CCTL || syntax[sep] == CBACK)) + sepq = 1; +param: + if (!(ap = shellparam.p)) + return -1; + while ((p = *ap++)) { + size_t partlen; + + partlen = strlen(p); + + len += partlen; + if (len > partlen && sep) { + char *q; + + len++; + if (subtype == VSPLUS || subtype == VSLENGTH) { + continue; + } + q = expdest; + if (sepq) + STPUTC(CTLESC, q); + STPUTC(sep, q); + expdest = q; + } + + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, partlen, syntax, quotes); + } + return len; + 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 = strlen(p); + if (!(subtype == VSPLUS || subtype == VSLENGTH)) + memtodest(p, len, syntax, quotes); + return len; + } + + if (subtype == VSPLUS || subtype == VSLENGTH) + STADJUST(-len, expdest); + return len; +} + + + +/* + * Record the fact that we have to scan this region of the + * string for IFS characters. + */ + +STATIC 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. + */ +STATIC void +ifsbreakup(char *string, struct arglist *arglist) +{ + struct ifsregion *ifsp; + struct strlist *sp; + char *start; + char *p; + char *q; + 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) { + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p)) { + if (!nulonly) + ifsspc = (strchr(defifs, *p) != NULL); + /* Ignore IFS whitespace at start */ + if (q == start && ifsspc) { + p++; + start = p; + continue; + } + *q = '\0'; + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + p++; + if (!nulonly) { + for (;;) { + if (p >= string + ifsp->endoff) { + break; + } + q = p; + if (*p == CTLESC) + p++; + if (strchr(ifs, *p) == NULL ) { + p = q; + break; + } else if (strchr(defifs, *p) == NULL) { + if (ifsspc) { + p++; + ifsspc = 0; + } else { + p = q; + break; + } + } else + p++; + } + } + start = p; + } else + p++; + } + } while ((ifsp = ifsp->next) != NULL); + if (nulonly) + goto add; + } + + if (!*start) + return; + +add: + sp = (struct strlist *)stalloc(sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; +} + +STATIC void +ifsfree(void) +{ + struct ifsregion *p; + + INTOFF; + p = ifsfirst.next; + do { + struct ifsregion *ifsp; + ifsp = p->next; + ckfree(p); + p = ifsp; + } while (p); + ifslastp = NULL; + ifsfirst.next = NULL; + INTON; +} + + + +/* + * Expand shell metacharacters. At this point, the only control characters + * should be escapes. The results are stored in the list exparg. + */ + +#if defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) +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, 0, 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 */ + 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 /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */ +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, 0, 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; + + metaflag = 0; + start = name; + for (p = name; *p; p++) { + 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 == '\\') + p++; + else if (*p == '/') { + if (metaflag) + goto out; + start = p + 1; + } + } +out: + 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'; + } + 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[-1] = '/'; +} +#endif /* defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN) */ + + +/* + * 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; +} + + +#if !(defined(__GLIBC__) && !defined(FNMATCH_BROKEN) && !defined(GLOB_BROKEN)) +/* + * 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, 0), string); +} + + +#if !defined(__GLIBC__) || defined(FNMATCH_BROKEN) +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 = *p; + 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; + 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; + static const char qchars[] = { CTLESC, CTLQUOTEMARK, 0 }; + 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) { + r = makestrspace(fulllen, expdest); + } else if (flag & RMESCAPE_HEAP) { + r = ckmalloc(fulllen); + } else { + r = stalloc(fulllen); + } + q = r; + if (len > 0) { +#ifdef _GNU_SOURCE + q = mempcpy(q, str, len); +#else + memcpy(q, str, len); + q += len; +#endif + } + } + inquotes = (flag & RMESCAPE_QUOTED) ^ RMESCAPE_QUOTED; + globbing = flag & RMESCAPE_GLOB; + notescaped = globbing; + while (*p) { + if (*p == CTLQUOTEMARK) { + inquotes = ~inquotes; + p++; + notescaped = globbing; + continue; + } + if (*p == '\\') { + /* naked back slash */ + notescaped = 0; + goto copy; + } + if (*p == CTLESC) { + p++; + if (notescaped && inquotes && *p != '/') { + *q++ = '\\'; + } + } + 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); + ifslastp = NULL; + argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); + STACKSTRNUL(expdest); + result = patmatch(stackblock(), val); + popstackmark(&smark); + return result; +} + +/* + * Our own itoa(). + */ + +STATIC int +cvtnum(long num) +{ + int len; + + expdest = makestrspace(32, expdest); + len = fmtstr(expdest, 32, "%ld", 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 == CTLENDVAR) { + if (varflags & VSNUL) + tail = " or null"; + } else + msg = umsg; + } + error("%.*s: %s%s", end - var - 1, var, msg, tail); +} |