ftp.nice.ch/pub/next/unix/shell/zsh.3.0.5.NIHS.bs.tar.gz#/zsh.3.0.5.NIHS.bs/src/Src/glob.c

This is glob.c in view mode; [Download] [Up]

/*
 * $Id: glob.c,v 2.35 1996/10/15 20:16:35 hzoli Exp $
 *
 * glob.c - filename generation
 *
 * 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"

static int
exists(char *s)
{
    char *us = unmeta(s);

    return access(us,0) == 0 || readlink(us,NULL,0) == 0;
}

static int mode;		/* != 0 if we are parsing glob patterns */
static int pathpos;		/* position in pathbuf                  */
static int matchsz;		/* size of matchbuf                     */
static int matchct;		/* number of matches found              */
static char pathbuf[PATH_MAX];	/* pathname buffer                      */
static char **matchbuf;		/* array of matches                     */
static char **matchptr;		/* &matchbuf[matchct]                   */
static char *colonmod;		/* colon modifiers in qualifier list    */
static ino_t old_ino;		/* ) remember old file and              */
static dev_t old_dev;		/* ) position in path in case           */
static int old_pos;		/* ) matching multiple directories      */

typedef struct stat *Statptr;	 /* This makes the Ultrix compiler happy.  Go figure. */

/* modifier for unit conversions */

#define TT_DAYS 0
#define TT_HOURS 1
#define TT_MINS 2
#define TT_WEEKS 3
#define TT_MONTHS 4

#define TT_BYTES 0
#define TT_POSIX_BLOCKS 1
#define TT_KILOBYTES 2
#define TT_MEGABYTES 3

/* max # of qualifiers */

typedef int (*TestMatchFunc) _((struct stat *, long));

struct qual {
    struct qual *next;		/* Next qualifier, must match                */
    struct qual *or;		/* Alternative set of qualifiers to match    */
    TestMatchFunc func;		/* Function to call to test match            */
    long data;			/* Argument passed to function               */
    int sense;			/* Whether asserting or negating             */
    int amc;			/* Flag for which time to test (a, m, c)     */
    int range;			/* Whether to test <, > or = (as per signum) */
    int units;			/* Multiplier for time or size, respectively */
};

/* Qualifiers pertaining to current pattern */
static struct qual *quals;

/* Other state values for current pattern */
static int qualct, qualorct;
static int range, amc, units;
static int gf_nullglob, gf_markdirs, gf_noglobdots, gf_listtypes, gf_follow;

/* Prefix, suffix for doing zle trickery */
char *glob_pre, *glob_suf;

/* pathname component in filename patterns */

struct complist {
    Complist next;
    Comp comp;
    int closure;		/* 1 if this is a (foo/)# */
    int follow; 		/* 1 to go thru symlinks */
};
struct comp {
    Comp left, right, next, exclude;
    char *str;
    int stat;
};

/* Type of Comp:  a closure with one or two #'s, the end of a *
 * pattern or path component, a piece of path to be added.    */
#define C_ONEHASH	1
#define C_TWOHASH	2
#define C_CLOSURE	(C_ONEHASH|C_TWOHASH)
#define C_LAST		4
#define C_PATHADD	8

/* Test macros for the above */
#define CLOSUREP(c)	(c->stat & C_CLOSURE)
#define ONEHASHP(c)	(c->stat & C_ONEHASH)
#define TWOHASHP(c)	(c->stat & C_TWOHASH)
#define LASTP(c)	(c->stat & C_LAST)
#define PATHADDP(c)	(c->stat & C_PATHADD)

/* Main entry point to the globbing code for filename globbing. *
 * np points to a node in the list list which will be expanded  *
 * into a series of nodes.                                      */

/**/
void
glob(LinkList list, LinkNode np)
{
    struct qual *qo, *qn, *ql;
    LinkNode node = prevnode(np);
    char *str;				/* the pattern                   */
    int sl;				/* length of the pattern         */
    Complist q;				/* pattern after parsing         */
    char *ostr = (char *)getdata(np);	/* the pattern before the parser */
					/* chops it up                   */

    MUSTUSEHEAP("glob");
    if (unset(GLOBOPT) || !(str = haswilds(ostr))) {
	untokenize(ostr);
	return;
    }
    if (*str == ':' && str[-1] == Inpar) {
	str[-1] = '\0';
	modify((char **) &getdata(np), &str);
	return;
    }
    str = dupstring(ostr);
    sl = strlen(str);
    uremnode(list, np);

    /* Initialise state variables for current file pattern */
    qo = qn = quals = ql = NULL;
    qualct = qualorct = 0;
    colonmod = NULL;
    gf_nullglob = isset(NULLGLOB);
    gf_markdirs = isset(MARKDIRS);
    gf_listtypes = gf_follow = 0;
    gf_noglobdots = unset(GLOBDOTS);
    if (str[sl - 1] == Outpar) {	/* check for qualifiers */
	char *s;
	int sense = 0;			/* bit 0 for match (0)/don't match (1)   */
					/* bit 1 for follow links (2), don't (0) */
	long data = 0;			/* Any numerical argument required       */

	int (*func) _((Statptr, long));

	/* Check these are really qualifiers, not a set of *
	 * alternatives or exclusions                      */
	for (s = str + sl - 2; s != str; s--)
	    if (*s == Bar || *s == Outpar || *s == Inpar
		|| (isset(EXTENDEDGLOB) && *s == Tilde))
		break;
	if (*s == Inpar) {
	    /* Real qualifiers found. */
	    *s++ = '\0';
	    while (*s != Outpar && !colonmod) {
		func = (int (*) _((Statptr, long)))0;
		if (idigit(*s)) {
		    /* Store numeric argument for qualifier */
		    func = qualflags;
		    data = 0;
		    while (idigit(*s))
			data = data * 010 + (*s++ - '0');
		} else if (*s == ',') {
		    /* A comma separates alternative sets of qualifiers */
		    s++;
		    sense = 0;
		    if (qualct) {
			qn = (struct qual *)hcalloc(sizeof *qn);
			qo->or = qn;
			qo = qn;
			qualorct++;
			qualct = 0;
			ql = NULL;
		    }
		} else
		    switch (*s++) {
		    case ':':
			/* Remaining arguments are history-type     *
			 * colon substitutions, handled separately. */
			colonmod = s - 1;
			break;
		    case Hat:
		    case '^':
			/* Toggle sense:  go from positive to *
			 * negative match and vice versa.     */
			sense ^= 1;
			break;
		    case '-':
			/* Toggle matching of symbolic links */
			sense ^= 2;
			break;
#ifdef S_IFLNK
		    case '@':
			/* Match symbolic links */
			func = qualmode;
			data = S_IFLNK;
			break;
#endif
#ifdef S_IFSOCK
		    case Equals:
		    case '=':
			/* Match sockets */
			func = qualmode;
			data = S_IFSOCK;
			break;
#endif
#ifdef S_IFIFO
		    case 'p':
			/* Match named pipes */
			func = qualmode;
			data = S_IFIFO;
			break;
#endif
		    case '/':
			/* Match directories */
			func = qualmode;
			data = S_IFDIR;
			break;
		    case '.':
			/* Match regular files */
			func = qualmode;
			data = S_IFREG;
			break;
		    case '%':
			/* Match special files: block, *
			 * character or any device     */
			if (*s == 'b')
			    s++, func = qualisblk;
			else if (*s == 'c')
			    s++, func = qualischar;
			else
			    func = qualisdev;
			break;
		    case Star:
			/* Match executable plain files */
			func = qualiscom;
			break;
		    case 'R':
			/* Match world-readable files */
			func = qualflags;
			data = 0004;
			break;
		    case 'W':
			/* Match world-writeable files */
			func = qualflags;
			data = 0002;
			break;
		    case 'X':
			/* Match world-executable files */
			func = qualflags;
			data = 0001;
			break;
		    case 'A':
			func = qualflags;
			data = 0040;
			break;
		    case 'I':
			func = qualflags;
			data = 0020;
			break;
		    case 'E':
			func = qualflags;
			data = 0010;
			break;
		    case 'r':
			/* Match files readable by current process */
			func = qualflags;
			data = 0400;
			break;
		    case 'w':
			/* Match files writeable by current process */
			func = qualflags;
			data = 0200;
			break;
		    case 'x':
			/* Match files executable by current process */
			func = qualflags;
			data = 0100;
			break;
		    case 's':
			/* Match setuid files */
			func = qualflags;
			data = 04000;
			break;
		    case 'S':
			/* Match setgid files */
			func = qualflags;
			data = 02000;
			break;
		    case 't':
			func = qualflags;
			data = 01000;
			break;
		    case 'd':
			/* Match device files by device number  *
			 * (as given by stat's st_dev element). */
			func = qualdev;
			data = qgetnum(&s);
			break;
		    case 'l':
			/* Match files with the given no. of hard links */
			func = qualnlink;
			amc = -1;
			goto getrange;
		    case 'U':
			/* Match files owned by effective user ID */
			func = qualuid;
			data = geteuid();
			break;
		    case 'G':
			/* Match files owned by effective group ID */
			func = qualgid;
			data = getegid();
			break;
		    case 'u':
			/* Match files owned by given user id */
			func = qualuid;
			/* either the actual uid... */
			if (idigit(*s))
			    data = qgetnum(&s);
			else {
			    /* ... or a user name */
			    struct passwd *pw;
			    char sav, *tt;

			    /* Find matching delimiters */
			    tt = get_strarg(s);
			    if (!*tt) {
				zerr("missing end of name",
				     NULL, 0);
				data = 0;
			    } else {
				sav = *tt;
				*tt = '\0';

				if ((pw = getpwnam(s + 1)))
				    data = pw->pw_uid;
				else {
				    zerr("unknown user", NULL, 0);
				    data = 0;
				}
				if ((*tt = sav) != Outpar)
				    s = tt + 1;
				else
				    s = tt;
			    }
			}
			break;
		    case 'g':
			/* Given gid or group id... works like `u' */
			func = qualgid;
			/* either the actual gid... */
			if (idigit(*s))
			    data = qgetnum(&s);
			else {
			    /* ...or a delimited group name. */
			    struct group *gr;
			    char sav, *tt;

			    tt = get_strarg(s);
			    if (!*tt) {
				zerr("missing end of name",
				     NULL, 0);
				data = 0;
			    } else {
				sav = *tt;
				*tt = '\0';

				if ((gr = getgrnam(s + 1)))
				    data = gr->gr_gid;
				else {
				    zerr("unknown group", NULL, 0);
				    data = 0;
				}
				if ((*tt = sav) != Outpar)
				    s = tt + 1;
				else
				    s = tt;
			    }
			}
			break;
		    case 'o':
			/* Match octal mode of file exactly. *
			 * Currently undocumented.           */
			func = qualeqflags;
			data = qgetoctnum(&s);
			break;
		    case 'M':
			/* Mark directories with a / */
			if ((gf_markdirs = !(sense & 1)))
			    gf_follow = sense & 2;
			break;
		    case 'T':
			/* Mark types in a `ls -F' type fashion */
			if ((gf_listtypes = !(sense & 1)))
			    gf_follow = sense & 2;
			break;
		    case 'N':
			/* Nullglob:  remove unmatched patterns. */
			gf_nullglob = !(sense & 1);
			break;
		    case 'D':
			/* Glob dots: match leading dots implicitly */
			gf_noglobdots = sense & 1;
			break;
		    case 'a':
			/* Access time in given range */
			amc = 0;
			func = qualtime;
			goto getrange;
		    case 'm':
			/* Modification time in given range */
			amc = 1;
			func = qualtime;
			goto getrange;
		    case 'c':
			/* Inode creation time in given range */
			amc = 2;
			func = qualtime;
			goto getrange;
		    case 'L':
			/* File size (Length) in given range */
			func = qualsize;
			amc = -1;
			/* Get size multiplier */
			units = TT_BYTES;
			if (*s == 'p' || *s == 'P')
			    units = TT_POSIX_BLOCKS, ++s;
			else if (*s == 'k' || *s == 'K')
			    units = TT_KILOBYTES, ++s;
			else if (*s == 'm' || *s == 'M')
			    units = TT_MEGABYTES, ++s;
		      getrange:
			/* Get time multiplier */
			if (amc >= 0) {
			    units = TT_DAYS;
			    if (*s == 'h')
				units = TT_HOURS, ++s;
			    else if (*s == 'm')
				units = TT_MINS, ++s;
			    else if (*s == 'w')
				units = TT_WEEKS, ++s;
			    else if (*s == 'M')
				units = TT_MONTHS, ++s;
			}
			/* See if it's greater than, equal to, or less than */
			if ((range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
			    ++s;
			data = qgetnum(&s);
			break;

		    default:
			zerr("unknown file attribute", NULL, 0);
			return;
		    }
		if (func) {
		    /* Requested test is performed by function func */
		    if (!qn)
			qn = (struct qual *)hcalloc(sizeof *qn);
		    if (ql)
			ql->next = qn;
		    ql = qn;
		    if (!quals)
			quals = qo = qn;
		    qn->func = func;
		    qn->sense = sense;
		    qn->data = data;
		    qn->range = range;
		    qn->units = units;
		    qn->amc = amc;
		    qn = NULL;
		    qualct++;
		}
		if (errflag)
		    return;
	    }
	}
    }
    if (*str == '/') {		/* pattern has absolute path */
	str++;
	pathbuf[0] = '/';
	pathbuf[pathpos = 1] = '\0';
    } else			/* pattern is relative to pwd */
	pathbuf[pathpos = 0] = '\0';
    q = parsepat(str);
    if (!q || errflag) {	/* if parsing failed */
	if (unset(BADPATTERN)) {
	    untokenize(ostr);
	    insertlinknode(list, node, ostr);
	    return;
	}
	errflag = 0;
	zerr("bad pattern: %s", ostr, 0);
	return;
    }

    /* Initialise receptacle for matched files, *
     * expanded by insert() where necessary.    */
    matchptr = matchbuf = (char **)zalloc((matchsz = 16) * sizeof(char *));
    matchct = 0;

    /* Initialise memory of last file matched */
    old_ino = (ino_t) 0;
    old_dev = (dev_t) 0;
    old_pos = -1;

    /* The actual processing takes place here: matches go into  *
     * matchbuf.  This is the only top-level call to scanner(). */
    scanner(q);

    /* Deal with failures to match depending on options */
    if (matchct)
	badcshglob |= 2;	/* at least one cmd. line expansion O.K. */
    else if (!gf_nullglob)
	if (isset(CSHNULLGLOB)) {
	    badcshglob |= 1;	/* at least one cmd. line expansion failed */
	} else if (isset(NOMATCH)) {
	    zerr("no matches found: %s", ostr, 0);
	    free(matchbuf);
	    return;
	} else {
	    /* treat as an ordinary string */
	    untokenize(*matchptr++ = dupstring(ostr));
	    matchct = 1;
	}
    /* Sort arguments in to lexical (and possibly numeric) order. *
     * This is reversed to facilitate insertion into the list.    */
    qsort((void *) & matchbuf[0], matchct, sizeof(char *),
	       (int (*) _((const void *, const void *)))notstrcmp);

    matchptr = matchbuf;
    while (matchct--)		/* insert matches in the arg list */
	insertlinknode(list, node, *matchptr++);
    free(matchbuf);
}

/* get number after qualifier */

/**/
long
qgetnum(char **s)
{
    long v = 0;

    if (!idigit(**s)) {
	zerr("number expected", NULL, 0);
	return 0;
    }
    while (idigit(**s))
	v = v * 10 + *(*s)++ - '0';
    return v;
}

/* get octal number after qualifier */

/**/
long
qgetoctnum(char **s)
{
    long v = 0;

    if (!idigit(**s)) {
	zerr("octal number expected", NULL, 0);
	return 0;
    }
    while (**s >= '0' && **s <= '7')
	v = v * 010 + *(*s)++ - '0';
    return v;
}

/* Return the order of two strings, taking into account *
 * possible numeric order if NUMERICGLOBSORT is set.    *
 * The comparison here is reversed.                     */

/**/
int
notstrcmp(char **a, char **b)
{
    char *c = *b, *d = *a;
    int cmp;

#ifdef HAVE_STRCOLL
    cmp = strcoll(c, d);
#endif
    for (; *c == *d && *c; c++, d++);
#ifndef HAVE_STRCOLL
    cmp = (int)STOUC(*c) - (int)STOUC(*d);
#endif
    if (isset(NUMERICGLOBSORT) && (idigit(*c) || idigit(*d))) {
	for (; c > *b && idigit(c[-1]); c--, d--);
	if (idigit(*c) && idigit(*d)) {
	    while (*c == '0')
		c++;
	    while (*d == '0')
		d++;
	    for (; idigit(*c) && *c == *d; c++, d++);
	    if (idigit(*c) || idigit(*d)) {
		cmp = (int)STOUC(*c) - (int)STOUC(*d);
		while (idigit(*c) && idigit(*d))
		    c++, d++;
		if (idigit(*c) && !idigit(*d))
		    return 1;
		if (idigit(*d) && !idigit(*c))
		    return -1;
	    }
	}
    }
    return cmp;
}

/* add a match to the list */

/**/
void
insert(char *s)
{
    struct stat buf, buf2, *bp;
    char *news = s;
    int statted = 0;

    if (gf_listtypes || gf_markdirs) {
	/* Add the type marker to the end of the filename */
	statted = -1;
	if (!lstat(unmeta(s), &buf)) {
	    mode_t mode = buf.st_mode;
	    statted = 1;
	    if (gf_follow) {
		if (!S_ISLNK(mode) || stat(unmeta(s), &buf2))
		    memcpy(&buf2, &buf, sizeof(buf));
		statted = 2;
		mode = buf2.st_mode;
	    }
	    if (gf_listtypes || S_ISDIR(mode)) {
		int ll = strlen(s);

		news = (char *)ncalloc(ll + 2);
		strcpy(news, s);
		news[ll] = file_type(buf.st_mode);
		news[ll + 1] = '\0';
	    }
	}
    }
    if (qualct || qualorct) {
	/* Go through the qualifiers, rejecting the file if appropriate */
	struct qual *qo, *qn;
	int t = 0;		/* reject file unless t is set */

	if (statted >= 0 && (statted || !lstat(unmeta(s), &buf))) {
	    for (qo = quals; qo && !t; qo = qo->or) {

		t = 1;
		for (qn = qo; t && qn && qn->func; qn = qn->next) {
		    range = qn->range;
		    amc = qn->amc;
		    units = qn->units;
		    if ((qn->sense & 2) && statted != 2) {
			/* If (sense & 2), we're following links */
			if (!S_ISLNK(buf.st_mode) || stat(unmeta(s), &buf2))
			    memcpy(&buf2, &buf, sizeof(buf));
			statted = 2;
		    }
		    bp = (qn->sense & 2) ? &buf2 : &buf;
		    /* Reject the file if the function returned zero *
		     * and the sense was positive (sense&1 == 0), or *
		     * vice versa.                                   */
		    if ((!((qn->func) (bp, qn->data)) ^ qn->sense) & 1) {
			t = 0;
			break;
		    }
		}
	    }
	}
	if (!t)
	    return;
    }
    if (colonmod) {
	/* Handle the remainder of the qualifer:  e.g. (:r:s/foo/bar/). */
	s = colonmod;
	modify(&news, &s);
    }
    *matchptr++ = news;
    if (++matchct == matchsz) {
	matchbuf = (char **)realloc((char *)matchbuf,
				    sizeof(char **) * (matchsz *= 2));

	matchptr = matchbuf + matchct;
    }
}

/* Return the trailing character for marking file types */

/**/
char
file_type(mode_t filemode)
{
    switch (filemode & S_IFMT) {/* screw POSIX */
    case S_IFDIR:
	return '/';
#ifdef S_IFIFO
    case S_IFIFO:
	return '|';
#endif
    case S_IFCHR:
	return '%';
    case S_IFBLK:
	return '#';
#ifdef S_IFLNK
    case S_IFLNK:
	return /* (access(pbuf, F_OK) == -1) ? '&' :*/ '@';
#endif
#ifdef S_IFSOCK
    case S_IFSOCK:
	return '=';
#endif
    default:
	if (filemode & 0111)
	    return '*';
	else
	    return ' ';
    }
}

/* check to see if str is eligible for filename generation
 * It returns NULL if no wilds or modifiers found.
 * If a leading % is immediately followed by a ?, that single
 * ? is not treated as a wildcard.
 * If str has wilds it returns a pointer to the first wildcard.
 * If str has no wilds but ends in a (:...) type modifier it returns
 * a pointer to the colon.
 * If str has no wilds but ends in (...:...) it returns a pointer
 * to the terminating null character of str.
 */

/**/
char *
haswilds(char *str)
{
    char *mod = NULL;
    int parlev = 0;

    if ((*str == Inbrack || *str == Outbrack) && !str[1])
	return NULL;

    /* If % is immediately followed by ?, then that ? is     *
     * not treated as a wildcard.  This is so you don't have *
     * to escape job references such as %?foo.               */
    if (str[0] == '%' && str[1] == Quest)
	str[1] = '?';
    for (; *str; str++)
	switch (*str) {
	case Inpar:
	    parlev++;
	    break;
	case Outpar:
	    if (! --parlev && str[1])
		mod = NULL;
	    break;
	case ':':
	    if (parlev == 1 && !mod)
		mod = str;
	    break;
	case Bar:
	    if (!parlev) {
		*str = '|';
		break;
	    } /* else fall through */
	case Star:
	case Inbrack:
	case Inang:
	case Quest:
	    return str;
	case Pound:
	case Hat:
	    if (isset(EXTENDEDGLOB))
		return str;
	}
    if (!mod || parlev)
	return NULL;
    if (mod[-1] == Inpar)
	return mod;
    return str;
}

/* check to see if str is eligible for brace expansion */

/**/
int
hasbraces(char *str)
{
    char *lbr, *mbr, *comma;

    if (isset(BRACECCL)) {
	/* In this case, any properly formed brace expression  *
	 * will match and expand to the characters in between. */
	int bc;

	for (bc = 0; *str; ++str)
	    if (*str == Inbrace) {
		if (!bc && str[1] == Outbrace)
		    *str++ = '{', *str = '}';
		else
		    bc++;
	    } else if (*str == Outbrace)
		if (!bc)
		    *str = '}';
		else if (!--bc)
		    return 1;
	return 0;
    }
    /* Otherwise we need to look for... */
    lbr = mbr = comma = NULL;
    for (;;) {
	switch (*str++) {
	case Inbrace:
	    if (!lbr) {
		lbr = str - 1;
		while (idigit(*str))
		    str++;
		if (*str == '.' && str[1] == '.') {
		    str++;
		    while (idigit(*++str));
		    if (*str == Outbrace &&
			(idigit(lbr[1]) || idigit(str[-1])))
			return 1;
		}
	    } else {
		char *s = --str;

		if (skipparens(Inbrace, Outbrace, &str)) {
		    *lbr = *s = '{';
		    if (comma)
			str = comma;
		    if (mbr && mbr < str)
			str = mbr;
		    lbr = mbr = comma = NULL;
		} else if (!mbr)
		    mbr = s;
	    }
	    break;
	case Outbrace:
	    if (!lbr)
		str[-1] = '}';
	    else if (comma)
		return 1;
	    else {
		*lbr = '{';
		str[-1] = '}';
		if (mbr)
		    str = mbr;
		mbr = lbr = NULL;
	    }
	    break;
	case Comma:
	    if (!lbr)
		str[-1] = ',';
	    else if (!comma)
		comma = str - 1;
	    break;
	case '\0':
	    if (lbr)
		*lbr = '{';
	    if (!mbr && !comma)
		return 0;
	    if (comma)
		str = comma;
	    if (mbr && mbr < str)
		str = mbr;
	    lbr = mbr = comma = NULL;
	    break;
	}
    }
}

/* expand stuff like >>*.c */

/**/
int
xpandredir(struct redir *fn, LinkList tab)
{
    LinkList fake;
    char *nam;
    struct redir *ff;
    int ret = 0;

    /* Stick the name in a list... */
    fake = newlinklist();
    addlinknode(fake, fn->name);
    /* ...which undergoes all the usual shell expansions */
    prefork(fake, isset(MULTIOS) ? 0 : 4);
    /* Globbing is only done for multios. */
    if (!errflag && isset(MULTIOS))
	globlist(fake);
    if (errflag)
	return 0;
    if (nonempty(fake) && !nextnode(firstnode(fake))) {
	/* Just one match, the usual case. */
	char *s = peekfirst(fake);
	fn->name = s;
	untokenize(s);
	if (fn->type == MERGEIN || fn->type == MERGEOUT) {
	    if (s[0] == '-' && !s[1])
		fn->type = CLOSE;
	    else if (s[0] == 'p' && !s[1]) 
		fn->fd2 = (fn->type == MERGEOUT) ? coprocout : coprocin;
	    else {
		while (idigit(*s))
		    s++;
		if (!*s && s > fn->name)
		    fn->fd2 = zstrtol(fn->name, NULL, 10);
		else if (fn->type == MERGEIN)
		    zerr("file number expected", NULL, 0);
		else
		    fn->type = ERRWRITE;
	    }
	}
    } else if (fn->type == MERGEIN)
	zerr("file number expected", NULL, 0);
    else {
	if (fn->type == MERGEOUT)
	    fn->type = ERRWRITE;
	while ((nam = (char *)ugetnode(fake))) {
	    /* Loop over matches, duplicating the *
	     * redirection for each file found.   */
	    ff = (struct redir *)alloc(sizeof *ff);
	    *ff = *fn;
	    ff->name = nam;
	    addlinknode(tab, ff);
	    ret = 1;
	}
    }
    return ret;
}

/* concatenate s1 and s2 in dynamically allocated buffer */

/**/
char *
dyncat(char *s1, char *s2)
{
    /* This version always uses space from the current heap. */
    char *ptr;
    int l1 = strlen(s1);

    ptr = (char *)ncalloc(l1 + strlen(s2) + 1);
    strcpy(ptr, s1);
    strcpy(ptr + l1, s2);
    return ptr;
}

/* concatenate s1, s2, and s3 in dynamically allocated buffer */

/**/
char *
tricat(char *s1, char *s2, char *s3)
{
    /* This version always uses permanently-allocated space. */
    char *ptr;

    ptr = (char *)zalloc(strlen(s1) + strlen(s2) + strlen(s3) + 1);
    strcpy(ptr, s1);
    strcat(ptr, s2);
    strcat(ptr, s3);
    return ptr;
}

/* brace expansion */

/**/
void
xpandbraces(LinkList list, LinkNode *np)
{
    LinkNode node = (*np), last = prevnode(node);
    char *str = (char *)getdata(node), *str3 = str, *str2;
    int prev, bc, comma, dotdot;

    for (; *str != Inbrace; str++);
    /* First, match up braces and see what we have. */
    for (str2 = str, bc = comma = dotdot = 0; *str2; ++str2)
	if (*str2 == Inbrace)
	    ++bc;
	else if (*str2 == Outbrace) {
	    if (--bc == 0)
		break;
	} else if (bc == 1)
	    if (*str2 == Comma)
		++comma;	/* we have {foo,bar} */
	    else if (*str2 == '.' && str2[1] == '.')
		dotdot++;	/* we have {num1..num2} */
    DPUTS(bc, "BUG: unmatched brace in xpandbraces()");
    if (!comma && dotdot) {
	/* Expand range like 0..10 numerically: comma or recursive
	   brace expansion take precedence. */
	char *dots, *p;
	LinkNode olast = last;
	/* Get the first number of the range */
	int rstart = zstrtol(str+1,&dots,10), rend = 0, err = 0, rev = 0;
	int wid1 = (dots - str) - 1, wid2 = (str2 - dots) - 2;
	int strp = str - str3;
      
	if (dots == str + 1 || *dots != '.' || dots[1] != '.')
	    err++;
	else {
	    /* Get the last number of the range */
	    rend = zstrtol(dots+2,&p,10);
	    if (p == dots+2 || p != str2)
		err++;
	}
	if (!err) {
	    /* If either no. begins with a zero, pad the output with   *
	     * zeroes. Otherwise, choose a min width to suppress them. */
	    int minw = (str[1] == '0') ? wid1 : (dots[2] == '0' ) ? wid2 :
		(wid2 > wid1) ? wid1 : wid2;
	    if (rstart > rend) {
		/* Handle decreasing ranges correctly. */
		int rt = rend;
		rend = rstart;
		rstart = rt;
		rev = 1;
	    }
	    uremnode(list, node);
	    for (; rend >= rstart; rend--) {
		/* Node added in at end, so do highest first */
		p = dupstring(str3);
		sprintf(p + strp, "%0*d", minw, rend);
		strcat(p + strp, str2 + 1);
		insertlinknode(list, last, p);
		if (rev)	/* decreasing:  add in reverse order. */
		    last = nextnode(last);
	    }
	    *np = nextnode(olast);
	    return;
	}
    }
    if (!comma && isset(BRACECCL)) {	/* {a-mnop} */
	/* Here we expand each character to a separate node,      *
	 * but also ranges of characters like a-m.  ccl is a      *
	 * set of flags saying whether each character is present; *
	 * the final list is in lexical order.                    */
	char ccl[256], *p;
	unsigned char c1, c2, lastch;
	unsigned int len, pl;

	uremnode(list, node);
	memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
	for (p = str + 1, lastch = 0; p < str2;) {
	    if (itok(c1 = *p++))
		c1 = ztokens[c1 - STOUC(Pound)];
	    if ((char) c1 == Meta)
		c1 = 32 ^ *p++;
	    if (itok(c2 = *p))
		c2 = ztokens[c2 - STOUC(Pound)];
	    if ((char) c2 == Meta)
		c2 = 32 ^ p[1];
	    if (c1 == '-' && lastch && p < str2 && (int)lastch <= (int)c2) {
		while ((int)lastch < (int)c2)
		    ccl[lastch++] = 1;
		lastch = 0;
	    } else
		ccl[lastch = c1] = 1;
	}
	pl = str - str3;
	len = pl + strlen(++str2) + 2;
	for (p = ccl + 255; p-- > ccl;)
	    if (*p) {
		c1 = p - ccl;
		if (imeta(c1)) {
		    str = ncalloc(len + 1);
		    str[pl] = Meta;
		    str[pl+1] = c1 ^ 32;
		    strcpy(str + pl + 2, str2);
		} else {
		    str = ncalloc(len);
		    str[pl] = c1;
		    strcpy(str + pl + 1, str2);
		}
		memcpy(str, str3, pl);
		insertlinknode(list, last, str);
	    }
	*np = nextnode(last);
	return;
    }
    prev = str++ - str3;
    str2++;
    uremnode(list, node);
    node = last;
    /* Finally, normal comma expansion               *
     * str1{foo,bar}str2 -> str1foostr2 str1barstr2. *
     * Any number of intervening commas is allowed.  */
    for (;;) {
	char *zz, *str4;
	int cnt;

	for (str4 = str, cnt = 0; cnt || (*str != Comma && *str !=
					  Outbrace); str++) {
	    if (*str == Inbrace)
		cnt++;
	    else if (*str == Outbrace)
		cnt--;
	    DPUTS(!*str, "BUG: illegal brace expansion");
	}
	/* Concatenate the string before the braces (str3), the section *
	 * just found (str4) and the text after the braces (str2)       */
	zz = (char *)ncalloc(prev + (str - str4) + strlen(str2) + 1);
	ztrncpy(zz, str3, prev);
	strncat(zz, str4, str - str4);
	strcat(zz, str2);
	/* and add this text to the argument list. */
	insertlinknode(list, node, zz);
	incnode(node);
	if (*str != Outbrace)
	    str++;
	else
	    break;
    }
    *np = nextnode(last);
}

/* check to see if a matches b (b is not a filename pattern) */

/**/
int
matchpat(char *a, char *b)
{
    Comp c;
    int val, len;
    char *b2;

    len = strlen(b);
    b2 = (char *)alloc(len + 3);
    strcpy(b2 + 1, b);
    b2[0] = Inpar;
    b2[len + 1] = Outpar;
    b2[len + 2] = '\0';
    c = parsereg(b2);
    if (!c) {
	zerr("bad pattern: %s", b, 0);
	return 0;
    }
    val = domatch(a, c, 0);
    return val;
}

/* do the ${foo%%bar}, ${foo#bar} stuff */
/* please do not laugh at this code. */

/* Having found a match in getmatch, decide what part of string
 * to return.  The matched part starts b characters into string s
 * and finishes e characters in: 0 <= b <= e <= strlen(s)
 * (yes, empty matches should work).
 * Bits 3 and higher in fl are used: the flags are
 *   8:		Result is matched portion.
 *  16:		Result is unmatched portion.
 *		(N.B. this should be set for standard ${foo#bar} etc. matches.)
 *  32:		Result is numeric position of start of matched portion.
 *  64:		Result is numeric position of end of matched portion.
 * 128:		Result is length of matched portion.
 */

/**/
char *
get_match_ret(char *s, int b, int e, int fl)
{
    char buf[80], *r, *p, *rr;
    int ll = 0, l = strlen(s), bl = 0, t = 0, i;

    if (fl & 8)			/* matched portion */
	ll += 1 + (e - b);
    if (fl & 16)		/* unmatched portion */
	ll += 1 + (l - (e - b));
    if (fl & 32) {
	/* position of start of matched portion */
	sprintf(buf, "%d ", b + 1);
	ll += (bl = strlen(buf));
    }
    if (fl & 64) {
	/* position of end of matched portion */
	sprintf(buf + bl, "%d ", e + 1);
	ll += (bl = strlen(buf));
    }
    if (fl & 128) {
	/* length of matched portion */
	sprintf(buf + bl, "%d ", e - b);
	ll += (bl = strlen(buf));
    }
    if (bl)
	buf[bl - 1] = '\0';

    rr = r = (char *)ncalloc(ll);

    if (fl & 8) {
	/* copy matched portion to new buffer */
	for (i = b, p = s + b; i < e; i++)
	    *rr++ = *p++;
	t = 1;
    }
    if (fl & 16) {
	/* Copy unmatched portion to buffer.  If both portions *
	 * requested, put a space in between (why?)            */
	if (t)
	    *rr++ = ' ';
	/* there may be unmatched bits at both beginning and end of string */
	for (i = 0, p = s; i < b; i++)
	    *rr++ = *p++;
	for (i = e, p = s + e; i < l; i++)
	    *rr++ = *p++;
	t = 1;
    }
    *rr = '\0';
    if (bl) {
	/* if there was a buffer (with a numeric result), add it; *
	 * if there was other stuff too, stick in a space first.  */
	if (t)
	    *rr++ = ' ';
	strcpy(rr, buf);
    }
    return r;
}

/* It is called from paramsubst to get the match for ${foo#bar} etc.
 * Bits of fl determines the required action:
 *   bit 0: match the end instead of the beginning (% or %%)
 *   bit 1: % or # was doubled so get the longest match
 *   bit 2: substring match
 *   bit 3: include the matched portion
 *   bit 4: include the unmatched portion
 *   bit 5: the index of the beginning
 *   bit 6: the index of the end
 *   bit 7: the length of the match
 *   bit 8: match the complete string
 * *sp points to the string we have to modify. The n'th match will be
 * returned in *sp. ncalloc is used to get memory for the result string.
 */

/**/
int
getmatch(char **sp, char *pat, int fl, int n)
{
    Comp c;
    char *s = *sp, *t, sav;
    int i, j, l = strlen(*sp);

    c = parsereg(pat);
    if (!c) {
	zerr("bad pattern: %s", pat, 0);
	return 1;
    }
    if (fl & 256) {
	i = domatch(s, c, 0);
	*sp = get_match_ret(*sp, 0, domatch(s, c, 0) ? l : 0, fl);
	if (! **sp && (((fl & 8) && !i) || ((fl & 16) && i)))
	    return 0;
	return 1;
    }
    switch (fl & 7) {
    case 0:
	/* Smallest possible match at head of string:    *
	 * start adding characters until we get a match. */
	for (i = 0, t = s; i <= l; i++, t++) {
	    sav = *t;
	    *t = '\0';
	    if (domatch(s, c, 0) && !--n) {
		*t = sav;
		*sp = get_match_ret(*sp, 0, i, fl);
		return 1;
	    }
	    if ((*t = sav) == Meta)
		i++, t++;
	}
	break;

    case 1:
	/* Smallest possible match at tail of string:  *
	 * move back down string until we get a match. */
	for (t = s + l; t >= s; t--) {
	    if (domatch(t, c, 0) && !--n) {
		*sp = get_match_ret(*sp, t - s, l, fl);
		return 1;
	    }
	    if (t > s+1 && t[-2] == Meta)
		t--;
	}
	break;

    case 2:
	/* Largest possible match at head of string:        *
	 * delete characters from end until we get a match. */
	for (t = s + l; t > s; t--) {
	    sav = *t;
	    *t = '\0';
	    if (domatch(s, c, 0) && !--n) {
		*t = sav;
		*sp = get_match_ret(*sp, 0, t - s, fl);
		return 1;
	    }
	    *t = sav;
	    if (t >= s+2 && t[-2] == Meta)
		t--;
	}
	break;

    case 3:
	/* Largest possible match at tail of string:       *
	 * move forward along string until we get a match. */
	for (i = 0, t = s; i < l; i++, t++) {
	    if (domatch(t, c, 0) && !--n) {
		*sp = get_match_ret(*sp, i, l, fl);
		return 1;
	    }
	    if (*t == Meta)
		i++, t++;
	}
	break;

    case 4:
	/* Smallest at start, but matching substrings. */
	if (domatch(s + l, c, 0) && !--n) {
	    *sp = get_match_ret(*sp, 0, 0, fl);
	    return 1;
	}
	for (i = 1; i <= l; i++) {
	    for (t = s, j = i; j <= l; j++, t++) {
		sav = s[j];
		s[j] = '\0';
		if (domatch(t, c, 0) && !--n) {
		    s[j] = sav;
		    *sp = get_match_ret(*sp, t - s, j, fl);
		    return 1;
		}
		if ((s[j] = sav) == Meta)
		    j++;
		if (*t == Meta)
		    t++;
	    }
	    if (s[i] == Meta)
		i++;
	}
	break;

    case 5:
	/* Smallest at end, matching substrings */
	if (domatch(s + l, c, 0) && !--n) {
	    *sp = get_match_ret(*sp, l, l, fl);
	    return 1;
	}
	for (i = l; i--;) {
	    if (i && s[i-1] == Meta)
		i--;
	    for (t = s + l, j = i; j >= 0; j--, t--) {
		sav = *t;
		*t = '\0';
		if (domatch(s + j, c, 0) && !--n) {
		    *t = sav;
		    *sp = get_match_ret(*sp, j, t - s, fl);
		    return 1;
		}
		*t = sav;
		if (t >= s+2 && t[-2] == Meta)
		    t--;
		if (j >= 2 && s[j-2] == Meta)
		    j--;
	    }
	}
	break;

    case 6:
	/* Largest at start, matching substrings. */
	for (i = l; i; i--) {
	    for (t = s, j = i; j <= l; j++, t++) {
		sav = s[j];
		s[j] = '\0';
		if (domatch(t, c, 0) && !--n) {
		    s[j] = sav;
		    *sp = get_match_ret(*sp, t - s, j, fl);
		    return 1;
		}
		if ((s[j] = sav) == Meta)
		    j++;
		if (*t == Meta)
		    t++;
	    }
	    if (i >= 2 && s[i-2] == Meta)
		i--;
	}
	if (domatch(s + l, c, 0) && !--n) {
	    *sp = get_match_ret(*sp, 0, 0, fl);
	    return 1;
	}
	break;

    case 7:
	/* Largest at end, matching substrings. */
	for (i = 0; i < l; i++) {
	    for (t = s + l, j = i; j >= 0; j--, t--) {
		sav = *t;
		*t = '\0';
		if (domatch(s + j, c, 0) && !--n) {
		    *t = sav;
		    *sp = get_match_ret(*sp, j, t - s, fl);
		    return 1;
		}
		*t = sav;
		if (t >= s+2 && t[-2] == Meta)
		    t--;
		if (j >= 2 && s[j-2] == Meta)
		    j--;
	    }
	    if (s[i] == Meta)
		i++;
	}
	if (domatch(s + l, c, 0) && !--n) {
	    *sp = get_match_ret(*sp, l, l, fl);
	    return 1;
	}
	break;
    }
    /* munge the whole string */
    *sp = get_match_ret(*sp, 0, 0, fl);
    return 1;
}

/* Add a component to pathbuf: This keeps track of how    *
 * far we are into a file name, since each path component *
 * must be matched separately.                            */

static int addpath _((char *s));

static int
addpath(char *s)
{
    if ((int)strlen(s) + pathpos + 2 >= PATH_MAX)
	return 0;
    while ((pathbuf[pathpos++] = *s++));
    pathbuf[pathpos - 1] = '/';
    pathbuf[pathpos] = '\0';
    return 1;
}

/* return full path for s, which has path as *
 * already added to pathbuf                  */

/**/
char *
getfullpath(char *s)
{
    static char buf[PATH_MAX];

    strcpy(buf, pathbuf);
    strncpy(buf + pathpos, s, PATH_MAX - pathpos - 1);
    return buf;
}

/* Do the globbing:  scanner is called recursively *
 * with successive bits of the path until we've    *
 * tried all of it.                                */

/**/
void
scanner(Complist q)
{
    Comp c;
    int closure;
    struct stat st;

    if (!q)
	return;

    /* make sure we haven't just done this one. */
    if (q->closure && old_pos != pathpos &&
	stat((*pathbuf) ? unmeta(pathbuf) : ".", &st) != -1)
	if (st.st_ino == old_ino && st.st_dev == old_dev)
	    return;
	else {
	    old_pos = pathpos;
	    old_ino = st.st_ino;
	    old_dev = st.st_dev;
	}
    if ((closure = q->closure))	/* (foo/)# - match zero or more dirs */
	if (q->closure == 2)	/* (foo/)## - match one or more dirs */
	    q->closure = 1;
	else
	    scanner(q->next);
    if ((c = q->comp)) {
	/* Now the actual matching for the current path section. */
	if (!(c->next || c->left) && !haswilds(c->str)) {
	    /* It's a straight string to the end of the path section. */
	    if (q->next) {
		/* Not the last path section. Just add it to the path. */
		int oppos = pathpos;

		if (errflag)
		    return;
		if (q->closure && !strcmp(c->str, "."))
		    return;
		if (!addpath(c->str))
		    return;
		if (!closure || exists(pathbuf))
		    scanner((q->closure) ? q : q->next);
		pathbuf[pathpos = oppos] = '\0';
	    } else if (!*c->str) {
		if (exists(getfullpath(".")))
		    insert(dupstring(pathbuf));
	    } else {
		/* Last path section.  See if there's a file there. */
		char *s;

		if (exists(s = getfullpath(c->str)))
		    insert(dupstring(s));
	    }
	} else {
	    /* Do pattern matching on current path section. */
	    char *fn;
	    int dirs = !!q->next;
	    DIR *lock = opendir((*pathbuf) ? unmeta(pathbuf) : ".");

	    if (lock == NULL)
		return;
	    while ((fn = zreaddir(lock))) {
		/* Loop through the directory */
		if (errflag)
		    break;
		/* skip this and parent directory */
		if (fn[0] == '.'
		    && (fn[1] == '\0'
			|| (fn[1] == '.' && fn[2] == '\0')))
		    continue;
		/* prefix and suffix are zle trickery */
		if (!dirs && !colonmod &&
		    ((glob_pre && !strpfx(glob_pre, fn))
		     || (glob_suf && !strsfx(glob_suf, fn))))
		    continue;
		if (domatch(fn, c, gf_noglobdots)) {
		    /* if this name matchs the pattern... */
		    int oppos = pathpos;

		    if (dirs) {
			/* if not the last component in the path */
			if (closure) {
			    /* if matching multiple directories */
			    struct stat buf;

			    if ((q->follow ?
				stat(unmeta(getfullpath(fn)), &buf) :
				lstat(unmeta(getfullpath(fn)), &buf)) == -1) {
				if (errno != ENOENT && errno != EINTR &&
				    errno != ENOTDIR) {
				    zerr("%e: %s", fn, errno);
				    errflag = 0;
				}
				continue;
			    }
			    if (!S_ISDIR(buf.st_mode))
				continue;
			}
			/* do next path component */
			if (addpath(fn))
			    scanner((q->closure) ? q : q->next);	/* scan next level */
			pathbuf[pathpos = oppos] = '\0';
		    } else
			insert(dyncat(pathbuf, fn));
		    /* if the last filename component, just add it */
		}
	    }
	    closedir(lock);
	}
    } else
	zerr("no idea how you got this error message.", NULL, 0);
}

/* Flags passed down to guts when compiling */
#define GF_PATHADD	1	/* file glob, adding path components */
#define GF_TOPLEV	2	/* outside (), so ~ ends main match */

static char *pptr;		/* current place in string being matched */
static Comp tail;
static int first;		/* are leading dots special? */

/* The main entry point for matching a string str against  *
 * a compiled pattern c.  `fist' indicates whether leading *
 * dots are special.                                       */

/**/
int
domatch(char *str, Comp c, int fist)
{
    pptr = str;
    first = fist;
    if (*pptr == Nularg)
	pptr++;
    return doesmatch(c);
}

#define untok(C)  (itok(C) ? ztokens[(C) - Pound] : (C))

/* See if pattern has a matching exclusion (~...) part */

/**/
int
excluded(Comp c, char *eptr)
{
    char *saves = pptr;
    int savei = first, ret;

    first = 0;
    pptr = (PATHADDP(c) && pathpos) ? getfullpath(eptr) : eptr;

    ret = doesmatch(c->exclude);

    pptr = saves;
    first = savei;

    return ret;
}

/* see if current string in pptr matches c */

/**/
int
doesmatch(Comp c)
{
    char *pat = c->str;
    int done = 0;

  tailrec:
    if (ONEHASHP(c) || (done && TWOHASHP(c))) {
	/* Do multiple matches like (pat)# and (pat)## */
	char *saves = pptr;

	if (first && *pptr == '.')
	    return 0;
	if (doesmatch(c->next))
	    return 1;
	pptr = saves;
	first = 0;
    }
    done++;
    for (;;) {
	/* loop until success or failure of pattern */
	if (!pat || !*pat) {
	    /* No current pattern (c->str). */
	    char *saves;
	    int savei;

	    if (errflag)
		return 0;
	    /* Remember state in case we need to go back and   *
	     * check for exclusion of pattern or alternatives. */
	    saves = pptr;
	    savei = first;
	    /* Loop over alternatives with exclusions: (foo~bar|...). *
	     * Exclusions apply to the pattern in c->left.            */
	    if (c->left || c->right)
		if (!doesmatch(c->left) ||
		    (c->exclude && excluded(c, saves)))
		    if (c->right) {
			pptr = saves;
			first = savei;
			if (!doesmatch(c->right))
			    return 0;
		    } else
			return 0;
	    if (*pptr && CLOSUREP(c)) {
		/* With a closure (#), need to keep trying */
		pat = c->str;
		goto tailrec;
	    }
	    if (!c->next)	/* no more patterns left */
		return (!LASTP(c) || !*pptr);
	    c = c->next;
	    done = 0;
	    pat = c->str;
	    goto tailrec;
	}
	/* Don't match leading dot if first is set */
	if (first && *pptr == '.' && *pat != '.')
	    return 0;
	if (*pat == Star) {	/* final * is not expanded to ?#; returns success */
	    while (*pptr)
		pptr++;
	    return 1;
	}
	first = 0;		/* finished checking start of pattern */
	if (*pat == Quest && *pptr) {
	    /* match exactly one character */
	    if (*pptr == Meta)
		pptr++;
	    pptr++;
	    pat++;
	    continue;
	}
	if (*pat == Hat)	/* following pattern is negated */
	    return 1 - doesmatch(c->next);
	if (*pat == Inbrack) {
	    /* Match groups of characters */
#define PAT(X) (pat[X] == Meta ? pat[(X)+1] ^ 32 : untok(pat[X]))
#define PPAT(X) (pat[(X)-1] == Meta ? pat[X] ^ 32 : untok(pat[X]))
	    char ch;
#ifdef HAVE_STRCOLL
	    char l_buf[2], r_buf[2], ch_buf[2];

	    l_buf[1] = r_buf[1] = ch_buf[1] = '\0';
#endif

	    if (!*pptr)
		break;
	    ch = *pptr == Meta ? pptr[1] ^ 32 : *pptr;
#ifdef HAVE_STRCOLL
	    ch_buf[0] = ch;
#endif
	    if (pat[1] == Hat || pat[1] == '^' || pat[1] == '!') {
		/* group is negated */
		pat[1] = Hat;
		for (pat += 2; *pat != Outbrack && *pat;
		     *pat == Meta ? pat += 2 : pat++)
		    if (*pat == '-' && pat[-1] != Hat && pat[1] != Outbrack) {
#ifdef HAVE_STRCOLL
			l_buf[0] = PPAT(-1);
			r_buf[0] = PAT(1);
			if (strcoll(l_buf, ch_buf) <= 0 &&
			    strcoll(ch_buf, r_buf) <= 0)
#else
			if (PPAT(-1) <= ch && PAT(1) >= ch)
#endif
			    break;
		    } else if (ch == PAT(0))
			break;
#ifdef DEBUG
		if (!*pat) {
		    zerr("something is very wrong.", NULL, 0);
		    return 0;
		}
#endif
		if (*pat != Outbrack)
		    break;
		pat++;
		*pptr == Meta ? pptr += 2 : pptr++;
		continue;
	    } else {
		/* pattern is not negated (affirmed? asserted?) */
		for (pat++; *pat != Outbrack && *pat;
		     *pat == Meta ? pat += 2 : pat++)
		    if (*pat == '-' && pat[-1] != Inbrack &&
			       pat[1] != Outbrack) {
#ifdef HAVE_STRCOLL
			l_buf[0] = PPAT(-1);
			r_buf[0] = PAT(1);
			if (strcoll(l_buf, ch_buf) <= 0 &&
			    strcoll(ch_buf, r_buf) <= 0)
#else
			if (PPAT(-1) <= ch && PAT(1) >= ch)
#endif
			    break;
		    } else if (ch == PAT(0))
			break;
#ifdef DEBUG
		if (!pat || !*pat) {
		    zerr("oh dear.  that CAN'T be right.", NULL, 0);
		    return 0;
		}
#endif
		if (*pat == Outbrack)
		    break;
		for (*pptr == Meta ? pptr += 2 : pptr++;
		     *pat != Outbrack; pat++);
		pat++;
		continue;
	    }
	}
	if (*pat == Inang) {
	    /* Numeric globbing. */
	    unsigned long t1, t2, t3;
	    char *ptr;

	    if (!idigit(*pptr))
		break;
	    if (*++pat == Outang || 
		(*pat == '-' && pat[1] == Outang && ++pat)) {
		/* <> or <->:  any number matches */
		while (idigit(*++pptr));
		pat++;
	    } else {
		/* Flag that there is no upper limit */
		int not3 = 0;
		/*
		 * Form is <a-b>, where a or b are numbers or blank.
		 * t1 = number supplied:  must be positive, so use
		 * unsigned arithmetic.
		 */
		t1 = (unsigned long)zstrtol(pptr, &ptr, 10);
		pptr = ptr;
		/* t2 = lower limit */
		if (idigit(*pat))
		    t2 = (unsigned long)zstrtol(pat, &ptr, 10);
		else
		    t2 = 0, ptr = pat;
		if (*ptr != '-' || (not3 = (ptr[1] == Outang)))
				/* exact match or no upper limit */
		    t3 = t2, pat = ptr + not3;
		else		/* t3 = upper limit */
		    t3 = (unsigned long)zstrtol(ptr + 1, &pat, 10);
		DPUTS(*pat != Outang, "BUG: wrong internal range pattern");
		pat++;
		if (t1 < t2 || (!not3 && t1 > t3))
		    break;
	    }
	    continue;
	}
	if (*pptr == *pat) {
	    /* just plain old characters */
	    pptr++;
	    pat++;
	    continue;
	}
	break;
    }
    return 0;
}

/* turn a string into a Complist struct:  this has path components */

/**/
Complist
parsepat(char *str)
{
    mode = 0;			/* path components present */
    pptr = str;
    tail = NULL;
    return parsecomplist();
}

/* turn a string into a Comp struct:  this doesn't treat / specially */

/**/
Comp
parsereg(char *str)
{
    remnulargs(str);
    mode = 1;			/* no path components */
    pptr = str;
    tail = NULL;
    return parsecompsw(GF_TOPLEV);
}

/* Parse a series of path components pointed to by pptr */

/* This function tokenizes a zsh glob pattern */

/**/
Complist
parsecomplist(void)
{
    Comp c1;
    Complist p1;
    char *str;

    if (pptr[0] == Star && pptr[1] == Star &&
        (pptr[2] == '/' || (pptr[2] == Star && pptr[3] == '/'))) {
	/* Match any number of directories. */
	int follow;

	/* with three stars, follow symbolic links */
	follow = (pptr[2] == Star);
	pptr += (3 + follow);

	/* Now get the next path component if there is one. */
	p1 = (Complist) alloc(sizeof *p1);
	if ((p1->next = parsecomplist()) == NULL) {
	    errflag = 1;
	    return NULL;
	}
	p1->comp = (Comp) alloc(sizeof *p1->comp);
	p1->comp->stat |= C_LAST;	/* end of path component  */
	p1->comp->str = dupstring("*");
	*p1->comp->str = Star;		/* match anything...      */
	p1->closure = 1;		/* ...zero or more times. */
	p1->follow = follow;
	return p1;
    }

    /* Parse repeated directories such as (dir/)# and (dir/)## */
    if (*(str = pptr) == Inpar && !skipparens(Inpar, Outpar, &str) &&
        *str == Pound && isset(EXTENDEDGLOB) && str[-2] == '/') {
	pptr++;
	if (!(c1 = parsecompsw(0)))
	    return NULL;
	if (pptr[0] == '/' && pptr[1] == Outpar && pptr[2] == Pound) {
	    int pdflag = 0;

	    pptr += 3;
	    if (*pptr == Pound) {
		pdflag = 1;
		pptr++;
	    }
	    p1 = (Complist) alloc(sizeof *p1);
	    p1->comp = c1;
	    p1->closure = 1 + pdflag;
	    p1->follow = 0;
	    p1->next = parsecomplist();
	    return (p1->comp) ? p1 : NULL;
	}
    } else {
	/* parse single path component */
	if (!(c1 = parsecompsw(GF_PATHADD|GF_TOPLEV)))
	    return NULL;
	/* then do the remaining path compoents */
	if (*pptr == '/' || !*pptr) {
	    int ef = *pptr == '/';

	    p1 = (Complist) alloc(sizeof *p1);
	    p1->comp = c1;
	    p1->closure = 0;
	    p1->next = ef ? (pptr++, parsecomplist()) : NULL;
	    return (ef && !p1->next) ? NULL : p1;
	}
    }
    errflag = 1;
    return NULL;
}

/* parse lowest level pattern */

/**/
Comp
parsecomp(int gflag)
{
    Comp c = (Comp) alloc(sizeof *c), c1, c2;
    char *cstr, *ls = NULL;

    /* In case of alternatives, code coming up is stored in tail. */
    c->next = tail;
    cstr = pptr;

    while (*pptr && (mode || *pptr != '/') && *pptr != Bar &&
	   (unset(EXTENDEDGLOB) || *pptr != Tilde ||
	    !pptr[1] || pptr[1] == Outpar || pptr[1] == Bar) &&
	   *pptr != Outpar) {
	/* Go through code until we find something separating alternatives,
	 * or path components if relevant.
	 */
	if (*pptr == Hat && isset(EXTENDEDGLOB)) {
	    /* negate remaining pattern */
	    pptr++;
	    c->str = dupstrpfx(cstr, pptr - cstr);
	    if (!(c->next = parsecomp(gflag)))
		return NULL;
	    return c;
	}
	if (*pptr == Star && pptr[1] &&
	    (unset(EXTENDEDGLOB) || pptr[1] != Tilde || !pptr[2] ||
	     pptr[2] == Bar ||
	     pptr[2] == Outpar) && (mode || pptr[1] != '/')) {
	    /* Star followed by other patterns is treated like a closure
	     * (zero or more repetitions) of the single character pattern
	     * operator `?'.
	     */
	    c->str = dupstrpfx(cstr, pptr - cstr);
	    pptr++;
	    c1 = (Comp) alloc(sizeof *c1);
	    *(c1->str = dupstring("?")) = Quest;
	    c1->stat |= C_ONEHASH;
	    if (!(c2 = parsecomp(gflag)))
		return NULL;
	    c1->next = c2;
	    c->next = c1;
	    return c;
	}
	if (*pptr == Inpar) {
	    /* Found a group (...) */
	    char *startp = pptr, *endp;
	    Comp stail = tail;
	    int dpnd = 0;

	    /* Need matching close parenthesis */
	    if (skipparens(Inpar, Outpar, &pptr)) {
		errflag = 1;
		return NULL;
	    }
	    if (*pptr == Pound && isset(EXTENDEDGLOB)) {
		/* Zero (or one) or more repetitions of group */
		dpnd = 1;
		pptr++;
		if (*pptr == Pound) {
		    pptr++;
		    dpnd = 2;
		}
	    }
	    /* Parse the remaining pattern following the group... */
	    if (!(c1 = parsecomp(gflag)))
		return NULL;
	    /* ...remembering what comes after it... */
	    tail = dpnd ? NULL : c1;
	    /* ...before going back and parsing inside the group. */
	    endp = pptr;
	    pptr = startp;
	    c->str = dupstrpfx(cstr, pptr - cstr);
	    pptr++;
	    c->next = (Comp) alloc(sizeof *c);
	    if (!(c->next->left = parsecompsw(0)))
		return NULL;
	    /* Remember closures for group. */
	    if (dpnd)
		c->next->stat |= (dpnd == 2) ? C_TWOHASH : C_ONEHASH;
	    c->next->next = dpnd ? c1 : (Comp) alloc(sizeof *c);
	    pptr = endp;
	    tail = stail;
	    return c;
	}
	if (*pptr == Pound && isset(EXTENDEDGLOB)) {
	    /* repeat whatever we've just had (ls) zero or more times */
	    if (!ls)
		return NULL;
	    c2 = (Comp) alloc(sizeof *c);
	    c2->str = dupstrpfx(ls, pptr - ls);
	    pptr++;
	    if (*pptr == Pound) {
		/* need one or more matches: cheat by copying previous char */
		pptr++;
		c->next = c1 = (Comp) alloc(sizeof *c);
		c1->str = c2->str;
	    } else
		c1 = c;
	    c1->next = c2;
	    c2->stat |= C_ONEHASH;
	    /* parse the rest of the pattern and return. */
	    c2->next = parsecomp(gflag);
	    if (!c2->next)
		return NULL;
	    c->str = dupstrpfx(cstr, ls - cstr);
	    return c;
	}
	ls = pptr;		/* whatever we just parsed */
	if (*pptr == Inang) {
	    /* Numeric glob */
	    int dshct;

	    dshct = (pptr[1] == Outang);
	    while (*++pptr && *pptr != Outang)
		if (*pptr == '-' && !dshct)
		    dshct = 1;
		else if (!idigit(*pptr))
		    break;
	    if (*pptr != Outang)
		return NULL;
	} else if (*pptr == Inbrack) {
	    /* Character set: brackets had better match */
	    while (*++pptr && *pptr != Outbrack)
		if (itok(*pptr))
		    *pptr = ztokens[*pptr - Pound];
	    if (*pptr != Outbrack)
		return NULL;
	} else if (itok(*pptr) && *pptr != Star && *pptr != Quest)
	    /* something that can be tokenised which isn't otherwise special */
	    *pptr = ztokens[*pptr - Pound];
	pptr++;
    }
    /* mark if last pattern component in path component or pattern */
    if (*pptr == '/' || !*pptr ||
	(isset(EXTENDEDGLOB) && *pptr == Tilde && (gflag & GF_TOPLEV)))
	c->stat |= C_LAST;
    c->str = dupstrpfx(cstr, pptr - cstr);
    return c;
}

/* Parse pattern possibly with different alternatives (|) */

/**/
Comp
parsecompsw(int gflag)
{
    Comp c1, c2, c3, excl = NULL;

    c1 = parsecomp(gflag);
    if (!c1)
	return NULL;
    if (isset(EXTENDEDGLOB) && *pptr == Tilde) {
	/* Matching remainder of pattern excludes the pattern from matching */
	int oldmode = mode;

	mode = 1;
	pptr++;
	excl = parsecomp(gflag);
	mode = oldmode;
	if (!excl)
	    return NULL;
    }
    if (*pptr == Bar || excl) {
	/* found an alternative or something to exclude */
	c2 = (Comp) alloc(sizeof *c2);
	if (*pptr == Bar) {
	    /* get the next alternative after the | */
	    pptr++;
	    c3 = parsecompsw(gflag);
	    if (!c3)
		return NULL;
	} else {
	    /* mark if end of pattern or path component */
	    if (!*pptr || *pptr == '/')
		c2->stat |= C_LAST;
	    c3 = NULL;
	}
	c2->str = dupstring("");
	c2->left = c1;
	c2->right = c3;
	c2->exclude = excl;
	if (gflag & GF_PATHADD)
	    c2->stat |= C_PATHADD;
	return c2;
    }
    return c1;
}

/* blindly turn a string into a tokenised expression without lexing */

/**/
void
tokenize(char *s)
{
    char *t;
    int bslash = 0;

    for (; *s; s++) {
      cont:
	switch (*s) {
	case Bnull:
	case '\\':
	    if (bslash) {
		s[-1] = Bnull;
		break;
	    }
	    bslash = 1;
	    continue;
	case '[':
	    if (bslash) {
		s[-1] = Bnull;
		break;
	    }
	    t = s;
	    if (*++s == '^' || *s == '!')
		s++;
	    while (*s && *++s != ']');
	    if (!*s)
		return;
	    *t = Inbrack;
	    *s = Outbrack;
	    break;
	case '<':
	    if (isset(SHGLOB))
		break;
	    if (bslash) {
		s[-1] = Bnull;
		break;
	    }
	    t = s;
	    while (idigit(*++s));
	    if (*s != '-')
		goto cont;
	    while (idigit(*++s));
	    if (*s != '>')
		goto cont;
	    *t = Inang;
	    *s = Outang;
	    break;
	case '^':
	case '#':
	case '~':
	    if (unset(EXTENDEDGLOB))
		break;
	case '(':
	case '|':
	case ')':
	    if (isset(SHGLOB))
		break;
	case '*':
	case '?':
	    for (t = ztokens; *t; t++)
		if (*t == *s) {
		    if (bslash)
			s[-1] = Bnull;
		    else
			*s = (t - ztokens) + Pound;
		    break;
		}
	}
	bslash = 0;
    }
}

/* remove unnecessary Nulargs */

/**/
void
remnulargs(char *s)
{
    int nl = *s;
    char *t = s;

    while (*s)
	if (INULL(*s))
	    chuck(s);
	else
	    s++;
    if (!*t && nl) {
	t[0] = Nularg;
	t[1] = '\0';
    }
}

/* qualifier functions:  mostly self-explanatory, see glob(). */

/* device number */

/**/
int
qualdev(struct stat *buf, long dv)
{
    return buf->st_dev == dv;
}

/* number of hard links to file */

/**/
int
qualnlink(struct stat *buf, long ct)
{
    return (range < 0 ? buf->st_nlink < ct :
	    range > 0 ? buf->st_nlink > ct :
	    buf->st_nlink == ct);
}

/* user ID */

/**/
int
qualuid(struct stat *buf, long uid)
{
    return buf->st_uid == uid;
}

/* group ID */

/**/
int
qualgid(struct stat *buf, long gid)
{
    return buf->st_gid == gid;
}

/* device special file? */

/**/
int
qualisdev(struct stat *buf, long junk)
{
    junk = buf->st_mode & S_IFMT;
    return junk == S_IFBLK || junk == S_IFCHR;
}

/* block special file? */

/**/
int
qualisblk(struct stat *buf, long junk)
{
    junk = buf->st_mode & S_IFMT;
    return junk == S_IFBLK;
}

/* character special file? */

/**/
int
qualischar(struct stat *buf, long junk)
{
    junk = buf->st_mode & S_IFMT;
    return junk == S_IFCHR;
}

/* file type is requested one */

/**/
int
qualmode(struct stat *buf, long mod)
{
    return (buf->st_mode & S_IFMT) == mod;
}

/* given flag is set in mode */

/**/
int
qualflags(struct stat *buf, long mod)
{
    return buf->st_mode & mod;
}

/* mode matches number supplied exactly  */

/**/
int
qualeqflags(struct stat *buf, long mod)
{
    return (buf->st_mode & 07777) == mod;
}

/* regular executable file? */

/**/
int
qualiscom(struct stat *buf, long mod)
{
    return (buf->st_mode & (S_IFMT | S_IEXEC)) == (S_IFREG | S_IEXEC);
}

/* size in required range? */

/**/
int
qualsize(struct stat *buf, long size)
{
    unsigned long scaled = buf->st_size;

    switch (units) {
    case TT_POSIX_BLOCKS:
	scaled += 511l;
	scaled /= 512l;
	break;
    case TT_KILOBYTES:
	scaled += 1023l;
	scaled /= 1024l;
	break;
    case TT_MEGABYTES:
	scaled += 1048575l;
	scaled /= 1048576l;
	break;
    }

    return (range < 0 ? scaled < (unsigned long) size :
	    range > 0 ? scaled > (unsigned long) size :
	    scaled == (unsigned long) size);
}

/* time in required range? */

/**/
int
qualtime(struct stat *buf, long days)
{
    time_t now, diff;

    time(&now);
    diff = now - (amc == 0 ? buf->st_atime : amc == 1 ? buf->st_mtime :
		  buf->st_ctime);
    /* handle multipliers indicating units */
    switch (units) {
    case TT_DAYS:
	diff /= 86400l;
	break;
    case TT_HOURS:
	diff /= 3600l;
	break;
    case TT_MINS:
	diff /= 60l;
	break;
    case TT_WEEKS:
	diff /= 604800l;
	break;
    case TT_MONTHS:
	diff /= 2592000l;
	break;
    }

    return (range < 0 ? diff < days :
	    range > 0 ? diff > days :
	    diff == days);
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.