* The Regents of the University of California. All rights reserved.
* Copyright (c) 1997-2005
* Herbert Xu <herbert@gondor.apana.org.au>. All rights reserved.
+ * Copyright (c) 2010-2015
+ * Jilles Tjoelker <jilles@stack.nl>. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Kenneth Almquist.
#endif
#endif /* not lint */
#include <sys/cdefs.h>
-__FBSDID("$FreeBSD$");
+__FBSDID("$FreeBSD: head/bin/sh/expand.c 303586 2016-07-31 13:11:34Z jilles $");
#include <sys/types.h>
#include <sys/time.h>
#include "show.h"
#include "builtins.h"
-/*
- * Structure specifying which parts of the string should be searched
- * for IFS characters.
- */
+enum wordstate { WORD_IDLE, WORD_WS_DELIMITED, WORD_QUOTEMARK };
-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 */
+struct worddest {
+ struct arglist *list;
+ enum wordstate state;
};
-
static char *expdest; /* output of current string */
static struct nodelist *argbackq; /* list of back quote expressions */
-static struct ifsregion ifsfirst; /* first struct in list of ifs regions */
-static struct ifsregion *ifslastp; /* last struct in list */
-static struct arglist exparg; /* holds expanded arg list */
-
-static char *argstr(char *, int);
-static char *exptilde(char *, int);
-static char *expari(char *);
-static void expbackq(union node *, int, int);
-static int subevalvar(char *, char *, int, int, int, int, int);
-static char *evalvar(char *, int);
+
+static const char *argstr(const char *, int, struct worddest *);
+static const char *exptilde(const char *, int);
+static const char *expari(const char *, int, struct worddest *);
+static void expbackq(union node *, int, int, struct worddest *);
+static void subevalvar_trim(const char *, int, int, int);
+static int subevalvar_misc(const char *, const char *, int, int, int);
+static const char *evalvar(const char *, int, struct worddest *);
static int varisset(const char *, int);
-static void strtodest(const char *, int, int, int);
-static void varvalue(const char *, int, int, int);
-static void recordregion(int, int, int);
-static void removerecordregions(int);
-static void ifsbreakup(char *, struct arglist *);
-static void expandmeta(struct strlist *);
-static void expmeta(char *, char *);
-static void addfname(char *);
-static struct strlist *expsort(struct strlist *);
-static struct strlist *msort(struct strlist *, int);
-static int patmatch(const char *, const char *, int);
-static char *cvtnum(int, char *);
+static void strtodest(const char *, int, int, int, struct worddest *);
+static void reprocess(int, int, int, int, struct worddest *);
+static void varvalue(const char *, int, int, int, struct worddest *);
+static void expandmeta(char *, struct arglist *);
+static void expmeta(char *, char *, struct arglist *);
+static int expsortcmp(const void *, const void *);
+static int patmatch(const char *, const char *);
+static void cvtnum(int, char *);
static int collate_range_cmp(wchar_t, wchar_t);
+void
+emptyarglist(struct arglist *list)
+{
+
+ list->args = list->smallarg;
+ list->count = 0;
+ list->capacity = sizeof(list->smallarg) / sizeof(list->smallarg[0]);
+}
+
+void
+appendarglist(struct arglist *list, char *str)
+{
+ char **newargs;
+ int newcapacity;
+
+ if (list->count >= list->capacity) {
+ newcapacity = list->capacity * 2;
+ if (newcapacity < 16)
+ newcapacity = 16;
+ if (newcapacity > INT_MAX / (int)sizeof(newargs[0]))
+ error("Too many entries in arglist");
+ newargs = stalloc(newcapacity * sizeof(newargs[0]));
+ memcpy(newargs, list->args, list->count * sizeof(newargs[0]));
+ list->args = newargs;
+ list->capacity = newcapacity;
+ }
+ list->args[list->count++] = str;
+}
+
static int
collate_range_cmp(wchar_t c1, wchar_t c2)
{
}
#define STPUTS_QUOTES(data, syntax, p) p = stputs_quotes((data), syntax, p)
+static char *
+nextword(char c, int flag, char *p, struct worddest *dst)
+{
+ int is_ws;
+
+ is_ws = c == '\t' || c == '\n' || c == ' ';
+ if (p != stackblock() || (is_ws ? dst->state == WORD_QUOTEMARK :
+ dst->state != WORD_WS_DELIMITED) || c == '\0') {
+ STPUTC('\0', p);
+ if (flag & EXP_GLOB)
+ expandmeta(grabstackstr(p), dst->list);
+ else
+ appendarglist(dst->list, grabstackstr(p));
+ dst->state = is_ws ? WORD_WS_DELIMITED : WORD_IDLE;
+ } else if (!is_ws && dst->state == WORD_WS_DELIMITED)
+ dst->state = WORD_IDLE;
+ /* Reserve space while the stack string is empty. */
+ appendarglist(dst->list, NULL);
+ dst->list->count--;
+ STARTSTACKSTR(p);
+ return p;
+}
+#define NEXTWORD(c, flag, p, dstlist) p = nextword(c, flag, p, dstlist)
+
+static char *
+stputs_split(const char *data, const char *syntax, int flag, char *p,
+ struct worddest *dst)
+{
+ const char *ifs;
+ char c;
+
+ ifs = ifsset() ? ifsval() : " \t\n";
+ while (*data) {
+ CHECKSTRSPACE(2, p);
+ c = *data++;
+ if (strchr(ifs, c) != NULL) {
+ NEXTWORD(c, flag, p, dst);
+ continue;
+ }
+ if (flag & EXP_GLOB && syntax[(int)c] == CCTL)
+ USTPUTC(CTLESC, p);
+ USTPUTC(c, p);
+ }
+ return (p);
+}
+#define STPUTS_SPLIT(data, syntax, flag, p, dst) p = stputs_split((data), syntax, flag, p, dst)
+
/*
* Perform expansions on an argument, placing the resulting list of arguments
* in arglist. Parameter expansion, command substitution and arithmetic
void
expandarg(union node *arg, struct arglist *arglist, int flag)
{
- struct strlist *sp;
- char *p;
+ struct worddest exparg;
+ if (fflag)
+ flag &= ~EXP_GLOB;
argbackq = arg->narg.backquote;
+ exparg.list = arglist;
+ exparg.state = WORD_IDLE;
STARTSTACKSTR(expdest);
- ifsfirst.next = NULL;
- ifslastp = NULL;
- argstr(arg->narg.text, flag);
+ argstr(arg->narg.text, flag, &exparg);
if (arglist == NULL) {
STACKSTRNUL(expdest);
return; /* here document expanded */
}
- STPUTC('\0', expdest);
- p = grabstackstr(expdest);
- exparg.lastp = &exparg.list;
- if (flag & EXP_FULL) {
- ifsbreakup(p, &exparg);
- *exparg.lastp = NULL;
- exparg.lastp = &exparg.list;
- expandmeta(exparg.list);
- } else {
- sp = (struct strlist *)stalloc(sizeof (struct strlist));
- sp->text = p;
- *exparg.lastp = sp;
- exparg.lastp = &sp->next;
- }
- while (ifsfirst.next != NULL) {
- struct ifsregion *ifsp;
- INTOFF;
- ifsp = ifsfirst.next->next;
- ckfree(ifsfirst.next);
- ifsfirst.next = ifsp;
- INTON;
- }
- *exparg.lastp = NULL;
- if (exparg.list) {
- *arglist->lastp = exparg.list;
- arglist->lastp = exparg.lastp;
+ if ((flag & EXP_SPLIT) == 0 || expdest != stackblock() ||
+ exparg.state == WORD_QUOTEMARK) {
+ STPUTC('\0', expdest);
+ if (flag & EXP_SPLIT) {
+ if (flag & EXP_GLOB)
+ expandmeta(grabstackstr(expdest), exparg.list);
+ else
+ appendarglist(exparg.list, grabstackstr(expdest));
+ }
}
+ if ((flag & EXP_SPLIT) == 0)
+ appendarglist(arglist, grabstackstr(expdest));
}
* expansion, and tilde expansion if requested via EXP_TILDE/EXP_VARTILDE.
* Processing ends at a CTLENDVAR or CTLENDARI character as well as '\0'.
* This is used to expand word in ${var+word} etc.
- * If EXP_FULL or EXP_CASE are set, keep and/or generate CTLESC
+ * If EXP_GLOB or EXP_CASE are set, keep and/or generate CTLESC
* characters to allow for further processing.
- * If EXP_FULL is set, also preserve CTLQUOTEMARK characters.
+ *
+ * If EXP_SPLIT is set, dst receives any complete words produced.
*/
-static char *
-argstr(char *p, int flag)
+static const char *
+argstr(const char *p, int flag, struct worddest *dst)
{
char c;
- int quotes = flag & (EXP_FULL | EXP_CASE); /* do CTLESC */
+ int quotes = flag & (EXP_GLOB | EXP_CASE); /* do CTLESC */
int firsteq = 1;
int split_lit;
int lit_quoted;
case CTLQUOTEMARK:
lit_quoted = 1;
/* "$@" syntax adherence hack */
- if (p[0] == CTLVAR && p[2] == '@' && p[3] == '=')
+ if (p[0] == CTLVAR && (p[1] & VSQUOTE) != 0 &&
+ p[2] == '@' && p[3] == '=')
break;
- if ((flag & EXP_FULL) != 0)
- USTPUTC(c, expdest);
+ if ((flag & EXP_SPLIT) != 0 && expdest == stackblock())
+ dst->state = WORD_QUOTEMARK;
break;
case CTLQUOTEEND:
lit_quoted = 0;
break;
case CTLESC:
- if (quotes)
- USTPUTC(c, expdest);
c = *p++;
+ if (split_lit && !lit_quoted &&
+ strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+ NEXTWORD(c, flag, expdest, dst);
+ break;
+ }
+ if (quotes)
+ USTPUTC(CTLESC, expdest);
USTPUTC(c, expdest);
- if (split_lit && !lit_quoted)
- recordregion(expdest - stackblock() -
- (quotes ? 2 : 1),
- expdest - stackblock(), 0);
break;
case CTLVAR:
- p = evalvar(p, flag);
+ p = evalvar(p, flag, dst);
break;
case CTLBACKQ:
case CTLBACKQ|CTLQUOTE:
- expbackq(argbackq->n, c & CTLQUOTE, flag);
+ expbackq(argbackq->n, c & CTLQUOTE, flag, dst);
argbackq = argbackq->next;
break;
case CTLARI:
- p = expari(p);
+ p = expari(p, flag, dst);
break;
case ':':
case '=':
* sort of a hack - expand tildes in variable
* assignments (after the first '=' and after ':'s).
*/
+ if (split_lit && !lit_quoted &&
+ strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+ NEXTWORD(c, flag, expdest, dst);
+ break;
+ }
USTPUTC(c, expdest);
- if (split_lit && !lit_quoted)
- recordregion(expdest - stackblock() - 1,
- expdest - stackblock(), 0);
if (flag & EXP_VARTILDE && *p == '~' &&
(c != '=' || firsteq)) {
if (c == '=')
}
break;
default:
+ if (split_lit && !lit_quoted &&
+ strchr(ifsset() ? ifsval() : " \t\n", c) != NULL) {
+ NEXTWORD(c, flag, expdest, dst);
+ break;
+ }
USTPUTC(c, expdest);
- if (split_lit && !lit_quoted)
- recordregion(expdest - stackblock() - 1,
- expdest - stackblock(), 0);
}
}
}
* Perform tilde expansion, placing the result in the stack string and
* returning the next position in the input string to process.
*/
-static char *
-exptilde(char *p, int flag)
+static const char *
+exptilde(const char *p, int flag)
{
- char c, *startp = p;
+ char c;
+ const char *startp = p;
+ const char *user;
struct passwd *pw;
char *home;
+ int len;
for (;;) {
c = *p;
case '\0':
case '/':
case CTLENDVAR:
- *p = '\0';
- if (*(startp+1) == '\0') {
+ len = p - startp - 1;
+ STPUTBIN(startp + 1, len, expdest);
+ STACKSTRNUL(expdest);
+ user = expdest - len;
+ if (*user == '\0') {
home = lookupvar("HOME");
} else {
- pw = getpwnam(startp+1);
+ pw = getpwnam(user);
home = pw != NULL ? pw->pw_dir : NULL;
}
- *p = c;
+ STADJUST(-len, expdest);
if (home == NULL || *home == '\0')
return (startp);
- strtodest(home, flag, VSNORMAL, 1);
+ strtodest(home, flag, VSNORMAL, 1, NULL);
return (p);
}
p++;
}
-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.
- * Note that flag is not required as digits never require CTLESC characters.
*/
-static char *
-expari(char *p)
+static const char *
+expari(const char *p, int flag, struct worddest *dst)
{
char *q, *start;
arith_t result;
quoted = *p++ == '"';
begoff = expdest - stackblock();
- p = argstr(p, 0);
- removerecordregions(begoff);
+ p = argstr(p, 0, NULL);
STPUTC('\0', expdest);
start = stackblock() + begoff;
adj = strlen(expdest);
STADJUST(adj, expdest);
if (!quoted)
- recordregion(begoff, expdest - stackblock(), 0);
+ reprocess(expdest - adj - stackblock(), flag, VSNORMAL, 0, dst);
return p;
}
* Perform command substitution.
*/
static void
-expbackq(union node *cmd, int quoted, int flag)
+expbackq(union node *cmd, int quoted, int flag, struct worddest *dst)
{
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 quotes = flag & (EXP_FULL | EXP_CASE);
+ int quotes = flag & (EXP_GLOB | EXP_CASE);
size_t nnl;
+ const char *ifs;
INTOFF;
- saveifs = ifsfirst;
- savelastp = ifslastp;
saveargbackq = argbackq;
p = grabstackstr(dest);
evalbackcmd(cmd, &in);
ungrabstackstr(p, dest);
- ifsfirst = saveifs;
- ifslastp = savelastp;
argbackq = saveargbackq;
p = in.buf;
- lastc = '\0';
nnl = 0;
+ if (!quoted && flag & EXP_SPLIT)
+ ifs = ifsset() ? ifsval() : " \t\n";
+ else
+ ifs = "";
/* Don't copy trailing newlines */
for (;;) {
if (--in.nleft < 0) {
if (in.fd < 0)
break;
- while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR);
+ while ((i = read(in.fd, buf, sizeof buf)) < 0 && errno == EINTR)
+ ;
TRACE(("expbackq: read returns %d\n", i));
if (i <= 0)
break;
in.nleft = i - 1;
}
lastc = *p++;
- if (lastc != '\0') {
- if (lastc == '\n') {
- nnl++;
- } else {
- CHECKSTRSPACE(nnl + 2, dest);
- while (nnl > 0) {
- nnl--;
- USTPUTC('\n', dest);
+ if (lastc == '\0')
+ continue;
+ if (lastc == '\n') {
+ nnl++;
+ } else {
+ if (nnl > 0) {
+ if (strchr(ifs, '\n') != NULL) {
+ NEXTWORD('\n', flag, dest, dst);
+ nnl = 0;
+ } else {
+ CHECKSTRSPACE(nnl + 2, dest);
+ while (nnl > 0) {
+ nnl--;
+ USTPUTC('\n', dest);
+ }
}
+ }
+ if (strchr(ifs, lastc) != NULL)
+ NEXTWORD(lastc, flag, dest, dst);
+ else {
+ CHECKSTRSPACE(2, dest);
if (quotes && syntax[(int)lastc] == CCTL)
USTPUTC(CTLESC, dest);
USTPUTC(lastc, dest);
ckfree(in.buf);
if (in.jp)
exitstatus = waitforjob(in.jp, (int *)NULL);
- if (quoted == 0)
- recordregion(startloc, dest - stackblock(), 0);
TRACE(("expbackq: size=%td: \"%.*s\"\n",
((dest - stackblock()) - startloc),
(int)((dest - stackblock()) - startloc),
*startp++ = *loc++;
}
-static int
-subevalvar(char *p, char *str, int strloc, int subtype, int startloc,
- int varflags, int quotes)
+static void
+subevalvar_trim(const char *p, int strloc, int subtype, int startloc)
{
char *startp;
char *loc = NULL;
- char *q;
+ char *str;
int c = 0;
struct nodelist *saveargbackq = argbackq;
int amount;
- argstr(p, (subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX ||
- subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX ?
- EXP_CASE : 0) | EXP_TILDE);
+ argstr(p, EXP_CASE | EXP_TILDE, NULL);
STACKSTRNUL(expdest);
argbackq = saveargbackq;
startp = stackblock() + startloc;
- if (str == NULL)
- str = stackblock() + strloc;
+ str = stackblock() + strloc;
switch (subtype) {
- case VSASSIGN:
- setvar(str, startp, 0);
- amount = startp - expdest;
- STADJUST(amount, expdest);
- varflags &= ~VSNUL;
- return 1;
-
- case VSQUESTION:
- if (*p != CTLENDVAR) {
- outfmt(out2, "%s\n", startp);
- error((char *)NULL);
- }
- error("%.*s: parameter %snot set", (int)(p - str - 1),
- str, (varflags & VSNUL) ? "null or " : "");
- return 0;
-
case VSTRIMLEFT:
for (loc = startp; loc < str; loc++) {
c = *loc;
*loc = '\0';
- if (patmatch(str, startp, quotes)) {
+ if (patmatch(str, startp)) {
*loc = c;
recordleft(str, loc, startp);
- return 1;
+ return;
}
*loc = c;
- if (quotes && *loc == CTLESC)
- loc++;
}
- return 0;
+ break;
case VSTRIMLEFTMAX:
for (loc = str - 1; loc >= startp;) {
c = *loc;
*loc = '\0';
- if (patmatch(str, startp, quotes)) {
+ if (patmatch(str, startp)) {
*loc = c;
recordleft(str, loc, startp);
- return 1;
+ return;
}
*loc = c;
loc--;
- if (quotes && loc > startp && *(loc - 1) == CTLESC) {
- for (q = startp; q < loc; q++)
- if (*q == CTLESC)
- q++;
- if (q > loc)
- loc--;
- }
}
- return 0;
+ break;
case VSTRIMRIGHT:
for (loc = str - 1; loc >= startp;) {
- if (patmatch(str, loc, quotes)) {
+ if (patmatch(str, loc)) {
amount = loc - expdest;
STADJUST(amount, expdest);
- return 1;
+ return;
}
loc--;
- if (quotes && loc > startp && *(loc - 1) == CTLESC) {
- for (q = startp; q < loc; q++)
- if (*q == CTLESC)
- q++;
- if (q > loc)
- loc--;
- }
}
- return 0;
+ break;
case VSTRIMRIGHTMAX:
for (loc = startp; loc < str - 1; loc++) {
- if (patmatch(str, loc, quotes)) {
+ if (patmatch(str, loc)) {
amount = loc - expdest;
STADJUST(amount, expdest);
- return 1;
+ return;
}
- if (quotes && *loc == CTLESC)
- loc++;
}
- return 0;
+ break;
+
+
+ default:
+ abort();
+ }
+ amount = (expdest - stackblock() - strloc) + 1;
+ STADJUST(-amount, expdest);
+}
+
+
+static int
+subevalvar_misc(const char *p, const char *var, int subtype, int startloc,
+ int varflags)
+{
+ char *startp;
+ struct nodelist *saveargbackq = argbackq;
+ int amount;
+ argstr(p, EXP_TILDE, NULL);
+ STACKSTRNUL(expdest);
+ argbackq = saveargbackq;
+ startp = stackblock() + startloc;
+
+ switch (subtype) {
+ case VSASSIGN:
+ setvar(var, startp, 0);
+ amount = startp - expdest;
+ STADJUST(amount, expdest);
+ return 1;
+
+ case VSQUESTION:
+ if (*p != CTLENDVAR) {
+ outfmt(out2, "%s\n", startp);
+ error((char *)NULL);
+ }
+ error("%.*s: parameter %snot set", (int)(p - var - 1),
+ var, (varflags & VSNUL) ? "null or " : "");
+ return 0;
default:
abort();
* input string.
*/
-static char *
-evalvar(char *p, int flag)
+static const char *
+evalvar(const char *p, int flag, struct worddest *dst)
{
int subtype;
int varflags;
- char *var;
+ const char *var;
const char *val;
int patloc;
int c;
int startloc;
int varlen;
int varlenb;
- int easy;
- int quotes = flag & (EXP_FULL | EXP_CASE);
- int record = 0;
+ char buf[21];
varflags = (unsigned char)*p++;
subtype = varflags & VSTYPE;
if (set && subtype != VSPLUS) {
/* insert the value of the variable */
if (special) {
- if (varflags & VSLINENO)
- STPUTBIN(var, p - var - 1, expdest);
- else
- varvalue(var, varflags & VSQUOTE, subtype, flag);
+ if (varflags & VSLINENO) {
+ if (p - var > (ptrdiff_t)sizeof(buf))
+ abort();
+ memcpy(buf, var, p - var - 1);
+ buf[p - var - 1] = '\0';
+ strtodest(buf, flag, subtype,
+ varflags & VSQUOTE, dst);
+ } else
+ varvalue(var, varflags & VSQUOTE, subtype, flag,
+ dst);
if (subtype == VSLENGTH) {
varlenb = expdest - stackblock() - startloc;
varlen = varlenb;
}
else
strtodest(val, flag, subtype,
- varflags & VSQUOTE);
+ varflags & VSQUOTE, dst);
}
}
if (subtype == VSPLUS)
set = ! set;
- easy = ((varflags & VSQUOTE) == 0 ||
- (*var == '@' && shellparam.nparam != 1));
-
-
switch (subtype) {
case VSLENGTH:
- expdest = cvtnum(varlen, expdest);
- record = 1;
+ cvtnum(varlen, buf);
+ strtodest(buf, flag, VSNORMAL, varflags & VSQUOTE, dst);
break;
case VSNORMAL:
- record = easy;
break;
case VSPLUS:
case VSMINUS:
if (!set) {
- argstr(p, flag | (flag & EXP_FULL ? EXP_SPLIT_LIT : 0) |
- (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0));
+ argstr(p, flag | (flag & EXP_SPLIT ? EXP_SPLIT_LIT : 0) |
+ (varflags & VSQUOTE ? EXP_LIT_QUOTED : 0), dst);
break;
}
- record = easy;
break;
case VSTRIMLEFT:
*/
STPUTC('\0', expdest);
patloc = expdest - stackblock();
- if (subevalvar(p, NULL, patloc, subtype,
- startloc, varflags, quotes) == 0) {
- int amount = (expdest - stackblock() - patloc) + 1;
- STADJUST(-amount, expdest);
- }
- /* Remove any recorded regions beyond start of variable */
- removerecordregions(startloc);
- record = 1;
+ subevalvar_trim(p, patloc, subtype, startloc);
+ reprocess(startloc, flag, VSNORMAL, varflags & VSQUOTE, dst);
+ if (flag & EXP_SPLIT && *var == '@' && varflags & VSQUOTE)
+ dst->state = WORD_QUOTEMARK;
break;
case VSASSIGN:
case VSQUESTION:
if (!set) {
- if (subevalvar(p, var, 0, subtype, startloc, varflags,
- quotes)) {
+ if (subevalvar_misc(p, var, subtype, startloc,
+ varflags)) {
varflags &= ~VSNUL;
- /*
- * Remove any recorded regions beyond
- * start of variable
- */
- removerecordregions(startloc);
goto again;
}
break;
}
- record = easy;
break;
case VSERROR:
abort();
}
- if (record)
- recordregion(startloc, expdest - stackblock(),
- varflags & VSQUOTE || (ifsset() && ifsval()[0] == '\0' &&
- (*var == '@' || *var == '*')));
-
if (subtype != VSNORMAL) { /* skip to end of alternative */
int nesting = 1;
for (;;) {
}
static void
-strtodest(const char *p, int flag, int subtype, int quoted)
+strtodest(const char *p, int flag, int subtype, int quoted,
+ struct worddest *dst)
{
- if (flag & (EXP_FULL | EXP_CASE) && subtype != VSLENGTH)
+ if (subtype == VSLENGTH || subtype == VSTRIMLEFT ||
+ subtype == VSTRIMLEFTMAX || subtype == VSTRIMRIGHT ||
+ subtype == VSTRIMRIGHTMAX)
+ STPUTS(p, expdest);
+ else if (flag & EXP_SPLIT && !quoted && dst != NULL)
+ STPUTS_SPLIT(p, BASESYNTAX, flag, expdest, dst);
+ else if (flag & (EXP_GLOB | EXP_CASE))
STPUTS_QUOTES(p, quoted ? DQSYNTAX : BASESYNTAX, expdest);
else
STPUTS(p, expdest);
}
+static void
+reprocess(int startloc, int flag, int subtype, int quoted,
+ struct worddest *dst)
+{
+ static char *buf = NULL;
+ static size_t buflen = 0;
+ char *startp;
+ size_t len, zpos, zlen;
+
+ startp = stackblock() + startloc;
+ len = expdest - startp;
+ if (len >= SIZE_MAX / 2)
+ abort();
+ INTOFF;
+ if (len >= buflen) {
+ ckfree(buf);
+ buf = NULL;
+ }
+ if (buflen < 128)
+ buflen = 128;
+ while (len >= buflen)
+ buflen <<= 1;
+ if (buf == NULL)
+ buf = ckmalloc(buflen);
+ INTON;
+ memcpy(buf, startp, len);
+ buf[len] = '\0';
+ STADJUST(-len, expdest);
+ for (zpos = 0;;) {
+ zlen = strlen(buf + zpos);
+ strtodest(buf + zpos, flag, subtype, quoted, dst);
+ zpos += zlen + 1;
+ if (zpos == len + 1)
+ break;
+ if (flag & EXP_SPLIT && (quoted || (zlen > 0 && zpos < len)))
+ NEXTWORD('\0', flag, expdest, dst);
+ }
+}
+
/*
* Add the value of a specialized variable to the stack string.
*/
static void
-varvalue(const char *name, int quoted, int subtype, int flag)
+varvalue(const char *name, int quoted, int subtype, int flag,
+ struct worddest *dst)
{
int num;
char *p;
int i;
+ int splitlater;
char sep[2];
char **ap;
+ char buf[(NSHORTOPTS > 10 ? NSHORTOPTS : 10) + 1];
+
+ if (subtype == VSLENGTH)
+ flag &= ~EXP_FULL;
+ splitlater = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX ||
+ subtype == VSTRIMRIGHT || subtype == VSTRIMRIGHTMAX;
switch (*name) {
case '$':
num = backgndpidval();
break;
case '-':
- for (i = 0 ; i < NOPTS ; i++) {
- if (optlist[i].val)
- STPUTC(optlist[i].letter, expdest);
+ p = buf;
+ for (i = 0 ; i < NSHORTOPTS ; i++) {
+ if (optval[i])
+ *p++ = optletter[i];
}
+ *p = '\0';
+ strtodest(buf, flag, subtype, quoted, dst);
return;
case '@':
- if (flag & EXP_FULL && quoted) {
+ if (flag & EXP_SPLIT && quoted) {
for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
- strtodest(p, flag, subtype, quoted);
- if (*ap)
- STPUTC('\0', expdest);
+ strtodest(p, flag, subtype, quoted, dst);
+ if (*ap) {
+ if (splitlater)
+ STPUTC('\0', expdest);
+ else
+ NEXTWORD('\0', flag, expdest,
+ dst);
+ }
}
+ if (shellparam.nparam > 0)
+ dst->state = WORD_QUOTEMARK;
return;
}
/* FALLTHROUGH */
sep[0] = ' ';
sep[1] = '\0';
for (ap = shellparam.p ; (p = *ap++) != NULL ; ) {
- strtodest(p, flag, subtype, quoted);
+ strtodest(p, flag, subtype, quoted, dst);
if (!*ap)
break;
if (sep[0])
- strtodest(sep, flag, subtype, quoted);
- else if (flag & EXP_FULL && !quoted && **ap != '\0')
- STPUTC('\0', expdest);
+ strtodest(sep, flag, subtype, quoted, dst);
+ else if (flag & EXP_SPLIT && !quoted && **ap != '\0') {
+ if (splitlater)
+ STPUTC('\0', expdest);
+ else
+ NEXTWORD('\0', flag, expdest, dst);
+ }
}
return;
default:
p = shellparam.p[num - 1];
else
return;
- strtodest(p, flag, subtype, quoted);
+ strtodest(p, flag, subtype, quoted, dst);
}
return;
}
- expdest = cvtnum(num, expdest);
+ cvtnum(num, buf);
+ strtodest(buf, flag, subtype, quoted, dst);
}
-/*
- * 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;
-
- INTOFF;
- if (ifslastp == NULL) {
- ifsp = &ifsfirst;
- } else {
- if (ifslastp->endoff == start
- && ifslastp->inquotes == inquotes) {
- /* extend previous area */
- ifslastp->endoff = end;
- INTON;
- 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;
- INTON;
-}
-
-
-
-/*
- * 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.
- * CTLESC characters are preserved but have little effect in this pass
- * other than escaping CTL* characters. In particular, they do not escape
- * IFS characters: that should be done with the ifsregion mechanism.
- * CTLQUOTEMARK characters are used to preserve empty quoted strings.
- * This pass treats them as a regular character, making the string non-empty.
- * Later, they are removed along with the other CTL* characters.
- */
-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 had_param_ch = 0;
-
- start = string;
-
- 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;
- while (p < string + ifsp->endoff) {
- q = p;
- if (*p == CTLESC)
- p++;
- if (ifsp->inquotes) {
- /* Only NULs (should be from "$@") end args */
- had_param_ch = 1;
- if (*p != 0) {
- p++;
- continue;
- }
- ifsspc = NULL;
- } else {
- if (!strchr(ifs, *p)) {
- had_param_ch = 1;
- p++;
- continue;
- }
- ifsspc = strchr(" \t\n", *p);
-
- /* Ignore IFS whitespace at start */
- if (q == start && ifsspc != NULL) {
- p++;
- start = p;
- continue;
- }
- had_param_ch = 0;
- }
-
- /* 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 (had_param_ch || *start != 0) {
- sp = (struct strlist *)stalloc(sizeof *sp);
- sp->text = start;
- *arglist->lastp = sp;
- arglist->lastp = &sp->next;
- }
-}
-
-
static char expdir[PATH_MAX];
#define expdir_end (expdir + sizeof(expdir))
/*
* Perform pathname generation and remove control characters.
- * At this point, the only control characters should be CTLESC and CTLQUOTEMARK.
- * The results are stored in the list exparg.
+ * At this point, the only control characters should be CTLESC.
+ * The results are stored in the list dstlist.
*/
static void
-expandmeta(struct strlist *str)
+expandmeta(char *pattern, struct arglist *dstlist)
{
char *p;
- struct strlist **savelastp;
- struct strlist *sp;
+ int firstmatch;
char c;
- while (str) {
- savelastp = exparg.lastp;
- if (!fflag) {
- p = str->text;
- for (; (c = *p) != '\0'; p++) {
- /* fast check for meta chars */
- if (c == '*' || c == '?' || c == '[') {
- INTOFF;
- expmeta(expdir, str->text);
- INTON;
- break;
- }
- }
- }
- if (exparg.lastp == savelastp) {
- /*
- * no matches
- */
- *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;
+ firstmatch = dstlist->count;
+ p = pattern;
+ for (; (c = *p) != '\0'; p++) {
+ /* fast check for meta chars */
+ if (c == '*' || c == '?' || c == '[') {
+ INTOFF;
+ expmeta(expdir, pattern, dstlist);
+ INTON;
+ break;
}
- str = str->next;
+ }
+ if (dstlist->count == firstmatch) {
+ /*
+ * no matches
+ */
+ rmescapes(pattern);
+ appendarglist(dstlist, pattern);
+ } else {
+ qsort(&dstlist->args[firstmatch],
+ dstlist->count - firstmatch,
+ sizeof(dstlist->args[0]), expsortcmp);
}
}
*/
static void
-expmeta(char *enddir, char *name)
+expmeta(char *enddir, char *name, struct arglist *arglist)
{
const char *p;
const char *q;
if (*q == '!' || *q == '^')
q++;
for (;;) {
- while (*q == CTLQUOTEMARK)
- q++;
if (*q == CTLESC)
q++;
if (*q == '/' || *q == '\0')
}
} else if (*p == '\0')
break;
- else if (*p == CTLQUOTEMARK)
- continue;
else {
if (*p == CTLESC)
esc++;
if (enddir != expdir)
metaflag++;
for (p = name ; ; p++) {
- if (*p == CTLQUOTEMARK)
- continue;
if (*p == CTLESC)
p++;
*enddir++ = *p;
return;
}
if (metaflag == 0 || lstat(expdir, &statb) >= 0)
- addfname(expdir);
+ appendarglist(arglist, stsavestr(expdir));
return;
}
endname = name + (p - name);
if (start != name) {
p = name;
while (p < start) {
- while (*p == CTLQUOTEMARK)
- p++;
if (*p == CTLESC)
p++;
*enddir++ = *p++;
}
matchdot = 0;
p = start;
- while (*p == CTLQUOTEMARK)
- p++;
if (*p == CTLESC)
p++;
if (*p == '.')
while (! int_pending() && (dp = readdir(dirp)) != NULL) {
if (dp->d_name[0] == '.' && ! matchdot)
continue;
- if (patmatch(start, dp->d_name, 0)) {
+ if (patmatch(start, dp->d_name)) {
namlen = dp->d_namlen;
if (enddir + namlen + 1 > expdir_end)
continue;
memcpy(enddir, dp->d_name, namlen + 1);
if (atend)
- addfname(expdir);
+ appendarglist(arglist, stsavestr(expdir));
else {
if (dp->d_type != DT_UNKNOWN &&
dp->d_type != DT_DIR &&
continue;
enddir[namlen] = '/';
enddir[namlen + 1] = '\0';
- expmeta(enddir + namlen + 1, endname);
+ expmeta(enddir + namlen + 1, endname, arglist);
}
}
}
}
-/*
- * Add a file name to the list.
- */
-
-static void
-addfname(char *name)
+static int
+expsortcmp(const void *p1, const void *p2)
{
- char *p;
- struct strlist *sp;
+ const char *s1 = *(const char * const *)p1;
+ const char *s2 = *(const char * const *)p2;
- p = stsavestr(name);
- 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;
+ return (strcoll(s1, s2));
}
*/
static int
-patmatch(const char *pattern, const char *string, int squoted)
+patmatch(const char *pattern, const char *string)
{
const char *p, *q, *end;
const char *bt_p, *bt_q;
goto backtrack;
return 1;
case CTLESC:
- if (squoted && *q == CTLESC)
- q++;
if (*q++ != *p++)
goto backtrack;
break;
- case CTLQUOTEMARK:
- continue;
case '?':
- if (squoted && *q == CTLESC)
- q++;
if (*q == '\0')
return 0;
if (localeisutf8) {
if (wc == 0)
goto backtrack;
} else
- wc = (unsigned char)*q++;
+ q++;
break;
case '*':
c = *p;
- while (c == CTLQUOTEMARK || c == '*')
+ while (c == '*')
c = *++p;
/*
* If the pattern ends here, we know the string
bt_q = q;
break;
case '[': {
- const char *endp;
+ const char *savep, *saveq;
int invert, found;
wchar_t chr;
- endp = p;
- if (*endp == '!' || *endp == '^')
- endp++;
- do {
- while (*endp == CTLQUOTEMARK)
- endp++;
- if (*endp == 0)
- goto dft; /* no matching ] */
- if (*endp == CTLESC)
- endp++;
- } while (*++endp != ']');
+ savep = p, saveq = q;
invert = 0;
if (*p == '!' || *p == '^') {
invert++;
p++;
}
found = 0;
- if (squoted && *q == CTLESC)
- q++;
if (*q == '\0')
return 0;
if (localeisutf8) {
chr = (unsigned char)*q++;
c = *p++;
do {
- if (c == CTLQUOTEMARK)
- continue;
+ if (c == '\0') {
+ p = savep, q = saveq;
+ c = '[';
+ goto dft;
+ }
if (c == '[' && *p == ':') {
found |= match_charclass(p, chr, &end);
if (end != NULL)
wc = (unsigned char)c;
if (*p == '-' && p[1] != ']') {
p++;
- while (*p == CTLQUOTEMARK)
- p++;
if (*p == CTLESC)
p++;
if (localeisutf8) {
break;
}
dft: default:
- if (squoted && *q == CTLESC)
- q++;
if (*q == '\0')
return 0;
if (*q++ == c)
*/
if (bt_p == NULL)
return 0;
- if (squoted && *bt_q == CTLESC)
- bt_q++;
if (*bt_q == '\0')
return 0;
bt_q++;
setstackmark(&smark);
argbackq = pattern->narg.backquote;
STARTSTACKSTR(expdest);
- ifslastp = NULL;
- argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
+ argstr(pattern->narg.text, EXP_TILDE | EXP_CASE, NULL);
STPUTC('\0', expdest);
p = grabstackstr(expdest);
- result = patmatch(p, val, 0);
+ result = patmatch(p, val);
popstackmark(&smark);
return result;
}
* Our own itoa().
*/
-static char *
+static void
cvtnum(int num, char *buf)
{
char temp[32];
if (neg)
*--p = '-';
- STPUTS(p, buf);
- return buf;
+ memcpy(buf, p, temp + 32 - p);
}
/*
outbin(argv[i], strlen(argv[i]) + 1, out1);
return (0);
}
+
+/*
+ * Do most of the work for wordexp(3), new version.
+ */
+
+int
+freebsd_wordexpcmd(int argc __unused, char **argv __unused)
+{
+ struct arglist arglist;
+ union node *args, *n;
+ size_t len;
+ int ch;
+ int protected = 0;
+ int fd = -1;
+ int i;
+
+ while ((ch = nextopt("f:p")) != '\0') {
+ switch (ch) {
+ case 'f':
+ fd = number(shoptarg);
+ break;
+ case 'p':
+ protected = 1;
+ break;
+ }
+ }
+ if (*argptr != NULL)
+ error("wrong number of arguments");
+ if (fd < 0)
+ error("missing fd");
+ INTOFF;
+ setinputfd(fd, 1);
+ INTON;
+ args = parsewordexp();
+ popfile(); /* will also close fd */
+ if (protected)
+ for (n = args; n != NULL; n = n->narg.next) {
+ if (n->narg.backquote != NULL) {
+ outcslow('C', out1);
+ error("command substitution disabled");
+ }
+ }
+ outcslow(' ', out1);
+ emptyarglist(&arglist);
+ for (n = args; n != NULL; n = n->narg.next)
+ expandarg(n, &arglist, EXP_FULL | EXP_TILDE);
+ for (i = 0, len = 0; i < arglist.count; i++)
+ len += strlen(arglist.args[i]);
+ out1fmt("%016x %016zx", arglist.count, len);
+ for (i = 0; i < arglist.count; i++)
+ outbin(arglist.args[i], strlen(arglist.args[i]) + 1, out1);
+ return (0);
+}