OSDN Git Service

Fix no pic
[uclinux-h8/uClinux-dist.git] / user / tinytcl / tclGlob.c
1 /* 
2  * tclGlob.c --
3  *
4  *      This file provides procedures and commands for file name
5  *      manipulation, such as tilde expansion and globbing.
6  *
7  * Copyright 1990-1991 Regents of the University of California
8  * Permission to use, copy, modify, and distribute this
9  * software and its documentation for any purpose and without
10  * fee is hereby granted, provided that the above copyright
11  * notice appear in all copies.  The University of California
12  * makes no representations about the suitability of this
13  * software for any purpose.  It is provided "as is" without
14  * express or implied warranty.
15  *
16  * $Id: tclGlob.c,v 1.1.1.1 2001/04/29 20:34:49 karll Exp $
17  */
18
19 #include "tclInt.h"
20 #include "tclUnix.h"
21
22 /*
23  * The structure below is used to keep track of a globbing result
24  * being built up (i.e. a partial list of file names).  The list
25  * grows dynamically to be as big as needed.
26  */
27
28 typedef struct {
29     char *result;               /* Pointer to result area. */
30     int totalSpace;             /* Total number of characters allocated
31                                  * for result. */
32     int spaceUsed;              /* Number of characters currently in use
33                                  * to hold the partial result (not including
34                                  * the terminating NULL). */
35     int dynamic;                /* 0 means result is static space, 1 means
36                                  * it's dynamic. */
37 } GlobResult;
38
39 /*
40  * Declarations for procedures local to this file:
41  */
42
43 static void             AppendResult _ANSI_ARGS_((Tcl_Interp *interp,
44                             char *dir, char *separator, char *name,
45                             int nameLength));
46 static int              DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
47                             char *rem));
48 \f
49 /*
50  *----------------------------------------------------------------------
51  *
52  * AppendResult --
53  *
54  *      Given two parts of a file name (directory and element within
55  *      directory), concatenate the two together and append them to
56  *      the result building up in interp.
57  *
58  * Results:
59  *      There is no return value.
60  *
61  * Side effects:
62  *      Interp->result gets extended.
63  *
64  *----------------------------------------------------------------------
65  */
66
67 static void
68 AppendResult(interp, dir, separator, name, nameLength)
69     Tcl_Interp *interp;         /* Interpreter whose result should be
70                                  * appended to. */
71     char *dir;                  /* Name of directory, without trailing
72                                  * slash except for root directory. */
73     char *separator;            /* Separator string so use between dir and
74                                  * name:  either "/" or "" depending on dir. */
75     char *name;                 /* Name of file withing directory (NOT
76                                  * necessarily null-terminated!). */
77     int nameLength;             /* Number of characters in name. */
78 {
79     int dirFlags, nameFlags;
80     char *p, saved;
81
82     /*
83      * Next, see if we can put together a valid list element from dir
84      * and name by calling Tcl_AppendResult.
85      */
86
87     if (*dir == 0) {
88         dirFlags = 0;
89     } else {
90         Tcl_ScanElement(dir, &dirFlags);
91     }
92     saved = name[nameLength];
93     name[nameLength] = 0;
94     Tcl_ScanElement(name, &nameFlags);
95     if ((dirFlags == 0) && (nameFlags == 0)) {
96         if (*interp->result != 0) {
97             Tcl_AppendResult(interp, " ", dir, separator, name, (char *) NULL);
98         } else {
99             Tcl_AppendResult(interp, dir, separator, name, (char *) NULL);
100         }
101         name[nameLength] = saved;
102         return;
103     }
104
105     /*
106      * This name has weird characters in it, so we have to convert it to
107      * a list element.  To do that, we have to merge the characters
108      * into a single name.  To do that, malloc a buffer to hold everything.
109      */
110
111     p = (char *) ckalloc((unsigned) (strlen(dir) + strlen(separator)
112             + nameLength + 1));
113     sprintf(p, "%s%s%s", dir, separator, name);
114     name[nameLength] = saved;
115     Tcl_AppendElement(interp, p, 0);
116     ckfree(p);
117 }
118 \f
119 /*
120  *----------------------------------------------------------------------
121  *
122  * DoGlob --
123  *
124  *      This recursive procedure forms the heart of the globbing
125  *      code.  It performs a depth-first traversal of the tree
126  *      given by the path name to be globbed.
127  *
128  * Results:
129  *      The return value is a standard Tcl result indicating whether
130  *      an error occurred in globbing.  After a normal return the
131  *      result in interp will be set to hold all of the file names
132  *      given by the dir and rem arguments.  After an error the
133  *      result in interp will hold an error message.
134  *
135  * Side effects:
136  *      None.
137  *
138  *----------------------------------------------------------------------
139  */
140
141 static int
142 DoGlob(interp, dir, rem)
143     Tcl_Interp *interp;                 /* Interpreter to use for error
144                                          * reporting (e.g. unmatched brace). */
145     char *dir;                          /* Name of a directory at which to
146                                          * start glob expansion.  This name
147                                          * is fixed: it doesn't contain any
148                                          * globbing chars. */
149     char *rem;                          /* Path to glob-expand. */
150 {
151     /*
152      * When this procedure is entered, the name to be globbed may
153      * already have been partly expanded by ancestor invocations of
154      * DoGlob.  The part that's already been expanded is in "dir"
155      * (this may initially be empty), and the part still to expand
156      * is in "rem".  This procedure expands "rem" one level, making
157      * recursive calls to itself if there's still more stuff left
158      * in the remainder.
159      */
160
161     register char *p;
162     register char c;
163     char *openBrace, *closeBrace;
164     int gotSpecial, result;
165     char *separator;
166
167     /*
168      * Figure out whether we'll need to add a slash between the directory
169      * name and file names within the directory when concatenating them
170      * together.
171      */
172
173     if ((dir[0] == 0) || ((dir[0] == '/') && (dir[1] == 0))) {
174         separator = "";
175     } else {
176         separator = "/";
177     }
178
179     /*
180      * When generating information for the next lower call,
181      * use static areas if the name is short, and malloc if the name
182      * is longer.
183      */
184
185 #define STATIC_SIZE 200
186
187     /*
188      * First, find the end of the next element in rem, checking
189      * along the way for special globbing characters.
190      */
191
192     gotSpecial = 0;
193     openBrace = closeBrace = NULL;
194     for (p = rem; ; p++) {
195         c = *p;
196         if ((c == '\0') || (c == '/')) {
197             break;
198         }
199         if ((c == '{') && (openBrace == NULL)) {
200             openBrace = p;
201         }
202         if ((c == '}') && (closeBrace == NULL)) {
203             closeBrace = p;
204         }
205         if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
206             gotSpecial = 1;
207         }
208     }
209
210     /*
211      * If there is an open brace in the argument, then make a recursive
212      * call for each element between the braces.  In this case, the
213      * recursive call to DoGlob uses the same "dir" that we got.
214      * If there are several brace-pairs in a single name, we just handle
215      * one here, and the others will be handled in recursive calls.
216      */
217
218     if (openBrace != NULL) {
219         int remLength, l1, l2;
220         char static1[STATIC_SIZE];
221         char *element, *newRem;
222
223         if (closeBrace == NULL) {
224             Tcl_ResetResult(interp);
225             interp->result = "unmatched open-brace in file name";
226             return TCL_ERROR;
227         }
228         remLength = strlen(rem) + 1;
229         if (remLength <= STATIC_SIZE) {
230             newRem = static1;
231         } else {
232             newRem = (char *) ckalloc((unsigned) remLength);
233         }
234         l1 = openBrace-rem;
235         strncpy(newRem, rem, l1);
236         for (p = openBrace; *p != '}'; ) {
237             element = p+1;
238             for (p = element; ((*p != '}') && (*p != ',')); p++) {
239                 /* Empty loop body:  just find end of this element. */
240             }
241             l2 = p - element;
242             strncpy(newRem+l1, element, l2);
243             strcpy(newRem+l1+l2, closeBrace+1);
244             if (DoGlob(interp, dir, newRem) != TCL_OK) {
245                 return TCL_ERROR;
246             }
247         }
248         if (remLength > STATIC_SIZE) {
249             ckfree(newRem);
250         }
251         return TCL_OK;
252     }
253
254     /*
255      * If there were any pattern-matching characters, then scan through
256      * the directory to find all the matching names.
257      */
258
259     if (gotSpecial) {
260         DIR *d;
261         struct dirent *entryPtr;
262         int l1, l2;
263         char *pattern, *newDir, *dirName;
264         char static1[STATIC_SIZE], static2[STATIC_SIZE];
265         struct stat statBuf;
266
267         /*
268          * Be careful not to do any actual file system operations on a
269          * directory named "";  instead, use ".".  This is needed because
270          * some versions of UNIX don't treat "" like "." automatically.
271          */
272
273         if (*dir == '\0') {
274             dirName = ".";
275         } else {
276             dirName = dir;
277         }
278         if ((stat(dirName, &statBuf) != 0) || !S_ISDIR(statBuf.st_mode)) {
279             return TCL_OK;
280         }
281         d = opendir(dirName);
282         if (d == NULL) {
283             Tcl_ResetResult(interp);
284             Tcl_AppendResult(interp, "couldn't read directory \"",
285                     dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
286             return TCL_ERROR;
287         }
288         l1 = strlen(dir);
289         l2 = (p - rem);
290         if (l2 < STATIC_SIZE) {
291             pattern = static2;
292         } else {
293             pattern = (char *) ckalloc((unsigned) (l2+1));
294         }
295         strncpy(pattern, rem, l2);
296         pattern[l2] = '\0';
297         result = TCL_OK;
298         while (1) {
299             entryPtr = readdir(d);
300             if (entryPtr == NULL) {
301                 break;
302             }
303
304             /*
305              * Don't match names starting with "." unless the "." is
306              * present in the pattern.
307              */
308
309             if ((*entryPtr->d_name == '.') && (*pattern != '.')) {
310                 continue;
311             }
312             if (Tcl_StringMatch(entryPtr->d_name, pattern)) {
313                 int nameLength = strlen(entryPtr->d_name);
314                 if (*p == 0) {
315                     AppendResult(interp, dir, separator, entryPtr->d_name,
316                             nameLength);
317                 } else {
318                     if ((l1+nameLength+2) <= STATIC_SIZE) {
319                         newDir = static1;
320                     } else {
321                         newDir = (char *) ckalloc((unsigned) (l1+nameLength+2));
322                     }
323                     sprintf(newDir, "%s%s%s", dir, separator, entryPtr->d_name);
324                     result = DoGlob(interp, newDir, p+1);
325                     if (newDir != static1) {
326                         ckfree(newDir);
327                     }
328                     if (result != TCL_OK) {
329                         break;
330                     }
331                 }
332             }
333         }
334         closedir(d);
335         if (pattern != static2) {
336             ckfree(pattern);
337         }
338         return result;
339     }
340
341     /*
342      * This is the simplest case:  just another path element.  Move
343      * it to the dir side and recurse (or just add the name to the
344      * list, if we're at the end of the path).
345      */
346
347     if (*p == 0) {
348         AppendResult(interp, dir, separator, rem, p-rem);
349     } else {
350         int l1, l2;
351         char *newDir;
352         char static1[STATIC_SIZE];
353
354         l1 = strlen(dir);
355         l2 = l1 + (p - rem) + 2;
356         if (l2 <= STATIC_SIZE) {
357             newDir = static1;
358         } else {
359             newDir = (char *) ckalloc((unsigned) l2);
360         }
361         sprintf(newDir, "%s%s%.*s", dir, separator, (int)(p-rem), rem);
362         result = DoGlob(interp, newDir, p+1);
363         if (newDir != static1) {
364             ckfree(newDir);
365         }
366         if (result != TCL_OK) {
367             return TCL_ERROR;
368         }
369     }
370     return TCL_OK;
371 }
372 \f
373 /*
374  *----------------------------------------------------------------------
375  *
376  * Tcl_TildeSubst --
377  *
378  *      Given a name starting with a tilde, produce a name where
379  *      the tilde and following characters have been replaced by
380  *      the home directory location for the named user.
381  *
382  * Results:
383  *      The result is a pointer to a static string containing
384  *      the new name.  This name will only persist until the next
385  *      call to Tcl_TildeSubst;  save it if you care about it for
386  *      the long term.  If there was an error in processing the
387  *      tilde, then an error message is left in interp->result
388  *      and the return value is NULL.
389  *
390  * Side effects:
391  *      None that the caller needs to worry about.
392  *
393  *----------------------------------------------------------------------
394  */
395
396 char *
397 Tcl_TildeSubst(interp, name)
398     Tcl_Interp *interp;         /* Interpreter in which to store error
399                                  * message (if necessary). */
400     char *name;                 /* File name, which may begin with "~/"
401                                  * (to indicate current user's home directory)
402                                  * or "~<user>/" (to indicate any user's
403                                  * home directory). */
404 {
405 #define STATIC_BUF_SIZE 50
406     static char staticBuf[STATIC_BUF_SIZE];
407     static int curSize = STATIC_BUF_SIZE;
408     static char *curBuf = staticBuf;
409     char *dir;
410     int length;
411     int fromPw = 0;
412     register char *p;
413
414     if (name[0] != '~') {
415         return name;
416     }
417
418     /*
419      * First, find the directory name corresponding to the tilde entry.
420      */
421
422     if ((name[1] == '/') || (name[1] == '\0')) {
423         dir = getenv("HOME");
424         if (dir == NULL) {
425             Tcl_ResetResult(interp);
426             Tcl_AppendResult(interp, "couldn't find HOME environment ",
427                     "variable to expand \"", name, "\"", (char *) NULL);
428             return NULL;
429         }
430         p = name+1;
431     } else {
432         struct passwd *pwPtr;
433
434         for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
435             /* Null body;  just find end of name. */
436         }
437         length = p-&name[1];
438         if (length >= curSize) {
439             length = curSize-1;
440         }
441         memcpy((VOID *) curBuf, (VOID *) (name+1), length);
442         curBuf[length] = '\0';
443         pwPtr = getpwnam(curBuf);
444         if (pwPtr == NULL) {
445             endpwent();
446             Tcl_ResetResult(interp);
447             Tcl_AppendResult(interp, "user \"", curBuf,
448                     "\" doesn't exist", (char *) NULL);
449             return NULL;
450         }
451         dir = pwPtr->pw_dir;
452         fromPw = 1;
453     }
454
455     /*
456      * Grow the buffer if necessary to make enough space for the
457      * full file name.
458      */
459
460     length = strlen(dir) + strlen(p);
461     if (length >= curSize) {
462         if (curBuf != staticBuf) {
463             ckfree(curBuf);
464         }
465         curSize = length + 1;
466         curBuf = (char *) ckalloc((unsigned) curSize);
467     }
468
469     /*
470      * Finally, concatenate the directory name with the remainder
471      * of the path in the buffer.
472      */
473
474     strcpy(curBuf, dir);
475     strcat(curBuf, p);
476     if (fromPw) {
477         endpwent();
478     }
479     return curBuf;
480 }
481 \f
482 /*
483  *----------------------------------------------------------------------
484  *
485  * Tcl_GlobCmd --
486  *
487  *      This procedure is invoked to process the "glob" Tcl command.
488  *      See the user documentation for details on what it does.
489  *
490  * Results:
491  *      A standard Tcl result.
492  *
493  * Side effects:
494  *      See the user documentation.
495  *
496  *----------------------------------------------------------------------
497  */
498
499         /* ARGSUSED */
500 int
501 Tcl_GlobCmd(dummy, interp, argc, argv)
502     ClientData dummy;                   /* Not used. */
503     Tcl_Interp *interp;                 /* Current interpreter. */
504     int argc;                           /* Number of arguments. */
505     char **argv;                        /* Argument strings. */
506 {
507     int i, result, noComplain;
508
509     if (argc < 2) {
510         notEnoughArgs:
511         Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
512                 " ?-nocomplain? name ?name ...?\"", (char *) NULL);
513         return TCL_ERROR;
514     }
515     noComplain = 0;
516     if ((argv[1][0] == '-') && (strcmp(argv[1], "-nocomplain") == 0)) {
517         if (argc < 3) {
518             goto notEnoughArgs;
519         }
520         noComplain = 1;
521     }
522
523     for (i = 1 + noComplain; i < argc; i++) {
524         char *thisName;
525
526         /*
527          * Do special checks for names starting at the root and for
528          * names beginning with ~.  Then let DoGlob do the rest.
529          */
530
531         thisName = argv[i];
532         if (*thisName == '~') {
533             thisName = Tcl_TildeSubst(interp, thisName);
534             if (thisName == NULL) {
535                 return TCL_ERROR;
536             }
537         }
538         if (*thisName == '/') {
539             result = DoGlob(interp, "/", thisName+1);
540         } else {
541             result = DoGlob(interp, "", thisName);
542         }
543         if (result != TCL_OK) {
544             return result;
545         }
546     }
547     if ((*interp->result == 0) && !noComplain) {
548         char *sep = "";
549
550         Tcl_AppendResult(interp, "no files matched glob pattern",
551                 (argc == 2) ? " \"" : "s \"", (char *) NULL);
552         for (i = 1; i < argc; i++) {
553             Tcl_AppendResult(interp, sep, argv[i], (char *) NULL);
554             sep = " ";
555         }
556         Tcl_AppendResult(interp, "\"", (char *) NULL);
557         return TCL_ERROR;
558     }
559     return TCL_OK;
560 }