Fix #if/#else bug introduced with MACOS_TRADITIONAL patches in change 9479.
[p5sagit/p5-mst-13.2.git] / ext / File-Glob / bsd_glob.c
1 /*
2  * Copyright (c) 1989, 1993
3  *      The Regents of the University of California.  All rights reserved.
4  *
5  * This code is derived from software contributed to Berkeley by
6  * Guido van Rossum.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in the
15  *    documentation and/or other materials provided with the distribution.
16  * 3. Neither the name of the University nor the names of its contributors
17  *    may be used to endorse or promote products derived from this software
18  *    without specific prior written permission.
19  *
20  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
21  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
24  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
25  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
26  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
28  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
29  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
30  * SUCH DAMAGE.
31  */
32
33 #if defined(LIBC_SCCS) && !defined(lint)
34 static char sccsid[] = "@(#)glob.c      8.3 (Berkeley) 10/13/93";
35 /* most changes between the version above and the one below have been ported:
36 static char sscsid[]=  "$OpenBSD: glob.c,v 1.8.10.1 2001/04/10 jason Exp $";
37  */
38 #endif /* LIBC_SCCS and not lint */
39
40 /*
41  * glob(3) -- a superset of the one defined in POSIX 1003.2.
42  *
43  * The [!...] convention to negate a range is supported (SysV, Posix, ksh).
44  *
45  * Optional extra services, controlled by flags not defined by POSIX:
46  *
47  * GLOB_QUOTE:
48  *      Escaping convention: \ inhibits any special meaning the following
49  *      character might have (except \ at end of string is retained).
50  * GLOB_MAGCHAR:
51  *      Set in gl_flags if pattern contained a globbing character.
52  * GLOB_NOMAGIC:
53  *      Same as GLOB_NOCHECK, but it will only append pattern if it did
54  *      not contain any magic characters.  [Used in csh style globbing]
55  * GLOB_ALTDIRFUNC:
56  *      Use alternately specified directory access functions.
57  * GLOB_TILDE:
58  *      expand ~user/foo to the /home/dir/of/user/foo
59  * GLOB_BRACE:
60  *      expand {1,2}{a,b} to 1a 1b 2a 2b
61  * gl_matchc:
62  *      Number of matches in the current invocation of glob.
63  * GLOB_ALPHASORT:
64  *      sort alphabetically like csh (case doesn't matter) instead of in ASCII
65  *      order
66  */
67
68 #include <EXTERN.h>
69 #include <perl.h>
70 #include <XSUB.h>
71
72 #include "bsd_glob.h"
73 #ifdef I_PWD
74 #       include <pwd.h>
75 #else
76 #if defined(HAS_PASSWD) && !defined(VMS)
77         struct passwd *getpwnam(char *);
78         struct passwd *getpwuid(Uid_t);
79 #endif
80 #endif
81
82 #ifndef MAXPATHLEN
83 #  ifdef PATH_MAX
84 #    define     MAXPATHLEN      PATH_MAX
85 #  else
86 #    ifdef MACOS_TRADITIONAL
87 #      define   MAXPATHLEN      255
88 #    else
89 #      define   MAXPATHLEN      1024
90 #    endif
91 #  endif
92 #endif
93
94 #ifdef I_LIMITS
95 #include <limits.h>
96 #endif
97
98 #ifndef ARG_MAX
99 #  ifdef MACOS_TRADITIONAL
100 #    define             ARG_MAX         65536   /* Mac OS is actually unlimited */
101 #  else
102 #    ifdef _SC_ARG_MAX
103 #      define           ARG_MAX         (sysconf(_SC_ARG_MAX))
104 #    else
105 #      ifdef _POSIX_ARG_MAX
106 #        define         ARG_MAX         _POSIX_ARG_MAX
107 #      else
108 #        ifdef WIN32
109 #          define       ARG_MAX         14500   /* from VC's limits.h */
110 #        else
111 #          define       ARG_MAX         4096    /* from POSIX, be conservative */
112 #        endif
113 #      endif
114 #    endif
115 #  endif
116 #endif
117
118 #define BG_DOLLAR       '$'
119 #define BG_DOT          '.'
120 #define BG_EOS          '\0'
121 #define BG_LBRACKET     '['
122 #define BG_NOT          '!'
123 #define BG_QUESTION     '?'
124 #define BG_QUOTE        '\\'
125 #define BG_RANGE        '-'
126 #define BG_RBRACKET     ']'
127 #ifdef MACOS_TRADITIONAL
128 #  define       BG_SEP  ':'
129 #else
130 #  define       BG_SEP  '/'
131 #endif
132 #ifdef DOSISH
133 #define BG_SEP2         '\\'
134 #endif
135 #define BG_STAR         '*'
136 #define BG_TILDE        '~'
137 #define BG_UNDERSCORE   '_'
138 #define BG_LBRACE       '{'
139 #define BG_RBRACE       '}'
140 #define BG_SLASH        '/'
141 #define BG_COMMA        ','
142
143 #ifndef GLOB_DEBUG
144
145 #define M_QUOTE         0x8000
146 #define M_PROTECT       0x4000
147 #define M_MASK          0xffff
148 #define M_ASCII         0x00ff
149
150 typedef U16 Char;
151
152 #else
153
154 #define M_QUOTE         0x80
155 #define M_PROTECT       0x40
156 #define M_MASK          0xff
157 #define M_ASCII         0x7f
158
159 typedef U8 Char;
160
161 #endif /* !GLOB_DEBUG */
162
163
164 #define CHAR(c)         ((Char)((c)&M_ASCII))
165 #define META(c)         ((Char)((c)|M_QUOTE))
166 #define M_ALL           META('*')
167 #define M_END           META(']')
168 #define M_NOT           META('!')
169 #define M_ONE           META('?')
170 #define M_RNG           META('-')
171 #define M_SET           META('[')
172 #define ismeta(c)       (((c)&M_QUOTE) != 0)
173
174
175 static int       compare(const void *, const void *);
176 static int       ci_compare(const void *, const void *);
177 static int       g_Ctoc(const Char *, char *, STRLEN);
178 static int       g_lstat(Char *, Stat_t *, glob_t *);
179 static DIR      *g_opendir(Char *, glob_t *);
180 static Char     *g_strchr(Char *, int);
181 static int       g_stat(Char *, Stat_t *, glob_t *);
182 static int       glob0(const Char *, glob_t *);
183 static int       glob1(Char *, Char *, glob_t *, size_t *);
184 static int       glob2(Char *, Char *, Char *, Char *, Char *, Char *,
185                        glob_t *, size_t *);
186 static int       glob3(Char *, Char *, Char *, Char *, Char *, Char *,
187                        Char *, Char *, glob_t *, size_t *);
188 static int       globextend(const Char *, glob_t *, size_t *);
189 static const Char *
190                  globtilde(const Char *, Char *, size_t, glob_t *);
191 static int       globexp1(const Char *, glob_t *);
192 static int       globexp2(const Char *, const Char *, glob_t *, int *);
193 static int       match(Char *, Char *, Char *, int);
194 #ifdef GLOB_DEBUG
195 static void      qprintf(const char *, Char *);
196 #endif /* GLOB_DEBUG */
197
198 #ifdef PERL_IMPLICIT_CONTEXT
199 static Direntry_t *     my_readdir(DIR*);
200
201 static Direntry_t *
202 my_readdir(DIR *d)
203 {
204 #ifndef NETWARE
205     return PerlDir_read(d);
206 #else
207     return (DIR *)PerlDir_read(d);
208 #endif
209 }
210 #else
211
212 /* ReliantUNIX (OS formerly known as SINIX) defines readdir
213  * in LFS-mode to be a 64-bit version of readdir.  */
214
215 #   ifdef sinix
216 static Direntry_t *    my_readdir(DIR*);
217
218 static Direntry_t *
219 my_readdir(DIR *d)
220 {
221     return readdir(d);
222 }
223 #   else
224
225 #       define  my_readdir      readdir
226
227 #   endif
228
229 #endif
230
231 #ifdef MACOS_TRADITIONAL
232 #include <Files.h>
233 #include <Types.h>
234 #include <string.h>
235
236 #define NO_UPDIR_ERR 1  /* updir resolving failed */
237
238 static Boolean g_matchVol; /* global variable */
239 static short updir(char *path);
240 static short resolve_updirs(char *new_pattern);
241 static void remove_trColon(char *path);
242 static short glob_mark_Mac(Char *pathbuf, Char *pathend, Char *pathend_last);
243 static OSErr GetVolInfo(short volume, Boolean indexed, FSSpec *spec);
244 static void name_f_FSSpec(StrFileName volname, FSSpec *spec);
245
246 #endif
247
248 int
249 bsd_glob(const char *pattern, int flags,
250          int (*errfunc)(const char *, int), glob_t *pglob)
251 {
252         const U8 *patnext;
253         int c;
254         Char *bufnext, *bufend, patbuf[MAXPATHLEN];
255
256 #ifdef MACOS_TRADITIONAL
257         char *new_pat, *p, *np;
258         int err;
259         size_t len;
260 #endif
261
262 #ifndef MACOS_TRADITIONAL
263         patnext = (U8 *) pattern;
264 #endif
265         /* TODO: GLOB_APPEND / GLOB_DOOFFS aren't supported yet */
266 #if 0
267         if (!(flags & GLOB_APPEND)) {
268                 pglob->gl_pathc = 0;
269                 pglob->gl_pathv = NULL;
270                 if (!(flags & GLOB_DOOFFS))
271                         pglob->gl_offs = 0;
272         }
273 #else
274         pglob->gl_pathc = 0;
275         pglob->gl_pathv = NULL;
276         pglob->gl_offs = 0;
277 #endif
278         pglob->gl_flags = flags & ~GLOB_MAGCHAR;
279         pglob->gl_errfunc = errfunc;
280         pglob->gl_matchc = 0;
281
282         bufnext = patbuf;
283         bufend = bufnext + MAXPATHLEN - 1;
284 #ifdef DOSISH
285         /* Nasty hack to treat patterns like "C:*" correctly. In this
286          * case, the * should match any file in the current directory
287          * on the C: drive. However, the glob code does not treat the
288          * colon specially, so it looks for files beginning "C:" in
289          * the current directory. To fix this, change the pattern to
290          * add an explicit "./" at the start (just after the drive
291          * letter and colon - ie change to "C:./").
292          */
293         if (isalpha(pattern[0]) && pattern[1] == ':' &&
294             pattern[2] != BG_SEP && pattern[2] != BG_SEP2 &&
295             bufend - bufnext > 4) {
296                 *bufnext++ = pattern[0];
297                 *bufnext++ = ':';
298                 *bufnext++ = '.';
299                 *bufnext++ = BG_SEP;
300                 patnext += 2;
301         }
302 #endif
303
304 #ifdef MACOS_TRADITIONAL
305         /* Check if we need to match a volume name (e.g. '*HD:*') */
306         g_matchVol = false;
307         p = (char *) pattern;
308         if (*p != BG_SEP) {
309             p++;
310             while (*p != BG_EOS) {
311                 if (*p == BG_SEP) {
312                     g_matchVol = true;
313                     break;
314                 }
315                 p++;
316             }
317         }
318
319         /* Transform the pattern:
320          * (a) Resolve updirs, e.g.
321          *     '*:t*p::'       -> '*:'
322          *         ':a*:tmp::::'   -> '::'
323          *         ':base::t*p:::' -> '::'
324          *     '*HD::'         -> return 0 (error, quit silently)
325          *
326          * (b) Remove a single trailing ':', unless it's a "match volume only"
327          *     pattern like '*HD:'; e.g.
328          *     '*:tmp:' -> '*:tmp'  but
329          *     '*HD:'   -> '*HD:'
330          *     (If we don't do that, even filenames will have a trailing ':' in
331          *     the result.)
332          */
333
334         /* We operate on a copy of the pattern */
335         len = strlen(pattern);
336         Newx(new_pat, len + 1, char);
337         if (new_pat == NULL)
338             return (GLOB_NOSPACE);
339
340         p = (char *) pattern;
341         np = new_pat;
342         while (*np++ = *p++) ;
343
344         /* Resolve updirs ... */
345         err = resolve_updirs(new_pat);
346         if (err) {
347             Safefree(new_pat);
348             /* The pattern is incorrect: tried to move
349                up above the volume root, see above.
350                We quit silently. */
351             return 0;
352         }
353         /* remove trailing colon ... */
354         remove_trColon(new_pat);
355         patnext = (U8 *) new_pat;
356
357 #endif /* MACOS_TRADITIONAL */
358
359         if (flags & GLOB_QUOTE) {
360                 /* Protect the quoted characters. */
361                 while (bufnext < bufend && (c = *patnext++) != BG_EOS)
362                         if (c == BG_QUOTE) {
363 #ifdef DOSISH
364                                     /* To avoid backslashitis on Win32,
365                                      * we only treat \ as a quoting character
366                                      * if it precedes one of the
367                                      * metacharacters []-{}~\
368                                      */
369                                 if ((c = *patnext++) != '[' && c != ']' &&
370                                     c != '-' && c != '{' && c != '}' &&
371                                     c != '~' && c != '\\') {
372 #else
373                                 if ((c = *patnext++) == BG_EOS) {
374 #endif
375                                         c = BG_QUOTE;
376                                         --patnext;
377                                 }
378                                 *bufnext++ = c | M_PROTECT;
379                         } else
380                                 *bufnext++ = c;
381         } else
382                 while (bufnext < bufend && (c = *patnext++) != BG_EOS)
383                         *bufnext++ = c;
384         *bufnext = BG_EOS;
385
386 #ifdef MACOS_TRADITIONAL
387         if (flags & GLOB_BRACE)
388             err = globexp1(patbuf, pglob);
389         else
390             err = glob0(patbuf, pglob);
391         Safefree(new_pat);
392         return err;
393 #else
394         if (flags & GLOB_BRACE)
395             return globexp1(patbuf, pglob);
396         else
397             return glob0(patbuf, pglob);
398 #endif
399 }
400
401 /*
402  * Expand recursively a glob {} pattern. When there is no more expansion
403  * invoke the standard globbing routine to glob the rest of the magic
404  * characters
405  */
406 static int
407 globexp1(const Char *pattern, glob_t *pglob)
408 {
409         const Char* ptr = pattern;
410         int rv;
411
412         /* Protect a single {}, for find(1), like csh */
413         if (pattern[0] == BG_LBRACE && pattern[1] == BG_RBRACE && pattern[2] == BG_EOS)
414                 return glob0(pattern, pglob);
415
416         while ((ptr = (const Char *) g_strchr((Char *) ptr, BG_LBRACE)) != NULL)
417                 if (!globexp2(ptr, pattern, pglob, &rv))
418                         return rv;
419
420         return glob0(pattern, pglob);
421 }
422
423
424 /*
425  * Recursive brace globbing helper. Tries to expand a single brace.
426  * If it succeeds then it invokes globexp1 with the new pattern.
427  * If it fails then it tries to glob the rest of the pattern and returns.
428  */
429 static int
430 globexp2(const Char *ptr, const Char *pattern,
431          glob_t *pglob, int *rv)
432 {
433         int     i;
434         Char   *lm, *ls;
435         const Char *pe, *pm, *pm1, *pl;
436         Char    patbuf[MAXPATHLEN];
437
438         /* copy part up to the brace */
439         for (lm = patbuf, pm = pattern; pm != ptr; *lm++ = *pm++)
440                 ;
441         *lm = BG_EOS;
442         ls = lm;
443
444         /* Find the balanced brace */
445         for (i = 0, pe = ++ptr; *pe; pe++)
446                 if (*pe == BG_LBRACKET) {
447                         /* Ignore everything between [] */
448                         for (pm = pe++; *pe != BG_RBRACKET && *pe != BG_EOS; pe++)
449                                 ;
450                         if (*pe == BG_EOS) {
451                                 /*
452                                  * We could not find a matching BG_RBRACKET.
453                                  * Ignore and just look for BG_RBRACE
454                                  */
455                                 pe = pm;
456                         }
457                 } else if (*pe == BG_LBRACE)
458                         i++;
459                 else if (*pe == BG_RBRACE) {
460                         if (i == 0)
461                                 break;
462                         i--;
463                 }
464
465         /* Non matching braces; just glob the pattern */
466         if (i != 0 || *pe == BG_EOS) {
467                 *rv = glob0(patbuf, pglob);
468                 return 0;
469         }
470
471         for (i = 0, pl = pm = ptr; pm <= pe; pm++) {
472                 switch (*pm) {
473                 case BG_LBRACKET:
474                         /* Ignore everything between [] */
475                         for (pm1 = pm++; *pm != BG_RBRACKET && *pm != BG_EOS; pm++)
476                                 ;
477                         if (*pm == BG_EOS) {
478                                 /*
479                                  * We could not find a matching BG_RBRACKET.
480                                  * Ignore and just look for BG_RBRACE
481                                  */
482                                 pm = pm1;
483                         }
484                         break;
485
486                 case BG_LBRACE:
487                         i++;
488                         break;
489
490                 case BG_RBRACE:
491                         if (i) {
492                                 i--;
493                                 break;
494                         }
495                         /* FALLTHROUGH */
496                 case BG_COMMA:
497                         if (i && *pm == BG_COMMA)
498                                 break;
499                         else {
500                                 /* Append the current string */
501                                 for (lm = ls; (pl < pm); *lm++ = *pl++)
502                                         ;
503
504                                 /*
505                                  * Append the rest of the pattern after the
506                                  * closing brace
507                                  */
508                                 for (pl = pe + 1; (*lm++ = *pl++) != BG_EOS; )
509                                         ;
510
511                                 /* Expand the current pattern */
512 #ifdef GLOB_DEBUG
513                                 qprintf("globexp2:", patbuf);
514 #endif /* GLOB_DEBUG */
515                                 *rv = globexp1(patbuf, pglob);
516
517                                 /* move after the comma, to the next string */
518                                 pl = pm + 1;
519                         }
520                         break;
521
522                 default:
523                         break;
524                 }
525         }
526         *rv = 0;
527         return 0;
528 }
529
530
531
532 /*
533  * expand tilde from the passwd file.
534  */
535 static const Char *
536 globtilde(const Char *pattern, Char *patbuf, size_t patbuf_len, glob_t *pglob)
537 {
538         char *h;
539         const Char *p;
540         Char *b, *eb;
541
542         if (*pattern != BG_TILDE || !(pglob->gl_flags & GLOB_TILDE))
543                 return pattern;
544
545         /* Copy up to the end of the string or / */
546         eb = &patbuf[patbuf_len - 1];
547         for (p = pattern + 1, h = (char *) patbuf;
548              h < (char*)eb && *p && *p != BG_SLASH; *h++ = (char)*p++)
549                 ;
550
551         *h = BG_EOS;
552
553 #if 0
554         if (h == (char *)eb)
555                 return what;
556 #endif
557
558         if (((char *) patbuf)[0] == BG_EOS) {
559                 /*
560                  * handle a plain ~ or ~/ by expanding $HOME
561                  * first and then trying the password file
562                  */
563                 if ((h = getenv("HOME")) == NULL) {
564 #ifdef HAS_PASSWD
565                         struct passwd *pwd;
566                         if ((pwd = getpwuid(getuid())) == NULL)
567                                 return pattern;
568                         else
569                                 h = pwd->pw_dir;
570 #else
571                         return pattern;
572 #endif
573                 }
574         } else {
575                 /*
576                  * Expand a ~user
577                  */
578 #ifdef HAS_PASSWD
579                 struct passwd *pwd;
580                 if ((pwd = getpwnam((char*) patbuf)) == NULL)
581                         return pattern;
582                 else
583                         h = pwd->pw_dir;
584 #else
585                 return pattern;
586 #endif
587         }
588
589         /* Copy the home directory */
590         for (b = patbuf; b < eb && *h; *b++ = *h++)
591                 ;
592
593         /* Append the rest of the pattern */
594         while (b < eb && (*b++ = *p++) != BG_EOS)
595                 ;
596         *b = BG_EOS;
597
598         return patbuf;
599 }
600
601
602 /*
603  * The main glob() routine: compiles the pattern (optionally processing
604  * quotes), calls glob1() to do the real pattern matching, and finally
605  * sorts the list (unless unsorted operation is requested).  Returns 0
606  * if things went well, nonzero if errors occurred.  It is not an error
607  * to find no matches.
608  */
609 static int
610 glob0(const Char *pattern, glob_t *pglob)
611 {
612         const Char *qpat, *qpatnext;
613         int c, err, oldflags, oldpathc;
614         Char *bufnext, patbuf[MAXPATHLEN];
615         size_t limit = 0;
616
617 #ifdef MACOS_TRADITIONAL
618         if ( (*pattern == BG_TILDE) && (pglob->gl_flags & GLOB_TILDE) ) {
619                 return(globextend(pattern, pglob, &limit));
620         }
621 #endif
622
623         qpat = globtilde(pattern, patbuf, MAXPATHLEN, pglob);
624         qpatnext = qpat;
625         oldflags = pglob->gl_flags;
626         oldpathc = pglob->gl_pathc;
627         bufnext = patbuf;
628
629         /* We don't need to check for buffer overflow any more. */
630         while ((c = *qpatnext++) != BG_EOS) {
631                 switch (c) {
632                 case BG_LBRACKET:
633                         c = *qpatnext;
634                         if (c == BG_NOT)
635                                 ++qpatnext;
636                         if (*qpatnext == BG_EOS ||
637                             g_strchr((Char *) qpatnext+1, BG_RBRACKET) == NULL) {
638                                 *bufnext++ = BG_LBRACKET;
639                                 if (c == BG_NOT)
640                                         --qpatnext;
641                                 break;
642                         }
643                         *bufnext++ = M_SET;
644                         if (c == BG_NOT)
645                                 *bufnext++ = M_NOT;
646                         c = *qpatnext++;
647                         do {
648                                 *bufnext++ = CHAR(c);
649                                 if (*qpatnext == BG_RANGE &&
650                                     (c = qpatnext[1]) != BG_RBRACKET) {
651                                         *bufnext++ = M_RNG;
652                                         *bufnext++ = CHAR(c);
653                                         qpatnext += 2;
654                                 }
655                         } while ((c = *qpatnext++) != BG_RBRACKET);
656                         pglob->gl_flags |= GLOB_MAGCHAR;
657                         *bufnext++ = M_END;
658                         break;
659                 case BG_QUESTION:
660                         pglob->gl_flags |= GLOB_MAGCHAR;
661                         *bufnext++ = M_ONE;
662                         break;
663                 case BG_STAR:
664                         pglob->gl_flags |= GLOB_MAGCHAR;
665                         /* collapse adjacent stars to one,
666                          * to avoid exponential behavior
667                          */
668                         if (bufnext == patbuf || bufnext[-1] != M_ALL)
669                                 *bufnext++ = M_ALL;
670                         break;
671                 default:
672                         *bufnext++ = CHAR(c);
673                         break;
674                 }
675         }
676         *bufnext = BG_EOS;
677 #ifdef GLOB_DEBUG
678         qprintf("glob0:", patbuf);
679 #endif /* GLOB_DEBUG */
680
681         if ((err = glob1(patbuf, patbuf+MAXPATHLEN-1, pglob, &limit)) != 0) {
682                 pglob->gl_flags = oldflags;
683                 return(err);
684         }
685
686         /*
687          * If there was no match we are going to append the pattern
688          * if GLOB_NOCHECK was specified or if GLOB_NOMAGIC was specified
689          * and the pattern did not contain any magic characters
690          * GLOB_NOMAGIC is there just for compatibility with csh.
691          */
692         if (pglob->gl_pathc == oldpathc &&
693             ((pglob->gl_flags & GLOB_NOCHECK) ||
694               ((pglob->gl_flags & GLOB_NOMAGIC) &&
695                !(pglob->gl_flags & GLOB_MAGCHAR))))
696         {
697 #ifdef GLOB_DEBUG
698                 printf("calling globextend from glob0\n");
699 #endif /* GLOB_DEBUG */
700                 pglob->gl_flags = oldflags;
701                 return(globextend(qpat, pglob, &limit));
702         }
703         else if (!(pglob->gl_flags & GLOB_NOSORT))
704                 qsort(pglob->gl_pathv + pglob->gl_offs + oldpathc,
705                     pglob->gl_pathc - oldpathc, sizeof(char *),
706                     (pglob->gl_flags & (GLOB_ALPHASORT|GLOB_NOCASE))
707                         ? ci_compare : compare);
708         pglob->gl_flags = oldflags;
709         return(0);
710 }
711
712 static int
713 ci_compare(const void *p, const void *q)
714 {
715         const char *pp = *(const char **)p;
716         const char *qq = *(const char **)q;
717         int ci;
718         while (*pp && *qq) {
719                 if (toLOWER(*pp) != toLOWER(*qq))
720                         break;
721                 ++pp;
722                 ++qq;
723         }
724         ci = toLOWER(*pp) - toLOWER(*qq);
725         if (ci == 0)
726                 return compare(p, q);
727         return ci;
728 }
729
730 static int
731 compare(const void *p, const void *q)
732 {
733         return(strcmp(*(char **)p, *(char **)q));
734 }
735
736 static int
737 glob1(Char *pattern, Char *pattern_last, glob_t *pglob, size_t *limitp)
738 {
739         Char pathbuf[MAXPATHLEN];
740
741         /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
742         if (*pattern == BG_EOS)
743                 return(0);
744         return(glob2(pathbuf, pathbuf+MAXPATHLEN-1,
745                      pathbuf, pathbuf+MAXPATHLEN-1,
746                      pattern, pattern_last, pglob, limitp));
747 }
748
749 /*
750  * The functions glob2 and glob3 are mutually recursive; there is one level
751  * of recursion for each segment in the pattern that contains one or more
752  * meta characters.
753  */
754 static int
755 glob2(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last,
756       Char *pattern, Char *pattern_last, glob_t *pglob, size_t *limitp)
757 {
758         Stat_t sb;
759         Char *p, *q;
760         int anymeta;
761
762         /*
763          * Loop over pattern segments until end of pattern or until
764          * segment with meta character found.
765          */
766         for (anymeta = 0;;) {
767                 if (*pattern == BG_EOS) {               /* End of pattern? */
768                         *pathend = BG_EOS;
769                         if (g_lstat(pathbuf, &sb, pglob))
770                                 return(0);
771
772                         if (((pglob->gl_flags & GLOB_MARK) &&
773                             pathend[-1] != BG_SEP
774 #ifdef DOSISH
775                             && pathend[-1] != BG_SEP2
776 #endif
777                             ) && (S_ISDIR(sb.st_mode) ||
778                                   (S_ISLNK(sb.st_mode) &&
779                             (g_stat(pathbuf, &sb, pglob) == 0) &&
780                             S_ISDIR(sb.st_mode)))) {
781 #ifdef MACOS_TRADITIONAL
782                                 short err;
783                                 err = glob_mark_Mac(pathbuf, pathend, pathend_last);
784                                 if (err)
785                                         return (err);
786 #else
787                                 if (pathend+1 > pathend_last)
788                                         return (1);
789                                 *pathend++ = BG_SEP;
790                                 *pathend = BG_EOS;
791 #endif
792                         }
793                         ++pglob->gl_matchc;
794 #ifdef GLOB_DEBUG
795                         printf("calling globextend from glob2\n");
796 #endif /* GLOB_DEBUG */
797                         return(globextend(pathbuf, pglob, limitp));
798                 }
799
800                 /* Find end of next segment, copy tentatively to pathend. */
801                 q = pathend;
802                 p = pattern;
803                 while (*p != BG_EOS && *p != BG_SEP
804 #ifdef DOSISH
805                        && *p != BG_SEP2
806 #endif
807                        ) {
808                         if (ismeta(*p))
809                                 anymeta = 1;
810                         if (q+1 > pathend_last)
811                                 return (1);
812                         *q++ = *p++;
813                 }
814
815                 if (!anymeta) {         /* No expansion, do next segment. */
816                         pathend = q;
817                         pattern = p;
818                         while (*pattern == BG_SEP
819 #ifdef DOSISH
820                                || *pattern == BG_SEP2
821 #endif
822                                ) {
823                                 if (pathend+1 > pathend_last)
824                                         return (1);
825                                 *pathend++ = *pattern++;
826                         }
827                 } else
828                         /* Need expansion, recurse. */
829                         return(glob3(pathbuf, pathbuf_last, pathend,
830                                      pathend_last, pattern, pattern_last,
831                                      p, pattern_last, pglob, limitp));
832         }
833         /* NOTREACHED */
834 }
835
836 static int
837 glob3(Char *pathbuf, Char *pathbuf_last, Char *pathend, Char *pathend_last,
838       Char *pattern, Char *pattern_last,
839       Char *restpattern, Char *restpattern_last, glob_t *pglob, size_t *limitp)
840 {
841         register Direntry_t *dp;
842         DIR *dirp;
843         int err;
844         int nocase;
845         char buf[MAXPATHLEN];
846
847         /*
848          * The readdirfunc declaration can't be prototyped, because it is
849          * assigned, below, to two functions which are prototyped in glob.h
850          * and dirent.h as taking pointers to differently typed opaque
851          * structures.
852          */
853         Direntry_t *(*readdirfunc)(DIR*);
854
855         if (pathend > pathend_last)
856                 return (1);
857         *pathend = BG_EOS;
858         errno = 0;
859
860 #ifdef VMS
861         {
862                 Char *q = pathend;
863                 if (q - pathbuf > 5) {
864                         q -= 5;
865                         if (q[0] == '.' &&
866                             tolower(q[1]) == 'd' && tolower(q[2]) == 'i' &&
867                             tolower(q[3]) == 'r' && q[4] == '/')
868                         {
869                                 q[0] = '/';
870                                 q[1] = BG_EOS;
871                                 pathend = q+1;
872                         }
873                 }
874         }
875 #endif
876
877 #ifdef MACOS_TRADITIONAL
878         if ((!*pathbuf) && (g_matchVol)) {
879             FSSpec spec;
880             short index;
881             StrFileName vol_name; /* unsigned char[64] on MacOS */
882
883             err = 0;
884             nocase = ((pglob->gl_flags & GLOB_NOCASE) != 0);
885
886             /* Get and match a list of volume names */
887             for (index = 0; !GetVolInfo(index+1, true, &spec); ++index) {
888                 register U8 *sc;
889                 register Char *dc;
890
891                 name_f_FSSpec(vol_name, &spec);
892
893                 /* Initial BG_DOT must be matched literally. */
894                 if (*vol_name == BG_DOT && *pattern != BG_DOT)
895                     continue;
896                 dc = pathend;
897                 sc = (U8 *) vol_name;
898                 while (dc < pathend_last && (*dc++ = *sc++) != BG_EOS)
899                     ;
900                 if (dc >= pathend_last) {
901                     *dc = BG_EOS;
902                     err = 1;
903                     break;
904                 }
905
906                 if (!match(pathend, pattern, restpattern, nocase)) {
907                     *pathend = BG_EOS;
908                     continue;
909                 }
910                 err = glob2(pathbuf, pathbuf_last, --dc, pathend_last,
911                     restpattern, restpattern_last, pglob, limitp);
912                 if (err)
913                     break;
914             }
915             return(err);
916
917         } else { /* open dir */
918 #endif /* MACOS_TRADITIONAL */
919
920         if ((dirp = g_opendir(pathbuf, pglob)) == NULL) {
921                 /* TODO: don't call for ENOENT or ENOTDIR? */
922                 if (pglob->gl_errfunc) {
923                         if (g_Ctoc(pathbuf, buf, sizeof(buf)))
924                                 return (GLOB_ABEND);
925                         if (pglob->gl_errfunc(buf, errno) ||
926                             (pglob->gl_flags & GLOB_ERR))
927                                 return (GLOB_ABEND);
928                 }
929                 return(0);
930         }
931
932         err = 0;
933         nocase = ((pglob->gl_flags & GLOB_NOCASE) != 0);
934
935         /* Search directory for matching names. */
936         if (pglob->gl_flags & GLOB_ALTDIRFUNC)
937                 readdirfunc = (Direntry_t *(*)(DIR *))pglob->gl_readdir;
938         else
939                 readdirfunc = (Direntry_t *(*)(DIR *))my_readdir;
940         while ((dp = (*readdirfunc)(dirp))) {
941                 register U8 *sc;
942                 register Char *dc;
943
944                 /* Initial BG_DOT must be matched literally. */
945                 if (dp->d_name[0] == BG_DOT && *pattern != BG_DOT)
946                         continue;
947                 dc = pathend;
948                 sc = (U8 *) dp->d_name;
949                 while (dc < pathend_last && (*dc++ = *sc++) != BG_EOS)
950                         ;
951                 if (dc >= pathend_last) {
952                         *dc = BG_EOS;
953                         err = 1;
954                         break;
955                 }
956
957                 if (!match(pathend, pattern, restpattern, nocase)) {
958                         *pathend = BG_EOS;
959                         continue;
960                 }
961                 err = glob2(pathbuf, pathbuf_last, --dc, pathend_last,
962                             restpattern, restpattern_last, pglob, limitp);
963                 if (err)
964                         break;
965         }
966
967         if (pglob->gl_flags & GLOB_ALTDIRFUNC)
968                 (*pglob->gl_closedir)(dirp);
969         else
970                 PerlDir_close(dirp);
971         return(err);
972
973 #ifdef MACOS_TRADITIONAL
974         }
975 #endif
976 }
977
978
979 /*
980  * Extend the gl_pathv member of a glob_t structure to accomodate a new item,
981  * add the new item, and update gl_pathc.
982  *
983  * This assumes the BSD realloc, which only copies the block when its size
984  * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
985  * behavior.
986  *
987  * Return 0 if new item added, error code if memory couldn't be allocated.
988  *
989  * Invariant of the glob_t structure:
990  *      Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
991  *      gl_pathv points to (gl_offs + gl_pathc + 1) items.
992  */
993 static int
994 globextend(const Char *path, glob_t *pglob, size_t *limitp)
995 {
996         register char **pathv;
997         register int i;
998         STRLEN newsize, len;
999         char *copy;
1000         const Char *p;
1001
1002 #ifdef GLOB_DEBUG
1003         printf("Adding ");
1004         for (p = path; *p; p++)
1005                 (void)printf("%c", CHAR(*p));
1006         printf("\n");
1007 #endif /* GLOB_DEBUG */
1008
1009         newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);
1010         if (pglob->gl_pathv)
1011                 pathv = Renew(pglob->gl_pathv,newsize,char*);
1012         else
1013                 Newx(pathv,newsize,char*);
1014         if (pathv == NULL) {
1015                 if (pglob->gl_pathv) {
1016                         Safefree(pglob->gl_pathv);
1017                         pglob->gl_pathv = NULL;
1018                 }
1019                 return(GLOB_NOSPACE);
1020         }
1021
1022         if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
1023                 /* first time around -- clear initial gl_offs items */
1024                 pathv += pglob->gl_offs;
1025                 for (i = pglob->gl_offs; --i >= 0; )
1026                         *--pathv = NULL;
1027         }
1028         pglob->gl_pathv = pathv;
1029
1030         for (p = path; *p++;)
1031                 ;
1032         len = (STRLEN)(p - path);
1033         *limitp += len;
1034         Newx(copy, p-path, char);
1035         if (copy != NULL) {
1036                 if (g_Ctoc(path, copy, len)) {
1037                         Safefree(copy);
1038                         return(GLOB_NOSPACE);
1039                 }
1040                 pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
1041         }
1042         pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
1043
1044         if ((pglob->gl_flags & GLOB_LIMIT) &&
1045             newsize + *limitp >= ARG_MAX) {
1046                 errno = 0;
1047                 return(GLOB_NOSPACE);
1048         }
1049
1050         return(copy == NULL ? GLOB_NOSPACE : 0);
1051 }
1052
1053
1054 /*
1055  * pattern matching function for filenames.  Each occurrence of the *
1056  * pattern causes a recursion level.
1057  */
1058 static int
1059 match(register Char *name, register Char *pat, register Char *patend, int nocase)
1060 {
1061         int ok, negate_range;
1062         Char c, k;
1063
1064         while (pat < patend) {
1065                 c = *pat++;
1066                 switch (c & M_MASK) {
1067                 case M_ALL:
1068                         if (pat == patend)
1069                                 return(1);
1070                         do
1071                             if (match(name, pat, patend, nocase))
1072                                     return(1);
1073                         while (*name++ != BG_EOS)
1074                                 ;
1075                         return(0);
1076                 case M_ONE:
1077                         if (*name++ == BG_EOS)
1078                                 return(0);
1079                         break;
1080                 case M_SET:
1081                         ok = 0;
1082                         if ((k = *name++) == BG_EOS)
1083                                 return(0);
1084                         if ((negate_range = ((*pat & M_MASK) == M_NOT)) != BG_EOS)
1085                                 ++pat;
1086                         while (((c = *pat++) & M_MASK) != M_END)
1087                                 if ((*pat & M_MASK) == M_RNG) {
1088                                         if (nocase) {
1089                                                 if (tolower(c) <= tolower(k) && tolower(k) <= tolower(pat[1]))
1090                                                         ok = 1;
1091                                         } else {
1092                                                 if (c <= k && k <= pat[1])
1093                                                         ok = 1;
1094                                         }
1095                                         pat += 2;
1096                                 } else if (nocase ? (tolower(c) == tolower(k)) : (c == k))
1097                                         ok = 1;
1098                         if (ok == negate_range)
1099                                 return(0);
1100                         break;
1101                 default:
1102                         k = *name++;
1103                         if (nocase ? (tolower(k) != tolower(c)) : (k != c))
1104                                 return(0);
1105                         break;
1106                 }
1107         }
1108         return(*name == BG_EOS);
1109 }
1110
1111 /* Free allocated data belonging to a glob_t structure. */
1112 void
1113 bsd_globfree(glob_t *pglob)
1114 {
1115         register int i;
1116         register char **pp;
1117
1118         if (pglob->gl_pathv != NULL) {
1119                 pp = pglob->gl_pathv + pglob->gl_offs;
1120                 for (i = pglob->gl_pathc; i--; ++pp)
1121                         if (*pp)
1122                                 Safefree(*pp);
1123                 Safefree(pglob->gl_pathv);
1124                 pglob->gl_pathv = NULL;
1125         }
1126 }
1127
1128 static DIR *
1129 g_opendir(register Char *str, glob_t *pglob)
1130 {
1131         char buf[MAXPATHLEN];
1132
1133         if (!*str) {
1134 #ifdef MACOS_TRADITIONAL
1135                 my_strlcpy(buf, ":", sizeof(buf));
1136 #else
1137                 my_strlcpy(buf, ".", sizeof(buf));
1138 #endif
1139         } else {
1140                 if (g_Ctoc(str, buf, sizeof(buf)))
1141                         return(NULL);
1142         }
1143
1144         if (pglob->gl_flags & GLOB_ALTDIRFUNC)
1145                 return((DIR*)(*pglob->gl_opendir)(buf));
1146
1147         return(PerlDir_open(buf));
1148 }
1149
1150 static int
1151 g_lstat(register Char *fn, Stat_t *sb, glob_t *pglob)
1152 {
1153         char buf[MAXPATHLEN];
1154
1155         if (g_Ctoc(fn, buf, sizeof(buf)))
1156                 return(-1);
1157         if (pglob->gl_flags & GLOB_ALTDIRFUNC)
1158                 return((*pglob->gl_lstat)(buf, sb));
1159 #ifdef HAS_LSTAT
1160         return(PerlLIO_lstat(buf, sb));
1161 #else
1162         return(PerlLIO_stat(buf, sb));
1163 #endif /* HAS_LSTAT */
1164 }
1165
1166 static int
1167 g_stat(register Char *fn, Stat_t *sb, glob_t *pglob)
1168 {
1169         char buf[MAXPATHLEN];
1170
1171         if (g_Ctoc(fn, buf, sizeof(buf)))
1172                 return(-1);
1173         if (pglob->gl_flags & GLOB_ALTDIRFUNC)
1174                 return((*pglob->gl_stat)(buf, sb));
1175         return(PerlLIO_stat(buf, sb));
1176 }
1177
1178 static Char *
1179 g_strchr(Char *str, int ch)
1180 {
1181         do {
1182                 if (*str == ch)
1183                         return (str);
1184         } while (*str++);
1185         return (NULL);
1186 }
1187
1188 static int
1189 g_Ctoc(register const Char *str, char *buf, STRLEN len)
1190 {
1191         while (len--) {
1192                 if ((*buf++ = (char)*str++) == BG_EOS)
1193                         return (0);
1194         }
1195         return (1);
1196 }
1197
1198 #ifdef GLOB_DEBUG
1199 static void
1200 qprintf(const char *str, register Char *s)
1201 {
1202         register Char *p;
1203
1204         (void)printf("%s:\n", str);
1205         for (p = s; *p; p++)
1206                 (void)printf("%c", CHAR(*p));
1207         (void)printf("\n");
1208         for (p = s; *p; p++)
1209                 (void)printf("%c", *p & M_PROTECT ? '"' : ' ');
1210         (void)printf("\n");
1211         for (p = s; *p; p++)
1212                 (void)printf("%c", ismeta(*p) ? '_' : ' ');
1213         (void)printf("\n");
1214 }
1215 #endif /* GLOB_DEBUG */
1216
1217
1218 #ifdef MACOS_TRADITIONAL
1219
1220 /* Replace the last occurrence of the pattern ":[^:]+::", e.g. ":lib::",
1221    with a single ':', if possible. It is not an error, if the pattern
1222    doesn't match (we return -1), but if there are two consecutive colons
1223    '::', there must be a preceding ':[^:]+'. Hence,  a volume path like
1224    "HD::" is considered to be an error (we return 1), that is, it can't
1225    be resolved. We return 0 on success.
1226 */
1227
1228 static short
1229 updir(char *path)
1230 {
1231         char *pb, *pe, *lastchar;
1232         char *bgn_mark, *end_mark;
1233         char *f, *m, *b; /* front, middle, back */
1234         size_t len;
1235
1236         len = strlen(path);
1237         lastchar = path + (len-1);
1238         b = lastchar;
1239         m = lastchar-1;
1240         f = lastchar-2;
1241
1242         /* find a '[^:]::' (e.g. b::) pattern ... */
1243         while ( !( (*f != BG_SEP) && (*m == BG_SEP) && (*b == BG_SEP) )
1244                 && (f >= path)) {
1245                 f--;
1246                 m--;
1247                 b--;
1248         }
1249
1250         if (f < path) { /* no (more) match */
1251                 return -1;
1252         }
1253
1254         end_mark = b;
1255
1256         /* ... and now find its preceding colon ':' */
1257         while ((*f != BG_SEP) && (f >= path)) {
1258                 f--;
1259         }
1260         if (f < path) {
1261                 /* No preceding colon found, must be a
1262                    volume path. We can't move up the
1263                    tree and that's an error */
1264                 return 1;
1265         }
1266         bgn_mark = f;
1267
1268         /* Shrink path, i.e. exclude all characters between
1269            bgn_mark and end_mark */
1270
1271         pb = bgn_mark;
1272         pe = end_mark;
1273         while (*pb++ = *pe++) ;
1274         return 0;
1275 }
1276
1277
1278 /* Resolve all updirs in pattern. */
1279
1280 static short
1281 resolve_updirs(char *new_pattern)
1282 {
1283         short err;
1284
1285         do {
1286                 err = updir(new_pattern);
1287         } while (!err);
1288         if (err == 1) {
1289                 return NO_UPDIR_ERR;
1290         }
1291         return 0;
1292 }
1293
1294
1295 /* Remove a trailing colon from the path, but only if it's
1296    not a volume path (e.g. HD:) and not a path consisting
1297    solely of colons. */
1298
1299 static void
1300 remove_trColon(char *path)
1301 {
1302         char *lastchar, *lc;
1303
1304         /* if path matches the pattern /:[^:]+:$/, we can
1305            remove the trailing ':' */
1306
1307         lc = lastchar = path + (strlen(path) - 1);
1308         if (*lastchar == BG_SEP) {
1309                 /* there's a trailing ':', there must be at least
1310                    one preceding char != ':' and a preceding ':' */
1311                 lc--;
1312                 if ((*lc != BG_SEP) && (lc >= path)) {
1313                         lc--;
1314                 } else {
1315                         return;
1316                 }
1317                 while ((*lc != BG_SEP) && (lc >= path)) {
1318                         lc--;
1319                 }
1320                 if (lc >= path) {
1321                         /* ... there's a preceding ':', we remove
1322                            the trailing colon */
1323                         *lastchar = BG_EOS;
1324                 }
1325         }
1326 }
1327
1328
1329 /* With the GLOB_MARK flag on, we append a colon, if pathbuf
1330    is a directory. If the directory name contains no colons,
1331    e.g. 'lib', we can't simply append a ':', since this (e.g.
1332    'lib:') is not a valid (relative) path on Mac OS. Instead,
1333    we add a leading _and_ trailing ':'. */
1334
1335 static short
1336 glob_mark_Mac(Char *pathbuf, Char *pathend, Char *pathend_last)
1337 {
1338         Char *p, *pe;
1339         Boolean is_file = true;
1340
1341         /* check if pathbuf contains a ':',
1342            i.e. is not a file name */
1343         p = pathbuf;
1344         while (*p != BG_EOS) {
1345                 if (*p == BG_SEP) {
1346                         is_file = false;
1347                         break;
1348                 }
1349                 p++;
1350         }
1351
1352         if (is_file) {
1353                 if (pathend+2 > pathend_last) {
1354                         return (1);
1355                 }
1356                 /* right shift one char */
1357                 pe = p = pathend;
1358                 p--;
1359                 pathend++;
1360                 while (p >= pathbuf) {
1361                         *pe-- = *p--;
1362                 }
1363                 /* first char becomes a colon */
1364                 *pathbuf = BG_SEP;
1365                 /* append a colon */
1366                 *pathend++ = BG_SEP;
1367                 *pathend = BG_EOS;
1368
1369         } else {
1370                 if (pathend+1 > pathend_last) {
1371                         return (1);
1372                 }
1373                 *pathend++ = BG_SEP;
1374                 *pathend = BG_EOS;
1375         }
1376         return 0;
1377 }
1378
1379
1380 /* Return a FSSpec record for the specified volume
1381    (borrowed from MacPerl.xs). */
1382
1383 static OSErr
1384 GetVolInfo(short volume, Boolean indexed, FSSpec* spec)
1385 {
1386         OSErr           err; /* OSErr: 16-bit integer */
1387         HParamBlockRec  pb;
1388
1389         pb.volumeParam.ioNamePtr        = spec->name;
1390         pb.volumeParam.ioVRefNum        = indexed ? 0 : volume;
1391         pb.volumeParam.ioVolIndex       = indexed ? volume : 0;
1392
1393         if (err = PBHGetVInfoSync(&pb))
1394                 return err;
1395
1396         spec->vRefNum   = pb.volumeParam.ioVRefNum;
1397         spec->parID     = 1;
1398
1399         return noErr; /* 0 */
1400 }
1401
1402 /* Extract a C name from a FSSpec. Note that there are
1403    no leading or trailing colons. */
1404
1405 static void
1406 name_f_FSSpec(StrFileName name, FSSpec *spec)
1407 {
1408         unsigned char *nc;
1409         const short len = spec->name[0];
1410         short i;
1411
1412         /* FSSpec.name is a Pascal string,
1413            convert it to C ... */
1414         nc = name;
1415         for (i=1; i<=len; i++) {
1416                 *nc++ = spec->name[i];
1417         }
1418         *nc = BG_EOS;
1419 }
1420
1421 #endif /* MACOS_TRADITIONAL */