tclPathObj.c

Go 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  doxygen 1.5.1