xref: /freebsd/crypto/openssh/openbsd-compat/fnmatch.c (revision 19261079b74319502c6ffa1249920079f0f69a72)
1*19261079SEd Maste /*	$OpenBSD: fnmatch.c,v 1.22 2020/03/13 03:25:45 djm Exp $	*/
2*19261079SEd Maste 
3*19261079SEd Maste /* Copyright (c) 2011, VMware, Inc.
4*19261079SEd Maste  * All rights reserved.
5*19261079SEd Maste  *
6*19261079SEd Maste  * Redistribution and use in source and binary forms, with or without
7*19261079SEd Maste  * modification, are permitted provided that the following conditions are met:
8*19261079SEd Maste  *     * Redistributions of source code must retain the above copyright
9*19261079SEd Maste  *       notice, this list of conditions and the following disclaimer.
10*19261079SEd Maste  *     * Redistributions in binary form must reproduce the above copyright
11*19261079SEd Maste  *       notice, this list of conditions and the following disclaimer in the
12*19261079SEd Maste  *       documentation and/or other materials provided with the distribution.
13*19261079SEd Maste  *     * Neither the name of the VMware, Inc. nor the names of its contributors
14*19261079SEd Maste  *       may be used to endorse or promote products derived from this software
15*19261079SEd Maste  *       without specific prior written permission.
16*19261079SEd Maste  *
17*19261079SEd Maste  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
18*19261079SEd Maste  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
19*19261079SEd Maste  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
20*19261079SEd Maste  * ARE DISCLAIMED. IN NO EVENT SHALL VMWARE, INC. OR CONTRIBUTORS BE LIABLE FOR
21*19261079SEd Maste  * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22*19261079SEd Maste  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23*19261079SEd Maste  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24*19261079SEd Maste  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25*19261079SEd Maste  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26*19261079SEd Maste  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27*19261079SEd Maste  */
28*19261079SEd Maste 
29*19261079SEd Maste /*
30*19261079SEd Maste  * Copyright (c) 2008, 2016 Todd C. Miller <millert@openbsd.org>
31*19261079SEd Maste  *
32*19261079SEd Maste  * Permission to use, copy, modify, and distribute this software for any
33*19261079SEd Maste  * purpose with or without fee is hereby granted, provided that the above
34*19261079SEd Maste  * copyright notice and this permission notice appear in all copies.
35*19261079SEd Maste  *
36*19261079SEd Maste  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
37*19261079SEd Maste  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
38*19261079SEd Maste  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
39*19261079SEd Maste  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
40*19261079SEd Maste  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
41*19261079SEd Maste  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
42*19261079SEd Maste  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
43*19261079SEd Maste  */
44*19261079SEd Maste 
45*19261079SEd Maste /* Authored by William A. Rowe Jr. <wrowe; apache.org, vmware.com>, April 2011
46*19261079SEd Maste  *
47*19261079SEd Maste  * Derived from The Open Group Base Specifications Issue 7, IEEE Std 1003.1-2008
48*19261079SEd Maste  * as described in;
49*19261079SEd Maste  *   http://pubs.opengroup.org/onlinepubs/9699919799/functions/fnmatch.html
50*19261079SEd Maste  *
51*19261079SEd Maste  * Filename pattern matches defined in section 2.13, "Pattern Matching Notation"
52*19261079SEd Maste  * from chapter 2. "Shell Command Language"
53*19261079SEd Maste  *   http://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13
54*19261079SEd Maste  * where; 1. A bracket expression starting with an unquoted <circumflex> '^'
55*19261079SEd Maste  * character CONTINUES to specify a non-matching list; 2. an explicit <period> '.'
56*19261079SEd Maste  * in a bracket expression matching list, e.g. "[.abc]" does NOT match a leading
57*19261079SEd Maste  * <period> in a filename; 3. a <left-square-bracket> '[' which does not introduce
58*19261079SEd Maste  * a valid bracket expression is treated as an ordinary character; 4. a differing
59*19261079SEd Maste  * number of consecutive slashes within pattern and string will NOT match;
60*19261079SEd Maste  * 5. a trailing '\' in FNM_ESCAPE mode is treated as an ordinary '\' character.
61*19261079SEd Maste  *
62*19261079SEd Maste  * Bracket expansion defined in section 9.3.5, "RE Bracket Expression",
63*19261079SEd Maste  * from chapter 9, "Regular Expressions"
64*19261079SEd Maste  *   http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap09.html#tag_09_03_05
65*19261079SEd Maste  * with no support for collating symbols, equivalence class expressions or
66*19261079SEd Maste  * character class expressions.  A partial range expression with a leading
67*19261079SEd Maste  * hyphen following a valid range expression will match only the ordinary
68*19261079SEd Maste  * <hyphen> and the ending character (e.g. "[a-m-z]" will match characters
69*19261079SEd Maste  * 'a' through 'm', a <hyphen> '-', or a 'z').
70*19261079SEd Maste  *
71*19261079SEd Maste  * Supports BSD extensions FNM_LEADING_DIR to match pattern to the end of one
72*19261079SEd Maste  * path segment of string, and FNM_CASEFOLD to ignore alpha case.
73*19261079SEd Maste  *
74*19261079SEd Maste  * NOTE: Only POSIX/C single byte locales are correctly supported at this time.
75*19261079SEd Maste  * Notably, non-POSIX locales with FNM_CASEFOLD produce undefined results,
76*19261079SEd Maste  * particularly in ranges of mixed case (e.g. "[A-z]") or spanning alpha and
77*19261079SEd Maste  * nonalpha characters within a range.
78*19261079SEd Maste  *
79*19261079SEd Maste  * XXX comments below indicate porting required for multi-byte character sets
80*19261079SEd Maste  * and non-POSIX locale collation orders; requires mbr* APIs to track shift
81*19261079SEd Maste  * state of pattern and string (rewinding pattern and string repeatedly).
82*19261079SEd Maste  *
83*19261079SEd Maste  * Certain parts of the code assume 0x00-0x3F are unique with any MBCS (e.g.
84*19261079SEd Maste  * UTF-8, SHIFT-JIS, etc).  Any implementation allowing '\' as an alternate
85*19261079SEd Maste  * path delimiter must be aware that 0x5C is NOT unique within SHIFT-JIS.
86*19261079SEd Maste  */
87*19261079SEd Maste 
88*19261079SEd Maste /* OPENBSD ORIGINAL: lib/libc/gen/fnmatch.c */
89*19261079SEd Maste 
90*19261079SEd Maste #include "includes.h"
91*19261079SEd Maste #ifndef HAVE_FNMATCH
92*19261079SEd Maste 
93*19261079SEd Maste #include <fnmatch.h>
94*19261079SEd Maste #include <string.h>
95*19261079SEd Maste #include <ctype.h>
96*19261079SEd Maste 
97*19261079SEd Maste #include "charclass.h"
98*19261079SEd Maste 
99*19261079SEd Maste #define	RANGE_MATCH	1
100*19261079SEd Maste #define	RANGE_NOMATCH	0
101*19261079SEd Maste #define	RANGE_ERROR	(-1)
102*19261079SEd Maste 
103*19261079SEd Maste static int
classmatch(const char * pattern,char test,int foldcase,const char ** ep)104*19261079SEd Maste classmatch(const char *pattern, char test, int foldcase, const char **ep)
105*19261079SEd Maste {
106*19261079SEd Maste 	const char * const mismatch = pattern;
107*19261079SEd Maste 	const char *colon;
108*19261079SEd Maste 	struct cclass *cc;
109*19261079SEd Maste 	int rval = RANGE_NOMATCH;
110*19261079SEd Maste 	size_t len;
111*19261079SEd Maste 
112*19261079SEd Maste 	if (pattern[0] != '[' || pattern[1] != ':') {
113*19261079SEd Maste 		*ep = mismatch;
114*19261079SEd Maste 		return RANGE_ERROR;
115*19261079SEd Maste 	}
116*19261079SEd Maste 	pattern += 2;
117*19261079SEd Maste 
118*19261079SEd Maste 	if ((colon = strchr(pattern, ':')) == NULL || colon[1] != ']') {
119*19261079SEd Maste 		*ep = mismatch;
120*19261079SEd Maste 		return RANGE_ERROR;
121*19261079SEd Maste 	}
122*19261079SEd Maste 	*ep = colon + 2;
123*19261079SEd Maste 	len = (size_t)(colon - pattern);
124*19261079SEd Maste 
125*19261079SEd Maste 	if (foldcase && strncmp(pattern, "upper:]", 7) == 0)
126*19261079SEd Maste 		pattern = "lower:]";
127*19261079SEd Maste 	for (cc = cclasses; cc->name != NULL; cc++) {
128*19261079SEd Maste 		if (!strncmp(pattern, cc->name, len) && cc->name[len] == '\0') {
129*19261079SEd Maste 			if (cc->isctype((unsigned char)test))
130*19261079SEd Maste 				rval = RANGE_MATCH;
131*19261079SEd Maste 			break;
132*19261079SEd Maste 		}
133*19261079SEd Maste 	}
134*19261079SEd Maste 	if (cc->name == NULL) {
135*19261079SEd Maste 		/* invalid character class, treat as normal text */
136*19261079SEd Maste 		*ep = mismatch;
137*19261079SEd Maste 		rval = RANGE_ERROR;
138*19261079SEd Maste 	}
139*19261079SEd Maste 	return rval;
140*19261079SEd Maste }
141*19261079SEd Maste 
142*19261079SEd Maste /* Most MBCS/collation/case issues handled here.  Wildcard '*' is not handled.
143*19261079SEd Maste  * EOS '\0' and the FNM_PATHNAME '/' delimiters are not advanced over,
144*19261079SEd Maste  * however the "\/" sequence is advanced to '/'.
145*19261079SEd Maste  *
146*19261079SEd Maste  * Both pattern and string are **char to support pointer increment of arbitrary
147*19261079SEd Maste  * multibyte characters for the given locale, in a later iteration of this code
148*19261079SEd Maste  */
fnmatch_ch(const char ** pattern,const char ** string,int flags)149*19261079SEd Maste static int fnmatch_ch(const char **pattern, const char **string, int flags)
150*19261079SEd Maste {
151*19261079SEd Maste 	const char * const mismatch = *pattern;
152*19261079SEd Maste 	const int nocase = !!(flags & FNM_CASEFOLD);
153*19261079SEd Maste 	const int escape = !(flags & FNM_NOESCAPE);
154*19261079SEd Maste 	const int slash = !!(flags & FNM_PATHNAME);
155*19261079SEd Maste 	int result = FNM_NOMATCH;
156*19261079SEd Maste 	const char *startch;
157*19261079SEd Maste 	int negate;
158*19261079SEd Maste 
159*19261079SEd Maste 	if (**pattern == '[') {
160*19261079SEd Maste 		++*pattern;
161*19261079SEd Maste 
162*19261079SEd Maste 		/* Handle negation, either leading ! or ^ operators */
163*19261079SEd Maste 		negate = (**pattern == '!') || (**pattern == '^');
164*19261079SEd Maste 		if (negate)
165*19261079SEd Maste 			++*pattern;
166*19261079SEd Maste 
167*19261079SEd Maste 		/* ']' is an ordinary char at the start of the range pattern */
168*19261079SEd Maste 		if (**pattern == ']')
169*19261079SEd Maste 			goto leadingclosebrace;
170*19261079SEd Maste 
171*19261079SEd Maste 		while (**pattern) {
172*19261079SEd Maste 			if (**pattern == ']') {
173*19261079SEd Maste 				++*pattern;
174*19261079SEd Maste 				/* XXX: Fix for MBCS character width */
175*19261079SEd Maste 				++*string;
176*19261079SEd Maste 				return (result ^ negate);
177*19261079SEd Maste 			}
178*19261079SEd Maste 
179*19261079SEd Maste 			if (escape && (**pattern == '\\')) {
180*19261079SEd Maste 				++*pattern;
181*19261079SEd Maste 
182*19261079SEd Maste 				/* Patterns must terminate with ']', not EOS */
183*19261079SEd Maste 				if (!**pattern)
184*19261079SEd Maste 					break;
185*19261079SEd Maste 			}
186*19261079SEd Maste 
187*19261079SEd Maste 			/* Patterns must terminate with ']' not '/' */
188*19261079SEd Maste 			if (slash && (**pattern == '/'))
189*19261079SEd Maste 				break;
190*19261079SEd Maste 
191*19261079SEd Maste 			/* Match character classes. */
192*19261079SEd Maste 			switch (classmatch(*pattern, **string, nocase, pattern)) {
193*19261079SEd Maste 			case RANGE_MATCH:
194*19261079SEd Maste 				result = 0;
195*19261079SEd Maste 				continue;
196*19261079SEd Maste 			case RANGE_NOMATCH:
197*19261079SEd Maste 				/* Valid character class but no match. */
198*19261079SEd Maste 				continue;
199*19261079SEd Maste 			default:
200*19261079SEd Maste 				/* Not a valid character class. */
201*19261079SEd Maste 				break;
202*19261079SEd Maste 			}
203*19261079SEd Maste 			if (!**pattern)
204*19261079SEd Maste 				break;
205*19261079SEd Maste 
206*19261079SEd Maste leadingclosebrace:
207*19261079SEd Maste 			/* Look at only well-formed range patterns;
208*19261079SEd Maste 			 * "x-]" is not allowed unless escaped ("x-\]")
209*19261079SEd Maste 			 * XXX: Fix for locale/MBCS character width
210*19261079SEd Maste 			 */
211*19261079SEd Maste 			if (((*pattern)[1] == '-') && ((*pattern)[2] != ']')) {
212*19261079SEd Maste 				startch = *pattern;
213*19261079SEd Maste 				*pattern += (escape && ((*pattern)[2] == '\\')) ? 3 : 2;
214*19261079SEd Maste 
215*19261079SEd Maste 				/*
216*19261079SEd Maste 				 * NOT a properly balanced [expr] pattern, EOS
217*19261079SEd Maste 				 * terminated or ranges containing a slash in
218*19261079SEd Maste 				 * FNM_PATHNAME mode pattern fall out to to the
219*19261079SEd Maste 				 * rewind and test '[' literal code path.
220*19261079SEd Maste 				 */
221*19261079SEd Maste 				if (!**pattern || (slash && (**pattern == '/')))
222*19261079SEd Maste 					break;
223*19261079SEd Maste 
224*19261079SEd Maste 				/* XXX: handle locale/MBCS comparison, advance by MBCS char width */
225*19261079SEd Maste 				if ((**string >= *startch) && (**string <= **pattern))
226*19261079SEd Maste 					result = 0;
227*19261079SEd Maste 				else if (nocase &&
228*19261079SEd Maste 				    (isupper((unsigned char)**string) ||
229*19261079SEd Maste 				     isupper((unsigned char)*startch) ||
230*19261079SEd Maste 				     isupper((unsigned char)**pattern)) &&
231*19261079SEd Maste 				    (tolower((unsigned char)**string) >=
232*19261079SEd Maste 				     tolower((unsigned char)*startch)) &&
233*19261079SEd Maste 				    (tolower((unsigned char)**string) <=
234*19261079SEd Maste 				     tolower((unsigned char)**pattern)))
235*19261079SEd Maste 					result = 0;
236*19261079SEd Maste 
237*19261079SEd Maste 				++*pattern;
238*19261079SEd Maste 				continue;
239*19261079SEd Maste 			}
240*19261079SEd Maste 
241*19261079SEd Maste 			/* XXX: handle locale/MBCS comparison, advance by MBCS char width */
242*19261079SEd Maste 			if ((**string == **pattern))
243*19261079SEd Maste 				result = 0;
244*19261079SEd Maste 			else if (nocase && (isupper((unsigned char)**string) ||
245*19261079SEd Maste 			    isupper((unsigned char)**pattern)) &&
246*19261079SEd Maste 			    (tolower((unsigned char)**string) ==
247*19261079SEd Maste 			    tolower((unsigned char)**pattern)))
248*19261079SEd Maste 				result = 0;
249*19261079SEd Maste 
250*19261079SEd Maste 			++*pattern;
251*19261079SEd Maste 		}
252*19261079SEd Maste 		/*
253*19261079SEd Maste 		 * NOT a properly balanced [expr] pattern;
254*19261079SEd Maste 		 * Rewind and reset result to test '[' literal
255*19261079SEd Maste 		 */
256*19261079SEd Maste 		*pattern = mismatch;
257*19261079SEd Maste 		result = FNM_NOMATCH;
258*19261079SEd Maste 	} else if (**pattern == '?') {
259*19261079SEd Maste 		/* Optimize '?' match before unescaping **pattern */
260*19261079SEd Maste 		if (!**string || (slash && (**string == '/')))
261*19261079SEd Maste 			return FNM_NOMATCH;
262*19261079SEd Maste 		result = 0;
263*19261079SEd Maste 		goto fnmatch_ch_success;
264*19261079SEd Maste 	} else if (escape && (**pattern == '\\') && (*pattern)[1]) {
265*19261079SEd Maste 		++*pattern;
266*19261079SEd Maste 	}
267*19261079SEd Maste 
268*19261079SEd Maste 	/* XXX: handle locale/MBCS comparison, advance by the MBCS char width */
269*19261079SEd Maste 	if (**string == **pattern)
270*19261079SEd Maste 		result = 0;
271*19261079SEd Maste 	else if (nocase && (isupper((unsigned char)**string) ||
272*19261079SEd Maste 	    isupper((unsigned char)**pattern)) &&
273*19261079SEd Maste 	    (tolower((unsigned char)**string) ==
274*19261079SEd Maste 	    tolower((unsigned char)**pattern)))
275*19261079SEd Maste 		result = 0;
276*19261079SEd Maste 
277*19261079SEd Maste 	/* Refuse to advance over trailing slash or NULs */
278*19261079SEd Maste 	if (**string == '\0' || **pattern == '\0' ||
279*19261079SEd Maste 	    (slash && ((**string == '/') || (**pattern == '/'))))
280*19261079SEd Maste 		return result;
281*19261079SEd Maste 
282*19261079SEd Maste fnmatch_ch_success:
283*19261079SEd Maste 	++*pattern;
284*19261079SEd Maste 	++*string;
285*19261079SEd Maste 	return result;
286*19261079SEd Maste }
287*19261079SEd Maste 
288*19261079SEd Maste 
fnmatch(const char * pattern,const char * string,int flags)289*19261079SEd Maste int fnmatch(const char *pattern, const char *string, int flags)
290*19261079SEd Maste {
291*19261079SEd Maste 	static const char dummystring[2] = {' ', 0};
292*19261079SEd Maste 	const int escape = !(flags & FNM_NOESCAPE);
293*19261079SEd Maste 	const int slash = !!(flags & FNM_PATHNAME);
294*19261079SEd Maste 	const int leading_dir = !!(flags & FNM_LEADING_DIR);
295*19261079SEd Maste 	const char *dummyptr, *matchptr, *strendseg;
296*19261079SEd Maste 	int wild;
297*19261079SEd Maste 	/* For '*' wild processing only; suppress 'used before initialization'
298*19261079SEd Maste 	 * warnings with dummy initialization values;
299*19261079SEd Maste 	 */
300*19261079SEd Maste 	const char *strstartseg = NULL;
301*19261079SEd Maste 	const char *mismatch = NULL;
302*19261079SEd Maste 	int matchlen = 0;
303*19261079SEd Maste 
304*19261079SEd Maste 	if (*pattern == '*')
305*19261079SEd Maste 		goto firstsegment;
306*19261079SEd Maste 
307*19261079SEd Maste 	while (*pattern && *string) {
308*19261079SEd Maste 		/*
309*19261079SEd Maste 		 * Pre-decode "\/" which has no special significance, and
310*19261079SEd Maste 		 * match balanced slashes, starting a new segment pattern.
311*19261079SEd Maste 		 */
312*19261079SEd Maste 		if (slash && escape && (*pattern == '\\') && (pattern[1] == '/'))
313*19261079SEd Maste 			++pattern;
314*19261079SEd Maste 		if (slash && (*pattern == '/') && (*string == '/')) {
315*19261079SEd Maste 			++pattern;
316*19261079SEd Maste 			++string;
317*19261079SEd Maste 		}
318*19261079SEd Maste 
319*19261079SEd Maste firstsegment:
320*19261079SEd Maste 		/*
321*19261079SEd Maste 		 * At the beginning of each segment, validate leading period
322*19261079SEd Maste 		 * behavior.
323*19261079SEd Maste 		 */
324*19261079SEd Maste 		if ((flags & FNM_PERIOD) && (*string == '.')) {
325*19261079SEd Maste 		    if (*pattern == '.')
326*19261079SEd Maste 			    ++pattern;
327*19261079SEd Maste 		    else if (escape && (*pattern == '\\') && (pattern[1] == '.'))
328*19261079SEd Maste 			    pattern += 2;
329*19261079SEd Maste 		    else
330*19261079SEd Maste 			    return FNM_NOMATCH;
331*19261079SEd Maste 		    ++string;
332*19261079SEd Maste 		}
333*19261079SEd Maste 
334*19261079SEd Maste 		/*
335*19261079SEd Maste 		 * Determine the end of string segment.  Presumes '/'
336*19261079SEd Maste 		 * character is unique, not composite in any MBCS encoding
337*19261079SEd Maste 		 */
338*19261079SEd Maste 		if (slash) {
339*19261079SEd Maste 			strendseg = strchr(string, '/');
340*19261079SEd Maste 			if (!strendseg)
341*19261079SEd Maste 				strendseg = strchr(string, '\0');
342*19261079SEd Maste 		} else {
343*19261079SEd Maste 			strendseg = strchr(string, '\0');
344*19261079SEd Maste 		}
345*19261079SEd Maste 
346*19261079SEd Maste 		/*
347*19261079SEd Maste 		 * Allow pattern '*' to be consumed even with no remaining
348*19261079SEd Maste 		 * string to match.
349*19261079SEd Maste 		 */
350*19261079SEd Maste 		while (*pattern) {
351*19261079SEd Maste 			if ((string > strendseg) ||
352*19261079SEd Maste 			    ((string == strendseg) && (*pattern != '*')))
353*19261079SEd Maste 				break;
354*19261079SEd Maste 
355*19261079SEd Maste 			if (slash && ((*pattern == '/') ||
356*19261079SEd Maste 			    (escape && (*pattern == '\\') && (pattern[1] == '/'))))
357*19261079SEd Maste 				break;
358*19261079SEd Maste 
359*19261079SEd Maste 			/*
360*19261079SEd Maste 			 * Reduce groups of '*' and '?' to n '?' matches
361*19261079SEd Maste 			 * followed by one '*' test for simplicity.
362*19261079SEd Maste 			 */
363*19261079SEd Maste 			for (wild = 0; (*pattern == '*') || (*pattern == '?'); ++pattern) {
364*19261079SEd Maste 				if (*pattern == '*') {
365*19261079SEd Maste 					wild = 1;
366*19261079SEd Maste 				} else if (string < strendseg) {  /* && (*pattern == '?') */
367*19261079SEd Maste 					/* XXX: Advance 1 char for MBCS locale */
368*19261079SEd Maste 					++string;
369*19261079SEd Maste 				}
370*19261079SEd Maste 				else {  /* (string >= strendseg) && (*pattern == '?') */
371*19261079SEd Maste 					return FNM_NOMATCH;
372*19261079SEd Maste 				}
373*19261079SEd Maste 			}
374*19261079SEd Maste 
375*19261079SEd Maste 			if (wild) {
376*19261079SEd Maste 				strstartseg = string;
377*19261079SEd Maste 				mismatch = pattern;
378*19261079SEd Maste 
379*19261079SEd Maste 				/*
380*19261079SEd Maste 				 * Count fixed (non '*') char matches remaining
381*19261079SEd Maste 				 * in pattern * excluding '/' (or "\/") and '*'.
382*19261079SEd Maste 				 */
383*19261079SEd Maste 				for (matchptr = pattern, matchlen = 0; 1; ++matchlen) {
384*19261079SEd Maste 					if ((*matchptr == '\0') ||
385*19261079SEd Maste 					    (slash && ((*matchptr == '/') ||
386*19261079SEd Maste 					    (escape && (*matchptr == '\\') &&
387*19261079SEd Maste 					    (matchptr[1] == '/'))))) {
388*19261079SEd Maste 						/* Compare precisely this many
389*19261079SEd Maste 						 * trailing string chars, the
390*19261079SEd Maste 						 * resulting match needs no
391*19261079SEd Maste 						 * wildcard loop.
392*19261079SEd Maste 						 */
393*19261079SEd Maste 						/* XXX: Adjust for MBCS */
394*19261079SEd Maste 						if (string + matchlen > strendseg)
395*19261079SEd Maste 							return FNM_NOMATCH;
396*19261079SEd Maste 
397*19261079SEd Maste 						string = strendseg - matchlen;
398*19261079SEd Maste 						wild = 0;
399*19261079SEd Maste 						break;
400*19261079SEd Maste 					}
401*19261079SEd Maste 
402*19261079SEd Maste 					if (*matchptr == '*') {
403*19261079SEd Maste 						/*
404*19261079SEd Maste 						 * Ensure at least this many
405*19261079SEd Maste 						 * trailing string chars remain
406*19261079SEd Maste 						 * for the first comparison.
407*19261079SEd Maste 						 */
408*19261079SEd Maste 						/* XXX: Adjust for MBCS */
409*19261079SEd Maste 						if (string + matchlen > strendseg)
410*19261079SEd Maste 							return FNM_NOMATCH;
411*19261079SEd Maste 
412*19261079SEd Maste 						/*
413*19261079SEd Maste 						 * Begin first wild comparison
414*19261079SEd Maste 						 * at the current position.
415*19261079SEd Maste 						 */
416*19261079SEd Maste 						break;
417*19261079SEd Maste 					}
418*19261079SEd Maste 
419*19261079SEd Maste 					/*
420*19261079SEd Maste 					 * Skip forward in pattern by a single
421*19261079SEd Maste 					 * character match Use a dummy
422*19261079SEd Maste 					 * fnmatch_ch() test to count one
423*19261079SEd Maste 					 * "[range]" escape.
424*19261079SEd Maste 					 */
425*19261079SEd Maste 					/* XXX: Adjust for MBCS */
426*19261079SEd Maste 					if (escape && (*matchptr == '\\') &&
427*19261079SEd Maste 					    matchptr[1]) {
428*19261079SEd Maste 						matchptr += 2;
429*19261079SEd Maste 					} else if (*matchptr == '[') {
430*19261079SEd Maste 						dummyptr = dummystring;
431*19261079SEd Maste 						fnmatch_ch(&matchptr, &dummyptr,
432*19261079SEd Maste 						    flags);
433*19261079SEd Maste 					} else {
434*19261079SEd Maste 						++matchptr;
435*19261079SEd Maste 					}
436*19261079SEd Maste 				}
437*19261079SEd Maste 			}
438*19261079SEd Maste 
439*19261079SEd Maste 			/* Incrementally match string against the pattern. */
440*19261079SEd Maste 			while (*pattern && (string < strendseg)) {
441*19261079SEd Maste 				/* Success; begin a new wild pattern search. */
442*19261079SEd Maste 				if (*pattern == '*')
443*19261079SEd Maste 					break;
444*19261079SEd Maste 
445*19261079SEd Maste 				if (slash && ((*string == '/') ||
446*19261079SEd Maste 				    (*pattern == '/') || (escape &&
447*19261079SEd Maste 				    (*pattern == '\\') && (pattern[1] == '/'))))
448*19261079SEd Maste 					break;
449*19261079SEd Maste 
450*19261079SEd Maste 				/*
451*19261079SEd Maste 				 * Compare ch's (the pattern is advanced over
452*19261079SEd Maste 				 * "\/" to the '/', but slashes will mismatch,
453*19261079SEd Maste 				 * and are not consumed).
454*19261079SEd Maste 				 */
455*19261079SEd Maste 				if (!fnmatch_ch(&pattern, &string, flags))
456*19261079SEd Maste 					continue;
457*19261079SEd Maste 
458*19261079SEd Maste 				/*
459*19261079SEd Maste 				 * Failed to match, loop against next char
460*19261079SEd Maste 				 * offset of string segment until not enough
461*19261079SEd Maste 				 * string chars remain to match the fixed
462*19261079SEd Maste 				 * pattern.
463*19261079SEd Maste 				 */
464*19261079SEd Maste 				if (wild) {
465*19261079SEd Maste 					/* XXX: Advance 1 char for MBCS locale */
466*19261079SEd Maste 					string = ++strstartseg;
467*19261079SEd Maste 					if (string + matchlen > strendseg)
468*19261079SEd Maste 						return FNM_NOMATCH;
469*19261079SEd Maste 
470*19261079SEd Maste 					pattern = mismatch;
471*19261079SEd Maste 					continue;
472*19261079SEd Maste 				} else
473*19261079SEd Maste 					return FNM_NOMATCH;
474*19261079SEd Maste 			}
475*19261079SEd Maste 		}
476*19261079SEd Maste 
477*19261079SEd Maste 		if (*string && !((slash || leading_dir) && (*string == '/')))
478*19261079SEd Maste 			return FNM_NOMATCH;
479*19261079SEd Maste 
480*19261079SEd Maste 		if (*pattern && !(slash && ((*pattern == '/') ||
481*19261079SEd Maste 		    (escape && (*pattern == '\\') && (pattern[1] == '/')))))
482*19261079SEd Maste 			return FNM_NOMATCH;
483*19261079SEd Maste 
484*19261079SEd Maste 		if (leading_dir && !*pattern && *string == '/')
485*19261079SEd Maste 			return 0;
486*19261079SEd Maste 	}
487*19261079SEd Maste 
488*19261079SEd Maste 	/* Where both pattern and string are at EOS, declare success.  */
489*19261079SEd Maste 	if (!*string && !*pattern)
490*19261079SEd Maste 		return 0;
491*19261079SEd Maste 
492*19261079SEd Maste 	/* Pattern didn't match to the end of string. */
493*19261079SEd Maste 	return FNM_NOMATCH;
494*19261079SEd Maste }
495*19261079SEd Maste #endif /* HAVE_FNMATCH */
496