/*
 *	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: /usr/build/vile/vile/RCS/glob.c,v 1.78 2002/11/05 20:39:42 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_OS2 || SYS_WINNT
# 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 */

/*
 * 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 = 0;	/* length and index of the expanded list */
static size_t myLen = 0;	/* length and index of the expanded list */
static char **myVec = 0;	/* the expanded list */

/*--------------------------------------------------------------------------*/
int
string_has_wildcards(const char *item)
{
#if OPT_VMS_PATH || SYS_WINNT	/* either host can support ~/whatever */
    if (*item == '~')
	return TRUE;
#endif
#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)
{
    int result = TRUE;

    beginDisplay();
    if (item != 0 && *item != EOS) {
	if ((item = strmalloc(item)) == 0) {
	    result = no_memory("glob-match");
	} else {
	    if (myLen + 2 >= myMax) {
		myMax = myLen + 2;
		if (myVec == 0)
		    myVec = typeallocn(char *, myMax);
		else
		    myVec = typereallocn(char *, myVec, myMax);
	    }
	    if (myVec == 0) {
		result = no_memory("glob-pointers");
	    } else {
		myVec[myLen++] = item;
		myVec[myLen] = 0;
	    }
	}
    }
    endofDisplay();
    return result;
}

#if !SMALLER || UNIX_GLOBBING

#if OPT_CASELESS
static int
cs_char(int ch)
{
    return isUpper(ch) ? toLower(ch) : ch;
}
#else
#define cs_char(ch) (ch)
#endif

static int
only_multi(char *pattern)
{
    int result = TRUE;
    while (*pattern != 0) {
	if (*pattern++ != GLOB_MULTI) {
	    result = FALSE;
	    break;
	}
    }
    return result;
}

/*
 * 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.
 */
int
glob_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 (glob_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 && only_multi(pattern));
}
#endif /* !SMALLER || UNIX_GLOBBING */

#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 !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 = skip_string(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 = skip_string(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 (!glob_match_leaf(leaf, wild))
		continue;
	    if (next != 0) {	/* there are more leaves */
		if (!string_has_wildcards(next)) {
		    s = skip_string(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
		    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 = skip_string(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 (!glob_match_leaf(leaf, wild))
		continue;

	    if (next != 0) {	/* there are more leaves */
		if (!string_has_wildcards(next)) {
		    s = skip_string(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(*(const char *const *) a, *(const char *const *) b);
#else
    return strcmp(*(const char *const *) a, *(const char *const *) b);
#endif
}
#endif /* UNIX_GLOBBING */

#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; (size_t) (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(char *item)
{
    int result = FALSE;
#if OPT_VMS_PATH
    DIR *dp;
    DIRENT *de;
    char actual[NFILEN];
    char *leaf;

    strcpy(actual, item);
    /*
     * If we're given a Unix-style pathname, split off the leaf (which may
     * contain wildcards), translate the directory to VMS form, and put the
     * leaf back.  The unix2vms_path function would otherwise mangle the
     * wildcards in the leaf.  We only handle wildcards in the leaf
     * filename, since native VMS syntax for wildcards in the directory is
     * too cumbersome to use.
     */
    if (!is_vms_pathname(actual, -TRUE)) {
	if ((leaf = pathleaf(actual)) != actual)
	    *leaf = EOS;
	unix2vms_path(actual, actual);
	if (leaf != actual)
	    pathcat(actual, actual, pathleaf(item));
    }

    if ((dp = opendir(actual)) != 0) {
	result = TRUE;
	while ((de = readdir(dp)) != 0) {
	    char temp[NFILEN];
#if USE_D_NAMLEN
	    size_t len = de->d_namlen;
	    (void) strncpy(temp, de->d_name, len);
	    temp[len] = EOS;
#else
	    (void) strcpy(temp, de->d_name);
#endif
	    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_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];
	/*
	 * Expand '~' expressions in case we've got a pattern like
	 * "~/test*.log".
	 */
#if !SMALLER
	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;
    beginDisplay();
    if (list_of_items != 0) {
	for (len = 0; list_of_items[len] != 0; len++)
	    free(list_of_items[len]);
	free((char *) list_of_items);
    }
    endofDisplay();
    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, *tmp;

	    /*
	     * strip off supported VMS options, else don't muck
	     * with pseudo-unix path delimiters so that the
	     * glob routines will correctly handle filenames
	     * like these:
	     *
	     *     ../readme.txt   ~/vile.rc
	     *
	     * however don't mess with command line arguments
	     * that look like this:
	     *
	     *    +/<string>
	     */
	    if (!(the_arg[0] == '+' && the_arg[1] == '/')) {
		tmp = the_arg;
		while ((item = strrchr(tmp, '/')) != 0) {
		    mkupper(item);
		    if (!strncmp(item,
				 "/READ_ONLY",
				 strlen(item))) {
			set_global_b_val(MDVIEW, TRUE);
			*item = EOS;
		    } else
			tmp = item + 1;
		}
	    }
	    while (*the_arg != EOS) {
		item = strchr(the_arg, ',');
		if (item == 0)
		    item = skip_string(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;
}
