|
|
1.1 root 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:
17: #ifndef lint
18: static char rcsid[] = "$Header: /user6/ouster/tcl/RCS/tclGlob.c,v 1.24 92/07/01 08:51:50 ouster Exp $ SPRITE (Berkeley)";
19: #endif /* not lint */
20:
21: #include <sys/types.h>
22:
23: #include "tclint.h"
24: #include "tclunix.h"
25:
26: void dvpath(char *);
27:
28: /*
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.
32: */
33:
34: typedef struct {
35: char *result; /* Pointer to result area. */
36: int totalSpace; /* Total number of characters allocated
37: * for result. */
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
42: * it's dynamic. */
43: } GlobResult;
44:
45: /*
46: * Declarations for procedures local to this file:
47: */
48:
49: static void AppendResult _ANSI_ARGS_((Tcl_Interp *interp,
50: char *dir, char *separator, char *name,
51: int nameLength));
52: static int DoGlob _ANSI_ARGS_((Tcl_Interp *interp, char *dir,
53: char *rem));
54:
55: /*
56: *----------------------------------------------------------------------
57: *
58: * AppendResult --
59: *
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.
63: *
64: * Results:
65: * There is no return value.
66: *
67: * Side effects:
68: * Interp->result gets extended.
69: *
70: *----------------------------------------------------------------------
71: */
72:
73: static void
74: AppendResult(interp, dir, separator, name, nameLength)
75: Tcl_Interp *interp; /* Interpreter whose result should be
76: * appended to. */
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. */
84: {
85: int dirFlags, nameFlags;
86: char *p, saved;
87:
88: /*
89: * Next, see if we can put together a valid list element from dir
90: * and name by calling Tcl_AppendResult.
91: */
92:
93: if (*dir == 0) {
94: dirFlags = 0;
95: } else {
96: Tcl_ScanElement(dir, &dirFlags);
97: }
98: saved = name[nameLength];
99: name[nameLength] = 0;
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);
104: } else {
105: Tcl_AppendResult(interp, dir, separator, name, (char *) NULL);
106: }
107: name[nameLength] = saved;
108: return;
109: }
110:
111: /*
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.
115: */
116:
117: p = (char *) ckalloc((unsigned) (strlen(dir) + strlen(separator)
118: + nameLength + 1));
119: sprintf(p, "%s%s%s", dir, separator, name);
120: name[nameLength] = saved;
121: Tcl_AppendElement(interp, p, 0);
122: ckfree(p);
123: }
124:
125: /*
126: *----------------------------------------------------------------------
127: *
128: * DoGlob --
129: *
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.
133: *
134: * Results:
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.
140: *
141: * Side effects:
142: * None.
143: *
144: *----------------------------------------------------------------------
145: */
146:
147: static int
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
154: * globbing chars. */
155: char *rem; /* Path to glob-expand. */
156: {
157: /*
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
164: * in the remainder.
165: */
166:
167: register char *p;
168: register char c;
169: char *openBrace, *closeBrace;
170: int gotSpecial, result;
171: char *separator;
172:
173: /*
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
176: * together.
177: */
178:
179: if ((dir[0] == 0) || ((dir[0] == '/') && (dir[1] == 0))) {
180: separator = "";
181: } else {
182: separator = "/";
183: }
184:
185: /*
186: * When generating information for the next lower call,
187: * use static areas if the name is short, and malloc if the name
188: * is longer.
189: */
190:
191: #define STATIC_SIZE 200
192:
193: /*
194: * First, find the end of the next element in rem, checking
195: * along the way for special globbing characters.
196: */
197:
198: gotSpecial = 0;
199: openBrace = closeBrace = NULL;
200: for (p = rem; ; p++) {
201: c = *p;
202: if ((c == '\0') || (c == '/')) {
203: break;
204: }
205: if ((c == '{') && (openBrace == NULL)) {
206: openBrace = p;
207: }
208: if ((c == '}') && (closeBrace == NULL)) {
209: closeBrace = p;
210: }
211: if ((c == '*') || (c == '[') || (c == '\\') || (c == '?')) {
212: gotSpecial = 1;
213: }
214: }
215:
216: /*
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.
222: */
223:
224: if (openBrace != NULL) {
225: int remLength, l1, l2;
226: char static1[STATIC_SIZE];
227: char *element, *newRem;
228:
229: if (closeBrace == NULL) {
230: Tcl_ResetResult(interp);
231: interp->result = "unmatched open-brace in file name";
232: return TCL_ERROR;
233: }
234: remLength = strlen(rem) + 1;
235: if (remLength <= STATIC_SIZE) {
236: newRem = static1;
237: } else {
238: newRem = (char *) ckalloc((unsigned) remLength);
239: }
240: l1 = openBrace-rem;
241: strncpy(newRem, rem, l1);
242: p = openBrace;
243: for (p = openBrace; *p != '}'; ) {
244: element = p+1;
245: for (p = element; ((*p != '}') && (*p != ',')); p++) {
246: /* Empty loop body: just find end of this element. */
247: }
248: l2 = p - element;
249: strncpy(newRem+l1, element, l2);
250: strcpy(newRem+l1+l2, closeBrace+1);
251: if (DoGlob(interp, dir, newRem) != TCL_OK) {
252: return TCL_ERROR;
253: }
254: }
255: if (remLength > STATIC_SIZE) {
256: ckfree(newRem);
257: }
258: return TCL_OK;
259: }
260:
261: /*
262: * If there were any pattern-matching characters, then scan through
263: * the directory to find all the matching names.
264: */
265:
266: if (gotSpecial) {
267: DIR *d;
268: struct dirent *entryPtr;
269: int l1, l2;
270: char *pattern, *newDir, *dirName;
271: char static1[STATIC_SIZE], static2[STATIC_SIZE];
272: struct stat statBuf;
273:
274: /*
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.
278: */
279:
280: if (*dir == '\0') {
281: dirName = ".";
282: } else {
283: dirName = dir;
284: }
285: if ((stat(dirName, &statBuf) != 0)
286: || ((statBuf.st_mode & S_IFMT) != S_IFDIR)) {
287: return TCL_OK;
288: }
289: d = opendir(dirName);
290: if (d == NULL) {
291: Tcl_ResetResult(interp);
292: Tcl_AppendResult(interp, "couldn't read directory \"",
293: dirName, "\": ", Tcl_UnixError(interp), (char *) NULL);
294: return TCL_ERROR;
295: }
296: l1 = strlen(dir);
297: l2 = (p - rem);
298: if (l2 < STATIC_SIZE) {
299: pattern = static2;
300: } else {
301: pattern = (char *) ckalloc((unsigned) (l2+1));
302: }
303: strncpy(pattern, rem, l2);
304: pattern[l2] = '\0';
305: result = TCL_OK;
306: while (1) {
307: entryPtr = readdir(d);
308: if (entryPtr == NULL) {
309: break;
310: }
311:
312: /*
313: * Don't match names starting with "." unless the "." is
314: * present in the pattern.
315: */
316:
317: if ((*entryPtr->d_name == '.') && (*pattern != '.')) {
318: continue;
319: }
320: if (Tcl_StringMatch(entryPtr->d_name, pattern)) {
321: int nameLength = strlen(entryPtr->d_name);
322: if (*p == 0) {
323: AppendResult(interp, dir, separator, entryPtr->d_name,
324: nameLength);
325: } else {
326: if ((l1+nameLength+2) <= STATIC_SIZE) {
327: newDir = static1;
328: } else {
329: newDir = (char *) ckalloc((unsigned) (l1+nameLength+2));
330: }
331: sprintf(newDir, "%s%s%s", dir, separator, entryPtr->d_name);
332: result = DoGlob(interp, newDir, p+1);
333: if (newDir != static1) {
334: ckfree(newDir);
335: }
336: if (result != TCL_OK) {
337: break;
338: }
339: }
340: }
341: }
342: closedir(d);
343: if (pattern != static2) {
344: ckfree(pattern);
345: }
346: return result;
347: }
348:
349: /*
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).
353: */
354:
355: if (*p == 0) {
356: AppendResult(interp, dir, separator, rem, p-rem);
357: } else {
358: int l1, l2;
359: char *newDir;
360: char static1[STATIC_SIZE];
361:
362: l1 = strlen(dir);
363: l2 = l1 + (p - rem) + 2;
364: if (l2 <= STATIC_SIZE) {
365: newDir = static1;
366: } else {
367: newDir = (char *) ckalloc((unsigned) l2);
368: }
369: sprintf(newDir, "%s%s%.*s", dir, separator, p-rem, rem);
370: result = DoGlob(interp, newDir, p+1);
371: if (newDir != static1) {
372: ckfree(newDir);
373: }
374: if (result != TCL_OK) {
375: return TCL_ERROR;
376: }
377: }
378: return TCL_OK;
379: }
380:
381: /*
382: *----------------------------------------------------------------------
383: *
384: * Tcl_TildeSubst --
385: *
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.
389: *
390: * Results:
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.
397: *
398: * Side effects:
399: * None that the caller needs to worry about.
400: *
401: *----------------------------------------------------------------------
402: */
403:
404: char *
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). */
412: {
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;
417: char *dir;
418: int length;
419: int fromPw = 0;
420: register char *p;
421:
422: if (name[0] != '~') {
423: return name;
424: }
425:
426: #ifdef MSDOS
427: dvpath(dir);
428: if (name[1] != '/') {
429: strcat(dir,"/");
430: p = name;
431: }
432: else
433: p = name +1;
434: #else
435:
436: /*
437: * First, find the directory name corresponding to the tilde entry.
438: */
439:
440: if ((name[1] == '/') || (name[1] == '\0')) {
441: dir = getenv("HOME");
442: if (dir == NULL) {
443: Tcl_ResetResult(interp);
444: Tcl_AppendResult(interp, "couldn't find HOME environment ",
445: "variable to expand \"", name, "\"", (char *) NULL);
446: return NULL;
447: }
448: p = name+1;
449: } else {
450: struct passwd *pwPtr;
451:
452: for (p = &name[1]; (*p != 0) && (*p != '/'); p++) {
453: /* Null body; just find end of name. */
454: }
455: length = p-&name[1];
456: if (length >= curSize) {
457: length = curSize-1;
458: }
459: memcpy((VOID *) curBuf, (VOID *) (name+1), length);
460: curBuf[length] = '\0';
461: pwPtr = getpwnam(curBuf);
462: if (pwPtr == NULL) {
463: Tcl_ResetResult(interp);
464: Tcl_AppendResult(interp, "user \"", curBuf,
465: "\" doesn't exist", (char *) NULL);
466: return NULL;
467: }
468: dir = pwPtr->pw_dir;
469: fromPw = 1;
470: }
471: #endif
472:
473: /*
474: * Grow the buffer if necessary to make enough space for the
475: * full file name.
476: */
477:
478: length = strlen(dir) + strlen(p);
479: if (length >= curSize) {
480: if (curBuf != staticBuf) {
481: ckfree(curBuf);
482: }
483: curSize = length + 1;
484: curBuf = (char *) ckalloc((unsigned) curSize);
485: }
486:
487: /*
488: * Finally, concatenate the directory name with the remainder
489: * of the path in the buffer.
490: */
491:
492: strcpy(curBuf, dir);
493: strcat(curBuf, p);
494: #ifndef MSDOS
495: if (fromPw) {
496: endpwent();
497: }
498: #endif
499: return curBuf;
500: }
501:
502: /*
503: *----------------------------------------------------------------------
504: *
505: * Tcl_GlobCmd --
506: *
507: * This procedure is invoked to process the "glob" Tcl command.
508: * See the user documentation for details on what it does.
509: *
510: * Results:
511: * A standard Tcl result.
512: *
513: * Side effects:
514: * See the user documentation.
515: *
516: *----------------------------------------------------------------------
517: */
518:
519: /* ARGSUSED */
520: int
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. */
526: {
527: int i, result, noComplain;
528:
529: if (argc < 2) {
530: notEnoughArgs:
531: Tcl_AppendResult(interp, "wrong # args: should be \"", argv[0],
532: " ?-nocomplain? name ?name ...?\"", (char *) NULL);
533: return TCL_ERROR;
534: }
535: noComplain = 0;
536: if ((argv[1][0] == '-') && (strcmp(argv[1], "-nocomplain") == 0)) {
537: if (argc < 3) {
538: goto notEnoughArgs;
539: }
540: noComplain = 1;
541: }
542:
543: for (i = 1 + noComplain; i < argc; i++) {
544: char *thisName;
545:
546: /*
547: * Do special checks for names starting at the root and for
548: * names beginning with ~. Then let DoGlob do the rest.
549: */
550:
551: thisName = argv[i];
552: if (*thisName == '~') {
553: thisName = Tcl_TildeSubst(interp, thisName);
554: if (thisName == NULL) {
555: return TCL_ERROR;
556: }
557: }
558: if (*thisName == '/') {
559: result = DoGlob(interp, "/", thisName+1);
560: } else {
561: result = DoGlob(interp, "", thisName);
562: }
563: if (result != TCL_OK) {
564: return result;
565: }
566: }
567: if ((*interp->result == 0) && !noComplain) {
568: char *sep = "";
569:
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);
574: sep = " ";
575: }
576: Tcl_AppendResult(interp, "\"", (char *) NULL);
577: return TCL_ERROR;
578: }
579: return TCL_OK;
580: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.