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.
16 * $Id: tclGlob.c,v 1.1.1.1 2001/04/29 20:34:49 karll Exp $
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.
29 char *result; /* Pointer to result area. */
30 int totalSpace; /* Total number of characters allocated
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
40 * Declarations for procedures local to this file:
43 static void AppendResult _ANSI_ARGS_((Tcl_Interp *interp,
44 char *dir, char *separator, char *name,
46 static int DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
50 *----------------------------------------------------------------------
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.
59 * There is no return value.
62 * Interp->result gets extended.
64 *----------------------------------------------------------------------
68 AppendResult(interp, dir, separator, name, nameLength)
69 Tcl_Interp *interp; /* Interpreter whose result should be
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. */
79 int dirFlags, nameFlags;
83 * Next, see if we can put together a valid list element from dir
84 * and name by calling Tcl_AppendResult.
90 Tcl_ScanElement(dir, &dirFlags);
92 saved = name[nameLength];
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);
99 Tcl_AppendResult(interp, dir, separator, name, (char *) NULL);
101 name[nameLength] = saved;
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.
111 p = (char *) ckalloc((unsigned) (strlen(dir) + strlen(separator)
113 sprintf(p, "%s%s%s", dir, separator, name);
114 name[nameLength] = saved;
115 Tcl_AppendElement(interp, p, 0);
120 *----------------------------------------------------------------------
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.
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.
138 *----------------------------------------------------------------------
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
149 char *rem; /* Path to glob-expand. */
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
163 char *openBrace, *closeBrace;
164 int gotSpecial, result;
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
173 if ((dir[0] == 0) || ((dir[0] == '/') && (dir[1] == 0))) {
180 * When generating information for the next lower call,
181 * use static areas if the name is short, and malloc if the name
185 #define STATIC_SIZE 200
188 * First, find the end of the next element in rem, checking
189 * along the way for special globbing characters.
193 openBrace = closeBrace = NULL;
194 for (p = rem; ; p++) {
196 if ((c == '\0') || (c == '/')) {
199 if ((c == '{') && (openBrace == NULL)) {
202 if ((c == '}') && (closeBrace == NULL)) {
205 if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
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.
218 if (openBrace != NULL) {
219 int remLength, l1, l2;
220 char static1[STATIC_SIZE];
221 char *element, *newRem;
223 if (closeBrace == NULL) {
224 Tcl_ResetResult(interp);
225 interp->result = "unmatched open-brace in file name";
228 remLength = strlen(rem) + 1;
229 if (remLength <= STATIC_SIZE) {
232 newRem = (char *) ckalloc((unsigned) remLength);
235 strncpy(newRem, rem, l1);
236 for (p = openBrace; *p != '}'; ) {
238 for (p = element; ((*p != '}') && (*p != ',')); p++) {
239 /* Empty loop body: just find end of this element. */
242 strncpy(newRem+l1, element, l2);
243 strcpy(newRem+l1+l2, closeBrace+1);
244 if (DoGlob(interp, dir, newRem) != TCL_OK) {
248 if (remLength > STATIC_SIZE) {
255 * If there were any pattern-matching characters, then scan through
256 * the directory to find all the matching names.
261 struct dirent *entryPtr;
263 char *pattern, *newDir, *dirName;
264 char static1[STATIC_SIZE], static2[STATIC_SIZE];
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.
278 if ((stat(dirName, &statBuf) != 0) || !S_ISDIR(statBuf.st_mode)) {
281 d = opendir(dirName);
283 Tcl_ResetResult(interp);
284 Tcl_AppendResult(interp, "couldn't read directory \"",
285 dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
290 if (l2 < STATIC_SIZE) {
293 pattern = (char *) ckalloc((unsigned) (l2+1));
295 strncpy(pattern, rem, l2);
299 entryPtr = readdir(d);
300 if (entryPtr == NULL) {
305 * Don't match names starting with "." unless the "." is
306 * present in the pattern.
309 if ((*entryPtr->d_name == '.') && (*pattern != '.')) {
312 if (Tcl_StringMatch(entryPtr->d_name, pattern)) {
313 int nameLength = strlen(entryPtr->d_name);
315 AppendResult(interp, dir, separator, entryPtr->d_name,
318 if ((l1+nameLength+2) <= STATIC_SIZE) {
321 newDir = (char *) ckalloc((unsigned) (l1+nameLength+2));
323 sprintf(newDir, "%s%s%s", dir, separator, entryPtr->d_name);
324 result = DoGlob(interp, newDir, p+1);
325 if (newDir != static1) {
328 if (result != TCL_OK) {
335 if (pattern != static2) {
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).
348 AppendResult(interp, dir, separator, rem, p-rem);
352 char static1[STATIC_SIZE];
355 l2 = l1 + (p - rem) + 2;
356 if (l2 <= STATIC_SIZE) {
359 newDir = (char *) ckalloc((unsigned) l2);
361 sprintf(newDir, "%s%s%.*s", dir, separator, (int)(p-rem), rem);
362 result = DoGlob(interp, newDir, p+1);
363 if (newDir != static1) {
366 if (result != TCL_OK) {
374 *----------------------------------------------------------------------
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.
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.
391 * None that the caller needs to worry about.
393 *----------------------------------------------------------------------
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). */
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;
414 if (name[0] != '~') {
419 * First, find the directory name corresponding to the tilde entry.
422 if ((name[1] == '/') || (name[1] == '\0')) {
423 dir = getenv("HOME");
425 Tcl_ResetResult(interp);
426 Tcl_AppendResult(interp, "couldn't find HOME environment ",
427 "variable to expand \"", name, "\"", (char *) NULL);
432 struct passwd *pwPtr;
434 for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
435 /* Null body; just find end of name. */
438 if (length >= curSize) {
441 memcpy((VOID *) curBuf, (VOID *) (name+1), length);
442 curBuf[length] = '\0';
443 pwPtr = getpwnam(curBuf);
446 Tcl_ResetResult(interp);
447 Tcl_AppendResult(interp, "user \"", curBuf,
448 "\" doesn't exist", (char *) NULL);
456 * Grow the buffer if necessary to make enough space for the
460 length = strlen(dir) + strlen(p);
461 if (length >= curSize) {
462 if (curBuf != staticBuf) {
465 curSize = length + 1;
466 curBuf = (char *) ckalloc((unsigned) curSize);
470 * Finally, concatenate the directory name with the remainder
471 * of the path in the buffer.
483 *----------------------------------------------------------------------
487 * This procedure is invoked to process the "glob" Tcl command.
488 * See the user documentation for details on what it does.
491 * A standard Tcl result.
494 * See the user documentation.
496 *----------------------------------------------------------------------
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. */
507 int i, result, noComplain;
511 Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
512 " ?-nocomplain? name ?name ...?\"", (char *) NULL);
516 if ((argv[1][0] == '-') && (strcmp(argv[1], "-nocomplain") == 0)) {
523 for (i = 1 + noComplain; i < argc; i++) {
527 * Do special checks for names starting at the root and for
528 * names beginning with ~. Then let DoGlob do the rest.
532 if (*thisName == '~') {
533 thisName = Tcl_TildeSubst(interp, thisName);
534 if (thisName == NULL) {
538 if (*thisName == '/') {
539 result = DoGlob(interp, "/", thisName+1);
541 result = DoGlob(interp, "", thisName);
543 if (result != TCL_OK) {
547 if ((*interp->result == 0) && !noComplain) {
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);
556 Tcl_AppendResult(interp, "\"", (char *) NULL);