mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-11-04 07:21:06 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			3948 lines
		
	
	
	
		
			96 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			3948 lines
		
	
	
	
		
			96 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
 * glob.c - filename generation
 | 
						|
 *
 | 
						|
 * This file is part of zsh, the Z shell.
 | 
						|
 *
 | 
						|
 * Copyright (c) 1992-1997 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.mdh"
 | 
						|
#include "glob.pro"
 | 
						|
 | 
						|
#if defined(OFF_T_IS_64_BIT) && defined(__GNUC__)
 | 
						|
# define ALIGN64 __attribute__((aligned(8)))
 | 
						|
#else
 | 
						|
# define ALIGN64
 | 
						|
#endif
 | 
						|
 | 
						|
/* flag for CSHNULLGLOB */
 | 
						|
 | 
						|
typedef struct gmatch *Gmatch;
 | 
						|
 | 
						|
struct gmatch {
 | 
						|
    /* Metafied file name */
 | 
						|
    char *name;
 | 
						|
    /* Unmetafied file name; embedded nulls can't occur in file names */
 | 
						|
    char *uname;
 | 
						|
    /*
 | 
						|
     * Array of sort strings:  one for each GS_EXEC sort type in
 | 
						|
     * the glob qualifiers.
 | 
						|
     */
 | 
						|
    char **sortstrs;
 | 
						|
    off_t size ALIGN64;
 | 
						|
    long atime;
 | 
						|
    long mtime;
 | 
						|
    long ctime;
 | 
						|
    long links;
 | 
						|
    off_t _size ALIGN64;
 | 
						|
    long _atime;
 | 
						|
    long _mtime;
 | 
						|
    long _ctime;
 | 
						|
    long _links;
 | 
						|
#ifdef GET_ST_ATIME_NSEC
 | 
						|
    long ansec;
 | 
						|
    long _ansec;
 | 
						|
#endif
 | 
						|
#ifdef GET_ST_MTIME_NSEC
 | 
						|
    long mnsec;
 | 
						|
    long _mnsec;
 | 
						|
#endif
 | 
						|
#ifdef GET_ST_CTIME_NSEC
 | 
						|
    long cnsec;
 | 
						|
    long _cnsec;
 | 
						|
#endif
 | 
						|
};
 | 
						|
 | 
						|
#define GS_NAME   1
 | 
						|
#define GS_DEPTH  2
 | 
						|
#define GS_EXEC	  4
 | 
						|
 | 
						|
#define GS_SHIFT_BASE	8
 | 
						|
 | 
						|
#define GS_SIZE  (GS_SHIFT_BASE)
 | 
						|
#define GS_ATIME (GS_SHIFT_BASE << 1)
 | 
						|
#define GS_MTIME (GS_SHIFT_BASE << 2)
 | 
						|
#define GS_CTIME (GS_SHIFT_BASE << 3)
 | 
						|
#define GS_LINKS (GS_SHIFT_BASE << 4)
 | 
						|
 | 
						|
#define GS_SHIFT  5
 | 
						|
#define GS__SIZE  (GS_SIZE << GS_SHIFT)
 | 
						|
#define GS__ATIME (GS_ATIME << GS_SHIFT)
 | 
						|
#define GS__MTIME (GS_MTIME << GS_SHIFT)
 | 
						|
#define GS__CTIME (GS_CTIME << GS_SHIFT)
 | 
						|
#define GS__LINKS (GS_LINKS << GS_SHIFT)
 | 
						|
 | 
						|
#define GS_DESC  (GS_SHIFT_BASE << (2*GS_SHIFT))
 | 
						|
#define GS_NONE  (GS_SHIFT_BASE << (2*GS_SHIFT+1))
 | 
						|
 | 
						|
#define GS_NORMAL (GS_SIZE | GS_ATIME | GS_MTIME | GS_CTIME | GS_LINKS)
 | 
						|
#define GS_LINKED (GS_NORMAL << GS_SHIFT)
 | 
						|
 | 
						|
/**/
 | 
						|
int badcshglob;
 | 
						|
 | 
						|
/**/
 | 
						|
int pathpos;		/* position in pathbuf (needed by pattern code) */
 | 
						|
 | 
						|
/*
 | 
						|
 * pathname buffer (needed by pattern code).
 | 
						|
 * It is currently believed the string in here is stored metafied and is
 | 
						|
 * unmetafied temporarily as needed by system calls.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
char *pathbuf;
 | 
						|
 | 
						|
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_SECONDS 5
 | 
						|
 | 
						|
#define TT_BYTES 0
 | 
						|
#define TT_POSIX_BLOCKS 1
 | 
						|
#define TT_KILOBYTES 2
 | 
						|
#define TT_MEGABYTES 3
 | 
						|
#define TT_GIGABYTES 4
 | 
						|
#define TT_TERABYTES 5
 | 
						|
 | 
						|
 | 
						|
typedef int (*TestMatchFunc) _((char *, struct stat *, off_t, char *));
 | 
						|
 | 
						|
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            */
 | 
						|
    off_t data ALIGN64;		/* 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 */
 | 
						|
    char *sdata;		/* currently only: expression to eval        */
 | 
						|
};
 | 
						|
 | 
						|
/* Prefix, suffix for doing zle trickery */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export char *glob_pre, *glob_suf;
 | 
						|
 | 
						|
/* Element of a glob sort */
 | 
						|
struct globsort {
 | 
						|
    /* Sort type */
 | 
						|
    int tp;
 | 
						|
    /* Sort code to eval, if type is GS_EXEC */
 | 
						|
    char *exec;
 | 
						|
};
 | 
						|
 | 
						|
/* Maximum entries in sort array */
 | 
						|
#define MAX_SORTS	(12)
 | 
						|
 | 
						|
/* struct to easily save/restore current state */
 | 
						|
 | 
						|
struct globdata {
 | 
						|
    int gd_pathpos;
 | 
						|
    char *gd_pathbuf;
 | 
						|
 | 
						|
    int gd_matchsz;		/* size of matchbuf                     */
 | 
						|
    int gd_matchct;		/* number of matches found              */
 | 
						|
    int gd_pathbufsz;		/* size of pathbuf			*/
 | 
						|
    int gd_pathbufcwd;		/* where did we chdir()'ed		*/
 | 
						|
    Gmatch gd_matchbuf;		/* array of matches                     */
 | 
						|
    Gmatch gd_matchptr;		/* &matchbuf[matchct]                   */
 | 
						|
    char *gd_colonmod;		/* colon modifiers in qualifier list    */
 | 
						|
 | 
						|
    /* Qualifiers pertaining to current pattern */
 | 
						|
    struct qual *gd_quals;
 | 
						|
 | 
						|
    /* Other state values for current pattern */
 | 
						|
    int gd_qualct, gd_qualorct;
 | 
						|
    int gd_range, gd_amc, gd_units;
 | 
						|
    int gd_gf_nullglob, gd_gf_markdirs, gd_gf_noglobdots, gd_gf_listtypes;
 | 
						|
    int gd_gf_numsort;
 | 
						|
    int gd_gf_follow, gd_gf_sorts, gd_gf_nsorts;
 | 
						|
    struct globsort gd_gf_sortlist[MAX_SORTS];
 | 
						|
    LinkList gd_gf_pre_words, gd_gf_post_words;
 | 
						|
 | 
						|
    char *gd_glob_pre, *gd_glob_suf;
 | 
						|
};
 | 
						|
 | 
						|
/* The variable with the current globbing state and convenience macros */
 | 
						|
 | 
						|
static struct globdata curglobdata;
 | 
						|
 | 
						|
#define matchsz       (curglobdata.gd_matchsz)
 | 
						|
#define matchct       (curglobdata.gd_matchct)
 | 
						|
#define pathbufsz     (curglobdata.gd_pathbufsz)
 | 
						|
#define pathbufcwd    (curglobdata.gd_pathbufcwd)
 | 
						|
#define matchbuf      (curglobdata.gd_matchbuf)
 | 
						|
#define matchptr      (curglobdata.gd_matchptr)
 | 
						|
#define colonmod      (curglobdata.gd_colonmod)
 | 
						|
#define quals         (curglobdata.gd_quals)
 | 
						|
#define qualct        (curglobdata.gd_qualct)
 | 
						|
#define qualorct      (curglobdata.gd_qualorct)
 | 
						|
#define g_range       (curglobdata.gd_range)
 | 
						|
#define g_amc         (curglobdata.gd_amc)
 | 
						|
#define g_units       (curglobdata.gd_units)
 | 
						|
#define gf_nullglob   (curglobdata.gd_gf_nullglob)
 | 
						|
#define gf_markdirs   (curglobdata.gd_gf_markdirs)
 | 
						|
#define gf_noglobdots (curglobdata.gd_gf_noglobdots)
 | 
						|
#define gf_listtypes  (curglobdata.gd_gf_listtypes)
 | 
						|
#define gf_numsort    (curglobdata.gd_gf_numsort)
 | 
						|
#define gf_follow     (curglobdata.gd_gf_follow)
 | 
						|
#define gf_sorts      (curglobdata.gd_gf_sorts)
 | 
						|
#define gf_nsorts     (curglobdata.gd_gf_nsorts)
 | 
						|
#define gf_sortlist   (curglobdata.gd_gf_sortlist)
 | 
						|
#define gf_pre_words  (curglobdata.gd_gf_pre_words)
 | 
						|
#define gf_post_words (curglobdata.gd_gf_post_words)
 | 
						|
 | 
						|
/* and macros for save/restore */
 | 
						|
 | 
						|
#define save_globstate(N) \
 | 
						|
  do { \
 | 
						|
    queue_signals(); \
 | 
						|
    memcpy(&(N), &curglobdata, sizeof(struct globdata)); \
 | 
						|
    (N).gd_pathpos = pathpos; \
 | 
						|
    (N).gd_pathbuf = pathbuf; \
 | 
						|
    (N).gd_glob_pre = glob_pre; \
 | 
						|
    (N).gd_glob_suf = glob_suf; \
 | 
						|
    pathbuf = NULL; \
 | 
						|
    unqueue_signals(); \
 | 
						|
  } while (0)
 | 
						|
 | 
						|
#define restore_globstate(N) \
 | 
						|
  do { \
 | 
						|
    queue_signals(); \
 | 
						|
    zfree(pathbuf, pathbufsz); \
 | 
						|
    memcpy(&curglobdata, &(N), sizeof(struct globdata)); \
 | 
						|
    pathpos = (N).gd_pathpos; \
 | 
						|
    pathbuf = (N).gd_pathbuf; \
 | 
						|
    glob_pre = (N).gd_glob_pre; \
 | 
						|
    glob_suf = (N).gd_glob_suf; \
 | 
						|
    unqueue_signals(); \
 | 
						|
  } while (0)
 | 
						|
 | 
						|
/* pathname component in filename patterns */
 | 
						|
 | 
						|
struct complist {
 | 
						|
    Complist next;
 | 
						|
    Patprog pat;
 | 
						|
    int closure;		/* 1 if this is a (foo/)# */
 | 
						|
    int follow; 		/* 1 to go thru symlinks */
 | 
						|
};
 | 
						|
 | 
						|
/* 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 void
 | 
						|
addpath(char *s, int l)
 | 
						|
{
 | 
						|
    DPUTS(!pathbuf, "BUG: pathbuf not initialised");
 | 
						|
    while (pathpos + l + 1 >= pathbufsz)
 | 
						|
	pathbuf = zrealloc(pathbuf, pathbufsz *= 2);
 | 
						|
    while (l--)
 | 
						|
	pathbuf[pathpos++] = *s++;
 | 
						|
    pathbuf[pathpos++] = '/';
 | 
						|
    pathbuf[pathpos] = '\0';
 | 
						|
}
 | 
						|
 | 
						|
/* stat the filename s appended to pathbuf.  l should be true for lstat,    *
 | 
						|
 * false for stat.  If st is NULL, the file is only checked for existence.  *
 | 
						|
 * s == "" is treated as s == ".".  This is necessary since on most systems *
 | 
						|
 * foo/ can be used to reference a non-directory foo.  Returns nonzero if   *
 | 
						|
 * the file does not exists.                                                */
 | 
						|
 | 
						|
static int
 | 
						|
statfullpath(const char *s, struct stat *st, int l)
 | 
						|
{
 | 
						|
    char buf[PATH_MAX+1];
 | 
						|
    int check_for_being_a_directory = 0;
 | 
						|
 | 
						|
    DPUTS(strlen(s) + !*s + pathpos - pathbufcwd >= PATH_MAX,
 | 
						|
	  "BUG: statfullpath(): pathname too long");
 | 
						|
    strcpy(buf, pathbuf + pathbufcwd);
 | 
						|
    strcpy(buf + pathpos - pathbufcwd, s);
 | 
						|
    if (!*s && *buf) {
 | 
						|
	/*
 | 
						|
	 * Don't add the '.' if the path so far is empty, since
 | 
						|
	 * then we get bogus empty strings inserted as files.
 | 
						|
	 */
 | 
						|
	if (st) {
 | 
						|
	    buf[pathpos - pathbufcwd] = '.';
 | 
						|
	    buf[pathpos - pathbufcwd + 1] = '\0';
 | 
						|
	    l = 0;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
	    check_for_being_a_directory = 1;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    unmetafy(buf, NULL);
 | 
						|
    if (st) {
 | 
						|
	return l ? lstat(buf, st) : stat(buf, st);
 | 
						|
    }
 | 
						|
    else if (check_for_being_a_directory) {
 | 
						|
	struct stat tmp;
 | 
						|
	if (stat(buf, &tmp))
 | 
						|
	    return -1;
 | 
						|
 | 
						|
	return S_ISDIR(tmp.st_mode) ? 0 : -1;
 | 
						|
    }
 | 
						|
    else {
 | 
						|
	char lbuf[1];
 | 
						|
 | 
						|
	/* If it exists, signal success. */
 | 
						|
	if (access(buf, F_OK) == 0)
 | 
						|
	    return 0;
 | 
						|
 | 
						|
	/* Would a dangling symlink be good enough? */
 | 
						|
	if (l == 0)
 | 
						|
	    return -1;
 | 
						|
 | 
						|
	/* Is it a dangling symlink? */
 | 
						|
	if (readlink(buf, lbuf, 1) >= 0)
 | 
						|
	    return 0;
 | 
						|
 | 
						|
	/* Guess it doesn't exist, then. */
 | 
						|
	return -1;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* This may be set by qualifier functions to an array of strings to insert
 | 
						|
 * into the list instead of the original string. */
 | 
						|
 | 
						|
static char **inserts;
 | 
						|
 | 
						|
/* add a match to the list */
 | 
						|
 | 
						|
/**/
 | 
						|
static void
 | 
						|
insert(char *s, int checked)
 | 
						|
{
 | 
						|
    struct stat buf, buf2, *bp;
 | 
						|
    char *news = s;
 | 
						|
    int statted = 0;
 | 
						|
 | 
						|
    queue_signals();
 | 
						|
    inserts = NULL;
 | 
						|
 | 
						|
    if (gf_listtypes || gf_markdirs) {
 | 
						|
	/* Add the type marker to the end of the filename */
 | 
						|
	mode_t mode;
 | 
						|
	if (statfullpath(s, &buf, 1)) {
 | 
						|
	    unqueue_signals();
 | 
						|
	    return;
 | 
						|
	}
 | 
						|
	else {
 | 
						|
	    checked = statted = 1;
 | 
						|
	}
 | 
						|
	mode = buf.st_mode;
 | 
						|
	if (gf_follow) {
 | 
						|
	    if (!S_ISLNK(mode) || statfullpath(s, &buf2, 0))
 | 
						|
		memcpy(&buf2, &buf, sizeof(buf));
 | 
						|
	    statted |= 2;
 | 
						|
	    mode = buf2.st_mode;
 | 
						|
	}
 | 
						|
	if (gf_listtypes || S_ISDIR(mode)) {
 | 
						|
	    int ll = strlen(s);
 | 
						|
 | 
						|
	    news = (char *) hcalloc(ll + 2);
 | 
						|
	    strcpy(news, s);
 | 
						|
	    news[ll] = file_type(mode);
 | 
						|
	    news[ll + 1] = '\0';
 | 
						|
	}
 | 
						|
    }
 | 
						|
    if (qualct || qualorct) {
 | 
						|
	/* Go through the qualifiers, rejecting the file if appropriate */
 | 
						|
	struct qual *qo, *qn;
 | 
						|
 | 
						|
	if (!statted && statfullpath(s, &buf, 1)) {
 | 
						|
	    unqueue_signals();
 | 
						|
	    return;
 | 
						|
	}
 | 
						|
	news = dyncat(pathbuf, news);
 | 
						|
 | 
						|
	statted = 1;
 | 
						|
	qo = quals;
 | 
						|
	for (qn = qo; qn && qn->func;) {
 | 
						|
	    g_range = qn->range;
 | 
						|
	    g_amc = qn->amc;
 | 
						|
	    g_units = qn->units;
 | 
						|
	    if ((qn->sense & 2) && !(statted & 2)) {
 | 
						|
		/* If (sense & 2), we're following links */
 | 
						|
		if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
 | 
						|
		    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) (news, bp, qn->data, qn->sdata))
 | 
						|
		 ^ qn->sense) & 1) {
 | 
						|
		/* Try next alternative, or return if there are no more */
 | 
						|
		if (!(qo = qo->or)) {
 | 
						|
		    unqueue_signals();
 | 
						|
		    return;
 | 
						|
		}
 | 
						|
		qn = qo;
 | 
						|
		continue;
 | 
						|
	    }
 | 
						|
	    qn = qn->next;
 | 
						|
	}
 | 
						|
    } else if (!checked) {
 | 
						|
	if (statfullpath(s, NULL, 1)) {
 | 
						|
	    unqueue_signals();
 | 
						|
	    return;
 | 
						|
	}
 | 
						|
	news = dyncat(pathbuf, news);
 | 
						|
    } else
 | 
						|
	news = dyncat(pathbuf, news);
 | 
						|
 | 
						|
    while (!inserts || (news = dupstring(*inserts++))) {
 | 
						|
	if (colonmod) {
 | 
						|
	    /* Handle the remainder of the qualifier:  e.g. (:r:s/foo/bar/). */
 | 
						|
	    char *mod = colonmod;
 | 
						|
	    modify(&news, &mod, 1);
 | 
						|
	}
 | 
						|
	if (!statted && (gf_sorts & GS_NORMAL)) {
 | 
						|
	    statfullpath(s, &buf, 1);
 | 
						|
	    statted = 1;
 | 
						|
	}
 | 
						|
	if (!(statted & 2) && (gf_sorts & GS_LINKED)) {
 | 
						|
	    if (statted) {
 | 
						|
		if (!S_ISLNK(buf.st_mode) || statfullpath(s, &buf2, 0))
 | 
						|
		    memcpy(&buf2, &buf, sizeof(buf));
 | 
						|
	    } else if (statfullpath(s, &buf2, 0))
 | 
						|
		statfullpath(s, &buf2, 1);
 | 
						|
	    statted |= 2;
 | 
						|
	}
 | 
						|
	matchptr->name = news;
 | 
						|
	if (statted & 1) {
 | 
						|
	    matchptr->size = buf.st_size;
 | 
						|
	    matchptr->atime = buf.st_atime;
 | 
						|
	    matchptr->mtime = buf.st_mtime;
 | 
						|
	    matchptr->ctime = buf.st_ctime;
 | 
						|
	    matchptr->links = buf.st_nlink;
 | 
						|
#ifdef GET_ST_ATIME_NSEC
 | 
						|
	    matchptr->ansec = GET_ST_ATIME_NSEC(buf);
 | 
						|
#endif
 | 
						|
#ifdef GET_ST_MTIME_NSEC
 | 
						|
	    matchptr->mnsec = GET_ST_MTIME_NSEC(buf);
 | 
						|
#endif
 | 
						|
#ifdef GET_ST_CTIME_NSEC
 | 
						|
	    matchptr->cnsec = GET_ST_CTIME_NSEC(buf);
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (statted & 2) {
 | 
						|
	    matchptr->_size = buf2.st_size;
 | 
						|
	    matchptr->_atime = buf2.st_atime;
 | 
						|
	    matchptr->_mtime = buf2.st_mtime;
 | 
						|
	    matchptr->_ctime = buf2.st_ctime;
 | 
						|
	    matchptr->_links = buf2.st_nlink;
 | 
						|
#ifdef GET_ST_ATIME_NSEC
 | 
						|
	    matchptr->_ansec = GET_ST_ATIME_NSEC(buf2);
 | 
						|
#endif
 | 
						|
#ifdef GET_ST_MTIME_NSEC
 | 
						|
	    matchptr->_mnsec = GET_ST_MTIME_NSEC(buf2);
 | 
						|
#endif
 | 
						|
#ifdef GET_ST_CTIME_NSEC
 | 
						|
	    matchptr->_cnsec = GET_ST_CTIME_NSEC(buf2);
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	matchptr++;
 | 
						|
 | 
						|
	if (++matchct == matchsz) {
 | 
						|
	    matchbuf = (Gmatch)zrealloc((char *)matchbuf,
 | 
						|
					sizeof(struct gmatch) * (matchsz *= 2));
 | 
						|
 | 
						|
	    matchptr = matchbuf + matchct;
 | 
						|
	}
 | 
						|
	if (!inserts)
 | 
						|
	    break;
 | 
						|
    }
 | 
						|
    unqueue_signals();
 | 
						|
    return;
 | 
						|
}
 | 
						|
 | 
						|
/* Do the globbing:  scanner is called recursively *
 | 
						|
 * with successive bits of the path until we've    *
 | 
						|
 * tried all of it.                                */
 | 
						|
 | 
						|
/**/
 | 
						|
static void
 | 
						|
scanner(Complist q, int shortcircuit)
 | 
						|
{
 | 
						|
    Patprog p;
 | 
						|
    int closure;
 | 
						|
    int pbcwdsav = pathbufcwd;
 | 
						|
    int errssofar = errsfound;
 | 
						|
    struct dirsav ds;
 | 
						|
 | 
						|
    if (!q || errflag)
 | 
						|
	return;
 | 
						|
    init_dirsav(&ds);
 | 
						|
 | 
						|
    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, shortcircuit);
 | 
						|
	    if (shortcircuit && shortcircuit == matchct)
 | 
						|
		return;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    p = q->pat;
 | 
						|
    /* Now the actual matching for the current path section. */
 | 
						|
    if (p->flags & PAT_PURES) {
 | 
						|
	/*
 | 
						|
	 * It's a straight string to the end of the path section.
 | 
						|
	 */
 | 
						|
	char *str = (char *)p + p->startoff;
 | 
						|
	int l = p->patmlen;
 | 
						|
 | 
						|
	if (l + !l + pathpos - pathbufcwd >= PATH_MAX) {
 | 
						|
	    int err;
 | 
						|
 | 
						|
	    if (l >= PATH_MAX)
 | 
						|
		return;
 | 
						|
	    err = lchdir(unmeta(pathbuf + pathbufcwd), &ds, 0);
 | 
						|
	    if (err == -1)
 | 
						|
		return;
 | 
						|
	    if (err) {
 | 
						|
		zerr("current directory lost during glob");
 | 
						|
		return;
 | 
						|
	    }
 | 
						|
	    pathbufcwd = pathpos;
 | 
						|
	}
 | 
						|
	if (q->next) {
 | 
						|
	    /* Not the last path section. Just add it to the path. */
 | 
						|
	    int oppos = pathpos;
 | 
						|
 | 
						|
	    if (!errflag) {
 | 
						|
		int add = 1;
 | 
						|
 | 
						|
		if (q->closure && *pathbuf) {
 | 
						|
		    if (!strcmp(str, "."))
 | 
						|
			add = 0;
 | 
						|
		    else if (!strcmp(str, "..")) {
 | 
						|
			struct stat sc, sr;
 | 
						|
 | 
						|
			add = (stat("/", &sr) || stat(unmeta(pathbuf), &sc) ||
 | 
						|
			       sr.st_ino != sc.st_ino ||
 | 
						|
			       sr.st_dev != sc.st_dev);
 | 
						|
		    }
 | 
						|
		}
 | 
						|
		if (add) {
 | 
						|
		    addpath(str, l);
 | 
						|
		    if (!closure || !statfullpath("", NULL, 1)) {
 | 
						|
			scanner((q->closure) ? q : q->next, shortcircuit);
 | 
						|
			if (shortcircuit && shortcircuit == matchct)
 | 
						|
			    return;
 | 
						|
		    }
 | 
						|
		    pathbuf[pathpos = oppos] = '\0';
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	} else {
 | 
						|
	    if (str[l])
 | 
						|
		str = dupstrpfx(str, l);
 | 
						|
	    insert(str, 0);
 | 
						|
	    if (shortcircuit && shortcircuit == matchct)
 | 
						|
		return;
 | 
						|
	}
 | 
						|
    } else {
 | 
						|
	/* Do pattern matching on current path section. */
 | 
						|
	char *fn = pathbuf[pathbufcwd] ? unmeta(pathbuf + pathbufcwd) : ".";
 | 
						|
	int dirs = !!q->next;
 | 
						|
	DIR *lock = opendir(fn);
 | 
						|
	char *subdirs = NULL;
 | 
						|
	int subdirlen = 0;
 | 
						|
 | 
						|
	if (lock == NULL)
 | 
						|
	    return;
 | 
						|
	while ((fn = zreaddir(lock, 1)) && !errflag) {
 | 
						|
	    /* prefix and suffix are zle trickery */
 | 
						|
	    if (!dirs && !colonmod &&
 | 
						|
		((glob_pre && !strpfx(glob_pre, fn))
 | 
						|
		 || (glob_suf && !strsfx(glob_suf, fn))))
 | 
						|
		continue;
 | 
						|
	    errsfound = errssofar;
 | 
						|
	    if (pattry(p, fn)) {
 | 
						|
		/* if this name matches the pattern... */
 | 
						|
		if (pbcwdsav == pathbufcwd &&
 | 
						|
		    strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) {
 | 
						|
		    int err;
 | 
						|
 | 
						|
		    DPUTS(pathpos == pathbufcwd,
 | 
						|
			  "BUG: filename longer than PATH_MAX");
 | 
						|
		    err = lchdir(unmeta(pathbuf + pathbufcwd), &ds, 0);
 | 
						|
		    if (err == -1)
 | 
						|
			break;
 | 
						|
		    if (err) {
 | 
						|
			zerr("current directory lost during glob");
 | 
						|
			break;
 | 
						|
		    }
 | 
						|
		    pathbufcwd = pathpos;
 | 
						|
		}
 | 
						|
		if (dirs) {
 | 
						|
		    int l;
 | 
						|
 | 
						|
		    /*
 | 
						|
		     * If not the last component in the path:
 | 
						|
		     *
 | 
						|
		     * If we made an approximation in the new path segment,
 | 
						|
		     * then it is possible we made too many errors.  For
 | 
						|
		     * example, (ab)#(cb)# will match the directory abcb
 | 
						|
		     * with one error if allowed to, even though it can
 | 
						|
		     * match with none.  This will stop later parts of the
 | 
						|
		     * path matching, so we need to check by reducing the
 | 
						|
		     * maximum number of errors and seeing if the directory
 | 
						|
		     * still matches.  Luckily, this is not a terribly
 | 
						|
		     * common case, since complex patterns typically occur
 | 
						|
		     * in the last part of the path which is not affected
 | 
						|
		     * by this problem.
 | 
						|
		     */
 | 
						|
		    if (errsfound > errssofar) {
 | 
						|
			forceerrs = errsfound - 1;
 | 
						|
			while (forceerrs >= errssofar) {
 | 
						|
			    errsfound = errssofar;
 | 
						|
			    if (!pattry(p, fn))
 | 
						|
				break;
 | 
						|
			    forceerrs = errsfound - 1;
 | 
						|
			}
 | 
						|
			errsfound = forceerrs + 1;
 | 
						|
			forceerrs = -1;
 | 
						|
		    }
 | 
						|
		    if (closure) {
 | 
						|
			/* if matching multiple directories */
 | 
						|
			struct stat buf;
 | 
						|
 | 
						|
			if (statfullpath(fn, &buf, !q->follow)) {
 | 
						|
			    if (errno != ENOENT && errno != EINTR &&
 | 
						|
				errno != ENOTDIR && !errflag) {
 | 
						|
				zwarn("%e: %s", errno, fn);
 | 
						|
			    }
 | 
						|
			    continue;
 | 
						|
			}
 | 
						|
			if (!S_ISDIR(buf.st_mode))
 | 
						|
			    continue;
 | 
						|
		    }
 | 
						|
		    l = strlen(fn) + 1;
 | 
						|
		    subdirs = hrealloc(subdirs, subdirlen, subdirlen + l
 | 
						|
				       + sizeof(int));
 | 
						|
		    strcpy(subdirs + subdirlen, fn);
 | 
						|
		    subdirlen += l;
 | 
						|
		    /* store the count of errors made so far, too */
 | 
						|
		    memcpy(subdirs + subdirlen, (char *)&errsfound,
 | 
						|
			   sizeof(int));
 | 
						|
		    subdirlen += sizeof(int);
 | 
						|
		} else {
 | 
						|
		    /* if the last filename component, just add it */
 | 
						|
		    insert(fn, 1);
 | 
						|
		    if (shortcircuit && shortcircuit == matchct) {
 | 
						|
			closedir(lock);
 | 
						|
			return;
 | 
						|
		    }
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	}
 | 
						|
	closedir(lock);
 | 
						|
	if (subdirs) {
 | 
						|
	    int oppos = pathpos;
 | 
						|
 | 
						|
	    for (fn = subdirs; fn < subdirs+subdirlen; ) {
 | 
						|
		int l = strlen(fn);
 | 
						|
		addpath(fn, l);
 | 
						|
		fn += l + 1;
 | 
						|
		memcpy((char *)&errsfound, fn, sizeof(int));
 | 
						|
		fn += sizeof(int);
 | 
						|
		/* scan next level */
 | 
						|
		scanner((q->closure) ? q : q->next, shortcircuit); 
 | 
						|
		if (shortcircuit && shortcircuit == matchct)
 | 
						|
		    return;
 | 
						|
		pathbuf[pathpos = oppos] = '\0';
 | 
						|
	    }
 | 
						|
	    hrealloc(subdirs, subdirlen, 0);
 | 
						|
	}
 | 
						|
    }
 | 
						|
    if (pbcwdsav < pathbufcwd) {
 | 
						|
	if (restoredir(&ds))
 | 
						|
	    zerr("current directory lost during glob");
 | 
						|
	zsfree(ds.dirname);
 | 
						|
	if (ds.dirfd >= 0)
 | 
						|
	    close(ds.dirfd);
 | 
						|
	pathbufcwd = pbcwdsav;
 | 
						|
    }
 | 
						|
    return;
 | 
						|
}
 | 
						|
 | 
						|
/* This function tokenizes a zsh glob pattern */
 | 
						|
 | 
						|
/**/
 | 
						|
static Complist
 | 
						|
parsecomplist(char *instr)
 | 
						|
{
 | 
						|
    Patprog p1;
 | 
						|
    Complist l1;
 | 
						|
    char *str;
 | 
						|
    int compflags = gf_noglobdots ? (PAT_FILE|PAT_NOGLD) : PAT_FILE;
 | 
						|
 | 
						|
    if (instr[0] == Star && instr[1] == Star) {
 | 
						|
	int shortglob = 0;
 | 
						|
        if (instr[2] == '/' || (instr[2] == Star && instr[3] == '/')
 | 
						|
	    || (shortglob = isset(GLOBSTARSHORT))) {
 | 
						|
	    /* Match any number of directories. */
 | 
						|
	    int follow;
 | 
						|
 | 
						|
	    /* with three stars, follow symbolic links */
 | 
						|
	    follow = (instr[2] == Star);
 | 
						|
	    /*
 | 
						|
	     * With GLOBSTARSHORT, leave a star in place for the
 | 
						|
	     * pattern inside the directory.
 | 
						|
	     */
 | 
						|
	    instr += ((shortglob ? 1 : 3) + follow);
 | 
						|
 | 
						|
	    /* Now get the next path component if there is one. */
 | 
						|
	    l1 = (Complist) zhalloc(sizeof *l1);
 | 
						|
	    if ((l1->next = parsecomplist(instr)) == NULL) {
 | 
						|
		errflag |= ERRFLAG_ERROR;
 | 
						|
		return NULL;
 | 
						|
	    }
 | 
						|
	    l1->pat = patcompile(NULL, compflags | PAT_ANY, NULL);
 | 
						|
	    l1->closure = 1;		/* ...zero or more times. */
 | 
						|
	    l1->follow = follow;
 | 
						|
	    return l1;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    /* Parse repeated directories such as (dir/)# and (dir/)## */
 | 
						|
    if (*(str = instr) == zpc_special[ZPC_INPAR] &&
 | 
						|
	!skipparens(Inpar, Outpar, (char **)&str) &&
 | 
						|
        *str == zpc_special[ZPC_HASH] && str[-2] == '/') {
 | 
						|
	instr++;
 | 
						|
	if (!(p1 = patcompile(instr, compflags, &instr)))
 | 
						|
	    return NULL;
 | 
						|
	if (instr[0] == '/' && instr[1] == Outpar && instr[2] == Pound) {
 | 
						|
	    int pdflag = 0;
 | 
						|
 | 
						|
	    instr += 3;
 | 
						|
	    if (*instr == Pound) {
 | 
						|
		pdflag = 1;
 | 
						|
		instr++;
 | 
						|
	    }
 | 
						|
	    l1 = (Complist) zhalloc(sizeof *l1);
 | 
						|
	    l1->pat = p1;
 | 
						|
	    /* special case (/)# to avoid infinite recursion */
 | 
						|
	    l1->closure = (*((char *)p1 + p1->startoff)) ? 1 + pdflag : 0;
 | 
						|
	    l1->follow = 0;
 | 
						|
	    l1->next = parsecomplist(instr);
 | 
						|
	    return (l1->pat) ? l1 : NULL;
 | 
						|
	}
 | 
						|
    } else {
 | 
						|
	/* parse single path component */
 | 
						|
	if (!(p1 = patcompile(instr, compflags|PAT_FILET, &instr)))
 | 
						|
	    return NULL;
 | 
						|
	/* then do the remaining path components */
 | 
						|
	if (*instr == '/' || !*instr) {
 | 
						|
	    int ef = *instr == '/';
 | 
						|
 | 
						|
	    l1 = (Complist) zhalloc(sizeof *l1);
 | 
						|
	    l1->pat = p1;
 | 
						|
	    l1->closure = 0;
 | 
						|
	    l1->next = ef ? parsecomplist(instr+1) : NULL;
 | 
						|
	    return (ef && !l1->next) ? NULL : l1;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    errflag |= ERRFLAG_ERROR;
 | 
						|
    return NULL;
 | 
						|
}
 | 
						|
 | 
						|
/* turn a string into a Complist struct:  this has path components */
 | 
						|
 | 
						|
/**/
 | 
						|
static Complist
 | 
						|
parsepat(char *str)
 | 
						|
{
 | 
						|
    long assert;
 | 
						|
    int ignore;
 | 
						|
 | 
						|
    patcompstart();
 | 
						|
    /*
 | 
						|
     * Check for initial globbing flags, so that they don't form
 | 
						|
     * a bogus path component.
 | 
						|
     */
 | 
						|
    if ((*str == zpc_special[ZPC_INPAR] && str[1] == zpc_special[ZPC_HASH]) ||
 | 
						|
	(*str == zpc_special[ZPC_KSH_AT] && str[1] == Inpar &&
 | 
						|
	 str[2] == zpc_special[ZPC_HASH])) {
 | 
						|
	str += (*str == Inpar) ? 2 : 3;
 | 
						|
	if (!patgetglobflags(&str, &assert, &ignore))
 | 
						|
	    return NULL;
 | 
						|
    }
 | 
						|
 | 
						|
    /* Now there is no (#X) in front, we can check the path. */
 | 
						|
    if (!pathbuf)
 | 
						|
	pathbuf = zalloc(pathbufsz = PATH_MAX+1);
 | 
						|
    DPUTS(pathbufcwd, "BUG: glob changed directory");
 | 
						|
    if (*str == '/') {		/* pattern has absolute path */
 | 
						|
	str++;
 | 
						|
	pathbuf[0] = '/';
 | 
						|
	pathbuf[pathpos = 1] = '\0';
 | 
						|
    } else			/* pattern is relative to pwd */
 | 
						|
	pathbuf[pathpos = 0] = '\0';
 | 
						|
 | 
						|
    return parsecomplist(str);
 | 
						|
}
 | 
						|
 | 
						|
/* get number after qualifier */
 | 
						|
 | 
						|
/**/
 | 
						|
static off_t
 | 
						|
qgetnum(char **s)
 | 
						|
{
 | 
						|
    off_t v = 0;
 | 
						|
 | 
						|
    if (!idigit(**s)) {
 | 
						|
	zerr("number expected");
 | 
						|
	return 0;
 | 
						|
    }
 | 
						|
    while (idigit(**s))
 | 
						|
	v = v * 10 + *(*s)++ - '0';
 | 
						|
    return v;
 | 
						|
}
 | 
						|
 | 
						|
/* get mode spec after qualifier */
 | 
						|
 | 
						|
/**/
 | 
						|
static zlong
 | 
						|
qgetmodespec(char **s)
 | 
						|
{
 | 
						|
    zlong yes = 0, no = 0, val, mask, t;
 | 
						|
    char *p = *s, c, how, end;
 | 
						|
 | 
						|
    if ((c = *p) == '=' || c == Equals || c == '+' || c == '-' ||
 | 
						|
	c == '?' || c == Quest || (c >= '0' && c <= '7')) {
 | 
						|
	end = 0;
 | 
						|
	c = 0;
 | 
						|
    } else {
 | 
						|
	end = (c == '<' ? '>' :
 | 
						|
	       (c == '[' ? ']' :
 | 
						|
		(c == '{' ? '}' :
 | 
						|
		 (c == Inang ? Outang :
 | 
						|
		  (c == Inbrack ? Outbrack :
 | 
						|
		   (c == Inbrace ? Outbrace : c))))));
 | 
						|
	p++;
 | 
						|
    }
 | 
						|
    do {
 | 
						|
	mask = 0;
 | 
						|
	while (((c = *p) == 'u' || c == 'g' || c == 'o' || c == 'a') && end) {
 | 
						|
	    switch (c) {
 | 
						|
	    case 'o': mask |= 01007; break;
 | 
						|
	    case 'g': mask |= 02070; break;
 | 
						|
	    case 'u': mask |= 04700; break;
 | 
						|
	    case 'a': mask |= 07777; break;
 | 
						|
	    }
 | 
						|
	    p++;
 | 
						|
	}
 | 
						|
	how = ((c == '+' || c == '-') ? c : '=');
 | 
						|
	if (c == '+' || c == '-' || c == '=' || c == Equals)
 | 
						|
	    p++;
 | 
						|
	val = 0;
 | 
						|
	if (mask) {
 | 
						|
	    while ((c = *p++) != ',' && c != end) {
 | 
						|
		switch (c) {
 | 
						|
		case 'x': val |= 00111; break;
 | 
						|
		case 'w': val |= 00222; break;
 | 
						|
		case 'r': val |= 00444; break;
 | 
						|
		case 's': val |= 06000; break;
 | 
						|
		case 't': val |= 01000; break;
 | 
						|
		case '0': case '1': case '2': case '3':
 | 
						|
		case '4': case '5': case '6': case '7':
 | 
						|
		    t = ((zlong) c - '0');
 | 
						|
		    val |= t | (t << 3) | (t << 6);
 | 
						|
		    break;
 | 
						|
		default:
 | 
						|
		    zerr("invalid mode specification");
 | 
						|
		    return 0;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    if (how == '=' || how == '+') {
 | 
						|
		yes |= val & mask;
 | 
						|
		val = ~val;
 | 
						|
	    }
 | 
						|
	    if (how == '=' || how == '-')
 | 
						|
		no |= val & mask;
 | 
						|
	} else if (!(end && c == end) && c != ',' && c) {
 | 
						|
	    t = 07777;
 | 
						|
	    while ((c = *p) == '?' || c == Quest ||
 | 
						|
		   (c >= '0' && c <= '7')) {
 | 
						|
		if (c == '?' || c == Quest) {
 | 
						|
		    t = (t << 3) | 7;
 | 
						|
		    val <<= 3;
 | 
						|
		} else {
 | 
						|
		    t <<= 3;
 | 
						|
		    val = (val << 3) | ((zlong) c - '0');
 | 
						|
		}
 | 
						|
		p++;
 | 
						|
	    }
 | 
						|
	    if (end && c != end && c != ',') {
 | 
						|
		zerr("invalid mode specification");
 | 
						|
		return 0;
 | 
						|
	    }
 | 
						|
	    if (how == '=') {
 | 
						|
		yes = (yes & ~t) | val;
 | 
						|
		no = (no & ~t) | (~val & ~t);
 | 
						|
	    } else if (how == '+')
 | 
						|
		yes |= val;
 | 
						|
	    else
 | 
						|
		no |= val;
 | 
						|
	} else {
 | 
						|
	    zerr("invalid mode specification");
 | 
						|
	    return 0;
 | 
						|
        }
 | 
						|
    } while (end && c != end);
 | 
						|
 | 
						|
    *s = p;
 | 
						|
    return ((yes & 07777) | ((no & 07777) << 12));
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
gmatchcmp(Gmatch a, Gmatch b)
 | 
						|
{
 | 
						|
    int i;
 | 
						|
    off_t r = 0L;
 | 
						|
    struct globsort *s;
 | 
						|
    char **asortstrp = NULL, **bsortstrp = NULL;
 | 
						|
 | 
						|
    for (i = gf_nsorts, s = gf_sortlist; i; i--, s++) {
 | 
						|
	switch (s->tp & ~GS_DESC) {
 | 
						|
	case GS_NAME:
 | 
						|
	    r = zstrcmp(b->uname, a->uname,
 | 
						|
			gf_numsort ? SORTIT_NUMERICALLY : 0);
 | 
						|
	    break;
 | 
						|
	case GS_DEPTH:
 | 
						|
	    {
 | 
						|
		char *aptr = a->name, *bptr = b->name;
 | 
						|
		int slasha = 0, slashb = 0;
 | 
						|
		/* Count slashes.  Trailing slashes don't count. */
 | 
						|
		while (*aptr && *aptr == *bptr)
 | 
						|
		    aptr++, bptr++;
 | 
						|
		/* Like I just said... */
 | 
						|
		if ((!*aptr || !*bptr) && aptr > a->name && aptr[-1] == '/')
 | 
						|
		    aptr--, bptr--;
 | 
						|
		if (*aptr)
 | 
						|
		    for (; aptr[1]; aptr++)
 | 
						|
			if (*aptr == '/') {
 | 
						|
			    slasha = 1;
 | 
						|
			    break;
 | 
						|
			}
 | 
						|
		if (*bptr)
 | 
						|
		    for (; bptr[1]; bptr++)
 | 
						|
			if (*bptr == '/') {
 | 
						|
			    slashb = 1;
 | 
						|
			    break;
 | 
						|
			}
 | 
						|
		r = slasha - slashb;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
	case GS_EXEC:
 | 
						|
	    if (!asortstrp) {
 | 
						|
		asortstrp = a->sortstrs;
 | 
						|
		bsortstrp = b->sortstrs;
 | 
						|
	    } else {
 | 
						|
		asortstrp++;
 | 
						|
		bsortstrp++;
 | 
						|
	    }
 | 
						|
	    r = zstrcmp(*bsortstrp, *asortstrp,
 | 
						|
			gf_numsort ? SORTIT_NUMERICALLY : 0);
 | 
						|
	    break;
 | 
						|
	case GS_SIZE:
 | 
						|
	    r = b->size - a->size;
 | 
						|
	    break;
 | 
						|
	case GS_ATIME:
 | 
						|
	    r = a->atime - b->atime;
 | 
						|
#ifdef GET_ST_ATIME_NSEC
 | 
						|
            if (!r)
 | 
						|
              r = a->ansec - b->ansec;
 | 
						|
#endif
 | 
						|
	    break;
 | 
						|
	case GS_MTIME:
 | 
						|
	    r = a->mtime - b->mtime;
 | 
						|
#ifdef GET_ST_MTIME_NSEC
 | 
						|
            if (!r)
 | 
						|
              r = a->mnsec - b->mnsec;
 | 
						|
#endif
 | 
						|
	    break;
 | 
						|
	case GS_CTIME:
 | 
						|
	    r = a->ctime - b->ctime;
 | 
						|
#ifdef GET_ST_CTIME_NSEC
 | 
						|
            if (!r)
 | 
						|
              r = a->cnsec - b->cnsec;
 | 
						|
#endif
 | 
						|
	    break;
 | 
						|
	case GS_LINKS:
 | 
						|
	    r = b->links - a->links;
 | 
						|
	    break;
 | 
						|
	case GS__SIZE:
 | 
						|
	    r = b->_size - a->_size;
 | 
						|
	    break;
 | 
						|
	case GS__ATIME:
 | 
						|
	    r = a->_atime - b->_atime;
 | 
						|
#ifdef GET_ST_ATIME_NSEC
 | 
						|
            if (!r)
 | 
						|
              r = a->_ansec - b->_ansec;
 | 
						|
#endif
 | 
						|
	    break;
 | 
						|
	case GS__MTIME:
 | 
						|
	    r = a->_mtime - b->_mtime;
 | 
						|
#ifdef GET_ST_MTIME_NSEC
 | 
						|
            if (!r)
 | 
						|
              r = a->_mnsec - b->_mnsec;
 | 
						|
#endif
 | 
						|
	    break;
 | 
						|
	case GS__CTIME:
 | 
						|
	    r = a->_ctime - b->_ctime;
 | 
						|
#ifdef GET_ST_CTIME_NSEC
 | 
						|
            if (!r)
 | 
						|
              r = a->_cnsec - b->_cnsec;
 | 
						|
#endif
 | 
						|
	    break;
 | 
						|
	case GS__LINKS:
 | 
						|
	    r = b->_links - a->_links;
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
	if (r)
 | 
						|
	    return (s->tp & GS_DESC) ?
 | 
						|
	      (r < 0L ? 1 : -1) :
 | 
						|
	      (r > 0L ? 1 : -1);
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Duplicate a list of qualifiers using the `next' linkage (not the
 | 
						|
 * `or' linkage).  Return the head element and set *last (if last non-NULL)
 | 
						|
 * to point to the last element of the new list.  All allocation is on the
 | 
						|
 * heap (or off the heap?)
 | 
						|
 */
 | 
						|
static struct qual *dup_qual_list(struct qual *orig, struct qual **lastp)
 | 
						|
{
 | 
						|
    struct qual *qfirst = NULL, *qlast = NULL;
 | 
						|
 | 
						|
    while (orig) {
 | 
						|
	struct qual *qnew = (struct qual *)zhalloc(sizeof(struct qual));
 | 
						|
	*qnew = *orig;
 | 
						|
	qnew->next = qnew->or = NULL;
 | 
						|
 | 
						|
	if (!qfirst)
 | 
						|
	    qfirst = qnew;
 | 
						|
	if (qlast)
 | 
						|
	    qlast->next = qnew;
 | 
						|
	qlast = qnew;
 | 
						|
 | 
						|
	orig = orig->next;
 | 
						|
    }
 | 
						|
 | 
						|
    if (lastp)
 | 
						|
	*lastp = qlast;
 | 
						|
    return qfirst;
 | 
						|
}
 | 
						|
 | 
						|
 | 
						|
/*
 | 
						|
 * Get a glob string for execution, following e, P or + qualifiers.
 | 
						|
 * Pointer is character after the e, P or +.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
static char *
 | 
						|
glob_exec_string(char **sp)
 | 
						|
{
 | 
						|
    char sav, *tt, *sdata, *s = *sp;
 | 
						|
    int plus;
 | 
						|
 | 
						|
    if (s[-1] == '+') {
 | 
						|
	plus = 0;
 | 
						|
	tt = itype_end(s, IIDENT, 0);
 | 
						|
	if (tt == s)
 | 
						|
	{
 | 
						|
	    zerr("missing identifier after `+'");
 | 
						|
	    return NULL;
 | 
						|
	}
 | 
						|
    } else {
 | 
						|
	tt = get_strarg(s, &plus);
 | 
						|
	if (!*tt)
 | 
						|
	{
 | 
						|
	    zerr("missing end of string");
 | 
						|
	    return NULL;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    sav = *tt;
 | 
						|
    *tt = '\0';
 | 
						|
    sdata = dupstring(s + plus);
 | 
						|
    untokenize(sdata);
 | 
						|
    *tt = sav;
 | 
						|
    if (sav)
 | 
						|
	*sp = tt + plus;
 | 
						|
    else
 | 
						|
	*sp = tt;
 | 
						|
 | 
						|
    return sdata;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Insert a glob match.
 | 
						|
 * If there were words to prepend given by the P glob qualifier, do so.
 | 
						|
 */
 | 
						|
static void
 | 
						|
insert_glob_match(LinkList list, LinkNode next, char *data)
 | 
						|
{
 | 
						|
    if (gf_pre_words) {
 | 
						|
	LinkNode added;
 | 
						|
	for (added = firstnode(gf_pre_words); added; incnode(added)) {
 | 
						|
	    next = insertlinknode(list, next, dupstring(getdata(added)));
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    next = insertlinknode(list, next, data);
 | 
						|
 | 
						|
    if (gf_post_words) {
 | 
						|
	LinkNode added;
 | 
						|
	for (added = firstnode(gf_post_words); added; incnode(added)) {
 | 
						|
	    next = insertlinknode(list, next, dupstring(getdata(added)));
 | 
						|
	}
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Return
 | 
						|
 *   1 if str ends in bare glob qualifiers
 | 
						|
 *   2 if str ends in non-bare glob qualifiers (#q)
 | 
						|
 *   0 otherwise.
 | 
						|
 *
 | 
						|
 * str is the string to check.
 | 
						|
 * sl is its length (to avoid recalculation).
 | 
						|
 * nobareglob is 1 if bare glob qualifiers are not allowed.
 | 
						|
 * *sp, if sp is not null, will be a pointer to the opening parenthesis.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
int
 | 
						|
checkglobqual(char *str, int sl, int nobareglob, char **sp)
 | 
						|
{
 | 
						|
    char *s;
 | 
						|
    int paren, ret = 1;
 | 
						|
 | 
						|
    if (str[sl - 1] != Outpar)
 | 
						|
	return 0;
 | 
						|
 | 
						|
    /* Check these are really qualifiers, not a set of *
 | 
						|
     * alternatives or exclusions.  We can be more     *
 | 
						|
     * lenient with an explicit (#q) than with a bare  *
 | 
						|
     * set of qualifiers.                              */
 | 
						|
    paren = 0;
 | 
						|
    for (s = str + sl - 2; *s && (*s != Inpar || paren); s--) {
 | 
						|
	switch (*s) {
 | 
						|
	case Outpar:
 | 
						|
	    paren++; /*FALLTHROUGH*/
 | 
						|
	case Bar:
 | 
						|
	    if (!zpc_disables[ZPC_BAR])
 | 
						|
		nobareglob = 1;
 | 
						|
	    break;
 | 
						|
	case Tilde:
 | 
						|
	    if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_TILDE])
 | 
						|
		nobareglob = 1;
 | 
						|
	    break;
 | 
						|
	case Inpar:
 | 
						|
	    paren--;
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
	if (s == str)
 | 
						|
	    break;
 | 
						|
    }
 | 
						|
    if (*s != Inpar)
 | 
						|
	return 0;
 | 
						|
    if (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH] && s[1] == Pound) {
 | 
						|
	if (s[2] != 'q')
 | 
						|
	    return 0;
 | 
						|
	ret = 2;
 | 
						|
    } else if (nobareglob)
 | 
						|
	return 0;
 | 
						|
 | 
						|
    if (sp)
 | 
						|
	*sp = s;
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* Main entry point to the globbing code for filename globbing. *
 | 
						|
 * np points to a node in the list which will be expanded  *
 | 
						|
 * into a series of nodes.                                      */
 | 
						|
 | 
						|
/**/
 | 
						|
void
 | 
						|
zglob(LinkList list, LinkNode np, int nountok)
 | 
						|
{
 | 
						|
    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                   */
 | 
						|
    int first = 0, end = -1;		/* index of first match to return */
 | 
						|
					/* and index+1 of the last match */
 | 
						|
    struct globdata saved;		/* saved glob state              */
 | 
						|
    int nobareglob = !isset(BAREGLOBQUAL);
 | 
						|
    int shortcircuit = 0;		/* How many files to match;      */
 | 
						|
					/* 0 means no limit              */
 | 
						|
 | 
						|
    if (unset(GLOBOPT) || !haswilds(ostr) || unset(EXECOPT)) {
 | 
						|
	if (!nountok)
 | 
						|
	    untokenize(ostr);
 | 
						|
	return;
 | 
						|
    }
 | 
						|
    save_globstate(saved);
 | 
						|
 | 
						|
    str = dupstring(ostr);
 | 
						|
    uremnode(list, np);
 | 
						|
 | 
						|
    /* quals will hold the complete list of qualifiers (file static). */
 | 
						|
    quals = NULL;
 | 
						|
    /*
 | 
						|
     * qualct and qualorct indicate we have qualifiers in the last
 | 
						|
     * alternative, or a set of alternatives, respectively.  They
 | 
						|
     * are not necessarily an accurate count, however.
 | 
						|
     */
 | 
						|
    qualct = qualorct = 0;
 | 
						|
    /*
 | 
						|
     * colonmod is a concatenated list of all colon modifiers found in
 | 
						|
     * all sets of qualifiers.
 | 
						|
     */
 | 
						|
    colonmod = NULL;
 | 
						|
    /* The gf_* flags are qualifiers which are applied globally. */
 | 
						|
    gf_nullglob = isset(NULLGLOB);
 | 
						|
    gf_markdirs = isset(MARKDIRS);
 | 
						|
    gf_listtypes = gf_follow = 0;
 | 
						|
    gf_noglobdots = unset(GLOBDOTS);
 | 
						|
    gf_numsort = isset(NUMERICGLOBSORT);
 | 
						|
    gf_sorts = gf_nsorts = 0;
 | 
						|
    gf_pre_words = gf_post_words = NULL;
 | 
						|
 | 
						|
    /* Check for qualifiers */
 | 
						|
    while (!nobareglob ||
 | 
						|
	   (isset(EXTENDEDGLOB) && !zpc_disables[ZPC_HASH])) {
 | 
						|
	struct qual *newquals;
 | 
						|
	char *s;
 | 
						|
	int sense, qualsfound;
 | 
						|
	off_t data;
 | 
						|
	char *sdata, *newcolonmod, *ptr;
 | 
						|
	int (*func) _((char *, Statptr, off_t, char *));
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Initialise state variables for current file pattern.
 | 
						|
	 * newquals is the root for the linked list of all qualifiers.
 | 
						|
	 * qo is the root of the current list of alternatives.
 | 
						|
	 * ql is the end of the current alternative where the `next' will go.
 | 
						|
	 * qn is the current qualifier node to be added.
 | 
						|
	 *
 | 
						|
	 * Here is an attempt at a diagram.  An `or' is added horizontally
 | 
						|
	 * to the top line, a `next' at the bottom of the right hand line.
 | 
						|
	 * `qn' is usually NULL unless a new `or' has just been added.
 | 
						|
	 *
 | 
						|
	 * quals -> x  -> x -> qo
 | 
						|
	 *          |     |    |
 | 
						|
	 *          x     x    x
 | 
						|
	 *          |          |
 | 
						|
	 *          x          ql
 | 
						|
	 *
 | 
						|
	 * In fact, after each loop the complete set is in the file static
 | 
						|
	 * `quals'.  Then, if we have a second set of qualifiers, we merge
 | 
						|
	 * the lists together.  This is only tricky if one or both have an
 | 
						|
	 * `or' in them; then we need to distribute over all alternatives.
 | 
						|
	 */
 | 
						|
	newquals = qo = qn = ql = NULL;
 | 
						|
 | 
						|
	sl = strlen(str);
 | 
						|
	if (!(qualsfound = checkglobqual(str, sl, nobareglob, &s)))
 | 
						|
	    break;
 | 
						|
 | 
						|
	/* Real qualifiers found. */
 | 
						|
	nobareglob = 1;
 | 
						|
	sense = 0;	   /* bit 0 for match (0)/don't match (1)   */
 | 
						|
			   /* bit 1 for follow links (2), don't (0) */
 | 
						|
	data = 0;	   /* Any numerical argument required       */
 | 
						|
	sdata = NULL;	   /* Any list argument required            */
 | 
						|
	newcolonmod = NULL; /* Contains trailing colon modifiers    */
 | 
						|
 | 
						|
	str[sl-1] = 0;
 | 
						|
	*s++ = 0;
 | 
						|
	if (qualsfound == 2)
 | 
						|
	    s += 2;
 | 
						|
	for (ptr = s; *ptr; ptr++)
 | 
						|
	    if (*ptr == Dash)
 | 
						|
		*ptr = '-';
 | 
						|
	while (*s && !newcolonmod) {
 | 
						|
	    func = (int (*) _((char *, Statptr, off_t, char *)))0;
 | 
						|
	    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. */
 | 
						|
		    newcolonmod = s - 1;
 | 
						|
		    untokenize(newcolonmod);
 | 
						|
		    if (colonmod) {
 | 
						|
			/* remember we're searching backwards */
 | 
						|
			colonmod = dyncat(newcolonmod, colonmod);
 | 
						|
		    } else
 | 
						|
			colonmod = newcolonmod;
 | 
						|
		    break;
 | 
						|
		case Hat:
 | 
						|
		case '^':
 | 
						|
		    /* Toggle sense:  go from positive to *
 | 
						|
		     * negative match and vice versa.     */
 | 
						|
		    sense ^= 1;
 | 
						|
		    break;
 | 
						|
		case '-':
 | 
						|
		case Dash:
 | 
						|
		    /* Toggle matching of symbolic links */
 | 
						|
		    sense ^= 2;
 | 
						|
		    break;
 | 
						|
		case '@':
 | 
						|
		    /* Match symbolic links */
 | 
						|
		    func = qualislnk;
 | 
						|
		    break;
 | 
						|
		case Equals:
 | 
						|
		case '=':
 | 
						|
		    /* Match sockets */
 | 
						|
		    func = qualissock;
 | 
						|
		    break;
 | 
						|
		case 'p':
 | 
						|
		    /* Match named pipes */
 | 
						|
		    func = qualisfifo;
 | 
						|
		    break;
 | 
						|
		case '/':
 | 
						|
		    /* Match directories */
 | 
						|
		    func = qualisdir;
 | 
						|
		    break;
 | 
						|
		case '.':
 | 
						|
		    /* Match regular files */
 | 
						|
		    func = qualisreg;
 | 
						|
		    break;
 | 
						|
		case '%':
 | 
						|
		    /* Match special files: block, *
 | 
						|
		     * character or any device     */
 | 
						|
		    if (*s == 'b')
 | 
						|
			s++, func = qualisblk;
 | 
						|
		    else if (*s == 'c')
 | 
						|
			s++, func = qualischr;
 | 
						|
		    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;
 | 
						|
		    g_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 */
 | 
						|
			char sav, *tt;
 | 
						|
			int arglen;
 | 
						|
 | 
						|
			/* Find matching delimiters */
 | 
						|
			tt = get_strarg(s, &arglen);
 | 
						|
			if (!*tt) {
 | 
						|
			    zerr("missing delimiter for 'u' glob qualifier");
 | 
						|
			    data = 0;
 | 
						|
			} else {
 | 
						|
#ifdef USE_GETPWNAM
 | 
						|
			    struct passwd *pw;
 | 
						|
			    sav = *tt;
 | 
						|
			    *tt = '\0';
 | 
						|
 | 
						|
			    if ((pw = getpwnam(s + arglen)))
 | 
						|
				data = pw->pw_uid;
 | 
						|
			    else {
 | 
						|
				zerr("unknown username '%s'", s + arglen);
 | 
						|
				data = 0;
 | 
						|
			    }
 | 
						|
			    *tt = sav;
 | 
						|
#else /* !USE_GETPWNAM */
 | 
						|
			    sav = *tt;
 | 
						|
			    *tt = '\0';
 | 
						|
			    zerr("unable to resolve non-numeric username '%s'", s + arglen);
 | 
						|
			    *tt = sav;
 | 
						|
			    data = 0;
 | 
						|
#endif /* !USE_GETPWNAM */
 | 
						|
			    if (sav)
 | 
						|
				s = tt + arglen;
 | 
						|
			    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. */
 | 
						|
			char sav, *tt;
 | 
						|
			int arglen;
 | 
						|
 | 
						|
			tt = get_strarg(s, &arglen);
 | 
						|
			if (!*tt) {
 | 
						|
			    zerr("missing delimiter for 'g' glob qualifier");
 | 
						|
			    data = 0;
 | 
						|
			} else {
 | 
						|
#ifdef USE_GETGRNAM
 | 
						|
			    struct group *gr;
 | 
						|
			    sav = *tt;
 | 
						|
			    *tt = '\0';
 | 
						|
 | 
						|
			    if ((gr = getgrnam(s + arglen)))
 | 
						|
				data = gr->gr_gid;
 | 
						|
			    else {
 | 
						|
				zerr("unknown group");
 | 
						|
				data = 0;
 | 
						|
			    }
 | 
						|
			    *tt = sav;
 | 
						|
#else /* !USE_GETGRNAM */
 | 
						|
			    sav = *tt;
 | 
						|
			    zerr("unknown group");
 | 
						|
			    data = 0;
 | 
						|
#endif /* !USE_GETGRNAM */
 | 
						|
			    if (sav)
 | 
						|
				s = tt + arglen;
 | 
						|
			    else
 | 
						|
				s = tt;
 | 
						|
			}
 | 
						|
		    }
 | 
						|
		    break;
 | 
						|
		case 'f':
 | 
						|
		    /* Match modes with chmod-spec. */
 | 
						|
		    func = qualmodeflags;
 | 
						|
		    data = qgetmodespec(&s);
 | 
						|
		    break;
 | 
						|
		case 'F':
 | 
						|
		    func = qualnonemptydir;
 | 
						|
		    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 'n':
 | 
						|
		    /* Numeric glob sort */
 | 
						|
		    gf_numsort = !(sense & 1);
 | 
						|
		    break;
 | 
						|
		case 'Y':
 | 
						|
		{
 | 
						|
		    /* Short circuit: limit number of matches */
 | 
						|
		    const char *s_saved = s;
 | 
						|
		    shortcircuit = !(sense & 1);
 | 
						|
		    if (shortcircuit) {
 | 
						|
			/* Parse the argument. */
 | 
						|
			data = qgetnum(&s);
 | 
						|
			if ((shortcircuit = data) != data) {
 | 
						|
			    /* Integer overflow */
 | 
						|
			    zerr("value too big: Y%s", s_saved);
 | 
						|
			    restore_globstate(saved);
 | 
						|
			    return;
 | 
						|
			}
 | 
						|
		    }
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
		case 'a':
 | 
						|
		    /* Access time in given range */
 | 
						|
		    g_amc = 0;
 | 
						|
		    func = qualtime;
 | 
						|
		    goto getrange;
 | 
						|
		case 'm':
 | 
						|
		    /* Modification time in given range */
 | 
						|
		    g_amc = 1;
 | 
						|
		    func = qualtime;
 | 
						|
		    goto getrange;
 | 
						|
		case 'c':
 | 
						|
		    /* Inode creation time in given range */
 | 
						|
		    g_amc = 2;
 | 
						|
		    func = qualtime;
 | 
						|
		    goto getrange;
 | 
						|
		case 'L':
 | 
						|
		    /* File size (Length) in given range */
 | 
						|
		    func = qualsize;
 | 
						|
		    g_amc = -1;
 | 
						|
		    /* Get size multiplier */
 | 
						|
		    g_units = TT_BYTES;
 | 
						|
		    if (*s == 'p' || *s == 'P')
 | 
						|
			g_units = TT_POSIX_BLOCKS, ++s;
 | 
						|
		    else if (*s == 'k' || *s == 'K')
 | 
						|
			g_units = TT_KILOBYTES, ++s;
 | 
						|
		    else if (*s == 'm' || *s == 'M')
 | 
						|
			g_units = TT_MEGABYTES, ++s;
 | 
						|
#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT)
 | 
						|
                    else if (*s == 'g' || *s == 'G')
 | 
						|
                        g_units = TT_GIGABYTES, ++s;
 | 
						|
                    else if (*s == 't' || *s == 'T')
 | 
						|
                        g_units = TT_TERABYTES, ++s;
 | 
						|
#endif
 | 
						|
		  getrange:
 | 
						|
		    /* Get time multiplier */
 | 
						|
		    if (g_amc >= 0) {
 | 
						|
			g_units = TT_DAYS;
 | 
						|
			if (*s == 'h')
 | 
						|
			    g_units = TT_HOURS, ++s;
 | 
						|
			else if (*s == 'm')
 | 
						|
			    g_units = TT_MINS, ++s;
 | 
						|
			else if (*s == 'w')
 | 
						|
			    g_units = TT_WEEKS, ++s;
 | 
						|
			else if (*s == 'M')
 | 
						|
			    g_units = TT_MONTHS, ++s;
 | 
						|
			else if (*s == 's')
 | 
						|
			    g_units = TT_SECONDS, ++s;
 | 
						|
			else if (*s == 'd')
 | 
						|
			    ++s;
 | 
						|
		    }
 | 
						|
		    /* See if it's greater than, equal to, or less than */
 | 
						|
		    if ((g_range = *s == '+' ? 1 : IS_DASH(*s) ? -1 : 0))
 | 
						|
			++s;
 | 
						|
		    data = qgetnum(&s);
 | 
						|
		    break;
 | 
						|
 | 
						|
		case 'o':
 | 
						|
		case 'O':
 | 
						|
		{
 | 
						|
		    int t;
 | 
						|
		    char *send;
 | 
						|
 | 
						|
		    if (gf_nsorts == MAX_SORTS) {
 | 
						|
			zerr("too many glob sort specifiers");
 | 
						|
			restore_globstate(saved);
 | 
						|
			return;
 | 
						|
		    }
 | 
						|
 | 
						|
		    /* usually just one character */
 | 
						|
		    send = s+1;
 | 
						|
		    switch (*s) {
 | 
						|
		    case 'n': t = GS_NAME; break;
 | 
						|
		    case 'L': t = GS_SIZE; break;
 | 
						|
		    case 'l': t = GS_LINKS; break;
 | 
						|
		    case 'a': t = GS_ATIME; break;
 | 
						|
		    case 'm': t = GS_MTIME; break;
 | 
						|
		    case 'c': t = GS_CTIME; break;
 | 
						|
		    case 'd': t = GS_DEPTH; break;
 | 
						|
		    case 'N': t = GS_NONE; break;
 | 
						|
		    case 'e':
 | 
						|
		    case '+':
 | 
						|
		    {
 | 
						|
			t = GS_EXEC;
 | 
						|
			if ((gf_sortlist[gf_nsorts].exec =
 | 
						|
			     glob_exec_string(&send)) == NULL)
 | 
						|
			{
 | 
						|
			    restore_globstate(saved);
 | 
						|
			    return;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    }
 | 
						|
		    default:
 | 
						|
			zerr("unknown sort specifier");
 | 
						|
			restore_globstate(saved);
 | 
						|
			return;
 | 
						|
		    }
 | 
						|
		    if ((sense & 2) &&
 | 
						|
			(t & (GS_SIZE|GS_ATIME|GS_MTIME|GS_CTIME|GS_LINKS)))
 | 
						|
			t <<= GS_SHIFT; /* HERE: GS_EXEC? */
 | 
						|
		    if (t != GS_EXEC) {
 | 
						|
			if (gf_sorts & t) {
 | 
						|
			    zerr("doubled sort specifier");
 | 
						|
			    restore_globstate(saved);
 | 
						|
			    return;
 | 
						|
			}
 | 
						|
		    }
 | 
						|
		    gf_sorts |= t;
 | 
						|
		    gf_sortlist[gf_nsorts++].tp = t |
 | 
						|
			(((sense & 1) ^ (s[-1] == 'O')) ? GS_DESC : 0);
 | 
						|
		    s = send;
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
		case '+':
 | 
						|
		case 'e':
 | 
						|
		{
 | 
						|
		    char *tt;
 | 
						|
 | 
						|
		    tt = glob_exec_string(&s);
 | 
						|
 | 
						|
		    if (tt == NULL) {
 | 
						|
			data = 0;
 | 
						|
		    } else {
 | 
						|
			func = qualsheval;
 | 
						|
			sdata = tt;
 | 
						|
		    }
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
		case '[':
 | 
						|
		case Inbrack:
 | 
						|
		{
 | 
						|
		    char *os = --s;
 | 
						|
		    struct value v;
 | 
						|
 | 
						|
		    v.isarr = SCANPM_WANTVALS;
 | 
						|
		    v.pm = NULL;
 | 
						|
		    v.end = -1;
 | 
						|
		    v.flags = 0;
 | 
						|
		    if (getindex(&s, &v, 0) || s == os) {
 | 
						|
			zerr("invalid subscript");
 | 
						|
			restore_globstate(saved);
 | 
						|
			return;
 | 
						|
		    }
 | 
						|
		    first = v.start;
 | 
						|
		    end = v.end;
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
		case 'P':
 | 
						|
		{
 | 
						|
		    char *tt;
 | 
						|
		    tt = glob_exec_string(&s);
 | 
						|
 | 
						|
		    if (tt != NULL)
 | 
						|
		    {
 | 
						|
			LinkList *words = sense & 1 ? &gf_post_words : &gf_pre_words;
 | 
						|
			if (!*words)
 | 
						|
			    *words = newlinklist();
 | 
						|
			addlinknode(*words, tt);
 | 
						|
		    }
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
		default:
 | 
						|
		    untokenize(--s);
 | 
						|
		    zerr("unknown file attribute: %c", *s);
 | 
						|
		    restore_globstate(saved);
 | 
						|
		    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 (!newquals)
 | 
						|
		    newquals = qo = qn;
 | 
						|
		qn->func = func;
 | 
						|
		qn->sense = sense;
 | 
						|
		qn->data = data;
 | 
						|
		qn->sdata = sdata;
 | 
						|
		qn->range = g_range;
 | 
						|
		qn->units = g_units;
 | 
						|
		qn->amc = g_amc;
 | 
						|
 | 
						|
		qn = NULL;
 | 
						|
		qualct++;
 | 
						|
	    }
 | 
						|
	    if (errflag) {
 | 
						|
		restore_globstate(saved);
 | 
						|
		return;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
 | 
						|
	if (quals && newquals) {
 | 
						|
	    /* Merge previous group of qualifiers with new set. */
 | 
						|
	    if (quals->or || newquals->or) {
 | 
						|
		/* The hard case. */
 | 
						|
		struct qual *qorhead = NULL, *qortail = NULL;
 | 
						|
		/*
 | 
						|
		 * Distribute in the most trivial way, by creating
 | 
						|
		 * all possible combinations of the two sets and chaining
 | 
						|
		 * these into one long set of alternatives given
 | 
						|
		 * by qorhead and qortail.
 | 
						|
		 */
 | 
						|
		for (qn = newquals; qn; qn = qn->or) {
 | 
						|
		    for (qo = quals; qo; qo = qo->or) {
 | 
						|
			struct qual *qfirst, *qlast;
 | 
						|
			int islast = !qn->or && !qo->or;
 | 
						|
			/* Generate first set of qualifiers... */
 | 
						|
			if (islast) {
 | 
						|
			    /* Last time round:  don't bother copying. */
 | 
						|
			    qfirst = qn;
 | 
						|
			    for (qlast = qfirst; qlast->next;
 | 
						|
				 qlast = qlast->next)
 | 
						|
				;			    
 | 
						|
			} else
 | 
						|
			    qfirst = dup_qual_list(qn, &qlast);
 | 
						|
			/* ... link into new `or' chain ... */
 | 
						|
			if (!qorhead)
 | 
						|
			    qorhead = qfirst;
 | 
						|
			if (qortail)
 | 
						|
			    qortail->or = qfirst;
 | 
						|
			qortail = qfirst;
 | 
						|
			/* ... and concatenate second set. */
 | 
						|
			qlast->next = islast ? qo : dup_qual_list(qo, NULL);
 | 
						|
		    }
 | 
						|
		}
 | 
						|
		quals = qorhead;
 | 
						|
	    } else {
 | 
						|
		/*
 | 
						|
		 * Easy: we can just chain the qualifiers together.
 | 
						|
		 * This is an optimisation; the code above will work, too.
 | 
						|
		 * We retain the original left to right ordering --- remember
 | 
						|
		 * we are searching for sets of qualifiers from the right.
 | 
						|
		 */
 | 
						|
		qn = newquals;
 | 
						|
		for ( ; newquals->next; newquals = newquals->next)
 | 
						|
		    ;
 | 
						|
		newquals->next = quals;
 | 
						|
		quals = qn;
 | 
						|
	    }
 | 
						|
	} else if (newquals)
 | 
						|
	    quals = newquals;
 | 
						|
    }
 | 
						|
    q = parsepat(str);
 | 
						|
    if (!q || errflag) {	/* if parsing failed */
 | 
						|
	restore_globstate(saved);
 | 
						|
	if (unset(BADPATTERN)) {
 | 
						|
	    if (!nountok)
 | 
						|
		untokenize(ostr);
 | 
						|
	    insertlinknode(list, node, ostr);
 | 
						|
	    return;
 | 
						|
	}
 | 
						|
	errflag &= ~ERRFLAG_ERROR;
 | 
						|
	zerr("bad pattern: %s", ostr);
 | 
						|
	return;
 | 
						|
    }
 | 
						|
    if (!gf_nsorts) {
 | 
						|
	gf_sortlist[0].tp = gf_sorts = (shortcircuit ? GS_NONE : GS_NAME);
 | 
						|
	gf_nsorts = 1;
 | 
						|
    }
 | 
						|
    /* Initialise receptacle for matched files, *
 | 
						|
     * expanded by insert() where necessary.    */
 | 
						|
    matchptr = matchbuf = (Gmatch)zalloc((matchsz = 16) *
 | 
						|
					 sizeof(struct gmatch));
 | 
						|
    matchct = 0;
 | 
						|
    pattrystart();
 | 
						|
 | 
						|
    /* The actual processing takes place here: matches go into  *
 | 
						|
     * matchbuf.  This is the only top-level call to scanner(). */
 | 
						|
    scanner(q, shortcircuit);
 | 
						|
 | 
						|
    /* 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);
 | 
						|
	    zfree(matchbuf, 0);
 | 
						|
	    restore_globstate(saved);
 | 
						|
	    return;
 | 
						|
	} else {
 | 
						|
	    /* treat as an ordinary string */
 | 
						|
	    untokenize(matchptr->name = dupstring(ostr));
 | 
						|
	    matchptr++;
 | 
						|
	    matchct = 1;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    if (!(gf_sortlist[0].tp & GS_NONE)) {
 | 
						|
	/*
 | 
						|
	 * Get the strings to use for sorting by executing
 | 
						|
	 * the code chunk.  We allow more than one of these.
 | 
						|
	 */
 | 
						|
	int nexecs = 0;
 | 
						|
	struct globsort *sortp;
 | 
						|
	struct globsort *lastsortp = gf_sortlist + gf_nsorts;
 | 
						|
	Gmatch gmptr;
 | 
						|
 | 
						|
	/* First find out if there are any GS_EXECs, counting them. */
 | 
						|
	for (sortp = gf_sortlist; sortp < lastsortp; sortp++)
 | 
						|
	{
 | 
						|
	    if (sortp->tp & GS_EXEC)
 | 
						|
		nexecs++;
 | 
						|
	}
 | 
						|
 | 
						|
	if (nexecs) {
 | 
						|
	    Gmatch tmpptr;
 | 
						|
	    int iexec = 0;
 | 
						|
 | 
						|
	    /* Yes; allocate enough space for strings for each */
 | 
						|
	    for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++)
 | 
						|
		tmpptr->sortstrs = (char **)zhalloc(nexecs*sizeof(char*));
 | 
						|
 | 
						|
	    /* Loop over each one, incrementing iexec */
 | 
						|
	    for (sortp = gf_sortlist; sortp < lastsortp; sortp++)
 | 
						|
	    {
 | 
						|
		/* Ignore unless this is a GS_EXEC */
 | 
						|
		if (sortp->tp & GS_EXEC) {
 | 
						|
		    Eprog prog;
 | 
						|
 | 
						|
		    if ((prog = parse_string(sortp->exec, 0))) {
 | 
						|
			int ef = errflag, lv = lastval;
 | 
						|
 | 
						|
			/* Parsed OK, execute for each name */
 | 
						|
			for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++) {
 | 
						|
			    setsparam("REPLY", ztrdup(tmpptr->name));
 | 
						|
			    execode(prog, 1, 0, "globsort");
 | 
						|
			    if (!errflag)
 | 
						|
				tmpptr->sortstrs[iexec] =
 | 
						|
				    dupstring(getsparam("REPLY"));
 | 
						|
			    else
 | 
						|
				tmpptr->sortstrs[iexec] = tmpptr->name;
 | 
						|
			}
 | 
						|
 | 
						|
			/* Retain any user interrupt error status */
 | 
						|
			errflag = ef | (errflag & ERRFLAG_INT);
 | 
						|
			lastval = lv;
 | 
						|
		    } else {
 | 
						|
			/* Failed, let's be safe */
 | 
						|
			for (tmpptr = matchbuf; tmpptr < matchptr; tmpptr++)
 | 
						|
			    tmpptr->sortstrs[iexec] = tmpptr->name;
 | 
						|
		    }
 | 
						|
 | 
						|
		    iexec++;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	}
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Where necessary, create unmetafied version of names
 | 
						|
	 * for comparison.  If no Meta characters just point
 | 
						|
	 * to original string.  All on heap.
 | 
						|
	 */
 | 
						|
	for (gmptr = matchbuf; gmptr < matchptr; gmptr++)
 | 
						|
	{
 | 
						|
	    if (strchr(gmptr->name, Meta))
 | 
						|
	    {
 | 
						|
		int dummy;
 | 
						|
		gmptr->uname = dupstring(gmptr->name);
 | 
						|
		unmetafy(gmptr->uname, &dummy);
 | 
						|
	    } else {
 | 
						|
		gmptr->uname = gmptr->name;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
 | 
						|
	/* Sort arguments in to lexical (and possibly numeric) order. *
 | 
						|
	 * This is reversed to facilitate insertion into the list.    */
 | 
						|
	qsort((void *) & matchbuf[0], matchct, sizeof(struct gmatch),
 | 
						|
	      (int (*) _((const void *, const void *)))gmatchcmp);
 | 
						|
    }
 | 
						|
 | 
						|
    if (first < 0) {
 | 
						|
	first += matchct;
 | 
						|
	if (first < 0)
 | 
						|
	    first = 0;
 | 
						|
    }
 | 
						|
    if (end < 0)
 | 
						|
	end += matchct + 1;
 | 
						|
    else if (end > matchct)
 | 
						|
	end = matchct;
 | 
						|
    if ((end -= first) > 0) {
 | 
						|
	if (gf_sortlist[0].tp & GS_NONE) {
 | 
						|
	    /* Match list was never reversed, so insert back to front. */
 | 
						|
	    matchptr = matchbuf + matchct - first - 1;
 | 
						|
	    while (end-- > 0) {
 | 
						|
		/* insert matches in the arg list */
 | 
						|
		insert_glob_match(list, node, matchptr->name);
 | 
						|
		matchptr--;
 | 
						|
	    }
 | 
						|
	} else {
 | 
						|
	    matchptr = matchbuf + matchct - first - end;
 | 
						|
	    while (end-- > 0) {
 | 
						|
		/* insert matches in the arg list */
 | 
						|
		insert_glob_match(list, node, matchptr->name);
 | 
						|
		matchptr++;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    } else if (!badcshglob && !isset(NOMATCH) && matchct == 1) {
 | 
						|
	insert_glob_match(list, node, (--matchptr)->name);
 | 
						|
    }
 | 
						|
    zfree(matchbuf, 0);
 | 
						|
 | 
						|
    restore_globstate(saved);
 | 
						|
}
 | 
						|
 | 
						|
/* Return the trailing character for marking file types */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export char
 | 
						|
file_type(mode_t filemode)
 | 
						|
{
 | 
						|
    if(S_ISBLK(filemode))
 | 
						|
	return '#';
 | 
						|
    else if(S_ISCHR(filemode))
 | 
						|
	return '%';
 | 
						|
    else if(S_ISDIR(filemode))
 | 
						|
	return '/';
 | 
						|
    else if(S_ISFIFO(filemode))
 | 
						|
	return '|';
 | 
						|
    else if(S_ISLNK(filemode))
 | 
						|
	return '@';
 | 
						|
    else if(S_ISREG(filemode))
 | 
						|
	return (filemode & S_IXUGO) ? '*' : ' ';
 | 
						|
    else if(S_ISSOCK(filemode))
 | 
						|
	return '=';
 | 
						|
    else
 | 
						|
	return '?';
 | 
						|
}
 | 
						|
 | 
						|
/* check to see if str is eligible for brace expansion */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export 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, c;
 | 
						|
 | 
						|
	for (bc = 0; (c = *str); ++str)
 | 
						|
	    if (c == Inbrace) {
 | 
						|
		if (!bc && str[1] == Outbrace)
 | 
						|
		    *str++ = '{', *str = '}';
 | 
						|
		else
 | 
						|
		    bc++;
 | 
						|
	    } else if (c == 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) {
 | 
						|
		if (bracechardots(str-1, NULL, NULL))
 | 
						|
		    return 1;
 | 
						|
		lbr = str - 1;
 | 
						|
		if (IS_DASH(*str))
 | 
						|
		    str++;
 | 
						|
		while (idigit(*str))
 | 
						|
		    str++;
 | 
						|
		if (*str == '.' && str[1] == '.') {
 | 
						|
		    str++; str++;
 | 
						|
		    if (IS_DASH(*str))
 | 
						|
			str++;
 | 
						|
		    while (idigit(*str))
 | 
						|
			str++;
 | 
						|
		    if (*str == Outbrace &&
 | 
						|
			(idigit(lbr[1]) || idigit(str[-1])))
 | 
						|
			return 1;
 | 
						|
		    else if (*str == '.' && str[1] == '.') {
 | 
						|
			str++; str++;
 | 
						|
			if (IS_DASH(*str))
 | 
						|
			    str++;
 | 
						|
			while (idigit(*str))
 | 
						|
			    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 redirtab)
 | 
						|
{
 | 
						|
    char *nam;
 | 
						|
    struct redir *ff;
 | 
						|
    int ret = 0;
 | 
						|
    local_list1(fake);
 | 
						|
 | 
						|
    /* Stick the name in a list... */
 | 
						|
    init_list1(fake, fn->name);
 | 
						|
    /* ...which undergoes all the usual shell expansions */
 | 
						|
    prefork(&fake, isset(MULTIOS) ? 0 : PREFORK_SINGLE, NULL);
 | 
						|
    /* Globbing is only done for multios. */
 | 
						|
    if (!errflag && isset(MULTIOS))
 | 
						|
	globlist(&fake, 0);
 | 
						|
    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 == REDIR_MERGEIN || fn->type == REDIR_MERGEOUT) {
 | 
						|
	    if (IS_DASH(s[0]) && !s[1])
 | 
						|
		fn->type = REDIR_CLOSE;
 | 
						|
	    else if (s[0] == 'p' && !s[1])
 | 
						|
		fn->fd2 = -2;
 | 
						|
	    else {
 | 
						|
		while (idigit(*s))
 | 
						|
		    s++;
 | 
						|
		if (!*s && s > fn->name)
 | 
						|
		    fn->fd2 = zstrtol(fn->name, NULL, 10);
 | 
						|
		else if (fn->type == REDIR_MERGEIN)
 | 
						|
		    zerr("file number expected");
 | 
						|
		else
 | 
						|
		    fn->type = REDIR_ERRWRITE;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    } else if (fn->type == REDIR_MERGEIN)
 | 
						|
	zerr("file number expected");
 | 
						|
    else {
 | 
						|
	if (fn->type == REDIR_MERGEOUT)
 | 
						|
	    fn->type = REDIR_ERRWRITE;
 | 
						|
	while ((nam = (char *)ugetnode(&fake))) {
 | 
						|
	    /* Loop over matches, duplicating the *
 | 
						|
	     * redirection for each file found.   */
 | 
						|
	    ff = (struct redir *) zhalloc(sizeof *ff);
 | 
						|
	    *ff = *fn;
 | 
						|
	    ff->name = nam;
 | 
						|
	    addlinknode(redirtab, ff);
 | 
						|
	    ret = 1;
 | 
						|
	}
 | 
						|
    }
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Check for a brace expansion of the form {<char>..<char>}.
 | 
						|
 * On input str must be positioned at an Inbrace, but the sequence
 | 
						|
 * of characters beyond that has not necessarily been checked.
 | 
						|
 * Return 1 if found else 0.
 | 
						|
 *
 | 
						|
 * The other parameters are optionaland if the function returns 1 are
 | 
						|
 * used to return:
 | 
						|
 * - *c1p: the first character in the expansion.
 | 
						|
 * - *c2p: the final character in the expansion.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
bracechardots(char *str, convchar_t *c1p, convchar_t *c2p)
 | 
						|
{
 | 
						|
    convchar_t cstart, cend;
 | 
						|
    char *pnext = str + 1, *pconv, convstr[2];
 | 
						|
    if (itok(*pnext)) {
 | 
						|
	if (*pnext == Inbrace)
 | 
						|
	    return 0;
 | 
						|
	convstr[0] = ztokens[*pnext - Pound];
 | 
						|
	convstr[1] = '\0';
 | 
						|
	pconv = convstr;
 | 
						|
    } else
 | 
						|
	pconv = pnext;
 | 
						|
    MB_METACHARINIT();
 | 
						|
    pnext += MB_METACHARLENCONV(pconv, &cstart);
 | 
						|
    if (
 | 
						|
#ifdef MULTIBYTE_SUPPORT
 | 
						|
	cstart == WEOF ||
 | 
						|
#else
 | 
						|
	!cstart ||
 | 
						|
#endif
 | 
						|
	pnext[0] != '.' || pnext[1] != '.')
 | 
						|
	return 0;
 | 
						|
    pnext += 2;
 | 
						|
    if (!*pnext)
 | 
						|
	return 0;
 | 
						|
    if (itok(*pnext)) {
 | 
						|
	if (*pnext == Inbrace)
 | 
						|
	    return 0;
 | 
						|
	convstr[0] = ztokens[*pnext - Pound];
 | 
						|
	convstr[1] = '\0';
 | 
						|
	pconv = convstr;
 | 
						|
    } else
 | 
						|
	pconv = pnext;
 | 
						|
    MB_METACHARINIT();
 | 
						|
    pnext += MB_METACHARLENCONV(pconv, &cend);
 | 
						|
    if (
 | 
						|
#ifdef MULTIBYTE_SUPPORT
 | 
						|
	cend == WEOF ||
 | 
						|
#else
 | 
						|
	!cend ||
 | 
						|
#endif
 | 
						|
	*pnext != Outbrace)
 | 
						|
	return 0;
 | 
						|
    if (c1p)
 | 
						|
	*c1p = cstart;
 | 
						|
    if (c2p)
 | 
						|
	*c2p = cend;
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
/* brace expansion */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export 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} */
 | 
						|
		++str2;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    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, *dots2 = NULL;
 | 
						|
	LinkNode olast = last;
 | 
						|
	/* Get the first number of the range */
 | 
						|
	zlong rstart, rend;
 | 
						|
	int err = 0, rev = 0, rincr = 1;
 | 
						|
	int wid1, wid2, wid3, strp;
 | 
						|
	convchar_t cstart, cend;
 | 
						|
 | 
						|
	if (bracechardots(str, &cstart, &cend)) {
 | 
						|
	    int lenalloc;
 | 
						|
	    /*
 | 
						|
	     * This is a character range.
 | 
						|
	     */
 | 
						|
	    if (cend < cstart) {
 | 
						|
		convchar_t ctmp = cend;
 | 
						|
		cend = cstart;
 | 
						|
		cstart = ctmp;
 | 
						|
		rev = 1;
 | 
						|
	    }
 | 
						|
	    uremnode(list, node);
 | 
						|
	    strp = str - str3;
 | 
						|
	    lenalloc = strp + strlen(str2+1) + 1;
 | 
						|
	    do {
 | 
						|
#ifdef MULTIBYTE_SUPPORT
 | 
						|
		char *ncptr;
 | 
						|
		int nclen;
 | 
						|
		mb_charinit();
 | 
						|
		ncptr = wcs_nicechar(cend, NULL, NULL);
 | 
						|
		nclen = strlen(ncptr);
 | 
						|
		p = zhalloc(lenalloc + nclen);
 | 
						|
		memcpy(p, str3, strp);
 | 
						|
		memcpy(p + strp, ncptr, nclen);
 | 
						|
		strcpy(p + strp + nclen, str2 + 1);
 | 
						|
#else
 | 
						|
		p = zhalloc(lenalloc + 1);
 | 
						|
		memcpy(p, str3, strp);
 | 
						|
		sprintf(p + strp, "%c", cend);
 | 
						|
		strcat(p + strp, str2 + 1);
 | 
						|
#endif
 | 
						|
		insertlinknode(list, last, p);
 | 
						|
		if (rev)	/* decreasing:  add in reverse order. */
 | 
						|
		    last = nextnode(last);
 | 
						|
	    } while (cend-- > cstart);
 | 
						|
	    *np = nextnode(olast);
 | 
						|
	    return;
 | 
						|
	}
 | 
						|
 | 
						|
	/* Get the first number of the range */
 | 
						|
	rstart = zstrtol(str+1,&dots,10);
 | 
						|
	rend = 0;
 | 
						|
	wid1 = (dots - str) - 1;
 | 
						|
	wid2 = (str2 - dots) - 2;
 | 
						|
	wid3 = 0;
 | 
						|
	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)
 | 
						|
		err++;
 | 
						|
	    /* check for {num1..num2..incr} */
 | 
						|
	    if (p != str2) {
 | 
						|
		wid2 = (p - dots) - 2;
 | 
						|
		dots2 = p;
 | 
						|
		if (dotdot == 2 && *p == '.' && p[1] == '.') {
 | 
						|
		    rincr = zstrtol(p+2, &p, 10);
 | 
						|
		    wid3 = p - dots2 - 2;
 | 
						|
		    if (p != str2 || !rincr)
 | 
						|
			err++;
 | 
						|
		} else
 | 
						|
		    err++;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
	if (!err) {
 | 
						|
	    /* If either no. begins with a zero, pad the output with   *
 | 
						|
	     * zeroes. Otherwise, set min width to 0 to suppress them.
 | 
						|
	     * str+1 is the first number in the range, dots+2 the last,
 | 
						|
	     * and dots2+2 is the increment if that's given. */
 | 
						|
	    /* TODO: sorry about this */
 | 
						|
	    int minw = (str[1] == '0' ||
 | 
						|
			(IS_DASH(str[1]) && str[2] == '0'))
 | 
						|
		       ? wid1
 | 
						|
		       : (dots[2] == '0' ||
 | 
						|
			  (IS_DASH(dots[2]) && dots[3] == '0'))
 | 
						|
		       ? wid2
 | 
						|
		       : (dots2 && (dots2[2] == '0' ||
 | 
						|
				    (IS_DASH(dots2[2]) && dots2[3] == '0')))
 | 
						|
		       ? wid3
 | 
						|
		       : 0;
 | 
						|
	    if (rincr < 0) {
 | 
						|
		/* Handle negative increment */
 | 
						|
		rincr = -rincr;
 | 
						|
		rev = !rev;
 | 
						|
	    }
 | 
						|
	    if (rstart > rend) {
 | 
						|
		/* Handle decreasing ranges correctly. */
 | 
						|
		zlong rt = rend;
 | 
						|
		rend = rstart;
 | 
						|
		rstart = rt;
 | 
						|
		rev = !rev;
 | 
						|
	    } else if (rincr > 1) {
 | 
						|
		/* when incr > 1, range is aligned to the highest number of str1,
 | 
						|
		 * compensate for this so that it is aligned to the first number */
 | 
						|
		rend -= (rend - rstart) % rincr;
 | 
						|
	    }
 | 
						|
	    uremnode(list, node);
 | 
						|
	    for (; rend >= rstart; rend -= rincr) {
 | 
						|
		/* Node added in at end, so do highest first */
 | 
						|
		p = dupstring(str3);
 | 
						|
#if defined(ZLONG_IS_LONG_LONG) && defined(PRINTF_HAS_LLD)
 | 
						|
		sprintf(p + strp, "%0*lld", minw, rend);
 | 
						|
#else
 | 
						|
		sprintf(p + strp, "%0*ld", minw, (long)rend);
 | 
						|
#endif
 | 
						|
		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;
 | 
						|
	unsigned int len, pl;
 | 
						|
	int lastch = -1;
 | 
						|
 | 
						|
	uremnode(list, node);
 | 
						|
	memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
 | 
						|
	for (p = str + 1; 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 (IS_DASH((char)c1) && lastch >= 0 &&
 | 
						|
		p < str2 && lastch <= (int)c2) {
 | 
						|
		while (lastch < (int)c2)
 | 
						|
		    ccl[lastch++] = 1;
 | 
						|
		lastch = -1;
 | 
						|
	    } else
 | 
						|
		ccl[lastch = c1] = 1;
 | 
						|
	}
 | 
						|
	pl = str - str3;
 | 
						|
	len = pl + strlen(++str2) + 2;
 | 
						|
	for (p = ccl + 256; p-- > ccl;)
 | 
						|
	    if (*p) {
 | 
						|
		c1 = p - ccl;
 | 
						|
		if (imeta(c1)) {
 | 
						|
		    str = hcalloc(len + 1);
 | 
						|
		    str[pl] = Meta;
 | 
						|
		    str[pl+1] = c1 ^ 32;
 | 
						|
		    strcpy(str + pl + 2, str2);
 | 
						|
		} else {
 | 
						|
		    str = hcalloc(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 *) hcalloc(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)
 | 
						|
{
 | 
						|
    Patprog p;
 | 
						|
    int ret;
 | 
						|
 | 
						|
    queue_signals();	/* Protect PAT_STATIC */
 | 
						|
 | 
						|
    if (!(p = patcompile(b, PAT_STATIC, NULL))) {
 | 
						|
	zerr("bad pattern: %s", b);
 | 
						|
	ret = 0;
 | 
						|
    } else
 | 
						|
      ret = pattry(p, a);
 | 
						|
 | 
						|
    unqueue_signals();
 | 
						|
 | 
						|
    return ret;
 | 
						|
}
 | 
						|
 | 
						|
/* 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 imd->ustr
 | 
						|
 * and finishes e characters in: 0 <= b <= e <= imd->ulen on input
 | 
						|
 * (yes, empty matches should work).
 | 
						|
 *
 | 
						|
 * imd->flags is a set of the SUB_* matches defined in zsh.h from
 | 
						|
 * SUB_MATCH onwards; the lower parts are ignored.
 | 
						|
 *
 | 
						|
 * imd->replstr is the replacement string for a substitution
 | 
						|
 *
 | 
						|
 * imd->replstr is metafied and the values put in imd->repllist are metafied.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
static char *
 | 
						|
get_match_ret(Imatchdata imd, int b, int e)
 | 
						|
{
 | 
						|
    char buf[80], *r, *p, *rr, *replstr = imd->replstr;
 | 
						|
    int ll = 0, bl = 0, t = 0, add = 0, fl = imd->flags, i;
 | 
						|
 | 
						|
    /* Account for b and e referring to unmetafied string */
 | 
						|
    for (p = imd->ustr; p < imd->ustr + b; p++)
 | 
						|
	if (imeta(*p))
 | 
						|
	    add++;
 | 
						|
    b += add;
 | 
						|
    for (; p < imd->ustr + e; p++)
 | 
						|
	if (imeta(*p))
 | 
						|
	    add++;
 | 
						|
    e += add;
 | 
						|
 | 
						|
    /* Everything now refers to metafied lengths. */
 | 
						|
    if (replstr || (fl & SUB_LIST)) {
 | 
						|
	if (fl & SUB_DOSUBST) {
 | 
						|
	    replstr = dupstring(replstr);
 | 
						|
	    singsub(&replstr);
 | 
						|
	    untokenize(replstr);
 | 
						|
	}
 | 
						|
	if ((fl & (SUB_GLOBAL|SUB_LIST)) && imd->repllist) {
 | 
						|
	    /* We are replacing the chunk, just add this to the list */
 | 
						|
	    Repldata rd = (Repldata)
 | 
						|
		((fl & SUB_LIST) ? zalloc(sizeof(*rd)) : zhalloc(sizeof(*rd)));
 | 
						|
	    rd->b = b;
 | 
						|
	    rd->e = e;
 | 
						|
	    rd->replstr = replstr;
 | 
						|
	    if (fl & SUB_LIST)
 | 
						|
		zaddlinknode(imd->repllist, rd);
 | 
						|
	    else
 | 
						|
		addlinknode(imd->repllist, rd);
 | 
						|
	    return imd->mstr;
 | 
						|
	}
 | 
						|
	ll += strlen(replstr);
 | 
						|
    }
 | 
						|
    if (fl & SUB_MATCH)			/* matched portion */
 | 
						|
	ll += 1 + (e - b);
 | 
						|
    if (fl & SUB_REST)		/* unmatched portion */
 | 
						|
	ll += 1 + (imd->mlen - (e - b));
 | 
						|
    if (fl & SUB_BIND) {
 | 
						|
	/* position of start of matched portion */
 | 
						|
	sprintf(buf, "%d ", MB_METASTRLEN2END(imd->mstr, 0, imd->mstr+b) + 1);
 | 
						|
	ll += (bl = strlen(buf));
 | 
						|
    }
 | 
						|
    if (fl & SUB_EIND) {
 | 
						|
	/* position of end of matched portion */
 | 
						|
	sprintf(buf + bl, "%d ",
 | 
						|
		MB_METASTRLEN2END(imd->mstr, 0, imd->mstr+e) + 1);
 | 
						|
	ll += (bl = strlen(buf));
 | 
						|
    }
 | 
						|
    if (fl & SUB_LEN) {
 | 
						|
	/* length of matched portion */
 | 
						|
	sprintf(buf + bl, "%d ", MB_METASTRLEN2END(imd->mstr+b, 0,
 | 
						|
						   imd->mstr+e));
 | 
						|
	ll += (bl = strlen(buf));
 | 
						|
    }
 | 
						|
    if (bl)
 | 
						|
	buf[bl - 1] = '\0';
 | 
						|
 | 
						|
    rr = r = (char *) hcalloc(ll);
 | 
						|
 | 
						|
    if (fl & SUB_MATCH) {
 | 
						|
	/* copy matched portion to new buffer */
 | 
						|
	for (i = b, p = imd->mstr + b; i < e; i++)
 | 
						|
	    *rr++ = *p++;
 | 
						|
	t = 1;
 | 
						|
    }
 | 
						|
    if (fl & SUB_REST) {
 | 
						|
	/* 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 = imd->mstr; i < b; i++)
 | 
						|
	    *rr++ = *p++;
 | 
						|
	if (replstr)
 | 
						|
	    for (p = replstr; *p; )
 | 
						|
		*rr++ = *p++;
 | 
						|
	for (i = e, p = imd->mstr + e; i < imd->mlen; 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;
 | 
						|
}
 | 
						|
 | 
						|
static Patprog
 | 
						|
compgetmatch(char *pat, int *flp, char **replstrp)
 | 
						|
{
 | 
						|
    Patprog p;
 | 
						|
    /*
 | 
						|
     * Flags to pattern compiler:  use static buffer since we only
 | 
						|
     * have one pattern at a time; we will try the must-match test ourselves,
 | 
						|
     * so tell the pattern compiler we are scanning.
 | 
						|
     */
 | 
						|
 | 
						|
    /* int patflags = PAT_STATIC|PAT_SCAN|PAT_NOANCH;*/
 | 
						|
 | 
						|
    /* Unfortunately, PAT_STATIC doesn't work if we have a replstr with
 | 
						|
     * something like ${x#...} in it which will be singsub()ed below because
 | 
						|
     * that would overwrite the pattern buffer. */
 | 
						|
 | 
						|
    int patflags = PAT_SCAN|PAT_NOANCH | (*replstrp ? 0 : PAT_STATIC);
 | 
						|
 | 
						|
    /*
 | 
						|
     * Search is anchored to the end of the string if we want to match
 | 
						|
     * it all, or if we are matching at the end of the string and not
 | 
						|
     * using substrings.
 | 
						|
     */
 | 
						|
    if ((*flp & SUB_ALL) || ((*flp & SUB_END) && !(*flp & SUB_SUBSTR)))
 | 
						|
	patflags &= ~PAT_NOANCH;
 | 
						|
    p = patcompile(pat, patflags, NULL);
 | 
						|
    if (!p) {
 | 
						|
	zerr("bad pattern: %s", pat);
 | 
						|
	return NULL;
 | 
						|
    }
 | 
						|
    if (*replstrp) {
 | 
						|
	if (p->patnpar || (p->globend & GF_MATCHREF)) {
 | 
						|
	    /*
 | 
						|
	     * Either backreferences or match references, so we
 | 
						|
	     * need to re-substitute replstr each time round.
 | 
						|
	     */
 | 
						|
	    *flp |= SUB_DOSUBST;
 | 
						|
	} else {
 | 
						|
	    singsub(replstrp);
 | 
						|
	    untokenize(*replstrp);
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    return p;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * This is called from paramsubst to get the match for ${foo#bar} etc.
 | 
						|
 * fl is a set of the SUB_* flags defined in zsh.h
 | 
						|
 * *sp points to the string we have to modify. The n'th match will be
 | 
						|
 * returned in *sp. The heap is used to get memory for the result string.
 | 
						|
 * replstr is the replacement string from a ${.../orig/repl}, in
 | 
						|
 * which case pat is the original.
 | 
						|
 *
 | 
						|
 * n is now ignored unless we are looking for a substring, in
 | 
						|
 * which case the n'th match from the start is counted such that
 | 
						|
 * there is no more than one match from each position.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
int
 | 
						|
getmatch(char **sp, char *pat, int fl, int n, char *replstr)
 | 
						|
{
 | 
						|
    Patprog p;
 | 
						|
 | 
						|
    if (!(p = compgetmatch(pat, &fl, &replstr)))
 | 
						|
	return 1;
 | 
						|
 | 
						|
    return igetmatch(sp, p, fl, n, replstr, NULL);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * This is the corresponding function for array variables.
 | 
						|
 * Matching is done with the same pattern on each element.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
void
 | 
						|
getmatcharr(char ***ap, char *pat, int fl, int n, char *replstr)
 | 
						|
{
 | 
						|
    char **arr = *ap, **pp;
 | 
						|
    Patprog p;
 | 
						|
 | 
						|
    if (!(p = compgetmatch(pat, &fl, &replstr)))
 | 
						|
	return;
 | 
						|
 | 
						|
    *ap = pp = hcalloc(sizeof(char *) * (arrlen(arr) + 1));
 | 
						|
    while ((*pp = *arr++))
 | 
						|
	if (igetmatch(pp, p, fl, n, replstr, NULL))
 | 
						|
	    pp++;
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * Match against str using pattern pp; return a list of
 | 
						|
 * Repldata matches in the linked list *repllistp; this is
 | 
						|
 * in permanent storage and to be freed by freematchlist()
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export int
 | 
						|
getmatchlist(char *str, Patprog p, LinkList *repllistp)
 | 
						|
{
 | 
						|
    char **sp = &str;
 | 
						|
 | 
						|
    /*
 | 
						|
     * We don't care if we have longest or shortest match, but SUB_LONG
 | 
						|
     * is cheaper since the pattern code does that by default.
 | 
						|
     * We need SUB_GLOBAL to get all matches.
 | 
						|
     * We need SUB_SUBSTR to scan through for substrings.
 | 
						|
     * We need SUB_LIST to activate the special handling of the list
 | 
						|
     * passed in.
 | 
						|
     */
 | 
						|
    return igetmatch(sp, p, SUB_LONG|SUB_GLOBAL|SUB_SUBSTR|SUB_LIST,
 | 
						|
		     0, NULL, repllistp);
 | 
						|
}
 | 
						|
 | 
						|
static void
 | 
						|
freerepldata(void *ptr)
 | 
						|
{
 | 
						|
    zfree(ptr, sizeof(struct repldata));
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export void
 | 
						|
freematchlist(LinkList repllist)
 | 
						|
{
 | 
						|
    freelinklist(repllist, freerepldata);
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
static void
 | 
						|
set_pat_start(Patprog p, int offs)
 | 
						|
{
 | 
						|
    /*
 | 
						|
     * If we are messing around with the test string by advancing up
 | 
						|
     * it from the start, we need to tell the pattern matcher that
 | 
						|
     * a start-of-string assertion, i.e. (#s), should fail.  Hence
 | 
						|
     * we test whether the offset of the real start of string from
 | 
						|
     * the actual start, passed as offs, is zero.
 | 
						|
     */
 | 
						|
    if (offs)
 | 
						|
	p->flags |= PAT_NOTSTART;
 | 
						|
    else
 | 
						|
	p->flags &= ~PAT_NOTSTART;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
static void
 | 
						|
set_pat_end(Patprog p, char null_me)
 | 
						|
{
 | 
						|
    /*
 | 
						|
     * If we are messing around with the string by shortening it at the
 | 
						|
     * tail, we need to tell the pattern matcher that an end-of-string
 | 
						|
     * assertion, i.e. (#e), should fail.  Hence we test whether
 | 
						|
     * the character null_me about to be zapped is or is not already a null.
 | 
						|
     */
 | 
						|
    if (null_me)
 | 
						|
	p->flags |= PAT_NOTEND;
 | 
						|
    else
 | 
						|
	p->flags &= ~PAT_NOTEND;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
#ifdef MULTIBYTE_SUPPORT
 | 
						|
 | 
						|
/*
 | 
						|
 * Increment *tp over character which may be multibyte.
 | 
						|
 * Return number of bytes.
 | 
						|
 * All unmetafied here.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
static int iincchar(char **tp, int left)
 | 
						|
{
 | 
						|
    char *t = *tp;
 | 
						|
    int mbclen = mb_charlenconv(t, left, NULL);
 | 
						|
    *tp = t + mbclen;
 | 
						|
 | 
						|
    return mbclen;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
igetmatch(char **sp, Patprog p, int fl, int n, char *replstr,
 | 
						|
	  LinkList *repllistp)
 | 
						|
{
 | 
						|
    char *s = *sp, *t, *tmatch, *send;
 | 
						|
    /*
 | 
						|
     * Note that ioff counts (possibly multibyte) characters in the
 | 
						|
     * character set (Meta's are not included), while l counts characters in
 | 
						|
     * the metafied string.
 | 
						|
     *
 | 
						|
     * umlen is a counter for (unmetafied) byte lengths---neither characters
 | 
						|
     * nor raw byte indices; this is simply an optimisation for allocation.
 | 
						|
     * umltot is the full length of the string in this scheme.
 | 
						|
     *
 | 
						|
     * l is the raw string length, used together with any pointers into
 | 
						|
     * the string (typically t).
 | 
						|
     */
 | 
						|
    int ioff, l = strlen(*sp), matched = 1, umltot = ztrlen(*sp);
 | 
						|
    int umlen, nmatches;
 | 
						|
    struct patstralloc patstralloc;
 | 
						|
    struct imatchdata imd;
 | 
						|
 | 
						|
    (void)patallocstr(p, s, l, umltot, 1, &patstralloc);
 | 
						|
    s = patstralloc.alloced;
 | 
						|
    DPUTS(!s, "forced patallocstr failed");
 | 
						|
    send = s + umltot;
 | 
						|
 | 
						|
    imd.mstr = *sp;
 | 
						|
    imd.mlen = l;
 | 
						|
    imd.ustr = s;
 | 
						|
    imd.ulen = umltot;
 | 
						|
    imd.flags = fl;
 | 
						|
    imd.replstr = replstr;
 | 
						|
    imd.repllist = NULL;
 | 
						|
 | 
						|
    /* perform must-match test for complex closures */
 | 
						|
    if (p->mustoff)
 | 
						|
    {
 | 
						|
	char *muststr = (char *)p + p->mustoff;
 | 
						|
 | 
						|
	matched = 0;
 | 
						|
	if (p->patmlen <= umltot)
 | 
						|
	{
 | 
						|
	    for (t = s; t <= send - p->patmlen; t++)
 | 
						|
	    {
 | 
						|
		if (!memcmp(muststr, t, p->patmlen)) {
 | 
						|
		    matched = 1;
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    /* in case we used the prog before... */
 | 
						|
    p->flags &= ~(PAT_NOTSTART|PAT_NOTEND);
 | 
						|
 | 
						|
    if (fl & SUB_ALL) {
 | 
						|
	int i = matched && pattrylen(p, s, umltot, 0, &patstralloc, 0);
 | 
						|
	if (!i) {
 | 
						|
	    /* Perform under no-match conditions */
 | 
						|
	    umltot = 0;
 | 
						|
	    imd.replstr = NULL;
 | 
						|
	}
 | 
						|
	*sp = get_match_ret(&imd, 0, umltot);
 | 
						|
	if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i)))
 | 
						|
	    return 0;
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    if (matched) {
 | 
						|
	/*
 | 
						|
	 * The default behaviour is to match at the start; this
 | 
						|
	 * is modified by SUB_END and SUB_SUBSTR.  SUB_END matches
 | 
						|
	 * at the end of the string instead of the start.  SUB_SUBSTR
 | 
						|
	 * without SUB_END matches substrings searching from the start;
 | 
						|
	 * with SUB_END it matches substrings searching from the end.
 | 
						|
	 *
 | 
						|
	 * The possibilities are further modified by whether we want the
 | 
						|
	 * longest (SUB_LONG) or shortest possible match.
 | 
						|
	 *
 | 
						|
	 * SUB_START is only used in the case where we are also
 | 
						|
	 * forcing a match at the end (SUB_END with no SUB_SUBSTR,
 | 
						|
	 * with or without SUB_LONG), to indicate we should match
 | 
						|
	 * the entire string.
 | 
						|
	 */
 | 
						|
	switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) {
 | 
						|
	case 0:
 | 
						|
	case SUB_LONG:
 | 
						|
	    /*
 | 
						|
	     * Largest/smallest possible match at head of string.
 | 
						|
	     * First get the longest match...
 | 
						|
	     */
 | 
						|
	    if (pattrylen(p, s, umltot, 0, &patstralloc, 0)) {
 | 
						|
		/* patmatchlen returns unmetafied length in this case */
 | 
						|
	        int mlen = patmatchlen();
 | 
						|
		if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | 
						|
		    send = s + mlen;
 | 
						|
		    /*
 | 
						|
		     * ... now we know whether it's worth looking for the
 | 
						|
		     * shortest, which we do by brute force.
 | 
						|
		     */
 | 
						|
		    mb_charinit();
 | 
						|
		    for (t = s, umlen = 0; t < send; ) {
 | 
						|
			set_pat_end(p, *t);
 | 
						|
			if (pattrylen(p, s, umlen, 0, &patstralloc, 0)) {
 | 
						|
			    mlen = patmatchlen();
 | 
						|
			    break;
 | 
						|
			}
 | 
						|
			umlen += iincchar(&t, send - t);
 | 
						|
		    }
 | 
						|
		}
 | 
						|
		*sp = get_match_ret(&imd, 0, mlen);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
 | 
						|
	case SUB_END:
 | 
						|
	    /*
 | 
						|
	     * Smallest possible match at tail of string.
 | 
						|
	     * As we can only be sure we've got wide characters right
 | 
						|
	     * when going forwards, we need to match at every point
 | 
						|
	     * until we fail and record the last successful match.
 | 
						|
	     *
 | 
						|
	     * It's important that we return the last successful match
 | 
						|
	     * so that match, mbegin, mend and MATCH, MBEGIN, MEND are
 | 
						|
	     * correct.
 | 
						|
	     */
 | 
						|
	    mb_charinit();
 | 
						|
	    tmatch = NULL;
 | 
						|
	    set_pat_start(p, l);
 | 
						|
	    if (pattrylen(p, send, 0, 0, &patstralloc, umltot) &&
 | 
						|
		!--n) {
 | 
						|
		*sp = get_match_ret(&imd, umltot, umltot);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) {
 | 
						|
		set_pat_start(p, t-s);
 | 
						|
		if (pattrylen(p, t, umlen, 0, &patstralloc, ioff))
 | 
						|
		    tmatch = t;
 | 
						|
		if (fl & SUB_START)
 | 
						|
		    break;
 | 
						|
		umlen -= iincchar(&t, send - t);
 | 
						|
	    }
 | 
						|
	    if (tmatch) {
 | 
						|
		*sp = get_match_ret(&imd, tmatch - s, umltot);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    if (!(fl & SUB_START) && pattrylen(p, s + umltot, 0, 0,
 | 
						|
					       &patstralloc, ioff)) {
 | 
						|
		*sp = get_match_ret(&imd, umltot, umltot);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
 | 
						|
	case (SUB_END|SUB_LONG):
 | 
						|
	    /* Largest possible match at tail of string:       *
 | 
						|
	     * move forward along string until we get a match. *
 | 
						|
	     * Again there's no optimisation.                  */
 | 
						|
	    mb_charinit();
 | 
						|
	    for (ioff = 0, t = s, umlen = umltot; t <= send ; ioff++) {
 | 
						|
		set_pat_start(p, t-s);
 | 
						|
		if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
 | 
						|
		    *sp = get_match_ret(&imd, t-s, umltot);
 | 
						|
		    return 1;
 | 
						|
		}
 | 
						|
		if (fl & SUB_START)
 | 
						|
		    break;
 | 
						|
		if (t == send)
 | 
						|
		    break;
 | 
						|
		umlen -= iincchar(&t, send - t);
 | 
						|
	    }
 | 
						|
	    if (!(fl & SUB_START) && pattrylen(p, send, 0, 0,
 | 
						|
					       &patstralloc, ioff)) {
 | 
						|
		*sp = get_match_ret(&imd, umltot, umltot);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
 | 
						|
	case SUB_SUBSTR:
 | 
						|
	    /* Smallest at start, but matching substrings. */
 | 
						|
	    set_pat_start(p, l);
 | 
						|
	    if (!(fl & SUB_GLOBAL) &&
 | 
						|
		pattrylen(p, send, 0, 0, &patstralloc, 0) &&
 | 
						|
		!--n) {
 | 
						|
		*sp = get_match_ret(&imd, 0, 0);
 | 
						|
		return 1;
 | 
						|
	    } /* fall through */
 | 
						|
	case (SUB_SUBSTR|SUB_LONG):
 | 
						|
	    /* longest or smallest at start with substrings */
 | 
						|
	    t = s;
 | 
						|
	    if (fl & SUB_GLOBAL) {
 | 
						|
		imd.repllist = (fl & SUB_LIST) ? znewlinklist() : newlinklist();
 | 
						|
		if (repllistp)
 | 
						|
		     *repllistp = imd.repllist;
 | 
						|
	    }
 | 
						|
	    ioff = 0;		/* offset into string */
 | 
						|
	    umlen = umltot;
 | 
						|
	    mb_charinit();
 | 
						|
	    do {
 | 
						|
		/* loop over all matches for global substitution */
 | 
						|
		matched = 0;
 | 
						|
		for (; t <= send; ioff++) {
 | 
						|
		    /* Find the longest match from this position. */
 | 
						|
		    set_pat_start(p, t-s);
 | 
						|
		    if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
 | 
						|
			char *mpos = t + patmatchlen();
 | 
						|
			if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | 
						|
			    char *ptr;
 | 
						|
			    int umlen2;
 | 
						|
			    /*
 | 
						|
			     * If searching for the shortest match,
 | 
						|
			     * start with a zero length and increase
 | 
						|
			     * it until we reach the longest possible
 | 
						|
			     * match, accepting the first successful
 | 
						|
			     * match.
 | 
						|
			     */
 | 
						|
			    for (ptr = t, umlen2 = 0; ptr < mpos;) {
 | 
						|
				set_pat_end(p, *ptr);
 | 
						|
				if (pattrylen(p, t, umlen2, 0,
 | 
						|
					      &patstralloc, ioff)) {
 | 
						|
				    mpos = t + patmatchlen();
 | 
						|
				    break;
 | 
						|
				}
 | 
						|
				umlen2 += iincchar(&ptr, mpos - ptr);
 | 
						|
			    }
 | 
						|
			}
 | 
						|
			if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) {
 | 
						|
			    *sp = get_match_ret(&imd, t-s, mpos-s);
 | 
						|
			    if (mpos == t)
 | 
						|
				mpos += mb_charlenconv(mpos, send - mpos, NULL);
 | 
						|
			}
 | 
						|
			if (!(fl & SUB_GLOBAL)) {
 | 
						|
			    if (n) {
 | 
						|
				/*
 | 
						|
				 * Looking for a later match: in this case,
 | 
						|
				 * we can continue looking for matches from
 | 
						|
				 * the next character, even if it overlaps
 | 
						|
				 * with what we just found.
 | 
						|
				 */
 | 
						|
				umlen -= iincchar(&t, send - t);
 | 
						|
				continue;
 | 
						|
			    } else {
 | 
						|
				return 1;
 | 
						|
			    }
 | 
						|
			}
 | 
						|
			/*
 | 
						|
			 * For a global match, we need to skip the stuff
 | 
						|
			 * which is already marked for replacement.
 | 
						|
			 */
 | 
						|
			matched = 1;
 | 
						|
			if (t == send)
 | 
						|
			    break;
 | 
						|
			while (t < mpos) {
 | 
						|
			    ioff++;
 | 
						|
			    umlen -= iincchar(&t, send - t);
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    }
 | 
						|
		    if (t == send)
 | 
						|
			break;
 | 
						|
		    umlen -= iincchar(&t, send - t);
 | 
						|
		}
 | 
						|
	    } while (matched && t < send);
 | 
						|
	    /*
 | 
						|
	     * check if we can match a blank string, if so do it
 | 
						|
	     * at the start.  Goodness knows if this is a good idea
 | 
						|
	     * with global substitution, so it doesn't happen.
 | 
						|
	     */
 | 
						|
	    set_pat_start(p, l);
 | 
						|
	    if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG &&
 | 
						|
		pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) {
 | 
						|
		*sp = get_match_ret(&imd, 0, 0);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
 | 
						|
	case (SUB_END|SUB_SUBSTR):
 | 
						|
	case (SUB_END|SUB_LONG|SUB_SUBSTR):
 | 
						|
	    /* Longest/shortest at end, matching substrings.       */
 | 
						|
	    {
 | 
						|
		set_pat_start(p, l);
 | 
						|
		if (pattrylen(p, send, 0, 0, &patstralloc, umltot) &&
 | 
						|
		    !--n) {
 | 
						|
		    *sp = get_match_ret(&imd, umltot, umltot);
 | 
						|
		    return 1;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    /*
 | 
						|
	     * If multibyte characters are present we need to start from the
 | 
						|
	     * beginning.  This is a bit unpleasant because we can't tell in
 | 
						|
	     * advance how many times it will match and from where, so if n is
 | 
						|
	     * greater then 1 we will need to count the number of times it
 | 
						|
	     * matched and then go through again until we reach the right
 | 
						|
	     * point.  (Either that or record every single match in a list,
 | 
						|
	     * which isn't stupid; it involves more memory management at this
 | 
						|
	     * level but less use of the pattern matcher.)
 | 
						|
	     */
 | 
						|
	    nmatches = 0;
 | 
						|
	    tmatch = NULL;
 | 
						|
	    mb_charinit();
 | 
						|
	    for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) {
 | 
						|
		set_pat_start(p, t-s);
 | 
						|
		if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
 | 
						|
		    nmatches++;
 | 
						|
		    tmatch = t;
 | 
						|
		}
 | 
						|
		umlen -= iincchar(&t, send - t);
 | 
						|
	    }
 | 
						|
	    if (nmatches) {
 | 
						|
		char *mpos;
 | 
						|
		if (n > 1) {
 | 
						|
		    /*
 | 
						|
		     * We need to find the n'th last match.
 | 
						|
		     */
 | 
						|
		    n = nmatches - n;
 | 
						|
		    mb_charinit();
 | 
						|
		    for (ioff = 0, t = s, umlen = umltot; t < send; ioff++) {
 | 
						|
			set_pat_start(p, t-s);
 | 
						|
			if (pattrylen(p, t, umlen, 0, &patstralloc, ioff) &&
 | 
						|
			    !n--) {
 | 
						|
			    tmatch = t;
 | 
						|
			    break;
 | 
						|
			}
 | 
						|
			umlen -= iincchar(&t, send - t);
 | 
						|
		    }
 | 
						|
		}
 | 
						|
		mpos = tmatch + patmatchlen();
 | 
						|
		/* Look for the shortest match if necessary */
 | 
						|
		if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | 
						|
		    for (t = tmatch, umlen = 0; t < mpos; ) {
 | 
						|
			set_pat_end(p, *t);
 | 
						|
			if (pattrylen(p, tmatch, umlen, 0,
 | 
						|
				      &patstralloc, ioff)) {
 | 
						|
			    mpos = tmatch + patmatchlen();
 | 
						|
			    break;
 | 
						|
			}
 | 
						|
			umlen += iincchar(&t, mpos - t);
 | 
						|
		    }
 | 
						|
		}
 | 
						|
		*sp = get_match_ret(&imd, tmatch-s, mpos-s);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    set_pat_start(p, l);
 | 
						|
	    if ((fl & SUB_LONG) && pattrylen(p, send, 0, 0,
 | 
						|
					     &patstralloc, umltot) &&
 | 
						|
		!--n) {
 | 
						|
		*sp = get_match_ret(&imd, umltot, umltot);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    if (imd.repllist && nonempty(imd.repllist)) {
 | 
						|
	/* Put all the bits of a global search and replace together. */
 | 
						|
	LinkNode nd;
 | 
						|
	Repldata rd;
 | 
						|
	int lleft;
 | 
						|
	char *ptr, *start;
 | 
						|
	int i;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Use metafied string again.
 | 
						|
	 * Results from get_match_ret in repllist are all metafied.
 | 
						|
	 */
 | 
						|
	s = *sp;
 | 
						|
	if (!(fl & SUB_LIST)) {
 | 
						|
	    lleft = 0;		/* size of returned string */
 | 
						|
	    i = 0;	       /* start of last chunk we got from *sp */
 | 
						|
	    for (nd = firstnode(imd.repllist); nd; incnode(nd)) {
 | 
						|
		rd = (Repldata) getdata(nd);
 | 
						|
		lleft += rd->b - i; /* previous chunk of *sp */
 | 
						|
		lleft += strlen(rd->replstr);	/* the replaced bit */
 | 
						|
		i = rd->e;		/* start of next chunk of *sp */
 | 
						|
	    }
 | 
						|
	    lleft += l - i;	/* final chunk from *sp */
 | 
						|
	    start = t = zhalloc(lleft+1);
 | 
						|
	    i = 0;
 | 
						|
	    for (nd = firstnode(imd.repllist); nd; incnode(nd)) {
 | 
						|
		rd = (Repldata) getdata(nd);
 | 
						|
		memcpy(t, s + i, rd->b - i);
 | 
						|
		t += rd->b - i;
 | 
						|
		ptr = rd->replstr;
 | 
						|
		while (*ptr)
 | 
						|
		    *t++ = *ptr++;
 | 
						|
		i = rd->e;
 | 
						|
	    }
 | 
						|
	    memcpy(t, s + i, l - i);
 | 
						|
	    start[lleft] = '\0';
 | 
						|
	    *sp = (char *)start;
 | 
						|
	}
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    if (fl & SUB_LIST) {	/* safety: don't think this can happen */
 | 
						|
	return 0;
 | 
						|
    }
 | 
						|
 | 
						|
    /* munge the whole string: no match, so no replstr */
 | 
						|
    imd.replstr = NULL;
 | 
						|
    imd.repllist = NULL;
 | 
						|
    *sp = get_match_ret(&imd, 0, 0);
 | 
						|
    return (fl & SUB_RETFAIL) ? 0 : 1;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
#else
 | 
						|
 | 
						|
/*
 | 
						|
 * Increment pointer which may be on a Meta (x is a pointer variable),
 | 
						|
 * returning the incremented value (i.e. like pre-increment).
 | 
						|
 */
 | 
						|
#define METAINC(x)	((x) += (*(x) == Meta) ? 2 : 1)
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
igetmatch(char **sp, Patprog p, int fl, int n, char *replstr,
 | 
						|
	  LinkList *repllistp)
 | 
						|
{
 | 
						|
    char *s = *sp, *t, *send;
 | 
						|
    /*
 | 
						|
     * Note that ioff and uml count characters in the character
 | 
						|
     * set (Meta's are not included), while l counts characters in the
 | 
						|
     * metafied string.  umlen is a counter for (unmetafied) character
 | 
						|
     * lengths.
 | 
						|
     */
 | 
						|
    int ioff, l = strlen(*sp), uml = ztrlen(*sp), matched = 1, umlen;
 | 
						|
    struct patstralloc patstralloc;
 | 
						|
    struct imatchdata imd;
 | 
						|
 | 
						|
    (void)patallocstr(p, s, l, uml, 1, &patstralloc);
 | 
						|
    s = patstralloc.alloced;
 | 
						|
    DPUTS(!s, "forced patallocstr failed");
 | 
						|
    send = s + uml;
 | 
						|
 | 
						|
    imd.mstr = *sp;
 | 
						|
    imd.mlen = l;
 | 
						|
    imd.ustr = s;
 | 
						|
    imd.ulen = uml;
 | 
						|
    imd.flags = fl;
 | 
						|
    imd.replstr = replstr;
 | 
						|
    imd.repllist = NULL;
 | 
						|
 | 
						|
    /* perform must-match test for complex closures */
 | 
						|
    if (p->mustoff)
 | 
						|
    {
 | 
						|
	char *muststr = (char *)p + p->mustoff;
 | 
						|
 | 
						|
	matched = 0;
 | 
						|
	if (p->patmlen <= uml)
 | 
						|
	{
 | 
						|
	    for (t = s; t <= send - p->patmlen; t++)
 | 
						|
	    {
 | 
						|
		if (!memcmp(muststr, t, p->patmlen)) {
 | 
						|
		    matched = 1;
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    /* in case we used the prog before... */
 | 
						|
    p->flags &= ~(PAT_NOTSTART|PAT_NOTEND);
 | 
						|
 | 
						|
    if (fl & SUB_ALL) {
 | 
						|
	int i = matched && pattrylen(p, s, uml, 0, &patstralloc, 0);
 | 
						|
	if (!i)
 | 
						|
	    imd.replstr = NULL;
 | 
						|
	*sp = get_match_ret(&imd, 0, i ? l : 0);
 | 
						|
	if (! **sp && (((fl & SUB_MATCH) && !i) || ((fl & SUB_REST) && i)))
 | 
						|
	    return 0;
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
    if (matched) {
 | 
						|
	switch (fl & (SUB_END|SUB_LONG|SUB_SUBSTR)) {
 | 
						|
	case 0:
 | 
						|
	case SUB_LONG:
 | 
						|
	    /*
 | 
						|
	     * Largest/smallest possible match at head of string.
 | 
						|
	     * First get the longest match...
 | 
						|
	     */
 | 
						|
	    if (pattrylen(p, s, uml, 0, &patstralloc, 0)) {
 | 
						|
		/* patmatchlen returns metafied length, as we need */
 | 
						|
	        int mlen = patmatchlen();
 | 
						|
		if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | 
						|
		    send = s + mlen;
 | 
						|
		    /*
 | 
						|
		     * ... now we know whether it's worth looking for the
 | 
						|
		     * shortest, which we do by brute force.
 | 
						|
		     */
 | 
						|
		    for (t = s, umlen = 0; t < s + mlen; METAINC(t), umlen++) {
 | 
						|
			set_pat_end(p, *t);
 | 
						|
			if (pattrylen(p, s, umlen, 0, &patstralloc, 0)) {
 | 
						|
			    mlen = patmatchlen();
 | 
						|
			    break;
 | 
						|
			}
 | 
						|
		    }
 | 
						|
		}
 | 
						|
		*sp = get_match_ret(&imd, 0, mlen);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
 | 
						|
	case SUB_END:
 | 
						|
	    /* Smallest possible match at tail of string:  *
 | 
						|
	     * move back down string until we get a match. *
 | 
						|
	     * There's no optimization here.               */
 | 
						|
	    for (ioff = uml, t = send, umlen = 0; t >= s;
 | 
						|
		 t--, ioff--, umlen++) {
 | 
						|
		set_pat_start(p, t-s);
 | 
						|
		if (pattrylen(p, t, umlen, 0, &patstralloc, ioff)) {
 | 
						|
		    *sp = get_match_ret(&imd, t - s, uml);
 | 
						|
		    return 1;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
 | 
						|
	case (SUB_END|SUB_LONG):
 | 
						|
	    /* Largest possible match at tail of string:       *
 | 
						|
	     * move forward along string until we get a match. *
 | 
						|
	     * Again there's no optimisation.                  */
 | 
						|
	    for (ioff = 0, t = s, umlen = uml; t < send;
 | 
						|
		 ioff++, t++, umlen--) {
 | 
						|
		set_pat_start(p, t-s);
 | 
						|
		if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff)) {
 | 
						|
		    *sp = get_match_ret(&imd, t-s, uml);
 | 
						|
		    return 1;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
 | 
						|
	case SUB_SUBSTR:
 | 
						|
	    /* Smallest at start, but matching substrings. */
 | 
						|
	    set_pat_start(p, l);
 | 
						|
	    if (!(fl & SUB_GLOBAL) &&
 | 
						|
		pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) {
 | 
						|
		*sp = get_match_ret(&imd, 0, 0);
 | 
						|
		return 1;
 | 
						|
	    } /* fall through */
 | 
						|
	case (SUB_SUBSTR|SUB_LONG):
 | 
						|
	    /* longest or smallest at start with substrings */
 | 
						|
	    t = s;
 | 
						|
	    if (fl & SUB_GLOBAL) {
 | 
						|
		imd.repllist = newlinklist();
 | 
						|
		if (repllistp)
 | 
						|
		    *repllistp = imd.repllist;
 | 
						|
	    }
 | 
						|
	    ioff = 0;		/* offset into string */
 | 
						|
	    umlen = uml;
 | 
						|
	    do {
 | 
						|
		/* loop over all matches for global substitution */
 | 
						|
		matched = 0;
 | 
						|
		for (; t < send; t++, ioff++, umlen--) {
 | 
						|
		    /* Find the longest match from this position. */
 | 
						|
		    set_pat_start(p, t-s);
 | 
						|
		    if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff)) {
 | 
						|
			char *mpos = t + patmatchlen();
 | 
						|
			if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | 
						|
			    char *ptr;
 | 
						|
			    int umlen2;
 | 
						|
			    for (ptr = t, umlen2 = 0; ptr < mpos;
 | 
						|
				 ptr++, umlen2++) {
 | 
						|
				set_pat_end(p, *ptr);
 | 
						|
				if (pattrylen(p, t, ptr - t, umlen2,
 | 
						|
					      &patstralloc, ioff)) {
 | 
						|
				    mpos = t + patmatchlen();
 | 
						|
				    break;
 | 
						|
				}
 | 
						|
			    }
 | 
						|
			}
 | 
						|
			if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) {
 | 
						|
			    *sp = get_match_ret(&imd, t-s, mpos-s);
 | 
						|
			    if (mpos == t)
 | 
						|
				mpos++;
 | 
						|
			}
 | 
						|
			if (!(fl & SUB_GLOBAL)) {
 | 
						|
			    if (n) {
 | 
						|
				/*
 | 
						|
				 * Looking for a later match: in this case,
 | 
						|
				 * we can continue looking for matches from
 | 
						|
				 * the next character, even if it overlaps
 | 
						|
				 * with what we just found.
 | 
						|
				 */
 | 
						|
				continue;
 | 
						|
			    } else {
 | 
						|
				return 1;
 | 
						|
			    }
 | 
						|
			}
 | 
						|
			/*
 | 
						|
			 * For a global match, we need to skip the stuff
 | 
						|
			 * which is already marked for replacement.
 | 
						|
			 */
 | 
						|
			matched = 1;
 | 
						|
			while (t < mpos) {
 | 
						|
			    ioff++;
 | 
						|
			    umlen--;
 | 
						|
			    t++;
 | 
						|
			}
 | 
						|
			break;
 | 
						|
		    }
 | 
						|
		}
 | 
						|
	    } while (matched);
 | 
						|
	    /*
 | 
						|
	     * check if we can match a blank string, if so do it
 | 
						|
	     * at the start.  Goodness knows if this is a good idea
 | 
						|
	     * with global substitution, so it doesn't happen.
 | 
						|
	     */
 | 
						|
	    set_pat_start(p, l);
 | 
						|
	    if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG &&
 | 
						|
		pattrylen(p, send, 0, 0, &patstralloc, 0) && !--n) {
 | 
						|
		*sp = get_match_ret(&imd, 0, 0);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
 | 
						|
	case (SUB_END|SUB_SUBSTR):
 | 
						|
	case (SUB_END|SUB_LONG|SUB_SUBSTR):
 | 
						|
	    /* Longest/shortest at end, matching substrings.       */
 | 
						|
	    {
 | 
						|
		set_pat_start(p, l);
 | 
						|
		if (pattrylen(p, send, 0, 0, &patstralloc, uml) && !--n) {
 | 
						|
		    *sp = get_match_ret(&imd, uml, uml);
 | 
						|
		    return 1;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    for (ioff = uml - 1, t = send - 1, umlen = 1; t >= s;
 | 
						|
		 t--, ioff--, umlen++) {
 | 
						|
		set_pat_start(p, t-s);
 | 
						|
		if (pattrylen(p, t, send - t, umlen, &patstralloc, ioff) &&
 | 
						|
		    !--n) {
 | 
						|
		    /* Found the longest match */
 | 
						|
		    char *mpos = t + patmatchlen();
 | 
						|
		    if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | 
						|
			char *ptr;
 | 
						|
			int umlen2;
 | 
						|
			for (ptr = t, umlen2 = 0; ptr < mpos;
 | 
						|
			     ptr++, umlen2++) {
 | 
						|
			    set_pat_end(p, *ptr);
 | 
						|
			    if (pattrylen(p, t, umlen2, 0, &patstralloc,
 | 
						|
					  ioff)) {
 | 
						|
				mpos = t + patmatchlen();
 | 
						|
				break;
 | 
						|
			    }
 | 
						|
			}
 | 
						|
		    }
 | 
						|
		    *sp = get_match_ret(&imd, t-s, mpos-s);
 | 
						|
		    return 1;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    set_pat_start(p, l);
 | 
						|
	    if ((fl & SUB_LONG) && pattrylen(p, send, 0, 0,
 | 
						|
					     &patstralloc, uml) &&
 | 
						|
		!--n) {
 | 
						|
		*sp = get_match_ret(&imd, uml, uml);
 | 
						|
		return 1;
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    if (imd.repllist && nonempty(imd.repllist)) {
 | 
						|
	/* Put all the bits of a global search and replace together. */
 | 
						|
	LinkNode nd;
 | 
						|
	Repldata rd;
 | 
						|
	int lleft = 0;		/* size of returned string */
 | 
						|
	char *ptr, *start;
 | 
						|
	int i;
 | 
						|
 | 
						|
	/*
 | 
						|
	 * Use metafied string again.
 | 
						|
	 * Results from get_match_ret in repllist are all metafied.
 | 
						|
	 */
 | 
						|
	s = *sp;
 | 
						|
	i = 0;			/* start of last chunk we got from *sp */
 | 
						|
	for (nd = firstnode(imd.repllist); nd; incnode(nd)) {
 | 
						|
	    rd = (Repldata) getdata(nd);
 | 
						|
	    lleft += rd->b - i; /* previous chunk of *sp */
 | 
						|
	    lleft += strlen(rd->replstr);	/* the replaced bit */
 | 
						|
	    i = rd->e;		/* start of next chunk of *sp */
 | 
						|
	}
 | 
						|
	lleft += l - i;	/* final chunk from *sp */
 | 
						|
	start = t = zhalloc(lleft+1);
 | 
						|
	i = 0;
 | 
						|
	for (nd = firstnode(imd.repllist); nd; incnode(nd)) {
 | 
						|
	    rd = (Repldata) getdata(nd);
 | 
						|
	    memcpy(t, s + i, rd->b - i);
 | 
						|
	    t += rd->b - i;
 | 
						|
	    ptr = rd->replstr;
 | 
						|
	    while (*ptr)
 | 
						|
		*t++ = *ptr++;
 | 
						|
	    i = rd->e;
 | 
						|
	}
 | 
						|
	memcpy(t, s + i, l - i);
 | 
						|
	start[lleft] = '\0';
 | 
						|
	*sp = (char *)start;
 | 
						|
	return 1;
 | 
						|
    }
 | 
						|
 | 
						|
    /* munge the whole string: no match, so no replstr */
 | 
						|
    imd.replstr = NULL;
 | 
						|
    imd.repllist = NULL;
 | 
						|
    *sp = get_match_ret(&imd, 0, 0);
 | 
						|
    return 1;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
#endif /* MULTIBYTE_SUPPORT */
 | 
						|
 | 
						|
/* blindly turn a string into a tokenised expression without lexing */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export void
 | 
						|
tokenize(char *s)
 | 
						|
{
 | 
						|
    zshtokenize(s, 0);
 | 
						|
}
 | 
						|
 | 
						|
/*
 | 
						|
 * shtokenize is used when we tokenize a string with GLOB_SUBST set.
 | 
						|
 * In that case we need to retain backslashes when we turn the
 | 
						|
 * pattern back into a string, so that the string is not
 | 
						|
 * modified if it failed to match a pattern.
 | 
						|
 *
 | 
						|
 * It may be modified by the effect of SH_GLOB which turns off
 | 
						|
 * various zsh-specific options.
 | 
						|
 */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export void
 | 
						|
shtokenize(char *s)
 | 
						|
{
 | 
						|
    int flags = ZSHTOK_SUBST;
 | 
						|
    if (isset(SHGLOB))
 | 
						|
	flags |= ZSHTOK_SHGLOB;
 | 
						|
    zshtokenize(s, flags);
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
static void
 | 
						|
zshtokenize(char *s, int flags)
 | 
						|
{
 | 
						|
    char *t;
 | 
						|
    int bslash = 0;
 | 
						|
 | 
						|
    for (; *s; s++) {
 | 
						|
      cont:
 | 
						|
	switch (*s) {
 | 
						|
	case Meta:
 | 
						|
	    /* skip both Meta and following character */
 | 
						|
	    s++;
 | 
						|
	    break;
 | 
						|
	case Bnull:
 | 
						|
	case Bnullkeep:
 | 
						|
	case '\\':
 | 
						|
	    if (bslash) {
 | 
						|
		s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    bslash = 1;
 | 
						|
	    continue;
 | 
						|
	case '<':
 | 
						|
	    if (flags & ZSHTOK_SHGLOB)
 | 
						|
		break;
 | 
						|
	    if (bslash) {
 | 
						|
		s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull;
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
	    t = s;
 | 
						|
	    while (idigit(*++s));
 | 
						|
	    if (!IS_DASH(*s))
 | 
						|
		goto cont;
 | 
						|
	    while (idigit(*++s));
 | 
						|
	    if (*s != '>')
 | 
						|
		goto cont;
 | 
						|
	    *t = Inang;
 | 
						|
	    *s = Outang;
 | 
						|
	    break;
 | 
						|
	case '(':
 | 
						|
	case '|':
 | 
						|
	case ')':
 | 
						|
	    if (flags & ZSHTOK_SHGLOB)
 | 
						|
		break;
 | 
						|
	    /*FALLTHROUGH*/
 | 
						|
	case '>':
 | 
						|
	case '^':
 | 
						|
	case '#':
 | 
						|
	case '~':
 | 
						|
	case '[':
 | 
						|
	case ']':
 | 
						|
	case '*':
 | 
						|
	case '?':
 | 
						|
	case '=':
 | 
						|
	case '-':
 | 
						|
	case '!':
 | 
						|
	    for (t = ztokens; *t; t++) {
 | 
						|
		if (*t == *s) {
 | 
						|
		    if (bslash)
 | 
						|
			s[-1] = (flags & ZSHTOK_SUBST) ? Bnullkeep : Bnull;
 | 
						|
		    else
 | 
						|
			*s = (t - ztokens) + Pound;
 | 
						|
		    break;
 | 
						|
		}
 | 
						|
	    }
 | 
						|
	    break;
 | 
						|
	}
 | 
						|
	bslash = 0;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* remove unnecessary Nulargs */
 | 
						|
 | 
						|
/**/
 | 
						|
mod_export void
 | 
						|
remnulargs(char *s)
 | 
						|
{
 | 
						|
    if (*s) {
 | 
						|
	char *o = s, c;
 | 
						|
 | 
						|
	while ((c = *s++))
 | 
						|
	    if (c == Bnullkeep) {
 | 
						|
		/*
 | 
						|
		 * An active backslash that needs to be turned back into
 | 
						|
		 * a real backslash for output.  However, we don't
 | 
						|
		 * do that yet since we need to ignore it during
 | 
						|
		 * pattern matching.
 | 
						|
		 */
 | 
						|
		continue;
 | 
						|
	    } else if (inull(c)) {
 | 
						|
		char *t = s - 1;
 | 
						|
 | 
						|
		while ((c = *s++)) {
 | 
						|
		    if (c == Bnullkeep)
 | 
						|
			*t++ = '\\';
 | 
						|
		    else if (!inull(c))
 | 
						|
			*t++ = c;
 | 
						|
		}
 | 
						|
		*t = '\0';
 | 
						|
		if (!*o) {
 | 
						|
		    o[0] = Nularg;
 | 
						|
		    o[1] = '\0';
 | 
						|
		}
 | 
						|
		break;
 | 
						|
	    }
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
/* qualifier functions:  mostly self-explanatory, see glob(). */
 | 
						|
 | 
						|
/* device number */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualdev(UNUSED(char *name), struct stat *buf, off_t dv, UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return (off_t)buf->st_dev == dv;
 | 
						|
}
 | 
						|
 | 
						|
/* number of hard links to file */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualnlink(UNUSED(char *name), struct stat *buf, off_t ct, UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return (g_range < 0 ? buf->st_nlink < ct :
 | 
						|
	    g_range > 0 ? buf->st_nlink > ct :
 | 
						|
	    buf->st_nlink == ct);
 | 
						|
}
 | 
						|
 | 
						|
/* user ID */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualuid(UNUSED(char *name), struct stat *buf, off_t uid, UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return buf->st_uid == uid;
 | 
						|
}
 | 
						|
 | 
						|
/* group ID */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualgid(UNUSED(char *name), struct stat *buf, off_t gid, UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return buf->st_gid == gid;
 | 
						|
}
 | 
						|
 | 
						|
/* device special file? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualisdev(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
 | 
						|
}
 | 
						|
 | 
						|
/* block special file? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualisblk(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISBLK(buf->st_mode);
 | 
						|
}
 | 
						|
 | 
						|
/* character special file? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualischr(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISCHR(buf->st_mode);
 | 
						|
}
 | 
						|
 | 
						|
/* directory? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualisdir(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISDIR(buf->st_mode);
 | 
						|
}
 | 
						|
 | 
						|
/* FIFO? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualisfifo(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISFIFO(buf->st_mode);
 | 
						|
}
 | 
						|
 | 
						|
/* symbolic link? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualislnk(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISLNK(buf->st_mode);
 | 
						|
}
 | 
						|
 | 
						|
/* regular file? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualisreg(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISREG(buf->st_mode);
 | 
						|
}
 | 
						|
 | 
						|
/* socket? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualissock(UNUSED(char *name), struct stat *buf, UNUSED(off_t junk), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISSOCK(buf->st_mode);
 | 
						|
}
 | 
						|
 | 
						|
/* given flag is set in mode */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualflags(UNUSED(char *name), struct stat *buf, off_t mod, UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return mode_to_octal(buf->st_mode) & mod;
 | 
						|
}
 | 
						|
 | 
						|
/* mode matches specification */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualmodeflags(UNUSED(char *name), struct stat *buf, off_t mod, UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    long v = mode_to_octal(buf->st_mode), y = mod & 07777, n = mod >> 12;
 | 
						|
 | 
						|
    return ((v & y) == y && !(v & n));
 | 
						|
}
 | 
						|
 | 
						|
/* regular executable file? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualiscom(UNUSED(char *name), struct stat *buf, UNUSED(off_t mod), UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
 | 
						|
}
 | 
						|
 | 
						|
/* size in required range? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualsize(UNUSED(char *name), struct stat *buf, off_t size, UNUSED(char *dummy))
 | 
						|
{
 | 
						|
#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT)
 | 
						|
# define QS_CAST_SIZE()
 | 
						|
    zlong scaled = buf->st_size;
 | 
						|
#else
 | 
						|
# define QS_CAST_SIZE() (unsigned long)
 | 
						|
    unsigned long scaled = (unsigned long)buf->st_size;
 | 
						|
#endif
 | 
						|
 | 
						|
    switch (g_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;
 | 
						|
#if defined(ZSH_64_BIT_TYPE) || defined(LONG_IS_64_BIT)
 | 
						|
    case TT_GIGABYTES:
 | 
						|
        scaled += ZLONG_CONST(1073741823);
 | 
						|
        scaled /= ZLONG_CONST(1073741824);
 | 
						|
        break;
 | 
						|
    case TT_TERABYTES:
 | 
						|
        scaled += ZLONG_CONST(1099511627775);
 | 
						|
        scaled /= ZLONG_CONST(1099511627776);
 | 
						|
        break;
 | 
						|
#endif
 | 
						|
    }
 | 
						|
 | 
						|
    return (g_range < 0 ? scaled < QS_CAST_SIZE() size :
 | 
						|
	    g_range > 0 ? scaled > QS_CAST_SIZE() size :
 | 
						|
	    scaled == QS_CAST_SIZE() size);
 | 
						|
#undef QS_CAST_SIZE
 | 
						|
}
 | 
						|
 | 
						|
/* time in required range? */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualtime(UNUSED(char *name), struct stat *buf, off_t days, UNUSED(char *dummy))
 | 
						|
{
 | 
						|
    time_t now, diff;
 | 
						|
 | 
						|
    time(&now);
 | 
						|
    diff = now - (g_amc == 0 ? buf->st_atime : g_amc == 1 ? buf->st_mtime :
 | 
						|
		  buf->st_ctime);
 | 
						|
    /* handle multipliers indicating units */
 | 
						|
    switch (g_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 (g_range < 0 ? diff < days :
 | 
						|
	    g_range > 0 ? diff > days :
 | 
						|
	    diff == days);
 | 
						|
}
 | 
						|
 | 
						|
/* evaluate a string */
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualsheval(char *name, UNUSED(struct stat *buf), UNUSED(off_t days), char *str)
 | 
						|
{
 | 
						|
    Eprog prog;
 | 
						|
 | 
						|
    if ((prog = parse_string(str, 0))) {
 | 
						|
	int ef = errflag, lv = lastval, ret;
 | 
						|
	int cshglob = badcshglob;
 | 
						|
 | 
						|
	unsetparam("reply");
 | 
						|
	setsparam("REPLY", ztrdup(name));
 | 
						|
	badcshglob = 0;
 | 
						|
 | 
						|
	execode(prog, 1, 0, "globqual");
 | 
						|
 | 
						|
	if ((ret = lastval))
 | 
						|
	    badcshglob |= cshglob;
 | 
						|
	/* Retain any user interrupt error status */
 | 
						|
	errflag = ef | (errflag & ERRFLAG_INT);
 | 
						|
	lastval = lv;
 | 
						|
 | 
						|
	if (!(inserts = getaparam("reply")) &&
 | 
						|
	    !(inserts = gethparam("reply"))) {
 | 
						|
	    char *tmp;
 | 
						|
 | 
						|
	    if ((tmp = getsparam("reply")) || (tmp = getsparam("REPLY"))) {
 | 
						|
		static char *tmparr[2];
 | 
						|
 | 
						|
		tmparr[0] = tmp;
 | 
						|
		tmparr[1] = NULL;
 | 
						|
 | 
						|
		inserts = tmparr;
 | 
						|
	    }
 | 
						|
	}
 | 
						|
 | 
						|
	return !ret;
 | 
						|
    }
 | 
						|
    return 0;
 | 
						|
}
 | 
						|
 | 
						|
/**/
 | 
						|
static int
 | 
						|
qualnonemptydir(char *name, struct stat *buf, UNUSED(off_t days), UNUSED(char *str))
 | 
						|
{
 | 
						|
    DIR *dirh;
 | 
						|
    struct dirent *de;
 | 
						|
    int unamelen;
 | 
						|
    char *uname = unmetafy(dupstring(name), &unamelen);
 | 
						|
 | 
						|
    if (!S_ISDIR(buf->st_mode))
 | 
						|
	return 0;
 | 
						|
 | 
						|
    if (buf->st_nlink > 2)
 | 
						|
	return 1;
 | 
						|
 | 
						|
    if (!(dirh = opendir(uname)))
 | 
						|
	return 0;
 | 
						|
 | 
						|
    while ((de = readdir(dirh))) {
 | 
						|
	if (strcmp(de->d_name, ".") && strcmp(de->d_name, "..")) {
 | 
						|
	    closedir(dirh);
 | 
						|
	    return 1;
 | 
						|
	}
 | 
						|
    }
 | 
						|
 | 
						|
    closedir(dirh);
 | 
						|
    return 0;
 | 
						|
}
 |