| /* $NetBSD: expand.c,v 1.68.2.2 2005/04/07 11:37:39 tron 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. 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.68.2.2 2005/04/07 11:37:39 tron Exp $"); |
| #endif |
| #endif /* not lint */ |
| |
| #include <sys/types.h> |
| #include <sys/time.h> |
| #include <sys/stat.h> |
| #include <errno.h> |
| #include <dirent.h> |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdio.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 "input.h" |
| #include "output.h" |
| #include "memalloc.h" |
| #include "error.h" |
| #include "mystring.h" |
| #include "show.h" |
| |
| /* |
| * Structure specifying which parts of the string should be searched |
| * for IFS characters. |
| */ |
| |
| struct ifsregion { |
| struct ifsregion *next; /* next region in list */ |
| int begoff; /* offset of start of region */ |
| int endoff; /* offset of end of region */ |
| int inquotes; /* search for nul bytes only */ |
| }; |
| |
| |
| char *expdest; /* output of current string */ |
| struct nodelist *argbackq; /* list of back quote expressions */ |
| struct ifsregion ifsfirst; /* first struct in list of ifs regions */ |
| struct ifsregion *ifslastp; /* last struct in list */ |
| struct arglist exparg; /* holds expanded arg list */ |
| |
| STATIC void argstr(char *, int); |
| STATIC char *exptilde(char *, int); |
| STATIC void expbackq(union node *, int, int); |
| STATIC int subevalvar(char *, char *, int, int, int, int); |
| STATIC char *evalvar(char *, int); |
| STATIC int varisset(char *, int); |
| STATIC void varvalue(char *, int, 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); |
| STATIC void expmeta(char *, char *); |
| STATIC void addfname(char *); |
| STATIC struct strlist *expsort(struct strlist *); |
| STATIC struct strlist *msort(struct strlist *, int); |
| STATIC int pmatch(char *, char *, int); |
| STATIC char *cvtnum(int, char *); |
| |
| /* |
| * 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 - 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); |
| if (arglist == NULL) { |
| return; /* here document expanded */ |
| } |
| STPUTC('\0', expdest); |
| p = grabstackstr(expdest); |
| exparg.lastp = &exparg.list; |
| /* |
| * TODO - EXP_REDIR |
| */ |
| if (flag & EXP_FULL) { |
| ifsbreakup(p, &exparg); |
| *exparg.lastp = NULL; |
| exparg.lastp = &exparg.list; |
| expandmeta(exparg.list, flag); |
| } else { |
| if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ |
| rmescapes(p); |
| sp = (struct strlist *)stalloc(sizeof (struct strlist)); |
| sp->text = p; |
| *exparg.lastp = sp; |
| exparg.lastp = &sp->next; |
| } |
| 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) |
| { |
| char c; |
| int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ |
| int firsteq = 1; |
| const char *ifs = 0; |
| int ifs_split = EXP_IFS_SPLIT; |
| |
| if (flag & EXP_IFS_SPLIT) |
| ifs = ifsset() ? ifsval() : " \t\n"; |
| |
| if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) |
| p = exptilde(p, flag); |
| for (;;) { |
| switch (c = *p++) { |
| case '\0': |
| case CTLENDVAR: /* end of expanding yyy in ${xxx-yyy} */ |
| return; |
| case CTLQUOTEMARK: |
| /* "$@" syntax adherence hack */ |
| if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=') |
| break; |
| if ((flag & EXP_FULL) != 0) |
| STPUTC(c, expdest); |
| ifs_split = 0; |
| break; |
| case CTLQUOTEEND: |
| ifs_split = EXP_IFS_SPLIT; |
| break; |
| case CTLESC: |
| if (quotes) |
| STPUTC(c, expdest); |
| c = *p++; |
| STPUTC(c, expdest); |
| break; |
| case CTLVAR: |
| p = evalvar(p, (flag & ~EXP_IFS_SPLIT) | (flag & ifs_split)); |
| break; |
| case CTLBACKQ: |
| case CTLBACKQ|CTLQUOTE: |
| expbackq(argbackq->n, c & CTLQUOTE, flag); |
| argbackq = argbackq->next; |
| break; |
| case CTLENDARI: |
| expari(flag); |
| break; |
| case ':': |
| case '=': |
| /* |
| * sort of a hack - expand tildes in variable |
| * assignments (after the first '=' and after ':'s). |
| */ |
| STPUTC(c, expdest); |
| if (flag & EXP_VARTILDE && *p == '~') { |
| if (c == '=') { |
| if (firsteq) |
| firsteq = 0; |
| else |
| break; |
| } |
| p = exptilde(p, flag); |
| } |
| break; |
| default: |
| STPUTC(c, expdest); |
| if (flag & EXP_IFS_SPLIT & ifs_split && strchr(ifs, c) != NULL) { |
| /* We need to get the output split here... */ |
| recordregion(expdest - stackblock() - 1, |
| expdest - stackblock(), 0); |
| } |
| break; |
| } |
| } |
| } |
| |
| STATIC char * |
| exptilde(char *p, int flag) |
| { |
| char c, *startp = p; |
| const char *home; |
| int quotes = flag & (EXP_FULL | EXP_CASE); |
| |
| while ((c = *p) != '\0') { |
| switch(c) { |
| case CTLESC: |
| return (startp); |
| case CTLQUOTEMARK: |
| return (startp); |
| case ':': |
| if (flag & EXP_VARTILDE) |
| goto done; |
| break; |
| case '/': |
| goto done; |
| } |
| p++; |
| } |
| done: |
| *p = '\0'; |
| if (*(startp+1) == '\0') { |
| if ((home = lookupvar("HOME")) == NULL) |
| goto lose; |
| } else |
| goto lose; |
| if (*home == '\0') |
| goto lose; |
| *p = c; |
| while ((c = *home++) != '\0') { |
| if (quotes && SQSYNTAX[(int)c] == CCTL) |
| STPUTC(CTLESC, expdest); |
| STPUTC(c, expdest); |
| } |
| 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 flag) |
| { |
| char *p, *start; |
| int result; |
| int begoff; |
| int quotes = flag & (EXP_FULL | EXP_CASE); |
| int quoted; |
| |
| /* ifsfree(); */ |
| |
| /* |
| * This routine is slightly over-complicated for |
| * efficiency. First we make sure there is |
| * enough space for the result, which may be bigger |
| * than the expression if we add exponentation. Next we |
| * scan backwards looking for the start of arithmetic. If the |
| * next previous character is a CTLESC character, then we |
| * have to rescan starting from the beginning since CTLESC |
| * characters have to be processed left to right. |
| */ |
| #if INT_MAX / 1000000000 >= 10 || INT_MIN / 1000000000 <= -10 |
| #error "integers with more than 10 digits are not supported" |
| #endif |
| CHECKSTRSPACE(12 - 2, expdest); |
| USTPUTC('\0', expdest); |
| start = stackblock(); |
| p = expdest - 1; |
| while (*p != CTLARI && p >= start) |
| --p; |
| if (*p != CTLARI) |
| error("missing CTLARI (shouldn't happen)"); |
| if (p > start && *(p-1) == CTLESC) |
| for (p = start; *p != CTLARI; p++) |
| if (*p == CTLESC) |
| p++; |
| |
| if (p[1] == '"') |
| quoted=1; |
| else |
| quoted=0; |
| begoff = p - start; |
| removerecordregions(begoff); |
| if (quotes) |
| rmescapes(p+2); |
| result = arith(p+2); |
| fmtstr(p, 12, "%d", result); |
| |
| while (*p++) |
| ; |
| |
| if (quoted == 0) |
| recordregion(begoff, p - 1 - start, 0); |
| result = expdest - p + 1; |
| STADJUST(-result, expdest); |
| } |
| |
| |
| /* |
| * Expand stuff in backwards quotes. |
| */ |
| |
| STATIC void |
| expbackq(union node *cmd, int quoted, int flag) |
| { |
| struct backcmd in; |
| int i; |
| char buf[128]; |
| char *p; |
| char *dest = expdest; |
| struct ifsregion saveifs, *savelastp; |
| struct nodelist *saveargbackq; |
| char lastc; |
| int startloc = dest - stackblock(); |
| char const *syntax = quoted? DQSYNTAX : BASESYNTAX; |
| int saveherefd; |
| int quotes = flag & (EXP_FULL | EXP_CASE); |
| |
| INTOFF; |
| saveifs = ifsfirst; |
| savelastp = ifslastp; |
| saveargbackq = argbackq; |
| saveherefd = herefd; |
| herefd = -1; |
| p = grabstackstr(dest); |
| evalbackcmd(cmd, &in); |
| ungrabstackstr(p, dest); |
| ifsfirst = saveifs; |
| ifslastp = savelastp; |
| argbackq = saveargbackq; |
| herefd = saveherefd; |
| |
| p = in.buf; |
| lastc = '\0'; |
| for (;;) { |
| if (--in.nleft < 0) { |
| if (in.fd < 0) |
| break; |
| while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR); |
| TRACE(("expbackq: read returns %d\n", i)); |
| if (i <= 0) |
| break; |
| p = buf; |
| in.nleft = i - 1; |
| } |
| lastc = *p++; |
| if (lastc != '\0') { |
| if (quotes && syntax[(int)lastc] == CCTL) |
| STPUTC(CTLESC, dest); |
| STPUTC(lastc, dest); |
| } |
| } |
| |
| /* Eat all trailing newlines */ |
| p = stackblock() + startloc; |
| while (dest > p && dest[-1] == '\n') |
| STUNPUTC(dest); |
| |
| if (in.fd >= 0) |
| close(in.fd); |
| if (in.buf) |
| ckfree(in.buf); |
| if (in.jp) |
| back_exitstatus = waitforjob(in.jp); |
| if (quoted == 0) |
| recordregion(startloc, dest - stackblock(), 0); |
| TRACE(("evalbackq: size=%d: \"%.*s\"\n", |
| (dest - stackblock()) - startloc, |
| (dest - stackblock()) - startloc, |
| stackblock() + startloc)); |
| expdest = dest; |
| INTON; |
| } |
| |
| |
| |
| STATIC int |
| subevalvar(char *p, char *str, int strloc, int subtype, int startloc, int varflags) |
| { |
| char *startp; |
| char *loc = NULL; |
| char *q; |
| int c = 0; |
| int saveherefd = herefd; |
| struct nodelist *saveargbackq = argbackq; |
| int amount; |
| |
| herefd = -1; |
| argstr(p, 0); |
| STACKSTRNUL(expdest); |
| herefd = saveherefd; |
| argbackq = saveargbackq; |
| startp = stackblock() + startloc; |
| if (str == NULL) |
| str = stackblock() + strloc; |
| |
| switch (subtype) { |
| case VSASSIGN: |
| setvar(str, startp, 0); |
| amount = startp - expdest; |
| STADJUST(amount, expdest); |
| varflags &= ~VSNUL; |
| if (c != 0) |
| *loc = c; |
| return 1; |
| |
| case VSQUESTION: |
| if (*p != CTLENDVAR) { |
| outfmt(&errout, "%s\n", startp); |
| error((char *)NULL); |
| } |
| error("%.*s: parameter %snot set", p - str - 1, |
| str, (varflags & VSNUL) ? "null or " |
| : nullstr); |
| /* NOTREACHED */ |
| |
| case VSTRIMLEFT: |
| for (loc = startp; loc < str; loc++) { |
| c = *loc; |
| *loc = '\0'; |
| if (patmatch(str, startp, varflags & VSQUOTE)) |
| goto recordleft; |
| *loc = c; |
| if ((varflags & VSQUOTE) && *loc == CTLESC) |
| loc++; |
| } |
| return 0; |
| |
| case VSTRIMLEFTMAX: |
| for (loc = str - 1; loc >= startp;) { |
| c = *loc; |
| *loc = '\0'; |
| if (patmatch(str, startp, varflags & VSQUOTE)) |
| goto recordleft; |
| *loc = c; |
| loc--; |
| if ((varflags & VSQUOTE) && loc > startp && |
| *(loc - 1) == CTLESC) { |
| for (q = startp; q < loc; q++) |
| if (*q == CTLESC) |
| q++; |
| if (q > loc) |
| loc--; |
| } |
| } |
| return 0; |
| |
| case VSTRIMRIGHT: |
| for (loc = str - 1; loc >= startp;) { |
| if (patmatch(str, loc, varflags & VSQUOTE)) |
| goto recordright; |
| loc--; |
| if ((varflags & VSQUOTE) && loc > startp && |
| *(loc - 1) == CTLESC) { |
| for (q = startp; q < loc; q++) |
| if (*q == CTLESC) |
| q++; |
| if (q > loc) |
| loc--; |
| } |
| } |
| return 0; |
| |
| case VSTRIMRIGHTMAX: |
| for (loc = startp; loc < str - 1; loc++) { |
| if (patmatch(str, loc, varflags & VSQUOTE)) |
| goto recordright; |
| if ((varflags & VSQUOTE) && *loc == CTLESC) |
| loc++; |
| } |
| return 0; |
| |
| default: |
| abort(); |
| } |
| |
| recordleft: |
| *loc = c; |
| amount = ((str - 1) - (loc - startp)) - expdest; |
| STADJUST(amount, expdest); |
| while (loc != str - 1) |
| *startp++ = *loc++; |
| return 1; |
| |
| recordright: |
| amount = loc - expdest; |
| STADJUST(amount, expdest); |
| STPUTC('\0', expdest); |
| STADJUST(-1, expdest); |
| return 1; |
| } |
| |
| |
| /* |
| * Expand a variable, and return a pointer to the next character in the |
| * input string. |
| */ |
| |
| STATIC char * |
| evalvar(char *p, int flag) |
| { |
| int subtype; |
| int varflags; |
| char *var; |
| char *val; |
| int patloc; |
| int c; |
| int set; |
| int special; |
| int startloc; |
| int varlen; |
| int apply_ifs; |
| int quotes = flag & (EXP_FULL | EXP_CASE); |
| |
| varflags = (unsigned char)*p++; |
| subtype = varflags & VSTYPE; |
| var = p; |
| special = !is_name(*p); |
| p = strchr(p, '=') + 1; |
| |
| again: /* jump here after setting a variable with ${var=text} */ |
| if (special) { |
| set = varisset(var, varflags & VSNUL); |
| val = NULL; |
| } else { |
| val = lookupvar(var); |
| if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { |
| val = NULL; |
| set = 0; |
| } else |
| set = 1; |
| } |
| |
| varlen = 0; |
| startloc = expdest - stackblock(); |
| |
| if (!set && uflag) { |
| switch (subtype) { |
| case VSNORMAL: |
| case VSTRIMLEFT: |
| case VSTRIMLEFTMAX: |
| case VSTRIMRIGHT: |
| case VSTRIMRIGHTMAX: |
| case VSLENGTH: |
| error("%.*s: parameter not set", p - var - 1, var); |
| /* NOTREACHED */ |
| } |
| } |
| |
| if (set && subtype != VSPLUS) { |
| /* insert the value of the variable */ |
| if (special) { |
| varvalue(var, varflags & VSQUOTE, subtype, flag); |
| if (subtype == VSLENGTH) { |
| varlen = expdest - stackblock() - startloc; |
| STADJUST(-varlen, expdest); |
| } |
| } else { |
| char const *syntax = (varflags & VSQUOTE) ? DQSYNTAX |
| : BASESYNTAX; |
| |
| if (subtype == VSLENGTH) { |
| for (;*val; val++) |
| varlen++; |
| } else { |
| while (*val) { |
| if (quotes && syntax[(int)*val] == CCTL) |
| STPUTC(CTLESC, expdest); |
| STPUTC(*val++, expdest); |
| } |
| |
| } |
| } |
| } |
| |
| |
| apply_ifs = ((varflags & VSQUOTE) == 0 || |
| (*var == '@' && shellparam.nparam != 1)); |
| |
| switch (subtype) { |
| case VSLENGTH: |
| expdest = cvtnum(varlen, expdest); |
| break; |
| |
| case VSNORMAL: |
| break; |
| |
| case VSPLUS: |
| set = !set; |
| /* FALLTHROUGH */ |
| case VSMINUS: |
| if (!set) { |
| argstr(p, flag | (apply_ifs ? EXP_IFS_SPLIT : 0)); |
| /* |
| * ${x-a b c} doesn't get split, but removing the |
| * 'apply_ifs = 0' apparantly breaks ${1+"$@"}.. |
| * ${x-'a b' c} should generate 2 args. |
| */ |
| /* We should have marked stuff already */ |
| apply_ifs = 0; |
| } |
| break; |
| |
| case VSTRIMLEFT: |
| case VSTRIMLEFTMAX: |
| case VSTRIMRIGHT: |
| case VSTRIMRIGHTMAX: |
| if (!set) |
| break; |
| /* |
| * Terminate the string and start recording the pattern |
| * right after it |
| */ |
| STPUTC('\0', expdest); |
| patloc = expdest - stackblock(); |
| if (subevalvar(p, NULL, patloc, subtype, |
| startloc, varflags) == 0) { |
| int amount = (expdest - stackblock() - patloc) + 1; |
| STADJUST(-amount, expdest); |
| } |
| /* Remove any recorded regions beyond start of variable */ |
| removerecordregions(startloc); |
| apply_ifs = 1; |
| break; |
| |
| case VSASSIGN: |
| case VSQUESTION: |
| if (set) |
| break; |
| if (subevalvar(p, var, 0, subtype, startloc, varflags)) { |
| varflags &= ~VSNUL; |
| /* |
| * Remove any recorded regions beyond |
| * start of variable |
| */ |
| removerecordregions(startloc); |
| goto again; |
| } |
| apply_ifs = 0; |
| break; |
| |
| default: |
| abort(); |
| } |
| |
| if (apply_ifs) |
| recordregion(startloc, expdest - stackblock(), |
| varflags & VSQUOTE); |
| |
| if (subtype != VSNORMAL) { /* skip to end of alternative */ |
| int nesting = 1; |
| for (;;) { |
| if ((c = *p++) == CTLESC) |
| p++; |
| else if (c == CTLBACKQ || c == (CTLBACKQ|CTLQUOTE)) { |
| if (set) |
| argbackq = argbackq->next; |
| } else if (c == CTLVAR) { |
| if ((*p++ & VSTYPE) != VSNORMAL) |
| nesting++; |
| } else if (c == CTLENDVAR) { |
| if (--nesting == 0) |
| break; |
| } |
| } |
| } |
| return p; |
| } |
| |
| |
| |
| /* |
| * Test whether a specialized variable is set. |
| */ |
| |
| STATIC int |
| varisset(char *name, int nulok) |
| { |
| if (*name == '!') |
| return backgndpid != -1; |
| else if (*name == '@' || *name == '*') { |
| if (*shellparam.p == NULL) |
| return 0; |
| |
| if (nulok) { |
| char **av; |
| |
| for (av = shellparam.p; *av; av++) |
| if (**av != '\0') |
| return 1; |
| return 0; |
| } |
| } else if (is_digit(*name)) { |
| char *ap; |
| int num = atoi(name); |
| |
| if (num > shellparam.nparam) |
| return 0; |
| |
| if (num == 0) |
| ap = arg0; |
| else |
| ap = shellparam.p[num - 1]; |
| |
| if (nulok && (ap == NULL || *ap == '\0')) |
| return 0; |
| } |
| return 1; |
| } |
| |
| |
| |
| /* |
| * Add the value of a specialized variable to the stack string. |
| */ |
| |
| STATIC void |
| varvalue(char *name, int quoted, int subtype, int flag) |
| { |
| int num; |
| char *p; |
| int i; |
| char sep; |
| char **ap; |
| char const *syntax; |
| |
| #define STRTODEST(p) \ |
| do {\ |
| if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH) { \ |
| syntax = quoted? DQSYNTAX : BASESYNTAX; \ |
| while (*p) { \ |
| if (syntax[(int)*p] == CCTL) \ |
| STPUTC(CTLESC, expdest); \ |
| STPUTC(*p++, expdest); \ |
| } \ |
| } else \ |
| while (*p) \ |
| STPUTC(*p++, expdest); \ |
| } while (0) |
| |
| |
| switch (*name) { |
| case '$': |
| num = rootpid; |
| goto numvar; |
| case '?': |
| num = exitstatus; |
| goto numvar; |
| case '#': |
| num = shellparam.nparam; |
| goto numvar; |
| case '!': |
| num = backgndpid; |
| numvar: |
| expdest = cvtnum(num, expdest); |
| break; |
| case '-': |
| for (i = 0; optlist[i].name; i++) { |
| if (optlist[i].val) |
| STPUTC(optlist[i].letter, expdest); |
| } |
| break; |
| case '@': |
| if (flag & EXP_FULL && quoted) { |
| for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { |
| STRTODEST(p); |
| if (*ap) |
| STPUTC('\0', expdest); |
| } |
| break; |
| } |
| /* fall through */ |
| case '*': |
| if (ifsset() != 0) |
| sep = ifsval()[0]; |
| else |
| sep = ' '; |
| for (ap = shellparam.p ; (p = *ap++) != NULL ; ) { |
| STRTODEST(p); |
| if (*ap && sep) |
| STPUTC(sep, expdest); |
| } |
| break; |
| case '0': |
| p = arg0; |
| STRTODEST(p); |
| break; |
| default: |
| if (is_digit(*name)) { |
| num = atoi(name); |
| if (num > 0 && num <= shellparam.nparam) { |
| p = shellparam.p[num - 1]; |
| STRTODEST(p); |
| } |
| } |
| break; |
| } |
| } |
| |
| |
| |
| /* |
| * Record the fact that we have to scan this region of the |
| * string for IFS characters. |
| */ |
| |
| STATIC void |
| recordregion(int start, int end, int inquotes) |
| { |
| struct ifsregion *ifsp; |
| |
| if (ifslastp == NULL) { |
| ifsp = &ifsfirst; |
| } else { |
| if (ifslastp->endoff == start |
| && ifslastp->inquotes == inquotes) { |
| /* extend previous area */ |
| ifslastp->endoff = end; |
| return; |
| } |
| ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion)); |
| ifslastp->next = ifsp; |
| } |
| ifslastp = ifsp; |
| ifslastp->next = NULL; |
| ifslastp->begoff = start; |
| ifslastp->endoff = end; |
| ifslastp->inquotes = inquotes; |
| } |
| |
| |
| |
| /* |
| * 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; |
| const char *ifsspc; |
| int inquotes; |
| |
| start = string; |
| ifsspc = NULL; |
| inquotes = 0; |
| |
| if (ifslastp == NULL) { |
| /* Return entire argument, IFS doesn't apply to any of it */ |
| sp = (struct strlist *)stalloc(sizeof *sp); |
| sp->text = start; |
| *arglist->lastp = sp; |
| arglist->lastp = &sp->next; |
| return; |
| } |
| |
| ifs = ifsset() ? ifsval() : " \t\n"; |
| |
| for (ifsp = &ifsfirst; ifsp != NULL; ifsp = ifsp->next) { |
| p = string + ifsp->begoff; |
| inquotes = ifsp->inquotes; |
| ifsspc = NULL; |
| while (p < string + ifsp->endoff) { |
| q = p; |
| if (*p == CTLESC) |
| p++; |
| if (inquotes) { |
| /* Only NULs (probably from "$@") end args */ |
| if (*p != 0) { |
| p++; |
| continue; |
| } |
| } else { |
| if (!strchr(ifs, *p)) { |
| p++; |
| continue; |
| } |
| ifsspc = strchr(" \t\n", *p); |
| |
| /* Ignore IFS whitespace at start */ |
| if (q == start && ifsspc != NULL) { |
| p++; |
| start = p; |
| continue; |
| } |
| } |
| |
| /* Save this argument... */ |
| *q = '\0'; |
| sp = (struct strlist *)stalloc(sizeof *sp); |
| sp->text = start; |
| *arglist->lastp = sp; |
| arglist->lastp = &sp->next; |
| p++; |
| |
| if (ifsspc != NULL) { |
| /* Ignore further trailing IFS whitespace */ |
| for (; p < string + ifsp->endoff; p++) { |
| q = p; |
| if (*p == CTLESC) |
| p++; |
| if (strchr(ifs, *p) == NULL) { |
| p = q; |
| break; |
| } |
| if (strchr(" \t\n", *p) == NULL) { |
| p++; |
| break; |
| } |
| } |
| } |
| start = p; |
| } |
| } |
| |
| /* |
| * Save anything left as an argument. |
| * Traditionally we have treated 'IFS=':'; set -- x$IFS' as |
| * generating 2 arguments, the second of which is empty. |
| * Some recent clarification of the Posix spec say that it |
| * should only generate one.... |
| */ |
| if (*start /* || (!ifsspc && start > string) */) { |
| sp = (struct strlist *)stalloc(sizeof *sp); |
| sp->text = start; |
| *arglist->lastp = sp; |
| arglist->lastp = &sp->next; |
| } |
| } |
| |
| STATIC void |
| ifsfree(void) |
| { |
| while (ifsfirst.next != NULL) { |
| struct ifsregion *ifsp; |
| INTOFF; |
| ifsp = ifsfirst.next->next; |
| ckfree(ifsfirst.next); |
| ifsfirst.next = ifsp; |
| INTON; |
| } |
| ifslastp = NULL; |
| ifsfirst.next = NULL; |
| } |
| |
| |
| |
| /* |
| * Expand shell metacharacters. At this point, the only control characters |
| * should be escapes. The results are stored in the list exparg. |
| */ |
| |
| char *expdir; |
| |
| |
| STATIC void |
| expandmeta(struct strlist *str, int flag) |
| { |
| char *p; |
| struct strlist **savelastp; |
| struct strlist *sp; |
| char c; |
| /* TODO - EXP_REDIR */ |
| |
| while (str) { |
| if (fflag) |
| goto nometa; |
| p = str->text; |
| for (;;) { /* fast check for meta chars */ |
| if ((c = *p++) == '\0') |
| goto nometa; |
| if (c == '*' || c == '?' || c == '[' || c == '!') |
| break; |
| } |
| savelastp = exparg.lastp; |
| INTOFF; |
| if (expdir == NULL) { |
| int i = strlen(str->text); |
| expdir = ckmalloc(i < 2048 ? 2048 : i); /* XXX */ |
| } |
| |
| expmeta(expdir, str->text); |
| ckfree(expdir); |
| expdir = NULL; |
| 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 *q; |
| char *start; |
| char *endname; |
| int metaflag; |
| struct stat statb; |
| DIR *dirp; |
| struct dirent *dp; |
| int atend; |
| int matchdot; |
| |
| metaflag = 0; |
| start = name; |
| for (p = name ; ; p++) { |
| if (*p == '*' || *p == '?') |
| metaflag = 1; |
| else if (*p == '[') { |
| q = p + 1; |
| if (*q == '!') |
| q++; |
| for (;;) { |
| while (*q == CTLQUOTEMARK) |
| q++; |
| if (*q == CTLESC) |
| q++; |
| if (*q == '/' || *q == '\0') |
| break; |
| if (*++q == ']') { |
| metaflag = 1; |
| break; |
| } |
| } |
| } else if (*p == '!' && p[1] == '!' && (p == name || p[-1] == '/')) { |
| metaflag = 1; |
| } else if (*p == '\0') |
| break; |
| else if (*p == CTLQUOTEMARK) |
| continue; |
| else if (*p == CTLESC) |
| p++; |
| if (*p == '/') { |
| if (metaflag) |
| break; |
| start = p + 1; |
| } |
| } |
| if (metaflag == 0) { /* we've reached the end of the file name */ |
| if (enddir != expdir) |
| metaflag++; |
| for (p = name ; ; p++) { |
| if (*p == CTLQUOTEMARK) |
| continue; |
| if (*p == CTLESC) |
| p++; |
| *enddir++ = *p; |
| if (*p == '\0') |
| break; |
| } |
| if (metaflag == 0 || lstat(expdir, &statb) >= 0) |
| addfname(expdir); |
| return; |
| } |
| endname = p; |
| if (start != name) { |
| p = name; |
| while (p < start) { |
| while (*p == CTLQUOTEMARK) |
| p++; |
| if (*p == CTLESC) |
| p++; |
| *enddir++ = *p++; |
| } |
| } |
| 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; |
| while (*p == CTLQUOTEMARK) |
| p++; |
| if (*p == CTLESC) |
| p++; |
| if (*p == '.') |
| matchdot++; |
| while (! int_pending() && (dp = readdir(dirp)) != NULL) { |
| if (dp->d_name[0] == '.' && ! matchdot) |
| continue; |
| if (patmatch(start, dp->d_name, 0)) { |
| 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] = '/'; |
| } |
| |
| |
| /* |
| * Add a file name to the list. |
| */ |
| |
| STATIC void |
| addfname(char *name) |
| { |
| char *p; |
| struct strlist *sp; |
| |
| p = stalloc(strlen(name) + 1); |
| scopy(name, p); |
| sp = (struct strlist *)stalloc(sizeof *sp); |
| sp->text = p; |
| *exparg.lastp = sp; |
| exparg.lastp = &sp->next; |
| } |
| |
| |
| /* |
| * Sort the results of file name expansion. It calculates the number of |
| * strings to sort and then calls msort (short for merge sort) to do the |
| * work. |
| */ |
| |
| STATIC struct strlist * |
| expsort(struct strlist *str) |
| { |
| int len; |
| struct strlist *sp; |
| |
| len = 0; |
| for (sp = str ; sp ; sp = sp->next) |
| len++; |
| return msort(str, len); |
| } |
| |
| |
| STATIC struct strlist * |
| msort(struct strlist *list, int len) |
| { |
| struct strlist *p, *q = NULL; |
| struct strlist **lpp; |
| int half; |
| int n; |
| |
| if (len <= 1) |
| return list; |
| half = len >> 1; |
| p = list; |
| for (n = half ; --n >= 0 ; ) { |
| q = p; |
| p = p->next; |
| } |
| q->next = NULL; /* terminate first half of list */ |
| q = msort(list, half); /* sort first half of list */ |
| p = msort(p, len - half); /* sort second half */ |
| lpp = &list; |
| for (;;) { |
| if (strcmp(p->text, q->text) < 0) { |
| *lpp = p; |
| lpp = &p->next; |
| if ((p = *lpp) == NULL) { |
| *lpp = q; |
| break; |
| } |
| } else { |
| *lpp = q; |
| lpp = &q->next; |
| if ((q = *lpp) == NULL) { |
| *lpp = p; |
| break; |
| } |
| } |
| } |
| return list; |
| } |
| |
| |
| |
| /* |
| * Returns true if the pattern matches the string. |
| */ |
| |
| int |
| patmatch(char *pattern, char *string, int squoted) |
| { |
| #ifdef notdef |
| if (pattern[0] == '!' && pattern[1] == '!') |
| return 1 - pmatch(pattern + 2, string); |
| else |
| #endif |
| return pmatch(pattern, string, squoted); |
| } |
| |
| |
| STATIC int |
| pmatch(char *pattern, char *string, int squoted) |
| { |
| char *p, *q; |
| char c; |
| |
| p = pattern; |
| q = string; |
| for (;;) { |
| switch (c = *p++) { |
| case '\0': |
| goto breakloop; |
| case CTLESC: |
| if (squoted && *q == CTLESC) |
| q++; |
| if (*q++ != *p++) |
| return 0; |
| break; |
| case CTLQUOTEMARK: |
| continue; |
| case '?': |
| if (squoted && *q == CTLESC) |
| q++; |
| if (*q++ == '\0') |
| return 0; |
| break; |
| case '*': |
| c = *p; |
| while (c == CTLQUOTEMARK || c == '*') |
| c = *++p; |
| if (c != CTLESC && c != CTLQUOTEMARK && |
| c != '?' && c != '*' && c != '[') { |
| while (*q != c) { |
| if (squoted && *q == CTLESC && |
| q[1] == c) |
| break; |
| if (*q == '\0') |
| return 0; |
| if (squoted && *q == CTLESC) |
| q++; |
| q++; |
| } |
| } |
| do { |
| if (pmatch(p, q, squoted)) |
| return 1; |
| if (squoted && *q == CTLESC) |
| q++; |
| } while (*q++ != '\0'); |
| return 0; |
| case '[': { |
| char *endp; |
| int invert, found; |
| char chr; |
| |
| endp = p; |
| if (*endp == '!') |
| endp++; |
| for (;;) { |
| while (*endp == CTLQUOTEMARK) |
| endp++; |
| if (*endp == '\0') |
| goto dft; /* no matching ] */ |
| if (*endp == CTLESC) |
| endp++; |
| if (*++endp == ']') |
| break; |
| } |
| invert = 0; |
| if (*p == '!') { |
| invert++; |
| p++; |
| } |
| found = 0; |
| chr = *q++; |
| if (squoted && chr == CTLESC) |
| chr = *q++; |
| if (chr == '\0') |
| return 0; |
| c = *p++; |
| do { |
| if (c == CTLQUOTEMARK) |
| continue; |
| if (c == CTLESC) |
| c = *p++; |
| if (*p == '-' && p[1] != ']') { |
| p++; |
| while (*p == CTLQUOTEMARK) |
| p++; |
| if (*p == CTLESC) |
| 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 (squoted && *q == CTLESC) |
| q++; |
| if (*q++ != c) |
| return 0; |
| break; |
| } |
| } |
| breakloop: |
| if (*q != '\0') |
| return 0; |
| return 1; |
| } |
| |
| |
| |
| /* |
| * Remove any CTLESC characters from a string. |
| */ |
| |
| void |
| rmescapes(char *str) |
| { |
| char *p, *q; |
| |
| p = str; |
| while (*p != CTLESC && *p != CTLQUOTEMARK) { |
| if (*p++ == '\0') |
| return; |
| } |
| q = p; |
| while (*p) { |
| if (*p == CTLQUOTEMARK) { |
| p++; |
| continue; |
| } |
| if (*p == CTLESC) |
| p++; |
| *q++ = *p++; |
| } |
| *q = '\0'; |
| } |
| |
| |
| |
| /* |
| * See if a pattern matches in a case statement. |
| */ |
| |
| int |
| casematch(union node *pattern, char *val) |
| { |
| struct stackmark smark; |
| int result; |
| char *p; |
| |
| setstackmark(&smark); |
| argbackq = pattern->narg.backquote; |
| STARTSTACKSTR(expdest); |
| ifslastp = NULL; |
| argstr(pattern->narg.text, EXP_TILDE | EXP_CASE); |
| STPUTC('\0', expdest); |
| p = grabstackstr(expdest); |
| result = patmatch(p, val, 0); |
| popstackmark(&smark); |
| return result; |
| } |
| |
| /* |
| * Our own itoa(). |
| */ |
| |
| STATIC char * |
| cvtnum(int num, char *buf) |
| { |
| char temp[32]; |
| int neg = num < 0; |
| char *p = temp + 31; |
| |
| temp[31] = '\0'; |
| |
| do { |
| *--p = num % 10 + '0'; |
| } while ((num /= 10) != 0); |
| |
| if (neg) |
| *--p = '-'; |
| |
| while (*p) |
| STPUTC(*p++, buf); |
| return buf; |
| } |
| |
| /* |
| * Do most of the work for wordexp(3). |
| */ |
| |
| int |
| wordexpcmd(int argc, char **argv) |
| { |
| size_t len; |
| int i; |
| |
| out1fmt("%d", argc - 1); |
| out1c('\0'); |
| for (i = 1, len = 0; i < argc; i++) |
| len += strlen(argv[i]); |
| out1fmt("%zd", len); |
| out1c('\0'); |
| for (i = 1; i < argc; i++) { |
| out1str(argv[i]); |
| out1c('\0'); |
| } |
| return (0); |
| } |