tclPathObj.cGo to the documentation of this file.00001 /* 00002 * tclPathObj.c -- 00003 * 00004 * This file contains the implementation of Tcl's "path" object type used 00005 * to represent and manipulate a general (virtual) filesystem entity in 00006 * an efficient manner. 00007 * 00008 * Copyright (c) 2003 Vince Darley. 00009 * 00010 * See the file "license.terms" for information on usage and redistribution of 00011 * this file, and for a DISCLAIMER OF ALL WARRANTIES. 00012 * 00013 * RCS: @(#) $Id: tclPathObj.c,v 1.66 2007/12/13 15:23:20 dgp Exp $ 00014 */ 00015 00016 #include "tclInt.h" 00017 #include "tclFileSystem.h" 00018 00019 /* 00020 * Prototypes for functions defined later in this file. 00021 */ 00022 00023 static void DupFsPathInternalRep(Tcl_Obj *srcPtr, 00024 Tcl_Obj *copyPtr); 00025 static void FreeFsPathInternalRep(Tcl_Obj *pathPtr); 00026 static void UpdateStringOfFsPath(Tcl_Obj *pathPtr); 00027 static int SetFsPathFromAny(Tcl_Interp *interp, Tcl_Obj *pathPtr); 00028 static int FindSplitPos(const char *path, int separator); 00029 static int IsSeparatorOrNull(int ch); 00030 static Tcl_Obj * GetExtension(Tcl_Obj *pathPtr); 00031 00032 /* 00033 * Define the 'path' object type, which Tcl uses to represent file paths 00034 * internally. 00035 */ 00036 00037 static Tcl_ObjType tclFsPathType = { 00038 "path", /* name */ 00039 FreeFsPathInternalRep, /* freeIntRepProc */ 00040 DupFsPathInternalRep, /* dupIntRepProc */ 00041 UpdateStringOfFsPath, /* updateStringProc */ 00042 SetFsPathFromAny /* setFromAnyProc */ 00043 }; 00044 00045 /* 00046 * struct FsPath -- 00047 * 00048 * Internal representation of a Tcl_Obj of "path" type. This can be used to 00049 * represent relative or absolute paths, and has certain optimisations when 00050 * used to represent paths which are already normalized and absolute. 00051 * 00052 * Note that both 'translatedPathPtr' and 'normPathPtr' can be a circular 00053 * reference to the container Tcl_Obj of this FsPath. 00054 * 00055 * There are two cases, with the first being the most common: 00056 * 00057 * (i) flags == 0, => Ordinary path. 00058 * 00059 * translatedPathPtr contains the translated path (which may be a circular 00060 * reference to the object itself). If it is NULL then the path is pure 00061 * normalized (and the normPathPtr will be a circular reference). cwdPtr is 00062 * null for an absolute path, and non-null for a relative path (unless the cwd 00063 * has never been set, in which case the cwdPtr may also be null for a 00064 * relative path). 00065 * 00066 * (ii) flags != 0, => Special path, see TclNewFSPathObj 00067 * 00068 * Now, this is a path like 'file join $dir $tail' where, cwdPtr is the $dir 00069 * and normPathPtr is the $tail. 00070 * 00071 */ 00072 00073 typedef struct FsPath { 00074 Tcl_Obj *translatedPathPtr; /* Name without any ~user sequences. If this 00075 * is NULL, then this is a pure normalized, 00076 * absolute path object, in which the parent 00077 * Tcl_Obj's string rep is already both 00078 * translated and normalized. */ 00079 Tcl_Obj *normPathPtr; /* Normalized absolute path, without ., .. or 00080 * ~user sequences. If the Tcl_Obj containing 00081 * this FsPath is already normalized, this may 00082 * be a circular reference back to the 00083 * container. If that is NOT the case, we have 00084 * a refCount on the object. */ 00085 Tcl_Obj *cwdPtr; /* If null, path is absolute, else this points 00086 * to the cwd object used for this path. We 00087 * have a refCount on the object. */ 00088 int flags; /* Flags to describe interpretation - see 00089 * below. */ 00090 ClientData nativePathPtr; /* Native representation of this path, which 00091 * is filesystem dependent. */ 00092 int filesystemEpoch; /* Used to ensure the path representation was 00093 * generated during the correct filesystem 00094 * epoch. The epoch changes when 00095 * filesystem-mounts are changed. */ 00096 struct FilesystemRecord *fsRecPtr; 00097 /* Pointer to the filesystem record entry to 00098 * use for this path. */ 00099 } FsPath; 00100 00101 /* 00102 * Flag values for FsPath->flags. 00103 */ 00104 00105 #define TCLPATH_APPENDED 1 00106 00107 /* 00108 * Define some macros to give us convenient access to path-object specific 00109 * fields. 00110 */ 00111 00112 #define PATHOBJ(pathPtr) ((FsPath *) (pathPtr)->internalRep.otherValuePtr) 00113 #define SETPATHOBJ(pathPtr,fsPathPtr) \ 00114 ((pathPtr)->internalRep.otherValuePtr = (VOID *) (fsPathPtr)) 00115 #define PATHFLAGS(pathPtr) (PATHOBJ(pathPtr)->flags) 00116 00117 /* 00118 *--------------------------------------------------------------------------- 00119 * 00120 * TclFSNormalizeAbsolutePath -- 00121 * 00122 * Takes an absolute path specification and computes a 'normalized' path 00123 * from it. 00124 * 00125 * A normalized path is one which has all '../', './' removed. Also it is 00126 * one which is in the 'standard' format for the native platform. On 00127 * Unix, this means the path must be free of symbolic links/aliases, and 00128 * on Windows it means we want the long form, with that long form's 00129 * case-dependence (which gives us a unique, case-dependent path). 00130 * 00131 * The behaviour of this function if passed a non-absolute path is NOT 00132 * defined. 00133 * 00134 * pathPtr may have a refCount of zero, or may be a shared object. 00135 * 00136 * Results: 00137 * The result is returned in a Tcl_Obj with a refCount of 1, which is 00138 * therefore owned by the caller. It must be freed (with 00139 * Tcl_DecrRefCount) by the caller when no longer needed. 00140 * 00141 * Side effects: 00142 * None (beyond the memory allocation for the result). 00143 * 00144 * Special note: 00145 * This code was originally based on code from Matt Newman and 00146 * Jean-Claude Wippler, but has since been totally rewritten by Vince 00147 * Darley to deal with symbolic links. 00148 * 00149 *--------------------------------------------------------------------------- 00150 */ 00151 00152 Tcl_Obj * 00153 TclFSNormalizeAbsolutePath( 00154 Tcl_Interp *interp, /* Interpreter to use */ 00155 Tcl_Obj *pathPtr, /* Absolute path to normalize */ 00156 ClientData *clientDataPtr) /* If non-NULL, then may be set to the 00157 * fs-specific clientData for this path. This 00158 * will happen when that extra information can 00159 * be calculated efficiently as a side-effect 00160 * of normalization. */ 00161 { 00162 ClientData clientData = NULL; 00163 const char *dirSep, *oldDirSep; 00164 int first = 1; /* Set to zero once we've passed the first 00165 * directory separator - we can't use '..' to 00166 * remove the volume in a path. */ 00167 Tcl_Obj *retVal = NULL; 00168 dirSep = TclGetString(pathPtr); 00169 00170 if (tclPlatform == TCL_PLATFORM_WINDOWS) { 00171 if ( (dirSep[0] == '/' || dirSep[0] == '\\') 00172 && (dirSep[1] == '/' || dirSep[1] == '\\') 00173 && (dirSep[2] == '?') 00174 && (dirSep[3] == '/' || dirSep[3] == '\\')) { 00175 /* NT extended path */ 00176 dirSep += 4; 00177 00178 if ( (dirSep[0] == 'U' || dirSep[0] == 'u') 00179 && (dirSep[1] == 'N' || dirSep[1] == 'n') 00180 && (dirSep[2] == 'C' || dirSep[2] == 'c') 00181 && (dirSep[3] == '/' || dirSep[3] == '\\')) { 00182 /* NT extended UNC path */ 00183 dirSep += 4; 00184 } 00185 } 00186 if (dirSep[0] != 0 && dirSep[1] == ':' && 00187 (dirSep[2] == '/' || dirSep[2] == '\\')) { 00188 /* Do nothing */ 00189 } else if ((dirSep[0] == '/' || dirSep[0] == '\\') 00190 && (dirSep[1] == '/' || dirSep[1] == '\\')) { 00191 /* 00192 * UNC style path, where we must skip over the first separator, 00193 * since the first two segments are actually inseparable. 00194 */ 00195 00196 dirSep += 2; 00197 dirSep += FindSplitPos(dirSep, '/'); 00198 if (*dirSep != 0) { 00199 dirSep++; 00200 } 00201 } 00202 } 00203 00204 /* 00205 * Scan forward from one directory separator to the next, checking for 00206 * '..' and '.' sequences which must be handled specially. In particular 00207 * handling of '..' can be complicated if the directory before is a link, 00208 * since we will have to expand the link to be able to back up one level. 00209 */ 00210 00211 while (*dirSep != 0) { 00212 oldDirSep = dirSep; 00213 if (!first) { 00214 dirSep++; 00215 } 00216 dirSep += FindSplitPos(dirSep, '/'); 00217 if (dirSep[0] == 0 || dirSep[1] == 0) { 00218 if (retVal != NULL) { 00219 Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep); 00220 } 00221 break; 00222 } 00223 if (dirSep[1] == '.') { 00224 if (retVal != NULL) { 00225 Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep); 00226 oldDirSep = dirSep; 00227 } 00228 again: 00229 if (IsSeparatorOrNull(dirSep[2])) { 00230 /* 00231 * Need to skip '.' in the path. 00232 */ 00233 int curLen; 00234 00235 if (retVal == NULL) { 00236 const char *path = TclGetString(pathPtr); 00237 retVal = Tcl_NewStringObj(path, dirSep - path); 00238 Tcl_IncrRefCount(retVal); 00239 } 00240 Tcl_GetStringFromObj(retVal, &curLen); 00241 if (curLen == 0) { 00242 Tcl_AppendToObj(retVal, dirSep, 1); 00243 } 00244 dirSep += 2; 00245 oldDirSep = dirSep; 00246 if (dirSep[0] != 0 && dirSep[1] == '.') { 00247 goto again; 00248 } 00249 continue; 00250 } 00251 if (dirSep[2] == '.' && IsSeparatorOrNull(dirSep[3])) { 00252 Tcl_Obj *link; 00253 int curLen; 00254 char *linkStr; 00255 00256 /* 00257 * Have '..' so need to skip previous directory. 00258 */ 00259 00260 if (retVal == NULL) { 00261 const char *path = TclGetString(pathPtr); 00262 retVal = Tcl_NewStringObj(path, dirSep - path); 00263 Tcl_IncrRefCount(retVal); 00264 } 00265 Tcl_GetStringFromObj(retVal, &curLen); 00266 if (curLen == 0) { 00267 Tcl_AppendToObj(retVal, dirSep, 1); 00268 } 00269 if (!first || (tclPlatform == TCL_PLATFORM_UNIX)) { 00270 link = Tcl_FSLink(retVal, NULL, 0); 00271 if (link != NULL) { 00272 /* 00273 * Got a link. Need to check if the link is relative 00274 * or absolute, for those platforms where relative 00275 * links exist. 00276 */ 00277 00278 if (tclPlatform != TCL_PLATFORM_WINDOWS && 00279 Tcl_FSGetPathType(link) == TCL_PATH_RELATIVE) { 00280 /* 00281 * We need to follow this link which is relative 00282 * to retVal's directory. This means concatenating 00283 * the link onto the directory of the path so far. 00284 */ 00285 00286 const char *path = 00287 Tcl_GetStringFromObj(retVal, &curLen); 00288 00289 while (--curLen >= 0) { 00290 if (IsSeparatorOrNull(path[curLen])) { 00291 break; 00292 } 00293 } 00294 if (Tcl_IsShared(retVal)) { 00295 TclDecrRefCount(retVal); 00296 retVal = Tcl_DuplicateObj(retVal); 00297 Tcl_IncrRefCount(retVal); 00298 } 00299 00300 /* 00301 * We want the trailing slash. 00302 */ 00303 00304 Tcl_SetObjLength(retVal, curLen+1); 00305 Tcl_AppendObjToObj(retVal, link); 00306 TclDecrRefCount(link); 00307 linkStr = Tcl_GetStringFromObj(retVal, &curLen); 00308 } else { 00309 /* 00310 * Absolute link. 00311 */ 00312 00313 TclDecrRefCount(retVal); 00314 retVal = link; 00315 linkStr = Tcl_GetStringFromObj(retVal, &curLen); 00316 00317 /* 00318 * Convert to forward-slashes on windows. 00319 */ 00320 00321 if (tclPlatform == TCL_PLATFORM_WINDOWS) { 00322 int i; 00323 for (i = 0; i < curLen; i++) { 00324 if (linkStr[i] == '\\') { 00325 linkStr[i] = '/'; 00326 } 00327 } 00328 } 00329 } 00330 } else { 00331 linkStr = Tcl_GetStringFromObj(retVal, &curLen); 00332 } 00333 00334 /* 00335 * Either way, we now remove the last path element. 00336 * (but not the first character of the path) 00337 */ 00338 00339 while (--curLen >= 0) { 00340 if (IsSeparatorOrNull(linkStr[curLen])) { 00341 if (curLen) { 00342 Tcl_SetObjLength(retVal, curLen); 00343 } else { 00344 Tcl_SetObjLength(retVal, 1); 00345 } 00346 break; 00347 } 00348 } 00349 } 00350 dirSep += 3; 00351 oldDirSep = dirSep; 00352 00353 if ((curLen == 0) && (dirSep[0] != 0)) { 00354 Tcl_SetObjLength(retVal, 0); 00355 } 00356 00357 if (dirSep[0] != 0 && dirSep[1] == '.') { 00358 goto again; 00359 } 00360 continue; 00361 } 00362 } 00363 first = 0; 00364 if (retVal != NULL) { 00365 Tcl_AppendToObj(retVal, oldDirSep, dirSep - oldDirSep); 00366 } 00367 } 00368 00369 /* 00370 * If we didn't make any changes, just use the input path. 00371 */ 00372 00373 if (retVal == NULL) { 00374 retVal = pathPtr; 00375 Tcl_IncrRefCount(retVal); 00376 00377 if (Tcl_IsShared(retVal)) { 00378 /* 00379 * Unfortunately, the platform-specific normalization code which 00380 * will be called below has no way of dealing with the case where 00381 * an object is shared. It is expecting to modify an object in 00382 * place. So, we must duplicate this here to ensure an object with 00383 * a single ref-count. 00384 * 00385 * If that changes in the future (e.g. the normalize proc is given 00386 * one object and is able to return a different one), then we 00387 * could remove this code. 00388 */ 00389 00390 TclDecrRefCount(retVal); 00391 retVal = Tcl_DuplicateObj(pathPtr); 00392 Tcl_IncrRefCount(retVal); 00393 } 00394 } 00395 00396 /* 00397 * Ensure a windows drive like C:/ has a trailing separator 00398 */ 00399 00400 if (tclPlatform == TCL_PLATFORM_WINDOWS) { 00401 int len; 00402 const char *path = Tcl_GetStringFromObj(retVal, &len); 00403 00404 if (len == 2 && path[0] != 0 && path[1] == ':') { 00405 if (Tcl_IsShared(retVal)) { 00406 TclDecrRefCount(retVal); 00407 retVal = Tcl_DuplicateObj(retVal); 00408 Tcl_IncrRefCount(retVal); 00409 } 00410 Tcl_AppendToObj(retVal, "/", 1); 00411 } 00412 } 00413 00414 /* 00415 * Now we have an absolute path, with no '..', '.' sequences, but it still 00416 * may not be in 'unique' form, depending on the platform. For instance, 00417 * Unix is case-sensitive, so the path is ok. Windows is case-insensitive, 00418 * and also has the weird 'longname/shortname' thing (e.g. C:/Program 00419 * Files/ and C:/Progra~1/ are equivalent). 00420 * 00421 * Virtual file systems which may be registered may have other criteria 00422 * for normalizing a path. 00423 */ 00424 00425 TclFSNormalizeToUniquePath(interp, retVal, 0, &clientData); 00426 00427 /* 00428 * Since we know it is a normalized path, we can actually convert this 00429 * object into an FsPath for greater efficiency 00430 */ 00431 00432 TclFSMakePathFromNormalized(interp, retVal, clientData); 00433 if (clientDataPtr != NULL) { 00434 *clientDataPtr = clientData; 00435 } 00436 00437 /* 00438 * This has a refCount of 1 for the caller, unlike many Tcl_Obj APIs. 00439 */ 00440 00441 return retVal; 00442 } 00443 00444 /* 00445 *---------------------------------------------------------------------- 00446 * 00447 * Tcl_FSGetPathType -- 00448 * 00449 * Determines whether a given path is relative to the current directory, 00450 * relative to the current volume, or absolute. 00451 * 00452 * Results: 00453 * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or 00454 * TCL_PATH_VOLUME_RELATIVE. 00455 * 00456 * Side effects: 00457 * None. 00458 * 00459 *---------------------------------------------------------------------- 00460 */ 00461 00462 Tcl_PathType 00463 Tcl_FSGetPathType( 00464 Tcl_Obj *pathPtr) 00465 { 00466 return TclFSGetPathType(pathPtr, NULL, NULL); 00467 } 00468 00469 /* 00470 *---------------------------------------------------------------------- 00471 * 00472 * TclFSGetPathType -- 00473 * 00474 * Determines whether a given path is relative to the current directory, 00475 * relative to the current volume, or absolute. If the caller wishes to 00476 * know which filesystem claimed the path (in the case for which the path 00477 * is absolute), then a reference to a filesystem pointer can be passed 00478 * in (but passing NULL is acceptable). 00479 * 00480 * Results: 00481 * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or 00482 * TCL_PATH_VOLUME_RELATIVE. The filesystem reference will be set if and 00483 * only if it is non-NULL and the function's return value is 00484 * TCL_PATH_ABSOLUTE. 00485 * 00486 * Side effects: 00487 * None. 00488 * 00489 *---------------------------------------------------------------------- 00490 */ 00491 00492 Tcl_PathType 00493 TclFSGetPathType( 00494 Tcl_Obj *pathPtr, 00495 Tcl_Filesystem **filesystemPtrPtr, 00496 int *driveNameLengthPtr) 00497 { 00498 FsPath *fsPathPtr; 00499 00500 if (Tcl_FSConvertToPathType(NULL, pathPtr) != TCL_OK) { 00501 return TclGetPathType(pathPtr, filesystemPtrPtr, driveNameLengthPtr, 00502 NULL); 00503 } 00504 00505 fsPathPtr = PATHOBJ(pathPtr); 00506 if (fsPathPtr->cwdPtr == NULL) { 00507 return TclGetPathType(pathPtr, filesystemPtrPtr, driveNameLengthPtr, 00508 NULL); 00509 } 00510 00511 if (PATHFLAGS(pathPtr) == 0) { 00512 return TCL_PATH_RELATIVE; 00513 } 00514 return TclFSGetPathType(fsPathPtr->cwdPtr, filesystemPtrPtr, 00515 driveNameLengthPtr); 00516 } 00517 00518 /* 00519 *--------------------------------------------------------------------------- 00520 * 00521 * TclPathPart 00522 * 00523 * This function calculates the requested part of the given path, which 00524 * can be: 00525 * 00526 * - the directory above ('file dirname') 00527 * - the tail ('file tail') 00528 * - the extension ('file extension') 00529 * - the root ('file root') 00530 * 00531 * The 'portion' parameter dictates which of these to calculate. There 00532 * are a number of special cases both to be more efficient, and because 00533 * the behaviour when given a path with only a single element is defined 00534 * to require the expansion of that single element, where possible. 00535 * 00536 * Should look into integrating 'FileBasename' in tclFCmd.c into this 00537 * function. 00538 * 00539 * Results: 00540 * NULL if an error occurred, otherwise a Tcl_Obj owned by the caller 00541 * (i.e. most likely with refCount 1). 00542 * 00543 * Side effects: 00544 * None. 00545 * 00546 *--------------------------------------------------------------------------- 00547 */ 00548 00549 Tcl_Obj * 00550 TclPathPart( 00551 Tcl_Interp *interp, /* Used for error reporting */ 00552 Tcl_Obj *pathPtr, /* Path to take dirname of */ 00553 Tcl_PathPart portion) /* Requested portion of name */ 00554 { 00555 if (pathPtr->typePtr == &tclFsPathType) { 00556 FsPath *fsPathPtr = PATHOBJ(pathPtr); 00557 00558 if (TclFSEpochOk(fsPathPtr->filesystemEpoch) 00559 && (PATHFLAGS(pathPtr) != 0)) { 00560 switch (portion) { 00561 case TCL_PATH_DIRNAME: { 00562 /* 00563 * Check if the joined-on bit has any directory delimiters in 00564 * it. If so, the 'dirname' would be a joining of the main 00565 * part with the dirname of the joined-on bit. We could handle 00566 * that special case here, but we don't, and instead just use 00567 * the standardPath code. 00568 */ 00569 00570 const char *rest = TclGetString(fsPathPtr->normPathPtr); 00571 00572 if (strchr(rest, '/') != NULL) { 00573 goto standardPath; 00574 } 00575 if (tclPlatform == TCL_PLATFORM_WINDOWS 00576 && strchr(rest, '\\') != NULL) { 00577 goto standardPath; 00578 } 00579 00580 /* 00581 * The joined-on path is simple, so we can just return here. 00582 */ 00583 00584 Tcl_IncrRefCount(fsPathPtr->cwdPtr); 00585 return fsPathPtr->cwdPtr; 00586 } 00587 case TCL_PATH_TAIL: { 00588 /* 00589 * Check if the joined-on bit has any directory delimiters in 00590 * it. If so, the 'tail' would be only the part following the 00591 * last delimiter. We could handle that special case here, but 00592 * we don't, and instead just use the standardPath code. 00593 */ 00594 00595 const char *rest = TclGetString(fsPathPtr->normPathPtr); 00596 00597 if (strchr(rest, '/') != NULL) { 00598 goto standardPath; 00599 } 00600 if (tclPlatform == TCL_PLATFORM_WINDOWS 00601 && strchr(rest, '\\') != NULL) { 00602 goto standardPath; 00603 } 00604 Tcl_IncrRefCount(fsPathPtr->normPathPtr); 00605 return fsPathPtr->normPathPtr; 00606 } 00607 case TCL_PATH_EXTENSION: 00608 return GetExtension(fsPathPtr->normPathPtr); 00609 case TCL_PATH_ROOT: { 00610 const char *fileName, *extension; 00611 int length; 00612 00613 fileName = Tcl_GetStringFromObj(fsPathPtr->normPathPtr, 00614 &length); 00615 extension = TclGetExtension(fileName); 00616 if (extension == NULL) { 00617 /* 00618 * There is no extension so the root is the same as the 00619 * path we were given. 00620 */ 00621 00622 Tcl_IncrRefCount(pathPtr); 00623 return pathPtr; 00624 } else { 00625 /* 00626 * Duplicate the object we were given and then trim off 00627 * the extension of the tail component of the path. 00628 */ 00629 00630 FsPath *fsDupPtr; 00631 Tcl_Obj *root = Tcl_DuplicateObj(pathPtr); 00632 00633 Tcl_IncrRefCount(root); 00634 fsDupPtr = PATHOBJ(root); 00635 if (Tcl_IsShared(fsDupPtr->normPathPtr)) { 00636 TclDecrRefCount(fsDupPtr->normPathPtr); 00637 fsDupPtr->normPathPtr = Tcl_NewStringObj(fileName, 00638 (int)(length - strlen(extension))); 00639 Tcl_IncrRefCount(fsDupPtr->normPathPtr); 00640 } else { 00641 Tcl_SetObjLength(fsDupPtr->normPathPtr, 00642 (int)(length - strlen(extension))); 00643 } 00644 00645 /* 00646 * Must also trim the string representation if we have it. 00647 */ 00648 00649 if (root->bytes != NULL && root->length > 0) { 00650 root->length -= strlen(extension); 00651 root->bytes[root->length] = 0; 00652 } 00653 return root; 00654 } 00655 } 00656 default: 00657 /* We should never get here */ 00658 Tcl_Panic("Bad portion to TclPathPart"); 00659 /* For less clever compilers */ 00660 return NULL; 00661 } 00662 } else if (fsPathPtr->cwdPtr != NULL) { 00663 /* Relative path */ 00664 goto standardPath; 00665 } else { 00666 /* Absolute path */ 00667 goto standardPath; 00668 } 00669 } else { 00670 int splitElements; 00671 Tcl_Obj *splitPtr, *resultPtr; 00672 00673 standardPath: 00674 resultPtr = NULL; 00675 if (portion == TCL_PATH_EXTENSION) { 00676 return GetExtension(pathPtr); 00677 } else if (portion == TCL_PATH_ROOT) { 00678 int length; 00679 const char *fileName, *extension; 00680 00681 fileName = Tcl_GetStringFromObj(pathPtr, &length); 00682 extension = TclGetExtension(fileName); 00683 if (extension == NULL) { 00684 Tcl_IncrRefCount(pathPtr); 00685 return pathPtr; 00686 } else { 00687 Tcl_Obj *root = Tcl_NewStringObj(fileName, 00688 (int) (length - strlen(extension))); 00689 Tcl_IncrRefCount(root); 00690 return root; 00691 } 00692 } 00693 00694 /* 00695 * The behaviour we want here is slightly different to the standard 00696 * Tcl_FSSplitPath in the handling of home directories; 00697 * Tcl_FSSplitPath preserves the "~" while this code computes the 00698 * actual full path name, if we had just a single component. 00699 */ 00700 00701 splitPtr = Tcl_FSSplitPath(pathPtr, &splitElements); 00702 Tcl_IncrRefCount(splitPtr); 00703 if (splitElements == 1 && TclGetString(pathPtr)[0] == '~') { 00704 Tcl_Obj *norm; 00705 00706 TclDecrRefCount(splitPtr); 00707 norm = Tcl_FSGetNormalizedPath(interp, pathPtr); 00708 if (norm == NULL) { 00709 return NULL; 00710 } 00711 splitPtr = Tcl_FSSplitPath(norm, &splitElements); 00712 Tcl_IncrRefCount(splitPtr); 00713 } 00714 if (portion == TCL_PATH_TAIL) { 00715 /* 00716 * Return the last component, unless it is the only component, and 00717 * it is the root of an absolute path. 00718 */ 00719 00720 if ((splitElements > 0) && ((splitElements > 1) || 00721 (Tcl_FSGetPathType(pathPtr) == TCL_PATH_RELATIVE))) { 00722 Tcl_ListObjIndex(NULL, splitPtr, splitElements-1, &resultPtr); 00723 } else { 00724 resultPtr = Tcl_NewObj(); 00725 } 00726 } else { 00727 /* 00728 * Return all but the last component. If there is only one 00729 * component, return it if the path was non-relative, otherwise 00730 * return the current directory. 00731 */ 00732 00733 if (splitElements > 1) { 00734 resultPtr = Tcl_FSJoinPath(splitPtr, splitElements - 1); 00735 } else if (splitElements == 0 || 00736 (Tcl_FSGetPathType(pathPtr) == TCL_PATH_RELATIVE)) { 00737 TclNewLiteralStringObj(resultPtr, "."); 00738 } else { 00739 Tcl_ListObjIndex(NULL, splitPtr, 0, &resultPtr); 00740 } 00741 } 00742 Tcl_IncrRefCount(resultPtr); 00743 TclDecrRefCount(splitPtr); 00744 return resultPtr; 00745 } 00746 } 00747 00748 /* 00749 * Simple helper function 00750 */ 00751 00752 static Tcl_Obj * 00753 GetExtension( 00754 Tcl_Obj *pathPtr) 00755 { 00756 const char *tail, *extension; 00757 Tcl_Obj *ret; 00758 00759 tail = TclGetString(pathPtr); 00760 extension = TclGetExtension(tail); 00761 if (extension == NULL) { 00762 ret = Tcl_NewObj(); 00763 } else { 00764 ret = Tcl_NewStringObj(extension, -1); 00765 } 00766 Tcl_IncrRefCount(ret); 00767 return ret; 00768 } 00769 00770 /* 00771 *--------------------------------------------------------------------------- 00772 * 00773 * Tcl_FSJoinPath -- 00774 * 00775 * This function takes the given Tcl_Obj, which should be a valid list, 00776 * and returns the path object given by considering the first 'elements' 00777 * elements as valid path segments (each path segment may be a complete 00778 * path, a partial path or just a single possible directory or file 00779 * name). If any path segment is actually an absolute path, then all 00780 * prior path segments are discarded. 00781 * 00782 * If elements < 0, we use the entire list that was given. 00783 * 00784 * It is possible that the returned object is actually an element of the 00785 * given list, so the caller should be careful to store a refCount to it 00786 * before freeing the list. 00787 * 00788 * Results: 00789 * Returns object with refCount of zero, (or if non-zero, it has 00790 * references elsewhere in Tcl). Either way, the caller must increment 00791 * its refCount before use. Note that in the case where the caller has 00792 * asked to join zero elements of the list, the return value will be an 00793 * empty-string Tcl_Obj. 00794 * 00795 * If the given listObj was invalid, then the calling routine has a bug, 00796 * and this function will just return NULL. 00797 * 00798 * Side effects: 00799 * None. 00800 * 00801 *--------------------------------------------------------------------------- 00802 */ 00803 00804 Tcl_Obj * 00805 Tcl_FSJoinPath( 00806 Tcl_Obj *listObj, /* Path elements to join, may have a zero 00807 * reference count. */ 00808 int elements) /* Number of elements to use (-1 = all) */ 00809 { 00810 Tcl_Obj *res; 00811 int i; 00812 Tcl_Filesystem *fsPtr = NULL; 00813 00814 if (elements < 0) { 00815 if (Tcl_ListObjLength(NULL, listObj, &elements) != TCL_OK) { 00816 return NULL; 00817 } 00818 } else { 00819 /* 00820 * Just make sure it is a valid list. 00821 */ 00822 00823 int listTest; 00824 00825 if (Tcl_ListObjLength(NULL, listObj, &listTest) != TCL_OK) { 00826 return NULL; 00827 } 00828 00829 /* 00830 * Correct this if it is too large, otherwise we will waste our time 00831 * joining null elements to the path. 00832 */ 00833 00834 if (elements > listTest) { 00835 elements = listTest; 00836 } 00837 } 00838 00839 res = NULL; 00840 00841 for (i = 0; i < elements; i++) { 00842 Tcl_Obj *elt, *driveName = NULL; 00843 int driveNameLength, strEltLen, length; 00844 Tcl_PathType type; 00845 char *strElt, *ptr; 00846 00847 Tcl_ListObjIndex(NULL, listObj, i, &elt); 00848 00849 /* 00850 * This is a special case where we can be much more efficient, where 00851 * we are joining a single relative path onto an object that is 00852 * already of path type. The 'TclNewFSPathObj' call below creates an 00853 * object which can be normalized more efficiently. Currently we only 00854 * use the special case when we have exactly two elements, but we 00855 * could expand that in the future. 00856 */ 00857 00858 if ((i == (elements-2)) && (i == 0) && (elt->typePtr == &tclFsPathType) 00859 && !(elt->bytes != NULL && (elt->bytes[0] == '\0'))) { 00860 Tcl_Obj *tail; 00861 Tcl_PathType type; 00862 00863 Tcl_ListObjIndex(NULL, listObj, i+1, &tail); 00864 type = TclGetPathType(tail, NULL, NULL, NULL); 00865 if (type == TCL_PATH_RELATIVE) { 00866 const char *str; 00867 int len; 00868 00869 str = Tcl_GetStringFromObj(tail, &len); 00870 if (len == 0) { 00871 /* 00872 * This happens if we try to handle the root volume '/'. 00873 * There's no need to return a special path object, when 00874 * the base itself is just fine! 00875 */ 00876 00877 if (res != NULL) { 00878 TclDecrRefCount(res); 00879 } 00880 return elt; 00881 } 00882 00883 /* 00884 * If it doesn't begin with '.' and is a unix path or it a 00885 * windows path without backslashes, then we can be very 00886 * efficient here. (In fact even a windows path with 00887 * backslashes can be joined efficiently, but the path object 00888 * would not have forward slashes only, and this would 00889 * therefore contradict our 'file join' documentation). 00890 */ 00891 00892 if (str[0] != '.' && ((tclPlatform != TCL_PLATFORM_WINDOWS) 00893 || (strchr(str, '\\') == NULL))) { 00894 /* 00895 * Finally, on Windows, 'file join' is defined to convert 00896 * all backslashes to forward slashes, so the base part 00897 * cannot have backslashes either. 00898 */ 00899 00900 if ((tclPlatform != TCL_PLATFORM_WINDOWS) 00901 || (strchr(Tcl_GetString(elt), '\\') == NULL)) { 00902 if (res != NULL) { 00903 TclDecrRefCount(res); 00904 } 00905 return TclNewFSPathObj(elt, str, len); 00906 } 00907 } 00908 00909 /* 00910 * Otherwise we don't have an easy join, and we must let the 00911 * more general code below handle things 00912 */ 00913 } else if (tclPlatform == TCL_PLATFORM_UNIX) { 00914 if (res != NULL) { 00915 TclDecrRefCount(res); 00916 } 00917 return tail; 00918 } else { 00919 const char *str = Tcl_GetString(tail); 00920 00921 if (tclPlatform == TCL_PLATFORM_WINDOWS) { 00922 if (strchr(str, '\\') == NULL) { 00923 if (res != NULL) { 00924 TclDecrRefCount(res); 00925 } 00926 return tail; 00927 } 00928 } 00929 } 00930 } 00931 strElt = Tcl_GetStringFromObj(elt, &strEltLen); 00932 type = TclGetPathType(elt, &fsPtr, &driveNameLength, &driveName); 00933 if (type != TCL_PATH_RELATIVE) { 00934 /* 00935 * Zero out the current result. 00936 */ 00937 00938 if (res != NULL) { 00939 TclDecrRefCount(res); 00940 } 00941 00942 if (driveName != NULL) { 00943 /* 00944 * We've been given a separate drive-name object, because the 00945 * prefix in 'elt' is not in a suitable format for us (e.g. it 00946 * may contain irrelevant multiple separators, like 00947 * C://///foo). 00948 */ 00949 00950 res = Tcl_DuplicateObj(driveName); 00951 TclDecrRefCount(driveName); 00952 00953 /* 00954 * Do not set driveName to NULL, because we will check its 00955 * value below (but we won't access the contents, since those 00956 * have been cleaned-up). 00957 */ 00958 } else { 00959 res = Tcl_NewStringObj(strElt, driveNameLength); 00960 } 00961 strElt += driveNameLength; 00962 } else if (driveName != NULL) { 00963 Tcl_DecrRefCount(driveName); 00964 } 00965 00966 /* 00967 * Optimisation block: if this is the last element to be examined, and 00968 * it is absolute or the only element, and the drive-prefix was ok (if 00969 * there is one), it might be that the path is already in a suitable 00970 * form to be returned. Then we can short-cut the rest of this 00971 * function. 00972 */ 00973 00974 if ((driveName == NULL) && (i == (elements - 1)) 00975 && (type != TCL_PATH_RELATIVE || res == NULL)) { 00976 /* 00977 * It's the last path segment. Perform a quick check if the path 00978 * is already in a suitable form. 00979 */ 00980 00981 if (tclPlatform == TCL_PLATFORM_WINDOWS) { 00982 if (strchr(strElt, '\\') != NULL) { 00983 goto noQuickReturn; 00984 } 00985 } 00986 ptr = strElt; 00987 while (*ptr != '\0') { 00988 if (*ptr == '/' && (ptr[1] == '/' || ptr[1] == '\0')) { 00989 /* 00990 * We have a repeated file separator, which means the path 00991 * is not in normalized form 00992 */ 00993 00994 goto noQuickReturn; 00995 } 00996 ptr++; 00997 } 00998 if (res != NULL) { 00999 TclDecrRefCount(res); 01000 } 01001 01002 /* 01003 * This element is just what we want to return already - no 01004 * further manipulation is requred. 01005 */ 01006 01007 return elt; 01008 } 01009 01010 /* 01011 * The path element was not of a suitable form to be returned as is. 01012 * We need to perform a more complex operation here. 01013 */ 01014 01015 noQuickReturn: 01016 if (res == NULL) { 01017 res = Tcl_NewObj(); 01018 ptr = Tcl_GetStringFromObj(res, &length); 01019 } else { 01020 ptr = Tcl_GetStringFromObj(res, &length); 01021 } 01022 01023 /* 01024 * Strip off any './' before a tilde, unless this is the beginning of 01025 * the path. 01026 */ 01027 01028 if (length > 0 && strEltLen > 0 && (strElt[0] == '.') && 01029 (strElt[1] == '/') && (strElt[2] == '~')) { 01030 strElt += 2; 01031 } 01032 01033 /* 01034 * A NULL value for fsPtr at this stage basically means we're trying 01035 * to join a relative path onto something which is also relative (or 01036 * empty). There's nothing particularly wrong with that. 01037 */ 01038 01039 if (*strElt == '\0') { 01040 continue; 01041 } 01042 01043 if (fsPtr == &tclNativeFilesystem || fsPtr == NULL) { 01044 TclpNativeJoinPath(res, strElt); 01045 } else { 01046 char separator = '/'; 01047 int needsSep = 0; 01048 01049 if (fsPtr->filesystemSeparatorProc != NULL) { 01050 Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(res); 01051 01052 if (sep != NULL) { 01053 separator = TclGetString(sep)[0]; 01054 } 01055 } 01056 01057 if (length > 0 && ptr[length -1] != '/') { 01058 Tcl_AppendToObj(res, &separator, 1); 01059 length++; 01060 } 01061 Tcl_SetObjLength(res, length + (int) strlen(strElt)); 01062 01063 ptr = TclGetString(res) + length; 01064 for (; *strElt != '\0'; strElt++) { 01065 if (*strElt == separator) { 01066 while (strElt[1] == separator) { 01067 strElt++; 01068 } 01069 if (strElt[1] != '\0') { 01070 if (needsSep) { 01071 *ptr++ = separator; 01072 } 01073 } 01074 } else { 01075 *ptr++ = *strElt; 01076 needsSep = 1; 01077 } 01078 } 01079 length = ptr - TclGetString(res); 01080 Tcl_SetObjLength(res, length); 01081 } 01082 } 01083 if (res == NULL) { 01084 res = Tcl_NewObj(); 01085 } 01086 return res; 01087 } 01088 01089 /* 01090 *--------------------------------------------------------------------------- 01091 * 01092 * Tcl_FSConvertToPathType -- 01093 * 01094 * This function tries to convert the given Tcl_Obj to a valid Tcl path 01095 * type, taking account of the fact that the cwd may have changed even if 01096 * this object is already supposedly of the correct type. 01097 * 01098 * The filename may begin with "~" (to indicate current user's home 01099 * directory) or "~<user>" (to indicate any user's home directory). 01100 * 01101 * Results: 01102 * Standard Tcl error code. 01103 * 01104 * Side effects: 01105 * The old representation may be freed, and new memory allocated. 01106 * 01107 *--------------------------------------------------------------------------- 01108 */ 01109 01110 int 01111 Tcl_FSConvertToPathType( 01112 Tcl_Interp *interp, /* Interpreter in which to store error message 01113 * (if necessary). */ 01114 Tcl_Obj *pathPtr) /* Object to convert to a valid, current path 01115 * type. */ 01116 { 01117 /* 01118 * While it is bad practice to examine an object's type directly, this is 01119 * actually the best thing to do here. The reason is that if we are 01120 * converting this object to FsPath type for the first time, we don't need 01121 * to worry whether the 'cwd' has changed. On the other hand, if this 01122 * object is already of FsPath type, and is a relative path, we do have to 01123 * worry about the cwd. If the cwd has changed, we must recompute the 01124 * path. 01125 */ 01126 01127 if (pathPtr->typePtr == &tclFsPathType) { 01128 if (TclFSEpochOk(PATHOBJ(pathPtr)->filesystemEpoch)) { 01129 return TCL_OK; 01130 } 01131 01132 if (pathPtr->bytes == NULL) { 01133 UpdateStringOfFsPath(pathPtr); 01134 } 01135 FreeFsPathInternalRep(pathPtr); 01136 pathPtr->typePtr = NULL; 01137 } 01138 01139 return Tcl_ConvertToType(interp, pathPtr, &tclFsPathType); 01140 01141 /* 01142 * We used to have more complex code here: 01143 * 01144 * FsPath *fsPathPtr = PATHOBJ(pathPtr); 01145 * if (fsPathPtr->cwdPtr == NULL || PATHFLAGS(pathPtr) != 0) { 01146 * return TCL_OK; 01147 * } else { 01148 * if (TclFSCwdPointerEquals(&fsPathPtr->cwdPtr)) { 01149 * return TCL_OK; 01150 * } else { 01151 * if (pathPtr->bytes == NULL) { 01152 * UpdateStringOfFsPath(pathPtr); 01153 * } 01154 * FreeFsPathInternalRep(pathPtr); 01155 * pathPtr->typePtr = NULL; 01156 * return Tcl_ConvertToType(interp, pathPtr, &tclFsPathType); 01157 * } 01158 * } 01159 * 01160 * But we no longer believe this is necessary. 01161 */ 01162 } 01163 01164 /* 01165 * Helper function for normalization. 01166 */ 01167 01168 static int 01169 IsSeparatorOrNull( 01170 int ch) 01171 { 01172 if (ch == 0) { 01173 return 1; 01174 } 01175 switch (tclPlatform) { 01176 case TCL_PLATFORM_UNIX: 01177 return (ch == '/' ? 1 : 0); 01178 case TCL_PLATFORM_WINDOWS: 01179 return ((ch == '/' || ch == '\\') ? 1 : 0); 01180 } 01181 return 0; 01182 } 01183 01184 /* 01185 * Helper function for SetFsPathFromAny. Returns position of first directory 01186 * delimiter in the path. If no separator is found, then returns the position 01187 * of the end of the string. 01188 */ 01189 01190 static int 01191 FindSplitPos( 01192 const char *path, 01193 int separator) 01194 { 01195 int count = 0; 01196 switch (tclPlatform) { 01197 case TCL_PLATFORM_UNIX: 01198 while (path[count] != 0) { 01199 if (path[count] == separator) { 01200 return count; 01201 } 01202 count++; 01203 } 01204 break; 01205 01206 case TCL_PLATFORM_WINDOWS: 01207 while (path[count] != 0) { 01208 if (path[count] == separator || path[count] == '\\') { 01209 return count; 01210 } 01211 count++; 01212 } 01213 break; 01214 } 01215 return count; 01216 } 01217 01218 /* 01219 *--------------------------------------------------------------------------- 01220 * 01221 * TclNewFSPathObj -- 01222 * 01223 * Creates a path object whose string representation is '[file join 01224 * dirPtr addStrRep]', but does so in a way that allows for more 01225 * efficient creation and caching of normalized paths, and more efficient 01226 * 'file dirname', 'file tail', etc. 01227 * 01228 * Assumptions: 01229 * 'dirPtr' must be an absolute path. 'len' may not be zero. 01230 * 01231 * Results: 01232 * The new Tcl object, with refCount zero. 01233 * 01234 * Side effects: 01235 * Memory is allocated. 'dirPtr' gets an additional refCount. 01236 * 01237 *--------------------------------------------------------------------------- 01238 */ 01239 01240 Tcl_Obj * 01241 TclNewFSPathObj( 01242 Tcl_Obj *dirPtr, 01243 const char *addStrRep, 01244 int len) 01245 { 01246 FsPath *fsPathPtr; 01247 Tcl_Obj *pathPtr; 01248 ThreadSpecificData *tsdPtr; 01249 01250 tsdPtr = TCL_TSD_INIT(&tclFsDataKey); 01251 01252 pathPtr = Tcl_NewObj(); 01253 fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath)); 01254 01255 /* 01256 * Set up the path. 01257 */ 01258 01259 fsPathPtr->translatedPathPtr = NULL; 01260 fsPathPtr->normPathPtr = Tcl_NewStringObj(addStrRep, len); 01261 Tcl_IncrRefCount(fsPathPtr->normPathPtr); 01262 fsPathPtr->cwdPtr = dirPtr; 01263 Tcl_IncrRefCount(dirPtr); 01264 fsPathPtr->nativePathPtr = NULL; 01265 fsPathPtr->fsRecPtr = NULL; 01266 fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch; 01267 01268 SETPATHOBJ(pathPtr, fsPathPtr); 01269 PATHFLAGS(pathPtr) = TCLPATH_APPENDED; 01270 pathPtr->typePtr = &tclFsPathType; 01271 pathPtr->bytes = NULL; 01272 pathPtr->length = 0; 01273 01274 return pathPtr; 01275 } 01276 01277 /* 01278 *--------------------------------------------------------------------------- 01279 * 01280 * TclFSMakePathRelative -- 01281 * 01282 * Only for internal use. 01283 * 01284 * Takes a path and a directory, where we _assume_ both path and 01285 * directory are absolute, normalized and that the path lies inside the 01286 * directory. Returns a Tcl_Obj representing filename of the path 01287 * relative to the directory. 01288 * 01289 * In the case where the resulting path would start with a '~', we take 01290 * special care to return an ordinary string. This means to use that path 01291 * (and not have it interpreted as a user name), one must prepend './'. 01292 * This may seem strange, but that is how 'glob' is currently defined. 01293 * 01294 * Results: 01295 * NULL on error, otherwise a valid object, typically with refCount of 01296 * zero, which it is assumed the caller will increment. 01297 * 01298 * Side effects: 01299 * The old representation may be freed, and new memory allocated. 01300 * 01301 *--------------------------------------------------------------------------- 01302 */ 01303 01304 Tcl_Obj * 01305 TclFSMakePathRelative( 01306 Tcl_Interp *interp, /* Used for error reporting if not NULL. */ 01307 Tcl_Obj *pathPtr, /* The path we have. */ 01308 Tcl_Obj *cwdPtr) /* Make it relative to this. */ 01309 { 01310 int cwdLen, len; 01311 const char *tempStr; 01312 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey); 01313 01314 if (pathPtr->typePtr == &tclFsPathType) { 01315 FsPath *fsPathPtr = PATHOBJ(pathPtr); 01316 01317 if (PATHFLAGS(pathPtr) != 0 01318 && fsPathPtr->cwdPtr == cwdPtr) { 01319 pathPtr = fsPathPtr->normPathPtr; 01320 01321 /* 01322 * Free old representation. 01323 */ 01324 01325 if (pathPtr->typePtr != NULL) { 01326 if (pathPtr->bytes == NULL) { 01327 if (pathPtr->typePtr->updateStringProc == NULL) { 01328 if (interp != NULL) { 01329 Tcl_ResetResult(interp); 01330 Tcl_AppendResult(interp, "can't find object" 01331 "string representation", NULL); 01332 } 01333 return NULL; 01334 } 01335 pathPtr->typePtr->updateStringProc(pathPtr); 01336 } 01337 TclFreeIntRep(pathPtr); 01338 } 01339 01340 /* 01341 * Now pathPtr is a string object. 01342 */ 01343 01344 if (Tcl_GetString(pathPtr)[0] == '~') { 01345 /* 01346 * If the first character of the path is a tilde, we must just 01347 * return the path as is, to agree with the defined behaviour 01348 * of 'glob'. 01349 */ 01350 01351 return pathPtr; 01352 } 01353 01354 fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath)); 01355 01356 /* 01357 * Circular reference, by design. 01358 */ 01359 01360 fsPathPtr->translatedPathPtr = pathPtr; 01361 fsPathPtr->normPathPtr = NULL; 01362 fsPathPtr->cwdPtr = cwdPtr; 01363 Tcl_IncrRefCount(cwdPtr); 01364 fsPathPtr->nativePathPtr = NULL; 01365 fsPathPtr->fsRecPtr = NULL; 01366 fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch; 01367 01368 SETPATHOBJ(pathPtr, fsPathPtr); 01369 PATHFLAGS(pathPtr) = 0; 01370 pathPtr->typePtr = &tclFsPathType; 01371 01372 return pathPtr; 01373 } 01374 } 01375 01376 /* 01377 * We know the cwd is a normalised object which does not end in a 01378 * directory delimiter, unless the cwd is the name of a volume, in which 01379 * case it will end in a delimiter! We handle this situation here. A 01380 * better test than the '!= sep' might be to simply check if 'cwd' is a 01381 * root volume. 01382 * 01383 * Note that if we get this wrong, we will strip off either too much or 01384 * too little below, leading to wrong answers returned by glob. 01385 */ 01386 01387 tempStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen); 01388 01389 /* 01390 * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the 01391 * Windows special case? Perhaps we should just check if cwd is a root 01392 * volume. 01393 */ 01394 01395 switch (tclPlatform) { 01396 case TCL_PLATFORM_UNIX: 01397 if (tempStr[cwdLen-1] != '/') { 01398 cwdLen++; 01399 } 01400 break; 01401 case TCL_PLATFORM_WINDOWS: 01402 if (tempStr[cwdLen-1] != '/' && tempStr[cwdLen-1] != '\\') { 01403 cwdLen++; 01404 } 01405 break; 01406 } 01407 tempStr = Tcl_GetStringFromObj(pathPtr, &len); 01408 01409 return Tcl_NewStringObj(tempStr + cwdLen, len - cwdLen); 01410 } 01411 01412 /* 01413 *--------------------------------------------------------------------------- 01414 * 01415 * TclFSMakePathFromNormalized -- 01416 * 01417 * Like SetFsPathFromAny, but assumes the given object is an absolute 01418 * normalized path. Only for internal use. 01419 * 01420 * Results: 01421 * Standard Tcl error code. 01422 * 01423 * Side effects: 01424 * The old representation may be freed, and new memory allocated. 01425 * 01426 *--------------------------------------------------------------------------- 01427 */ 01428 01429 int 01430 TclFSMakePathFromNormalized( 01431 Tcl_Interp *interp, /* Used for error reporting if not NULL. */ 01432 Tcl_Obj *pathPtr, /* The object to convert. */ 01433 ClientData nativeRep) /* The native rep for the object, if known 01434 * else NULL. */ 01435 { 01436 FsPath *fsPathPtr; 01437 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey); 01438 01439 if (pathPtr->typePtr == &tclFsPathType) { 01440 return TCL_OK; 01441 } 01442 01443 /* 01444 * Free old representation 01445 */ 01446 01447 if (pathPtr->typePtr != NULL) { 01448 if (pathPtr->bytes == NULL) { 01449 if (pathPtr->typePtr->updateStringProc == NULL) { 01450 if (interp != NULL) { 01451 Tcl_ResetResult(interp); 01452 Tcl_AppendResult(interp, "can't find object" 01453 "string representation", NULL); 01454 } 01455 return TCL_ERROR; 01456 } 01457 pathPtr->typePtr->updateStringProc(pathPtr); 01458 } 01459 TclFreeIntRep(pathPtr); 01460 } 01461 01462 fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath)); 01463 01464 /* 01465 * It's a pure normalized absolute path. 01466 */ 01467 01468 fsPathPtr->translatedPathPtr = NULL; 01469 01470 /* 01471 * Circular reference by design. 01472 */ 01473 01474 fsPathPtr->normPathPtr = pathPtr; 01475 fsPathPtr->cwdPtr = NULL; 01476 fsPathPtr->nativePathPtr = nativeRep; 01477 fsPathPtr->fsRecPtr = NULL; 01478 fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch; 01479 01480 SETPATHOBJ(pathPtr, fsPathPtr); 01481 PATHFLAGS(pathPtr) = 0; 01482 pathPtr->typePtr = &tclFsPathType; 01483 01484 return TCL_OK; 01485 } 01486 01487 /* 01488 *--------------------------------------------------------------------------- 01489 * 01490 * Tcl_FSNewNativePath -- 01491 * 01492 * This function performs the something like the reverse of the usual 01493 * obj->path->nativerep conversions. If some code retrieves a path in 01494 * native form (from, e.g. readlink or a native dialog), and that path is 01495 * to be used at the Tcl level, then calling this function is an 01496 * efficient way of creating the appropriate path object type. 01497 * 01498 * Any memory which is allocated for 'clientData' should be retained 01499 * until clientData is passed to the filesystem's freeInternalRepProc 01500 * when it can be freed. The built in platform-specific filesystems use 01501 * 'ckalloc' to allocate clientData, and ckfree to free it. 01502 * 01503 * Results: 01504 * NULL or a valid path object pointer, with refCount zero. 01505 * 01506 * Side effects: 01507 * New memory may be allocated. 01508 * 01509 *--------------------------------------------------------------------------- 01510 */ 01511 01512 Tcl_Obj * 01513 Tcl_FSNewNativePath( 01514 Tcl_Filesystem *fromFilesystem, 01515 ClientData clientData) 01516 { 01517 Tcl_Obj *pathPtr; 01518 FsPath *fsPathPtr; 01519 01520 FilesystemRecord *fsFromPtr; 01521 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey); 01522 01523 pathPtr = TclFSInternalToNormalized(fromFilesystem, clientData, 01524 &fsFromPtr); 01525 if (pathPtr == NULL) { 01526 return NULL; 01527 } 01528 01529 /* 01530 * Free old representation; shouldn't normally be any, but best to be 01531 * safe. 01532 */ 01533 01534 if (pathPtr->typePtr != NULL) { 01535 if (pathPtr->bytes == NULL) { 01536 if (pathPtr->typePtr->updateStringProc == NULL) { 01537 return NULL; 01538 } 01539 pathPtr->typePtr->updateStringProc(pathPtr); 01540 } 01541 TclFreeIntRep(pathPtr); 01542 } 01543 01544 fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath)); 01545 01546 fsPathPtr->translatedPathPtr = NULL; 01547 01548 /* 01549 * Circular reference, by design. 01550 */ 01551 01552 fsPathPtr->normPathPtr = pathPtr; 01553 fsPathPtr->cwdPtr = NULL; 01554 fsPathPtr->nativePathPtr = clientData; 01555 fsPathPtr->fsRecPtr = fsFromPtr; 01556 fsPathPtr->fsRecPtr->fileRefCount++; 01557 fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch; 01558 01559 SETPATHOBJ(pathPtr, fsPathPtr); 01560 PATHFLAGS(pathPtr) = 0; 01561 pathPtr->typePtr = &tclFsPathType; 01562 01563 return pathPtr; 01564 } 01565 01566 /* 01567 *--------------------------------------------------------------------------- 01568 * 01569 * Tcl_FSGetTranslatedPath -- 01570 * 01571 * This function attempts to extract the translated path from the given 01572 * Tcl_Obj. If the translation succeeds (i.e. the object is a valid 01573 * path), then it is returned. Otherwise NULL will be returned, and an 01574 * error message may be left in the interpreter (if it is non-NULL) 01575 * 01576 * Results: 01577 * NULL or a valid Tcl_Obj pointer. 01578 * 01579 * Side effects: 01580 * Only those of 'Tcl_FSConvertToPathType' 01581 * 01582 *--------------------------------------------------------------------------- 01583 */ 01584 01585 Tcl_Obj * 01586 Tcl_FSGetTranslatedPath( 01587 Tcl_Interp *interp, 01588 Tcl_Obj *pathPtr) 01589 { 01590 Tcl_Obj *retObj = NULL; 01591 FsPath *srcFsPathPtr; 01592 01593 if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) { 01594 return NULL; 01595 } 01596 srcFsPathPtr = PATHOBJ(pathPtr); 01597 if (srcFsPathPtr->translatedPathPtr == NULL) { 01598 if (PATHFLAGS(pathPtr) != 0) { 01599 retObj = Tcl_FSGetNormalizedPath(interp, pathPtr); 01600 } else { 01601 /* 01602 * It is a pure absolute, normalized path object. This is 01603 * something like being a 'pure list'. The object's string, 01604 * translatedPath and normalizedPath are all identical. 01605 */ 01606 01607 retObj = srcFsPathPtr->normPathPtr; 01608 } 01609 } else { 01610 /* 01611 * It is an ordinary path object. 01612 */ 01613 01614 retObj = srcFsPathPtr->translatedPathPtr; 01615 } 01616 01617 if (retObj != NULL) { 01618 Tcl_IncrRefCount(retObj); 01619 } 01620 return retObj; 01621 } 01622 01623 /* 01624 *--------------------------------------------------------------------------- 01625 * 01626 * Tcl_FSGetTranslatedStringPath -- 01627 * 01628 * This function attempts to extract the translated path from the given 01629 * Tcl_Obj. If the translation succeeds (i.e. the object is a valid 01630 * path), then the path is returned. Otherwise NULL will be returned, and 01631 * an error message may be left in the interpreter (if it is non-NULL) 01632 * 01633 * Results: 01634 * NULL or a valid string. 01635 * 01636 * Side effects: 01637 * Only those of 'Tcl_FSConvertToPathType' 01638 * 01639 *--------------------------------------------------------------------------- 01640 */ 01641 01642 const char * 01643 Tcl_FSGetTranslatedStringPath( 01644 Tcl_Interp *interp, 01645 Tcl_Obj *pathPtr) 01646 { 01647 Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr); 01648 01649 if (transPtr != NULL) { 01650 int len; 01651 const char *orig = Tcl_GetStringFromObj(transPtr, &len); 01652 char *result = (char *) ckalloc((unsigned) len+1); 01653 01654 memcpy(result, orig, (size_t) len+1); 01655 TclDecrRefCount(transPtr); 01656 return result; 01657 } 01658 01659 return NULL; 01660 } 01661 01662 /* 01663 *--------------------------------------------------------------------------- 01664 * 01665 * Tcl_FSGetNormalizedPath -- 01666 * 01667 * This important function attempts to extract from the given Tcl_Obj a 01668 * unique normalised path representation, whose string value can be used 01669 * as a unique identifier for the file. 01670 * 01671 * Results: 01672 * NULL or a valid path object pointer. 01673 * 01674 * Side effects: 01675 * New memory may be allocated. The Tcl 'errno' may be modified in the 01676 * process of trying to examine various path possibilities. 01677 * 01678 *--------------------------------------------------------------------------- 01679 */ 01680 01681 Tcl_Obj * 01682 Tcl_FSGetNormalizedPath( 01683 Tcl_Interp *interp, 01684 Tcl_Obj *pathPtr) 01685 { 01686 FsPath *fsPathPtr; 01687 01688 if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) { 01689 return NULL; 01690 } 01691 fsPathPtr = PATHOBJ(pathPtr); 01692 01693 if (PATHFLAGS(pathPtr) != 0) { 01694 /* 01695 * This is a special path object which is the result of something like 01696 * 'file join' 01697 */ 01698 01699 Tcl_Obj *dir, *copy; 01700 int cwdLen; 01701 int pathType; 01702 const char *cwdStr; 01703 ClientData clientData = NULL; 01704 01705 pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr); 01706 dir = Tcl_FSGetNormalizedPath(interp, fsPathPtr->cwdPtr); 01707 if (dir == NULL) { 01708 return NULL; 01709 } 01710 if (pathPtr->bytes == NULL) { 01711 UpdateStringOfFsPath(pathPtr); 01712 } 01713 copy = Tcl_DuplicateObj(dir); 01714 Tcl_IncrRefCount(copy); 01715 Tcl_IncrRefCount(dir); 01716 01717 /* 01718 * We now own a reference on both 'dir' and 'copy' 01719 */ 01720 01721 cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); 01722 01723 /* 01724 * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about 01725 * the Windows special case? Perhaps we should just check if cwd is a 01726 * root volume. We should never get cwdLen == 0 in this code path. 01727 */ 01728 01729 switch (tclPlatform) { 01730 case TCL_PLATFORM_UNIX: 01731 if (cwdStr[cwdLen-1] != '/') { 01732 Tcl_AppendToObj(copy, "/", 1); 01733 cwdLen++; 01734 } 01735 break; 01736 case TCL_PLATFORM_WINDOWS: 01737 if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') { 01738 Tcl_AppendToObj(copy, "/", 1); 01739 cwdLen++; 01740 } 01741 break; 01742 } 01743 Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr); 01744 01745 /* 01746 * Normalize the combined string, but only starting after the end of 01747 * the previously normalized 'dir'. This should be much faster! We use 01748 * 'cwdLen-1' so that we are already pointing at the dir-separator 01749 * that we know about. The normalization code will actually start off 01750 * directly after that separator. 01751 */ 01752 01753 TclFSNormalizeToUniquePath(interp, copy, cwdLen-1, 01754 (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL)); 01755 01756 /* 01757 * Now we need to construct the new path object 01758 */ 01759 01760 if (pathType == TCL_PATH_RELATIVE) { 01761 Tcl_Obj *origDir = fsPathPtr->cwdPtr; 01762 FsPath *origDirFsPathPtr = PATHOBJ(origDir); 01763 01764 fsPathPtr->cwdPtr = origDirFsPathPtr->cwdPtr; 01765 Tcl_IncrRefCount(fsPathPtr->cwdPtr); 01766 01767 TclDecrRefCount(fsPathPtr->normPathPtr); 01768 fsPathPtr->normPathPtr = copy; 01769 01770 /* 01771 * That's our reference to copy used. 01772 */ 01773 01774 TclDecrRefCount(dir); 01775 TclDecrRefCount(origDir); 01776 } else { 01777 TclDecrRefCount(fsPathPtr->cwdPtr); 01778 fsPathPtr->cwdPtr = NULL; 01779 TclDecrRefCount(fsPathPtr->normPathPtr); 01780 fsPathPtr->normPathPtr = copy; 01781 01782 /* 01783 * That's our reference to copy used. 01784 */ 01785 01786 TclDecrRefCount(dir); 01787 } 01788 if (clientData != NULL) { 01789 fsPathPtr->nativePathPtr = clientData; 01790 } 01791 PATHFLAGS(pathPtr) = 0; 01792 } 01793 01794 /* 01795 * Ensure cwd hasn't changed. 01796 */ 01797 01798 if (fsPathPtr->cwdPtr != NULL) { 01799 if (!TclFSCwdPointerEquals(&fsPathPtr->cwdPtr)) { 01800 if (pathPtr->bytes == NULL) { 01801 UpdateStringOfFsPath(pathPtr); 01802 } 01803 FreeFsPathInternalRep(pathPtr); 01804 pathPtr->typePtr = NULL; 01805 if (Tcl_ConvertToType(interp, pathPtr, &tclFsPathType) != TCL_OK) { 01806 return NULL; 01807 } 01808 fsPathPtr = PATHOBJ(pathPtr); 01809 } else if (fsPathPtr->normPathPtr == NULL) { 01810 int cwdLen; 01811 Tcl_Obj *copy; 01812 const char *cwdStr; 01813 ClientData clientData = NULL; 01814 01815 copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr); 01816 Tcl_IncrRefCount(copy); 01817 cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); 01818 01819 /* 01820 * Should we perhaps use 'Tcl_FSPathSeparator'? But then what 01821 * about the Windows special case? Perhaps we should just check if 01822 * cwd is a root volume. We should never get cwdLen == 0 in this 01823 * code path. 01824 */ 01825 01826 switch (tclPlatform) { 01827 case TCL_PLATFORM_UNIX: 01828 if (cwdStr[cwdLen-1] != '/') { 01829 Tcl_AppendToObj(copy, "/", 1); 01830 cwdLen++; 01831 } 01832 break; 01833 case TCL_PLATFORM_WINDOWS: 01834 if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') { 01835 Tcl_AppendToObj(copy, "/", 1); 01836 cwdLen++; 01837 } 01838 break; 01839 } 01840 Tcl_AppendObjToObj(copy, pathPtr); 01841 01842 /* 01843 * Normalize the combined string, but only starting after the end 01844 * of the previously normalized 'dir'. This should be much faster! 01845 */ 01846 01847 TclFSNormalizeToUniquePath(interp, copy, cwdLen-1, 01848 (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL)); 01849 fsPathPtr->normPathPtr = copy; 01850 if (clientData != NULL) { 01851 fsPathPtr->nativePathPtr = clientData; 01852 } 01853 } 01854 } 01855 if (fsPathPtr->normPathPtr == NULL) { 01856 ClientData clientData = NULL; 01857 Tcl_Obj *useThisCwd = NULL; 01858 01859 /* 01860 * Since normPathPtr is NULL, but this is a valid path object, we know 01861 * that the translatedPathPtr cannot be NULL. 01862 */ 01863 01864 Tcl_Obj *absolutePath = fsPathPtr->translatedPathPtr; 01865 const char *path = TclGetString(absolutePath); 01866 Tcl_IncrRefCount(absolutePath); 01867 01868 /* 01869 * We have to be a little bit careful here to avoid infinite loops 01870 * we're asking Tcl_FSGetPathType to return the path's type, but that 01871 * call can actually result in a lot of other filesystem action, which 01872 * might loop back through here. 01873 */ 01874 01875 if (path[0] != '\0') { 01876 /* 01877 * We don't ask for the type of 'pathPtr' here, because that is 01878 * not correct for our purposes when we have a path like '~'. Tcl 01879 * has a bit of a contradiction in that '~' paths are defined as 01880 * 'absolute', but in reality can be just about anything, 01881 * depending on how env(HOME) is set. 01882 */ 01883 01884 Tcl_PathType type = Tcl_FSGetPathType(absolutePath); 01885 01886 if (type == TCL_PATH_RELATIVE) { 01887 useThisCwd = Tcl_FSGetCwd(interp); 01888 01889 if (useThisCwd == NULL) { 01890 return NULL; 01891 } 01892 01893 Tcl_DecrRefCount(absolutePath); 01894 absolutePath = Tcl_FSJoinToPath(useThisCwd, 1, &absolutePath); 01895 Tcl_IncrRefCount(absolutePath); 01896 01897 /* 01898 * We have a refCount on the cwd. 01899 */ 01900 #ifdef __WIN32__ 01901 } else if (type == TCL_PATH_VOLUME_RELATIVE) { 01902 /* 01903 * Only Windows has volume-relative paths. 01904 */ 01905 01906 Tcl_DecrRefCount(absolutePath); 01907 absolutePath = TclWinVolumeRelativeNormalize(interp, 01908 path, &useThisCwd); 01909 if (absolutePath == NULL) { 01910 return NULL; 01911 } 01912 #endif /* __WIN32__ */ 01913 } 01914 } 01915 01916 /* 01917 * Already has refCount incremented. 01918 */ 01919 01920 fsPathPtr->normPathPtr = TclFSNormalizeAbsolutePath(interp, 01921 absolutePath, 01922 (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL)); 01923 if (0 && (clientData != NULL)) { 01924 fsPathPtr->nativePathPtr = 01925 (*fsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc)(clientData); 01926 } 01927 01928 /* 01929 * Check if path is pure normalized (this can only be the case if it 01930 * is an absolute path). 01931 */ 01932 01933 if (useThisCwd == NULL) { 01934 if (!strcmp(TclGetString(fsPathPtr->normPathPtr), 01935 TclGetString(pathPtr))) { 01936 /* 01937 * The path was already normalized. Get rid of the duplicate. 01938 */ 01939 01940 TclDecrRefCount(fsPathPtr->normPathPtr); 01941 01942 /* 01943 * We do *not* increment the refCount for this circular 01944 * reference. 01945 */ 01946 01947 fsPathPtr->normPathPtr = pathPtr; 01948 } 01949 } else { 01950 /* 01951 * We just need to free an object we allocated above for relative 01952 * paths (this was returned by Tcl_FSJoinToPath above), and then 01953 * of course store the cwd. 01954 */ 01955 01956 fsPathPtr->cwdPtr = useThisCwd; 01957 } 01958 TclDecrRefCount(absolutePath); 01959 } 01960 01961 return fsPathPtr->normPathPtr; 01962 } 01963 01964 /* 01965 *--------------------------------------------------------------------------- 01966 * 01967 * Tcl_FSGetInternalRep -- 01968 * 01969 * Extract the internal representation of a given path object, in the 01970 * given filesystem. If the path object belongs to a different 01971 * filesystem, we return NULL. 01972 * 01973 * If the internal representation is currently NULL, we attempt to 01974 * generate it, by calling the filesystem's 01975 * 'Tcl_FSCreateInternalRepProc'. 01976 * 01977 * Results: 01978 * NULL or a valid internal representation. 01979 * 01980 * Side effects: 01981 * An attempt may be made to convert the object. 01982 * 01983 *--------------------------------------------------------------------------- 01984 */ 01985 01986 ClientData 01987 Tcl_FSGetInternalRep( 01988 Tcl_Obj *pathPtr, 01989 Tcl_Filesystem *fsPtr) 01990 { 01991 FsPath *srcFsPathPtr; 01992 01993 if (Tcl_FSConvertToPathType(NULL, pathPtr) != TCL_OK) { 01994 return NULL; 01995 } 01996 srcFsPathPtr = PATHOBJ(pathPtr); 01997 01998 /* 01999 * We will only return the native representation for the caller's 02000 * filesystem. Otherwise we will simply return NULL. This means that there 02001 * must be a unique bi-directional mapping between paths and filesystems, 02002 * and that this mapping will not allow 'remapped' files -- files which 02003 * are in one filesystem but mapped into another. Another way of putting 02004 * this is that 'stacked' filesystems are not allowed. We recognise that 02005 * this is a potentially useful feature for the future. 02006 * 02007 * Even something simple like a 'pass through' filesystem which logs all 02008 * activity and passes the calls onto the native system would be nice, but 02009 * not easily achievable with the current implementation. 02010 */ 02011 02012 if (srcFsPathPtr->fsRecPtr == NULL) { 02013 /* 02014 * This only usually happens in wrappers like TclpStat which create a 02015 * string object and pass it to TclpObjStat. Code which calls the 02016 * Tcl_FS.. functions should always have a filesystem already set. 02017 * Whether this code path is legal or not depends on whether we decide 02018 * to allow external code to call the native filesystem directly. It 02019 * is at least safer to allow this sub-optimal routing. 02020 */ 02021 02022 Tcl_FSGetFileSystemForPath(pathPtr); 02023 02024 /* 02025 * If we fail through here, then the path is probably not a valid path 02026 * in the filesystsem, and is most likely to be a use of the empty 02027 * path "" via a direct call to one of the objectified interfaces 02028 * (e.g. from the Tcl testsuite). 02029 */ 02030 02031 srcFsPathPtr = PATHOBJ(pathPtr); 02032 if (srcFsPathPtr->fsRecPtr == NULL) { 02033 return NULL; 02034 } 02035 } 02036 02037 /* 02038 * There is still one possibility we should consider; if the file belongs 02039 * to a different filesystem, perhaps it is actually linked through to a 02040 * file in our own filesystem which we do care about. The way we can check 02041 * for this is we ask what filesystem this path belongs to. 02042 */ 02043 02044 if (fsPtr != srcFsPathPtr->fsRecPtr->fsPtr) { 02045 const Tcl_Filesystem *actualFs = Tcl_FSGetFileSystemForPath(pathPtr); 02046 02047 if (actualFs == fsPtr) { 02048 return Tcl_FSGetInternalRep(pathPtr, fsPtr); 02049 } 02050 return NULL; 02051 } 02052 02053 if (srcFsPathPtr->nativePathPtr == NULL) { 02054 Tcl_FSCreateInternalRepProc *proc; 02055 char *nativePathPtr; 02056 02057 proc = srcFsPathPtr->fsRecPtr->fsPtr->createInternalRepProc; 02058 if (proc == NULL) { 02059 return NULL; 02060 } 02061 02062 nativePathPtr = (*proc)(pathPtr); 02063 srcFsPathPtr = PATHOBJ(pathPtr); 02064 srcFsPathPtr->nativePathPtr = nativePathPtr; 02065 } 02066 02067 return srcFsPathPtr->nativePathPtr; 02068 } 02069 02070 /* 02071 *--------------------------------------------------------------------------- 02072 * 02073 * TclFSEnsureEpochOk -- 02074 * 02075 * This will ensure the pathPtr is up to date and can be converted into a 02076 * "path" type, and that we are able to generate a complete normalized 02077 * path which is used to determine the filesystem match. 02078 * 02079 * Results: 02080 * Standard Tcl return code. 02081 * 02082 * Side effects: 02083 * An attempt may be made to convert the object. 02084 * 02085 *--------------------------------------------------------------------------- 02086 */ 02087 02088 int 02089 TclFSEnsureEpochOk( 02090 Tcl_Obj *pathPtr, 02091 Tcl_Filesystem **fsPtrPtr) 02092 { 02093 FsPath *srcFsPathPtr; 02094 02095 if (pathPtr->typePtr != &tclFsPathType) { 02096 return TCL_OK; 02097 } 02098 02099 srcFsPathPtr = PATHOBJ(pathPtr); 02100 02101 /* 02102 * Check if the filesystem has changed in some way since this object's 02103 * internal representation was calculated. 02104 */ 02105 02106 if (!TclFSEpochOk(srcFsPathPtr->filesystemEpoch)) { 02107 /* 02108 * We have to discard the stale representation and recalculate it. 02109 */ 02110 02111 if (pathPtr->bytes == NULL) { 02112 UpdateStringOfFsPath(pathPtr); 02113 } 02114 FreeFsPathInternalRep(pathPtr); 02115 pathPtr->typePtr = NULL; 02116 if (SetFsPathFromAny(NULL, pathPtr) != TCL_OK) { 02117 return TCL_ERROR; 02118 } 02119 srcFsPathPtr = PATHOBJ(pathPtr); 02120 } 02121 02122 /* 02123 * Check whether the object is already assigned to a fs. 02124 */ 02125 02126 if (srcFsPathPtr->fsRecPtr != NULL) { 02127 *fsPtrPtr = srcFsPathPtr->fsRecPtr->fsPtr; 02128 } 02129 return TCL_OK; 02130 } 02131 02132 /* 02133 *--------------------------------------------------------------------------- 02134 * 02135 * TclFSSetPathDetails -- 02136 * 02137 * ??? 02138 * 02139 * Results: 02140 * None 02141 * 02142 * Side effects: 02143 * ??? 02144 * 02145 *--------------------------------------------------------------------------- 02146 */ 02147 02148 void 02149 TclFSSetPathDetails( 02150 Tcl_Obj *pathPtr, 02151 FilesystemRecord *fsRecPtr, 02152 ClientData clientData) 02153 { 02154 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey); 02155 FsPath *srcFsPathPtr; 02156 02157 /* 02158 * Make sure pathPtr is of the correct type. 02159 */ 02160 02161 if (pathPtr->typePtr != &tclFsPathType) { 02162 if (SetFsPathFromAny(NULL, pathPtr) != TCL_OK) { 02163 return; 02164 } 02165 } 02166 02167 srcFsPathPtr = PATHOBJ(pathPtr); 02168 srcFsPathPtr->fsRecPtr = fsRecPtr; 02169 srcFsPathPtr->nativePathPtr = clientData; 02170 srcFsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch; 02171 fsRecPtr->fileRefCount++; 02172 } 02173 02174 /* 02175 *--------------------------------------------------------------------------- 02176 * 02177 * Tcl_FSEqualPaths -- 02178 * 02179 * This function tests whether the two paths given are equal path 02180 * objects. If either or both is NULL, 0 is always returned. 02181 * 02182 * Results: 02183 * 1 or 0. 02184 * 02185 * Side effects: 02186 * None. 02187 * 02188 *--------------------------------------------------------------------------- 02189 */ 02190 02191 int 02192 Tcl_FSEqualPaths( 02193 Tcl_Obj *firstPtr, 02194 Tcl_Obj *secondPtr) 02195 { 02196 char *firstStr, *secondStr; 02197 int firstLen, secondLen, tempErrno; 02198 02199 if (firstPtr == secondPtr) { 02200 return 1; 02201 } 02202 02203 if (firstPtr == NULL || secondPtr == NULL) { 02204 return 0; 02205 } 02206 firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen); 02207 secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen); 02208 if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) { 02209 return 1; 02210 } 02211 02212 /* 02213 * Try the most thorough, correct method of comparing fully normalized 02214 * paths. 02215 */ 02216 02217 tempErrno = Tcl_GetErrno(); 02218 firstPtr = Tcl_FSGetNormalizedPath(NULL, firstPtr); 02219 secondPtr = Tcl_FSGetNormalizedPath(NULL, secondPtr); 02220 Tcl_SetErrno(tempErrno); 02221 02222 if (firstPtr == NULL || secondPtr == NULL) { 02223 return 0; 02224 } 02225 02226 firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen); 02227 secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen); 02228 return (firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0); 02229 } 02230 02231 /* 02232 *--------------------------------------------------------------------------- 02233 * 02234 * SetFsPathFromAny -- 02235 * 02236 * This function tries to convert the given Tcl_Obj to a valid Tcl path 02237 * type. 02238 * 02239 * The filename may begin with "~" (to indicate current user's home 02240 * directory) or "~<user>" (to indicate any user's home directory). 02241 * 02242 * Results: 02243 * Standard Tcl error code. 02244 * 02245 * Side effects: 02246 * The old representation may be freed, and new memory allocated. 02247 * 02248 *--------------------------------------------------------------------------- 02249 */ 02250 02251 static int 02252 SetFsPathFromAny( 02253 Tcl_Interp *interp, /* Used for error reporting if not NULL. */ 02254 Tcl_Obj *pathPtr) /* The object to convert. */ 02255 { 02256 int len; 02257 FsPath *fsPathPtr; 02258 Tcl_Obj *transPtr; 02259 char *name; 02260 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&tclFsDataKey); 02261 02262 if (pathPtr->typePtr == &tclFsPathType) { 02263 return TCL_OK; 02264 } 02265 02266 /* 02267 * First step is to translate the filename. This is similar to 02268 * Tcl_TranslateFilename, but shouldn't convert everything to windows 02269 * backslashes on that platform. The current implementation of this piece 02270 * is a slightly optimised version of the various Tilde/Split/Join stuff 02271 * to avoid multiple split/join operations. 02272 * 02273 * We remove any trailing directory separator. 02274 * 02275 * However, the split/join routines are quite complex, and one has to make 02276 * sure not to break anything on Unix or Win (fCmd.test, fileName.test and 02277 * cmdAH.test exercise most of the code). 02278 */ 02279 02280 name = Tcl_GetStringFromObj(pathPtr, &len); 02281 02282 /* 02283 * Handle tilde substitutions, if needed. 02284 */ 02285 02286 if (name[0] == '~') { 02287 char *expandedUser; 02288 Tcl_DString temp; 02289 int split; 02290 char separator='/'; 02291 02292 split = FindSplitPos(name, separator); 02293 if (split != len) { 02294 /* 02295 * We have multiple pieces '~user/foo/bar...' 02296 */ 02297 02298 name[split] = '\0'; 02299 } 02300 02301 /* 02302 * Do some tilde substitution. 02303 */ 02304 02305 if (name[1] == '\0') { 02306 /* 02307 * We have just '~' 02308 */ 02309 02310 const char *dir; 02311 Tcl_DString dirString; 02312 02313 if (split != len) { 02314 name[split] = separator; 02315 } 02316 02317 dir = TclGetEnv("HOME", &dirString); 02318 if (dir == NULL) { 02319 if (interp) { 02320 Tcl_ResetResult(interp); 02321 Tcl_AppendResult(interp, "couldn't find HOME environment " 02322 "variable to expand path", NULL); 02323 } 02324 return TCL_ERROR; 02325 } 02326 Tcl_DStringInit(&temp); 02327 Tcl_JoinPath(1, &dir, &temp); 02328 Tcl_DStringFree(&dirString); 02329 } else { 02330 /* 02331 * We have a user name '~user' 02332 */ 02333 02334 Tcl_DStringInit(&temp); 02335 if (TclpGetUserHome(name+1, &temp) == NULL) { 02336 if (interp != NULL) { 02337 Tcl_ResetResult(interp); 02338 Tcl_AppendResult(interp, "user \"", name+1, 02339 "\" doesn't exist", NULL); 02340 } 02341 Tcl_DStringFree(&temp); 02342 if (split != len) { 02343 name[split] = separator; 02344 } 02345 return TCL_ERROR; 02346 } 02347 if (split != len) { 02348 name[split] = separator; 02349 } 02350 } 02351 02352 expandedUser = Tcl_DStringValue(&temp); 02353 transPtr = Tcl_NewStringObj(expandedUser, Tcl_DStringLength(&temp)); 02354 02355 if (split != len) { 02356 /* 02357 * Join up the tilde substitution with the rest. 02358 */ 02359 02360 if (name[split+1] == separator) { 02361 /* 02362 * Somewhat tricky case like ~//foo/bar. Make use of 02363 * Split/Join machinery to get it right. Assumes all paths 02364 * beginning with ~ are part of the native filesystem. 02365 */ 02366 02367 int objc; 02368 Tcl_Obj **objv; 02369 Tcl_Obj *parts = TclpNativeSplitPath(pathPtr, NULL); 02370 02371 Tcl_ListObjGetElements(NULL, parts, &objc, &objv); 02372 02373 /* 02374 * Skip '~'. It's replaced by its expansion. 02375 */ 02376 02377 objc--; objv++; 02378 while (objc--) { 02379 TclpNativeJoinPath(transPtr, Tcl_GetString(*objv++)); 02380 } 02381 TclDecrRefCount(parts); 02382 } else { 02383 /* 02384 * Simple case. "rest" is relative path. Just join it. The 02385 * "rest" object will be freed when Tcl_FSJoinToPath returns 02386 * (unless something else claims a refCount on it). 02387 */ 02388 02389 Tcl_Obj *joined; 02390 Tcl_Obj *rest = Tcl_NewStringObj(name+split+1, -1); 02391 02392 Tcl_IncrRefCount(transPtr); 02393 joined = Tcl_FSJoinToPath(transPtr, 1, &rest); 02394 TclDecrRefCount(transPtr); 02395 transPtr = joined; 02396 } 02397 } 02398 Tcl_DStringFree(&temp); 02399 } else { 02400 transPtr = Tcl_FSJoinToPath(pathPtr, 0, NULL); 02401 } 02402 02403 #if defined(__CYGWIN__) && defined(__WIN32__) 02404 { 02405 extern int cygwin_conv_to_win32_path(const char *, char *); 02406 char winbuf[MAX_PATH+1]; 02407 02408 /* 02409 * In the Cygwin world, call conv_to_win32_path in order to use the 02410 * mount table to translate the file name into something Windows will 02411 * understand. Take care when converting empty strings! 02412 */ 02413 02414 name = Tcl_GetStringFromObj(transPtr, &len); 02415 if (len > 0) { 02416 cygwin_conv_to_win32_path(name, winbuf); 02417 TclWinNoBackslash(winbuf); 02418 Tcl_SetStringObj(transPtr, winbuf, -1); 02419 } 02420 } 02421 #endif /* __CYGWIN__ && __WIN32__ */ 02422 02423 /* 02424 * Now we have a translated filename in 'transPtr'. This will have forward 02425 * slashes on Windows, and will not contain any ~user sequences. 02426 */ 02427 02428 fsPathPtr = (FsPath *) ckalloc(sizeof(FsPath)); 02429 02430 fsPathPtr->translatedPathPtr = transPtr; 02431 if (transPtr != pathPtr) { 02432 Tcl_IncrRefCount(fsPathPtr->translatedPathPtr); 02433 } 02434 fsPathPtr->normPathPtr = NULL; 02435 fsPathPtr->cwdPtr = NULL; 02436 fsPathPtr->nativePathPtr = NULL; 02437 fsPathPtr->fsRecPtr = NULL; 02438 fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch; 02439 02440 /* 02441 * Free old representation before installing our new one. 02442 */ 02443 02444 TclFreeIntRep(pathPtr); 02445 SETPATHOBJ(pathPtr, fsPathPtr); 02446 PATHFLAGS(pathPtr) = 0; 02447 pathPtr->typePtr = &tclFsPathType; 02448 02449 return TCL_OK; 02450 } 02451 02452 static void 02453 FreeFsPathInternalRep( 02454 Tcl_Obj *pathPtr) /* Path object with internal rep to free. */ 02455 { 02456 FsPath *fsPathPtr = PATHOBJ(pathPtr); 02457 02458 if (fsPathPtr->translatedPathPtr != NULL) { 02459 if (fsPathPtr->translatedPathPtr != pathPtr) { 02460 TclDecrRefCount(fsPathPtr->translatedPathPtr); 02461 } 02462 } 02463 if (fsPathPtr->normPathPtr != NULL) { 02464 if (fsPathPtr->normPathPtr != pathPtr) { 02465 TclDecrRefCount(fsPathPtr->normPathPtr); 02466 } 02467 fsPathPtr->normPathPtr = NULL; 02468 } 02469 if (fsPathPtr->cwdPtr != NULL) { 02470 TclDecrRefCount(fsPathPtr->cwdPtr); 02471 } 02472 if (fsPathPtr->nativePathPtr != NULL && fsPathPtr->fsRecPtr != NULL) { 02473 Tcl_FSFreeInternalRepProc *freeProc = 02474 fsPathPtr->fsRecPtr->fsPtr->freeInternalRepProc; 02475 if (freeProc != NULL) { 02476 (*freeProc)(fsPathPtr->nativePathPtr); 02477 fsPathPtr->nativePathPtr = NULL; 02478 } 02479 } 02480 if (fsPathPtr->fsRecPtr != NULL) { 02481 fsPathPtr->fsRecPtr->fileRefCount--; 02482 if (fsPathPtr->fsRecPtr->fileRefCount <= 0) { 02483 /* 02484 * It has been unregistered already. 02485 */ 02486 02487 ckfree((char *) fsPathPtr->fsRecPtr); 02488 } 02489 } 02490 02491 ckfree((char*) fsPathPtr); 02492 } 02493 02494 static void 02495 DupFsPathInternalRep( 02496 Tcl_Obj *srcPtr, /* Path obj with internal rep to copy. */ 02497 Tcl_Obj *copyPtr) /* Path obj with internal rep to set. */ 02498 { 02499 FsPath *srcFsPathPtr = PATHOBJ(srcPtr); 02500 FsPath *copyFsPathPtr = (FsPath *) ckalloc(sizeof(FsPath)); 02501 02502 SETPATHOBJ(copyPtr, copyFsPathPtr); 02503 02504 if (srcFsPathPtr->translatedPathPtr != NULL) { 02505 copyFsPathPtr->translatedPathPtr = srcFsPathPtr->translatedPathPtr; 02506 if (copyFsPathPtr->translatedPathPtr != copyPtr) { 02507 Tcl_IncrRefCount(copyFsPathPtr->translatedPathPtr); 02508 } 02509 } else { 02510 copyFsPathPtr->translatedPathPtr = NULL; 02511 } 02512 02513 if (srcFsPathPtr->normPathPtr != NULL) { 02514 copyFsPathPtr->normPathPtr = srcFsPathPtr->normPathPtr; 02515 if (copyFsPathPtr->normPathPtr != copyPtr) { 02516 Tcl_IncrRefCount(copyFsPathPtr->normPathPtr); 02517 } 02518 } else { 02519 copyFsPathPtr->normPathPtr = NULL; 02520 } 02521 02522 if (srcFsPathPtr->cwdPtr != NULL) { 02523 copyFsPathPtr->cwdPtr = srcFsPathPtr->cwdPtr; 02524 Tcl_IncrRefCount(copyFsPathPtr->cwdPtr); 02525 } else { 02526 copyFsPathPtr->cwdPtr = NULL; 02527 } 02528 02529 copyFsPathPtr->flags = srcFsPathPtr->flags; 02530 02531 if (srcFsPathPtr->fsRecPtr != NULL 02532 && srcFsPathPtr->nativePathPtr != NULL) { 02533 Tcl_FSDupInternalRepProc *dupProc = 02534 srcFsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc; 02535 if (dupProc != NULL) { 02536 copyFsPathPtr->nativePathPtr = 02537 (*dupProc)(srcFsPathPtr->nativePathPtr); 02538 } else { 02539 copyFsPathPtr->nativePathPtr = NULL; 02540 } 02541 } else { 02542 copyFsPathPtr->nativePathPtr = NULL; 02543 } 02544 copyFsPathPtr->fsRecPtr = srcFsPathPtr->fsRecPtr; 02545 copyFsPathPtr->filesystemEpoch = srcFsPathPtr->filesystemEpoch; 02546 if (copyFsPathPtr->fsRecPtr != NULL) { 02547 copyFsPathPtr->fsRecPtr->fileRefCount++; 02548 } 02549 02550 copyPtr->typePtr = &tclFsPathType; 02551 } 02552 02553 /* 02554 *--------------------------------------------------------------------------- 02555 * 02556 * UpdateStringOfFsPath -- 02557 * 02558 * Gives an object a valid string rep. 02559 * 02560 * Results: 02561 * None. 02562 * 02563 * Side effects: 02564 * Memory may be allocated. 02565 * 02566 *--------------------------------------------------------------------------- 02567 */ 02568 02569 static void 02570 UpdateStringOfFsPath( 02571 register Tcl_Obj *pathPtr) /* path obj with string rep to update. */ 02572 { 02573 FsPath *fsPathPtr = PATHOBJ(pathPtr); 02574 const char *cwdStr; 02575 int cwdLen; 02576 Tcl_Obj *copy; 02577 02578 if (PATHFLAGS(pathPtr) == 0 || fsPathPtr->cwdPtr == NULL) { 02579 Tcl_Panic("Called UpdateStringOfFsPath with invalid object"); 02580 } 02581 02582 copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr); 02583 Tcl_IncrRefCount(copy); 02584 02585 cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); 02586 02587 /* 02588 * Should we perhaps use 'Tcl_FSPathSeparator'? But then what about the 02589 * Windows special case? Perhaps we should just check if cwd is a root 02590 * volume. We should never get cwdLen == 0 in this code path. 02591 */ 02592 02593 switch (tclPlatform) { 02594 case TCL_PLATFORM_UNIX: 02595 if (cwdStr[cwdLen-1] != '/') { 02596 Tcl_AppendToObj(copy, "/", 1); 02597 cwdLen++; 02598 } 02599 break; 02600 02601 case TCL_PLATFORM_WINDOWS: 02602 /* 02603 * We need the extra 'cwdLen != 2', and ':' checks because a volume 02604 * relative path doesn't get a '/'. For example 'glob C:*cat*.exe' 02605 * will return 'C:cat32.exe' 02606 */ 02607 02608 if (cwdStr[cwdLen-1] != '/' && cwdStr[cwdLen-1] != '\\') { 02609 if (cwdLen != 2 || cwdStr[1] != ':') { 02610 Tcl_AppendToObj(copy, "/", 1); 02611 cwdLen++; 02612 } 02613 } 02614 break; 02615 } 02616 02617 Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr); 02618 pathPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen); 02619 pathPtr->length = cwdLen; 02620 copy->bytes = tclEmptyStringRep; 02621 copy->length = 0; 02622 TclDecrRefCount(copy); 02623 } 02624 02625 /* 02626 *--------------------------------------------------------------------------- 02627 * 02628 * TclNativePathInFilesystem -- 02629 * 02630 * Any path object is acceptable to the native filesystem, by default (we 02631 * will throw errors when illegal paths are actually tried to be used). 02632 * 02633 * However, this behavior means the native filesystem must be the last 02634 * filesystem in the lookup list (otherwise it will claim all files 02635 * belong to it, and other filesystems will never get a look in). 02636 * 02637 * Results: 02638 * TCL_OK, to indicate 'yes', -1 to indicate no. 02639 * 02640 * Side effects: 02641 * None. 02642 * 02643 *--------------------------------------------------------------------------- 02644 */ 02645 02646 int 02647 TclNativePathInFilesystem( 02648 Tcl_Obj *pathPtr, 02649 ClientData *clientDataPtr) 02650 { 02651 /* 02652 * A special case is required to handle the empty path "". This is a valid 02653 * path (i.e. the user should be able to do 'file exists ""' without 02654 * throwing an error), but equally the path doesn't exist. Those are the 02655 * semantics of Tcl (at present anyway), so we have to abide by them here. 02656 */ 02657 02658 if (pathPtr->typePtr == &tclFsPathType) { 02659 if (pathPtr->bytes != NULL && pathPtr->bytes[0] == '\0') { 02660 /* 02661 * We reject the empty path "". 02662 */ 02663 02664 return -1; 02665 } 02666 02667 /* 02668 * Otherwise there is no way this path can be empty. 02669 */ 02670 } else { 02671 /* 02672 * It is somewhat unusual to reach this code path without the object 02673 * being of tclFsPathType. However, we do our best to deal with the 02674 * situation. 02675 */ 02676 02677 int len; 02678 02679 Tcl_GetStringFromObj(pathPtr, &len); 02680 if (len == 0) { 02681 /* 02682 * We reject the empty path "". 02683 */ 02684 02685 return -1; 02686 } 02687 } 02688 02689 /* 02690 * Path is of correct type, or is of non-zero length, so we accept it. 02691 */ 02692 02693 return TCL_OK; 02694 } 02695 02696 /* 02697 * Local Variables: 02698 * mode: c 02699 * c-basic-offset: 4 02700 * fill-column: 78 02701 * End: 02702 */
Generated on Wed Mar 12 12:18:20 2008 by 1.5.1 |