/*
 * Copyright (c) 1989, 1993
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Guido van Rossum.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

/*
 * Copyright (c) 2013 Gary Mills
 */

/*
 * glob(3) -- a superset of the one defined in POSIX 1003.2.
 *
 * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
 *
 * Optional extra services, controlled by flags not defined by POSIX:
 *
 * GLOB_QUOTE:
 *	Escaping convention: \ inhibits any special meaning the following
 *	character might have (except \ at end of string is retained).
 * GLOB_MAGCHAR:
 *	Set in gl_flags if pattern contained a globbing character.
 * GLOB_NOMAGIC:
 *	Same as GLOB_NOCHECK, but it will only append pattern if it did
 *	not contain any magic characters.  [Used in csh style globbing]
 * GLOB_ALTDIRFUNC:
 *	Use alternately specified directory access functions.
 * GLOB_TILDE:
 *	expand ~user/foo to the /home/dir/of/user/foo
 * GLOB_BRACE:
 *	expand {1,2}{a,b} to 1a 1b 2a 2b
 * gl_matchc:
 *	Number of matches in the current invocation of glob.
 */

#include "lint.h"

#include <sys/param.h>
#include <sys/stat.h>

#include <ctype.h>
#include <dirent.h>
#include <errno.h>
#include <glob.h>
#include <limits.h>
#include <pwd.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <wchar.h>
#include <wctype.h>

/*
 * This is the legacy glob_t prior to illumos enhancement 1097,
 * used when old programs call the old libc glob functions.
 * (New programs call the _glob_ext, _globfree_ext functions.)
 * This struct should be considered "carved in stone".
 */
typedef	struct	old_glob	{
	size_t	gl_pathc;		/* Count of paths matched by pattern */
	char	**gl_pathv;		/* List of matched pathnames */
	size_t	gl_offs;		/* # of slots reserved in gl_pathv */
	/* following are internal to the implementation */
	char	**gl_pathp;		/* gl_pathv + gl_offs */
	int	gl_pathn;		/* # of elements allocated */
}	old_glob_t;

/*
 * For old programs, the external names need to be the old names:
 * glob() and globfree() .  We've redefined those already to
 *  _glob_ext() and _globfree_ext() .  Now redefine old_glob()
 * and old_globfree() to glob() and globfree() .
 */
#ifdef __PRAGMA_REDEFINE_EXTNAME
#pragma	redefine_extname	old_glob	glob
#pragma	redefine_extname	old_globfree	globfree
#endif /* __PRAGMA_REDEFINE_EXTNAME */
extern int old_glob(const char *, int, int (*)(const char *, int),
    old_glob_t *);
extern void old_globfree(old_glob_t *);

/*
 * The various extensions to glob(3C) allow for stat and dirent structures to
 * show up whose size may change in a largefile environment. If libc defines
 * _FILE_OFFSET_BITS to be 64 that is the key to indicate that we're building
 * the LFS version of this file. As such, we rename the public functions here,
 * _glob_ext() and _globfree_ext() to have a 64 suffix. When building the LFS
 * version, we do not include the old versions.
 */
#if !defined(_LP64) && _FILE_OFFSET_BITS == 64
#define	_glob_ext	_glob_ext64
#define	_globfree_ext	_globfree_ext64
#endif	/* !_LP64 && _FILE_OFFSET_BITS == 64 */

#define	DOLLAR		'$'
#define	DOT		'.'
#define	EOS		'\0'
#define	LBRACKET	'['
#define	NOT		'!'
#define	QUESTION	'?'
#define	QUOTE		'\\'
#define	RANGE		'-'
#define	RBRACKET	']'
#define	SEP		'/'
#define	STAR		'*'
#define	TILDE		'~'
#define	UNDERSCORE	'_'
#define	LBRACE		'{'
#define	RBRACE		'}'
#define	SLASH		'/'
#define	COMMA		','
#define	COLON		':'

#define	M_QUOTE		0x800000
#define	M_PROTECT	0x400000

typedef struct wcat {
	wchar_t w_wc;
	uint_t w_at;
} wcat_t;

#define	M_ALL		'*'	/* Plus M_QUOTE */
#define	M_END		']'	/* Plus M_QUOTE */
#define	M_NOT		'!'	/* Plus M_QUOTE */
#define	M_ONE		'?'	/* Plus M_QUOTE */
#define	M_RNG		'-'	/* Plus M_QUOTE */
#define	M_SET		'['	/* Plus M_QUOTE */
#define	M_CLASS		':'	/* Plus M_QUOTE */
#define	ismeta(c)	(((c).w_at&M_QUOTE) != 0)

#define	INITIAL			8	/* initial pathv allocation */

#define	GLOB_LIMIT_MALLOC	65536
#define	GLOB_LIMIT_STAT		2048
#define	GLOB_LIMIT_READDIR	16384

struct glob_lim {
	size_t	glim_malloc;
	size_t	glim_stat;
	size_t	glim_readdir;
};

struct glob_path_stat {
	char		*gps_path;
	struct stat	*gps_stat;
};

static int	 compare(const void *, const void *);
static int	 compare_gps(const void *, const void *);
static int	 g_Ctoc(const wcat_t *, char *, uint_t);
static int	 g_lstat(wcat_t *, struct stat *, glob_t *);
static DIR	*g_opendir(wcat_t *, glob_t *);
static wcat_t	*g_strchr(const wcat_t *, wchar_t);
static int	 g_stat(wcat_t *, struct stat *, glob_t *);
static int	 glob0(const wcat_t *, glob_t *, struct glob_lim *,
			int (*)(const char *, int));
static int	 glob1(wcat_t *, wcat_t *, glob_t *, struct glob_lim *,
			int (*)(const char *, int));
static int	 glob2(wcat_t *, wcat_t *, wcat_t *, wcat_t *, wcat_t *,
			wcat_t *, glob_t *, struct glob_lim *,
			int (*)(const char *, int));
static int	 glob3(wcat_t *, wcat_t *, wcat_t *, wcat_t *, wcat_t *,
			wcat_t *, wcat_t *, glob_t *, struct glob_lim *,
			int (*)(const char *, int));
static int	 globextend(const wcat_t *, glob_t *, struct glob_lim *,
		    struct stat *);
static
const wcat_t	*globtilde(const wcat_t *, wcat_t *, size_t, glob_t *);
static int	 globexp1(const wcat_t *, glob_t *, struct glob_lim *,
		    int (*)(const char *, int));
static int	 globexp2(const wcat_t *, const wcat_t *, glob_t *,
		    struct glob_lim *, int (*)(const char *, int));
static int	 match(wcat_t *, wcat_t *, wcat_t *);

/*
 * Extended glob() function, selected by #pragma redefine_extname
 * in glob.h with the external name _glob_ext() .
 */
int
_glob_ext(const char *pattern, int flags, int (*errfunc)(const char *, int),
    glob_t *pglob)
{
	const char *patnext;
	int n;
	size_t patlen;
	wchar_t c;
	wcat_t *bufnext, *bufend, patbuf[PATH_MAX];
	struct glob_lim limit = { 0, 0, 0 };

	patnext = pattern;
	if (!(flags & GLOB_APPEND)) {
		pglob->gl_pathc = 0;
		pglob->gl_pathn = 0;
		pglob->gl_pathv = NULL;
		if ((flags & GLOB_KEEPSTAT) != 0)
			pglob->gl_statv = NULL;
		if (!(flags & GLOB_DOOFFS))
			pglob->gl_offs = 0;
	}
	pglob->gl_flags = flags & ~GLOB_MAGCHAR;
	pglob->gl_matchc = 0;

	if ((patlen = strnlen(pattern, PATH_MAX)) == PATH_MAX)
		return (GLOB_NOMATCH);

	if (pglob->gl_offs >= INT_MAX || pglob->gl_pathc >= INT_MAX ||
	    pglob->gl_pathc >= INT_MAX - pglob->gl_offs - 1)
		return (GLOB_NOSPACE);

	bufnext = patbuf;
	bufend = bufnext + PATH_MAX - 1;
	patlen += 1;
	if (flags & GLOB_NOESCAPE) {
		while (bufnext < bufend) {
			if ((n = mbtowc(&c, patnext, patlen)) > 0) {
				patnext += n;
				patlen -= n;
				bufnext->w_at = 0;
				(bufnext++)->w_wc = c;
			} else if (n == 0) {
				break;
			} else {
				return (GLOB_NOMATCH);
			}
		}
	} else {
		/* Protect the quoted characters. */
		while (bufnext < bufend) {
			if ((n = mbtowc(&c, patnext, patlen)) > 0) {
				patnext += n;
				patlen -= n;
				if (c == QUOTE) {
					n = mbtowc(&c, patnext, patlen);
					if (n < 0)
						return (GLOB_NOMATCH);
					if (n > 0) {
						patnext += n;
						patlen -= n;
					}
					if (n == 0)
						c = QUOTE;
					bufnext->w_at = M_PROTECT;
					(bufnext++)->w_wc = c;
				} else {
					bufnext->w_at = 0;
					(bufnext++)->w_wc = c;
				}
			} else if (n == 0) {
				break;
			} else {
				return (GLOB_NOMATCH);
			}
		}
	}
	bufnext->w_at = 0;
	bufnext->w_wc = EOS;

	if (flags & GLOB_BRACE)
		return (globexp1(patbuf, pglob, &limit, errfunc));
	else
		return (glob0(patbuf, pglob, &limit, errfunc));
}

/*
 * Expand recursively a glob {} pattern. When there is no more expansion
 * invoke the standard globbing routine to glob the rest of the magic
 * characters
 */
static int
globexp1(const wcat_t *pattern, glob_t *pglob, struct glob_lim *limitp,
    int (*errfunc)(const char *, int))
{
	const wcat_t *ptr = pattern;

	/* Protect a single {}, for find(1), like csh */
	if (pattern[0].w_wc == LBRACE && pattern[1].w_wc == RBRACE &&
	    pattern[2].w_wc == EOS)
		return (glob0(pattern, pglob, limitp, errfunc));

	if ((ptr = (const wcat_t *) g_strchr(ptr, LBRACE)) != NULL)
		return (globexp2(ptr, pattern, pglob, limitp, errfunc));

	return (glob0(pattern, pglob, limitp, errfunc));
}


/*
 * Recursive brace globbing helper. Tries to expand a single brace.
 * If it succeeds then it invokes globexp1 with the new pattern.
 * If it fails then it tries to glob the rest of the pattern and returns.
 */
static int
globexp2(const wcat_t *ptr, const wcat_t *pattern, glob_t *pglob,
    struct glob_lim *limitp, int (*errfunc)(const char *, int))
{
	int	i, rv;
	wcat_t   *lm, *ls;
	const wcat_t *pe, *pm, *pl;
	wcat_t    patbuf[PATH_MAX];

	/* copy part up to the brace */
	for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
		;
	lm->w_at = 0;
	lm->w_wc = EOS;
	ls = lm;

	/* Find the balanced brace */
	for (i = 0, pe = ++ptr; pe->w_wc != EOS; pe++)
		if (pe->w_wc == LBRACKET) {
			/* Ignore everything between [] */
			for (pm = pe++; pe->w_wc != RBRACKET &&
			    pe->w_wc != EOS; pe++)
				;
			if (pe->w_wc == EOS) {
				/*
				 * We could not find a matching RBRACKET.
				 * Ignore and just look for RBRACE
				 */
				pe = pm;
			}
		} else if (pe->w_wc == LBRACE) {
			i++;
		} else if (pe->w_wc == RBRACE) {
			if (i == 0)
				break;
			i--;
		}

	/* Non matching braces; just glob the pattern */
	if (i != 0 || pe->w_wc == EOS)
		return (glob0(patbuf, pglob, limitp, errfunc));

	for (i = 0, pl = pm = ptr; pm <= pe; pm++) {
		switch (pm->w_wc) {
		case LBRACKET:
			/* Ignore everything between [] */
			for (pl = pm++; pm->w_wc != RBRACKET && pm->w_wc != EOS;
			    pm++)
				;
			if (pm->w_wc == EOS) {
				/*
				 * We could not find a matching RBRACKET.
				 * Ignore and just look for RBRACE
				 */
				pm = pl;
			}
			break;

		case LBRACE:
			i++;
			break;

		case RBRACE:
			if (i) {
				i--;
				break;
			}
			/* FALLTHROUGH */
		case COMMA:
			if (i && pm->w_wc == COMMA)
				break;
			else {
				/* Append the current string */
				for (lm = ls; (pl < pm); *lm++ = *pl++)
					;

				/*
				 * Append the rest of the pattern after the
				 * closing brace
				 */
				for (pl = pe + 1;
				    (*lm++ = *pl++).w_wc != EOS; /* */)
					;

				/* Expand the current pattern */
				rv = globexp1(patbuf, pglob, limitp, errfunc);
				if (rv && rv != GLOB_NOMATCH)
					return (rv);

				/* move after the comma, to the next string */
				pl = pm + 1;
			}
			break;

		default:
			break;
		}
	}
	return (0);
}



/*
 * expand tilde from the passwd file.
 */
static const wcat_t *
globtilde(const wcat_t *pattern, wcat_t *patbuf, size_t patbuf_len,
    glob_t *pglob)
{
	struct passwd *pwd;
	char *h;
	const wcat_t *p;
	wcat_t *b, *eb, *q;
	int n;
	size_t lenh;
	wchar_t c;

	if (pattern->w_wc != TILDE || !(pglob->gl_flags & GLOB_TILDE))
		return (pattern);

	/* Copy up to the end of the string or / */
	eb = &patbuf[patbuf_len - 1];
	for (p = pattern + 1, q = patbuf;
	    q < eb && p->w_wc != EOS && p->w_wc != SLASH; *q++ = *p++)
		;

	q->w_at = 0;
	q->w_wc = EOS;

	/* What to do if patbuf is full? */

	if (patbuf[0].w_wc == EOS) {
		/*
		 * handle a plain ~ or ~/ by expanding $HOME
		 * first and then trying the password file
		 */
		if (issetugid() != 0)
			return (pattern);
		if ((h = getenv("HOME")) == NULL) {
			if ((pwd = getpwuid(getuid())) == NULL)
				return (pattern);
			else
				h = pwd->pw_dir;
		}
	} else {
		/*
		 * Expand a ~user
		 */
		if ((pwd = getpwnam((char *)patbuf)) == NULL)
			return (pattern);
		else
			h = pwd->pw_dir;
	}

	/* Copy the home directory */
	lenh = strlen(h) + 1;
	for (b = patbuf; b < eb && *h != EOS; b++) {
		if ((n = mbtowc(&c, h, lenh)) > 0) {
			h += n;
			lenh -= n;
			b->w_at = 0;
			b->w_wc = c;
		} else if (n < 0) {
			return (pattern);
		} else {
			break;
		}
	}

	/* Append the rest of the pattern */
	while (b < eb && (*b++ = *p++).w_wc != EOS)
		;
	b->w_at = 0;
	b->w_wc = EOS;

	return (patbuf);
}

static int
g_charclass(const wcat_t **patternp, wcat_t **bufnextp)
{
	const wcat_t *pattern = *patternp + 1;
	wcat_t *bufnext = *bufnextp;
	const wcat_t *colon;
	char cbuf[MB_LEN_MAX + 32];
	wctype_t cc;
	size_t len;

	if ((colon = g_strchr(pattern, COLON)) == NULL ||
	    colon[1].w_wc != RBRACKET)
		return (1);	/* not a character class */

	len = (size_t)(colon - pattern);
	if (len + MB_LEN_MAX + 1 > sizeof (cbuf))
		return (-1);	/* invalid character class */
	{
		wchar_t w;
		const wcat_t *s1 = pattern;
		char *s2 = cbuf;
		size_t n = len;

		/* Copy the string. */
		while (n > 0) {
			w = (s1++)->w_wc;
			/* Character class names must be ASCII. */
			if (iswascii(w)) {
				n--;
				*s2++ = w;
			} else {
				return (-1);	/* invalid character class */
			}
		}
		*s2 = EOS;
	}
	if ((cc = wctype(cbuf)) == 0)
		return (-1);	/* invalid character class */
	bufnext->w_at = M_QUOTE;
	(bufnext++)->w_wc = M_CLASS;
	bufnext->w_at = 0;
	(bufnext++)->w_wc = cc;
	*bufnextp = bufnext;
	*patternp += len + 3;

	return (0);
}

/*
 * The main glob() routine: compiles the pattern (optionally processing
 * quotes), calls glob1() to do the real pattern matching, and finally
 * sorts the list (unless unsorted operation is requested).  Returns 0
 * if things went well, nonzero if errors occurred.  It is not an error
 * to find no matches.
 */
static int
glob0(const wcat_t *pattern, glob_t *pglob, struct glob_lim *limitp,
    int (*errfunc)(const char *, int))
{
	const wcat_t *qpatnext;
	int err, oldpathc;
	wchar_t c;
	int a;
	wcat_t *bufnext, patbuf[PATH_MAX];

	qpatnext = globtilde(pattern, patbuf, PATH_MAX, pglob);
	oldpathc = pglob->gl_pathc;
	bufnext = patbuf;

	/*
	 * We don't need to check for buffer overflow any more.
	 * The pattern has already been copied to an internal buffer.
	 */
	while ((a = qpatnext->w_at), (c = (qpatnext++)->w_wc) != EOS) {
		switch (c) {
		case LBRACKET:
			if (a != 0) {
				bufnext->w_at = a;
				(bufnext++)->w_wc = c;
				break;
			}
			a = qpatnext->w_at;
			c = qpatnext->w_wc;
			if (a == 0 && c == NOT)
				++qpatnext;
			if (qpatnext->w_wc == EOS ||
			    g_strchr(qpatnext+1, RBRACKET) == NULL) {
				bufnext->w_at = 0;
				(bufnext++)->w_wc = LBRACKET;
				if (a == 0 && c == NOT)
					--qpatnext;
				break;
			}
			bufnext->w_at = M_QUOTE;
			(bufnext++)->w_wc = M_SET;
			if (a == 0 && c == NOT) {
				bufnext->w_at = M_QUOTE;
				(bufnext++)->w_wc = M_NOT;
			}
			a = qpatnext->w_at;
			c = (qpatnext++)->w_wc;
			do {
				if (a == 0 && c == LBRACKET &&
				    qpatnext->w_wc == COLON) {
					do {
						err = g_charclass(&qpatnext,
						    &bufnext);
						if (err)
							break;
						a = qpatnext->w_at;
						c = (qpatnext++)->w_wc;
					} while (a == 0 && c == LBRACKET &&
					    qpatnext->w_wc == COLON);
					if (err == -1 &&
					    !(pglob->gl_flags & GLOB_NOCHECK))
						return (GLOB_NOMATCH);
					if (a == 0 && c == RBRACKET)
						break;
				}
				bufnext->w_at = a;
				(bufnext++)->w_wc = c;
				if (qpatnext->w_at == 0 &&
				    qpatnext->w_wc == RANGE) {
					a = qpatnext[1].w_at;
					c = qpatnext[1].w_wc;
					if (qpatnext[1].w_at != 0 ||
					    qpatnext[1].w_wc != RBRACKET) {
						bufnext->w_at = M_QUOTE;
						(bufnext++)->w_wc = M_RNG;
						bufnext->w_at = a;
						(bufnext++)->w_wc = c;
						qpatnext += 2;
					}
				}
				a = qpatnext->w_at;
				c = (qpatnext++)->w_wc;
			} while (a != 0 || c != RBRACKET);
			pglob->gl_flags |= GLOB_MAGCHAR;
			bufnext->w_at = M_QUOTE;
			(bufnext++)->w_wc = M_END;
			break;
		case QUESTION:
			if (a != 0) {
				bufnext->w_at = a;
				(bufnext++)->w_wc = c;
				break;
			}
			pglob->gl_flags |= GLOB_MAGCHAR;
			bufnext->w_at = M_QUOTE;
			(bufnext++)->w_wc = M_ONE;
			break;
		case STAR:
			if (a != 0) {
				bufnext->w_at = a;
				(bufnext++)->w_wc = c;
				break;
			}
			pglob->gl_flags |= GLOB_MAGCHAR;
			/*
			 * collapse adjacent stars to one,
			 * to avoid exponential behavior
			 */
			if (bufnext == patbuf ||
			    bufnext[-1].w_at != M_QUOTE ||
			    bufnext[-1].w_wc != M_ALL) {
				bufnext->w_at = M_QUOTE;
				(bufnext++)->w_wc = M_ALL;
			}
			break;
		default:
			bufnext->w_at = a;
			(bufnext++)->w_wc = c;
			break;
		}
	}
	bufnext->w_at = 0;
	bufnext->w_wc = EOS;

	if ((err = glob1(patbuf, patbuf+PATH_MAX-1, pglob, limitp, errfunc))
	    != 0)
		return (err);

	/*
	 * If there was no match we are going to append the pattern
	 * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified
	 * and the pattern did not contain any magic characters
	 * GLOB_NOMAGIC is there just for compatibility with csh.
	 */
	if (pglob->gl_pathc == oldpathc) {
		if ((pglob->gl_flags & GLOB_NOCHECK) ||
		    ((pglob->gl_flags & GLOB_NOMAGIC) &&
		    !(pglob->gl_flags & GLOB_MAGCHAR)))
			return (globextend(pattern, pglob, limitp, NULL));
		else
			return (GLOB_NOMATCH);
	}
	if (!(pglob->gl_flags & GLOB_NOSORT)) {
		if ((pglob->gl_flags & GLOB_KEEPSTAT)) {
			/* Keep the paths and stat info synced during sort */
			struct glob_path_stat *path_stat;
			int i;
			int n = pglob->gl_pathc - oldpathc;
			int o = pglob->gl_offs + oldpathc;

			if ((path_stat = calloc(n, sizeof (*path_stat))) ==
			    NULL)
				return (GLOB_NOSPACE);
			for (i = 0; i < n; i++) {
				path_stat[i].gps_path = pglob->gl_pathv[o + i];
				path_stat[i].gps_stat = pglob->gl_statv[o + i];
			}
			qsort(path_stat, n, sizeof (*path_stat), compare_gps);
			for (i = 0; i < n; i++) {
				pglob->gl_pathv[o + i] = path_stat[i].gps_path;
				pglob->gl_statv[o + i] = path_stat[i].gps_stat;
			}
			free(path_stat);
		} else {
			qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
			    pglob->gl_pathc - oldpathc, sizeof (char *),
			    compare);
		}
	}
	return (0);
}

static int
compare(const void *p, const void *q)
{
	return (strcmp(*(char **)p, *(char **)q));
}

static int
compare_gps(const void *_p, const void *_q)
{
	const struct glob_path_stat *p = (const struct glob_path_stat *)_p;
	const struct glob_path_stat *q = (const struct glob_path_stat *)_q;

	return (strcmp(p->gps_path, q->gps_path));
}

static int
glob1(wcat_t *pattern, wcat_t *pattern_last, glob_t *pglob,
    struct glob_lim *limitp, int (*errfunc)(const char *, int))
{
	wcat_t pathbuf[PATH_MAX];

	/* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
	if (pattern->w_wc == EOS)
		return (0);
	return (glob2(pathbuf, pathbuf+PATH_MAX-1,
	    pathbuf, pathbuf+PATH_MAX-1,
	    pattern, pattern_last, pglob, limitp, errfunc));
}

/*
 * The functions glob2 and glob3 are mutually recursive; there is one level
 * of recursion for each segment in the pattern that contains one or more
 * meta characters.
 */
static int
glob2(wcat_t *pathbuf, wcat_t *pathbuf_last, wcat_t *pathend,
    wcat_t *pathend_last, wcat_t *pattern, wcat_t *pattern_last,
    glob_t *pglob, struct glob_lim *limitp, int (*errfunc)(const char *, int))
{
	struct stat sb;
	wcat_t *p, *q;
	int anymeta;

	/*
	 * Loop over pattern segments until end of pattern or until
	 * segment with meta character found.
	 */
	for (anymeta = 0; ; ) {
		if (pattern->w_wc == EOS) {		/* End of pattern? */
			pathend->w_at = 0;
			pathend->w_wc = EOS;

			if ((pglob->gl_flags & GLOB_LIMIT) &&
			    limitp->glim_stat++ >= GLOB_LIMIT_STAT) {
				errno = 0;
				pathend->w_at = 0;
				(pathend++)->w_wc = SEP;
				pathend->w_at = 0;
				pathend->w_wc = EOS;
				return (GLOB_NOSPACE);
			}
			if (g_lstat(pathbuf, &sb, pglob))
				return (0);

			if (((pglob->gl_flags & GLOB_MARK) &&
			    (pathend[-1].w_at != 0 ||
			    pathend[-1].w_wc != SEP)) &&
			    (S_ISDIR(sb.st_mode) ||
			    (S_ISLNK(sb.st_mode) &&
			    (g_stat(pathbuf, &sb, pglob) == 0) &&
			    S_ISDIR(sb.st_mode)))) {
				if (pathend+1 > pathend_last)
					return (GLOB_NOSPACE);
				pathend->w_at = 0;
				(pathend++)->w_wc = SEP;
				pathend->w_at = 0;
				pathend->w_wc = EOS;
			}
			++pglob->gl_matchc;
			return (globextend(pathbuf, pglob, limitp, &sb));
		}

		/* Find end of next segment, copy tentatively to pathend. */
		q = pathend;
		p = pattern;
		while (p->w_wc != EOS && p->w_wc != SEP) {
			if (ismeta(*p))
				anymeta = 1;
			if (q+1 > pathend_last)
				return (GLOB_NOSPACE);
			*q++ = *p++;
		}

		if (!anymeta) {		/* No expansion, do next segment. */
			pathend = q;
			pattern = p;
			while (pattern->w_wc == SEP) {
				if (pathend+1 > pathend_last)
					return (GLOB_NOSPACE);
				*pathend++ = *pattern++;
			}
		} else  {
			/* Need expansion, recurse. */
			return (glob3(pathbuf, pathbuf_last, pathend,
			    pathend_last, pattern, p, pattern_last,
			    pglob, limitp, errfunc));
		}
	}
	/* NOTREACHED */
}

static int
glob3(wcat_t *pathbuf, wcat_t *pathbuf_last, wcat_t *pathend,
    wcat_t *pathend_last, wcat_t *pattern, wcat_t *restpattern,
    wcat_t *restpattern_last, glob_t *pglob, struct glob_lim *limitp,
    int (*errfunc)(const char *, int))
{
	struct dirent *dp;
	DIR *dirp;
	int err;
	char buf[PATH_MAX];

	/*
	 * The readdirfunc declaration can't be prototyped, because it is
	 * assigned, below, to two functions which are prototyped in glob.h
	 * and dirent.h as taking pointers to differently typed opaque
	 * structures.
	 */
	struct dirent *(*readdirfunc)(void *);

	if (pathend > pathend_last)
		return (GLOB_NOSPACE);
	pathend->w_at = 0;
	pathend->w_wc = EOS;
	errno = 0;

	if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
		/* TODO: don't call for ENOENT or ENOTDIR? */
		if (errfunc) {
			if (g_Ctoc(pathbuf, buf, sizeof (buf)))
				return (GLOB_ABORTED);
			if (errfunc(buf, errno) ||
			    pglob->gl_flags & GLOB_ERR)
				return (GLOB_ABORTED);
		}
		return (0);
	}

	err = 0;

	/* Search directory for matching names. */
	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
		readdirfunc = pglob->gl_readdir;
	else
		readdirfunc = (struct dirent *(*)(void *))readdir;
	while ((dp = (*readdirfunc)(dirp))) {
		char *sc;
		wcat_t *dc;
		int n;
		int lensc;
		wchar_t w;

		if ((pglob->gl_flags & GLOB_LIMIT) &&
		    limitp->glim_readdir++ >= GLOB_LIMIT_READDIR) {
			errno = 0;
			pathend->w_at = 0;
			(pathend++)->w_wc = SEP;
			pathend->w_at = 0;
			pathend->w_wc = EOS;
			err = GLOB_NOSPACE;
			break;
		}

		/* Initial DOT must be matched literally. */
		if (dp->d_name[0] == DOT && pattern->w_wc != DOT)
			continue;
		dc = pathend;
		sc = dp->d_name;
		lensc = strlen(sc) + 1;
		while (dc < pathend_last) {
			if ((n = mbtowc(&w, sc, lensc)) <= 0) {
				sc += 1;
				lensc -= 1;
				dc->w_at = 0;
				dc->w_wc = EOS;
			} else {
				sc += n;
				lensc -= n;
				dc->w_at = 0;
				dc->w_wc = w;
			}
			dc++;
			if (n <= 0)
				break;
		}
		if (dc >= pathend_last) {
			dc->w_at = 0;
			dc->w_wc = EOS;
			err = GLOB_NOSPACE;
			break;
		}
		if (n < 0) {
			err = GLOB_NOMATCH;
			break;
		}

		if (!match(pathend, pattern, restpattern)) {
			pathend->w_at = 0;
			pathend->w_wc = EOS;
			continue;
		}
		err = glob2(pathbuf, pathbuf_last, --dc, pathend_last,
		    restpattern, restpattern_last, pglob, limitp,
		    errfunc);
		if (err)
			break;
	}

	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
		(*pglob->gl_closedir)(dirp);
	else
		(void) closedir(dirp);
	return (err);
}


/*
 * Extend the gl_pathv member of a glob_t structure to accommodate a new item,
 * add the new item, and update gl_pathc.  Avoids excessive reallocation
 * by doubling the number of elements each time.  Uses gl_pathn to contain
 * the number.
 *
 * Return 0 if new item added, error code if memory couldn't be allocated.
 *
 * Invariant of the glob_t structure:
 *	Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
 *	gl_pathv points to (gl_offs + gl_pathc + 1) items.
 */
static int
globextend(const wcat_t *path, glob_t *pglob, struct glob_lim *limitp,
    struct stat *sb)
{
	char **pathv;
	ssize_t i;
	size_t allocn, newn, len;
	char *copy = NULL;
	const wcat_t *p;
	struct stat **statv;
	char junk[MB_LEN_MAX];
	int n;

	allocn = pglob->gl_pathn;
	newn = 2 + pglob->gl_pathc + pglob->gl_offs;

	if (newn <= allocn) {
		pathv = pglob->gl_pathv;
		if ((pglob->gl_flags & GLOB_KEEPSTAT) != 0)
			statv = pglob->gl_statv;
	} else {
		if (allocn == 0)
			allocn = pglob->gl_offs + INITIAL;
		allocn *= 2;
		if (pglob->gl_offs >= INT_MAX ||
		    pglob->gl_pathc >= INT_MAX ||
		    allocn >= INT_MAX ||
		    SIZE_MAX / sizeof (*pathv) <= allocn ||
		    SIZE_MAX / sizeof (*statv) <= allocn) {
		nospace:
			for (i = pglob->gl_offs; i < (ssize_t)(newn - 2);
			    i++) {
				if (pglob->gl_pathv && pglob->gl_pathv[i])
					free(pglob->gl_pathv[i]);
				if ((pglob->gl_flags & GLOB_KEEPSTAT) != 0 &&
				    pglob->gl_statv && pglob->gl_statv[i])
					free(pglob->gl_statv[i]);
			}
			free(pglob->gl_pathv);
			pglob->gl_pathv = NULL;
			if ((pglob->gl_flags & GLOB_KEEPSTAT) != 0) {
				free(pglob->gl_statv);
				pglob->gl_statv = NULL;
			}
			return (GLOB_NOSPACE);
		}
		limitp->glim_malloc += allocn * sizeof (*pathv);
		pathv = reallocarray(pglob->gl_pathv, allocn, sizeof (*pathv));
		if (pathv == NULL)
			goto nospace;
		if ((pglob->gl_flags & GLOB_KEEPSTAT) != 0) {
			limitp->glim_malloc += allocn * sizeof (*statv);
			statv = reallocarray(pglob->gl_statv, allocn,
			    sizeof (*statv));
			if (statv == NULL)
				goto nospace;
		}
	}
	pglob->gl_pathn = allocn;

	if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
		/* first time around -- clear initial gl_offs items */
		pathv += pglob->gl_offs;
		for (i = pglob->gl_offs; --i >= 0; )
			*--pathv = NULL;
	}
	pglob->gl_pathv = pathv;

	if ((pglob->gl_flags & GLOB_KEEPSTAT) != 0) {
		if (pglob->gl_statv == NULL && pglob->gl_offs > 0) {
			/* first time around -- clear initial gl_offs items */
			statv += pglob->gl_offs;
			for (i = pglob->gl_offs; --i >= 0; )
				*--statv = NULL;
		}
		pglob->gl_statv = statv;
		if (sb == NULL)
			statv[pglob->gl_offs + pglob->gl_pathc] = NULL;
		else {
			limitp->glim_malloc += sizeof (**statv);
			if ((statv[pglob->gl_offs + pglob->gl_pathc] =
			    malloc(sizeof (**statv))) == NULL)
				goto copy_error;
			(void) memcpy(statv[pglob->gl_offs + pglob->gl_pathc],
			    sb, sizeof (*sb));
		}
		statv[pglob->gl_offs + pglob->gl_pathc + 1] = NULL;
	}

	len = MB_LEN_MAX;
	p = path;
	while ((n = wctomb(junk, p->w_wc)) > 0) {
		len += n;
		if ((p++)->w_wc == EOS)
			break;
	}
	if (n < 0)
		return (GLOB_NOMATCH);

	limitp->glim_malloc += len;
	if ((copy = malloc(len)) != NULL) {
		if (g_Ctoc(path, copy, len)) {
			free(copy);
			return (GLOB_NOSPACE);
		}
		pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
	}
	pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;

	if ((pglob->gl_flags & GLOB_LIMIT) &&
	    limitp->glim_malloc >= GLOB_LIMIT_MALLOC) {
		errno = 0;
		return (GLOB_NOSPACE);
	}
	copy_error:
	return (copy == NULL ? GLOB_NOSPACE : 0);
}


/*
 * pattern matching function for filenames.  Each occurrence of the *
 * pattern causes an iteration.
 *
 * Note, this function differs from the original as per the discussion
 * here: https://research.swtch.com/glob
 *
 * Basically we removed the recursion and made it use the algorithm
 * from Russ Cox to not go quadratic on cases like a file called
 * ("a" x 100) . "x" matched against a pattern like "a*a*a*a*a*a*a*y".
 */
static int
match(wcat_t *name, wcat_t *pat, wcat_t *patend)
{
	int ok, negate_range;
	wcat_t c, k;
	wcat_t *nextp = NULL;
	wcat_t *nextn = NULL;

loop:
	while (pat < patend) {
		c = *pat++;
		switch (c.w_wc) {
		case M_ALL:
			if (c.w_at != M_QUOTE) {
				k = *name++;
				if (k.w_at != c.w_at || k.w_wc != c.w_wc)
					return (0);
				break;
			}
			while (pat < patend && pat->w_at == M_QUOTE &&
			    pat->w_wc == M_ALL)
				pat++;	/* eat consecutive '*' */
			if (pat == patend)
				return (1);
			if (name->w_wc == EOS)
				return (0);
			nextn = name + 1;
			nextp = pat - 1;
			break;
		case M_ONE:
			if (c.w_at != M_QUOTE) {
				k = *name++;
				if (k.w_at != c.w_at || k.w_wc != c.w_wc)
					goto fail;
				break;
			}
			if ((name++)->w_wc == EOS)
				goto fail;
			break;
		case M_SET:
			if (c.w_at != M_QUOTE) {
				k = *name++;
				if (k.w_at != c.w_at || k.w_wc != c.w_wc)
					goto fail;
				break;
			}
			ok = 0;
			if ((k = *name++).w_wc == EOS)
				goto fail;
			if ((negate_range = (pat->w_at == M_QUOTE &&
			    pat->w_wc == M_NOT)) != 0)
				++pat;
			while (((c = *pat++).w_at != M_QUOTE) ||
			    c.w_wc != M_END) {
				if (c.w_at == M_QUOTE && c.w_wc == M_CLASS) {
					wcat_t cc;

					cc.w_at = pat->w_at;
					cc.w_wc = pat->w_wc;
					if (iswctype(k.w_wc, cc.w_wc))
						ok = 1;
					++pat;
				}
				if (pat->w_at == M_QUOTE &&
				    pat->w_wc == M_RNG) {
					if (c.w_wc <= k.w_wc &&
					    k.w_wc <= pat[1].w_wc)
						ok = 1;
					pat += 2;
				} else if (c.w_wc == k.w_wc)
					ok = 1;
			}
			if (ok == negate_range)
				goto fail;
			break;
		default:
			k = *name++;
			if (k.w_at != c.w_at || k.w_wc != c.w_wc)
				goto fail;
			break;
		}
	}
	if (name->w_wc == EOS)
		return (1);

fail:
	if (nextn) {
		pat = nextp;
		name = nextn;
		goto loop;
	}
	return (0);
}

/*
 * Extended globfree() function, selected by #pragma redefine_extname
 * in glob.h with the external name _globfree_ext() .
 */
void
_globfree_ext(glob_t *pglob)
{
	int i;
	char **pp;

	if (pglob->gl_pathv != NULL) {
		pp = pglob->gl_pathv + pglob->gl_offs;
		for (i = pglob->gl_pathc; i--; ++pp)
			free(*pp);
		free(pglob->gl_pathv);
		pglob->gl_pathv = NULL;
	}
	if ((pglob->gl_flags & GLOB_KEEPSTAT) != 0 &&
	    pglob->gl_statv != NULL) {
		for (i = 0; i < pglob->gl_pathc; i++) {
			free(pglob->gl_statv[i]);
		}
		free(pglob->gl_statv);
		pglob->gl_statv = NULL;
	}
}

static DIR *
g_opendir(wcat_t *str, glob_t *pglob)
{
	char buf[PATH_MAX];

	if (str->w_wc == EOS)
		(void) strlcpy(buf, ".", sizeof (buf));
	else {
		if (g_Ctoc(str, buf, sizeof (buf)))
			return (NULL);
	}

	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
		return ((*pglob->gl_opendir)(buf));

	return (opendir(buf));
}

static int
g_lstat(wcat_t *fn, struct stat *sb, glob_t *pglob)
{
	char buf[PATH_MAX];

	if (g_Ctoc(fn, buf, sizeof (buf)))
		return (-1);
	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
		return ((*pglob->gl_lstat)(buf, sb));
	return (lstat(buf, sb));
}

static int
g_stat(wcat_t *fn, struct stat *sb, glob_t *pglob)
{
	char buf[PATH_MAX];

	if (g_Ctoc(fn, buf, sizeof (buf)))
		return (-1);
	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
		return ((*pglob->gl_stat)(buf, sb));
	return (stat(buf, sb));
}

static wcat_t *
g_strchr(const wcat_t *str, wchar_t ch)
{
	do {
		if (str->w_at == 0 && str->w_wc == ch)
			return ((wcat_t *)str);
	} while ((str++)->w_wc != EOS);
	return (NULL);
}

static int
g_Ctoc(const wcat_t *str, char *buf, uint_t len)
{
	int n;
	wchar_t w;

	while (len >= MB_LEN_MAX) {
		w = (str++)->w_wc;
		if ((n = wctomb(buf, w)) > 0) {
			len -= n;
			buf += n;
		}
		if (n < 0)
			break;
		if (w == EOS)
			return (0);
	}
	return (1);
}

#if defined(_LP64) || _FILE_OFFSET_BITS != 64

/* glob() function with legacy glob structure */
int
old_glob(const char *pattern, int flags, int (*errfunc)(const char *, int),
    old_glob_t *pglob)
{

	glob_t gl;
	int rv;

	flags &= GLOB_POSIX;

	(void) memset(&gl, 0, sizeof (gl));

	/*
	 * Copy all the members, old to new.  There's
	 * really no point in micro-optimizing the copying.
	 * Other members are set to zero.
	 */
	gl.gl_pathc = pglob->gl_pathc;
	gl.gl_pathv = pglob->gl_pathv;
	gl.gl_offs = pglob->gl_offs;
	gl.gl_pathp = pglob->gl_pathp;
	gl.gl_pathn = pglob->gl_pathn;

	rv = _glob_ext(pattern, flags, errfunc, &gl);

	/*
	 * Copy all the members, new to old.  There's
	 * really no point in micro-optimizing the copying.
	 */
	pglob->gl_pathc = gl.gl_pathc;
	pglob->gl_pathv = gl.gl_pathv;
	pglob->gl_offs = gl.gl_offs;
	pglob->gl_pathp = gl.gl_pathp;
	pglob->gl_pathn = gl.gl_pathn;

	return (rv);
}

/* globfree() function with legacy glob structure */
void
old_globfree(old_glob_t *pglob)
{
	glob_t gl;

	(void) memset(&gl, 0, sizeof (gl));

	/*
	 * Copy all the members, old to new.  There's
	 * really no point in micro-optimizing the copying.
	 * Other members are set to zero.
	 */
	gl.gl_pathc = pglob->gl_pathc;
	gl.gl_pathv = pglob->gl_pathv;
	gl.gl_offs = pglob->gl_offs;
	gl.gl_pathp = pglob->gl_pathp;
	gl.gl_pathn = pglob->gl_pathn;

	_globfree_ext(&gl);

	/*
	 * Copy all the members, new to old.  There's
	 * really no point in micro-optimizing the copying.
	 */
	pglob->gl_pathc = gl.gl_pathc;
	pglob->gl_pathv = gl.gl_pathv;
	pglob->gl_offs = gl.gl_offs;
	pglob->gl_pathp = gl.gl_pathp;
	pglob->gl_pathn = gl.gl_pathn;

}

#endif	/* _LP64 || _FILE_OFFSET_BITS != 64 */