/*
 * subst.c - various substitutions
 *
 * This file is part of zsh, the Z shell.
 *
 * Copyright (c) 1992-1996 Paul Falstad
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and to distribute modified versions of this software for any
 * purpose, provided that the above copyright notice and the following
 * two paragraphs appear in all copies of this software.
 *
 * In no event shall Paul Falstad or the Zsh Development Group be liable
 * to any party for direct, indirect, special, incidental, or consequential
 * damages arising out of the use of this software and its documentation,
 * even if Paul Falstad and the Zsh Development Group have been advised of
 * the possibility of such damage.
 *
 * Paul Falstad and the Zsh Development Group specifically disclaim any
 * warranties, including, but not limited to, the implied warranties of
 * merchantability and fitness for a particular purpose.  The software
 * provided hereunder is on an "as is" basis, and Paul Falstad and the
 * Zsh Development Group have no obligation to provide maintenance,
 * support, updates, enhancements, or modifications.
 *
 */

#include "zsh.h"

/* Do substitutions before fork. These are:
 *  - Process substitution: <(...), >(...), =(...)
 *  - Parameter substitution
 *  - Command substitution
 * Followed by
 *  - Quote removal
 *  - Brace expansion
 *  - Tilde and equals substitution
 *
 * Bits 0 and 1 of flags are used in filesub.
 * bit 0 is set when we are doing MAGIC_EQUALSUBST or normal
 *	 assignment but not a typeset.
 * bit 1 is set on a real assignment (both typeset and normal).
 * bit 2 is a flag to paramsubst (single word sub)
 */

/**/
void
prefork(LinkList list, int flags)
{
    LinkNode node;

    MUSTUSEHEAP("prefork");
    for (node = firstnode(list); node; incnode(node)) {
	char *str;

	str = (char *)getdata(node);
	if ((*str == Inang || *str == Outang || *str == Equals) &&
	    str[1] == Inpar) {
	    if (*str == Inang || *str == Outang)
		setdata(node, (void *) getproc(str));	/* <(...) or >(...) */
	    else
		setdata(node, (void *) getoutputfile(str));	/* =(...) */
	    if (!getdata(node))
		return;
	} else {
	    if (isset(SHFILEEXPANSION))
		filesub((char **)getaddrdata(node), flags & 3);
	    if (!(node = stringsubst(list, node, flags & 4)))
		return;
	}
    }
    for (node = firstnode(list); node; incnode(node)) {
	if (*(char *)getdata(node)) {
	    remnulargs(getdata(node));
	    if (unset(IGNOREBRACES) && !(flags & 4))
		while (hasbraces(getdata(node)))
		    xpandbraces(list, &node);
	    if (unset(SHFILEEXPANSION))
		filesub((char **)getaddrdata(node), flags & 3);
	} else if (!(flags & 4))
	    uremnode(list, node);
	if (errflag)
	    return;
    }
}

/**/
LinkNode
stringsubst(LinkList list, LinkNode node, int ssub)
{
    int qt;
    char *str3 = (char *)getdata(node);
    char *str  = str3;

    while (!errflag && *str) {
	if ((qt = *str == Qstring) || *str == String) {
	    if (str[1] == Inpar) {
		str++;
		goto comsub;
	    } else if (str[1] == Inbrack) {
		/* $[...] */
		char *str2 = str;
		str2++;
		if (skipparens(Inbrack, Outbrack, &str2)) {
		    zerr("closing bracket missing", NULL, 0);
		    return NULL;
		}
		str2[-1] = *str = '\0';
		str = arithsubst(str + 2, &str3, str2);
		setdata(node, (void *) str3);
		continue;
	    } else {
		node = paramsubst(list, node, &str, qt, ssub);
		if (errflag || !node)
		    return NULL;
		str3 = (char *)getdata(node);
		continue;
	    }
	} else if ((qt = *str == Qtick) || *str == Tick)
	  comsub: {
	    LinkList pl;
	    char *s, *str2 = str;
	    char endchar;
	    int l1, l2;

	    if (*str == Inpar) {
		endchar = Outpar;
		str[-1] = '\0';
		if (skipparens(Inpar, Outpar, &str))
		    DPUTS(1, "Oops. parse error in command substitution");
		str--;
	    } else {
		endchar = *str;
		*str = '\0';

		while (*++str && *str != endchar)
		    ;
		DPUTS(!*str, "Oops. parse error in command substitution");
	    }
	    *str++ = '\0';
	    if (endchar == Outpar && str2[1] == '(' && str[-2] == ')') {
		/* Math substitution of the form $((...)) */
		str = arithsubst(str2 + 1, &str3, str);
		setdata(node, (void *) str3);
		continue;
	    }

	    /* It is a command substitution, which will be parsed again   *
	     * by the lexer, so we untokenize it first, but we cannot use *
	     * untokenize() since in the case of `...` some Bnulls should *
	     * be left unchanged.  Note that the lexer doesn't tokenize   *
	     * the body of a command substitution so if there are some    *
	     * tokens here they are from a ${(e)~...} substitution.       */
	    for (str = str2; *++str; )
		if (itok(*str) && *str != Nularg &&
		    !(endchar != Outpar && *str == Bnull &&
		      (str[1] == '$' || str[1] == '\\' || str[1] == '`' ||
		       (qt && str[1] == '"'))))
		    *str = ztokens[*str - Pound];
	    str++;
	    if (!(pl = getoutput(str2 + 1, qt || ssub))) {
		zerr("parse error in command substitution", NULL, 0);
		return NULL;
	    }
	    if (endchar == Outpar)
		str2--;
	    if (!(s = (char *) ugetnode(pl))) {
		str = strcpy(str2, str);
		continue;
	    }
	    if (!qt && ssub && isset(GLOBSUBST))
		tokenize(s);
	    l1 = str2 - str3;
	    l2 = strlen(s);
	    if (nonempty(pl)) {
		LinkNode n = lastnode(pl);
		str2 = (char *) ncalloc(l1 + l2 + 1);
		strcpy(str2, str3);
		strcpy(str2 + l1, s);
		setdata(node, str2);
		insertlinklist(pl, node, list);
		s = (char *) getdata(node = n);
		l1 = 0;
		l2 = strlen(s);
	    }
	    str2 = (char *) ncalloc(l1 + l2 + strlen(str) + 1);
	    if (l1)
		strcpy(str2, str3);
	    strcpy(str2 + l1, s);
	    str = strcpy(str2 + l1 + l2, str);
	    str3 = str2;
	    setdata(node, str3);
	    continue;
	}
	str++;
    }
    return errflag ? NULL : node;
}

/**/
void
globlist(LinkList list)
{
    LinkNode node, next;

    badcshglob = 0;
    for (node = firstnode(list); !errflag && node; node = next) {
	next = nextnode(node);
	glob(list, node);
    }
    if (badcshglob == 1)
	zerr("no match", NULL, 0);
}

/* perform substitution on a single word */

/**/
void
singsub(char **s)
{
    LinkList foo;

    foo = newlinklist();
    addlinknode(foo, *s);
    prefork(foo, 4);
    if (errflag)
	return;
    *s = (char *) ugetnode(foo);
    DPUTS(nonempty(foo), "BUG: singsub() produced more than one word!");
}

/* Perform substitution on a single word. Unlike with singsub, the      *
 * result can have more than one words. A single word result is sroted  *
 * in *s and *isarr is set to zero; otherwise *isarr is set to 1 and    *
 * the result is stored in *a. If `a' is zero a multiple word result is *
 * joined using sep or the IFS parameter if sep is zero and the result  *
 * is returned in *s.  The return value is true iff the expansion       *
 * resulted in an empty list                                            */

/**/
int
multsub(char **s, char ***a, int *isarr, char *sep)
{
    LinkList foo;
    int l;
    char **r, **p;

    foo = newlinklist();
    addlinknode(foo, *s);
    prefork(foo, 0);
    if (errflag) {
	if (isarr)
	    *isarr = 0;
	return 0;
    }
    if ((l = countlinknodes(foo)) > 1) {
	p = r = ncalloc((l + 1) * sizeof(char*));
	while (nonempty(foo))
	    *p++ = (char *)ugetnode(foo);
	*p = NULL;
	if (a) {
	    *a = r;
	    *isarr = 1;
	    return 0;
	}
	*s = sepjoin(r, NULL);
	return 0;
    }
    if (l)
	*s = (char *) ugetnode(foo);
    else
	*s = dupstring("");
    if (isarr)
	*isarr = 0;
    return !l;
}

/* ~, = subs: assign = 2 => typeset; assign = 1 => something that looks
	like an assignment but may not be; assign = 3 => normal assignment */

/**/
void
filesub(char **namptr, int assign)
{
    char *sub = NULL, *str, *ptr;
    int len;

    filesubstr(namptr, assign);

    if (!assign)
	return;

    if (assign < 3) {
	if ((*namptr)[1] && (sub = strchr(*namptr + 1, Equals))) {
	    if (assign == 1)
		for (ptr = *namptr; ptr != sub; ptr++)
		    if (!iident(*ptr) && !INULL(*ptr))
			return;
	    str = sub + 1;
	    if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) {
		sub[1] = '\0';
		*namptr = dyncat(*namptr, str);
	    }
	} else
	    return;
    }

    ptr = *namptr;
    while ((sub = strchr(ptr, ':'))) {
	str = sub + 1;
	len = sub - *namptr;
	if ((sub[1] == Tilde || sub[1] == Equals) && filesubstr(&str, assign)) {
	    sub[1] = '\0';
	    *namptr = dyncat(*namptr, str);
	}
	ptr = *namptr + len + 1;
    }
}

/**/
int
filesubstr(char **namptr, int assign)
{
#define isend(c) ( !(c) || (c)=='/' || (c)==Inpar || (assign && (c)==':') )
#define isend2(c) ( !(c) || (c)==Inpar || (assign && (c)==':') )
    char *str = *namptr;

    if (*str == Tilde && str[1] != '=' && str[1] != Equals) {
	char *ptr;
	int val;

	val = zstrtol(str + 1, &ptr, 10);
	if (isend(str[1])) {   /* ~ */
	    *namptr = dyncat(home, str + 1);
	    return 1;
	} else if (str[1] == '+' && isend(str[2])) {   /* ~+ */
	    *namptr = dyncat(pwd, str + 2);
	    return 1;
	} else if (str[1] == '-' && isend(str[2])) {   /* ~- */
	    char *tmp;
	    *namptr = dyncat((tmp = oldpwd) ? tmp : pwd, str + 2);
	    return 1;
	} else if (!inblank(str[1]) && isend(*ptr) &&
		   (!idigit(str[1]) || (ptr - str < 4))) {
	    char *ds;

	    if (val < 0)
		val = -val;
	    ds = dstackent(str[1], val);
	    if (!ds)
		return 0;
	    *namptr = dyncat(ds, ptr);
	    return 1;
	} else if (iuser(str[1])) {   /* ~foo */
	    char *ptr, *hom, save;

	    for (ptr = ++str; *ptr && iuser(*ptr); ptr++);
	    save = *ptr;
	    if (!isend(save))
		return 0;
	    *ptr = 0;
	    if (!(hom = getnameddir(str))) {
		if (isset(NOMATCH))
		    zerr("no such user or named directory: %s", str, 0);
		*ptr = save;
		return 0;
	    }
	    *ptr = save;
	    *namptr = dyncat(hom, ptr);
	    return 1;
	}
    } else if (*str == Equals && isset(EQUALS) && str[1]) {   /* =foo */
	char sav, *pp, *cnam;

	for (pp = str + 1; !isend2(*pp); pp++);
	sav = *pp;
	*pp = 0;
	if (!(cnam = findcmd(str + 1))) {
	    Alias a = (Alias) aliastab->getnode(aliastab, str + 1);
	    
	    if (a)
		cnam = ztrdup(a->text);
	    else {
		if (isset(NOMATCH))
		    zerr("%s not found", str + 1, 0);
		return 0;
	    }
	}
	*namptr = dupstring(cnam);
	zsfree(cnam);
	if (sav) {
	    *pp = sav;
	    *namptr = dyncat(*namptr, pp);
	}
	return 1;
    }
    return 0;
#undef isend
#undef isend2
}

/**/
char *
strcatsub(char **d, char *pb, char *pe, char *src, int l, char *s, int glbsub)
{
    int pl = pe - pb;
    char *dest = ncalloc(pl + l + (s ? strlen(s) : 0) + 1);

    *d = dest;
    strncpy(dest, pb, pl);
    dest += pl;
    strcpy(dest, src);
    if (glbsub)
	tokenize(dest);
    dest += l;
    if (s)
	strcpy(dest, s);
    return dest;
}

typedef int (*CompareFn) _((const void *, const void *));

/**/
int
strpcmp(const void *a, const void *b)
{
#ifdef HAVE_STRCOLL
    return strcoll(*(char **)a, *(char **)b);
#else
    return strcmp(*(char **)a, *(char **)b);
#endif
}

/**/
int
invstrpcmp(const void *a, const void *b)
{
#ifdef HAVE_STRCOLL
    return -strcoll(*(char **)a, *(char **)b);
#else
    return -strcmp(*(char **)a, *(char **)b);
#endif
}

/**/
int
cstrpcmp(const void *a, const void *b)
{
#ifdef HAVE_STRCOLL
# ifdef __GNUC__
    char c[strlen(*(char **) a) + 1];
    char d[strlen(*(char **) b) + 1];
# else
    char *c = halloc(strlen(*(char **) a) + 1);
    char *d = halloc(strlen(*(char **) b) + 1);
# endif
    char *s, *t;
    int   cmp;

    for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
    for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););

    cmp = strcoll(c, d);

    return cmp;
#else
    char *c = *(char **)a, *d = *(char **)b;

    for (; *c && tulower(*c) == tulower(*d); c++, d++);

    return (int)STOUC(tulower(*c)) - (int)STOUC(tulower(*d));
#endif
}

/**/
int
invcstrpcmp(const void *a, const void *b)
{
#ifdef HAVE_STRCOLL
# ifdef __GNUC__
    char c[strlen(*(char **) a) + 1];
    char d[strlen(*(char **) b) + 1];
# else
    char *c = halloc(strlen(*(char **) a) + 1);
    char *d = halloc(strlen(*(char **) b) + 1);
# endif
    char *s, *t;
    int   cmp;

    for (s = *(char **) a, t = c; (*t++ = tulower(*s++)););
    for (s = *(char **) b, t = d; (*t++ = tulower(*s++)););

    cmp = strcoll(c, d);

    return -cmp;
#else
    char *c = *(char **)a, *d = *(char **)b;

    for (; *c && tulower(*c) == tulower(*d); c++, d++);

    return (int)STOUC(tulower(*d)) - (int)STOUC(tulower(*c));
#endif
}

/**/
char *
dopadding(char *str, int prenum, int postnum, char *preone, char *postone, char *premul, char *postmul)
{
    char def[3], *ret, *t, *r;
    int ls, ls2, lpreone, lpostone, lpremul, lpostmul, lr, f, m, c, cc;

    def[0] = *ifs ? *ifs : ' ';
    def[1] = *ifs == Meta ? ifs[1] ^ 32 : '\0';
    def[2] = '\0';
    if (preone && !*preone)
	preone = def;
    if (postone && !*postone)
	postone = def;
    if (!premul || !*premul)
	premul = def;
    if (!postmul || !*postmul)
	postmul = def;

    ls = strlen(str);
    lpreone = preone ? strlen(preone) : 0;
    lpostone = postone ? strlen(postone) : 0;
    lpremul = strlen(premul);
    lpostmul = strlen(postmul);

    lr = prenum + postnum;

    if (lr == ls)
	return str;

    r = ret = (char *)halloc(lr + 1);

    if (prenum) {
	if (postnum) {
	    ls2 = ls / 2;

	    f = prenum - ls2;
	    if (f <= 0)
		for (str -= f, c = prenum; c--; *r++ = *str++);
	    else {
		if (f <= lpreone)
		    for (c = f, t = preone + lpreone - f; c--; *r++ = *t++);
		else {
		    f -= lpreone;
		    if ((m = f % lpremul))
			for (c = m, t = premul + lpremul - m; c--; *r++ = *t++);
		    for (cc = f / lpremul; cc--;)
			for (c = lpremul, t = premul; c--; *r++ = *t++);
		    for (c = lpreone; c--; *r++ = *preone++);
		}
		for (c = ls2; c--; *r++ = *str++);
	    }
	    ls2 = ls - ls2;
	    f = postnum - ls2;
	    if (f <= 0)
		for (c = postnum; c--; *r++ = *str++);
	    else {
		for (c = ls2; c--; *r++ = *str++);
		if (f <= lpostone)
		    for (c = f; c--; *r++ = *postone++);
		else {
		    f -= lpostone;
		    for (c = lpostone; c--; *r++ = *postone++);
		    for (cc = f / lpostmul; cc--;)
			for (c = lpostmul, t = postmul; c--; *r++ = *t++);
		    if ((m = f % lpostmul))
			for (; m--; *r++ = *postmul++);
		}
	    }
	} else {
	    f = prenum - ls;
	    if (f <= 0)
		for (c = prenum, str -= f; c--; *r++ = *str++);
	    else {
		if (f <= lpreone)
		    for (c = f, t = preone + lpreone - f; c--; *r++ = *t++);
		else {
		    f -= lpreone;
		    if ((m = f % lpremul))
			for (c = m, t = premul + lpremul - m; c--; *r++ = *t++);
		    for (cc = f / lpremul; cc--;)
			for (c = lpremul, t = premul; c--; *r++ = *t++);
		    for (c = lpreone; c--; *r++ = *preone++);
		}
		for (c = ls; c--; *r++ = *str++);
	    }
	}
    } else if (postnum) {
	f = postnum - ls;
	if (f <= 0)
	    for (c = postnum; c--; *r++ = *str++);
	else {
	    for (c = ls; c--; *r++ = *str++);
	    if (f <= lpostone)
		for (c = f; c--; *r++ = *postone++);
	    else {
		f -= lpostone;
		for (c = lpostone; c--; *r++ = *postone++);
		for (cc = f / lpostmul; cc--;)
		    for (c = lpostmul, t = postmul; c--; *r++ = *t++);
		if ((m = f % lpostmul))
		    for (; m--; *r++ = *postmul++);
	    }
	}
    }
    *r = '\0';

    return ret;
}

/**/
char *
get_strarg(char *s)
{
    char t = *s++;

    if (!t)
	return s - 1;

    switch (t) {
    case '(':
	t = ')';
	break;
    case '[':
	t = ']';
	break;
    case '{':
	t = '}';
	break;
    case '<':
	t = '>';
	break;
    case Inpar:
	t = Outpar;
	break;
    case Inang:
	t = Outang;
	break;
    case Inbrace:
	t = Outbrace;
	break;
    case Inbrack:
	t = Outbrack;
	break;
    }

    while (*s && *s != t)
	s++;

    return s;
}

/**/
int
get_intarg(char **s)
{
    char *t = get_strarg(*s + 1);
    char *p, sav;
    zlong ret;

    if (!*t)
	return -1;
    sav = *t;
    *t = '\0';
    p = dupstring(*s + 2);
    *s = t;
    *t = sav;
    if (parsestr(p))
	return -1;
    singsub(&p);
    if (errflag)
	return -1;
    ret = matheval(p);
    if (errflag)
	return -1;
    if (ret < 0)
	ret = -ret;
    return ret < 0 ? -ret : ret;
}

/* Parsing for the (e) flag. */

static int
subst_parse_str(char *s, int single)
{
    if (!parsestr(s)) {
	if (!single) {
	    for (; *s; s++)
		if (*s == Qstring)
		    *s = String;
	}
	return 0;
    }
    return 1;
}

/* parameter substitution */

#define	isstring(c) ((c) == '$' || (char)(c) == String || (char)(c) == Qstring)
#define isbrack(c)  ((c) == '[' || (char)(c) == Inbrack)

/**/
LinkNode
paramsubst(LinkList l, LinkNode n, char **str, int qt, int ssub)
{
    char *aptr = *str;
    char *s = aptr, *fstr, *idbeg, *idend, *ostr = (char *) getdata(n);
    int colf;			/* != 0 means we found a colon after the name */
    int doub = 0;		/* != 0 means we have %%, not %, or ##, not # */
    int isarr = 0;
    int plan9 = isset(RCEXPANDPARAM);
    int globsubst = isset(GLOBSUBST);
    int getlen = 0;
    int whichlen = 0;
    int chkset = 0;
    int vunset = 0;
    int spbreak = isset(SHWORDSPLIT) && !ssub && !qt;
    char *val = NULL, **aval = NULL;
    unsigned int fwidth = 0;
    Value v;
    int flags = 0;
    int flnum = 0;
    int substr = 0;
    int sortit = 0, casind = 0;
    int casmod = 0;
    char *sep = NULL, *spsep = NULL;
    char *premul = NULL, *postmul = NULL, *preone = NULL, *postone = NULL;
    zlong prenum = 0, postnum = 0;
    int copied = 0;
    int arrasg = 0;
    int eval = 0;
    int nojoin = 0;
    char inbrace = 0;		/* != 0 means ${...}, otherwise $... */

    *s++ = '\0';
    if (!ialnum(*s) && *s != '#' && *s != Pound && *s != '-' &&
	*s != '!' && *s != '$' && *s != String && *s != Qstring &&
	*s != '?' && *s != Quest && *s != '_' &&
	*s != '*' && *s != Star && *s != '@' && *s != '{' &&
	*s != Inbrace && *s != '=' && *s != Equals && *s != Hat &&
	*s != '^' && *s != '~' && *s != Tilde && *s != '+') {
	s[-1] = '$';
	*str = s;
	return n;
    }
    DPUTS(*s == '{', "BUG: inbrace == '{' in paramsubst()");
    if (*s == Inbrace) {
	inbrace = 1;
	s++;
	if (*s == '(' || *s == Inpar) {
	    char *t, sav;
	    int tt = 0;
	    zlong num;
	    int escapes = 0;
	    int klen;
#define UNTOK(C)  (itok(C) ? ztokens[(C) - Pound] : (C))
#define UNTOK_AND_ESCAPE(X) {\
		untokenize(X = dupstring(s + 1));\
		if (escapes) {\
		    X = getkeystring(X, &klen, 3, NULL);\
		    X = metafy(X, klen, META_USEHEAP);\
		}\
	    }

	    for (s++; *s != ')' && *s != Outpar; s++, tt = 0) {
		switch (*s) {
		case ')':
		case Outpar:
		    break;
		case 'A':
		    arrasg = 1;
		    break;
		case '@':
		    nojoin = 1;
		    break;
		case 'M':
		    flags |= 8;
		    break;
		case 'R':
		    flags |= 16;
		    break;
		case 'B':
		    flags |= 32;
		    break;
		case 'E':
		    flags |= 64;
		    break;
		case 'N':
		    flags |= 128;
		    break;
		case 'S':
		    substr = 1;
		    break;
		case 'I':
		    flnum = get_intarg(&s);
		    if (flnum < 0)
			goto flagerr;
		    break;

		case 'L':
		    casmod = 2;
		    break;
		case 'U':
		    casmod = 1;
		    break;
		case 'C':
		    casmod = 3;
		    break;

		case 'o':
		    sortit = 1;
		    break;
		case 'O':
		    sortit = 2;
		    break;
		case 'i':
		    casind = 1;
		    break;
		case 'e':
		    eval = 1;
		    break;

		case 'c':
		    whichlen = 1;
		    break;
		case 'w':
		    whichlen = 2;
		    break;
		case 'W':
		    whichlen = 3;
		    break;

		case 'f':
		    spsep = "\n";
		    break;
		case 'F':
		    sep = "\n";
		    break;

		case 's':
		    tt = 1;
		/* fall through */
		case 'j':
		    t = get_strarg(++s);
		    if (*t) {
			sav = *t;
			*t = '\0';
			if (tt)
			    UNTOK_AND_ESCAPE(spsep)
			else
			    UNTOK_AND_ESCAPE(sep)
			*t = sav;
			s = t;
		    } else
			goto flagerr;
		    break;

		case 'l':
		    tt = 1;
		/* fall through */
		case 'r':
		    sav = s[1];
		    num = get_intarg(&s);
		    if (num < 0)
			goto flagerr;
		    if (tt)
			prenum = num;
		    else
			postnum = num;
		    if (UNTOK(s[1]) != UNTOK(sav))
			break;
		    t = get_strarg(++s);
		    if (!*t)
			goto flagerr;
		    sav = *t;
		    *t = '\0';
		    if (tt)
			UNTOK_AND_ESCAPE(premul)
		    else
			UNTOK_AND_ESCAPE(postmul)
		    *t = sav;
		    sav = *s;
		    s = t + 1;
		    if (UNTOK(*s) != UNTOK(sav)) {
			s--;
			break;
		    }
		    t = get_strarg(s);
		    if (!*t)
			goto flagerr;
		    sav = *t;
		    *t = '\0';
		    if (tt)
			UNTOK_AND_ESCAPE(preone)
		    else
			UNTOK_AND_ESCAPE(postone)
		    *t = sav;
		    s = t;
		    break;

		case 'p':
		    escapes = 1;
		    break;

		default:
		  flagerr:
		    zerr("error in flags", NULL, 0);
		    return NULL;
		}
	    }
	    s++;
	}
    }
    if (sortit)
	sortit += (casind << 1);

    if (!premul)
	premul = " ";
    if (!postmul)
	postmul = " ";

    for (;;) {
	if (*s == '^' || *s == Hat) {
	    if (*++s == '^' || *s == Hat) {
		plan9 = 0;
		s++;
	    } else
		plan9 = 1;
	} else if (*s == '=' || *s == Equals) {
	    if (*++s == '=' || *s == Equals) {
		spbreak = 0;
		s++;
	    } else
		spbreak = 1;
	} else if ((*s == '#' || *s == Pound) &&
		   (iident(s[1])
		    || s[1] == '*' || s[1] == Star || s[1] == '@'
		    || (isstring(s[1]) && (s[2] == Inbrace || s[2] == Inpar))))
	    getlen = 1 + whichlen, s++;
	else if (*s == '~' || *s == Tilde) {
	    if (*++s == '~' || *s == Tilde) {
		globsubst = 0;
		s++;
	    } else
		globsubst = 1;
	} else if (*s == '+') {
	    if (iident(s[1]))
		chkset = 1, s++;
	    else if (!inbrace) {
		*aptr = '$';
		*str = aptr + 1;
		return n;
	    } else {
		zerr("bad substitution", NULL, 0);
		return NULL;
	    }
	} else if (inbrace && INULL(*s))
	    s++;
	else
	    break;
    }
    globsubst = globsubst && !qt;

    idbeg = s;
    if (inbrace && s[-1] && isstring(*s) &&
	(s[1] == Inbrace || s[1] == Inpar)) {
	int sav;
	int quoted = *s == Qstring;

	val = s++;
	skipparens(*s, *s == Inpar ? Outpar : Outbrace, &s);
	sav = *s;
	*s = 0;
	if (multsub(&val, &aval, &isarr, NULL) && quoted) {
	    isarr = -1;
	    aval = alloc(sizeof(char *));
	}
	if (isarr)
	    isarr = -1;
	copied = 1;
	*s = sav;
	while (INULL(*s))
	    s++;
	v = (Value) NULL;
    } else if (!(v = getvalue(&s, (unset(KSHARRAYS) || inbrace) ? 1 : -1)))
	vunset = 1;
    while (v || ((inbrace || (unset(KSHARRAYS) && vunset)) && isbrack(*s))) {
	if (!v) {
	    Param pm;
	    char *os = s;

	    if (!isbrack(*s))
		break;
	    if (vunset) {
		val = dupstring("");
		isarr = 0;
	    }
	    pm = createparam(nulstring, isarr ? PM_ARRAY : PM_SCALAR);
	    if (isarr)
		pm->u.arr = aval;
	    else
		pm->u.str = val;
	    v = (Value) hcalloc(sizeof *v);
	    v->isarr = isarr;
	    v->pm = pm;
	    v->b = -1;
	    if (getindex(&s, v) || s == os)
		break;
	}
	if ((isarr = v->isarr))
	    aval = getarrvalue(v);
	else {
	    if (v->pm->flags & PM_ARRAY) {
		int tmplen = arrlen(v->pm->gets.afn(v->pm));

		if (v->a < 0)
		    v->a += tmplen + v->inv;
		if (!v->inv && (v->a >= tmplen || v->a < 0))
		    vunset = 1;
	    }
	    if (!vunset) {
		val = getstrvalue(v);
		fwidth = v->pm->ct ? v->pm->ct : strlen(val);
		switch (v->pm->flags & (PM_LEFT | PM_RIGHT_B | PM_RIGHT_Z)) {
		    char *t;
		    unsigned int t0;

		case PM_LEFT:
		case PM_LEFT | PM_RIGHT_Z:
		    t = val;
		    if (v->pm->flags & PM_RIGHT_Z)
			while (*t == '0')
			    t++;
		    else
			while (iblank(*t))
			    t++;
		    val = (char *)ncalloc(fwidth + 1);
		    val[fwidth] = '\0';
		    if ((t0 = strlen(t)) > fwidth)
			t0 = fwidth;
		    memset(val, ' ', fwidth);
		    strncpy(val, t, t0);
		    break;
		case PM_RIGHT_B:
		case PM_RIGHT_Z:
		case PM_RIGHT_Z | PM_RIGHT_B:
		    {
			int zero = 1;

			if (strlen(val) < fwidth) {
			    if (v->pm->flags & PM_RIGHT_Z) {
				for (t = val; iblank(*t); t++);
				if (!*t || !idigit(*t))
				    zero = 0;
			    }
			    t = (char *)ncalloc(fwidth + 1);
			    memset(t, (((v->pm->flags & PM_RIGHT_B) || !zero) ?
				       ' ' : '0'), fwidth);
			    if ((t0 = strlen(val)) > fwidth)
				t0 = fwidth;
			    strcpy(t + (fwidth - t0), val);
			    val = t;
			} else {
			    t = (char *)ncalloc(fwidth + 1);
			    t[fwidth] = '\0';
			    strncpy(t, val + strlen(val) - fwidth, fwidth);
			    val = t;
			}
		    }
		    break;
		}
		switch (v->pm->flags & (PM_LOWER | PM_UPPER)) {
		    char *t;

		case PM_LOWER:
		    t = val;
		    for (; *t; t++)
			*t = tulower(*t);
		    break;
		case PM_UPPER:
		    t = val;
		    for (; *t; t++)
			*t = tuupper(*t);
		    break;
		}
	    }
	}
	v = NULL;
	if (!inbrace)
	    break;
    }
    if (isarr) {
	if (nojoin)
	    isarr = -1;
	if (qt && !getlen && isarr > 0) {
	    val = sepjoin(aval, sep);
	    isarr = 0;
	}
    }

    idend = s;
    if (inbrace)
	while (INULL(*s))
	    s++;
    if ((colf = *s == ':'))
	s++;


    /* fstr is to be the text following the substitution.  If we have *
     * braces, we look for it here, else we infer it later on.        */
    fstr = s;
    if (inbrace) {
	int bct;
	for (bct = 1;; fstr++) {
	    if (!*fstr)
		break;
	    else if (*fstr == Inbrace)
		bct++;
	    else if (*fstr == Outbrace && !--bct)
		break;
	}

	if (bct) {
	noclosebrace:
	    zerr("closing brace expected", NULL, 0);
	    return NULL;
	}
	if (*fstr)
	    *fstr++ = '\0';
    }

    /* Check for ${..?..} or ${..=..} or one of those. *
     * Only works if the name is in braces.            */

    if (inbrace && (*s == '-' ||
		    *s == '+' ||
		    *s == ':' ||
		    *s == '=' || *s == Equals ||
		    *s == '%' ||
		    *s == '#' || *s == Pound ||
		    *s == '?' || *s == Quest)) {

	if (!flnum)
	    flnum++;
	if (*s == '%')
	    flags |= 1;

	/* Check for ${..%%..} or ${..##..} */
	if ((*s == '%' || *s == '#' || *s == Pound) && *s == s[1]) {
	    s++;
	    doub = 1;
	}
	s++;

	flags |= (doub << 1) | (substr << 2) | (colf << 8);
	if (!(flags & 0xf8))
	    flags |= 16;

	if (colf && !vunset)
	    vunset = (isarr) ? !*aval : !*val || (*val == Nularg && !val[1]);

	switch (s[-1]) {
	case '+':
	    if (vunset) {
		val = dupstring("");
		copied = 1;
		isarr = 0;
		break;
	    }
	    vunset = 1;
	/* Fall Through! */
	case '-':
	    if (vunset) {
		val = dupstring(s);
		multsub(&val, &aval, &isarr, NULL);
		copied = 1;
	    }
	    break;
	case ':':
	    if (*s != '=' && *s != Equals)
		goto noclosebrace;
	    vunset = 1;
	    s++;
	    /* Fall through */
	case '=':
	case Equals:
	    if (vunset) {
		char sav = *idend;
		int l;

		*idend = '\0';
		val = dupstring(s);
		isarr = 0;
		if (spsep || spbreak || !arrasg)
		    multsub(&val, NULL, NULL, sep);
		else
		    multsub(&val, &aval, &isarr, NULL);
		if (arrasg) {
		    char *arr[2], **t, **a, **p;
		    if (spsep || spbreak) {
			aval = sepsplit(val, spsep, 0);
			isarr = 2;
			sep = spsep = NULL;
			spbreak = 0;
			l = arrlen(aval);
			if (l && !*(aval[l-1]))
			    l--;
			if (l && !**aval)
			    l--, t = aval + 1;
			else
			    t = aval;
		    } else if (!isarr) {
			arr[0] = val;
			arr[1] = NULL;
			t = aval = arr;
			l = 1;
		    } else
			l = arrlen(aval), t = aval;
		    p = a = zalloc(sizeof(char *) * (l + 1));
		    while (l--) {
			untokenize(*t);
			*p++ = ztrdup(*t++);
		    }
		    *p++ = NULL;
		    setaparam(idbeg, a);
		} else {
		    untokenize(val);
		    setsparam(idbeg, ztrdup(val));
		}
		*idend = sav;
		copied = 1;
	    }
	    break;
	case '?':
	case Quest:
	    if (vunset) {
		char *msg;

		*idend = '\0';
		msg = tricat(idbeg, ": ", *s ? s : "parameter not set");
		zerr("%s", msg, 0);
		zsfree(msg);
		if (!interact)
		    exit(1);
		return NULL;
	    }
	    break;
	case '%':
	case '#':
	case Pound:
	    if (qt)
		if (parse_subst_string(s)) {
		    zerr("parse error in ${...%c...} substitution",
			 NULL, s[-1]);
		    return NULL;
		}
	    singsub(&s);

	    if (!vunset && isarr) {
		char **ap = aval;
		char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1));

		while ((*pp = *ap++)) {
		    if (getmatch(pp, s, flags, flnum))
			pp++;
		}
		copied = 1;
	    } else {
		if (vunset)
		    val = dupstring("");
		getmatch(&val, s, flags, flnum);
		copied = 1;
	    }
	    break;
	}
    } else {			/* no ${...=...} or anything, but possible modifiers. */
	if (chkset) {
	    val = dupstring(vunset ? "0" : "1");
	    isarr = 0;
	} else if (vunset) {
	    if (unset(UNSET)) {
		*idend = '\0';
		zerr("%s: parameter not set", idbeg, 0);
		return NULL;
	    }
	    val = dupstring("");
	}
	if (colf) {
	    s--;
	    if (unset(KSHARRAYS) || inbrace) {
		if (!isarr)
		    modify(&val, &s);
		else {
		    char *ss;
		    char **ap = aval;
		    char **pp = aval = (char **)ncalloc(sizeof(char *) * (arrlen(aval) + 1));

		    while ((*pp = *ap++)) {
			ss = s;
			modify(pp++, &ss);
		    }
		    if (pp == aval) {
			char *t = "";
			ss = s;
			modify(&t, &ss);
		    }
		    s = ss;
		}
		if (inbrace && *s) {
		    if (*s == ':' && !imeta(s[1]))
			zerr("unrecognized modifier `%c'", NULL, s[1]);
		    else
			zerr("unrecognized modifier", NULL, 0);
		    return NULL;
		}
	    }
	}
	if (!inbrace)
	    fstr = s;
    }
    if (errflag)
	return NULL;
    if (getlen) {
	long len = 0;
	char buf[14];

	if (isarr) {
	    char **ctr;
	    int sl = sep ? ztrlen(sep) : 1;

	    if (getlen == 1)
		for (ctr = aval; *ctr; ctr++, len++);
	    else if (getlen == 2) {
		if (*aval)
		    for (len = -sl, ctr = aval;
			 len += sl + ztrlen(*ctr), *++ctr;);
	    }
	    else
		for (ctr = aval;
		     *ctr;
		     len += wordcount(*ctr, spsep, getlen > 3), ctr++);
	} else {
	    if (getlen < 3)
		len = ztrlen(val);
	    else
		len = wordcount(val, spsep, getlen > 3);
	}

	sprintf(buf, "%ld", len);
	val = dupstring(buf);
	isarr = 0;
    }
    if (isarr > 0 && !plan9 && (!aval || !aval[0])) {
	val = dupstring("");
	isarr = 0;
    } else if (isarr && aval && aval[0] && !aval[1]) {
	val = aval[0];
	isarr = 0;
    }
    /* ssub is true when we are called from singsub (via prefork).
     * It means that we must join arrays and should not split words. */
    if (ssub || spbreak || spsep || sep) {
	if (isarr)
	    val = sepjoin(aval, sep), isarr = 0;
	if (!ssub && (spbreak || spsep)) {
	    aval = sepsplit(val, spsep, 0);
	    if (!aval || !aval[0])
		val = dupstring("");
	    else if (!aval[1])
		val = aval[0];
	    else
		isarr = 2;
	}
    }
    if (casmod) {
	if (isarr) {
	    char **ap;

	    if (!copied)
		aval = arrdup(aval), copied = 1;
	    ap = aval;

	    if (casmod == 1)
		for (; *ap; ap++)
		    makeuppercase(ap);
	    else if (casmod == 2)
		for (; *ap; ap++)
		    makelowercase(ap);
	    else
		for (; *ap; ap++)
		    makecapitals(ap);

	} else {
	    if (!copied)
		val = dupstring(val), copied = 1;
	    if (casmod == 1)
		makeuppercase(&val);
	    else if (casmod == 2)
		makelowercase(&val);
	    else
		makecapitals(&val);
	}
    }
    if (isarr) {
	char *x;
	char *y;
	int xlen;
	int i;
	LinkNode on = n;

	if (!aval[0] && !plan9) {
	    if (aptr > (char *) getdata(n) &&
		aptr[-1] == Dnull && *fstr == Dnull)
		*--aptr = '\0', fstr++;
	    y = (char *)ncalloc((aptr - ostr) + strlen(fstr) + 1);
	    strcpy(y, ostr);
	    *str = y + (aptr - ostr);
	    strcpy(*str, fstr);
	    setdata(n, y);
	    return n;
	}
	if (sortit) {
	    static CompareFn sortfn[] = {
		strpcmp, invstrpcmp, cstrpcmp, invcstrpcmp
	    };

	    if (!copied)
		aval = arrdup(aval);

	    i = arrlen(aval);
	    if (i && (*aval[i-1] || --i))
		qsort(aval, i, sizeof(char *), sortfn[sortit-1]);
	}
	if (plan9) {
	    LinkList tl = newlinklist();
	    LinkNode tn;

	    *--fstr = Marker;
	    addlinknode(tl, fstr);
	    if (!eval && !stringsubst(tl, firstnode(tl), ssub))
		return NULL;
	    *str = aptr;
	    tn = firstnode(tl);
	    while ((x = *aval++)) {
		if (prenum || postnum)
		    x = dopadding(x, prenum, postnum, preone, postone,
				  premul, postmul);
		if (eval && subst_parse_str(x, (qt && !nojoin)))
		    return NULL;
		xlen = strlen(x);
		for (tn = firstnode(tl);
		     tn && *(y = (char *) getdata(tn)) == Marker;
		     incnode(tn)) {
		    strcatsub(&y, ostr, aptr, x, xlen, y + 1, globsubst);
		    if (qt && !*y && isarr != 2)
			y = dupstring(nulstring);
		    if (plan9)
			setdata(n, (void *) y), plan9 = 0;
		    else
			insertlinknode(l, n, (void *) y), incnode(n);
		}
	    }
	    for (; tn; incnode(tn)) {
		y = (char *) getdata(tn);
		if (*y == Marker)
		    continue;
		if (qt && !*y && isarr != 2)
		    y = dupstring(nulstring);
		if (plan9)
		    setdata(n, (void *) y), plan9 = 0;
		else
		    insertlinknode(l, n, (void *) y), incnode(n);
	    }
	    if (plan9) {
		uremnode(l, n);
		return n;
	    }
	} else {
	    x = aval[0];
	    if (prenum || postnum)
		x = dopadding(x, prenum, postnum, preone, postone,
			      premul, postmul);
	    if (eval && subst_parse_str(x, (qt && !nojoin)))
		return NULL;
	    xlen = strlen(x);
	    strcatsub(&y, ostr, aptr, x, xlen, NULL, globsubst);
	    if (qt && !*y && isarr != 2)
		y = dupstring(nulstring);
	    setdata(n, (void *) y);

	    i = 1;
	    /* aval[1] is non-null here */
	    while (aval[i + 1]) {
		x = aval[i++];
		if (prenum || postnum)
		    x = dopadding(x, prenum, postnum, preone, postone,
				  premul, postmul);
		if (eval && subst_parse_str(x, (qt && !nojoin)))
		    return NULL;
		if (qt && !*x && isarr != 2)
		    y = dupstring(nulstring);
		else {
		    y = dupstring(x);
		    if (globsubst)
			tokenize(y);
		}
		insertlinknode(l, n, (void *) y), incnode(n);
	    }

	    x = aval[i];
	    if (prenum || postnum)
		x = dopadding(x, prenum, postnum, preone, postone,
			      premul, postmul);
	    if (eval && subst_parse_str(x, (qt && !nojoin)))
		return NULL;
	    xlen = strlen(x);
	    *str = strcatsub(&y, aptr, aptr, x, xlen, fstr, globsubst);
	    if (qt && !*y && isarr != 2)
		y = dupstring(nulstring);
	    insertlinknode(l, n, (void *) y), incnode(n);
	}
	if (eval)
	    n = on;
    } else {
	int xlen;
	char *x;
	char *y;

	x = val;
	if (prenum || postnum)
	    x = dopadding(x, prenum, postnum, preone, postone,
			  premul, postmul);
	if (eval && subst_parse_str(x, (qt && !nojoin)))
	    return NULL;
	xlen = strlen(x);
	*str = strcatsub(&y, ostr, aptr, x, xlen, fstr, globsubst);
	if (qt && !*y && isarr != 2)
	    y = dupstring(nulstring);
	setdata(n, (void *) y);
    }
    if (eval)
	*str = (char *) getdata(n);

    return n;
}

/*
 * Arithmetic substitution: `a' is the string to be evaluated, `bptr'
 * points to the beginning of the string containing it.  The tail of
 * the string is given by `rest'. *bptr is modified with the substituted
 * string. The function returns a pointer to the tail in the substituted
 * string.
 */

/**/
char *
arithsubst(char *a, char **bptr, char *rest)
{
    char *s = *bptr, *t, buf[DIGBUFSIZE];
    char *b = buf;
    zlong v;

    singsub(&a);
    v = matheval(a);
    convbase(buf, v, 0);
    t = *bptr = (char *)ncalloc(strlen(*bptr) + strlen(buf) + strlen(rest) + 1);
    t--;
    while ((*++t = *s++));
    t--;
    while ((*++t = *b++));
    strcat(t, rest);
    return t;
}

/**/
void
modify(char **str, char **ptr)
{
    char *ptr1, *ptr2, *ptr3, del, *lptr, c, *test, *sep, *t, *tt, tc, *e;
    char *copy, *all, *tmp, sav;
    int gbal, wall, rec, al, nl;

    test = NULL;

    if (**ptr == ':')
	*str = dupstring(*str);

    while (**ptr == ':') {
	lptr = *ptr;
	(*ptr)++;
	wall = gbal = 0;
	rec = 1;
	c = '\0';
	sep = NULL;

	for (; !c && **ptr;) {
	    switch (**ptr) {
	    case 'h':
	    case 'r':
	    case 'e':
	    case 't':
	    case 'l':
	    case 'u':
		c = **ptr;
		break;

	    case 's':
		c = **ptr;
		(*ptr)++;
		ptr1 = *ptr;
		del = *ptr1++;
		for (ptr2 = ptr1; *ptr2 != del && *ptr2; ptr2++);
		if (!*ptr2) {
		    zerr("bad substitution", NULL, 0);
		    return;
		}
		*ptr2++ = '\0';
		for (ptr3 = ptr2; *ptr3 != del && *ptr3; ptr3++);
		if ((sav = *ptr3))
		    *ptr3++ = '\0';
		if (*ptr1) {
		    zsfree(hsubl);
		    hsubl = ztrdup(ptr1);
		}
		if (!hsubl) {
		    zerr("no previous substitution", NULL, 0);
		    return;
		}
		zsfree(hsubr);
		for (tt = hsubl; *tt; tt++)
		    if (INULL(*tt))
			chuck(tt--);
		untokenize(hsubl);
		for (tt = hsubr = ztrdup(ptr2); *tt; tt++)
		    if (INULL(*tt))
			chuck(tt--);
		ptr2[-1] = del;
		if (sav)
		    ptr3[-1] = sav;
		*ptr = ptr3 - 1;
		break;

	    case '&':
		c = 's';
		break;

	    case 'g':
		(*ptr)++;
		gbal = 1;
		break;

	    case 'w':
		wall = 1;
		(*ptr)++;
		break;
	    case 'W':
		wall = 1;
		(*ptr)++;
		ptr1 = get_strarg(ptr2 = *ptr);
		if ((sav = *ptr1))
		    *ptr1 = '\0';
		sep = dupstring(ptr2 + 1);
		if (sav)
		    *ptr1 = sav;
		*ptr = ptr1 + 1;
		c = '\0';
		break;

	    case 'f':
		rec = -1;
		(*ptr)++;
		break;
	    case 'F':
		rec = get_intarg(ptr);
		(*ptr)++;
		break;
	    default:
		*ptr = lptr;
		return;
	    }
	}
	(*ptr)++;
	if (!c) {
	    *ptr = lptr;
	    return;
	}
	if (rec < 0)
	    test = dupstring(*str);

	while (rec--) {
	    if (wall) {
		al = 0;
		all = NULL;
		for (t = e = *str; (tt = findword(&e, sep));) {
		    tc = *e;
		    *e = '\0';
		    copy = dupstring(tt);
		    *e = tc;
		    switch (c) {
		    case 'h':
			remtpath(&copy);
			break;
		    case 'r':
			remtext(&copy);
			break;
		    case 'e':
			rembutext(&copy);
			break;
		    case 't':
			remlpaths(&copy);
			break;
		    case 'l':
			downcase(&copy);
			break;
		    case 'u':
			upcase(&copy);
			break;
		    case 's':
			if (hsubl && hsubr)
			    subst(&copy, hsubl, hsubr, gbal);
			break;
		    }
		    tc = *tt;
		    *tt = '\0';
		    nl = al + strlen(t) + strlen(copy);
		    ptr1 = tmp = (char *)halloc(nl + 1);
		    if (all)
			for (ptr2 = all; *ptr2;)
			    *ptr1++ = *ptr2++;
		    for (ptr2 = t; *ptr2;)
			*ptr1++ = *ptr2++;
		    *tt = tc;
		    for (ptr2 = copy; *ptr2;)
			*ptr1++ = *ptr2++;
		    *ptr1 = '\0';
		    al = nl;
		    all = tmp;
		    t = e;
		}
		*str = all;

	    } else {
		switch (c) {
		case 'h':
		    remtpath(str);
		    break;
		case 'r':
		    remtext(str);
		    break;
		case 'e':
		    rembutext(str);
		    break;
		case 't':
		    remlpaths(str);
		    break;
		case 'l':
		    downcase(str);
		    break;
		case 'u':
		    upcase(str);
		    break;
		case 's':
		    if (hsubl && hsubr) {
			char *oldstr = *str;

			subst(str, hsubl, hsubr, gbal);
			if (*str != oldstr) {
			    *str = dupstring(oldstr = *str);
			    zsfree(oldstr);
			}
		    }
		    break;
		}
	    }
	    if (rec < 0) {
		if (!strcmp(test, *str))
		    rec = 0;
		else
		    test = dupstring(*str);
	    }
	}
    }
}

/* get a directory stack entry */

/**/
char *
dstackent(char ch, int val)
{
    int backwards;
    LinkNode end=(LinkNode)dirstack, n;

    backwards = ch == (isset(PUSHDMINUS) ? '+' : '-');
    if(!backwards && !val--)
	return pwd;
    if (backwards)
	for (n=lastnode(dirstack); n != end && val; val--, n=prevnode(n));
    else
	for (end=NULL, n=firstnode(dirstack); n && val; val--, n=nextnode(n));
    if (n == end) {
	if (backwards && !val)
	    return pwd;
	if (isset(NOMATCH))
	    zerr("not enough directory stack entries.", NULL, 0);
	return NULL;
    }
    return (char *)getdata(n);
}
