diff options
Diffstat (limited to 'generic/tclIOUtil.c')
-rw-r--r-- | generic/tclIOUtil.c | 2518 |
1 files changed, 434 insertions, 2084 deletions
diff --git a/generic/tclIOUtil.c b/generic/tclIOUtil.c index 29ba700..f27fff9 100644 --- a/generic/tclIOUtil.c +++ b/generic/tclIOUtil.c @@ -17,7 +17,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclIOUtil.c,v 1.77 2003/03/03 20:22:41 das Exp $ + * RCS: @(#) $Id: tclIOUtil.c,v 1.78 2003/04/11 15:59:54 vincentdarley Exp $ */ #include "tclInt.h" @@ -29,45 +29,15 @@ /* for tclWinProcs->useWide */ #include "tclWinInt.h" #endif +#include "tclFileSystem.h" /* * Prototypes for procedures defined later in this file. */ -static void DupFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr, - Tcl_Obj *copyPtr)); -static void FreeFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *listPtr)); -static void UpdateStringOfFsPath _ANSI_ARGS_((Tcl_Obj *objPtr)); -static int SetFsPathFromAny _ANSI_ARGS_((Tcl_Interp *interp, - Tcl_Obj *objPtr)); -static Tcl_Obj* MakeFsPathFromRelative _ANSI_ARGS_((Tcl_Interp *interp, - Tcl_Obj *objPtr, Tcl_Obj *cwdPtr)); -static Tcl_Obj* FSNormalizeAbsolutePath - _ANSI_ARGS_((Tcl_Interp* interp, Tcl_Obj *pathPtr)); -static int TclNormalizeToUniquePath - _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *pathPtr, - int startAt)); -static int SetFsPathFromAbsoluteNormalized - _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *objPtr)); -static int FindSplitPos _ANSI_ARGS_((char *path, char *separator)); -static Tcl_PathType FSGetPathType _ANSI_ARGS_((Tcl_Obj *pathObjPtr, - Tcl_Filesystem **filesystemPtrPtr, - int *driveNameLengthPtr)); -static Tcl_PathType GetPathType _ANSI_ARGS_((Tcl_Obj *pathObjPtr, - Tcl_Filesystem **filesystemPtrPtr, - int *driveNameLengthPtr, Tcl_Obj **driveNameRef)); +static FilesystemRecord* FsGetIterator(void); +static void FsReleaseIterator(void); -/* - * Define the 'path' object type, which Tcl uses to represent - * file paths internally. - */ -Tcl_ObjType tclFsPathType = { - "path", /* name */ - FreeFsPathInternalRep, /* freeIntRepProc */ - DupFsPathInternalRep, /* dupIntRepProc */ - UpdateStringOfFsPath, /* updateStringProc */ - SetFsPathFromAny /* setFromAnyProc */ -}; /* * These form part of the native filesystem support. They are needed @@ -306,26 +276,6 @@ TCL_DECLARE_MUTEX(obsoleteFsHookMutex) #endif /* USE_OBSOLETE_FS_HOOKS */ /* - * A filesystem record is used to keep track of each - * filesystem currently registered with the core, - * in a linked list. - */ -typedef struct FilesystemRecord { - ClientData clientData; /* Client specific data for the new - * filesystem (can be NULL) */ - Tcl_Filesystem *fsPtr; /* Pointer to filesystem dispatch - * table. */ - int fileRefCount; /* How many Tcl_Obj's use this - * filesystem. */ - struct FilesystemRecord *nextPtr; - /* The next filesystem registered - * to Tcl, or NULL if no more. */ -} FilesystemRecord; - -static FilesystemRecord* GetFilesystemRecord - _ANSI_ARGS_((Tcl_Filesystem *fromFilesystem, int *epoch)); - -/* * Declare the native filesystem support. These functions should * be considered private to Tcl, and should really not be called * directly by any code other than this file (i.e. neither by @@ -338,10 +288,9 @@ static FilesystemRecord* GetFilesystemRecord * We cannot make all of these static, since some of them * are implemented in the platform-specific directories. */ -static Tcl_FSPathInFilesystemProc NativePathInFilesystem; static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator; static Tcl_FSFreeInternalRepProc NativeFreeInternalRep; -static Tcl_FSDupInternalRepProc NativeDupInternalRep; +Tcl_FSDupInternalRepProc NativeDupInternalRep; static Tcl_FSCreateInternalRepProc NativeCreateNativeRep; static Tcl_FSFileAttrStringsProc NativeFileAttrStrings; static Tcl_FSFileAttrsGetProc NativeFileAttrsGet; @@ -383,7 +332,7 @@ Tcl_FSListVolumesProc TclpObjListVolumes; * 'native filesystem implementation' should not be delving inside * here! */ -static Tcl_Filesystem tclNativeFilesystem = { +Tcl_Filesystem tclNativeFilesystem = { "native", sizeof(Tcl_Filesystem), TCL_FILESYSTEM_VERSION_1, @@ -447,7 +396,7 @@ static FilesystemRecord nativeFilesystemRecord = { * filesystems. Any time it changes, all cached filesystem * representations are suspect and must be freed. */ -static int theFilesystemEpoch = 0; +int theFilesystemEpoch = 0; /* * Stores the linked list of filesystems. @@ -472,49 +421,6 @@ static Tcl_Condition filesystemOkToModify = NULL; TCL_DECLARE_MUTEX(filesystemMutex) /* - * struct FsPath -- - * - * Internal representation of a Tcl_Obj of "path" type. This - * can be used to represent relative or absolute paths, and has - * certain optimisations when used to represent paths which are - * already normalized and absolute. - * - * Note that 'normPathPtr' can be a circular reference to the - * container Tcl_Obj of this FsPath. - */ -typedef struct FsPath { - Tcl_Obj *translatedPathPtr; /* Name without any ~user sequences. - * If this is NULL, then this is a - * pure normalized, absolute path - * object, in which the parent Tcl_Obj's - * string rep is already both translated - * and normalized. */ - Tcl_Obj *normPathPtr; /* Normalized absolute path, without - * ., .. or ~user sequences. If the - * Tcl_Obj containing - * this FsPath is already normalized, - * this may be a circular reference back - * to the container. If that is NOT the - * case, we have a refCount on the object. */ - Tcl_Obj *cwdPtr; /* If null, path is absolute, else - * this points to the cwd object used - * for this path. We have a refCount - * on the object. */ - int flags; /* Flags to describe interpretation */ - ClientData nativePathPtr; /* Native representation of this path, - * which is filesystem dependent. */ - int filesystemEpoch; /* Used to ensure the path representation - * was generated during the correct - * filesystem epoch. The epoch changes - * when filesystem-mounts are changed. */ - struct FilesystemRecord *fsRecPtr; - /* Pointer to the filesystem record - * entry to use for this path. */ -} FsPath; - -#define TCLPATH_APPENDED 1 -#define TCLPATH_RELATIVE 2 -/* * Used to implement Tcl_FSGetCwd in a file-system independent way. * This is protected by the cwdMutex below. */ @@ -546,8 +452,8 @@ typedef struct FsDivertLoad { /* Now move on to the basic filesystem implementation */ -static int -FsCwdPointerEquals(objPtr) +int +TclFSCwdPointerEquals(objPtr) Tcl_Obj* objPtr; { Tcl_MutexLock(&cwdMutex); @@ -559,7 +465,7 @@ FsCwdPointerEquals(objPtr) return 0; } } - + static FilesystemRecord* FsGetIterator(void) { @@ -856,6 +762,131 @@ Tcl_FSUnregister(fsPtr) /* *---------------------------------------------------------------------- * + * Tcl_FSMatchInDirectory -- + * + * This routine is used by the globbing code to search a directory + * for all files which match a given pattern. The appropriate + * function for the filesystem to which pathPtr belongs will be + * called. If pathPtr does not belong to any filesystem and if it + * is NULL or the empty string, then we assume the pattern is to be + * matched in the current working directory. To avoid each + * filesystem's Tcl_FSMatchInDirectoryProc having to deal with this + * issue, we create a pathPtr on the fly (equal to the cwd), and + * then remove it from the results returned. This makes filesystems + * easy to write, since they can assume the pathPtr passed to them + * is an ordinary path. In fact this means we could remove such + * special case handling from Tcl's native filesystems. + * + * If 'pattern' is NULL, then pathPtr is assumed to be a fully + * specified path of a single file/directory which must be + * checked for existence and correct type. + * + * Results: + * + * The return value is a standard Tcl result indicating whether an + * error occurred in globbing. Error messages are placed in + * interp, but good results are placed in the resultPtr given. + * + * Recursive searches, e.g. + * + * glob -dir $dir -join * pkgIndex.tcl + * + * which must recurse through each directory matching '*' are + * handled internally by Tcl, by passing specific flags in a + * modified 'types' parameter. This means the actual filesystem + * only ever sees patterns which match in a single directory. + * + * Side effects: + * The interpreter may have an error message inserted into it. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_FSMatchInDirectory(interp, result, pathPtr, pattern, types) + Tcl_Interp *interp; /* Interpreter to receive error messages. */ + Tcl_Obj *result; /* List object to receive results. */ + Tcl_Obj *pathPtr; /* Contains path to directory to search. */ + CONST char *pattern; /* Pattern to match against. */ + Tcl_GlobTypeData *types; /* Object containing list of acceptable types. + * May be NULL. In particular the directory + * flag is very important. */ +{ + Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); + if (fsPtr != NULL) { + Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc; + if (proc != NULL) { + return (*proc)(interp, result, pathPtr, pattern, types); + } + } else { + Tcl_Obj* cwd; + int ret = -1; + if (pathPtr != NULL) { + int len; + Tcl_GetStringFromObj(pathPtr,&len); + if (len != 0) { + /* + * We have no idea how to match files in a directory + * which belongs to no known filesystem + */ + Tcl_SetErrno(ENOENT); + return -1; + } + } + /* + * We have an empty or NULL path. This is defined to mean we + * must search for files within the current 'cwd'. We + * therefore use that, but then since the proc we call will + * return results which include the cwd we must then trim it + * off the front of each path in the result. We choose to deal + * with this here (in the generic code), since if we don't, + * every single filesystem's implementation of + * Tcl_FSMatchInDirectory will have to deal with it for us. + */ + cwd = Tcl_FSGetCwd(NULL); + if (cwd == NULL) { + if (interp != NULL) { + Tcl_SetResult(interp, "glob couldn't determine " + "the current working directory", TCL_STATIC); + } + return TCL_ERROR; + } + fsPtr = Tcl_FSGetFileSystemForPath(cwd); + if (fsPtr != NULL) { + Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc; + if (proc != NULL) { + Tcl_Obj* tmpResultPtr = Tcl_NewListObj(0, NULL); + Tcl_IncrRefCount(tmpResultPtr); + ret = (*proc)(interp, tmpResultPtr, cwd, pattern, types); + if (ret == TCL_OK) { + int resLength; + + ret = Tcl_ListObjLength(interp, tmpResultPtr, &resLength); + if (ret == TCL_OK) { + int i; + + for (i = 0; i < resLength; i++) { + Tcl_Obj *elt; + + Tcl_ListObjIndex(interp, tmpResultPtr, i, &elt); + Tcl_ListObjAppendElement(interp, result, + TclFSMakePathRelative(interp, elt, cwd)); + } + } + } + Tcl_DecrRefCount(tmpResultPtr); + } + } + Tcl_DecrRefCount(cwd); + return ret; + } + Tcl_SetErrno(ENOENT); + return -1; +} + +/* + *---------------------------------------------------------------------- + * * Tcl_FSMountsChanged -- * * Notify the filesystem that the available mounted filesystems @@ -1001,9 +1032,10 @@ Tcl_FSData(fsPtr) *--------------------------------------------------------------------------- */ static Tcl_Obj* -FSNormalizeAbsolutePath(interp, pathPtr) +FSNormalizeAbsolutePath(interp, pathPtr, clientDataPtr) Tcl_Interp* interp; /* Interpreter to use */ Tcl_Obj *pathPtr; /* Absolute path to normalize */ + ClientData *clientDataPtr; { int splen = 0, nplen, eltLen, i; char *eltName; @@ -1039,6 +1071,8 @@ FSNormalizeAbsolutePath(interp, pathPtr) } } if (nplen > 0) { + ClientData clientData = NULL; + retVal = Tcl_FSJoinPath(split, nplen); /* * Now we have an absolute path, with no '..', '.' sequences, @@ -1052,13 +1086,16 @@ FSNormalizeAbsolutePath(interp, pathPtr) * other criteria for normalizing a path. */ Tcl_IncrRefCount(retVal); - TclNormalizeToUniquePath(interp, retVal, 0); + TclFSNormalizeToUniquePath(interp, retVal, 0, &clientData); /* * Since we know it is a normalized path, we can - * actually convert this object into an FsPath for + * actually convert this object into an "path" object for * greater efficiency */ - SetFsPathFromAbsoluteNormalized(interp, retVal); + TclFSMakePathFromNormalized(interp, retVal, clientData); + if (clientDataPtr != NULL) { + *clientDataPtr = clientData; + } } else { /* Init to an empty string */ retVal = Tcl_NewStringObj("",0); @@ -1083,7 +1120,7 @@ FSNormalizeAbsolutePath(interp, pathPtr) /* *--------------------------------------------------------------------------- * - * TclNormalizeToUniquePath -- + * TclFSNormalizeToUniquePath -- * * Description: * Takes a path specification containing no ../, ./ sequences, @@ -1113,14 +1150,17 @@ FSNormalizeAbsolutePath(interp, pathPtr) * after the separator). *--------------------------------------------------------------------------- */ -static int -TclNormalizeToUniquePath(interp, pathPtr, startAt) +int +TclFSNormalizeToUniquePath(interp, pathPtr, startAt, clientDataPtr) Tcl_Interp *interp; Tcl_Obj *pathPtr; int startAt; + ClientData *clientDataPtr; { FilesystemRecord *fsRecPtr; - + /* Ignore this variable */ + (void)clientDataPtr; + /* * Call each of the "normalise path" functions in succession. This is * a special case, in which if we have a native filesystem handler, @@ -1374,7 +1414,7 @@ Tcl_FSEvalFile(interp, pathPtr) Tcl_Channel chan; Tcl_Obj *objPtr; - if (Tcl_FSGetTranslatedPath(interp, pathPtr) == NULL) { + if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) { return TCL_ERROR; } @@ -1757,39 +1797,48 @@ Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions) { Tcl_Filesystem *fsPtr; #ifdef USE_OBSOLETE_FS_HOOKS - OpenFileChannelProc *openFileChannelProcPtr; Tcl_Channel retVal = NULL; - char *path; -#endif /* USE_OBSOLETE_FS_HOOKS */ - Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr); - if (transPtr == NULL) { - return NULL; - } -#ifdef USE_OBSOLETE_FS_HOOKS - if (transPtr == NULL) { - path = NULL; - } else { - path = Tcl_GetString(transPtr); - } /* - * Call each of the "Tcl_OpenFileChannel" function in succession. + * Call each of the "Tcl_OpenFileChannel" functions in succession. * A non-NULL return value indicates the particular function has * succeeded. */ Tcl_MutexLock(&obsoleteFsHookMutex); - openFileChannelProcPtr = openFileChannelProcList; - while ((retVal == NULL) && (openFileChannelProcPtr != NULL)) { - retVal = (*openFileChannelProcPtr->proc)(interp, path, - modeString, permissions); - openFileChannelProcPtr = openFileChannelProcPtr->nextPtr; + if (openFileChannelProcList != NULL) { + OpenFileChannelProc *openFileChannelProcPtr; + char *path; + Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr); + + if (transPtr == NULL) { + path = NULL; + } else { + path = Tcl_GetString(transPtr); + } + + openFileChannelProcPtr = openFileChannelProcList; + + while ((retVal == NULL) && (openFileChannelProcPtr != NULL)) { + retVal = (*openFileChannelProcPtr->proc)(interp, path, + modeString, permissions); + openFileChannelProcPtr = openFileChannelProcPtr->nextPtr; + } } Tcl_MutexUnlock(&obsoleteFsHookMutex); if (retVal != NULL) { return retVal; } #endif /* USE_OBSOLETE_FS_HOOKS */ + + /* + * We need this just to ensure we return the correct error messages + * under some circumstances. + */ + if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) { + return NULL; + } + fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL) { Tcl_FSOpenFileChannelProc *proc = fsPtr->openFileChannelProc; @@ -1831,371 +1880,6 @@ Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions) /* *---------------------------------------------------------------------- * - * Tcl_FSMatchInDirectory -- - * - * This routine is used by the globbing code to search a directory - * for all files which match a given pattern. The appropriate - * function for the filesystem to which pathPtr belongs will be - * called. If pathPtr does not belong to any filesystem and if it - * is NULL or the empty string, then we assume the pattern is to be - * matched in the current working directory. To avoid each - * filesystem's Tcl_FSMatchInDirectoryProc having to deal with this - * issue, we create a pathPtr on the fly (equal to the cwd), and - * then remove it from the results returned. This makes filesystems - * easy to write, since they can assume the pathPtr passed to them - * is an ordinary path. In fact this means we could remove such - * special case handling from Tcl's native filesystems. - * - * If 'pattern' is NULL, then pathPtr is assumed to be a fully - * specified path of a single file/directory which must be - * checked for existence and correct type. - * - * Results: - * - * The return value is a standard Tcl result indicating whether an - * error occurred in globbing. Error messages are placed in - * interp, but good results are placed in the resultPtr given. - * - * Recursive searches, e.g. - * - * glob -dir $dir -join * pkgIndex.tcl - * - * which must recurse through each directory matching '*' are - * handled internally by Tcl, by passing specific flags in a - * modified 'types' parameter. This means the actual filesystem - * only ever sees patterns which match in a single directory. - * - * Side effects: - * The interpreter may have an error message inserted into it. - * - *---------------------------------------------------------------------- - */ - -int -Tcl_FSMatchInDirectory(interp, result, pathPtr, pattern, types) - Tcl_Interp *interp; /* Interpreter to receive error messages. */ - Tcl_Obj *result; /* List object to receive results. */ - Tcl_Obj *pathPtr; /* Contains path to directory to search. */ - CONST char *pattern; /* Pattern to match against. */ - Tcl_GlobTypeData *types; /* Object containing list of acceptable types. - * May be NULL. In particular the directory - * flag is very important. */ -{ - Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - if (fsPtr != NULL) { - Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc; - if (proc != NULL) { - return (*proc)(interp, result, pathPtr, pattern, types); - } - } else { - Tcl_Obj* cwd; - int ret = -1; - if (pathPtr != NULL) { - int len; - Tcl_GetStringFromObj(pathPtr,&len); - if (len != 0) { - /* - * We have no idea how to match files in a directory - * which belongs to no known filesystem - */ - Tcl_SetErrno(ENOENT); - return -1; - } - } - /* - * We have an empty or NULL path. This is defined to mean we - * must search for files within the current 'cwd'. We - * therefore use that, but then since the proc we call will - * return results which include the cwd we must then trim it - * off the front of each path in the result. We choose to deal - * with this here (in the generic code), since if we don't, - * every single filesystem's implementation of - * Tcl_FSMatchInDirectory will have to deal with it for us. - */ - cwd = Tcl_FSGetCwd(NULL); - if (cwd == NULL) { - if (interp != NULL) { - Tcl_SetResult(interp, "glob couldn't determine " - "the current working directory", TCL_STATIC); - } - return TCL_ERROR; - } - fsPtr = Tcl_FSGetFileSystemForPath(cwd); - if (fsPtr != NULL) { - Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc; - if (proc != NULL) { - int cwdLen; - char *cwdStr; - Tcl_Obj* tmpResultPtr = Tcl_NewListObj(0, NULL); - Tcl_IncrRefCount(tmpResultPtr); - /* - * We know the cwd is a normalised object which does - * not end in a directory delimiter, unless the cwd - * is the name of a volume, in which case it will - * end in a delimiter! We handle this situation here. - * A better test than the '!= sep' might be to simply - * check if 'cwd' is a root volume. - * - * Note that if we get this wrong, we will strip off - * either too much or too little below, leading to - * wrong answers returned by glob. - */ - cwdStr = Tcl_GetStringFromObj(cwd, &cwdLen); - /* - * Should we perhaps use 'Tcl_FSPathSeparator'? - * But then what about the Windows special case? - * Perhaps we should just check if cwd is a root - * volume. - */ - switch (tclPlatform) { - case TCL_PLATFORM_UNIX: - if (cwdStr[cwdLen-1] != '/') { - cwdLen++; - } - break; - case TCL_PLATFORM_WINDOWS: - if (cwdStr[cwdLen-1] != '/' - && cwdStr[cwdLen-1] != '\\') { - cwdLen++; - } - break; - case TCL_PLATFORM_MAC: - if (cwdStr[cwdLen-1] != ':') { - cwdLen++; - } - break; - } - ret = (*proc)(interp, tmpResultPtr, cwd, pattern, types); - if (ret == TCL_OK) { - int resLength; - - ret = Tcl_ListObjLength(interp, tmpResultPtr, &resLength); - if (ret == TCL_OK) { - int i; - - for (i = 0; i < resLength; i++) { - Tcl_Obj *cutElt, *elt; - char *eltStr; - int eltLen; - - Tcl_ListObjIndex(interp, tmpResultPtr, i, &elt); - if (elt->typePtr == &tclFsPathType) { - FsPath* fsPathPtr = (FsPath*) - elt->internalRep.otherValuePtr; - if (fsPathPtr->flags != 0 - && fsPathPtr->cwdPtr == cwd) { - Tcl_ListObjAppendElement(interp, result, - MakeFsPathFromRelative(interp, - fsPathPtr->normPathPtr, cwd)); - continue; - } - } - eltStr = Tcl_GetStringFromObj(elt, &eltLen); - cutElt = Tcl_NewStringObj(eltStr + cwdLen, - eltLen - cwdLen); - Tcl_ListObjAppendElement(interp, result, cutElt); - } - } - } - Tcl_DecrRefCount(tmpResultPtr); - } - } - Tcl_DecrRefCount(cwd); - return ret; - } - Tcl_SetErrno(ENOENT); - return -1; -} - -/* - *---------------------------------------------------------------------- - * - * Tcl_FSGetCwd -- - * - * This function replaces the library version of getcwd(). - * - * Most VFS's will *not* implement a 'cwdProc'. Tcl now maintains - * its own record (in a Tcl_Obj) of the cwd, and an attempt - * is made to synchronise this with the cwd's containing filesystem, - * if that filesystem provides a cwdProc (e.g. the native filesystem). - * - * Note that if Tcl's cwd is not in the native filesystem, then of - * course Tcl's cwd and the native cwd are different: extensions - * should therefore ensure they only access the cwd through this - * function to avoid confusion. - * - * If a global cwdPathPtr already exists, it is returned, subject - * to a synchronisation attempt in that cwdPathPtr's fs. - * Otherwise, the chain of functions that have been "inserted" - * into the filesystem will be called in succession until either a - * value other than NULL is returned, or the entire list is - * visited. - * - * Results: - * The result is a pointer to a Tcl_Obj specifying the current - * directory, or NULL if the current directory could not be - * determined. If NULL is returned, an error message is left in the - * interp's result. - * - * The result already has its refCount incremented for the caller. - * When it is no longer needed, that refCount should be decremented. - * This is needed for thread-safety purposes, to allow multiple - * threads to access this and related functions, while ensuring the - * results are always valid. - * - * Of course it is probably a bad idea for multiple threads to - * be *setting* the cwd anyway, but we can at least try to - * help the case of multiple reads with occasional sets. - * - * Side effects: - * Various objects may be freed and allocated. - * - *---------------------------------------------------------------------- - */ - -Tcl_Obj* -Tcl_FSGetCwd(interp) - Tcl_Interp *interp; -{ - Tcl_Obj *cwdToReturn; - - if (FsCwdPointerEquals(NULL)) { - FilesystemRecord *fsRecPtr; - Tcl_Obj *retVal = NULL; - - /* - * We've never been called before, try to find a cwd. Call - * each of the "Tcl_GetCwd" function in succession. A non-NULL - * return value indicates the particular function has - * succeeded. - */ - - fsRecPtr = FsGetIterator(); - while ((retVal == NULL) && (fsRecPtr != NULL)) { - Tcl_FSGetCwdProc *proc = fsRecPtr->fsPtr->getCwdProc; - if (proc != NULL) { - retVal = (*proc)(interp); - } - fsRecPtr = fsRecPtr->nextPtr; - } - FsReleaseIterator(); - /* - * Now the 'cwd' may NOT be normalized, at least on some - * platforms. For the sake of efficiency, we want a completely - * normalized cwd at all times. - * - * Finally, if retVal is NULL, we do not have a cwd, which - * could be problematic. - */ - if (retVal != NULL) { - Tcl_Obj *norm = FSNormalizeAbsolutePath(interp, retVal); - if (norm != NULL) { - /* - * We found a cwd, which is now in our global storage. - * We must make a copy. Norm already has a refCount of - * 1. - * - * Threading issue: note that multiple threads at system - * startup could in principle call this procedure - * simultaneously. They will therefore each set the - * cwdPathPtr independently. That behaviour is a bit - * peculiar, but should be fine. Once we have a cwd, - * we'll always be in the 'else' branch below which - * is simpler. - */ - Tcl_MutexLock(&cwdMutex); - /* Just in case the pointer has been set by another - * thread between now and the test above */ - if (cwdPathPtr != NULL) { - Tcl_DecrRefCount(cwdPathPtr); - } - cwdPathPtr = norm; - Tcl_MutexUnlock(&cwdMutex); - } - Tcl_DecrRefCount(retVal); - } - } else { - /* - * We already have a cwd cached, but we want to give the - * filesystem it is in a chance to check whether that cwd - * has changed, or is perhaps no longer accessible. This - * allows an error to be thrown if, say, the permissions on - * that directory have changed. - */ - Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(cwdPathPtr); - /* - * If the filesystem couldn't be found, or if no cwd function - * exists for this filesystem, then we simply assume the cached - * cwd is ok. If we do call a cwd, we must watch for errors - * (if the cwd returns NULL). This ensures that, say, on Unix - * if the permissions of the cwd change, 'pwd' does actually - * throw the correct error in Tcl. (This is tested for in the - * test suite on unix). - */ - if (fsPtr != NULL) { - Tcl_FSGetCwdProc *proc = fsPtr->getCwdProc; - if (proc != NULL) { - Tcl_Obj *retVal = (*proc)(interp); - if (retVal != NULL) { - Tcl_Obj *norm = FSNormalizeAbsolutePath(interp, retVal); - /* - * Check whether cwd has changed from the value - * previously stored in cwdPathPtr. Really 'norm' - * shouldn't be null, but we are careful. - */ - if (norm == NULL) { - /* Do nothing */ - } else if (Tcl_FSEqualPaths(cwdPathPtr, norm)) { - /* - * If the paths were equal, we can be more - * efficient and retain the old path object - * which will probably already be shared. In - * this case we can simply free the normalized - * path we just calculated. - */ - Tcl_DecrRefCount(norm); - } else { - /* The cwd has in fact changed, so we must - * lock down the cwdMutex to modify. */ - Tcl_MutexLock(&cwdMutex); - Tcl_DecrRefCount(cwdPathPtr); - cwdPathPtr = norm; - Tcl_MutexUnlock(&cwdMutex); - } - Tcl_DecrRefCount(retVal); - } else { - /* The 'cwd' function returned an error, so we - * reset the cwd after locking down the mutex. */ - Tcl_MutexLock(&cwdMutex); - Tcl_DecrRefCount(cwdPathPtr); - cwdPathPtr = NULL; - Tcl_MutexUnlock(&cwdMutex); - } - } - } - } - - /* - * The paths all eventually fall through to here. Note that - * we use a bunch of separate mutex locks throughout this - * code to help prevent deadlocks between threads. Really - * the only weirdness will arise if multiple threads are setting - * and reading the cwd, and that behaviour is always going to be - * a little suspect. - */ - Tcl_MutexLock(&cwdMutex); - cwdToReturn = cwdPathPtr; - if (cwdToReturn != NULL) { - Tcl_IncrRefCount(cwdToReturn); - } - Tcl_MutexUnlock(&cwdMutex); - - return (cwdToReturn); -} - -/* - *---------------------------------------------------------------------- - * * Tcl_FSUtime -- * * This procedure replaces the library version of utime. @@ -2442,6 +2126,192 @@ Tcl_FSFileAttrsSet(interp, index, pathPtr, objPtr) /* *---------------------------------------------------------------------- * + * Tcl_FSGetCwd -- + * + * This function replaces the library version of getcwd(). + * + * Most VFS's will *not* implement a 'cwdProc'. Tcl now maintains + * its own record (in a Tcl_Obj) of the cwd, and an attempt + * is made to synchronise this with the cwd's containing filesystem, + * if that filesystem provides a cwdProc (e.g. the native filesystem). + * + * Note that if Tcl's cwd is not in the native filesystem, then of + * course Tcl's cwd and the native cwd are different: extensions + * should therefore ensure they only access the cwd through this + * function to avoid confusion. + * + * If a global cwdPathPtr already exists, it is returned, subject + * to a synchronisation attempt in that cwdPathPtr's fs. + * Otherwise, the chain of functions that have been "inserted" + * into the filesystem will be called in succession until either a + * value other than NULL is returned, or the entire list is + * visited. + * + * Results: + * The result is a pointer to a Tcl_Obj specifying the current + * directory, or NULL if the current directory could not be + * determined. If NULL is returned, an error message is left in the + * interp's result. + * + * The result already has its refCount incremented for the caller. + * When it is no longer needed, that refCount should be decremented. + * This is needed for thread-safety purposes, to allow multiple + * threads to access this and related functions, while ensuring the + * results are always valid. + * + * Of course it is probably a bad idea for multiple threads to + * be *setting* the cwd anyway, but we can at least try to + * help the case of multiple reads with occasional sets. + * + * Side effects: + * Various objects may be freed and allocated. + * + *---------------------------------------------------------------------- + */ + +Tcl_Obj* +Tcl_FSGetCwd(interp) + Tcl_Interp *interp; +{ + Tcl_Obj *cwdToReturn; + + if (TclFSCwdPointerEquals(NULL)) { + FilesystemRecord *fsRecPtr; + Tcl_Obj *retVal = NULL; + + /* + * We've never been called before, try to find a cwd. Call + * each of the "Tcl_GetCwd" function in succession. A non-NULL + * return value indicates the particular function has + * succeeded. + */ + + fsRecPtr = FsGetIterator(); + while ((retVal == NULL) && (fsRecPtr != NULL)) { + Tcl_FSGetCwdProc *proc = fsRecPtr->fsPtr->getCwdProc; + if (proc != NULL) { + retVal = (*proc)(interp); + } + fsRecPtr = fsRecPtr->nextPtr; + } + FsReleaseIterator(); + /* + * Now the 'cwd' may NOT be normalized, at least on some + * platforms. For the sake of efficiency, we want a completely + * normalized cwd at all times. + * + * Finally, if retVal is NULL, we do not have a cwd, which + * could be problematic. + */ + if (retVal != NULL) { + Tcl_Obj *norm = FSNormalizeAbsolutePath(interp, retVal, NULL); + if (norm != NULL) { + /* + * We found a cwd, which is now in our global storage. + * We must make a copy. Norm already has a refCount of + * 1. + * + * Threading issue: note that multiple threads at system + * startup could in principle call this procedure + * simultaneously. They will therefore each set the + * cwdPathPtr independently. That behaviour is a bit + * peculiar, but should be fine. Once we have a cwd, + * we'll always be in the 'else' branch below which + * is simpler. + */ + Tcl_MutexLock(&cwdMutex); + /* Just in case the pointer has been set by another + * thread between now and the test above */ + if (cwdPathPtr != NULL) { + Tcl_DecrRefCount(cwdPathPtr); + } + cwdPathPtr = norm; + Tcl_MutexUnlock(&cwdMutex); + } + Tcl_DecrRefCount(retVal); + } + } else { + /* + * We already have a cwd cached, but we want to give the + * filesystem it is in a chance to check whether that cwd + * has changed, or is perhaps no longer accessible. This + * allows an error to be thrown if, say, the permissions on + * that directory have changed. + */ + Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(cwdPathPtr); + /* + * If the filesystem couldn't be found, or if no cwd function + * exists for this filesystem, then we simply assume the cached + * cwd is ok. If we do call a cwd, we must watch for errors + * (if the cwd returns NULL). This ensures that, say, on Unix + * if the permissions of the cwd change, 'pwd' does actually + * throw the correct error in Tcl. (This is tested for in the + * test suite on unix). + */ + if (fsPtr != NULL) { + Tcl_FSGetCwdProc *proc = fsPtr->getCwdProc; + if (proc != NULL) { + Tcl_Obj *retVal = (*proc)(interp); + if (retVal != NULL) { + Tcl_Obj *norm = FSNormalizeAbsolutePath(interp, retVal, NULL); + /* + * Check whether cwd has changed from the value + * previously stored in cwdPathPtr. Really 'norm' + * shouldn't be null, but we are careful. + */ + if (norm == NULL) { + /* Do nothing */ + } else if (Tcl_FSEqualPaths(cwdPathPtr, norm)) { + /* + * If the paths were equal, we can be more + * efficient and retain the old path object + * which will probably already be shared. In + * this case we can simply free the normalized + * path we just calculated. + */ + Tcl_DecrRefCount(norm); + } else { + /* The cwd has in fact changed, so we must + * lock down the cwdMutex to modify. */ + Tcl_MutexLock(&cwdMutex); + Tcl_DecrRefCount(cwdPathPtr); + cwdPathPtr = norm; + Tcl_MutexUnlock(&cwdMutex); + } + Tcl_DecrRefCount(retVal); + } else { + /* The 'cwd' function returned an error, so we + * reset the cwd after locking down the mutex. */ + Tcl_MutexLock(&cwdMutex); + Tcl_DecrRefCount(cwdPathPtr); + cwdPathPtr = NULL; + Tcl_MutexUnlock(&cwdMutex); + } + } + } + } + + /* + * The paths all eventually fall through to here. Note that + * we use a bunch of separate mutex locks throughout this + * code to help prevent deadlocks between threads. Really + * the only weirdness will arise if multiple threads are setting + * and reading the cwd, and that behaviour is always going to be + * a little suspect. + */ + Tcl_MutexLock(&cwdMutex); + cwdToReturn = cwdPathPtr; + if (cwdToReturn != NULL) { + Tcl_IncrRefCount(cwdToReturn); + } + Tcl_MutexUnlock(&cwdMutex); + + return (cwdToReturn); +} + +/* + *---------------------------------------------------------------------- + * * Tcl_FSChdir -- * * This function replaces the library version of chdir(). @@ -2528,6 +2398,8 @@ Tcl_FSChdir(pathPtr) cwdPathPtr = normDirName; Tcl_MutexUnlock(&cwdMutex); } + } else { + Tcl_SetErrno(ENOENT); } return (retVal); @@ -3002,79 +2874,6 @@ Tcl_FSListVolumes(void) } /* - *---------------------------------------------------------------------- - * - * Tcl_FSGetPathType -- - * - * Determines whether a given path is relative to the current - * directory, relative to the current volume, or absolute. - * - * Results: - * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or - * TCL_PATH_VOLUME_RELATIVE. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -Tcl_PathType -Tcl_FSGetPathType(pathObjPtr) - Tcl_Obj *pathObjPtr; -{ - return FSGetPathType(pathObjPtr, NULL, NULL); -} - -/* - *---------------------------------------------------------------------- - * - * FSGetPathType -- - * - * Determines whether a given path is relative to the current - * directory, relative to the current volume, or absolute. If the - * caller wishes to know which filesystem claimed the path (in the - * case for which the path is absolute), then a reference to a - * filesystem pointer can be passed in (but passing NULL is - * acceptable). - * - * Results: - * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or - * TCL_PATH_VOLUME_RELATIVE. The filesystem reference will - * be set if and only if it is non-NULL and the function's - * return value is TCL_PATH_ABSOLUTE. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -static Tcl_PathType -FSGetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr) - Tcl_Obj *pathObjPtr; - Tcl_Filesystem **filesystemPtrPtr; - int *driveNameLengthPtr; -{ - if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) { - return GetPathType(pathObjPtr, filesystemPtrPtr, - driveNameLengthPtr, NULL); - } else { - FsPath *fsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr; - if (fsPathPtr->cwdPtr != NULL) { - if (fsPathPtr->flags == 0) { - return TCL_PATH_RELATIVE; - } - return FSGetPathType(fsPathPtr->cwdPtr, filesystemPtrPtr, - driveNameLengthPtr); - } else { - return GetPathType(pathObjPtr, filesystemPtrPtr, - driveNameLengthPtr, NULL); - } - } -} - -/* *--------------------------------------------------------------------------- * * Tcl_FSSplitPath -- @@ -3171,188 +2970,31 @@ Tcl_FSSplitPath(pathPtr, lenPtr) return result; } -/* - *--------------------------------------------------------------------------- - * - * Tcl_FSJoinPath -- - * - * This function takes the given Tcl_Obj, which should be a valid - * list, and returns the path object given by considering the - * first 'elements' elements as valid path segments. If elements < 0, - * we use the entire list. - * - * Results: - * Returns object with refCount of zero, (or if non-zero, it has - * references elsewhere in Tcl). Either way, the caller must - * increment its refCount before use. - * - * Side effects: - * None. - * - *--------------------------------------------------------------------------- - */ +/* Simple helper function */ Tcl_Obj* -Tcl_FSJoinPath(listObj, elements) - Tcl_Obj *listObj; - int elements; +TclFSInternalToNormalized(fromFilesystem, clientData, fsRecPtrPtr, epochPtr) + Tcl_Filesystem *fromFilesystem; + ClientData clientData; + FilesystemRecord **fsRecPtrPtr; + int *epochPtr; { - Tcl_Obj *res; - int i; - Tcl_Filesystem *fsPtr = NULL; - - if (elements < 0) { - if (Tcl_ListObjLength(NULL, listObj, &elements) != TCL_OK) { - return NULL; - } - } else { - /* Just make sure it is a valid list */ - int listTest; - if (Tcl_ListObjLength(NULL, listObj, &listTest) != TCL_OK) { - return NULL; - } - /* - * Correct this if it is too large, otherwise we will - * waste our time joining null elements to the path - */ - if (elements > listTest) { - elements = listTest; - } - } - - if (elements == 2) { - /* - * This is a special case where we can be much more - * efficient - */ - Tcl_Obj *base; - - Tcl_ListObjIndex(NULL, listObj, 0, &base); - /* - * There is only any value in doing this if the first object is - * of path type, otherwise we'll never actually get any - * efficiency benefit elsewhere in the code (from re-using the - * normalized representation of the base object). - */ - if (base->typePtr == &tclFsPathType - && !(base->bytes != NULL && base->bytes[0] == '\0')) { - Tcl_Obj *tail; - Tcl_PathType type; - Tcl_ListObjIndex(NULL, listObj, 1, &tail); - type = GetPathType(tail, NULL, NULL, NULL); - if (type == TCL_PATH_RELATIVE) { - CONST char *str; - int len; - str = Tcl_GetStringFromObj(tail,&len); - if (len == 0) { - /* - * This happens if we try to handle the root volume - * '/'. There's no need to return a special path - * object, when the base itself is just fine! - */ - return base; - } - if (str[0] != '.') { - return TclNewFSPathObj(base, str, len); - } - /* - * Otherwise we don't have an easy join, and - * we must let the more general code below handle - * things - */ - } else { - return tail; - } + FilesystemRecord *fsRecPtr = FsGetIterator(); + while (fsRecPtr != NULL) { + if (fsRecPtr->fsPtr == fromFilesystem) { + *epochPtr = theFilesystemEpoch; + *fsRecPtrPtr = fsRecPtr; + break; } + fsRecPtr = fsRecPtr->nextPtr; } + FsReleaseIterator(); - res = Tcl_NewObj(); - - for (i = 0; i < elements; i++) { - Tcl_Obj *elt; - int driveNameLength; - Tcl_PathType type; - char *strElt; - int strEltLen; - int length; - char *ptr; - Tcl_Obj *driveName = NULL; - - Tcl_ListObjIndex(NULL, listObj, i, &elt); - strElt = Tcl_GetStringFromObj(elt, &strEltLen); - type = GetPathType(elt, &fsPtr, &driveNameLength, &driveName); - if (type != TCL_PATH_RELATIVE) { - /* Zero out the current result */ - Tcl_DecrRefCount(res); - if (driveName != NULL) { - res = Tcl_DuplicateObj(driveName); - Tcl_DecrRefCount(driveName); - } else { - res = Tcl_NewStringObj(strElt, driveNameLength); - } - strElt += driveNameLength; - } - - ptr = Tcl_GetStringFromObj(res, &length); - - /* - * Strip off any './' before a tilde, unless this is the - * beginning of the path. - */ - if (length > 0 && strEltLen > 0) { - if ((strElt[0] == '.') && (strElt[1] == '/') - && (strElt[2] == '~')) { - strElt += 2; - } - } - - /* - * A NULL value for fsPtr at this stage basically means - * we're trying to join a relative path onto something - * which is also relative (or empty). There's nothing - * particularly wrong with that. - */ - if (*strElt == '\0') continue; - - if (fsPtr == &tclNativeFilesystem || fsPtr == NULL) { - TclpNativeJoinPath(res, strElt); - } else { - char separator = '/'; - int needsSep = 0; - - if (fsPtr->filesystemSeparatorProc != NULL) { - Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(res); - if (sep != NULL) { - separator = Tcl_GetString(sep)[0]; - } - } - - if (length > 0 && ptr[length -1] != '/') { - Tcl_AppendToObj(res, &separator, 1); - length++; - } - Tcl_SetObjLength(res, length + (int) strlen(strElt)); - - ptr = Tcl_GetString(res) + length; - for (; *strElt != '\0'; strElt++) { - if (*strElt == separator) { - while (strElt[1] == separator) { - strElt++; - } - if (strElt[1] != '\0') { - if (needsSep) { - *ptr++ = separator; - } - } - } else { - *ptr++ = *strElt; - needsSep = 1; - } - } - length = ptr - Tcl_GetString(res); - Tcl_SetObjLength(res, length); - } + if ((fsRecPtr != NULL) + && (fromFilesystem->internalToNormalizedProc != NULL)) { + return (*fromFilesystem->internalToNormalizedProc)(clientData); + } else { + return NULL; } - return res; } /* @@ -3374,7 +3016,7 @@ Tcl_FSJoinPath(listObj, elements) *---------------------------------------------------------------------- */ -static Tcl_PathType +Tcl_PathType GetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr, driveNameRef) Tcl_Obj *pathObjPtr; Tcl_Filesystem **filesystemPtrPtr; @@ -3810,1135 +3452,77 @@ Tcl_FSRemoveDirectory(pathPtr, recursive, errorPtr) /* *--------------------------------------------------------------------------- * - * Tcl_FSConvertToPathType -- - * - * This function tries to convert the given Tcl_Obj to a valid - * Tcl path type, taking account of the fact that the cwd may - * have changed even if this object is already supposedly of - * the correct type. - * - * The filename may begin with "~" (to indicate current user's - * home directory) or "~<user>" (to indicate any user's home - * directory). - * - * Results: - * Standard Tcl error code. - * - * Side effects: - * The old representation may be freed, and new memory allocated. - * - *--------------------------------------------------------------------------- - */ -int -Tcl_FSConvertToPathType(interp, objPtr) - Tcl_Interp *interp; /* Interpreter in which to store error - * message (if necessary). */ - Tcl_Obj *objPtr; /* Object to convert to a valid, current - * path type. */ -{ - /* - * While it is bad practice to examine an object's type directly, - * this is actually the best thing to do here. The reason is that - * if we are converting this object to FsPath type for the first - * time, we don't need to worry whether the 'cwd' has changed. - * On the other hand, if this object is already of FsPath type, - * and is a relative path, we do have to worry about the cwd. - * If the cwd has changed, we must recompute the path. - */ - if (objPtr->typePtr == &tclFsPathType) { - FsPath *fsPathPtr = (FsPath*) objPtr->internalRep.otherValuePtr; - if (fsPathPtr->filesystemEpoch != theFilesystemEpoch) { - if (objPtr->bytes == NULL) { - UpdateStringOfFsPath(objPtr); - } - FreeFsPathInternalRep(objPtr); - objPtr->typePtr = NULL; - return Tcl_ConvertToType(interp, objPtr, &tclFsPathType); - } - return TCL_OK; - /* - * This code is intentionally never reached. Once fs-optimisation - * is complete, it will be removed/replaced - */ - if (fsPathPtr->cwdPtr == NULL) { - return TCL_OK; - } else { - if (FsCwdPointerEquals(fsPathPtr->cwdPtr)) { - return TCL_OK; - } else { - if (objPtr->bytes == NULL) { - UpdateStringOfFsPath(objPtr); - } - FreeFsPathInternalRep(objPtr); - objPtr->typePtr = NULL; - return Tcl_ConvertToType(interp, objPtr, &tclFsPathType); - } - } - } else { - return Tcl_ConvertToType(interp, objPtr, &tclFsPathType); - } -} - - -/* - * Helper function for SetFsPathFromAny. Returns position of first - * directory delimiter in the path. - */ -static int -FindSplitPos(path, separator) - char *path; - char *separator; -{ - int count = 0; - switch (tclPlatform) { - case TCL_PLATFORM_UNIX: - case TCL_PLATFORM_MAC: - while (path[count] != 0) { - if (path[count] == *separator) { - return count; - } - count++; - } - break; - - case TCL_PLATFORM_WINDOWS: - while (path[count] != 0) { - if (path[count] == *separator || path[count] == '\\') { - return count; - } - count++; - } - break; - } - return count; -} - -/* - *--------------------------------------------------------------------------- - * - * UpdateStringOfFsPath -- - * - * Gives an object a valid string rep. - * - * Results: - * None. - * - * Side effects: - * Memory may be allocated. - * - *--------------------------------------------------------------------------- - */ - -static void -UpdateStringOfFsPath(objPtr) - register Tcl_Obj *objPtr; /* path obj with string rep to update. */ -{ - register FsPath* fsPathPtr = - (FsPath*) objPtr->internalRep.otherValuePtr; - CONST char *cwdStr; - int cwdLen; - Tcl_Obj *copy; - - if (fsPathPtr->flags == 0 || fsPathPtr->cwdPtr == NULL) { - panic("Called UpdateStringOfFsPath with invalid object"); - } - - copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr); - Tcl_IncrRefCount(copy); - - cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); - /* - * Should we perhaps use 'Tcl_FSPathSeparator'? - * But then what about the Windows special case? - * Perhaps we should just check if cwd is a root volume. - * We should never get cwdLen == 0 in this code path. - */ - switch (tclPlatform) { - case TCL_PLATFORM_UNIX: - if (cwdStr[cwdLen-1] != '/') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - case TCL_PLATFORM_WINDOWS: - /* - * We need the extra 'cwdLen != 2', and ':' checks because - * a volume relative path doesn't get a '/'. For example - * 'glob C:*cat*.exe' will return 'C:cat32.exe' - */ - if (cwdStr[cwdLen-1] != '/' - && cwdStr[cwdLen-1] != '\\') { - if (cwdLen != 2 || cwdStr[1] != ':') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - } - break; - case TCL_PLATFORM_MAC: - if (cwdStr[cwdLen-1] != ':') { - Tcl_AppendToObj(copy, ":", 1); - cwdLen++; - } - break; - } - Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr); - objPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen); - objPtr->length = cwdLen; - copy->bytes = tclEmptyStringRep; - copy->length = 0; - Tcl_DecrRefCount(copy); -} - -/* - *--------------------------------------------------------------------------- - * - * TclNewFSPathObj -- - * - * Creates a path object whose string representation is - * '[file join dirPtr addStrRep]', but does so in a way that - * allows for more efficient caching of normalized paths. - * - * Assumptions: - * 'dirPtr' must be an absolute path. - * 'len' may not be zero. - * - * Results: - * The new Tcl object. - * - * Side effects: - * Memory is allocated. 'dirPtr' gets an additional refCount. - * - *--------------------------------------------------------------------------- - */ - -Tcl_Obj* -TclNewFSPathObj(Tcl_Obj *dirPtr, CONST char *addStrRep, int len) -{ - FsPath *fsPathPtr; - Tcl_Obj *objPtr; - - objPtr = Tcl_NewObj(); - fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath)); - - if (tclPlatform == TCL_PLATFORM_MAC) { - /* - * Mac relative paths may begin with a directory separator ':'. - * If present, we need to skip this ':' because we assume that - * we can join dirPtr and addStrRep by concatenating them as - * strings (and we ensure that dirPtr is terminated by a ':'). - */ - if (addStrRep[0] == ':') { - addStrRep++; - len--; - } - } - /* Setup the path */ - fsPathPtr->translatedPathPtr = NULL; - fsPathPtr->normPathPtr = Tcl_NewStringObj(addStrRep, len); - Tcl_IncrRefCount(fsPathPtr->normPathPtr); - fsPathPtr->cwdPtr = dirPtr; - Tcl_IncrRefCount(dirPtr); - fsPathPtr->flags = TCLPATH_RELATIVE | TCLPATH_APPENDED; - fsPathPtr->nativePathPtr = NULL; - fsPathPtr->fsRecPtr = NULL; - fsPathPtr->filesystemEpoch = theFilesystemEpoch; - - objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr; - objPtr->typePtr = &tclFsPathType; - objPtr->bytes = NULL; - objPtr->length = 0; - return objPtr; -} - -/* - *--------------------------------------------------------------------------- - * - * MakeFsPathFromRelative -- - * - * Like SetFsPathFromAny, but assumes the given object is an - * absolute normalized path. Only for internal use. - * - * Results: - * Standard Tcl error code. - * - * Side effects: - * The old representation may be freed, and new memory allocated. - * - *--------------------------------------------------------------------------- - */ - -static Tcl_Obj* -MakeFsPathFromRelative(interp, objPtr, cwdPtr) - Tcl_Interp *interp; /* Used for error reporting if not NULL. */ - Tcl_Obj *objPtr; /* The object to convert. */ - Tcl_Obj *cwdPtr; /* The object to convert. */ -{ - FsPath *fsPathPtr; - - /* Free old representation */ - if (objPtr->typePtr != NULL) { - if (objPtr->bytes == NULL) { - if (objPtr->typePtr->updateStringProc == NULL) { - if (interp != NULL) { - Tcl_ResetResult(interp); - Tcl_AppendResult(interp, "can't find object", - "string representation", (char *) NULL); - } - return NULL; - } - objPtr->typePtr->updateStringProc(objPtr); - } - if ((objPtr->typePtr->freeIntRepProc) != NULL) { - (*objPtr->typePtr->freeIntRepProc)(objPtr); - } - } - - fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath)); - - /* Circular reference, by design */ - fsPathPtr->translatedPathPtr = objPtr; - fsPathPtr->normPathPtr = NULL; - fsPathPtr->flags = 0; - fsPathPtr->cwdPtr = cwdPtr; - Tcl_IncrRefCount(cwdPtr); - fsPathPtr->nativePathPtr = NULL; - fsPathPtr->fsRecPtr = NULL; - fsPathPtr->filesystemEpoch = theFilesystemEpoch; - - objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr; - objPtr->typePtr = &tclFsPathType; - - return objPtr; -} - -/* - *--------------------------------------------------------------------------- - * - * SetFsPathFromAbsoluteNormalized -- - * - * Like SetFsPathFromAny, but assumes the given object is an - * absolute normalized path. Only for internal use. - * - * Results: - * Standard Tcl error code. - * - * Side effects: - * The old representation may be freed, and new memory allocated. - * - *--------------------------------------------------------------------------- - */ - -static int -SetFsPathFromAbsoluteNormalized(interp, objPtr) - Tcl_Interp *interp; /* Used for error reporting if not NULL. */ - Tcl_Obj *objPtr; /* The object to convert. */ -{ - FsPath *fsPathPtr; - - if (objPtr->typePtr == &tclFsPathType) { - return TCL_OK; - } - - /* Free old representation */ - if (objPtr->typePtr != NULL) { - if (objPtr->bytes == NULL) { - if (objPtr->typePtr->updateStringProc == NULL) { - if (interp != NULL) { - Tcl_ResetResult(interp); - Tcl_AppendResult(interp, "can't find object", - "string representation", (char *) NULL); - } - return TCL_ERROR; - } - objPtr->typePtr->updateStringProc(objPtr); - } - if ((objPtr->typePtr->freeIntRepProc) != NULL) { - (*objPtr->typePtr->freeIntRepProc)(objPtr); - } - } - - fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath)); - /* It's a pure normalized absolute path */ - fsPathPtr->translatedPathPtr = NULL; - fsPathPtr->normPathPtr = objPtr; - fsPathPtr->flags = 0; - fsPathPtr->cwdPtr = NULL; - fsPathPtr->nativePathPtr = NULL; - fsPathPtr->fsRecPtr = NULL; - fsPathPtr->filesystemEpoch = theFilesystemEpoch; - - objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr; - objPtr->typePtr = &tclFsPathType; - - return TCL_OK; -} - -/* - *--------------------------------------------------------------------------- - * - * SetFsPathFromAny -- + * Tcl_FSGetFileSystemForPath -- * - * This function tries to convert the given Tcl_Obj to a valid - * Tcl path type. - * - * The filename may begin with "~" (to indicate current user's - * home directory) or "~<user>" (to indicate any user's home - * directory). + * This function determines which filesystem to use for a + * particular path object, and returns the filesystem which + * accepts this file. If no filesystem will accept this object + * as a valid file path, then NULL is returned. * * Results: - * Standard Tcl error code. +.* NULL or a filesystem which will accept this path. * * Side effects: - * The old representation may be freed, and new memory allocated. + * The object may be converted to a path type. * *--------------------------------------------------------------------------- */ -static int -SetFsPathFromAny(interp, objPtr) - Tcl_Interp *interp; /* Used for error reporting if not NULL. */ - Tcl_Obj *objPtr; /* The object to convert. */ +Tcl_Filesystem* +Tcl_FSGetFileSystemForPath(pathObjPtr) + Tcl_Obj* pathObjPtr; { - int len; - FsPath *fsPathPtr; - Tcl_Obj *transPtr; - char *name; - - if (objPtr->typePtr == &tclFsPathType) { - return TCL_OK; - } + FilesystemRecord *fsRecPtr; + Tcl_Filesystem* retVal = NULL; /* - * First step is to translate the filename. This is similar to - * Tcl_TranslateFilename, but shouldn't convert everything to - * windows backslashes on that platform. The current - * implementation of this piece is a slightly optimised version - * of the various Tilde/Split/Join stuff to avoid multiple - * split/join operations. - * - * We remove any trailing directory separator. - * - * However, the split/join routines are quite complex, and - * one has to make sure not to break anything on Unix, Win - * or MacOS (fCmd.test, fileName.test and cmdAH.test exercise - * most of the code). - */ - name = Tcl_GetStringFromObj(objPtr,&len); - - /* - * Handle tilde substitutions, if needed. - */ - if (name[0] == '~') { - char *expandedUser; - Tcl_DString temp; - int split; - char separator='/'; - - if (tclPlatform==TCL_PLATFORM_MAC) { - if (strchr(name, ':') != NULL) separator = ':'; - } - - split = FindSplitPos(name, &separator); - if (split != len) { - /* We have multiple pieces '~user/foo/bar...' */ - name[split] = '\0'; - } - /* Do some tilde substitution */ - if (name[1] == '\0') { - /* We have just '~' */ - CONST char *dir; - Tcl_DString dirString; - if (split != len) { name[split] = separator; } - - dir = TclGetEnv("HOME", &dirString); - if (dir == NULL) { - if (interp) { - Tcl_ResetResult(interp); - Tcl_AppendResult(interp, "couldn't find HOME environment ", - "variable to expand path", (char *) NULL); - } - return TCL_ERROR; - } - Tcl_DStringInit(&temp); - Tcl_JoinPath(1, &dir, &temp); - Tcl_DStringFree(&dirString); - } else { - /* We have a user name '~user' */ - Tcl_DStringInit(&temp); - if (TclpGetUserHome(name+1, &temp) == NULL) { - if (interp != NULL) { - Tcl_ResetResult(interp); - Tcl_AppendResult(interp, "user \"", (name+1), - "\" doesn't exist", (char *) NULL); - } - Tcl_DStringFree(&temp); - if (split != len) { name[split] = separator; } - return TCL_ERROR; - } - if (split != len) { name[split] = separator; } - } - - expandedUser = Tcl_DStringValue(&temp); - transPtr = Tcl_NewStringObj(expandedUser, Tcl_DStringLength(&temp)); - - if (split != len) { - /* Join up the tilde substitution with the rest */ - if (name[split+1] == separator) { - - /* - * Somewhat tricky case like ~//foo/bar. - * Make use of Split/Join machinery to get it right. - * Assumes all paths beginning with ~ are part of the - * native filesystem. - */ - - int objc; - Tcl_Obj **objv; - Tcl_Obj *parts = TclpNativeSplitPath(objPtr, NULL); - Tcl_ListObjGetElements(NULL, parts, &objc, &objv); - /* Skip '~'. It's replaced by its expansion */ - objc--; objv++; - while (objc--) { - TclpNativeJoinPath(transPtr, Tcl_GetString(*objv++)); - } - Tcl_DecrRefCount(parts); - } else { - /* Simple case. "rest" is relative path. Just join it. */ - Tcl_Obj *rest = Tcl_NewStringObj(name+split+1,-1); - transPtr = Tcl_FSJoinToPath(transPtr, 1, &rest); - } - } - Tcl_DStringFree(&temp); - } else { - transPtr = Tcl_FSJoinToPath(objPtr,0,NULL); - } - - /* - * Now we have a translated filename in 'transPtr'. This will have - * forward slashes on Windows, and will not contain any ~user - * sequences. + * If the object has a refCount of zero, we reject it. This + * is to avoid possible segfaults or nondeterministic memory + * leaks (i.e. the user doesn't know if they should decrement + * the ref count on return or not). */ - fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath)); - fsPathPtr->translatedPathPtr = transPtr; - Tcl_IncrRefCount(fsPathPtr->translatedPathPtr); - fsPathPtr->normPathPtr = NULL; - fsPathPtr->flags = 0; - fsPathPtr->cwdPtr = NULL; - fsPathPtr->nativePathPtr = NULL; - fsPathPtr->fsRecPtr = NULL; - fsPathPtr->filesystemEpoch = theFilesystemEpoch; - - /* - * Free old representation before installing our new one. - */ - if (objPtr->typePtr != NULL && objPtr->typePtr->freeIntRepProc != NULL) { - (objPtr->typePtr->freeIntRepProc)(objPtr); - } - objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr; - objPtr->typePtr = &tclFsPathType; - - return TCL_OK; -} - -/* - *--------------------------------------------------------------------------- - * - * Tcl_FSNewNativePath -- - * - * This function performs the something like that reverse of the - * usual obj->path->nativerep conversions. If some code retrieves - * a path in native form (from, e.g. readlink or a native dialog), - * and that path is to be used at the Tcl level, then calling - * this function is an efficient way of creating the appropriate - * path object type. - * - * Any memory which is allocated for 'clientData' should be retained - * until clientData is passed to the filesystem's freeInternalRepProc - * when it can be freed. The built in platform-specific filesystems - * use 'ckalloc' to allocate clientData, and ckfree to free it. - * - * Results: - * NULL or a valid path object pointer, with refCount zero. - * - * Side effects: - * New memory may be allocated. - * - *--------------------------------------------------------------------------- - */ - -Tcl_Obj * -Tcl_FSNewNativePath(fromFilesystem, clientData) - Tcl_Filesystem* fromFilesystem; - ClientData clientData; -{ - Tcl_Obj *objPtr; - FsPath *fsPathPtr; - FilesystemRecord *fsFromPtr; - Tcl_FSInternalToNormalizedProc *proc; - int epoch; - - fsFromPtr = GetFilesystemRecord(fromFilesystem, &epoch); - - if (fsFromPtr == NULL) { + if (pathObjPtr->refCount == 0) { + panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0"); return NULL; } - proc = fsFromPtr->fsPtr->internalToNormalizedProc; - - if (proc == NULL) { - return NULL; - } - - objPtr = (*proc)(clientData); - if (objPtr == NULL) { - return NULL; - } - /* - * Free old representation; shouldn't normally be any, - * but best to be safe. + * Check if the filesystem has changed in some way since + * this object's internal representation was calculated. */ - if (objPtr->typePtr != NULL) { - if (objPtr->bytes == NULL) { - if (objPtr->typePtr->updateStringProc == NULL) { - return NULL; - } - objPtr->typePtr->updateStringProc(objPtr); - } - if ((objPtr->typePtr->freeIntRepProc) != NULL) { - (*objPtr->typePtr->freeIntRepProc)(objPtr); - } - } - - fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath)); - fsPathPtr->translatedPathPtr = NULL; - /* Circular reference, by design */ - fsPathPtr->normPathPtr = objPtr; - fsPathPtr->flags = 0; - fsPathPtr->cwdPtr = NULL; - fsPathPtr->nativePathPtr = clientData; - fsPathPtr->fsRecPtr = fsFromPtr; - /* We must increase the refCount for this filesystem. */ - fsPathPtr->fsRecPtr->fileRefCount++; - fsPathPtr->filesystemEpoch = epoch; - - objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr; - objPtr->typePtr = &tclFsPathType; - return objPtr; -} - -static void -FreeFsPathInternalRep(pathObjPtr) - Tcl_Obj *pathObjPtr; /* Path object with internal rep to free. */ -{ - register FsPath* fsPathPtr = - (FsPath*) pathObjPtr->internalRep.otherValuePtr; - - if (fsPathPtr->translatedPathPtr != NULL) { - if (fsPathPtr->translatedPathPtr != pathObjPtr) { - Tcl_DecrRefCount(fsPathPtr->translatedPathPtr); - } - } - if (fsPathPtr->normPathPtr != NULL) { - if (fsPathPtr->normPathPtr != pathObjPtr) { - Tcl_DecrRefCount(fsPathPtr->normPathPtr); - } - fsPathPtr->normPathPtr = NULL; - } - if (fsPathPtr->cwdPtr != NULL) { - Tcl_DecrRefCount(fsPathPtr->cwdPtr); - } - if (fsPathPtr->nativePathPtr != NULL) { - if (fsPathPtr->fsRecPtr != NULL) { - if (fsPathPtr->fsRecPtr->fsPtr->freeInternalRepProc != NULL) { - (*fsPathPtr->fsRecPtr->fsPtr - ->freeInternalRepProc)(fsPathPtr->nativePathPtr); - fsPathPtr->nativePathPtr = NULL; - } - } - } - if (fsPathPtr->fsRecPtr != NULL) { - fsPathPtr->fsRecPtr->fileRefCount--; - if (fsPathPtr->fsRecPtr->fileRefCount <= 0) { - /* It has been unregistered already */ - ckfree((char *)fsPathPtr->fsRecPtr); - } - } - - ckfree((char*) fsPathPtr); -} - -static void -DupFsPathInternalRep(srcPtr, copyPtr) - Tcl_Obj *srcPtr; /* Path obj with internal rep to copy. */ - Tcl_Obj *copyPtr; /* Path obj with internal rep to set. */ -{ - register FsPath* srcFsPathPtr = - (FsPath*) srcPtr->internalRep.otherValuePtr; - register FsPath* copyFsPathPtr = - (FsPath*) ckalloc((unsigned)sizeof(FsPath)); - Tcl_FSDupInternalRepProc *dupProc; - - copyPtr->internalRep.otherValuePtr = (VOID *) copyFsPathPtr; - - if (srcFsPathPtr->translatedPathPtr != NULL) { - copyFsPathPtr->translatedPathPtr = srcFsPathPtr->translatedPathPtr; - if (copyFsPathPtr->translatedPathPtr != copyPtr) { - Tcl_IncrRefCount(copyFsPathPtr->translatedPathPtr); - } - } else { - copyFsPathPtr->translatedPathPtr = NULL; - } - - if (srcFsPathPtr->normPathPtr != NULL) { - copyFsPathPtr->normPathPtr = srcFsPathPtr->normPathPtr; - if (copyFsPathPtr->normPathPtr != copyPtr) { - Tcl_IncrRefCount(copyFsPathPtr->normPathPtr); - } - } else { - copyFsPathPtr->normPathPtr = NULL; - } - - if (srcFsPathPtr->cwdPtr != NULL) { - copyFsPathPtr->cwdPtr = srcFsPathPtr->cwdPtr; - Tcl_IncrRefCount(copyFsPathPtr->cwdPtr); - } else { - copyFsPathPtr->cwdPtr = NULL; - } - - copyFsPathPtr->flags = srcFsPathPtr->flags; - - if (srcFsPathPtr->fsRecPtr != NULL - && srcFsPathPtr->nativePathPtr != NULL) { - dupProc = srcFsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc; - if (dupProc != NULL) { - copyFsPathPtr->nativePathPtr = - (*dupProc)(srcFsPathPtr->nativePathPtr); - } else { - copyFsPathPtr->nativePathPtr = NULL; - } - } else { - copyFsPathPtr->nativePathPtr = NULL; - } - copyFsPathPtr->fsRecPtr = srcFsPathPtr->fsRecPtr; - copyFsPathPtr->filesystemEpoch = srcFsPathPtr->filesystemEpoch; - if (copyFsPathPtr->fsRecPtr != NULL) { - copyFsPathPtr->fsRecPtr->fileRefCount++; - } - - copyPtr->typePtr = &tclFsPathType; -} - -/* - *--------------------------------------------------------------------------- - * - * Tcl_FSGetTranslatedPath -- - * - * This function attempts to extract the translated path - * from the given Tcl_Obj. If the translation succeeds (i.e. the - * object is a valid path), then it is returned. Otherwise NULL - * will be returned, and an error message may be left in the - * interpreter (if it is non-NULL) - * - * Results: - * NULL or a valid Tcl_Obj pointer. - * - * Side effects: - * Only those of 'Tcl_FSConvertToPathType' - * - *--------------------------------------------------------------------------- - */ - -Tcl_Obj* -Tcl_FSGetTranslatedPath(interp, pathPtr) - Tcl_Interp *interp; - Tcl_Obj* pathPtr; -{ - register FsPath* srcFsPathPtr; - if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) { + if (TclFSEnsureEpochOk(pathObjPtr, theFilesystemEpoch, + &retVal) != TCL_OK) { return NULL; } - srcFsPathPtr = (FsPath*) pathPtr->internalRep.otherValuePtr; - if (srcFsPathPtr->translatedPathPtr == NULL) { - if (srcFsPathPtr->flags != 0) { - return Tcl_FSGetNormalizedPath(interp, pathPtr); - } - /* - * It is a pure absolute, normalized path object. - * This is something like being a 'pure list'. The - * object's string, translatedPath and normalizedPath - * are all identical. - */ - return srcFsPathPtr->normPathPtr; - } else { - /* It is an ordinary path object */ - return srcFsPathPtr->translatedPathPtr; - } -} - -/* - *--------------------------------------------------------------------------- - * - * Tcl_FSGetTranslatedStringPath -- - * - * This function attempts to extract the translated path - * from the given Tcl_Obj. If the translation succeeds (i.e. the - * object is a valid path), then the path is returned. Otherwise NULL - * will be returned, and an error message may be left in the - * interpreter (if it is non-NULL) - * - * Results: - * NULL or a valid string. - * - * Side effects: - * Only those of 'Tcl_FSConvertToPathType' - * - *--------------------------------------------------------------------------- - */ -CONST char* -Tcl_FSGetTranslatedStringPath(interp, pathPtr) - Tcl_Interp *interp; - Tcl_Obj* pathPtr; -{ - Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr); - if (transPtr == NULL) { - return NULL; - } else { - return Tcl_GetString(transPtr); - } -} - -/* - *--------------------------------------------------------------------------- - * - * Tcl_FSGetNormalizedPath -- - * - * This important function attempts to extract from the given Tcl_Obj - * a unique normalised path representation, whose string value can - * be used as a unique identifier for the file. - * - * Results: - * NULL or a valid path object pointer. - * - * Side effects: - * New memory may be allocated. The Tcl 'errno' may be modified - * in the process of trying to examine various path possibilities. - * - *--------------------------------------------------------------------------- - */ - -Tcl_Obj* -Tcl_FSGetNormalizedPath(interp, pathObjPtr) - Tcl_Interp *interp; - Tcl_Obj* pathObjPtr; -{ - register FsPath* fsPathPtr; - if (Tcl_FSConvertToPathType(interp, pathObjPtr) != TCL_OK) { - return NULL; - } - fsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr; - - if (fsPathPtr->flags != 0) { - /* - * This is a special path object which is the result of - * something like 'file join' - */ - Tcl_Obj *dir, *copy; - int cwdLen; - int pathType; - CONST char *cwdStr; - - pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr); - dir = Tcl_FSGetNormalizedPath(interp, fsPathPtr->cwdPtr); - if (dir == NULL) { - return NULL; - } - if (pathObjPtr->bytes == NULL) { - UpdateStringOfFsPath(pathObjPtr); - } - copy = Tcl_DuplicateObj(dir); - Tcl_IncrRefCount(copy); - Tcl_IncrRefCount(dir); - /* We now own a reference on both 'dir' and 'copy' */ - - cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); - /* - * Should we perhaps use 'Tcl_FSPathSeparator'? - * But then what about the Windows special case? - * Perhaps we should just check if cwd is a root volume. - * We should never get cwdLen == 0 in this code path. - */ - switch (tclPlatform) { - case TCL_PLATFORM_UNIX: - if (cwdStr[cwdLen-1] != '/') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - case TCL_PLATFORM_WINDOWS: - if (cwdStr[cwdLen-1] != '/' - && cwdStr[cwdLen-1] != '\\') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - case TCL_PLATFORM_MAC: - if (cwdStr[cwdLen-1] != ':') { - Tcl_AppendToObj(copy, ":", 1); - cwdLen++; - } - break; - } - Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr); - /* - * Normalize the combined string, but only starting after - * the end of the previously normalized 'dir'. This should - * be much faster! We use 'cwdLen-1' so that we are - * already pointing at the dir-separator that we know about. - * The normalization code will actually start off directly - * after that separator. - */ - TclNormalizeToUniquePath(interp, copy, cwdLen-1); - /* Now we need to construct the new path object */ - - if (pathType == TCL_PATH_RELATIVE) { - register FsPath* origDirFsPathPtr; - Tcl_Obj *origDir = fsPathPtr->cwdPtr; - origDirFsPathPtr = (FsPath*) origDir->internalRep.otherValuePtr; - - fsPathPtr->cwdPtr = origDirFsPathPtr->cwdPtr; - Tcl_IncrRefCount(fsPathPtr->cwdPtr); - - Tcl_DecrRefCount(fsPathPtr->normPathPtr); - fsPathPtr->normPathPtr = copy; - /* That's our reference to copy used */ - Tcl_DecrRefCount(dir); - Tcl_DecrRefCount(origDir); - } else { - Tcl_DecrRefCount(fsPathPtr->cwdPtr); - fsPathPtr->cwdPtr = NULL; - Tcl_DecrRefCount(fsPathPtr->normPathPtr); - fsPathPtr->normPathPtr = copy; - /* That's our reference to copy used */ - Tcl_DecrRefCount(dir); - } - fsPathPtr->flags = 0; - } - /* Ensure cwd hasn't changed */ - if (fsPathPtr->cwdPtr != NULL) { - if (!FsCwdPointerEquals(fsPathPtr->cwdPtr)) { - if (pathObjPtr->bytes == NULL) { - UpdateStringOfFsPath(pathObjPtr); - } - FreeFsPathInternalRep(pathObjPtr); - pathObjPtr->typePtr = NULL; - if (Tcl_ConvertToType(interp, pathObjPtr, - &tclFsPathType) != TCL_OK) { - return NULL; - } - fsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr; - } else if (fsPathPtr->normPathPtr == NULL) { - int cwdLen; - Tcl_Obj *copy; - CONST char *cwdStr; - - copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr); - Tcl_IncrRefCount(copy); - cwdStr = Tcl_GetStringFromObj(copy, &cwdLen); - /* - * Should we perhaps use 'Tcl_FSPathSeparator'? - * But then what about the Windows special case? - * Perhaps we should just check if cwd is a root volume. - * We should never get cwdLen == 0 in this code path. - */ - switch (tclPlatform) { - case TCL_PLATFORM_UNIX: - if (cwdStr[cwdLen-1] != '/') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - case TCL_PLATFORM_WINDOWS: - if (cwdStr[cwdLen-1] != '/' - && cwdStr[cwdLen-1] != '\\') { - Tcl_AppendToObj(copy, "/", 1); - cwdLen++; - } - break; - case TCL_PLATFORM_MAC: - if (cwdStr[cwdLen-1] != ':') { - Tcl_AppendToObj(copy, ":", 1); - cwdLen++; - } - break; - } - Tcl_AppendObjToObj(copy, pathObjPtr); - /* - * Normalize the combined string, but only starting after - * the end of the previously normalized 'dir'. This should - * be much faster! - */ - TclNormalizeToUniquePath(interp, copy, cwdLen-1); - fsPathPtr->normPathPtr = copy; - } - } - if (fsPathPtr->normPathPtr == NULL) { - int relative = 0; - /* - * Since normPathPtr is NULL, but this is a valid path - * object, we know that the translatedPathPtr cannot be NULL. - */ - Tcl_Obj *absolutePath = fsPathPtr->translatedPathPtr; - char *path = Tcl_GetString(absolutePath); - - /* - * We have to be a little bit careful here to avoid infinite loops - * we're asking Tcl_FSGetPathType to return the path's type, but - * that call can actually result in a lot of other filesystem - * action, which might loop back through here. - */ - if ((path[0] != '\0') && - (Tcl_FSGetPathType(pathObjPtr) == TCL_PATH_RELATIVE)) { - Tcl_Obj *cwd = Tcl_FSGetCwd(interp); - - if (cwd == NULL) { - return NULL; - } - absolutePath = Tcl_FSJoinToPath(cwd, 1, &absolutePath); - Tcl_IncrRefCount(absolutePath); - Tcl_DecrRefCount(cwd); - - relative = 1; - } - /* Already has refCount incremented */ - fsPathPtr->normPathPtr = FSNormalizeAbsolutePath(interp, absolutePath); - if (!strcmp(Tcl_GetString(fsPathPtr->normPathPtr), - Tcl_GetString(pathObjPtr))) { - /* - * The path was already normalized. - * Get rid of the duplicate. - */ - Tcl_DecrRefCount(fsPathPtr->normPathPtr); - /* - * We do *not* increment the refCount for - * this circular reference - */ - fsPathPtr->normPathPtr = pathObjPtr; - } - if (relative) { - /* This was returned by Tcl_FSJoinToPath above */ - Tcl_DecrRefCount(absolutePath); - - /* Get a quick, temporary lock on the cwd while we copy it */ - Tcl_MutexLock(&cwdMutex); - fsPathPtr->cwdPtr = cwdPathPtr; - Tcl_IncrRefCount(fsPathPtr->cwdPtr); - Tcl_MutexUnlock(&cwdMutex); - } - } - return fsPathPtr->normPathPtr; -} - -/* - *--------------------------------------------------------------------------- - * - * Tcl_FSGetInternalRep -- - * - * Extract the internal representation of a given path object, - * in the given filesystem. If the path object belongs to a - * different filesystem, we return NULL. - * - * If the internal representation is currently NULL, we attempt - * to generate it, by calling the filesystem's - * 'Tcl_FSCreateInternalRepProc'. - * - * Results: - * NULL or a valid internal representation. - * - * Side effects: - * An attempt may be made to convert the object. - * - *--------------------------------------------------------------------------- - */ - -ClientData -Tcl_FSGetInternalRep(pathObjPtr, fsPtr) - Tcl_Obj* pathObjPtr; - Tcl_Filesystem *fsPtr; -{ - register FsPath* srcFsPathPtr; - - if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) { - return NULL; - } - srcFsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr; - - /* - * We will only return the native representation for the caller's - * filesystem. Otherwise we will simply return NULL. This means - * that there must be a unique bi-directional mapping between paths - * and filesystems, and that this mapping will not allow 'remapped' - * files -- files which are in one filesystem but mapped into - * another. Another way of putting this is that 'stacked' - * filesystems are not allowed. We recognise that this is a - * potentially useful feature for the future. - * - * Even something simple like a 'pass through' filesystem which - * logs all activity and passes the calls onto the native system - * would be nice, but not easily achievable with the current - * implementation. + /* + * Call each of the "pathInFilesystem" functions in succession. A + * non-return value of -1 indicates the particular function has + * succeeded. */ - if (srcFsPathPtr->fsRecPtr == NULL) { - /* - * This only usually happens in wrappers like TclpStat which - * create a string object and pass it to TclpObjStat. Code - * which calls the Tcl_FS.. functions should always have a - * filesystem already set. Whether this code path is legal or - * not depends on whether we decide to allow external code to - * call the native filesystem directly. It is at least safer - * to allow this sub-optimal routing. - */ - Tcl_FSGetFileSystemForPath(pathObjPtr); - - /* - * If we fail through here, then the path is probably not a - * valid path in the filesystsem, and is most likely to be a - * use of the empty path "" via a direct call to one of the - * objectified interfaces (e.g. from the Tcl testsuite). - */ - srcFsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr; - if (srcFsPathPtr->fsRecPtr == NULL) { - return NULL; - } - } - if (fsPtr != srcFsPathPtr->fsRecPtr->fsPtr) { - /* - * There is still one possibility we should consider; if the - * file belongs to a different filesystem, perhaps it is - * actually linked through to a file in our own filesystem - * which we do care about. The way we can check for this - * is we ask what filesystem this path belongs to. - */ - Tcl_Filesystem *actualFs = Tcl_FSGetFileSystemForPath(pathObjPtr); - if (actualFs == fsPtr) { - return Tcl_FSGetInternalRep(pathObjPtr, fsPtr); + fsRecPtr = FsGetIterator(); + while ((retVal == NULL) && (fsRecPtr != NULL)) { + Tcl_FSPathInFilesystemProc *proc = fsRecPtr->fsPtr->pathInFilesystemProc; + if (proc != NULL) { + ClientData clientData = NULL; + int ret = (*proc)(pathObjPtr, &clientData); + if (ret != -1) { + /* + * We assume the type of pathObjPtr hasn't been changed + * by the above call to the pathInFilesystemProc. + */ + TclFSSetPathDetails(pathObjPtr, fsRecPtr, clientData, + theFilesystemEpoch); + retVal = fsRecPtr->fsPtr; + } } - return NULL; + fsRecPtr = fsRecPtr->nextPtr; } + FsReleaseIterator(); - if (srcFsPathPtr->nativePathPtr == NULL) { - Tcl_FSCreateInternalRepProc *proc; - proc = srcFsPathPtr->fsRecPtr->fsPtr->createInternalRepProc; - - if (proc == NULL) { - return NULL; - } - srcFsPathPtr->nativePathPtr = (*proc)(pathObjPtr); - } - return srcFsPathPtr->nativePathPtr; + return retVal; } /* @@ -5101,7 +3685,7 @@ TclpNativeToNormalized(clientData) * *--------------------------------------------------------------------------- */ -static ClientData +ClientData NativeDupInternalRep(clientData) ClientData clientData; { @@ -5133,43 +3717,6 @@ NativeDupInternalRep(clientData) /* *--------------------------------------------------------------------------- * - * NativePathInFilesystem -- - * - * Any path object is acceptable to the native filesystem, by - * default (we will throw errors when illegal paths are actually - * tried to be used). - * - * However, this behavior means the native filesystem must be - * the last filesystem in the lookup list (otherwise it will - * claim all files belong to it, and other filesystems will - * never get a look in). - * - * Results: - * TCL_OK, to indicate 'yes', -1 to indicate no. - * - * Side effects: - * None. - * - *--------------------------------------------------------------------------- - */ -static int -NativePathInFilesystem(pathPtr, clientDataPtr) - Tcl_Obj *pathPtr; - ClientData *clientDataPtr; -{ - int len; - Tcl_GetStringFromObj(pathPtr,&len); - if (len == 0) { - return -1; - } else { - /* We accept any path as valid */ - return TCL_OK; - } -} - -/* - *--------------------------------------------------------------------------- - * * NativeFreeInternalRep -- * * Free a native internal representation, which will be non-NULL. @@ -5303,203 +3850,6 @@ NativeFilesystemSeparator(pathObjPtr) } return Tcl_NewStringObj(separator,1); } - -/* - *--------------------------------------------------------------------------- - * - * Tcl_FSGetFileSystemForPath -- - * - * This function determines which filesystem to use for a - * particular path object, and returns the filesystem which - * accepts this file. If no filesystem will accept this object - * as a valid file path, then NULL is returned. - * - * Results: -.* NULL or a filesystem which will accept this path. - * - * Side effects: - * The object may be converted to a path type. - * - *--------------------------------------------------------------------------- - */ - -Tcl_Filesystem* -Tcl_FSGetFileSystemForPath(pathObjPtr) - Tcl_Obj* pathObjPtr; -{ - FilesystemRecord *fsRecPtr; - Tcl_Filesystem* retVal = NULL; - FsPath* srcFsPathPtr; - - /* - * If the object has a refCount of zero, we reject it. This - * is to avoid possible segfaults or nondeterministic memory - * leaks (i.e. the user doesn't know if they should decrement - * the ref count on return or not). - */ - - if (pathObjPtr->refCount == 0) { - panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0"); - return NULL; - } - - /* - * This will ensure the pathObjPtr can be converted into a - * "path" type, and that we are able to generate a complete - * normalized path which is used to determine the filesystem - * match. - */ - - if (Tcl_FSGetNormalizedPath(NULL, pathObjPtr) == NULL) { - return NULL; - } - - /* - * Get a lock on theFilesystemEpoch and the filesystemList - * - * While we don't need the fsRecPtr until the while loop below, we - * do want to make sure the theFilesystemEpoch doesn't change - * between the 'if' and 'while' blocks, getting this iterator will - * ensure that everything is consistent - */ - fsRecPtr = FsGetIterator(); - - /* Make sure pathObjPtr is of the correct epoch */ - - srcFsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr; - - /* - * Check if the filesystem has changed in some way since - * this object's internal representation was calculated. - */ - if (srcFsPathPtr->filesystemEpoch != theFilesystemEpoch) { - /* - * We have to discard the stale representation and - * recalculate it - */ - if (pathObjPtr->bytes == NULL) { - UpdateStringOfFsPath(pathObjPtr); - } - FreeFsPathInternalRep(pathObjPtr); - pathObjPtr->typePtr = NULL; - if (SetFsPathFromAny(NULL, pathObjPtr) != TCL_OK) { - goto done; - } - srcFsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr; - } - - /* Check whether the object is already assigned to a fs */ - if (srcFsPathPtr->fsRecPtr != NULL) { - retVal = srcFsPathPtr->fsRecPtr->fsPtr; - goto done; - } - - /* - * Call each of the "pathInFilesystem" functions in succession. A - * non-return value of -1 indicates the particular function has - * succeeded. - */ - - while ((retVal == NULL) && (fsRecPtr != NULL)) { - Tcl_FSPathInFilesystemProc *proc = fsRecPtr->fsPtr->pathInFilesystemProc; - if (proc != NULL) { - ClientData clientData = NULL; - int ret = (*proc)(pathObjPtr, &clientData); - if (ret != -1) { - /* - * We assume the srcFsPathPtr hasn't been changed - * by the above call to the pathInFilesystemProc. - */ - srcFsPathPtr->fsRecPtr = fsRecPtr; - srcFsPathPtr->nativePathPtr = clientData; - srcFsPathPtr->filesystemEpoch = theFilesystemEpoch; - fsRecPtr->fileRefCount++; - retVal = fsRecPtr->fsPtr; - } - } - fsRecPtr = fsRecPtr->nextPtr; - } - - done: - FsReleaseIterator(); - return retVal; -} - -/* Simple helper function */ -static FilesystemRecord* -GetFilesystemRecord(fromFilesystem, epoch) - Tcl_Filesystem *fromFilesystem; - int *epoch; -{ - FilesystemRecord *fsRecPtr = FsGetIterator(); - while (fsRecPtr != NULL) { - if (fsRecPtr->fsPtr == fromFilesystem) { - *epoch = theFilesystemEpoch; - break; - } - fsRecPtr = fsRecPtr->nextPtr; - } - FsReleaseIterator(); - return fsRecPtr; -} - -/* - *--------------------------------------------------------------------------- - * - * Tcl_FSEqualPaths -- - * - * This function tests whether the two paths given are equal path - * objects. If either or both is NULL, 0 is always returned. - * - * Results: - * 1 or 0. - * - * Side effects: - * None. - * - *--------------------------------------------------------------------------- - */ - -int -Tcl_FSEqualPaths(firstPtr, secondPtr) - Tcl_Obj* firstPtr; - Tcl_Obj* secondPtr; -{ - if (firstPtr == secondPtr) { - return 1; - } else { - char *firstStr, *secondStr; - int firstLen, secondLen, tempErrno; - - if (firstPtr == NULL || secondPtr == NULL) { - return 0; - } - firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen); - secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen); - if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) { - return 1; - } - /* - * Try the most thorough, correct method of comparing fully - * normalized paths - */ - - tempErrno = Tcl_GetErrno(); - firstPtr = Tcl_FSGetNormalizedPath(NULL, firstPtr); - secondPtr = Tcl_FSGetNormalizedPath(NULL, secondPtr); - Tcl_SetErrno(tempErrno); - - if (firstPtr == NULL || secondPtr == NULL) { - return 0; - } - firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen); - secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen); - if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) { - return 1; - } - } - return 0; -} /* Everything from here on is contained in this obsolete ifdef */ #ifdef USE_OBSOLETE_FS_HOOKS |