diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-11 08:21:29 +0000 |
commit | 29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc (patch) | |
tree | 63ef546b10a81d461e5cf5ed9e98a68cd7dee1aa /src/kash/expand.c | |
parent | Initial commit. (diff) | |
download | kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.tar.xz kbuild-29cd838eab01ed7110f3ccb2e8c6a35c8a31dbcc.zip |
Adding upstream version 1:0.1.9998svn3589+dfsg.upstream/1%0.1.9998svn3589+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r-- | src/kash/expand.c | 1617 |
1 files changed, 1617 insertions, 0 deletions
diff --git a/src/kash/expand.c b/src/kash/expand.c new file mode 100644 index 0000000..ff30455 --- /dev/null +++ b/src/kash/expand.c @@ -0,0 +1,1617 @@ +/* $NetBSD: expand.c,v 1.71 2005/06/01 15:41:19 lukem 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. + */ + +#if 0 +#ifndef lint +static char sccsid[] = "@(#)expand.c 8.5 (Berkeley) 5/15/95"; +#else +__RCSID("$NetBSD: expand.c,v 1.71 2005/06/01 15:41:19 lukem Exp $"); +#endif /* not lint */ +#endif + +#include <sys/types.h> +#include <errno.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" +#include "shinstance.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(shinstance *, char *, int); +STATIC void expari(shinstance *, int); +STATIC char *exptilde(shinstance *, char *, int); +STATIC void expbackq(shinstance *, union node *, int, int); +STATIC int subevalvar(shinstance *, char *, char *, int, int, int, int); +STATIC char *evalvar(shinstance *, char *, int); +STATIC int varisset(shinstance *, char *, int); +STATIC void varvalue(shinstance *, char *, int, int, int); +STATIC void recordregion(shinstance *, int, int, int); +STATIC void removerecordregions(shinstance *, int); +STATIC void ifsbreakup(shinstance *, char *, struct arglist *); +STATIC void ifsfree(shinstance *); +STATIC void expandmeta(shinstance *, struct strlist *, int); +STATIC void expmeta(shinstance *, char *, char *); +STATIC void addfname(shinstance *, char *); +STATIC struct strlist *expsort(struct strlist *); +STATIC struct strlist *msort(struct strlist *, int); +STATIC int pmatch(char *, char *, int); +STATIC char *cvtnum(shinstance *, int, char *); +STATIC char *cvtnum64(shinstance *, KI64, char *); + +/* + * Expand shell variables and backquotes inside a here document. + */ + +void +expandhere(shinstance *psh, union node *arg, int fd) +{ + psh->herefd = fd; + expandarg(psh, arg, (struct arglist *)NULL, 0); + xwrite(psh, fd, stackblock(psh), psh->expdest - stackblock(psh)); +} + + +/* + * 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(shinstance *psh, union node *arg, struct arglist *arglist, int flag) +{ + struct strlist *sp; + char *p; + + psh->argbackq = arg->narg.backquote; + STARTSTACKSTR(psh, psh->expdest); + psh->ifsfirst.next = NULL; + psh->ifslastp = NULL; + argstr(psh, arg->narg.text, flag); + if (arglist == NULL) { + return; /* here document expanded */ + } + STPUTC(psh, '\0', psh->expdest); + p = grabstackstr(psh, psh->expdest); + TRACE2((psh, "expandarg: p='%s'\n", p)); + psh->exparg.lastp = &psh->exparg.list; + /* + * TODO - EXP_REDIR + */ + if (flag & EXP_FULL) { + ifsbreakup(psh, p, &psh->exparg); + *psh->exparg.lastp = NULL; + psh->exparg.lastp = &psh->exparg.list; + expandmeta(psh, psh->exparg.list, flag); + } else { + if (flag & EXP_REDIR) /*XXX - for now, just remove escapes */ + rmescapes(psh, p); + sp = (struct strlist *)stalloc(psh, sizeof (struct strlist)); + sp->text = p; + *psh->exparg.lastp = sp; + psh->exparg.lastp = &sp->next; + } + ifsfree(psh); + *psh->exparg.lastp = NULL; + if (psh->exparg.list) { + *arglist->lastp = psh->exparg.list; + arglist->lastp = psh->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(shinstance *psh, char *p, int flag) +{ + char c; + int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */ + int firsteq = 1; + const char *ifs = NULL; + int ifs_split = EXP_IFS_SPLIT; + + if (flag & EXP_IFS_SPLIT) + ifs = ifsset(psh) ? ifsval(psh) : " \t\n"; + + if (*p == '~' && (flag & (EXP_TILDE | EXP_VARTILDE))) + p = exptilde(psh, 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(psh, c, psh->expdest); + ifs_split = 0; + break; + case CTLQUOTEEND: + ifs_split = EXP_IFS_SPLIT; + break; + case CTLESC: + if (quotes) + STPUTC(psh, c, psh->expdest); + c = *p++; + STPUTC(psh, c, psh->expdest); + break; + case CTLVAR: + p = evalvar(psh, p, (flag & ~EXP_IFS_SPLIT) | (flag & ifs_split)); + break; + case CTLBACKQ: + case CTLBACKQ|CTLQUOTE: + expbackq(psh, psh->argbackq->n, c & CTLQUOTE, flag); + psh->argbackq = psh->argbackq->next; + break; + case CTLENDARI: + expari(psh, flag); + break; + case ':': + case '=': + /* + * sort of a hack - expand tildes in variable + * assignments (after the first '=' and after ':'s). + */ + STPUTC(psh, c, psh->expdest); + if (flag & EXP_VARTILDE && *p == '~') { + if (c == '=') { + if (firsteq) + firsteq = 0; + else + break; + } + p = exptilde(psh, p, flag); + } + break; + default: + STPUTC(psh, c, psh->expdest); + if (flag & EXP_IFS_SPLIT & ifs_split && strchr(ifs, c) != NULL) { + /* We need to get the output split here... */ + recordregion(psh, (int)(psh->expdest - stackblock(psh) - 1), + (int)(psh->expdest - stackblock(psh)), 0); + } + break; + } + } +} + +STATIC char * +exptilde(shinstance *psh, 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(psh, "HOME")) == NULL) + goto lose; + } else { + if ((home = sh_gethomedir(psh, startp+1)) == NULL) + goto lose; + } + if (*home == '\0') + goto lose; + *p = c; + while ((c = *home++) != '\0') { + if (quotes && SQSYNTAX[(int)c] == CCTL) + STPUTC(psh, CTLESC, psh->expdest); + STPUTC(psh, c, psh->expdest); + } + return (p); +lose: + *p = c; + return (startp); +} + + +STATIC void +removerecordregions(shinstance *psh, int endoff) +{ + if (psh->ifslastp == NULL) + return; + + if (psh->ifsfirst.endoff > endoff) { + while (psh->ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = psh->ifsfirst.next->next; + ckfree(psh, psh->ifsfirst.next); + psh->ifsfirst.next = ifsp; + INTON; + } + if (psh->ifsfirst.begoff > endoff) + psh->ifslastp = NULL; + else { + psh->ifslastp = &psh->ifsfirst; + psh->ifsfirst.endoff = endoff; + } + return; + } + + psh->ifslastp = &psh->ifsfirst; + while (psh->ifslastp->next && psh->ifslastp->next->begoff < endoff) + psh->ifslastp=psh->ifslastp->next; + while (psh->ifslastp->next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = psh->ifslastp->next->next; + ckfree(psh, psh->ifslastp->next); + psh->ifslastp->next = ifsp; + INTON; + } + if (psh->ifslastp->endoff > endoff) + psh->ifslastp->endoff = endoff; +} + + +/* + * Expand arithmetic expression. Backup to start of expression, + * evaluate, place result in (backed up) result, adjust string position. + */ +STATIC void +expari(shinstance *psh, 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(psh, 12 - 2, psh->expdest); + USTPUTC(psh, '\0', psh->expdest); + start = stackblock(psh); + p = psh->expdest - 1; + while (*p != CTLARI && p >= start) + --p; + if (*p != CTLARI) + error(psh, "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 = (int)(p - start); + removerecordregions(psh, begoff); + if (quotes) + rmescapes(psh, p+2); + result = arith(psh, p+2); + fmtstr(p, 12, "%d", result); + + while (*p++) + ; + + if (quoted == 0) + recordregion(psh, begoff, (int)(p - 1 - start), 0); + result = (int)(psh->expdest - p + 1); + STADJUST(psh, -result, psh->expdest); +} + + +/* + * Expand stuff in backwards quotes. + */ + +STATIC void +expbackq(shinstance *psh, union node *cmd, int quoted, int flag) +{ + struct backcmd in; + int i; + char buf[128]; + char *p; + char *dest = psh->expdest; + struct ifsregion saveifs, *savelastp; + struct nodelist *saveargbackq; + char lastc; + int startloc = (int)(dest - stackblock(psh)); + char const *syntax = quoted? DQSYNTAX : BASESYNTAX; + int saveherefd; + int quotes = flag & (EXP_FULL | EXP_CASE); +#ifdef SH_DEAL_WITH_CRLF + int pending_cr = 0; +#endif + + INTOFF; + saveifs = psh->ifsfirst; + savelastp = psh->ifslastp; + saveargbackq = psh->argbackq; + saveherefd = psh->herefd; + psh->herefd = -1; + p = grabstackstr(psh, dest); + evalbackcmd(psh, cmd, &in); + ungrabstackstr(psh, p, dest); + psh->ifsfirst = saveifs; + psh->ifslastp = savelastp; + psh->argbackq = saveargbackq; + psh->herefd = saveherefd; + + p = in.buf; + lastc = '\0'; + for (;;) { + if (--in.nleft < 0) { + if (in.fd < 0) + break; + while ((i = shfile_read(&psh->fdtab, in.fd, buf, sizeof buf)) < 0 && errno == EINTR); + TRACE((psh, "expbackq: read returns %d\n", i)); + if (i <= 0) + break; + p = buf; + in.nleft = i - 1; + } + lastc = *p++; +#ifdef SH_DEAL_WITH_CRLF + if (pending_cr) { + pending_cr = 0; + if (lastc != '\n') { + if (quotes && syntax[(int)'\r'] == CCTL) + STPUTC(psh, CTLESC, dest); + STPUTC(psh, '\r', dest); + } + } + if (lastc == '\r') + pending_cr = '\r'; + else +#endif + if (lastc != '\0') { + if (quotes && syntax[(int)lastc] == CCTL) + STPUTC(psh, CTLESC, dest); + STPUTC(psh, lastc, dest); + } + } +#ifdef SH_DEAL_WITH_CRLF + if (pending_cr) { + if (quotes && syntax[(int)'\r'] == CCTL) + STPUTC(psh, CTLESC, dest); + STPUTC(psh, '\r', dest); + } +#endif + + /* Eat all trailing newlines */ + p = stackblock(psh) + startloc; + while (dest > p && dest[-1] == '\n') + STUNPUTC(psh, dest); + + if (in.fd >= 0) + shfile_close(&psh->fdtab, in.fd); + if (in.buf) + ckfree(psh, in.buf); + if (in.jp) + psh->back_exitstatus = waitforjob(psh, in.jp); + if (quoted == 0) + recordregion(psh, startloc, (int)(dest - stackblock(psh)), 0); + TRACE((psh, "evalbackq: size=%d: \"%.*s\"\n", + (dest - stackblock(psh)) - startloc, + (dest - stackblock(psh)) - startloc, + stackblock(psh) + startloc)); + psh->expdest = dest; + INTON; +} + + + +STATIC int +subevalvar(shinstance *psh, char *p, char *str, int strloc, int subtype, int startloc, int varflags) +{ + char *startp; + char *loc = NULL; + char *q; + int c = 0; + int saveherefd = psh->herefd; + struct nodelist *saveargbackq = psh->argbackq; + int amount; + + psh->herefd = -1; + argstr(psh, p, 0); + STACKSTRNUL(psh, psh->expdest); + psh->herefd = saveherefd; + psh->argbackq = saveargbackq; + startp = stackblock(psh) + startloc; + if (str == NULL) + str = stackblock(psh) + strloc; + + switch (subtype) { + case VSASSIGN: + setvar(psh, str, startp, 0); + amount = (int)(startp - psh->expdest); + STADJUST(psh, amount, psh->expdest); + varflags &= ~VSNUL; + if (c != 0) + *loc = c; + return 1; + + case VSQUESTION: + if (*p != CTLENDVAR) { + outfmt(&psh->errout, "%s\n", startp); + error(psh, (char *)NULL); + } + error(psh, "%.*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(psh, 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(psh, 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(psh, 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(psh, str, loc, varflags & VSQUOTE)) + goto recordright; + if ((varflags & VSQUOTE) && *loc == CTLESC) + loc++; + } + return 0; + + default: + sh_abort(psh); + } + +recordleft: + *loc = c; + amount = (int)(((str - 1) - (loc - startp)) - psh->expdest); + STADJUST(psh, amount, psh->expdest); + while (loc != str - 1) + *startp++ = *loc++; + return 1; + +recordright: + amount = (int)(loc - psh->expdest); + STADJUST(psh, amount, psh->expdest); + STPUTC(psh, '\0', psh->expdest); + STADJUST(psh, -1, psh->expdest); + return 1; +} + + +/* + * Expand a variable, and return a pointer to the next character in the + * input string. + */ + +STATIC char * +evalvar(shinstance *psh, 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(psh, var, varflags & VSNUL); + val = NULL; + } else { + val = lookupvar(psh, var); + if (val == NULL || ((varflags & VSNUL) && val[0] == '\0')) { + val = NULL; + set = 0; + } else + set = 1; + } + + varlen = 0; + startloc = (int)(psh->expdest - stackblock(psh)); + + if (!set && uflag(psh)) { + switch (subtype) { + case VSNORMAL: + case VSTRIMLEFT: + case VSTRIMLEFTMAX: + case VSTRIMRIGHT: + case VSTRIMRIGHTMAX: + case VSLENGTH: + error(psh, "%.*s: parameter not set", p - var - 1, var); + /* NOTREACHED */ + } + } + + if (set && subtype != VSPLUS) { + /* insert the value of the variable */ + if (special) { + varvalue(psh, var, varflags & VSQUOTE, subtype, flag); + if (subtype == VSLENGTH) { + varlen = (int)(psh->expdest - stackblock(psh) - startloc); + STADJUST(psh, -varlen, psh->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(psh, CTLESC, psh->expdest); + STPUTC(psh, *val++, psh->expdest); + } + + } + } + } + + + apply_ifs = ((varflags & VSQUOTE) == 0 || + (*var == '@' && psh->shellparam.nparam != 1)); + + switch (subtype) { + case VSLENGTH: + psh->expdest = cvtnum(psh, varlen, psh->expdest); + break; + + case VSNORMAL: + break; + + case VSPLUS: + set = !set; + /* FALLTHROUGH */ + case VSMINUS: + if (!set) { + argstr(psh, 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(psh, '\0', psh->expdest); + patloc = (int)(psh->expdest - stackblock(psh)); + if (subevalvar(psh, p, NULL, patloc, subtype, + startloc, varflags) == 0) { + int amount = (int)(psh->expdest - stackblock(psh) - patloc) + 1; + STADJUST(psh, -amount, psh->expdest); + } + /* Remove any recorded regions beyond start of variable */ + removerecordregions(psh, startloc); + apply_ifs = 1; + break; + + case VSASSIGN: + case VSQUESTION: + if (set) + break; + if (subevalvar(psh, p, var, 0, subtype, startloc, varflags)) { + varflags &= ~VSNUL; + /* + * Remove any recorded regions beyond + * start of variable + */ + removerecordregions(psh, startloc); + goto again; + } + apply_ifs = 0; + break; + + default: + sh_abort(psh); + } + + if (apply_ifs) + recordregion(psh, startloc, (int)(psh->expdest - stackblock(psh)), + 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) + psh->argbackq = psh->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(shinstance *psh, char *name, int nulok) +{ + if (*name == '!') + return psh->backgndpid != -1; + else if (*name == '@' || *name == '*') { + if (*psh->shellparam.p == NULL) + return 0; + + if (nulok) { + char **av; + + for (av = psh->shellparam.p; *av; av++) + if (**av != '\0') + return 1; + return 0; + } + } else if (is_digit(*name)) { + char *ap; + int num = atoi(name); + + if (num > psh->shellparam.nparam) + return 0; + + if (num == 0) + ap = psh->arg0; + else + ap = psh->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(shinstance *psh, 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(psh, CTLESC, psh->expdest); \ + STPUTC(psh, *p++, psh->expdest); \ + } \ + } else \ + while (*p) \ + STPUTC(psh, *p++, psh->expdest); \ + } while (0) + + + switch (*name) { + case '$': +#ifndef SH_FORKED_MODE + psh->expdest = cvtnum64(psh, psh->rootpid, psh->expdest); + break; +#else + num = psh->rootpid; + goto numvar; +#endif + case '?': + num = psh->exitstatus; + goto numvar; + case '#': + num = psh->shellparam.nparam; +numvar: + psh->expdest = cvtnum(psh, num, psh->expdest); + break; + case '!': +#ifndef SH_FORKED_MODE + psh->expdest = cvtnum64(psh, psh->backgndpid, psh->expdest); + break; +#else + num = psh->backgndpid; + goto numvar; +#endif + case '-': + for (i = 0; psh->optlist[i].name; i++) { + if (psh->optlist[i].val) + STPUTC(psh, psh->optlist[i].letter, psh->expdest); + } + break; + case '@': + if (flag & EXP_FULL && quoted) { + for (ap = psh->shellparam.p ; (p = *ap++) != NULL ; ) { + STRTODEST(p); + if (*ap) + STPUTC(psh, '\0', psh->expdest); + } + break; + } + /* fall through */ + case '*': + if (ifsset(psh) != 0) + sep = ifsval(psh)[0]; + else + sep = ' '; + for (ap = psh->shellparam.p ; (p = *ap++) != NULL ; ) { + STRTODEST(p); + if (*ap && sep) + STPUTC(psh, sep, psh->expdest); + } + break; + case '0': + p = psh->arg0; + STRTODEST(p); + break; + default: + if (is_digit(*name)) { + num = atoi(name); + if (num > 0 && num <= psh->shellparam.nparam) { + p = psh->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(shinstance *psh, int start, int end, int inquotes) +{ + struct ifsregion *ifsp; + + if (psh->ifslastp == NULL) { + ifsp = &psh->ifsfirst; + } else { + if (psh->ifslastp->endoff == start + && psh->ifslastp->inquotes == inquotes) { + /* extend previous area */ + psh->ifslastp->endoff = end; + return; + } + ifsp = (struct ifsregion *)ckmalloc(psh, sizeof (struct ifsregion)); + psh->ifslastp->next = ifsp; + } + psh->ifslastp = ifsp; + psh->ifslastp->next = NULL; + psh->ifslastp->begoff = start; + psh->ifslastp->endoff = end; + psh->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(shinstance *psh, 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 (psh->ifslastp == NULL) { + /* Return entire argument, IFS doesn't apply to any of it */ + sp = (struct strlist *)stalloc(psh, sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + return; + } + + ifs = ifsset(psh) ? ifsval(psh) : " \t\n"; + + for (ifsp = &psh->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(psh, 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(psh, sizeof *sp); + sp->text = start; + *arglist->lastp = sp; + arglist->lastp = &sp->next; + } +} + +STATIC void +ifsfree(shinstance *psh) +{ + while (psh->ifsfirst.next != NULL) { + struct ifsregion *ifsp; + INTOFF; + ifsp = psh->ifsfirst.next->next; + ckfree(psh, psh->ifsfirst.next); + psh->ifsfirst.next = ifsp; + INTON; + } + psh->ifslastp = NULL; + psh->ifsfirst.next = NULL; +} + + + +/* + * Expand shell metacharacters. At this point, the only control characters + * should be escapes. The results are stored in the list psh->exparg. + */ + +//char *expdir; + + +STATIC void +expandmeta(shinstance *psh, struct strlist *str, int flag) +{ + char *p; + struct strlist **savelastp; + struct strlist *sp; + char c; + /* TODO - EXP_REDIR */ + + while (str) { + if (fflag(psh)) + goto nometa; + p = str->text; + for (;;) { /* fast check for meta chars */ + if ((c = *p++) == '\0') + goto nometa; + if (c == '*' || c == '?' || c == '[' || c == '!') + break; + } + savelastp = psh->exparg.lastp; + INTOFF; + if (psh->expdir == NULL) { + size_t i = strlen(str->text); + psh->expdir = ckmalloc(psh, i < 2048 ? 2048 : i); /* XXX */ + } + + expmeta(psh, psh->expdir, str->text); + ckfree(psh, psh->expdir); + psh->expdir = NULL; + INTON; + if (psh->exparg.lastp == savelastp) { + /* + * no matches + */ +nometa: + *psh->exparg.lastp = str; + rmescapes(psh, str->text); + psh->exparg.lastp = &str->next; + } else { + *psh->exparg.lastp = NULL; + *savelastp = sp = expsort(*savelastp); + while (sp->next != NULL) + sp = sp->next; + psh->exparg.lastp = &sp->next; + } + str = str->next; + } +} + + +/* + * Do metacharacter (i.e. *, ?, [...]) expansion. + */ + +STATIC void +expmeta(shinstance *psh, char *enddir, char *name) +{ + char *p; + const char *cp; + char *q; + char *start; + char *endname; + int metaflag; + struct stat statb; + shdir *dirp; + shdirent *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 != psh->expdir) + metaflag++; + for (p = name ; ; p++) { + if (*p == CTLQUOTEMARK) + continue; + if (*p == CTLESC) + p++; + *enddir++ = *p; + if (*p == '\0') + break; + } + if (metaflag == 0 || shfile_lstat(&psh->fdtab, psh->expdir, &statb) >= 0) + addfname(psh, psh->expdir); + TRACE2((psh, "expandarg: return #1 (metaflag=%d)\n", metaflag)); + return; + } + endname = p; + if (start != name) { + p = name; + while (p < start) { + while (*p == CTLQUOTEMARK) + p++; + if (*p == CTLESC) + p++; + *enddir++ = *p++; + } + } + if (enddir == psh->expdir) { + cp = "."; + } else if (enddir == psh->expdir + 1 && *psh->expdir == '/') { + cp = "/"; + } else { + cp = psh->expdir; + enddir[-1] = '\0'; + } + if ((dirp = shfile_opendir(&psh->fdtab, cp)) == NULL) { + TRACE2((psh, "expandarg: return #2 (shfile_opendir(,%s) failed)\n", cp)); + return; + } + if (enddir != psh->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 = shfile_readdir(dirp)) != NULL) { + if (dp->name[0] == '.' && ! matchdot) + continue; + if (patmatch(psh, start, dp->name, 0)) { + if (atend) { + scopy(dp->name, enddir); + addfname(psh, psh->expdir); + } else { + for (p = enddir, cp = dp->name; + (*p++ = *cp++) != '\0';) + continue; + p[-1] = '/'; + expmeta(psh, p, endname); + } + } + } + shfile_closedir(dirp); + if (! atend) + endname[-1] = '/'; +} + + +/* + * Add a file name to the list. + */ + +STATIC void +addfname(shinstance *psh, char *name) +{ + char *p; + struct strlist *sp; + + p = stalloc(psh, strlen(name) + 1); + scopy(name, p); + sp = (struct strlist *)stalloc(psh, sizeof *sp); + sp->text = p; + *psh->exparg.lastp = sp; + psh->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(shinstance *psh, 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(shinstance *psh, 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(shinstance *psh, union node *pattern, char *val) +{ + struct stackmark smark; + int result; + char *p; + + setstackmark(psh, &smark); + psh->argbackq = pattern->narg.backquote; + STARTSTACKSTR(psh, psh->expdest); + psh->ifslastp = NULL; + argstr(psh, pattern->narg.text, EXP_TILDE | EXP_CASE); + STPUTC(psh, '\0', psh->expdest); + p = grabstackstr(psh, psh->expdest); + result = patmatch(psh, p, val, 0); + popstackmark(psh, &smark); + return result; +} + +/* + * Our own itoa(). + */ + +STATIC char * +cvtnum(shinstance *psh, 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(psh, *p++, buf); + return buf; +} + +STATIC char * +cvtnum64(shinstance *psh, KI64 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(psh, *p++, buf); + return buf; +} + +/* + * Do most of the work for wordexp(3). + */ + +int +wordexpcmd(shinstance *psh, int argc, char **argv) +{ + size_t len; + int i; + + out1fmt(psh, "%d", argc - 1); + out1c(psh, '\0'); + for (i = 1, len = 0; i < argc; i++) + len += strlen(argv[i]); + out1fmt(psh, "%zd", len); + out1c(psh, '\0'); + for (i = 1; i < argc; i++) { + out1str(psh, argv[i]); + out1c(psh, '\0'); + } + return (0); +} |