|
|
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.