mirror of
				git://git.code.sf.net/p/zsh/code
				synced 2025-10-31 06:00:54 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			2590 lines
		
	
	
	
		
			62 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			2590 lines
		
	
	
	
		
			62 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 {
 | |
|     char *name;
 | |
|     off_t size ALIGN64;
 | |
|     long atime;
 | |
|     long mtime;
 | |
|     long ctime;
 | |
|     long links;
 | |
|     off_t _size ALIGN64;
 | |
|     long _atime;
 | |
|     long _mtime;
 | |
|     long _ctime;
 | |
|     long _links;
 | |
| };
 | |
| 
 | |
| #define GS_NAME   1
 | |
| #define GS_SIZE   2
 | |
| #define GS_ATIME  4
 | |
| #define GS_MTIME  8
 | |
| #define GS_CTIME 16
 | |
| #define GS_LINKS 32
 | |
| 
 | |
| #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  2048
 | |
| 
 | |
| #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) */
 | |
| 
 | |
| /**/
 | |
| char *pathbuf;		/* pathname buffer (needed by pattern code) */
 | |
| 
 | |
| 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
 | |
| 
 | |
| 
 | |
| 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;
 | |
| 
 | |
| /* 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, gd_gf_sortlist[11];
 | |
| 
 | |
|     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)
 | |
| 
 | |
| /* and macros for save/restore */
 | |
| 
 | |
| #define save_globstate(N) \
 | |
|   do { \
 | |
|     memcpy(&(N), &curglobdata, sizeof(struct globdata)); \
 | |
|     (N).gd_pathpos = pathpos; \
 | |
|     (N).gd_pathbuf = pathbuf; \
 | |
|     (N).gd_pathbufsz = 0; \
 | |
|     (N).gd_pathbuf = NULL; \
 | |
|     (N).gd_glob_pre = glob_pre; \
 | |
|     (N).gd_glob_suf = glob_suf; \
 | |
|   } while (0)
 | |
| 
 | |
| #define restore_globstate(N) \
 | |
|   do { \
 | |
|     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; \
 | |
|   } 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 */
 | |
| };
 | |
| 
 | |
| /* Next character after one which may be a Meta (x is any char *) */
 | |
| #define METANEXT(x)	(*(x) == Meta ? (x)+2 : (x)+1)
 | |
| /*
 | |
|  * 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)
 | |
| /*
 | |
|  * Return unmetafied char from string (x is any char *)
 | |
|  */
 | |
| #define UNMETA(x)	(*(x) == Meta ? (x)[1] ^ 32 : *(x))
 | |
| 
 | |
| /* 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)
 | |
| {
 | |
|     DPUTS(!pathbuf, "BUG: pathbuf not initialised");
 | |
|     while (pathpos + (int) strlen(s) + 1 >= pathbufsz)
 | |
| 	pathbuf = realloc(pathbuf, pathbufsz *= 2);
 | |
|     while ((pathbuf[pathpos++] = *s++));
 | |
|     pathbuf[pathpos - 1] = '/';
 | |
|     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 chechked for existance. *
 | |
|  * 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];
 | |
| 
 | |
|     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.
 | |
| 	 */
 | |
| 	buf[pathpos - pathbufcwd] = '.';
 | |
| 	buf[pathpos - pathbufcwd + 1] = '\0';
 | |
| 	l = 0;
 | |
|     }
 | |
|     unmetafy(buf, NULL);
 | |
|     if (!st)
 | |
| 	return access(buf, F_OK) && (!l || readlink(buf, NULL, 0));
 | |
|     return l ? lstat(buf, st) : stat(buf, st);
 | |
| }
 | |
| 
 | |
| /* This may be set by qualifier functions to an array of strings to insert
 | |
|  * into the list instead of the original string. */
 | |
| 
 | |
| 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;
 | |
| 
 | |
|     inserts = NULL;
 | |
| 
 | |
|     if (gf_listtypes || gf_markdirs) {
 | |
| 	/* Add the type marker to the end of the filename */
 | |
| 	mode_t mode;
 | |
| 	checked = statted = 1;
 | |
| 	if (statfullpath(s, &buf, 1))
 | |
| 	    return;
 | |
| 	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))
 | |
| 	    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))
 | |
| 		    return;
 | |
| 		qn = qo;
 | |
| 		continue;
 | |
| 	    }
 | |
| 	    qn = qn->next;
 | |
| 	}
 | |
|     } else if (!checked) {
 | |
| 	if (statfullpath(s, NULL, 1))
 | |
| 	    return;
 | |
| 	statted = 1;
 | |
| 	news = dyncat(pathbuf, news);
 | |
|     } else
 | |
| 	news = dyncat(pathbuf, news);
 | |
| 
 | |
|     while (!inserts || (news = dupstring(*inserts++))) {
 | |
| 	if (colonmod) {
 | |
| 	    /* Handle the remainder of the qualifer:  e.g. (:r:s/foo/bar/). */
 | |
| 	    s = colonmod;
 | |
| 	    modify(&news, &s);
 | |
| 	}
 | |
| 	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;
 | |
| 	}
 | |
| 	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;
 | |
| 	}
 | |
| 	matchptr++;
 | |
| 
 | |
| 	if (++matchct == matchsz) {
 | |
| 	    matchbuf = (Gmatch )realloc((char *)matchbuf,
 | |
| 					sizeof(struct gmatch) * (matchsz *= 2));
 | |
| 
 | |
| 	    matchptr = matchbuf + matchct;
 | |
| 	}
 | |
| 	if (!inserts)
 | |
| 	    break;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* Check to see if str is eligible for filename generation. */
 | |
| 
 | |
| /**/
 | |
| mod_export int
 | |
| haswilds(char *str)
 | |
| {
 | |
|     /* `[' and `]' are legal even if bad patterns are usually not. */
 | |
|     if ((*str == Inbrack || *str == Outbrack) && !str[1])
 | |
| 	return 0;
 | |
| 
 | |
|     /* If % is immediately followed by ?, then that ? is     *
 | |
|      * not treated as a wildcard.  This is so you don't have *
 | |
|      * to escape job references such as %?foo.               */
 | |
|     if (str[0] == '%' && str[1] == Quest)
 | |
| 	str[1] = '?';
 | |
| 
 | |
|     for (; *str; str++) {
 | |
| 	switch (*str) {
 | |
| 	    case Inpar:
 | |
| 	    case Bar:
 | |
| 	    case Star:
 | |
| 	    case Inbrack:
 | |
| 	    case Inang:
 | |
| 	    case Quest:
 | |
| 		return 1;
 | |
| 	    case Pound:
 | |
| 	    case Hat:
 | |
| 		if (isset(EXTENDEDGLOB))
 | |
| 		    return 1;
 | |
| 		break;
 | |
| 	}
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /* 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)
 | |
| {
 | |
|     Patprog p;
 | |
|     int closure;
 | |
|     int pbcwdsav = pathbufcwd;
 | |
|     int errssofar = errsfound;
 | |
|     struct dirsav ds;
 | |
| 
 | |
|     ds.ino = ds.dev = 0;
 | |
|     ds.dirname = NULL;
 | |
|     ds.dirfd = ds.level = -1;
 | |
|     if (!q)
 | |
| 	return;
 | |
| 
 | |
|     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);
 | |
|     }
 | |
|     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(pathbuf + pathbufcwd, &ds, 0);
 | |
| 	    if (err == -1)
 | |
| 		return;
 | |
| 	    if (err) {
 | |
| 		zerr("current directory lost during glob", NULL, 0);
 | |
| 		return;
 | |
| 	    }
 | |
| 	    pathbufcwd = pathpos;
 | |
| 	}
 | |
| 	if (q->next) {
 | |
| 	    /* Not the last path section. Just add it to the path. */
 | |
| 	    int oppos = pathpos;
 | |
| 
 | |
| 	    if (!errflag && !(q->closure && !strcmp(str, "."))) {
 | |
| 		addpath(str);
 | |
| 		if (!closure || !statfullpath("", NULL, 1))
 | |
| 		    scanner((q->closure) ? q : q->next);
 | |
| 		pathbuf[pathpos = oppos] = '\0';
 | |
| 	    }
 | |
| 	} else
 | |
| 	    insert(str, 0);
 | |
|     } 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 matchs the pattern... */
 | |
| 		if (pbcwdsav == pathbufcwd &&
 | |
| 		    strlen(fn) + pathpos - pathbufcwd >= PATH_MAX) {
 | |
| 		    int err;
 | |
| 
 | |
| 		    DPUTS(pathpos == pathbufcwd,
 | |
| 			  "BUG: filename longer than PATH_MAX");
 | |
| 		    err = lchdir(pathbuf + pathbufcwd, &ds, 0);
 | |
| 		    if (err == -1)
 | |
| 			break;
 | |
| 		    if (err) {
 | |
| 			zerr("current directory lost during glob", NULL, 0);
 | |
| 			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) {
 | |
| 				zerr("%e: %s", fn, errno);
 | |
| 				errflag = 0;
 | |
| 			    }
 | |
| 			    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);
 | |
| 	    }
 | |
| 	}
 | |
| 	closedir(lock);
 | |
| 	if (subdirs) {
 | |
| 	    int oppos = pathpos;
 | |
| 
 | |
| 	    for (fn = subdirs; fn < subdirs+subdirlen; ) {
 | |
| 		addpath(fn);
 | |
| 		fn += strlen(fn) + 1;
 | |
| 		memcpy((char *)&errsfound, fn, sizeof(int));
 | |
| 		fn += sizeof(int);
 | |
| 		scanner((q->closure) ? q : q->next);  /* scan next level */
 | |
| 		pathbuf[pathpos = oppos] = '\0';
 | |
| 	    }
 | |
| 	    hrealloc(subdirs, subdirlen, 0);
 | |
| 	}
 | |
|     }
 | |
|     if (pbcwdsav < pathbufcwd) {
 | |
| 	if (restoredir(&ds))
 | |
| 	    zerr("current directory lost during glob", NULL, 0);
 | |
| 	zsfree(ds.dirname);
 | |
| 	if (ds.dirfd >= 0)
 | |
| 	    close(ds.dirfd);
 | |
| 	pathbufcwd = pbcwdsav;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* 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 &&
 | |
|         (instr[2] == '/' || (instr[2] == Star && instr[3] == '/'))) {
 | |
| 	/* Match any number of directories. */
 | |
| 	int follow;
 | |
| 
 | |
| 	/* with three stars, follow symbolic links */
 | |
| 	follow = (instr[2] == Star);
 | |
| 	instr += (3 + follow);
 | |
| 
 | |
| 	/* Now get the next path component if there is one. */
 | |
| 	l1 = (Complist) zhalloc(sizeof *l1);
 | |
| 	if ((l1->next = parsecomplist(instr)) == NULL) {
 | |
| 	    errflag = 1;
 | |
| 	    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) == Inpar && !skipparens(Inpar, Outpar, (char **)&str) &&
 | |
|         *str == Pound && isset(EXTENDEDGLOB) && 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;
 | |
| 	    l1->closure = 1 + pdflag;
 | |
| 	    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 compoents */
 | |
| 	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 = 1;
 | |
|     return NULL;
 | |
| }
 | |
| 
 | |
| /* turn a string into a Complist struct:  this has path components */
 | |
| 
 | |
| /**/
 | |
| static Complist
 | |
| parsepat(char *str)
 | |
| {
 | |
|     patcompstart();
 | |
|     /*
 | |
|      * Check for initial globbing flags, so that they don't form
 | |
|      * a bogus path component.
 | |
|      */
 | |
|     if ((*str == Inpar && str[1] == Pound && isset(EXTENDEDGLOB)) ||
 | |
| 	(isset(KSHGLOB) && *str == '@' && str[1] == Inpar &&
 | |
| 	 str[2] == Pound)) {
 | |
| 	str += (*str == Inpar) ? 2 : 3;
 | |
| 	if (!patgetglobflags(&str))
 | |
| 	    return NULL;
 | |
|     }
 | |
| 
 | |
|     /* Now there is no (#X) in front, we can check the path. */
 | |
|     if (!pathbuf)
 | |
| 	pathbuf = zalloc(pathbufsz = PATH_MAX);
 | |
|     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", NULL, 0);
 | |
| 	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", NULL, 0);
 | |
| 		    return 0;
 | |
| 		}
 | |
| 	    }
 | |
| 	    if (how == '=' || how == '+') {
 | |
| 		yes |= val & mask;
 | |
| 		val = ~val;
 | |
| 	    }
 | |
| 	    if (how == '=' || how == '-')
 | |
| 		no |= val & mask;
 | |
| 	} else {
 | |
| 	    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", NULL, 0);
 | |
| 		return 0;
 | |
| 	    }
 | |
| 	    if (how == '=') {
 | |
| 		yes = (yes & ~t) | val;
 | |
| 		no = (no & ~t) | (~val & ~t);
 | |
| 	    } else if (how == '+')
 | |
| 		yes |= val;
 | |
| 	    else
 | |
| 		no |= val;
 | |
| 	}
 | |
|     } while (end && c != end);
 | |
| 
 | |
|     *s = p;
 | |
|     return ((yes & 07777) | ((no & 07777) << 12));
 | |
| }
 | |
| 
 | |
| static int
 | |
| gmatchcmp(Gmatch a, Gmatch b)
 | |
| {
 | |
|     int i, *s;
 | |
|     off_t r = 0L;
 | |
| 
 | |
|     for (i = gf_nsorts, s = gf_sortlist; i; i--, s++) {
 | |
| 	switch (*s & ~GS_DESC) {
 | |
| 	case GS_NAME:
 | |
| 	    r = notstrcmp(&a->name, &b->name);
 | |
| 	    break;
 | |
| 	case GS_SIZE:
 | |
| 	    r = b->size - a->size;
 | |
| 	    break;
 | |
| 	case GS_ATIME:
 | |
| 	    r = a->atime - b->atime;
 | |
| 	    break;
 | |
| 	case GS_MTIME:
 | |
| 	    r = a->mtime - b->mtime;
 | |
| 	    break;
 | |
| 	case GS_CTIME:
 | |
| 	    r = a->ctime - b->ctime;
 | |
| 	    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;
 | |
| 	    break;
 | |
| 	case GS__MTIME:
 | |
| 	    r = a->_mtime - b->_mtime;
 | |
| 	    break;
 | |
| 	case GS__CTIME:
 | |
| 	    r = a->_ctime - b->_ctime;
 | |
| 	    break;
 | |
| 	case GS__LINKS:
 | |
| 	    r = b->_links - a->_links;
 | |
| 	    break;
 | |
| 	}
 | |
| 	if (r)
 | |
| 	    return (int) ((*s & GS_DESC) ? -r : r);
 | |
|     }
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| /* Main entry point to the globbing code for filename globbing. *
 | |
|  * np points to a node in the list list which will be expanded  *
 | |
|  * into a series of nodes.                                      */
 | |
| 
 | |
| /**/
 | |
| void
 | |
| glob(LinkList list, LinkNode np, 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, last = -1;		/* index of first/last match to  */
 | |
| 				        /* return */
 | |
|     struct globdata saved;		/* saved glob state              */
 | |
| 
 | |
|     if (unset(GLOBOPT) || !haswilds(ostr)) {
 | |
| 	if (!nountok)
 | |
| 	    untokenize(ostr);
 | |
| 	return;
 | |
|     }
 | |
|     save_globstate(saved);
 | |
| 
 | |
|     str = dupstring(ostr);
 | |
|     sl = strlen(str);
 | |
|     uremnode(list, np);
 | |
| 
 | |
|     /* Initialise state variables for current file pattern */
 | |
|     qo = qn = quals = ql = NULL;
 | |
|     qualct = qualorct = 0;
 | |
|     colonmod = NULL;
 | |
|     gf_nullglob = isset(NULLGLOB);
 | |
|     gf_markdirs = isset(MARKDIRS);
 | |
|     gf_listtypes = gf_follow = 0;
 | |
|     gf_noglobdots = unset(GLOBDOTS);
 | |
|     gf_numsort = isset(NUMERICGLOBSORT);
 | |
|     gf_sorts = gf_nsorts = 0;
 | |
| 
 | |
|     /* Check for qualifiers */
 | |
|     if (isset(BAREGLOBQUAL) && str[sl - 1] == Outpar) {
 | |
| 	char *s;
 | |
| 
 | |
| 	/* Check these are really qualifiers, not a set of *
 | |
| 	 * alternatives or exclusions                      */
 | |
| 	for (s = str + sl - 2; *s != Inpar; s--)
 | |
| 	    if (*s == Bar || *s == Outpar ||
 | |
| 		(isset(EXTENDEDGLOB) && *s == Tilde))
 | |
| 		break;
 | |
| 	if (*s == Inpar && (!isset(EXTENDEDGLOB) || s[1] != Pound)) {
 | |
| 	    /* Real qualifiers found. */
 | |
| 	    int sense = 0;	   /* bit 0 for match (0)/don't match (1)   */
 | |
| 				   /* bit 1 for follow links (2), don't (0) */
 | |
| 	    off_t data = 0;	   /* Any numerical argument required       */
 | |
| 	    char *sdata = NULL; /* Any list argument required            */
 | |
| 	    int (*func) _((char *, Statptr, off_t, char *));
 | |
| 
 | |
| 	    str[sl-1] = 0;
 | |
| 	    *s++ = 0;
 | |
| 	    while (*s && !colonmod) {
 | |
| 		func = (int (*) _((char *, Statptr, off_t, char *)))0;
 | |
| 		if (idigit(*s)) {
 | |
| 		    /* Store numeric argument for qualifier */
 | |
| 		    func = qualflags;
 | |
| 		    data = 0;
 | |
| 		    sdata = NULL;
 | |
| 		    while (idigit(*s))
 | |
| 			data = data * 010 + (*s++ - '0');
 | |
| 		} else if (*s == ',') {
 | |
| 		    /* A comma separates alternative sets of qualifiers */
 | |
| 		    s++;
 | |
| 		    sense = 0;
 | |
| 		    if (qualct) {
 | |
| 			qn = (struct qual *)hcalloc(sizeof *qn);
 | |
| 			qo->or = qn;
 | |
| 			qo = qn;
 | |
| 			qualorct++;
 | |
| 			qualct = 0;
 | |
| 			ql = NULL;
 | |
| 		    }
 | |
| 		} else
 | |
| 		    switch (*s++) {
 | |
| 		    case ':':
 | |
| 			/* Remaining arguments are history-type     *
 | |
| 			 * colon substitutions, handled separately. */
 | |
| 			colonmod = s - 1;
 | |
| 			untokenize(colonmod);
 | |
| 			break;
 | |
| 		    case Hat:
 | |
| 		    case '^':
 | |
| 			/* Toggle sense:  go from positive to *
 | |
| 			 * negative match and vice versa.     */
 | |
| 			sense ^= 1;
 | |
| 			break;
 | |
| 		    case '-':
 | |
| 			/* Toggle matching of symbolic links */
 | |
| 			sense ^= 2;
 | |
| 			break;
 | |
| 		    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;
 | |
| 
 | |
| 			    /* Find matching delimiters */
 | |
| 			    tt = get_strarg(s);
 | |
| 			    if (!*tt) {
 | |
| 				zerr("missing end of name",
 | |
| 				     NULL, 0);
 | |
| 				data = 0;
 | |
| 			    } else {
 | |
| #ifdef HAVE_GETPWNAM
 | |
| 				struct passwd *pw;
 | |
| 				sav = *tt;
 | |
| 				*tt = '\0';
 | |
| 
 | |
| 				if ((pw = getpwnam(s + 1)))
 | |
| 				    data = pw->pw_uid;
 | |
| 				else {
 | |
| 				    zerr("unknown user", NULL, 0);
 | |
| 				    data = 0;
 | |
| 				}
 | |
| 				*tt = sav;
 | |
| #else /* !HAVE_GETPWNAM */
 | |
| 				sav = *tt;
 | |
| 				zerr("unknown user", NULL, 0);
 | |
| 				data = 0;
 | |
| #endif /* !HAVE_GETPWNAM */
 | |
| 				if (sav)
 | |
| 				    s = tt + 1;
 | |
| 				else
 | |
| 				    s = tt;
 | |
| 			    }
 | |
| 			}
 | |
| 			break;
 | |
| 		    case 'g':
 | |
| 			/* Given gid or group id... works like `u' */
 | |
| 			func = qualgid;
 | |
| 			/* either the actual gid... */
 | |
| 			if (idigit(*s))
 | |
| 			    data = qgetnum(&s);
 | |
| 			else {
 | |
| 			    /* ...or a delimited group name. */
 | |
| 			    char sav, *tt;
 | |
| 
 | |
| 			    tt = get_strarg(s);
 | |
| 			    if (!*tt) {
 | |
| 				zerr("missing end of name",
 | |
| 				     NULL, 0);
 | |
| 				data = 0;
 | |
| 			    } else {
 | |
| #ifdef HAVE_GETGRNAM
 | |
| 				struct group *gr;
 | |
| 				sav = *tt;
 | |
| 				*tt = '\0';
 | |
| 
 | |
| 				if ((gr = getgrnam(s + 1)))
 | |
| 				    data = gr->gr_gid;
 | |
| 				else {
 | |
| 				    zerr("unknown group", NULL, 0);
 | |
| 				    data = 0;
 | |
| 				}
 | |
| 				*tt = sav;
 | |
| #else /* !HAVE_GETGRNAM */
 | |
| 				sav = *tt;
 | |
| 				zerr("unknown group", NULL, 0);
 | |
| 				data = 0;
 | |
| #endif /* !HAVE_GETGRNAM */
 | |
| 				if (sav)
 | |
| 				    s = tt + 1;
 | |
| 				else
 | |
| 				    s = tt;
 | |
| 			    }
 | |
| 			}
 | |
| 			break;
 | |
| 		    case 'f':
 | |
| 			/* Match modes with chmod-spec. */
 | |
| 			func = qualmodeflags;
 | |
| 			data = qgetmodespec(&s);
 | |
| 			break;
 | |
| 		    case 'M':
 | |
| 			/* Mark directories with a / */
 | |
| 			if ((gf_markdirs = !(sense & 1)))
 | |
| 			    gf_follow = sense & 2;
 | |
| 			break;
 | |
| 		    case 'T':
 | |
| 			/* Mark types in a `ls -F' type fashion */
 | |
| 			if ((gf_listtypes = !(sense & 1)))
 | |
| 			    gf_follow = sense & 2;
 | |
| 			break;
 | |
| 		    case 'N':
 | |
| 			/* Nullglob:  remove unmatched patterns. */
 | |
| 			gf_nullglob = !(sense & 1);
 | |
| 			break;
 | |
| 		    case 'D':
 | |
| 			/* Glob dots: match leading dots implicitly */
 | |
| 			gf_noglobdots = sense & 1;
 | |
| 			break;
 | |
| 		    case 'n':
 | |
| 			/* Numeric glob sort */
 | |
| 			gf_numsort = !(sense & 1);
 | |
| 			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;
 | |
| 		      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;
 | |
| 			}
 | |
| 			/* See if it's greater than, equal to, or less than */
 | |
| 			if ((g_range = *s == '+' ? 1 : *s == '-' ? -1 : 0))
 | |
| 			    ++s;
 | |
| 			data = qgetnum(&s);
 | |
| 			break;
 | |
| 
 | |
| 		    case 'o':
 | |
| 		    case 'O':
 | |
| 			{
 | |
| 			    int t;
 | |
| 
 | |
| 			    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;
 | |
| 			    default:
 | |
| 				zerr("unknown sort specifier", NULL, 0);
 | |
| 				restore_globstate(saved);
 | |
| 				return;
 | |
| 			    }
 | |
| 			    if ((sense & 2) && t != GS_NAME)
 | |
| 				t <<= GS_SHIFT;
 | |
| 			    if (gf_sorts & t) {
 | |
| 				zerr("doubled sort specifier", NULL, 0);
 | |
| 				restore_globstate(saved);
 | |
| 				return;
 | |
| 			    }
 | |
| 			    gf_sorts |= t;
 | |
| 			    gf_sortlist[gf_nsorts++] = t |
 | |
| 				(((sense & 1) ^ (s[-1] == 'O')) ? GS_DESC : 0);
 | |
| 			    s++;
 | |
| 			    break;
 | |
| 			}
 | |
| 		    case 'e':
 | |
| 			{
 | |
| 			    char sav, *tt = get_strarg(s);
 | |
| 
 | |
| 			    if (!*tt) {
 | |
| 				zerr("missing end of string", NULL, 0);
 | |
| 				data = 0;
 | |
| 			    } else {
 | |
| 				sav = *tt;
 | |
| 				*tt = '\0';
 | |
| 				func = qualsheval;
 | |
| 				sdata = dupstring(s + 1);
 | |
| 				untokenize(sdata);
 | |
| 				*tt = sav;
 | |
| 				if (sav)
 | |
| 				    s = tt + 1;
 | |
| 				else
 | |
| 				    s = tt;
 | |
| 			    }
 | |
| 			    break;
 | |
| 			}
 | |
| 		    case '[':
 | |
| 		    case Inbrack:
 | |
| 			{
 | |
| 			    char *os = --s;
 | |
| 			    struct value v;
 | |
| 
 | |
| 			    v.isarr = SCANPM_WANTVALS;
 | |
| 			    v.pm = NULL;
 | |
| 			    v.b = -1;
 | |
| 			    v.inv = 0;
 | |
| 			    if (getindex(&s, &v) || s == os) {
 | |
| 				zerr("invalid subscript", NULL, 0);
 | |
| 				restore_globstate(saved);
 | |
| 				return;
 | |
| 			    }
 | |
| 			    first = v.a;
 | |
| 			    last = v.b;
 | |
| 			    break;
 | |
| 			}
 | |
| 		    default:
 | |
| 			zerr("unknown file attribute", NULL, 0);
 | |
| 			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 (!quals)
 | |
| 			quals = 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;
 | |
| 		}
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|     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 = 0;
 | |
| 	zerr("bad pattern: %s", ostr, 0);
 | |
| 	return;
 | |
|     }
 | |
|     if (!gf_nsorts) {
 | |
| 	gf_sortlist[0] = gf_sorts = 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);
 | |
| 
 | |
|     /* Deal with failures to match depending on options */
 | |
|     if (matchct)
 | |
| 	badcshglob |= 2;	/* at least one cmd. line expansion O.K. */
 | |
|     else if (!gf_nullglob) {
 | |
| 	if (isset(CSHNULLGLOB)) {
 | |
| 	    badcshglob |= 1;	/* at least one cmd. line expansion failed */
 | |
| 	} else if (isset(NOMATCH)) {
 | |
| 	    zerr("no matches found: %s", ostr, 0);
 | |
| 	    free(matchbuf);
 | |
| 	    return;
 | |
| 	} else {
 | |
| 	    /* treat as an ordinary string */
 | |
| 	    untokenize(matchptr->name = dupstring(ostr));
 | |
| 	    matchptr++;
 | |
| 	    matchct = 1;
 | |
| 	}
 | |
|     }
 | |
|     /* Sort arguments in to lexical (and possibly numeric) order. *
 | |
|      * This is reversed to facilitate insertion into the list.    */
 | |
|     qsort((void *) & matchbuf[0], matchct, sizeof(struct gmatch),
 | |
| 	       (int (*) _((const void *, const void *)))gmatchcmp);
 | |
| 
 | |
|     if (first < 0)
 | |
| 	first += matchct;
 | |
|     if (last < 0)
 | |
| 	last += matchct;
 | |
|     if (first < 0)
 | |
| 	first = 0;
 | |
|     if (last >= matchct)
 | |
| 	last = matchct - 1;
 | |
|     if (first <= last) {
 | |
| 	matchptr = matchbuf + matchct - 1 - last;
 | |
| 	last -= first;
 | |
| 	while (last-- >= 0) {		/* insert matches in the arg list */
 | |
| 	    insertlinknode(list, node, matchptr->name);
 | |
| 	    matchptr++;
 | |
| 	}
 | |
|     }
 | |
|     free(matchbuf);
 | |
| 
 | |
|     restore_globstate(saved);
 | |
| }
 | |
| 
 | |
| /* Return the order of two strings, taking into account *
 | |
|  * possible numeric order if NUMERICGLOBSORT is set.    *
 | |
|  * The comparison here is reversed.                     */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| notstrcmp(char **a, char **b)
 | |
| {
 | |
|     char *c = *b, *d = *a;
 | |
|     int cmp;
 | |
| 
 | |
| #ifdef HAVE_STRCOLL
 | |
|     cmp = strcoll(c, d);
 | |
| #endif
 | |
|     for (; *c == *d && *c; c++, d++);
 | |
| #ifndef HAVE_STRCOLL
 | |
|     cmp = (int)STOUC(*c) - (int)STOUC(*d);
 | |
| #endif
 | |
|     if (gf_numsort && (idigit(*c) || idigit(*d))) {
 | |
| 	for (; c > *b && idigit(c[-1]); c--, d--);
 | |
| 	if (idigit(*c) && idigit(*d)) {
 | |
| 	    while (*c == '0')
 | |
| 		c++;
 | |
| 	    while (*d == '0')
 | |
| 		d++;
 | |
| 	    for (; idigit(*c) && *c == *d; c++, d++);
 | |
| 	    if (idigit(*c) || idigit(*d)) {
 | |
| 		cmp = (int)STOUC(*c) - (int)STOUC(*d);
 | |
| 		while (idigit(*c) && idigit(*d))
 | |
| 		    c++, d++;
 | |
| 		if (idigit(*c) && !idigit(*d))
 | |
| 		    return 1;
 | |
| 		if (idigit(*d) && !idigit(*c))
 | |
| 		    return -1;
 | |
| 	    }
 | |
| 	}
 | |
|     }
 | |
|     return cmp;
 | |
| }
 | |
| 
 | |
| /* 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 */
 | |
| 
 | |
| /**/
 | |
| 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) {
 | |
| 		lbr = str - 1;
 | |
| 		while (idigit(*str))
 | |
| 		    str++;
 | |
| 		if (*str == '.' && str[1] == '.') {
 | |
| 		    str++;
 | |
| 		    while (idigit(*++str));
 | |
| 		    if (*str == Outbrace &&
 | |
| 			(idigit(lbr[1]) || idigit(str[-1])))
 | |
| 			return 1;
 | |
| 		}
 | |
| 	    } else {
 | |
| 		char *s = --str;
 | |
| 
 | |
| 		if (skipparens(Inbrace, Outbrace, &str)) {
 | |
| 		    *lbr = *s = '{';
 | |
| 		    if (comma)
 | |
| 			str = comma;
 | |
| 		    if (mbr && mbr < str)
 | |
| 			str = mbr;
 | |
| 		    lbr = mbr = comma = NULL;
 | |
| 		} else if (!mbr)
 | |
| 		    mbr = s;
 | |
| 	    }
 | |
| 	    break;
 | |
| 	case Outbrace:
 | |
| 	    if (!lbr)
 | |
| 		str[-1] = '}';
 | |
| 	    else if (comma)
 | |
| 		return 1;
 | |
| 	    else {
 | |
| 		*lbr = '{';
 | |
| 		str[-1] = '}';
 | |
| 		if (mbr)
 | |
| 		    str = mbr;
 | |
| 		mbr = lbr = NULL;
 | |
| 	    }
 | |
| 	    break;
 | |
| 	case Comma:
 | |
| 	    if (!lbr)
 | |
| 		str[-1] = ',';
 | |
| 	    else if (!comma)
 | |
| 		comma = str - 1;
 | |
| 	    break;
 | |
| 	case '\0':
 | |
| 	    if (lbr)
 | |
| 		*lbr = '{';
 | |
| 	    if (!mbr && !comma)
 | |
| 		return 0;
 | |
| 	    if (comma)
 | |
| 		str = comma;
 | |
| 	    if (mbr && mbr < str)
 | |
| 		str = mbr;
 | |
| 	    lbr = mbr = comma = NULL;
 | |
| 	    break;
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* expand stuff like >>*.c */
 | |
| 
 | |
| /**/
 | |
| int
 | |
| xpandredir(struct redir *fn, LinkList tab)
 | |
| {
 | |
|     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 : PF_SINGLE);
 | |
|     /* 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 == MERGEIN || fn->type == MERGEOUT) {
 | |
| 	    if (s[0] == '-' && !s[1])
 | |
| 		fn->type = 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 == MERGEIN)
 | |
| 		    zerr("file number expected", NULL, 0);
 | |
| 		else
 | |
| 		    fn->type = ERRWRITE;
 | |
| 	    }
 | |
| 	}
 | |
|     } else if (fn->type == MERGEIN)
 | |
| 	zerr("file number expected", NULL, 0);
 | |
|     else {
 | |
| 	if (fn->type == MERGEOUT)
 | |
| 	    fn->type = ERRWRITE;
 | |
| 	while ((nam = (char *)ugetnode(&fake))) {
 | |
| 	    /* Loop over matches, duplicating the *
 | |
| 	     * redirection for each file found.   */
 | |
| 	    ff = (struct redir *) zhalloc(sizeof *ff);
 | |
| 	    *ff = *fn;
 | |
| 	    ff->name = nam;
 | |
| 	    addlinknode(tab, ff);
 | |
| 	    ret = 1;
 | |
| 	}
 | |
|     }
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| /* concatenate s1 and s2 in dynamically allocated buffer */
 | |
| 
 | |
| /**/
 | |
| mod_export char *
 | |
| dyncat(char *s1, char *s2)
 | |
| {
 | |
|     /* This version always uses space from the current heap. */
 | |
|     char *ptr;
 | |
|     int l1 = strlen(s1);
 | |
| 
 | |
|     ptr = (char *)zhalloc(l1 + strlen(s2) + 1);
 | |
|     strcpy(ptr, s1);
 | |
|     strcpy(ptr + l1, s2);
 | |
|     return ptr;
 | |
| }
 | |
| 
 | |
| /* concatenate s1, s2, and s3 in dynamically allocated buffer */
 | |
| 
 | |
| /**/
 | |
| mod_export char *
 | |
| tricat(char const *s1, char const *s2, char const *s3)
 | |
| {
 | |
|     /* This version always uses permanently-allocated space. */
 | |
|     char *ptr;
 | |
| 
 | |
|     ptr = (char *)zalloc(strlen(s1) + strlen(s2) + strlen(s3) + 1);
 | |
|     strcpy(ptr, s1);
 | |
|     strcat(ptr, s2);
 | |
|     strcat(ptr, s3);
 | |
|     return ptr;
 | |
| }
 | |
| 
 | |
| /* brace expansion */
 | |
| 
 | |
| /**/
 | |
| void
 | |
| xpandbraces(LinkList list, LinkNode *np)
 | |
| {
 | |
|     LinkNode node = (*np), last = prevnode(node);
 | |
|     char *str = (char *)getdata(node), *str3 = str, *str2;
 | |
|     int prev, bc, comma, dotdot;
 | |
| 
 | |
|     for (; *str != Inbrace; str++);
 | |
|     /* First, match up braces and see what we have. */
 | |
|     for (str2 = str, bc = comma = dotdot = 0; *str2; ++str2)
 | |
| 	if (*str2 == Inbrace)
 | |
| 	    ++bc;
 | |
| 	else if (*str2 == Outbrace) {
 | |
| 	    if (--bc == 0)
 | |
| 		break;
 | |
| 	} else if (bc == 1) {
 | |
| 	    if (*str2 == Comma)
 | |
| 		++comma;	/* we have {foo,bar} */
 | |
| 	    else if (*str2 == '.' && str2[1] == '.')
 | |
| 		dotdot++;	/* we have {num1..num2} */
 | |
| 	}
 | |
|     DPUTS(bc, "BUG: unmatched brace in xpandbraces()");
 | |
|     if (!comma && dotdot) {
 | |
| 	/* Expand range like 0..10 numerically: comma or recursive
 | |
| 	   brace expansion take precedence. */
 | |
| 	char *dots, *p;
 | |
| 	LinkNode olast = last;
 | |
| 	/* Get the first number of the range */
 | |
| 	int rstart = zstrtol(str+1,&dots,10), rend = 0, err = 0, rev = 0;
 | |
| 	int wid1 = (dots - str) - 1, wid2 = (str2 - dots) - 2;
 | |
| 	int strp = str - str3;
 | |
|       
 | |
| 	if (dots == str + 1 || *dots != '.' || dots[1] != '.')
 | |
| 	    err++;
 | |
| 	else {
 | |
| 	    /* Get the last number of the range */
 | |
| 	    rend = zstrtol(dots+2,&p,10);
 | |
| 	    if (p == dots+2 || p != str2)
 | |
| 		err++;
 | |
| 	}
 | |
| 	if (!err) {
 | |
| 	    /* If either no. begins with a zero, pad the output with   *
 | |
| 	     * zeroes. Otherwise, choose a min width to suppress them. */
 | |
| 	    int minw = (str[1] == '0') ? wid1 : (dots[2] == '0' ) ? wid2 :
 | |
| 		(wid2 > wid1) ? wid1 : wid2;
 | |
| 	    if (rstart > rend) {
 | |
| 		/* Handle decreasing ranges correctly. */
 | |
| 		int rt = rend;
 | |
| 		rend = rstart;
 | |
| 		rstart = rt;
 | |
| 		rev = 1;
 | |
| 	    }
 | |
| 	    uremnode(list, node);
 | |
| 	    for (; rend >= rstart; rend--) {
 | |
| 		/* Node added in at end, so do highest first */
 | |
| 		p = dupstring(str3);
 | |
| 		sprintf(p + strp, "%0*d", minw, rend);
 | |
| 		strcat(p + strp, str2 + 1);
 | |
| 		insertlinknode(list, last, p);
 | |
| 		if (rev)	/* decreasing:  add in reverse order. */
 | |
| 		    last = nextnode(last);
 | |
| 	    }
 | |
| 	    *np = nextnode(olast);
 | |
| 	    return;
 | |
| 	}
 | |
|     }
 | |
|     if (!comma && isset(BRACECCL)) {	/* {a-mnop} */
 | |
| 	/* Here we expand each character to a separate node,      *
 | |
| 	 * but also ranges of characters like a-m.  ccl is a      *
 | |
| 	 * set of flags saying whether each character is present; *
 | |
| 	 * the final list is in lexical order.                    */
 | |
| 	char ccl[256], *p;
 | |
| 	unsigned char c1, c2, lastch;
 | |
| 	unsigned int len, pl;
 | |
| 
 | |
| 	uremnode(list, node);
 | |
| 	memset(ccl, 0, sizeof(ccl) / sizeof(ccl[0]));
 | |
| 	for (p = str + 1, lastch = 0; p < str2;) {
 | |
| 	    if (itok(c1 = *p++))
 | |
| 		c1 = ztokens[c1 - STOUC(Pound)];
 | |
| 	    if ((char) c1 == Meta)
 | |
| 		c1 = 32 ^ *p++;
 | |
| 	    if (itok(c2 = *p))
 | |
| 		c2 = ztokens[c2 - STOUC(Pound)];
 | |
| 	    if ((char) c2 == Meta)
 | |
| 		c2 = 32 ^ p[1];
 | |
| 	    if (c1 == '-' && lastch && p < str2 && (int)lastch <= (int)c2) {
 | |
| 		while ((int)lastch < (int)c2)
 | |
| 		    ccl[lastch++] = 1;
 | |
| 		lastch = 0;
 | |
| 	    } else
 | |
| 		ccl[lastch = c1] = 1;
 | |
| 	}
 | |
| 	pl = str - str3;
 | |
| 	len = pl + strlen(++str2) + 2;
 | |
| 	for (p = ccl + 255; p-- > ccl;)
 | |
| 	    if (*p) {
 | |
| 		c1 = p - ccl;
 | |
| 		if (imeta(c1)) {
 | |
| 		    str = 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 = patcompile(b, PAT_STATIC, NULL);
 | |
| 
 | |
|     if (!p) {
 | |
| 	zerr("bad pattern: %s", b, 0);
 | |
| 	return 0;
 | |
|     }
 | |
|     return pattry(p, a);
 | |
| }
 | |
| 
 | |
| /* do the ${foo%%bar}, ${foo#bar} stuff */
 | |
| /* please do not laugh at this code. */
 | |
| 
 | |
| struct repldata {
 | |
|     int b, e;			/* beginning and end of chunk to replace */
 | |
|     char *replstr;		/* replacement string to use */
 | |
| };
 | |
| typedef struct repldata *Repldata;
 | |
| 
 | |
| /* 
 | |
|  * List of bits of matches to concatenate with replacement string.
 | |
|  * The data is a struct repldata.  It is not used in cases like
 | |
|  * ${...//#foo/bar} even though SUB_GLOBAL is set, since the match
 | |
|  * is anchored.  It goes on the heap.
 | |
|  */
 | |
| 
 | |
| static LinkList repllist;
 | |
| 
 | |
| /* Having found a match in getmatch, decide what part of string
 | |
|  * to return.  The matched part starts b characters into string s
 | |
|  * and finishes e characters in: 0 <= b <= e <= strlen(s)
 | |
|  * (yes, empty matches should work).
 | |
|  * fl is a set of the SUB_* matches defined in zsh.h from SUB_MATCH onwards;
 | |
|  * the lower parts are ignored.
 | |
|  * replstr is the replacement string for a substitution
 | |
|  */
 | |
| 
 | |
| /**/
 | |
| static char *
 | |
| get_match_ret(char *s, int b, int e, int fl, char *replstr)
 | |
| {
 | |
|     char buf[80], *r, *p, *rr;
 | |
|     int ll = 0, l = strlen(s), bl = 0, t = 0, i;
 | |
| 
 | |
|     if (replstr) {
 | |
| 	if (fl & SUB_DOSUBST) {
 | |
| 	    replstr = dupstring(replstr);
 | |
| 	    singsub(&replstr);
 | |
| 	    untokenize(replstr);
 | |
| 	}
 | |
| 	if ((fl & SUB_GLOBAL) && repllist) {
 | |
| 	    /* We are replacing the chunk, just add this to the list */
 | |
| 	    Repldata rd = (Repldata) zhalloc(sizeof(*rd));
 | |
| 	    rd->b = b;
 | |
| 	    rd->e = e;
 | |
| 	    rd->replstr = replstr;
 | |
| 	    addlinknode(repllist, rd);
 | |
| 	    return s;
 | |
| 	}
 | |
| 	ll += strlen(replstr);
 | |
|     }
 | |
|     if (fl & SUB_MATCH)			/* matched portion */
 | |
| 	ll += 1 + (e - b);
 | |
|     if (fl & SUB_REST)		/* unmatched portion */
 | |
| 	ll += 1 + (l - (e - b));
 | |
|     if (fl & SUB_BIND) {
 | |
| 	/* position of start of matched portion */
 | |
| 	sprintf(buf, "%d ", b + 1);
 | |
| 	ll += (bl = strlen(buf));
 | |
|     }
 | |
|     if (fl & SUB_EIND) {
 | |
| 	/* position of end of matched portion */
 | |
| 	sprintf(buf + bl, "%d ", e + 1);
 | |
| 	ll += (bl = strlen(buf));
 | |
|     }
 | |
|     if (fl & SUB_LEN) {
 | |
| 	/* length of matched portion */
 | |
| 	sprintf(buf + bl, "%d ", e - b);
 | |
| 	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 = s + 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 = s; i < b; i++)
 | |
| 	    *rr++ = *p++;
 | |
| 	if (replstr)
 | |
| 	    for (p = replstr; *p; )
 | |
| 		*rr++ = *p++;
 | |
| 	for (i = e, p = s + e; i < l; i++)
 | |
| 	    *rr++ = *p++;
 | |
| 	t = 1;
 | |
|     }
 | |
|     *rr = '\0';
 | |
|     if (bl) {
 | |
| 	/* if there was a buffer (with a numeric result), add it; *
 | |
| 	 * if there was other stuff too, stick in a space first.  */
 | |
| 	if (t)
 | |
| 	    *rr++ = ' ';
 | |
| 	strcpy(rr, buf);
 | |
|     }
 | |
|     return r;
 | |
| }
 | |
| 
 | |
| 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, 0);
 | |
| 	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);
 | |
| }
 | |
| 
 | |
| /**/
 | |
| 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))
 | |
| 	    pp++;
 | |
| }
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| igetmatch(char **sp, Patprog p, int fl, int n, char *replstr)
 | |
| {
 | |
|     char *s = *sp, *t, sav;
 | |
|     int i, l = strlen(*sp), ml = ztrlen(*sp), matched = 1;
 | |
| 
 | |
|     repllist = NULL;
 | |
| 
 | |
|     /* perform must-match test for complex closures */
 | |
|     if (p->mustoff && !strstr((char *)s, (char *)p + p->mustoff))
 | |
| 	matched = 0;
 | |
| 
 | |
|     if (fl & SUB_ALL) {
 | |
| 	i = matched && pattry(p, s);
 | |
| 	*sp = get_match_ret(*sp, 0, i ? l : 0, fl, i ? replstr : 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 (pattry(p, s)) {
 | |
| 		char *mpos = patinput;
 | |
| 		if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | |
| 		    /*
 | |
| 		     * ... now we know whether it's worth looking for the
 | |
| 		     * shortest, which we do by brute force.
 | |
| 		     */
 | |
| 		    for (t = s; t < mpos; METAINC(t)) {
 | |
| 			sav = *t;
 | |
| 			*t = '\0';
 | |
| 			if (pattry(p, s)) {
 | |
| 			    mpos = patinput;
 | |
| 			    *t = sav;
 | |
| 			    break;
 | |
| 			}
 | |
| 			*t = sav;
 | |
| 		    }
 | |
| 		}
 | |
| 		*sp = get_match_ret(*sp, 0, mpos-s, fl, replstr);
 | |
| 		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.               */
 | |
| 	    patoffset = ml;
 | |
| 	    for (t = s + l; t >= s; t--, patoffset--) {
 | |
| 		if (pattry(p, t)) {
 | |
| 		    *sp = get_match_ret(*sp, t - s, l, fl, replstr);
 | |
| 		    patoffset = 0;
 | |
| 		    return 1;
 | |
| 		}
 | |
| 		if (t > s+1 && t[-2] == Meta)
 | |
| 		    t--;
 | |
| 	    }
 | |
| 	    patoffset = 0;
 | |
| 	    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 (i = 0, t = s; i < l; i++, t++, patoffset++) {
 | |
| 		if (pattry(p, t)) {
 | |
| 		    *sp = get_match_ret(*sp, i, l, fl, replstr);
 | |
| 		    patoffset = 0;
 | |
| 		    return 1;
 | |
| 		}
 | |
| 		if (*t == Meta)
 | |
| 		    i++, t++;
 | |
| 	    }
 | |
| 	    patoffset = 0;
 | |
| 	    break;
 | |
| 
 | |
| 	case SUB_SUBSTR:
 | |
| 	    /* Smallest at start, but matching substrings. */
 | |
| 	    if (!(fl & SUB_GLOBAL) && pattry(p, s + l) && !--n) {
 | |
| 		*sp = get_match_ret(*sp, 0, 0, fl, replstr);
 | |
| 		return 1;
 | |
| 	    } /* fall through */
 | |
| 	case (SUB_SUBSTR|SUB_LONG):
 | |
| 	    /* longest or smallest at start with substrings */
 | |
| 	    t = s;
 | |
| 	    if (fl & SUB_GLOBAL)
 | |
| 		repllist = newlinklist();
 | |
| 	    do {
 | |
| 		/* loop over all matches for global substitution */
 | |
| 		matched = 0;
 | |
| 		for (; t < s + l; t++, patoffset++) {
 | |
| 		    /* Find the longest match from this position. */
 | |
| 		    if (pattry(p, t) && patinput > t) {
 | |
| 			char *mpos = patinput;
 | |
| 			if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | |
| 			    char *ptr;
 | |
| 			    for (ptr = t; ptr < mpos; METAINC(ptr)) {
 | |
| 				sav = *ptr;
 | |
| 				*ptr = '\0';
 | |
| 				if (pattry(p, t)) {
 | |
| 				    mpos = patinput;
 | |
| 				    *ptr = sav;
 | |
| 				    break;
 | |
| 				}
 | |
| 				*ptr = sav;
 | |
| 			    }
 | |
| 			}
 | |
| 			if (!--n || (n <= 0 && (fl & SUB_GLOBAL))) {
 | |
| 			    *sp = get_match_ret(*sp, t-s, mpos-s, fl, replstr);
 | |
| 			    if (mpos == t)
 | |
| 				METAINC(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 {
 | |
| 				patoffset = 0;
 | |
| 				return 1;
 | |
| 			    }
 | |
| 			}
 | |
| 			/*
 | |
| 			 * For a global match, we need to skip the stuff
 | |
| 			 * which is already marked for replacement.
 | |
| 			 */
 | |
| 			matched = 1;
 | |
| 			for ( ; t < mpos; t++, patoffset++)
 | |
| 			    if (*t == Meta)
 | |
| 				t++;
 | |
| 			break;
 | |
| 		    }
 | |
| 		    if (*t == Meta)
 | |
| 			t++;
 | |
| 		}
 | |
| 	    } while (matched);
 | |
| 	    patoffset = 0;
 | |
| 	    /*
 | |
| 	     * 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.
 | |
| 	     */
 | |
| 	    if ((fl & (SUB_LONG|SUB_GLOBAL)) == SUB_LONG &&
 | |
| 		pattry(p, s + l) && !--n) {
 | |
| 		*sp = get_match_ret(*sp, 0, 0, fl, replstr);
 | |
| 		return 1;
 | |
| 	    }
 | |
| 	    break;
 | |
| 
 | |
| 	case (SUB_END|SUB_SUBSTR):
 | |
| 	    /* Shortest at end with substrings */
 | |
| 	    patoffset = ml;
 | |
| 	    if (pattry(p, s + l) && !--n) {
 | |
| 		*sp = get_match_ret(*sp, l, l, fl, replstr);
 | |
| 		patoffset = 0;
 | |
| 		return 1;
 | |
| 	    } /* fall through */
 | |
| 	case (SUB_END|SUB_LONG|SUB_SUBSTR):
 | |
| 	    /* Longest/shortest at end, matching substrings.       */
 | |
| 	    patoffset--;
 | |
| 	    for (t = s + l - 1; t >= s; t--, patoffset--) {
 | |
| 		if (t > s && t[-1] == Meta)
 | |
| 		    t--;
 | |
| 		if (pattry(p, t) && patinput > t && !--n) {
 | |
| 		    /* Found the longest match */
 | |
| 		    char *mpos = patinput;
 | |
| 		    if (!(fl & SUB_LONG) && !(p->flags & PAT_PURES)) {
 | |
| 			char *ptr;
 | |
| 			for (ptr = t; ptr < mpos; METAINC(ptr)) {
 | |
| 			    sav = *ptr;
 | |
| 			    *ptr = '\0';
 | |
| 			    if (pattry(p, t)) {
 | |
| 				mpos = patinput;
 | |
| 				*ptr = sav;
 | |
| 				break;
 | |
| 			    }
 | |
| 			    *ptr = sav;
 | |
| 			}
 | |
| 		    }
 | |
| 		    *sp = get_match_ret(*sp, t-s, mpos-s, fl, replstr);
 | |
| 		    patoffset = 0;
 | |
| 		    return 1;
 | |
| 		}
 | |
| 	    }
 | |
| 	    patoffset = ml;
 | |
| 	    if ((fl & SUB_LONG) && pattry(p, s + l) && !--n) {
 | |
| 		*sp = get_match_ret(*sp, l, l, fl, replstr);
 | |
| 		patoffset = 0;
 | |
| 		return 1;
 | |
| 	    }
 | |
| 	    patoffset = 0;
 | |
| 	    break;
 | |
| 	}
 | |
|     }
 | |
| 
 | |
|     if (repllist && nonempty(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;
 | |
| 
 | |
| 	i = 0;			/* start of last chunk we got from *sp */
 | |
| 	for (nd = firstnode(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(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 */
 | |
|     *sp = get_match_ret(*sp, 0, 0, fl, 0);
 | |
|     return 1;
 | |
| }
 | |
| 
 | |
| /* blindly turn a string into a tokenised expression without lexing */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| tokenize(char *s)
 | |
| {
 | |
|     char *t;
 | |
|     int bslash = 0;
 | |
| 
 | |
|     for (; *s; s++) {
 | |
|       cont:
 | |
| 	switch (*s) {
 | |
| 	case Bnull:
 | |
| 	case '\\':
 | |
| 	    if (bslash) {
 | |
| 		s[-1] = Bnull;
 | |
| 		break;
 | |
| 	    }
 | |
| 	    bslash = 1;
 | |
| 	    continue;
 | |
| 	case '<':
 | |
| 	    if (isset(SHGLOB))
 | |
| 		break;
 | |
| 	    if (bslash) {
 | |
| 		s[-1] = Bnull;
 | |
| 		break;
 | |
| 	    }
 | |
| 	    t = s;
 | |
| 	    while (idigit(*++s));
 | |
| 	    if (*s != '-')
 | |
| 		goto cont;
 | |
| 	    while (idigit(*++s));
 | |
| 	    if (*s != '>')
 | |
| 		goto cont;
 | |
| 	    *t = Inang;
 | |
| 	    *s = Outang;
 | |
| 	    break;
 | |
| 	case '(':
 | |
| 	case '|':
 | |
| 	case ')':
 | |
| 	    if (isset(SHGLOB))
 | |
| 		break;
 | |
| 	case '^':
 | |
| 	case '#':
 | |
| 	case '~':
 | |
| 	case '[':
 | |
| 	case ']':
 | |
| 	case '*':
 | |
| 	case '?':
 | |
| 	    for (t = ztokens; *t; t++)
 | |
| 		if (*t == *s) {
 | |
| 		    if (bslash)
 | |
| 			s[-1] = Bnull;
 | |
| 		    else
 | |
| 			*s = (t - ztokens) + Pound;
 | |
| 		    break;
 | |
| 		}
 | |
| 	}
 | |
| 	bslash = 0;
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* remove unnecessary Nulargs */
 | |
| 
 | |
| /**/
 | |
| mod_export void
 | |
| remnulargs(char *s)
 | |
| {
 | |
|     if (*s) {
 | |
| 	char *t = s, *p = s, c;
 | |
| 
 | |
| 	while ((c = *s++))
 | |
| 	    if (!INULL(c))
 | |
| 		*p++ = c;
 | |
| 	*p = '\0';
 | |
| 	if (!*t) {
 | |
| 	    t[0] = Nularg;
 | |
| 	    t[1] = '\0';
 | |
| 	}
 | |
|     }
 | |
| }
 | |
| 
 | |
| /* qualifier functions:  mostly self-explanatory, see glob(). */
 | |
| 
 | |
| /* device number */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualdev(char *name, struct stat *buf, off_t dv, char *dummy)
 | |
| {
 | |
|     return buf->st_dev == dv;
 | |
| }
 | |
| 
 | |
| /* number of hard links to file */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualnlink(char *name, struct stat *buf, off_t ct, 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(char *name, struct stat *buf, off_t uid, char *dummy)
 | |
| {
 | |
|     return buf->st_uid == uid;
 | |
| }
 | |
| 
 | |
| /* group ID */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualgid(char *name, struct stat *buf, off_t gid, char *dummy)
 | |
| {
 | |
|     return buf->st_gid == gid;
 | |
| }
 | |
| 
 | |
| /* device special file? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualisdev(char *name, struct stat *buf, off_t junk, char *dummy)
 | |
| {
 | |
|     return S_ISBLK(buf->st_mode) || S_ISCHR(buf->st_mode);
 | |
| }
 | |
| 
 | |
| /* block special file? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualisblk(char *name, struct stat *buf, off_t junk, char *dummy)
 | |
| {
 | |
|     return S_ISBLK(buf->st_mode);
 | |
| }
 | |
| 
 | |
| /* character special file? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualischr(char *name, struct stat *buf, off_t junk, char *dummy)
 | |
| {
 | |
|     return S_ISCHR(buf->st_mode);
 | |
| }
 | |
| 
 | |
| /* directory? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualisdir(char *name, struct stat *buf, off_t junk, char *dummy)
 | |
| {
 | |
|     return S_ISDIR(buf->st_mode);
 | |
| }
 | |
| 
 | |
| /* FIFO? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualisfifo(char *name, struct stat *buf, off_t junk, char *dummy)
 | |
| {
 | |
|     return S_ISFIFO(buf->st_mode);
 | |
| }
 | |
| 
 | |
| /* symbolic link? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualislnk(char *name, struct stat *buf, off_t junk, char *dummy)
 | |
| {
 | |
|     return S_ISLNK(buf->st_mode);
 | |
| }
 | |
| 
 | |
| /* regular file? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualisreg(char *name, struct stat *buf, off_t junk, char *dummy)
 | |
| {
 | |
|     return S_ISREG(buf->st_mode);
 | |
| }
 | |
| 
 | |
| /* socket? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualissock(char *name, struct stat *buf, off_t junk, char *dummy)
 | |
| {
 | |
|     return S_ISSOCK(buf->st_mode);
 | |
| }
 | |
| 
 | |
| /* given flag is set in mode */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualflags(char *name, struct stat *buf, off_t mod, char *dummy)
 | |
| {
 | |
|     return mode_to_octal(buf->st_mode) & mod;
 | |
| }
 | |
| 
 | |
| /* mode matches specification */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualmodeflags(char *name, struct stat *buf, off_t mod, 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(char *name, struct stat *buf, off_t mod, char *dummy)
 | |
| {
 | |
|     return S_ISREG(buf->st_mode) && (buf->st_mode & S_IXUGO);
 | |
| }
 | |
| 
 | |
| /* size in required range? */
 | |
| 
 | |
| /**/
 | |
| static int
 | |
| qualsize(char *name, struct stat *buf, off_t size, char *dummy)
 | |
| {
 | |
| #if defined(LONG_IS_64_BIT) || defined(OFF_T_IS_64_BIT)
 | |
| # define QS_CAST_SIZE()
 | |
|     off_t 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;
 | |
|     }
 | |
| 
 | |
|     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(char *name, struct stat *buf, off_t days, 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, struct stat *buf, off_t days, char *str)
 | |
| {
 | |
|     Eprog prog;
 | |
| 
 | |
|     if ((prog = parse_string(str, 0))) {
 | |
| 	int ef = errflag, lv = lastval, ret;
 | |
| 
 | |
| 	unsetparam("reply");
 | |
| 	setsparam("REPLY", ztrdup(name));
 | |
| 
 | |
| 	execode(prog, 1, 0);
 | |
| 
 | |
| 	ret = lastval;
 | |
| 	errflag = ef;
 | |
| 	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;
 | |
| }
 |