]>
cvs.zerfleddert.de Git - micropolis/blob - src/tcl/tclglob.c
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 *----------------------------------------------------------------------
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. */
86 int dirFlags
, nameFlags
;
90 * Next, see if we can put together a valid list element from dir
91 * and name by calling Tcl_AppendResult.
97 Tcl_ScanElement(dir
, &dirFlags
);
99 saved
= name
[nameLength
];
100 name
[nameLength
] = 0;
101 Tcl_ScanElement(name
, &nameFlags
);
102 if ((dirFlags
== 0) && (nameFlags
== 0)) {
103 if (*interp
->result
!= 0) {
104 Tcl_AppendResult(interp
, " ", dir
, separator
, name
, (char *) NULL
);
106 Tcl_AppendResult(interp
, dir
, separator
, name
, (char *) NULL
);
108 name
[nameLength
] = saved
;
113 * This name has weird characters in it, so we have to convert it to
114 * a list element. To do that, we have to merge the characters
115 * into a single name. To do that, malloc a buffer to hold everything.
118 p
= (char *) ckalloc((unsigned) (strlen(dir
) + strlen(separator
)
120 sprintf(p
, "%s%s%s", dir
, separator
, name
);
121 name
[nameLength
] = saved
;
122 Tcl_AppendElement(interp
, p
, 0);
127 *----------------------------------------------------------------------
131 * This recursive procedure forms the heart of the globbing
132 * code. It performs a depth-first traversal of the tree
133 * given by the path name to be globbed.
136 * The return value is a standard Tcl result indicating whether
137 * an error occurred in globbing. After a normal return the
138 * result in interp will be set to hold all of the file names
139 * given by the dir and rem arguments. After an error the
140 * result in interp will hold an error message.
145 *----------------------------------------------------------------------
150 Tcl_Interp
*interp
, /* Interpreter to use for error
151 * reporting (e.g. unmatched brace). */
152 char *dir
, /* Name of a directory at which to
153 * start glob expansion. This name
154 * is fixed: it doesn't contain any
156 char *rem
/* Path to glob-expand. */
160 * When this procedure is entered, the name to be globbed may
161 * already have been partly expanded by ancestor invocations of
162 * DoGlob. The part that's already been expanded is in "dir"
163 * (this may initially be empty), and the part still to expand
164 * is in "rem". This procedure expands "rem" one level, making
165 * recursive calls to itself if there's still more stuff left
171 char *openBrace
, *closeBrace
;
172 int gotSpecial
, result
;
176 * Figure out whether we'll need to add a slash between the directory
177 * name and file names within the directory when concatenating them
181 if ((dir
[0] == 0) || ((dir
[0] == '/') && (dir
[1] == 0))) {
188 * When generating information for the next lower call,
189 * use static areas if the name is short, and malloc if the name
193 #define STATIC_SIZE 200
196 * First, find the end of the next element in rem, checking
197 * along the way for special globbing characters.
201 openBrace
= closeBrace
= NULL
;
202 for (p
= rem
; ; p
++) {
204 if ((c
== '\0') || (c
== '/')) {
207 if ((c
== '{') && (openBrace
== NULL
)) {
210 if ((c
== '}') && (closeBrace
== NULL
)) {
213 if ((c
== '*') || (c
== '[') || (c
== '\\') || (c
== '?')) {
219 * If there is an open brace in the argument, then make a recursive
220 * call for each element between the braces. In this case, the
221 * recursive call to DoGlob uses the same "dir" that we got.
222 * If there are several brace-pairs in a single name, we just handle
223 * one here, and the others will be handled in recursive calls.
226 if (openBrace
!= NULL
) {
227 int remLength
, l1
, l2
;
228 char static1
[STATIC_SIZE
];
229 char *element
, *newRem
;
231 if (closeBrace
== NULL
) {
232 Tcl_ResetResult(interp
);
233 interp
->result
= "unmatched open-brace in file name";
236 remLength
= strlen(rem
) + 1;
237 if (remLength
<= STATIC_SIZE
) {
240 newRem
= (char *) ckalloc((unsigned) remLength
);
243 strncpy(newRem
, rem
, l1
);
245 for (p
= openBrace
; *p
!= '}'; ) {
247 for (p
= element
; ((*p
!= '}') && (*p
!= ',')); p
++) {
248 /* Empty loop body: just find end of this element. */
251 strncpy(newRem
+l1
, element
, l2
);
252 strcpy(newRem
+l1
+l2
, closeBrace
+1);
253 if (DoGlob(interp
, dir
, newRem
) != TCL_OK
) {
257 if (remLength
> STATIC_SIZE
) {
264 * If there were any pattern-matching characters, then scan through
265 * the directory to find all the matching names.
270 struct dirent
*entryPtr
;
272 char *pattern
, *newDir
, *dirName
;
273 char static1
[STATIC_SIZE
], static2
[STATIC_SIZE
];
277 * Be careful not to do any actual file system operations on a
278 * directory named ""; instead, use ".". This is needed because
279 * some versions of UNIX don't treat "" like "." automatically.
287 if ((stat(dirName
, &statBuf
) != 0)
288 || ((statBuf
.st_mode
& S_IFMT
) != S_IFDIR
)) {
291 d
= opendir(dirName
);
293 Tcl_ResetResult(interp
);
294 Tcl_AppendResult(interp
, "couldn't read directory \"",
295 dirName
, "\": ", Tcl_UnixError(interp
), (char *) NULL
);
300 if (l2
< STATIC_SIZE
) {
303 pattern
= (char *) ckalloc((unsigned) (l2
+1));
305 strncpy(pattern
, rem
, l2
);
309 entryPtr
= readdir(d
);
310 if (entryPtr
== NULL
) {
315 * Don't match names starting with "." unless the "." is
316 * present in the pattern.
319 if ((*entryPtr
->d_name
== '.') && (*pattern
!= '.')) {
322 if (Tcl_StringMatch(entryPtr
->d_name
, pattern
)) {
323 int nameLength
= strlen(entryPtr
->d_name
);
325 AppendResult(interp
, dir
, separator
, entryPtr
->d_name
,
328 if ((l1
+nameLength
+2) <= STATIC_SIZE
) {
331 newDir
= (char *) ckalloc((unsigned) (l1
+nameLength
+2));
333 sprintf(newDir
, "%s%s%s", dir
, separator
, entryPtr
->d_name
);
334 result
= DoGlob(interp
, newDir
, p
+1);
335 if (newDir
!= static1
) {
338 if (result
!= TCL_OK
) {
345 if (pattern
!= static2
) {
352 * This is the simplest case: just another path element. Move
353 * it to the dir side and recurse (or just add the name to the
354 * list, if we're at the end of the path).
358 AppendResult(interp
, dir
, separator
, rem
, p
-rem
);
362 char static1
[STATIC_SIZE
];
365 l2
= l1
+ (p
- rem
) + 2;
366 if (l2
<= STATIC_SIZE
) {
369 newDir
= (char *) ckalloc((unsigned) l2
);
371 sprintf(newDir
, "%s%s%.*s", dir
, separator
, p
-rem
, rem
);
372 result
= DoGlob(interp
, newDir
, p
+1);
373 if (newDir
!= static1
) {
376 if (result
!= TCL_OK
) {
384 *----------------------------------------------------------------------
388 * Given a name starting with a tilde, produce a name where
389 * the tilde and following characters have been replaced by
390 * the home directory location for the named user.
393 * The result is a pointer to a static string containing
394 * the new name. This name will only persist until the next
395 * call to Tcl_TildeSubst; save it if you care about it for
396 * the long term. If there was an error in processing the
397 * tilde, then an error message is left in interp->result
398 * and the return value is NULL.
401 * None that the caller needs to worry about.
403 *----------------------------------------------------------------------
408 Tcl_Interp
*interp
, /* Interpreter in which to store error
409 * message (if necessary). */
410 char *name
/* File name, which may begin with "~/"
411 * (to indicate current user's home directory)
412 * or "~<user>/" (to indicate any user's
413 * home directory). */
416 #define STATIC_BUF_SIZE 50
417 static char staticBuf
[STATIC_BUF_SIZE
];
418 static int curSize
= STATIC_BUF_SIZE
;
419 static char *curBuf
= staticBuf
;
425 if (name
[0] != '~') {
431 if (name
[1] != '/') {
440 * First, find the directory name corresponding to the tilde entry.
443 if ((name
[1] == '/') || (name
[1] == '\0')) {
444 dir
= getenv("HOME");
446 Tcl_ResetResult(interp
);
447 Tcl_AppendResult(interp
, "couldn't find HOME environment ",
448 "variable to expand \"", name
, "\"", (char *) NULL
);
453 struct passwd
*pwPtr
;
455 for (p
= &name
[1]; (*p
!= 0) && (*p
!= '/'); p
++) {
456 /* Null body; just find end of name. */
459 if (length
>= curSize
) {
462 memcpy((VOID
*) curBuf
, (VOID
*) (name
+1), length
);
463 curBuf
[length
] = '\0';
464 pwPtr
= getpwnam(curBuf
);
466 Tcl_ResetResult(interp
);
467 Tcl_AppendResult(interp
, "user \"", curBuf
,
468 "\" doesn't exist", (char *) NULL
);
477 * Grow the buffer if necessary to make enough space for the
481 length
= strlen(dir
) + strlen(p
);
482 if (length
>= curSize
) {
483 if (curBuf
!= staticBuf
) {
486 curSize
= length
+ 1;
487 curBuf
= (char *) ckalloc((unsigned) curSize
);
491 * Finally, concatenate the directory name with the remainder
492 * of the path in the buffer.
506 *----------------------------------------------------------------------
510 * This procedure is invoked to process the "glob" Tcl command.
511 * See the user documentation for details on what it does.
514 * A standard Tcl result.
517 * See the user documentation.
519 *----------------------------------------------------------------------
525 ClientData dummy
, /* Not used. */
526 Tcl_Interp
*interp
, /* Current interpreter. */
527 int argc
, /* Number of arguments. */
528 char **argv
/* Argument strings. */
531 int i
, result
, noComplain
;
535 Tcl_AppendResult(interp
, "wrong # args: should be \"", argv
[0],
536 " ?-nocomplain? name ?name ...?\"", (char *) NULL
);
540 if ((argv
[1][0] == '-') && (strcmp(argv
[1], "-nocomplain") == 0)) {
547 for (i
= 1 + noComplain
; i
< argc
; i
++) {
551 * Do special checks for names starting at the root and for
552 * names beginning with ~. Then let DoGlob do the rest.
556 if (*thisName
== '~') {
557 thisName
= Tcl_TildeSubst(interp
, thisName
);
558 if (thisName
== NULL
) {
562 if (*thisName
== '/') {
563 result
= DoGlob(interp
, "/", thisName
+1);
565 result
= DoGlob(interp
, "", thisName
);
567 if (result
!= TCL_OK
) {
571 if ((*interp
->result
== 0) && !noComplain
) {
574 Tcl_AppendResult(interp
, "no files matched glob pattern",
575 (argc
== 2) ? " \"" : "s \"", (char *) NULL
);
576 for (i
= 1; i
< argc
; i
++) {
577 Tcl_AppendResult(interp
, sep
, argv
[i
], (char *) NULL
);
580 Tcl_AppendResult(interp
, "\"", (char *) NULL
);