summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorvincentdarley <vincentdarley>2003-04-11 15:59:49 (GMT)
committervincentdarley <vincentdarley>2003-04-11 15:59:49 (GMT)
commita5499a51a90ae1c06f3f39ee05c4b42185e0f28c (patch)
tree324d5cddf5f2dfe379c3cf1427347351d8d683a5
parent3c51da6d9db3a5e20f2e38f667ef5c0791b2e88d (diff)
downloadtcl-a5499a51a90ae1c06f3f39ee05c4b42185e0f28c.zip
tcl-a5499a51a90ae1c06f3f39ee05c4b42185e0f28c.tar.gz
tcl-a5499a51a90ae1c06f3f39ee05c4b42185e0f28c.tar.bz2
fix 5 small filesystem bugs, and some typos
-rw-r--r--ChangeLog42
-rw-r--r--generic/tclCmdMZ.c4
-rw-r--r--generic/tclFileSystem.h75
-rw-r--r--generic/tclIOUtil.c2518
-rw-r--r--generic/tclPathObj.c1857
-rw-r--r--generic/tclTest.c28
-rw-r--r--tests/cmdAH.test13
-rw-r--r--tests/fCmd.test7
-rw-r--r--tests/fileSystem.test8
-rw-r--r--tests/ioUtil.test6
-rw-r--r--tests/reg.test90
-rw-r--r--tests/unixFCmd.test20
-rw-r--r--tests/winFile.test6
-rw-r--r--unix/Makefile.in8
-rw-r--r--win/makefile.vc3
-rw-r--r--win/tclWin32Dll.c193
-rw-r--r--win/tclWinFile.c231
-rw-r--r--win/tclWinInt.h9
18 files changed, 2923 insertions, 2195 deletions
diff --git a/ChangeLog b/ChangeLog
index bf906ba..fffc552 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,45 @@
+2003-04-11 Vince Darley <vincentdarley@users.sourceforge.net>
+
+ * tests/cmdAH.test: fix test suite problem if /home is a symlink
+ [Bug #703264]
+ * generic/tclIOUtil.c: fix bad error message with 'cd ""'
+ [Bug #704917]
+ * win/tclWinFile.c:
+ * win/tclWin32Dll.c:
+ * win/tclWinInt.h: allow Tcl to differentiate between reparse
+ points which are symlinks and mounted volumes, and correctly
+ handle the latter. This involves some elaborate code to find
+ the actual drive letter (if possible) corresponding to a mounted
+ volume. [Bug #697862]
+ * tests/fileSystem.test: add constraints to stop tests running
+ in ordinary tcl interpreter. [Bug #705675]
+
+ * generic/tclIOUtil.c:
+ * generic/tclPathObj.c: (new file)
+ * generic/tclFileSystem.h: (new file)
+ * win/makefile.vc:
+ Split path object handling out of the virtual filesystem layer,
+ into tclPathObj.c. This refactoring cleans up the internal
+ filesystem code, and will make any future optimisations and
+ forthcoming better thread-safety much easier.
+
+ * generic/tclTest.c:
+ * tests/reg.test: added some 'knownBug' tests for problems in
+ Tcl's regexp code with the TCL_REG_CAN_MATCH flag (see Bug #703709).
+ Code too impenetrable to fix right now, but a fix is needed
+ for tip113 to work correctly.
+
+ * tests/fCmd.test
+ * win/tclWinFile.c: added some filesystem optimisation to the
+ 'glob' implementation, and some new tests.
+
+ * generic/tclCmdMZ.c: fix typo in comment
+
+ * tests/winFile.test:
+ * tests/ioUtil.test:
+ * tests/unixFCmd.test: renumbered tests with duplicate numbers.
+ (Bug #710361)
+
2003-04-10 Donal K. Fellows <fellowsd@cs.man.ac.uk>
* doc/binary.n: Fixed typo in [binary format w] desc. [Bug 718543]
diff --git a/generic/tclCmdMZ.c b/generic/tclCmdMZ.c
index b72772b..508cfa0 100644
--- a/generic/tclCmdMZ.c
+++ b/generic/tclCmdMZ.c
@@ -14,7 +14,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclCmdMZ.c,v 1.84 2003/04/07 16:55:13 dgp Exp $
+ * RCS: @(#) $Id: tclCmdMZ.c,v 1.85 2003/04/11 15:59:52 vincentdarley Exp $
*/
#include "tclInt.h"
@@ -3695,7 +3695,7 @@ Tcl_TraceCommand(interp, cmdName, flags, proc, clientData)
* of TCL_TRACE_RENAME, TCL_TRACE_DELETE,
* and any of the TRACE_*_EXEC flags */
Tcl_CommandTraceProc *proc; /* Procedure to call when specified ops are
- * invoked upon varName. */
+ * invoked upon cmdName. */
ClientData clientData; /* Arbitrary argument to pass to proc. */
{
Command *cmdPtr;
diff --git a/generic/tclFileSystem.h b/generic/tclFileSystem.h
new file mode 100644
index 0000000..30a6399
--- /dev/null
+++ b/generic/tclFileSystem.h
@@ -0,0 +1,75 @@
+/*
+ * tclFileSystem.h --
+ *
+ * This file contains the common defintions and prototypes for
+ * use by Tcl's filesystem and path handling layers.
+ *
+ * Copyright (c) 2003 Vince Darley.
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * RCS: @(#) $Id: tclFileSystem.h,v 1.1 2003/04/11 15:59:54 vincentdarley Exp $
+ */
+
+/*
+ * struct FilesystemRecord --
+ *
+ * A filesystem record is used to keep track of each
+ * filesystem currently registered with the core,
+ * in a linked list. Pointers to these structures
+ * are also kept by each "path" Tcl_Obj, and we must
+ * retain a refCount on the number of such references.
+ */
+typedef struct FilesystemRecord {
+ ClientData clientData; /* Client specific data for the new
+ * filesystem (can be NULL) */
+ Tcl_Filesystem *fsPtr; /* Pointer to filesystem dispatch
+ * table. */
+ int fileRefCount; /* How many Tcl_Obj's use this
+ * filesystem. */
+ struct FilesystemRecord *nextPtr;
+ /* The next filesystem registered
+ * to Tcl, or NULL if no more. */
+} FilesystemRecord;
+
+/*
+ * The internal TclFS API provides routines for handling and
+ * manipulating paths efficiently, taking direct advantage of
+ * the "path" Tcl_Obj type.
+ *
+ * These functions are not exported at all at present.
+ */
+
+int TclFSCwdPointerEquals _ANSI_ARGS_((Tcl_Obj* objPtr));
+int TclFSMakePathFromNormalized _ANSI_ARGS_((Tcl_Interp *interp,
+ Tcl_Obj *objPtr, ClientData clientData));
+int TclFSNormalizeToUniquePath _ANSI_ARGS_((Tcl_Interp *interp,
+ Tcl_Obj *pathPtr, int startAt, ClientData *clientDataPtr));
+Tcl_Obj* TclFSMakePathRelative _ANSI_ARGS_((Tcl_Interp *interp,
+ Tcl_Obj *objPtr, Tcl_Obj *cwdPtr));
+Tcl_Obj* TclFSInternalToNormalized _ANSI_ARGS_((
+ Tcl_Filesystem *fromFilesystem, ClientData clientData,
+ FilesystemRecord **fsRecPtrPtr, int *epochPtr));
+int TclFSEnsureEpochOk _ANSI_ARGS_((Tcl_Obj* pathObjPtr, int theEpoch,
+ Tcl_Filesystem **fsPtrPtr));
+void TclFSSetPathDetails _ANSI_ARGS_((Tcl_Obj *pathObjPtr,
+ FilesystemRecord *fsRecPtr, ClientData clientData,
+ int theEpoch));
+
+/*
+ * Private shared variables for use by tclIOUtil.c and tclPathObj.c
+ */
+extern Tcl_Filesystem tclNativeFilesystem;
+extern int theFilesystemEpoch;
+
+/*
+ * Private shared functions for use by tclIOUtil.c and tclPathObj.c
+ */
+Tcl_PathType FSGetPathType _ANSI_ARGS_((Tcl_Obj *pathObjPtr,
+ Tcl_Filesystem **filesystemPtrPtr,
+ int *driveNameLengthPtr));
+Tcl_PathType GetPathType _ANSI_ARGS_((Tcl_Obj *pathObjPtr,
+ Tcl_Filesystem **filesystemPtrPtr,
+ int *driveNameLengthPtr, Tcl_Obj **driveNameRef));
+Tcl_FSPathInFilesystemProc NativePathInFilesystem;
diff --git a/generic/tclIOUtil.c b/generic/tclIOUtil.c
index 29ba700..f27fff9 100644
--- a/generic/tclIOUtil.c
+++ b/generic/tclIOUtil.c
@@ -17,7 +17,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclIOUtil.c,v 1.77 2003/03/03 20:22:41 das Exp $
+ * RCS: @(#) $Id: tclIOUtil.c,v 1.78 2003/04/11 15:59:54 vincentdarley Exp $
*/
#include "tclInt.h"
@@ -29,45 +29,15 @@
/* for tclWinProcs->useWide */
#include "tclWinInt.h"
#endif
+#include "tclFileSystem.h"
/*
* Prototypes for procedures defined later in this file.
*/
-static void DupFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr,
- Tcl_Obj *copyPtr));
-static void FreeFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *listPtr));
-static void UpdateStringOfFsPath _ANSI_ARGS_((Tcl_Obj *objPtr));
-static int SetFsPathFromAny _ANSI_ARGS_((Tcl_Interp *interp,
- Tcl_Obj *objPtr));
-static Tcl_Obj* MakeFsPathFromRelative _ANSI_ARGS_((Tcl_Interp *interp,
- Tcl_Obj *objPtr, Tcl_Obj *cwdPtr));
-static Tcl_Obj* FSNormalizeAbsolutePath
- _ANSI_ARGS_((Tcl_Interp* interp, Tcl_Obj *pathPtr));
-static int TclNormalizeToUniquePath
- _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *pathPtr,
- int startAt));
-static int SetFsPathFromAbsoluteNormalized
- _ANSI_ARGS_((Tcl_Interp *interp, Tcl_Obj *objPtr));
-static int FindSplitPos _ANSI_ARGS_((char *path, char *separator));
-static Tcl_PathType FSGetPathType _ANSI_ARGS_((Tcl_Obj *pathObjPtr,
- Tcl_Filesystem **filesystemPtrPtr,
- int *driveNameLengthPtr));
-static Tcl_PathType GetPathType _ANSI_ARGS_((Tcl_Obj *pathObjPtr,
- Tcl_Filesystem **filesystemPtrPtr,
- int *driveNameLengthPtr, Tcl_Obj **driveNameRef));
+static FilesystemRecord* FsGetIterator(void);
+static void FsReleaseIterator(void);
-/*
- * Define the 'path' object type, which Tcl uses to represent
- * file paths internally.
- */
-Tcl_ObjType tclFsPathType = {
- "path", /* name */
- FreeFsPathInternalRep, /* freeIntRepProc */
- DupFsPathInternalRep, /* dupIntRepProc */
- UpdateStringOfFsPath, /* updateStringProc */
- SetFsPathFromAny /* setFromAnyProc */
-};
/*
* These form part of the native filesystem support. They are needed
@@ -306,26 +276,6 @@ TCL_DECLARE_MUTEX(obsoleteFsHookMutex)
#endif /* USE_OBSOLETE_FS_HOOKS */
/*
- * A filesystem record is used to keep track of each
- * filesystem currently registered with the core,
- * in a linked list.
- */
-typedef struct FilesystemRecord {
- ClientData clientData; /* Client specific data for the new
- * filesystem (can be NULL) */
- Tcl_Filesystem *fsPtr; /* Pointer to filesystem dispatch
- * table. */
- int fileRefCount; /* How many Tcl_Obj's use this
- * filesystem. */
- struct FilesystemRecord *nextPtr;
- /* The next filesystem registered
- * to Tcl, or NULL if no more. */
-} FilesystemRecord;
-
-static FilesystemRecord* GetFilesystemRecord
- _ANSI_ARGS_((Tcl_Filesystem *fromFilesystem, int *epoch));
-
-/*
* Declare the native filesystem support. These functions should
* be considered private to Tcl, and should really not be called
* directly by any code other than this file (i.e. neither by
@@ -338,10 +288,9 @@ static FilesystemRecord* GetFilesystemRecord
* We cannot make all of these static, since some of them
* are implemented in the platform-specific directories.
*/
-static Tcl_FSPathInFilesystemProc NativePathInFilesystem;
static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator;
static Tcl_FSFreeInternalRepProc NativeFreeInternalRep;
-static Tcl_FSDupInternalRepProc NativeDupInternalRep;
+Tcl_FSDupInternalRepProc NativeDupInternalRep;
static Tcl_FSCreateInternalRepProc NativeCreateNativeRep;
static Tcl_FSFileAttrStringsProc NativeFileAttrStrings;
static Tcl_FSFileAttrsGetProc NativeFileAttrsGet;
@@ -383,7 +332,7 @@ Tcl_FSListVolumesProc TclpObjListVolumes;
* 'native filesystem implementation' should not be delving inside
* here!
*/
-static Tcl_Filesystem tclNativeFilesystem = {
+Tcl_Filesystem tclNativeFilesystem = {
"native",
sizeof(Tcl_Filesystem),
TCL_FILESYSTEM_VERSION_1,
@@ -447,7 +396,7 @@ static FilesystemRecord nativeFilesystemRecord = {
* filesystems. Any time it changes, all cached filesystem
* representations are suspect and must be freed.
*/
-static int theFilesystemEpoch = 0;
+int theFilesystemEpoch = 0;
/*
* Stores the linked list of filesystems.
@@ -472,49 +421,6 @@ static Tcl_Condition filesystemOkToModify = NULL;
TCL_DECLARE_MUTEX(filesystemMutex)
/*
- * struct FsPath --
- *
- * Internal representation of a Tcl_Obj of "path" type. This
- * can be used to represent relative or absolute paths, and has
- * certain optimisations when used to represent paths which are
- * already normalized and absolute.
- *
- * Note that 'normPathPtr' can be a circular reference to the
- * container Tcl_Obj of this FsPath.
- */
-typedef struct FsPath {
- Tcl_Obj *translatedPathPtr; /* Name without any ~user sequences.
- * If this is NULL, then this is a
- * pure normalized, absolute path
- * object, in which the parent Tcl_Obj's
- * string rep is already both translated
- * and normalized. */
- Tcl_Obj *normPathPtr; /* Normalized absolute path, without
- * ., .. or ~user sequences. If the
- * Tcl_Obj containing
- * this FsPath is already normalized,
- * this may be a circular reference back
- * to the container. If that is NOT the
- * case, we have a refCount on the object. */
- Tcl_Obj *cwdPtr; /* If null, path is absolute, else
- * this points to the cwd object used
- * for this path. We have a refCount
- * on the object. */
- int flags; /* Flags to describe interpretation */
- ClientData nativePathPtr; /* Native representation of this path,
- * which is filesystem dependent. */
- int filesystemEpoch; /* Used to ensure the path representation
- * was generated during the correct
- * filesystem epoch. The epoch changes
- * when filesystem-mounts are changed. */
- struct FilesystemRecord *fsRecPtr;
- /* Pointer to the filesystem record
- * entry to use for this path. */
-} FsPath;
-
-#define TCLPATH_APPENDED 1
-#define TCLPATH_RELATIVE 2
-/*
* Used to implement Tcl_FSGetCwd in a file-system independent way.
* This is protected by the cwdMutex below.
*/
@@ -546,8 +452,8 @@ typedef struct FsDivertLoad {
/* Now move on to the basic filesystem implementation */
-static int
-FsCwdPointerEquals(objPtr)
+int
+TclFSCwdPointerEquals(objPtr)
Tcl_Obj* objPtr;
{
Tcl_MutexLock(&cwdMutex);
@@ -559,7 +465,7 @@ FsCwdPointerEquals(objPtr)
return 0;
}
}
-
+
static FilesystemRecord*
FsGetIterator(void) {
@@ -856,6 +762,131 @@ Tcl_FSUnregister(fsPtr)
/*
*----------------------------------------------------------------------
*
+ * Tcl_FSMatchInDirectory --
+ *
+ * This routine is used by the globbing code to search a directory
+ * for all files which match a given pattern. The appropriate
+ * function for the filesystem to which pathPtr belongs will be
+ * called. If pathPtr does not belong to any filesystem and if it
+ * is NULL or the empty string, then we assume the pattern is to be
+ * matched in the current working directory. To avoid each
+ * filesystem's Tcl_FSMatchInDirectoryProc having to deal with this
+ * issue, we create a pathPtr on the fly (equal to the cwd), and
+ * then remove it from the results returned. This makes filesystems
+ * easy to write, since they can assume the pathPtr passed to them
+ * is an ordinary path. In fact this means we could remove such
+ * special case handling from Tcl's native filesystems.
+ *
+ * If 'pattern' is NULL, then pathPtr is assumed to be a fully
+ * specified path of a single file/directory which must be
+ * checked for existence and correct type.
+ *
+ * Results:
+ *
+ * The return value is a standard Tcl result indicating whether an
+ * error occurred in globbing. Error messages are placed in
+ * interp, but good results are placed in the resultPtr given.
+ *
+ * Recursive searches, e.g.
+ *
+ * glob -dir $dir -join * pkgIndex.tcl
+ *
+ * which must recurse through each directory matching '*' are
+ * handled internally by Tcl, by passing specific flags in a
+ * modified 'types' parameter. This means the actual filesystem
+ * only ever sees patterns which match in a single directory.
+ *
+ * Side effects:
+ * The interpreter may have an error message inserted into it.
+ *
+ *----------------------------------------------------------------------
+ */
+
+int
+Tcl_FSMatchInDirectory(interp, result, pathPtr, pattern, types)
+ Tcl_Interp *interp; /* Interpreter to receive error messages. */
+ Tcl_Obj *result; /* List object to receive results. */
+ Tcl_Obj *pathPtr; /* Contains path to directory to search. */
+ CONST char *pattern; /* Pattern to match against. */
+ Tcl_GlobTypeData *types; /* Object containing list of acceptable types.
+ * May be NULL. In particular the directory
+ * flag is very important. */
+{
+ Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
+ if (fsPtr != NULL) {
+ Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc;
+ if (proc != NULL) {
+ return (*proc)(interp, result, pathPtr, pattern, types);
+ }
+ } else {
+ Tcl_Obj* cwd;
+ int ret = -1;
+ if (pathPtr != NULL) {
+ int len;
+ Tcl_GetStringFromObj(pathPtr,&len);
+ if (len != 0) {
+ /*
+ * We have no idea how to match files in a directory
+ * which belongs to no known filesystem
+ */
+ Tcl_SetErrno(ENOENT);
+ return -1;
+ }
+ }
+ /*
+ * We have an empty or NULL path. This is defined to mean we
+ * must search for files within the current 'cwd'. We
+ * therefore use that, but then since the proc we call will
+ * return results which include the cwd we must then trim it
+ * off the front of each path in the result. We choose to deal
+ * with this here (in the generic code), since if we don't,
+ * every single filesystem's implementation of
+ * Tcl_FSMatchInDirectory will have to deal with it for us.
+ */
+ cwd = Tcl_FSGetCwd(NULL);
+ if (cwd == NULL) {
+ if (interp != NULL) {
+ Tcl_SetResult(interp, "glob couldn't determine "
+ "the current working directory", TCL_STATIC);
+ }
+ return TCL_ERROR;
+ }
+ fsPtr = Tcl_FSGetFileSystemForPath(cwd);
+ if (fsPtr != NULL) {
+ Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc;
+ if (proc != NULL) {
+ Tcl_Obj* tmpResultPtr = Tcl_NewListObj(0, NULL);
+ Tcl_IncrRefCount(tmpResultPtr);
+ ret = (*proc)(interp, tmpResultPtr, cwd, pattern, types);
+ if (ret == TCL_OK) {
+ int resLength;
+
+ ret = Tcl_ListObjLength(interp, tmpResultPtr, &resLength);
+ if (ret == TCL_OK) {
+ int i;
+
+ for (i = 0; i < resLength; i++) {
+ Tcl_Obj *elt;
+
+ Tcl_ListObjIndex(interp, tmpResultPtr, i, &elt);
+ Tcl_ListObjAppendElement(interp, result,
+ TclFSMakePathRelative(interp, elt, cwd));
+ }
+ }
+ }
+ Tcl_DecrRefCount(tmpResultPtr);
+ }
+ }
+ Tcl_DecrRefCount(cwd);
+ return ret;
+ }
+ Tcl_SetErrno(ENOENT);
+ return -1;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* Tcl_FSMountsChanged --
*
* Notify the filesystem that the available mounted filesystems
@@ -1001,9 +1032,10 @@ Tcl_FSData(fsPtr)
*---------------------------------------------------------------------------
*/
static Tcl_Obj*
-FSNormalizeAbsolutePath(interp, pathPtr)
+FSNormalizeAbsolutePath(interp, pathPtr, clientDataPtr)
Tcl_Interp* interp; /* Interpreter to use */
Tcl_Obj *pathPtr; /* Absolute path to normalize */
+ ClientData *clientDataPtr;
{
int splen = 0, nplen, eltLen, i;
char *eltName;
@@ -1039,6 +1071,8 @@ FSNormalizeAbsolutePath(interp, pathPtr)
}
}
if (nplen > 0) {
+ ClientData clientData = NULL;
+
retVal = Tcl_FSJoinPath(split, nplen);
/*
* Now we have an absolute path, with no '..', '.' sequences,
@@ -1052,13 +1086,16 @@ FSNormalizeAbsolutePath(interp, pathPtr)
* other criteria for normalizing a path.
*/
Tcl_IncrRefCount(retVal);
- TclNormalizeToUniquePath(interp, retVal, 0);
+ TclFSNormalizeToUniquePath(interp, retVal, 0, &clientData);
/*
* Since we know it is a normalized path, we can
- * actually convert this object into an FsPath for
+ * actually convert this object into an "path" object for
* greater efficiency
*/
- SetFsPathFromAbsoluteNormalized(interp, retVal);
+ TclFSMakePathFromNormalized(interp, retVal, clientData);
+ if (clientDataPtr != NULL) {
+ *clientDataPtr = clientData;
+ }
} else {
/* Init to an empty string */
retVal = Tcl_NewStringObj("",0);
@@ -1083,7 +1120,7 @@ FSNormalizeAbsolutePath(interp, pathPtr)
/*
*---------------------------------------------------------------------------
*
- * TclNormalizeToUniquePath --
+ * TclFSNormalizeToUniquePath --
*
* Description:
* Takes a path specification containing no ../, ./ sequences,
@@ -1113,14 +1150,17 @@ FSNormalizeAbsolutePath(interp, pathPtr)
* after the separator).
*---------------------------------------------------------------------------
*/
-static int
-TclNormalizeToUniquePath(interp, pathPtr, startAt)
+int
+TclFSNormalizeToUniquePath(interp, pathPtr, startAt, clientDataPtr)
Tcl_Interp *interp;
Tcl_Obj *pathPtr;
int startAt;
+ ClientData *clientDataPtr;
{
FilesystemRecord *fsRecPtr;
-
+ /* Ignore this variable */
+ (void)clientDataPtr;
+
/*
* Call each of the "normalise path" functions in succession. This is
* a special case, in which if we have a native filesystem handler,
@@ -1374,7 +1414,7 @@ Tcl_FSEvalFile(interp, pathPtr)
Tcl_Channel chan;
Tcl_Obj *objPtr;
- if (Tcl_FSGetTranslatedPath(interp, pathPtr) == NULL) {
+ if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
return TCL_ERROR;
}
@@ -1757,39 +1797,48 @@ Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions)
{
Tcl_Filesystem *fsPtr;
#ifdef USE_OBSOLETE_FS_HOOKS
- OpenFileChannelProc *openFileChannelProcPtr;
Tcl_Channel retVal = NULL;
- char *path;
-#endif /* USE_OBSOLETE_FS_HOOKS */
- Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
- if (transPtr == NULL) {
- return NULL;
- }
-#ifdef USE_OBSOLETE_FS_HOOKS
- if (transPtr == NULL) {
- path = NULL;
- } else {
- path = Tcl_GetString(transPtr);
- }
/*
- * Call each of the "Tcl_OpenFileChannel" function in succession.
+ * Call each of the "Tcl_OpenFileChannel" functions in succession.
* A non-NULL return value indicates the particular function has
* succeeded.
*/
Tcl_MutexLock(&obsoleteFsHookMutex);
- openFileChannelProcPtr = openFileChannelProcList;
- while ((retVal == NULL) && (openFileChannelProcPtr != NULL)) {
- retVal = (*openFileChannelProcPtr->proc)(interp, path,
- modeString, permissions);
- openFileChannelProcPtr = openFileChannelProcPtr->nextPtr;
+ if (openFileChannelProcList != NULL) {
+ OpenFileChannelProc *openFileChannelProcPtr;
+ char *path;
+ Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
+
+ if (transPtr == NULL) {
+ path = NULL;
+ } else {
+ path = Tcl_GetString(transPtr);
+ }
+
+ openFileChannelProcPtr = openFileChannelProcList;
+
+ while ((retVal == NULL) && (openFileChannelProcPtr != NULL)) {
+ retVal = (*openFileChannelProcPtr->proc)(interp, path,
+ modeString, permissions);
+ openFileChannelProcPtr = openFileChannelProcPtr->nextPtr;
+ }
}
Tcl_MutexUnlock(&obsoleteFsHookMutex);
if (retVal != NULL) {
return retVal;
}
#endif /* USE_OBSOLETE_FS_HOOKS */
+
+ /*
+ * We need this just to ensure we return the correct error messages
+ * under some circumstances.
+ */
+ if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
+ return NULL;
+ }
+
fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
if (fsPtr != NULL) {
Tcl_FSOpenFileChannelProc *proc = fsPtr->openFileChannelProc;
@@ -1831,371 +1880,6 @@ Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions)
/*
*----------------------------------------------------------------------
*
- * Tcl_FSMatchInDirectory --
- *
- * This routine is used by the globbing code to search a directory
- * for all files which match a given pattern. The appropriate
- * function for the filesystem to which pathPtr belongs will be
- * called. If pathPtr does not belong to any filesystem and if it
- * is NULL or the empty string, then we assume the pattern is to be
- * matched in the current working directory. To avoid each
- * filesystem's Tcl_FSMatchInDirectoryProc having to deal with this
- * issue, we create a pathPtr on the fly (equal to the cwd), and
- * then remove it from the results returned. This makes filesystems
- * easy to write, since they can assume the pathPtr passed to them
- * is an ordinary path. In fact this means we could remove such
- * special case handling from Tcl's native filesystems.
- *
- * If 'pattern' is NULL, then pathPtr is assumed to be a fully
- * specified path of a single file/directory which must be
- * checked for existence and correct type.
- *
- * Results:
- *
- * The return value is a standard Tcl result indicating whether an
- * error occurred in globbing. Error messages are placed in
- * interp, but good results are placed in the resultPtr given.
- *
- * Recursive searches, e.g.
- *
- * glob -dir $dir -join * pkgIndex.tcl
- *
- * which must recurse through each directory matching '*' are
- * handled internally by Tcl, by passing specific flags in a
- * modified 'types' parameter. This means the actual filesystem
- * only ever sees patterns which match in a single directory.
- *
- * Side effects:
- * The interpreter may have an error message inserted into it.
- *
- *----------------------------------------------------------------------
- */
-
-int
-Tcl_FSMatchInDirectory(interp, result, pathPtr, pattern, types)
- Tcl_Interp *interp; /* Interpreter to receive error messages. */
- Tcl_Obj *result; /* List object to receive results. */
- Tcl_Obj *pathPtr; /* Contains path to directory to search. */
- CONST char *pattern; /* Pattern to match against. */
- Tcl_GlobTypeData *types; /* Object containing list of acceptable types.
- * May be NULL. In particular the directory
- * flag is very important. */
-{
- Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
- if (fsPtr != NULL) {
- Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc;
- if (proc != NULL) {
- return (*proc)(interp, result, pathPtr, pattern, types);
- }
- } else {
- Tcl_Obj* cwd;
- int ret = -1;
- if (pathPtr != NULL) {
- int len;
- Tcl_GetStringFromObj(pathPtr,&len);
- if (len != 0) {
- /*
- * We have no idea how to match files in a directory
- * which belongs to no known filesystem
- */
- Tcl_SetErrno(ENOENT);
- return -1;
- }
- }
- /*
- * We have an empty or NULL path. This is defined to mean we
- * must search for files within the current 'cwd'. We
- * therefore use that, but then since the proc we call will
- * return results which include the cwd we must then trim it
- * off the front of each path in the result. We choose to deal
- * with this here (in the generic code), since if we don't,
- * every single filesystem's implementation of
- * Tcl_FSMatchInDirectory will have to deal with it for us.
- */
- cwd = Tcl_FSGetCwd(NULL);
- if (cwd == NULL) {
- if (interp != NULL) {
- Tcl_SetResult(interp, "glob couldn't determine "
- "the current working directory", TCL_STATIC);
- }
- return TCL_ERROR;
- }
- fsPtr = Tcl_FSGetFileSystemForPath(cwd);
- if (fsPtr != NULL) {
- Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc;
- if (proc != NULL) {
- int cwdLen;
- char *cwdStr;
- Tcl_Obj* tmpResultPtr = Tcl_NewListObj(0, NULL);
- Tcl_IncrRefCount(tmpResultPtr);
- /*
- * We know the cwd is a normalised object which does
- * not end in a directory delimiter, unless the cwd
- * is the name of a volume, in which case it will
- * end in a delimiter! We handle this situation here.
- * A better test than the '!= sep' might be to simply
- * check if 'cwd' is a root volume.
- *
- * Note that if we get this wrong, we will strip off
- * either too much or too little below, leading to
- * wrong answers returned by glob.
- */
- cwdStr = Tcl_GetStringFromObj(cwd, &cwdLen);
- /*
- * Should we perhaps use 'Tcl_FSPathSeparator'?
- * But then what about the Windows special case?
- * Perhaps we should just check if cwd is a root
- * volume.
- */
- switch (tclPlatform) {
- case TCL_PLATFORM_UNIX:
- if (cwdStr[cwdLen-1] != '/') {
- cwdLen++;
- }
- break;
- case TCL_PLATFORM_WINDOWS:
- if (cwdStr[cwdLen-1] != '/'
- && cwdStr[cwdLen-1] != '\\') {
- cwdLen++;
- }
- break;
- case TCL_PLATFORM_MAC:
- if (cwdStr[cwdLen-1] != ':') {
- cwdLen++;
- }
- break;
- }
- ret = (*proc)(interp, tmpResultPtr, cwd, pattern, types);
- if (ret == TCL_OK) {
- int resLength;
-
- ret = Tcl_ListObjLength(interp, tmpResultPtr, &resLength);
- if (ret == TCL_OK) {
- int i;
-
- for (i = 0; i < resLength; i++) {
- Tcl_Obj *cutElt, *elt;
- char *eltStr;
- int eltLen;
-
- Tcl_ListObjIndex(interp, tmpResultPtr, i, &elt);
- if (elt->typePtr == &tclFsPathType) {
- FsPath* fsPathPtr = (FsPath*)
- elt->internalRep.otherValuePtr;
- if (fsPathPtr->flags != 0
- && fsPathPtr->cwdPtr == cwd) {
- Tcl_ListObjAppendElement(interp, result,
- MakeFsPathFromRelative(interp,
- fsPathPtr->normPathPtr, cwd));
- continue;
- }
- }
- eltStr = Tcl_GetStringFromObj(elt, &eltLen);
- cutElt = Tcl_NewStringObj(eltStr + cwdLen,
- eltLen - cwdLen);
- Tcl_ListObjAppendElement(interp, result, cutElt);
- }
- }
- }
- Tcl_DecrRefCount(tmpResultPtr);
- }
- }
- Tcl_DecrRefCount(cwd);
- return ret;
- }
- Tcl_SetErrno(ENOENT);
- return -1;
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * Tcl_FSGetCwd --
- *
- * This function replaces the library version of getcwd().
- *
- * Most VFS's will *not* implement a 'cwdProc'. Tcl now maintains
- * its own record (in a Tcl_Obj) of the cwd, and an attempt
- * is made to synchronise this with the cwd's containing filesystem,
- * if that filesystem provides a cwdProc (e.g. the native filesystem).
- *
- * Note that if Tcl's cwd is not in the native filesystem, then of
- * course Tcl's cwd and the native cwd are different: extensions
- * should therefore ensure they only access the cwd through this
- * function to avoid confusion.
- *
- * If a global cwdPathPtr already exists, it is returned, subject
- * to a synchronisation attempt in that cwdPathPtr's fs.
- * Otherwise, the chain of functions that have been "inserted"
- * into the filesystem will be called in succession until either a
- * value other than NULL is returned, or the entire list is
- * visited.
- *
- * Results:
- * The result is a pointer to a Tcl_Obj specifying the current
- * directory, or NULL if the current directory could not be
- * determined. If NULL is returned, an error message is left in the
- * interp's result.
- *
- * The result already has its refCount incremented for the caller.
- * When it is no longer needed, that refCount should be decremented.
- * This is needed for thread-safety purposes, to allow multiple
- * threads to access this and related functions, while ensuring the
- * results are always valid.
- *
- * Of course it is probably a bad idea for multiple threads to
- * be *setting* the cwd anyway, but we can at least try to
- * help the case of multiple reads with occasional sets.
- *
- * Side effects:
- * Various objects may be freed and allocated.
- *
- *----------------------------------------------------------------------
- */
-
-Tcl_Obj*
-Tcl_FSGetCwd(interp)
- Tcl_Interp *interp;
-{
- Tcl_Obj *cwdToReturn;
-
- if (FsCwdPointerEquals(NULL)) {
- FilesystemRecord *fsRecPtr;
- Tcl_Obj *retVal = NULL;
-
- /*
- * We've never been called before, try to find a cwd. Call
- * each of the "Tcl_GetCwd" function in succession. A non-NULL
- * return value indicates the particular function has
- * succeeded.
- */
-
- fsRecPtr = FsGetIterator();
- while ((retVal == NULL) && (fsRecPtr != NULL)) {
- Tcl_FSGetCwdProc *proc = fsRecPtr->fsPtr->getCwdProc;
- if (proc != NULL) {
- retVal = (*proc)(interp);
- }
- fsRecPtr = fsRecPtr->nextPtr;
- }
- FsReleaseIterator();
- /*
- * Now the 'cwd' may NOT be normalized, at least on some
- * platforms. For the sake of efficiency, we want a completely
- * normalized cwd at all times.
- *
- * Finally, if retVal is NULL, we do not have a cwd, which
- * could be problematic.
- */
- if (retVal != NULL) {
- Tcl_Obj *norm = FSNormalizeAbsolutePath(interp, retVal);
- if (norm != NULL) {
- /*
- * We found a cwd, which is now in our global storage.
- * We must make a copy. Norm already has a refCount of
- * 1.
- *
- * Threading issue: note that multiple threads at system
- * startup could in principle call this procedure
- * simultaneously. They will therefore each set the
- * cwdPathPtr independently. That behaviour is a bit
- * peculiar, but should be fine. Once we have a cwd,
- * we'll always be in the 'else' branch below which
- * is simpler.
- */
- Tcl_MutexLock(&cwdMutex);
- /* Just in case the pointer has been set by another
- * thread between now and the test above */
- if (cwdPathPtr != NULL) {
- Tcl_DecrRefCount(cwdPathPtr);
- }
- cwdPathPtr = norm;
- Tcl_MutexUnlock(&cwdMutex);
- }
- Tcl_DecrRefCount(retVal);
- }
- } else {
- /*
- * We already have a cwd cached, but we want to give the
- * filesystem it is in a chance to check whether that cwd
- * has changed, or is perhaps no longer accessible. This
- * allows an error to be thrown if, say, the permissions on
- * that directory have changed.
- */
- Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(cwdPathPtr);
- /*
- * If the filesystem couldn't be found, or if no cwd function
- * exists for this filesystem, then we simply assume the cached
- * cwd is ok. If we do call a cwd, we must watch for errors
- * (if the cwd returns NULL). This ensures that, say, on Unix
- * if the permissions of the cwd change, 'pwd' does actually
- * throw the correct error in Tcl. (This is tested for in the
- * test suite on unix).
- */
- if (fsPtr != NULL) {
- Tcl_FSGetCwdProc *proc = fsPtr->getCwdProc;
- if (proc != NULL) {
- Tcl_Obj *retVal = (*proc)(interp);
- if (retVal != NULL) {
- Tcl_Obj *norm = FSNormalizeAbsolutePath(interp, retVal);
- /*
- * Check whether cwd has changed from the value
- * previously stored in cwdPathPtr. Really 'norm'
- * shouldn't be null, but we are careful.
- */
- if (norm == NULL) {
- /* Do nothing */
- } else if (Tcl_FSEqualPaths(cwdPathPtr, norm)) {
- /*
- * If the paths were equal, we can be more
- * efficient and retain the old path object
- * which will probably already be shared. In
- * this case we can simply free the normalized
- * path we just calculated.
- */
- Tcl_DecrRefCount(norm);
- } else {
- /* The cwd has in fact changed, so we must
- * lock down the cwdMutex to modify. */
- Tcl_MutexLock(&cwdMutex);
- Tcl_DecrRefCount(cwdPathPtr);
- cwdPathPtr = norm;
- Tcl_MutexUnlock(&cwdMutex);
- }
- Tcl_DecrRefCount(retVal);
- } else {
- /* The 'cwd' function returned an error, so we
- * reset the cwd after locking down the mutex. */
- Tcl_MutexLock(&cwdMutex);
- Tcl_DecrRefCount(cwdPathPtr);
- cwdPathPtr = NULL;
- Tcl_MutexUnlock(&cwdMutex);
- }
- }
- }
- }
-
- /*
- * The paths all eventually fall through to here. Note that
- * we use a bunch of separate mutex locks throughout this
- * code to help prevent deadlocks between threads. Really
- * the only weirdness will arise if multiple threads are setting
- * and reading the cwd, and that behaviour is always going to be
- * a little suspect.
- */
- Tcl_MutexLock(&cwdMutex);
- cwdToReturn = cwdPathPtr;
- if (cwdToReturn != NULL) {
- Tcl_IncrRefCount(cwdToReturn);
- }
- Tcl_MutexUnlock(&cwdMutex);
-
- return (cwdToReturn);
-}
-
-/*
- *----------------------------------------------------------------------
- *
* Tcl_FSUtime --
*
* This procedure replaces the library version of utime.
@@ -2442,6 +2126,192 @@ Tcl_FSFileAttrsSet(interp, index, pathPtr, objPtr)
/*
*----------------------------------------------------------------------
*
+ * Tcl_FSGetCwd --
+ *
+ * This function replaces the library version of getcwd().
+ *
+ * Most VFS's will *not* implement a 'cwdProc'. Tcl now maintains
+ * its own record (in a Tcl_Obj) of the cwd, and an attempt
+ * is made to synchronise this with the cwd's containing filesystem,
+ * if that filesystem provides a cwdProc (e.g. the native filesystem).
+ *
+ * Note that if Tcl's cwd is not in the native filesystem, then of
+ * course Tcl's cwd and the native cwd are different: extensions
+ * should therefore ensure they only access the cwd through this
+ * function to avoid confusion.
+ *
+ * If a global cwdPathPtr already exists, it is returned, subject
+ * to a synchronisation attempt in that cwdPathPtr's fs.
+ * Otherwise, the chain of functions that have been "inserted"
+ * into the filesystem will be called in succession until either a
+ * value other than NULL is returned, or the entire list is
+ * visited.
+ *
+ * Results:
+ * The result is a pointer to a Tcl_Obj specifying the current
+ * directory, or NULL if the current directory could not be
+ * determined. If NULL is returned, an error message is left in the
+ * interp's result.
+ *
+ * The result already has its refCount incremented for the caller.
+ * When it is no longer needed, that refCount should be decremented.
+ * This is needed for thread-safety purposes, to allow multiple
+ * threads to access this and related functions, while ensuring the
+ * results are always valid.
+ *
+ * Of course it is probably a bad idea for multiple threads to
+ * be *setting* the cwd anyway, but we can at least try to
+ * help the case of multiple reads with occasional sets.
+ *
+ * Side effects:
+ * Various objects may be freed and allocated.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_Obj*
+Tcl_FSGetCwd(interp)
+ Tcl_Interp *interp;
+{
+ Tcl_Obj *cwdToReturn;
+
+ if (TclFSCwdPointerEquals(NULL)) {
+ FilesystemRecord *fsRecPtr;
+ Tcl_Obj *retVal = NULL;
+
+ /*
+ * We've never been called before, try to find a cwd. Call
+ * each of the "Tcl_GetCwd" function in succession. A non-NULL
+ * return value indicates the particular function has
+ * succeeded.
+ */
+
+ fsRecPtr = FsGetIterator();
+ while ((retVal == NULL) && (fsRecPtr != NULL)) {
+ Tcl_FSGetCwdProc *proc = fsRecPtr->fsPtr->getCwdProc;
+ if (proc != NULL) {
+ retVal = (*proc)(interp);
+ }
+ fsRecPtr = fsRecPtr->nextPtr;
+ }
+ FsReleaseIterator();
+ /*
+ * Now the 'cwd' may NOT be normalized, at least on some
+ * platforms. For the sake of efficiency, we want a completely
+ * normalized cwd at all times.
+ *
+ * Finally, if retVal is NULL, we do not have a cwd, which
+ * could be problematic.
+ */
+ if (retVal != NULL) {
+ Tcl_Obj *norm = FSNormalizeAbsolutePath(interp, retVal, NULL);
+ if (norm != NULL) {
+ /*
+ * We found a cwd, which is now in our global storage.
+ * We must make a copy. Norm already has a refCount of
+ * 1.
+ *
+ * Threading issue: note that multiple threads at system
+ * startup could in principle call this procedure
+ * simultaneously. They will therefore each set the
+ * cwdPathPtr independently. That behaviour is a bit
+ * peculiar, but should be fine. Once we have a cwd,
+ * we'll always be in the 'else' branch below which
+ * is simpler.
+ */
+ Tcl_MutexLock(&cwdMutex);
+ /* Just in case the pointer has been set by another
+ * thread between now and the test above */
+ if (cwdPathPtr != NULL) {
+ Tcl_DecrRefCount(cwdPathPtr);
+ }
+ cwdPathPtr = norm;
+ Tcl_MutexUnlock(&cwdMutex);
+ }
+ Tcl_DecrRefCount(retVal);
+ }
+ } else {
+ /*
+ * We already have a cwd cached, but we want to give the
+ * filesystem it is in a chance to check whether that cwd
+ * has changed, or is perhaps no longer accessible. This
+ * allows an error to be thrown if, say, the permissions on
+ * that directory have changed.
+ */
+ Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(cwdPathPtr);
+ /*
+ * If the filesystem couldn't be found, or if no cwd function
+ * exists for this filesystem, then we simply assume the cached
+ * cwd is ok. If we do call a cwd, we must watch for errors
+ * (if the cwd returns NULL). This ensures that, say, on Unix
+ * if the permissions of the cwd change, 'pwd' does actually
+ * throw the correct error in Tcl. (This is tested for in the
+ * test suite on unix).
+ */
+ if (fsPtr != NULL) {
+ Tcl_FSGetCwdProc *proc = fsPtr->getCwdProc;
+ if (proc != NULL) {
+ Tcl_Obj *retVal = (*proc)(interp);
+ if (retVal != NULL) {
+ Tcl_Obj *norm = FSNormalizeAbsolutePath(interp, retVal, NULL);
+ /*
+ * Check whether cwd has changed from the value
+ * previously stored in cwdPathPtr. Really 'norm'
+ * shouldn't be null, but we are careful.
+ */
+ if (norm == NULL) {
+ /* Do nothing */
+ } else if (Tcl_FSEqualPaths(cwdPathPtr, norm)) {
+ /*
+ * If the paths were equal, we can be more
+ * efficient and retain the old path object
+ * which will probably already be shared. In
+ * this case we can simply free the normalized
+ * path we just calculated.
+ */
+ Tcl_DecrRefCount(norm);
+ } else {
+ /* The cwd has in fact changed, so we must
+ * lock down the cwdMutex to modify. */
+ Tcl_MutexLock(&cwdMutex);
+ Tcl_DecrRefCount(cwdPathPtr);
+ cwdPathPtr = norm;
+ Tcl_MutexUnlock(&cwdMutex);
+ }
+ Tcl_DecrRefCount(retVal);
+ } else {
+ /* The 'cwd' function returned an error, so we
+ * reset the cwd after locking down the mutex. */
+ Tcl_MutexLock(&cwdMutex);
+ Tcl_DecrRefCount(cwdPathPtr);
+ cwdPathPtr = NULL;
+ Tcl_MutexUnlock(&cwdMutex);
+ }
+ }
+ }
+ }
+
+ /*
+ * The paths all eventually fall through to here. Note that
+ * we use a bunch of separate mutex locks throughout this
+ * code to help prevent deadlocks between threads. Really
+ * the only weirdness will arise if multiple threads are setting
+ * and reading the cwd, and that behaviour is always going to be
+ * a little suspect.
+ */
+ Tcl_MutexLock(&cwdMutex);
+ cwdToReturn = cwdPathPtr;
+ if (cwdToReturn != NULL) {
+ Tcl_IncrRefCount(cwdToReturn);
+ }
+ Tcl_MutexUnlock(&cwdMutex);
+
+ return (cwdToReturn);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
* Tcl_FSChdir --
*
* This function replaces the library version of chdir().
@@ -2528,6 +2398,8 @@ Tcl_FSChdir(pathPtr)
cwdPathPtr = normDirName;
Tcl_MutexUnlock(&cwdMutex);
}
+ } else {
+ Tcl_SetErrno(ENOENT);
}
return (retVal);
@@ -3002,79 +2874,6 @@ Tcl_FSListVolumes(void)
}
/*
- *----------------------------------------------------------------------
- *
- * Tcl_FSGetPathType --
- *
- * Determines whether a given path is relative to the current
- * directory, relative to the current volume, or absolute.
- *
- * Results:
- * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
- * TCL_PATH_VOLUME_RELATIVE.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-Tcl_PathType
-Tcl_FSGetPathType(pathObjPtr)
- Tcl_Obj *pathObjPtr;
-{
- return FSGetPathType(pathObjPtr, NULL, NULL);
-}
-
-/*
- *----------------------------------------------------------------------
- *
- * FSGetPathType --
- *
- * Determines whether a given path is relative to the current
- * directory, relative to the current volume, or absolute. If the
- * caller wishes to know which filesystem claimed the path (in the
- * case for which the path is absolute), then a reference to a
- * filesystem pointer can be passed in (but passing NULL is
- * acceptable).
- *
- * Results:
- * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
- * TCL_PATH_VOLUME_RELATIVE. The filesystem reference will
- * be set if and only if it is non-NULL and the function's
- * return value is TCL_PATH_ABSOLUTE.
- *
- * Side effects:
- * None.
- *
- *----------------------------------------------------------------------
- */
-
-static Tcl_PathType
-FSGetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr)
- Tcl_Obj *pathObjPtr;
- Tcl_Filesystem **filesystemPtrPtr;
- int *driveNameLengthPtr;
-{
- if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) {
- return GetPathType(pathObjPtr, filesystemPtrPtr,
- driveNameLengthPtr, NULL);
- } else {
- FsPath *fsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr;
- if (fsPathPtr->cwdPtr != NULL) {
- if (fsPathPtr->flags == 0) {
- return TCL_PATH_RELATIVE;
- }
- return FSGetPathType(fsPathPtr->cwdPtr, filesystemPtrPtr,
- driveNameLengthPtr);
- } else {
- return GetPathType(pathObjPtr, filesystemPtrPtr,
- driveNameLengthPtr, NULL);
- }
- }
-}
-
-/*
*---------------------------------------------------------------------------
*
* Tcl_FSSplitPath --
@@ -3171,188 +2970,31 @@ Tcl_FSSplitPath(pathPtr, lenPtr)
return result;
}
-/*
- *---------------------------------------------------------------------------
- *
- * Tcl_FSJoinPath --
- *
- * This function takes the given Tcl_Obj, which should be a valid
- * list, and returns the path object given by considering the
- * first 'elements' elements as valid path segments. If elements < 0,
- * we use the entire list.
- *
- * Results:
- * Returns object with refCount of zero, (or if non-zero, it has
- * references elsewhere in Tcl). Either way, the caller must
- * increment its refCount before use.
- *
- * Side effects:
- * None.
- *
- *---------------------------------------------------------------------------
- */
+/* Simple helper function */
Tcl_Obj*
-Tcl_FSJoinPath(listObj, elements)
- Tcl_Obj *listObj;
- int elements;
+TclFSInternalToNormalized(fromFilesystem, clientData, fsRecPtrPtr, epochPtr)
+ Tcl_Filesystem *fromFilesystem;
+ ClientData clientData;
+ FilesystemRecord **fsRecPtrPtr;
+ int *epochPtr;
{
- Tcl_Obj *res;
- int i;
- Tcl_Filesystem *fsPtr = NULL;
-
- if (elements < 0) {
- if (Tcl_ListObjLength(NULL, listObj, &elements) != TCL_OK) {
- return NULL;
- }
- } else {
- /* Just make sure it is a valid list */
- int listTest;
- if (Tcl_ListObjLength(NULL, listObj, &listTest) != TCL_OK) {
- return NULL;
- }
- /*
- * Correct this if it is too large, otherwise we will
- * waste our time joining null elements to the path
- */
- if (elements > listTest) {
- elements = listTest;
- }
- }
-
- if (elements == 2) {
- /*
- * This is a special case where we can be much more
- * efficient
- */
- Tcl_Obj *base;
-
- Tcl_ListObjIndex(NULL, listObj, 0, &base);
- /*
- * There is only any value in doing this if the first object is
- * of path type, otherwise we'll never actually get any
- * efficiency benefit elsewhere in the code (from re-using the
- * normalized representation of the base object).
- */
- if (base->typePtr == &tclFsPathType
- && !(base->bytes != NULL && base->bytes[0] == '\0')) {
- Tcl_Obj *tail;
- Tcl_PathType type;
- Tcl_ListObjIndex(NULL, listObj, 1, &tail);
- type = GetPathType(tail, NULL, NULL, NULL);
- if (type == TCL_PATH_RELATIVE) {
- CONST char *str;
- int len;
- str = Tcl_GetStringFromObj(tail,&len);
- if (len == 0) {
- /*
- * This happens if we try to handle the root volume
- * '/'. There's no need to return a special path
- * object, when the base itself is just fine!
- */
- return base;
- }
- if (str[0] != '.') {
- return TclNewFSPathObj(base, str, len);
- }
- /*
- * Otherwise we don't have an easy join, and
- * we must let the more general code below handle
- * things
- */
- } else {
- return tail;
- }
+ FilesystemRecord *fsRecPtr = FsGetIterator();
+ while (fsRecPtr != NULL) {
+ if (fsRecPtr->fsPtr == fromFilesystem) {
+ *epochPtr = theFilesystemEpoch;
+ *fsRecPtrPtr = fsRecPtr;
+ break;
}
+ fsRecPtr = fsRecPtr->nextPtr;
}
+ FsReleaseIterator();
- res = Tcl_NewObj();
-
- for (i = 0; i < elements; i++) {
- Tcl_Obj *elt;
- int driveNameLength;
- Tcl_PathType type;
- char *strElt;
- int strEltLen;
- int length;
- char *ptr;
- Tcl_Obj *driveName = NULL;
-
- Tcl_ListObjIndex(NULL, listObj, i, &elt);
- strElt = Tcl_GetStringFromObj(elt, &strEltLen);
- type = GetPathType(elt, &fsPtr, &driveNameLength, &driveName);
- if (type != TCL_PATH_RELATIVE) {
- /* Zero out the current result */
- Tcl_DecrRefCount(res);
- if (driveName != NULL) {
- res = Tcl_DuplicateObj(driveName);
- Tcl_DecrRefCount(driveName);
- } else {
- res = Tcl_NewStringObj(strElt, driveNameLength);
- }
- strElt += driveNameLength;
- }
-
- ptr = Tcl_GetStringFromObj(res, &length);
-
- /*
- * Strip off any './' before a tilde, unless this is the
- * beginning of the path.
- */
- if (length > 0 && strEltLen > 0) {
- if ((strElt[0] == '.') && (strElt[1] == '/')
- && (strElt[2] == '~')) {
- strElt += 2;
- }
- }
-
- /*
- * A NULL value for fsPtr at this stage basically means
- * we're trying to join a relative path onto something
- * which is also relative (or empty). There's nothing
- * particularly wrong with that.
- */
- if (*strElt == '\0') continue;
-
- if (fsPtr == &tclNativeFilesystem || fsPtr == NULL) {
- TclpNativeJoinPath(res, strElt);
- } else {
- char separator = '/';
- int needsSep = 0;
-
- if (fsPtr->filesystemSeparatorProc != NULL) {
- Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(res);
- if (sep != NULL) {
- separator = Tcl_GetString(sep)[0];
- }
- }
-
- if (length > 0 && ptr[length -1] != '/') {
- Tcl_AppendToObj(res, &separator, 1);
- length++;
- }
- Tcl_SetObjLength(res, length + (int) strlen(strElt));
-
- ptr = Tcl_GetString(res) + length;
- for (; *strElt != '\0'; strElt++) {
- if (*strElt == separator) {
- while (strElt[1] == separator) {
- strElt++;
- }
- if (strElt[1] != '\0') {
- if (needsSep) {
- *ptr++ = separator;
- }
- }
- } else {
- *ptr++ = *strElt;
- needsSep = 1;
- }
- }
- length = ptr - Tcl_GetString(res);
- Tcl_SetObjLength(res, length);
- }
+ if ((fsRecPtr != NULL)
+ && (fromFilesystem->internalToNormalizedProc != NULL)) {
+ return (*fromFilesystem->internalToNormalizedProc)(clientData);
+ } else {
+ return NULL;
}
- return res;
}
/*
@@ -3374,7 +3016,7 @@ Tcl_FSJoinPath(listObj, elements)
*----------------------------------------------------------------------
*/
-static Tcl_PathType
+Tcl_PathType
GetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr, driveNameRef)
Tcl_Obj *pathObjPtr;
Tcl_Filesystem **filesystemPtrPtr;
@@ -3810,1135 +3452,77 @@ Tcl_FSRemoveDirectory(pathPtr, recursive, errorPtr)
/*
*---------------------------------------------------------------------------
*
- * Tcl_FSConvertToPathType --
- *
- * This function tries to convert the given Tcl_Obj to a valid
- * Tcl path type, taking account of the fact that the cwd may
- * have changed even if this object is already supposedly of
- * the correct type.
- *
- * The filename may begin with "~" (to indicate current user's
- * home directory) or "~<user>" (to indicate any user's home
- * directory).
- *
- * Results:
- * Standard Tcl error code.
- *
- * Side effects:
- * The old representation may be freed, and new memory allocated.
- *
- *---------------------------------------------------------------------------
- */
-int
-Tcl_FSConvertToPathType(interp, objPtr)
- Tcl_Interp *interp; /* Interpreter in which to store error
- * message (if necessary). */
- Tcl_Obj *objPtr; /* Object to convert to a valid, current
- * path type. */
-{
- /*
- * While it is bad practice to examine an object's type directly,
- * this is actually the best thing to do here. The reason is that
- * if we are converting this object to FsPath type for the first
- * time, we don't need to worry whether the 'cwd' has changed.
- * On the other hand, if this object is already of FsPath type,
- * and is a relative path, we do have to worry about the cwd.
- * If the cwd has changed, we must recompute the path.
- */
- if (objPtr->typePtr == &tclFsPathType) {
- FsPath *fsPathPtr = (FsPath*) objPtr->internalRep.otherValuePtr;
- if (fsPathPtr->filesystemEpoch != theFilesystemEpoch) {
- if (objPtr->bytes == NULL) {
- UpdateStringOfFsPath(objPtr);
- }
- FreeFsPathInternalRep(objPtr);
- objPtr->typePtr = NULL;
- return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
- }
- return TCL_OK;
- /*
- * This code is intentionally never reached. Once fs-optimisation
- * is complete, it will be removed/replaced
- */
- if (fsPathPtr->cwdPtr == NULL) {
- return TCL_OK;
- } else {
- if (FsCwdPointerEquals(fsPathPtr->cwdPtr)) {
- return TCL_OK;
- } else {
- if (objPtr->bytes == NULL) {
- UpdateStringOfFsPath(objPtr);
- }
- FreeFsPathInternalRep(objPtr);
- objPtr->typePtr = NULL;
- return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
- }
- }
- } else {
- return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
- }
-}
-
-
-/*
- * Helper function for SetFsPathFromAny. Returns position of first
- * directory delimiter in the path.
- */
-static int
-FindSplitPos(path, separator)
- char *path;
- char *separator;
-{
- int count = 0;
- switch (tclPlatform) {
- case TCL_PLATFORM_UNIX:
- case TCL_PLATFORM_MAC:
- while (path[count] != 0) {
- if (path[count] == *separator) {
- return count;
- }
- count++;
- }
- break;
-
- case TCL_PLATFORM_WINDOWS:
- while (path[count] != 0) {
- if (path[count] == *separator || path[count] == '\\') {
- return count;
- }
- count++;
- }
- break;
- }
- return count;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * UpdateStringOfFsPath --
- *
- * Gives an object a valid string rep.
- *
- * Results:
- * None.
- *
- * Side effects:
- * Memory may be allocated.
- *
- *---------------------------------------------------------------------------
- */
-
-static void
-UpdateStringOfFsPath(objPtr)
- register Tcl_Obj *objPtr; /* path obj with string rep to update. */
-{
- register FsPath* fsPathPtr =
- (FsPath*) objPtr->internalRep.otherValuePtr;
- CONST char *cwdStr;
- int cwdLen;
- Tcl_Obj *copy;
-
- if (fsPathPtr->flags == 0 || fsPathPtr->cwdPtr == NULL) {
- panic("Called UpdateStringOfFsPath with invalid object");
- }
-
- copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
- Tcl_IncrRefCount(copy);
-
- cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
- /*
- * Should we perhaps use 'Tcl_FSPathSeparator'?
- * But then what about the Windows special case?
- * Perhaps we should just check if cwd is a root volume.
- * We should never get cwdLen == 0 in this code path.
- */
- switch (tclPlatform) {
- case TCL_PLATFORM_UNIX:
- if (cwdStr[cwdLen-1] != '/') {
- Tcl_AppendToObj(copy, "/", 1);
- cwdLen++;
- }
- break;
- case TCL_PLATFORM_WINDOWS:
- /*
- * We need the extra 'cwdLen != 2', and ':' checks because
- * a volume relative path doesn't get a '/'. For example
- * 'glob C:*cat*.exe' will return 'C:cat32.exe'
- */
- if (cwdStr[cwdLen-1] != '/'
- && cwdStr[cwdLen-1] != '\\') {
- if (cwdLen != 2 || cwdStr[1] != ':') {
- Tcl_AppendToObj(copy, "/", 1);
- cwdLen++;
- }
- }
- break;
- case TCL_PLATFORM_MAC:
- if (cwdStr[cwdLen-1] != ':') {
- Tcl_AppendToObj(copy, ":", 1);
- cwdLen++;
- }
- break;
- }
- Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
- objPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen);
- objPtr->length = cwdLen;
- copy->bytes = tclEmptyStringRep;
- copy->length = 0;
- Tcl_DecrRefCount(copy);
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * TclNewFSPathObj --
- *
- * Creates a path object whose string representation is
- * '[file join dirPtr addStrRep]', but does so in a way that
- * allows for more efficient caching of normalized paths.
- *
- * Assumptions:
- * 'dirPtr' must be an absolute path.
- * 'len' may not be zero.
- *
- * Results:
- * The new Tcl object.
- *
- * Side effects:
- * Memory is allocated. 'dirPtr' gets an additional refCount.
- *
- *---------------------------------------------------------------------------
- */
-
-Tcl_Obj*
-TclNewFSPathObj(Tcl_Obj *dirPtr, CONST char *addStrRep, int len)
-{
- FsPath *fsPathPtr;
- Tcl_Obj *objPtr;
-
- objPtr = Tcl_NewObj();
- fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
-
- if (tclPlatform == TCL_PLATFORM_MAC) {
- /*
- * Mac relative paths may begin with a directory separator ':'.
- * If present, we need to skip this ':' because we assume that
- * we can join dirPtr and addStrRep by concatenating them as
- * strings (and we ensure that dirPtr is terminated by a ':').
- */
- if (addStrRep[0] == ':') {
- addStrRep++;
- len--;
- }
- }
- /* Setup the path */
- fsPathPtr->translatedPathPtr = NULL;
- fsPathPtr->normPathPtr = Tcl_NewStringObj(addStrRep, len);
- Tcl_IncrRefCount(fsPathPtr->normPathPtr);
- fsPathPtr->cwdPtr = dirPtr;
- Tcl_IncrRefCount(dirPtr);
- fsPathPtr->flags = TCLPATH_RELATIVE | TCLPATH_APPENDED;
- fsPathPtr->nativePathPtr = NULL;
- fsPathPtr->fsRecPtr = NULL;
- fsPathPtr->filesystemEpoch = theFilesystemEpoch;
-
- objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr;
- objPtr->typePtr = &tclFsPathType;
- objPtr->bytes = NULL;
- objPtr->length = 0;
- return objPtr;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * MakeFsPathFromRelative --
- *
- * Like SetFsPathFromAny, but assumes the given object is an
- * absolute normalized path. Only for internal use.
- *
- * Results:
- * Standard Tcl error code.
- *
- * Side effects:
- * The old representation may be freed, and new memory allocated.
- *
- *---------------------------------------------------------------------------
- */
-
-static Tcl_Obj*
-MakeFsPathFromRelative(interp, objPtr, cwdPtr)
- Tcl_Interp *interp; /* Used for error reporting if not NULL. */
- Tcl_Obj *objPtr; /* The object to convert. */
- Tcl_Obj *cwdPtr; /* The object to convert. */
-{
- FsPath *fsPathPtr;
-
- /* Free old representation */
- if (objPtr->typePtr != NULL) {
- if (objPtr->bytes == NULL) {
- if (objPtr->typePtr->updateStringProc == NULL) {
- if (interp != NULL) {
- Tcl_ResetResult(interp);
- Tcl_AppendResult(interp, "can't find object",
- "string representation", (char *) NULL);
- }
- return NULL;
- }
- objPtr->typePtr->updateStringProc(objPtr);
- }
- if ((objPtr->typePtr->freeIntRepProc) != NULL) {
- (*objPtr->typePtr->freeIntRepProc)(objPtr);
- }
- }
-
- fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
-
- /* Circular reference, by design */
- fsPathPtr->translatedPathPtr = objPtr;
- fsPathPtr->normPathPtr = NULL;
- fsPathPtr->flags = 0;
- fsPathPtr->cwdPtr = cwdPtr;
- Tcl_IncrRefCount(cwdPtr);
- fsPathPtr->nativePathPtr = NULL;
- fsPathPtr->fsRecPtr = NULL;
- fsPathPtr->filesystemEpoch = theFilesystemEpoch;
-
- objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr;
- objPtr->typePtr = &tclFsPathType;
-
- return objPtr;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * SetFsPathFromAbsoluteNormalized --
- *
- * Like SetFsPathFromAny, but assumes the given object is an
- * absolute normalized path. Only for internal use.
- *
- * Results:
- * Standard Tcl error code.
- *
- * Side effects:
- * The old representation may be freed, and new memory allocated.
- *
- *---------------------------------------------------------------------------
- */
-
-static int
-SetFsPathFromAbsoluteNormalized(interp, objPtr)
- Tcl_Interp *interp; /* Used for error reporting if not NULL. */
- Tcl_Obj *objPtr; /* The object to convert. */
-{
- FsPath *fsPathPtr;
-
- if (objPtr->typePtr == &tclFsPathType) {
- return TCL_OK;
- }
-
- /* Free old representation */
- if (objPtr->typePtr != NULL) {
- if (objPtr->bytes == NULL) {
- if (objPtr->typePtr->updateStringProc == NULL) {
- if (interp != NULL) {
- Tcl_ResetResult(interp);
- Tcl_AppendResult(interp, "can't find object",
- "string representation", (char *) NULL);
- }
- return TCL_ERROR;
- }
- objPtr->typePtr->updateStringProc(objPtr);
- }
- if ((objPtr->typePtr->freeIntRepProc) != NULL) {
- (*objPtr->typePtr->freeIntRepProc)(objPtr);
- }
- }
-
- fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
- /* It's a pure normalized absolute path */
- fsPathPtr->translatedPathPtr = NULL;
- fsPathPtr->normPathPtr = objPtr;
- fsPathPtr->flags = 0;
- fsPathPtr->cwdPtr = NULL;
- fsPathPtr->nativePathPtr = NULL;
- fsPathPtr->fsRecPtr = NULL;
- fsPathPtr->filesystemEpoch = theFilesystemEpoch;
-
- objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr;
- objPtr->typePtr = &tclFsPathType;
-
- return TCL_OK;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * SetFsPathFromAny --
+ * Tcl_FSGetFileSystemForPath --
*
- * This function tries to convert the given Tcl_Obj to a valid
- * Tcl path type.
- *
- * The filename may begin with "~" (to indicate current user's
- * home directory) or "~<user>" (to indicate any user's home
- * directory).
+ * This function determines which filesystem to use for a
+ * particular path object, and returns the filesystem which
+ * accepts this file. If no filesystem will accept this object
+ * as a valid file path, then NULL is returned.
*
* Results:
- * Standard Tcl error code.
+.* NULL or a filesystem which will accept this path.
*
* Side effects:
- * The old representation may be freed, and new memory allocated.
+ * The object may be converted to a path type.
*
*---------------------------------------------------------------------------
*/
-static int
-SetFsPathFromAny(interp, objPtr)
- Tcl_Interp *interp; /* Used for error reporting if not NULL. */
- Tcl_Obj *objPtr; /* The object to convert. */
+Tcl_Filesystem*
+Tcl_FSGetFileSystemForPath(pathObjPtr)
+ Tcl_Obj* pathObjPtr;
{
- int len;
- FsPath *fsPathPtr;
- Tcl_Obj *transPtr;
- char *name;
-
- if (objPtr->typePtr == &tclFsPathType) {
- return TCL_OK;
- }
+ FilesystemRecord *fsRecPtr;
+ Tcl_Filesystem* retVal = NULL;
/*
- * First step is to translate the filename. This is similar to
- * Tcl_TranslateFilename, but shouldn't convert everything to
- * windows backslashes on that platform. The current
- * implementation of this piece is a slightly optimised version
- * of the various Tilde/Split/Join stuff to avoid multiple
- * split/join operations.
- *
- * We remove any trailing directory separator.
- *
- * However, the split/join routines are quite complex, and
- * one has to make sure not to break anything on Unix, Win
- * or MacOS (fCmd.test, fileName.test and cmdAH.test exercise
- * most of the code).
- */
- name = Tcl_GetStringFromObj(objPtr,&len);
-
- /*
- * Handle tilde substitutions, if needed.
- */
- if (name[0] == '~') {
- char *expandedUser;
- Tcl_DString temp;
- int split;
- char separator='/';
-
- if (tclPlatform==TCL_PLATFORM_MAC) {
- if (strchr(name, ':') != NULL) separator = ':';
- }
-
- split = FindSplitPos(name, &separator);
- if (split != len) {
- /* We have multiple pieces '~user/foo/bar...' */
- name[split] = '\0';
- }
- /* Do some tilde substitution */
- if (name[1] == '\0') {
- /* We have just '~' */
- CONST char *dir;
- Tcl_DString dirString;
- if (split != len) { name[split] = separator; }
-
- dir = TclGetEnv("HOME", &dirString);
- if (dir == NULL) {
- if (interp) {
- Tcl_ResetResult(interp);
- Tcl_AppendResult(interp, "couldn't find HOME environment ",
- "variable to expand path", (char *) NULL);
- }
- return TCL_ERROR;
- }
- Tcl_DStringInit(&temp);
- Tcl_JoinPath(1, &dir, &temp);
- Tcl_DStringFree(&dirString);
- } else {
- /* We have a user name '~user' */
- Tcl_DStringInit(&temp);
- if (TclpGetUserHome(name+1, &temp) == NULL) {
- if (interp != NULL) {
- Tcl_ResetResult(interp);
- Tcl_AppendResult(interp, "user \"", (name+1),
- "\" doesn't exist", (char *) NULL);
- }
- Tcl_DStringFree(&temp);
- if (split != len) { name[split] = separator; }
- return TCL_ERROR;
- }
- if (split != len) { name[split] = separator; }
- }
-
- expandedUser = Tcl_DStringValue(&temp);
- transPtr = Tcl_NewStringObj(expandedUser, Tcl_DStringLength(&temp));
-
- if (split != len) {
- /* Join up the tilde substitution with the rest */
- if (name[split+1] == separator) {
-
- /*
- * Somewhat tricky case like ~//foo/bar.
- * Make use of Split/Join machinery to get it right.
- * Assumes all paths beginning with ~ are part of the
- * native filesystem.
- */
-
- int objc;
- Tcl_Obj **objv;
- Tcl_Obj *parts = TclpNativeSplitPath(objPtr, NULL);
- Tcl_ListObjGetElements(NULL, parts, &objc, &objv);
- /* Skip '~'. It's replaced by its expansion */
- objc--; objv++;
- while (objc--) {
- TclpNativeJoinPath(transPtr, Tcl_GetString(*objv++));
- }
- Tcl_DecrRefCount(parts);
- } else {
- /* Simple case. "rest" is relative path. Just join it. */
- Tcl_Obj *rest = Tcl_NewStringObj(name+split+1,-1);
- transPtr = Tcl_FSJoinToPath(transPtr, 1, &rest);
- }
- }
- Tcl_DStringFree(&temp);
- } else {
- transPtr = Tcl_FSJoinToPath(objPtr,0,NULL);
- }
-
- /*
- * Now we have a translated filename in 'transPtr'. This will have
- * forward slashes on Windows, and will not contain any ~user
- * sequences.
+ * If the object has a refCount of zero, we reject it. This
+ * is to avoid possible segfaults or nondeterministic memory
+ * leaks (i.e. the user doesn't know if they should decrement
+ * the ref count on return or not).
*/
- fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
- fsPathPtr->translatedPathPtr = transPtr;
- Tcl_IncrRefCount(fsPathPtr->translatedPathPtr);
- fsPathPtr->normPathPtr = NULL;
- fsPathPtr->flags = 0;
- fsPathPtr->cwdPtr = NULL;
- fsPathPtr->nativePathPtr = NULL;
- fsPathPtr->fsRecPtr = NULL;
- fsPathPtr->filesystemEpoch = theFilesystemEpoch;
-
- /*
- * Free old representation before installing our new one.
- */
- if (objPtr->typePtr != NULL && objPtr->typePtr->freeIntRepProc != NULL) {
- (objPtr->typePtr->freeIntRepProc)(objPtr);
- }
- objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr;
- objPtr->typePtr = &tclFsPathType;
-
- return TCL_OK;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * Tcl_FSNewNativePath --
- *
- * This function performs the something like that reverse of the
- * usual obj->path->nativerep conversions. If some code retrieves
- * a path in native form (from, e.g. readlink or a native dialog),
- * and that path is to be used at the Tcl level, then calling
- * this function is an efficient way of creating the appropriate
- * path object type.
- *
- * Any memory which is allocated for 'clientData' should be retained
- * until clientData is passed to the filesystem's freeInternalRepProc
- * when it can be freed. The built in platform-specific filesystems
- * use 'ckalloc' to allocate clientData, and ckfree to free it.
- *
- * Results:
- * NULL or a valid path object pointer, with refCount zero.
- *
- * Side effects:
- * New memory may be allocated.
- *
- *---------------------------------------------------------------------------
- */
-
-Tcl_Obj *
-Tcl_FSNewNativePath(fromFilesystem, clientData)
- Tcl_Filesystem* fromFilesystem;
- ClientData clientData;
-{
- Tcl_Obj *objPtr;
- FsPath *fsPathPtr;
- FilesystemRecord *fsFromPtr;
- Tcl_FSInternalToNormalizedProc *proc;
- int epoch;
-
- fsFromPtr = GetFilesystemRecord(fromFilesystem, &epoch);
-
- if (fsFromPtr == NULL) {
+ if (pathObjPtr->refCount == 0) {
+ panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0");
return NULL;
}
- proc = fsFromPtr->fsPtr->internalToNormalizedProc;
-
- if (proc == NULL) {
- return NULL;
- }
-
- objPtr = (*proc)(clientData);
- if (objPtr == NULL) {
- return NULL;
- }
-
/*
- * Free old representation; shouldn't normally be any,
- * but best to be safe.
+ * Check if the filesystem has changed in some way since
+ * this object's internal representation was calculated.
*/
- if (objPtr->typePtr != NULL) {
- if (objPtr->bytes == NULL) {
- if (objPtr->typePtr->updateStringProc == NULL) {
- return NULL;
- }
- objPtr->typePtr->updateStringProc(objPtr);
- }
- if ((objPtr->typePtr->freeIntRepProc) != NULL) {
- (*objPtr->typePtr->freeIntRepProc)(objPtr);
- }
- }
-
- fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
- fsPathPtr->translatedPathPtr = NULL;
- /* Circular reference, by design */
- fsPathPtr->normPathPtr = objPtr;
- fsPathPtr->flags = 0;
- fsPathPtr->cwdPtr = NULL;
- fsPathPtr->nativePathPtr = clientData;
- fsPathPtr->fsRecPtr = fsFromPtr;
- /* We must increase the refCount for this filesystem. */
- fsPathPtr->fsRecPtr->fileRefCount++;
- fsPathPtr->filesystemEpoch = epoch;
-
- objPtr->internalRep.otherValuePtr = (VOID *) fsPathPtr;
- objPtr->typePtr = &tclFsPathType;
- return objPtr;
-}
-
-static void
-FreeFsPathInternalRep(pathObjPtr)
- Tcl_Obj *pathObjPtr; /* Path object with internal rep to free. */
-{
- register FsPath* fsPathPtr =
- (FsPath*) pathObjPtr->internalRep.otherValuePtr;
-
- if (fsPathPtr->translatedPathPtr != NULL) {
- if (fsPathPtr->translatedPathPtr != pathObjPtr) {
- Tcl_DecrRefCount(fsPathPtr->translatedPathPtr);
- }
- }
- if (fsPathPtr->normPathPtr != NULL) {
- if (fsPathPtr->normPathPtr != pathObjPtr) {
- Tcl_DecrRefCount(fsPathPtr->normPathPtr);
- }
- fsPathPtr->normPathPtr = NULL;
- }
- if (fsPathPtr->cwdPtr != NULL) {
- Tcl_DecrRefCount(fsPathPtr->cwdPtr);
- }
- if (fsPathPtr->nativePathPtr != NULL) {
- if (fsPathPtr->fsRecPtr != NULL) {
- if (fsPathPtr->fsRecPtr->fsPtr->freeInternalRepProc != NULL) {
- (*fsPathPtr->fsRecPtr->fsPtr
- ->freeInternalRepProc)(fsPathPtr->nativePathPtr);
- fsPathPtr->nativePathPtr = NULL;
- }
- }
- }
- if (fsPathPtr->fsRecPtr != NULL) {
- fsPathPtr->fsRecPtr->fileRefCount--;
- if (fsPathPtr->fsRecPtr->fileRefCount <= 0) {
- /* It has been unregistered already */
- ckfree((char *)fsPathPtr->fsRecPtr);
- }
- }
-
- ckfree((char*) fsPathPtr);
-}
-
-static void
-DupFsPathInternalRep(srcPtr, copyPtr)
- Tcl_Obj *srcPtr; /* Path obj with internal rep to copy. */
- Tcl_Obj *copyPtr; /* Path obj with internal rep to set. */
-{
- register FsPath* srcFsPathPtr =
- (FsPath*) srcPtr->internalRep.otherValuePtr;
- register FsPath* copyFsPathPtr =
- (FsPath*) ckalloc((unsigned)sizeof(FsPath));
- Tcl_FSDupInternalRepProc *dupProc;
-
- copyPtr->internalRep.otherValuePtr = (VOID *) copyFsPathPtr;
-
- if (srcFsPathPtr->translatedPathPtr != NULL) {
- copyFsPathPtr->translatedPathPtr = srcFsPathPtr->translatedPathPtr;
- if (copyFsPathPtr->translatedPathPtr != copyPtr) {
- Tcl_IncrRefCount(copyFsPathPtr->translatedPathPtr);
- }
- } else {
- copyFsPathPtr->translatedPathPtr = NULL;
- }
-
- if (srcFsPathPtr->normPathPtr != NULL) {
- copyFsPathPtr->normPathPtr = srcFsPathPtr->normPathPtr;
- if (copyFsPathPtr->normPathPtr != copyPtr) {
- Tcl_IncrRefCount(copyFsPathPtr->normPathPtr);
- }
- } else {
- copyFsPathPtr->normPathPtr = NULL;
- }
-
- if (srcFsPathPtr->cwdPtr != NULL) {
- copyFsPathPtr->cwdPtr = srcFsPathPtr->cwdPtr;
- Tcl_IncrRefCount(copyFsPathPtr->cwdPtr);
- } else {
- copyFsPathPtr->cwdPtr = NULL;
- }
-
- copyFsPathPtr->flags = srcFsPathPtr->flags;
-
- if (srcFsPathPtr->fsRecPtr != NULL
- && srcFsPathPtr->nativePathPtr != NULL) {
- dupProc = srcFsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc;
- if (dupProc != NULL) {
- copyFsPathPtr->nativePathPtr =
- (*dupProc)(srcFsPathPtr->nativePathPtr);
- } else {
- copyFsPathPtr->nativePathPtr = NULL;
- }
- } else {
- copyFsPathPtr->nativePathPtr = NULL;
- }
- copyFsPathPtr->fsRecPtr = srcFsPathPtr->fsRecPtr;
- copyFsPathPtr->filesystemEpoch = srcFsPathPtr->filesystemEpoch;
- if (copyFsPathPtr->fsRecPtr != NULL) {
- copyFsPathPtr->fsRecPtr->fileRefCount++;
- }
-
- copyPtr->typePtr = &tclFsPathType;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * Tcl_FSGetTranslatedPath --
- *
- * This function attempts to extract the translated path
- * from the given Tcl_Obj. If the translation succeeds (i.e. the
- * object is a valid path), then it is returned. Otherwise NULL
- * will be returned, and an error message may be left in the
- * interpreter (if it is non-NULL)
- *
- * Results:
- * NULL or a valid Tcl_Obj pointer.
- *
- * Side effects:
- * Only those of 'Tcl_FSConvertToPathType'
- *
- *---------------------------------------------------------------------------
- */
-
-Tcl_Obj*
-Tcl_FSGetTranslatedPath(interp, pathPtr)
- Tcl_Interp *interp;
- Tcl_Obj* pathPtr;
-{
- register FsPath* srcFsPathPtr;
- if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) {
+ if (TclFSEnsureEpochOk(pathObjPtr, theFilesystemEpoch,
+ &retVal) != TCL_OK) {
return NULL;
}
- srcFsPathPtr = (FsPath*) pathPtr->internalRep.otherValuePtr;
- if (srcFsPathPtr->translatedPathPtr == NULL) {
- if (srcFsPathPtr->flags != 0) {
- return Tcl_FSGetNormalizedPath(interp, pathPtr);
- }
- /*
- * It is a pure absolute, normalized path object.
- * This is something like being a 'pure list'. The
- * object's string, translatedPath and normalizedPath
- * are all identical.
- */
- return srcFsPathPtr->normPathPtr;
- } else {
- /* It is an ordinary path object */
- return srcFsPathPtr->translatedPathPtr;
- }
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * Tcl_FSGetTranslatedStringPath --
- *
- * This function attempts to extract the translated path
- * from the given Tcl_Obj. If the translation succeeds (i.e. the
- * object is a valid path), then the path is returned. Otherwise NULL
- * will be returned, and an error message may be left in the
- * interpreter (if it is non-NULL)
- *
- * Results:
- * NULL or a valid string.
- *
- * Side effects:
- * Only those of 'Tcl_FSConvertToPathType'
- *
- *---------------------------------------------------------------------------
- */
-CONST char*
-Tcl_FSGetTranslatedStringPath(interp, pathPtr)
- Tcl_Interp *interp;
- Tcl_Obj* pathPtr;
-{
- Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
- if (transPtr == NULL) {
- return NULL;
- } else {
- return Tcl_GetString(transPtr);
- }
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * Tcl_FSGetNormalizedPath --
- *
- * This important function attempts to extract from the given Tcl_Obj
- * a unique normalised path representation, whose string value can
- * be used as a unique identifier for the file.
- *
- * Results:
- * NULL or a valid path object pointer.
- *
- * Side effects:
- * New memory may be allocated. The Tcl 'errno' may be modified
- * in the process of trying to examine various path possibilities.
- *
- *---------------------------------------------------------------------------
- */
-
-Tcl_Obj*
-Tcl_FSGetNormalizedPath(interp, pathObjPtr)
- Tcl_Interp *interp;
- Tcl_Obj* pathObjPtr;
-{
- register FsPath* fsPathPtr;
- if (Tcl_FSConvertToPathType(interp, pathObjPtr) != TCL_OK) {
- return NULL;
- }
- fsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr;
-
- if (fsPathPtr->flags != 0) {
- /*
- * This is a special path object which is the result of
- * something like 'file join'
- */
- Tcl_Obj *dir, *copy;
- int cwdLen;
- int pathType;
- CONST char *cwdStr;
-
- pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr);
- dir = Tcl_FSGetNormalizedPath(interp, fsPathPtr->cwdPtr);
- if (dir == NULL) {
- return NULL;
- }
- if (pathObjPtr->bytes == NULL) {
- UpdateStringOfFsPath(pathObjPtr);
- }
- copy = Tcl_DuplicateObj(dir);
- Tcl_IncrRefCount(copy);
- Tcl_IncrRefCount(dir);
- /* We now own a reference on both 'dir' and 'copy' */
-
- cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
- /*
- * Should we perhaps use 'Tcl_FSPathSeparator'?
- * But then what about the Windows special case?
- * Perhaps we should just check if cwd is a root volume.
- * We should never get cwdLen == 0 in this code path.
- */
- switch (tclPlatform) {
- case TCL_PLATFORM_UNIX:
- if (cwdStr[cwdLen-1] != '/') {
- Tcl_AppendToObj(copy, "/", 1);
- cwdLen++;
- }
- break;
- case TCL_PLATFORM_WINDOWS:
- if (cwdStr[cwdLen-1] != '/'
- && cwdStr[cwdLen-1] != '\\') {
- Tcl_AppendToObj(copy, "/", 1);
- cwdLen++;
- }
- break;
- case TCL_PLATFORM_MAC:
- if (cwdStr[cwdLen-1] != ':') {
- Tcl_AppendToObj(copy, ":", 1);
- cwdLen++;
- }
- break;
- }
- Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
- /*
- * Normalize the combined string, but only starting after
- * the end of the previously normalized 'dir'. This should
- * be much faster! We use 'cwdLen-1' so that we are
- * already pointing at the dir-separator that we know about.
- * The normalization code will actually start off directly
- * after that separator.
- */
- TclNormalizeToUniquePath(interp, copy, cwdLen-1);
- /* Now we need to construct the new path object */
-
- if (pathType == TCL_PATH_RELATIVE) {
- register FsPath* origDirFsPathPtr;
- Tcl_Obj *origDir = fsPathPtr->cwdPtr;
- origDirFsPathPtr = (FsPath*) origDir->internalRep.otherValuePtr;
-
- fsPathPtr->cwdPtr = origDirFsPathPtr->cwdPtr;
- Tcl_IncrRefCount(fsPathPtr->cwdPtr);
-
- Tcl_DecrRefCount(fsPathPtr->normPathPtr);
- fsPathPtr->normPathPtr = copy;
- /* That's our reference to copy used */
- Tcl_DecrRefCount(dir);
- Tcl_DecrRefCount(origDir);
- } else {
- Tcl_DecrRefCount(fsPathPtr->cwdPtr);
- fsPathPtr->cwdPtr = NULL;
- Tcl_DecrRefCount(fsPathPtr->normPathPtr);
- fsPathPtr->normPathPtr = copy;
- /* That's our reference to copy used */
- Tcl_DecrRefCount(dir);
- }
- fsPathPtr->flags = 0;
- }
- /* Ensure cwd hasn't changed */
- if (fsPathPtr->cwdPtr != NULL) {
- if (!FsCwdPointerEquals(fsPathPtr->cwdPtr)) {
- if (pathObjPtr->bytes == NULL) {
- UpdateStringOfFsPath(pathObjPtr);
- }
- FreeFsPathInternalRep(pathObjPtr);
- pathObjPtr->typePtr = NULL;
- if (Tcl_ConvertToType(interp, pathObjPtr,
- &tclFsPathType) != TCL_OK) {
- return NULL;
- }
- fsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr;
- } else if (fsPathPtr->normPathPtr == NULL) {
- int cwdLen;
- Tcl_Obj *copy;
- CONST char *cwdStr;
-
- copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
- Tcl_IncrRefCount(copy);
- cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
- /*
- * Should we perhaps use 'Tcl_FSPathSeparator'?
- * But then what about the Windows special case?
- * Perhaps we should just check if cwd is a root volume.
- * We should never get cwdLen == 0 in this code path.
- */
- switch (tclPlatform) {
- case TCL_PLATFORM_UNIX:
- if (cwdStr[cwdLen-1] != '/') {
- Tcl_AppendToObj(copy, "/", 1);
- cwdLen++;
- }
- break;
- case TCL_PLATFORM_WINDOWS:
- if (cwdStr[cwdLen-1] != '/'
- && cwdStr[cwdLen-1] != '\\') {
- Tcl_AppendToObj(copy, "/", 1);
- cwdLen++;
- }
- break;
- case TCL_PLATFORM_MAC:
- if (cwdStr[cwdLen-1] != ':') {
- Tcl_AppendToObj(copy, ":", 1);
- cwdLen++;
- }
- break;
- }
- Tcl_AppendObjToObj(copy, pathObjPtr);
- /*
- * Normalize the combined string, but only starting after
- * the end of the previously normalized 'dir'. This should
- * be much faster!
- */
- TclNormalizeToUniquePath(interp, copy, cwdLen-1);
- fsPathPtr->normPathPtr = copy;
- }
- }
- if (fsPathPtr->normPathPtr == NULL) {
- int relative = 0;
- /*
- * Since normPathPtr is NULL, but this is a valid path
- * object, we know that the translatedPathPtr cannot be NULL.
- */
- Tcl_Obj *absolutePath = fsPathPtr->translatedPathPtr;
- char *path = Tcl_GetString(absolutePath);
-
- /*
- * We have to be a little bit careful here to avoid infinite loops
- * we're asking Tcl_FSGetPathType to return the path's type, but
- * that call can actually result in a lot of other filesystem
- * action, which might loop back through here.
- */
- if ((path[0] != '\0') &&
- (Tcl_FSGetPathType(pathObjPtr) == TCL_PATH_RELATIVE)) {
- Tcl_Obj *cwd = Tcl_FSGetCwd(interp);
-
- if (cwd == NULL) {
- return NULL;
- }
- absolutePath = Tcl_FSJoinToPath(cwd, 1, &absolutePath);
- Tcl_IncrRefCount(absolutePath);
- Tcl_DecrRefCount(cwd);
-
- relative = 1;
- }
- /* Already has refCount incremented */
- fsPathPtr->normPathPtr = FSNormalizeAbsolutePath(interp, absolutePath);
- if (!strcmp(Tcl_GetString(fsPathPtr->normPathPtr),
- Tcl_GetString(pathObjPtr))) {
- /*
- * The path was already normalized.
- * Get rid of the duplicate.
- */
- Tcl_DecrRefCount(fsPathPtr->normPathPtr);
- /*
- * We do *not* increment the refCount for
- * this circular reference
- */
- fsPathPtr->normPathPtr = pathObjPtr;
- }
- if (relative) {
- /* This was returned by Tcl_FSJoinToPath above */
- Tcl_DecrRefCount(absolutePath);
-
- /* Get a quick, temporary lock on the cwd while we copy it */
- Tcl_MutexLock(&cwdMutex);
- fsPathPtr->cwdPtr = cwdPathPtr;
- Tcl_IncrRefCount(fsPathPtr->cwdPtr);
- Tcl_MutexUnlock(&cwdMutex);
- }
- }
- return fsPathPtr->normPathPtr;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * Tcl_FSGetInternalRep --
- *
- * Extract the internal representation of a given path object,
- * in the given filesystem. If the path object belongs to a
- * different filesystem, we return NULL.
- *
- * If the internal representation is currently NULL, we attempt
- * to generate it, by calling the filesystem's
- * 'Tcl_FSCreateInternalRepProc'.
- *
- * Results:
- * NULL or a valid internal representation.
- *
- * Side effects:
- * An attempt may be made to convert the object.
- *
- *---------------------------------------------------------------------------
- */
-
-ClientData
-Tcl_FSGetInternalRep(pathObjPtr, fsPtr)
- Tcl_Obj* pathObjPtr;
- Tcl_Filesystem *fsPtr;
-{
- register FsPath* srcFsPathPtr;
-
- if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) {
- return NULL;
- }
- srcFsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr;
-
- /*
- * We will only return the native representation for the caller's
- * filesystem. Otherwise we will simply return NULL. This means
- * that there must be a unique bi-directional mapping between paths
- * and filesystems, and that this mapping will not allow 'remapped'
- * files -- files which are in one filesystem but mapped into
- * another. Another way of putting this is that 'stacked'
- * filesystems are not allowed. We recognise that this is a
- * potentially useful feature for the future.
- *
- * Even something simple like a 'pass through' filesystem which
- * logs all activity and passes the calls onto the native system
- * would be nice, but not easily achievable with the current
- * implementation.
+ /*
+ * Call each of the "pathInFilesystem" functions in succession. A
+ * non-return value of -1 indicates the particular function has
+ * succeeded.
*/
- if (srcFsPathPtr->fsRecPtr == NULL) {
- /*
- * This only usually happens in wrappers like TclpStat which
- * create a string object and pass it to TclpObjStat. Code
- * which calls the Tcl_FS.. functions should always have a
- * filesystem already set. Whether this code path is legal or
- * not depends on whether we decide to allow external code to
- * call the native filesystem directly. It is at least safer
- * to allow this sub-optimal routing.
- */
- Tcl_FSGetFileSystemForPath(pathObjPtr);
-
- /*
- * If we fail through here, then the path is probably not a
- * valid path in the filesystsem, and is most likely to be a
- * use of the empty path "" via a direct call to one of the
- * objectified interfaces (e.g. from the Tcl testsuite).
- */
- srcFsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr;
- if (srcFsPathPtr->fsRecPtr == NULL) {
- return NULL;
- }
- }
- if (fsPtr != srcFsPathPtr->fsRecPtr->fsPtr) {
- /*
- * There is still one possibility we should consider; if the
- * file belongs to a different filesystem, perhaps it is
- * actually linked through to a file in our own filesystem
- * which we do care about. The way we can check for this
- * is we ask what filesystem this path belongs to.
- */
- Tcl_Filesystem *actualFs = Tcl_FSGetFileSystemForPath(pathObjPtr);
- if (actualFs == fsPtr) {
- return Tcl_FSGetInternalRep(pathObjPtr, fsPtr);
+ fsRecPtr = FsGetIterator();
+ while ((retVal == NULL) && (fsRecPtr != NULL)) {
+ Tcl_FSPathInFilesystemProc *proc = fsRecPtr->fsPtr->pathInFilesystemProc;
+ if (proc != NULL) {
+ ClientData clientData = NULL;
+ int ret = (*proc)(pathObjPtr, &clientData);
+ if (ret != -1) {
+ /*
+ * We assume the type of pathObjPtr hasn't been changed
+ * by the above call to the pathInFilesystemProc.
+ */
+ TclFSSetPathDetails(pathObjPtr, fsRecPtr, clientData,
+ theFilesystemEpoch);
+ retVal = fsRecPtr->fsPtr;
+ }
}
- return NULL;
+ fsRecPtr = fsRecPtr->nextPtr;
}
+ FsReleaseIterator();
- if (srcFsPathPtr->nativePathPtr == NULL) {
- Tcl_FSCreateInternalRepProc *proc;
- proc = srcFsPathPtr->fsRecPtr->fsPtr->createInternalRepProc;
-
- if (proc == NULL) {
- return NULL;
- }
- srcFsPathPtr->nativePathPtr = (*proc)(pathObjPtr);
- }
- return srcFsPathPtr->nativePathPtr;
+ return retVal;
}
/*
@@ -5101,7 +3685,7 @@ TclpNativeToNormalized(clientData)
*
*---------------------------------------------------------------------------
*/
-static ClientData
+ClientData
NativeDupInternalRep(clientData)
ClientData clientData;
{
@@ -5133,43 +3717,6 @@ NativeDupInternalRep(clientData)
/*
*---------------------------------------------------------------------------
*
- * NativePathInFilesystem --
- *
- * Any path object is acceptable to the native filesystem, by
- * default (we will throw errors when illegal paths are actually
- * tried to be used).
- *
- * However, this behavior means the native filesystem must be
- * the last filesystem in the lookup list (otherwise it will
- * claim all files belong to it, and other filesystems will
- * never get a look in).
- *
- * Results:
- * TCL_OK, to indicate 'yes', -1 to indicate no.
- *
- * Side effects:
- * None.
- *
- *---------------------------------------------------------------------------
- */
-static int
-NativePathInFilesystem(pathPtr, clientDataPtr)
- Tcl_Obj *pathPtr;
- ClientData *clientDataPtr;
-{
- int len;
- Tcl_GetStringFromObj(pathPtr,&len);
- if (len == 0) {
- return -1;
- } else {
- /* We accept any path as valid */
- return TCL_OK;
- }
-}
-
-/*
- *---------------------------------------------------------------------------
- *
* NativeFreeInternalRep --
*
* Free a native internal representation, which will be non-NULL.
@@ -5303,203 +3850,6 @@ NativeFilesystemSeparator(pathObjPtr)
}
return Tcl_NewStringObj(separator,1);
}
-
-/*
- *---------------------------------------------------------------------------
- *
- * Tcl_FSGetFileSystemForPath --
- *
- * This function determines which filesystem to use for a
- * particular path object, and returns the filesystem which
- * accepts this file. If no filesystem will accept this object
- * as a valid file path, then NULL is returned.
- *
- * Results:
-.* NULL or a filesystem which will accept this path.
- *
- * Side effects:
- * The object may be converted to a path type.
- *
- *---------------------------------------------------------------------------
- */
-
-Tcl_Filesystem*
-Tcl_FSGetFileSystemForPath(pathObjPtr)
- Tcl_Obj* pathObjPtr;
-{
- FilesystemRecord *fsRecPtr;
- Tcl_Filesystem* retVal = NULL;
- FsPath* srcFsPathPtr;
-
- /*
- * If the object has a refCount of zero, we reject it. This
- * is to avoid possible segfaults or nondeterministic memory
- * leaks (i.e. the user doesn't know if they should decrement
- * the ref count on return or not).
- */
-
- if (pathObjPtr->refCount == 0) {
- panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0");
- return NULL;
- }
-
- /*
- * This will ensure the pathObjPtr can be converted into a
- * "path" type, and that we are able to generate a complete
- * normalized path which is used to determine the filesystem
- * match.
- */
-
- if (Tcl_FSGetNormalizedPath(NULL, pathObjPtr) == NULL) {
- return NULL;
- }
-
- /*
- * Get a lock on theFilesystemEpoch and the filesystemList
- *
- * While we don't need the fsRecPtr until the while loop below, we
- * do want to make sure the theFilesystemEpoch doesn't change
- * between the 'if' and 'while' blocks, getting this iterator will
- * ensure that everything is consistent
- */
- fsRecPtr = FsGetIterator();
-
- /* Make sure pathObjPtr is of the correct epoch */
-
- srcFsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr;
-
- /*
- * Check if the filesystem has changed in some way since
- * this object's internal representation was calculated.
- */
- if (srcFsPathPtr->filesystemEpoch != theFilesystemEpoch) {
- /*
- * We have to discard the stale representation and
- * recalculate it
- */
- if (pathObjPtr->bytes == NULL) {
- UpdateStringOfFsPath(pathObjPtr);
- }
- FreeFsPathInternalRep(pathObjPtr);
- pathObjPtr->typePtr = NULL;
- if (SetFsPathFromAny(NULL, pathObjPtr) != TCL_OK) {
- goto done;
- }
- srcFsPathPtr = (FsPath*) pathObjPtr->internalRep.otherValuePtr;
- }
-
- /* Check whether the object is already assigned to a fs */
- if (srcFsPathPtr->fsRecPtr != NULL) {
- retVal = srcFsPathPtr->fsRecPtr->fsPtr;
- goto done;
- }
-
- /*
- * Call each of the "pathInFilesystem" functions in succession. A
- * non-return value of -1 indicates the particular function has
- * succeeded.
- */
-
- while ((retVal == NULL) && (fsRecPtr != NULL)) {
- Tcl_FSPathInFilesystemProc *proc = fsRecPtr->fsPtr->pathInFilesystemProc;
- if (proc != NULL) {
- ClientData clientData = NULL;
- int ret = (*proc)(pathObjPtr, &clientData);
- if (ret != -1) {
- /*
- * We assume the srcFsPathPtr hasn't been changed
- * by the above call to the pathInFilesystemProc.
- */
- srcFsPathPtr->fsRecPtr = fsRecPtr;
- srcFsPathPtr->nativePathPtr = clientData;
- srcFsPathPtr->filesystemEpoch = theFilesystemEpoch;
- fsRecPtr->fileRefCount++;
- retVal = fsRecPtr->fsPtr;
- }
- }
- fsRecPtr = fsRecPtr->nextPtr;
- }
-
- done:
- FsReleaseIterator();
- return retVal;
-}
-
-/* Simple helper function */
-static FilesystemRecord*
-GetFilesystemRecord(fromFilesystem, epoch)
- Tcl_Filesystem *fromFilesystem;
- int *epoch;
-{
- FilesystemRecord *fsRecPtr = FsGetIterator();
- while (fsRecPtr != NULL) {
- if (fsRecPtr->fsPtr == fromFilesystem) {
- *epoch = theFilesystemEpoch;
- break;
- }
- fsRecPtr = fsRecPtr->nextPtr;
- }
- FsReleaseIterator();
- return fsRecPtr;
-}
-
-/*
- *---------------------------------------------------------------------------
- *
- * Tcl_FSEqualPaths --
- *
- * This function tests whether the two paths given are equal path
- * objects. If either or both is NULL, 0 is always returned.
- *
- * Results:
- * 1 or 0.
- *
- * Side effects:
- * None.
- *
- *---------------------------------------------------------------------------
- */
-
-int
-Tcl_FSEqualPaths(firstPtr, secondPtr)
- Tcl_Obj* firstPtr;
- Tcl_Obj* secondPtr;
-{
- if (firstPtr == secondPtr) {
- return 1;
- } else {
- char *firstStr, *secondStr;
- int firstLen, secondLen, tempErrno;
-
- if (firstPtr == NULL || secondPtr == NULL) {
- return 0;
- }
- firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen);
- secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
- if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
- return 1;
- }
- /*
- * Try the most thorough, correct method of comparing fully
- * normalized paths
- */
-
- tempErrno = Tcl_GetErrno();
- firstPtr = Tcl_FSGetNormalizedPath(NULL, firstPtr);
- secondPtr = Tcl_FSGetNormalizedPath(NULL, secondPtr);
- Tcl_SetErrno(tempErrno);
-
- if (firstPtr == NULL || secondPtr == NULL) {
- return 0;
- }
- firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen);
- secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
- if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
- return 1;
- }
- }
- return 0;
-}
/* Everything from here on is contained in this obsolete ifdef */
#ifdef USE_OBSOLETE_FS_HOOKS
diff --git a/generic/tclPathObj.c b/generic/tclPathObj.c
new file mode 100644
index 0000000..9dcde63
--- /dev/null
+++ b/generic/tclPathObj.c
@@ -0,0 +1,1857 @@
+/*
+ * tclPathObj.c --
+ *
+ * This file contains the implementation of Tcl's "path" object
+ * type used to represent and manipulate a general (virtual)
+ * filesystem entity in an efficient manner.
+ *
+ * Copyright (c) 2003 Vince Darley.
+ *
+ * See the file "license.terms" for information on usage and redistribution
+ * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+ *
+ * RCS: @(#) $Id: tclPathObj.c,v 1.1 2003/04/11 15:59:58 vincentdarley Exp $
+ */
+
+#include "tclInt.h"
+#include "tclPort.h"
+#ifdef MAC_TCL
+#include "tclMacInt.h"
+#endif
+#include "tclFileSystem.h"
+
+/*
+ * Prototypes for procedures defined later in this file.
+ */
+
+static void DupFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr,
+ Tcl_Obj *copyPtr));
+static void FreeFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *listPtr));
+static void UpdateStringOfFsPath _ANSI_ARGS_((Tcl_Obj *objPtr));
+static int SetFsPathFromAny _ANSI_ARGS_((Tcl_Interp *interp,
+ Tcl_Obj *objPtr));
+static Tcl_Obj* FSNormalizeAbsolutePath
+ _ANSI_ARGS_((Tcl_Interp* interp, Tcl_Obj *pathPtr,
+ ClientData *clientDataPtr));
+static int FindSplitPos _ANSI_ARGS_((char *path, char *separator));
+
+
+
+/*
+ * Define the 'path' object type, which Tcl uses to represent
+ * file paths internally.
+ */
+Tcl_ObjType tclFsPathType = {
+ "path", /* name */
+ FreeFsPathInternalRep, /* freeIntRepProc */
+ DupFsPathInternalRep, /* dupIntRepProc */
+ UpdateStringOfFsPath, /* updateStringProc */
+ SetFsPathFromAny /* setFromAnyProc */
+};
+
+/*
+ * struct FsPath --
+ *
+ * Internal representation of a Tcl_Obj of "path" type. This
+ * can be used to represent relative or absolute paths, and has
+ * certain optimisations when used to represent paths which are
+ * already normalized and absolute.
+ *
+ * Note that 'normPathPtr' can be a circular reference to the
+ * container Tcl_Obj of this FsPath.
+ */
+typedef struct FsPath {
+ Tcl_Obj *translatedPathPtr; /* Name without any ~user sequences.
+ * If this is NULL, then this is a
+ * pure normalized, absolute path
+ * object, in which the parent Tcl_Obj's
+ * string rep is already both translated
+ * and normalized. */
+ Tcl_Obj *normPathPtr; /* Normalized absolute path, without
+ * ., .. or ~user sequences. If the
+ * Tcl_Obj containing
+ * this FsPath is already normalized,
+ * this may be a circular reference back
+ * to the container. If that is NOT the
+ * case, we have a refCount on the object. */
+ Tcl_Obj *cwdPtr; /* If null, path is absolute, else
+ * this points to the cwd object used
+ * for this path. We have a refCount
+ * on the object. */
+ int flags; /* Flags to describe interpretation */
+ ClientData nativePathPtr; /* Native representation of this path,
+ * which is filesystem dependent. */
+ int filesystemEpoch; /* Used to ensure the path representation
+ * was generated during the correct
+ * filesystem epoch. The epoch changes
+ * when filesystem-mounts are changed. */
+ struct FilesystemRecord *fsRecPtr;
+ /* Pointer to the filesystem record
+ * entry to use for this path. */
+} FsPath;
+
+/*
+ * Define some macros to give us convenient access to path-object
+ * specific fields.
+ */
+#define PATHOBJ(objPtr) (objPtr->internalRep.otherValuePtr)
+#define PATHFLAGS(objPtr) \
+ (((FsPath*)(objPtr->internalRep.otherValuePtr))->flags)
+
+#define TCLPATH_APPENDED 1
+#define TCLPATH_RELATIVE 2
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * FSNormalizeAbsolutePath --
+ *
+ * Description:
+ * Takes an absolute path specification and computes a 'normalized'
+ * path from it.
+ *
+ * A normalized path is one which has all '../', './' removed.
+ * Also it is one which is in the 'standard' format for the native
+ * platform. On MacOS, 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).
+ *
+ * The behaviour of this function if passed a non-absolute path
+ * is NOT defined.
+ *
+ * Results:
+ * The result is returned in a Tcl_Obj with a refCount of 1,
+ * which is therefore owned by the caller. It must be
+ * freed (with Tcl_DecrRefCount) by the caller when no longer needed.
+ *
+ * Side effects:
+ * None (beyond the memory allocation for the result).
+ *
+ * Special note:
+ * This code is based on code from Matt Newman and Jean-Claude
+ * Wippler, with additions from Vince Darley and is copyright
+ * those respective authors.
+ *
+ *---------------------------------------------------------------------------
+ */
+static Tcl_Obj*
+FSNormalizeAbsolutePath(interp, pathPtr, clientDataPtr)
+ Tcl_Interp* interp; /* Interpreter to use */
+ Tcl_Obj *pathPtr; /* Absolute path to normalize */
+ ClientData *clientDataPtr;
+{
+ int splen = 0, nplen, eltLen, i;
+ char *eltName;
+ Tcl_Obj *retVal;
+ Tcl_Obj *split;
+ Tcl_Obj *elt;
+
+ /* Split has refCount zero */
+ split = Tcl_FSSplitPath(pathPtr, &splen);
+
+ /*
+ * Modify the list of entries in place, by removing '.', and
+ * removing '..' and the entry before -- unless that entry before
+ * is the top-level entry, i.e. the name of a volume.
+ */
+ nplen = 0;
+ for (i = 0; i < splen; i++) {
+ Tcl_ListObjIndex(NULL, split, nplen, &elt);
+ eltName = Tcl_GetStringFromObj(elt, &eltLen);
+
+ if ((eltLen == 1) && (eltName[0] == '.')) {
+ Tcl_ListObjReplace(NULL, split, nplen, 1, 0, NULL);
+ } else if ((eltLen == 2)
+ && (eltName[0] == '.') && (eltName[1] == '.')) {
+ if (nplen > 1) {
+ nplen--;
+ Tcl_ListObjReplace(NULL, split, nplen, 2, 0, NULL);
+ } else {
+ Tcl_ListObjReplace(NULL, split, nplen, 1, 0, NULL);
+ }
+ } else {
+ nplen++;
+ }
+ }
+ if (nplen > 0) {
+ ClientData clientData = NULL;
+
+ retVal = Tcl_FSJoinPath(split, nplen);
+ /*
+ * Now we have an absolute path, with no '..', '.' sequences,
+ * but it still may not be in 'unique' form, depending on the
+ * platform. For instance, Unix is case-sensitive, so the
+ * path is ok. Windows is case-insensitive, and also has the
+ * weird 'longname/shortname' thing (e.g. C:/Program Files/ and
+ * C:/Progra~1/ are equivalent). MacOS is case-insensitive.
+ *
+ * Virtual file systems which may be registered may have
+ * other criteria for normalizing a path.
+ */
+ Tcl_IncrRefCount(retVal);
+ TclFSNormalizeToUniquePath(interp, retVal, 0, &clientData);
+ /*
+ * Since we know it is a normalized path, we can
+ * actually convert this object into an FsPath for
+ * greater efficiency
+ */
+ TclFSMakePathFromNormalized(interp, retVal, clientData);
+ if (clientDataPtr != NULL) {
+ *clientDataPtr = clientData;
+ }
+ } else {
+ /* Init to an empty string */
+ retVal = Tcl_NewStringObj("",0);
+ Tcl_IncrRefCount(retVal);
+ }
+ /*
+ * We increment and then decrement the refCount of split to free
+ * it. We do this right at the end, in case there are
+ * optimisations in Tcl_FSJoinPath(split, nplen) above which would
+ * let it make use of split more effectively if it has a refCount
+ * of zero. Also we can't just decrement the ref count, in case
+ * 'split' was actually returned by the join call above, in a
+ * single-element optimisation when nplen == 1.
+ */
+ Tcl_IncrRefCount(split);
+ Tcl_DecrRefCount(split);
+
+ /* This has a refCount of 1 for the caller */
+ return retVal;
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * Tcl_FSGetPathType --
+ *
+ * Determines whether a given path is relative to the current
+ * directory, relative to the current volume, or absolute.
+ *
+ * Results:
+ * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
+ * TCL_PATH_VOLUME_RELATIVE.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_PathType
+Tcl_FSGetPathType(pathObjPtr)
+ Tcl_Obj *pathObjPtr;
+{
+ return FSGetPathType(pathObjPtr, NULL, NULL);
+}
+
+/*
+ *----------------------------------------------------------------------
+ *
+ * FSGetPathType --
+ *
+ * Determines whether a given path is relative to the current
+ * directory, relative to the current volume, or absolute. If the
+ * caller wishes to know which filesystem claimed the path (in the
+ * case for which the path is absolute), then a reference to a
+ * filesystem pointer can be passed in (but passing NULL is
+ * acceptable).
+ *
+ * Results:
+ * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
+ * TCL_PATH_VOLUME_RELATIVE. The filesystem reference will
+ * be set if and only if it is non-NULL and the function's
+ * return value is TCL_PATH_ABSOLUTE.
+ *
+ * Side effects:
+ * None.
+ *
+ *----------------------------------------------------------------------
+ */
+
+Tcl_PathType
+FSGetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr)
+ Tcl_Obj *pathObjPtr;
+ Tcl_Filesystem **filesystemPtrPtr;
+ int *driveNameLengthPtr;
+{
+ if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) {
+ return GetPathType(pathObjPtr, filesystemPtrPtr,
+ driveNameLengthPtr, NULL);
+ } else {
+ FsPath *fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+ if (fsPathPtr->cwdPtr != NULL) {
+ if (PATHFLAGS(pathObjPtr) == 0) {
+ return TCL_PATH_RELATIVE;
+ }
+ return FSGetPathType(fsPathPtr->cwdPtr, filesystemPtrPtr,
+ driveNameLengthPtr);
+ } else {
+ return GetPathType(pathObjPtr, filesystemPtrPtr,
+ driveNameLengthPtr, NULL);
+ }
+ }
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSJoinPath --
+ *
+ * This function takes the given Tcl_Obj, which should be a valid
+ * list, and returns the path object given by considering the
+ * first 'elements' elements as valid path segments. If elements < 0,
+ * we use the entire list.
+ *
+ * Results:
+ * Returns object with refCount of zero, (or if non-zero, it has
+ * references elsewhere in Tcl). Either way, the caller must
+ * increment its refCount before use.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+Tcl_Obj*
+Tcl_FSJoinPath(listObj, elements)
+ Tcl_Obj *listObj;
+ int elements;
+{
+ Tcl_Obj *res;
+ int i;
+ Tcl_Filesystem *fsPtr = NULL;
+
+ if (elements < 0) {
+ if (Tcl_ListObjLength(NULL, listObj, &elements) != TCL_OK) {
+ return NULL;
+ }
+ } else {
+ /* Just make sure it is a valid list */
+ int listTest;
+ if (Tcl_ListObjLength(NULL, listObj, &listTest) != TCL_OK) {
+ return NULL;
+ }
+ /*
+ * Correct this if it is too large, otherwise we will
+ * waste our time joining null elements to the path
+ */
+ if (elements > listTest) {
+ elements = listTest;
+ }
+ }
+
+ if (elements == 2) {
+ /*
+ * This is a special case where we can be much more
+ * efficient
+ */
+ Tcl_Obj *base;
+
+ Tcl_ListObjIndex(NULL, listObj, 0, &base);
+ /*
+ * There is only any value in doing this if the first object is
+ * of path type, otherwise we'll never actually get any
+ * efficiency benefit elsewhere in the code (from re-using the
+ * normalized representation of the base object).
+ */
+ if (base->typePtr == &tclFsPathType
+ && !(base->bytes != NULL && base->bytes[0] == '\0')) {
+ Tcl_Obj *tail;
+ Tcl_PathType type;
+ Tcl_ListObjIndex(NULL, listObj, 1, &tail);
+ type = GetPathType(tail, NULL, NULL, NULL);
+ if (type == TCL_PATH_RELATIVE) {
+ CONST char *str;
+ int len;
+ str = Tcl_GetStringFromObj(tail,&len);
+ if (len == 0) {
+ /*
+ * This happens if we try to handle the root volume
+ * '/'. There's no need to return a special path
+ * object, when the base itself is just fine!
+ */
+ return base;
+ }
+ if (str[0] != '.') {
+ return TclNewFSPathObj(base, str, len);
+ }
+ /*
+ * Otherwise we don't have an easy join, and
+ * we must let the more general code below handle
+ * things
+ */
+ } else {
+ return tail;
+ }
+ }
+ }
+
+ res = Tcl_NewObj();
+
+ for (i = 0; i < elements; i++) {
+ Tcl_Obj *elt;
+ int driveNameLength;
+ Tcl_PathType type;
+ char *strElt;
+ int strEltLen;
+ int length;
+ char *ptr;
+ Tcl_Obj *driveName = NULL;
+
+ Tcl_ListObjIndex(NULL, listObj, i, &elt);
+ strElt = Tcl_GetStringFromObj(elt, &strEltLen);
+ type = GetPathType(elt, &fsPtr, &driveNameLength, &driveName);
+ if (type != TCL_PATH_RELATIVE) {
+ /* Zero out the current result */
+ Tcl_DecrRefCount(res);
+ if (driveName != NULL) {
+ res = Tcl_DuplicateObj(driveName);
+ Tcl_DecrRefCount(driveName);
+ } else {
+ res = Tcl_NewStringObj(strElt, driveNameLength);
+ }
+ strElt += driveNameLength;
+ }
+
+ ptr = Tcl_GetStringFromObj(res, &length);
+
+ /*
+ * Strip off any './' before a tilde, unless this is the
+ * beginning of the path.
+ */
+ if (length > 0 && strEltLen > 0) {
+ if ((strElt[0] == '.') && (strElt[1] == '/')
+ && (strElt[2] == '~')) {
+ strElt += 2;
+ }
+ }
+
+ /*
+ * A NULL value for fsPtr at this stage basically means
+ * we're trying to join a relative path onto something
+ * which is also relative (or empty). There's nothing
+ * particularly wrong with that.
+ */
+ if (*strElt == '\0') continue;
+
+ if (fsPtr == &tclNativeFilesystem || fsPtr == NULL) {
+ TclpNativeJoinPath(res, strElt);
+ } else {
+ char separator = '/';
+ int needsSep = 0;
+
+ if (fsPtr->filesystemSeparatorProc != NULL) {
+ Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(res);
+ if (sep != NULL) {
+ separator = Tcl_GetString(sep)[0];
+ }
+ }
+
+ if (length > 0 && ptr[length -1] != '/') {
+ Tcl_AppendToObj(res, &separator, 1);
+ length++;
+ }
+ Tcl_SetObjLength(res, length + (int) strlen(strElt));
+
+ ptr = Tcl_GetString(res) + length;
+ for (; *strElt != '\0'; strElt++) {
+ if (*strElt == separator) {
+ while (strElt[1] == separator) {
+ strElt++;
+ }
+ if (strElt[1] != '\0') {
+ if (needsSep) {
+ *ptr++ = separator;
+ }
+ }
+ } else {
+ *ptr++ = *strElt;
+ needsSep = 1;
+ }
+ }
+ length = ptr - Tcl_GetString(res);
+ Tcl_SetObjLength(res, length);
+ }
+ }
+ return res;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSConvertToPathType --
+ *
+ * This function tries to convert the given Tcl_Obj to a valid
+ * Tcl path type, taking account of the fact that the cwd may
+ * have changed even if this object is already supposedly of
+ * the correct type.
+ *
+ * The filename may begin with "~" (to indicate current user's
+ * home directory) or "~<user>" (to indicate any user's home
+ * directory).
+ *
+ * Results:
+ * Standard Tcl error code.
+ *
+ * Side effects:
+ * The old representation may be freed, and new memory allocated.
+ *
+ *---------------------------------------------------------------------------
+ */
+int
+Tcl_FSConvertToPathType(interp, objPtr)
+ Tcl_Interp *interp; /* Interpreter in which to store error
+ * message (if necessary). */
+ Tcl_Obj *objPtr; /* Object to convert to a valid, current
+ * path type. */
+{
+ /*
+ * While it is bad practice to examine an object's type directly,
+ * this is actually the best thing to do here. The reason is that
+ * if we are converting this object to FsPath type for the first
+ * time, we don't need to worry whether the 'cwd' has changed.
+ * On the other hand, if this object is already of FsPath type,
+ * and is a relative path, we do have to worry about the cwd.
+ * If the cwd has changed, we must recompute the path.
+ */
+ if (objPtr->typePtr == &tclFsPathType) {
+ FsPath *fsPathPtr = (FsPath*) PATHOBJ(objPtr);
+ if (fsPathPtr->filesystemEpoch != theFilesystemEpoch) {
+ if (objPtr->bytes == NULL) {
+ UpdateStringOfFsPath(objPtr);
+ }
+ FreeFsPathInternalRep(objPtr);
+ objPtr->typePtr = NULL;
+ return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
+ }
+ return TCL_OK;
+ /*
+ * This code is intentionally never reached. Once fs-optimisation
+ * is complete, it will be removed/replaced
+ */
+ if (fsPathPtr->cwdPtr == NULL) {
+ return TCL_OK;
+ } else {
+ if (TclFSCwdPointerEquals(fsPathPtr->cwdPtr)) {
+ return TCL_OK;
+ } else {
+ if (objPtr->bytes == NULL) {
+ UpdateStringOfFsPath(objPtr);
+ }
+ FreeFsPathInternalRep(objPtr);
+ objPtr->typePtr = NULL;
+ return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
+ }
+ }
+ } else {
+ return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
+ }
+}
+
+/*
+ * Helper function for SetFsPathFromAny. Returns position of first
+ * directory delimiter in the path.
+ */
+static int
+FindSplitPos(path, separator)
+ char *path;
+ char *separator;
+{
+ int count = 0;
+ switch (tclPlatform) {
+ case TCL_PLATFORM_UNIX:
+ case TCL_PLATFORM_MAC:
+ while (path[count] != 0) {
+ if (path[count] == *separator) {
+ return count;
+ }
+ count++;
+ }
+ break;
+
+ case TCL_PLATFORM_WINDOWS:
+ while (path[count] != 0) {
+ if (path[count] == *separator || path[count] == '\\') {
+ return count;
+ }
+ count++;
+ }
+ break;
+ }
+ return count;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclNewFSPathObj --
+ *
+ * Creates a path object whose string representation is
+ * '[file join dirPtr addStrRep]', but does so in a way that
+ * allows for more efficient caching of normalized paths.
+ *
+ * Assumptions:
+ * 'dirPtr' must be an absolute path.
+ * 'len' may not be zero.
+ *
+ * Results:
+ * The new Tcl object, with refCount zero.
+ *
+ * Side effects:
+ * Memory is allocated. 'dirPtr' gets an additional refCount.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj*
+TclNewFSPathObj(Tcl_Obj *dirPtr, CONST char *addStrRep, int len)
+{
+ FsPath *fsPathPtr;
+ Tcl_Obj *objPtr;
+
+ objPtr = Tcl_NewObj();
+ fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
+
+ if (tclPlatform == TCL_PLATFORM_MAC) {
+ /*
+ * Mac relative paths may begin with a directory separator ':'.
+ * If present, we need to skip this ':' because we assume that
+ * we can join dirPtr and addStrRep by concatenating them as
+ * strings (and we ensure that dirPtr is terminated by a ':').
+ */
+ if (addStrRep[0] == ':') {
+ addStrRep++;
+ len--;
+ }
+ }
+ /* Setup the path */
+ fsPathPtr->translatedPathPtr = NULL;
+ fsPathPtr->normPathPtr = Tcl_NewStringObj(addStrRep, len);
+ Tcl_IncrRefCount(fsPathPtr->normPathPtr);
+ fsPathPtr->cwdPtr = dirPtr;
+ Tcl_IncrRefCount(dirPtr);
+ fsPathPtr->nativePathPtr = NULL;
+ fsPathPtr->fsRecPtr = NULL;
+ fsPathPtr->filesystemEpoch = theFilesystemEpoch;
+
+ PATHOBJ(objPtr) = (VOID *) fsPathPtr;
+ PATHFLAGS(objPtr) = TCLPATH_RELATIVE | TCLPATH_APPENDED;
+ objPtr->typePtr = &tclFsPathType;
+ objPtr->bytes = NULL;
+ objPtr->length = 0;
+ return objPtr;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclFSMakePathRelative --
+ *
+ * Like SetFsPathFromAny, but assumes the given object is an
+ * absolute normalized path. Only for internal use.
+ *
+ * Results:
+ * Standard Tcl error code.
+ *
+ * Side effects:
+ * The old representation may be freed, and new memory allocated.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj*
+TclFSMakePathRelative(interp, objPtr, cwdPtr)
+ Tcl_Interp *interp; /* Used for error reporting if not NULL. */
+ Tcl_Obj *objPtr; /* The object we have. */
+ Tcl_Obj *cwdPtr; /* Make it relative to this. */
+{
+ int cwdLen, len;
+ CONST char *tempStr;
+
+ if (objPtr->typePtr == &tclFsPathType) {
+ FsPath* fsPathPtr = (FsPath*) PATHOBJ(objPtr);
+ if (PATHFLAGS(objPtr) != 0
+ && fsPathPtr->cwdPtr == cwdPtr) {
+ objPtr = fsPathPtr->normPathPtr;
+ /* Free old representation */
+ if (objPtr->typePtr != NULL) {
+ if (objPtr->bytes == NULL) {
+ if (objPtr->typePtr->updateStringProc == NULL) {
+ if (interp != NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "can't find object",
+ "string representation", (char *) NULL);
+ }
+ return NULL;
+ }
+ objPtr->typePtr->updateStringProc(objPtr);
+ }
+ if ((objPtr->typePtr->freeIntRepProc) != NULL) {
+ (*objPtr->typePtr->freeIntRepProc)(objPtr);
+ }
+ }
+
+ fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
+
+ /* Circular reference, by design */
+ fsPathPtr->translatedPathPtr = objPtr;
+ fsPathPtr->normPathPtr = NULL;
+ fsPathPtr->cwdPtr = cwdPtr;
+ Tcl_IncrRefCount(cwdPtr);
+ fsPathPtr->nativePathPtr = NULL;
+ fsPathPtr->fsRecPtr = NULL;
+ fsPathPtr->filesystemEpoch = theFilesystemEpoch;
+
+ PATHOBJ(objPtr) = (VOID *) fsPathPtr;
+ PATHFLAGS(objPtr) = 0;
+ objPtr->typePtr = &tclFsPathType;
+
+ return objPtr;
+ }
+ }
+ /*
+ * We know the cwd is a normalised object which does
+ * not end in a directory delimiter, unless the cwd
+ * is the name of a volume, in which case it will
+ * end in a delimiter! We handle this situation here.
+ * A better test than the '!= sep' might be to simply
+ * check if 'cwd' is a root volume.
+ *
+ * Note that if we get this wrong, we will strip off
+ * either too much or too little below, leading to
+ * wrong answers returned by glob.
+ */
+ tempStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen);
+ /*
+ * Should we perhaps use 'Tcl_FSPathSeparator'?
+ * But then what about the Windows special case?
+ * Perhaps we should just check if cwd is a root
+ * volume.
+ */
+ switch (tclPlatform) {
+ case TCL_PLATFORM_UNIX:
+ if (tempStr[cwdLen-1] != '/') {
+ cwdLen++;
+ }
+ break;
+ case TCL_PLATFORM_WINDOWS:
+ if (tempStr[cwdLen-1] != '/'
+ && tempStr[cwdLen-1] != '\\') {
+ cwdLen++;
+ }
+ break;
+ case TCL_PLATFORM_MAC:
+ if (tempStr[cwdLen-1] != ':') {
+ cwdLen++;
+ }
+ break;
+ }
+ tempStr = Tcl_GetStringFromObj(objPtr, &len);
+ return Tcl_NewStringObj(tempStr + cwdLen, len - cwdLen);
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclFSMakePathFromNormalized --
+ *
+ * Like SetFsPathFromAny, but assumes the given object is an
+ * absolute normalized path. Only for internal use.
+ *
+ * Results:
+ * Standard Tcl error code.
+ *
+ * Side effects:
+ * The old representation may be freed, and new memory allocated.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TclFSMakePathFromNormalized(interp, objPtr, nativeRep)
+ Tcl_Interp *interp; /* Used for error reporting if not NULL. */
+ Tcl_Obj *objPtr; /* The object to convert. */
+ ClientData nativeRep; /* The native rep for the object, if known
+ * else NULL. */
+{
+ FsPath *fsPathPtr;
+
+ if (objPtr->typePtr == &tclFsPathType) {
+ return TCL_OK;
+ }
+
+ /* Free old representation */
+ if (objPtr->typePtr != NULL) {
+ if (objPtr->bytes == NULL) {
+ if (objPtr->typePtr->updateStringProc == NULL) {
+ if (interp != NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "can't find object",
+ "string representation", (char *) NULL);
+ }
+ return TCL_ERROR;
+ }
+ objPtr->typePtr->updateStringProc(objPtr);
+ }
+ if ((objPtr->typePtr->freeIntRepProc) != NULL) {
+ (*objPtr->typePtr->freeIntRepProc)(objPtr);
+ }
+ }
+
+ fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
+ /* It's a pure normalized absolute path */
+ fsPathPtr->translatedPathPtr = NULL;
+ fsPathPtr->normPathPtr = objPtr;
+ fsPathPtr->cwdPtr = NULL;
+ fsPathPtr->nativePathPtr = nativeRep;
+ fsPathPtr->fsRecPtr = NULL;
+ fsPathPtr->filesystemEpoch = theFilesystemEpoch;
+
+ PATHOBJ(objPtr) = (VOID *) fsPathPtr;
+ PATHFLAGS(objPtr) = 0;
+ objPtr->typePtr = &tclFsPathType;
+
+ return TCL_OK;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSNewNativePath --
+ *
+ * This function performs the something like that reverse of the
+ * usual obj->path->nativerep conversions. If some code retrieves
+ * a path in native form (from, e.g. readlink or a native dialog),
+ * and that path is to be used at the Tcl level, then calling
+ * this function is an efficient way of creating the appropriate
+ * path object type.
+ *
+ * Any memory which is allocated for 'clientData' should be retained
+ * until clientData is passed to the filesystem's freeInternalRepProc
+ * when it can be freed. The built in platform-specific filesystems
+ * use 'ckalloc' to allocate clientData, and ckfree to free it.
+ *
+ * Results:
+ * NULL or a valid path object pointer, with refCount zero.
+ *
+ * Side effects:
+ * New memory may be allocated.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj *
+Tcl_FSNewNativePath(fromFilesystem, clientData)
+ Tcl_Filesystem* fromFilesystem;
+ ClientData clientData;
+{
+ Tcl_Obj *objPtr;
+ FsPath *fsPathPtr;
+
+ FilesystemRecord *fsFromPtr;
+ int epoch;
+
+ objPtr = TclFSInternalToNormalized(fromFilesystem, clientData,
+ &fsFromPtr, &epoch);
+
+ if (objPtr == NULL) {
+ return NULL;
+ }
+
+ /*
+ * Free old representation; shouldn't normally be any,
+ * but best to be safe.
+ */
+ if (objPtr->typePtr != NULL) {
+ if (objPtr->bytes == NULL) {
+ if (objPtr->typePtr->updateStringProc == NULL) {
+ return NULL;
+ }
+ objPtr->typePtr->updateStringProc(objPtr);
+ }
+ if ((objPtr->typePtr->freeIntRepProc) != NULL) {
+ (*objPtr->typePtr->freeIntRepProc)(objPtr);
+ }
+ }
+
+ fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
+ fsPathPtr->translatedPathPtr = NULL;
+ /* Circular reference, by design */
+ fsPathPtr->normPathPtr = objPtr;
+ fsPathPtr->cwdPtr = NULL;
+ fsPathPtr->nativePathPtr = clientData;
+ fsPathPtr->fsRecPtr = fsFromPtr;
+ /* We must increase the refCount for this filesystem. */
+ fsPathPtr->fsRecPtr->fileRefCount++;
+ fsPathPtr->filesystemEpoch = epoch;
+
+ PATHOBJ(objPtr) = (VOID *) fsPathPtr;
+ PATHFLAGS(objPtr) = 0;
+ objPtr->typePtr = &tclFsPathType;
+ return objPtr;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSGetTranslatedPath --
+ *
+ * This function attempts to extract the translated path
+ * from the given Tcl_Obj. If the translation succeeds (i.e. the
+ * object is a valid path), then it is returned. Otherwise NULL
+ * will be returned, and an error message may be left in the
+ * interpreter (if it is non-NULL)
+ *
+ * Results:
+ * NULL or a valid Tcl_Obj pointer.
+ *
+ * Side effects:
+ * Only those of 'Tcl_FSConvertToPathType'
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj*
+Tcl_FSGetTranslatedPath(interp, pathPtr)
+ Tcl_Interp *interp;
+ Tcl_Obj* pathPtr;
+{
+ register FsPath* srcFsPathPtr;
+ if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) {
+ return NULL;
+ }
+ srcFsPathPtr = (FsPath*) PATHOBJ(pathPtr);
+ if (srcFsPathPtr->translatedPathPtr == NULL) {
+ if (PATHFLAGS(pathPtr) != 0) {
+ return Tcl_FSGetNormalizedPath(interp, pathPtr);
+ }
+ /*
+ * It is a pure absolute, normalized path object.
+ * This is something like being a 'pure list'. The
+ * object's string, translatedPath and normalizedPath
+ * are all identical.
+ */
+ return srcFsPathPtr->normPathPtr;
+ } else {
+ /* It is an ordinary path object */
+ return srcFsPathPtr->translatedPathPtr;
+ }
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSGetTranslatedStringPath --
+ *
+ * This function attempts to extract the translated path
+ * from the given Tcl_Obj. If the translation succeeds (i.e. the
+ * object is a valid path), then the path is returned. Otherwise NULL
+ * will be returned, and an error message may be left in the
+ * interpreter (if it is non-NULL)
+ *
+ * Results:
+ * NULL or a valid string.
+ *
+ * Side effects:
+ * Only those of 'Tcl_FSConvertToPathType'
+ *
+ *---------------------------------------------------------------------------
+ */
+CONST char*
+Tcl_FSGetTranslatedStringPath(interp, pathPtr)
+ Tcl_Interp *interp;
+ Tcl_Obj* pathPtr;
+{
+ Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
+ if (transPtr == NULL) {
+ return NULL;
+ } else {
+ return Tcl_GetString(transPtr);
+ }
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSGetNormalizedPath --
+ *
+ * This important function attempts to extract from the given Tcl_Obj
+ * a unique normalised path representation, whose string value can
+ * be used as a unique identifier for the file.
+ *
+ * Results:
+ * NULL or a valid path object pointer.
+ *
+ * Side effects:
+ * New memory may be allocated. The Tcl 'errno' may be modified
+ * in the process of trying to examine various path possibilities.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+Tcl_Obj*
+Tcl_FSGetNormalizedPath(interp, pathObjPtr)
+ Tcl_Interp *interp;
+ Tcl_Obj* pathObjPtr;
+{
+ register FsPath* fsPathPtr;
+ if (Tcl_FSConvertToPathType(interp, pathObjPtr) != TCL_OK) {
+ return NULL;
+ }
+ fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+
+ if (PATHFLAGS(pathObjPtr) != 0) {
+ /*
+ * This is a special path object which is the result of
+ * something like 'file join'
+ */
+ Tcl_Obj *dir, *copy;
+ int cwdLen;
+ int pathType;
+ CONST char *cwdStr;
+ ClientData clientData = NULL;
+
+ pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr);
+ dir = Tcl_FSGetNormalizedPath(interp, fsPathPtr->cwdPtr);
+ if (dir == NULL) {
+ return NULL;
+ }
+ if (pathObjPtr->bytes == NULL) {
+ UpdateStringOfFsPath(pathObjPtr);
+ }
+ copy = Tcl_DuplicateObj(dir);
+ Tcl_IncrRefCount(copy);
+ Tcl_IncrRefCount(dir);
+ /* We now own a reference on both 'dir' and 'copy' */
+
+ cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
+ /*
+ * Should we perhaps use 'Tcl_FSPathSeparator'?
+ * But then what about the Windows special case?
+ * Perhaps we should just check if cwd is a root volume.
+ * We should never get cwdLen == 0 in this code path.
+ */
+ switch (tclPlatform) {
+ case TCL_PLATFORM_UNIX:
+ if (cwdStr[cwdLen-1] != '/') {
+ Tcl_AppendToObj(copy, "/", 1);
+ cwdLen++;
+ }
+ break;
+ case TCL_PLATFORM_WINDOWS:
+ if (cwdStr[cwdLen-1] != '/'
+ && cwdStr[cwdLen-1] != '\\') {
+ Tcl_AppendToObj(copy, "/", 1);
+ cwdLen++;
+ }
+ break;
+ case TCL_PLATFORM_MAC:
+ if (cwdStr[cwdLen-1] != ':') {
+ Tcl_AppendToObj(copy, ":", 1);
+ cwdLen++;
+ }
+ break;
+ }
+ Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
+ /*
+ * Normalize the combined string, but only starting after
+ * the end of the previously normalized 'dir'. This should
+ * be much faster! We use 'cwdLen-1' so that we are
+ * already pointing at the dir-separator that we know about.
+ * The normalization code will actually start off directly
+ * after that separator.
+ */
+ TclFSNormalizeToUniquePath(interp, copy, cwdLen-1,
+ (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
+ /* Now we need to construct the new path object */
+
+ if (pathType == TCL_PATH_RELATIVE) {
+ register FsPath* origDirFsPathPtr;
+ Tcl_Obj *origDir = fsPathPtr->cwdPtr;
+ origDirFsPathPtr = (FsPath*) PATHOBJ(origDir);
+
+ fsPathPtr->cwdPtr = origDirFsPathPtr->cwdPtr;
+ Tcl_IncrRefCount(fsPathPtr->cwdPtr);
+
+ Tcl_DecrRefCount(fsPathPtr->normPathPtr);
+ fsPathPtr->normPathPtr = copy;
+ /* That's our reference to copy used */
+ Tcl_DecrRefCount(dir);
+ Tcl_DecrRefCount(origDir);
+ } else {
+ Tcl_DecrRefCount(fsPathPtr->cwdPtr);
+ fsPathPtr->cwdPtr = NULL;
+ Tcl_DecrRefCount(fsPathPtr->normPathPtr);
+ fsPathPtr->normPathPtr = copy;
+ /* That's our reference to copy used */
+ Tcl_DecrRefCount(dir);
+ }
+ if (clientData != NULL) {
+ fsPathPtr->nativePathPtr = clientData;
+ }
+ PATHFLAGS(pathObjPtr) = 0;
+ }
+ /* Ensure cwd hasn't changed */
+ if (fsPathPtr->cwdPtr != NULL) {
+ if (!TclFSCwdPointerEquals(fsPathPtr->cwdPtr)) {
+ if (pathObjPtr->bytes == NULL) {
+ UpdateStringOfFsPath(pathObjPtr);
+ }
+ FreeFsPathInternalRep(pathObjPtr);
+ pathObjPtr->typePtr = NULL;
+ if (Tcl_ConvertToType(interp, pathObjPtr,
+ &tclFsPathType) != TCL_OK) {
+ return NULL;
+ }
+ fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+ } else if (fsPathPtr->normPathPtr == NULL) {
+ int cwdLen;
+ Tcl_Obj *copy;
+ CONST char *cwdStr;
+ ClientData clientData = NULL;
+
+ copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
+ Tcl_IncrRefCount(copy);
+ cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
+ /*
+ * Should we perhaps use 'Tcl_FSPathSeparator'?
+ * But then what about the Windows special case?
+ * Perhaps we should just check if cwd is a root volume.
+ * We should never get cwdLen == 0 in this code path.
+ */
+ switch (tclPlatform) {
+ case TCL_PLATFORM_UNIX:
+ if (cwdStr[cwdLen-1] != '/') {
+ Tcl_AppendToObj(copy, "/", 1);
+ cwdLen++;
+ }
+ break;
+ case TCL_PLATFORM_WINDOWS:
+ if (cwdStr[cwdLen-1] != '/'
+ && cwdStr[cwdLen-1] != '\\') {
+ Tcl_AppendToObj(copy, "/", 1);
+ cwdLen++;
+ }
+ break;
+ case TCL_PLATFORM_MAC:
+ if (cwdStr[cwdLen-1] != ':') {
+ Tcl_AppendToObj(copy, ":", 1);
+ cwdLen++;
+ }
+ break;
+ }
+ Tcl_AppendObjToObj(copy, pathObjPtr);
+ /*
+ * Normalize the combined string, but only starting after
+ * the end of the previously normalized 'dir'. This should
+ * be much faster!
+ */
+ TclFSNormalizeToUniquePath(interp, copy, cwdLen-1,
+ (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
+ fsPathPtr->normPathPtr = copy;
+ if (clientData != NULL) {
+ fsPathPtr->nativePathPtr = clientData;
+ }
+ }
+ }
+ if (fsPathPtr->normPathPtr == NULL) {
+ ClientData clientData = NULL;
+ Tcl_Obj *useThisCwd = NULL;
+ /*
+ * Since normPathPtr is NULL, but this is a valid path
+ * object, we know that the translatedPathPtr cannot be NULL.
+ */
+ Tcl_Obj *absolutePath = fsPathPtr->translatedPathPtr;
+ char *path = Tcl_GetString(absolutePath);
+
+ /*
+ * We have to be a little bit careful here to avoid infinite loops
+ * we're asking Tcl_FSGetPathType to return the path's type, but
+ * that call can actually result in a lot of other filesystem
+ * action, which might loop back through here.
+ */
+ if ((path[0] != '\0') &&
+ (Tcl_FSGetPathType(pathObjPtr) == TCL_PATH_RELATIVE)) {
+ useThisCwd = Tcl_FSGetCwd(interp);
+
+ if (useThisCwd == NULL) {
+ return NULL;
+ }
+
+ absolutePath = Tcl_FSJoinToPath(useThisCwd, 1, &absolutePath);
+ Tcl_IncrRefCount(absolutePath);
+ /* We have a refCount on the cwd */
+ }
+ /* Already has refCount incremented */
+ fsPathPtr->normPathPtr = FSNormalizeAbsolutePath(interp, absolutePath,
+ (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
+ if (0 && (clientData != NULL)) {
+ fsPathPtr->nativePathPtr =
+ (*fsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc)(clientData);
+ }
+ if (!strcmp(Tcl_GetString(fsPathPtr->normPathPtr),
+ Tcl_GetString(pathObjPtr))) {
+ /*
+ * The path was already normalized.
+ * Get rid of the duplicate.
+ */
+ Tcl_DecrRefCount(fsPathPtr->normPathPtr);
+ /*
+ * We do *not* increment the refCount for
+ * this circular reference
+ */
+ fsPathPtr->normPathPtr = pathObjPtr;
+ }
+ if (useThisCwd != NULL) {
+ /* This was returned by Tcl_FSJoinToPath above */
+ Tcl_DecrRefCount(absolutePath);
+ fsPathPtr->cwdPtr = useThisCwd;
+ }
+ }
+ return fsPathPtr->normPathPtr;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSGetInternalRep --
+ *
+ * Extract the internal representation of a given path object,
+ * in the given filesystem. If the path object belongs to a
+ * different filesystem, we return NULL.
+ *
+ * If the internal representation is currently NULL, we attempt
+ * to generate it, by calling the filesystem's
+ * 'Tcl_FSCreateInternalRepProc'.
+ *
+ * Results:
+ * NULL or a valid internal representation.
+ *
+ * Side effects:
+ * An attempt may be made to convert the object.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+ClientData
+Tcl_FSGetInternalRep(pathObjPtr, fsPtr)
+ Tcl_Obj* pathObjPtr;
+ Tcl_Filesystem *fsPtr;
+{
+ register FsPath* srcFsPathPtr;
+
+ if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) {
+ return NULL;
+ }
+ srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+
+ /*
+ * We will only return the native representation for the caller's
+ * filesystem. Otherwise we will simply return NULL. This means
+ * that there must be a unique bi-directional mapping between paths
+ * and filesystems, and that this mapping will not allow 'remapped'
+ * files -- files which are in one filesystem but mapped into
+ * another. Another way of putting this is that 'stacked'
+ * filesystems are not allowed. We recognise that this is a
+ * potentially useful feature for the future.
+ *
+ * Even something simple like a 'pass through' filesystem which
+ * logs all activity and passes the calls onto the native system
+ * would be nice, but not easily achievable with the current
+ * implementation.
+ */
+ if (srcFsPathPtr->fsRecPtr == NULL) {
+ /*
+ * This only usually happens in wrappers like TclpStat which
+ * create a string object and pass it to TclpObjStat. Code
+ * which calls the Tcl_FS.. functions should always have a
+ * filesystem already set. Whether this code path is legal or
+ * not depends on whether we decide to allow external code to
+ * call the native filesystem directly. It is at least safer
+ * to allow this sub-optimal routing.
+ */
+ Tcl_FSGetFileSystemForPath(pathObjPtr);
+
+ /*
+ * If we fail through here, then the path is probably not a
+ * valid path in the filesystsem, and is most likely to be a
+ * use of the empty path "" via a direct call to one of the
+ * objectified interfaces (e.g. from the Tcl testsuite).
+ */
+ srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+ if (srcFsPathPtr->fsRecPtr == NULL) {
+ return NULL;
+ }
+ }
+
+ if (fsPtr != srcFsPathPtr->fsRecPtr->fsPtr) {
+ /*
+ * There is still one possibility we should consider; if the
+ * file belongs to a different filesystem, perhaps it is
+ * actually linked through to a file in our own filesystem
+ * which we do care about. The way we can check for this
+ * is we ask what filesystem this path belongs to.
+ */
+ Tcl_Filesystem *actualFs = Tcl_FSGetFileSystemForPath(pathObjPtr);
+ if (actualFs == fsPtr) {
+ return Tcl_FSGetInternalRep(pathObjPtr, fsPtr);
+ }
+ return NULL;
+ }
+
+ if (srcFsPathPtr->nativePathPtr == NULL) {
+ Tcl_FSCreateInternalRepProc *proc;
+ proc = srcFsPathPtr->fsRecPtr->fsPtr->createInternalRepProc;
+
+ if (proc == NULL) {
+ return NULL;
+ }
+ srcFsPathPtr->nativePathPtr = (*proc)(pathObjPtr);
+ }
+ return srcFsPathPtr->nativePathPtr;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * TclFSEnsureEpochOk --
+ *
+ * This will ensure the pathObjPtr is up to date and can be
+ * converted into a "path" type, and that we are able to generate a
+ * complete normalized path which is used to determine the
+ * filesystem match.
+ *
+ * Results:
+ * Standard Tcl return code.
+ *
+ * Side effects:
+ * An attempt may be made to convert the object.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+TclFSEnsureEpochOk(pathObjPtr, theEpoch, fsPtrPtr)
+ Tcl_Obj* pathObjPtr;
+ int theEpoch;
+ Tcl_Filesystem **fsPtrPtr;
+{
+ FsPath* srcFsPathPtr;
+
+ /*
+ * SHOULD BE ABLE TO IMPROVE EFFICIENCY HERE.
+ */
+
+ if (Tcl_FSGetNormalizedPath(NULL, pathObjPtr) == NULL) {
+ return TCL_ERROR;
+ }
+
+ srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+
+ /*
+ * Check if the filesystem has changed in some way since
+ * this object's internal representation was calculated.
+ */
+ if (srcFsPathPtr->filesystemEpoch != theEpoch) {
+ /*
+ * We have to discard the stale representation and
+ * recalculate it
+ */
+ if (pathObjPtr->bytes == NULL) {
+ UpdateStringOfFsPath(pathObjPtr);
+ }
+ FreeFsPathInternalRep(pathObjPtr);
+ pathObjPtr->typePtr = NULL;
+ if (SetFsPathFromAny(NULL, pathObjPtr) != TCL_OK) {
+ return TCL_ERROR;
+ }
+ srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+ }
+ /* Check whether the object is already assigned to a fs */
+ if (srcFsPathPtr->fsRecPtr != NULL) {
+ *fsPtrPtr = srcFsPathPtr->fsRecPtr->fsPtr;
+ }
+ return TCL_OK;
+}
+
+void
+TclFSSetPathDetails(pathObjPtr, fsRecPtr, clientData, theEpoch)
+ Tcl_Obj *pathObjPtr;
+ FilesystemRecord *fsRecPtr;
+ ClientData clientData;
+ int theEpoch;
+{
+ /* We assume pathObjPtr is already of the correct type */
+ FsPath* srcFsPathPtr;
+
+ srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+ srcFsPathPtr->fsRecPtr = fsRecPtr;
+ srcFsPathPtr->nativePathPtr = clientData;
+ srcFsPathPtr->filesystemEpoch = theEpoch;
+ fsRecPtr->fileRefCount++;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * Tcl_FSEqualPaths --
+ *
+ * This function tests whether the two paths given are equal path
+ * objects. If either or both is NULL, 0 is always returned.
+ *
+ * Results:
+ * 1 or 0.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+int
+Tcl_FSEqualPaths(firstPtr, secondPtr)
+ Tcl_Obj* firstPtr;
+ Tcl_Obj* secondPtr;
+{
+ if (firstPtr == secondPtr) {
+ return 1;
+ } else {
+ char *firstStr, *secondStr;
+ int firstLen, secondLen, tempErrno;
+
+ if (firstPtr == NULL || secondPtr == NULL) {
+ return 0;
+ }
+ firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen);
+ secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
+ if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
+ return 1;
+ }
+ /*
+ * Try the most thorough, correct method of comparing fully
+ * normalized paths
+ */
+
+ tempErrno = Tcl_GetErrno();
+ firstPtr = Tcl_FSGetNormalizedPath(NULL, firstPtr);
+ secondPtr = Tcl_FSGetNormalizedPath(NULL, secondPtr);
+ Tcl_SetErrno(tempErrno);
+
+ if (firstPtr == NULL || secondPtr == NULL) {
+ return 0;
+ }
+ firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen);
+ secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
+ if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
+ return 1;
+ }
+ }
+ return 0;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * SetFsPathFromAny --
+ *
+ * This function tries to convert the given Tcl_Obj to a valid
+ * Tcl path type.
+ *
+ * The filename may begin with "~" (to indicate current user's
+ * home directory) or "~<user>" (to indicate any user's home
+ * directory).
+ *
+ * Results:
+ * Standard Tcl error code.
+ *
+ * Side effects:
+ * The old representation may be freed, and new memory allocated.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static int
+SetFsPathFromAny(interp, objPtr)
+ Tcl_Interp *interp; /* Used for error reporting if not NULL. */
+ Tcl_Obj *objPtr; /* The object to convert. */
+{
+ int len;
+ FsPath *fsPathPtr;
+ Tcl_Obj *transPtr;
+ char *name;
+
+ if (objPtr->typePtr == &tclFsPathType) {
+ return TCL_OK;
+ }
+
+ /*
+ * First step is to translate the filename. This is similar to
+ * Tcl_TranslateFilename, but shouldn't convert everything to
+ * windows backslashes on that platform. The current
+ * implementation of this piece is a slightly optimised version
+ * of the various Tilde/Split/Join stuff to avoid multiple
+ * split/join operations.
+ *
+ * We remove any trailing directory separator.
+ *
+ * However, the split/join routines are quite complex, and
+ * one has to make sure not to break anything on Unix, Win
+ * or MacOS (fCmd.test, fileName.test and cmdAH.test exercise
+ * most of the code).
+ */
+ name = Tcl_GetStringFromObj(objPtr,&len);
+
+ /*
+ * Handle tilde substitutions, if needed.
+ */
+ if (name[0] == '~') {
+ char *expandedUser;
+ Tcl_DString temp;
+ int split;
+ char separator='/';
+
+ if (tclPlatform==TCL_PLATFORM_MAC) {
+ if (strchr(name, ':') != NULL) separator = ':';
+ }
+
+ split = FindSplitPos(name, &separator);
+ if (split != len) {
+ /* We have multiple pieces '~user/foo/bar...' */
+ name[split] = '\0';
+ }
+ /* Do some tilde substitution */
+ if (name[1] == '\0') {
+ /* We have just '~' */
+ CONST char *dir;
+ Tcl_DString dirString;
+ if (split != len) { name[split] = separator; }
+
+ dir = TclGetEnv("HOME", &dirString);
+ if (dir == NULL) {
+ if (interp) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "couldn't find HOME environment ",
+ "variable to expand path", (char *) NULL);
+ }
+ return TCL_ERROR;
+ }
+ Tcl_DStringInit(&temp);
+ Tcl_JoinPath(1, &dir, &temp);
+ Tcl_DStringFree(&dirString);
+ } else {
+ /* We have a user name '~user' */
+ Tcl_DStringInit(&temp);
+ if (TclpGetUserHome(name+1, &temp) == NULL) {
+ if (interp != NULL) {
+ Tcl_ResetResult(interp);
+ Tcl_AppendResult(interp, "user \"", (name+1),
+ "\" doesn't exist", (char *) NULL);
+ }
+ Tcl_DStringFree(&temp);
+ if (split != len) { name[split] = separator; }
+ return TCL_ERROR;
+ }
+ if (split != len) { name[split] = separator; }
+ }
+
+ expandedUser = Tcl_DStringValue(&temp);
+ transPtr = Tcl_NewStringObj(expandedUser, Tcl_DStringLength(&temp));
+
+ if (split != len) {
+ /* Join up the tilde substitution with the rest */
+ if (name[split+1] == separator) {
+
+ /*
+ * Somewhat tricky case like ~//foo/bar.
+ * Make use of Split/Join machinery to get it right.
+ * Assumes all paths beginning with ~ are part of the
+ * native filesystem.
+ */
+
+ int objc;
+ Tcl_Obj **objv;
+ Tcl_Obj *parts = TclpNativeSplitPath(objPtr, NULL);
+ Tcl_ListObjGetElements(NULL, parts, &objc, &objv);
+ /* Skip '~'. It's replaced by its expansion */
+ objc--; objv++;
+ while (objc--) {
+ TclpNativeJoinPath(transPtr, Tcl_GetString(*objv++));
+ }
+ Tcl_DecrRefCount(parts);
+ } else {
+ /* Simple case. "rest" is relative path. Just join it. */
+ Tcl_Obj *rest = Tcl_NewStringObj(name+split+1,-1);
+ transPtr = Tcl_FSJoinToPath(transPtr, 1, &rest);
+ }
+ }
+ Tcl_DStringFree(&temp);
+ } else {
+ transPtr = Tcl_FSJoinToPath(objPtr,0,NULL);
+ }
+
+ /*
+ * Now we have a translated filename in 'transPtr'. This will have
+ * forward slashes on Windows, and will not contain any ~user
+ * sequences.
+ */
+
+ fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
+ fsPathPtr->translatedPathPtr = transPtr;
+ Tcl_IncrRefCount(fsPathPtr->translatedPathPtr);
+ fsPathPtr->normPathPtr = NULL;
+ fsPathPtr->cwdPtr = NULL;
+ fsPathPtr->nativePathPtr = NULL;
+ fsPathPtr->fsRecPtr = NULL;
+ fsPathPtr->filesystemEpoch = theFilesystemEpoch;
+
+ /*
+ * Free old representation before installing our new one.
+ */
+ if (objPtr->typePtr != NULL && objPtr->typePtr->freeIntRepProc != NULL) {
+ (objPtr->typePtr->freeIntRepProc)(objPtr);
+ }
+ PATHOBJ(objPtr) = (VOID *) fsPathPtr;
+ PATHFLAGS(objPtr) = 0;
+ objPtr->typePtr = &tclFsPathType;
+
+ return TCL_OK;
+}
+
+static void
+FreeFsPathInternalRep(pathObjPtr)
+ Tcl_Obj *pathObjPtr; /* Path object with internal rep to free. */
+{
+ register FsPath* fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
+
+ if (fsPathPtr->translatedPathPtr != NULL) {
+ if (fsPathPtr->translatedPathPtr != pathObjPtr) {
+ Tcl_DecrRefCount(fsPathPtr->translatedPathPtr);
+ }
+ }
+ if (fsPathPtr->normPathPtr != NULL) {
+ if (fsPathPtr->normPathPtr != pathObjPtr) {
+ Tcl_DecrRefCount(fsPathPtr->normPathPtr);
+ }
+ fsPathPtr->normPathPtr = NULL;
+ }
+ if (fsPathPtr->cwdPtr != NULL) {
+ Tcl_DecrRefCount(fsPathPtr->cwdPtr);
+ }
+ if (fsPathPtr->nativePathPtr != NULL) {
+ if (fsPathPtr->fsRecPtr != NULL) {
+ if (fsPathPtr->fsRecPtr->fsPtr->freeInternalRepProc != NULL) {
+ (*fsPathPtr->fsRecPtr->fsPtr
+ ->freeInternalRepProc)(fsPathPtr->nativePathPtr);
+ fsPathPtr->nativePathPtr = NULL;
+ }
+ }
+ }
+ if (fsPathPtr->fsRecPtr != NULL) {
+ fsPathPtr->fsRecPtr->fileRefCount--;
+ if (fsPathPtr->fsRecPtr->fileRefCount <= 0) {
+ /* It has been unregistered already */
+ ckfree((char *)fsPathPtr->fsRecPtr);
+ }
+ }
+
+ ckfree((char*) fsPathPtr);
+}
+
+
+static void
+DupFsPathInternalRep(srcPtr, copyPtr)
+ Tcl_Obj *srcPtr; /* Path obj with internal rep to copy. */
+ Tcl_Obj *copyPtr; /* Path obj with internal rep to set. */
+{
+ register FsPath* srcFsPathPtr = (FsPath*) PATHOBJ(srcPtr);
+ register FsPath* copyFsPathPtr =
+ (FsPath*) ckalloc((unsigned)sizeof(FsPath));
+ Tcl_FSDupInternalRepProc *dupProc;
+
+ PATHOBJ(copyPtr) = (VOID *) copyFsPathPtr;
+
+ if (srcFsPathPtr->translatedPathPtr != NULL) {
+ copyFsPathPtr->translatedPathPtr = srcFsPathPtr->translatedPathPtr;
+ if (copyFsPathPtr->translatedPathPtr != copyPtr) {
+ Tcl_IncrRefCount(copyFsPathPtr->translatedPathPtr);
+ }
+ } else {
+ copyFsPathPtr->translatedPathPtr = NULL;
+ }
+
+ if (srcFsPathPtr->normPathPtr != NULL) {
+ copyFsPathPtr->normPathPtr = srcFsPathPtr->normPathPtr;
+ if (copyFsPathPtr->normPathPtr != copyPtr) {
+ Tcl_IncrRefCount(copyFsPathPtr->normPathPtr);
+ }
+ } else {
+ copyFsPathPtr->normPathPtr = NULL;
+ }
+
+ if (srcFsPathPtr->cwdPtr != NULL) {
+ copyFsPathPtr->cwdPtr = srcFsPathPtr->cwdPtr;
+ Tcl_IncrRefCount(copyFsPathPtr->cwdPtr);
+ } else {
+ copyFsPathPtr->cwdPtr = NULL;
+ }
+
+ copyFsPathPtr->flags = srcFsPathPtr->flags;
+
+ if (srcFsPathPtr->fsRecPtr != NULL
+ && srcFsPathPtr->nativePathPtr != NULL) {
+ dupProc = srcFsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc;
+ if (dupProc != NULL) {
+ copyFsPathPtr->nativePathPtr =
+ (*dupProc)(srcFsPathPtr->nativePathPtr);
+ } else {
+ copyFsPathPtr->nativePathPtr = NULL;
+ }
+ } else {
+ copyFsPathPtr->nativePathPtr = NULL;
+ }
+ copyFsPathPtr->fsRecPtr = srcFsPathPtr->fsRecPtr;
+ copyFsPathPtr->filesystemEpoch = srcFsPathPtr->filesystemEpoch;
+ if (copyFsPathPtr->fsRecPtr != NULL) {
+ copyFsPathPtr->fsRecPtr->fileRefCount++;
+ }
+
+ copyPtr->typePtr = &tclFsPathType;
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * UpdateStringOfFsPath --
+ *
+ * Gives an object a valid string rep.
+ *
+ * Results:
+ * None.
+ *
+ * Side effects:
+ * Memory may be allocated.
+ *
+ *---------------------------------------------------------------------------
+ */
+
+static void
+UpdateStringOfFsPath(objPtr)
+ register Tcl_Obj *objPtr; /* path obj with string rep to update. */
+{
+ register FsPath* fsPathPtr = (FsPath*) PATHOBJ(objPtr);
+ CONST char *cwdStr;
+ int cwdLen;
+ Tcl_Obj *copy;
+
+ if (PATHFLAGS(objPtr) == 0 || fsPathPtr->cwdPtr == NULL) {
+ panic("Called UpdateStringOfFsPath with invalid object");
+ }
+
+ copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
+ Tcl_IncrRefCount(copy);
+
+ cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
+ /*
+ * Should we perhaps use 'Tcl_FSPathSeparator'?
+ * But then what about the Windows special case?
+ * Perhaps we should just check if cwd is a root volume.
+ * We should never get cwdLen == 0 in this code path.
+ */
+ switch (tclPlatform) {
+ case TCL_PLATFORM_UNIX:
+ if (cwdStr[cwdLen-1] != '/') {
+ Tcl_AppendToObj(copy, "/", 1);
+ cwdLen++;
+ }
+ break;
+ case TCL_PLATFORM_WINDOWS:
+ /*
+ * We need the extra 'cwdLen != 2', and ':' checks because
+ * a volume relative path doesn't get a '/'. For example
+ * 'glob C:*cat*.exe' will return 'C:cat32.exe'
+ */
+ if (cwdStr[cwdLen-1] != '/'
+ && cwdStr[cwdLen-1] != '\\') {
+ if (cwdLen != 2 || cwdStr[1] != ':') {
+ Tcl_AppendToObj(copy, "/", 1);
+ cwdLen++;
+ }
+ }
+ break;
+ case TCL_PLATFORM_MAC:
+ if (cwdStr[cwdLen-1] != ':') {
+ Tcl_AppendToObj(copy, ":", 1);
+ cwdLen++;
+ }
+ break;
+ }
+ Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
+ objPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen);
+ objPtr->length = cwdLen;
+ copy->bytes = tclEmptyStringRep;
+ copy->length = 0;
+ Tcl_DecrRefCount(copy);
+}
+
+/*
+ *---------------------------------------------------------------------------
+ *
+ * NativePathInFilesystem --
+ *
+ * Any path object is acceptable to the native filesystem, by
+ * default (we will throw errors when illegal paths are actually
+ * tried to be used).
+ *
+ * However, this behavior means the native filesystem must be
+ * the last filesystem in the lookup list (otherwise it will
+ * claim all files belong to it, and other filesystems will
+ * never get a look in).
+ *
+ * Results:
+ * TCL_OK, to indicate 'yes', -1 to indicate no.
+ *
+ * Side effects:
+ * None.
+ *
+ *---------------------------------------------------------------------------
+ */
+int
+NativePathInFilesystem(pathPtr, clientDataPtr)
+ Tcl_Obj *pathPtr;
+ ClientData *clientDataPtr;
+{
+ /*
+ * A special case is required to handle the empty path "".
+ * This is a valid path (i.e. the user should be able
+ * to do 'file exists ""' without throwing an error), but
+ * equally the path doesn't exist. Those are the semantics
+ * of Tcl (at present anyway), so we have to abide by them
+ * here.
+ */
+ if (pathPtr->typePtr == &tclFsPathType) {
+ if (pathPtr->bytes != NULL && pathPtr->bytes[0] == '\0') {
+ /* We reject the empty path "" */
+ return -1;
+ }
+ /* Otherwise there is no way this path can be empty */
+ } else {
+ /*
+ * It is somewhat unusual to reach this code path without
+ * the object being of tclFsPathType. However, we do
+ * our best to deal with the situation.
+ */
+ int len;
+ Tcl_GetStringFromObj(pathPtr,&len);
+ if (len == 0) {
+ /* We reject the empty path "" */
+ return -1;
+ }
+ }
+ /*
+ * Path is of correct type, or is of non-zero length,
+ * so we accept it.
+ */
+ return TCL_OK;
+}
diff --git a/generic/tclTest.c b/generic/tclTest.c
index cd6acc4..852a2d1 100644
--- a/generic/tclTest.c
+++ b/generic/tclTest.c
@@ -14,7 +14,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclTest.c,v 1.64 2003/03/09 14:22:06 kennykb Exp $
+ * RCS: @(#) $Id: tclTest.c,v 1.65 2003/04/11 15:59:58 vincentdarley Exp $
*/
#define TCL_TEST
@@ -3339,12 +3339,26 @@ TestregexpObjCmd(dummy, interp, objc, objv)
char *varName;
CONST char *value;
int start, end;
- char info[TCL_INTEGER_SPACE * 2];
+ char resinfo[TCL_INTEGER_SPACE * 2];
varName = Tcl_GetString(objv[2]);
TclRegExpRangeUniChar(regExpr, -1, &start, &end);
- sprintf(info, "%d %d", start, end-1);
- value = Tcl_SetVar(interp, varName, info, 0);
+ sprintf(resinfo, "%d %d", start, end-1);
+ value = Tcl_SetVar(interp, varName, resinfo, 0);
+ if (value == NULL) {
+ Tcl_AppendResult(interp, "couldn't set variable \"",
+ varName, "\"", (char *) NULL);
+ return TCL_ERROR;
+ }
+ } else if (cflags & TCL_REG_CANMATCH) {
+ char *varName;
+ CONST char *value;
+ char resinfo[TCL_INTEGER_SPACE * 2];
+
+ Tcl_RegExpGetInfo(regExpr, &info);
+ varName = Tcl_GetString(objv[2]);
+ sprintf(resinfo, "%d", info.extendStart);
+ value = Tcl_SetVar(interp, varName, resinfo, 0);
if (value == NULL) {
Tcl_AppendResult(interp, "couldn't set variable \"",
varName, "\"", (char *) NULL);
@@ -3463,6 +3477,10 @@ TestregexpXflags(string, length, cflagsPtr, eflagsPtr)
cflags &= ~REG_ADVANCED;
break;
}
+ case 'c': {
+ cflags |= TCL_REG_CANMATCH;
+ break;
+ }
case 'e': {
cflags &= ~REG_ADVANCED;
cflags |= REG_EXTENDED;
@@ -4014,7 +4032,7 @@ TestfileCmd(dummy, interp, argc, argv)
}
for (j = i; j < argc; j++) {
- if (Tcl_FSGetTranslatedPath(interp, argv[j]) == NULL) {
+ if (Tcl_FSGetNormalizedPath(interp, argv[j]) == NULL) {
return TCL_ERROR;
}
}
diff --git a/tests/cmdAH.test b/tests/cmdAH.test
index fe16def..0a0228b 100644
--- a/tests/cmdAH.test
+++ b/tests/cmdAH.test
@@ -10,7 +10,7 @@
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: cmdAH.test,v 1.30 2003/01/09 10:38:32 vincentdarley Exp $
+# RCS: @(#) $Id: cmdAH.test,v 1.31 2003/04/11 15:59:59 vincentdarley Exp $
if {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest 2.1
@@ -86,6 +86,9 @@ test cmdAH-2.5 {Tcl_CdObjCmd} {
test cmdAH-2.6 {Tcl_CdObjCmd} {
list [catch {cd _foobar} msg] $msg
} {1 {couldn't change working directory to "_foobar": no such file or directory}}
+test cmdAH-2.6.1 {Tcl_CdObjCmd} {
+ list [catch {cd ""} msg] $msg
+} {1 {couldn't change working directory to "": no such file or directory}}
test cmdAH-2.7 {Tcl_ConcatObjCmd} {
concat
@@ -396,12 +399,12 @@ test cmdAH-8.42 {Tcl_FileObjCmd: dirname} {
test cmdAH-8.43 {Tcl_FileObjCmd: dirname} {
global env
set temp $env(HOME)
- set env(HOME) "/home/test"
+ set env(HOME) "/homewontexist/test"
testsetplatform unix
set result [list [catch {file dirname ~} msg] $msg]
set env(HOME) $temp
set result
-} {0 /home}
+} {0 /homewontexist}
test cmdAH-8.44 {Tcl_FileObjCmd: dirname} {
global env
set temp $env(HOME)
@@ -414,12 +417,12 @@ test cmdAH-8.44 {Tcl_FileObjCmd: dirname} {
test cmdAH-8.45 {Tcl_FileObjCmd: dirname} {
global env
set temp $env(HOME)
- set env(HOME) "/home/test"
+ set env(HOME) "/homewontexist/test"
testsetplatform windows
set result [list [catch {file dirname ~} msg] $msg]
set env(HOME) $temp
set result
-} {0 /home}
+} {0 /homewontexist}
test cmdAH-8.46 {Tcl_FileObjCmd: dirname} {
global env
set temp $env(HOME)
diff --git a/tests/fCmd.test b/tests/fCmd.test
index e3bec24..8c5d944 100644
--- a/tests/fCmd.test
+++ b/tests/fCmd.test
@@ -10,7 +10,7 @@
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: fCmd.test,v 1.26 2003/02/12 19:18:13 vincentdarley Exp $
+# RCS: @(#) $Id: fCmd.test,v 1.27 2003/04/11 15:59:59 vincentdarley Exp $
#
if {[lsearch [namespace children] ::tcltest] == -1} {
@@ -673,6 +673,11 @@ test fCmd-8.2 {FileBasename: basename of ~user: argc == 1 && *path == ~} \
{unixOnly notRoot} {
file tail ~$user
} "$user"
+test fCmd-8.3 {file copy and path translation: ensure correct error} {
+ list [catch {file copy ~ [file join this file doesnt exist]} res] $res
+} [list 1 \
+ "error copying \"~\" to \"[file join this file doesnt exist]\":\
+ no such file or directory"]
test fCmd-9.1 {file rename: comprehensive: EACCES} {unixOnly notRoot} {
cleanup
diff --git a/tests/fileSystem.test b/tests/fileSystem.test
index f35eae1..08d4a88 100644
--- a/tests/fileSystem.test
+++ b/tests/fileSystem.test
@@ -43,6 +43,9 @@ if {[catch {
tcltest::testConstraint hasLinks 1
}
+tcltest::testConstraint testsimplefilesystem \
+ [string equal testsimplefilesystem [info commands testsimplefilesystem]]
+
test filesystem-1.0 {link normalisation} {hasLinks} {
string equal [file normalize gorp.file] [file normalize link.file]
} {0}
@@ -389,7 +392,7 @@ test filesystem-6.33 {empty file name} {
while {![catch {testfilesystem 0}]} {}
}
-test filesystem-7.1 {load from vfs} {win} {
+test filesystem-7.1 {load from vfs} {win testsimplefilesystem} {
# This may cause a crash on exit
set dir [pwd]
cd [file dirname [info nameof]]
@@ -403,7 +406,8 @@ test filesystem-7.1 {load from vfs} {win} {
# The real result of this test is what happens when Tcl exits.
} {ok}
-test filesystem-7.2 {cross-filesystem copy from vfs maintains mtime} {
+test filesystem-7.2 {cross-filesystem copy from vfs maintains mtime} \
+ {testsimplefilesystem} {
set dir [pwd]
cd [tcltest::temporaryDirectory]
# We created this file several tests ago.
diff --git a/tests/ioUtil.test b/tests/ioUtil.test
index 812e3e4..273f47e 100644
--- a/tests/ioUtil.test
+++ b/tests/ioUtil.test
@@ -8,7 +8,7 @@
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: ioUtil.test,v 1.13 2002/07/18 09:40:24 vincentdarley Exp $
+# RCS: @(#) $Id: ioUtil.test,v 1.14 2003/04/11 16:00:00 vincentdarley Exp $
if {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest 2
@@ -113,14 +113,14 @@ test ioUtil-1.8 {TclStatDeleteProc: Verify that all procs have been deleted.} {t
eval $unsetScript
-test ioUtil-1.1 {TclAccess: Check that none of the test procs are there.} {
+test ioUtil-1.9 {TclAccess: Check that none of the test procs are there.} {
catch {file exists testAccess1%.fil} err1
catch {file exists testAccess2%.fil} err2
catch {file exists testAccess3%.fil} err3
list $err1 $err2 $err3
} {0 0 0}
-test ioUtil-1.2 {TclAccessInsertProc: Insert the 3 test TclAccess_ procedures.} {testaccessproc} {
+test ioUtil-1.10 {TclAccessInsertProc: Insert the 3 test TclAccess_ procedures.} {testaccessproc} {
catch {testaccessproc insert TclpAccess} err1
testaccessproc insert TestAccessProc1
testaccessproc insert TestAccessProc2
diff --git a/tests/reg.test b/tests/reg.test
index 86f0098..1d1c8aa 100644
--- a/tests/reg.test
+++ b/tests/reg.test
@@ -9,7 +9,7 @@
#
# Copyright (c) 1998, 1999 Henry Spencer. All rights reserved.
#
-# RCS: @(#) $Id: reg.test,v 1.16 2002/07/29 12:28:35 dkf Exp $
+# RCS: @(#) $Id: reg.test,v 1.17 2003/04/11 16:00:00 vincentdarley Exp $
if {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest 2
@@ -995,6 +995,94 @@ test reg-31.1 {[[:xdigit:]] behaves correctly when followed by [[:space:]]} {
# Code used to produce {1 2:::DebugWin32 2 :::DebugWin32} !!!
} {1 2 2 {}}
+test reg-32.1 {canmatch functionality -- at end} {
+ set pat {blah}
+ set line "asd asd"
+ # can match at the final d, if '%' follows
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 7}
+
+test reg-32.2 {canmatch functionality -- at end} {
+ set pat {s%$}
+ set line "asd asd"
+ # can only match after the end of the string
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 7}
+
+test reg-32.3 {canmatch functionality -- not last char} {
+ set pat {[^d]%$}
+ set line "asd asd"
+ # can only match after the end of the string
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 7}
+
+test reg-32.3.1 {canmatch functionality -- no match} {
+ set pat {\Zx}
+ set line "asd asd"
+ # can match the last char, if followed by x
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 -1}
+
+test reg-32.4 {canmatch functionality -- last char} {knownBug} {
+ set pat {.x}
+ set line "asd asd"
+ # can match the last char, if followed by x
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 6}
+
+test reg-32.4.1 {canmatch functionality -- last char} {knownBug} {
+ set pat {.x$}
+ set line "asd asd"
+ # can match the last char, if followed by x
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 6}
+
+test reg-32.5 {canmatch functionality -- last char} {knownBug} {
+ set pat {.[^d]x$}
+ set line "asd asd"
+ # can match the last char, if followed by not-d and x.
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 6}
+
+test reg-32.6 {canmatch functionality -- last char} {knownBug} {
+ set pat {[^a]%[^\r\n]*$}
+ set line "asd asd"
+ # can match at the final d, if '%' follows
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 6}
+
+test reg-32.7 {canmatch functionality -- last char} {knownBug} {
+ set pat {[^a]%$}
+ set line "asd asd"
+ # can match at the final d, if '%' follows
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 6}
+
+test reg-32.8 {canmatch functionality -- last char} {knownBug} {
+ set pat {[^x]%$}
+ set line "asd asd"
+ # can match at the final d, if '%' follows
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 6}
+
+test reg-32.9 {canmatch functionality -- more complex case} {knownBug} {
+ set pat {((\B\B|\Bh+line)[ \t]*|[^\B]%[^\r\n]*)$}
+ set line "asd asd"
+ # can match at the final d, if '%' follows
+ set res [testregexp -xflags -- c $pat $line resvar]
+ lappend res $resvar
+} {0 6}
+
# cleanup
::tcltest::cleanupTests
return
diff --git a/tests/unixFCmd.test b/tests/unixFCmd.test
index ca707a4..574c5cc 100644
--- a/tests/unixFCmd.test
+++ b/tests/unixFCmd.test
@@ -9,7 +9,7 @@
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: unixFCmd.test,v 1.17 2003/01/25 00:16:39 hobbs Exp $
+# RCS: @(#) $Id: unixFCmd.test,v 1.18 2003/04/11 16:00:02 vincentdarley Exp $
if {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest
@@ -285,7 +285,7 @@ test unixFCmd-17.3 {SetPermissionsAttribute} {unixOnly notRoot} {
list [catch {file attributes foo.test -permissions foo} msg] $msg \
[file delete -force -- foo.test]
} {1 {unknown permission string format "foo"} {}}
-test unixFCmd-17.3 {SetPermissionsAttribute} {unixOnly notRoot} {
+test unixFCmd-17.4 {SetPermissionsAttribute} {unixOnly notRoot} {
catch {file delete -force -- foo.test}
close [open foo.test w]
list [catch {file attributes foo.test -permissions ---rwx} msg] $msg \
@@ -300,14 +300,14 @@ proc permcheck {testnum permstr expected} {
file attributes foo.test -permissions
} $expected
}
-permcheck unixFCmd-17.4 rwxrwxrwx 00777
-permcheck unixFCmd-17.5 r--r---w- 00442
-permcheck unixFCmd-17.6 0 00000
-permcheck unixFCmd-17.7 u+rwx,g+r 00740
-permcheck unixFCmd-17.8 u-w 00540
-permcheck unixFCmd-17.9 o+rwx 00547
-permcheck unixFCmd-17.10 --x--x--x 00111
-permcheck unixFCmd-17.11 a+rwx 00777
+permcheck unixFCmd-17.5 rwxrwxrwx 00777
+permcheck unixFCmd-17.6 r--r---w- 00442
+permcheck unixFCmd-17.7 0 00000
+permcheck unixFCmd-17.8 u+rwx,g+r 00740
+permcheck unixFCmd-17.9 u-w 00540
+permcheck unixFCmd-17.10 o+rwx 00547
+permcheck unixFCmd-17.11 --x--x--x 00111
+permcheck unixFCmd-17.12 a+rwx 00777
file delete -force -- foo.test
test unixFCmd-18.1 {Unix pwd} {nonPortable unixOnly notRoot} {
diff --git a/tests/winFile.test b/tests/winFile.test
index 4b04096..3f4c294 100644
--- a/tests/winFile.test
+++ b/tests/winFile.test
@@ -10,7 +10,7 @@
# See the file "license.terms" for information on usage and redistribution
# of this file, and for a DISCLAIMER OF ALL WARRANTIES.
#
-# RCS: @(#) $Id: winFile.test,v 1.9 2002/07/18 16:36:56 vincentdarley Exp $
+# RCS: @(#) $Id: winFile.test,v 1.10 2003/04/11 16:00:05 vincentdarley Exp $
if {[lsearch [namespace children] ::tcltest] == -1} {
package require tcltest
@@ -25,7 +25,7 @@ test winFile-1.2 {TclpGetUserHome} {pcOnly nt nonPortable} {
catch {glob ~administrator}
} {0}
-test winFile-1.2 {TclpGetUserHome} {pcOnly 95} {
+test winFile-1.3 {TclpGetUserHome} {pcOnly 95} {
# Find some user in system.ini and then see if they have a home.
set f [open $::env(windir)/system.ini]
@@ -44,7 +44,7 @@ test winFile-1.2 {TclpGetUserHome} {pcOnly 95} {
close $f
set x
} {0}
-test winFile-1.3 {TclpGetUserHome} {pcOnly nt nonPortable} {
+test winFile-1.4 {TclpGetUserHome} {pcOnly nt nonPortable} {
catch {glob ~stanton@workgroup}
} {0}
diff --git a/unix/Makefile.in b/unix/Makefile.in
index e134180..fc6c42c 100644
--- a/unix/Makefile.in
+++ b/unix/Makefile.in
@@ -5,7 +5,7 @@
# "autoconf" program (constructs like "@foo@" will get replaced in the
# actual Makefile.
#
-# RCS: @(#) $Id: Makefile.in,v 1.123 2003/04/05 01:25:11 dkf Exp $
+# RCS: @(#) $Id: Makefile.in,v 1.124 2003/04/11 16:09:51 vincentdarley Exp $
VERSION = @TCL_VERSION@
MAJOR_VERSION = @TCL_MAJOR_VERSION@
@@ -309,7 +309,7 @@ GENERIC_OBJS = regcomp.o regexec.o regfree.o regerror.o tclAlloc.o \
tclHash.o tclHistory.o tclIndexObj.o tclInterp.o tclIO.o tclIOCmd.o \
tclIOGT.o tclIOSock.o tclIOUtil.o tclLink.o tclListObj.o \
tclLiteral.o tclLoad.o tclMain.o tclNamesp.o tclNotify.o \
- tclObj.o tclPanic.o tclParse.o tclParseExpr.o tclPipe.o \
+ tclObj.o tclPanic.o tclParse.o tclParseExpr.o tclPathObj.o tclPipe.o \
tclPkg.o tclPosixStr.o tclPreserve.o tclProc.o tclRegexp.o \
tclResolve.o tclResult.o tclScan.o tclStringObj.o tclThread.o \
tclThreadAlloc.o tclThreadJoin.o tclStubInit.o tclStubLib.o \
@@ -380,6 +380,7 @@ GENERIC_SRCS = \
$(GENERIC_DIR)/tclObj.c \
$(GENERIC_DIR)/tclParse.c \
$(GENERIC_DIR)/tclParseExpr.c \
+ $(GENERIC_DIR)/tclPathObj.c \
$(GENERIC_DIR)/tclPipe.c \
$(GENERIC_DIR)/tclPkg.c \
$(GENERIC_DIR)/tclPosixStr.c \
@@ -945,6 +946,9 @@ tclParseExpr.o: $(GENERIC_DIR)/tclParseExpr.c
tclPanic.o: $(GENERIC_DIR)/tclPanic.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPanic.c
+tclPathObj.o: $(GENERIC_DIR)/tclPathObj.c
+ $(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPathObj.c
+
tclPipe.o: $(GENERIC_DIR)/tclPipe.c
$(CC) -c $(CC_SWITCHES) $(GENERIC_DIR)/tclPipe.c
diff --git a/win/makefile.vc b/win/makefile.vc
index 771ebe8..f42d67b 100644
--- a/win/makefile.vc
+++ b/win/makefile.vc
@@ -12,7 +12,7 @@
# Copyright (c) 2001-2002 David Gravereaux.
#
#------------------------------------------------------------------------------
-# RCS: @(#) $Id: makefile.vc,v 1.104 2003/04/05 01:25:11 dkf Exp $
+# RCS: @(#) $Id: makefile.vc,v 1.105 2003/04/11 16:00:07 vincentdarley Exp $
#------------------------------------------------------------------------------
!if "$(MSVCDIR)" == ""
@@ -264,6 +264,7 @@ TCLOBJS = \
$(TMP_DIR)\tclPanic.obj \
$(TMP_DIR)\tclParse.obj \
$(TMP_DIR)\tclParseExpr.obj \
+ $(TMP_DIR)\tclPathObj.obj \
$(TMP_DIR)\tclPipe.obj \
$(TMP_DIR)\tclPkg.obj \
$(TMP_DIR)\tclPosixStr.obj \
diff --git a/win/tclWin32Dll.c b/win/tclWin32Dll.c
index 5b939f9..ded9c6f 100644
--- a/win/tclWin32Dll.c
+++ b/win/tclWin32Dll.c
@@ -9,7 +9,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclWin32Dll.c,v 1.24 2003/02/04 17:06:52 vincentdarley Exp $
+ * RCS: @(#) $Id: tclWin32Dll.c,v 1.25 2003/04/11 16:00:08 vincentdarley Exp $
*/
#include "tclWinInt.h"
@@ -88,7 +88,7 @@ static TclWinProcs asciiProcs = {
(BOOL (WINAPI *)(CONST TCHAR *)) SetCurrentDirectoryA,
(BOOL (WINAPI *)(CONST TCHAR *, DWORD)) SetFileAttributesA,
/*
- * These two function pointers will only be set when
+ * The three NULL function pointers will only be set when
* Tcl_FindExecutable is called. If you don't ever call that
* function, the application will crash whenever WinTcl tries to call
* functions through these null pointers. That is not a bug in Tcl
@@ -97,6 +97,8 @@ static TclWinProcs asciiProcs = {
NULL,
NULL,
(int (__cdecl*)(CONST TCHAR *, struct _utimbuf *)) _utime,
+ NULL,
+ NULL,
};
static TclWinProcs unicodeProcs = {
@@ -135,7 +137,7 @@ static TclWinProcs unicodeProcs = {
(BOOL (WINAPI *)(CONST TCHAR *)) SetCurrentDirectoryW,
(BOOL (WINAPI *)(CONST TCHAR *, DWORD)) SetFileAttributesW,
/*
- * These two function pointers will only be set when
+ * The three NULL function pointers will only be set when
* Tcl_FindExecutable is called. If you don't ever call that
* function, the application will crash whenever WinTcl tries to call
* functions through these null pointers. That is not a bug in Tcl
@@ -144,6 +146,8 @@ static TclWinProcs unicodeProcs = {
NULL,
NULL,
(int (__cdecl*)(CONST TCHAR *, struct _utimbuf *)) _wutime,
+ NULL,
+ NULL,
};
TclWinProcs *tclWinProcs;
@@ -156,6 +160,28 @@ static Tcl_Encoding tclWinTCharEncoding;
BOOL APIENTRY DllMain(HINSTANCE hInst, DWORD reason,
LPVOID reserved);
+/*
+ * The following structure and linked list is to allow us to map between
+ * volume mount points and drive letters on the fly (no Win API exists
+ * for this).
+ */
+typedef struct MountPointMap {
+ CONST WCHAR* volumeName; /* Native wide string volume name */
+ char driveLetter; /* Drive letter corresponding to
+ * the volume name. */
+ struct MountPointMap* nextPtr; /* Pointer to next structure in list,
+ * or NULL */
+} MountPointMap;
+
+/*
+ * This is the head of the linked list, which is protected by the
+ * mutex which follows, for thread-enabled builds.
+ */
+MountPointMap *driveLetterLookup = NULL;
+TCL_DECLARE_MUTEX(mountPointMap)
+
+/* We will need this below */
+extern Tcl_FSDupInternalRepProc NativeDupInternalRep;
#ifdef __WIN32__
#ifndef STATIC_BUILD
@@ -531,6 +557,14 @@ TclWinSetInterfaces(
(BOOL (WINAPI *)(CONST TCHAR *, CONST TCHAR*,
LPSECURITY_ATTRIBUTES)) GetProcAddress(hInstance,
"CreateHardLinkW");
+ tclWinProcs->findFirstFileExProc =
+ (HANDLE (WINAPI *)(CONST TCHAR*, UINT,
+ LPVOID, UINT, LPVOID, DWORD)) GetProcAddress(hInstance,
+ "FindFirstFileExW");
+ tclWinProcs->getVolumeNameForVMPProc =
+ (BOOL (WINAPI *)(CONST TCHAR*, TCHAR*,
+ DWORD)) GetProcAddress(hInstance,
+ "GetVolumeNameForVolumeMountPointW");
FreeLibrary(hInstance);
}
}
@@ -547,6 +581,14 @@ TclWinSetInterfaces(
(BOOL (WINAPI *)(CONST TCHAR *, CONST TCHAR*,
LPSECURITY_ATTRIBUTES)) GetProcAddress(hInstance,
"CreateHardLinkA");
+ tclWinProcs->findFirstFileExProc =
+ (HANDLE (WINAPI *)(CONST TCHAR*, UINT,
+ LPVOID, UINT, LPVOID, DWORD)) GetProcAddress(hInstance,
+ "FindFirstFileExA");
+ tclWinProcs->getVolumeNameForVMPProc =
+ (BOOL (WINAPI *)(CONST TCHAR*, TCHAR*,
+ DWORD)) GetProcAddress(hInstance,
+ "GetVolumeNameForVolumeMountPointA");
FreeLibrary(hInstance);
}
}
@@ -562,6 +604,11 @@ TclWinSetInterfaces(
* The tclWinProcs-> look up table is still ok to use after
* this call, provided no encoding conversion is required.
*
+ * We also clean up any memory allocated in our mount point
+ * map which is used to follow certain kinds of symlinks.
+ * That code should never be used once encodings are taken
+ * down.
+ *
* Results:
* None.
*
@@ -573,10 +620,21 @@ TclWinSetInterfaces(
void
TclWinResetInterfaceEncodings()
{
+ MountPointMap *dlIter, *dlIter2;
if (tclWinTCharEncoding != NULL) {
Tcl_FreeEncoding(tclWinTCharEncoding);
tclWinTCharEncoding = NULL;
}
+ /* Clean up the mount point map */
+ Tcl_MutexLock(&mountPointMap);
+ dlIter = driveLetterLookup;
+ while (dlIter != NULL) {
+ dlIter2 = dlIter->nextPtr;
+ ckfree((char*)dlIter->volumeName);
+ ckfree((char*)dlIter);
+ dlIter = dlIter2;
+ }
+ Tcl_MutexUnlock(&mountPointMap);
}
/*
@@ -603,6 +661,135 @@ TclWinResetInterfaces()
}
/*
+ *--------------------------------------------------------------------
+ *
+ * TclWinDriveLetterForVolMountPoint
+ *
+ * Unfortunately, Windows provides no easy way at all to get hold
+ * of the drive letter for a volume mount point, but we need that
+ * information to understand paths correctly. So, we have to
+ * build an associated array to find these correctly, and allow
+ * quick and easy lookup from volume mount points to drive letters.
+ *
+ * We assume here that we are running on a system for which the wide
+ * character interfaces are used, which is valid for Win 2000 and WinXP
+ * which are the only systems on which this function will ever be called.
+ *
+ * Result: the drive letter, or -1 if no drive letter corresponds to
+ * the given mount point.
+ *
+ *--------------------------------------------------------------------
+ */
+char
+TclWinDriveLetterForVolMountPoint(CONST WCHAR *mountPoint)
+{
+ MountPointMap *dlIter, *dlPtr2;
+ WCHAR Target[55]; /* Target of mount at mount point */
+ WCHAR drive[4] = { L'A', L':', L'\\', L'\0' };
+
+ /*
+ * Detect the volume mounted there. Unfortunately, there is no
+ * simple way to map a unique volume name to a DOS drive letter.
+ * So, we have to build an associative array.
+ */
+
+ Tcl_MutexLock(&mountPointMap);
+ dlIter = driveLetterLookup;
+ while (dlIter != NULL) {
+ if (wcscmp(dlIter->volumeName, mountPoint) == 0) {
+ /*
+ * We need to check whether this information is
+ * still valid, since either the user or various
+ * programs could have adjusted the mount points on
+ * the fly.
+ */
+ drive[0] = L'A' + (dlIter->driveLetter - 'A');
+ /* Try to read the volume mount point and see where it points */
+ if ((*tclWinProcs->getVolumeNameForVMPProc)((TCHAR*)drive,
+ (TCHAR*)Target, 55) != 0) {
+ if (wcscmp((WCHAR*)dlIter->volumeName, Target) == 0) {
+ /* Nothing has changed */
+ Tcl_MutexUnlock(&mountPointMap);
+ return dlIter->driveLetter;
+ }
+ }
+ /*
+ * If we reach here, unfortunately, this mount point is
+ * no longer valid at all
+ */
+ if (driveLetterLookup == dlIter) {
+ dlPtr2 = dlIter;
+ driveLetterLookup = dlIter->nextPtr;
+ } else {
+ for (dlPtr2 = driveLetterLookup;
+ dlPtr2 != NULL; dlPtr2 = dlPtr2->nextPtr) {
+ if (dlPtr2->nextPtr == dlIter) {
+ dlPtr2->nextPtr = dlIter->nextPtr;
+ dlPtr2 = dlIter;
+ break;
+ }
+ }
+ }
+ /* Now dlPtr2 points to the structure to free */
+ ckfree((char*)dlPtr2->volumeName);
+ ckfree((char*)dlPtr2);
+ /*
+ * Restart the loop --- we could try to be clever
+ * and continue half way through, but the logic is a
+ * bit messy, so it's cleanest just to restart
+ */
+ dlIter = driveLetterLookup;
+ continue;
+ }
+ dlIter = dlIter->nextPtr;
+ }
+
+ /* We couldn't find it, so we must iterate over the letters */
+
+ for (drive[0] = L'A'; drive[0] <= L'Z'; drive[0]++) {
+ /* Try to read the volume mount point and see where it points */
+ if ((*tclWinProcs->getVolumeNameForVMPProc)((TCHAR*)drive,
+ (TCHAR*)Target, 55) != 0) {
+ int alreadyStored = 0;
+ for (dlIter = driveLetterLookup; dlIter != NULL;
+ dlIter = dlIter->nextPtr) {
+ if (wcscmp((WCHAR*)dlIter->volumeName, Target) == 0) {
+ alreadyStored = 1;
+ break;
+ }
+ }
+ if (!alreadyStored) {
+ dlPtr2 = (MountPointMap*) ckalloc(sizeof(MountPointMap));
+ dlPtr2->volumeName = NativeDupInternalRep(Target);
+ dlPtr2->driveLetter = 'A' + (drive[0] - L'A');
+ dlPtr2->nextPtr = driveLetterLookup;
+ driveLetterLookup = dlPtr2;
+ }
+ }
+ }
+ /* Try again */
+ for (dlIter = driveLetterLookup; dlIter != NULL;
+ dlIter = dlIter->nextPtr) {
+ if (wcscmp(dlIter->volumeName, mountPoint) == 0) {
+ Tcl_MutexUnlock(&mountPointMap);
+ return dlIter->driveLetter;
+ }
+ }
+ /*
+ * The volume doesn't appear to correspond to a drive letter -- we
+ * remember that fact and store '-1' so we don't have to look it
+ * up each time.
+ */
+ dlPtr2 = (MountPointMap*) ckalloc(sizeof(MountPointMap));
+ dlPtr2->volumeName = NativeDupInternalRep((ClientData)mountPoint);
+ dlPtr2->driveLetter = -1;
+ dlPtr2->nextPtr = driveLetterLookup;
+ driveLetterLookup = dlPtr2;
+ Tcl_MutexUnlock(&mountPointMap);
+ return -1;
+}
+
+/*
*---------------------------------------------------------------------------
*
* Tcl_WinUtfToTChar, Tcl_WinTCharToUtf --
diff --git a/win/tclWinFile.c b/win/tclWinFile.c
index 8213e6d..895747a 100644
--- a/win/tclWinFile.c
+++ b/win/tclWinFile.c
@@ -11,7 +11,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclWinFile.c,v 1.44 2003/02/10 12:50:32 vincentdarley Exp $
+ * RCS: @(#) $Id: tclWinFile.c,v 1.45 2003/04/11 16:00:13 vincentdarley Exp $
*/
//#define _WIN32_WINNT 0x0500
@@ -128,6 +128,18 @@ typedef struct {
WCHAR dummyBuf[MAX_PATH*3];
} DUMMY_REPARSE_BUFFER;
+/* These two aren't in VC++ 5.2 headers */
+typedef enum _FINDEX_INFO_LEVELS {
+ FindExInfoStandard,
+ FindExInfoMaxInfoLevel
+} FINDEX_INFO_LEVELS;
+typedef enum _FINDEX_SEARCH_OPS {
+ FindExSearchNameMatch,
+ FindExSearchLimitToDirectories,
+ FindExSearchLimitToDevices,
+ FindExSearchMaxSearchOp
+} FINDEX_SEARCH_OPS;
+
/* Other typedefs required by this code */
static time_t ToCTime(FILETIME fileTime);
@@ -141,6 +153,8 @@ typedef NET_API_STATUS NET_API_FUNCTION NETAPIBUFFERFREEPROC
typedef NET_API_STATUS NET_API_FUNCTION NETGETDCNAMEPROC
(LPWSTR servername, LPWSTR domainname, LPBYTE *bufptr);
+extern Tcl_FSDupInternalRepProc NativeDupInternalRep;
+
/*
* Declarations for local procedures defined in this file:
*/
@@ -162,7 +176,6 @@ static int WinLink(CONST TCHAR* LinkSource, CONST TCHAR* LinkTarget,
int linkAction);
static int WinSymLinkDirectory(CONST TCHAR* LinkDirectory,
CONST TCHAR* LinkTarget);
-
/*
*--------------------------------------------------------------------
@@ -249,8 +262,7 @@ WinLink(LinkSource, LinkTarget, linkAction)
*
* WinReadLink
*
- * What does 'LinkSource' point to? We need the original 'pathPtr'
- * just so we can construct a path object in the correct filesystem.
+ * What does 'LinkSource' point to?
*--------------------------------------------------------------------
*/
static Tcl_Obj*
@@ -429,7 +441,11 @@ TclWinSymLinkDelete(LinkOriginal, linkOnly)
*
* Assumption that LinkDirectory is a valid, existing directory.
*
- * Returns a Tcl_Obj with refCount of 1 (i.e. owned by the caller).
+ * Returns a Tcl_Obj with refCount of 1 (i.e. owned by the caller),
+ * or NULL if anything went wrong.
+ *
+ * In the future we should enhance this to return a path object
+ * rather than a string.
*--------------------------------------------------------------------
*/
static Tcl_Obj*
@@ -457,28 +473,77 @@ WinReadLinkDirectory(LinkDirectory)
Tcl_DString ds;
CONST char *copy;
int len;
+ int offset = 0;
- Tcl_WinTCharToUtf(
- (CONST char*)reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer,
- (int)reparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength,
- &ds);
-
- copy = Tcl_DStringValue(&ds);
- len = Tcl_DStringLength(&ds);
/*
- * Certain native path representations on Windows have this special
- * prefix to indicate that they are to be treated specially. For
- * example extremely long paths, or symlinks
+ * Certain native path representations on Windows have a
+ * special prefix to indicate that they are to be treated
+ * specially. For example extremely long paths, or symlinks,
+ * or volumes mounted inside directories.
+ *
+ * There is an assumption in this code that 'wide' interfaces
+ * are being used (see tclWin32Dll.c), which is true for the
+ * only systems which support reparse tags at present. If
+ * that changes in the future, this code will have to be
+ * generalised.
*/
- if (*copy == '\\') {
- if (0 == strncmp(copy,"\\??\\",4)) {
- copy += 4;
- len -= 4;
- } else if (0 == strncmp(copy,"\\\\?\\",4)) {
- copy += 4;
- len -= 4;
+ if (reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer[0]
+ == L'\\') {
+ /* Check whether this is a mounted volume */
+ if (wcsncmp(reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer,
+ L"\\??\\Volume{",11) == 0) {
+ char drive;
+ /*
+ * There is some confusion between \??\ and \\?\ which
+ * we have to fix here. It doesn't seem very well
+ * documented.
+ */
+ reparseBuffer->SymbolicLinkReparseBuffer
+ .PathBuffer[1] = L'\\';
+ /*
+ * Check if a corresponding drive letter exists, and
+ * use that if it is found
+ */
+ drive = TclWinDriveLetterForVolMountPoint(reparseBuffer
+ ->SymbolicLinkReparseBuffer.PathBuffer);
+ if (drive != -1) {
+ char driveSpec[3] = {
+ drive, ':', '\0'
+ };
+ retVal = Tcl_NewStringObj(driveSpec,2);
+ Tcl_IncrRefCount(retVal);
+ return retVal;
+ }
+ /*
+ * This is actually a mounted drive, which doesn't
+ * exists as a DOS drive letter. This means the path
+ * isn't actually a link, although we partially treat
+ * it like one ('file type' will return 'link'), but
+ * then the link will actually just be treated like
+ * an ordinary directory. I don't believe any
+ * serious inconsistency will arise from this, but it
+ * is something to be aware of.
+ */
+ Tcl_SetErrno(EINVAL);
+ return NULL;
+ } else if (wcsncmp(reparseBuffer->SymbolicLinkReparseBuffer
+ .PathBuffer, L"\\\\?\\",4) == 0) {
+ /* Strip off the prefix */
+ offset = 4;
+ } else if (wcsncmp(reparseBuffer->SymbolicLinkReparseBuffer
+ .PathBuffer, L"\\??\\",4) == 0) {
+ /* Strip off the prefix */
+ offset = 4;
}
}
+
+ Tcl_WinTCharToUtf(
+ (CONST char*)reparseBuffer->SymbolicLinkReparseBuffer.PathBuffer,
+ (int)reparseBuffer->SymbolicLinkReparseBuffer
+ .SubstituteNameLength, &ds);
+
+ copy = Tcl_DStringValue(&ds)+offset;
+ len = Tcl_DStringLength(&ds)-offset;
retVal = Tcl_NewStringObj(copy,len);
Tcl_IncrRefCount(retVal);
Tcl_DStringFree(&ds);
@@ -702,77 +767,97 @@ TclpMatchInDirectory(interp, resultPtr, pathPtr, pattern, types)
DWORD attr;
HANDLE handle;
WIN32_FIND_DATAT data;
- CONST char *dirName;
+ CONST char *dirName; /* utf-8 dir name, later
+ * with pattern appended */
int dirLength;
int matchSpecialDots;
- Tcl_DString ds; /* native encoding of dir */
+ Tcl_DString ds; /* native encoding of dir, also used
+ * temporarily for other things. */
Tcl_DString dsOrig; /* utf-8 encoding of dir */
- Tcl_DString dirString; /* utf-8 encoding of dir with \'s */
Tcl_Obj *fileNamePtr;
+ char lastChar;
/*
- * Convert the path to normalized form since some interfaces only
- * accept backslashes. Also, ensure that the directory ends with a
- * separator character.
+ * Get the normalized path representation
+ * (the main thing is we dont want any '~' sequences).
*/
- fileNamePtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
+ fileNamePtr = Tcl_FSGetNormalizedPath(interp, pathPtr);
if (fileNamePtr == NULL) {
return TCL_ERROR;
}
- Tcl_DStringInit(&dsOrig);
- dirName = Tcl_GetStringFromObj(fileNamePtr, &dirLength);
- Tcl_DStringAppend(&dsOrig, dirName, dirLength);
-
- Tcl_DStringInit(&dirString);
- if (dirLength == 0) {
- Tcl_DStringAppend(&dirString, ".\\", 2);
- } else {
- char *p;
-
- Tcl_DStringAppend(&dirString, dirName, dirLength);
- for (p = Tcl_DStringValue(&dirString); *p != '\0'; p++) {
- if (*p == '/') {
- *p = '\\';
- }
- }
- p--;
- /* Make sure we have a trailing directory delimiter */
- if ((*p != '\\') && (*p != ':')) {
- Tcl_DStringAppend(&dirString, "\\", 1);
- Tcl_DStringAppend(&dsOrig, "/", 1);
- dirLength++;
- }
- }
- dirName = Tcl_DStringValue(&dirString);
/*
- * First verify that the specified path is actually a directory.
+ * Verify that the specified path exists and
+ * is actually a directory.
*/
-
- native = Tcl_WinUtfToTChar(dirName, Tcl_DStringLength(&dirString),
- &ds);
+ native = Tcl_FSGetNativePath(pathPtr);
+ if (native == NULL) {
+ return TCL_OK;
+ }
attr = (*tclWinProcs->getFileAttributesProc)(native);
- Tcl_DStringFree(&ds);
if ((attr == 0xffffffff) || ((attr & FILE_ATTRIBUTE_DIRECTORY) == 0)) {
- Tcl_DStringFree(&dirString);
return TCL_OK;
}
+ /*
+ * Build up the directory name for searching, including
+ * a trailing directory separator.
+ */
+
+ Tcl_DStringInit(&dsOrig);
+ dirName = Tcl_GetStringFromObj(fileNamePtr, &dirLength);
+ Tcl_DStringAppend(&dsOrig, dirName, dirLength);
+
+ lastChar = dirName[dirLength -1];
+ if ((lastChar != '\\') && (lastChar != '/') && (lastChar != ':')) {
+ Tcl_DStringAppend(&dsOrig, "/", 1);
+ dirLength++;
+ }
+ dirName = Tcl_DStringValue(&dsOrig);
+
/*
- * We need to check all files in the directory, so append a *.*
- * to the path.
+ * We need to check all files in the directory, so we append
+ * '*.*' to the path, unless the pattern we've been given is
+ * rather simple, when we can use that instead.
*/
- dirName = Tcl_DStringAppend(&dirString, "*.*", 3);
+ if (strpbrk(pattern, "[]\\") == NULL) {
+ /*
+ * The pattern is a simple one containing just '*' and/or '?'.
+ * This means we can get the OS to help us, by passing
+ * it the pattern.
+ */
+ dirName = Tcl_DStringAppend(&dsOrig, pattern, -1);
+ } else {
+ dirName = Tcl_DStringAppend(&dsOrig, "*.*", 3);
+ }
native = Tcl_WinUtfToTChar(dirName, -1, &ds);
- handle = (*tclWinProcs->findFirstFileProc)(native, &data);
+ if (tclWinProcs->findFirstFileExProc == NULL
+ || (types == NULL)
+ || (types->type != TCL_GLOB_TYPE_DIR)) {
+ handle = (*tclWinProcs->findFirstFileProc)(native, &data);
+ } else {
+ /* We can be more efficient, for pure directory requests */
+ handle = (*tclWinProcs->findFirstFileExProc)(native,
+ FindExInfoStandard, &data,
+ FindExSearchLimitToDirectories, NULL, 0);
+ }
Tcl_DStringFree(&ds);
if (handle == INVALID_HANDLE_VALUE) {
- Tcl_DStringFree(&dirString);
- TclWinConvertError(GetLastError());
+ DWORD err = GetLastError();
+ if (err == ERROR_FILE_NOT_FOUND) {
+ /*
+ * We used our 'pattern' above, and matched nothing
+ * This means we just return TCL_OK, indicating
+ * no results found.
+ */
+ Tcl_DStringFree(&dsOrig);
+ return TCL_OK;
+ }
+ TclWinConvertError(err);
Tcl_ResetResult(interp);
Tcl_AppendResult(interp, "couldn't read directory \"",
Tcl_DStringValue(&dsOrig), "\": ",
@@ -781,6 +866,12 @@ TclpMatchInDirectory(interp, resultPtr, pathPtr, pattern, types)
return TCL_ERROR;
}
+ /*
+ * We may use this later, so we must restore it to its
+ * length including the directory delimiter
+ */
+ Tcl_DStringSetLength(&dsOrig, dirLength);
+
/*
* Check to see if the pattern should match the special
* . and .. names, referring to the current directory,
@@ -874,7 +965,6 @@ TclpMatchInDirectory(interp, resultPtr, pathPtr, pattern, types)
} while ((*tclWinProcs->findNextFileProc)(handle, &data) == TRUE);
FindClose(handle);
- Tcl_DStringFree(&dirString);
Tcl_DStringFree(&dsOrig);
return TCL_OK;
}
@@ -942,10 +1032,9 @@ WinIsDrive(
* volume, because for NTFS root volumes, the getFileAttributesProc
* returns a 'hidden' attribute when it should not.
*
- * We only ever make one call to a 'get attributes' routine here,
- * so that this function is as fast as possible. Unfortunately,
- * it still means we have to make the call for every single file
- * we return from 'glob', which is not ideal.
+ * We never make any calss to a 'get attributes' routine here,
+ * since we have arranged things so that our caller already knows
+ * such information.
*
* Results:
* 0 = file doesn't match
diff --git a/win/tclWinInt.h b/win/tclWinInt.h
index 0e0f11d..c2fe5ae 100644
--- a/win/tclWinInt.h
+++ b/win/tclWinInt.h
@@ -8,7 +8,7 @@
* See the file "license.terms" for information on usage and redistribution
* of this file, and for a DISCLAIMER OF ALL WARRANTIES.
*
- * RCS: @(#) $Id: tclWinInt.h,v 1.20 2003/02/04 17:06:53 vincentdarley Exp $
+ * RCS: @(#) $Id: tclWinInt.h,v 1.21 2003/04/11 16:00:17 vincentdarley Exp $
*/
#ifndef _TCLWININT
@@ -102,7 +102,11 @@ typedef struct TclWinProcs {
LPSECURITY_ATTRIBUTES);
INT (__cdecl *utimeProc)(CONST TCHAR*, struct _utimbuf *);
-
+ /* These two are also NULL at start; see comment above */
+ HANDLE (WINAPI *findFirstFileExProc)(CONST TCHAR*, UINT,
+ LPVOID, UINT,
+ LPVOID, DWORD);
+ BOOL (WINAPI *getVolumeNameForVMPProc)(CONST TCHAR*, TCHAR*, DWORD);
} TclWinProcs;
EXTERN TclWinProcs *tclWinProcs;
@@ -119,6 +123,7 @@ EXTERN int TclWinSymLinkCopyDirectory(CONST TCHAR* LinkOriginal,
CONST TCHAR* LinkCopy);
EXTERN int TclWinSymLinkDelete(CONST TCHAR* LinkOriginal,
int linkOnly);
+EXTERN char TclWinDriveLetterForVolMountPoint(CONST WCHAR *mountPoint);
#if defined(TCL_THREADS) && defined(USE_THREAD_ALLOC)
EXTERN void TclWinFreeAllocCache(void);
EXTERN void TclFreeAllocCache(void *);