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