diff options
Diffstat (limited to 'generic/tclIOUtil.c')
| -rw-r--r-- | generic/tclIOUtil.c | 4191 |
1 files changed, 2272 insertions, 1919 deletions
diff --git a/generic/tclIOUtil.c b/generic/tclIOUtil.c index b3643fe..82ffd88 100644 --- a/generic/tclIOUtil.c +++ b/generic/tclIOUtil.c @@ -1,22 +1,25 @@ /* * tclIOUtil.c -- * - * Provides an interface for managing filesystems in Tcl, and also for - * creating a filesystem interface in Tcl arbitrary facilities. All - * filesystem operations are performed via this interface. Vince Darley - * is the primary author. Other signifiant contributors are Karl - * Lehenbauer, Mark Diekhans and Peter da Silva. + * This file contains the implementation of Tcl's generic filesystem + * code, which supports a pluggable filesystem architecture allowing both + * platform specific filesystems and 'virtual filesystems'. All + * filesystem access should go through the functions defined in this + * file. Most of this code was contributed by Vince Darley. * - * Copyright © 1991-1994 The Regents of the University of California. - * Copyright © 1994-1997 Sun Microsystems, Inc. - * Copyright © 2001-2004 Vincent Darley. + * Parts of this file are based on code contributed by Karl Lehenbauer, + * Mark Diekhans and Peter da Silva. + * + * Copyright (c) 1991-1994 The Regents of the University of California. + * Copyright (c) 1994-1997 Sun Microsystems, Inc. + * Copyright (c) 2001-2004 Vincent Darley. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" -#ifdef _WIN32 +#ifdef __WIN32__ # include "tclWinInt.h" #endif #include "tclFileSystem.h" @@ -30,223 +33,82 @@ /* * struct FilesystemRecord -- * - * An item in a linked list of registered filesystems + * A filesystem record is used to keep track of each filesystem currently + * registered with the core, in a linked list. */ typedef struct FilesystemRecord { - void *clientData; /* Client-specific data for the filesystem + ClientData clientData; /* Client specific data for the new filesystem * (can be NULL) */ - const Tcl_Filesystem *fsPtr;/* Pointer to filesystem dispatch table. */ + Tcl_Filesystem *fsPtr; /* Pointer to filesystem dispatch table. */ struct FilesystemRecord *nextPtr; - /* The next registered filesystem, or NULL to - * indicate the end of the list. */ + /* The next filesystem registered to Tcl, or + * NULL if no more. */ struct FilesystemRecord *prevPtr; - /* The previous filesystem, or NULL to indicate - * the ned of the list */ + /* The previous filesystem registered to Tcl, + * or NULL if no more. */ } FilesystemRecord; /* + * This structure holds per-thread private copy of the current directory + * maintained by the global cwdPathPtr. This structure holds per-thread + * private copies of some global data. This way we avoid most of the + * synchronization calls which boosts performance, at cost of having to update + * this information each time the corresponding epoch counter changes. */ -typedef struct { +typedef struct ThreadSpecificData { int initialized; - size_t cwdPathEpoch; /* Compared with the global cwdPathEpoch to - * determine whether cwdPathPtr is stale. - */ - size_t filesystemEpoch; - Tcl_Obj *cwdPathPtr; /* A private copy of cwdPathPtr. Updated when - * the value is accessed and cwdPathEpoch has - * changed. - */ - void *cwdClientData; + int cwdPathEpoch; + int filesystemEpoch; + Tcl_Obj *cwdPathPtr; + ClientData cwdClientData; FilesystemRecord *filesystemList; - size_t claims; + int claims; } ThreadSpecificData; /* - * Forward declarations. + * Prototypes for functions defined later in this file. */ -static Tcl_NRPostProc EvalFileCallback; static FilesystemRecord*FsGetFirstFilesystem(void); -static void FsThrExitProc(void *cd); +static void FsThrExitProc(ClientData cd); static Tcl_Obj * FsListMounts(Tcl_Obj *pathPtr, const char *pattern); static void FsAddMountsToGlobResult(Tcl_Obj *resultPtr, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); -static void FsUpdateCwd(Tcl_Obj *cwdObj, void *clientData); +static void FsUpdateCwd(Tcl_Obj *cwdObj, ClientData clientData); + static void FsRecacheFilesystemList(void); static void Claim(void); static void Disclaim(void); -static void * DivertFindSymbol(Tcl_Interp *interp, - Tcl_LoadHandle loadHandle, const char *symbol); -static void DivertUnloadFile(Tcl_LoadHandle loadHandle); - -/* - * Functions that provide native filesystem support. They are private and - * should be used only here. They should be called instead of calling Tclp... - * native filesystem functions. Others should use the Tcl_FS... functions - * which ensure correct and complete virtual filesystem support. - */ - -static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator; -static Tcl_FSFreeInternalRepProc NativeFreeInternalRep; -static Tcl_FSFileAttrStringsProc NativeFileAttrStrings; -static Tcl_FSFileAttrsGetProc NativeFileAttrsGet; -static Tcl_FSFileAttrsSetProc NativeFileAttrsSet; /* - * Functions that support the native filesystem functions listed above. They - * are the same for win/unix, and not in tclInt.h because they are and should - * be used only here. + * These form part of the native filesystem support. They are needed here + * because we have a few native filesystem functions (which are the same for + * win/unix) in this file. There is no need to place them in tclInt.h, because + * they are not (and should not be) used anywhere else. */ -MODULE_SCOPE const char *const tclpFileAttrStrings[]; +MODULE_SCOPE const char * tclpFileAttrStrings[]; MODULE_SCOPE const TclFileAttrProcs tclpFileAttrProcs[]; - -/* - * These these functions are not static either because routines in the native - * (win/unix) directories call them or they are actually implemented in those - * directories. They should be called from outside Tcl's native filesystem - * routines. If we ever built the native filesystem support into a separate - * code library, this could actually be enforced. - */ - -Tcl_FSFilesystemPathTypeProc TclpFilesystemPathType; -Tcl_FSInternalToNormalizedProc TclpNativeToNormalized; -Tcl_FSStatProc TclpObjStat; -Tcl_FSAccessProc TclpObjAccess; -Tcl_FSMatchInDirectoryProc TclpMatchInDirectory; -Tcl_FSChdirProc TclpObjChdir; -Tcl_FSLstatProc TclpObjLstat; -Tcl_FSCopyFileProc TclpObjCopyFile; -Tcl_FSDeleteFileProc TclpObjDeleteFile; -Tcl_FSRenameFileProc TclpObjRenameFile; -Tcl_FSCreateDirectoryProc TclpObjCreateDirectory; -Tcl_FSCopyDirectoryProc TclpObjCopyDirectory; -Tcl_FSRemoveDirectoryProc TclpObjRemoveDirectory; -Tcl_FSLinkProc TclpObjLink; -Tcl_FSListVolumesProc TclpObjListVolumes; - -/* - * The native filesystem dispatch table. This could me made public but it - * should only be accessed by the functions it points to, or perhaps - * subordinate helper functions. - */ - -const Tcl_Filesystem tclNativeFilesystem = { - "native", - sizeof(Tcl_Filesystem), - TCL_FILESYSTEM_VERSION_2, - TclNativePathInFilesystem, - TclNativeDupInternalRep, - NativeFreeInternalRep, - TclpNativeToNormalized, - TclNativeCreateNativeRep, - TclpObjNormalizePath, - TclpFilesystemPathType, - NativeFilesystemSeparator, - TclpObjStat, - TclpObjAccess, - TclpOpenFileChannel, - TclpMatchInDirectory, - TclpUtime, -#ifndef S_IFLNK - NULL, -#else - TclpObjLink, -#endif /* S_IFLNK */ - TclpObjListVolumes, - NativeFileAttrStrings, - NativeFileAttrsGet, - NativeFileAttrsSet, - TclpObjCreateDirectory, - TclpObjRemoveDirectory, - TclpObjDeleteFile, - TclpObjCopyFile, - TclpObjRenameFile, - TclpObjCopyDirectory, - TclpObjLstat, - /* Needs casts since we're using version_2. */ - (Tcl_FSLoadFileProc *)(void *) TclpDlopen, - (Tcl_FSGetCwdProc *) TclpGetNativeCwd, - TclpObjChdir -}; - -/* - * An initial record in the linked list for the native filesystem. Remains at - * the tail of the list and is never freed. Currently the native filesystem is - * hard-coded. It may make sense to modify this to accommodate unconventional - * uses of Tcl that provide no native filesystem. - */ - -static FilesystemRecord nativeFilesystemRecord = { - NULL, - &tclNativeFilesystem, - NULL, - NULL -}; - -/* - * Incremented each time the linked list of filesystems is modified. For - * multithreaded builds, invalidates all cached filesystem internal - * representations. - */ - -static size_t theFilesystemEpoch = 1; - -/* - * The linked list of filesystems. To minimize locking each thread maintains a - * local copy of this list. - * - */ - -static FilesystemRecord *filesystemList = &nativeFilesystemRecord; -TCL_DECLARE_MUTEX(filesystemMutex) - -/* - * A files-system indepent sense of the current directory. - */ - -static Tcl_Obj *cwdPathPtr = NULL; -static size_t cwdPathEpoch = 0; /* The pathname of the current directory */ -static void *cwdClientData = NULL; -TCL_DECLARE_MUTEX(cwdMutex) - -static Tcl_ThreadDataKey fsDataKey; - /* - * When a temporary copy of a file is created on the native filesystem in order - * to load the file, an FsDivertLoad structure is created to track both the - * actual unloadProc/clientData combination which was used, and the original and - * modified filenames. This makes it possible to correctly undo the entire - * operation in order to unload the library. + * The following functions are obsolete string based APIs, and should be + * removed in a future release (Tcl 9 would be a good time). */ -typedef struct { - Tcl_LoadHandle loadHandle; - Tcl_FSUnloadFileProc *unloadProcPtr; - Tcl_Obj *divertedFile; - const Tcl_Filesystem *divertedFilesystem; - void *divertedFileNativeRep; -} FsDivertLoad; -/* - * Obsolete string-based APIs that should be removed in a future release, - * perhaps in Tcl 9. - */ - /* Obsolete */ int Tcl_Stat( - const char *path, /* Pathname of file to stat (in current CP). */ + const char *path, /* Path of file to stat (in current CP). */ struct stat *oldStyleBuf) /* Filled with results of stat call. */ { int ret; Tcl_StatBuf buf; - Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1); + Tcl_Obj *pathPtr = Tcl_NewStringObj(path, -1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSStat(pathPtr, &buf); @@ -254,10 +116,9 @@ Tcl_Stat( if (ret != -1) { #ifndef TCL_WIDE_INT_IS_LONG Tcl_WideInt tmp1, tmp2, tmp3 = 0; - # define OUT_OF_RANGE(x) \ - (((Tcl_WideInt)(x)) < LONG_MIN || \ - ((Tcl_WideInt)(x)) > LONG_MAX) + (((Tcl_WideInt)(x)) < Tcl_LongAsWide(LONG_MIN) || \ + ((Tcl_WideInt)(x)) > Tcl_LongAsWide(LONG_MAX)) # define OUT_OF_URANGE(x) \ (((Tcl_WideUInt)(x)) > ((Tcl_WideUInt)ULONG_MAX)) @@ -271,10 +132,10 @@ Tcl_Stat( * Tcl_WideInt. */ - tmp1 = (Tcl_WideInt) buf.st_ino; - tmp2 = (Tcl_WideInt) buf.st_size; + tmp1 = (Tcl_WideInt) buf.st_ino; + tmp2 = (Tcl_WideInt) buf.st_size; #ifdef HAVE_STRUCT_STAT_ST_BLOCKS - tmp3 = (Tcl_WideInt) buf.st_blocks; + tmp3 = (Tcl_WideInt) buf.st_blocks; #endif if (OUT_OF_URANGE(tmp1) || OUT_OF_RANGE(tmp2) || OUT_OF_RANGE(tmp3)) { @@ -293,7 +154,7 @@ Tcl_Stat( #endif /* !TCL_WIDE_INT_IS_LONG */ /* - * Copy across all supported fields, with possible type coercion on + * Copy across all supported fields, with possible type coercions on * those fields that change between the normal and lf64 versions of * the stat structure (on Solaris at least). This is slow when the * structure sizes coincide, but that's what you get for using an @@ -308,9 +169,9 @@ Tcl_Stat( oldStyleBuf->st_uid = buf.st_uid; oldStyleBuf->st_gid = buf.st_gid; oldStyleBuf->st_size = (off_t) buf.st_size; - oldStyleBuf->st_atime = Tcl_GetAccessTimeFromStat(&buf); - oldStyleBuf->st_mtime = Tcl_GetModificationTimeFromStat(&buf); - oldStyleBuf->st_ctime = Tcl_GetChangeTimeFromStat(&buf); + oldStyleBuf->st_atime = buf.st_atime; + oldStyleBuf->st_mtime = buf.st_mtime; + oldStyleBuf->st_ctime = buf.st_ctime; #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE oldStyleBuf->st_blksize = buf.st_blksize; #endif @@ -328,8 +189,7 @@ Tcl_Stat( /* Obsolete */ int Tcl_Access( - const char *path, /* Pathname of file to access (in current CP). - */ + const char *path, /* Path of file to access (in current CP). */ int mode) /* Permission setting. */ { int ret; @@ -345,12 +205,13 @@ Tcl_Access( /* Obsolete */ Tcl_Channel Tcl_OpenFileChannel( - Tcl_Interp *interp, /* Interpreter for error reporting. May be + Tcl_Interp *interp, /* Interpreter for error reporting; can be * NULL. */ - const char *path, /* Pathname of file to open. */ + const char *path, /* Name of file to open. */ const char *modeString, /* A list of POSIX open modes or a string such * as "rw". */ - int permissions) /* The modes to use if creating a new file. */ + int permissions) /* If the open involves creating a file, with + * what modes to create it? */ { Tcl_Channel ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1); @@ -381,27 +242,27 @@ Tcl_GetCwd( Tcl_Interp *interp, Tcl_DString *cwdPtr) { - Tcl_Obj *cwd = Tcl_FSGetCwd(interp); - + Tcl_Obj *cwd; + cwd = Tcl_FSGetCwd(interp); if (cwd == NULL) { return NULL; + } else { + Tcl_DStringInit(cwdPtr); + Tcl_DStringAppend(cwdPtr, Tcl_GetString(cwd), -1); + Tcl_DecrRefCount(cwd); + return Tcl_DStringValue(cwdPtr); } - Tcl_DStringInit(cwdPtr); - TclDStringAppendObj(cwdPtr, cwd); - Tcl_DecrRefCount(cwd); - return Tcl_DStringValue(cwdPtr); } +/* Obsolete */ int Tcl_EvalFile( - Tcl_Interp *interp, /* Interpreter in which to evaluate the script. */ - const char *fileName) /* Pathname of the file containing the script. - * Performs Tilde-substitution on this - * pathaname. */ + Tcl_Interp *interp, /* Interpreter in which to process file. */ + const char *fileName) /* Name of file to process. Tilde-substitution + * will be performed on this name. */ { int ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(fileName,-1); - Tcl_IncrRefCount(pathPtr); ret = Tcl_FSEvalFile(interp, pathPtr); Tcl_DecrRefCount(pathPtr); @@ -409,18 +270,238 @@ Tcl_EvalFile( } /* - * The basic filesystem implementation. + * The 3 hooks for Stat, Access and OpenFileChannel are obsolete. The + * complete, general hooked filesystem APIs should be used instead. This + * define decides whether to include the obsolete hooks and related code. If + * these are removed, we'll also want to remove them from stubs/tclInt. The + * only known users of these APIs are prowrap and mktclapp. New + * code/extensions should not use them, since they do not provide as full + * support as the full filesystem API. + * + * As soon as prowrap and mktclapp are updated to use the full filesystem + * support, I suggest all these hooks are removed. + */ + +#undef USE_OBSOLETE_FS_HOOKS + +#ifdef USE_OBSOLETE_FS_HOOKS + +/* + * The following typedef declarations allow for hooking into the chain of + * functions maintained for 'Tcl_Stat(...)', 'Tcl_Access(...)' & + * 'Tcl_OpenFileChannel(...)'. Basically for each hookable function a linked + * list is defined. + */ + +typedef struct StatProc { + TclStatProc_ *proc; /* Function to process a 'stat()' call */ + struct StatProc *nextPtr; /* The next 'stat()' function to call */ +} StatProc; + +typedef struct AccessProc { + TclAccessProc_ *proc; /* Function to process a 'access()' call */ + struct AccessProc *nextPtr; /* The next 'access()' function to call */ +} AccessProc; + +typedef struct OpenFileChannelProc { + TclOpenFileChannelProc_ *proc; + /* Function to process a + * 'Tcl_OpenFileChannel()' call */ + struct OpenFileChannelProc *nextPtr; + /* The next 'Tcl_OpenFileChannel()' function + * to call */ +} OpenFileChannelProc; + +/* + * For each type of (obsolete) hookable function, a static node is declared to + * hold the function pointer for the "built-in" routine (e.g. 'TclpStat(...)') + * and the respective list is initialized as a pointer to that node. + * + * The "delete" functions (e.g. 'TclStatDeleteProc(...)') ensure that these + * statically declared list entry cannot be inadvertently removed. + * + * This method avoids the need to call any sort of "initialization" function. + * + * All three lists are protected by a global obsoleteFsHookMutex. + */ + +static StatProc *statProcList = NULL; +static AccessProc *accessProcList = NULL; +static OpenFileChannelProc *openFileChannelProcList = NULL; + +TCL_DECLARE_MUTEX(obsoleteFsHookMutex) + +#endif /* USE_OBSOLETE_FS_HOOKS */ + +/* + * 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 Tcl's core nor by extensions). Similarly, + * the old string-based Tclp... native filesystem functions should not be + * called. + * + * The correct API to use now is the Tcl_FS... set of functions, which ensure + * correct and complete virtual filesystem support. + * + * We cannot make all of these static, since some of them are implemented in + * the platform-specific directories. + */ + +static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator; +static Tcl_FSFreeInternalRepProc NativeFreeInternalRep; +static Tcl_FSFileAttrStringsProc NativeFileAttrStrings; +static Tcl_FSFileAttrsGetProc NativeFileAttrsGet; +static Tcl_FSFileAttrsSetProc NativeFileAttrsSet; + +/* + * The only reason these functions are not static is that they are either + * called by code in the native (win/unix) directories or they are actually + * implemented in those directories. They should simply not be called by code + * outside Tcl's native filesystem core i.e. they should be considered + * 'static' to Tcl's filesystem code (if we ever built the native filesystem + * support into a separate code library, this could actually be enforced). + */ + +Tcl_FSFilesystemPathTypeProc TclpFilesystemPathType; +Tcl_FSInternalToNormalizedProc TclpNativeToNormalized; +Tcl_FSStatProc TclpObjStat; +Tcl_FSAccessProc TclpObjAccess; +Tcl_FSMatchInDirectoryProc TclpMatchInDirectory; +Tcl_FSChdirProc TclpObjChdir; +Tcl_FSLstatProc TclpObjLstat; +Tcl_FSCopyFileProc TclpObjCopyFile; +Tcl_FSDeleteFileProc TclpObjDeleteFile; +Tcl_FSRenameFileProc TclpObjRenameFile; +Tcl_FSCreateDirectoryProc TclpObjCreateDirectory; +Tcl_FSCopyDirectoryProc TclpObjCopyDirectory; +Tcl_FSRemoveDirectoryProc TclpObjRemoveDirectory; +Tcl_FSUnloadFileProc TclpUnloadFile; +Tcl_FSLinkProc TclpObjLink; +Tcl_FSListVolumesProc TclpObjListVolumes; + +/* + * Define the native filesystem dispatch table. If necessary, it is ok to make + * this non-static, but it should only be accessed by the functions actually + * listed within it (or perhaps other helper functions of them). Anything + * which is not part of this 'native filesystem implementation' should not be + * delving inside here! + */ + +Tcl_Filesystem tclNativeFilesystem = { + "native", + sizeof(Tcl_Filesystem), + TCL_FILESYSTEM_VERSION_2, + &TclNativePathInFilesystem, + &TclNativeDupInternalRep, + &NativeFreeInternalRep, + &TclpNativeToNormalized, + &TclNativeCreateNativeRep, + &TclpObjNormalizePath, + &TclpFilesystemPathType, + &NativeFilesystemSeparator, + &TclpObjStat, + &TclpObjAccess, + &TclpOpenFileChannel, + &TclpMatchInDirectory, + &TclpUtime, +#ifndef S_IFLNK + NULL, +#else + &TclpObjLink, +#endif /* S_IFLNK */ + &TclpObjListVolumes, + &NativeFileAttrStrings, + &NativeFileAttrsGet, + &NativeFileAttrsSet, + &TclpObjCreateDirectory, + &TclpObjRemoveDirectory, + &TclpObjDeleteFile, + &TclpObjCopyFile, + &TclpObjRenameFile, + &TclpObjCopyDirectory, + &TclpObjLstat, + &TclpDlopen, + /* Needs a cast since we're using version_2 */ + (Tcl_FSGetCwdProc *) &TclpGetNativeCwd, + &TclpObjChdir +}; + +/* + * Define the tail of the linked list. Note that for unconventional uses of + * Tcl without a native filesystem, we may in the future wish to modify the + * current approach of hard-coding the native filesystem in the lookup list + * 'filesystemList' below. + * + * We initialize the record so that it thinks one file uses it. This means it + * will never be freed. + */ + +static FilesystemRecord nativeFilesystemRecord = { + NULL, + &tclNativeFilesystem, + NULL, + NULL +}; + +/* + * This is incremented each time we modify the linked list of filesystems. Any + * time it changes, all cached filesystem representations are suspect and must + * be freed. For multithreading builds, change of the filesystem epoch will + * trigger cache cleanup in all threads. + */ + +static int theFilesystemEpoch = 1; + +/* + * Stores the linked list of filesystems. A 1:1 copy of this list is also + * maintained in the TSD for each thread. This is to avoid synchronization + * issues. + */ + +static FilesystemRecord *filesystemList = &nativeFilesystemRecord; +TCL_DECLARE_MUTEX(filesystemMutex) + +/* + * Used to implement Tcl_FSGetCwd in a file-system independent way. + */ + +static Tcl_Obj* cwdPathPtr = NULL; +static int cwdPathEpoch = 0; +static ClientData cwdClientData = NULL; +TCL_DECLARE_MUTEX(cwdMutex) + +static Tcl_ThreadDataKey fsDataKey; + +/* + * One of these structures is used each time we successfully load a file from + * a file system by way of making a temporary copy of the file on the native + * filesystem. We need to store both the actual unloadProc/clientData + * combination which was used, and the original and modified filenames, so + * that we can correctly undo the entire operation when we want to unload the + * code. + */ + +typedef struct FsDivertLoad { + Tcl_LoadHandle loadHandle; + Tcl_FSUnloadFileProc *unloadProcPtr; + Tcl_Obj *divertedFile; + const Tcl_Filesystem *divertedFilesystem; + ClientData divertedFileNativeRep; +} FsDivertLoad; + +/* + * Now move on to the basic filesystem implementation */ static void FsThrExitProc( - void *cd) + ClientData cd) { - ThreadSpecificData *tsdPtr = (ThreadSpecificData *)cd; + ThreadSpecificData *tsdPtr = (ThreadSpecificData *) cd; FilesystemRecord *fsRecPtr = NULL, *tmpFsRecPtr = NULL; /* - * Discard the cwd copy. + * Trash the cwd copy. */ if (tsdPtr->cwdPathPtr != NULL) { @@ -432,14 +513,14 @@ FsThrExitProc( } /* - * Discard the filesystems cache. + * Trash the filesystems cache. */ fsRecPtr = tsdPtr->filesystemList; while (fsRecPtr != NULL) { tmpFsRecPtr = fsRecPtr->nextPtr; fsRecPtr->fsPtr = NULL; - ckfree(fsRecPtr); + ckfree((char *)fsRecPtr); fsRecPtr = tmpFsRecPtr; } tsdPtr->filesystemList = NULL; @@ -462,26 +543,26 @@ TclFSCwdIsNative(void) *---------------------------------------------------------------------- * * TclFSCwdPointerEquals -- - * Determine whether the given pathname is equal to the current working - * directory. + * + * Check whether the current working directory is equal to the path + * given. * * Results: - * 1 if equal, 0 otherwise. + * 1 (equal) or 0 (un-equal) as appropriate. * * Side effects: - * Updates TSD if needed. + * If the paths are equal, but are not the same object, this method will + * modify the given pathPtrPtr to refer to the same object. In this case + * the object pointed to by pathPtrPtr will have its refCount + * decremented, and it will be adjusted to point to the cwd (with a new + * refCount). * - * Stores a pointer to the current directory in *pathPtrPtr if it is not - * already there and the current directory is not NULL. - * - * If *pathPtrPtr is not null its reference count is decremented - * before it is replaced. *---------------------------------------------------------------------- */ int TclFSCwdPointerEquals( - Tcl_Obj **pathPtrPtr) + Tcl_Obj** pathPtrPtr) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); @@ -510,7 +591,7 @@ TclFSCwdPointerEquals( Tcl_MutexUnlock(&cwdMutex); if (tsdPtr->initialized == 0) { - Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr); + Tcl_CreateThreadExitHandler(FsThrExitProc, (ClientData) tsdPtr); tsdPtr->initialized = 1; } @@ -521,15 +602,15 @@ TclFSCwdPointerEquals( if (tsdPtr->cwdPathPtr == *pathPtrPtr) { return 1; } else { - Tcl_Size len1, len2; + int len1, len2; const char *str1, *str2; - str1 = TclGetStringFromObj(tsdPtr->cwdPathPtr, &len1); - str2 = TclGetStringFromObj(*pathPtrPtr, &len2); - if ((len1 == len2) && !memcmp(str1, str2, len1)) { + str1 = Tcl_GetStringFromObj(tsdPtr->cwdPathPtr, &len1); + str2 = Tcl_GetStringFromObj(*pathPtrPtr, &len2); + if (len1 == len2 && !strcmp(str1,str2)) { /* - * The values are equal but the objects are different. Cache the - * current structure in place of the old one. + * They are equal, but different objects. Update so they will be + * the same object in the future. */ Tcl_DecrRefCount(*pathPtrPtr); @@ -572,13 +653,13 @@ FsRecacheFilesystemList(void) } /* - * Refill the cache, honouring the order. + * Refill the cache honouring the order. */ list = NULL; fsRecPtr = tmpFsRecPtr; while (fsRecPtr != NULL) { - tmpFsRecPtr = (FilesystemRecord *)ckalloc(sizeof(FilesystemRecord)); + tmpFsRecPtr = (FilesystemRecord *) ckalloc(sizeof(FilesystemRecord)); *tmpFsRecPtr = *fsRecPtr; tmpFsRecPtr->nextPtr = list; tmpFsRecPtr->prevPtr = NULL; @@ -591,9 +672,8 @@ FsRecacheFilesystemList(void) while (toFree) { FilesystemRecord *next = toFree->nextPtr; - toFree->fsPtr = NULL; - ckfree(toFree); + ckfree((char *)toFree); toFree = next; } @@ -602,7 +682,7 @@ FsRecacheFilesystemList(void) */ if (tsdPtr->initialized == 0) { - Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr); + Tcl_CreateThreadExitHandler(FsThrExitProc, (ClientData) tsdPtr); tsdPtr->initialized = 1; } } @@ -619,56 +699,54 @@ FsGetFirstFilesystem(void) } /* - * The epoch can is changed when a filesystems is added or removed, when - * "system encoding" changes, and when env(HOME) changes. + * The epoch can be changed both by filesystems being added or removed and by + * env(HOME) changing. */ int TclFSEpochOk( - size_t filesystemEpoch) + int filesystemEpoch) { return (filesystemEpoch == 0 || filesystemEpoch == theFilesystemEpoch); } static void -Claim(void) +Claim() { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); - tsdPtr->claims++; } static void -Disclaim(void) +Disclaim() { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); - tsdPtr->claims--; } -size_t -TclFSEpoch(void) +int +TclFSEpoch() { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); - return tsdPtr->filesystemEpoch; } + /* - * If non-NULL, take posession of clientData and free it later. + * If non-NULL, clientData is owned by us and must be freed later. */ static void FsUpdateCwd( Tcl_Obj *cwdObj, - void *clientData) + ClientData clientData) { - Tcl_Size len; - const char *str = NULL; + int len; + char *str = NULL; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); if (cwdObj != NULL) { - str = TclGetStringFromObj(cwdObj, &len); + str = Tcl_GetStringFromObj(cwdObj, &len); } Tcl_MutexLock(&cwdMutex); @@ -684,17 +762,15 @@ FsUpdateCwd( cwdClientData = NULL; } else { /* - * This must be stored as a string obj! + * This must be stored as string obj! */ cwdPathPtr = Tcl_NewStringObj(str, len); - Tcl_IncrRefCount(cwdPathPtr); + Tcl_IncrRefCount(cwdPathPtr); cwdClientData = TclNativeDupInternalRep(clientData); } - if (++cwdPathEpoch == 0) { - ++cwdPathEpoch; - } + cwdPathEpoch++; tsdPtr->cwdPathEpoch = cwdPathEpoch; Tcl_MutexUnlock(&cwdMutex); @@ -720,17 +796,17 @@ FsUpdateCwd( * * TclFinalizeFilesystem -- * - * Clean up the filesystem. After this, any call to a Tcl_FS... function - * fails. + * Clean up the filesystem. After this, calls to all Tcl_FS... functions + * will fail. * - * If TclResetFilesystem is called later, it restores the filesystem to a - * pristine state. + * We will later call TclResetFilesystem to restore the FS to a pristine + * state. * * Results: * None. * * Side effects: - * Frees memory allocated for the filesystem. + * Frees any memory allocated by the filesystem. * *---------------------------------------------------------------------- */ @@ -741,9 +817,8 @@ TclFinalizeFilesystem(void) FilesystemRecord *fsRecPtr; /* - * Assume that only one thread is active. Otherwise mutexes would be needed - * around this code. - * TO DO: This assumption is false, isn't it? + * Assumption that only one thread is active now. Otherwise we would need + * to put various mutexes around this code. */ if (cwdPathPtr != NULL) { @@ -758,34 +833,34 @@ TclFinalizeFilesystem(void) /* * Remove all filesystems, freeing any allocated memory that is no longer - * needed. + * needed */ - TclZipfsFinalize(); fsRecPtr = filesystemList; while (fsRecPtr != NULL) { FilesystemRecord *tmpFsRecPtr = fsRecPtr->nextPtr; - /* - * The native filesystem is static, so don't free it. - */ + /* The native filesystem is static, so we don't free it. */ if (fsRecPtr != &nativeFilesystemRecord) { - ckfree(fsRecPtr); + ckfree((char *)fsRecPtr); } fsRecPtr = tmpFsRecPtr; } - if (++theFilesystemEpoch == 0) { - ++theFilesystemEpoch; - } + theFilesystemEpoch++; filesystemList = NULL; /* - * filesystemList is now NULL. Any attempt to use the filesystem is likely - * to fail. + * Now filesystemList is NULL. This means that any attempt to use the + * filesystem is likely to fail. */ -#ifdef _WIN32 +#ifdef USE_OBSOLETE_FS_HOOKS + statProcList = NULL; + accessProcList = NULL; + openFileChannelProcList = NULL; +#endif +#ifdef __WIN32__ TclWinEncodingsCleanup(); #endif } @@ -810,9 +885,16 @@ void TclResetFilesystem(void) { filesystemList = &nativeFilesystemRecord; - if (++theFilesystemEpoch == 0) { - ++theFilesystemEpoch; - } + theFilesystemEpoch++; + +#ifdef __WIN32__ + /* + * Cleans up the win32 API filesystem proc lookup table. This must happen + * very late in finalization so that deleting of copied dlls can occur. + */ + + TclWinResetInterfaces(); +#endif } /* @@ -820,32 +902,35 @@ TclResetFilesystem(void) * * Tcl_FSRegister -- * - * Prepends to the list of registered fileystems a new FilesystemRecord - * for the given Tcl_Filesystem, which is added even if it is already in - * the list. To determine whether the filesystem is already in the list, - * use Tcl_FSData(). + * Insert the filesystem function table at the head of the list of + * functions which are used during calls to all file-system operations. + * The filesystem will be added even if it is already in the list. (You + * can use Tcl_FSData to check if it is in the list, provided the + * ClientData used was not NULL). + * + * Note that the filesystem handling is head-to-tail of the list. Each + * filesystem is asked in turn whether it can handle a particular + * request, until one of them says 'yes'. At that point no further + * filesystems are asked. * - * Functions that use the list generally process it from head to tail and - * use the first filesystem that is suitable. Therefore, when adding a - * diagnostic filsystem (one which simply reports all fs activity), it - * must be at the head of the list. I.e. it must be the last one - * registered. + * In particular this means if you want to add a diagnostic filesystem + * (which simply reports all fs activity), it must be at the head of the + * list: i.e. it must be the last registered. * * Results: - * TCL_OK, or TCL_ERROR if memory for a new node in the list could + * Normally TCL_OK; TCL_ERROR if memory for a new node in the list could * not be allocated. * * Side effects: - * Allocates memory for a filesystem record and modifies the list of - * registered filesystems. + * Memory allocated and modifies the link list for filesystems. * *---------------------------------------------------------------------- */ int Tcl_FSRegister( - void *clientData, /* Client-specific data for this filesystem. */ - const Tcl_Filesystem *fsPtr)/* The filesystem record for the new fs. */ + ClientData clientData, /* Client specific data for this fs */ + Tcl_Filesystem *fsPtr) /* The filesystem record for the new fs. */ { FilesystemRecord *newFilesystemPtr; @@ -853,11 +938,24 @@ Tcl_FSRegister( return TCL_ERROR; } - newFilesystemPtr = (FilesystemRecord *)ckalloc(sizeof(FilesystemRecord)); + newFilesystemPtr = (FilesystemRecord *) ckalloc(sizeof(FilesystemRecord)); newFilesystemPtr->clientData = clientData; newFilesystemPtr->fsPtr = fsPtr; + /* + * Is this lock and wait strictly speaking necessary? Since any iterators + * out there will have grabbed a copy of the head of the list and be + * iterating away from that, if we add a new element to the head of the + * list, it can't possibly have any effect on any of their loops. In fact + * it could be better not to wait, since we are adjusting the filesystem + * epoch, any cached representations calculated by existing iterators are + * going to have to be thrown away anyway. + * + * However, since registering and unregistering filesystems is a very rare + * action, this is not a very important point. + */ + Tcl_MutexLock(&filesystemMutex); newFilesystemPtr->nextPtr = filesystemList; @@ -868,13 +966,11 @@ Tcl_FSRegister( filesystemList = newFilesystemPtr; /* - * Increment the filesystem epoch counter since existing pathnames might + * Increment the filesystem epoch counter, since existing paths might * conceivably now belong to different filesystems. */ - if (++theFilesystemEpoch == 0) { - ++theFilesystemEpoch; - } + theFilesystemEpoch++; Tcl_MutexUnlock(&filesystemMutex); return TCL_OK; @@ -885,26 +981,28 @@ Tcl_FSRegister( * * Tcl_FSUnregister -- * - * Removes the record for given filesystem from the list of registered - * filesystems. Refuses to remove the built-in (native) filesystem. This - * might be changed in the future to allow a smaller Tcl core in which the - * native filesystem is not used at all, e.g. initializing Tcl over a - * network connection. + * Remove the passed filesystem from the list of filesystem function + * tables. It also ensures that the built-in (native) filesystem is not + * removable, although we may wish to change that decision in the future + * to allow a smaller Tcl core, in which the native filesystem is not + * used at all (we could, say, initialise Tcl completely over a network + * connection). * * Results: - * TCL_OK if the function pointer was successfully removed, or TCL_ERROR + * TCL_OK if the function pointer was successfully removed, TCL_ERROR * otherwise. * * Side effects: - * The list of registered filesystems is updated. Memory for the - * corresponding FilesystemRecord is eventually freed. + * Memory may be deallocated (or will be later, once no "path" objects + * refer to this filesystem), but the list of registered filesystems is + * updated immediately. * *---------------------------------------------------------------------- */ int Tcl_FSUnregister( - const Tcl_Filesystem *fsPtr)/* The filesystem record to remove. */ + Tcl_Filesystem *fsPtr) /* The filesystem record to remove. */ { int retVal = TCL_ERROR; FilesystemRecord *fsRecPtr; @@ -912,9 +1010,9 @@ Tcl_FSUnregister( Tcl_MutexLock(&filesystemMutex); /* - * Traverse filesystemList in search of the record whose - * 'fsPtr' member matches 'fsPtr' and remove that record from the list. - * Do not revmoe the record for the native filesystem. + * Traverse the 'filesystemList' looking for the particular node whose + * 'fsPtr' member matches 'fsPtr' and remove that one from the list. + * Ensure that the "default" node cannot be removed. */ fsRecPtr = filesystemList; @@ -930,16 +1028,16 @@ Tcl_FSUnregister( } /* - * Each cached pathname could now belong to a different filesystem, - * so increment the filesystem epoch counter to ensure that cached - * information about the removed filesystem is not used. + * Increment the filesystem epoch counter, since existing paths + * might conceivably now belong to different filesystems. This + * should also ensure that paths which have cached the filesystem + * which is about to be deleted do not reference that filesystem + * (which would of course lead to memory exceptions). */ - if (++theFilesystemEpoch == 0) { - ++theFilesystemEpoch; - } + theFilesystemEpoch++; - ckfree(fsRecPtr); + ckfree((char *)fsRecPtr); retVal = TCL_OK; } else { @@ -956,49 +1054,63 @@ Tcl_FSUnregister( * * Tcl_FSMatchInDirectory -- * - * Search in the given pathname for files matching the given pattern. - * Used by [glob]. Processes just one pattern for one directory. Callers - * such as TclGlob and DoGlob implement manage the searching of multiple - * directories in cases such as - * glob -dir $dir -join * pkgIndex.tcl + * 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 have the Tcl_FSMatchInDirectoryProc for + * each filesystem from 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: * - * TCL_OK, or TCL_ERROR + * 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: - * resultPtr is populated, or in the case of an TCL_ERROR, an error message is - * set in the interpreter. + * The interpreter may have an error message inserted into it. * *---------------------------------------------------------------------- */ int Tcl_FSMatchInDirectory( - Tcl_Interp *interp, /* Interpreter to receive error messages, or - * NULL */ - Tcl_Obj *resultPtr, /* List that results are added to. */ - Tcl_Obj *pathPtr, /* Pathname of directory to search. If NULL, - * the current working directory is used. */ - const char *pattern, /* Pattern to match. If NULL, pathPtr must be - * a fully-specified pathname of a single - * file/directory which already exists and is - * of the correct type. */ - Tcl_GlobTypeData *types) /* Specifies acceptable types. - * May be NULL. The directory flag is - * particularly significant. */ + Tcl_Interp *interp, /* Interpreter to receive error messages, but + * may be NULL. */ + Tcl_Obj *resultPtr, /* 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. */ { const Tcl_Filesystem *fsPtr; Tcl_Obj *cwd, *tmpResultPtr, **elemsPtr; - Tcl_Size resLength, i; - int ret = -1; + int resLength, i, ret = -1; - if (types != NULL && (types->type & TCL_GLOB_TYPE_MOUNT)) { + if (types != NULL && types->type & TCL_GLOB_TYPE_MOUNT) { /* - * Currently external callers may not query mounts, which would be a - * valuable future step. This is the only routine that knows about - * mounts, so we're being called recursively by ourself. Return no - * matches. + * We don't currently allow querying of mounts by external code (a + * valuable future step), so since we're the only function that + * actually knows about mounts, this means we're being called + * recursively by ourself. Return no matches. */ return TCL_OK; @@ -1010,46 +1122,49 @@ Tcl_FSMatchInDirectory( fsPtr = NULL; } - if (fsPtr != NULL) { - /* - * A corresponding filesystem was found. Search within it. - */ + /* + * Check if we've successfully mapped the path to a filesystem within + * which to search. + */ + if (fsPtr != NULL) { if (fsPtr->matchInDirectoryProc == NULL) { Tcl_SetErrno(ENOENT); return -1; } - ret = fsPtr->matchInDirectoryProc(interp, resultPtr, pathPtr, pattern, - types); + ret = (*fsPtr->matchInDirectoryProc)(interp, resultPtr, pathPtr, + pattern, types); if (ret == TCL_OK && pattern != NULL) { FsAddMountsToGlobResult(resultPtr, pathPtr, pattern, types); } return ret; } - if (pathPtr != NULL && TclGetString(pathPtr)[0] != '\0') { - /* - * There is a pathname but it belongs to no known filesystem. Mayday! - */ + /* + * If the path isn't empty, we have no idea how to match files in a + * directory which belongs to no known filesystem + */ + if (pathPtr != NULL && TclGetString(pathPtr)[0] != '\0') { Tcl_SetErrno(ENOENT); return -1; } /* - * The pathname is empty or NULL so search in the current working - * directory. matchInDirectoryProc prefixes each result with this - * directory, so trim it from each result. Deal with this here in the - * generic code because otherwise every filesystem implementation of - * Tcl_FSMatchInDirectory has to do it. + * 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_SetObjResult(interp, Tcl_NewStringObj( - "glob couldn't determine the current working directory", - -1)); + Tcl_SetResult(interp, "glob couldn't determine " + "the current working directory", TCL_STATIC); } return TCL_ERROR; } @@ -1058,16 +1173,16 @@ Tcl_FSMatchInDirectory( if (fsPtr != NULL && fsPtr->matchInDirectoryProc != NULL) { TclNewObj(tmpResultPtr); Tcl_IncrRefCount(tmpResultPtr); - ret = fsPtr->matchInDirectoryProc(interp, tmpResultPtr, cwd, pattern, - types); + ret = (*fsPtr->matchInDirectoryProc)(interp, tmpResultPtr, cwd, + pattern, types); if (ret == TCL_OK) { FsAddMountsToGlobResult(tmpResultPtr, cwd, pattern, types); /* - * resultPtr and tmpResultPtr are guaranteed to be distinct. + * Note that we know resultPtr and tmpResultPtr are distinct. */ - ret = TclListObjGetElements(interp, tmpResultPtr, + ret = Tcl_ListObjGetElements(interp, tmpResultPtr, &resLength, &elemsPtr); for (i=0 ; ret==TCL_OK && i<resLength ; i++) { ret = Tcl_ListObjAppendElement(interp, resultPtr, @@ -1084,30 +1199,32 @@ Tcl_FSMatchInDirectory( *---------------------------------------------------------------------- * * FsAddMountsToGlobResult -- - * Adds any mounted pathnames to a set of results so that simple things - * like 'glob *' merge mounts and listings correctly. Used by the - * Tcl_FSMatchInDirectory. + * + * This routine is used by the globbing code to take the results of a + * directory listing and add any mounted paths to that listing. This is + * required so that simple things like 'glob *' merge mounts and listings + * correctly. * * Results: * None. * * Side effects: - * Stores a result in resultPtr. + * Modifies the resultPtr. * *---------------------------------------------------------------------- */ static void FsAddMountsToGlobResult( - Tcl_Obj *resultPtr, /* The current list of matching pathnames. Must - * not be shared. */ - Tcl_Obj *pathPtr, /* The directory that was searched. */ - const char *pattern, /* Pattern to match mounts against. */ - Tcl_GlobTypeData *types) /* Acceptable types. May be NULL. The - * directory flag is particularly significant. - */ + Tcl_Obj *resultPtr, /* The current list of matching paths; must + * not be shared! */ + Tcl_Obj *pathPtr, /* The directory in question */ + 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_Size mLength, gLength, i; + int mLength, gLength, i; int dir = (types == NULL || (types->type & TCL_GLOB_TYPE_DIR)); Tcl_Obj *mounts = FsListMounts(pathPtr, pattern); @@ -1115,15 +1232,15 @@ FsAddMountsToGlobResult( return; } - if (TclListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) { + if (Tcl_ListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) { goto endOfMounts; } - if (TclListObjLength(NULL, resultPtr, &gLength) != TCL_OK) { + if (Tcl_ListObjLength(NULL, resultPtr, &gLength) != TCL_OK) { goto endOfMounts; } for (i=0 ; i<mLength ; i++) { Tcl_Obj *mElt; - Tcl_Size j; + int j; int found = 0; Tcl_ListObjIndex(NULL, mounts, i, &mElt); @@ -1142,25 +1259,25 @@ FsAddMountsToGlobResult( Tcl_ListObjReplace(NULL, resultPtr, j, 1, 0, NULL); gLength--; } - break; /* Break out of for loop. */ + break; /* Break out of for loop */ } } if (!found && dir) { Tcl_Obj *norm; - Tcl_Size len, mlen; + int len, mlen; /* - * mElt is normalized and lies inside pathPtr so - * add to the result the right representation of mElt, - * i.e. the representation relative to pathPtr. + * We know mElt is absolute normalized and lies inside pathPtr, so + * now we must add to the result the right representation of mElt, + * i.e. the representation which is relative to pathPtr. */ norm = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (norm != NULL) { const char *path, *mount; - mount = TclGetStringFromObj(mElt, &mlen); - path = TclGetStringFromObj(norm, &len); + mount = Tcl_GetStringFromObj(mElt, &mlen); + path = Tcl_GetStringFromObj(norm, &len); if (path[len-1] == '/') { /* * Deal with the root of the volume. @@ -1168,14 +1285,13 @@ FsAddMountsToGlobResult( len--; } - len++; /* account for '/' in the mElt [Bug 1602539] */ - - + len++; /* account for '/' in the mElt [Bug 1602539] */ mElt = TclNewFSPathObj(pathPtr, mount + len, mlen - len); Tcl_ListObjAppendElement(NULL, resultPtr, mElt); } /* - * Not comparing mounts to mounts, so no need to increment gLength + * No need to increment gLength, since we don't want to compare + * mounts against mounts. */ } } @@ -1189,62 +1305,67 @@ FsAddMountsToGlobResult( * * Tcl_FSMountsChanged -- * - * Announecs that mount points have changed or that the system encoding - * has changed. + * Notify the filesystem that the available mounted filesystems (or + * within any one filesystem type, the number or location of mount + * points) have changed. * * Results: * None. * * Side effects: - * The shared 'theFilesystemEpoch' is incremented, invalidating every - * exising cached internal representation of a pathname. Avoid calling - * Tcl_FSMountsChanged whenever possible. It must be called when: + * The global filesystem variable 'theFilesystemEpoch' is incremented. + * The effect of this is to make all cached path representations invalid. + * Clearly it should only therefore be called when it is really required! + * There are a few circumstances when it should be called: * - * (1) A filesystem is registered or unregistered. This is only necessary - * if the new filesystem accepts file pathnames as-is. Normally the - * filesystem is really a shell which doesn't yet have any mount points - * established and so its 'pathInFilesystem' routine always fails. - * However, for safety, Tcl calls 'Tcl_FSMountsChanged' each time a - * filesystem is registered or unregistered. + * (1) when a new filesystem is registered or unregistered. Strictly + * speaking this is only necessary if the new filesystem accepts file + * paths as is (normally the filesystem itself is really a shell which + * hasn't yet had any mount points established and so its + * 'pathInFilesystem' proc will always fail). However, for safety, Tcl + * always calls this for you in these circumstances. * - * (2) An additional mount point is established inside an existing - * filesystem (except for the native file system; see note below). + * (2) when additional mount points are established inside any existing + * filesystem (except the native fs) * - * (3) A filesystem changes the list of available volumes (except for the - * native file system; see note below). + * (3) when any filesystem (except the native fs) changes the list of + * available volumes. * - * (4) The mapping from a string representation of a file to a full, - * normalized pathname changes. For example, if 'env(HOME)' is modified, - * then any pathname containing '~' maps to a different item, possibly in - * a different filesystem. + * (4) when the mapping from a string representation of a file to a full, + * normalized path changes. For example, if 'env(HOME)' is modified, then + * any path containing '~' will map to a different filesystem location. + * Therefore all such paths need to have their internal representation + * invalidated. * - * Tcl has no control over (2) and (3), so each registered filesystem must - * call Tcl_FSMountsChnaged in each of those circumstances. + * Tcl has no control over (2) and (3), so any registered filesystem must + * make sure it calls this function when those situations occur. * - * The reason for the exception in 2,3 for the native filesystem is that - * the native filesystem claims every file without determining whether - * whether the file exists, or even whether the pathname makes sense. + * (Note: the reason for the exception in 2,3 for the native filesystem + * is that the native filesystem by default claims all unknown files even + * if it really doesn't understand them or if they don't exist). * *---------------------------------------------------------------------- */ void Tcl_FSMountsChanged( - TCL_UNUSED(const Tcl_Filesystem *) /*fsPtr*/) + Tcl_Filesystem *fsPtr) +{ /* - * fsPtr is currently unused. In the future it might invalidate files for - * a particular filesystem, or take some other more advanced action. + * We currently don't do anything with this parameter. We could in the + * future only invalidate files for this filesystem or otherwise take more + * advanced action. */ -{ + + (void)fsPtr; + /* - * Increment the filesystem epoch to invalidate every existing cached - * internal representation. + * Increment the filesystem epoch counter, since existing paths might now + * belong to different filesystems. */ Tcl_MutexLock(&filesystemMutex); - if (++theFilesystemEpoch == 0) { - ++theFilesystemEpoch; - } + theFilesystemEpoch++; Tcl_MutexUnlock(&filesystemMutex); } @@ -1253,11 +1374,13 @@ Tcl_FSMountsChanged( * * Tcl_FSData -- * - * Retrieves the clientData member of the given filesystem. + * Retrieve the clientData field for the filesystem given, or NULL if + * that filesystem is not registered. * * Results: - * A clientData value, or NULL if the given filesystem is not registered. - * The clientData value itself may also be NULL. + * A clientData value, or NULL. Note that if the filesystem was + * registered with a NULL clientData field, this function will return + * that NULL value. * * Side effects: * None. @@ -1265,16 +1388,17 @@ Tcl_FSMountsChanged( *---------------------------------------------------------------------- */ -void * +ClientData Tcl_FSData( - const Tcl_Filesystem *fsPtr) /* The filesystem to find in the list of - * registered filesystems. */ + Tcl_Filesystem *fsPtr) /* The filesystem record to query. */ { - void *retVal = NULL; + ClientData retVal = NULL; FilesystemRecord *fsRecPtr = FsGetFirstFilesystem(); /* - * Find the filesystem in and retrieve its clientData. + * Traverse the list of filesystems look for a particular one. If found, + * return that filesystem's clientData (originally provided when calling + * Tcl_FSRegister). */ while ((retVal == NULL) && (fsRecPtr != NULL)) { @@ -1292,24 +1416,27 @@ Tcl_FSData( * * TclFSNormalizeToUniquePath -- * - * Converts the given pathname, containing no ../, ./ components, into a - * unique pathname for the given platform. On Unix the resulting pathname - * is free of symbolic links/aliases, and on Windows it is the long - * case-preserving form. - * + * Takes a path specification containing no ../, ./ sequences, and + * converts it into a unique path for the given platform. On Unix, this + * means the path must be free of symbolic links/aliases, and on Windows + * it means we want the long form, with that long form's case-dependence + * (which gives us a unique, case-dependent path). * * Results: - * Stores the resulting pathname in pathPtr and returns the offset of the - * last byte processed in pathPtr. + * The pathPtr is modified in place. The return value is the last byte + * offset which was recognised in the path string. * * Side effects: * None (beyond the memory allocation for the result). * * Special notes: - * If the filesystem-specific normalizePathProcs can reintroduce ../, ./ - * components into the pathname, this function does not return the correct - * result. This may be possible with symbolic links on unix. + * If the filesystem-specific normalizePathProcs can re-introduce ../, ./ + * sequences into the path, then this function will not return the + * correct result. This may be possible with symbolic links on unix. * + * Important assumption: if startAt is non-zero, it must point to a + * directory separator that we know exists and is already normalized (so + * it is important not to point to the char just after the separator). * *--------------------------------------------------------------------------- */ @@ -1317,92 +1444,52 @@ Tcl_FSData( int TclFSNormalizeToUniquePath( Tcl_Interp *interp, /* Used for error messages. */ - Tcl_Obj *pathPtr, /* An Pathname to normalize in-place. Must be - * unshared. */ - int startAt) /* Offset the string of pathPtr to start at. - * Must either be 0 or offset of a directory - * separator at the end of a pathname part that - * is already normalized, I.e. not the index of - * the byte just after the separator. */ + Tcl_Obj *pathPtr, /* The path to normalize in place */ + int startAt) /* Start at this char-offset */ { FilesystemRecord *fsRecPtr, *firstFsRecPtr; - Tcl_Size i; - int isVfsPath = 0; - const char *path; - /* - * Pathnames starting with a UNC prefix and ending with a colon character - * are reserved for VFS use. These names can not conflict with real UNC - * pathnames per https://msdn.microsoft.com/en-us/library/gg465305.aspx and - * rfc3986's definition of reg-name. - * - * We check these first to avoid useless calls to the native filesystem's - * normalizePathProc. + * Call each of the "normalise path" functions in succession. This is a + * special case, in which if we have a native filesystem handler, we call + * it first. This is because the root of Tcl's filesystem is always a + * native filesystem (i.e. '/' on unix is native). */ - path = TclGetStringFromObj(pathPtr, &i); - if ( (i >= 3) && ( (path[0] == '/' && path[1] == '/') - || (path[0] == '\\' && path[1] == '\\') ) ) { - for ( i = 2; ; i++) { - if (path[i] == '\0') break; - if (path[i] == path[0]) break; - } - --i; - if (path[i] == ':') isVfsPath = 1; - } - - /* - * Call the the normalizePathProc routine of each registered filesystem. - */ firstFsRecPtr = FsGetFirstFilesystem(); Claim(); + fsRecPtr = firstFsRecPtr; + while (fsRecPtr != NULL) { + if (fsRecPtr->fsPtr == &tclNativeFilesystem) { + Tcl_FSNormalizePathProc *proc = fsRecPtr->fsPtr->normalizePathProc; + if (proc != NULL) { + startAt = (*proc)(interp, pathPtr, startAt); + } + break; + } + fsRecPtr = fsRecPtr->nextPtr; + } - if (!isVfsPath) { - + fsRecPtr = firstFsRecPtr; + while (fsRecPtr != NULL) { /* - * Find and call the native filesystem handler first if there is one - * because the root of Tcl's filesystem is always a native filesystem - * (i.e., '/' on unix is native). + * Skip the native system next time through. */ - for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) { - if (fsRecPtr->fsPtr != &tclNativeFilesystem) { - continue; - } - - /* - * TODO: Always call the normalizePathProc here because it should - * always exist. - */ - - if (fsRecPtr->fsPtr->normalizePathProc != NULL) { - startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr, - startAt); + if (fsRecPtr->fsPtr != &tclNativeFilesystem) { + Tcl_FSNormalizePathProc *proc = fsRecPtr->fsPtr->normalizePathProc; + if (proc != NULL) { + startAt = (*proc)(interp, pathPtr, startAt); } - break; - } - } - for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) { - if (fsRecPtr->fsPtr == &tclNativeFilesystem) { /* - * Skip the native system this time through. + * We could add an efficiency check like this: + * if (retVal == length-of(pathPtr)) {break;} + * but there's not much benefit. */ - continue; - } - - if (fsRecPtr->fsPtr->normalizePathProc != NULL) { - startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr, - startAt); } - - /* - * This efficiency check could be added: - * if (retVal == length-of(pathPtr)) {break;} - * but there's not much benefit. - */ + fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); @@ -1414,27 +1501,26 @@ TclFSNormalizeToUniquePath( * * TclGetOpenMode -- * - * Obsolete. A limited version of TclGetOpenModeEx() which exists only to - * satisfy any extensions imprudently using it via Tcl's internal stubs - * table. + * This routine is an obsolete, limited version of TclGetOpenModeEx() + * below. It exists only to satisfy any extensions imprudently using it + * via Tcl's internal stubs table. * * Results: - * See TclGetOpenModeEx(). + * Same as TclGetOpenModeEx(). * * Side effects: - * See TclGetOpenModeEx(). + * Same as TclGetOpenModeEx(). * *--------------------------------------------------------------------------- */ int TclGetOpenMode( - Tcl_Interp *interp, /* Interpreter to use for error reporting. May - * be NULL. */ - const char *modeString, /* e.g. "r+" or "RDONLY CREAT". */ - int *seekFlagPtr) /* Sets this to 1 to tell the caller to seek to - EOF after opening the file, and - * 0 otherwise. */ + Tcl_Interp *interp, /* Interpreter to use for error reporting - + * may be NULL. */ + const char *modeString, /* Mode string, e.g. "r+" or "RDONLY CREAT" */ + int *seekFlagPtr) /* Set this to 1 if the caller should seek to + * EOF during the opening of the file. */ { int binary = 0; return TclGetOpenModeEx(interp, modeString, seekFlagPtr, &binary); @@ -1445,45 +1531,46 @@ TclGetOpenMode( * * TclGetOpenModeEx -- * - * Computes a POSIX mode mask for opening a file. + * Computes a POSIX mode mask for opening a file, from a given string, + * and also sets flags to indicate whether the caller should seek to EOF + * after opening the file, and whether the caller should configure the + * channel for binary data. * * Results: - * The mode to pass to "open", or -1 if an error occurs. + * On success, returns mode to pass to "open". If an error occurs, the + * return value is -1 and if interp is not NULL, sets interp's result + * object to an error message. * * Side effects: - * Sets *seekFlagPtr to 1 to tell the caller to - * seek to EOF after opening the file, or to 0 otherwise. - * - * Sets *binaryPtr to 1 to tell the caller to configure the channel as a - * binary channel, or to 0 otherwise. - * - * If there is an error and interp is not NULL, sets interpreter result to - * an error message. + * Sets the integer referenced by seekFlagPtr to 1 to tell the caller to + * seek to EOF after opening the file, or to 0 otherwise. Sets the + * integer referenced by binaryPtr to 1 to tell the caller to seek to + * configure the channel for binary data, or to 0 otherwise. * * Special note: - * Based on a prototype implementation contributed by Mark Diekhans. + * This code is based on a prototype implementation contributed by Mark + * Diekhans. * *--------------------------------------------------------------------------- */ int TclGetOpenModeEx( - Tcl_Interp *interp, /* Interpreter, possibly NULL, to use for - * error reporting. */ + Tcl_Interp *interp, /* Interpreter to use for error reporting - + * may be NULL. */ const char *modeString, /* Mode string, e.g. "r+" or "RDONLY CREAT" */ - int *seekFlagPtr, /* Sets this to 1 to tell the the caller to seek to - * EOF after opening the file, and 0 otherwise. */ - int *binaryPtr) /* Sets this to 1 to tell the caller to - * configure the channel for binary - * operations after opening the file. */ + int *seekFlagPtr, /* Set this to 1 if the caller should seek to + * EOF during the opening of the file. */ + int *binaryPtr) /* Set this to 1 if the caller should + * configure the opened channel for binary + * operations */ { - int mode, c, gotRW; - Tcl_Size modeArgc, i; + int mode, modeArgc, c, i, gotRW; const char **modeArgv, *flag; #define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR) /* - * Check for the simpler fopen-like access modes like "r" which are + * Check for the simpler fopen-like access modes (e.g. "r"). They are * distinguished from the POSIX access modes by the presence of a * lower-case first letter. */ @@ -1493,7 +1580,8 @@ TclGetOpenModeEx( mode = 0; /* - * Guard against wide characters before using byte-oriented routines. + * Guard against international characters before using byte oriented + * routines. */ if (!(modeString[0] & 0x80) @@ -1507,7 +1595,7 @@ TclGetOpenModeEx( break; case 'a': /* - * Add O_APPEND for proper automatic seek-to-end-on-write by the + * Added O_APPEND for proper automatic seek-to-end-on-write by the * OS. [Bug 680143] */ @@ -1517,7 +1605,7 @@ TclGetOpenModeEx( default: goto error; } - i = 1; + i=1; while (i<3 && modeString[i]) { if (modeString[i] == modeString[i-1]) { goto error; @@ -1525,8 +1613,8 @@ TclGetOpenModeEx( switch (modeString[i++]) { case '+': /* - * Remove O_APPEND so that the seek command works. [Bug - * 1773127] + * Must remove the O_APPEND flag so that the seek command + * works. [Bug 1773127] */ mode &= ~(O_RDONLY|O_WRONLY|O_APPEND); @@ -1548,16 +1636,18 @@ TclGetOpenModeEx( *seekFlagPtr = 0; *binaryPtr = 0; if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "illegal access mode \"%s\"", modeString)); + Tcl_AppendResult(interp, "illegal access mode \"", modeString, + "\"", NULL); } return -1; } /* - * The access modes are specified as a list of POSIX modes like O_CREAT. + * The access modes are specified using a list of POSIX modes such as + * O_CREAT. * - * Tcl_SplitList must work correctly when interp is NULL. + * IMPORTANT NOTE: We rely on Tcl_SplitList working correctly when a NULL + * interpreter is passed in. */ if (Tcl_SplitList(interp, modeString, &modeArgc, &modeArgv) != TCL_OK) { @@ -1596,11 +1686,10 @@ TclGetOpenModeEx( mode |= O_NOCTTY; #else if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "access mode \"%s\" not supported by this system", - flag)); + Tcl_AppendResult(interp, "access mode \"", flag, + "\" not supported by this system", NULL); } - ckfree(modeArgv); + ckfree((char *) modeArgv); return -1; #endif @@ -1609,11 +1698,10 @@ TclGetOpenModeEx( mode |= O_NONBLOCK; #else if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "access mode \"%s\" not supported by this system", - flag)); + Tcl_AppendResult(interp, "access mode \"", flag, + "\" not supported by this system", NULL); } - ckfree(modeArgv); + ckfree((char *) modeArgv); return -1; #endif @@ -1624,23 +1712,21 @@ TclGetOpenModeEx( } else { if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "invalid access mode \"%s\": must be RDONLY, WRONLY, " - "RDWR, APPEND, BINARY, CREAT, EXCL, NOCTTY, NONBLOCK," - " or TRUNC", flag)); + Tcl_AppendResult(interp, "invalid access mode \"", flag, + "\": must be RDONLY, WRONLY, RDWR, APPEND, BINARY, " + "CREAT, EXCL, NOCTTY, NONBLOCK, or TRUNC", NULL); } - ckfree(modeArgv); + ckfree((char *) modeArgv); return -1; } } - ckfree(modeArgv); + ckfree((char *) modeArgv); if (!gotRW) { if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "access mode must include either RDONLY, WRONLY, or RDWR", - -1)); + Tcl_AppendResult(interp, "access mode must include either" + " RDONLY, WRONLY, or RDWR", NULL); } return -1; } @@ -1648,53 +1734,51 @@ TclGetOpenModeEx( } /* + * Tcl_FSEvalFile is Tcl_FSEvalFileEx without encoding argument. + */ + +int +Tcl_FSEvalFile( + Tcl_Interp *interp, /* Interpreter in which to process file. */ + Tcl_Obj *pathPtr) /* Path of file to process. Tilde-substitution + * will be performed on this name. */ +{ + return Tcl_FSEvalFileEx(interp, pathPtr, NULL); +} + +/* *---------------------------------------------------------------------- * - * Tcl_FSEvalFile, Tcl_FSEvalFileEx, TclNREvalFile -- + * Tcl_FSEvalFileEx -- * - * Reads a file and evaluates it as a script. - * - * Tcl_FSEvalFile is Tcl_FSEvalFileEx without the encoding argument. - * - * TclNREvalFile is an NRE-enabled version of Tcl_FSEvalFileEx. + * Read in a file and process the entire file as one gigantic Tcl + * command. * * Results: * A standard Tcl result, which is either the result of executing the * file or an error indicating why the file couldn't be read. * * Side effects: - * Arbitrary, depending on the contents of the script. While the script - * is evaluated iPtr->scriptFile is a reference to pathPtr, and after the - * evaluation completes, has its original value restored again. + * Depends on the commands in the file. During the evaluation of the + * contents of the file, iPtr->scriptFile is made to point to pathPtr + * (the old value is cached and replaced when this function returns). * *---------------------------------------------------------------------- */ int -Tcl_FSEvalFile( - Tcl_Interp *interp, /* Interpreter that evaluates the script. */ - Tcl_Obj *pathPtr) /* Pathname of file containing the script. - * Tilde-substitution is performed on this - * pathname. */ -{ - return Tcl_FSEvalFileEx(interp, pathPtr, NULL); -} - -int Tcl_FSEvalFileEx( - Tcl_Interp *interp, /* Interpreter that evaluates the script. */ - Tcl_Obj *pathPtr, /* Pathname of the file to process. - * Tilde-substitution is performed on this - * pathname. */ - const char *encodingName) /* Either the name of an encoding or NULL to - use the utf-8 encoding. */ + Tcl_Interp *interp, /* Interpreter in which to process file. */ + Tcl_Obj *pathPtr, /* Path of file to process. Tilde-substitution + * will be performed on this name. */ + const char *encodingName) /* If non-NULL, then use this encoding for the + * file. NULL means use the system encoding. */ { - Tcl_Size length; - int result = TCL_ERROR; + int length, result = TCL_ERROR; Tcl_StatBuf statBuf; Tcl_Obj *oldScriptFile; Interp *iPtr; - const char *string; + char *string; Tcl_Channel chan; Tcl_Obj *objPtr; @@ -1704,67 +1788,59 @@ Tcl_FSEvalFileEx( if (Tcl_FSStat(pathPtr, &statBuf) == -1) { Tcl_SetErrno(errno); - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read file \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); + Tcl_AppendResult(interp, "couldn't read file \"", + Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL); return result; } chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644); - if (chan == NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read file \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); + if (chan == (Tcl_Channel) NULL) { + Tcl_ResetResult(interp); + Tcl_AppendResult(interp, "couldn't read file \"", + Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL); return result; } /* - * The eof character is \x1A (^Z). Tcl uses it on every platform to allow - * for scripted documents. [Bug: 2040] + * The eofchar is \32 (^Z). This is the usual on Windows, but we effect + * this cross-platform to allow for scripted documents. [Bug: 2040] */ - Tcl_SetChannelOption(interp, chan, "-eofchar", "\x1A {}"); + Tcl_SetChannelOption(interp, chan, "-eofchar", "\32"); /* - * If the encoding is specified, set the channel to that encoding. - * Otherwise use utf-8. If the encoding is unknown report an error. + * If the encoding is specified, set it for the channel. Else don't touch + * it (and use the system encoding) Report error on unknown encoding. */ - if (encodingName == NULL) { - encodingName = "utf-8"; - } - if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName) - != TCL_OK) { - Tcl_Close(interp,chan); - return result; + if (encodingName != NULL) { + if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName) + != TCL_OK) { + Tcl_Close(interp,chan); + return result; + } } - TclNewObj(objPtr); + objPtr = Tcl_NewObj(); Tcl_IncrRefCount(objPtr); - - /* - * Read first character of stream to check for utf-8 BOM + /* Try to read first character of stream, so we can + * check for utf-8 BOM to be handled especially. */ - - if (Tcl_ReadChars(chan, objPtr, 1, 0) == TCL_IO_FAILURE) { + if (Tcl_ReadChars(chan, objPtr, 1, 0) < 0) { Tcl_Close(interp, chan); - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read file \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); + Tcl_AppendResult(interp, "couldn't read file \"", + Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL); goto end; } string = Tcl_GetString(objPtr); - /* - * If first character is not a BOM, append the remaining characters. - * Otherwise, replace them. [Bug 3466099] + * If first character is not a BOM, append the remaining characters, + * otherwise replace them [Bug 3466099]. */ - - if (Tcl_ReadChars(chan, objPtr, TCL_INDEX_NONE, - memcmp(string, "\xEF\xBB\xBF", 3)) == TCL_IO_FAILURE) { + if (Tcl_ReadChars(chan, objPtr, -1, + memcmp(string, "\xef\xbb\xbf", 3)) < 0) { Tcl_Close(interp, chan); - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read file \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); + Tcl_AppendResult(interp, "couldn't read file \"", + Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL); goto end; } @@ -1776,19 +1852,16 @@ Tcl_FSEvalFileEx( oldScriptFile = iPtr->scriptFile; iPtr->scriptFile = pathPtr; Tcl_IncrRefCount(iPtr->scriptFile); - string = TclGetStringFromObj(objPtr, &length); - - /* - * TIP #280: Open a frame for the evaluated script. - */ - + string = Tcl_GetStringFromObj(objPtr, &length); + /* TIP #280 Force the evaluator to open a frame for a sourced + * file. */ iPtr->evalFlags |= TCL_EVAL_FILE; - result = TclEvalEx(interp, string, length, 0, 1, NULL, string); + result = Tcl_EvalEx(interp, string, length, 0); /* - * Restore the original iPtr->scriptFile value, but because the value may - * have hanged during evaluation, don't assume it currently points to - * pathPtr. + * Now we have to be careful; the script may have changed the + * iPtr->scriptFile value, so we must reset it without assuming it still + * points to 'pathPtr'. */ if (iPtr->scriptFile != NULL) { @@ -1800,190 +1873,39 @@ Tcl_FSEvalFileEx( result = TclUpdateReturnInfo(iPtr); } else if (result == TCL_ERROR) { /* - * Record information about where the error occurred. + * Record information telling where the error occurred. */ - const char *pathString = TclGetStringFromObj(pathPtr, &length); + const char *pathString = Tcl_GetStringFromObj(pathPtr, &length); int limit = 150; int overflow = (length > limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (file \"%.*s%s\" line %d)", (overflow ? limit : length), pathString, - (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); + (overflow ? "..." : ""), interp->errorLine)); } end: Tcl_DecrRefCount(objPtr); return result; } - -int -TclNREvalFile( - Tcl_Interp *interp, /* Interpreter in which to evaluate the script. */ - Tcl_Obj *pathPtr, /* Pathname of a file containing the script to - * evaluate. Tilde-substitution is performed on - * this pathname. */ - const char *encodingName) /* The name of an encoding to use, or NULL to - * use the utf-8 encoding. */ -{ - Tcl_StatBuf statBuf; - Tcl_Obj *oldScriptFile, *objPtr; - Interp *iPtr; - Tcl_Channel chan; - const char *string; - - if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) { - return TCL_ERROR; - } - - if (Tcl_FSStat(pathPtr, &statBuf) == -1) { - Tcl_SetErrno(errno); - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read file \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); - return TCL_ERROR; - } - chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644); - if (chan == NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read file \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); - return TCL_ERROR; - } - TclPkgFileSeen(interp, Tcl_GetString(pathPtr)); - - /* - * The eof character is \x1A (^Z). Tcl uses it on every platform to allow - * for scripted documents. [Bug: 2040] - */ - - Tcl_SetChannelOption(interp, chan, "-eofchar", "\x1A {}"); - - /* - * If the encoding is specified, set the channel to that encoding. - * Otherwise use utf-8. If the encoding is unknown report an error. - */ - - if (encodingName == NULL) { - encodingName = "utf-8"; - } - if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName) - != TCL_OK) { - Tcl_Close(interp, chan); - return TCL_ERROR; - } - - TclNewObj(objPtr); - Tcl_IncrRefCount(objPtr); - - /* - * Read first character of stream to check for utf-8 BOM - */ - - if (Tcl_ReadChars(chan, objPtr, 1, 0) == TCL_IO_FAILURE) { - Tcl_Close(interp, chan); - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read file \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); - Tcl_DecrRefCount(objPtr); - return TCL_ERROR; - } - string = Tcl_GetString(objPtr); - - /* - * If first character is not a BOM, append the remaining characters. - * Otherwise, replace them. [Bug 3466099] - */ - - if (Tcl_ReadChars(chan, objPtr, TCL_INDEX_NONE, - memcmp(string, "\xEF\xBB\xBF", 3)) == TCL_IO_FAILURE) { - Tcl_Close(interp, chan); - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't read file \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); - Tcl_DecrRefCount(objPtr); - return TCL_ERROR; - } - - if (Tcl_Close(interp, chan) != TCL_OK) { - Tcl_DecrRefCount(objPtr); - return TCL_ERROR; - } - - iPtr = (Interp *) interp; - oldScriptFile = iPtr->scriptFile; - iPtr->scriptFile = pathPtr; - Tcl_IncrRefCount(iPtr->scriptFile); - - /* - * TIP #280: Open a frame for the evaluated script. - */ - - iPtr->evalFlags |= TCL_EVAL_FILE; - TclNRAddCallback(interp, EvalFileCallback, oldScriptFile, pathPtr, objPtr, - NULL); - return TclNREvalObjEx(interp, objPtr, 0, NULL, INT_MIN); -} - -static int -EvalFileCallback( - void *data[], - Tcl_Interp *interp, - int result) -{ - Interp *iPtr = (Interp *) interp; - Tcl_Obj *oldScriptFile = (Tcl_Obj *)data[0]; - Tcl_Obj *pathPtr = (Tcl_Obj *)data[1]; - Tcl_Obj *objPtr = (Tcl_Obj *)data[2]; - - /* - * Restore the original iPtr->scriptFile value, but because the value may - * have hanged during evaluation, don't assume it currently points to - * pathPtr. - */ - - if (iPtr->scriptFile != NULL) { - Tcl_DecrRefCount(iPtr->scriptFile); - } - iPtr->scriptFile = oldScriptFile; - - if (result == TCL_RETURN) { - result = TclUpdateReturnInfo(iPtr); - } else if (result == TCL_ERROR) { - /* - * Record information about where the error occurred. - */ - - Tcl_Size length; - const char *pathString = TclGetStringFromObj(pathPtr, &length); - const int limit = 150; - int overflow = (length > limit); - - Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( - "\n (file \"%.*s%s\" line %d)", - (overflow ? limit : length), pathString, - (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); - } - - Tcl_DecrRefCount(objPtr); - return result; -} /* *---------------------------------------------------------------------- * * Tcl_GetErrno -- * - * Currently the global variable "errno", but could in the future change + * Gets the current value of the Tcl error code variable. This is + * currently the global variable "errno" but could in the future change * to something else. * * Results: - * The current Tcl error number. + * The value of the Tcl error code variable. * * Side effects: - * None. The value of the Tcl error code variable is only defined if it - * was set by a previous call to Tcl_SetErrno. + * None. Note that the value of the Tcl error code variable is UNDEFINED + * if a call to Tcl_SetErrno did not precede this call. * *---------------------------------------------------------------------- */ @@ -1991,11 +1913,6 @@ EvalFileCallback( int Tcl_GetErrno(void) { - /* - * On some platforms errno is thread-local, as implemented by the C - * library. - */ - return errno; } @@ -2004,15 +1921,13 @@ Tcl_GetErrno(void) * * Tcl_SetErrno -- * - * Sets the Tcl error code to the given value. On some saner platforms - * this is implemented in the C library as a thread-local value , but this - * is *really* unsafe to assume! + * Sets the Tcl error code variable to the supplied value. * * Results: * None. * * Side effects: - * Modifies the the Tcl error code value. + * Modifies the value of the Tcl error code variable. * *---------------------------------------------------------------------- */ @@ -2021,11 +1936,6 @@ void Tcl_SetErrno( int err) /* The new value. */ { - /* - * On some platforms, errno is implemented by the C library as a thread - * local value - */ - errno = err; } @@ -2034,28 +1944,31 @@ Tcl_SetErrno( * * Tcl_PosixError -- * - * Typically called after a UNIX kernel call returns an error. Sets the - * interpreter errorCode to machine-parsable information about the error. + * This function is typically called after UNIX kernel calls return + * errors. It stores machine-readable information about the error in + * errorCode field of interp and returns an information string for the + * caller's use. * * Results: - * A human-readable sring describing the error. + * The return value is a human-readable string describing the error. * * Side effects: - * Sets the errorCode value of the interpreter. + * The errorCode field of the interp is set. * *---------------------------------------------------------------------- */ const char * Tcl_PosixError( - Tcl_Interp *interp) /* Interpreter to set the errorCode of */ + Tcl_Interp *interp) /* Interpreter whose errorCode field is to be + * set. */ { const char *id, *msg; msg = Tcl_ErrnoMsg(errno); id = Tcl_ErrnoId(); if (interp) { - Tcl_SetErrorCode(interp, "POSIX", id, msg, (void *)NULL); + Tcl_SetErrorCode(interp, "POSIX", id, msg, NULL); } return msg; } @@ -2064,9 +1977,11 @@ Tcl_PosixError( *---------------------------------------------------------------------- * * Tcl_FSStat -- - * Calls 'statProc' of the filesystem corresponding to pathPtr. * - * Replaces the standard library "stat" routine. + * This function replaces the library version of stat and lsat. + * + * The appropriate function for the filesystem to which pathPtr belongs + * will be called. * * Results: * See stat documentation. @@ -2079,15 +1994,75 @@ Tcl_PosixError( int Tcl_FSStat( - Tcl_Obj *pathPtr, /* Pathname of the file to call stat on (in - * current CP). */ - Tcl_StatBuf *buf) /* A buffer to hold the results of the call to - * stat. */ + Tcl_Obj *pathPtr, /* Path of file to stat (in current CP). */ + Tcl_StatBuf *buf) /* Filled with results of stat call. */ { - const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); + const Tcl_Filesystem *fsPtr; +#ifdef USE_OBSOLETE_FS_HOOKS + struct stat oldStyleStatBuffer; + int retVal = -1; + + /* + * Call each of the "stat" function in succession. A non-return value of + * -1 indicates the particular function has succeeded. + */ - if (fsPtr != NULL && fsPtr->statProc != NULL) { - return fsPtr->statProc(pathPtr, buf); + Tcl_MutexLock(&obsoleteFsHookMutex); + + if (statProcList != NULL) { + StatProc *statProcPtr; + char *path; + Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr); + if (transPtr == NULL) { + path = NULL; + } else { + path = Tcl_GetString(transPtr); + } + + statProcPtr = statProcList; + while ((retVal == -1) && (statProcPtr != NULL)) { + retVal = (*statProcPtr->proc)(path, &oldStyleStatBuffer); + statProcPtr = statProcPtr->nextPtr; + } + if (transPtr != NULL) { + Tcl_DecrRefCount(transPtr); + } + } + + Tcl_MutexUnlock(&obsoleteFsHookMutex); + if (retVal != -1) { + /* + * Note that EOVERFLOW is not a problem here, and these assignments + * should all be widening (if not identity.) + */ + + buf->st_mode = oldStyleStatBuffer.st_mode; + buf->st_ino = oldStyleStatBuffer.st_ino; + buf->st_dev = oldStyleStatBuffer.st_dev; + buf->st_rdev = oldStyleStatBuffer.st_rdev; + buf->st_nlink = oldStyleStatBuffer.st_nlink; + buf->st_uid = oldStyleStatBuffer.st_uid; + buf->st_gid = oldStyleStatBuffer.st_gid; + buf->st_size = Tcl_LongAsWide(oldStyleStatBuffer.st_size); + buf->st_atime = oldStyleStatBuffer.st_atime; + buf->st_mtime = oldStyleStatBuffer.st_mtime; + buf->st_ctime = oldStyleStatBuffer.st_ctime; +#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE + buf->st_blksize = oldStyleStatBuffer.st_blksize; +#endif +#ifdef HAVE_STRUCT_STAT_ST_BLOCKS + buf->st_blocks = Tcl_LongAsWide(oldStyleStatBuffer.st_blocks); +#endif + return retVal; + } +#endif /* USE_OBSOLETE_FS_HOOKS */ + + fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); + if (fsPtr != NULL) { + Tcl_FSStatProc *proc = fsPtr->statProc; + if (proc != NULL) { + return (*proc)(pathPtr, buf); + } } Tcl_SetErrno(ENOENT); return -1; @@ -2097,11 +2072,11 @@ Tcl_FSStat( *---------------------------------------------------------------------- * * Tcl_FSLstat -- - * Calls the 'lstatProc' of the filesystem corresponding to pathPtr. * - * Replaces the library version of lstat. If the filesystem doesn't - * provide lstatProc but does provide statProc, Tcl falls back to - * statProc. + * This function replaces the library version of lstat. The appropriate + * function for the filesystem to which pathPtr belongs will be called. + * If no 'lstat' function is listed, but a 'stat' function is, then Tcl + * will fall back on the stat function. * * Results: * See lstat documentation. @@ -2114,18 +2089,19 @@ Tcl_FSStat( int Tcl_FSLstat( - Tcl_Obj *pathPtr, /* Pathname of the file to call stat on (in - current CP). */ - Tcl_StatBuf *buf) /* Filled with results of that call to stat. */ + Tcl_Obj *pathPtr, /* Path of file to stat (in current CP). */ + Tcl_StatBuf *buf) /* Filled with results of stat call. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - if (fsPtr != NULL) { - if (fsPtr->lstatProc != NULL) { - return fsPtr->lstatProc(pathPtr, buf); - } - if (fsPtr->statProc != NULL) { - return fsPtr->statProc(pathPtr, buf); + Tcl_FSLstatProc *proc = fsPtr->lstatProc; + if (proc != NULL) { + return (*proc)(pathPtr, buf); + } else { + Tcl_FSStatProc *sproc = fsPtr->statProc; + if (sproc != NULL) { + return (*sproc)(pathPtr, buf); + } } } Tcl_SetErrno(ENOENT); @@ -2137,9 +2113,8 @@ Tcl_FSLstat( * * Tcl_FSAccess -- * - * Calls 'accessProc' of the filesystem corresponding to pathPtr. - * - * Replaces the library version of access. + * This function replaces the library version of access. The appropriate + * function for the filesystem to which pathPtr belongs will be called. * * Results: * See access documentation. @@ -2152,14 +2127,54 @@ Tcl_FSLstat( int Tcl_FSAccess( - Tcl_Obj *pathPtr, /* Pathname of file to access (in current CP). */ + Tcl_Obj *pathPtr, /* Path of file to access (in current CP). */ int mode) /* Permission setting. */ { - const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); + const Tcl_Filesystem *fsPtr; +#ifdef USE_OBSOLETE_FS_HOOKS + int retVal = -1; + + /* + * Call each of the "access" function in succession. A non-return value of + * -1 indicates the particular function has succeeded. + */ + + Tcl_MutexLock(&obsoleteFsHookMutex); - if (fsPtr != NULL && fsPtr->accessProc != NULL) { - return fsPtr->accessProc(pathPtr, mode); + if (accessProcList != NULL) { + AccessProc *accessProcPtr; + char *path; + Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr); + if (transPtr == NULL) { + path = NULL; + } else { + path = Tcl_GetString(transPtr); + } + + accessProcPtr = accessProcList; + while ((retVal == -1) && (accessProcPtr != NULL)) { + retVal = (*accessProcPtr->proc)(path, mode); + accessProcPtr = accessProcPtr->nextPtr; + } + if (transPtr != NULL) { + Tcl_DecrRefCount(transPtr); + } + } + + Tcl_MutexUnlock(&obsoleteFsHookMutex); + if (retVal != -1) { + return retVal; } +#endif /* USE_OBSOLETE_FS_HOOKS */ + + fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); + if (fsPtr != NULL) { + Tcl_FSAccessProc *proc = fsPtr->accessProc; + if (proc != NULL) { + return (*proc)(pathPtr, mode); + } + } + Tcl_SetErrno(ENOENT); return -1; } @@ -2169,81 +2184,120 @@ Tcl_FSAccess( * * Tcl_FSOpenFileChannel -- * - * Calls 'openfileChannelProc' of the filesystem corresponding to - * pathPtr. + * The appropriate function for the filesystem to which pathPtr belongs + * will be called. * * Results: - * The new channel, or NULL if the named file could not be opened. + * The new channel or NULL, if the named file could not be opened. * * Side effects: - * Opens a channel, possibly creating the corresponding the file on the - * filesystem. + * May open the channel and may cause creation of a file on the file + * system. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_FSOpenFileChannel( - Tcl_Interp *interp, /* Interpreter for error reporting, or NULL */ - Tcl_Obj *pathPtr, /* Pathname of file to open. */ + Tcl_Interp *interp, /* Interpreter for error reporting; can be + * NULL. */ + Tcl_Obj *pathPtr, /* Name of file to open. */ const char *modeString, /* A list of POSIX open modes or a string such * as "rw". */ - int permissions) /* What modes to use if opening the file - involves creating it. */ + int permissions) /* If the open involves creating a file, with + * what modes to create it? */ { const Tcl_Filesystem *fsPtr; Tcl_Channel retVal = NULL; +#ifdef USE_OBSOLETE_FS_HOOKS + /* + * Call each of the "Tcl_OpenFileChannel" functions in succession. A + * non-NULL return value indicates the particular function has succeeded. + */ + + Tcl_MutexLock(&obsoleteFsHookMutex); + 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; + } + if (transPtr != NULL) { + Tcl_DecrRefCount(transPtr); + } + } + 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 the correct error message. - */ return NULL; } fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - if (fsPtr != NULL && fsPtr->openFileChannelProc != NULL) { - int mode, seekFlag, binary; + if (fsPtr != NULL) { + Tcl_FSOpenFileChannelProc *proc = fsPtr->openFileChannelProc; + if (proc != NULL) { + int mode, seekFlag, binary; - /* - * Parse the mode to determine whether to seek at the outset - * and/or set the channel into binary mode. - */ + /* + * Parse the mode, picking up whether we want to seek to start + * with and/or set the channel automatically into binary mode. + */ - mode = TclGetOpenModeEx(interp, modeString, &seekFlag, &binary); - if (mode == -1) { - return NULL; - } + mode = TclGetOpenModeEx(interp, modeString, &seekFlag, &binary); + if (mode == -1) { + return NULL; + } - /* - * Open the file. - */ + /* + * Do the actual open() call. + */ - retVal = fsPtr->openFileChannelProc(interp, pathPtr, mode, - permissions); - if (retVal == NULL) { - return NULL; - } + retVal = (*proc)(interp, pathPtr, mode, permissions); + if (retVal == NULL) { + return NULL; + } - /* - * Seek and/or set binary mode as determined above. - */ + /* + * Apply appropriate flags parsed out above. + */ - if (seekFlag && Tcl_Seek(retVal, (Tcl_WideInt) 0, SEEK_END) - < (Tcl_WideInt) 0) { - if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "could not seek to end of file while opening \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); + if (seekFlag && Tcl_Seek(retVal, (Tcl_WideInt)0, + SEEK_END) < (Tcl_WideInt)0) { + if (interp != NULL) { + Tcl_AppendResult(interp, "could not seek to end " + "of file while opening \"", Tcl_GetString(pathPtr), + "\": ", Tcl_PosixError(interp), NULL); + } + Tcl_Close(NULL, retVal); + return NULL; } - Tcl_Close(NULL, retVal); - return NULL; - } - if (binary) { - Tcl_SetChannelOption(interp, retVal, "-translation", "binary"); + if (binary) { + Tcl_SetChannelOption(interp, retVal, "-translation", "binary"); + } + return retVal; } - return retVal; } /* @@ -2252,9 +2306,8 @@ Tcl_FSOpenFileChannel( Tcl_SetErrno(ENOENT); if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't open \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); + Tcl_AppendResult(interp, "couldn't open \"", Tcl_GetString(pathPtr), + "\": ", Tcl_PosixError(interp), NULL); } return NULL; } @@ -2264,10 +2317,8 @@ Tcl_FSOpenFileChannel( * * Tcl_FSUtime -- * - * Calls 'uTimeProc' of the filesystem corresponding to the given - * pathname. - * - * Replaces the library version of utime. + * This function replaces the library version of utime. The appropriate + * function for the filesystem to which pathPtr belongs will be called. * * Results: * See utime documentation. @@ -2280,22 +2331,17 @@ Tcl_FSOpenFileChannel( int Tcl_FSUtime( - Tcl_Obj *pathPtr, /* Pathaname of file to call uTimeProc on */ - struct utimbuf *tval) /* Specifies the access/modification + Tcl_Obj *pathPtr, /* File to change access/modification times */ + struct utimbuf *tval) /* Structure containing access/modification * times to use. Should not be modified. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - int err; - - if (fsPtr == NULL) { - err = ENOENT; - } else { - if (fsPtr->utimeProc != NULL) { - return fsPtr->utimeProc(pathPtr, tval); + if (fsPtr != NULL) { + Tcl_FSUtimeProc *proc = fsPtr->utimeProc; + if (proc != NULL) { + return (*proc)(pathPtr, tval); } - err = ENOTSUP; } - Tcl_SetErrno(err); return -1; } @@ -2304,10 +2350,11 @@ Tcl_FSUtime( * * NativeFileAttrStrings -- * - * Implements the platform-dependent 'file attributes' subcommand for the - * native filesystem, for listing the set of possible attribute strings. - * Part of Tcl's native filesystem support. Placed here because it is used - * under both Unix and Windows. + * This function implements the platform dependent 'file attributes' + * subcommand, for the native filesystem, for listing the set of possible + * attribute strings. This function is part of Tcl's native filesystem + * support, and is placed here because it is shared by Unix and Windows + * code. * * Results: * An array of strings @@ -2318,10 +2365,10 @@ Tcl_FSUtime( *---------------------------------------------------------------------- */ -static const char *const * +static const char ** NativeFileAttrStrings( - TCL_UNUSED(Tcl_Obj *), - TCL_UNUSED(Tcl_Obj **)) + Tcl_Obj *pathPtr, + Tcl_Obj **objPtrRef) { return tclpFileAttrStrings; } @@ -2331,18 +2378,16 @@ NativeFileAttrStrings( * * NativeFileAttrsGet -- * - * Implements the platform-dependent 'file attributes' subcommand for the - * native filesystem for 'get' operations. Part of Tcl's native - * filesystem support. Defined here because it is used under both Unix - * and Windows. + * This function implements the platform dependent 'file attributes' + * subcommand, for the native filesystem, for 'get' operations. This + * function is part of Tcl's native filesystem support, and is placed + * here because it is shared by Unix and Windows code. * * Results: - * Standard Tcl return code. - * - * If there was no error, stores in objPtrRef a pointer to a new object - * having a refCount of zero and holding the result. The caller should - * store it somewhere, e.g. as the Tcl result, or decrement its refCount - * to free it. + * Standard Tcl return code. The object placed in objPtrRef (if TCL_OK + * was returned) is likely to have a refCount of zero. Either way we must + * either store it somewhere (e.g. the Tcl result), or Incr/Decr its + * refCount to ensure it is properly freed. * * Side effects: * None. @@ -2354,10 +2399,11 @@ static int NativeFileAttrsGet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* index of the attribute command. */ - Tcl_Obj *pathPtr, /* Pathname of the file */ - Tcl_Obj **objPtrRef) /* Where to store the a pointer to the result. */ + Tcl_Obj *pathPtr, /* path of file we are operating on. */ + Tcl_Obj **objPtrRef) /* for output. */ { - return tclpFileAttrProcs[index].getProc(interp, index, pathPtr,objPtrRef); + return (*tclpFileAttrProcs[index].getProc)(interp, index, pathPtr, + objPtrRef); } /* @@ -2365,13 +2411,13 @@ NativeFileAttrsGet( * * NativeFileAttrsSet -- * - * Implements the platform-dependent 'file attributes' subcommand for the - * native filesystem for 'set' operations. A part of Tcl's native - * filesystem support, it is defined here because it is used under both - * Unix and Windows. + * This function implements the platform dependent 'file attributes' + * subcommand, for the native filesystem, for 'set' operations. This + * function is part of Tcl's native filesystem support, and is placed + * here because it is shared by Unix and Windows code. * * Results: - * A standard Tcl return code. + * Standard Tcl return code. * * Side effects: * None. @@ -2383,10 +2429,10 @@ static int NativeFileAttrsSet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* index of the attribute command. */ - Tcl_Obj *pathPtr, /* Pathname of the file */ - Tcl_Obj *objPtr) /* The value to set. */ + Tcl_Obj *pathPtr, /* path of file we are operating on. */ + Tcl_Obj *objPtr) /* set to this value. */ { - return tclpFileAttrProcs[index].setProc(interp, index, pathPtr, objPtr); + return (*tclpFileAttrProcs[index].setProc)(interp, index, pathPtr, objPtr); } /* @@ -2394,16 +2440,18 @@ NativeFileAttrsSet( * * Tcl_FSFileAttrStrings -- * - * Implements part of the hookable 'file attributes' - * subcommand. - * - * Calls 'fileAttrStringsProc' of the filesystem corresponding to the - * given pathname. + * This function implements part of the hookable 'file attributes' + * subcommand. The appropriate function for the filesystem to which + * pathPtr belongs will be called. * * Results: - * Returns an array of strings, or returns NULL and stores in objPtrRef - * a pointer to a new Tcl list having a refCount of zero, and containing - * the file attribute strings. + * The called function may either return an array of strings, or may + * instead return NULL and place a Tcl list into the given objPtrRef. + * Tcl will take that list and first increment its refCount before using + * it. On completion of that use, Tcl will decrement its refCount. Hence + * if the list should be disposed of by Tcl when done, it should have a + * refCount of zero, and if the list should not be disposed of, the + * filesystem should ensure it retains a refCount on the object. * * Side effects: * None. @@ -2411,15 +2459,18 @@ NativeFileAttrsSet( *---------------------------------------------------------------------- */ -const char *const * +const char ** Tcl_FSFileAttrStrings( Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef) { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - if (fsPtr != NULL && fsPtr->fileAttrStringsProc != NULL) { - return fsPtr->fileAttrStringsProc(pathPtr, objPtrRef); + if (fsPtr != NULL) { + Tcl_FSFileAttrStringsProc *proc = fsPtr->fileAttrStringsProc; + if (proc != NULL) { + return (*proc)(pathPtr, objPtrRef); + } } Tcl_SetErrno(ENOENT); return NULL; @@ -2430,13 +2481,11 @@ Tcl_FSFileAttrStrings( * * TclFSFileAttrIndex -- * - * Given an attribute name, determines the index of the attribute in the + * Helper function for converting an attribute name to an index into the * attribute table. * * Results: - * A standard Tcl result code. - * - * If there is no error, stores the index in *indexPtr. + * Tcl result code, index written to *indexPtr on result==TCL_OK * * Side effects: * None. @@ -2446,12 +2495,13 @@ Tcl_FSFileAttrStrings( int TclFSFileAttrIndex( - Tcl_Obj *pathPtr, /* Pathname of the file. */ - const char *attributeName, /* The name of the attribute. */ - int *indexPtr) /* A place to store the result. */ + Tcl_Obj *pathPtr, /* File whose attributes are to be indexed + * into. */ + const char *attributeName, /* The attribute being looked for. */ + int *indexPtr) /* Where to write the found index. */ { Tcl_Obj *listObj = NULL; - const char *const *attrTable; + const char **attrTable; /* * Get the attribute table for the file. @@ -2482,10 +2532,10 @@ TclFSFileAttrIndex( * It's a non-constant attribute list, so do a literal search. */ - Tcl_Size i, objc; + int i, objc; Tcl_Obj **objv; - if (TclListObjGetElements(NULL, listObj, &objc, &objv) != TCL_OK) { + if (Tcl_ListObjGetElements(NULL, listObj, &objc, &objv) != TCL_OK) { TclDecrRefCount(listObj); return TCL_ERROR; } @@ -2508,16 +2558,15 @@ TclFSFileAttrIndex( * * Tcl_FSFileAttrsGet -- * - * Implements read access for the hookable 'file attributes' subcommand. - * - * Calls 'fileAttrsGetProc' of the filesystem corresponding to the given - * pathname. + * This function implements read access for the hookable 'file + * attributes' subcommand. The appropriate function for the filesystem to + * which pathPtr belongs will be called. * * Results: - * A standard Tcl return code. - * - * On success, stores in objPtrRef a pointer to a new Tcl_Obj having a - * refCount of zero, and containing the result. + * Standard Tcl return code. The object placed in objPtrRef (if TCL_OK + * was returned) is likely to have a refCount of zero. Either way we must + * either store it somewhere (e.g. the Tcl result), or Incr/Decr its + * refCount to ensure it is properly freed. * * Side effects: * None. @@ -2528,14 +2577,17 @@ TclFSFileAttrIndex( int Tcl_FSFileAttrsGet( Tcl_Interp *interp, /* The interpreter for error reporting. */ - int index, /* The index of the attribute command. */ - Tcl_Obj *pathPtr, /* The pathname of the file. */ - Tcl_Obj **objPtrRef) /* A place to store the result. */ + int index, /* index of the attribute command. */ + Tcl_Obj *pathPtr, /* filename we are operating on. */ + Tcl_Obj **objPtrRef) /* for output. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - if (fsPtr != NULL && fsPtr->fileAttrsGetProc != NULL) { - return fsPtr->fileAttrsGetProc(interp, index, pathPtr, objPtrRef); + if (fsPtr != NULL) { + Tcl_FSFileAttrsGetProc *proc = fsPtr->fileAttrsGetProc; + if (proc != NULL) { + return (*proc)(interp, index, pathPtr, objPtrRef); + } } Tcl_SetErrno(ENOENT); return -1; @@ -2546,14 +2598,12 @@ Tcl_FSFileAttrsGet( * * Tcl_FSFileAttrsSet -- * - * Implements write access for the hookable 'file - * attributes' subcommand. - * - * Calls 'fileAttrsSetProc' for the filesystem corresponding to the given - * pathname. + * This function implements write access for the hookable 'file + * attributes' subcommand. The appropriate function for the filesystem to + * which pathPtr belongs will be called. * * Results: - * A standard Tcl return code. + * Standard Tcl return code. * * Side effects: * None. @@ -2564,14 +2614,17 @@ Tcl_FSFileAttrsGet( int Tcl_FSFileAttrsSet( Tcl_Interp *interp, /* The interpreter for error reporting. */ - int index, /* The index of the attribute command. */ - Tcl_Obj *pathPtr, /* The pathname of the file. */ - Tcl_Obj *objPtr) /* A place to store the result. */ + int index, /* index of the attribute command. */ + Tcl_Obj *pathPtr, /* filename we are operating on. */ + Tcl_Obj *objPtr) /* Input value. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - if (fsPtr != NULL && fsPtr->fileAttrsSetProc != NULL) { - return fsPtr->fileAttrsSetProc(interp, index, pathPtr, objPtr); + if (fsPtr != NULL) { + Tcl_FSFileAttrsSetProc *proc = fsPtr->fileAttrsSetProc; + if (proc != NULL) { + return (*proc)(interp, index, pathPtr, objPtr); + } } Tcl_SetErrno(ENOENT); return -1; @@ -2582,25 +2635,33 @@ Tcl_FSFileAttrsSet( * * Tcl_FSGetCwd -- * - * Replaces the library version of getcwd(). + * This function replaces the library version of getcwd(). * - * Most virtual filesystems do not implement cwdProc. Tcl maintains its - * own record of the current directory which it keeps synchronized with - * the filesystem corresponding to the pathname of the current directory - * if the filesystem provides a cwdProc (the native filesystem does). + * 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 synch this + * with the cwd's containing filesystem, if that filesystem provides a + * cwdProc (e.g. the native filesystem). * - * If Tcl's current directory is not in the native filesystem, Tcl's - * current directory and the current directory of the process are - * different. To avoid confusion, extensions should call Tcl_FSGetCwd to - * obtain the current directory from Tcl rather than from the operating - * system. + * 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 cached in the thread's + * private data structures and reference to the cached copy 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: - * Returns a pointer to a Tcl_Obj having a refCount of 1 and containing - * the current thread's local copy of the global cwdPathPtr value. + * 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. * - * Returns NULL if the current directory could not be determined, and - * leaves an error message in the interpreter's result. + * The result already has its refCount incremented for the caller. When + * it is no longer needed, that refCount should be decremented. * * Side effects: * Various objects may be freed and allocated. @@ -2619,216 +2680,201 @@ Tcl_FSGetCwd( Tcl_Obj *retVal = NULL; /* - * This is the first time this routine has been called. Call - * 'getCwdProc' for each registered filsystems until one returns - * something other than NULL, which is a pointer to the pathname of the - * current directory. + * 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 = FsGetFirstFilesystem(); Claim(); - for (; (retVal == NULL) && (fsRecPtr != NULL); - fsRecPtr = fsRecPtr->nextPtr) { - void *retCd; - TclFSGetCwdProc2 *proc2; - - if (fsRecPtr->fsPtr->getCwdProc == NULL) { - continue; - } - - if (fsRecPtr->fsPtr->version == TCL_FILESYSTEM_VERSION_1) { - retVal = fsRecPtr->fsPtr->getCwdProc(interp); - continue; - } - - proc2 = (TclFSGetCwdProc2 *) fsRecPtr->fsPtr->getCwdProc; - retCd = proc2(NULL); - if (retCd != NULL) { - Tcl_Obj *norm; - - /* - * Found the pathname of the current directory. - */ - - retVal = fsRecPtr->fsPtr->internalToNormalizedProc(retCd); - Tcl_IncrRefCount(retVal); - norm = TclFSNormalizeAbsolutePath(interp,retVal); - if (norm != NULL) { - /* - * Assign to global storage the pathname of the current - * directory and copy it into thread-local storage as - * well. - * - * At system startup multiple threads could in principle - * call this function simultaneously, which is a little - * peculiar, but should be fine given the mutex locks in - * FSUPdateCWD. Once some value is assigned to the global - * variable the 'else' branch below is always taken, which - * is simpler. - */ - - FsUpdateCwd(norm, retCd); - Tcl_DecrRefCount(norm); + while ((retVal == NULL) && (fsRecPtr != NULL)) { + Tcl_FSGetCwdProc *proc = fsRecPtr->fsPtr->getCwdProc; + if (proc != NULL) { + if (fsRecPtr->fsPtr->version != TCL_FILESYSTEM_VERSION_1) { + ClientData retCd; + TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2*)proc; + + retCd = (*proc2)(NULL); + if (retCd != NULL) { + Tcl_Obj *norm; + /* Looks like a new current directory */ + retVal = (*fsRecPtr->fsPtr->internalToNormalizedProc)( + retCd); + Tcl_IncrRefCount(retVal); + norm = TclFSNormalizeAbsolutePath(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 + * function 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. + */ + + FsUpdateCwd(norm, retCd); + Tcl_DecrRefCount(norm); + } else { + (*fsRecPtr->fsPtr->freeInternalRepProc)(retCd); + } + Tcl_DecrRefCount(retVal); + retVal = NULL; + Disclaim(); + goto cdDidNotChange; + } else if (interp != NULL) { + Tcl_AppendResult(interp, + "error getting working directory name: ", + Tcl_PosixError(interp), NULL); + } } else { - fsRecPtr->fsPtr->freeInternalRepProc(retCd); + retVal = (*proc)(interp); } - Tcl_DecrRefCount(retVal); - retVal = NULL; - Disclaim(); - goto cdDidNotChange; - } else if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "error getting working directory name: %s", - Tcl_PosixError(interp))); } + fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); - if (retVal != NULL) { - /* - * On some platforms the pathname of the current directory might - * not be normalized. For efficiency, ensure that it is - * normalized. For the sake of efficiency, we want a completely - * normalized current working directory at all times. - */ + /* + * 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 = TclFSNormalizeAbsolutePath(interp, retVal); - if (norm != NULL) { /* - * We found a current working directory, which is now in our - * global storage. We must make a copy. Norm already has a - * refCount of 1. + * 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: Multiple threads at system startup could in - * principle call this function simultaneously. They will - * therefore each set the cwdPathPtr independently, which 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. + * Threading issue: note that multiple threads at system + * startup could in principle call this function + * 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. */ - void *cd = (void *) Tcl_FSGetNativePath(norm); - + ClientData cd = (ClientData) Tcl_FSGetNativePath(norm); FsUpdateCwd(norm, TclNativeDupInternalRep(cd)); Tcl_DecrRefCount(norm); } Tcl_DecrRefCount(retVal); - } else { - /* - * retVal is NULL. There is no current directory, which could be - * problematic. - */ } } else { /* - * There is a thread-local value for the pathname of the current - * directory. Give corresponding filesystem a chance update the value - * if it is out-of-date. This allows an error to be thrown if, for - * example, the permissions on the current working directory have - * changed. + * 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. */ - const Tcl_Filesystem *fsPtr = - Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr); - void *retCd = NULL; - Tcl_Obj *retVal, *norm; - - if (fsPtr == NULL || fsPtr->getCwdProc == NULL) { - /* - * There is no corresponding filesystem or the filesystem does not - * have a getCwd routine. Just assume current local value is ok. - */ - goto cdDidNotChange; - } - - if (fsPtr->version == TCL_FILESYSTEM_VERSION_1) { - retVal = fsPtr->getCwdProc(interp); - } else { - /* - * New API. - */ - - TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc; - - retCd = proc2(tsdPtr->cwdClientData); - if (retCd == NULL && interp != NULL) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "error getting working directory name: %s", - Tcl_PosixError(interp))); - } - - if (retCd == tsdPtr->cwdClientData) { - goto cdDidNotChange; - } - - /* - * Looks like a new current directory. - */ + const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr); - retVal = fsPtr->internalToNormalizedProc(retCd); - Tcl_IncrRefCount(retVal); - } + /* + * 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 (retVal == NULL) { - /* - * The current directory could not not determined. Reset the - * current direcory to ensure, for example, that '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; + ClientData retCd = NULL; + if (proc != NULL) { + Tcl_Obj *retVal; + if (fsPtr->version != TCL_FILESYSTEM_VERSION_1) { + TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2*)proc; + + retCd = (*proc2)(tsdPtr->cwdClientData); + if (retCd == NULL && interp != NULL) { + Tcl_AppendResult(interp, + "error getting working directory name: ", + Tcl_PosixError(interp), NULL); + } - FsUpdateCwd(NULL, NULL); - goto cdDidNotChange; - } + if (retCd == tsdPtr->cwdClientData) { + goto cdDidNotChange; + } - norm = TclFSNormalizeAbsolutePath(interp, retVal); + /* + * Looks like a new current directory. + */ - if (norm == NULL) { - /* - * 'norm' shouldn't ever be NULL, but we are careful. - */ + retVal = (*fsPtr->internalToNormalizedProc)(retCd); + Tcl_IncrRefCount(retVal); + } else { + retVal = (*proc)(interp); + } + if (retVal != NULL) { + Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal); - /* Do nothing */ - if (retCd != NULL) { - fsPtr->freeInternalRepProc(retCd); - } - } else if (norm == tsdPtr->cwdPathPtr) { - goto cdEqual; - } else { - /* - * Determine whether the filesystem's answer is the same as the - * cached local value. Since both 'norm' and 'tsdPtr->cwdPathPtr' - * are normalized pathnames, do something more efficient than - * calling 'Tcl_FSEqualPaths', and in addition avoid a nasty - * infinite loop bug when trying to normalize tsdPtr->cwdPathPtr. - */ + /* + * Check whether cwd has changed from the value previously + * stored in cwdPathPtr. Really 'norm' shouldn't be NULL, + * but we are careful. + */ - Tcl_Size len1, len2; - const char *str1, *str2; + if (norm == NULL) { + /* Do nothing */ + if (retCd != NULL) { + (*fsPtr->freeInternalRepProc)(retCd); + } + } else if (norm == tsdPtr->cwdPathPtr) { + goto cdEqual; + } else { + /* + * Note that both 'norm' and 'tsdPtr->cwdPathPtr' are + * normalized paths. Therefore we can be more + * efficient than calling 'Tcl_FSEqualPaths', and in + * addition avoid a nasty infinite loop bug when + * trying to normalize tsdPtr->cwdPathPtr. + */ + + int len1, len2; + char *str1, *str2; + + str1 = Tcl_GetStringFromObj(tsdPtr->cwdPathPtr, &len1); + str2 = Tcl_GetStringFromObj(norm, &len2); + if ((len1 == len2) && (strcmp(str1, str2) == 0)) { + /* + * 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. + */ + + cdEqual: + Tcl_DecrRefCount(norm); + if (retCd != NULL) { + (*fsPtr->freeInternalRepProc)(retCd); + } + } else { + FsUpdateCwd(norm, retCd); + Tcl_DecrRefCount(norm); + } + } + Tcl_DecrRefCount(retVal); + } else { + /* + * The 'cwd' function returned an error; reset the cwd. + */ - str1 = TclGetStringFromObj(tsdPtr->cwdPathPtr, &len1); - str2 = TclGetStringFromObj(norm, &len2); - if ((len1 == len2) && (strcmp(str1, str2) == 0)) { - /* - * The pathname values are equal so retain the old pathname - * object which is probably already shared and free the - * normalized pathname that was just produced. - */ - cdEqual: - Tcl_DecrRefCount(norm); - if (retCd != NULL) { - fsPtr->freeInternalRepProc(retCd); + FsUpdateCwd(NULL, NULL); } - } else { - /* - * The pathname of the current directory is not the same as - * this thread's local cached value. Replace the local value. - */ - FsUpdateCwd(norm, retCd); - Tcl_DecrRefCount(norm); } } - Tcl_DecrRefCount(retVal); } cdDidNotChange: @@ -2844,19 +2890,17 @@ Tcl_FSGetCwd( * * Tcl_FSChdir -- * - * Replaces the library version of chdir(). + * This function replaces the library version of chdir(). * - * Calls 'chdirProc' of the filesystem that corresponds to the given - * pathname. + * The path is normalized and then passed to the filesystem which claims + * it. * * Results: - * See chdir() documentation. + * See chdir() documentation. If successful, we keep a record of the + * successful path in cwdPathPtr for subsequent calls to getcwd. * * Side effects: - * See chdir() documentation. - * - * On success stores in cwdPathPtr the pathname of the new current - * directory. + * See chdir() documentation. The global cwdPathPtr may change value. * *---------------------------------------------------------------------- */ @@ -2865,13 +2909,9 @@ int Tcl_FSChdir( Tcl_Obj *pathPtr) { - const Tcl_Filesystem *fsPtr, *oldFsPtr = NULL; - ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); + const Tcl_Filesystem *fsPtr; int retVal = -1; - if (tsdPtr->cwdPathPtr != NULL) { - oldFsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr); - } if (Tcl_FSGetNormalizedPath(NULL, pathPtr) == NULL) { Tcl_SetErrno(ENOENT); return retVal; @@ -2879,48 +2919,75 @@ Tcl_FSChdir( fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL) { - if (fsPtr->chdirProc != NULL) { + Tcl_FSChdirProc *proc = fsPtr->chdirProc; + if (proc != NULL) { /* - * If this fails Tcl_SetErrno() has already been called. + * If this fails, an appropriate errno will have been stored using + * 'Tcl_SetErrno()'. */ - retVal = fsPtr->chdirProc(pathPtr); + retVal = (*proc)(pathPtr); } else { /* - * Fallback to stat-based implementation. + * Fallback on stat-based implementation. */ Tcl_StatBuf buf; + /* + * If the file can be stat'ed and is a directory and is readable, + * then we can chdir. If any of these actions fail, then + * 'Tcl_SetErrno()' should automatically have been called to set + * an appropriate error code + */ + if ((Tcl_FSStat(pathPtr, &buf) == 0) && (S_ISDIR(buf.st_mode)) && (Tcl_FSAccess(pathPtr, R_OK) == 0)) { /* - * stat was successful, and the file is a directory and is - * readable. Can proceed to change the current directory. + * We allow the chdir. */ retVal = 0; - } else { - /* - * 'Tcl_SetErrno()' has already been called. - */ } } } else { Tcl_SetErrno(ENOENT); } - if (retVal == 0) { + /* + * The cwd changed, or an error was thrown. If an error was thrown, we can + * just continue (and that will report the error to the user). If there + * was no error we must assume that the cwd was actually changed to the + * normalized value we calculated above, and we must therefore cache that + * information. + */ - /* Assume that the cwd was actually changed to the normalized value - * just calculated, and cache that information. */ + /* + * If the filesystem in question has a getCwdProc, then the correct logic + * which performs the part below is already part of the Tcl_FSGetCwd() + * call, so no need to replicate it again. This will have a side effect + * though. The private authoritative representation of the current working + * directory stored in cwdPathPtr in static memory will be out-of-sync + * with the real OS-maintained value. The first call to Tcl_FSGetCwd will + * however recalculate the private copy to match the OS-value so + * everything will work right. + * + * However, if there is no getCwdProc, then we _must_ update our private + * storage of the cwd, since this is the only opportunity to do that! + * + * Note: We currently call this block of code irrespective of whether + * there was a getCwdProc or not, but the code should all in principle + * work if we only call this block if fsPtr->getCwdProc == NULL. + */ + if (retVal == 0) { /* - * If the filesystem epoch changed recently, the normalized pathname or - * its internal handle may be different from what was found above. - * This can easily be the case with scripted documents . Therefore get - * the normalized pathname again. The correct value will have been - * cached as a result of the Tcl_FSGetFileSystemForPath call, above. + * Note that this normalized path may be different to what we found + * above (or at least a different object), if the filesystem epoch + * changed recently. This can actually happen with scripted documents + * very easily. Therefore we ask for the normalized path again (the + * correct value will have been cached as a result of the + * Tcl_FSGetFileSystemForPath call above anyway). */ Tcl_Obj *normDirName = Tcl_FSGetNormalizedPath(NULL, pathPtr); @@ -2932,60 +2999,37 @@ Tcl_FSChdir( } if (fsPtr == &tclNativeFilesystem) { - void *cd; - void *oldcd = tsdPtr->cwdClientData; - /* - * Assume that the native filesystem has a getCwdProc and that it - * is at version 2. + * For the native filesystem, we keep a cache of the native + * representation of the cwd. But, we want to do that for the + * exact format that is returned by 'getcwd' (so that we can later + * compare the two representations for equality), which might not + * be exactly the same char-string as the native representation of + * the fully normalized path (e.g. on Windows there's a + * forward-slash vs backslash difference). Hence we ask for this + * again here. On Unix it might actually be true that we always + * have the correct form in the native rep in which case we could + * simply use: + * cd = Tcl_FSGetNativePath(pathPtr); + * instead. This should be examined by someone on Unix. */ - TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); + ClientData cd; + ClientData oldcd = tsdPtr->cwdClientData; - cd = proc2(oldcd); - if (cd != oldcd) { - /* - * Call getCwdProc() and store the resulting internal handle to - * compare things with it later. This might might not be - * exactly the same string as that of the fully normalized - * pathname. For example, for the Windows internal handle the - * separator is the backslash character. On Unix it might well - * be true that the internal handle is the fully normalized - * pathname and one could simply use: - * cd = Tcl_FSGetNativePath(pathPtr); - * but this can't be guaranteed in the general case. In fact, - * the internal handle could be any value the filesystem - * decides to use to identify a node. - */ + /* + * Assumption we are using a filesystem version 2. + */ + TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2*)fsPtr->getCwdProc; + cd = (*proc2)(oldcd); + if (cd != oldcd) { FsUpdateCwd(normDirName, cd); } } else { - /* - * Tcl_FSGetCwd() synchronizes the file-global cwdPathPtr if - * needed. However, if there is no 'getCwdProc', cwdPathPtr must be - * updated right now because there won't be another chance. This - * block of code is currently executed whether or not the - * filesystem provides a getCwdProc, but it should in principle - * work to only call this block if fsPtr->getCwdProc == NULL. - */ - FsUpdateCwd(normDirName, NULL); } - - if (oldFsPtr != NULL && fsPtr != oldFsPtr) { - /* - * The filesystem of the current directory is not the same as the - * filesystem of the previous current directory. Invalidate All - * FsPath objects. - */ - Tcl_FSMountsChanged(NULL); - } - } else { - /* - * The current directory is now changed or an error occurred and an - * error message is now set. Just continue. - */ } return retVal; @@ -2996,17 +3040,25 @@ Tcl_FSChdir( * * Tcl_FSLoadFile -- * - * Loads a dynamic shared object by passing the given pathname unmodified - * to Tcl_LoadFile, and provides pointers to the functions named by 'sym1' - * and 'sym2', and another pointer to a function that unloads the object. + * Dynamically loads a binary code file into memory and returns the + * addresses of two functions within that file, if they are defined. The + * appropriate function for the filesystem to which pathPtr belongs will + * be called. + * + * Note that the native filesystem doesn't actually assume 'pathPtr' is a + * path. Rather it assumes pathPtr is either a path or just the name + * (tail) of a file which can be found somewhere in the environment's + * loadable path. This behaviour is not very compatible with virtual + * filesystems (and has other problems documented in the load man-page), + * so it is advised that full paths are always used. * * Results: - * A standard Tcl completion code. If an error occurs, sets the - * interpreter's result to an error message. + * A standard Tcl completion code. If an error occurs, an error message + * is left in the interp's result. * * Side effects: - * A dynamic shared object is loaded into memory. This may later be - * unloaded by passing the handlePtr to *unloadProcPtr. + * New code suddenly appears in memory. This may later be unloaded by + * passing the clientData to the unloadProc. * *---------------------------------------------------------------------- */ @@ -3014,192 +3066,224 @@ Tcl_FSChdir( int Tcl_FSLoadFile( Tcl_Interp *interp, /* Used for error reporting. */ - Tcl_Obj *pathPtr, /* Pathname of the file containing the dynamic shared object. - */ + Tcl_Obj *pathPtr, /* Name of the file containing the desired + * code. */ const char *sym1, const char *sym2, - /* Names of two functions to find in the - * dynamic shared object. */ - Tcl_LibraryInitProc **proc1Ptr, Tcl_LibraryInitProc **proc2Ptr, - /* Places to store pointers to the functions - * named by sym1 and sym2. */ - Tcl_LoadHandle *handlePtr, /* A place to store the token for the loaded - * object. Can be passed to + /* Names of two functions to look up in the + * file's symbol table. */ + Tcl_PackageInitProc **proc1Ptr, Tcl_PackageInitProc **proc2Ptr, + /* Where to return the addresses corresponding + * to sym1 and sym2. */ + Tcl_LoadHandle *handlePtr, /* Filled with token for dynamically loaded + * file which will be passed back to * (*unloadProcPtr)() to unload the file. */ - TCL_UNUSED(Tcl_FSUnloadFileProc **)) + Tcl_FSUnloadFileProc **unloadProcPtr) + /* Filled with address of Tcl_FSUnloadFileProc + * function which should be used for this + * file. */ { - const char *symbols[3]; - void *procPtrs[2]; + const char *symbols[2]; + Tcl_PackageInitProc **procPtrs[2]; + ClientData clientData; int res; + /* + * Initialize the arrays. + */ + symbols[0] = sym1; symbols[1] = sym2; - symbols[2] = NULL; + procPtrs[0] = proc1Ptr; + procPtrs[1] = proc2Ptr; - res = Tcl_LoadFile(interp, pathPtr, symbols, 0, procPtrs, handlePtr); - if (res == TCL_OK) { - *proc1Ptr = (Tcl_LibraryInitProc *) procPtrs[0]; - *proc2Ptr = (Tcl_LibraryInitProc *) procPtrs[1]; - } else { - *proc1Ptr = *proc2Ptr = NULL; - } + /* + * Perform the load. + */ + + res = TclLoadFile(interp, pathPtr, 2, symbols, procPtrs, handlePtr, + &clientData, unloadProcPtr); + + /* + * Due to an unfortunate mis-design in Tcl 8.4 fs, when loading a shared + * library, we don't keep the loadHandle (for TclpFindSymbol) and the + * clientData (for the unloadProc) separately. In fact we effectively + * throw away the loadHandle and only use the clientData. It just so + * happens, for the native filesystem only, that these two are identical. + * + * This also means that the signatures Tcl_FSUnloadFileProc and + * Tcl_FSLoadFileProc are both misleading. + */ + *handlePtr = (Tcl_LoadHandle) clientData; return res; } /* *---------------------------------------------------------------------- * - * Tcl_LoadFile -- + * TclLoadFile -- * - * Load a dynamic shared object by calling 'loadFileProc' of the - * filesystem corresponding to the given pathname, and then finds within - * the loaded object the functions named in symbols[]. + * Dynamically loads a binary code file into memory and returns the + * addresses of a number of given functions within that file, if they are + * defined. The appropriate function for the filesystem to which pathPtr + * belongs will be called. * - * The given pathname is passed unmodified to `loadFileProc`, which - * decides how to resolve it. On POSIX systems the native filesystem - * passes the given pathname to dlopen(), which resolves the filename - * according to its own set of rules. This behaviour is not very - * compatible with virtual filesystems, and has other problems as - * documented for [load], so it is recommended to use an absolute - * pathname. + * Note that the native filesystem doesn't actually assume 'pathPtr' is a + * path. Rather it assumes pathPtr is either a path or just the name + * (tail) of a file which can be found somewhere in the environment's + * loadable path. This behaviour is not very compatible with virtual + * filesystems (and has other problems documented in the load man-page), + * so it is advised that full paths are always used. + * + * This function is currently private to Tcl. It may be exported in the + * future and its interface fixed (but we should clean up the + * loadHandle/clientData confusion at that time -- see the above comments + * in Tcl_FSLoadFile for details). For a public function, see + * Tcl_FSLoadFile. * * Results: - * A standard Tcl completion code. If an error occurs, sets the - * interpreter result to an error message. + * A standard Tcl completion code. If an error occurs, an error message + * is left in the interp's result. * * Side effects: - * Memory is allocated for the new object. May be freed by calling - * TclFS_UnloadFile. + * New code suddenly appears in memory. This may later be unloaded by + * passing the clientData to the unloadProc. * *---------------------------------------------------------------------- */ +typedef int (Tcl_FSLoadFileProc2) (Tcl_Interp *interp, Tcl_Obj *pathPtr, + Tcl_LoadHandle *handlePtr, Tcl_FSUnloadFileProc **unloadProcPtr, int flags); + /* - * Modern HPUX allows the unlink (no ETXTBSY error) yet somehow trashes some - * internal data structures, preventing any additional dynamic shared objects - * from getting properly loaded. Only the first is ok. Work around the issue - * by not unlinking, i.e., emulating the behaviour of the older HPUX which - * denied removal. + * Workaround for issue with modern HPUX which do allow the unlink (no ETXTBSY + * error) yet somehow trash some internal data structures which prevents the + * second and further shared libraries from getting properly loaded. Only the + * first is ok. We try to get around the issue by not unlinking, + * i.e. emulating the behaviour of the older HPUX which denied removal. * * Doing the unlink is also an issue within docker containers, whose AUFS * bungles this as well, see * https://github.com/dotcloud/docker/issues/1911 * + * For these situations the change below makes the execution of the unlink + * semi-controllable at runtime. + * + * An AUFS filesystem (if it can be detected) will force avoidance of + * unlink. The env variable TCL_TEMPLOAD_NO_UNLINK allows detection of a + * users general request (unlink and not. + * + * By default the unlink is done (if not in AUFS). However if the variable is + * present and set to true (any integer > 0) then the unlink is skipped. */ -#ifdef _WIN32 -#define getenv(x) _wgetenv(L##x) -#define atoi(x) _wtoi(x) -#else -#define WCHAR char -#endif - -static int -skipUnlink( - Tcl_Obj *shlibFile) +int +TclSkipUnlink (Tcl_Obj* shlibFile) { - /* - * Unlinking is not performed in the following cases: + /* Order of testing: + * 1. On hpux we generally want to skip unlink in general * - * 1. The operating system is HPUX. - * - * 2. If the environment variable TCL_TEMPLOAD_NO_UNLINK is present and - * set to true (an integer > 0) - * - * 3. TCL_TEMPLOAD_NO_UNLINK is not true (an integer > 0) and AUFS filesystem can be detected (using statfs, if available). + * Outside of hpux then: + * 2. For a general user request (TCL_TEMPLOAD_NO_UNLINK present, non-empty, => int) + * 3. For general AUFS environment (statfs, if available). * + * Ad 2: This variable can disable/override the AUFS detection, i.e. for + * testing if a newer AUFS does not have the bug any more. + * + * Ad 3: This is conditionally compiled in. Condition currently must be set manually. + * This part needs proper tests in the configure(.in). */ - #ifdef hpux - (void)shlibFile; return 1; #else - WCHAR *skipstr = getenv("TCL_TEMPLOAD_NO_UNLINK"); + char* skipstr; + skipstr = getenv ("TCL_TEMPLOAD_NO_UNLINK"); if (skipstr && (skipstr[0] != '\0')) { return atoi(skipstr); } -#ifndef TCL_TEMPLOAD_NO_UNLINK - (void)shlibFile; -#else -/* At built time TCL_TEMPLOAD_NO_UNLINK can be set manually to control whether - * this automatic overriding of unlink is included. - */ +#ifdef TCL_TEMPLOAD_NO_UNLINK #ifndef NO_FSTATFS { struct statfs fs; - /* - * Have fstatfs. May not have the AUFS super magic ... Indeed our build + /* Have fstatfs. May not have the AUFS super magic ... Indeed our build * box is too old to have it directly in the headers. Define taken from * http://mooon.googlecode.com/svn/trunk/linux_include/linux/aufs_type.h * http://aufs.sourceforge.net/ - * Better reference will be gladly accepted. + * Better reference will be gladly taken. */ #ifndef AUFS_SUPER_MAGIC -/* AUFS_SUPER_MAGIC can disable/override the AUFS detection, i.e. for - * testing if a newer AUFS does not have the bug any more. -*/ #define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's') #endif /* AUFS_SUPER_MAGIC */ - if ((statfs(Tcl_GetString(shlibFile), &fs) == 0) - && (fs.f_type == AUFS_SUPER_MAGIC)) { + if ((statfs(Tcl_GetString (shlibFile), &fs) == 0) && + (fs.f_type == AUFS_SUPER_MAGIC)) { return 1; } } #endif /* ... NO_FSTATFS */ #endif /* ... TCL_TEMPLOAD_NO_UNLINK */ - /* - * No HPUX, environment variable override, or AUFS detected. Perform - * unlink. - */ + /* Fallback: !hpux, no EV override, no AUFS (detection, nor detected): + * Don't skip */ return 0; #endif /* hpux */ } int -Tcl_LoadFile( +TclLoadFile( Tcl_Interp *interp, /* Used for error reporting. */ - Tcl_Obj *pathPtr, /* Pathname of the file containing the dynamic - * shared object. */ - const char *const symbols[],/* A null-terminated array of names of - * functions to find in the loaded object. */ - int flags, /* Flags */ - void *procVPtrs, /* A place to store pointers to the functions - * named by symbols[]. */ - Tcl_LoadHandle *handlePtr) /* A place to hold a token for the loaded object. - * Can be used by TclpFindSymbol. */ + Tcl_Obj *pathPtr, /* Name of the file containing the desired + * code. */ + int symc, /* Number of symbols/procPtrs in the next two + * arrays. */ + const char *symbols[], /* Names of functions to look up in the file's + * symbol table. */ + Tcl_PackageInitProc **procPtrs[], + /* Where to return the addresses corresponding + * to symbols[]. */ + Tcl_LoadHandle *handlePtr, /* Filled with token for shared library + * information which can be used in + * TclpFindSymbol. */ + ClientData *clientDataPtr, /* Filled with token for dynamically loaded + * file which will be passed back to + * (*unloadProcPtr)() to unload the file. */ + Tcl_FSUnloadFileProc **unloadProcPtr) + /* Filled with address of Tcl_FSUnloadFileProc + * function which should be used for this + * file. */ { - void **procPtrs = (void **) procVPtrs; const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - const Tcl_Filesystem *copyFsPtr; - Tcl_FSUnloadFileProc *unloadProcPtr; + Tcl_FSLoadFileProc *proc; + Tcl_Filesystem *copyFsPtr; Tcl_Obj *copyToPtr; Tcl_LoadHandle newLoadHandle = NULL; - Tcl_LoadHandle divertedLoadHandle = NULL; + ClientData newClientData = NULL; Tcl_FSUnloadFileProc *newUnloadProcPtr = NULL; FsDivertLoad *tvdlPtr; int retVal; - int i; if (fsPtr == NULL) { Tcl_SetErrno(ENOENT); return TCL_ERROR; } - if (fsPtr->loadFileProc != NULL) { - retVal = ((Tcl_FSLoadFileProc2 *)(void *)(fsPtr->loadFileProc)) - (interp, pathPtr, handlePtr, &unloadProcPtr, flags); - + proc = fsPtr->loadFileProc; + if (proc != NULL) { + int retVal = ((Tcl_FSLoadFileProc2 *)proc) + (interp, pathPtr, handlePtr, unloadProcPtr, 0); if (retVal == TCL_OK) { if (*handlePtr == NULL) { return TCL_ERROR; } - if (interp) { - Tcl_ResetResult(interp); - } + + /* + * Copy this across, since both are equal for the native fs. + */ + + *clientDataPtr = (ClientData)*handlePtr; + Tcl_ResetResult(interp); goto resolveSymbols; } if (Tcl_GetErrno() != EXDEV) { @@ -3208,27 +3292,23 @@ Tcl_LoadFile( } /* - * The filesystem doesn't support 'load'. Fall to the following: - */ - - /* - * Make sure the file is accessible. + * The filesystem doesn't support 'load', so we fall back on the following + * technique: + * + * First check if it is readable -- and exists! */ if (Tcl_FSAccess(pathPtr, R_OK) != 0) { - if (interp) { - Tcl_SetObjResult(interp, Tcl_ObjPrintf( - "couldn't load library \"%s\": %s", - Tcl_GetString(pathPtr), Tcl_PosixError(interp))); - } + Tcl_AppendResult(interp, "couldn't load library \"", + Tcl_GetString(pathPtr), "\": ", Tcl_PosixError(interp), NULL); return TCL_ERROR; } #ifdef TCL_LOAD_FROM_MEMORY /* - * The platform supports loading a dynamic shared object from memory. - * Create a sufficiently large buffer, read the file into it, and then load - * the dynamic shared object from the buffer: + * The platform supports loading code from memory, so ask for a buffer of + * the appropriate size, read the file into it and load the code from the + * buffer: */ { @@ -3244,7 +3324,7 @@ Tcl_LoadFile( size = (int) statBuf.st_size; /* - * Tcl_Read takes an int: Determine whether the file size is wide. + * Tcl_Read takes an int: check that file size isn't wide. */ if (size != (Tcl_WideInt) statBuf.st_size) { @@ -3259,27 +3339,29 @@ Tcl_LoadFile( Tcl_Close(interp, data); goto mustCopyToTempAnyway; } - ret = Tcl_Read(data, (char *)buffer, size); + ret = Tcl_Read(data, buffer, size); Tcl_Close(interp, data); ret = TclpLoadMemory(interp, buffer, size, ret, handlePtr, - &unloadProcPtr, flags); + unloadProcPtr); if (ret == TCL_OK && *handlePtr != NULL) { + *clientDataPtr = (ClientData) *handlePtr; goto resolveSymbols; } } mustCopyToTempAnyway: - if (interp) { - Tcl_ResetResult(interp); - } -#endif /* TCL_LOAD_FROM_MEMORY */ + Tcl_ResetResult(interp); +#endif /* - * Get a temporary filename, first to copy the file into, and then to load. + * Get a temporary filename to use, first to copy the file into, and then + * to load. */ - copyToPtr = TclpTempFileNameForLibrary(interp, pathPtr); + copyToPtr = TclpTempFileName(); if (copyToPtr == NULL) { + Tcl_AppendResult(interp, "couldn't create temporary file: ", + Tcl_PosixError(interp), NULL); return TCL_ERROR; } Tcl_IncrRefCount(copyToPtr); @@ -3287,35 +3369,33 @@ Tcl_LoadFile( copyFsPtr = Tcl_FSGetFileSystemForPath(copyToPtr); if ((copyFsPtr == NULL) || (copyFsPtr == fsPtr)) { /* - * Tcl_FSLoadFile isn't available for the filesystem of the temporary - * file. In order to avoid a possible infinite loop, do not attempt to - * load further. + * We already know we can't use Tcl_FSLoadFile from this filesystem, + * and we must avoid a possible infinite loop. Try to delete the file + * we probably created, and then exit. */ - /* - * Try to delete the file we probably created and then exit. - */ - Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); - if (interp) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "couldn't load from current filesystem", -1)); - } + Tcl_AppendResult(interp, "couldn't load from current filesystem",NULL); return TCL_ERROR; } if (TclCrossFilesystemCopy(interp, pathPtr, copyToPtr) != TCL_OK) { + /* + * Cross-platform copy failed. + */ + Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); return TCL_ERROR; } -#ifndef _WIN32 +#if !defined(__WIN32__) /* - * It might be necessary on some systems to set the appropriate permissions - * on the file. On Unix we could loop over the file attributes and set any - * that are called "-permissions" to 0700, but just do it directly instead: + * Do we need to set appropriate permissions on the file? This may be + * required on some systems. On Unix we could loop over the file + * attributes, and set any that are called "-permissions" to 0700. However + * we just do this directly, like this: */ { @@ -3332,80 +3412,90 @@ Tcl_LoadFile( #endif /* - * The cross-filesystem copy may have stored the number of bytes in the - * result, so reset the result now. + * We need to reset the result now, because the cross-filesystem copy may + * have stored the number of bytes in the result. */ - if (interp) { - Tcl_ResetResult(interp); - } + Tcl_ResetResult(interp); - retVal = Tcl_LoadFile(interp, copyToPtr, symbols, flags, procPtrs, - &newLoadHandle); + retVal = TclLoadFile(interp, copyToPtr, symc, symbols, procPtrs, + &newLoadHandle, &newClientData, &newUnloadProcPtr); if (retVal != TCL_OK) { + /* + * The file didn't load successfully. + */ + Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); return retVal; } /* - * Try to delete the file immediately. Some operatings systems allow this, - * and it avoids leaving the copy laying around after exit. + * Try to delete the file immediately - this is possible in some OSes, and + * avoids any worries about leaving the copy laying around on exit. */ - if (!skipUnlink(copyToPtr) && - (Tcl_FSDeleteFile(copyToPtr) == TCL_OK)) { + if ( + !TclSkipUnlink (copyToPtr) && + (Tcl_FSDeleteFile(copyToPtr) == TCL_OK)) { Tcl_DecrRefCount(copyToPtr); /* - * Tell the caller all the details: The package list maintained by - * 'load' stores the original (vfs) pathname, the handle of object - * loaded from the temporary file, and the unloadProcPtr. + * We tell our caller about the real shared library which was loaded. + * Note that this does mean that the package list maintained by 'load' + * will store the original (vfs) path alongside the temporary load + * handle and unload proc ptr. */ - *handlePtr = newLoadHandle; - if (interp) { - Tcl_ResetResult(interp); - } + (*handlePtr) = newLoadHandle; + (*clientDataPtr) = newClientData; + (*unloadProcPtr) = newUnloadProcPtr; + Tcl_ResetResult(interp); return TCL_OK; } /* - * Divert the unloading in order to unload and cleanup the temporary file. + * When we unload this file, we need to divert the unloading so we can + * unload and cleanup the temporary file correctly. */ - tvdlPtr = (FsDivertLoad *)ckalloc(sizeof(FsDivertLoad)); + tvdlPtr = (FsDivertLoad *) ckalloc(sizeof(FsDivertLoad)); /* - * Remember three pieces of information in order to clean up the diverted - * load completely on platforms which allow proper unloading of code. + * Remember three pieces of information. This allows us to cleanup the + * diverted load completely, on platforms which allow proper unloading of + * code. */ tvdlPtr->loadHandle = newLoadHandle; tvdlPtr->unloadProcPtr = newUnloadProcPtr; if (copyFsPtr != &tclNativeFilesystem) { - /* refCount of copyToPtr is already incremented. */ + /* + * copyToPtr is already incremented for this reference. + */ + tvdlPtr->divertedFile = copyToPtr; /* - * This is the filesystem for the temporary file the object was loaded - * from. A reference to copyToPtr is already stored in - * tvdlPtr->divertedFile, so need need to increment the refCount again. + * This is the filesystem we loaded it into. Since we have a reference + * to 'copyToPtr', we already have a refCount on this filesystem, so + * we don't need to worry about it disappearing on us. */ tvdlPtr->divertedFilesystem = copyFsPtr; tvdlPtr->divertedFileNativeRep = NULL; } else { /* - * Grab the native representation. + * We need the native rep. */ tvdlPtr->divertedFileNativeRep = TclNativeDupInternalRep( Tcl_FSGetInternalRep(copyToPtr, copyFsPtr)); /* - * Don't keeep a reference to the Tcl_Obj or the native filesystem. + * We don't need or want references to the copied Tcl_Obj or the + * filesystem if it is the native one. */ tvdlPtr->divertedFile = NULL; @@ -3414,37 +3504,20 @@ Tcl_LoadFile( } copyToPtr = NULL; + (*handlePtr) = newLoadHandle; + (*clientDataPtr) = (ClientData) tvdlPtr; + (*unloadProcPtr) = TclFSUnloadTempFile; - divertedLoadHandle = (Tcl_LoadHandle)ckalloc(sizeof(struct Tcl_LoadHandle_)); - divertedLoadHandle->clientData = tvdlPtr; - divertedLoadHandle->findSymbolProcPtr = DivertFindSymbol; - divertedLoadHandle->unloadFileProcPtr = DivertUnloadFile; - *handlePtr = divertedLoadHandle; - - if (interp) { - Tcl_ResetResult(interp); - } + Tcl_ResetResult(interp); return retVal; resolveSymbols: - /* - * handlePtr now contains a token for the loaded object. - * Resolve the symbols. - */ - - if (symbols != NULL) { - for (i=0 ; symbols[i] != NULL; i++) { - procPtrs[i] = Tcl_FindSymbol(interp, *handlePtr, symbols[i]); - if (procPtrs[i] == NULL) { - /* - * At least one symbol in the list was not found. Unload the - * file and return an error code. Tcl_FindSymbol should have - * already left an appropriate error message. - */ + { + int i; - (*handlePtr)->unloadFileProcPtr(*handlePtr); - *handlePtr = NULL; - return TCL_ERROR; + for (i=0 ; i<symc ; i++) { + if (symbols[i] != NULL) { + *procPtrs[i] = TclpFindSymbol(interp, *handlePtr, symbols[i]); } } } @@ -3452,164 +3525,97 @@ Tcl_LoadFile( } /* - *---------------------------------------------------------------------- - * - * DivertFindSymbol -- + *--------------------------------------------------------------------------- * - * Find a symbol in a shared library loaded by making a copying a file - * from the virtual filesystem to a native filesystem. + * TclFSUnloadTempFile -- * - *---------------------------------------------------------------------- - */ - -static void * -DivertFindSymbol( - Tcl_Interp *interp, /* The relevant interpreter. */ - Tcl_LoadHandle loadHandle, /* A handle to the diverted module. */ - const char *symbol) /* The name of symbol to resolve. */ -{ - FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData; - Tcl_LoadHandle originalHandle = tvdlPtr->loadHandle; - - return originalHandle->findSymbolProcPtr(interp, originalHandle, symbol); -} - -/* - *---------------------------------------------------------------------- + * This function is called when we loaded a library of code via an + * intermediate temporary file. This function ensures the library is + * correctly unloaded and the temporary file is correctly deleted. * - * DivertUnloadFile -- + * Results: + * None. * - * Unloads an object that was loaded from a temporary file copied from the - * virtual filesystem the native filesystem. + * Side effects: + * The effects of the 'unload' function called, and of course the + * temporary file will be deleted. * - *---------------------------------------------------------------------- + *--------------------------------------------------------------------------- */ -static void -DivertUnloadFile( - Tcl_LoadHandle loadHandle) /* A handle for the loaded object. */ +void +TclFSUnloadTempFile( + Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to + * Tcl_FSLoadFile(). The loadHandle is a token + * that represents the loaded file. */ { - FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData; - Tcl_LoadHandle originalHandle; + FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle; - if (tvdlPtr == NULL) { - /* - * tvdlPtr was provided by Tcl_LoadFile so it should not be NULL here. - */ + /* + * This test should never trigger, since we give the client data in the + * function above. + */ + if (tvdlPtr == NULL) { return; } - originalHandle = tvdlPtr->loadHandle; /* - * Call the real 'unloadfile' proc. This must be called first so that the - * shared library is actually unloaded by the OS. Otherwise, the following - * 'delete' may fail because the shared library is still in use. + * Call the real 'unloadfile' proc we actually used. It is very important + * that we call this first, so that the shared library is actually + * unloaded by the OS. Otherwise, the following 'delete' may well fail + * because the shared library is still in use. */ - originalHandle->unloadFileProcPtr(originalHandle); - - /* - * Determine which filesystem contains the temporary copy of the file. - */ + if (tvdlPtr->unloadProcPtr != NULL) { + (*tvdlPtr->unloadProcPtr)(tvdlPtr->loadHandle); + } if (tvdlPtr->divertedFilesystem == NULL) { /* - * Use the function for the native filsystem, which works works even at - * this late stage. + * It was the native filesystem, and we have a special function + * available just for this purpose, which we know works even at this + * late stage. */ TclpDeleteFile(tvdlPtr->divertedFileNativeRep); NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep); + } else { /* - * Remove the temporary file. If encodings have been cleaned up - * already, this may crash. + * Remove the temporary file we created. Note, we may crash here + * because encodings have been taken down already. */ if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile) != TCL_OK) { /* - * This may have happened because Tcl is exiting, and encodings may - * have already been deleted or something else the filesystem - * depends on may be gone. + * The above may have failed because the filesystem, or something + * it depends upon (e.g. encodings) have been taken down because + * Tcl is exiting. * - * TO DO: Figure out how to delete this file more robustly, or - * give the filesystem the information it needs to delete the file - * more robustly. One problem might be that the filesystem cannot - * extract the information it needs from the above pathname object + * We may need to work out how to delete this file more robustly + * (or give the filesystem the information it needs to delete the + * file more robustly). + * + * In particular, one problem might be that the filesystem cannot + * extract the information it needs from the above path object * because Tcl's entire filesystem apparatus (the code in this - * file) has been finalized and there is no way to get the native - * handle of the file. + * file) has been finalized, and it refuses to pass the internal + * representation to the filesystem. */ } /* - * This also decrements the refCount of the Tcl_Filesystem - * corresponding to this file. which might cause the filesystem to be - * deallocated if Tcl is exiting. + * And free up the allocations. This will also of course remove a + * refCount from the Tcl_Filesystem to which this file belongs, which + * could then free up the filesystem if we are exiting. */ Tcl_DecrRefCount(tvdlPtr->divertedFile); } - ckfree(tvdlPtr); - ckfree(loadHandle); -} - -/* - *---------------------------------------------------------------------- - * - * Tcl_FindSymbol -- - * - * Find a symbol in a loaded object. - * - * Previously filesystem-specific, but has been made portable by having - * TclpDlopen return a structure that includes procedure pointers. - * - * Results: - * Returns a pointer to the symbol if found. Otherwise, sets - * an error message in the interpreter result and returns NULL. - * - *---------------------------------------------------------------------- - */ - -void * -Tcl_FindSymbol( - Tcl_Interp *interp, /* The relevant interpreter. */ - Tcl_LoadHandle loadHandle, /* A handle for the loaded object. */ - const char *symbol) /* The name name of the symbol to resolve. */ -{ - return loadHandle->findSymbolProcPtr(interp, loadHandle, symbol); -} - -/* - *---------------------------------------------------------------------- - * - * Tcl_FSUnloadFile -- - * - * Unloads a loaded object if unloading is supported for the object. - * - *---------------------------------------------------------------------- - */ - -int -Tcl_FSUnloadFile( - Tcl_Interp *interp, /* The relevant interpreter. */ - Tcl_LoadHandle handle) /* A handle for the object to unload. */ -{ - if (handle->unloadFileProcPtr == NULL) { - if (interp != NULL) { - Tcl_SetObjResult(interp, Tcl_NewStringObj( - "cannot unload: filesystem does not support unloading", - -1)); - } - return TCL_ERROR; - } - if (handle->unloadFileProcPtr != NULL) { - handle->unloadFileProcPtr(handle); - } - return TCL_OK; + ckfree((char*)tvdlPtr); } /* @@ -3617,63 +3623,60 @@ Tcl_FSUnloadFile( * * Tcl_FSLink -- * - * Creates or inspects a link by calling 'linkProc' of the filesystem - * corresponding to the given pathname. Replaces the library version of - * readlink(). + * This function replaces the library version of readlink() and can also + * be used to make links. The appropriate function for the filesystem to + * which pathPtr belongs will be called. * * Results: - * If toPtr is NULL, a Tcl_Obj containing the value the symbolic link for - * 'pathPtr', or NULL if a symbolic link was not accessible. The caller - * should Tcl_DecrRefCount on the result to release it. Otherwise NULL. + * If toPtr is NULL, then the result is a Tcl_Obj specifying the contents + * of the symbolic link given by 'pathPtr', or NULL if the symbolic link + * could not be read. The result is owned by the caller, which should + * call Tcl_DecrRefCount when the result is no longer needed. * - * In this case the result has no additional reference count and need not - * be freed. The actual action to perform is given by the 'linkAction' - * flags, which is a combination of: + * If toPtr is non-NULL, then the result is toPtr if the link action was + * successful, or NULL if not. In this case the result has no additional + * reference count, and need not be freed. The actual action to perform + * is given by the 'linkAction' flags, which is an or'd combination of: * * TCL_CREATE_SYMBOLIC_LINK * TCL_CREATE_HARD_LINK * - * Most filesystems do not support linking across to different - * filesystems, so this function usually fails if the filesystem - * corresponding to toPtr is not the same as the filesystem corresponding - * to pathPtr. + * Note that most filesystems will not support linking across to + * different filesystems, so this function will usually fail unless toPtr + * is in the same FS as pathPtr. * * Side effects: - * Creates or sets a link if toPtr is not NULL. - * - * See readlink(). + * See readlink() documentation. A new filesystem link object may appear. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSLink( - Tcl_Obj *pathPtr, /* Pathaname of file. */ - Tcl_Obj *toPtr, /* - * NULL or the pathname of a file to link to. - */ - int linkAction) /* Action to perform. */ + Tcl_Obj *pathPtr, /* Path of file to readlink or link */ + Tcl_Obj *toPtr, /* NULL or path to be linked to */ + int linkAction) /* Action to perform */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - if (fsPtr) { - if (fsPtr->linkProc == NULL) { - Tcl_SetErrno(ENOTSUP); - return NULL; - } else { - return fsPtr->linkProc(pathPtr, toPtr, linkAction); + if (fsPtr != NULL) { + Tcl_FSLinkProc *proc = fsPtr->linkProc; + + if (proc != NULL) { + return (*proc)(pathPtr, toPtr, linkAction); } } /* - * If S_IFLNK isn't defined the machine doesn't support symbolic links, so - * the file can't possibly be a symbolic link. Generate an EINVAL error, - * which is what happens on machines that do support symbolic links when - * readlink is called for a file that isn't a symbolic link. + * If S_IFLNK isn't defined it means that the machine doesn't support + * symbolic links, so the file can't possibly be a symbolic link. Generate + * an EINVAL error, which is what happens on machines that do support + * symbolic links when you invoke readlink on a file that isn't a symbolic + * link. */ #ifndef S_IFLNK - errno = EINVAL; /* TODO: Change to Tcl_SetErrno()? */ + errno = EINVAL; #else Tcl_SetErrno(ENOENT); #endif /* S_IFLNK */ @@ -3685,9 +3688,16 @@ Tcl_FSLink( * * Tcl_FSListVolumes -- * - * Lists the currently mounted volumes by calling `listVolumesProc` of - * each registered filesystem, and combining the results to form a list of - * volumes. + * Lists the currently mounted volumes. The chain of functions that have + * been "inserted" into the filesystem will be called in succession; each + * may return a list of volumes, all of which are added to the result + * until all mounted file systems are listed. + * + * Notice that we assume the lists returned by each filesystem (if non + * NULL) have been given a refCount for us already. However, we are NOT + * allowed to hang on to the list itself (it belongs to the filesystem we + * called). Therefore we quite naturally add its contents to the result + * we are building, and then decrement the refCount. * * Results: * The list of volumes, in an object which has refCount 0. @@ -3698,33 +3708,27 @@ Tcl_FSLink( *--------------------------------------------------------------------------- */ -Tcl_Obj * +Tcl_Obj* Tcl_FSListVolumes(void) { FilesystemRecord *fsRecPtr; - Tcl_Obj *resultPtr; + Tcl_Obj *resultPtr = Tcl_NewObj(); /* - * Call each "listVolumes" function of each registered filesystem in - * succession. A non-NULL return value indicates the particular function - * has succeeded. + * Call each of the "listVolumes" function in succession. A non-NULL + * return value indicates the particular function has succeeded. We call + * all the functions registered, since we want a list of all drives from + * all filesystems. */ - TclNewObj(resultPtr); fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { - if (fsRecPtr->fsPtr->listVolumesProc != NULL) { - Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc(); - + Tcl_FSListVolumesProc *proc = fsRecPtr->fsPtr->listVolumesProc; + if (proc != NULL) { + Tcl_Obj *thisFsVolumes = (*proc)(); if (thisFsVolumes != NULL) { Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes); - /* - * The refCount of each list returned by a `listVolumesProc` - * is already incremented. Do not hang onto the list, though. - * It belongs to the filesystem. Add its contents to the - * result we are building, and then decrement the refCount. - */ Tcl_DecrRefCount(thisFsVolumes); } } @@ -3740,21 +3744,22 @@ Tcl_FSListVolumes(void) * * FsListMounts -- * - * Lists the mounts mathing the given pattern in the given directory. + * List all mounts within the given directory, which match the given + * pattern. * * Results: - * A list, having a refCount of 0, of the matching mounts, or NULL if no - * search was performed because no filesystem provided a search routine. + * The list of mounts, in a list object which has refCount 0, or NULL if + * we didn't even find any filesystems to try to list mounts. * * Side effects: - * None. + * None * *--------------------------------------------------------------------------- */ static Tcl_Obj * FsListMounts( - Tcl_Obj *pathPtr, /* Pathname of directory to search. */ + Tcl_Obj *pathPtr, /* Contains path to directory to search. */ const char *pattern) /* Pattern to match against. */ { FilesystemRecord *fsRecPtr; @@ -3762,20 +3767,24 @@ FsListMounts( Tcl_Obj *resultPtr = NULL; /* - * Call the matchInDirectory function of each registered filesystem, - * passing it 'mountsOnly'. Results accumulate in resultPtr. + * Call each of the "matchInDirectory" functions in succession, with the + * specific type information 'mountsOnly'. A non-NULL return value + * indicates the particular function has succeeded. We call all the + * functions registered, since we want a list from each filesystems. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { - if (fsRecPtr->fsPtr != &tclNativeFilesystem && - fsRecPtr->fsPtr->matchInDirectoryProc != NULL) { - if (resultPtr == NULL) { - TclNewObj(resultPtr); + if (fsRecPtr->fsPtr != &tclNativeFilesystem) { + Tcl_FSMatchInDirectoryProc *proc = + fsRecPtr->fsPtr->matchInDirectoryProc; + if (proc != NULL) { + if (resultPtr == NULL) { + resultPtr = Tcl_NewObj(); + } + (*proc)(NULL, resultPtr, pathPtr, pattern, &mountsOnly); } - fsRecPtr->fsPtr->matchInDirectoryProc(NULL, resultPtr, pathPtr, - pattern, &mountsOnly); } fsRecPtr = fsRecPtr->nextPtr; } @@ -3789,31 +3798,34 @@ FsListMounts( * * Tcl_FSSplitPath -- * - * Splits a pathname into its components. + * This function takes the given Tcl_Obj, which should be a valid path, + * and returns a Tcl List object containing each segment of that path as + * an element. * * Results: - * A list with refCount of zero. + * Returns list object with refCount of zero. If the passed in lenPtr is + * non-NULL, we use it to return the number of elements in the returned + * list. * * Side effects: - * If lenPtr is not null, sets it to the number of elements in the result. + * None. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSSplitPath( - Tcl_Obj *pathPtr, /* The pathname to split. */ - Tcl_Size *lenPtr) /* A place to hold the number of pathname - * elements. */ + Tcl_Obj *pathPtr, /* Path to split. */ + int *lenPtr) /* int to store number of path elements. */ { - Tcl_Obj *result = NULL; /* Just to squelch gcc warnings. */ - const Tcl_Filesystem *fsPtr; + Tcl_Obj *result = NULL; /* Needed only to prevent gcc warnings. */ + Tcl_Filesystem *fsPtr; char separator = '/'; - Tcl_Size driveNameLength; - const char *p; + int driveNameLength; + char *p; /* - * Perform platform-specific splitting. + * Perform platform specific splitting. */ if (TclFSGetPathType(pathPtr, &fsPtr, @@ -3825,11 +3837,12 @@ Tcl_FSSplitPath( return TclpNativeSplitPath(pathPtr, lenPtr); } - /* Assume each separator is a single character. */ + /* + * We assume separators are single characters. + */ if (fsPtr->filesystemSeparatorProc != NULL) { - Tcl_Obj *sep = fsPtr->filesystemSeparatorProc(pathPtr); - + Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(pathPtr); if (sep != NULL) { Tcl_IncrRefCount(sep); separator = Tcl_GetString(sep)[0]; @@ -3838,32 +3851,30 @@ Tcl_FSSplitPath( } /* - * Add the drive name as first element of the result. The drive name may - * contain strange characters like colons and sequences of forward slashes - * For example, 'ftp://' is a valid drive name. + * Place the drive name as first element of the result list. The drive + * name may contain strange characters, like colons and multiple forward + * slashes (for example 'ftp://' is a valid vfs drive name) */ - TclNewObj(result); + result = Tcl_NewObj(); p = Tcl_GetString(pathPtr); Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(p, driveNameLength)); p += driveNameLength; /* - * Add the remaining pathname elements to the list. + * Add the remaining path elements to the list. */ for (;;) { - const char *elementStart = p; - Tcl_Size length; - + char *elementStart = p; + int length; while ((*p != '\0') && (*p != separator)) { p++; } length = p - elementStart; if (length > 0) { Tcl_Obj *nextElt; - if (elementStart[0] == '~') { TclNewLiteralStringObj(nextElt, "./"); Tcl_AppendToObj(nextElt, elementStart, length); @@ -3877,46 +3888,57 @@ Tcl_FSSplitPath( } } + /* + * Compute the number of elements in the result. + */ + if (lenPtr != NULL) { TclListObjLength(NULL, result, lenPtr); } return result; } + /* *---------------------------------------------------------------------- * * TclGetPathType -- * - * Helper function used by TclFSGetPathType and TclJoinPath. + * Helper function used by FSGetPathType. * * Results: - * One of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or - * TCL_PATH_VOLUME_RELATIVE. + * 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: - * See **filesystemPtrptr, *driveNameLengthPtr and **driveNameRef, + * None. * *---------------------------------------------------------------------- */ Tcl_PathType TclGetPathType( - Tcl_Obj *pathPtr, /* Pathname to determine type of. */ - const Tcl_Filesystem **filesystemPtrPtr, - /* If not NULL, a place in which to store a - * pointer to the filesystem for this pathname - * if it is absolute. */ - Tcl_Size *driveNameLengthPtr, /* If not NULL, a place in which to store the - * length of the volume name. */ - Tcl_Obj **driveNameRef) /* If not NULL, for an absolute pathname, a - * place to store a pointer to an object with a - * refCount of 1, and whose value is the name - * of the volume. */ + Tcl_Obj *pathPtr, /* Path to determine type for */ + Tcl_Filesystem **filesystemPtrPtr, + /* If absolute path and this is not NULL, then + * set to the filesystem which claims this + * path. */ + int *driveNameLengthPtr, /* If the path is absolute, and this is + * non-NULL, then set to the length of the + * driveName. */ + Tcl_Obj **driveNameRef) /* If the path is absolute, and this is + * non-NULL, then set to the name of the + * drive, network-volume which contains the + * path, already with a refCount for the + * caller. */ { - Tcl_Size pathLen; - const char *path = TclGetStringFromObj(pathPtr, &pathLen); + int pathLen; + char *path; Tcl_PathType type; + path = Tcl_GetStringFromObj(pathPtr, &pathLen); + type = TclFSNonnativePathType(path, pathLen, filesystemPtrPtr, driveNameLengthPtr, driveNameRef); @@ -3935,14 +3957,14 @@ TclGetPathType( * * TclFSNonnativePathType -- * - * Helper function used by TclGetPathType. Checks whether the given - * pathname starts with a string which corresponds to a file volume in - * some registered filesystem other than the native one. For speed and - * historical reasons the native filesystem has special hard-coded checks - * dotted here and there in the filesystem code. + * Helper function used by TclGetPathType. Its purpose is to check + * whether the given path starts with a string which corresponds to a + * file volume in any registered filesystem except the native one. For + * speed and historical reasons the native filesystem has special + * hard-coded checks dotted here and there in the filesystem code. * * Results: - * One of TCL_PATH_ABSOLUTE or TCL_PATH_RELATIVE. The filesystem + * Returns one of TCL_PATH_ABSOLUTE or TCL_PATH_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. * @@ -3954,78 +3976,85 @@ TclGetPathType( Tcl_PathType TclFSNonnativePathType( - const char *path, /* Pathname to determine the type of. */ - Tcl_Size pathLen, /* Length of the pathname. */ - const Tcl_Filesystem **filesystemPtrPtr, - /* If not NULL, a place to store a pointer to - * the filesystem for this pathname when it is - * an absolute pathname. */ - Tcl_Size *driveNameLengthPtr,/* If not NULL, a place to store the length of - * the volume name if the pathname is absolute. - */ - Tcl_Obj **driveNameRef) /* If not NULL, a place to store a pointer to - * an object having its its refCount already - * incremented, and contining the name of the - * volume if the pathname is absolute. */ + const char *path, /* Path to determine type for */ + int pathLen, /* Length of the path */ + Tcl_Filesystem **filesystemPtrPtr, + /* If absolute path and this is not NULL, then + * set to the filesystem which claims this + * path. */ + int *driveNameLengthPtr, /* If the path is absolute, and this is + * non-NULL, then set to the length of the + * driveName. */ + Tcl_Obj **driveNameRef) /* If the path is absolute, and this is + * non-NULL, then set to the name of the + * drive, network-volume which contains the + * path, already with a refCount for the + * caller. */ { FilesystemRecord *fsRecPtr; Tcl_PathType type = TCL_PATH_RELATIVE; /* - * Determine whether the given pathname is an absolute pathname on some - * filesystem other than the native filesystem. + * Call each of the "listVolumes" function in succession, checking whether + * the given path is an absolute path on any of the volumes returned (this + * is done by checking whether the path's prefix matches). */ fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { + Tcl_FSListVolumesProc *proc = fsRecPtr->fsPtr->listVolumesProc; + /* - * Skip the the native filesystem because otherwise some of the tests - * in the Tcl testsuite might fail because some of the tests - * artificially change the current platform (between win, unix) but the - * list of volumes obtained by calling fsRecPtr->fsPtr->listVolumesProc - * reflects the current (real) platform only. In particular, on Unix - * '/' matchs the beginning of certain absolute Windows pathnames - * starting '//' and those tests go wrong. + * We want to skip the native filesystem in this loop because + * otherwise we won't necessarily pass all the Tcl testsuite -- this + * is because some of the tests artificially change the current + * platform (between win, unix) but the list of volumes we get by + * calling (*proc) will reflect the current (real) platform only and + * this may cause some tests to fail. In particular, on unix '/' will + * match the beginning of certain absolute Windows paths starting '//' + * and those tests will go wrong. * - * There is another reason to skip the native filesystem: Since the - * tclFilename.c code has nice fast 'absolute path' checkers, there is - * no reason to waste time doing that in this frequently-called - * function. It is better to save the overhead of the native - * filesystem continuously returning a list of volumes. + * Besides these test-suite issues, there is one other reason to skip + * the native filesystem --- since the tclFilename.c code has nice + * fast 'absolute path' checkers, we don't want to waste time + * repeating that effort here, and this function is actually called + * quite often, so if we can save the overhead of the native + * filesystem returning us a list of volumes all the time, it is + * better. */ - if ((fsRecPtr->fsPtr != &tclNativeFilesystem) - && (fsRecPtr->fsPtr->listVolumesProc != NULL)) { - Tcl_Size numVolumes; - Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc(); + if ((fsRecPtr->fsPtr != &tclNativeFilesystem) && (proc != NULL)) { + int numVolumes; + Tcl_Obj *thisFsVolumes = (*proc)(); if (thisFsVolumes != NULL) { - if (TclListObjLength(NULL, thisFsVolumes, &numVolumes) + if (Tcl_ListObjLength(NULL, thisFsVolumes, &numVolumes) != TCL_OK) { /* - * This is VERY bad; the listVolumesProc didn't return a - * valid list. Set numVolumes to -1 to skip the loop below - * and just return with the current value of 'type'. + * This is VERY bad; the Tcl_FSListVolumesProc didn't + * return a valid list. Set numVolumes to -1 so that we + * skip the while loop below and just return with the + * current value of 'type'. * - * It would be better to signal an error here, but - * Tcl_Panic seems a bit excessive. + * It would be better if we could signal an error here + * (but Tcl_Panic seems a bit excessive). */ - numVolumes = TCL_INDEX_NONE; + numVolumes = -1; } while (numVolumes > 0) { Tcl_Obj *vol; - Tcl_Size len; - const char *strVol; + int len; + char *strVol; numVolumes--; Tcl_ListObjIndex(NULL, thisFsVolumes, numVolumes, &vol); - strVol = TclGetStringFromObj(vol,&len); + strVol = Tcl_GetStringFromObj(vol,&len); if (pathLen < len) { continue; } - if (strncmp(strVol, path, len) == 0) { + if (strncmp(strVol, path, (size_t) len) == 0) { type = TCL_PATH_ABSOLUTE; if (filesystemPtrPtr != NULL) { *filesystemPtrPtr = fsRecPtr->fsPtr; @@ -4043,9 +4072,8 @@ TclFSNonnativePathType( Tcl_DecrRefCount(thisFsVolumes); if (type == TCL_PATH_ABSOLUTE) { /* - * No need to to examine additional filesystems. + * We don't need to examine any more filesystems. */ - break; } } @@ -4061,13 +4089,12 @@ TclFSNonnativePathType( * * Tcl_FSRenameFile -- * - * If the two pathnames correspond to the same filesystem, call - * 'renameFileProc' of that filesystem. Otherwise return the POSIX error - * 'EXDEV', and -1. + * If the two paths given belong to the same filesystem, we call that + * filesystems rename function. Otherwise we simply return the POSIX + * error 'EXDEV', and -1. * * Results: - * A standard Tcl error code if a rename function was called, or -1 - * otherwise. + * Standard Tcl error code if a function was called. * * Side effects: * A file may be renamed. @@ -4077,19 +4104,21 @@ TclFSNonnativePathType( int Tcl_FSRenameFile( - Tcl_Obj *srcPathPtr, /* The pathname of a file or directory to be - renamed. */ - Tcl_Obj *destPathPtr) /* The new pathname for the file. */ + Tcl_Obj* srcPathPtr, /* Pathname of file or dir to be renamed + * (UTF-8). */ + Tcl_Obj *destPathPtr) /* New pathname of file or directory + * (UTF-8). */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; - fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); - if ((fsPtr == fsPtr2) && (fsPtr != NULL) - && (fsPtr->renameFileProc != NULL)) { - retVal = fsPtr->renameFileProc(srcPathPtr, destPathPtr); + if ((fsPtr == fsPtr2) && (fsPtr != NULL)) { + Tcl_FSRenameFileProc *proc = fsPtr->renameFileProc; + if (proc != NULL) { + retVal = (*proc)(srcPathPtr, destPathPtr); + } } if (retVal == -1) { Tcl_SetErrno(EXDEV); @@ -4102,36 +4131,38 @@ Tcl_FSRenameFile( * * Tcl_FSCopyFile -- * - * If both pathnames correspond to the same filesystem, calls - * 'copyFileProc' of that filesystem. + * If the two paths given belong to the same filesystem, we call that + * filesystem's copy function. Otherwise we simply return the POSIX error + * 'EXDEV', and -1. * - * In the native filesystems, 'copyFileProc' copies a link itself, not the - * thing the link points to. + * Note that in the native filesystems, 'copyFileProc' is defined to copy + * soft links (i.e. it copies the links themselves, not the things they + * point to). * * Results: - * A standard Tcl return code if a copyFileProc was called, or -1 - * otherwise. + * Standard Tcl error code if a function was called. * * Side effects: - * A file might be copied. The POSIX error 'EXDEV' is set if a copy - * function was not called. + * A file may be copied. * *--------------------------------------------------------------------------- */ int Tcl_FSCopyFile( - Tcl_Obj *srcPathPtr, /* The pathname of file to be copied. */ - Tcl_Obj *destPathPtr) /* The new pathname to copy the file to. */ + Tcl_Obj *srcPathPtr, /* Pathname of file to be copied (UTF-8). */ + Tcl_Obj *destPathPtr) /* Pathname of file to copy to (UTF-8). */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; - fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); - if (fsPtr == fsPtr2 && fsPtr != NULL && fsPtr->copyFileProc != NULL) { - retVal = fsPtr->copyFileProc(srcPathPtr, destPathPtr); + if (fsPtr == fsPtr2 && fsPtr != NULL) { + Tcl_FSCopyFileProc *proc = fsPtr->copyFileProc; + if (proc != NULL) { + retVal = (*proc)(srcPathPtr, destPathPtr); + } } if (retVal == -1) { Tcl_SetErrno(EXDEV); @@ -4144,23 +4175,23 @@ Tcl_FSCopyFile( * * TclCrossFilesystemCopy -- * - * Helper for Tcl_FSCopyFile and Tcl_FSLoadFile. Copies a file from one - * filesystem to another, overwiting any file that already exists. + * Helper for above function, and for Tcl_FSLoadFile, to copy files from + * one filesystem to another. This function will overwrite the target + * file if it already exists. * * Results: - * A standard Tcl return code. + * Standard Tcl error code. * * Side effects: - * A file may be copied. + * A file may be created. * *--------------------------------------------------------------------------- */ - int TclCrossFilesystemCopy( - Tcl_Interp *interp, /* For error messages. */ - Tcl_Obj *source, /* Pathname of file to be copied. */ - Tcl_Obj *target) /* Pathname to copy the file to. */ + Tcl_Interp *interp, /* For error messages */ + Tcl_Obj *source, /* Pathname of file to be copied (UTF-8). */ + Tcl_Obj *target) /* Pathname of file to copy to (UTF-8). */ { int result = TCL_ERROR; int prot = 0666; @@ -4171,7 +4202,7 @@ TclCrossFilesystemCopy( out = Tcl_FSOpenFileChannel(interp, target, "wb", prot); if (out == NULL) { /* - * Failed to open an output channel. Bail out. + * It looks like we cannot copy it over. Bail out... */ goto done; } @@ -4179,7 +4210,7 @@ TclCrossFilesystemCopy( in = Tcl_FSOpenFileChannel(interp, source, "rb", prot); if (in == NULL) { /* - * Could not open an input channel. Why didn't the caller check this? + * This is very strange, caller should have checked this... */ Tcl_Close(interp, out); @@ -4187,8 +4218,8 @@ TclCrossFilesystemCopy( } /* - * Copy the file synchronously. TO DO: Maybe add an asynchronous option - * to support virtual filesystems that are slow (e.g. network sockets). + * Copy it synchronously. We might wish to add an asynchronous option to + * support vfs's which are slow (e.g. network sockets). */ if (TclCopyChannel(interp, in, out, -1, NULL) == TCL_OK) { @@ -4196,7 +4227,7 @@ TclCrossFilesystemCopy( } /* - * If the copy failed, assume that copy channel left an error message. + * If the copy failed, assume that copy channel left a good error message. */ Tcl_Close(interp, in); @@ -4207,8 +4238,8 @@ TclCrossFilesystemCopy( */ if (Tcl_FSLstat(source, &sourceStatBuf) == 0) { - tval.actime = Tcl_GetAccessTimeFromStat(&sourceStatBuf); - tval.modtime = Tcl_GetModificationTimeFromStat(&sourceStatBuf); + tval.actime = sourceStatBuf.st_atime; + tval.modtime = sourceStatBuf.st_mtime; Tcl_FSUtime(target, &tval); } @@ -4221,11 +4252,11 @@ TclCrossFilesystemCopy( * * Tcl_FSDeleteFile -- * - * Calls 'deleteFileProc' of the filesystem corresponding to the given - * pathname. + * The appropriate function for the filesystem to which pathPtr belongs + * will be called. * * Results: - * A standard Tcl return code. + * Standard Tcl error code. * * Side effects: * A file may be deleted. @@ -4238,17 +4269,13 @@ Tcl_FSDeleteFile( Tcl_Obj *pathPtr) /* Pathname of file to be removed (UTF-8). */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - int err; - - if (fsPtr == NULL) { - err = ENOENT; - } else { - if (fsPtr->deleteFileProc != NULL) { - return fsPtr->deleteFileProc(pathPtr); + if (fsPtr != NULL) { + Tcl_FSDeleteFileProc *proc = fsPtr->deleteFileProc; + if (proc != NULL) { + return (*proc)(pathPtr); } - err = ENOTSUP; } - Tcl_SetErrno(err); + Tcl_SetErrno(ENOENT); return -1; } @@ -4257,15 +4284,14 @@ Tcl_FSDeleteFile( * * Tcl_FSCreateDirectory -- * - * Calls 'createDirectoryProc' of the filesystem corresponding to the - * given pathname. + * The appropriate function for the filesystem to which pathPtr belongs + * will be called. * * Results: - * A standard Tcl return code, or -1 if no createDirectoryProc is found. + * Standard Tcl error code. * * Side effects: - * A directory may be created. POSIX error 'ENOENT' is set if no - * createDirectoryProc is found. + * A directory may be created. * *--------------------------------------------------------------------------- */ @@ -4275,17 +4301,13 @@ Tcl_FSCreateDirectory( Tcl_Obj *pathPtr) /* Pathname of directory to create (UTF-8). */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - int err; - - if (fsPtr == NULL) { - err = ENOENT; - } else { - if (fsPtr->createDirectoryProc != NULL) { - return fsPtr->createDirectoryProc(pathPtr); + if (fsPtr != NULL) { + Tcl_FSCreateDirectoryProc *proc = fsPtr->createDirectoryProc; + if (proc != NULL) { + return (*proc)(pathPtr); } - err = ENOTSUP; } - Tcl_SetErrno(err); + Tcl_SetErrno(ENOENT); return -1; } @@ -4294,38 +4316,38 @@ Tcl_FSCreateDirectory( * * Tcl_FSCopyDirectory -- * - * If both pathnames correspond to the the same filesystem, calls - * 'copyDirectoryProc' of that filesystem. + * If the two paths given belong to the same filesystem, we call that + * filesystems copy-directory function. Otherwise we simply return the + * POSIX error 'EXDEV', and -1. * * Results: - * A standard Tcl return code, or -1 if no 'copyDirectoryProc' is found. + * Standard Tcl error code if a function was called. * * Side effects: - * A directory may be copied. POSIX error 'EXDEV' is set if no - * copyDirectoryProc is found. + * A directory may be copied. * *--------------------------------------------------------------------------- */ int Tcl_FSCopyDirectory( - Tcl_Obj *srcPathPtr, /* The pathname of the directory to be - * copied. */ - Tcl_Obj *destPathPtr, /* The pathname of the target directory. */ - Tcl_Obj **errorPtr) /* If not NULL, and there is an error, a place - * to store a pointer to a new object, with - * its refCount already incremented, and - * containing the pathname name of file - * causing the error. */ + Tcl_Obj* srcPathPtr, /* Pathname of directory to be copied + * (UTF-8). */ + Tcl_Obj *destPathPtr, /* Pathname of target directory (UTF-8). */ + Tcl_Obj **errorPtr) /* If non-NULL, then will be set to a new + * object containing name of file causing + * error, with refCount 1. */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; - fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); - if (fsPtr == fsPtr2 && fsPtr != NULL && fsPtr->copyDirectoryProc != NULL){ - retVal = fsPtr->copyDirectoryProc(srcPathPtr, destPathPtr, errorPtr); + if (fsPtr == fsPtr2 && fsPtr != NULL) { + Tcl_FSCopyDirectoryProc *proc = fsPtr->copyDirectoryProc; + if (proc != NULL) { + retVal = (*proc)(srcPathPtr, destPathPtr, errorPtr); + } } if (retVal == -1) { Tcl_SetErrno(EXDEV); @@ -4338,71 +4360,69 @@ Tcl_FSCopyDirectory( * * Tcl_FSRemoveDirectory -- * - * Calls 'removeDirectoryProc' of the filesystem corresponding to remove - * pathPtr. + * The appropriate function for the filesystem to which pathPtr belongs + * will be called. * * Results: - * A standard Tcl return code, or -1 if no removeDirectoryProc is found. + * Standard Tcl error code. * * Side effects: - * A directory may be removed. POSIX error 'ENOENT' is set if no - * removeDirectoryProc is found. + * A directory may be deleted. * *--------------------------------------------------------------------------- */ int Tcl_FSRemoveDirectory( - Tcl_Obj *pathPtr, /* The pathname of the directory to be removed. - */ - int recursive, /* If zero, removes only an empty directory. - * Otherwise, removes the directory and all its - * contents. */ - Tcl_Obj **errorPtr) /* If not NULL and an error occurs, stores a - * place to store a a pointer to a new - * object having a refCount of 1 and containing - * the name of the file that produced an error. - * */ + Tcl_Obj *pathPtr, /* Pathname of directory to be removed + * (UTF-8). */ + int recursive, /* If non-zero, removes directories that are + * nonempty. Otherwise, will only remove empty + * directories. */ + Tcl_Obj **errorPtr) /* If non-NULL, then will be set to a new + * object containing name of file causing + * error, with refCount 1. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); + if (fsPtr != NULL && fsPtr->removeDirectoryProc != NULL) { + Tcl_FSRemoveDirectoryProc *proc = fsPtr->removeDirectoryProc; + if (recursive) { + /* + * We check whether the cwd lies inside this directory and move it + * if it does. + */ - if (fsPtr == NULL) { - Tcl_SetErrno(ENOENT); - return -1; - } - if (fsPtr->removeDirectoryProc == NULL) { - Tcl_SetErrno(ENOTSUP); - return -1; - } + Tcl_Obj *cwdPtr = Tcl_FSGetCwd(NULL); - if (recursive) { - Tcl_Obj *cwdPtr = Tcl_FSGetCwd(NULL); - if (cwdPtr != NULL) { - const char *cwdStr, *normPathStr; - Tcl_Size cwdLen, normLen; - Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr); + if (cwdPtr != NULL) { + char *cwdStr, *normPathStr; + int cwdLen, normLen; + Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr); - if (normPath != NULL) { - normPathStr = TclGetStringFromObj(normPath, &normLen); - cwdStr = TclGetStringFromObj(cwdPtr, &cwdLen); - if ((cwdLen >= normLen) && (strncmp(normPathStr, cwdStr, - normLen) == 0)) { - /* - * The cwd is inside the directory to be removed. Change - * the cwd to [file dirname $path]. - */ + if (normPath != NULL) { + normPathStr = Tcl_GetStringFromObj(normPath, &normLen); + cwdStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen); + if ((cwdLen >= normLen) && (strncmp(normPathStr, cwdStr, + (size_t) normLen) == 0)) { + /* + * The cwd is inside the directory, so we perform a + * 'cd [file dirname $path]'. + */ - Tcl_Obj *dirPtr = TclPathPart(NULL, pathPtr, - TCL_PATH_DIRNAME); + Tcl_Obj *dirPtr = TclPathPart(NULL, pathPtr, + TCL_PATH_DIRNAME); - Tcl_FSChdir(dirPtr); - Tcl_DecrRefCount(dirPtr); + Tcl_FSChdir(dirPtr); + Tcl_DecrRefCount(dirPtr); + } } + Tcl_DecrRefCount(cwdPtr); } - Tcl_DecrRefCount(cwdPtr); } + return (*proc)(pathPtr, recursive, errorPtr); } - return fsPtr->removeDirectoryProc(pathPtr, recursive, errorPtr); + Tcl_SetErrno(ENOENT); + return -1; } /* @@ -4410,83 +4430,84 @@ Tcl_FSRemoveDirectory( * * Tcl_FSGetFileSystemForPath -- * - * Produces the filesystem that corresponds to the given pathname. + * 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: - * The corresponding Tcl_Filesystem, or NULL if the pathname is invalid. + * NULL or a filesystem which will accept this path. * * Side effects: - * The internal representation of fsPtrPtr is converted to fsPathType if - * needed, and that internal representation is updated as needed. + * The object may be converted to a path type. * *--------------------------------------------------------------------------- */ -const Tcl_Filesystem * +Tcl_Filesystem * Tcl_FSGetFileSystemForPath( - Tcl_Obj *pathPtr) + Tcl_Obj* pathPtr) { FilesystemRecord *fsRecPtr; - const Tcl_Filesystem *retVal = NULL; + Tcl_Filesystem* retVal = NULL; if (pathPtr == NULL) { Tcl_Panic("Tcl_FSGetFileSystemForPath called with NULL object"); return NULL; } + /* + * 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 (pathPtr->refCount == 0) { - /* - * Avoid possible segfaults or nondeterministic memory leaks where the - * reference count has been incorreclty managed. - */ Tcl_Panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0"); return NULL; } - /* Start with an up-to-date copy of the filesystem. */ + /* + * Check if the filesystem has changed in some way since this object's + * internal representation was calculated. Before doing that, assure we + * have the most up-to-date copy of the master filesystem. This is + * accomplished by the FsGetFirstFilesystem() call. + */ + fsRecPtr = FsGetFirstFilesystem(); Claim(); - /* - * Ensure that pathPtr is a valid pathname. - */ if (TclFSEnsureEpochOk(pathPtr, &retVal) != TCL_OK) { - /* not a valid pathname */ Disclaim(); return NULL; - } else if (retVal != NULL) { - /* - * Found the filesystem in the internal representation of pathPtr. - */ - Disclaim(); - return retVal; } /* - * Call each of the "pathInFilesystem" functions in succession until the - * corresponding filesystem is found. + * Call each of the "pathInFilesystem" functions in succession. A + * non-return value of -1 indicates the particular function has succeeded. */ - for (; fsRecPtr!=NULL ; fsRecPtr=fsRecPtr->nextPtr) { - void *clientData = NULL; - if (fsRecPtr->fsPtr->pathInFilesystemProc == NULL) { - continue; - } + while ((retVal == NULL) && (fsRecPtr != NULL)) { + Tcl_FSPathInFilesystemProc *proc = + fsRecPtr->fsPtr->pathInFilesystemProc; - if (fsRecPtr->fsPtr->pathInFilesystemProc(pathPtr, &clientData)!=-1) { - /* This is the filesystem for pathPtr. Assume the type of pathPtr - * hasn't been changed by the above call to the - * pathInFilesystemProc, and cache this result in the internal - * representation of pathPtr. */ + if (proc != NULL) { + ClientData clientData = NULL; + if ((*proc)(pathPtr, &clientData) != -1) { + /* + * We assume the type of pathPtr hasn't been changed by the + * above call to the pathInFilesystemProc. + */ - TclFSSetPathDetails(pathPtr, fsRecPtr->fsPtr, clientData); - Disclaim(); - return fsRecPtr->fsPtr; + TclFSSetPathDetails(pathPtr, fsRecPtr->fsPtr, clientData); + retVal = fsRecPtr->fsPtr; + } } + fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); - return NULL; + return retVal; } /* @@ -4494,16 +4515,35 @@ Tcl_FSGetFileSystemForPath( * * Tcl_FSGetNativePath -- * - * See Tcl_FSGetInternalRep. + * This function is for use by the Win/Unix native filesystems, so that + * they can easily retrieve the native (char* or TCHAR*) representation + * of a path. Other filesystems will probably want to implement similar + * functions. They basically act as a safety net around + * Tcl_FSGetInternalRep. Normally your file-system functions will always + * be called with path objects already converted to the correct + * filesystem, but if for some reason they are called directly (i.e. by + * functions not in this file), then one cannot necessarily guarantee + * that the path object pointer is from the correct filesystem. + * + * Note: in the future it might be desireable to have separate versions + * of this function with different signatures, for example + * Tcl_FSGetNativeWinPath, Tcl_FSGetNativeUnixPath etc. Right now, since + * native paths are all string based, we use just one function. + * + * Results: + * NULL or a valid native path. + * + * Side effects: + * See Tcl_FSGetInternalRep. * *--------------------------------------------------------------------------- */ -const void * +const char * Tcl_FSGetNativePath( Tcl_Obj *pathPtr) { - return Tcl_FSGetInternalRep(pathPtr, &tclNativeFilesystem); + return (const char *) Tcl_FSGetInternalRep(pathPtr, &tclNativeFilesystem); } /* @@ -4511,7 +4551,7 @@ Tcl_FSGetNativePath( * * NativeFreeInternalRep -- * - * Free a native internal representation. + * Free a native internal representation, which will be non-NULL. * * Results: * None. @@ -4524,26 +4564,25 @@ Tcl_FSGetNativePath( static void NativeFreeInternalRep( - void *clientData) + ClientData clientData) { - ckfree(clientData); + ckfree((char *) clientData); } /* *--------------------------------------------------------------------------- * * Tcl_FSFileSystemInfo -- - * Produce the type of a pathname and the type of its filesystem. * + * This function returns a list of two elements. The first element is the + * name of the filesystem (e.g. "native" or "vfs"), and the second is the + * particular type of the given path within that filesystem. * * Results: - * A list where the first item is the name of the filesystem (e.g. - * "native" or "vfs"), and the second item is the type of the given - * pathname within that filesystem. + * A list of two elements. * * Side effects: - * The internal representation of pathPtr may be converted to a - * fsPathType. + * The object may be converted to a path type. * *--------------------------------------------------------------------------- */ @@ -4553,6 +4592,7 @@ Tcl_FSFileSystemInfo( Tcl_Obj *pathPtr) { Tcl_Obj *resPtr; + Tcl_FSFilesystemPathTypeProc *proc; const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr == NULL) { @@ -4560,12 +4600,11 @@ Tcl_FSFileSystemInfo( } resPtr = Tcl_NewListObj(0, NULL); - Tcl_ListObjAppendElement(NULL, resPtr, - Tcl_NewStringObj(fsPtr->typeName, -1)); - - if (fsPtr->filesystemPathTypeProc != NULL) { - Tcl_Obj *typePtr = fsPtr->filesystemPathTypeProc(pathPtr); + Tcl_ListObjAppendElement(NULL,resPtr,Tcl_NewStringObj(fsPtr->typeName,-1)); + proc = fsPtr->filesystemPathTypeProc; + if (proc != NULL) { + Tcl_Obj *typePtr = (*proc)(pathPtr); if (typePtr != NULL) { Tcl_ListObjAppendElement(NULL, resPtr, typePtr); } @@ -4579,13 +4618,16 @@ Tcl_FSFileSystemInfo( * * Tcl_FSPathSeparator -- * - * Produces the separator for given pathname. + * This function returns the separator to be used for a given path. The + * object returned should have a refCount of zero * * Results: - * A Tcl object having a refCount of zero. + * A Tcl object, with a refCount of zero. If the caller needs to retain a + * reference to the object, it should call Tcl_IncrRefCount, and should + * otherwise free the object. * * Side effects: - * The internal representation of pathPtr may be converted to a fsPathType + * The path object may be converted to a path type. * *--------------------------------------------------------------------------- */ @@ -4595,23 +4637,23 @@ Tcl_FSPathSeparator( Tcl_Obj *pathPtr) { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); - Tcl_Obj *resultObj; if (fsPtr == NULL) { return NULL; } - if (fsPtr->filesystemSeparatorProc != NULL) { - return fsPtr->filesystemSeparatorProc(pathPtr); - } + return (*fsPtr->filesystemSeparatorProc)(pathPtr); + } else { + Tcl_Obj *resultObj; - /* - * Use the standard forward slash character if filesystem does not to - * provide a filesystemSeparatorProc. - */ + /* + * Allow filesystems not to provide a filesystemSeparatorProc if they + * wish to use the standard forward slash. + */ - TclNewLiteralStringObj(resultObj, "/"); - return resultObj; + TclNewLiteralStringObj(resultObj, "/"); + return resultObj; + } } /* @@ -4619,11 +4661,11 @@ Tcl_FSPathSeparator( * * NativeFilesystemSeparator -- * - * This function, part of the native filesystem support, returns the - * separator for the given pathname. + * This function is part of the native filesystem support, and returns + * the separator for the given path. * * Results: - * The separator character. + * String object containing the separator character. * * Side effects: * None. @@ -4633,10 +4675,9 @@ Tcl_FSPathSeparator( static Tcl_Obj * NativeFilesystemSeparator( - TCL_UNUSED(Tcl_Obj *) /*pathPtr*/) + Tcl_Obj *pathPtr) { - const char *separator = NULL; - + const char *separator = NULL; /* lint */ switch (tclPlatform) { case TCL_PLATFORM_UNIX: separator = "/"; @@ -4647,6 +4688,318 @@ NativeFilesystemSeparator( } return Tcl_NewStringObj(separator,1); } + +/* Everything from here on is contained in this obsolete ifdef */ +#ifdef USE_OBSOLETE_FS_HOOKS + +/* + *---------------------------------------------------------------------- + * + * TclStatInsertProc -- + * + * Insert the passed function pointer at the head of the list of + * functions which are used during a call to 'TclStat(...)'. The passed + * function should behave exactly like 'TclStat' when called during that + * time (see 'TclStat(...)' for more information). The function will be + * added even if it already in the list. + * + * Results: + * Normally TCL_OK; TCL_ERROR if memory for a new node in the list could + * not be allocated. + * + * Side effects: + * Memory allocated and modifies the link list for 'TclStat' functions. + * + *---------------------------------------------------------------------- + */ + +int +TclStatInsertProc( + TclStatProc_ *proc) +{ + int retVal = TCL_ERROR; + + if (proc != NULL) { + StatProc *newStatProcPtr; + + newStatProcPtr = (StatProc *)ckalloc(sizeof(StatProc)); + + if (newStatProcPtr != NULL) { + newStatProcPtr->proc = proc; + Tcl_MutexLock(&obsoleteFsHookMutex); + newStatProcPtr->nextPtr = statProcList; + statProcList = newStatProcPtr; + Tcl_MutexUnlock(&obsoleteFsHookMutex); + + retVal = TCL_OK; + } + } + + return retVal; +} + +/* + *---------------------------------------------------------------------- + * + * TclStatDeleteProc -- + * + * Removed the passed function pointer from the list of 'TclStat' + * functions. Ensures that the built-in stat function is not removable. + * + * Results: + * TCL_OK if the function pointer was successfully removed, TCL_ERROR + * otherwise. + * + * Side effects: + * Memory is deallocated and the respective list updated. + * + *---------------------------------------------------------------------- + */ + +int +TclStatDeleteProc( + TclStatProc_ *proc) +{ + int retVal = TCL_ERROR; + StatProc *tmpStatProcPtr; + StatProc *prevStatProcPtr = NULL; + + Tcl_MutexLock(&obsoleteFsHookMutex); + tmpStatProcPtr = statProcList; + + /* + * Traverse the 'statProcList' looking for the particular node whose + * 'proc' member matches 'proc' and remove that one from the list. Ensure + * that the "default" node cannot be removed. + */ + + while ((retVal == TCL_ERROR) && (tmpStatProcPtr != NULL)) { + if (tmpStatProcPtr->proc == proc) { + if (prevStatProcPtr == NULL) { + statProcList = tmpStatProcPtr->nextPtr; + } else { + prevStatProcPtr->nextPtr = tmpStatProcPtr->nextPtr; + } + + ckfree((char *)tmpStatProcPtr); + + retVal = TCL_OK; + } else { + prevStatProcPtr = tmpStatProcPtr; + tmpStatProcPtr = tmpStatProcPtr->nextPtr; + } + } + + Tcl_MutexUnlock(&obsoleteFsHookMutex); + + return retVal; +} + +/* + *---------------------------------------------------------------------- + * + * TclAccessInsertProc -- + * + * Insert the passed function pointer at the head of the list of + * functions which are used during a call to 'TclAccess(...)'. The passed + * function should behave exactly like 'TclAccess' when called during + * that time (see 'TclAccess(...)' for more information). The function + * will be added even if it already in the list. + * + * Results: + * Normally TCL_OK; TCL_ERROR if memory for a new node in the list could + * not be allocated. + * + * Side effects: + * Memory allocated and modifies the link list for 'TclAccess' functions. + * + *---------------------------------------------------------------------- + */ + +int +TclAccessInsertProc( + TclAccessProc_ *proc) +{ + int retVal = TCL_ERROR; + + if (proc != NULL) { + AccessProc *newAccessProcPtr; + + newAccessProcPtr = (AccessProc *)ckalloc(sizeof(AccessProc)); + + if (newAccessProcPtr != NULL) { + newAccessProcPtr->proc = proc; + Tcl_MutexLock(&obsoleteFsHookMutex); + newAccessProcPtr->nextPtr = accessProcList; + accessProcList = newAccessProcPtr; + Tcl_MutexUnlock(&obsoleteFsHookMutex); + + retVal = TCL_OK; + } + } + + return retVal; +} + +/* + *---------------------------------------------------------------------- + * + * TclAccessDeleteProc -- + * + * Removed the passed function pointer from the list of 'TclAccess' + * functions. Ensures that the built-in access function is not removable. + * + * Results: + * TCL_OK if the function pointer was successfully removed, TCL_ERROR + * otherwise. + * + * Side effects: + * Memory is deallocated and the respective list updated. + * + *---------------------------------------------------------------------- + */ + +int +TclAccessDeleteProc( + TclAccessProc_ *proc) +{ + int retVal = TCL_ERROR; + AccessProc *tmpAccessProcPtr; + AccessProc *prevAccessProcPtr = NULL; + + /* + * Traverse the 'accessProcList' looking for the particular node whose + * 'proc' member matches 'proc' and remove that one from the list. Ensure + * that the "default" node cannot be removed. + */ + + Tcl_MutexLock(&obsoleteFsHookMutex); + tmpAccessProcPtr = accessProcList; + while ((retVal == TCL_ERROR) && (tmpAccessProcPtr != NULL)) { + if (tmpAccessProcPtr->proc == proc) { + if (prevAccessProcPtr == NULL) { + accessProcList = tmpAccessProcPtr->nextPtr; + } else { + prevAccessProcPtr->nextPtr = tmpAccessProcPtr->nextPtr; + } + + ckfree((char *)tmpAccessProcPtr); + + retVal = TCL_OK; + } else { + prevAccessProcPtr = tmpAccessProcPtr; + tmpAccessProcPtr = tmpAccessProcPtr->nextPtr; + } + } + Tcl_MutexUnlock(&obsoleteFsHookMutex); + + return retVal; +} + +/* + *---------------------------------------------------------------------- + * + * TclOpenFileChannelInsertProc -- + * + * Insert the passed function pointer at the head of the list of + * functions which are used during a call to 'Tcl_OpenFileChannel(...)'. + * The passed function should behave exactly like 'Tcl_OpenFileChannel' + * when called during that time (see 'Tcl_OpenFileChannel(...)' for more + * information). The function will be added even if it already in the + * list. + * + * Results: + * Normally TCL_OK; TCL_ERROR if memory for a new node in the list could + * not be allocated. + * + * Side effects: + * Memory allocated and modifies the link list for 'Tcl_OpenFileChannel' + * functions. + * + *---------------------------------------------------------------------- + */ + +int +TclOpenFileChannelInsertProc( + TclOpenFileChannelProc_ *proc) +{ + int retVal = TCL_ERROR; + + if (proc != NULL) { + OpenFileChannelProc *newOpenFileChannelProcPtr; + + newOpenFileChannelProcPtr = (OpenFileChannelProc *) + ckalloc(sizeof(OpenFileChannelProc)); + + newOpenFileChannelProcPtr->proc = proc; + Tcl_MutexLock(&obsoleteFsHookMutex); + newOpenFileChannelProcPtr->nextPtr = openFileChannelProcList; + openFileChannelProcList = newOpenFileChannelProcPtr; + Tcl_MutexUnlock(&obsoleteFsHookMutex); + + retVal = TCL_OK; + } + + return retVal; +} + +/* + *---------------------------------------------------------------------- + * + * TclOpenFileChannelDeleteProc -- + * + * Removed the passed function pointer from the list of + * 'Tcl_OpenFileChannel' functions. Ensures that the built-in open file + * channel function is not removable. + * + * Results: + * TCL_OK if the function pointer was successfully removed, TCL_ERROR + * otherwise. + * + * Side effects: + * Memory is deallocated and the respective list updated. + * + *---------------------------------------------------------------------- + */ + +int +TclOpenFileChannelDeleteProc( + TclOpenFileChannelProc_ *proc) +{ + int retVal = TCL_ERROR; + OpenFileChannelProc *tmpOpenFileChannelProcPtr = openFileChannelProcList; + OpenFileChannelProc *prevOpenFileChannelProcPtr = NULL; + + /* + * Traverse the 'openFileChannelProcList' looking for the particular node + * whose 'proc' member matches 'proc' and remove that one from the list. + */ + + Tcl_MutexLock(&obsoleteFsHookMutex); + tmpOpenFileChannelProcPtr = openFileChannelProcList; + while ((retVal == TCL_ERROR) && + (tmpOpenFileChannelProcPtr != NULL)) { + if (tmpOpenFileChannelProcPtr->proc == proc) { + if (prevOpenFileChannelProcPtr == NULL) { + openFileChannelProcList = tmpOpenFileChannelProcPtr->nextPtr; + } else { + prevOpenFileChannelProcPtr->nextPtr = + tmpOpenFileChannelProcPtr->nextPtr; + } + + ckfree((char *) tmpOpenFileChannelProcPtr); + + retVal = TCL_OK; + } else { + prevOpenFileChannelProcPtr = tmpOpenFileChannelProcPtr; + tmpOpenFileChannelProcPtr = tmpOpenFileChannelProcPtr->nextPtr; + } + } + Tcl_MutexUnlock(&obsoleteFsHookMutex); + + return retVal; +} +#endif /* USE_OBSOLETE_FS_HOOKS */ /* * Local Variables: |
