OSDN Git Service

Please enter the commit message for your changes. Lines starting
[eos/base.git] / util / src / TclTk / tcl8.6.12 / generic / tclIOUtil.c
diff --git a/util/src/TclTk/tcl8.6.12/generic/tclIOUtil.c b/util/src/TclTk/tcl8.6.12/generic/tclIOUtil.c
new file mode 100644 (file)
index 0000000..312fd08
--- /dev/null
@@ -0,0 +1,4859 @@
+/*
+ * tclIOUtil.c --
+ *
+ *     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.
+ *
+ *     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
+#   include "tclWinInt.h"
+#endif
+#include "tclFileSystem.h"
+
+#ifdef TCL_TEMPLOAD_NO_UNLINK
+#ifndef NO_FSTATFS
+#include <sys/statfs.h>
+#endif
+#endif
+
+/*
+ * struct FilesystemRecord --
+ *
+ * A filesystem record is used to keep track of each filesystem currently
+ * registered with the core, in a linked list.
+ */
+
+typedef struct FilesystemRecord {
+    ClientData clientData;     /* Client specific data for the new filesystem
+                                * (can be NULL) */
+    const Tcl_Filesystem *fsPtr;/* Pointer to filesystem dispatch table. */
+    struct FilesystemRecord *nextPtr;
+                               /* The next filesystem registered to Tcl, or
+                                * NULL if no more. */
+    struct FilesystemRecord *prevPtr;
+                               /* 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 ThreadSpecificData {
+    int initialized;
+    size_t cwdPathEpoch;
+    size_t filesystemEpoch;
+    Tcl_Obj *cwdPathPtr;
+    ClientData cwdClientData;
+    FilesystemRecord *filesystemList;
+    size_t claims;
+} ThreadSpecificData;
+
+/*
+ * Prototypes for functions defined later in this file.
+ */
+
+static Tcl_NRPostProc  EvalFileCallback;
+static FilesystemRecord*FsGetFirstFilesystem(void);
+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, 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);
+
+/*
+ * 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 TclFileAttrProcs    tclpFileAttrProcs[];
+\f
+/*
+ * 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_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!
+ */
+
+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
+};
+
+/*
+ * 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 size_t 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 size_t 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;
+\f
+/*
+ * The following functions are obsolete string based APIs, and should be
+ * removed in a future release (Tcl 9 would be a good time).
+ */
+
+/* Obsolete */
+int
+Tcl_Stat(
+    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_IncrRefCount(pathPtr);
+    ret = Tcl_FSStat(pathPtr, &buf);
+    Tcl_DecrRefCount(pathPtr);
+    if (ret != -1) {
+#ifndef TCL_WIDE_INT_IS_LONG
+       Tcl_WideInt tmp1, tmp2, tmp3 = 0;
+
+# define OUT_OF_RANGE(x) \
+       (((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))
+
+       /*
+        * Perform the result-buffer overflow check manually.
+        *
+        * Note that ino_t/ino64_t is unsigned...
+        *
+        * Workaround gcc warning of "comparison is always false due to
+        * limited range of data type" by assigning to tmp var of type
+        * Tcl_WideInt.
+        */
+
+       tmp1 = (Tcl_WideInt) buf.st_ino;
+       tmp2 = (Tcl_WideInt) buf.st_size;
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+       tmp3 = (Tcl_WideInt) buf.st_blocks;
+#endif
+
+       if (OUT_OF_URANGE(tmp1) || OUT_OF_RANGE(tmp2) || OUT_OF_RANGE(tmp3)) {
+#if defined(EFBIG)
+           errno = EFBIG;
+#elif defined(EOVERFLOW)
+           errno = EOVERFLOW;
+#else
+#error "What status should be returned for file size out of range?"
+#endif
+           return -1;
+       }
+
+#   undef OUT_OF_RANGE
+#   undef OUT_OF_URANGE
+#endif /* !TCL_WIDE_INT_IS_LONG */
+
+       /*
+        * 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
+        * obsolete interface.
+        */
+
+       oldStyleBuf->st_mode    = buf.st_mode;
+       oldStyleBuf->st_ino     = (ino_t) buf.st_ino;
+       oldStyleBuf->st_dev     = buf.st_dev;
+       oldStyleBuf->st_rdev    = buf.st_rdev;
+       oldStyleBuf->st_nlink   = buf.st_nlink;
+       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);
+#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
+       oldStyleBuf->st_blksize = buf.st_blksize;
+#endif
+#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
+#ifdef HAVE_BLKCNT_T
+       oldStyleBuf->st_blocks  = (blkcnt_t) buf.st_blocks;
+#else
+       oldStyleBuf->st_blocks  = (unsigned long) buf.st_blocks;
+#endif
+#endif
+    }
+    return ret;
+}
+\f
+/* Obsolete */
+int
+Tcl_Access(
+    const char *path,          /* Path of file to access (in current CP). */
+    int mode)                  /* Permission setting. */
+{
+    int ret;
+    Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
+
+    Tcl_IncrRefCount(pathPtr);
+    ret = Tcl_FSAccess(pathPtr,mode);
+    Tcl_DecrRefCount(pathPtr);
+
+    return ret;
+}
+\f
+/* Obsolete */
+Tcl_Channel
+Tcl_OpenFileChannel(
+    Tcl_Interp *interp,                /* Interpreter for error reporting; can be
+                                * NULL. */
+    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)           /* If the open involves creating a file, with
+                                * what modes to create it? */
+{
+    Tcl_Channel ret;
+    Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
+
+    Tcl_IncrRefCount(pathPtr);
+    ret = Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions);
+    Tcl_DecrRefCount(pathPtr);
+
+    return ret;
+}
+\f
+/* Obsolete */
+int
+Tcl_Chdir(
+    const char *dirName)
+{
+    int ret;
+    Tcl_Obj *pathPtr = Tcl_NewStringObj(dirName,-1);
+    Tcl_IncrRefCount(pathPtr);
+    ret = Tcl_FSChdir(pathPtr);
+    Tcl_DecrRefCount(pathPtr);
+    return ret;
+}
+\f
+/* Obsolete */
+char *
+Tcl_GetCwd(
+    Tcl_Interp *interp,
+    Tcl_DString *cwdPtr)
+{
+    Tcl_Obj *cwd = Tcl_FSGetCwd(interp);
+
+    if (cwd == NULL) {
+       return NULL;
+    }
+    Tcl_DStringInit(cwdPtr);
+    TclDStringAppendObj(cwdPtr, cwd);
+    Tcl_DecrRefCount(cwd);
+    return Tcl_DStringValue(cwdPtr);
+}
+\f
+int
+Tcl_EvalFile(
+    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);
+    return ret;
+}
+\f
+/*
+ * Now move on to the basic filesystem implementation.
+ */
+
+static void
+FsThrExitProc(
+    ClientData cd)
+{
+    ThreadSpecificData *tsdPtr = cd;
+    FilesystemRecord *fsRecPtr = NULL, *tmpFsRecPtr = NULL;
+
+    /*
+     * Trash the cwd copy.
+     */
+
+    if (tsdPtr->cwdPathPtr != NULL) {
+       Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
+       tsdPtr->cwdPathPtr = NULL;
+    }
+    if (tsdPtr->cwdClientData != NULL) {
+       NativeFreeInternalRep(tsdPtr->cwdClientData);
+    }
+
+    /*
+     * Trash the filesystems cache.
+     */
+
+    fsRecPtr = tsdPtr->filesystemList;
+    while (fsRecPtr != NULL) {
+       tmpFsRecPtr = fsRecPtr->nextPtr;
+       fsRecPtr->fsPtr = NULL;
+       ckfree(fsRecPtr);
+       fsRecPtr = tmpFsRecPtr;
+    }
+    tsdPtr->filesystemList = NULL;
+    tsdPtr->initialized = 0;
+}
+\f
+int
+TclFSCwdIsNative(void)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+
+    if (tsdPtr->cwdClientData != NULL) {
+       return 1;
+    } else {
+       return 0;
+    }
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclFSCwdPointerEquals --
+ *
+ *     Check whether the current working directory is equal to the path
+ *     given.
+ *
+ * Results:
+ *     1 (equal) or 0 (un-equal) as appropriate.
+ *
+ * Side effects:
+ *     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).
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TclFSCwdPointerEquals(
+    Tcl_Obj **pathPtrPtr)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+
+    Tcl_MutexLock(&cwdMutex);
+    if (tsdPtr->cwdPathPtr == NULL
+           || tsdPtr->cwdPathEpoch != cwdPathEpoch) {
+       if (tsdPtr->cwdPathPtr != NULL) {
+           Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
+       }
+       if (tsdPtr->cwdClientData != NULL) {
+           NativeFreeInternalRep(tsdPtr->cwdClientData);
+       }
+       if (cwdPathPtr == NULL) {
+           tsdPtr->cwdPathPtr = NULL;
+       } else {
+           tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr);
+           Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
+       }
+       if (cwdClientData == NULL) {
+           tsdPtr->cwdClientData = NULL;
+       } else {
+           tsdPtr->cwdClientData = TclNativeDupInternalRep(cwdClientData);
+       }
+       tsdPtr->cwdPathEpoch = cwdPathEpoch;
+    }
+    Tcl_MutexUnlock(&cwdMutex);
+
+    if (tsdPtr->initialized == 0) {
+       Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr);
+       tsdPtr->initialized = 1;
+    }
+
+    if (pathPtrPtr == NULL) {
+       return (tsdPtr->cwdPathPtr == NULL);
+    }
+
+    if (tsdPtr->cwdPathPtr == *pathPtrPtr) {
+       return 1;
+    } else {
+       int len1, len2;
+       const char *str1, *str2;
+
+       str1 = Tcl_GetStringFromObj(tsdPtr->cwdPathPtr, &len1);
+       str2 = Tcl_GetStringFromObj(*pathPtrPtr, &len2);
+       if ((len1 == len2) && !memcmp(str1, str2, len1)) {
+           /*
+            * They are equal, but different objects. Update so they will be
+            * the same object in the future.
+            */
+
+           Tcl_DecrRefCount(*pathPtrPtr);
+           *pathPtrPtr = tsdPtr->cwdPathPtr;
+           Tcl_IncrRefCount(*pathPtrPtr);
+           return 1;
+       } else {
+           return 0;
+       }
+    }
+}
+\f
+static void
+FsRecacheFilesystemList(void)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+    FilesystemRecord *fsRecPtr, *tmpFsRecPtr = NULL, *toFree = NULL, *list;
+
+    /*
+     * Trash the current cache.
+     */
+
+    fsRecPtr = tsdPtr->filesystemList;
+    while (fsRecPtr != NULL) {
+       tmpFsRecPtr = fsRecPtr->nextPtr;
+       fsRecPtr->nextPtr = toFree;
+       toFree = fsRecPtr;
+       fsRecPtr = tmpFsRecPtr;
+    }
+
+    /*
+     * Locate tail of the global filesystem list.
+     */
+
+    Tcl_MutexLock(&filesystemMutex);
+    fsRecPtr = filesystemList;
+    while (fsRecPtr != NULL) {
+       tmpFsRecPtr = fsRecPtr;
+       fsRecPtr = fsRecPtr->nextPtr;
+    }
+
+    /*
+     * Refill the cache honouring the order.
+     */
+
+    list = NULL;
+    fsRecPtr = tmpFsRecPtr;
+    while (fsRecPtr != NULL) {
+       tmpFsRecPtr = ckalloc(sizeof(FilesystemRecord));
+       *tmpFsRecPtr = *fsRecPtr;
+       tmpFsRecPtr->nextPtr = list;
+       tmpFsRecPtr->prevPtr = NULL;
+       list = tmpFsRecPtr;
+       fsRecPtr = fsRecPtr->prevPtr;
+    }
+    tsdPtr->filesystemList = list;
+    tsdPtr->filesystemEpoch = theFilesystemEpoch;
+    Tcl_MutexUnlock(&filesystemMutex);
+
+    while (toFree) {
+       FilesystemRecord *next = toFree->nextPtr;
+       toFree->fsPtr = NULL;
+       ckfree(toFree);
+       toFree = next;
+    }
+
+    /*
+     * Make sure the above gets released on thread exit.
+     */
+
+    if (tsdPtr->initialized == 0) {
+       Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr);
+       tsdPtr->initialized = 1;
+    }
+}
+\f
+static FilesystemRecord *
+FsGetFirstFilesystem(void)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+    if (tsdPtr->filesystemList == NULL || ((tsdPtr->claims == 0)
+           && (tsdPtr->filesystemEpoch != theFilesystemEpoch))) {
+       FsRecacheFilesystemList();
+    }
+    return tsdPtr->filesystemList;
+}
+\f
+/*
+ * The epoch can be changed by filesystems being added or removed, by changing
+ * the "system encoding" and by env(HOME) changing.
+ */
+
+int
+TclFSEpochOk(
+    size_t filesystemEpoch)
+{
+    return (filesystemEpoch == 0 || filesystemEpoch == theFilesystemEpoch);
+}
+
+static void
+Claim(void)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+
+    tsdPtr->claims++;
+}
+
+static void
+Disclaim(void)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+
+    tsdPtr->claims--;
+}
+
+size_t
+TclFSEpoch(void)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+
+    return tsdPtr->filesystemEpoch;
+}
+
+\f
+/*
+ * If non-NULL, clientData is owned by us and must be freed later.
+ */
+
+static void
+FsUpdateCwd(
+    Tcl_Obj *cwdObj,
+    ClientData clientData)
+{
+    int len;
+    const char *str = NULL;
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+
+    if (cwdObj != NULL) {
+       str = Tcl_GetStringFromObj(cwdObj, &len);
+    }
+
+    Tcl_MutexLock(&cwdMutex);
+    if (cwdPathPtr != NULL) {
+       Tcl_DecrRefCount(cwdPathPtr);
+    }
+    if (cwdClientData != NULL) {
+       NativeFreeInternalRep(cwdClientData);
+    }
+
+    if (cwdObj == NULL) {
+       cwdPathPtr = NULL;
+       cwdClientData = NULL;
+    } else {
+       /*
+        * This must be stored as string obj!
+        */
+
+       cwdPathPtr = Tcl_NewStringObj(str, len);
+       Tcl_IncrRefCount(cwdPathPtr);
+       cwdClientData = TclNativeDupInternalRep(clientData);
+    }
+
+    if (++cwdPathEpoch == 0) {
+       ++cwdPathEpoch;
+    }
+    tsdPtr->cwdPathEpoch = cwdPathEpoch;
+    Tcl_MutexUnlock(&cwdMutex);
+
+    if (tsdPtr->cwdPathPtr) {
+       Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
+    }
+    if (tsdPtr->cwdClientData) {
+       NativeFreeInternalRep(tsdPtr->cwdClientData);
+    }
+
+    if (cwdObj == NULL) {
+       tsdPtr->cwdPathPtr = NULL;
+       tsdPtr->cwdClientData = NULL;
+    } else {
+       tsdPtr->cwdPathPtr = Tcl_NewStringObj(str, len);
+       tsdPtr->cwdClientData = clientData;
+       Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
+    }
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclFinalizeFilesystem --
+ *
+ *     Clean up the filesystem. After this, calls to all Tcl_FS... functions
+ *     will fail.
+ *
+ *     We will later call TclResetFilesystem to restore the FS to a pristine
+ *     state.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     Frees any memory allocated by the filesystem.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclFinalizeFilesystem(void)
+{
+    FilesystemRecord *fsRecPtr;
+
+    /*
+     * Assumption that only one thread is active now. Otherwise we would need
+     * to put various mutexes around this code.
+     */
+
+    if (cwdPathPtr != NULL) {
+       Tcl_DecrRefCount(cwdPathPtr);
+       cwdPathPtr = NULL;
+       cwdPathEpoch = 0;
+    }
+    if (cwdClientData != NULL) {
+       NativeFreeInternalRep(cwdClientData);
+       cwdClientData = NULL;
+    }
+
+    /*
+     * Remove all filesystems, freeing any allocated memory that is no longer
+     * needed.
+     */
+
+    fsRecPtr = filesystemList;
+    while (fsRecPtr != NULL) {
+       FilesystemRecord *tmpFsRecPtr = fsRecPtr->nextPtr;
+
+       /* The native filesystem is static, so we don't free it. */
+
+       if (fsRecPtr != &nativeFilesystemRecord) {
+           ckfree(fsRecPtr);
+       }
+       fsRecPtr = tmpFsRecPtr;
+    }
+    if (++theFilesystemEpoch == 0) {
+       ++theFilesystemEpoch;
+    }
+    filesystemList = NULL;
+
+    /*
+     * Now filesystemList is NULL. This means that any attempt to use the
+     * filesystem is likely to fail.
+     */
+
+#ifdef _WIN32
+    TclWinEncodingsCleanup();
+#endif
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclResetFilesystem --
+ *
+ *     Restore the filesystem to a pristine state.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+TclResetFilesystem(void)
+{
+    filesystemList = &nativeFilesystemRecord;
+    if (++theFilesystemEpoch == 0) {
+       ++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
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSRegister --
+ *
+ *     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.
+ *
+ *     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:
+ *     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 filesystems.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSRegister(
+    ClientData clientData,     /* Client specific data for this fs. */
+    const Tcl_Filesystem *fsPtr)/* The filesystem record for the new fs. */
+{
+    FilesystemRecord *newFilesystemPtr;
+
+    if (fsPtr == NULL) {
+       return TCL_ERROR;
+    }
+
+    newFilesystemPtr = 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;
+    newFilesystemPtr->prevPtr = NULL;
+    if (filesystemList) {
+       filesystemList->prevPtr = newFilesystemPtr;
+    }
+    filesystemList = newFilesystemPtr;
+
+    /*
+     * Increment the filesystem epoch counter, since existing paths might
+     * conceivably now belong to different filesystems.
+     */
+
+    if (++theFilesystemEpoch == 0) {
+       ++theFilesystemEpoch;
+    }
+    Tcl_MutexUnlock(&filesystemMutex);
+
+    return TCL_OK;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSUnregister --
+ *
+ *     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, TCL_ERROR
+ *     otherwise.
+ *
+ * Side effects:
+ *     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. */
+{
+    int retVal = TCL_ERROR;
+    FilesystemRecord *fsRecPtr;
+
+    Tcl_MutexLock(&filesystemMutex);
+
+    /*
+     * 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;
+    while ((retVal == TCL_ERROR) && (fsRecPtr != &nativeFilesystemRecord)) {
+       if (fsRecPtr->fsPtr == fsPtr) {
+           if (fsRecPtr->prevPtr) {
+               fsRecPtr->prevPtr->nextPtr = fsRecPtr->nextPtr;
+           } else {
+               filesystemList = fsRecPtr->nextPtr;
+           }
+           if (fsRecPtr->nextPtr) {
+               fsRecPtr->nextPtr->prevPtr = fsRecPtr->prevPtr;
+           }
+
+           /*
+            * 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;
+           }
+
+           ckfree(fsRecPtr);
+
+           retVal = TCL_OK;
+       } else {
+           fsRecPtr = fsRecPtr->nextPtr;
+       }
+    }
+
+    Tcl_MutexUnlock(&filesystemMutex);
+    return retVal;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSMatchInDirectory --
+ *
+ *     This routine is used by the globbing code to search a directory for
+ *     all files which match a given pattern. The appropriate function for
+ *     the filesystem to which pathPtr belongs will be called. If pathPtr
+ *     does not belong to any filesystem and if it is NULL or the empty
+ *     string, then we assume the pattern is to be matched in the current
+ *     working directory. To avoid 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:
+ *
+ *     The return value is a standard Tcl result indicating whether an error
+ *     occurred in globbing. Error messages are placed in interp, but good
+ *     results are placed in the resultPtr given.
+ *
+ *     Recursive searches, e.g.
+ *             glob -dir $dir -join * pkgIndex.tcl
+ *     which must recurse through each directory matching '*' are handled
+ *     internally by Tcl, by passing specific flags in a modified 'types'
+ *     parameter. This means the actual filesystem only ever sees patterns
+ *     which match in a single directory.
+ *
+ * Side effects:
+ *     The interpreter may have an error message inserted into it.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSMatchInDirectory(
+    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;
+    int resLength, i, ret = -1;
+
+    if (types != NULL && (types->type & TCL_GLOB_TYPE_MOUNT)) {
+       /*
+        * 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;
+    }
+
+    if (pathPtr != NULL) {
+       fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+    } else {
+       fsPtr = NULL;
+    }
+
+    /*
+     * 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);
+       if (ret == TCL_OK && pattern != NULL) {
+           FsAddMountsToGlobResult(resultPtr, pathPtr, pattern, types);
+       }
+       return ret;
+    }
+
+    /*
+     * 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;
+    }
+
+    /*
+     * 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));
+       }
+       return TCL_ERROR;
+    }
+
+    fsPtr = Tcl_FSGetFileSystemForPath(cwd);
+    if (fsPtr != NULL && fsPtr->matchInDirectoryProc != NULL) {
+       TclNewObj(tmpResultPtr);
+       Tcl_IncrRefCount(tmpResultPtr);
+       ret = fsPtr->matchInDirectoryProc(interp, tmpResultPtr, cwd, pattern,
+               types);
+       if (ret == TCL_OK) {
+           FsAddMountsToGlobResult(tmpResultPtr, cwd, pattern, types);
+
+           /*
+            * Note that we know resultPtr and tmpResultPtr are distinct.
+            */
+
+           ret = Tcl_ListObjGetElements(interp, tmpResultPtr,
+                   &resLength, &elemsPtr);
+           for (i=0 ; ret==TCL_OK && i<resLength ; i++) {
+               ret = Tcl_ListObjAppendElement(interp, resultPtr,
+                       TclFSMakePathRelative(interp, elemsPtr[i], cwd));
+           }
+       }
+       TclDecrRefCount(tmpResultPtr);
+    }
+    Tcl_DecrRefCount(cwd);
+    return ret;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * FsAddMountsToGlobResult --
+ *
+ *     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:
+ *     Modifies the resultPtr.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+FsAddMountsToGlobResult(
+    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. */
+{
+    int mLength, gLength, i;
+    int dir = (types == NULL || (types->type & TCL_GLOB_TYPE_DIR));
+    Tcl_Obj *mounts = FsListMounts(pathPtr, pattern);
+
+    if (mounts == NULL) {
+       return;
+    }
+
+    if (Tcl_ListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) {
+       goto endOfMounts;
+    }
+    if (Tcl_ListObjLength(NULL, resultPtr, &gLength) != TCL_OK) {
+       goto endOfMounts;
+    }
+    for (i=0 ; i<mLength ; i++) {
+       Tcl_Obj *mElt;
+       int j;
+       int found = 0;
+
+       Tcl_ListObjIndex(NULL, mounts, i, &mElt);
+
+       for (j=0 ; j<gLength ; j++) {
+           Tcl_Obj *gElt;
+
+           Tcl_ListObjIndex(NULL, resultPtr, j, &gElt);
+           if (Tcl_FSEqualPaths(mElt, gElt)) {
+               found = 1;
+               if (!dir) {
+                   /*
+                    * We don't want to list this.
+                    */
+
+                   Tcl_ListObjReplace(NULL, resultPtr, j, 1, 0, NULL);
+                   gLength--;
+               }
+               break;          /* Break out of for loop. */
+           }
+       }
+       if (!found && dir) {
+           Tcl_Obj *norm;
+           int len, mlen;
+
+           /*
+            * 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 = Tcl_GetStringFromObj(mElt, &mlen);
+               path = Tcl_GetStringFromObj(norm, &len);
+               if (path[len-1] == '/') {
+                   /*
+                    * Deal with the root of the volume.
+                    */
+
+                   len--;
+               }
+               len++; /* account for '/' in the mElt [Bug 1602539] */
+               mElt = TclNewFSPathObj(pathPtr, mount + len, mlen - len);
+               Tcl_ListObjAppendElement(NULL, resultPtr, mElt);
+           }
+           /*
+            * No need to increment gLength, since we don't want to compare
+            * mounts against mounts.
+            */
+       }
+    }
+
+  endOfMounts:
+    Tcl_DecrRefCount(mounts);
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSMountsChanged --
+ *
+ *     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 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) 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) when additional mount points are established inside any existing
+ *     filesystem (except the native fs)
+ *
+ *     (3) when any filesystem (except the native fs) changes the list of
+ *     available volumes.
+ *
+ *     (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 any registered filesystem must
+ *     make sure it calls this function when those situations occur.
+ *
+ *     (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(
+    const Tcl_Filesystem *fsPtr)
+{
+    /*
+     * 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 counter, since existing paths might now
+     * belong to different filesystems.
+     */
+
+    Tcl_MutexLock(&filesystemMutex);
+    if (++theFilesystemEpoch == 0) {
+       ++theFilesystemEpoch;
+    }
+    Tcl_MutexUnlock(&filesystemMutex);
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSData --
+ *
+ *     Retrieve the clientData field for the filesystem given, or NULL if
+ *     that filesystem is not registered.
+ *
+ * Results:
+ *     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.
+ *
+ *----------------------------------------------------------------------
+ */
+
+ClientData
+Tcl_FSData(
+    const Tcl_Filesystem *fsPtr) /* The filesystem record to query. */
+{
+    ClientData retVal = NULL;
+    FilesystemRecord *fsRecPtr = FsGetFirstFilesystem();
+
+    /*
+     * 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)) {
+       if (fsRecPtr->fsPtr == fsPtr) {
+           retVal = fsRecPtr->clientData;
+       }
+       fsRecPtr = fsRecPtr->nextPtr;
+    }
+
+    return retVal;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclFSNormalizeToUniquePath --
+ *
+ *     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:
+ *     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 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).
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TclFSNormalizeToUniquePath(
+    Tcl_Interp *interp,                /* Used for error messages. */
+    Tcl_Obj *pathPtr,          /* The path to normalize in place. */
+    int startAt)               /* Start at this char-offset. */
+{
+    FilesystemRecord *fsRecPtr, *firstFsRecPtr;
+
+    /*
+     * 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).
+     */
+
+    firstFsRecPtr = FsGetFirstFilesystem();
+
+    Claim();
+    for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) {
+       if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
+           continue;
+       }
+
+       /*
+        * TODO: Assume that we always find the native file system; it should
+        * always be there...
+        */
+
+       if (fsRecPtr->fsPtr->normalizePathProc != NULL) {
+           startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr,
+                   startAt);
+       }
+       break;
+    }
+
+    for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) {
+       /*
+        * Skip the native system next time through.
+        */
+
+       if (fsRecPtr->fsPtr == &tclNativeFilesystem) {
+           continue;
+       }
+
+       if (fsRecPtr->fsPtr->normalizePathProc != NULL) {
+           startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr,
+                   startAt);
+       }
+
+       /*
+        * We could add an efficiency check like this:
+        *              if (retVal == length-of(pathPtr)) {break;}
+        * but there's not much benefit.
+        */
+    }
+    Disclaim();
+
+    return startAt;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclGetOpenMode --
+ *
+ *     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:
+ *     Same as TclGetOpenModeEx().
+ *
+ * Side effects:
+ *     Same as TclGetOpenModeEx().
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TclGetOpenMode(
+    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);
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclGetOpenModeEx --
+ *
+ *     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:
+ *     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 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:
+ *     This code is based on a prototype implementation contributed by Mark
+ *     Diekhans.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TclGetOpenModeEx(
+    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 *binaryPtr)            /* Set this to 1 if the caller should
+                                * configure the opened channel for binary
+                                * operations. */
+{
+    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 (e.g. "r"). They are
+     * distinguished from the POSIX access modes by the presence of a
+     * lower-case first letter.
+     */
+
+    *seekFlagPtr = 0;
+    *binaryPtr = 0;
+    mode = 0;
+
+    /*
+     * Guard against international characters before using byte oriented
+     * routines.
+     */
+
+    if (!(modeString[0] & 0x80)
+           && islower(UCHAR(modeString[0]))) { /* INTL: ISO only. */
+       switch (modeString[0]) {
+       case 'r':
+           mode = O_RDONLY;
+           break;
+       case 'w':
+           mode = O_WRONLY|O_CREAT|O_TRUNC;
+           break;
+       case 'a':
+           /*
+            * Added O_APPEND for proper automatic seek-to-end-on-write by the
+            * OS. [Bug 680143]
+            */
+
+           mode = O_WRONLY|O_CREAT|O_APPEND;
+           *seekFlagPtr = 1;
+           break;
+       default:
+           goto error;
+       }
+       i = 1;
+       while (i<3 && modeString[i]) {
+           if (modeString[i] == modeString[i-1]) {
+               goto error;
+           }
+           switch (modeString[i++]) {
+           case '+':
+               /*
+                * Must remove the O_APPEND flag so that the seek command
+                * works. [Bug 1773127]
+                */
+
+               mode &= ~(O_RDONLY|O_WRONLY|O_APPEND);
+               mode |= O_RDWR;
+               break;
+           case 'b':
+               *binaryPtr = 1;
+               break;
+           default:
+               goto error;
+           }
+       }
+       if (modeString[i] != 0) {
+           goto error;
+       }
+       return mode;
+
+    error:
+       *seekFlagPtr = 0;
+       *binaryPtr = 0;
+       if (interp != NULL) {
+           Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+                   "illegal access mode \"%s\"", modeString));
+       }
+       return -1;
+    }
+
+    /*
+     * The access modes are specified using a list of POSIX modes such as
+     * O_CREAT.
+     *
+     * 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) {
+       if (interp != NULL) {
+           Tcl_AddErrorInfo(interp,
+                   "\n    while processing open access modes \"");
+           Tcl_AddErrorInfo(interp, modeString);
+           Tcl_AddErrorInfo(interp, "\"");
+       }
+       return -1;
+    }
+
+    gotRW = 0;
+    for (i = 0; i < modeArgc; i++) {
+       flag = modeArgv[i];
+       c = flag[0];
+       if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {
+           mode = (mode & ~RW_MODES) | O_RDONLY;
+           gotRW = 1;
+       } else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {
+           mode = (mode & ~RW_MODES) | O_WRONLY;
+           gotRW = 1;
+       } else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {
+           mode = (mode & ~RW_MODES) | O_RDWR;
+           gotRW = 1;
+       } else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {
+           mode |= O_APPEND;
+           *seekFlagPtr = 1;
+       } else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {
+           mode |= O_CREAT;
+       } else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {
+           mode |= O_EXCL;
+
+       } else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {
+#ifdef O_NOCTTY
+           mode |= O_NOCTTY;
+#else
+           if (interp != NULL) {
+               Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+                       "access mode \"%s\" not supported by this system",
+                       flag));
+           }
+           ckfree(modeArgv);
+           return -1;
+#endif
+
+       } else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {
+#ifdef O_NONBLOCK
+           mode |= O_NONBLOCK;
+#else
+           if (interp != NULL) {
+               Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+                       "access mode \"%s\" not supported by this system",
+                       flag));
+           }
+           ckfree(modeArgv);
+           return -1;
+#endif
+
+       } else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {
+           mode |= O_TRUNC;
+       } else if ((c == 'B') && (strcmp(flag, "BINARY") == 0)) {
+           *binaryPtr = 1;
+       } 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));
+           }
+           ckfree(modeArgv);
+           return -1;
+       }
+    }
+
+    ckfree(modeArgv);
+
+    if (!gotRW) {
+       if (interp != NULL) {
+           Tcl_SetObjResult(interp, Tcl_NewStringObj(
+                   "access mode must include either RDONLY, WRONLY, or RDWR",
+                   -1));
+       }
+       return -1;
+    }
+    return mode;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSEvalFile, Tcl_FSEvalFileEx, TclNREvalFile --
+ *
+ *     Read in a file and process the entire file as one gigantic Tcl
+ *     command. Tcl_FSEvalFile is Tcl_FSEvalFileEx without encoding argument.
+ *     TclNREvalFile is an NRE-enabled version of Tcl_FSEvalFileEx.
+ *
+ * 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:
+ *     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 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);
+}
+
+int
+Tcl_FSEvalFileEx(
+    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. */
+{
+    int length, result = TCL_ERROR;
+    Tcl_StatBuf statBuf;
+    Tcl_Obj *oldScriptFile;
+    Interp *iPtr;
+    const char *string;
+    Tcl_Channel chan;
+    Tcl_Obj *objPtr;
+
+    if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
+       return result;
+    }
+
+    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 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)));
+       return result;
+    }
+
+    /*
+     * 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", "\32 {}");
+
+    /*
+     * 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) {
+       if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName)
+               != TCL_OK) {
+           Tcl_Close(interp,chan);
+           return result;
+       }
+    }
+
+    objPtr = Tcl_NewObj();
+    Tcl_IncrRefCount(objPtr);
+
+    /*
+     * 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) < 0) {
+       Tcl_Close(interp, chan);
+       Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+               "couldn't read file \"%s\": %s",
+               Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
+       goto end;
+    }
+    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, -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)));
+       goto end;
+    }
+
+    if (Tcl_Close(interp, chan) != TCL_OK) {
+       goto end;
+    }
+
+    iPtr = (Interp *) interp;
+    oldScriptFile = iPtr->scriptFile;
+    iPtr->scriptFile = pathPtr;
+    Tcl_IncrRefCount(iPtr->scriptFile);
+    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);
+
+    /*
+     * 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) {
+       Tcl_DecrRefCount(iPtr->scriptFile);
+    }
+    iPtr->scriptFile = oldScriptFile;
+
+    if (result == TCL_RETURN) {
+       result = TclUpdateReturnInfo(iPtr);
+    } else if (result == TCL_ERROR) {
+       /*
+        * Record information telling where the error occurred.
+        */
+
+       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)));
+    }
+
+  end:
+    Tcl_DecrRefCount(objPtr);
+    return result;
+}
+
+int
+TclNREvalFile(
+    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_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;
+    }
+
+    /*
+     * 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", "\32 {}");
+
+    /*
+     * 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) {
+       if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName)
+               != TCL_OK) {
+           Tcl_Close(interp,chan);
+           return TCL_ERROR;
+       }
+    }
+
+    objPtr = Tcl_NewObj();
+    Tcl_IncrRefCount(objPtr);
+
+    /*
+     * 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) < 0) {
+       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, -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_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: Force the evaluator to open a frame for a sourced file.
+     */
+
+    iPtr->evalFlags |= TCL_EVAL_FILE;
+    TclNRAddCallback(interp, EvalFileCallback, oldScriptFile, pathPtr, objPtr,
+           NULL);
+    return TclNREvalObjEx(interp, objPtr, 0, NULL, INT_MIN);
+}
+
+static int
+EvalFileCallback(
+    ClientData data[],
+    Tcl_Interp *interp,
+    int result)
+{
+    Interp *iPtr = (Interp *) interp;
+    Tcl_Obj *oldScriptFile = data[0];
+    Tcl_Obj *pathPtr = data[1];
+    Tcl_Obj *objPtr = data[2];
+
+    /*
+     * 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) {
+       Tcl_DecrRefCount(iPtr->scriptFile);
+    }
+    iPtr->scriptFile = oldScriptFile;
+
+    if (result == TCL_RETURN) {
+       result = TclUpdateReturnInfo(iPtr);
+    } else if (result == TCL_ERROR) {
+       /*
+        * Record information telling where the error occurred.
+        */
+
+       int length;
+       const char *pathString = Tcl_GetStringFromObj(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;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_GetErrno --
+ *
+ *     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 value of the Tcl error code variable.
+ *
+ * Side effects:
+ *     None. Note that the value of the Tcl error code variable is UNDEFINED
+ *     if a call to Tcl_SetErrno did not precede this call.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_GetErrno(void)
+{
+    /*
+     * On some platforms, errno is really a thread local (implemented by the C
+     * library).
+     */
+
+    return errno;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_SetErrno --
+ *
+ *     Sets the Tcl error code variable to the supplied value. On some saner
+ *     platforms this is actually a thread-local (this is implemented in the
+ *     C library) but this is *really* unsafe to assume!
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     Modifies the value of the Tcl error code variable.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void
+Tcl_SetErrno(
+    int err)                   /* The new value. */
+{
+    /*
+     * On some platforms, errno is really a thread local (implemented by the C
+     * library).
+     */
+
+    errno = err;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_PosixError --
+ *
+ *     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:
+ *     The return value is a human-readable string describing the error.
+ *
+ * Side effects:
+ *     The errorCode field of the interp is set.
+ *
+ *----------------------------------------------------------------------
+ */
+
+const char *
+Tcl_PosixError(
+    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, NULL);
+    }
+    return msg;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSStat --
+ *
+ *     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.
+ *
+ * Side effects:
+ *     See stat documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSStat(
+    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 && fsPtr->statProc != NULL) {
+       return fsPtr->statProc(pathPtr, buf);
+    }
+    Tcl_SetErrno(ENOENT);
+    return -1;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSLstat --
+ *
+ *     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.
+ *
+ * Side effects:
+ *     See lstat documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSLstat(
+    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_SetErrno(ENOENT);
+    return -1;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSAccess --
+ *
+ *     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.
+ *
+ * Side effects:
+ *     See access documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSAccess(
+    Tcl_Obj *pathPtr,          /* Path of file to access (in current CP). */
+    int mode)                  /* Permission setting. */
+{
+    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+
+    if (fsPtr != NULL && fsPtr->accessProc != NULL) {
+       return fsPtr->accessProc(pathPtr, mode);
+    }
+    Tcl_SetErrno(ENOENT);
+    return -1;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSOpenFileChannel --
+ *
+ *     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.
+ *
+ * Side effects:
+ *     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; 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)           /* If the open involves creating a file, with
+                                * what modes to create it? */
+{
+    const Tcl_Filesystem *fsPtr;
+    Tcl_Channel retVal = NULL;
+
+    /*
+     * We need this just to ensure we return the correct error messages under
+     * some circumstances.
+     */
+
+    if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
+       return NULL;
+    }
+
+    fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+    if (fsPtr != NULL && fsPtr->openFileChannelProc != NULL) {
+       int mode, seekFlag, binary;
+
+       /*
+        * 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;
+       }
+
+       /*
+        * Do the actual open() call.
+        */
+
+       retVal = fsPtr->openFileChannelProc(interp, pathPtr, mode,
+               permissions);
+       if (retVal == NULL) {
+           return NULL;
+       }
+
+       /*
+        * 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)));
+           }
+           Tcl_Close(NULL, retVal);
+           return NULL;
+       }
+       if (binary) {
+           Tcl_SetChannelOption(interp, retVal, "-translation", "binary");
+       }
+       return retVal;
+    }
+
+    /*
+     * File doesn't belong to any filesystem that can open it.
+     */
+
+    Tcl_SetErrno(ENOENT);
+    if (interp != NULL) {
+       Tcl_SetObjResult(interp, Tcl_ObjPrintf(
+               "couldn't open \"%s\": %s",
+               Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
+    }
+    return NULL;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSUtime --
+ *
+ *     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.
+ *
+ * Side effects:
+ *     See utime documentation.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSUtime(
+    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);
+
+    if (fsPtr != NULL && fsPtr->utimeProc != NULL) {
+       return fsPtr->utimeProc(pathPtr, tval);
+    }
+    /* TODO: set errno here? Tcl_SetErrno(ENOENT); */
+    return -1;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * NativeFileAttrStrings --
+ *
+ *     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
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static const char *const *
+NativeFileAttrStrings(
+    Tcl_Obj *pathPtr,
+    Tcl_Obj **objPtrRef)
+{
+    return tclpFileAttrStrings;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * NativeFileAttrsGet --
+ *
+ *     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. 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.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NativeFileAttrsGet(
+    Tcl_Interp *interp,                /* The interpreter for error reporting. */
+    int index,                 /* index of the attribute command. */
+    Tcl_Obj *pathPtr,          /* path of file we are operating on. */
+    Tcl_Obj **objPtrRef)       /* for output. */
+{
+    return tclpFileAttrProcs[index].getProc(interp, index, pathPtr,objPtrRef);
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * NativeFileAttrsSet --
+ *
+ *     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:
+ *     Standard Tcl return code.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static int
+NativeFileAttrsSet(
+    Tcl_Interp *interp,                /* The interpreter for error reporting. */
+    int index,                 /* index of the attribute command. */
+    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);
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSFileAttrStrings --
+ *
+ *     This function implements part of the hookable 'file attributes'
+ *     subcommand. The appropriate function for the filesystem to which
+ *     pathPtr belongs will be called.
+ *
+ * Results:
+ *     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.
+ *
+ *----------------------------------------------------------------------
+ */
+
+const char *const *
+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);
+    }
+    Tcl_SetErrno(ENOENT);
+    return NULL;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclFSFileAttrIndex --
+ *
+ *     Helper function for converting an attribute name to an index into the
+ *     attribute table.
+ *
+ * Results:
+ *     Tcl result code, index written to *indexPtr on result==TCL_OK
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+TclFSFileAttrIndex(
+    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;
+
+    /*
+     * Get the attribute table for the file.
+     */
+
+    attrTable = Tcl_FSFileAttrStrings(pathPtr, &listObj);
+    if (listObj != NULL) {
+       Tcl_IncrRefCount(listObj);
+    }
+
+    if (attrTable != NULL) {
+       /*
+        * It's a constant attribute table, so use T_GIFO.
+        */
+
+       Tcl_Obj *tmpObj = Tcl_NewStringObj(attributeName, -1);
+       int result;
+
+       result = Tcl_GetIndexFromObj(NULL, tmpObj, attrTable, NULL, TCL_EXACT,
+               indexPtr);
+       TclDecrRefCount(tmpObj);
+       if (listObj != NULL) {
+           TclDecrRefCount(listObj);
+       }
+       return result;
+    } else if (listObj != NULL) {
+       /*
+        * It's a non-constant attribute list, so do a literal search.
+        */
+
+       int i, objc;
+       Tcl_Obj **objv;
+
+       if (Tcl_ListObjGetElements(NULL, listObj, &objc, &objv) != TCL_OK) {
+           TclDecrRefCount(listObj);
+           return TCL_ERROR;
+       }
+       for (i=0 ; i<objc ; i++) {
+           if (!strcmp(attributeName, TclGetString(objv[i]))) {
+               TclDecrRefCount(listObj);
+               *indexPtr = i;
+               return TCL_OK;
+           }
+       }
+       TclDecrRefCount(listObj);
+       return TCL_ERROR;
+    } else {
+       return TCL_ERROR;
+    }
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSFileAttrsGet --
+ *
+ *     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:
+ *     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.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSFileAttrsGet(
+    Tcl_Interp *interp,                /* The interpreter for error reporting. */
+    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);
+    }
+    Tcl_SetErrno(ENOENT);
+    return -1;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSFileAttrsSet --
+ *
+ *     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:
+ *     Standard Tcl return code.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSFileAttrsSet(
+    Tcl_Interp *interp,                /* The interpreter for error reporting. */
+    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);
+    }
+    Tcl_SetErrno(ENOENT);
+    return -1;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSGetCwd --
+ *
+ *     This function replaces the library version of getcwd().
+ *
+ *     Most VFS's will *not* implement a 'cwdProc'. Tcl now maintains its own
+ *     record (in a Tcl_Obj) of the cwd, and an attempt is made to synch this
+ *     with the cwd's containing filesystem, if that filesystem provides a
+ *     cwdProc (e.g. the native filesystem).
+ *
+ *     Note that if Tcl's cwd is not in the native filesystem, then of course
+ *     Tcl's cwd and the native cwd are different: extensions should
+ *     therefore ensure they only access the cwd through this function to
+ *     avoid confusion.
+ *
+ *     If a global cwdPathPtr already exists, it is 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:
+ *     The result is a pointer to a Tcl_Obj specifying the current directory,
+ *     or NULL if the current directory could not be determined. If NULL is
+ *     returned, an error message is left in the interp's result.
+ *
+ *     The result already has its refCount incremented for the caller. When
+ *     it is no longer needed, that refCount should be decremented.
+ *
+ * Side effects:
+ *     Various objects may be freed and allocated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+Tcl_FSGetCwd(
+    Tcl_Interp *interp)
+{
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+
+    if (TclFSCwdPointerEquals(NULL)) {
+       FilesystemRecord *fsRecPtr;
+       Tcl_Obj *retVal = NULL;
+
+       /*
+        * We've never been called before, try to find a cwd. Call each of the
+        * "Tcl_GetCwd" function in succession. A non-NULL return value
+        * indicates the particular function has succeeded.
+        */
+
+       fsRecPtr = FsGetFirstFilesystem();
+       Claim();
+       for (; (retVal == NULL) && (fsRecPtr != NULL);
+               fsRecPtr = fsRecPtr->nextPtr) {
+           ClientData 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;
+
+               /*
+                * 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_SetObjResult(interp, Tcl_ObjPrintf(
+                       "error getting working directory name: %s",
+                       Tcl_PosixError(interp)));
+           }
+       }
+       Disclaim();
+
+       /*
+        * 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 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.
+                */
+
+               ClientData cd = (ClientData) Tcl_FSGetNativePath(norm);
+
+               FsUpdateCwd(norm, TclNativeDupInternalRep(cd));
+               Tcl_DecrRefCount(norm);
+           }
+           Tcl_DecrRefCount(retVal);
+       }
+    } else {
+       /*
+        * We already have a cwd cached, but we want to give the filesystem it
+        * is in a chance to check whether that cwd has changed, or is perhaps
+        * no longer accessible. This allows an error to be thrown if, say,
+        * the permissions on that directory have changed.
+        */
+
+       const Tcl_Filesystem *fsPtr =
+               Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr);
+       ClientData retCd = NULL;
+       Tcl_Obj *retVal, *norm;
+
+       /*
+        * If the filesystem couldn't be found, or if no cwd function exists
+        * for this filesystem, then we simply assume the cached cwd is ok.
+        * If we do call a cwd, we must watch for errors (if the cwd returns
+        * NULL). This ensures that, say, on Unix if the permissions of the
+        * cwd change, 'pwd' does actually throw the correct error in Tcl.
+        * (This is tested for in the test suite on unix).
+        */
+
+       if (fsPtr == NULL || fsPtr->getCwdProc == NULL) {
+           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.
+            */
+
+           retVal = fsPtr->internalToNormalizedProc(retCd);
+           Tcl_IncrRefCount(retVal);
+       }
+
+       /*
+        * Check if the 'cwd' function returned an error; if so, reset the
+        * cwd.
+        */
+
+       if (retVal == NULL) {
+           FsUpdateCwd(NULL, NULL);
+           goto cdDidNotChange;
+       }
+
+       /*
+        * Normalize the path.
+        */
+
+       norm = TclFSNormalizeAbsolutePath(interp, retVal);
+
+       /*
+        * Check whether cwd has changed from the value previously stored in
+        * cwdPathPtr. Really 'norm' shouldn't be NULL, but we are careful.
+        */
+
+       if (norm == NULL) {
+           /* Do nothing */
+           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;
+           const 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);
+    }
+
+  cdDidNotChange:
+    if (tsdPtr->cwdPathPtr != NULL) {
+       Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
+    }
+
+    return tsdPtr->cwdPathPtr;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSChdir --
+ *
+ *     This function replaces the library version of chdir().
+ *
+ *     The path is normalized and then passed to the filesystem which claims
+ *     it.
+ *
+ * Results:
+ *     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. The global cwdPathPtr may change value.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSChdir(
+    Tcl_Obj *pathPtr)
+{
+    const Tcl_Filesystem *fsPtr, *oldFsPtr = NULL;
+    ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
+    int retVal = -1;
+
+    if (tsdPtr->cwdPathPtr != NULL) {
+       oldFsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr);
+    }
+    if (Tcl_FSGetNormalizedPath(NULL, pathPtr) == NULL) {
+       Tcl_SetErrno(ENOENT);
+       return retVal;
+    }
+
+    fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+    if (fsPtr != NULL) {
+       if (fsPtr->chdirProc != NULL) {
+           /*
+            * If this fails, an appropriate errno will have been stored using
+            * 'Tcl_SetErrno()'.
+            */
+
+           retVal = fsPtr->chdirProc(pathPtr);
+       } else {
+           /*
+            * 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)) {
+               /*
+                * We allow the chdir.
+                */
+
+               retVal = 0;
+           }
+       }
+    } else {
+       Tcl_SetErrno(ENOENT);
+    }
+
+    /*
+     * 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.
+     *
+     * 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) {
+       /*
+        * 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);
+
+       if (normDirName == NULL) {
+           /* Not really true, but what else to do? */
+           Tcl_SetErrno(ENOENT);
+           return -1;
+       }
+
+       if (fsPtr == &tclNativeFilesystem) {
+           /*
+            * 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.
+            */
+
+           ClientData cd;
+           ClientData oldcd = tsdPtr->cwdClientData;
+
+           /*
+            * Assumption we are using a filesystem version 2.
+            */
+
+           TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc;
+
+           cd = proc2(oldcd);
+           if (cd != oldcd) {
+               FsUpdateCwd(normDirName, cd);
+           }
+       } else {
+           FsUpdateCwd(normDirName, NULL);
+       }
+
+       /*
+        * If the filesystem changed between old and new cwd
+        * force filesystem refresh on path objects.
+        */
+       if (oldFsPtr != NULL && fsPtr != oldFsPtr) {
+           Tcl_FSMountsChanged(NULL);
+       }
+    }
+
+    return retVal;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSLoadFile --
+ *
+ *     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, an error message
+ *     is left in the interp's result.
+ *
+ * Side effects:
+ *     New code suddenly appears in memory. This may later be unloaded by
+ *     passing the clientData to the unloadProc.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSLoadFile(
+    Tcl_Interp *interp,                /* Used for error reporting. */
+    Tcl_Obj *pathPtr,          /* Name of the file containing the desired
+                                * code. */
+    const char *sym1, const char *sym2,
+                               /* 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_FSUnloadFileProc **unloadProcPtr)
+                               /* Filled with address of Tcl_FSUnloadFileProc
+                                * function which should be used for this
+                                * file. */
+{
+    const char *symbols[3];
+    void *procPtrs[2];
+    int res;
+
+    /*
+     * Initialize the arrays.
+     */
+
+    symbols[0] = sym1;
+    symbols[1] = sym2;
+    symbols[2] = NULL;
+
+    /*
+     * Perform the load.
+     */
+
+    res = Tcl_LoadFile(interp, pathPtr, symbols, 0, procPtrs, handlePtr);
+    if (res == TCL_OK) {
+       *proc1Ptr = (Tcl_PackageInitProc *) procPtrs[0];
+       *proc2Ptr = (Tcl_PackageInitProc *) procPtrs[1];
+    } else {
+       *proc1Ptr = *proc2Ptr = NULL;
+    }
+
+    return res;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_LoadFile --
+ *
+ *     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.
+ *
+ *     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, an error message
+ *     is left in the interp's result.
+ *
+ * Side effects:
+ *     New code suddenly appears in memory. This may later be unloaded by
+ *     calling TclFS_UnloadFile.
+ *
+ *----------------------------------------------------------------------
+ */
+
+/*
+ * 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)
+{
+    /* Order of testing:
+     * 1. On hpux we generally want to skip unlink in general
+     *
+     * 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
+    return 1;
+#else
+    WCHAR *skipstr;
+
+    skipstr = getenv("TCL_TEMPLOAD_NO_UNLINK");
+    if (skipstr && (skipstr[0] != '\0')) {
+       return atoi(skipstr);
+    }
+
+#ifdef TCL_TEMPLOAD_NO_UNLINK
+#ifndef NO_FSTATFS
+    {
+       struct statfs fs;
+       /* 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 taken.
+        */
+#ifndef AUFS_SUPER_MAGIC
+#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)) {
+           return 1;
+       }
+    }
+#endif /* ... NO_FSTATFS */
+#endif /* ... TCL_TEMPLOAD_NO_UNLINK */
+
+    /* Fallback: !hpux, no EV override, no AUFS (detection, nor detected):
+     * Don't skip */
+    return 0;
+#endif /* hpux */
+}
+
+int
+Tcl_LoadFile(
+    Tcl_Interp *interp,                /* Used for error reporting. */
+    Tcl_Obj *pathPtr,          /* Name of the file containing the desired
+                                * code. */
+    const char *const symbols[],/* Names of functions to look up in the file's
+                                * symbol table. */
+    int flags,                 /* Flags */
+    void *procVPtrs,           /* Where to return the addresses corresponding
+                                * to symbols[]. */
+    Tcl_LoadHandle *handlePtr) /* Filled with token for shared library
+                                * information which can be used in
+                                * TclpFindSymbol. */
+{
+    void **procPtrs = (void **) procVPtrs;
+    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+    const Tcl_Filesystem *copyFsPtr;
+    Tcl_FSUnloadFileProc *unloadProcPtr;
+    Tcl_Obj *copyToPtr;
+    Tcl_LoadHandle newLoadHandle = NULL;
+    Tcl_LoadHandle divertedLoadHandle = 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);
+
+       if (retVal == TCL_OK) {
+           if (*handlePtr == NULL) {
+               return TCL_ERROR;
+           }
+           if (interp) {
+               Tcl_ResetResult(interp);
+           }
+           goto resolveSymbols;
+       }
+       if (Tcl_GetErrno() != EXDEV) {
+           return retVal;
+       }
+    }
+
+    /*
+     * 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)));
+       }
+       return TCL_ERROR;
+    }
+
+#ifdef TCL_LOAD_FROM_MEMORY
+    /*
+     * 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:
+     */
+
+    {
+       int ret, size;
+       void *buffer;
+       Tcl_StatBuf statBuf;
+       Tcl_Channel data;
+
+       ret = Tcl_FSStat(pathPtr, &statBuf);
+       if (ret < 0) {
+           goto mustCopyToTempAnyway;
+       }
+       size = (int) statBuf.st_size;
+
+       /*
+        * Tcl_Read takes an int: check that file size isn't wide.
+        */
+
+       if (size != (Tcl_WideInt) statBuf.st_size) {
+           goto mustCopyToTempAnyway;
+       }
+       data = Tcl_FSOpenFileChannel(interp, pathPtr, "rb", 0666);
+       if (!data) {
+           goto mustCopyToTempAnyway;
+       }
+       buffer = TclpLoadMemoryGetBuffer(interp, size);
+       if (!buffer) {
+           Tcl_Close(interp, data);
+           goto mustCopyToTempAnyway;
+       }
+       ret = Tcl_Read(data, buffer, size);
+       Tcl_Close(interp, data);
+       ret = TclpLoadMemory(interp, buffer, size, ret, handlePtr,
+               &unloadProcPtr, flags);
+       if (ret == TCL_OK && *handlePtr != NULL) {
+           goto resolveSymbols;
+       }
+    }
+
+  mustCopyToTempAnyway:
+    if (interp) {
+       Tcl_ResetResult(interp);
+    }
+#endif /* TCL_LOAD_FROM_MEMORY */
+
+    /*
+     * Get a temporary filename to use, first to copy the file into, and then
+     * to load.
+     */
+
+    copyToPtr = TclpTempFileNameForLibrary(interp, pathPtr);
+    if (copyToPtr == NULL) {
+       return TCL_ERROR;
+    }
+    Tcl_IncrRefCount(copyToPtr);
+
+    copyFsPtr = Tcl_FSGetFileSystemForPath(copyToPtr);
+    if ((copyFsPtr == NULL) || (copyFsPtr == fsPtr)) {
+       /*
+        * 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.
+        */
+
+       Tcl_FSDeleteFile(copyToPtr);
+       Tcl_DecrRefCount(copyToPtr);
+       if (interp) {
+           Tcl_SetObjResult(interp, Tcl_NewStringObj(
+                   "couldn't load from current filesystem", -1));
+       }
+       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
+    /*
+     * 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:
+     */
+
+    {
+       int index;
+       Tcl_Obj *perm;
+
+       TclNewLiteralStringObj(perm, "0700");
+       Tcl_IncrRefCount(perm);
+       if (TclFSFileAttrIndex(copyToPtr, "-permissions", &index) == TCL_OK) {
+           Tcl_FSFileAttrsSet(NULL, index, copyToPtr, perm);
+       }
+       Tcl_DecrRefCount(perm);
+    }
+#endif
+
+    /*
+     * 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);
+    }
+
+    retVal = Tcl_LoadFile(interp, copyToPtr, symbols, flags, procPtrs,
+           &newLoadHandle);
+    if (retVal != TCL_OK) {
+       /*
+        * The file didn't load successfully.
+        */
+
+       Tcl_FSDeleteFile(copyToPtr);
+       Tcl_DecrRefCount(copyToPtr);
+       return retVal;
+    }
+
+    /*
+     * 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)) {
+       Tcl_DecrRefCount(copyToPtr);
+
+       /*
+        * 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);
+       }
+       return TCL_OK;
+    }
+
+    /*
+     * When we unload this file, we need to divert the unloading so we can
+     * unload and cleanup the temporary file correctly.
+     */
+
+    tvdlPtr = ckalloc(sizeof(FsDivertLoad));
+
+    /*
+     * 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) {
+       /*
+        * copyToPtr is already incremented for this reference.
+        */
+
+       tvdlPtr->divertedFile = copyToPtr;
+
+       /*
+        * 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 {
+       /*
+        * We need the native rep.
+        */
+
+       tvdlPtr->divertedFileNativeRep = TclNativeDupInternalRep(
+               Tcl_FSGetInternalRep(copyToPtr, copyFsPtr));
+
+       /*
+        * We don't need or want references to the copied Tcl_Obj or the
+        * filesystem if it is the native one.
+        */
+
+       tvdlPtr->divertedFile = NULL;
+       tvdlPtr->divertedFilesystem = NULL;
+       Tcl_DecrRefCount(copyToPtr);
+    }
+
+    copyToPtr = NULL;
+
+    divertedLoadHandle = ckalloc(sizeof(struct Tcl_LoadHandle_));
+    divertedLoadHandle->clientData = tvdlPtr;
+    divertedLoadHandle->findSymbolProcPtr = DivertFindSymbol;
+    divertedLoadHandle->unloadFileProcPtr = DivertUnloadFile;
+    *handlePtr = divertedLoadHandle;
+
+    if (interp) {
+       Tcl_ResetResult(interp);
+    }
+    return retVal;
+
+  resolveSymbols:
+    /*
+     * At this point, *handlePtr is already set up to the handle for the
+     * loaded library. We now try to 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 report the problem back to the caller.
+                * (Tcl_FindSymbol should already have left an appropriate
+                * error message.)
+                */
+
+               (*handlePtr)->unloadFileProcPtr(*handlePtr);
+               *handlePtr = NULL;
+               return TCL_ERROR;
+           }
+       }
+    }
+    return TCL_OK;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * DivertFindSymbol --
+ *
+ *     Find a symbol in a shared library loaded by copy-from-VFS.
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void *
+DivertFindSymbol(
+    Tcl_Interp *interp,        /* Tcl interpreter */
+    Tcl_LoadHandle loadHandle, /* Handle to the diverted module */
+    const char *symbol)                /* Symbol to resolve */
+{
+    FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData;
+    Tcl_LoadHandle originalHandle = tvdlPtr->loadHandle;
+
+    return originalHandle->findSymbolProcPtr(interp, originalHandle, symbol);
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * DivertUnloadFile --
+ *
+ *     Unloads a file that has been loaded by copying from VFS to the native
+ *     filesystem.
+ *
+ * Parameters:
+ *     loadHandle -- Handle of the file to unload
+ *
+ *----------------------------------------------------------------------
+ */
+
+static void
+DivertUnloadFile(
+    Tcl_LoadHandle loadHandle)
+{
+    FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData;
+    Tcl_LoadHandle originalHandle;
+
+    /*
+     * 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 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);
+
+    /*
+     * What filesystem contains the temp copy of the library?
+     */
+
+    if (tvdlPtr->divertedFilesystem == NULL) {
+       /*
+        * 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 we created. Note, we may crash here
+        * because encodings have been taken down already.
+        */
+
+       if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile)
+               != TCL_OK) {
+           /*
+            * The above may have failed because the filesystem, or something
+            * it depends upon (e.g. encodings) have been taken down because
+            * Tcl is exiting.
+            *
+            * 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 it refuses to pass the internal
+            * representation to the filesystem.
+            */
+       }
+
+       /*
+        * 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);
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FindSymbol --
+ *
+ *     Find a symbol in a loaded library
+ *
+ * Results:
+ *     Returns a pointer to the symbol if found. If not found, returns NULL
+ *     and leaves an error message in the interpreter result.
+ *
+ * This function was once filesystem-specific, but has been made portable by
+ * having TclpDlopen return a structure that includes procedure pointers.
+ *
+ *----------------------------------------------------------------------
+ */
+
+void *
+Tcl_FindSymbol(
+    Tcl_Interp *interp,                /* Tcl interpreter */
+    Tcl_LoadHandle loadHandle, /* Handle to the loaded library */
+    const char *symbol)                /* Name of the symbol to resolve */
+{
+    return loadHandle->findSymbolProcPtr(interp, loadHandle, symbol);
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSUnloadFile --
+ *
+ *     Unloads a library given its handle. Checks first that the library
+ *     supports unloading.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSUnloadFile(
+    Tcl_Interp *interp,                /* Tcl interpreter */
+    Tcl_LoadHandle handle)     /* Handle of the file 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;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclFSUnloadTempFile --
+ *
+ *     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.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     The effects of the 'unload' function called, and of course the
+ *     temporary file will be deleted.
+ *
+ *----------------------------------------------------------------------
+ */
+
+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;
+
+    /*
+     * This test should never trigger, since we give the client data in the
+     * function above.
+     */
+
+    if (tvdlPtr == NULL) {
+       return;
+    }
+
+    /*
+     * 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.
+     */
+
+    if (tvdlPtr->unloadProcPtr != NULL) {
+       tvdlPtr->unloadProcPtr(tvdlPtr->loadHandle);
+    }
+
+    if (tvdlPtr->divertedFilesystem == NULL) {
+       /*
+        * 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 we created. Note, we may crash here
+        * because encodings have been taken down already.
+        */
+
+       if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile)
+               != TCL_OK) {
+           /*
+            * The above may have failed because the filesystem, or something
+            * it depends upon (e.g. encodings) have been taken down because
+            * Tcl is exiting.
+            *
+            * 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 it refuses to pass the internal
+            * representation to the filesystem.
+            */
+       }
+
+       /*
+        * 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);
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSLink --
+ *
+ *     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, 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.
+ *
+ *     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
+ *
+ *     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:
+ *     See readlink() documentation. A new filesystem link object may appear.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+Tcl_FSLink(
+    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 != NULL && fsPtr->linkProc != NULL) {
+       return fsPtr->linkProc(pathPtr, toPtr, linkAction);
+    }
+
+    /*
+     * 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()? */
+#else
+    Tcl_SetErrno(ENOENT);
+#endif /* S_IFLNK */
+    return NULL;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSListVolumes --
+ *
+ *     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.
+ *
+ * Side effects:
+ *     None
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+Tcl_FSListVolumes(void)
+{
+    FilesystemRecord *fsRecPtr;
+    Tcl_Obj *resultPtr = Tcl_NewObj();
+
+    /*
+     * 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.
+     */
+
+    fsRecPtr = FsGetFirstFilesystem();
+    Claim();
+    while (fsRecPtr != NULL) {
+       if (fsRecPtr->fsPtr->listVolumesProc != NULL) {
+           Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc();
+
+           if (thisFsVolumes != NULL) {
+               Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes);
+               Tcl_DecrRefCount(thisFsVolumes);
+           }
+       }
+       fsRecPtr = fsRecPtr->nextPtr;
+    }
+    Disclaim();
+
+    return resultPtr;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * FsListMounts --
+ *
+ *     List all mounts within the given directory, which match the given
+ *     pattern.
+ *
+ * Results:
+ *     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
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+FsListMounts(
+    Tcl_Obj *pathPtr,          /* Contains path to directory to search. */
+    const char *pattern)       /* Pattern to match against. */
+{
+    FilesystemRecord *fsRecPtr;
+    Tcl_GlobTypeData mountsOnly = { TCL_GLOB_TYPE_MOUNT, 0, NULL, NULL };
+    Tcl_Obj *resultPtr = NULL;
+
+    /*
+     * 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) {
+               resultPtr = Tcl_NewObj();
+           }
+           fsRecPtr->fsPtr->matchInDirectoryProc(NULL, resultPtr, pathPtr,
+                   pattern, &mountsOnly);
+       }
+       fsRecPtr = fsRecPtr->nextPtr;
+    }
+    Disclaim();
+
+    return resultPtr;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSSplitPath --
+ *
+ *     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:
+ *     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:
+ *     None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+Tcl_FSSplitPath(
+    Tcl_Obj *pathPtr,          /* Path to split. */
+    int *lenPtr)               /* int to store number of path elements. */
+{
+    Tcl_Obj *result = NULL;    /* Needed only to prevent gcc warnings. */
+    const Tcl_Filesystem *fsPtr;
+    char separator = '/';
+    int driveNameLength;
+    const char *p;
+
+    /*
+     * Perform platform specific splitting.
+     */
+
+    if (TclFSGetPathType(pathPtr, &fsPtr,
+           &driveNameLength) == TCL_PATH_ABSOLUTE) {
+       if (fsPtr == &tclNativeFilesystem) {
+           return TclpNativeSplitPath(pathPtr, lenPtr);
+       }
+    } else {
+       return TclpNativeSplitPath(pathPtr, lenPtr);
+    }
+
+    /*
+     * We assume separators are single characters.
+     */
+
+    if (fsPtr->filesystemSeparatorProc != NULL) {
+       Tcl_Obj *sep = fsPtr->filesystemSeparatorProc(pathPtr);
+
+       if (sep != NULL) {
+           Tcl_IncrRefCount(sep);
+           separator = Tcl_GetString(sep)[0];
+           Tcl_DecrRefCount(sep);
+       }
+    }
+
+    /*
+     * 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)
+     */
+
+    result = Tcl_NewObj();
+    p = Tcl_GetString(pathPtr);
+    Tcl_ListObjAppendElement(NULL, result,
+           Tcl_NewStringObj(p, driveNameLength));
+    p += driveNameLength;
+
+    /*
+     * Add the remaining path elements to the list.
+     */
+
+    for (;;) {
+       const 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);
+           } else {
+               nextElt = Tcl_NewStringObj(elementStart, length);
+           }
+           Tcl_ListObjAppendElement(NULL, result, nextElt);
+       }
+       if (*p++ == '\0') {
+           break;
+       }
+    }
+
+    /*
+     * Compute the number of elements in the result.
+     */
+
+    if (lenPtr != NULL) {
+       TclListObjLength(NULL, result, lenPtr);
+    }
+    return result;
+}
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclGetPathType --
+ *
+ *     Helper function used by FSGetPathType.
+ *
+ * Results:
+ *     Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
+ *     TCL_PATH_VOLUME_RELATIVE. The filesystem reference will be set if and
+ *     only if it is non-NULL and the function's return value is
+ *     TCL_PATH_ABSOLUTE.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_PathType
+TclGetPathType(
+    Tcl_Obj *pathPtr,          /* Path to determine type for. */
+    const 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. */
+{
+    int pathLen;
+    const char *path = Tcl_GetStringFromObj(pathPtr, &pathLen);
+    Tcl_PathType type;
+
+    type = TclFSNonnativePathType(path, pathLen, filesystemPtrPtr,
+           driveNameLengthPtr, driveNameRef);
+
+    if (type != TCL_PATH_ABSOLUTE) {
+       type = TclpGetNativePathType(pathPtr, driveNameLengthPtr,
+               driveNameRef);
+       if ((type == TCL_PATH_ABSOLUTE) && (filesystemPtrPtr != NULL)) {
+           *filesystemPtrPtr = &tclNativeFilesystem;
+       }
+    }
+    return type;
+}
+\f
+/*
+ *----------------------------------------------------------------------
+ *
+ * TclFSNonnativePathType --
+ *
+ *     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:
+ *     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.
+ *
+ * Side effects:
+ *     None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_PathType
+TclFSNonnativePathType(
+    const char *path,          /* Path to determine type for. */
+    int pathLen,               /* Length of the path. */
+    const 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;
+
+    /*
+     * 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) {
+       /*
+        * 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
+        * fsRecPtr->fsPtr->listVolumesProc 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.
+        *
+        * 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)) {
+           int numVolumes;
+           Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc();
+
+           if (thisFsVolumes != NULL) {
+               if (Tcl_ListObjLength(NULL, thisFsVolumes, &numVolumes)
+                       != TCL_OK) {
+                   /*
+                    * This is VERY bad; the listVolumesProc 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 if we could signal an error here
+                    * (but Tcl_Panic seems a bit excessive).
+                    */
+
+                   numVolumes = -1;
+               }
+               while (numVolumes > 0) {
+                   Tcl_Obj *vol;
+                   int len;
+                   const char *strVol;
+
+                   numVolumes--;
+                   Tcl_ListObjIndex(NULL, thisFsVolumes, numVolumes, &vol);
+                   strVol = Tcl_GetStringFromObj(vol,&len);
+                   if (pathLen < len) {
+                       continue;
+                   }
+                   if (strncmp(strVol, path, len) == 0) {
+                       type = TCL_PATH_ABSOLUTE;
+                       if (filesystemPtrPtr != NULL) {
+                           *filesystemPtrPtr = fsRecPtr->fsPtr;
+                       }
+                       if (driveNameLengthPtr != NULL) {
+                           *driveNameLengthPtr = len;
+                       }
+                       if (driveNameRef != NULL) {
+                           *driveNameRef = vol;
+                           Tcl_IncrRefCount(vol);
+                       }
+                       break;
+                   }
+               }
+               Tcl_DecrRefCount(thisFsVolumes);
+               if (type == TCL_PATH_ABSOLUTE) {
+                   /*
+                    * We don't need to examine any more filesystems.
+                    */
+
+                   break;
+               }
+           }
+       }
+       fsRecPtr = fsRecPtr->nextPtr;
+    }
+    Disclaim();
+    return type;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSRenameFile --
+ *
+ *     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:
+ *     Standard Tcl error code if a function was called.
+ *
+ * Side effects:
+ *     A file may be renamed.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+Tcl_FSRenameFile(
+    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 (retVal == -1) {
+       Tcl_SetErrno(EXDEV);
+    }
+    return retVal;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSCopyFile --
+ *
+ *     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.
+ *
+ *     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:
+ *     Standard Tcl error code if a function was called.
+ *
+ * Side effects:
+ *     A file may be copied.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+Tcl_FSCopyFile(
+    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 (retVal == -1) {
+       Tcl_SetErrno(EXDEV);
+    }
+    return retVal;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclCrossFilesystemCopy --
+ *
+ *     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:
+ *     Standard Tcl error code.
+ *
+ * Side effects:
+ *     A file may be created.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TclCrossFilesystemCopy(
+    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;
+    Tcl_Channel in, out;
+    Tcl_StatBuf sourceStatBuf;
+    struct utimbuf tval;
+
+    out = Tcl_FSOpenFileChannel(interp, target, "wb", prot);
+    if (out == NULL) {
+       /*
+        * It looks like we cannot copy it over. Bail out...
+        */
+       goto done;
+    }
+
+    in = Tcl_FSOpenFileChannel(interp, source, "rb", prot);
+    if (in == NULL) {
+       /*
+        * This is very strange, caller should have checked this...
+        */
+
+       Tcl_Close(interp, out);
+       goto done;
+    }
+
+    /*
+     * 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) {
+       result = TCL_OK;
+    }
+
+    /*
+     * If the copy failed, assume that copy channel left a good error message.
+     */
+
+    Tcl_Close(interp, in);
+    Tcl_Close(interp, out);
+
+    /*
+     * Set modification date of copied file.
+     */
+
+    if (Tcl_FSLstat(source, &sourceStatBuf) == 0) {
+       tval.actime = Tcl_GetAccessTimeFromStat(&sourceStatBuf);
+       tval.modtime = Tcl_GetModificationTimeFromStat(&sourceStatBuf);
+       Tcl_FSUtime(target, &tval);
+    }
+
+  done:
+    return result;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSDeleteFile --
+ *
+ *     The appropriate function for the filesystem to which pathPtr belongs
+ *     will be called.
+ *
+ * Results:
+ *     Standard Tcl error code.
+ *
+ * Side effects:
+ *     A file may be deleted.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+Tcl_FSDeleteFile(
+    Tcl_Obj *pathPtr)          /* Pathname of file to be removed (UTF-8). */
+{
+    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+
+    if (fsPtr != NULL && fsPtr->deleteFileProc != NULL) {
+       return fsPtr->deleteFileProc(pathPtr);
+    }
+    Tcl_SetErrno(ENOENT);
+    return -1;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSCreateDirectory --
+ *
+ *     The appropriate function for the filesystem to which pathPtr belongs
+ *     will be called.
+ *
+ * Results:
+ *     Standard Tcl error code.
+ *
+ * Side effects:
+ *     A directory may be created.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+Tcl_FSCreateDirectory(
+    Tcl_Obj *pathPtr)          /* Pathname of directory to create (UTF-8). */
+{
+    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+
+    if (fsPtr != NULL && fsPtr->createDirectoryProc != NULL) {
+       return fsPtr->createDirectoryProc(pathPtr);
+    }
+    Tcl_SetErrno(ENOENT);
+    return -1;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSCopyDirectory --
+ *
+ *     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:
+ *     Standard Tcl error code if a function was called.
+ *
+ * Side effects:
+ *     A directory may be copied.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+Tcl_FSCopyDirectory(
+    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 (retVal == -1) {
+       Tcl_SetErrno(EXDEV);
+    }
+    return retVal;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSRemoveDirectory --
+ *
+ *     The appropriate function for the filesystem to which pathPtr belongs
+ *     will be called.
+ *
+ * Results:
+ *     Standard Tcl error code.
+ *
+ * Side effects:
+ *     A directory may be deleted.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+Tcl_FSRemoveDirectory(
+    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_SetErrno(ENOENT);
+       return -1;
+    }
+
+    /*
+     * When working recursively, we check whether the cwd lies inside this
+     * directory and move it if it does.
+     */
+
+    if (recursive) {
+       Tcl_Obj *cwdPtr = Tcl_FSGetCwd(NULL);
+
+       if (cwdPtr != NULL) {
+           const char *cwdStr, *normPathStr;
+           int cwdLen, normLen;
+           Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr);
+
+           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_FSChdir(dirPtr);
+                   Tcl_DecrRefCount(dirPtr);
+               }
+           }
+           Tcl_DecrRefCount(cwdPtr);
+       }
+    }
+    return fsPtr->removeDirectoryProc(pathPtr, recursive, errorPtr);
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSGetFileSystemForPath --
+ *
+ *     This function determines which filesystem to use for a particular path
+ *     object, and returns the filesystem which accepts this file. If no
+ *     filesystem will accept this object as a valid file path, then NULL is
+ *     returned.
+ *
+ * Results:
+ *     NULL or a filesystem which will accept this path.
+ *
+ * Side effects:
+ *     The object may be converted to a path type.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+const Tcl_Filesystem *
+Tcl_FSGetFileSystemForPath(
+    Tcl_Obj *pathPtr)
+{
+    FilesystemRecord *fsRecPtr;
+    const 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) {
+       Tcl_Panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0");
+       return NULL;
+    }
+
+    /*
+     * 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 first filesystem. This is
+     * accomplished by the FsGetFirstFilesystem() call.
+     */
+
+    fsRecPtr = FsGetFirstFilesystem();
+    Claim();
+
+    if (TclFSEnsureEpochOk(pathPtr, &retVal) != TCL_OK) {
+       Disclaim();
+       return NULL;
+    } else if (retVal != NULL) {
+       /* TODO: Can this happen? */
+       Disclaim();
+       return retVal;
+    }
+
+    /*
+     * 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) {
+       ClientData clientData = NULL;
+
+       if (fsRecPtr->fsPtr->pathInFilesystemProc == NULL) {
+           continue;
+       }
+
+       if (fsRecPtr->fsPtr->pathInFilesystemProc(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;
+       }
+    }
+    Disclaim();
+
+    return NULL;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSGetNativePath --
+ *
+ *     This function is for use by the Win/Unix native filesystems, so that
+ *     they can easily retrieve the native (char* or WCHAR*) 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 desirable 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 *
+Tcl_FSGetNativePath(
+    Tcl_Obj *pathPtr)
+{
+    return Tcl_FSGetInternalRep(pathPtr, &tclNativeFilesystem);
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * NativeFreeInternalRep --
+ *
+ *     Free a native internal representation, which will be non-NULL.
+ *
+ * Results:
+ *     None.
+ *
+ * Side effects:
+ *     Memory is released.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static void
+NativeFreeInternalRep(
+    ClientData clientData)
+{
+    ckfree(clientData);
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSFileSystemInfo --
+ *
+ *     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 of two elements.
+ *
+ * Side effects:
+ *     The object may be converted to a path type.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+Tcl_FSFileSystemInfo(
+    Tcl_Obj *pathPtr)
+{
+    Tcl_Obj *resPtr;
+    const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+
+    if (fsPtr == NULL) {
+       return NULL;
+    }
+
+    resPtr = Tcl_NewListObj(0, NULL);
+    Tcl_ListObjAppendElement(NULL, resPtr,
+           Tcl_NewStringObj(fsPtr->typeName, -1));
+
+    if (fsPtr->filesystemPathTypeProc != NULL) {
+       Tcl_Obj *typePtr = fsPtr->filesystemPathTypeProc(pathPtr);
+
+       if (typePtr != NULL) {
+           Tcl_ListObjAppendElement(NULL, resPtr, typePtr);
+       }
+    }
+
+    return resPtr;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSPathSeparator --
+ *
+ *     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, 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 path object may be converted to a path type.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+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);
+    }
+
+    /*
+     * Allow filesystems not to provide a filesystemSeparatorProc if they wish
+     * to use the standard forward slash.
+     */
+
+    TclNewLiteralStringObj(resultObj, "/");
+    return resultObj;
+}
+\f
+/*
+ *---------------------------------------------------------------------------
+ *
+ * NativeFilesystemSeparator --
+ *
+ *     This function is part of the native filesystem support, and returns
+ *     the separator for the given path.
+ *
+ * Results:
+ *     String object containing the separator character.
+ *
+ * Side effects:
+ *     None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static Tcl_Obj *
+NativeFilesystemSeparator(
+    Tcl_Obj *pathPtr)
+{
+    const char *separator = NULL; /* lint */
+
+    switch (tclPlatform) {
+    case TCL_PLATFORM_UNIX:
+       separator = "/";
+       break;
+    case TCL_PLATFORM_WINDOWS:
+       separator = "\\";
+       break;
+    }
+    return Tcl_NewStringObj(separator,1);
+}
+\f
+/*
+ * Local Variables:
+ * mode: c
+ * c-basic-offset: 4
+ * fill-column: 78
+ * End:
+ */