]>
git.zerfleddert.de Git - micropolis/blob - src/tcl/tclglob.c
c5492906ce2351945ea88a2c665b91c29d55ed4a
4 * This file provides procedures and commands for file name
5 * manipulation, such as tilde expansion and globbing.
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.
18 static char rcsid
[] = "$Header: /user6/ouster/tcl/RCS/tclGlob.c,v 1.24 92/07/01 08:51:50 ouster Exp $ SPRITE (Berkeley)";
21 #include <sys/types.h>
29 * The structure below is used to keep track of a globbing result
30 * being built up (i.e. a partial list of file names). The list
31 * grows dynamically to be as big as needed.
35 char *result
; /* Pointer to result area. */
36 int totalSpace
; /* Total number of characters allocated
38 int spaceUsed
; /* Number of characters currently in use
39 * to hold the partial result (not including
40 * the terminating NULL). */
41 int dynamic
; /* 0 means result is static space, 1 means
46 * Declarations for procedures local to this file:
49 static void AppendResult
_ANSI_ARGS_((Tcl_Interp
*interp
,
50 char *dir
, char *separator
, char *name
,
52 static int DoGlob
_ANSI_ARGS_((Tcl_Interp
*interp
, char *dir
,
56 *----------------------------------------------------------------------
60 * Given two parts of a file name (directory and element within
61 * directory), concatenate the two together and append them to
62 * the result building up in interp.
65 * There is no return value.
68 * Interp->result gets extended.
70 *----------------------------------------------------------------------
74 AppendResult(interp
, dir
, separator
, name
, nameLength
)
75 Tcl_Interp
*interp
; /* Interpreter whose result should be
77 char *dir
; /* Name of directory, without trailing
78 * slash except for root directory. */
79 char *separator
; /* Separator string so use between dir and
80 * name: either "/" or "" depending on dir. */
81 char *name
; /* Name of file withing directory (NOT
82 * necessarily null-terminated!). */
83 int nameLength
; /* Number of characters in name. */
85 int dirFlags
, nameFlags
;
89 * Next, see if we can put together a valid list element from dir
90 * and name by calling Tcl_AppendResult.
96 Tcl_ScanElement(dir
, &dirFlags
);
98 saved
= name
[nameLength
];
100 Tcl_ScanElement(name
, &nameFlags
);
101 if ((dirFlags
== 0) && (nameFlags
== 0)) {
102 if (*interp
->result
!= 0) {
103 Tcl_AppendResult(interp
, " ", dir
, separator
, name
, (char *) NULL
);
105 Tcl_AppendResult(interp
, dir
, separator
, name
, (char *) NULL
);
107 name
[nameLength
] = saved
;
112 * This name has weird characters in it, so we have to convert it to
113 * a list element. To do that, we have to merge the characters
114 * into a single name. To do that, malloc a buffer to hold everything.
117 p
= (char *) ckalloc((unsigned) (strlen(dir
) + strlen(separator
)
119 sprintf(p
, "%s%s%s", dir
, separator
, name
);
120 name
[nameLength
] = saved
;
121 Tcl_AppendElement(interp
, p
, 0);
126 *----------------------------------------------------------------------
130 * This recursive procedure forms the heart of the globbing
131 * code. It performs a depth-first traversal of the tree
132 * given by the path name to be globbed.
135 * The return value is a standard Tcl result indicating whether
136 * an error occurred in globbing. After a normal return the
137 * result in interp will be set to hold all of the file names
138 * given by the dir and rem arguments. After an error the
139 * result in interp will hold an error message.
144 *----------------------------------------------------------------------
148 DoGlob(interp
, dir
, rem
)
149 Tcl_Interp
*interp
; /* Interpreter to use for error
150 * reporting (e.g. unmatched brace). */
151 char *dir
; /* Name of a directory at which to
152 * start glob expansion. This name
153 * is fixed: it doesn't contain any
155 char *rem
; /* Path to glob-expand. */
158 * When this procedure is entered, the name to be globbed may
159 * already have been partly expanded by ancestor invocations of
160 * DoGlob. The part that's already been expanded is in "dir"
161 * (this may initially be empty), and the part still to expand
162 * is in "rem". This procedure expands "rem" one level, making
163 * recursive calls to itself if there's still more stuff left
169 char *openBrace
, *closeBrace
;
170 int gotSpecial
, result
;
174 * Figure out whether we'll need to add a slash between the directory
175 * name and file names within the directory when concatenating them
179 if ((dir
[0] == 0) || ((dir
[0] == '/') && (dir
[1] == 0))) {
186 * When generating information for the next lower call,
187 * use static areas if the name is short, and malloc if the name
191 #define STATIC_SIZE 200
194 * First, find the end of the next element in rem, checking
195 * along the way for special globbing characters.
199 openBrace
= closeBrace
= NULL
;
200 for (p
= rem
; ; p
++) {
202 if ((c
== '\0') || (c
== '/')) {
205 if ((c
== '{') && (openBrace
== NULL
)) {
208 if ((c
== '}') && (closeBrace
== NULL
)) {
211 if ((c
== '*') || (c
== '[') || (c
== '\\') || (c
== '?')) {
217 * If there is an open brace in the argument, then make a recursive
218 * call for each element between the braces. In this case, the
219 * recursive call to DoGlob uses the same "dir" that we got.
220 * If there are several brace-pairs in a single name, we just handle
221 * one here, and the others will be handled in recursive calls.
224 if (openBrace
!= NULL
) {
225 int remLength
, l1
, l2
;
226 char static1
[STATIC_SIZE
];
227 char *element
, *newRem
;
229 if (closeBrace
== NULL
) {
230 Tcl_ResetResult(interp
);
231 interp
->result
= "unmatched open-brace in file name";
234 remLength
= strlen(rem
) + 1;
235 if (remLength
<= STATIC_SIZE
) {
238 newRem
= (char *) ckalloc((unsigned) remLength
);
241 strncpy(newRem
, rem
, l1
);
243 for (p
= openBrace
; *p
!= '}'; ) {
245 for (p
= element
; ((*p
!= '}') && (*p
!= ',')); p
++) {
246 /* Empty loop body: just find end of this element. */
249 strncpy(newRem
+l1
, element
, l2
);
250 strcpy(newRem
+l1
+l2
, closeBrace
+1);
251 if (DoGlob(interp
, dir
, newRem
) != TCL_OK
) {
255 if (remLength
> STATIC_SIZE
) {
262 * If there were any pattern-matching characters, then scan through
263 * the directory to find all the matching names.
268 struct dirent
*entryPtr
;
270 char *pattern
, *newDir
, *dirName
;
271 char static1
[STATIC_SIZE
], static2
[STATIC_SIZE
];
275 * Be careful not to do any actual file system operations on a
276 * directory named ""; instead, use ".". This is needed because
277 * some versions of UNIX don't treat "" like "." automatically.
285 if ((stat(dirName
, &statBuf
) != 0)
286 || ((statBuf
.st_mode
& S_IFMT
) != S_IFDIR
)) {
289 d
= opendir(dirName
);
291 Tcl_ResetResult(interp
);
292 Tcl_AppendResult(interp
, "couldn't read directory \"",
293 dirName
, "\": ", Tcl_UnixError(interp
), (char *) NULL
);
298 if (l2
< STATIC_SIZE
) {
301 pattern
= (char *) ckalloc((unsigned) (l2
+1));
303 strncpy(pattern
, rem
, l2
);
307 entryPtr
= readdir(d
);
308 if (entryPtr
== NULL
) {
313 * Don't match names starting with "." unless the "." is
314 * present in the pattern.
317 if ((*entryPtr
->d_name
== '.') && (*pattern
!= '.')) {
320 if (Tcl_StringMatch(entryPtr
->d_name
, pattern
)) {
321 int nameLength
= strlen(entryPtr
->d_name
);
323 AppendResult(interp
, dir
, separator
, entryPtr
->d_name
,
326 if ((l1
+nameLength
+2) <= STATIC_SIZE
) {
329 newDir
= (char *) ckalloc((unsigned) (l1
+nameLength
+2));
331 sprintf(newDir
, "%s%s%s", dir
, separator
, entryPtr
->d_name
);
332 result
= DoGlob(interp
, newDir
, p
+1);
333 if (newDir
!= static1
) {
336 if (result
!= TCL_OK
) {
343 if (pattern
!= static2
) {
350 * This is the simplest case: just another path element. Move
351 * it to the dir side and recurse (or just add the name to the
352 * list, if we're at the end of the path).
356 AppendResult(interp
, dir
, separator
, rem
, p
-rem
);
360 char static1
[STATIC_SIZE
];
363 l2
= l1
+ (p
- rem
) + 2;
364 if (l2
<= STATIC_SIZE
) {
367 newDir
= (char *) ckalloc((unsigned) l2
);
369 sprintf(newDir
, "%s%s%.*s", dir
, separator
, p
-rem
, rem
);
370 result
= DoGlob(interp
, newDir
, p
+1);
371 if (newDir
!= static1
) {
374 if (result
!= TCL_OK
) {
382 *----------------------------------------------------------------------
386 * Given a name starting with a tilde, produce a name where
387 * the tilde and following characters have been replaced by
388 * the home directory location for the named user.
391 * The result is a pointer to a static string containing
392 * the new name. This name will only persist until the next
393 * call to Tcl_TildeSubst; save it if you care about it for
394 * the long term. If there was an error in processing the
395 * tilde, then an error message is left in interp->result
396 * and the return value is NULL.
399 * None that the caller needs to worry about.
401 *----------------------------------------------------------------------
405 Tcl_TildeSubst(interp
, name
)
406 Tcl_Interp
*interp
; /* Interpreter in which to store error
407 * message (if necessary). */
408 char *name
; /* File name, which may begin with "~/"
409 * (to indicate current user's home directory)
410 * or "~<user>/" (to indicate any user's
411 * home directory). */
413 #define STATIC_BUF_SIZE 50
414 static char staticBuf
[STATIC_BUF_SIZE
];
415 static int curSize
= STATIC_BUF_SIZE
;
416 static char *curBuf
= staticBuf
;
422 if (name
[0] != '~') {
428 if (name
[1] != '/') {
437 * First, find the directory name corresponding to the tilde entry.
440 if ((name
[1] == '/') || (name
[1] == '\0')) {
441 dir
= getenv("HOME");
443 Tcl_ResetResult(interp
);
444 Tcl_AppendResult(interp
, "couldn't find HOME environment ",
445 "variable to expand \"", name
, "\"", (char *) NULL
);
450 struct passwd
*pwPtr
;
452 for (p
= &name
[1]; (*p
!= 0) && (*p
!= '/'); p
++) {
453 /* Null body; just find end of name. */
456 if (length
>= curSize
) {
459 memcpy((VOID
*) curBuf
, (VOID
*) (name
+1), length
);
460 curBuf
[length
] = '\0';
461 pwPtr
= getpwnam(curBuf
);
463 Tcl_ResetResult(interp
);
464 Tcl_AppendResult(interp
, "user \"", curBuf
,
465 "\" doesn't exist", (char *) NULL
);
474 * Grow the buffer if necessary to make enough space for the
478 length
= strlen(dir
) + strlen(p
);
479 if (length
>= curSize
) {
480 if (curBuf
!= staticBuf
) {
483 curSize
= length
+ 1;
484 curBuf
= (char *) ckalloc((unsigned) curSize
);
488 * Finally, concatenate the directory name with the remainder
489 * of the path in the buffer.
503 *----------------------------------------------------------------------
507 * This procedure is invoked to process the "glob" Tcl command.
508 * See the user documentation for details on what it does.
511 * A standard Tcl result.
514 * See the user documentation.
516 *----------------------------------------------------------------------
521 Tcl_GlobCmd(dummy
, interp
, argc
, argv
)
522 ClientData dummy
; /* Not used. */
523 Tcl_Interp
*interp
; /* Current interpreter. */
524 int argc
; /* Number of arguments. */
525 char **argv
; /* Argument strings. */
527 int i
, result
, noComplain
;
531 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
532 " ?-nocomplain? name ?name ...?\"", (char *) NULL
);
536 if ((argv
[1][0] == '-') && (strcmp(argv
[1], "-nocomplain") == 0)) {
543 for (i
= 1 + noComplain
; i
< argc
; i
++) {
547 * Do special checks for names starting at the root and for
548 * names beginning with ~. Then let DoGlob do the rest.
552 if (*thisName
== '~') {
553 thisName
= Tcl_TildeSubst(interp
, thisName
);
554 if (thisName
== NULL
) {
558 if (*thisName
== '/') {
559 result
= DoGlob(interp
, "/", thisName
+1);
561 result
= DoGlob(interp
, "", thisName
);
563 if (result
!= TCL_OK
) {
567 if ((*interp
->result
== 0) && !noComplain
) {
570 Tcl_AppendResult(interp
, "no files matched glob pattern",
571 (argc
== 2) ? " \"" : "s \"", (char *) NULL
);
572 for (i
= 1; i
< argc
; i
++) {
573 Tcl_AppendResult(interp
, sep
, argv
[i
], (char *) NULL
);
576 Tcl_AppendResult(interp
, "\"", (char *) NULL
);