2009-05-20 Miguel de Icaza <miguel@novell.com>
[mono.git] / mono / io-layer / wapi_glob.c
1 /*      $OpenBSD: glob.c,v 1.26 2005/11/28 17:50:12 deraadt Exp $ */
2 /*
3  * Copyright (c) 1989, 1993
4  *      The Regents of the University of California.  All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Guido van Rossum.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33
34 /*
35  * _wapi_glob(3) -- a subset of the one defined in POSIX 1003.2.
36  *
37  * Optional extra services, controlled by flags not defined by POSIX:
38  *
39  * GLOB_MAGCHAR:
40  *      Set in gl_flags if pattern contained a globbing character.
41  */
42 #include <sys/param.h>
43 #include <sys/stat.h>
44
45 #include <glib.h>
46 #include <ctype.h>
47 #include <dirent.h>
48 #include <errno.h>
49 #include <stdio.h>
50 #include <stdlib.h>
51 #include <string.h>
52 #include <unistd.h>
53
54 #include "wapi_glob.h"
55
56 #define EOS             '\0'
57 #define NOT             '!'
58 #define QUESTION        '?'
59 #define QUOTE           '\\'
60 #define STAR            '*'
61
62 #ifndef DEBUG
63
64 #define M_QUOTE         0x8000
65 #define M_PROTECT       0x4000
66 #define M_MASK          0xffff
67 #define M_ASCII         0x00ff
68
69 typedef u_short Char;
70
71 #else
72
73 #define M_QUOTE         0x80
74 #define M_PROTECT       0x40
75 #define M_MASK          0xff
76 #define M_ASCII         0x7f
77
78 typedef char Char;
79
80 #endif
81
82
83 #define CHAR(c)         ((gchar)((c)&M_ASCII))
84 #define META(c)         ((gchar)((c)|M_QUOTE))
85 #define M_ALL           META('*')
86 #define M_ONE           META('?')
87 #define ismeta(c)       (((c)&M_QUOTE) != 0)
88
89
90 static int       g_Ctoc(const gchar *, char *, u_int);
91 static int       glob0(GDir *dir, const gchar *, wapi_glob_t *, gboolean,
92                        gboolean);
93 static int       glob1(GDir *dir, gchar *, gchar *, wapi_glob_t *, size_t *,
94                        gboolean, gboolean);
95 static int       glob3(GDir *dir, gchar *, gchar *, wapi_glob_t *, size_t *,
96                        gboolean, gboolean);
97 static int       globextend(const gchar *, wapi_glob_t *, size_t *);
98 static int       match(const gchar *, gchar *, gchar *, gboolean);
99 #ifdef DEBUG
100 static void      qprintf(const char *, Char *);
101 #endif
102
103 int
104 _wapi_glob(GDir *dir, const char *pattern, int flags, wapi_glob_t *pglob)
105 {
106         const u_char *patnext;
107         int c;
108         gchar *bufnext, *bufend, patbuf[PATH_MAX];
109
110         patnext = (u_char *) pattern;
111         if (!(flags & WAPI_GLOB_APPEND)) {
112                 pglob->gl_pathc = 0;
113                 pglob->gl_pathv = NULL;
114                 pglob->gl_offs = 0;
115         }
116         pglob->gl_flags = flags & ~WAPI_GLOB_MAGCHAR;
117
118         bufnext = patbuf;
119         bufend = bufnext + PATH_MAX - 1;
120
121         /* Protect the quoted characters. */
122         while (bufnext < bufend && (c = *patnext++) != EOS)
123                 if (c == QUOTE) {
124                         if ((c = *patnext++) == EOS) {
125                                 c = QUOTE;
126                                 --patnext;
127                         }
128                         *bufnext++ = c | M_PROTECT;
129                 } else
130                         *bufnext++ = c;
131
132         *bufnext = EOS;
133
134         return glob0(dir, patbuf, pglob, flags & WAPI_GLOB_IGNORECASE,
135                      flags & WAPI_GLOB_UNIQUE);
136 }
137
138 /*
139  * The main glob() routine: compiles the pattern (optionally processing
140  * quotes), calls glob1() to do the real pattern matching, and finally
141  * sorts the list (unless unsorted operation is requested).  Returns 0
142  * if things went well, nonzero if errors occurred.  It is not an error
143  * to find no matches.
144  */
145 static int
146 glob0(GDir *dir, const gchar *pattern, wapi_glob_t *pglob, gboolean ignorecase,
147         gboolean unique)
148 {
149         const gchar *qpatnext;
150         int c, err, oldpathc;
151         gchar *bufnext, patbuf[PATH_MAX];
152         size_t limit = 0;
153
154         qpatnext = pattern;
155         oldpathc = pglob->gl_pathc;
156         bufnext = patbuf;
157
158         /* We don't need to check for buffer overflow any more. */
159         while ((c = *qpatnext++) != EOS) {
160                 switch (c) {
161                 case QUESTION:
162                         pglob->gl_flags |= WAPI_GLOB_MAGCHAR;
163                         *bufnext++ = M_ONE;
164                         break;
165                 case STAR:
166                         pglob->gl_flags |= WAPI_GLOB_MAGCHAR;
167                         /* collapse adjacent stars to one,
168                          * to avoid exponential behavior
169                          */
170                         if (bufnext == patbuf || bufnext[-1] != M_ALL)
171                                 *bufnext++ = M_ALL;
172                         break;
173                 default:
174                         *bufnext++ = CHAR(c);
175                         break;
176                 }
177         }
178         *bufnext = EOS;
179 #ifdef DEBUG
180         qprintf("glob0:", patbuf);
181 #endif
182
183         if ((err = glob1(dir, patbuf, patbuf+PATH_MAX-1, pglob, &limit,
184                          ignorecase, unique)) != 0)
185                 return(err);
186
187         if (pglob->gl_pathc == oldpathc) {
188                 return(WAPI_GLOB_NOMATCH);
189         }
190
191         return(0);
192 }
193
194 static int
195 glob1(GDir *dir, gchar *pattern, gchar *pattern_last, wapi_glob_t *pglob,
196       size_t *limitp, gboolean ignorecase, gboolean unique)
197 {
198         /* A null pathname is invalid -- POSIX 1003.1 sect. 2.4. */
199         if (*pattern == EOS)
200                 return(0);
201         return(glob3(dir, pattern, pattern_last, pglob, limitp, ignorecase,
202                      unique));
203 }
204
205 static gboolean contains (wapi_glob_t *pglob, const gchar *name)
206 {
207         int i;
208         char **pp;
209         
210         if (pglob->gl_pathv != NULL) {
211                 pp = pglob->gl_pathv + pglob->gl_offs;
212                 for (i = pglob->gl_pathc; i--; ++pp) {
213                         if (*pp) {
214                                 if (!strcmp (*pp, name)) {
215                                         return(TRUE);
216                                 }
217                         }
218                 }
219         }
220         
221         return(FALSE);
222 }
223
224 static int
225 glob3(GDir *dir, gchar *pattern, gchar *pattern_last, wapi_glob_t *pglob,
226       size_t *limitp, gboolean ignorecase, gboolean unique)
227 {
228         const gchar *name;
229
230         /* Search directory for matching names. */
231         while ((name = g_dir_read_name(dir))) {
232                 if (!match(name, pattern, pattern + strlen (pattern),
233                            ignorecase)) {
234                         continue;
235                 }
236                 if (!unique ||
237                     !contains (pglob, name)) {
238                         globextend (name, pglob, limitp);
239                 }
240         }
241
242         return(0);
243 }
244
245
246 /*
247  * Extend the gl_pathv member of a wapi_glob_t structure to accommodate a new item,
248  * add the new item, and update gl_pathc.
249  *
250  * This assumes the BSD realloc, which only copies the block when its size
251  * crosses a power-of-two boundary; for v7 realloc, this would cause quadratic
252  * behavior.
253  *
254  * Return 0 if new item added, error code if memory couldn't be allocated.
255  *
256  * Invariant of the wapi_glob_t structure:
257  *      Either gl_pathc is zero and gl_pathv is NULL; or gl_pathc > 0 and
258  *      gl_pathv points to (gl_offs + gl_pathc + 1) items.
259  */
260 static int
261 globextend(const gchar *path, wapi_glob_t *pglob, size_t *limitp)
262 {
263         char **pathv;
264         int i;
265         u_int newsize, len;
266         char *copy;
267         const gchar *p;
268
269         newsize = sizeof(*pathv) * (2 + pglob->gl_pathc + pglob->gl_offs);
270         pathv = pglob->gl_pathv ? realloc((char *)pglob->gl_pathv, newsize) :
271             malloc(newsize);
272         if (pathv == NULL) {
273                 if (pglob->gl_pathv) {
274                         free(pglob->gl_pathv);
275                         pglob->gl_pathv = NULL;
276                 }
277                 return(WAPI_GLOB_NOSPACE);
278         }
279
280         if (pglob->gl_pathv == NULL && pglob->gl_offs > 0) {
281                 /* first time around -- clear initial gl_offs items */
282                 pathv += pglob->gl_offs;
283                 for (i = pglob->gl_offs; --i >= 0; )
284                         *--pathv = NULL;
285         }
286         pglob->gl_pathv = pathv;
287
288         for (p = path; *p++;)
289                 ;
290         len = (size_t)(p - path);
291         *limitp += len;
292         if ((copy = malloc(len)) != NULL) {
293                 if (g_Ctoc(path, copy, len)) {
294                         free(copy);
295                         return(WAPI_GLOB_NOSPACE);
296                 }
297                 pathv[pglob->gl_offs + pglob->gl_pathc++] = copy;
298         }
299         pathv[pglob->gl_offs + pglob->gl_pathc] = NULL;
300
301 #if 0
302         /* Broken on opensuse 11 */
303         if ((pglob->gl_flags & WAPI_GLOB_LIMIT) &&
304             newsize + *limitp >= ARG_MAX) {
305                 errno = 0;
306                 return(WAPI_GLOB_NOSPACE);
307         }
308 #endif
309
310         return(copy == NULL ? WAPI_GLOB_NOSPACE : 0);
311 }
312
313
314 /*
315  * pattern matching function for filenames.  Each occurrence of the *
316  * pattern causes a recursion level.
317  */
318 static int
319 match(const gchar *name, gchar *pat, gchar *patend, gboolean ignorecase)
320 {
321         gchar c;
322
323         while (pat < patend) {
324                 c = *pat++;
325                 switch (c & M_MASK) {
326                 case M_ALL:
327                         if (pat == patend)
328                                 return(1);
329                         do {
330                                 if (match(name, pat, patend, ignorecase))
331                                         return(1);
332                         } while (*name++ != EOS);
333                         return(0);
334                 case M_ONE:
335                         if (*name++ == EOS)
336                                 return(0);
337                         break;
338                 default:
339                         if (ignorecase) {
340                                 if (g_ascii_tolower (*name++) != g_ascii_tolower (c))
341                                         return(0);
342                         } else {
343                                 if (*name++ != c)
344                                         return(0);
345                         }
346                         
347                         break;
348                 }
349         }
350         return(*name == EOS);
351 }
352
353 /* Free allocated data belonging to a wapi_glob_t structure. */
354 void
355 _wapi_globfree(wapi_glob_t *pglob)
356 {
357         int i;
358         char **pp;
359
360         if (pglob->gl_pathv != NULL) {
361                 pp = pglob->gl_pathv + pglob->gl_offs;
362                 for (i = pglob->gl_pathc; i--; ++pp)
363                         if (*pp)
364                                 free(*pp);
365                 free(pglob->gl_pathv);
366                 pglob->gl_pathv = NULL;
367         }
368 }
369
370 static int
371 g_Ctoc(const gchar *str, char *buf, u_int len)
372 {
373
374         while (len--) {
375                 if ((*buf++ = *str++) == EOS)
376                         return (0);
377         }
378         return (1);
379 }
380
381 #ifdef DEBUG
382 static void
383 qprintf(const char *str, Char *s)
384 {
385         Char *p;
386
387         (void)printf("%s:\n", str);
388         for (p = s; *p; p++)
389                 (void)printf("%c", CHAR(*p));
390         (void)printf("\n");
391         for (p = s; *p; p++)
392                 (void)printf("%c", *p & M_PROTECT ? '"' : ' ');
393         (void)printf("\n");
394         for (p = s; *p; p++)
395                 (void)printf("%c", ismeta(*p) ? '_' : ' ');
396         (void)printf("\n");
397 }
398 #endif