11573Srgrimes/*
21573Srgrimes * Copyright (c) 1989, 1993
31573Srgrimes *	The Regents of the University of California.  All rights reserved.
41573Srgrimes *
51573Srgrimes * This code is derived from software contributed to Berkeley by
61573Srgrimes * Guido van Rossum.
71573Srgrimes *
8227753Stheraven * Copyright (c) 2011 The FreeBSD Foundation
9227753Stheraven * All rights reserved.
10227753Stheraven * Portions of this software were developed by David Chisnall
11227753Stheraven * under sponsorship from the FreeBSD Foundation.
12227753Stheraven *
131573Srgrimes * Redistribution and use in source and binary forms, with or without
141573Srgrimes * modification, are permitted provided that the following conditions
151573Srgrimes * are met:
161573Srgrimes * 1. Redistributions of source code must retain the above copyright
171573Srgrimes *    notice, this list of conditions and the following disclaimer.
181573Srgrimes * 2. Redistributions in binary form must reproduce the above copyright
191573Srgrimes *    notice, this list of conditions and the following disclaimer in the
201573Srgrimes *    documentation and/or other materials provided with the distribution.
211573Srgrimes * 4. Neither the name of the University nor the names of its contributors
221573Srgrimes *    may be used to endorse or promote products derived from this software
231573Srgrimes *    without specific prior written permission.
241573Srgrimes *
251573Srgrimes * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
261573Srgrimes * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
271573Srgrimes * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
281573Srgrimes * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
291573Srgrimes * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
301573Srgrimes * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
311573Srgrimes * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
321573Srgrimes * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
331573Srgrimes * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
341573Srgrimes * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
351573Srgrimes * SUCH DAMAGE.
361573Srgrimes */
371573Srgrimes
381573Srgrimes#if defined(LIBC_SCCS) && !defined(lint)
391573Srgrimesstatic char sccsid[] = "@(#)glob.c	8.3 (Berkeley) 10/13/93";
401573Srgrimes#endif /* LIBC_SCCS and not lint */
4190045Sobrien#include <sys/cdefs.h>
4290045Sobrien__FBSDID("$FreeBSD$");
431573Srgrimes
441573Srgrimes/*
451573Srgrimes * glob(3) -- a superset of the one defined in POSIX 1003.2.
461573Srgrimes *
471573Srgrimes * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
481573Srgrimes *
491573Srgrimes * Optional extra services, controlled by flags not defined by POSIX:
501573Srgrimes *
511573Srgrimes * GLOB_QUOTE:
521573Srgrimes *	Escaping convention: \ inhibits any special meaning the following
531573Srgrimes *	character might have (except \ at end of string is retained).
541573Srgrimes * GLOB_MAGCHAR:
551573Srgrimes *	Set in gl_flags if pattern contained a globbing character.
561573Srgrimes * GLOB_NOMAGIC:
571573Srgrimes *	Same as GLOB_NOCHECK, but it will only append pattern if it did
581573Srgrimes *	not contain any magic characters.  [Used in csh style globbing]
591573Srgrimes * GLOB_ALTDIRFUNC:
601573Srgrimes *	Use alternately specified directory access functions.
611573Srgrimes * GLOB_TILDE:
621573Srgrimes *	expand ~user/foo to the /home/dir/of/user/foo
631573Srgrimes * GLOB_BRACE:
648870Srgrimes *	expand {1,2}{a,b} to 1a 1b 2a 2b
651573Srgrimes * gl_matchc:
661573Srgrimes *	Number of matches in the current invocation of glob.
671573Srgrimes */
681573Srgrimes
69132817Stjr/*
70132817Stjr * Some notes on multibyte character support:
71132817Stjr * 1. Patterns with illegal byte sequences match nothing - even if
72132817Stjr *    GLOB_NOCHECK is specified.
73132817Stjr * 2. Illegal byte sequences in filenames are handled by treating them as
74132817Stjr *    single-byte characters with a value of the first byte of the sequence
75132817Stjr *    cast to wchar_t.
76132817Stjr * 3. State-dependent encodings are not currently supported.
77132817Stjr */
78132817Stjr
791573Srgrimes#include <sys/param.h>
801573Srgrimes#include <sys/stat.h>
811573Srgrimes
821573Srgrimes#include <ctype.h>
831573Srgrimes#include <dirent.h>
841573Srgrimes#include <errno.h>
851573Srgrimes#include <glob.h>
86132817Stjr#include <limits.h>
871573Srgrimes#include <pwd.h>
88132817Stjr#include <stdint.h>
891573Srgrimes#include <stdio.h>
901573Srgrimes#include <stdlib.h>
911573Srgrimes#include <string.h>
921573Srgrimes#include <unistd.h>
93132817Stjr#include <wchar.h>
941573Srgrimes
9519276Sache#include "collate.h"
9619276Sache
97243779Smarcel/*
98243779Smarcel * glob(3) expansion limits. Stop the expansion if any of these limits
99243779Smarcel * is reached. This caps the runtime in the face of DoS attacks. See
100243779Smarcel * also CVE-2010-2632
101243779Smarcel */
102243779Smarcel#define	GLOB_LIMIT_BRACE	128	/* number of brace calls */
103243779Smarcel#define	GLOB_LIMIT_PATH		65536	/* number of path elements */
104243779Smarcel#define	GLOB_LIMIT_READDIR	16384	/* number of readdirs */
105243779Smarcel#define	GLOB_LIMIT_STAT		1024	/* number of stat system calls */
106243779Smarcel#define	GLOB_LIMIT_STRING	ARG_MAX	/* maximum total size for paths */
107243779Smarcel
108243779Smarcelstruct glob_limit {
109243779Smarcel	size_t	l_brace_cnt;
110243779Smarcel	size_t	l_path_lim;
111243779Smarcel	size_t	l_readdir_cnt;
112243779Smarcel	size_t	l_stat_cnt;
113243779Smarcel	size_t	l_string_cnt;
114243779Smarcel};
115243779Smarcel
1161573Srgrimes#define	DOLLAR		'$'
1171573Srgrimes#define	DOT		'.'
1181573Srgrimes#define	EOS		'\0'
1191573Srgrimes#define	LBRACKET	'['
1201573Srgrimes#define	NOT		'!'
1211573Srgrimes#define	QUESTION	'?'
1221573Srgrimes#define	QUOTE		'\\'
1231573Srgrimes#define	RANGE		'-'
1241573Srgrimes#define	RBRACKET	']'
1251573Srgrimes#define	SEP		'/'
1261573Srgrimes#define	STAR		'*'
1271573Srgrimes#define	TILDE		'~'
1281573Srgrimes#define	UNDERSCORE	'_'
1291573Srgrimes#define	LBRACE		'{'
1301573Srgrimes#define	RBRACE		'}'
1311573Srgrimes#define	SLASH		'/'
1321573Srgrimes#define	COMMA		','
1331573Srgrimes
1341573Srgrimes#ifndef DEBUG
1351573Srgrimes
136132817Stjr#define	M_QUOTE		0x8000000000ULL
137132817Stjr#define	M_PROTECT	0x4000000000ULL
138132817Stjr#define	M_MASK		0xffffffffffULL
139132817Stjr#define	M_CHAR		0x00ffffffffULL
1401573Srgrimes
141132817Stjrtypedef uint_fast64_t Char;
1421573Srgrimes
1431573Srgrimes#else
1441573Srgrimes
1451573Srgrimes#define	M_QUOTE		0x80
1461573Srgrimes#define	M_PROTECT	0x40
1471573Srgrimes#define	M_MASK		0xff
148132817Stjr#define	M_CHAR		0x7f
1491573Srgrimes
1501573Srgrimestypedef char Char;
1511573Srgrimes
1521573Srgrimes#endif
1531573Srgrimes
1541573Srgrimes
155132817Stjr#define	CHAR(c)		((Char)((c)&M_CHAR))
1561573Srgrimes#define	META(c)		((Char)((c)|M_QUOTE))
1571573Srgrimes#define	M_ALL		META('*')
1581573Srgrimes#define	M_END		META(']')
1591573Srgrimes#define	M_NOT		META('!')
1601573Srgrimes#define	M_ONE		META('?')
1611573Srgrimes#define	M_RNG		META('-')
1621573Srgrimes#define	M_SET		META('[')
1631573Srgrimes#define	ismeta(c)	(((c)&M_QUOTE) != 0)
1641573Srgrimes
1651573Srgrimes
16690045Sobrienstatic int	 compare(const void *, const void *);
167158812Sachestatic int	 g_Ctoc(const Char *, char *, size_t);
16890045Sobrienstatic int	 g_lstat(Char *, struct stat *, glob_t *);
16990045Sobrienstatic DIR	*g_opendir(Char *, glob_t *);
170180021Smtmstatic const Char *g_strchr(const Char *, wchar_t);
1711573Srgrimes#ifdef notdef
17290045Sobrienstatic Char	*g_strcat(Char *, const Char *);
1731573Srgrimes#endif
17490045Sobrienstatic int	 g_stat(Char *, struct stat *, glob_t *);
175243779Smarcelstatic int	 glob0(const Char *, glob_t *, struct glob_limit *);
176243779Smarcelstatic int	 glob1(Char *, glob_t *, struct glob_limit *);
177243779Smarcelstatic int	 glob2(Char *, Char *, Char *, Char *, glob_t *,
178243779Smarcel    struct glob_limit *);
179243779Smarcelstatic int	 glob3(Char *, Char *, Char *, Char *, Char *, glob_t *,
180243779Smarcel    struct glob_limit *);
181243779Smarcelstatic int	 globextend(const Char *, glob_t *, struct glob_limit *);
182243779Smarcelstatic const Char *
18390045Sobrien		 globtilde(const Char *, Char *, size_t, glob_t *);
184243779Smarcelstatic int	 globexp1(const Char *, glob_t *, struct glob_limit *);
185243779Smarcelstatic int	 globexp2(const Char *, const Char *, glob_t *, int *,
186243779Smarcel    struct glob_limit *);
18790045Sobrienstatic int	 match(Char *, Char *, Char *);
1881573Srgrimes#ifdef DEBUG
18990045Sobrienstatic void	 qprintf(const char *, Char *);
1901573Srgrimes#endif
1911573Srgrimes
1921573Srgrimesint
193228754Seadlerglob(const char * __restrict pattern, int flags,
194228754Seadler	 int (*errfunc)(const char *, int), glob_t * __restrict pglob)
1951573Srgrimes{
196243779Smarcel	struct glob_limit limit = { 0, 0, 0, 0, 0 };
197159294Sdelphij	const char *patnext;
198132817Stjr	Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot;
199132817Stjr	mbstate_t mbs;
200132817Stjr	wchar_t wc;
201132817Stjr	size_t clen;
2021573Srgrimes
203159294Sdelphij	patnext = pattern;
2041573Srgrimes	if (!(flags & GLOB_APPEND)) {
2051573Srgrimes		pglob->gl_pathc = 0;
2061573Srgrimes		pglob->gl_pathv = NULL;
2071573Srgrimes		if (!(flags & GLOB_DOOFFS))
2081573Srgrimes			pglob->gl_offs = 0;
2091573Srgrimes	}
21080525Smikeh	if (flags & GLOB_LIMIT) {
211243779Smarcel		limit.l_path_lim = pglob->gl_matchc;
212243779Smarcel		if (limit.l_path_lim == 0)
213243779Smarcel			limit.l_path_lim = GLOB_LIMIT_PATH;
214243779Smarcel	}
2151573Srgrimes	pglob->gl_flags = flags & ~GLOB_MAGCHAR;
2161573Srgrimes	pglob->gl_errfunc = errfunc;
2171573Srgrimes	pglob->gl_matchc = 0;
2181573Srgrimes
2191573Srgrimes	bufnext = patbuf;
22074963Speter	bufend = bufnext + MAXPATHLEN - 1;
221132817Stjr	if (flags & GLOB_NOESCAPE) {
222132817Stjr		memset(&mbs, 0, sizeof(mbs));
223132817Stjr		while (bufend - bufnext >= MB_CUR_MAX) {
224132817Stjr			clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
225132817Stjr			if (clen == (size_t)-1 || clen == (size_t)-2)
226132817Stjr				return (GLOB_NOMATCH);
227132817Stjr			else if (clen == 0)
228132817Stjr				break;
229132817Stjr			*bufnext++ = wc;
230132817Stjr			patnext += clen;
231132817Stjr		}
232132817Stjr	} else {
2331573Srgrimes		/* Protect the quoted characters. */
234132817Stjr		memset(&mbs, 0, sizeof(mbs));
235132817Stjr		while (bufend - bufnext >= MB_CUR_MAX) {
236132817Stjr			if (*patnext == QUOTE) {
237132817Stjr				if (*++patnext == EOS) {
238132817Stjr					*bufnext++ = QUOTE | M_PROTECT;
239132817Stjr					continue;
2401573Srgrimes				}
241132817Stjr				prot = M_PROTECT;
242132817Stjr			} else
243132817Stjr				prot = 0;
244132817Stjr			clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs);
245132817Stjr			if (clen == (size_t)-1 || clen == (size_t)-2)
246132817Stjr				return (GLOB_NOMATCH);
247132817Stjr			else if (clen == 0)
248132817Stjr				break;
249132817Stjr			*bufnext++ = wc | prot;
250132817Stjr			patnext += clen;
251132817Stjr		}
2521573Srgrimes	}
2531573Srgrimes	*bufnext = EOS;
2541573Srgrimes
2551573Srgrimes	if (flags & GLOB_BRACE)
256228755Seadler	    return (globexp1(patbuf, pglob, &limit));
2571573Srgrimes	else
258228755Seadler	    return (glob0(patbuf, pglob, &limit));
2591573Srgrimes}
2601573Srgrimes
2611573Srgrimes/*
2621573Srgrimes * Expand recursively a glob {} pattern. When there is no more expansion
2631573Srgrimes * invoke the standard globbing routine to glob the rest of the magic
2641573Srgrimes * characters
2651573Srgrimes */
26674963Speterstatic int
267243779Smarcelglobexp1(const Char *pattern, glob_t *pglob, struct glob_limit *limit)
2681573Srgrimes{
2691573Srgrimes	const Char* ptr = pattern;
2701573Srgrimes	int rv;
2711573Srgrimes
272243779Smarcel	if ((pglob->gl_flags & GLOB_LIMIT) &&
273243779Smarcel	    limit->l_brace_cnt++ >= GLOB_LIMIT_BRACE) {
274243779Smarcel		errno = 0;
275243779Smarcel		return (GLOB_NOSPACE);
276243779Smarcel	}
277243779Smarcel
2781573Srgrimes	/* Protect a single {}, for find(1), like csh */
2791573Srgrimes	if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS)
28074469Sjlemon		return glob0(pattern, pglob, limit);
2811573Srgrimes
282180021Smtm	while ((ptr = g_strchr(ptr, LBRACE)) != NULL)
28374469Sjlemon		if (!globexp2(ptr, pattern, pglob, &rv, limit))
2841573Srgrimes			return rv;
2851573Srgrimes
28674469Sjlemon	return glob0(pattern, pglob, limit);
2871573Srgrimes}
2881573Srgrimes
2891573Srgrimes
2901573Srgrimes/*
2911573Srgrimes * Recursive brace globbing helper. Tries to expand a single brace.
2921573Srgrimes * If it succeeds then it invokes globexp1 with the new pattern.
2931573Srgrimes * If it fails then it tries to glob the rest of the pattern and returns.
2941573Srgrimes */
29574963Speterstatic int
296243779Smarcelglobexp2(const Char *ptr, const Char *pattern, glob_t *pglob, int *rv,
297243779Smarcel    struct glob_limit *limit)
2981573Srgrimes{
2991573Srgrimes	int     i;
3001573Srgrimes	Char   *lm, *ls;
301150137Sache	const Char *pe, *pm, *pm1, *pl;
30274963Speter	Char    patbuf[MAXPATHLEN];
3031573Srgrimes
3041573Srgrimes	/* copy part up to the brace */
3051573Srgrimes	for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
3061573Srgrimes		continue;
30774963Speter	*lm = EOS;
3081573Srgrimes	ls = lm;
3091573Srgrimes
3101573Srgrimes	/* Find the balanced brace */
3111573Srgrimes	for (i = 0, pe = ++ptr; *pe; pe++)
3121573Srgrimes		if (*pe == LBRACKET) {
3131573Srgrimes			/* Ignore everything between [] */
3141573Srgrimes			for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++)
3151573Srgrimes				continue;
3161573Srgrimes			if (*pe == EOS) {
3178870Srgrimes				/*
3181573Srgrimes				 * We could not find a matching RBRACKET.
3191573Srgrimes				 * Ignore and just look for RBRACE
3201573Srgrimes				 */
3211573Srgrimes				pe = pm;
3221573Srgrimes			}
3231573Srgrimes		}
3241573Srgrimes		else if (*pe == LBRACE)
3251573Srgrimes			i++;
3261573Srgrimes		else if (*pe == RBRACE) {
3271573Srgrimes			if (i == 0)
3281573Srgrimes				break;
3291573Srgrimes			i--;
3301573Srgrimes		}
3311573Srgrimes
3321573Srgrimes	/* Non matching braces; just glob the pattern */
3331573Srgrimes	if (i != 0 || *pe == EOS) {
33474469Sjlemon		*rv = glob0(patbuf, pglob, limit);
335228755Seadler		return (0);
3361573Srgrimes	}
3371573Srgrimes
3381573Srgrimes	for (i = 0, pl = pm = ptr; pm <= pe; pm++)
3391573Srgrimes		switch (*pm) {
3401573Srgrimes		case LBRACKET:
3411573Srgrimes			/* Ignore everything between [] */
342150137Sache			for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++)
3431573Srgrimes				continue;
3441573Srgrimes			if (*pm == EOS) {
3458870Srgrimes				/*
3461573Srgrimes				 * We could not find a matching RBRACKET.
3471573Srgrimes				 * Ignore and just look for RBRACE
3481573Srgrimes				 */
349150137Sache				pm = pm1;
3501573Srgrimes			}
3511573Srgrimes			break;
3521573Srgrimes
3531573Srgrimes		case LBRACE:
3541573Srgrimes			i++;
3551573Srgrimes			break;
3561573Srgrimes
3571573Srgrimes		case RBRACE:
3581573Srgrimes			if (i) {
3591573Srgrimes			    i--;
3601573Srgrimes			    break;
3611573Srgrimes			}
3621573Srgrimes			/* FALLTHROUGH */
3631573Srgrimes		case COMMA:
3641573Srgrimes			if (i && *pm == COMMA)
3651573Srgrimes				break;
3661573Srgrimes			else {
3671573Srgrimes				/* Append the current string */
3681573Srgrimes				for (lm = ls; (pl < pm); *lm++ = *pl++)
3691573Srgrimes					continue;
3708870Srgrimes				/*
3711573Srgrimes				 * Append the rest of the pattern after the
3721573Srgrimes				 * closing brace
3731573Srgrimes				 */
3741573Srgrimes				for (pl = pe + 1; (*lm++ = *pl++) != EOS;)
3751573Srgrimes					continue;
3761573Srgrimes
3771573Srgrimes				/* Expand the current pattern */
3781573Srgrimes#ifdef DEBUG
3791573Srgrimes				qprintf("globexp2:", patbuf);
3801573Srgrimes#endif
38174469Sjlemon				*rv = globexp1(patbuf, pglob, limit);
3821573Srgrimes
3831573Srgrimes				/* move after the comma, to the next string */
3841573Srgrimes				pl = pm + 1;
3851573Srgrimes			}
3861573Srgrimes			break;
3871573Srgrimes
3881573Srgrimes		default:
3891573Srgrimes			break;
3901573Srgrimes		}
3911573Srgrimes	*rv = 0;
392228755Seadler	return (0);
3931573Srgrimes}
3941573Srgrimes
3951573Srgrimes
3961573Srgrimes
3971573Srgrimes/*
3981573Srgrimes * expand tilde from the passwd file.
3991573Srgrimes */
4001573Srgrimesstatic const Char *
401159294Sdelphijglobtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)
4021573Srgrimes{
4031573Srgrimes	struct passwd *pwd;
4041573Srgrimes	char *h;
4051573Srgrimes	const Char *p;
40624158Simp	Char *b, *eb;
4071573Srgrimes
4081573Srgrimes	if (*pattern != TILDE || !(pglob->gl_flags & GLOB_TILDE))
409228755Seadler		return (pattern);
4101573Srgrimes
41124158Simp	/*
41224158Simp	 * Copy up to the end of the string or /
41324158Simp	 */
41424158Simp	eb = &patbuf[patbuf_len - 1];
41524158Simp	for (p = pattern + 1, h = (char *) patbuf;
41624158Simp	    h < (char *)eb && *p && *p != SLASH; *h++ = *p++)
4171573Srgrimes		continue;
4181573Srgrimes
4191573Srgrimes	*h = EOS;
4201573Srgrimes
4211573Srgrimes	if (((char *) patbuf)[0] == EOS) {
4228870Srgrimes		/*
42328820Simp		 * handle a plain ~ or ~/ by expanding $HOME first (iff
42428820Simp		 * we're not running setuid or setgid) and then trying
42528820Simp		 * the password file
4261573Srgrimes		 */
427121667Stjr		if (issetugid() != 0 ||
42833664Sjb		    (h = getenv("HOME")) == NULL) {
42928836Sache			if (((h = getlogin()) != NULL &&
43028836Sache			     (pwd = getpwnam(h)) != NULL) ||
43128836Sache			    (pwd = getpwuid(getuid())) != NULL)
43228836Sache				h = pwd->pw_dir;
43328836Sache			else
434228755Seadler				return (pattern);
4351573Srgrimes		}
4361573Srgrimes	}
4371573Srgrimes	else {
4381573Srgrimes		/*
4391573Srgrimes		 * Expand a ~user
4401573Srgrimes		 */
4411573Srgrimes		if ((pwd = getpwnam((char*) patbuf)) == NULL)
442228755Seadler			return (pattern);
4431573Srgrimes		else
4441573Srgrimes			h = pwd->pw_dir;
4451573Srgrimes	}
4461573Srgrimes
4471573Srgrimes	/* Copy the home directory */
44824158Simp	for (b = patbuf; b < eb && *h; *b++ = *h++)
4491573Srgrimes		continue;
4508870Srgrimes
4511573Srgrimes	/* Append the rest of the pattern */
45224158Simp	while (b < eb && (*b++ = *p++) != EOS)
4531573Srgrimes		continue;
45424158Simp	*b = EOS;
4551573Srgrimes
456228755Seadler	return (patbuf);
4571573Srgrimes}
4581573Srgrimes
4598870Srgrimes
4601573Srgrimes/*
4611573Srgrimes * The main glob() routine: compiles the pattern (optionally processing
4621573Srgrimes * quotes), calls glob1() to do the real pattern matching, and finally
4631573Srgrimes * sorts the list (unless unsorted operation is requested).  Returns 0
464100217Smikeh * if things went well, nonzero if errors occurred.
4651573Srgrimes */
4661573Srgrimesstatic int
467243779Smarcelglob0(const Char *pattern, glob_t *pglob, struct glob_limit *limit)
4681573Srgrimes{
4691573Srgrimes	const Char *qpatnext;
470207981Sgordon	int err;
471158812Sache	size_t oldpathc;
472207981Sgordon	Char *bufnext, c, patbuf[MAXPATHLEN];
4731573Srgrimes
47474963Speter	qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob);
4751573Srgrimes	oldpathc = pglob->gl_pathc;
4761573Srgrimes	bufnext = patbuf;
4771573Srgrimes
4781573Srgrimes	/* We don't need to check for buffer overflow any more. */
4791573Srgrimes	while ((c = *qpatnext++) != EOS) {
4801573Srgrimes		switch (c) {
4811573Srgrimes		case LBRACKET:
4821573Srgrimes			c = *qpatnext;
4831573Srgrimes			if (c == NOT)
4841573Srgrimes				++qpatnext;
4851573Srgrimes			if (*qpatnext == EOS ||
486180021Smtm			    g_strchr(qpatnext+1, RBRACKET) == NULL) {
4871573Srgrimes				*bufnext++ = LBRACKET;
4881573Srgrimes				if (c == NOT)
4891573Srgrimes					--qpatnext;
4901573Srgrimes				break;
4911573Srgrimes			}
4921573Srgrimes			*bufnext++ = M_SET;
4931573Srgrimes			if (c == NOT)
4941573Srgrimes				*bufnext++ = M_NOT;
4951573Srgrimes			c = *qpatnext++;
4961573Srgrimes			do {
4971573Srgrimes				*bufnext++ = CHAR(c);
4981573Srgrimes				if (*qpatnext == RANGE &&
4991573Srgrimes				    (c = qpatnext[1]) != RBRACKET) {
5001573Srgrimes					*bufnext++ = M_RNG;
5011573Srgrimes					*bufnext++ = CHAR(c);
5021573Srgrimes					qpatnext += 2;
5031573Srgrimes				}
5041573Srgrimes			} while ((c = *qpatnext++) != RBRACKET);
5051573Srgrimes			pglob->gl_flags |= GLOB_MAGCHAR;
5061573Srgrimes			*bufnext++ = M_END;
5071573Srgrimes			break;
5081573Srgrimes		case QUESTION:
5091573Srgrimes			pglob->gl_flags |= GLOB_MAGCHAR;
5101573Srgrimes			*bufnext++ = M_ONE;
5111573Srgrimes			break;
5121573Srgrimes		case STAR:
5131573Srgrimes			pglob->gl_flags |= GLOB_MAGCHAR;
5148870Srgrimes			/* collapse adjacent stars to one,
5151573Srgrimes			 * to avoid exponential behavior
5161573Srgrimes			 */
5171573Srgrimes			if (bufnext == patbuf || bufnext[-1] != M_ALL)
5181573Srgrimes			    *bufnext++ = M_ALL;
5191573Srgrimes			break;
5201573Srgrimes		default:
5211573Srgrimes			*bufnext++ = CHAR(c);
5221573Srgrimes			break;
5231573Srgrimes		}
5241573Srgrimes	}
5251573Srgrimes	*bufnext = EOS;
5261573Srgrimes#ifdef DEBUG
5271573Srgrimes	qprintf("glob0:", patbuf);
5281573Srgrimes#endif
5291573Srgrimes
53074469Sjlemon	if ((err = glob1(patbuf, pglob, limit)) != 0)
5311573Srgrimes		return(err);
5321573Srgrimes
5331573Srgrimes	/*
5348870Srgrimes	 * If there was no match we are going to append the pattern
5351573Srgrimes	 * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified
5361573Srgrimes	 * and the pattern did not contain any magic characters
5371573Srgrimes	 * GLOB_NOMAGIC is there just for compatibility with csh.
5381573Srgrimes	 */
539100217Smikeh	if (pglob->gl_pathc == oldpathc) {
540100217Smikeh		if (((pglob->gl_flags & GLOB_NOCHECK) ||
541100217Smikeh		    ((pglob->gl_flags & GLOB_NOMAGIC) &&
542100217Smikeh			!(pglob->gl_flags & GLOB_MAGCHAR))))
543228755Seadler			return (globextend(pattern, pglob, limit));
544100217Smikeh		else
545228755Seadler			return (GLOB_NOMATCH);
546100217Smikeh	}
547100217Smikeh	if (!(pglob->gl_flags & GLOB_NOSORT))
5481573Srgrimes		qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
5491573Srgrimes		    pglob->gl_pathc - oldpathc, sizeof(char *), compare);
550228755Seadler	return (0);
5511573Srgrimes}
5521573Srgrimes
5531573Srgrimesstatic int
554159294Sdelphijcompare(const void *p, const void *q)
5551573Srgrimes{
556228755Seadler	return (strcmp(*(char **)p, *(char **)q));
5571573Srgrimes}
5581573Srgrimes
5591573Srgrimesstatic int
560243779Smarcelglob1(Char *pattern, glob_t *pglob, struct glob_limit *limit)
5611573Srgrimes{
56274963Speter	Char pathbuf[MAXPATHLEN];
5631573Srgrimes
5641573Srgrimes	/* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
5651573Srgrimes	if (*pattern == EOS)
566228755Seadler		return (0);
567228755Seadler	return (glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1,
56874963Speter	    pattern, pglob, limit));
5691573Srgrimes}
5701573Srgrimes
5711573Srgrimes/*
5721573Srgrimes * The functions glob2 and glob3 are mutually recursive; there is one level
5731573Srgrimes * of recursion for each segment in the pattern that contains one or more
5741573Srgrimes * meta characters.
5751573Srgrimes */
5761573Srgrimesstatic int
577159294Sdelphijglob2(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern,
578243779Smarcel      glob_t *pglob, struct glob_limit *limit)
5791573Srgrimes{
5801573Srgrimes	struct stat sb;
5811573Srgrimes	Char *p, *q;
5821573Srgrimes	int anymeta;
5831573Srgrimes
5841573Srgrimes	/*
5851573Srgrimes	 * Loop over pattern segments until end of pattern or until
5861573Srgrimes	 * segment with meta character found.
5871573Srgrimes	 */
5881573Srgrimes	for (anymeta = 0;;) {
5891573Srgrimes		if (*pattern == EOS) {		/* End of pattern? */
5901573Srgrimes			*pathend = EOS;
5911573Srgrimes			if (g_lstat(pathbuf, &sb, pglob))
592228755Seadler				return (0);
5938870Srgrimes
594243779Smarcel			if ((pglob->gl_flags & GLOB_LIMIT) &&
595243779Smarcel			    limit->l_stat_cnt++ >= GLOB_LIMIT_STAT) {
596243779Smarcel				errno = 0;
597243779Smarcel				if (pathend + 1 > pathend_last)
598243779Smarcel					return (GLOB_ABORTED);
599243779Smarcel				*pathend++ = SEP;
600243779Smarcel				*pathend = EOS;
601243779Smarcel				return (GLOB_NOSPACE);
602243779Smarcel			}
6031573Srgrimes			if (((pglob->gl_flags & GLOB_MARK) &&
6041573Srgrimes			    pathend[-1] != SEP) && (S_ISDIR(sb.st_mode)
6051573Srgrimes			    || (S_ISLNK(sb.st_mode) &&
6061573Srgrimes			    (g_stat(pathbuf, &sb, pglob) == 0) &&
6071573Srgrimes			    S_ISDIR(sb.st_mode)))) {
60874963Speter				if (pathend + 1 > pathend_last)
609100217Smikeh					return (GLOB_ABORTED);
6101573Srgrimes				*pathend++ = SEP;
6111573Srgrimes				*pathend = EOS;
6121573Srgrimes			}
6131573Srgrimes			++pglob->gl_matchc;
614228755Seadler			return (globextend(pathbuf, pglob, limit));
6151573Srgrimes		}
6161573Srgrimes
6171573Srgrimes		/* Find end of next segment, copy tentatively to pathend. */
6181573Srgrimes		q = pathend;
6191573Srgrimes		p = pattern;
6201573Srgrimes		while (*p != EOS && *p != SEP) {
6211573Srgrimes			if (ismeta(*p))
6221573Srgrimes				anymeta = 1;
62374963Speter			if (q + 1 > pathend_last)
624100217Smikeh				return (GLOB_ABORTED);
6251573Srgrimes			*q++ = *p++;
6261573Srgrimes		}
6271573Srgrimes
6281573Srgrimes		if (!anymeta) {		/* No expansion, do next segment. */
6291573Srgrimes			pathend = q;
6301573Srgrimes			pattern = p;
63174963Speter			while (*pattern == SEP) {
63274963Speter				if (pathend + 1 > pathend_last)
633100217Smikeh					return (GLOB_ABORTED);
6341573Srgrimes				*pathend++ = *pattern++;
63574963Speter			}
6361573Srgrimes		} else			/* Need expansion, recurse. */
637228755Seadler			return (glob3(pathbuf, pathend, pathend_last, pattern,
638228755Seadler			    p, pglob, limit));
6391573Srgrimes	}
6401573Srgrimes	/* NOTREACHED */
6411573Srgrimes}
6421573Srgrimes
6431573Srgrimesstatic int
644159294Sdelphijglob3(Char *pathbuf, Char *pathend, Char *pathend_last,
645159294Sdelphij      Char *pattern, Char *restpattern,
646243779Smarcel      glob_t *pglob, struct glob_limit *limit)
6471573Srgrimes{
64890045Sobrien	struct dirent *dp;
6491573Srgrimes	DIR *dirp;
6501573Srgrimes	int err;
6511573Srgrimes	char buf[MAXPATHLEN];
6521573Srgrimes
6531573Srgrimes	/*
6541573Srgrimes	 * The readdirfunc declaration can't be prototyped, because it is
6551573Srgrimes	 * assigned, below, to two functions which are prototyped in glob.h
6561573Srgrimes	 * and dirent.h as taking pointers to differently typed opaque
6571573Srgrimes	 * structures.
6581573Srgrimes	 */
6591573Srgrimes	struct dirent *(*readdirfunc)();
6601573Srgrimes
66174963Speter	if (pathend > pathend_last)
662100217Smikeh		return (GLOB_ABORTED);
6631573Srgrimes	*pathend = EOS;
6641573Srgrimes	errno = 0;
6658870Srgrimes
6661573Srgrimes	if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
6671573Srgrimes		/* TODO: don't call for ENOENT or ENOTDIR? */
6681573Srgrimes		if (pglob->gl_errfunc) {
66974921Speter			if (g_Ctoc(pathbuf, buf, sizeof(buf)))
670100217Smikeh				return (GLOB_ABORTED);
6711573Srgrimes			if (pglob->gl_errfunc(buf, errno) ||
6721573Srgrimes			    pglob->gl_flags & GLOB_ERR)
673100217Smikeh				return (GLOB_ABORTED);
6741573Srgrimes		}
675228755Seadler		return (0);
6761573Srgrimes	}
6771573Srgrimes
6781573Srgrimes	err = 0;
6791573Srgrimes
6801573Srgrimes	/* Search directory for matching names. */
6811573Srgrimes	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
6821573Srgrimes		readdirfunc = pglob->gl_readdir;
6831573Srgrimes	else
6841573Srgrimes		readdirfunc = readdir;
6851573Srgrimes	while ((dp = (*readdirfunc)(dirp))) {
686159294Sdelphij		char *sc;
68790045Sobrien		Char *dc;
688132817Stjr		wchar_t wc;
689132817Stjr		size_t clen;
690132817Stjr		mbstate_t mbs;
6911573Srgrimes
692243779Smarcel		if ((pglob->gl_flags & GLOB_LIMIT) &&
693243779Smarcel		    limit->l_readdir_cnt++ >= GLOB_LIMIT_READDIR) {
694243779Smarcel			errno = 0;
695243779Smarcel			if (pathend + 1 > pathend_last)
696243779Smarcel				err = GLOB_ABORTED;
697243779Smarcel			else {
698243779Smarcel				*pathend++ = SEP;
699243779Smarcel				*pathend = EOS;
700243779Smarcel				err = GLOB_NOSPACE;
701243779Smarcel			}
702243779Smarcel			break;
703243779Smarcel		}
704243779Smarcel
7051573Srgrimes		/* Initial DOT must be matched literally. */
7061573Srgrimes		if (dp->d_name[0] == DOT && *pattern != DOT)
7071573Srgrimes			continue;
708132817Stjr		memset(&mbs, 0, sizeof(mbs));
70974963Speter		dc = pathend;
710159294Sdelphij		sc = dp->d_name;
711132817Stjr		while (dc < pathend_last) {
712132817Stjr			clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs);
713132817Stjr			if (clen == (size_t)-1 || clen == (size_t)-2) {
714132817Stjr				wc = *sc;
715132817Stjr				clen = 1;
716132817Stjr				memset(&mbs, 0, sizeof(mbs));
717132817Stjr			}
718132817Stjr			if ((*dc++ = wc) == EOS)
719132817Stjr				break;
720132817Stjr			sc += clen;
721132817Stjr		}
7221573Srgrimes		if (!match(pathend, pattern, restpattern)) {
7231573Srgrimes			*pathend = EOS;
7241573Srgrimes			continue;
7251573Srgrimes		}
72674963Speter		err = glob2(pathbuf, --dc, pathend_last, restpattern,
72774963Speter		    pglob, limit);
7281573Srgrimes		if (err)
7291573Srgrimes			break;
7301573Srgrimes	}
7311573Srgrimes
7321573Srgrimes	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
7331573Srgrimes		(*pglob->gl_closedir)(dirp);
7341573Srgrimes	else
7351573Srgrimes		closedir(dirp);
736228755Seadler	return (err);
7371573Srgrimes}
7381573Srgrimes
7391573Srgrimes
7401573Srgrimes/*
741249381Semaste * Extend the gl_pathv member of a glob_t structure to accommodate a new item,
7421573Srgrimes * add the new item, and update gl_pathc.
7431573Srgrimes *
7441573Srgrimes * This assumes the BSD realloc, which only copies the block when its size
7451573Srgrimes * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
7461573Srgrimes * behavior.
7471573Srgrimes *
7481573Srgrimes * Return 0 if new item added, error code if memory couldn't be allocated.
7491573Srgrimes *
7501573Srgrimes * Invariant of the glob_t structure:
7511573Srgrimes *	Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
7521573Srgrimes *	gl_pathv points to (gl_offs + gl_pathc + 1) items.
7531573Srgrimes */
7541573Srgrimesstatic int
755243779Smarcelglobextend(const Char *path, glob_t *pglob, struct glob_limit *limit)
7561573Srgrimes{
75790045Sobrien	char **pathv;
758158812Sache	size_t i, newsize, len;
7591573Srgrimes	char *copy;
7601573Srgrimes	const Char *p;
7611573Srgrimes
762243779Smarcel	if ((pglob->gl_flags & GLOB_LIMIT) &&
763243779Smarcel	    pglob->gl_matchc > limit->l_path_lim) {
76480525Smikeh		errno = 0;
76580525Smikeh		return (GLOB_NOSPACE);
76680525Smikeh	}
76774307Sjlemon
7681573Srgrimes	newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);
769243759Smarcel	/* realloc(NULL, newsize) is equivalent to malloc(newsize). */
770243759Smarcel	pathv = realloc((void *)pglob->gl_pathv, newsize);
771243758Smarcel	if (pathv == NULL)
772228755Seadler		return (GLOB_NOSPACE);
7731573Srgrimes
7741573Srgrimes	if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
7751573Srgrimes		/* first time around -- clear initial gl_offs items */
7761573Srgrimes		pathv += pglob->gl_offs;
777158812Sache		for (i = pglob->gl_offs + 1; --i > 0; )
7781573Srgrimes			*--pathv = NULL;
7791573Srgrimes	}
7801573Srgrimes	pglob->gl_pathv = pathv;
7811573Srgrimes
7821573Srgrimes	for (p = path; *p++;)
7831573Srgrimes		continue;
784132817Stjr	len = MB_CUR_MAX * (size_t)(p - path);	/* XXX overallocation */
785243779Smarcel	limit->l_string_cnt += len;
786243779Smarcel	if ((pglob->gl_flags & GLOB_LIMIT) &&
787243779Smarcel	    limit->l_string_cnt >= GLOB_LIMIT_STRING) {
788243779Smarcel		errno = 0;
789243779Smarcel		return (GLOB_NOSPACE);
790243779Smarcel	}
79174921Speter	if ((copy = malloc(len)) != NULL) {
79274921Speter		if (g_Ctoc(path, copy, len)) {
79374918Speter			free(copy);
79474918Speter			return (GLOB_NOSPACE);
79574918Speter		}
7961573Srgrimes		pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
7971573Srgrimes	}
7981573Srgrimes	pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
799228755Seadler	return (copy == NULL ? GLOB_NOSPACE : 0);
8001573Srgrimes}
8011573Srgrimes
8021573Srgrimes/*
8031573Srgrimes * pattern matching function for filenames.  Each occurrence of the *
8041573Srgrimes * pattern causes a recursion level.
8051573Srgrimes */
8061573Srgrimesstatic int
807159294Sdelphijmatch(Char *name, Char *pat, Char *patend)
8081573Srgrimes{
8091573Srgrimes	int ok, negate_range;
8101573Srgrimes	Char c, k;
811227753Stheraven	struct xlocale_collate *table =
812227753Stheraven		(struct xlocale_collate*)__get_locale()->components[XLC_COLLATE];
8131573Srgrimes
8141573Srgrimes	while (pat < patend) {
8151573Srgrimes		c = *pat++;
8161573Srgrimes		switch (c & M_MASK) {
8171573Srgrimes		case M_ALL:
8181573Srgrimes			if (pat == patend)
819228755Seadler				return (1);
8208870Srgrimes			do
8211573Srgrimes			    if (match(name, pat, patend))
822228755Seadler				    return (1);
8231573Srgrimes			while (*name++ != EOS);
824228755Seadler			return (0);
8251573Srgrimes		case M_ONE:
8261573Srgrimes			if (*name++ == EOS)
827228755Seadler				return (0);
8281573Srgrimes			break;
8291573Srgrimes		case M_SET:
8301573Srgrimes			ok = 0;
8311573Srgrimes			if ((k = *name++) == EOS)
832228755Seadler				return (0);
8331573Srgrimes			if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS)
8341573Srgrimes				++pat;
8351573Srgrimes			while (((c = *pat++) & M_MASK) != M_END)
8361573Srgrimes				if ((*pat & M_MASK) == M_RNG) {
837227753Stheraven					if (table->__collate_load_error ?
83824633Sache					    CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1]) :
839227753Stheraven					       __collate_range_cmp(table, CHAR(c), CHAR(k)) <= 0
840227753Stheraven					    && __collate_range_cmp(table, CHAR(k), CHAR(pat[1])) <= 0
84117528Sache					   )
8421573Srgrimes						ok = 1;
8431573Srgrimes					pat += 2;
8441573Srgrimes				} else if (c == k)
8451573Srgrimes					ok = 1;
8461573Srgrimes			if (ok == negate_range)
847228755Seadler				return (0);
8481573Srgrimes			break;
8491573Srgrimes		default:
8501573Srgrimes			if (*name++ != c)
851228755Seadler				return (0);
8521573Srgrimes			break;
8531573Srgrimes		}
8541573Srgrimes	}
855228755Seadler	return (*name == EOS);
8561573Srgrimes}
8571573Srgrimes
8581573Srgrimes/* Free allocated data belonging to a glob_t structure. */
8591573Srgrimesvoid
860159294Sdelphijglobfree(glob_t *pglob)
8611573Srgrimes{
862158812Sache	size_t i;
86390045Sobrien	char **pp;
8641573Srgrimes
8651573Srgrimes	if (pglob->gl_pathv != NULL) {
8661573Srgrimes		pp = pglob->gl_pathv + pglob->gl_offs;
8671573Srgrimes		for (i = pglob->gl_pathc; i--; ++pp)
8681573Srgrimes			if (*pp)
8691573Srgrimes				free(*pp);
8701573Srgrimes		free(pglob->gl_pathv);
87174918Speter		pglob->gl_pathv = NULL;
8721573Srgrimes	}
8731573Srgrimes}
8741573Srgrimes
8751573Srgrimesstatic DIR *
876159294Sdelphijg_opendir(Char *str, glob_t *pglob)
8771573Srgrimes{
8781573Srgrimes	char buf[MAXPATHLEN];
8791573Srgrimes
8801573Srgrimes	if (!*str)
8811573Srgrimes		strcpy(buf, ".");
88274918Speter	else {
88374921Speter		if (g_Ctoc(str, buf, sizeof(buf)))
88474918Speter			return (NULL);
88574918Speter	}
8861573Srgrimes
8871573Srgrimes	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
888228755Seadler		return ((*pglob->gl_opendir)(buf));
8891573Srgrimes
890228755Seadler	return (opendir(buf));
8911573Srgrimes}
8921573Srgrimes
8931573Srgrimesstatic int
894159294Sdelphijg_lstat(Char *fn, struct stat *sb, glob_t *pglob)
8951573Srgrimes{
8961573Srgrimes	char buf[MAXPATHLEN];
8971573Srgrimes
89874921Speter	if (g_Ctoc(fn, buf, sizeof(buf))) {
89974918Speter		errno = ENAMETOOLONG;
90074918Speter		return (-1);
90174918Speter	}
9021573Srgrimes	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
9031573Srgrimes		return((*pglob->gl_lstat)(buf, sb));
904228755Seadler	return (lstat(buf, sb));
9051573Srgrimes}
9061573Srgrimes
9071573Srgrimesstatic int
908159294Sdelphijg_stat(Char *fn, struct stat *sb, glob_t *pglob)
9091573Srgrimes{
9101573Srgrimes	char buf[MAXPATHLEN];
9111573Srgrimes
91274921Speter	if (g_Ctoc(fn, buf, sizeof(buf))) {
91374918Speter		errno = ENAMETOOLONG;
91474918Speter		return (-1);
91574918Speter	}
9161573Srgrimes	if (pglob->gl_flags & GLOB_ALTDIRFUNC)
917228755Seadler		return ((*pglob->gl_stat)(buf, sb));
918228755Seadler	return (stat(buf, sb));
9191573Srgrimes}
9201573Srgrimes
921180021Smtmstatic const Char *
922180021Smtmg_strchr(const Char *str, wchar_t ch)
9231573Srgrimes{
924159294Sdelphij
9251573Srgrimes	do {
9261573Srgrimes		if (*str == ch)
9271573Srgrimes			return (str);
9281573Srgrimes	} while (*str++);
9291573Srgrimes	return (NULL);
9301573Srgrimes}
9311573Srgrimes
93274918Speterstatic int
933159294Sdelphijg_Ctoc(const Char *str, char *buf, size_t len)
9341573Srgrimes{
935132817Stjr	mbstate_t mbs;
936132817Stjr	size_t clen;
9371573Srgrimes
938132817Stjr	memset(&mbs, 0, sizeof(mbs));
939132817Stjr	while (len >= MB_CUR_MAX) {
940132817Stjr		clen = wcrtomb(buf, *str, &mbs);
941132817Stjr		if (clen == (size_t)-1)
942132817Stjr			return (1);
943132817Stjr		if (*str == L'\0')
94474921Speter			return (0);
945132817Stjr		str++;
946132817Stjr		buf += clen;
947132817Stjr		len -= clen;
94874921Speter	}
94974921Speter	return (1);
9501573Srgrimes}
9511573Srgrimes
9521573Srgrimes#ifdef DEBUG
9538870Srgrimesstatic void
954159294Sdelphijqprintf(const char *str, Char *s)
9551573Srgrimes{
95690045Sobrien	Char *p;
9571573Srgrimes
9581573Srgrimes	(void)printf("%s:\n", str);
9591573Srgrimes	for (p = s; *p; p++)
9601573Srgrimes		(void)printf("%c", CHAR(*p));
9611573Srgrimes	(void)printf("\n");
9621573Srgrimes	for (p = s; *p; p++)
9631573Srgrimes		(void)printf("%c", *p & M_PROTECT ? '"' : ' ');
9641573Srgrimes	(void)printf("\n");
9651573Srgrimes	for (p = s; *p; p++)
9661573Srgrimes		(void)printf("%c", ismeta(*p) ? '_' : ' ');
9671573Srgrimes	(void)printf("\n");
9681573Srgrimes}
9691573Srgrimes#endif
970