summary refs log tree commit diff
path: root/bin/dash/src/expand.c
diff options
context:
space:
mode:
Diffstat (limited to 'bin/dash/src/expand.c')
-rw-r--r--bin/dash/src/expand.c1753
1 files changed, 0 insertions, 1753 deletions
diff --git a/bin/dash/src/expand.c b/bin/dash/src/expand.c
deleted file mode 100644
index 1730670e..00000000
--- a/bin/dash/src/expand.c
+++ /dev/null
@@ -1,1753 +0,0 @@
-/*-
- * Copyright (c) 1991, 1993
- *	The Regents of the University of California.  All rights reserved.
- * Copyright (c) 1997-2005
- *	Herbert Xu <herbert@gondor.apana.org.au>.  All rights reserved.
- *
- * This code is derived from software contributed to Berkeley by
- * Kenneth Almquist.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- *    notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- *    notice, this list of conditions and the following disclaimer in the
- *    documentation and/or other materials provided with the distribution.
- * 3. Neither the name of the University nor the names of its contributors
- *    may be used to endorse or promote products derived from this software
- *    without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
-
-#include <sys/types.h>
-#include <sys/time.h>
-#include <sys/stat.h>
-#include <dirent.h>
-#include <unistd.h>
-#ifdef HAVE_GETPWNAM
-#include <pwd.h>
-#endif
-#include <stdlib.h>
-#include <stdio.h>
-#include <inttypes.h>
-#include <limits.h>
-#include <string.h>
-#ifdef HAVE_FNMATCH
-#include <fnmatch.h>
-#endif
-#ifdef HAVE_GLOB
-#include <glob.h>
-#endif
-#include <ctype.h>
-#include <stdbool.h>
-
-/*
- * Routines to expand arguments to commands.  We have to deal with
- * backquotes, shell variables, and file metacharacters.
- */
-
-#include "shell.h"
-#include "main.h"
-#include "nodes.h"
-#include "eval.h"
-#include "expand.h"
-#include "syntax.h"
-#include "parser.h"
-#include "jobs.h"
-#include "options.h"
-#include "var.h"
-#include "output.h"
-#include "memalloc.h"
-#include "error.h"
-#include "mystring.h"
-#include "show.h"
-#include "system.h"
-
-/*
- * _rmescape() flags
- */
-#define RMESCAPE_ALLOC	0x1	/* Allocate a new string */
-#define RMESCAPE_GLOB	0x2	/* Add backslashes for glob */
-#define RMESCAPE_GROW	0x8	/* Grow strings instead of stalloc */
-#define RMESCAPE_HEAP	0x10	/* Malloc strings instead of stalloc */
-
-/* Add CTLESC when necessary. */
-#define QUOTES_ESC	(EXP_FULL | EXP_CASE)
-
-/*
- * Structure specifying which parts of the string should be searched
- * for IFS characters.
- */
-
-struct ifsregion {
-	struct ifsregion *next;	/* next region in list */
-	int begoff;		/* offset of start of region */
-	int endoff;		/* offset of end of region */
-	int nulonly;		/* search for nul bytes only */
-};
-
-/* output of current string */
-static char *expdest;
-/* list of back quote expressions */
-static struct nodelist *argbackq;
-/* first struct in list of ifs regions */
-static struct ifsregion ifsfirst;
-/* last struct in list */
-static struct ifsregion *ifslastp;
-/* holds expanded arg list */
-static struct arglist exparg;
-
-static char *argstr(char *p, int flag);
-static char *exptilde(char *startp, int flag);
-static char *expari(char *start, int flag);
-STATIC void expbackq(union node *, int);
-STATIC char *evalvar(char *, int);
-static size_t strtodest(const char *p, int flags);
-static size_t memtodest(const char *p, size_t len, int flags);
-STATIC ssize_t varvalue(char *, int, int, int);
-STATIC void expandmeta(struct strlist *);
-#ifdef HAVE_GLOB
-STATIC void addglob(const glob_t *);
-#else
-STATIC void expmeta(char *, unsigned, unsigned);
-STATIC struct strlist *expsort(struct strlist *);
-STATIC struct strlist *msort(struct strlist *, int);
-#endif
-STATIC void addfname(char *);
-STATIC int patmatch(char *, const char *);
-#ifndef HAVE_FNMATCH
-STATIC int pmatch(const char *, const char *);
-#else
-#define pmatch(a, b) !fnmatch((a), (b), 0)
-#endif
-static size_t cvtnum(intmax_t num, int flags);
-STATIC size_t esclen(const char *, const char *);
-STATIC char *scanleft(char *, char *, char *, char *, int, int);
-STATIC char *scanright(char *, char *, char *, char *, int, int);
-STATIC void varunset(const char *, const char *, const char *, int)
-	__attribute__((__noreturn__));
-
-
-/*
- * Prepare a pattern for a glob(3) call.
- *
- * Returns an stalloced string.
- */
-
-STATIC inline char *
-preglob(const char *pattern, int flag) {
-	flag |= RMESCAPE_GLOB;
-	return _rmescapes((char *)pattern, flag);
-}
-
-
-STATIC size_t
-esclen(const char *start, const char *p) {
-	size_t esc = 0;
-
-	while (p > start && *--p == (char)CTLESC) {
-		esc++;
-	}
-	return esc;
-}
-
-
-static inline const char *getpwhome(const char *name)
-{
-#ifdef HAVE_GETPWNAM
-	struct passwd *pw = getpwnam(name);
-	return pw ? pw->pw_dir : 0;
-#else
-	return 0;
-#endif
-}
-
-
-/*
- * Perform variable substitution and command substitution on an argument,
- * placing the resulting list of arguments in arglist.  If EXP_FULL is true,
- * perform splitting and file name expansion.  When arglist is NULL, perform
- * here document expansion.
- */
-
-void
-expandarg(union node *arg, struct arglist *arglist, int flag)
-{
-	struct strlist *sp;
-	char *p;
-
-	argbackq = arg->narg.backquote;
-	STARTSTACKSTR(expdest);
-	argstr(arg->narg.text, flag);
-	if (arglist == NULL) {
-		/* here document expanded */
-		goto out;
-	}
-	p = grabstackstr(expdest);
-	exparg.lastp = &exparg.list;
-	/*
-	 * TODO - EXP_REDIR
-	 */
-	if (flag & EXP_FULL) {
-		ifsbreakup(p, -1, &exparg);
-		*exparg.lastp = NULL;
-		exparg.lastp = &exparg.list;
-		expandmeta(exparg.list);
-	} else {
-		sp = (struct strlist *)stalloc(sizeof (struct strlist));
-		sp->text = p;
-		*exparg.lastp = sp;
-		exparg.lastp = &sp->next;
-	}
-	*exparg.lastp = NULL;
-	if (exparg.list) {
-		*arglist->lastp = exparg.list;
-		arglist->lastp = exparg.lastp;
-	}
-
-out:
-	ifsfree();
-}
-
-
-
-/*
- * Perform variable and command substitution.  If EXP_FULL is set, output CTLESC
- * characters to allow for further processing.  Otherwise treat
- * $@ like $* since no splitting will be performed.
- */
-
-static char *argstr(char *p, int flag)
-{
-	static const char spclchars[] = {
-		'=',
-		':',
-		CTLQUOTEMARK,
-		CTLENDVAR,
-		CTLESC,
-		CTLVAR,
-		CTLBACKQ,
-		CTLARI,
-		CTLENDARI,
-		0
-	};
-	const char *reject = spclchars;
-	int c;
-	int breakall = (flag & (EXP_WORD | EXP_QUOTED)) == EXP_WORD;
-	int inquotes;
-	size_t length;
-	int startloc;
-
-	reject += !!(flag & EXP_VARTILDE2);
-	reject += flag & EXP_VARTILDE ? 0 : 2;
-	inquotes = 0;
-	length = 0;
-	if (flag & EXP_TILDE) {
-		flag &= ~EXP_TILDE;
-tilde:
-		if (*p == '~')
-			p = exptilde(p, flag);
-	}
-start:
-	startloc = expdest - (char *)stackblock();
-	for (;;) {
-		int end;
-
-		length += strcspn(p + length, reject);
-		end = 0;
-		c = (signed char)p[length];
-		if (!(c & 0x80) || c == CTLENDARI || c == CTLENDVAR) {
-			/*
-			 * c == '=' || c == ':' || c == '\0' ||
-			 * c == CTLENDARI || c == CTLENDVAR
-			 */
-			length++;
-			/* c == '\0' || c == CTLENDARI || c == CTLENDVAR */
-			end = !!((c - 1) & 0x80);
-		}
-		if (length > 0 && !(flag & EXP_DISCARD)) {
-			int newloc;
-			char *q;
-
-			q = stnputs(p, length, expdest);
-			q[-1] &= end - 1;
-			expdest = q - (flag & EXP_WORD ? end : 0);
-			newloc = q - (char *)stackblock() - end;
-			if (breakall && !inquotes && newloc > startloc) {
-				recordregion(startloc, newloc, 0);
-			}
-			startloc = newloc;
-		}
-		p += length + 1;
-		length = 0;
-
-		if (end)
-			break;
-
-		switch (c) {
-		case '=':
-			flag |= EXP_VARTILDE2;
-			reject++;
-			/* fall through */
-		case ':':
-			/*
-			 * sort of a hack - expand tildes in variable
-			 * assignments (after the first '=' and after ':'s).
-			 */
-			if (*--p == '~') {
-				goto tilde;
-			}
-			continue;
-		case CTLQUOTEMARK:
-			/* "$@" syntax adherence hack */
-			if (!inquotes && !memcmp(p, dolatstr + 1,
-						 DOLATSTRLEN - 1)) {
-				p = evalvar(p + 1, flag | EXP_QUOTED) + 1;
-				goto start;
-			}
-			inquotes ^= EXP_QUOTED;
-addquote:
-			if (flag & QUOTES_ESC) {
-				p--;
-				length++;
-				startloc++;
-			}
-			break;
-		case CTLESC:
-			startloc++;
-			length++;
-			goto addquote;
-		case CTLVAR:
-			p = evalvar(p, flag | inquotes);
-			goto start;
-		case CTLBACKQ:
-			expbackq(argbackq->n, flag | inquotes);
-			goto start;
-		case CTLARI:
-			p = expari(p, flag | inquotes);
-			goto start;
-		}
-	}
-	return p - 1;
-}
-
-static char *exptilde(char *startp, int flag)
-{
-	signed char c;
-	char *name;
-	const char *home;
-	char *p;
-
-	p = startp;
-	name = p + 1;
-
-	while ((c = *++p) != '\0') {
-		switch(c) {
-		case CTLESC:
-			return (startp);
-		case CTLQUOTEMARK:
-			return (startp);
-		case ':':
-			if (flag & EXP_VARTILDE)
-				goto done;
-			break;
-		case '/':
-		case CTLENDVAR:
-			goto done;
-		}
-	}
-done:
-	if (flag & EXP_DISCARD)
-		goto out;
-	*p = '\0';
-	if (*name == '\0') {
-		home = lookupvar(homestr);
-	} else {
-		home = getpwhome(name);
-	}
-	*p = c;
-	if (!home)
-		goto lose;
-	strtodest(home, flag | EXP_QUOTED);
-out:
-	return (p);
-lose:
-	return (startp);
-}
-
-
-void 
-removerecordregions(int endoff)
-{
-	if (ifslastp == NULL)
-		return;
-
-	if (ifsfirst.endoff > endoff) {
-		while (ifsfirst.next != NULL) {
-			struct ifsregion *ifsp;
-			INTOFF;
-			ifsp = ifsfirst.next->next;
-			ckfree(ifsfirst.next);
-			ifsfirst.next = ifsp;
-			INTON;
-		}
-		if (ifsfirst.begoff > endoff)
-			ifslastp = NULL;
-		else {
-			ifslastp = &ifsfirst;
-			ifsfirst.endoff = endoff;
-		}
-		return;
-	}
-	
-	ifslastp = &ifsfirst;
-	while (ifslastp->next && ifslastp->next->begoff < endoff)
-		ifslastp=ifslastp->next;
-	while (ifslastp->next != NULL) {
-		struct ifsregion *ifsp;
-		INTOFF;
-		ifsp = ifslastp->next->next;
-		ckfree(ifslastp->next);
-		ifslastp->next = ifsp;
-		INTON;
-	}
-	if (ifslastp->endoff > endoff)
-		ifslastp->endoff = endoff;
-}
-
-
-/*
- * Expand arithmetic expression.  Backup to start of expression,
- * evaluate, place result in (backed up) result, adjust string position.
- */
-static char *expari(char *start, int flag)
-{
-	struct stackmark sm;
-	int begoff;
-	int endoff;
-	int len;
-	intmax_t result;
-	char *p;
-
-	p = stackblock();
-	begoff = expdest - p;
-	p = argstr(start, flag & EXP_DISCARD);
-
-	if (flag & EXP_DISCARD)
-		goto out;
-
-	start = stackblock();
-	endoff = expdest - start;
-	start += begoff;
-	STADJUST(start - expdest, expdest);
-
-	removerecordregions(begoff);
-
-	if (likely(flag & QUOTES_ESC))
-		rmescapes(start);
-
-	pushstackmark(&sm, endoff);
-	result = arith(start);
-	popstackmark(&sm);
-
-	len = cvtnum(result, flag);
-
-	if (likely(!(flag & EXP_QUOTED)))
-		recordregion(begoff, begoff + len, 0);
-
-out:
-	return p;
-}
-
-
-/*
- * Expand stuff in backwards quotes.
- */
-
-STATIC void
-expbackq(union node *cmd, int flag)
-{
-	struct backcmd in;
-	int i;
-	char buf[128];
-	char *p;
-	char *dest;
-	int startloc;
-	struct stackmark smark;
-
-	if (flag & EXP_DISCARD)
-		goto out;
-
-	INTOFF;
-	startloc = expdest - (char *)stackblock();
-	pushstackmark(&smark, startloc);
-	evalbackcmd(cmd, (struct backcmd *) &in);
-	popstackmark(&smark);
-
-	p = in.buf;
-	i = in.nleft;
-	if (i == 0)
-		goto read;
-	for (;;) {
-		memtodest(p, i, flag);
-read:
-		if (in.fd < 0)
-			break;
-		do {
-			i = read(in.fd, buf, sizeof buf);
-		} while (i < 0 && errno == EINTR);
-		TRACE(("expbackq: read returns %d\n", i));
-		if (i <= 0)
-			break;
-		p = buf;
-	}
-
-	if (in.buf)
-		ckfree(in.buf);
-	if (in.fd >= 0) {
-		close(in.fd);
-		back_exitstatus = waitforjob(in.jp);
-	}
-	INTON;
-
-	/* Eat all trailing newlines */
-	dest = expdest;
-	for (; dest > ((char *)stackblock() + startloc) && dest[-1] == '\n';)
-		STUNPUTC(dest);
-	expdest = dest;
-
-	if (!(flag & EXP_QUOTED))
-		recordregion(startloc, dest - (char *)stackblock(), 0);
-	TRACE(("evalbackq: size=%d: \"%.*s\"\n",
-		(dest - (char *)stackblock()) - startloc,
-		(dest - (char *)stackblock()) - startloc,
-		stackblock() + startloc));
-
-out:
-	argbackq = argbackq->next;
-}
-
-
-STATIC char *
-scanleft(
-	char *startp, char *rmesc, char *rmescend, char *str, int quotes,
-	int zero
-) {
-	char *loc;
-	char *loc2;
-	char c;
-
-	loc = startp;
-	loc2 = rmesc;
-	do {
-		int match;
-		const char *s = loc2;
-		c = *loc2;
-		if (zero) {
-			*loc2 = '\0';
-			s = rmesc;
-		}
-		match = pmatch(str, s);
-		*loc2 = c;
-		if (match)
-			return loc;
-		if (quotes && *loc == (char)CTLESC)
-			loc++;
-		loc++;
-		loc2++;
-	} while (c);
-	return 0;
-}
-
-
-STATIC char *
-scanright(
-	char *startp, char *rmesc, char *rmescend, char *str, int quotes,
-	int zero
-) {
-	int esc = 0;
-	char *loc;
-	char *loc2;
-
-	for (loc = str - 1, loc2 = rmescend; loc >= startp; loc2--) {
-		int match;
-		char c = *loc2;
-		const char *s = loc2;
-		if (zero) {
-			*loc2 = '\0';
-			s = rmesc;
-		}
-		match = pmatch(str, s);
-		*loc2 = c;
-		if (match)
-			return loc;
-		loc--;
-		if (quotes) {
-			if (--esc < 0) {
-				esc = esclen(startp, loc);
-			}
-			if (esc % 2) {
-				esc--;
-				loc--;
-			}
-		}
-	}
-	return 0;
-}
-
-static char *subevalvar(char *start, char *str, int strloc, int startloc,
-			int varflags, int flag)
-{
-	int subtype = varflags & VSTYPE;
-	int quotes = flag & QUOTES_ESC;
-	char *startp;
-	char *loc;
-	long amount;
-	char *rmesc, *rmescend;
-	int zero;
-	char *(*scan)(char *, char *, char *, char *, int , int);
-	char *p;
-
-	p = argstr(start, (flag & EXP_DISCARD) | EXP_TILDE |
-			  (str ?  0 : EXP_CASE));
-	if (flag & EXP_DISCARD)
-		return p;
-
-	startp = stackblock() + startloc;
-
-	switch (subtype) {
-	case VSASSIGN:
-		setvar(str, startp, 0);
-
-		loc = startp;
-		goto out;
-
-	case VSQUESTION:
-		varunset(start, str, startp, varflags);
-		/* NOTREACHED */
-	}
-
-	subtype -= VSTRIMRIGHT;
-#ifdef DEBUG
-	if (subtype < 0 || subtype > 3)
-		abort();
-#endif
-
-	rmesc = startp;
-	rmescend = stackblock() + strloc;
-	if (quotes) {
-		rmesc = _rmescapes(startp, RMESCAPE_ALLOC | RMESCAPE_GROW);
-		if (rmesc != startp) {
-			rmescend = expdest;
-			startp = stackblock() + startloc;
-		}
-	}
-	rmescend--;
-	str = stackblock() + strloc;
-	preglob(str, 0);
-
-	/* zero = subtype == VSTRIMLEFT || subtype == VSTRIMLEFTMAX */
-	zero = subtype >> 1;
-	/* VSTRIMLEFT/VSTRIMRIGHTMAX -> scanleft */
-	scan = (subtype & 1) ^ zero ? scanleft : scanright;
-
-	loc = scan(startp, rmesc, rmescend, str, quotes, zero);
-	if (loc) {
-		if (zero) {
-			memmove(startp, loc, str - loc);
-			loc = startp + (str - loc) - 1;
-		}
-		*loc = '\0';
-	} else
-		loc = str - 1;
-
-out:
-	amount = loc - expdest;
-	STADJUST(amount, expdest);
-
-	/* Remove any recorded regions beyond start of variable */
-	removerecordregions(startloc);
-
-	return p;
-}
-
-
-/*
- * Expand a variable, and return a pointer to the next character in the
- * input string.
- */
-STATIC char *
-evalvar(char *p, int flag)
-{
-	int subtype;
-	int varflags;
-	char *var;
-	int patloc;
-	int startloc;
-	ssize_t varlen;
-	int discard;
-	int quoted;
-
-	varflags = *p++;
-	subtype = varflags & VSTYPE;
-
-	quoted = flag & EXP_QUOTED;
-	var = p;
-	startloc = expdest - (char *)stackblock();
-	p = strchr(p, '=') + 1;
-
-again:
-	varlen = varvalue(var, varflags, flag, quoted);
-	if (varflags & VSNUL)
-		varlen--;
-
-	discard = varlen < 0 ? EXP_DISCARD : 0;
-
-	switch (subtype) {
-	case VSPLUS:
-		discard ^= EXP_DISCARD;
-		/* fall through */
-
-	case 0:
-	case VSMINUS:
-		p = argstr(p, flag | EXP_TILDE | EXP_WORD |
-			      (discard ^ EXP_DISCARD));
-		goto record;
-
-	case VSASSIGN:
-	case VSQUESTION:
-		p = subevalvar(p, var, 0, startloc, varflags,
-			       (flag & ~QUOTES_ESC) |
-			       (discard ^ EXP_DISCARD));
-
-		if ((flag | ~discard) & EXP_DISCARD)
-			goto record;
-
-		varflags &= ~VSNUL;
-		subtype = VSNORMAL;
-		goto again;
-	}
-
-	if ((discard & ~flag) && uflag)
-		varunset(p, var, 0, 0);
-
-	if (subtype == VSLENGTH) {
-		p++;
-		if (flag & EXP_DISCARD)
-			return p;
-		cvtnum(varlen > 0 ? varlen : 0, flag);
-		goto really_record;
-	}
-
-	if (subtype == VSNORMAL)
-		goto record;
-
-#ifdef DEBUG
-	switch (subtype) {
-	case VSTRIMLEFT:
-	case VSTRIMLEFTMAX:
-	case VSTRIMRIGHT:
-	case VSTRIMRIGHTMAX:
-		break;
-	default:
-		abort();
-	}
-#endif
-
-	flag |= discard;
-	if (!(flag & EXP_DISCARD)) {
-		/*
-		 * Terminate the string and start recording the pattern
-		 * right after it
-		 */
-		STPUTC('\0', expdest);
-	}
-
-	patloc = expdest - (char *)stackblock();
-	p = subevalvar(p, NULL, patloc, startloc, varflags, flag);
-
-record:
-	if ((flag | discard) & EXP_DISCARD)
-		return p;
-
-really_record:
-	if (quoted) {
-		quoted = *var == '@' && shellparam.nparam;
-		if (!quoted)
-			return p;
-	}
-	recordregion(startloc, expdest - (char *)stackblock(), quoted);
-	return p;
-}
-
-
-/*
- * Put a string on the stack.
- */
-
-static size_t memtodest(const char *p, size_t len, int flags)
-{
-	const char *syntax = flags & EXP_QUOTED ? DQSYNTAX : BASESYNTAX;
-	char *q;
-	char *s;
-
-	if (unlikely(!len))
-		return 0;
-
-	q = makestrspace(len * 2, expdest);
-	s = q;
-
-	do {
-		int c = (signed char)*p++;
-		if (c) {
-			if ((flags & QUOTES_ESC) &&
-			    ((syntax[c] == CCTL) ||
-			     (flags & EXP_QUOTED && syntax[c] == CBACK)))
-				USTPUTC(CTLESC, q);
-		} else if (!(flags & EXP_KEEPNUL))
-			continue;
-		USTPUTC(c, q);
-	} while (--len);
-
-	expdest = q;
-	return q - s;
-}
-
-
-static size_t strtodest(const char *p, int flags)
-{
-	size_t len = strlen(p);
-	memtodest(p, len, flags);
-	return len;
-}
-
-
-
-/*
- * Add the value of a specialized variable to the stack string.
- */
-
-STATIC ssize_t
-varvalue(char *name, int varflags, int flags, int quoted)
-{
-	int num;
-	char *p;
-	int i;
-	int sep;
-	char sepc;
-	char **ap;
-	int subtype = varflags & VSTYPE;
-	int discard = (subtype == VSPLUS || subtype == VSLENGTH) |
-		      (flags & EXP_DISCARD);
-	ssize_t len = 0;
-	char c;
-
-	if (!subtype) {
-		if (discard)
-			return -1;
-
-		sh_error("Bad substitution");
-	}
-
-	flags |= EXP_KEEPNUL;
-	flags &= discard ? ~QUOTES_ESC : ~0;
-	sep = (flags & EXP_FULL) << CHAR_BIT;
-
-	switch (*name) {
-	case '$':
-		num = rootpid;
-		goto numvar;
-	case '?':
-		num = exitstatus;
-		goto numvar;
-	case '#':
-		num = shellparam.nparam;
-		goto numvar;
-	case '!':
-		num = backgndpid;
-		if (num == 0)
-			return -1;
-numvar:
-		len = cvtnum(num, flags);
-		break;
-	case '-':
-		p = makestrspace(NOPTS, expdest);
-		for (i = NOPTS - 1; i >= 0; i--) {
-			if (optlist[i] && optletters[i]) {
-				USTPUTC(optletters[i], p);
-				len++;
-			}
-		}
-		expdest = p;
-		break;
-	case '@':
-		if (quoted && sep)
-			goto param;
-		/* fall through */
-	case '*':
-		/* We will set c to 0 or ~0 depending on whether
-		 * we're doing field splitting.  We won't do field
-		 * splitting if either we're quoted or sep is zero.
-		 *
-		 * Instead of testing (quoted || !sep) the following
-		 * trick optimises away any branches by using the
-		 * fact that EXP_QUOTED (which is the only bit that
-		 * can be set in quoted) is the same as EXP_FULL <<
-		 * CHAR_BIT (which is the only bit that can be set
-		 * in sep).
-		 */
-#if EXP_QUOTED >> CHAR_BIT != EXP_FULL
-#error The following two lines expect EXP_QUOTED == EXP_FULL << CHAR_BIT
-#endif
-		c = !((quoted | ~sep) & EXP_QUOTED) - 1;
-		sep &= ~quoted;
-		sep |= ifsset() ? (unsigned char)(c & ifsval()[0]) : ' ';
-param:
-		sepc = sep;
-		if (!(ap = shellparam.p))
-			return -1;
-		while ((p = *ap++)) {
-			len += strtodest(p, flags);
-
-			if (*ap && sep) {
-				len++;
-				memtodest(&sepc, 1, flags);
-			}
-		}
-		break;
-	case '0':
-	case '1':
-	case '2':
-	case '3':
-	case '4':
-	case '5':
-	case '6':
-	case '7':
-	case '8':
-	case '9':
-		num = atoi(name);
-		if (num < 0 || num > shellparam.nparam)
-			return -1;
-		p = num ? shellparam.p[num - 1] : arg0;
-		goto value;
-	default:
-		p = lookupvar(name);
-value:
-		if (!p)
-			return -1;
-
-		len = strtodest(p, flags);
-		break;
-	}
-
-	if (discard)
-		STADJUST(-len, expdest);
-
-	return len;
-}
-
-
-
-/*
- * Record the fact that we have to scan this region of the
- * string for IFS characters.
- */
-
-void
-recordregion(int start, int end, int nulonly)
-{
-	struct ifsregion *ifsp;
-
-	if (ifslastp == NULL) {
-		ifsp = &ifsfirst;
-	} else {
-		INTOFF;
-		ifsp = (struct ifsregion *)ckmalloc(sizeof (struct ifsregion));
-		ifsp->next = NULL;
-		ifslastp->next = ifsp;
-		INTON;
-	}
-	ifslastp = ifsp;
-	ifslastp->begoff = start;
-	ifslastp->endoff = end;
-	ifslastp->nulonly = nulonly;
-}
-
-
-
-/*
- * Break the argument string into pieces based upon IFS and add the
- * strings to the argument list.  The regions of the string to be
- * searched for IFS characters have been stored by recordregion.
- * If maxargs is non-negative, at most maxargs arguments will be created, by
- * joining together the last arguments.
- */
-void
-ifsbreakup(char *string, int maxargs, struct arglist *arglist)
-{
-	struct ifsregion *ifsp;
-	struct strlist *sp;
-	char *start;
-	char *p;
-	char *q;
-	char *r = NULL;
-	const char *ifs, *realifs;
-	int ifsspc;
-	int nulonly;
-
-
-	start = string;
-	if (ifslastp != NULL) {
-		ifsspc = 0;
-		nulonly = 0;
-		realifs = ifsset() ? ifsval() : defifs;
-		ifsp = &ifsfirst;
-		do {
-			int afternul;
-
-			p = string + ifsp->begoff;
-			afternul = nulonly;
-			nulonly = ifsp->nulonly;
-			ifs = nulonly ? nullstr : realifs;
-			ifsspc = 0;
-			while (p < string + ifsp->endoff) {
-				int c;
-				bool isifs;
-				bool isdefifs;
-
-				q = p;
-				c = *p++;
-				if (c == (char)CTLESC)
-					c = *p++;
-
-				isifs = strchr(ifs, c);
-				isdefifs = false;
-				if (isifs)
-					isdefifs = strchr(defifs, c);
-
-				/* If only reading one more argument:
-				 * If we have exactly one field,
-				 * read that field without its terminator.
-				 * If we have more than one field,
-				 * read all fields including their terminators,
-				 * except for trailing IFS whitespace.
-				 *
-				 * This means that if we have only IFS
-				 * characters left, and at most one
-				 * of them is non-whitespace, we stop
-				 * reading here.
-				 * Otherwise, we read all the remaining
-				 * characters except for trailing
-				 * IFS whitespace.
-				 *
-				 * In any case, r indicates the start
-				 * of the characters to remove, or NULL
-				 * if no characters should be removed.
-				 */
-				if (!maxargs) {
-					if (isdefifs) {
-						if (!r)
-							r = q;
-						continue;
-					}
-
-					if (!(isifs && ifsspc))
-						r = NULL;
-
-					ifsspc = 0;
-					continue;
-				}
-
-				if (ifsspc) {
-					if (isifs)
-						q = p;
-
-					start = q;
-
-					if (isdefifs)
-						continue;
-
-					isifs = false;
-				}
-
-				if (isifs) {
-					if (!(afternul || nulonly))
-						ifsspc = isdefifs;
-					/* Ignore IFS whitespace at start */
-					if (q == start && ifsspc) {
-						start = p;
-						ifsspc = 0;
-						continue;
-					}
-					if (maxargs > 0 && !--maxargs) {
-						r = q;
-						continue;
-					}
-					*q = '\0';
-					sp = (struct strlist *)stalloc(sizeof *sp);
-					sp->text = start;
-					*arglist->lastp = sp;
-					arglist->lastp = &sp->next;
-					start = p;
-					continue;
-				}
-
-				ifsspc = 0;
-			}
-		} while ((ifsp = ifsp->next) != NULL);
-		if (nulonly)
-			goto add;
-	}
-
-	if (r)
-		*r = '\0';
-
-	if (!*start)
-		return;
-
-add:
-	sp = (struct strlist *)stalloc(sizeof *sp);
-	sp->text = start;
-	*arglist->lastp = sp;
-	arglist->lastp = &sp->next;
-}
-
-void ifsfree(void)
-{
-	struct ifsregion *p = ifsfirst.next;
-
-	if (!p)
-		goto out;
-
-	INTOFF;
-	do {
-		struct ifsregion *ifsp;
-		ifsp = p->next;
-		ckfree(p);
-		p = ifsp;
-	} while (p);
-	ifsfirst.next = NULL;
-	INTON;
-
-out:
-	ifslastp = NULL;
-}
-
-
-
-/*
- * Expand shell metacharacters.  At this point, the only control characters
- * should be escapes.  The results are stored in the list exparg.
- */
-
-#ifdef HAVE_GLOB
-STATIC void
-expandmeta(struct strlist *str)
-{
-	/* TODO - EXP_REDIR */
-
-	while (str) {
-		const char *p;
-		glob_t pglob;
-		int i;
-
-		if (fflag)
-			goto nometa;
-		INTOFF;
-		p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
-		i = glob(p, GLOB_NOMAGIC, 0, &pglob);
-		if (p != str->text)
-			ckfree(p);
-		switch (i) {
-		case 0:
-			if ((pglob.gl_flags & (GLOB_NOMAGIC | GLOB_NOCHECK)) ==
-			    (GLOB_NOMAGIC | GLOB_NOCHECK))
-				goto nometa2;
-			addglob(&pglob);
-			globfree(&pglob);
-			INTON;
-			break;
-		case GLOB_NOMATCH:
-nometa2:
-			globfree(&pglob);
-			INTON;
-nometa:
-			*exparg.lastp = str;
-			rmescapes(str->text);
-			exparg.lastp = &str->next;
-			break;
-		default:	/* GLOB_NOSPACE */
-			sh_error("Out of space");
-		}
-		str = str->next;
-	}
-}
-
-
-/*
- * Add the result of glob(3) to the list.
- */
-
-STATIC void
-addglob(pglob)
-	const glob_t *pglob;
-{
-	char **p = pglob->gl_pathv;
-
-	do {
-		addfname(*p);
-	} while (*++p);
-}
-
-
-#else	/* HAVE_GLOB */
-STATIC char *expdir;
-STATIC unsigned expdir_max;
-
-
-STATIC void
-expandmeta(struct strlist *str)
-{
-	static const char metachars[] = {
-		'*', '?', '[', 0
-	};
-	/* TODO - EXP_REDIR */
-
-	while (str) {
-		struct strlist **savelastp;
-		struct strlist *sp;
-		char *p;
-		unsigned len;
-
-		if (fflag)
-			goto nometa;
-		if (!strpbrk(str->text, metachars))
-			goto nometa;
-		savelastp = exparg.lastp;
-
-		INTOFF;
-		p = preglob(str->text, RMESCAPE_ALLOC | RMESCAPE_HEAP);
-		len = strlen(p);
-		expdir_max = len + PATH_MAX;
-		expdir = ckmalloc(expdir_max);
-
-		expmeta(p, len, 0);
-		ckfree(expdir);
-		if (p != str->text)
-			ckfree(p);
-		INTON;
-		if (exparg.lastp == savelastp) {
-			/*
-			 * no matches
-			 */
-nometa:
-			*exparg.lastp = str;
-			rmescapes(str->text);
-			exparg.lastp = &str->next;
-		} else {
-			*exparg.lastp = NULL;
-			*savelastp = sp = expsort(*savelastp);
-			while (sp->next != NULL)
-				sp = sp->next;
-			exparg.lastp = &sp->next;
-		}
-		str = str->next;
-	}
-}
-
-
-/*
- * Do metacharacter (i.e. *, ?, [...]) expansion.
- */
-
-STATIC void
-expmeta(char *name, unsigned name_len, unsigned expdir_len)
-{
-	char *enddir = expdir + expdir_len;
-	char *p;
-	const char *cp;
-	char *start;
-	char *endname;
-	int metaflag;
-	struct stat64 statb;
-	DIR *dirp;
-	struct dirent64 *dp;
-	int atend;
-	int matchdot;
-	int esc;
-
-	metaflag = 0;
-	start = name;
-	for (p = name; esc = 0, *p; p += esc + 1) {
-		if (*p == '*' || *p == '?')
-			metaflag = 1;
-		else if (*p == '[') {
-			char *q = p + 1;
-			if (*q == '!')
-				q++;
-			for (;;) {
-				if (*q == '\\')
-					q++;
-				if (*q == '/' || *q == '\0')
-					break;
-				if (*++q == ']') {
-					metaflag = 1;
-					break;
-				}
-			}
-		} else {
-			if (*p == '\\' && p[1])
-				esc++;
-			if (p[esc] == '/') {
-				if (metaflag)
-					break;
-				start = p + esc + 1;
-			}
-		}
-	}
-	if (metaflag == 0) {	/* we've reached the end of the file name */
-		if (!expdir_len)
-			return;
-		p = name;
-		do {
-			if (*p == '\\' && p[1])
-				p++;
-			*enddir++ = *p;
-		} while (*p++);
-		if (lstat64(expdir, &statb) >= 0)
-			addfname(expdir);
-		return;
-	}
-	endname = p;
-	if (name < start) {
-		p = name;
-		do {
-			if (*p == '\\' && p[1])
-				p++;
-			*enddir++ = *p++;
-		} while (p < start);
-	}
-	*enddir = 0;
-	cp = expdir;
-	expdir_len = enddir - cp;
-	if (!expdir_len)
-		cp = ".";
-	if ((dirp = opendir(cp)) == NULL)
-		return;
-	if (*endname == 0) {
-		atend = 1;
-	} else {
-		atend = 0;
-		*endname = '\0';
-		endname += esc + 1;
-	}
-	name_len -= endname - name;
-	matchdot = 0;
-	p = start;
-	if (*p == '\\')
-		p++;
-	if (*p == '.')
-		matchdot++;
-	while (! int_pending() && (dp = readdir64(dirp)) != NULL) {
-		if (dp->d_name[0] == '.' && ! matchdot)
-			continue;
-		if (pmatch(start, dp->d_name)) {
-			if (atend) {
-				scopy(dp->d_name, enddir);
-				addfname(expdir);
-			} else {
-				unsigned offset;
-				unsigned len;
-
-				p = stpcpy(enddir, dp->d_name);
-				*p = '/';
-
-				offset = p - expdir + 1;
-				len = offset + name_len + NAME_MAX;
-				if (len > expdir_max) {
-					len += PATH_MAX;
-					expdir = ckrealloc(expdir, len);
-					expdir_max = len;
-				}
-
-				expmeta(endname, name_len, offset);
-				enddir = expdir + expdir_len;
-			}
-		}
-	}
-	closedir(dirp);
-	if (! atend)
-		endname[-esc - 1] = esc ? '\\' : '/';
-}
-#endif	/* HAVE_GLOB */
-
-
-/*
- * Add a file name to the list.
- */
-
-STATIC void
-addfname(char *name)
-{
-	struct strlist *sp;
-
-	sp = (struct strlist *)stalloc(sizeof *sp);
-	sp->text = sstrdup(name);
-	*exparg.lastp = sp;
-	exparg.lastp = &sp->next;
-}
-
-
-#ifndef HAVE_GLOB
-/*
- * Sort the results of file name expansion.  It calculates the number of
- * strings to sort and then calls msort (short for merge sort) to do the
- * work.
- */
-
-STATIC struct strlist *
-expsort(struct strlist *str)
-{
-	int len;
-	struct strlist *sp;
-
-	len = 0;
-	for (sp = str ; sp ; sp = sp->next)
-		len++;
-	return msort(str, len);
-}
-
-
-STATIC struct strlist *
-msort(struct strlist *list, int len)
-{
-	struct strlist *p, *q = NULL;
-	struct strlist **lpp;
-	int half;
-	int n;
-
-	if (len <= 1)
-		return list;
-	half = len >> 1;
-	p = list;
-	for (n = half ; --n >= 0 ; ) {
-		q = p;
-		p = p->next;
-	}
-	q->next = NULL;			/* terminate first half of list */
-	q = msort(list, half);		/* sort first half of list */
-	p = msort(p, len - half);		/* sort second half */
-	lpp = &list;
-	for (;;) {
-		if (strcmp(p->text, q->text) < 0) {
-			*lpp = p;
-			lpp = &p->next;
-			if ((p = *lpp) == NULL) {
-				*lpp = q;
-				break;
-			}
-		} else {
-			*lpp = q;
-			lpp = &q->next;
-			if ((q = *lpp) == NULL) {
-				*lpp = p;
-				break;
-			}
-		}
-	}
-	return list;
-}
-#endif
-
-
-/*
- * Returns true if the pattern matches the string.
- */
-
-STATIC inline int
-patmatch(char *pattern, const char *string)
-{
-	return pmatch(preglob(pattern, 0), string);
-}
-
-
-#ifndef HAVE_FNMATCH
-STATIC int ccmatch(const char *p, int chr, const char **r)
-{
-	static const struct class {
-		char name[10];
-		int (*fn)(int);
-	} classes[] = {
-		{ .name = ":alnum:]", .fn = isalnum },
-		{ .name = ":cntrl:]", .fn = iscntrl },
-		{ .name = ":lower:]", .fn = islower },
-		{ .name = ":space:]", .fn = isspace },
-		{ .name = ":alpha:]", .fn = isalpha },
-		{ .name = ":digit:]", .fn = isdigit },
-		{ .name = ":print:]", .fn = isprint },
-		{ .name = ":upper:]", .fn = isupper },
-		{ .name = ":blank:]", .fn = isblank },
-		{ .name = ":graph:]", .fn = isgraph },
-		{ .name = ":punct:]", .fn = ispunct },
-		{ .name = ":xdigit:]", .fn = isxdigit },
-	};
-	const struct class *class, *end;
-
-	end = classes + sizeof(classes) / sizeof(classes[0]);
-	for (class = classes; class < end; class++) {
-		const char *q;
-
-		q = prefix(p, class->name);
-		if (!q)
-			continue;
-		*r = q;
-		return class->fn(chr);
-	}
-
-	*r = 0;
-	return 0;
-}
-
-STATIC int
-pmatch(const char *pattern, const char *string)
-{
-	const char *p, *q;
-	char c;
-
-	p = pattern;
-	q = string;
-	for (;;) {
-		switch (c = *p++) {
-		case '\0':
-			goto breakloop;
-		case '\\':
-			if (*p) {
-				c = *p++;
-			}
-			goto dft;
-		case '?':
-			if (*q++ == '\0')
-				return 0;
-			break;
-		case '*':
-			c = *p;
-			while (c == '*')
-				c = *++p;
-			if (c != '\\' && c != '?' && c != '*' && c != '[') {
-				while (*q != c) {
-					if (*q == '\0')
-						return 0;
-					q++;
-				}
-			}
-			do {
-				if (pmatch(p, q))
-					return 1;
-			} while (*q++ != '\0');
-			return 0;
-		case '[': {
-			const char *startp;
-			int invert, found;
-			char chr;
-
-			startp = p;
-			invert = 0;
-			if (*p == '!') {
-				invert++;
-				p++;
-			}
-			found = 0;
-			chr = *q;
-			if (chr == '\0')
-				return 0;
-			c = *p++;
-			do {
-				if (!c) {
-					p = startp;
-					c = '[';
-					goto dft;
-				}
-				if (c == '[') {
-					const char *r;
-
-					found |= !!ccmatch(p, chr, &r);
-					if (r) {
-						p = r;
-						continue;
-					}
-				} else if (c == '\\')
-					c = *p++;
-				if (*p == '-' && p[1] != ']') {
-					p++;
-					if (*p == '\\')
-						p++;
-					if (chr >= c && chr <= *p)
-						found = 1;
-					p++;
-				} else {
-					if (chr == c)
-						found = 1;
-				}
-			} while ((c = *p++) != ']');
-			if (found == invert)
-				return 0;
-			q++;
-			break;
-		}
-dft:	        default:
-			if (*q++ != c)
-				return 0;
-			break;
-		}
-	}
-breakloop:
-	if (*q != '\0')
-		return 0;
-	return 1;
-}
-#endif
-
-
-
-/*
- * Remove any CTLESC characters from a string.
- */
-
-char *
-_rmescapes(char *str, int flag)
-{
-	char *p, *q, *r;
-	int notescaped;
-	int globbing;
-
-	p = strpbrk(str, qchars);
-	if (!p) {
-		return str;
-	}
-	q = p;
-	r = str;
-	if (flag & RMESCAPE_ALLOC) {
-		size_t len = p - str;
-		size_t fulllen = len + strlen(p) + 1;
-
-		if (flag & RMESCAPE_GROW) {
-			int strloc = str - (char *)stackblock();
-
-			r = makestrspace(fulllen, expdest);
-			str = (char *)stackblock() + strloc;
-			p = str + len;
-		} else if (flag & RMESCAPE_HEAP) {
-			r = ckmalloc(fulllen);
-		} else {
-			r = stalloc(fulllen);
-		}
-		q = r;
-		if (len > 0) {
-			q = mempcpy(q, str, len);
-		}
-	}
-	globbing = flag & RMESCAPE_GLOB;
-	notescaped = globbing;
-	while (*p) {
-		if (*p == (char)CTLQUOTEMARK) {
-			p++;
-			notescaped = globbing;
-			continue;
-		}
-		if (*p == '\\') {
-			/* naked back slash */
-			notescaped = 0;
-			goto copy;
-		}
-		if (*p == (char)CTLESC) {
-			p++;
-			if (notescaped)
-				*q++ = '\\';
-		}
-		notescaped = globbing;
-copy:
-		*q++ = *p++;
-	}
-	*q = '\0';
-	if (flag & RMESCAPE_GROW) {
-		expdest = r;
-		STADJUST(q - r + 1, expdest);
-	}
-	return r;
-}
-
-
-
-/*
- * See if a pattern matches in a case statement.
- */
-
-int
-casematch(union node *pattern, char *val)
-{
-	struct stackmark smark;
-	int result;
-
-	setstackmark(&smark);
-	argbackq = pattern->narg.backquote;
-	STARTSTACKSTR(expdest);
-	argstr(pattern->narg.text, EXP_TILDE | EXP_CASE);
-	ifsfree();
-	result = patmatch(stackblock(), val);
-	popstackmark(&smark);
-	return result;
-}
-
-/*
- * Our own itoa().
- */
-
-static size_t cvtnum(intmax_t num, int flags)
-{
-	int len = max_int_length(sizeof(num));
-	char buf[len];
-
-	len = fmtstr(buf, len, "%" PRIdMAX, num);
-	return memtodest(buf, len, flags);
-}
-
-STATIC void
-varunset(const char *end, const char *var, const char *umsg, int varflags)
-{
-	const char *msg;
-	const char *tail;
-
-	tail = nullstr;
-	msg = "parameter not set";
-	if (umsg) {
-		if (*end == (char)CTLENDVAR) {
-			if (varflags & VSNUL)
-				tail = " or null";
-		} else
-			msg = umsg;
-	}
-	sh_error("%.*s: %s%s", end - var - 1, var, msg, tail);
-}
-
-#ifdef mkinit
-
-INCLUDE "expand.h"
-
-EXITRESET {
-	ifsfree();
-}
-
-#endif