Index: stable/11/lib/libc/gen/glob.3 =================================================================== --- stable/11/lib/libc/gen/glob.3 (revision 304283) +++ stable/11/lib/libc/gen/glob.3 (revision 304284) @@ -1,471 +1,457 @@ .\" Copyright (c) 1989, 1991, 1993, 1994 .\" 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. .\" 4. 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. .\" .\" @(#)glob.3 8.3 (Berkeley) 4/16/94 .\" $FreeBSD$ .\" .Dd December 20, 2011 .Dt GLOB 3 .Os .Sh NAME .Nm glob , .Nm globfree .Nd generate pathnames matching a pattern .Sh LIBRARY .Lb libc .Sh SYNOPSIS .In glob.h .Ft int .Fn glob "const char * restrict pattern" "int flags" "int (*errfunc)(const char *, int)" "glob_t * restrict pglob" .Ft void .Fn globfree "glob_t *pglob" .Sh DESCRIPTION The .Fn glob function is a pathname generator that implements the rules for file name pattern matching used by the shell. .Pp The include file .In glob.h defines the structure type .Fa glob_t , which contains at least the following fields: .Bd -literal typedef struct { size_t gl_pathc; /* count of total paths so far */ size_t gl_matchc; /* count of paths matching pattern */ size_t gl_offs; /* reserved at beginning of gl_pathv */ int gl_flags; /* returned flags */ char **gl_pathv; /* list of paths matching pattern */ } glob_t; .Ed .Pp The argument .Fa pattern is a pointer to a pathname pattern to be expanded. The .Fn glob argument matches all accessible pathnames against the pattern and creates a list of the pathnames that match. In order to have access to a pathname, .Fn glob requires search permission on every component of a path except the last and read permission on each directory of any filename component of .Fa pattern that contains any of the special characters .Ql * , .Ql ?\& or .Ql \&[ . .Pp The .Fn glob argument stores the number of matched pathnames into the .Fa gl_pathc field, and a pointer to a list of pointers to pathnames into the .Fa gl_pathv field. The first pointer after the last pathname is .Dv NULL . If the pattern does not match any pathnames, the returned number of matched paths is set to zero. .Pp It is the caller's responsibility to create the structure pointed to by .Fa pglob . The .Fn glob function allocates other space as needed, including the memory pointed to by .Fa gl_pathv . .Pp The argument .Fa flags is used to modify the behavior of .Fn glob . The value of .Fa flags is the bitwise inclusive .Tn OR of any of the following values defined in .In glob.h : .Bl -tag -width GLOB_ALTDIRFUNC .It Dv GLOB_APPEND Append pathnames generated to the ones from a previous call (or calls) to .Fn glob . The value of .Fa gl_pathc will be the total matches found by this call and the previous call(s). The pathnames are appended to, not merged with the pathnames returned by the previous call(s). Between calls, the caller must not change the setting of the .Dv GLOB_DOOFFS flag, nor change the value of .Fa gl_offs when .Dv GLOB_DOOFFS is set, nor (obviously) call .Fn globfree for .Fa pglob . .It Dv GLOB_DOOFFS Make use of the .Fa gl_offs field. If this flag is set, .Fa gl_offs is used to specify how many .Dv NULL pointers to prepend to the beginning of the .Fa gl_pathv field. In other words, .Fa gl_pathv will point to .Fa gl_offs .Dv NULL pointers, followed by .Fa gl_pathc pathname pointers, followed by a .Dv NULL pointer. .It Dv GLOB_ERR Causes .Fn glob to return when it encounters a directory that it cannot open or read. Ordinarily, .Fn glob continues to find matches. .It Dv GLOB_MARK Each pathname that is a directory that matches .Fa pattern has a slash appended. .It Dv GLOB_NOCHECK If .Fa pattern does not match any pathname, then .Fn glob returns a list consisting of only .Fa pattern , with the number of total pathnames set to 1, and the number of matched pathnames set to 0. The effect of backslash escaping is present in the pattern returned. .It Dv GLOB_NOESCAPE By default, a backslash .Pq Ql \e character is used to escape the following character in the pattern, avoiding any special interpretation of the character. If .Dv GLOB_NOESCAPE is set, backslash escaping is disabled. .It Dv GLOB_NOSORT By default, the pathnames are sorted in ascending .Tn ASCII order; this flag prevents that sorting (speeding up .Fn glob ) . .El .Pp The following values may also be included in .Fa flags , however, they are non-standard extensions to .St -p1003.2 . .Bl -tag -width GLOB_ALTDIRFUNC .It Dv GLOB_ALTDIRFUNC The following additional fields in the pglob structure have been initialized with alternate functions for glob to use to open, read, and close directories and to get stat information on names found in those directories. .Bd -literal void *(*gl_opendir)(const char * name); struct dirent *(*gl_readdir)(void *); void (*gl_closedir)(void *); int (*gl_lstat)(const char *name, struct stat *st); int (*gl_stat)(const char *name, struct stat *st); .Ed .Pp This extension is provided to allow programs such as .Xr restore 8 to provide globbing from directories stored on tape. .It Dv GLOB_BRACE Pre-process the pattern string to expand .Ql {pat,pat,...} strings like .Xr csh 1 . The pattern .Ql {} is left unexpanded for historical reasons (and .Xr csh 1 does the same thing to ease typing of .Xr find 1 patterns). .It Dv GLOB_MAGCHAR Set by the .Fn glob function if the pattern included globbing characters. See the description of the usage of the .Fa gl_matchc structure member for more details. .It Dv GLOB_NOMAGIC Is the same as .Dv GLOB_NOCHECK but it only appends the .Fa pattern if it does not contain any of the special characters ``*'', ``?'' or ``[''. .Dv GLOB_NOMAGIC is provided to simplify implementing the historic .Xr csh 1 globbing behavior and should probably not be used anywhere else. .It Dv GLOB_TILDE Expand patterns that start with .Ql ~ to user name home directories. .It Dv GLOB_LIMIT Limit the total number of returned pathnames to the value provided in .Fa gl_matchc (default .Dv ARG_MAX ) . This option should be set for programs that can be coerced into a denial of service attack via patterns that expand to a very large number of matches, such as a long string of .Ql */../*/.. . .El .Pp If, during the search, a directory is encountered that cannot be opened or read and .Fa errfunc is .Pf non- Dv NULL , .Fn glob calls -.Fa \*(lp*errfunc\*(rp Ns ( Fa path , errno ) . -This may be unintuitive: a pattern like -.Ql */Makefile -will try to -.Xr stat 2 -.Ql foo/Makefile -even if -.Ql foo -is not a directory, resulting in a -call to -.Fa errfunc . -The error routine can suppress this action by testing for -.Er ENOENT -and -.Er ENOTDIR ; +.Fa \*(lp*errfunc\*(rp Ns ( Fa path , errno ) , however, the .Dv GLOB_ERR -flag will still cause an immediate +flag will cause an immediate return when this happens. .Pp If .Fa errfunc returns non-zero, .Fn glob stops the scan and returns .Dv GLOB_ABORTED after setting .Fa gl_pathc and .Fa gl_pathv to reflect any paths already matched. This also happens if an error is encountered and .Dv GLOB_ERR is set in .Fa flags , regardless of the return value of .Fa errfunc , if called. If .Dv GLOB_ERR is not set and either .Fa errfunc is .Dv NULL or .Fa errfunc returns zero, the error is ignored. .Pp The .Fn globfree function frees any space associated with .Fa pglob from a previous call(s) to .Fn glob . .Sh RETURN VALUES On successful completion, .Fn glob returns zero. In addition the fields of .Fa pglob contain the values described below: .Bl -tag -width GLOB_NOCHECK .It Fa gl_pathc contains the total number of matched pathnames so far. This includes other matches from previous invocations of .Fn glob if .Dv GLOB_APPEND was specified. .It Fa gl_matchc contains the number of matched pathnames in the current invocation of .Fn glob . .It Fa gl_flags contains a copy of the .Fa flags argument with the bit .Dv GLOB_MAGCHAR set if .Fa pattern contained any of the special characters ``*'', ``?'' or ``['', cleared if not. .It Fa gl_pathv contains a pointer to a .Dv NULL Ns -terminated list of matched pathnames. However, if .Fa gl_pathc is zero, the contents of .Fa gl_pathv are undefined. .El .Pp If .Fn glob terminates due to an error, it sets errno and returns one of the following non-zero constants, which are defined in the include file .In glob.h : .Bl -tag -width GLOB_NOCHECK .It Dv GLOB_NOSPACE An attempt to allocate memory failed, or if .Fa errno -was 0 +was E2BIG, .Dv GLOB_LIMIT was specified in the flags and .Fa pglob\->gl_matchc or more patterns were matched. .It Dv GLOB_ABORTED The scan was stopped because an error was encountered and either .Dv GLOB_ERR was set or .Fa \*(lp*errfunc\*(rp\*(lp\*(rp returned non-zero. .It Dv GLOB_NOMATCH The pattern did not match a pathname and .Dv GLOB_NOCHECK was not set. .El .Pp The arguments .Fa pglob\->gl_pathc and .Fa pglob\->gl_pathv are still set as specified above. .Sh EXAMPLES A rough equivalent of .Ql "ls -l *.c *.h" can be obtained with the following code: .Bd -literal -offset indent glob_t g; g.gl_offs = 2; glob("*.c", GLOB_DOOFFS, NULL, &g); glob("*.h", GLOB_DOOFFS | GLOB_APPEND, NULL, &g); g.gl_pathv[0] = "ls"; g.gl_pathv[1] = "-l"; execvp("ls", g.gl_pathv); .Ed .Sh SEE ALSO .Xr sh 1 , .Xr fnmatch 3 , .Xr regex 3 .Sh STANDARDS The current implementation of the .Fn glob function .Em does not conform to .St -p1003.2 . Collating symbol expressions, equivalence class expressions and character class expressions are not supported. .Pp The flags .Dv GLOB_ALTDIRFUNC , .Dv GLOB_BRACE , .Dv GLOB_LIMIT , .Dv GLOB_MAGCHAR , .Dv GLOB_NOMAGIC , and .Dv GLOB_TILDE , and the fields .Fa gl_matchc and .Fa gl_flags are extensions to the .Tn POSIX standard and should not be used by applications striving for strict conformance. .Sh HISTORY The .Fn glob and .Fn globfree functions first appeared in .Bx 4.4 . .Sh BUGS Patterns longer than .Dv MAXPATHLEN may cause unchecked errors. .Pp The .Fn glob argument may fail and set errno for any of the errors specified for the library routines .Xr stat 2 , .Xr closedir 3 , .Xr opendir 3 , .Xr readdir 3 , .Xr malloc 3 , and .Xr free 3 . Index: stable/11/lib/libc/gen/glob.c =================================================================== --- stable/11/lib/libc/gen/glob.c (revision 304283) +++ stable/11/lib/libc/gen/glob.c (revision 304284) @@ -1,965 +1,1105 @@ /* * 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. * * Copyright (c) 2011 The FreeBSD Foundation * All rights reserved. * Portions of this software were developed by David Chisnall * under sponsorship from the FreeBSD Foundation. * * 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. * 4. 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. */ #if defined(LIBC_SCCS) && !defined(lint) static char sccsid[] = "@(#)glob.c 8.3 (Berkeley) 10/13/93"; #endif /* LIBC_SCCS and not lint */ #include __FBSDID("$FreeBSD$"); /* * 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. */ /* * Some notes on multibyte character support: * 1. Patterns with illegal byte sequences match nothing - even if * GLOB_NOCHECK is specified. * 2. Illegal byte sequences in filenames are handled by treating them as - * single-byte characters with a value of the first byte of the sequence + * single-byte characters with a values of such bytes of the sequence * cast to wchar_t. * 3. State-dependent encodings are not currently supported. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "collate.h" /* * glob(3) expansion limits. Stop the expansion if any of these limits * is reached. This caps the runtime in the face of DoS attacks. See * also CVE-2010-2632 */ #define GLOB_LIMIT_BRACE 128 /* number of brace calls */ #define GLOB_LIMIT_PATH 65536 /* number of path elements */ #define GLOB_LIMIT_READDIR 16384 /* number of readdirs */ #define GLOB_LIMIT_STAT 1024 /* number of stat system calls */ #define GLOB_LIMIT_STRING ARG_MAX /* maximum total size for paths */ struct glob_limit { size_t l_brace_cnt; size_t l_path_lim; size_t l_readdir_cnt; size_t l_stat_cnt; size_t l_string_cnt; }; -#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 DOT L'.' +#define EOS L'\0' +#define LBRACKET L'[' +#define NOT L'!' +#define QUESTION L'?' +#define QUOTE L'\\' +#define RANGE L'-' +#define RBRACKET L']' +#define SEP L'/' +#define STAR L'*' +#define TILDE L'~' +#define LBRACE L'{' +#define RBRACE L'}' +#define COMMA L',' -#ifndef DEBUG - #define M_QUOTE 0x8000000000ULL #define M_PROTECT 0x4000000000ULL #define M_MASK 0xffffffffffULL #define M_CHAR 0x00ffffffffULL typedef uint_fast64_t Char; -#else - -#define M_QUOTE 0x80 -#define M_PROTECT 0x40 -#define M_MASK 0xff -#define M_CHAR 0x7f - -typedef char Char; - -#endif - - #define CHAR(c) ((Char)((c)&M_CHAR)) #define META(c) ((Char)((c)|M_QUOTE)) -#define M_ALL META('*') -#define M_END META(']') -#define M_NOT META('!') -#define M_ONE META('?') -#define M_RNG META('-') -#define M_SET META('[') +#define UNPROT(c) ((c) & ~M_PROTECT) +#define M_ALL META(L'*') +#define M_END META(L']') +#define M_NOT META(L'!') +#define M_ONE META(L'?') +#define M_RNG META(L'-') +#define M_SET META(L'[') #define ismeta(c) (((c)&M_QUOTE) != 0) +#ifdef DEBUG +#define isprot(c) (((c)&M_PROTECT) != 0) +#endif - static int compare(const void *, const void *); static int g_Ctoc(const Char *, char *, size_t); static int g_lstat(Char *, struct stat *, glob_t *); static DIR *g_opendir(Char *, glob_t *); static const Char *g_strchr(const Char *, wchar_t); #ifdef notdef static Char *g_strcat(Char *, const Char *); #endif static int g_stat(Char *, struct stat *, glob_t *); -static int glob0(const Char *, glob_t *, struct glob_limit *); +static int glob0(const Char *, glob_t *, struct glob_limit *, + const char *); static int glob1(Char *, glob_t *, struct glob_limit *); static int glob2(Char *, Char *, Char *, Char *, glob_t *, struct glob_limit *); static int glob3(Char *, Char *, Char *, Char *, Char *, glob_t *, struct glob_limit *); -static int globextend(const Char *, glob_t *, struct glob_limit *); +static int globextend(const Char *, glob_t *, struct glob_limit *, + const char *); static const Char * globtilde(const Char *, Char *, size_t, glob_t *); +static int globexp0(const Char *, glob_t *, struct glob_limit *, + const char *); static int globexp1(const Char *, glob_t *, struct glob_limit *); -static int globexp2(const Char *, const Char *, glob_t *, int *, +static int globexp2(const Char *, const Char *, glob_t *, struct glob_limit *); +static int globfinal(glob_t *, struct glob_limit *, size_t, + const char *); static int match(Char *, Char *, Char *); +static int err_nomatch(glob_t *, struct glob_limit *, const char *); +static int err_aborted(glob_t *, int, char *); #ifdef DEBUG static void qprintf(const char *, Char *); #endif int glob(const char * __restrict pattern, int flags, int (*errfunc)(const char *, int), glob_t * __restrict pglob) { struct glob_limit limit = { 0, 0, 0, 0, 0 }; const char *patnext; Char *bufnext, *bufend, patbuf[MAXPATHLEN], prot; mbstate_t mbs; wchar_t wc; size_t clen; + int too_long; patnext = pattern; if (!(flags & GLOB_APPEND)) { pglob->gl_pathc = 0; pglob->gl_pathv = NULL; if (!(flags & GLOB_DOOFFS)) pglob->gl_offs = 0; } if (flags & GLOB_LIMIT) { limit.l_path_lim = pglob->gl_matchc; if (limit.l_path_lim == 0) limit.l_path_lim = GLOB_LIMIT_PATH; } pglob->gl_flags = flags & ~GLOB_MAGCHAR; pglob->gl_errfunc = errfunc; pglob->gl_matchc = 0; bufnext = patbuf; bufend = bufnext + MAXPATHLEN - 1; + too_long = 1; if (flags & GLOB_NOESCAPE) { memset(&mbs, 0, sizeof(mbs)); - while (bufend - bufnext >= MB_CUR_MAX) { + while (bufnext <= bufend) { clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); if (clen == (size_t)-1 || clen == (size_t)-2) - return (GLOB_NOMATCH); - else if (clen == 0) + return (err_nomatch(pglob, &limit, pattern)); + else if (clen == 0) { + too_long = 0; break; + } *bufnext++ = wc; patnext += clen; } } else { /* Protect the quoted characters. */ memset(&mbs, 0, sizeof(mbs)); - while (bufend - bufnext >= MB_CUR_MAX) { - if (*patnext == QUOTE) { - if (*++patnext == EOS) { - *bufnext++ = QUOTE | M_PROTECT; + while (bufnext <= bufend) { + if (*patnext == '\\') { + if (*++patnext == '\0') { + *bufnext++ = QUOTE; continue; } prot = M_PROTECT; } else prot = 0; clen = mbrtowc(&wc, patnext, MB_LEN_MAX, &mbs); if (clen == (size_t)-1 || clen == (size_t)-2) - return (GLOB_NOMATCH); - else if (clen == 0) + return (err_nomatch(pglob, &limit, pattern)); + else if (clen == 0) { + too_long = 0; break; + } *bufnext++ = wc | prot; patnext += clen; } } + if (too_long) + return (err_nomatch(pglob, &limit, pattern)); *bufnext = EOS; if (flags & GLOB_BRACE) - return (globexp1(patbuf, pglob, &limit)); + return (globexp0(patbuf, pglob, &limit, pattern)); else - return (glob0(patbuf, pglob, &limit)); + return (glob0(patbuf, pglob, &limit, pattern)); } +static int +globexp0(const Char *pattern, glob_t *pglob, struct glob_limit *limit, + const char *origpat) { + int rv; + size_t oldpathc; + + /* Protect a single {}, for find(1), like csh */ + if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS) { + if ((pglob->gl_flags & GLOB_LIMIT) && + limit->l_brace_cnt++ >= GLOB_LIMIT_BRACE) { + errno = E2BIG; + return (GLOB_NOSPACE); + } + return (glob0(pattern, pglob, limit, origpat)); + } + + oldpathc = pglob->gl_pathc; + + if ((rv = globexp1(pattern, pglob, limit)) != 0) + return rv; + + return (globfinal(pglob, limit, oldpathc, origpat)); +} + /* * 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 Char *pattern, glob_t *pglob, struct glob_limit *limit) { - const Char* ptr = pattern; - int rv; + const Char* ptr; - if ((pglob->gl_flags & GLOB_LIMIT) && - limit->l_brace_cnt++ >= GLOB_LIMIT_BRACE) { - errno = 0; - return (GLOB_NOSPACE); + if ((ptr = g_strchr(pattern, LBRACE)) != NULL) { + if ((pglob->gl_flags & GLOB_LIMIT) && + limit->l_brace_cnt++ >= GLOB_LIMIT_BRACE) { + errno = E2BIG; + return (GLOB_NOSPACE); + } + return (globexp2(ptr, pattern, pglob, limit)); } - /* Protect a single {}, for find(1), like csh */ - if (pattern[0] == LBRACE && pattern[1] == RBRACE && pattern[2] == EOS) - return glob0(pattern, pglob, limit); - - while ((ptr = g_strchr(ptr, LBRACE)) != NULL) - if (!globexp2(ptr, pattern, pglob, &rv, limit)) - return rv; - - return glob0(pattern, pglob, limit); + return (glob0(pattern, pglob, limit, NULL)); } /* * 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 Char *ptr, const Char *pattern, glob_t *pglob, int *rv, +globexp2(const Char *ptr, const Char *pattern, glob_t *pglob, struct glob_limit *limit) { - int i; + int i, rv; Char *lm, *ls; const Char *pe, *pm, *pm1, *pl; Char patbuf[MAXPATHLEN]; /* copy part up to the brace */ for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++) continue; *lm = EOS; ls = lm; /* Find the balanced brace */ - for (i = 0, pe = ++ptr; *pe; pe++) + for (i = 0, pe = ++ptr; *pe != EOS; pe++) if (*pe == LBRACKET) { /* Ignore everything between [] */ for (pm = pe++; *pe != RBRACKET && *pe != EOS; pe++) continue; if (*pe == EOS) { /* * We could not find a matching RBRACKET. * Ignore and just look for RBRACE */ pe = pm; } } else if (*pe == LBRACE) i++; else if (*pe == RBRACE) { if (i == 0) break; i--; } /* Non matching braces; just glob the pattern */ - if (i != 0 || *pe == EOS) { - *rv = glob0(patbuf, pglob, limit); - return (0); - } + if (i != 0 || *pe == EOS) + return (glob0(pattern, pglob, limit, NULL)); for (i = 0, pl = pm = ptr; pm <= pe; pm++) switch (*pm) { case LBRACKET: /* Ignore everything between [] */ for (pm1 = pm++; *pm != RBRACKET && *pm != EOS; pm++) continue; if (*pm == EOS) { /* * We could not find a matching RBRACKET. * Ignore and just look for RBRACE */ pm = pm1; } break; case LBRACE: i++; break; case RBRACE: if (i) { i--; break; } /* FALLTHROUGH */ case COMMA: if (i && *pm == COMMA) break; else { /* Append the current string */ for (lm = ls; (pl < pm); *lm++ = *pl++) continue; /* * Append the rest of the pattern after the * closing brace */ for (pl = pe + 1; (*lm++ = *pl++) != EOS;) continue; /* Expand the current pattern */ #ifdef DEBUG qprintf("globexp2:", patbuf); #endif - *rv = globexp1(patbuf, pglob, limit); + rv = globexp1(patbuf, pglob, limit); + if (rv) + return (rv); /* move after the comma, to the next string */ pl = pm + 1; } break; default: break; } - *rv = 0; return (0); } /* * expand tilde from the passwd file. */ static const Char * globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob) { struct passwd *pwd; - char *h; + char *h, *sc; const Char *p; Char *b, *eb; + wchar_t wc; + wchar_t wbuf[MAXPATHLEN]; + wchar_t *wbufend, *dc; + size_t clen; + mbstate_t mbs; + int too_long; if (*pattern != 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, h = (char *) patbuf; - h < (char *)eb && *p && *p != SLASH; *h++ = *p++) + for (p = pattern + 1, b = patbuf; + b < eb && *p != EOS && UNPROT(*p) != SEP; *b++ = *p++) continue; - *h = EOS; + if (*p != EOS && UNPROT(*p) != SEP) + return (NULL); - if (((char *) patbuf)[0] == EOS) { + *b = EOS; + h = NULL; + + if (patbuf[0] == EOS) { /* * handle a plain ~ or ~/ by expanding $HOME first (iff * we're not running setuid or setgid) and then trying * the password file */ if (issetugid() != 0 || (h = getenv("HOME")) == NULL) { if (((h = getlogin()) != NULL && (pwd = getpwnam(h)) != NULL) || (pwd = getpwuid(getuid())) != NULL) h = pwd->pw_dir; else return (pattern); } } else { /* * Expand a ~user */ - if ((pwd = getpwnam((char*) patbuf)) == NULL) + if (g_Ctoc(patbuf, (char *)wbuf, sizeof(wbuf))) + return (NULL); + if ((pwd = getpwnam((char *)wbuf)) == NULL) return (pattern); else h = pwd->pw_dir; } /* Copy the home directory */ - for (b = patbuf; b < eb && *h; *b++ = *h++) + dc = wbuf; + sc = h; + wbufend = wbuf + MAXPATHLEN - 1; + too_long = 1; + memset(&mbs, 0, sizeof(mbs)); + while (dc <= wbufend) { + clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs); + if (clen == (size_t)-1 || clen == (size_t)-2) { + /* XXX See initial comment #2. */ + wc = (unsigned char)*sc; + clen = 1; + memset(&mbs, 0, sizeof(mbs)); + } + if ((*dc++ = wc) == EOS) { + too_long = 0; + break; + } + sc += clen; + } + if (too_long) + return (NULL); + + dc = wbuf; + for (b = patbuf; b < eb && *dc != EOS; *b++ = *dc++ | M_PROTECT) continue; + if (*dc != EOS) + return (NULL); /* Append the rest of the pattern */ - while (b < eb && (*b++ = *p++) != EOS) - continue; - *b = EOS; + if (*p != EOS) { + too_long = 1; + while (b <= eb) { + if ((*b++ = *p++) == EOS) { + too_long = 0; + break; + } + } + if (too_long) + return (NULL); + } else + *b = EOS; return (patbuf); } /* * 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. */ static int -glob0(const Char *pattern, glob_t *pglob, struct glob_limit *limit) -{ +glob0(const Char *pattern, glob_t *pglob, struct glob_limit *limit, + const char *origpat) { const Char *qpatnext; int err; size_t oldpathc; Char *bufnext, c, patbuf[MAXPATHLEN]; qpatnext = globtilde(pattern, patbuf, MAXPATHLEN, pglob); + if (qpatnext == NULL) { + errno = E2BIG; + return (GLOB_NOSPACE); + } oldpathc = pglob->gl_pathc; bufnext = patbuf; /* We don't need to check for buffer overflow any more. */ while ((c = *qpatnext++) != EOS) { switch (c) { case LBRACKET: c = *qpatnext; if (c == NOT) ++qpatnext; if (*qpatnext == EOS || g_strchr(qpatnext+1, RBRACKET) == NULL) { *bufnext++ = LBRACKET; if (c == NOT) --qpatnext; break; } *bufnext++ = M_SET; if (c == NOT) *bufnext++ = M_NOT; c = *qpatnext++; do { *bufnext++ = CHAR(c); if (*qpatnext == RANGE && (c = qpatnext[1]) != RBRACKET) { *bufnext++ = M_RNG; *bufnext++ = CHAR(c); qpatnext += 2; } } while ((c = *qpatnext++) != RBRACKET); pglob->gl_flags |= GLOB_MAGCHAR; *bufnext++ = M_END; break; case QUESTION: pglob->gl_flags |= GLOB_MAGCHAR; *bufnext++ = M_ONE; break; case STAR: pglob->gl_flags |= GLOB_MAGCHAR; /* collapse adjacent stars to one, * to avoid exponential behavior */ if (bufnext == patbuf || bufnext[-1] != M_ALL) *bufnext++ = M_ALL; break; default: *bufnext++ = CHAR(c); break; } } *bufnext = EOS; #ifdef DEBUG qprintf("glob0:", patbuf); #endif if ((err = glob1(patbuf, pglob, limit)) != 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, limit)); - else - return (GLOB_NOMATCH); - } + if (origpat != NULL) + return (globfinal(pglob, limit, oldpathc, origpat)); + + return (0); +} + +static int +globfinal(glob_t *pglob, struct glob_limit *limit, size_t oldpathc, + const char *origpat) { + if (pglob->gl_pathc == oldpathc) + return (err_nomatch(pglob, limit, origpat)); + if (!(pglob->gl_flags & GLOB_NOSORT)) 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)); + return (strcoll(*(char **)p, *(char **)q)); } static int glob1(Char *pattern, glob_t *pglob, struct glob_limit *limit) { Char pathbuf[MAXPATHLEN]; /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */ if (*pattern == EOS) return (0); return (glob2(pathbuf, pathbuf, pathbuf + MAXPATHLEN - 1, pattern, pglob, limit)); } /* * 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(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern, glob_t *pglob, struct glob_limit *limit) { struct stat sb; Char *p, *q; int anymeta; /* * Loop over pattern segments until end of pattern or until * segment with meta character found. */ for (anymeta = 0;;) { if (*pattern == EOS) { /* End of pattern? */ *pathend = EOS; if (g_lstat(pathbuf, &sb, pglob)) return (0); if ((pglob->gl_flags & GLOB_LIMIT) && limit->l_stat_cnt++ >= GLOB_LIMIT_STAT) { - errno = 0; - if (pathend + 1 > pathend_last) - return (GLOB_ABORTED); - *pathend++ = SEP; - *pathend = EOS; + errno = E2BIG; return (GLOB_NOSPACE); } - if (((pglob->gl_flags & GLOB_MARK) && - pathend[-1] != SEP) && (S_ISDIR(sb.st_mode) - || (S_ISLNK(sb.st_mode) && - (g_stat(pathbuf, &sb, pglob) == 0) && + if ((pglob->gl_flags & GLOB_MARK) && + UNPROT(pathend[-1]) != 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_ABORTED); + if (pathend + 1 > pathend_last) { + errno = E2BIG; + return (GLOB_NOSPACE); + } *pathend++ = SEP; *pathend = EOS; } ++pglob->gl_matchc; - return (globextend(pathbuf, pglob, limit)); + return (globextend(pathbuf, pglob, limit, NULL)); } /* Find end of next segment, copy tentatively to pathend. */ q = pathend; p = pattern; - while (*p != EOS && *p != SEP) { + while (*p != EOS && UNPROT(*p) != SEP) { if (ismeta(*p)) anymeta = 1; - if (q + 1 > pathend_last) - return (GLOB_ABORTED); + if (q + 1 > pathend_last) { + errno = E2BIG; + return (GLOB_NOSPACE); + } *q++ = *p++; } if (!anymeta) { /* No expansion, do next segment. */ pathend = q; pattern = p; - while (*pattern == SEP) { - if (pathend + 1 > pathend_last) - return (GLOB_ABORTED); + while (UNPROT(*pattern) == SEP) { + if (pathend + 1 > pathend_last) { + errno = E2BIG; + return (GLOB_NOSPACE); + } *pathend++ = *pattern++; } } else /* Need expansion, recurse. */ return (glob3(pathbuf, pathend, pathend_last, pattern, p, pglob, limit)); } /* NOTREACHED */ } static int glob3(Char *pathbuf, Char *pathend, Char *pathend_last, Char *pattern, Char *restpattern, glob_t *pglob, struct glob_limit *limit) { struct dirent *dp; DIR *dirp; - int err; - char buf[MAXPATHLEN]; + int err, too_long, saverrno, saverrno2; + char buf[MAXPATHLEN + MB_LEN_MAX - 1]; struct dirent *(*readdirfunc)(DIR *); - if (pathend > pathend_last) - return (GLOB_ABORTED); + if (pathend > pathend_last) { + errno = E2BIG; + return (GLOB_NOSPACE); + } *pathend = EOS; - errno = 0; + if (pglob->gl_errfunc != NULL && + g_Ctoc(pathbuf, buf, sizeof(buf))) { + errno = E2BIG; + return (GLOB_NOSPACE); + } + saverrno = errno; + errno = 0; if ((dirp = g_opendir(pathbuf, pglob)) == NULL) { - /* TODO: don't call for ENOENT or ENOTDIR? */ - if (pglob->gl_errfunc) { - if (g_Ctoc(pathbuf, buf, sizeof(buf))) - return (GLOB_ABORTED); - if (pglob->gl_errfunc(buf, errno) || - pglob->gl_flags & GLOB_ERR) - return (GLOB_ABORTED); - } - return (0); + if (errno == ENOENT || errno == ENOTDIR) + return (0); + err = err_aborted(pglob, errno, buf); + if (errno == 0) + errno = saverrno; + return (err); } err = 0; /* pglob->gl_readdir takes a void *, fix this manually */ if (pglob->gl_flags & GLOB_ALTDIRFUNC) readdirfunc = (struct dirent *(*)(DIR *))pglob->gl_readdir; else readdirfunc = readdir; + errno = 0; /* Search directory for matching names. */ while ((dp = (*readdirfunc)(dirp)) != NULL) { char *sc; Char *dc; wchar_t wc; size_t clen; mbstate_t mbs; if ((pglob->gl_flags & GLOB_LIMIT) && limit->l_readdir_cnt++ >= GLOB_LIMIT_READDIR) { - errno = 0; - if (pathend + 1 > pathend_last) - err = GLOB_ABORTED; - else { - *pathend++ = SEP; - *pathend = EOS; - err = GLOB_NOSPACE; - } + errno = E2BIG; + err = GLOB_NOSPACE; break; } /* Initial DOT must be matched literally. */ - if (dp->d_name[0] == DOT && *pattern != DOT) + if (dp->d_name[0] == '.' && UNPROT(*pattern) != DOT) { + errno = 0; continue; + } memset(&mbs, 0, sizeof(mbs)); dc = pathend; sc = dp->d_name; - while (dc < pathend_last) { + too_long = 1; + while (dc <= pathend_last) { clen = mbrtowc(&wc, sc, MB_LEN_MAX, &mbs); if (clen == (size_t)-1 || clen == (size_t)-2) { - wc = *sc; + /* XXX See initial comment #2. */ + wc = (unsigned char)*sc; clen = 1; memset(&mbs, 0, sizeof(mbs)); } - if ((*dc++ = wc) == EOS) + if ((*dc++ = wc) == EOS) { + too_long = 0; break; + } sc += clen; } - if (!match(pathend, pattern, restpattern)) { + if (too_long && (err = err_aborted(pglob, ENAMETOOLONG, + buf))) { + errno = ENAMETOOLONG; + break; + } + if (too_long || !match(pathend, pattern, restpattern)) { *pathend = EOS; + errno = 0; continue; } + if (errno == 0) + errno = saverrno; err = glob2(pathbuf, --dc, pathend_last, restpattern, pglob, limit); if (err) break; + errno = 0; } + saverrno2 = errno; if (pglob->gl_flags & GLOB_ALTDIRFUNC) (*pglob->gl_closedir)(dirp); else closedir(dirp); - return (err); + errno = saverrno2; + + if (err) + return (err); + + if (dp == NULL && errno != 0 && + (err = err_aborted(pglob, errno, buf))) + return (err); + + if (errno == 0) + errno = saverrno; + return (0); } /* * Extend the gl_pathv member of a glob_t structure to accommodate a new item, * add the new item, and update gl_pathc. * * This assumes the BSD realloc, which only copies the block when its size * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic * behavior. * * 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 Char *path, glob_t *pglob, struct glob_limit *limit) +globextend(const Char *path, glob_t *pglob, struct glob_limit *limit, + const char *origpat) { char **pathv; size_t i, newsize, len; char *copy; const Char *p; if ((pglob->gl_flags & GLOB_LIMIT) && pglob->gl_matchc > limit->l_path_lim) { - errno = 0; + errno = E2BIG; return (GLOB_NOSPACE); } newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs); /* realloc(NULL, newsize) is equivalent to malloc(newsize). */ pathv = realloc((void *)pglob->gl_pathv, newsize); if (pathv == NULL) return (GLOB_NOSPACE); 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 + 1; --i > 0; ) *--pathv = NULL; } pglob->gl_pathv = pathv; - for (p = path; *p++;) - continue; - len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */ - limit->l_string_cnt += len; - if ((pglob->gl_flags & GLOB_LIMIT) && - limit->l_string_cnt >= GLOB_LIMIT_STRING) { - errno = 0; - return (GLOB_NOSPACE); + if (origpat != NULL) + copy = strdup(origpat); + else { + for (p = path; *p++ != EOS;) + continue; + len = MB_CUR_MAX * (size_t)(p - path); /* XXX overallocation */ + if ((copy = malloc(len)) != NULL) { + if (g_Ctoc(path, copy, len)) { + free(copy); + errno = E2BIG; + return (GLOB_NOSPACE); + } + } } - if ((copy = malloc(len)) != NULL) { - if (g_Ctoc(path, copy, len)) { + if (copy != NULL) { + limit->l_string_cnt += strlen(copy) + 1; + if ((pglob->gl_flags & GLOB_LIMIT) && + limit->l_string_cnt >= GLOB_LIMIT_STRING) { free(copy); + errno = E2BIG; return (GLOB_NOSPACE); } pathv[pglob->gl_offs + pglob->gl_pathc++] = copy; } pathv[pglob->gl_offs + pglob->gl_pathc] = NULL; return (copy == NULL ? GLOB_NOSPACE : 0); } /* * pattern matching function for filenames. Each occurrence of the * * pattern causes a recursion level. */ static int match(Char *name, Char *pat, Char *patend) { int ok, negate_range; Char c, k; struct xlocale_collate *table = (struct xlocale_collate*)__get_locale()->components[XLC_COLLATE]; while (pat < patend) { c = *pat++; switch (c & M_MASK) { case M_ALL: if (pat == patend) return (1); do if (match(name, pat, patend)) return (1); while (*name++ != EOS); return (0); case M_ONE: if (*name++ == EOS) return (0); break; case M_SET: ok = 0; if ((k = *name++) == EOS) return (0); - if ((negate_range = ((*pat & M_MASK) == M_NOT)) != EOS) + if ((negate_range = ((*pat & M_MASK) == M_NOT)) != 0) ++pat; while (((c = *pat++) & M_MASK) != M_END) if ((*pat & M_MASK) == M_RNG) { if (table->__collate_load_error ? - CHAR(c) <= CHAR(k) && CHAR(k) <= CHAR(pat[1]) : - __wcollate_range_cmp(CHAR(c), CHAR(k)) <= 0 - && __wcollate_range_cmp(CHAR(k), CHAR(pat[1])) <= 0 - ) + CHAR(c) <= CHAR(k) && + CHAR(k) <= CHAR(pat[1]) : + __wcollate_range_cmp(CHAR(c), + CHAR(k)) <= 0 && + __wcollate_range_cmp(CHAR(k), + CHAR(pat[1])) <= 0) ok = 1; pat += 2; } else if (c == k) ok = 1; if (ok == negate_range) return (0); break; default: if (*name++ != c) return (0); break; } } return (*name == EOS); } /* Free allocated data belonging to a glob_t structure. */ void globfree(glob_t *pglob) { size_t i; char **pp; if (pglob->gl_pathv != NULL) { pp = pglob->gl_pathv + pglob->gl_offs; for (i = pglob->gl_pathc; i--; ++pp) if (*pp) free(*pp); free(pglob->gl_pathv); pglob->gl_pathv = NULL; } } static DIR * g_opendir(Char *str, glob_t *pglob) { - char buf[MAXPATHLEN]; + char buf[MAXPATHLEN + MB_LEN_MAX - 1]; - if (!*str) + if (*str == EOS) strcpy(buf, "."); else { - if (g_Ctoc(str, buf, sizeof(buf))) + if (g_Ctoc(str, buf, sizeof(buf))) { + errno = ENAMETOOLONG; return (NULL); + } } if (pglob->gl_flags & GLOB_ALTDIRFUNC) return ((*pglob->gl_opendir)(buf)); return (opendir(buf)); } static int g_lstat(Char *fn, struct stat *sb, glob_t *pglob) { - char buf[MAXPATHLEN]; + char buf[MAXPATHLEN + MB_LEN_MAX - 1]; if (g_Ctoc(fn, buf, sizeof(buf))) { errno = ENAMETOOLONG; return (-1); } if (pglob->gl_flags & GLOB_ALTDIRFUNC) return((*pglob->gl_lstat)(buf, sb)); return (lstat(buf, sb)); } static int g_stat(Char *fn, struct stat *sb, glob_t *pglob) { - char buf[MAXPATHLEN]; + char buf[MAXPATHLEN + MB_LEN_MAX - 1]; if (g_Ctoc(fn, buf, sizeof(buf))) { errno = ENAMETOOLONG; return (-1); } if (pglob->gl_flags & GLOB_ALTDIRFUNC) return ((*pglob->gl_stat)(buf, sb)); return (stat(buf, sb)); } static const Char * g_strchr(const Char *str, wchar_t ch) { do { if (*str == ch) return (str); } while (*str++); return (NULL); } static int g_Ctoc(const Char *str, char *buf, size_t len) { mbstate_t mbs; size_t clen; memset(&mbs, 0, sizeof(mbs)); while (len >= MB_CUR_MAX) { - clen = wcrtomb(buf, *str, &mbs); - if (clen == (size_t)-1) - return (1); - if (*str == L'\0') + clen = wcrtomb(buf, CHAR(*str), &mbs); + if (clen == (size_t)-1) { + /* XXX See initial comment #2. */ + *buf = (char)CHAR(*str); + clen = 1; + memset(&mbs, 0, sizeof(mbs)); + } + if (CHAR(*str) == EOS) return (0); str++; buf += clen; len -= clen; } return (1); } +static int +err_nomatch(glob_t *pglob, struct glob_limit *limit, const char *origpat) { + /* + * If there was no match we are going to append the origpat + * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified + * and the origpat did not contain any magic characters + * GLOB_NOMAGIC is there just for compatibility with csh. + */ + if ((pglob->gl_flags & GLOB_NOCHECK) || + ((pglob->gl_flags & GLOB_NOMAGIC) && + !(pglob->gl_flags & GLOB_MAGCHAR))) + return (globextend(NULL, pglob, limit, origpat)); + return (GLOB_NOMATCH); +} + +static int +err_aborted(glob_t *pglob, int err, char *buf) { + if ((pglob->gl_errfunc != NULL && pglob->gl_errfunc(buf, err)) || + (pglob->gl_flags & GLOB_ERR)) + return (GLOB_ABORTED); + return (0); +} + #ifdef DEBUG static void qprintf(const char *str, Char *s) { Char *p; - (void)printf("%s:\n", str); - for (p = s; *p; p++) - (void)printf("%c", CHAR(*p)); - (void)printf("\n"); - for (p = s; *p; p++) - (void)printf("%c", *p & M_PROTECT ? '"' : ' '); - (void)printf("\n"); - for (p = s; *p; p++) - (void)printf("%c", ismeta(*p) ? '_' : ' '); - (void)printf("\n"); + (void)printf("%s\n", str); + if (s != NULL) { + for (p = s; *p != EOS; p++) + (void)printf("%c", (char)CHAR(*p)); + (void)printf("\n"); + for (p = s; *p != EOS; p++) + (void)printf("%c", (isprot(*p) ? '\\' : ' ')); + (void)printf("\n"); + for (p = s; *p != EOS; p++) + (void)printf("%c", (ismeta(*p) ? '_' : ' ')); + (void)printf("\n"); + } } #endif Index: stable/11 =================================================================== --- stable/11 (revision 304283) +++ stable/11 (revision 304284) Property changes on: stable/11 ___________________________________________________________________ Modified: svn:mergeinfo ## -0,0 +0,1 ## Merged /head:r302943-302944,303004,303010-303011,303013-303014,303074,303088,303142,303208,303210,303530,303536,303564-303565,303706