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