This is glob.c in view mode; [Download] [Up]
/* * glob.c * * Performs wildcard-expansion for UNIX, VMS and MS-DOS systems. * Written by T.E.Dickey for vile (april 1993). * * (MS-DOS code was originally taken from the winc app example of the * zortech compiler - pjr) * * To do: * make the wildcard expansion know about escaped characters (e.g., * with backslash a la UNIX. * * modify (ifdef-style) 'expand_leaf()' to allow ellipsis. * * $Header: /home/tom/src/vile/RCS/glob.c,v 1.54 1997/02/09 20:08:15 tom Exp $ * */ #include "estruct.h" /* global structures and defines */ #include "edef.h" /* defines 'slash' */ #if SYS_OS2 # define INCL_DOSFILEMGR # define INCL_ERRORS # include <os2.h> #else # include "dirstuff.h" /* directory-scanning interface & definitions */ #endif #define BAKTIK '`' /* used in UNIX shell for pipe */ #define isname(c) (isalnum(c) || ((c) == '_')) #define isdelim(c) ((c) == '(' || ((c) == '{')) #if SYS_MSDOS || SYS_WIN31 || SYS_OS2 || SYS_WINNT # define UNIX_GLOBBING OPT_GLOB_ENVIRON # if UNIX_GLOBBING # define DirEntryStr(p) p->d_name # else # ifdef __ZTC__ # define DeclareFind(p) struct FIND *p # define DirEntryStr(p) p->name # define DirFindFirst(path,p) (p = findfirst(path, 0)) # define DirFindNext(p) (p = findnext()) # else # define DeclareFind(p) struct find_t p # define DirEntryStr(p) p.name # define DirFindFirst(path,p) (!_dos_findfirst(path, 0, &p)) # define DirFindNext(p) (!_dos_findnext(&p)) # endif # endif # define DirEntryLen(p) strlen(DirEntryStr(p)) #endif /* SYS_MSDOS */ /* * Make the default unix globbing code use 'echo' rather than our internal * globber if we do not configure the 'glob' string-mode. */ #if SYS_UNIX && defined(GVAL_GLOB) && !OPT_VMS_PATH # define UNIX_GLOBBING 1 #endif #ifndef UNIX_GLOBBING # define UNIX_GLOBBING 0 #endif /* * Verify that we don't have both boolean- and string-valued 'glob' modes. */ #if defined(GMDGLOB) && defined(GVAL_GLOB) huh?? #else # ifdef GMDGLOB /* boolean */ # define globbing_active() global_g_val(GMDGLOB) # endif # ifdef GVAL_GLOB /* string */ # define globbing_active() !is_falsem(global_g_val_ptr(GVAL_GLOB)) # endif # ifndef globbing_active # define globbing_active() TRUE # endif #endif /* * Some things simply don't work on VMS: pipes and $variables */ #if OPT_VMS_PATH # # undef UNIX_GLOBBING # define UNIX_GLOBBING 0 # # undef OPT_GLOB_ENVIRON # define OPT_GLOB_ENVIRON 0 # # undef OPT_GLOB_PIPE # define OPT_GLOB_PIPE 0 # #endif /*--------------------------------------------------------------------------*/ /* the expanded list is defined outside of the functions because if we * handle ellipsis, the generating function must be recursive. */ static SIZE_T myMax, myLen; /* length and index of the expanded list */ static char ** myVec; /* the expanded list */ /*--------------------------------------------------------------------------*/ static int string_has_wildcards (const char *item) { #if OPT_VMS_PATH || SYS_UNIX || OPT_MSDOS_PATH while (*item != EOS) { #if UNIX_GLOBBING if (iswild(*item)) return TRUE; #endif if (*item == GLOB_SINGLE || *item == GLOB_MULTI) return TRUE; #if OPT_GLOB_ELLIPSIS || SYS_VMS if (!strncmp(item, GLOB_ELLIPSIS, sizeof(GLOB_ELLIPSIS)-1)) return TRUE; #endif #if !OPT_VMS_PATH #if OPT_GLOB_RANGE if (*item == GLOB_RANGE[0]) return TRUE; #endif #if OPT_GLOB_ENVIRON if (*item == '$' && (isname(item[1]) || isdelim(item[1]))) return TRUE; #endif #endif item++; } #endif return FALSE; } /* * Record a pattern-match in 'myVec[]', returning false if an error occurs */ static int record_a_match(char *item) { if (item != 0 && *item != EOS) { if ((item = strmalloc(item)) == 0) return no_memory("glob-match"); if (myLen + 2 >= myMax) { myMax = myLen + 2; if (myVec == 0) myVec = typeallocn(char *, myMax); else myVec = typereallocn(char *, myVec, myMax); } if (myVec == 0) return no_memory("glob-pointers"); myVec[myLen++] = item; myVec[myLen] = 0; } return TRUE; } #if UNIX_GLOBBING /* * Point to the leaf following the given string (i.e., skip a slash), returns * null if none is found. */ static char * next_leaf (char *path) { if (path != 0) { while (*path != EOS) { if (is_slashc(*path)) return path+1; path++; } } return 0; } /* * Point to the beginning (after slash, if present) of the first leaf in * the given pattern argument that contains a wildcard. */ static char * wild_leaf (char *pattern) { register int j, k, ok; register char c; /* skip leading slashes */ for (j = 0; pattern[j] != EOS && is_slashc(pattern[j]); j++) ; /* skip to the leaf with wildcards */ while (pattern[j] != EOS) { int skip = FALSE; for (k = j+1; (c = pattern[k]) != EOS; k++) { if (is_slashc(c)) { pattern[k] = EOS; ok = string_has_wildcards(pattern+j); pattern[k] = c; if (ok) return pattern+j; skip = TRUE; /* skip this leaf */ break; } } if (skip) j = k+1; /* point past slash */ else if (c == EOS) break; else j++; /* leaf is empty */ } return string_has_wildcards(pattern+j) ? pattern+j : 0; } #if OPT_CASELESS static int cs_char(int ch) { return isupper(ch) ? tolower(ch) : ch; } #else #define cs_char(ch) (ch) #endif /* * This is the heart of the wildcard matching. We are given a directory * leaf and a pointer to the leaf that contains wildcards that we must * match against the leaf. */ static int match_leaf(char *leaf, char *pattern) { while (*leaf != EOS && *pattern != EOS) { if (*pattern == GLOB_SINGLE) { leaf++; pattern++; } else if (*pattern == GLOB_MULTI) { int multi = FALSE; pattern++; while (*leaf != EOS) { if (match_leaf(leaf, pattern)) { multi = TRUE; break; } leaf++; } if (!multi && *leaf != EOS) return FALSE; #if OPT_GLOB_RANGE } else if (*pattern == GLOB_RANGE[0]) { int found = FALSE; char * first = ++pattern; int negate = 0; if (*first == GLOB_NEGATE[0] || (GLOB_NEGATE[1] && *first == GLOB_NEGATE[1])) { negate = 1; first = pattern++; } while (*pattern != EOS) { if (*pattern == GLOB_RANGE[1]) { pattern++; break; } if (*pattern == '-' && pattern != first) { int lo = pattern[-1]; int hi = pattern[1]; if (hi == GLOB_RANGE[1]) hi = '~'; if (((cs_char(lo) <= cs_char(*leaf)) && (cs_char(*leaf) <= cs_char(hi))) != negate) found = TRUE; if (pattern[1] != GLOB_RANGE[1]) pattern++; } else if ((cs_char(*pattern++) == cs_char(*leaf)) != negate) found = TRUE; } if (!found) return FALSE; leaf++; #endif } else if (cs_char(*pattern++) != cs_char(*leaf++)) return FALSE; } return (*leaf == EOS && *pattern == EOS); } #if !SYS_OS2 /* * Recursive procedure that allows any leaf (or all!) leaves in a path to * have wildcards. Except for an ellipsis, each wildcard is completed * within a single leaf. * * Returns false if we ran out of memory (a problem on ms-dos), and true * if everything went well (e.g., matches). */ static int expand_leaf ( char *path, /* built-up pathname, top-level */ char *pattern) { DIR *dp; DIRENT *de; int result = TRUE; char save = 0; /* warning suppression */ SIZE_T len; char *leaf; char *wild = wild_leaf(pattern); char *next = next_leaf(wild); register char *s; /* Fill-in 'path[]' with the non-wild leaves that we skipped to get * to 'wild'. */ if (wild == pattern) { /* top-level, first leaf is wild */ if (*path == EOS) (void)strcpy(path, "."); leaf = strend(path) + 1; } else { len = (int)(wild - pattern) - 1; if (*(s = path) != EOS) { s += strlen(s); *s++ = SLASHC; } if (len != 0) { (void)strncpy(s, pattern, len); s[len] = EOS; } if (*path == EOS) { path[0] = SLASHC; path[1] = EOS; leaf = path + 1; } #if OPT_MSDOS_PATH /* Force the strncpy from 'pattern' to pick up a slash just * after the ':' in a drive specification. */ else if ((s = is_msdos_drive(path)) != 0 && s[0] == EOS) { s[0] = SLASHC; s[1] = EOS; leaf = s + 1; } #endif else { leaf = strend(path) + 1; } } if (next != 0) { save = next[-1]; next[-1] = EOS; /* restrict 'wild[]' to one leaf */ } /* Scan the directory, looking for leaves that match the pattern. */ if ((dp = opendir(SL_TO_BSL(path))) != 0) { leaf[-1] = SLASHC; /* connect the path to the leaf */ while ((de = readdir(dp)) != 0) { #if OPT_MSDOS_PATH (void)strcpy(leaf, de->d_name); #if !OPT_CASELESS (void)mklower(leaf); #endif if (strchr(pattern, '.') && !strchr(leaf, '.')) (void)strcat(leaf, "."); #else #if USE_D_NAMLEN len = de->d_namlen; (void)strncpy(leaf, de->d_name, len); leaf[len] = EOS; #else (void)strcpy(leaf, de->d_name); #endif #endif if (!strcmp(leaf, ".") || !strcmp(leaf, "..")) continue; if (!match_leaf(leaf, wild)) continue; if (next != 0) { /* there are more leaves */ if (!string_has_wildcards(next)) { s = strend(leaf); *s++ = SLASHC; (void)strcpy(s, next); if (ffexists(path) && !record_a_match(path)) { result = FALSE; break; } } else if (is_directory(path)) { #if SYS_MSDOS || SYS_WIN31 s = strrchr(path, '.'); if (s[1] == EOS) s[0] = EOS; #endif if (!expand_leaf(path, next)) { result = FALSE; break; } } } else if (!record_a_match(path)) { result = FALSE; break; } } (void)closedir(dp); } else result = SORTOFTRUE; /* at least we didn't run out of memory */ if (next != 0) next[-1] = save; return result; } #else /* SYS_OS2 */ /* * Recursive procedure that allows any leaf (or all!) leaves in a path to * have wildcards. Except for an ellipsis, each wildcard is completed * within a single leaf. * * Returns false if we ran out of memory (a problem on ms-dos), and true * if everything went well (e.g., matches). */ static int expand_leaf ( char *path, /* built-up pathname, top-level */ char *pattern) { FILEFINDBUF3 fb; ULONG entries; HDIR hdir; APIRET rc; int result = TRUE; char save = 0; /* warning suppression */ SIZE_T len; char *leaf; char *wild = wild_leaf(pattern); char *next = next_leaf(wild); register char *s; /* Fill-in 'path[]' with the non-wild leaves that we skipped to get * to 'wild'. */ if (wild == pattern) { /* top-level, first leaf is wild */ if (*path == EOS) (void)strcpy(path, "."); } else { len = wild - pattern - 1; if (*(s = path) != EOS) { s += strlen(s); *s++ = SLASHC; } if (len != 0) strncpy(s, pattern, len)[len] = EOS; } leaf = strend(path) + 1; if (next != 0) { save = next[-1]; next[-1] = EOS; /* restrict 'wild[]' to one leaf */ } /* Scan the directory, looking for leaves that match the pattern. */ len = strlen(path); if (!is_slashc(path[len - 1])) (void)strcat(path, "/*.*"); else (void)strcat(path, "*.*"); hdir = HDIR_CREATE; entries = 1; rc = DosFindFirst(SL_TO_BSL(path), &hdir, FILE_DIRECTORY | FILE_READONLY, &fb, sizeof(fb), &entries, FIL_STANDARD); if (rc == NO_ERROR) { leaf[-1] = SLASHC; do { (void) mklower(strcpy(leaf, fb.achName)); if (strcmp(leaf, ".") == 0 || strcmp(leaf, "..") == 0) continue; if (!match_leaf(leaf, wild)) continue; if (next != 0) { /* there are more leaves */ if (!string_has_wildcards(next)) { s = strend(leaf); *s++ = SLASHC; (void)strcpy(s, next); if (!record_a_match(path)) { result = FALSE; break; } } else if (is_directory(path)) { s = strrchr(path, '.'); if (s[1] == EOS) s[0] = EOS; if (!expand_leaf(path, next)) { result = FALSE; break; } } } else if (!record_a_match(path)) { result = FALSE; break; } } while (entries = 1, DosFindNext(hdir, &fb, sizeof(fb), &entries) == NO_ERROR && entries == 1); DosFindClose(hdir); } else { result = SORTOFTRUE; /* at least we didn't run out of memory */ } if (next != 0) next[-1] = save; return result; } #endif /* SYS_OS2 */ #if ! ANSI_QSORT static int compar(char **a, char **b) #else static int compar (const void *a, const void *b) #endif { #if OPT_CASELESS return stricmp(*(char **)a, *(char **)b); #else return strcmp(*(char *const *)a, *(char *const *)b); #endif } #endif #if OPT_GLOB_PIPE static int glob_from_pipe(const char *pattern) { #ifdef GVAL_GLOB char *cmd = global_g_val_ptr(GVAL_GLOB); int single; #else static char cmd[] = "!echo %s"; static int single = TRUE; #endif FILE *cf; char tmp[NFILEN]; int result = FALSE; register SIZE_T len; register char *s, *d; #ifdef GVAL_GLOB /* * For now, assume that only 'echo' will supply the result all on one * line. Other programs (e.g., 'ls' and 'find' do the sensible thing * and break up the output with newlines. */ if (!isShellOrPipe(cmd)) { cmd = "!echo %s"; single = TRUE; d = cmd + 1; } else { char save = EOS; for (d = cmd+1; *d != EOS && isspace(*d); d++) ; for (s = d; *s != EOS; s++) { if (isspace(*s)) { save = *s; *s = EOS; break; } } single = !strcmp(pathleaf(d), "echo"); if (save != EOS) *s = save; } #else d = cmd+1; #endif (void)lsprintf(tmp, d, pattern); if ((cf = npopen(tmp, "r")) != 0) { char old[NFILEN+1]; *(d = old) = EOS; while ((len = fread(tmp, sizeof(*tmp), sizeof(tmp), cf)) > 0) { /* * Split the buffer up. If 'single', split on all * whitespace, otherwise only on newlines. */ for (s = tmp; s-tmp < len; s++) { if ((single && isspace(*s)) || (!single && (*s == '\n' || *s == EOS))) { *d = EOS; result = record_a_match(d = old); *d = EOS; if (!result) break; } else { *d++ = *s; } } } if (*old != EOS) result = record_a_match(old); npclose(cf); } else result = FALSE; return result; } #endif #if OPT_GLOB_ENVIRON && UNIX_GLOBBING /* * Expand environment variables in 'pattern[]' * It allows names of the form * * $NAME * $(NAME) * ${NAME} */ static void expand_environ(char *pattern) { register int j, k; int delim, left, right; const char *s; char save[NFILEN]; for (j = 0; pattern[j] != EOS; j++) { if (pattern[j] == '$') { k = j+1; if (pattern[k] == '(') delim = ')'; else if (pattern[k] == '{') delim = '}'; else delim = EOS; if (delim != EOS) k++; left = right = k; while (pattern[k] != EOS) { right = k; if (delim != EOS) { if (pattern[k++] == delim) break; } else if (isname(pattern[k])) { k++; } else { break; } } if (delim == EOS) right = k; (void)strcpy(save, pattern+k); if (right != left) { pattern[right] = EOS; #if SYS_MSDOS || SYS_OS2 mkupper(pattern+left); #endif if ((s = getenv(pattern+left)) == 0) s = ""; } else s = ""; (void)strcpy(pattern+j, s); (void)strcat(pattern, save); j += strlen(s) - 1; } } } #else #define expand_environ(pattern) #endif /* * Notes: * VMS's sys$search function (embedded in our fake 'readdir()') handles * all of the VMS wildcards. * * MS-DOS has non-UNIX functions that scan a directory and recognize DOS-style * wildcards. Use these to get the smallest executable. However, DOS- * style wildcards are crude and clumsy compared to UNIX, so we provide them as * an option. (For example, the DOS-wildcards won't match "..\*\*.bak"). */ static int expand_pattern (const char *item) { int result = FALSE; #if OPT_VMS_PATH && !SYS_UNIX DIR *dp; DIRENT *de; if ((dp = opendir((char *)SL_TO_BSL(item))) != 0) { result = TRUE; while ((de = readdir(dp)) != 0) { char temp[NFILEN]; size_t len = de->d_namlen; (void)strncpy(temp, de->d_name, len); temp[len] = EOS; if (!record_a_match(temp)) { result = FALSE; break; } } (void)closedir(dp); } #else /* UNIX or MSDOS, etc. */ #if OPT_GLOB_PIPE # ifdef GVAL_GLOB /* * The 'glob' mode value can be on/off or set to a pipe expression, * e.g., "!echo %s". This allows using the shell to expand the * pattern, which is slower than vile's internal code, but may allow * using specific features to which the user is accustomed. * * As a special case, we read from a pipe if the expression begins with * a back-tick (e.g., `which script`). */ if (isShellOrPipe(global_g_val_ptr(GVAL_GLOB)) || *item == BAKTIK) { result = glob_from_pipe(item); } else # else result = glob_from_pipe(item); # if UNIX_GLOBBING huh ?? /* thought I turned that off ... */ # endif # endif #endif #if UNIX_GLOBBING { char builtup[NFILEN]; char pattern[NFILEN]; SIZE_T first = myLen; (void)strcpy(pattern, item); *builtup = EOS; #if OPT_MSDOS_PATH #if !OPT_CASELESS (void)mklower(pattern); #endif #endif expand_environ(pattern); if (string_has_wildcards(pattern)) { if ((result = expand_leaf(builtup, pattern)) != FALSE && (myLen-first > 1)) { qsort((char *)&myVec[first], myLen-first, sizeof(*myVec), compar); } } else result = record_a_match(pattern); } #endif /* UNIX-style globbing */ #if (SYS_MSDOS || SYS_WIN31 || SYS_OS2 || SYS_WINNT) && !UNIX_GLOBBING /* native DOS-wildcards */ DeclareFind(p); char temp[FILENAME_MAX + 1]; char path[FILENAME_MAX + 1]; char *cp = pathleaf( strcpy(temp, strcpy(path, item))); if (DirFindFirst(path,p)) { result = TRUE; do { (void)strcpy(cp, DirEntryStr(p)); if (!record_a_match(temp)) { result = FALSE; break; } } while (DirFindNext(p)); } #endif /* native MS-DOS globbing */ #endif /* OPT_VMS_PATH */ return result; /* true iff no errors noticed */ } /*--------------------------------------------------------------------------*/ /* * Tests a list of items to see if at least one of them needs to be globbed. */ #if !SYS_UNIX int glob_needed (char **list_of_items) { register int n; for (n = 0; list_of_items[n] != 0; n++) if (string_has_wildcards(list_of_items[n])) return TRUE; return FALSE; } #endif /* * Expands the items in a list, returning an entirely new list (so it can be * freed without knowing where it came from originally). This should only * return 0 if we run out of memory. */ static char ** glob_expand (char **list_of_items) { int len = glob_length(list_of_items); int i; myMax = 0; myLen = 0; myVec = 0; for (i = 0; i < len; ++i) { char *item = list_of_items[i]; /* * For UNIX, expand '~' expressions in case we've got a pattern * like "~/test*.log". */ #if SYS_UNIX char temp[NFILEN]; item = home_path(strcpy(temp, item)); #endif if (!isInternalName(item) && globbing_active() && string_has_wildcards(item)) { if (!expand_pattern(item)) return 0; } else if (!record_a_match(item)) { return 0; } } return myVec; } /* * A special case of 'glob_expand()', expands a single string into a list. */ char ** glob_string (char *item) { char *vec[2]; vec[0] = item; vec[1] = 0; return glob_expand(vec); } /* * Counts the number of items in a list of strings. This is simpler (and * more useful) than returning the length and the list as arguments from * a procedure. Note that since the standard argc/argv convention puts a * null pointer on the end, this function is applicable to the 'argv[]' * parameter of the main program as well. */ int glob_length (char **list_of_items) { register int len; if (list_of_items != 0) { for (len = 0; list_of_items[len] != 0; len++) ; } else len = 0; return len; } /* * Frees the strings in a list, and the list itself. Note that this should * not be used for the main program's original argv, because on some systems * it is a part of a larger data area, as are the command strings. */ char ** glob_free (char **list_of_items) { register int len; if (list_of_items != 0) { for (len = 0; list_of_items[len] != 0; len++) free(list_of_items[len]); free ((char *)list_of_items); } return 0; } #if !SYS_UNIX /* * Expand wildcards for the main program a la UNIX shell. */ void expand_wild_args(int *argcp, char ***argvp) { #if SYS_VMS int j, k; int comma = 0; int option = 0; /* * VMS command-line arguments may be a comma-separated list of * filenames, with an optional "/read_only" flag appended. */ for (j = 1; j < *argcp; j++) { char *s = (*argvp)[j]; int c; while ((c = *s++) != EOS) { if (c == ',') comma++; if (c == '/') option++; } } if (comma || option) { char **newvec = typeallocn(char *, comma + *argcp + 1); for (j = k = 0; j < *argcp; j++) { char *the_arg = strmalloc((*argvp)[j]); char *item; /* strip off VMS options */ while ((item = strrchr(the_arg, '/')) != 0) { mkupper(item); if (!strncmp(item, "/READ_ONLY", strlen(item))) { set_global_b_val(MDVIEW,TRUE); } *item = EOS; } while (*the_arg != EOS) { item = strchr(the_arg, ','); if (item == 0) item = strend(the_arg); else *item++ = EOS; newvec[k++] = the_arg; the_arg = item; } } newvec[k] = 0; *argcp = k; *argvp = newvec; } #endif if (glob_needed(*argvp)) { char **newargs = glob_expand(*argvp); if (newargs != 0) { *argvp = newargs; *argcp = glob_length(newargs); } } } #endif /* * Expand a string, permitting only one match. */ int doglob (char *path) { char **expand = glob_string(path); int len = glob_length(expand); if (len > 1) { if (mlyesno("Too many filenames. Use first") != TRUE) { (void)glob_free(expand); return FALSE; } } if (len > 0) { (void)strcpy(path, expand[0]); (void)glob_free(expand); } return TRUE; }
These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.