os/persistentdata/persistentstorage/sqlite3api/TEST/TCL/tcldistribution/generic/tclIOUtil.c
author sl
Tue, 10 Jun 2014 14:32:02 +0200
changeset 1 260cb5ec6c19
permissions -rw-r--r--
Update contrib.
     1 /* 
     2  * tclIOUtil.c --
     3  *
     4  *	This file contains the implementation of Tcl's generic
     5  *	filesystem code, which supports a pluggable filesystem
     6  *	architecture allowing both platform specific filesystems and
     7  *	'virtual filesystems'.  All filesystem access should go through
     8  *	the functions defined in this file.  Most of this code was
     9  *	contributed by Vince Darley.
    10  *
    11  *	Parts of this file are based on code contributed by Karl
    12  *	Lehenbauer, Mark Diekhans and Peter da Silva.
    13  *
    14  * Copyright (c) 1991-1994 The Regents of the University of California.
    15  * Copyright (c) 1994-1997 Sun Microsystems, Inc.
    16  * Portions Copyright (c) 2007-2008 Nokia Corporation and/or its subsidiaries. All rights reserved.  
    17  *
    18  * See the file "license.terms" for information on usage and redistribution
    19  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
    20  *
    21  * RCS: @(#) $Id: tclIOUtil.c,v 1.77.2.34 2007/02/19 23:49:05 hobbs Exp $
    22  */
    23 
    24 #include "tclInt.h"
    25 #include "tclPort.h"
    26 #ifdef MAC_TCL
    27 #include "tclMacInt.h"
    28 #endif
    29 #ifdef __WIN32__
    30 /* for tclWinProcs->useWide */
    31 #include "tclWinInt.h"
    32 #endif
    33 #if defined(__SYMBIAN32__) && defined(__WINSCW__)
    34 #include "tclSymbianGlobals.h"
    35 #define dataKey getdataKey(4)
    36 #endif 
    37 
    38 /* 
    39  * struct FilesystemRecord --
    40  * 
    41  * A filesystem record is used to keep track of each
    42  * filesystem currently registered with the core,
    43  * in a linked list.  Pointers to these structures
    44  * are also kept by each "path" Tcl_Obj, and we must
    45  * retain a refCount on the number of such references.
    46  */
    47 typedef struct FilesystemRecord {
    48     ClientData	     clientData;  /* Client specific data for the new
    49 				   * filesystem (can be NULL) */
    50     Tcl_Filesystem *fsPtr;        /* Pointer to filesystem dispatch
    51 				   * table. */
    52     int fileRefCount;             /* How many Tcl_Obj's use this
    53 				   * filesystem. */
    54     struct FilesystemRecord *nextPtr;  
    55 				  /* The next filesystem registered
    56 				   * to Tcl, or NULL if no more. */
    57     struct FilesystemRecord *prevPtr;  
    58 				  /* The previous filesystem registered
    59 				   * to Tcl, or NULL if no more. */
    60 } FilesystemRecord;
    61 
    62 /* 
    63  * The internal TclFS API provides routines for handling and
    64  * manipulating paths efficiently, taking direct advantage of
    65  * the "path" Tcl_Obj type.
    66  * 
    67  * These functions are not exported at all at present.
    68  */
    69 
    70 int      TclFSCwdPointerEquals _ANSI_ARGS_((Tcl_Obj* objPtr));
    71 int	 TclFSMakePathFromNormalized _ANSI_ARGS_((Tcl_Interp *interp, 
    72 		Tcl_Obj *objPtr, ClientData clientData));
    73 int      TclFSNormalizeToUniquePath _ANSI_ARGS_((Tcl_Interp *interp, 
    74 		Tcl_Obj *pathPtr, int startAt, ClientData *clientDataPtr));
    75 Tcl_Obj* TclFSMakePathRelative _ANSI_ARGS_((Tcl_Interp *interp, 
    76 		Tcl_Obj *objPtr, Tcl_Obj *cwdPtr));
    77 Tcl_Obj* TclFSInternalToNormalized _ANSI_ARGS_((
    78 		Tcl_Filesystem *fromFilesystem, ClientData clientData,
    79 		FilesystemRecord **fsRecPtrPtr));
    80 int      TclFSEnsureEpochOk _ANSI_ARGS_((Tcl_Obj* pathObjPtr,
    81 		Tcl_Filesystem **fsPtrPtr));
    82 void     TclFSSetPathDetails _ANSI_ARGS_((Tcl_Obj *pathObjPtr, 
    83 		FilesystemRecord *fsRecPtr, ClientData clientData)); 
    84 
    85 /* 
    86  * Private variables for use in this file
    87  */
    88 extern Tcl_Filesystem tclNativeFilesystem;
    89 extern int theFilesystemEpoch;
    90 
    91 /* 
    92  * Private functions for use in this file
    93  */
    94 static Tcl_PathType     FSGetPathType  _ANSI_ARGS_((Tcl_Obj *pathObjPtr, 
    95 			    Tcl_Filesystem **filesystemPtrPtr, 
    96 			    int *driveNameLengthPtr));
    97 static Tcl_PathType     GetPathType  _ANSI_ARGS_((Tcl_Obj *pathObjPtr, 
    98 			    Tcl_Filesystem **filesystemPtrPtr, 
    99 			    int *driveNameLengthPtr, Tcl_Obj **driveNameRef));
   100 static Tcl_FSPathInFilesystemProc NativePathInFilesystem;
   101 static Tcl_Obj*  TclFSNormalizeAbsolutePath 
   102 			    _ANSI_ARGS_((Tcl_Interp* interp, Tcl_Obj *pathPtr,
   103 					 ClientData *clientDataPtr));
   104 /*
   105  * Prototypes for procedures defined later in this file.
   106  */
   107 
   108 static FilesystemRecord* FsGetFirstFilesystem(void);
   109 static void FsThrExitProc(ClientData cd);
   110 static Tcl_Obj* FsListMounts          _ANSI_ARGS_((Tcl_Obj *pathPtr, 
   111 						   CONST char *pattern));
   112 static Tcl_Obj* FsAddMountsToGlobResult  _ANSI_ARGS_((Tcl_Obj *result, 
   113 	   Tcl_Obj *pathPtr, CONST char *pattern, Tcl_GlobTypeData *types));
   114 
   115 #ifdef TCL_THREADS
   116 static void FsRecacheFilesystemList(void);
   117 #endif
   118 
   119 /* 
   120  * These form part of the native filesystem support.  They are needed
   121  * here because we have a few native filesystem functions (which are
   122  * the same for mac/win/unix) in this file.  There is no need to place
   123  * them in tclInt.h, because they are not (and should not be) used
   124  * anywhere else.
   125  */
   126 extern CONST char *		tclpFileAttrStrings[];
   127 extern CONST TclFileAttrProcs	tclpFileAttrProcs[];
   128 
   129 /* 
   130  * The following functions are obsolete string based APIs, and should
   131  * be removed in a future release (Tcl 9 would be a good time).
   132  */
   133 
   134 /* Obsolete */
   135 EXPORT_C int
   136 Tcl_Stat(path, oldStyleBuf)
   137     CONST char *path;		/* Path of file to stat (in current CP). */
   138     struct stat *oldStyleBuf;	/* Filled with results of stat call. */
   139 {
   140     int ret;
   141     Tcl_StatBuf buf;
   142     Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
   143 
   144     Tcl_IncrRefCount(pathPtr);
   145     ret = Tcl_FSStat(pathPtr, &buf);
   146     Tcl_DecrRefCount(pathPtr);
   147     if (ret != -1) {
   148 #ifndef TCL_WIDE_INT_IS_LONG
   149 #   define OUT_OF_RANGE(x) \
   150 	(((Tcl_WideInt)(x)) < Tcl_LongAsWide(LONG_MIN) || \
   151 	 ((Tcl_WideInt)(x)) > Tcl_LongAsWide(LONG_MAX))
   152 #if defined(__GNUC__) && __GNUC__ >= 2
   153 /*
   154  * Workaround gcc warning of "comparison is always false due to limited range of
   155  * data type" in this macro by checking max type size, and when necessary ANDing
   156  * with the complement of ULONG_MAX instead of the comparison:
   157  */
   158 #   define OUT_OF_URANGE(x) \
   159 	((((Tcl_WideUInt)(~ (__typeof__(x)) 0)) > (Tcl_WideUInt)ULONG_MAX) && \
   160 	 (((Tcl_WideUInt)(x)) & ~(Tcl_WideUInt)ULONG_MAX))
   161 #else
   162 #   define OUT_OF_URANGE(x) \
   163 	(((Tcl_WideUInt)(x)) > (Tcl_WideUInt)ULONG_MAX)
   164 #endif
   165 
   166 	/*
   167 	 * Perform the result-buffer overflow check manually.
   168 	 *
   169 	 * Note that ino_t/ino64_t is unsigned...
   170 	 */
   171 
   172         if (OUT_OF_URANGE(buf.st_ino) || OUT_OF_RANGE(buf.st_size)
   173 #ifdef HAVE_ST_BLOCKS
   174 		|| OUT_OF_RANGE(buf.st_blocks)
   175 #endif
   176 	    ) {
   177 #ifdef EFBIG
   178 	    errno = EFBIG;
   179 #else
   180 #  ifdef EOVERFLOW
   181 	    errno = EOVERFLOW;
   182 #  else
   183 #    error  "What status should be returned for file size out of range?"
   184 #  endif
   185 #endif
   186 	    return -1;
   187 	}
   188 
   189 #   undef OUT_OF_RANGE
   190 #   undef OUT_OF_URANGE
   191 #endif /* !TCL_WIDE_INT_IS_LONG */
   192 
   193 	/*
   194 	 * Copy across all supported fields, with possible type
   195 	 * coercions on those fields that change between the normal
   196 	 * and lf64 versions of the stat structure (on Solaris at
   197 	 * least.)  This is slow when the structure sizes coincide,
   198 	 * but that's what you get for using an obsolete interface.
   199 	 */
   200 
   201 	oldStyleBuf->st_mode    = buf.st_mode;
   202 	oldStyleBuf->st_ino     = (ino_t) buf.st_ino;
   203 	oldStyleBuf->st_dev     = buf.st_dev;
   204 	oldStyleBuf->st_rdev    = buf.st_rdev;
   205 	oldStyleBuf->st_nlink   = buf.st_nlink;
   206 	oldStyleBuf->st_uid     = buf.st_uid;
   207 	oldStyleBuf->st_gid     = buf.st_gid;
   208 	oldStyleBuf->st_size    = (off_t) buf.st_size;
   209 	oldStyleBuf->st_atime   = buf.st_atime;
   210 	oldStyleBuf->st_mtime   = buf.st_mtime;
   211 	oldStyleBuf->st_ctime   = buf.st_ctime;
   212 #ifdef HAVE_ST_BLOCKS
   213 	oldStyleBuf->st_blksize = buf.st_blksize;
   214 	oldStyleBuf->st_blocks  = (blkcnt_t) buf.st_blocks;
   215 #endif
   216     }
   217     return ret;
   218 }
   219 
   220 /* Obsolete */
   221 EXPORT_C int
   222 Tcl_Access(path, mode)
   223     CONST char *path;		/* Path of file to access (in current CP). */
   224     int mode;                   /* Permission setting. */
   225 {
   226     int ret;
   227     Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
   228     Tcl_IncrRefCount(pathPtr);
   229     ret = Tcl_FSAccess(pathPtr,mode);
   230     Tcl_DecrRefCount(pathPtr);
   231     return ret;
   232 }
   233 
   234 /* Obsolete */
   235 EXPORT_C Tcl_Channel
   236 Tcl_OpenFileChannel(interp, path, modeString, permissions)
   237     Tcl_Interp *interp;                 /* Interpreter for error reporting;
   238 					 * can be NULL. */
   239     CONST char *path;                   /* Name of file to open. */
   240     CONST char *modeString;             /* A list of POSIX open modes or
   241 					 * a string such as "rw". */
   242     int permissions;                    /* If the open involves creating a
   243 					 * file, with what modes to create
   244 					 * it? */
   245 {
   246     Tcl_Channel ret;
   247     Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
   248     Tcl_IncrRefCount(pathPtr);
   249     ret = Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions);
   250     Tcl_DecrRefCount(pathPtr);
   251     return ret;
   252 
   253 }
   254 
   255 /* Obsolete */
   256 EXPORT_C int
   257 Tcl_Chdir(dirName)
   258     CONST char *dirName;
   259 {
   260     int ret;
   261     Tcl_Obj *pathPtr = Tcl_NewStringObj(dirName,-1);
   262     Tcl_IncrRefCount(pathPtr);
   263     ret = Tcl_FSChdir(pathPtr);
   264     Tcl_DecrRefCount(pathPtr);
   265     return ret;
   266 }
   267 
   268 /* Obsolete */
   269 EXPORT_C char *
   270 Tcl_GetCwd(interp, cwdPtr)
   271     Tcl_Interp *interp;
   272     Tcl_DString *cwdPtr;
   273 {
   274     Tcl_Obj *cwd;
   275     cwd = Tcl_FSGetCwd(interp);
   276     if (cwd == NULL) {
   277 	return NULL;
   278     } else {
   279 	Tcl_DStringInit(cwdPtr);
   280 	Tcl_DStringAppend(cwdPtr, Tcl_GetString(cwd), -1);
   281 	Tcl_DecrRefCount(cwd);
   282 	return Tcl_DStringValue(cwdPtr);
   283     }
   284 }
   285 
   286 /* Obsolete */
   287 EXPORT_C int
   288 Tcl_EvalFile(interp, fileName)
   289     Tcl_Interp *interp;		/* Interpreter in which to process file. */
   290     CONST char *fileName;	/* Name of file to process.  Tilde-substitution
   291 				 * will be performed on this name. */
   292 {
   293     int ret;
   294     Tcl_Obj *pathPtr = Tcl_NewStringObj(fileName,-1);
   295     Tcl_IncrRefCount(pathPtr);
   296     ret = Tcl_FSEvalFile(interp, pathPtr);
   297     Tcl_DecrRefCount(pathPtr);
   298     return ret;
   299 }
   300 
   301 
   302 /* 
   303  * The 3 hooks for Stat, Access and OpenFileChannel are obsolete.  The
   304  * complete, general hooked filesystem APIs should be used instead.
   305  * This define decides whether to include the obsolete hooks and
   306  * related code.  If these are removed, we'll also want to remove them
   307  * from stubs/tclInt.  The only known users of these APIs are prowrap
   308  * and mktclapp.  New code/extensions should not use them, since they
   309  * do not provide as full support as the full filesystem API.
   310  * 
   311  * As soon as prowrap and mktclapp are updated to use the full
   312  * filesystem support, I suggest all these hooks are removed.
   313  */
   314 #define USE_OBSOLETE_FS_HOOKS
   315 
   316 
   317 #ifdef USE_OBSOLETE_FS_HOOKS
   318 /*
   319  * The following typedef declarations allow for hooking into the chain
   320  * of functions maintained for 'Tcl_Stat(...)', 'Tcl_Access(...)' &
   321  * 'Tcl_OpenFileChannel(...)'.  Basically for each hookable function
   322  * a linked list is defined.
   323  */
   324 
   325 typedef struct StatProc {
   326     TclStatProc_ *proc;		 /* Function to process a 'stat()' call */
   327     struct StatProc *nextPtr;    /* The next 'stat()' function to call */
   328 } StatProc;
   329 
   330 typedef struct AccessProc {
   331     TclAccessProc_ *proc;	 /* Function to process a 'access()' call */
   332     struct AccessProc *nextPtr;  /* The next 'access()' function to call */
   333 } AccessProc;
   334 
   335 typedef struct OpenFileChannelProc {
   336     TclOpenFileChannelProc_ *proc;  /* Function to process a
   337 				     * 'Tcl_OpenFileChannel()' call */
   338     struct OpenFileChannelProc *nextPtr;
   339 				    /* The next 'Tcl_OpenFileChannel()'
   340 				     * function to call */
   341 } OpenFileChannelProc;
   342 
   343 /*
   344  * For each type of (obsolete) hookable function, a static node is
   345  * declared to hold the function pointer for the "built-in" routine
   346  * (e.g. 'TclpStat(...)') and the respective list is initialized as a
   347  * pointer to that node.
   348  * 
   349  * The "delete" functions (e.g. 'TclStatDeleteProc(...)') ensure that
   350  * these statically declared list entry cannot be inadvertently removed.
   351  *
   352  * This method avoids the need to call any sort of "initialization"
   353  * function.
   354  *
   355  * All three lists are protected by a global obsoleteFsHookMutex.
   356  */
   357 
   358 static StatProc *statProcList = NULL;
   359 static AccessProc *accessProcList = NULL;
   360 static OpenFileChannelProc *openFileChannelProcList = NULL;
   361 
   362 TCL_DECLARE_MUTEX(obsoleteFsHookMutex)
   363 
   364 #endif /* USE_OBSOLETE_FS_HOOKS */
   365 
   366 /* 
   367  * Declare the native filesystem support.  These functions should
   368  * be considered private to Tcl, and should really not be called
   369  * directly by any code other than this file (i.e. neither by
   370  * Tcl's core nor by extensions).  Similarly, the old string-based
   371  * Tclp... native filesystem functions should not be called.
   372  * 
   373  * The correct API to use now is the Tcl_FS... set of functions,
   374  * which ensure correct and complete virtual filesystem support.
   375  * 
   376  * We cannot make all of these static, since some of them
   377  * are implemented in the platform-specific directories.
   378  */
   379 static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator;
   380 static Tcl_FSFreeInternalRepProc NativeFreeInternalRep;
   381 static Tcl_FSCreateInternalRepProc NativeCreateNativeRep;
   382 static Tcl_FSFileAttrStringsProc NativeFileAttrStrings;
   383 static Tcl_FSFileAttrsGetProc NativeFileAttrsGet;
   384 static Tcl_FSFileAttrsSetProc NativeFileAttrsSet;
   385 
   386 /* 
   387  * The only reason these functions are not static is that they
   388  * are either called by code in the native (win/unix/mac) directories
   389  * or they are actually implemented in those directories.  They
   390  * should simply not be called by code outside Tcl's native
   391  * filesystem core.  i.e. they should be considered 'static' to
   392  * Tcl's filesystem code (if we ever built the native filesystem
   393  * support into a separate code library, this could actually be
   394  * enforced).
   395  */
   396 Tcl_FSFilesystemPathTypeProc TclpFilesystemPathType;
   397 Tcl_FSInternalToNormalizedProc TclpNativeToNormalized;
   398 Tcl_FSStatProc TclpObjStat;
   399 Tcl_FSAccessProc TclpObjAccess;	    
   400 Tcl_FSMatchInDirectoryProc TclpMatchInDirectory;  
   401 Tcl_FSGetCwdProc TclpObjGetCwd;     
   402 Tcl_FSChdirProc TclpObjChdir;	    
   403 Tcl_FSLstatProc TclpObjLstat;	    
   404 Tcl_FSCopyFileProc TclpObjCopyFile; 
   405 Tcl_FSDeleteFileProc TclpObjDeleteFile;	    
   406 Tcl_FSRenameFileProc TclpObjRenameFile;	    
   407 Tcl_FSCreateDirectoryProc TclpObjCreateDirectory;	    
   408 Tcl_FSCopyDirectoryProc TclpObjCopyDirectory;	    
   409 Tcl_FSRemoveDirectoryProc TclpObjRemoveDirectory;	    
   410 Tcl_FSUnloadFileProc TclpUnloadFile;	    
   411 Tcl_FSLinkProc TclpObjLink; 
   412 Tcl_FSListVolumesProc TclpObjListVolumes;	    
   413 
   414 /* 
   415  * Define the native filesystem dispatch table.  If necessary, it
   416  * is ok to make this non-static, but it should only be accessed
   417  * by the functions actually listed within it (or perhaps other
   418  * helper functions of them).  Anything which is not part of this
   419  * 'native filesystem implementation' should not be delving inside
   420  * here!
   421  */
   422 Tcl_Filesystem tclNativeFilesystem = {
   423     "native",
   424     sizeof(Tcl_Filesystem),
   425     TCL_FILESYSTEM_VERSION_1,
   426     &NativePathInFilesystem,
   427     &TclNativeDupInternalRep,
   428     &NativeFreeInternalRep,
   429     &TclpNativeToNormalized,
   430     &NativeCreateNativeRep,
   431     &TclpObjNormalizePath,
   432     &TclpFilesystemPathType,
   433     &NativeFilesystemSeparator,
   434     &TclpObjStat,
   435     &TclpObjAccess,
   436     &TclpOpenFileChannel,
   437     &TclpMatchInDirectory,
   438     &TclpUtime,
   439 #ifndef S_IFLNK
   440     NULL,
   441 #else
   442     &TclpObjLink,
   443 #endif /* S_IFLNK */
   444     &TclpObjListVolumes,
   445     &NativeFileAttrStrings,
   446     &NativeFileAttrsGet,
   447     &NativeFileAttrsSet,
   448     &TclpObjCreateDirectory,
   449     &TclpObjRemoveDirectory, 
   450     &TclpObjDeleteFile,
   451     &TclpObjCopyFile,
   452     &TclpObjRenameFile,
   453     &TclpObjCopyDirectory, 
   454     &TclpObjLstat,
   455     &TclpDlopen,
   456     &TclpObjGetCwd,
   457     &TclpObjChdir
   458 };
   459 
   460 /* 
   461  * Define the tail of the linked list.  Note that for unconventional
   462  * uses of Tcl without a native filesystem, we may in the future wish
   463  * to modify the current approach of hard-coding the native filesystem
   464  * in the lookup list 'filesystemList' below.
   465  * 
   466  * We initialize the record so that it thinks one file uses it.  This
   467  * means it will never be freed.
   468  */
   469 static FilesystemRecord nativeFilesystemRecord = {
   470     NULL,
   471     &tclNativeFilesystem,
   472     1,
   473     NULL
   474 };
   475 
   476 /* 
   477  * This is incremented each time we modify the linked list of
   478  * filesystems.  Any time it changes, all cached filesystem
   479  * representations are suspect and must be freed.
   480  * For multithreading builds, change of the filesystem epoch
   481  * will trigger cache cleanup in all threads.  
   482  */
   483 int theFilesystemEpoch = 0;
   484 
   485 /*
   486  * Stores the linked list of filesystems. A 1:1 copy of this
   487  * list is also maintained in the TSD for each thread. This
   488  * is to avoid synchronization issues.
   489  */
   490 static FilesystemRecord *filesystemList = &nativeFilesystemRecord;
   491 
   492 TCL_DECLARE_MUTEX(filesystemMutex)
   493 
   494 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)
   495 /* 
   496  * Used to implement Tcl_FSGetCwd in a file-system independent way.
   497  */
   498 static Tcl_Obj* cwdPathPtr = NULL;
   499 static int cwdPathEpoch = 0;
   500 #endif
   501 TCL_DECLARE_MUTEX(cwdMutex)
   502 
   503 /*
   504  * This structure holds per-thread private copies of
   505  * some global data. This way we avoid most of the
   506  * synchronization calls which boosts performance, at
   507  * cost of having to update this information each
   508  * time the corresponding epoch counter changes.
   509  * 
   510  */
   511 typedef struct ThreadSpecificData {
   512     int initialized;
   513     int cwdPathEpoch;
   514     int filesystemEpoch; 
   515     Tcl_Obj *cwdPathPtr;
   516     FilesystemRecord *filesystemList;
   517 } ThreadSpecificData;
   518 
   519 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)
   520 static Tcl_ThreadDataKey dataKey;
   521 #endif
   522 
   523 /* 
   524  * Declare fallback support function and 
   525  * information for Tcl_FSLoadFile 
   526  */
   527 static Tcl_FSUnloadFileProc FSUnloadTempFile;
   528 
   529 /*
   530  * One of these structures is used each time we successfully load a
   531  * file from a file system by way of making a temporary copy of the
   532  * file on the native filesystem.  We need to store both the actual
   533  * unloadProc/clientData combination which was used, and the original
   534  * and modified filenames, so that we can correctly undo the entire
   535  * operation when we want to unload the code.
   536  */
   537 typedef struct FsDivertLoad {
   538     Tcl_LoadHandle loadHandle;
   539     Tcl_FSUnloadFileProc *unloadProcPtr;	
   540     Tcl_Obj *divertedFile;
   541     Tcl_Filesystem *divertedFilesystem;
   542     ClientData divertedFileNativeRep;
   543 } FsDivertLoad;
   544 
   545 /* Now move on to the basic filesystem implementation */
   546 
   547 static void
   548 FsThrExitProc(cd)
   549     ClientData cd;
   550 {
   551     ThreadSpecificData *tsdPtr = (ThreadSpecificData*)cd;
   552     FilesystemRecord *fsRecPtr = NULL, *tmpFsRecPtr = NULL;
   553 
   554     /* Trash the cwd copy */
   555     if (tsdPtr->cwdPathPtr != NULL) {
   556 	Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
   557 	tsdPtr->cwdPathPtr = NULL;
   558     }
   559     /* Trash the filesystems cache */
   560     fsRecPtr = tsdPtr->filesystemList;
   561     while (fsRecPtr != NULL) {
   562 	tmpFsRecPtr = fsRecPtr->nextPtr;
   563 	if (--fsRecPtr->fileRefCount <= 0) {
   564 	    ckfree((char *)fsRecPtr);
   565 	}
   566 	fsRecPtr = tmpFsRecPtr;
   567     }
   568     tsdPtr->initialized = 0;
   569 }
   570 
   571 int 
   572 TclFSCwdPointerEquals(objPtr)
   573     Tcl_Obj* objPtr;
   574 {
   575     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
   576 
   577     Tcl_MutexLock(&cwdMutex);
   578 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)    
   579     if (tsdPtr->cwdPathPtr == NULL) {
   580     if (cwdPathPtr == NULL) {
   581 	    tsdPtr->cwdPathPtr = NULL;
   582 	} else {
   583 		tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr);	
   584 	    Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
   585 	}
   586 	tsdPtr->cwdPathEpoch = cwdPathEpoch;
   587     } else if (tsdPtr->cwdPathEpoch != cwdPathEpoch) { 	
   588 	Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
   589 	if (cwdPathPtr == NULL) {
   590 	    tsdPtr->cwdPathPtr = NULL;
   591 	} else {
   592 	    tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr);
   593 	    Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
   594 	}
   595     }
   596 #else
   597     if (tsdPtr->cwdPathPtr == NULL) {
   598     if (glcwdPathPtr == NULL) {    
   599 	    tsdPtr->cwdPathPtr = NULL;
   600 	} else {
   601 		tsdPtr->cwdPathPtr = Tcl_DuplicateObj(glcwdPathPtr);	
   602 	    Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
   603 	}
   604 	tsdPtr->cwdPathEpoch = glcwdPathEpoch;	
   605     } else if (tsdPtr->cwdPathEpoch != glcwdPathEpoch) { 	
   606 	Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
   607 	if (glcwdPathPtr == NULL) {
   608 	    tsdPtr->cwdPathPtr = NULL;
   609 	} else {
   610 	    tsdPtr->cwdPathPtr = Tcl_DuplicateObj(glcwdPathPtr);
   611 	    Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
   612 	}
   613     }
   614 #endif
   615     Tcl_MutexUnlock(&cwdMutex);
   616 
   617     if (tsdPtr->initialized == 0) {
   618 	Tcl_CreateThreadExitHandler(FsThrExitProc, (ClientData)tsdPtr);
   619 	tsdPtr->initialized = 1;
   620     }
   621     return (tsdPtr->cwdPathPtr == objPtr); 
   622 }
   623 #ifdef TCL_THREADS
   624 
   625 static void
   626 FsRecacheFilesystemList(void)
   627 {
   628     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
   629     FilesystemRecord *fsRecPtr, *tmpFsRecPtr = NULL;
   630 
   631     /* Trash the current cache */
   632     fsRecPtr = tsdPtr->filesystemList;
   633     while (fsRecPtr != NULL) {
   634 	tmpFsRecPtr = fsRecPtr->nextPtr;
   635 	if (--fsRecPtr->fileRefCount <= 0) {
   636 	    ckfree((char *)fsRecPtr);
   637 	}
   638 	fsRecPtr = tmpFsRecPtr;
   639     }
   640     tsdPtr->filesystemList = NULL;
   641 
   642     /*
   643      * Code below operates on shared data. We
   644      * are already called under mutex lock so   
   645      * we can safely proceed.
   646      */
   647 
   648     /* Locate tail of the global filesystem list */
   649     fsRecPtr = filesystemList;
   650     while (fsRecPtr != NULL) {
   651 	tmpFsRecPtr = fsRecPtr;
   652 	fsRecPtr = fsRecPtr->nextPtr;
   653     }
   654     
   655     /* Refill the cache honouring the order */
   656     fsRecPtr = tmpFsRecPtr;
   657     while (fsRecPtr != NULL) {
   658 	tmpFsRecPtr = (FilesystemRecord *)ckalloc(sizeof(FilesystemRecord));
   659 	*tmpFsRecPtr = *fsRecPtr;
   660 	tmpFsRecPtr->nextPtr = tsdPtr->filesystemList;
   661 	tmpFsRecPtr->prevPtr = NULL;
   662 	if (tsdPtr->filesystemList) {
   663 	    tsdPtr->filesystemList->prevPtr = tmpFsRecPtr;
   664 	}
   665 	tsdPtr->filesystemList = tmpFsRecPtr;
   666         fsRecPtr = fsRecPtr->prevPtr;
   667     }
   668 
   669     /* Make sure the above gets released on thread exit */
   670     if (tsdPtr->initialized == 0) {
   671 	Tcl_CreateThreadExitHandler(FsThrExitProc, (ClientData)tsdPtr);
   672 	tsdPtr->initialized = 1;
   673     }
   674 }
   675 #endif
   676 
   677 static FilesystemRecord *
   678 FsGetFirstFilesystem(void) {
   679     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
   680     FilesystemRecord *fsRecPtr;
   681 #ifndef TCL_THREADS
   682     tsdPtr->filesystemEpoch = theFilesystemEpoch;
   683     fsRecPtr = filesystemList;
   684 #else
   685     Tcl_MutexLock(&filesystemMutex);
   686     if (tsdPtr->filesystemList == NULL
   687 	    || (tsdPtr->filesystemEpoch != theFilesystemEpoch)) {
   688  	FsRecacheFilesystemList();
   689 	tsdPtr->filesystemEpoch = theFilesystemEpoch;
   690     }
   691     Tcl_MutexUnlock(&filesystemMutex);
   692     fsRecPtr = tsdPtr->filesystemList;
   693 #endif
   694     return fsRecPtr;
   695 }
   696 
   697 static void
   698 FsUpdateCwd(cwdObj)
   699     Tcl_Obj *cwdObj;
   700 {
   701     int len;
   702     char *str = NULL;
   703     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
   704 
   705     if (cwdObj != NULL) {
   706 	str = Tcl_GetStringFromObj(cwdObj, &len);
   707     }
   708 
   709     Tcl_MutexLock(&cwdMutex);
   710 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)        
   711     if (cwdPathPtr != NULL) {
   712         Tcl_DecrRefCount(cwdPathPtr);
   713     }
   714     if (cwdObj == NULL) {
   715 	cwdPathPtr = NULL;
   716     } else {
   717 	/* This MUST be stored as string object! */
   718 	cwdPathPtr = Tcl_NewStringObj(str, len); 
   719     	Tcl_IncrRefCount(cwdPathPtr);
   720     }
   721     cwdPathEpoch++;
   722     tsdPtr->cwdPathEpoch = cwdPathEpoch;
   723 #else
   724     if (glcwdPathPtr != NULL) {
   725     Tcl_DecrRefCount(glcwdPathPtr);
   726     }
   727 	if (cwdObj == NULL) {
   728 	glcwdPathPtr = NULL;
   729 	} else {
   730 	/* This MUST be stored as string object! */
   731 	glcwdPathPtr = Tcl_NewStringObj(str, len); 
   732 		Tcl_IncrRefCount(glcwdPathPtr);
   733 	}
   734 	glcwdPathEpoch++;
   735 	tsdPtr->cwdPathEpoch = glcwdPathEpoch;
   736 #endif
   737     Tcl_MutexUnlock(&cwdMutex);
   738 
   739     if (tsdPtr->cwdPathPtr) {
   740         Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
   741     }
   742     if (cwdObj == NULL) {
   743 	tsdPtr->cwdPathPtr = NULL;
   744     } else {
   745 	tsdPtr->cwdPathPtr = Tcl_NewStringObj(str, len); 
   746 	Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
   747     }
   748 }
   749 
   750 /*
   751  *----------------------------------------------------------------------
   752  *
   753  * TclFinalizeFilesystem --
   754  *
   755  *	Clean up the filesystem.  After this, calls to all Tcl_FS...
   756  *	functions will fail.
   757  *	
   758  *	We will later call TclResetFilesystem to restore the FS
   759  *	to a pristine state.
   760  *	
   761  * Results:
   762  *	None.
   763  *
   764  * Side effects:
   765  *	Frees any memory allocated by the filesystem.
   766  *
   767  *----------------------------------------------------------------------
   768  */
   769 
   770 void
   771 TclFinalizeFilesystem()
   772 {
   773     FilesystemRecord *fsRecPtr;
   774 
   775     /* 
   776      * Assumption that only one thread is active now.  Otherwise
   777      * we would need to put various mutexes around this code.
   778      */
   779 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)    	
   780     if (cwdPathPtr != NULL) {
   781 	Tcl_DecrRefCount(cwdPathPtr);
   782 	cwdPathPtr = NULL;
   783     cwdPathEpoch = 0;
   784 #else
   785     if (glcwdPathPtr != NULL) {
   786 	Tcl_DecrRefCount(glcwdPathPtr);
   787 	glcwdPathPtr = NULL;
   788     glcwdPathEpoch = 0;
   789 #endif
   790     }
   791 
   792     /* 
   793      * Remove all filesystems, freeing any allocated memory
   794      * that is no longer needed
   795      */
   796 
   797     fsRecPtr = filesystemList;
   798     while (fsRecPtr != NULL) {
   799 	FilesystemRecord *tmpFsRecPtr = fsRecPtr->nextPtr;
   800 	if (fsRecPtr->fileRefCount <= 0) {
   801 	    /* The native filesystem is static, so we don't free it */
   802 	    if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
   803 		ckfree((char *)fsRecPtr);
   804 	    }
   805 	}
   806 	fsRecPtr = tmpFsRecPtr;
   807     }
   808     filesystemList = NULL;
   809 
   810     /*
   811      * Now filesystemList is NULL.  This means that any attempt
   812      * to use the filesystem is likely to fail.
   813      */
   814 
   815     statProcList = NULL;
   816     accessProcList = NULL;
   817     openFileChannelProcList = NULL;
   818 #ifdef __WIN32__
   819     TclWinEncodingsCleanup();
   820 #endif
   821 }
   822 
   823 /*
   824  *----------------------------------------------------------------------
   825  *
   826  * TclResetFilesystem --
   827  *
   828  *	Restore the filesystem to a pristine state.
   829  *	
   830  * Results:
   831  *	None.
   832  *
   833  * Side effects:
   834  *	None.
   835  *
   836  *----------------------------------------------------------------------
   837  */
   838 
   839 void
   840 TclResetFilesystem()
   841 {
   842     filesystemList = &nativeFilesystemRecord;
   843 
   844     /* 
   845      * Note, at this point, I believe nativeFilesystemRecord ->
   846      * fileRefCount should equal 1 and if not, we should try to track
   847      * down the cause.
   848      */
   849     
   850 #ifdef __WIN32__
   851     /* 
   852      * Cleans up the win32 API filesystem proc lookup table. This must
   853      * happen very late in finalization so that deleting of copied
   854      * dlls can occur.
   855      */
   856     TclWinResetInterfaces();
   857 #endif
   858 }
   859 
   860 /*
   861  *----------------------------------------------------------------------
   862  *
   863  * Tcl_FSRegister --
   864  *
   865  *    Insert the filesystem function table at the head of the list of
   866  *    functions which are used during calls to all file-system
   867  *    operations.  The filesystem will be added even if it is 
   868  *    already in the list.  (You can use Tcl_FSData to
   869  *    check if it is in the list, provided the ClientData used was
   870  *    not NULL).
   871  *    
   872  *    Note that the filesystem handling is head-to-tail of the list.
   873  *    Each filesystem is asked in turn whether it can handle a
   874  *    particular request, _until_ one of them says 'yes'. At that
   875  *    point no further filesystems are asked.
   876  *    
   877  *    In particular this means if you want to add a diagnostic
   878  *    filesystem (which simply reports all fs activity), it must be 
   879  *    at the head of the list: i.e. it must be the last registered.
   880  *
   881  * Results:
   882  *    Normally TCL_OK; TCL_ERROR if memory for a new node in the list
   883  *    could not be allocated.
   884  *
   885  * Side effects:
   886  *    Memory allocated and modifies the link list for filesystems.
   887  *
   888  *----------------------------------------------------------------------
   889  */
   890 
   891 EXPORT_C int
   892 Tcl_FSRegister(clientData, fsPtr)
   893     ClientData clientData;    /* Client specific data for this fs */
   894     Tcl_Filesystem  *fsPtr;   /* The filesystem record for the new fs. */
   895 {
   896     FilesystemRecord *newFilesystemPtr;
   897 
   898     if (fsPtr == NULL) {
   899 	return TCL_ERROR;
   900     }
   901 
   902     newFilesystemPtr = (FilesystemRecord *) ckalloc(sizeof(FilesystemRecord));
   903 
   904     newFilesystemPtr->clientData = clientData;
   905     newFilesystemPtr->fsPtr = fsPtr;
   906     /* 
   907      * We start with a refCount of 1.  If this drops to zero, then
   908      * anyone is welcome to ckfree us.
   909      */
   910     newFilesystemPtr->fileRefCount = 1;
   911 
   912     /* 
   913      * Is this lock and wait strictly speaking necessary?  Since any
   914      * iterators out there will have grabbed a copy of the head of
   915      * the list and be iterating away from that, if we add a new
   916      * element to the head of the list, it can't possibly have any
   917      * effect on any of their loops.  In fact it could be better not
   918      * to wait, since we are adjusting the filesystem epoch, any
   919      * cached representations calculated by existing iterators are
   920      * going to have to be thrown away anyway.
   921      * 
   922      * However, since registering and unregistering filesystems is
   923      * a very rare action, this is not a very important point.
   924      */
   925     Tcl_MutexLock(&filesystemMutex);
   926 
   927     newFilesystemPtr->nextPtr = filesystemList;
   928     newFilesystemPtr->prevPtr = NULL;
   929     if (filesystemList) {
   930 	filesystemList->prevPtr = newFilesystemPtr;
   931     }
   932     filesystemList = newFilesystemPtr;
   933 
   934     /* 
   935      * Increment the filesystem epoch counter, since existing paths
   936      * might conceivably now belong to different filesystems.
   937      */
   938     theFilesystemEpoch++;
   939     Tcl_MutexUnlock(&filesystemMutex);
   940 
   941     return TCL_OK;
   942 }
   943 
   944 /*
   945  *----------------------------------------------------------------------
   946  *
   947  * Tcl_FSUnregister --
   948  *
   949  *    Remove the passed filesystem from the list of filesystem
   950  *    function tables.  It also ensures that the built-in
   951  *    (native) filesystem is not removable, although we may wish
   952  *    to change that decision in the future to allow a smaller
   953  *    Tcl core, in which the native filesystem is not used at
   954  *    all (we could, say, initialise Tcl completely over a network
   955  *    connection).
   956  *
   957  * Results:
   958  *    TCL_OK if the procedure pointer was successfully removed,
   959  *    TCL_ERROR otherwise.
   960  *
   961  * Side effects:
   962  *    Memory may be deallocated (or will be later, once no "path" 
   963  *    objects refer to this filesystem), but the list of registered
   964  *    filesystems is updated immediately.
   965  *
   966  *----------------------------------------------------------------------
   967  */
   968 
   969 EXPORT_C int
   970 Tcl_FSUnregister(fsPtr)
   971     Tcl_Filesystem  *fsPtr;   /* The filesystem record to remove. */
   972 {
   973     int retVal = TCL_ERROR;
   974     FilesystemRecord *fsRecPtr;
   975 
   976     Tcl_MutexLock(&filesystemMutex);
   977 
   978     /*
   979      * Traverse the 'filesystemList' looking for the particular node
   980      * whose 'fsPtr' member matches 'fsPtr' and remove that one from
   981      * the list.  Ensure that the "default" node cannot be removed.
   982      */
   983 
   984     fsRecPtr = filesystemList;
   985     while ((retVal == TCL_ERROR) && (fsRecPtr->fsPtr != &tclNativeFilesystem)) {
   986 	if (fsRecPtr->fsPtr == fsPtr) {
   987 	    if (fsRecPtr->prevPtr) {
   988 		fsRecPtr->prevPtr->nextPtr = fsRecPtr->nextPtr;
   989 	    } else {
   990 		filesystemList = fsRecPtr->nextPtr;
   991 	    }
   992 	    if (fsRecPtr->nextPtr) {
   993 		fsRecPtr->nextPtr->prevPtr = fsRecPtr->prevPtr;
   994 	    }
   995 	    /* 
   996 	     * Increment the filesystem epoch counter, since existing
   997 	     * paths might conceivably now belong to different
   998 	     * filesystems.  This should also ensure that paths which
   999 	     * have cached the filesystem which is about to be deleted
  1000 	     * do not reference that filesystem (which would of course
  1001 	     * lead to memory exceptions).
  1002 	     */
  1003 	    theFilesystemEpoch++;
  1004 	    
  1005 	    fsRecPtr->fileRefCount--;
  1006 	    if (fsRecPtr->fileRefCount <= 0) {
  1007 	        ckfree((char *)fsRecPtr);
  1008 	    }
  1009 
  1010 	    retVal = TCL_OK;
  1011 	} else {
  1012 	    fsRecPtr = fsRecPtr->nextPtr;
  1013 	}
  1014     }
  1015 
  1016     Tcl_MutexUnlock(&filesystemMutex);
  1017     return (retVal);
  1018 }
  1019 
  1020 /*
  1021  *----------------------------------------------------------------------
  1022  *
  1023  * Tcl_FSMatchInDirectory --
  1024  *
  1025  *	This routine is used by the globbing code to search a directory
  1026  *	for all files which match a given pattern.  The appropriate
  1027  *	function for the filesystem to which pathPtr belongs will be
  1028  *	called.  If pathPtr does not belong to any filesystem and if it
  1029  *	is NULL or the empty string, then we assume the pattern is to be
  1030  *	matched in the current working directory.  To avoid each
  1031  *	filesystem's Tcl_FSMatchInDirectoryProc having to deal with this
  1032  *	issue, we create a pathPtr on the fly (equal to the cwd), and
  1033  *	then remove it from the results returned.  This makes filesystems
  1034  *	easy to write, since they can assume the pathPtr passed to them
  1035  *	is an ordinary path.  In fact this means we could remove such
  1036  *	special case handling from Tcl's native filesystems.
  1037  *	
  1038  *	If 'pattern' is NULL, then pathPtr is assumed to be a fully
  1039  *	specified path of a single file/directory which must be
  1040  *	checked for existence and correct type.
  1041  *
  1042  * Results: 
  1043  *	
  1044  *	The return value is a standard Tcl result indicating whether an
  1045  *	error occurred in globbing.  Error messages are placed in
  1046  *	interp, but good results are placed in the resultPtr given.
  1047  *	
  1048  *	Recursive searches, e.g.
  1049  *	
  1050  *	   glob -dir $dir -join * pkgIndex.tcl
  1051  *	   
  1052  *	which must recurse through each directory matching '*' are
  1053  *	handled internally by Tcl, by passing specific flags in a 
  1054  *	modified 'types' parameter.  This means the actual filesystem
  1055  *	only ever sees patterns which match in a single directory.
  1056  *
  1057  * Side effects:
  1058  *	The interpreter may have an error message inserted into it.
  1059  *
  1060  *---------------------------------------------------------------------- 
  1061  */
  1062 
  1063 EXPORT_C int
  1064 Tcl_FSMatchInDirectory(interp, result, pathPtr, pattern, types)
  1065     Tcl_Interp *interp;		/* Interpreter to receive error messages. */
  1066     Tcl_Obj *result;		/* List object to receive results. */
  1067     Tcl_Obj *pathPtr;	        /* Contains path to directory to search. */
  1068     CONST char *pattern;	/* Pattern to match against. */
  1069     Tcl_GlobTypeData *types;	/* Object containing list of acceptable types.
  1070 				 * May be NULL. In particular the directory
  1071 				 * flag is very important. */
  1072 {
  1073     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  1074     if (fsPtr != NULL) {
  1075 	Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc;
  1076 	if (proc != NULL) {
  1077 	    int ret = (*proc)(interp, result, pathPtr, pattern, types);
  1078 	    if (ret == TCL_OK && pattern != NULL) {
  1079 		result = FsAddMountsToGlobResult(result, pathPtr, 
  1080 						 pattern, types);
  1081 	    }
  1082 	    return ret;
  1083 	}
  1084     } else {
  1085 	Tcl_Obj* cwd;
  1086 	int ret = -1;
  1087 	if (pathPtr != NULL) {
  1088 	    int len;
  1089 	    Tcl_GetStringFromObj(pathPtr,&len);
  1090 	    if (len != 0) {
  1091 		/* 
  1092 		 * We have no idea how to match files in a directory
  1093 		 * which belongs to no known filesystem
  1094 		 */
  1095 		Tcl_SetErrno(ENOENT);
  1096 		return -1;
  1097 	    }
  1098 	}
  1099 	/* 
  1100 	 * We have an empty or NULL path.  This is defined to mean we
  1101 	 * must search for files within the current 'cwd'.  We
  1102 	 * therefore use that, but then since the proc we call will
  1103 	 * return results which include the cwd we must then trim it
  1104 	 * off the front of each path in the result.  We choose to deal
  1105 	 * with this here (in the generic code), since if we don't,
  1106 	 * every single filesystem's implementation of
  1107 	 * Tcl_FSMatchInDirectory will have to deal with it for us.
  1108 	 */
  1109 	cwd = Tcl_FSGetCwd(NULL);
  1110 	if (cwd == NULL) {
  1111 	    if (interp != NULL) {
  1112 		Tcl_SetResult(interp, "glob couldn't determine "
  1113 			  "the current working directory", TCL_STATIC);
  1114 	    }
  1115 	    return TCL_ERROR;
  1116 	}
  1117 	fsPtr = Tcl_FSGetFileSystemForPath(cwd);
  1118 	if (fsPtr != NULL) {
  1119 	    Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc;
  1120 	    if (proc != NULL) {
  1121 		Tcl_Obj* tmpResultPtr = Tcl_NewListObj(0, NULL);
  1122 		Tcl_IncrRefCount(tmpResultPtr);
  1123 		ret = (*proc)(interp, tmpResultPtr, cwd, pattern, types);
  1124 		if (ret == TCL_OK) {
  1125 		    int resLength;
  1126 
  1127 		    tmpResultPtr = FsAddMountsToGlobResult(tmpResultPtr, cwd,
  1128 							   pattern, types);
  1129 
  1130 		    ret = Tcl_ListObjLength(interp, tmpResultPtr, &resLength);
  1131 		    if (ret == TCL_OK) {
  1132 			int i;
  1133 
  1134 			for (i = 0; i < resLength; i++) {
  1135 			    Tcl_Obj *elt;
  1136 			    
  1137 			    Tcl_ListObjIndex(interp, tmpResultPtr, i, &elt);
  1138 			    Tcl_ListObjAppendElement(interp, result, 
  1139 				TclFSMakePathRelative(interp, elt, cwd));
  1140 			}
  1141 		    }
  1142 		}
  1143 		Tcl_DecrRefCount(tmpResultPtr);
  1144 	    }
  1145 	}
  1146 	Tcl_DecrRefCount(cwd);
  1147 	return ret;
  1148     }
  1149     Tcl_SetErrno(ENOENT);
  1150     return -1;
  1151 }
  1152 
  1153 /*
  1154  *----------------------------------------------------------------------
  1155  *
  1156  * FsAddMountsToGlobResult --
  1157  *
  1158  *	This routine is used by the globbing code to take the results
  1159  *	of a directory listing and add any mounted paths to that
  1160  *	listing.  This is required so that simple things like 
  1161  *	'glob *' merge mounts and listings correctly.
  1162  *	
  1163  * Results: 
  1164  *	
  1165  *	The passed in 'result' may be modified (in place, if
  1166  *	necessary), and the correct list is returned.
  1167  *
  1168  * Side effects:
  1169  *	None.
  1170  *
  1171  *---------------------------------------------------------------------- 
  1172  */
  1173 static Tcl_Obj*
  1174 FsAddMountsToGlobResult(result, pathPtr, pattern, types)
  1175     Tcl_Obj *result;    /* The current list of matching paths */
  1176     Tcl_Obj *pathPtr;   /* The directory in question */
  1177     CONST char *pattern;
  1178     Tcl_GlobTypeData *types;
  1179 {
  1180     int mLength, gLength, i;
  1181     int dir = (types == NULL || (types->type & TCL_GLOB_TYPE_DIR));
  1182     Tcl_Obj *mounts = FsListMounts(pathPtr, pattern);
  1183 
  1184     if (mounts == NULL) return result; 
  1185 
  1186     if (Tcl_ListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) {
  1187 	goto endOfMounts;
  1188     }
  1189     if (Tcl_ListObjLength(NULL, result, &gLength) != TCL_OK) {
  1190 	goto endOfMounts;
  1191     }
  1192     for (i = 0; i < mLength; i++) {
  1193 	Tcl_Obj *mElt;
  1194 	int j;
  1195 	int found = 0;
  1196 	
  1197 	Tcl_ListObjIndex(NULL, mounts, i, &mElt);
  1198 
  1199 	for (j = 0; j < gLength; j++) {
  1200 	    Tcl_Obj *gElt;
  1201 	    Tcl_ListObjIndex(NULL, result, j, &gElt);
  1202 	    if (Tcl_FSEqualPaths(mElt, gElt)) {
  1203 		found = 1;
  1204 		if (!dir) {
  1205 		    /* We don't want to list this */
  1206 		    if (Tcl_IsShared(result)) {
  1207 			Tcl_Obj *newList;
  1208 			newList = Tcl_DuplicateObj(result);
  1209 			Tcl_DecrRefCount(result);
  1210 			result = newList;
  1211 		    }
  1212 		    Tcl_ListObjReplace(NULL, result, j, 1, 0, NULL);
  1213 		    gLength--;
  1214 		}
  1215 		/* Break out of for loop */
  1216 		break;
  1217 	    }
  1218 	}
  1219 	if (!found && dir) {
  1220 	    if (Tcl_IsShared(result)) {
  1221 		Tcl_Obj *newList;
  1222 		newList = Tcl_DuplicateObj(result);
  1223 		Tcl_DecrRefCount(result);
  1224 		result = newList;
  1225 	    }
  1226 	    Tcl_ListObjAppendElement(NULL, result, mElt);
  1227 	    /* 
  1228 	     * No need to increment gLength, since we
  1229 	     * don't want to compare mounts against
  1230 	     * mounts.
  1231 	     */
  1232 	}
  1233     }
  1234   endOfMounts:
  1235     Tcl_DecrRefCount(mounts);
  1236     return result;
  1237 }
  1238 
  1239 /*
  1240  *----------------------------------------------------------------------
  1241  *
  1242  * Tcl_FSMountsChanged --
  1243  *
  1244  *    Notify the filesystem that the available mounted filesystems
  1245  *    (or within any one filesystem type, the number or location of
  1246  *    mount points) have changed.
  1247  *
  1248  * Results:
  1249  *    None.
  1250  *
  1251  * Side effects:
  1252  *    The global filesystem variable 'theFilesystemEpoch' is
  1253  *    incremented.  The effect of this is to make all cached
  1254  *    path representations invalid.  Clearly it should only therefore
  1255  *    be called when it is really required!  There are a few 
  1256  *    circumstances when it should be called:
  1257  *    
  1258  *    (1) when a new filesystem is registered or unregistered.  
  1259  *    Strictly speaking this is only necessary if the new filesystem
  1260  *    accepts file paths as is (normally the filesystem itself is
  1261  *    really a shell which hasn't yet had any mount points established
  1262  *    and so its 'pathInFilesystem' proc will always fail).  However,
  1263  *    for safety, Tcl always calls this for you in these circumstances.
  1264  * 
  1265  *    (2) when additional mount points are established inside any
  1266  *    existing filesystem (except the native fs)
  1267  *    
  1268  *    (3) when any filesystem (except the native fs) changes the list
  1269  *    of available volumes.
  1270  *    
  1271  *    (4) when the mapping from a string representation of a file to
  1272  *    a full, normalized path changes.  For example, if 'env(HOME)' 
  1273  *    is modified, then any path containing '~' will map to a different
  1274  *    filesystem location.  Therefore all such paths need to have
  1275  *    their internal representation invalidated.
  1276  *    
  1277  *    Tcl has no control over (2) and (3), so any registered filesystem
  1278  *    must make sure it calls this function when those situations
  1279  *    occur.
  1280  *    
  1281  *    (Note: the reason for the exception in 2,3 for the native
  1282  *    filesystem is that the native filesystem by default claims all
  1283  *    unknown files even if it really doesn't understand them or if
  1284  *    they don't exist).
  1285  *
  1286  *----------------------------------------------------------------------
  1287  */
  1288 
  1289 EXPORT_C void
  1290 Tcl_FSMountsChanged(fsPtr)
  1291     Tcl_Filesystem *fsPtr;
  1292 {
  1293     /* 
  1294      * We currently don't do anything with this parameter.  We
  1295      * could in the future only invalidate files for this filesystem
  1296      * or otherwise take more advanced action.
  1297      */
  1298     (void)fsPtr;
  1299     /* 
  1300      * Increment the filesystem epoch counter, since existing paths
  1301      * might now belong to different filesystems.
  1302      */
  1303     Tcl_MutexLock(&filesystemMutex);
  1304     theFilesystemEpoch++;
  1305     Tcl_MutexUnlock(&filesystemMutex);
  1306 }
  1307 
  1308 /*
  1309  *----------------------------------------------------------------------
  1310  *
  1311  * Tcl_FSData --
  1312  *
  1313  *    Retrieve the clientData field for the filesystem given,
  1314  *    or NULL if that filesystem is not registered.
  1315  *
  1316  * Results:
  1317  *    A clientData value, or NULL.  Note that if the filesystem
  1318  *    was registered with a NULL clientData field, this function
  1319  *    will return that NULL value.
  1320  *
  1321  * Side effects:
  1322  *    None.
  1323  *
  1324  *----------------------------------------------------------------------
  1325  */
  1326 
  1327 EXPORT_C ClientData
  1328 Tcl_FSData(fsPtr)
  1329     Tcl_Filesystem  *fsPtr;   /* The filesystem record to query. */
  1330 {
  1331     ClientData retVal = NULL;
  1332     FilesystemRecord *fsRecPtr = FsGetFirstFilesystem();
  1333 
  1334     /*
  1335      * Traverse the 'filesystemList' looking for the particular node
  1336      * whose 'fsPtr' member matches 'fsPtr' and remove that one from
  1337      * the list.  Ensure that the "default" node cannot be removed.
  1338      */
  1339 
  1340     while ((retVal == NULL) && (fsRecPtr != NULL)) {
  1341 	if (fsRecPtr->fsPtr == fsPtr) {
  1342 	    retVal = fsRecPtr->clientData;
  1343 	}
  1344 	fsRecPtr = fsRecPtr->nextPtr;
  1345     }
  1346 
  1347     return retVal;
  1348 }
  1349 
  1350 /*
  1351  *---------------------------------------------------------------------------
  1352  *
  1353  * TclFSNormalizeAbsolutePath --
  1354  *
  1355  * Description:
  1356  *	Takes an absolute path specification and computes a 'normalized'
  1357  *	path from it.
  1358  *	
  1359  *	A normalized path is one which has all '../', './' removed.
  1360  *	Also it is one which is in the 'standard' format for the native
  1361  *	platform.  On MacOS, Unix, this means the path must be free of
  1362  *	symbolic links/aliases, and on Windows it means we want the
  1363  *	long form, with that long form's case-dependence (which gives
  1364  *	us a unique, case-dependent path).
  1365  *	
  1366  *	The behaviour of this function if passed a non-absolute path
  1367  *	is NOT defined.
  1368  *
  1369  * Results:
  1370  *	The result is returned in a Tcl_Obj with a refCount of 1,
  1371  *	which is therefore owned by the caller.  It must be
  1372  *	freed (with Tcl_DecrRefCount) by the caller when no longer needed.
  1373  *
  1374  * Side effects:
  1375  *	None (beyond the memory allocation for the result).
  1376  *
  1377  * Special note:
  1378  *	This code is based on code from Matt Newman and Jean-Claude
  1379  *	Wippler, with additions from Vince Darley and is copyright 
  1380  *	those respective authors.
  1381  *
  1382  *---------------------------------------------------------------------------
  1383  */
  1384 static Tcl_Obj *
  1385 TclFSNormalizeAbsolutePath(interp, pathPtr, clientDataPtr)
  1386     Tcl_Interp* interp;    /* Interpreter to use */
  1387     Tcl_Obj *pathPtr;      /* Absolute path to normalize */
  1388     ClientData *clientDataPtr;
  1389 {
  1390     int splen = 0, nplen, eltLen, i;
  1391     char *eltName;
  1392     Tcl_Obj *retVal;
  1393     Tcl_Obj *split;
  1394     Tcl_Obj *elt;
  1395     
  1396     /* Split has refCount zero */
  1397     split = Tcl_FSSplitPath(pathPtr, &splen);
  1398 
  1399     /* 
  1400      * Modify the list of entries in place, by removing '.', and
  1401      * removing '..' and the entry before -- unless that entry before
  1402      * is the top-level entry, i.e. the name of a volume.
  1403      */
  1404     nplen = 0;
  1405     for (i = 0; i < splen; i++) {
  1406 	Tcl_ListObjIndex(NULL, split, nplen, &elt);
  1407 	eltName = Tcl_GetStringFromObj(elt, &eltLen);
  1408 
  1409 	if ((eltLen == 1) && (eltName[0] == '.')) {
  1410 	    Tcl_ListObjReplace(NULL, split, nplen, 1, 0, NULL);
  1411 	} else if ((eltLen == 2)
  1412 		&& (eltName[0] == '.') && (eltName[1] == '.')) {
  1413 	    if (nplen > 1) {
  1414 	        nplen--;
  1415 		Tcl_ListObjReplace(NULL, split, nplen, 2, 0, NULL);
  1416 	    } else {
  1417 		Tcl_ListObjReplace(NULL, split, nplen, 1, 0, NULL);
  1418 	    }
  1419 	} else {
  1420 	    nplen++;
  1421 	}
  1422     }
  1423     if (nplen > 0) {
  1424 	ClientData clientData = NULL;
  1425 	
  1426 	retVal = Tcl_FSJoinPath(split, nplen);
  1427 	/* 
  1428 	 * Now we have an absolute path, with no '..', '.' sequences,
  1429 	 * but it still may not be in 'unique' form, depending on the
  1430 	 * platform.  For instance, Unix is case-sensitive, so the
  1431 	 * path is ok.  Windows is case-insensitive, and also has the
  1432 	 * weird 'longname/shortname' thing (e.g. C:/Program Files/ and
  1433 	 * C:/Progra~1/ are equivalent).  MacOS is case-insensitive.
  1434 	 * 
  1435 	 * Virtual file systems which may be registered may have
  1436 	 * other criteria for normalizing a path.
  1437 	 */
  1438 	Tcl_IncrRefCount(retVal);
  1439 	TclFSNormalizeToUniquePath(interp, retVal, 0, &clientData);
  1440 	/* 
  1441 	 * Since we know it is a normalized path, we can
  1442 	 * actually convert this object into an "path" object for
  1443 	 * greater efficiency 
  1444 	 */
  1445 	TclFSMakePathFromNormalized(interp, retVal, clientData);
  1446 	if (clientDataPtr != NULL) {
  1447 	    *clientDataPtr = clientData;
  1448 	}
  1449     } else {
  1450 	/* Init to an empty string */
  1451 	retVal = Tcl_NewStringObj("",0);
  1452 	Tcl_IncrRefCount(retVal);
  1453     }
  1454     /* 
  1455      * We increment and then decrement the refCount of split to free
  1456      * it.  We do this right at the end, in case there are
  1457      * optimisations in Tcl_FSJoinPath(split, nplen) above which would
  1458      * let it make use of split more effectively if it has a refCount
  1459      * of zero.  Also we can't just decrement the ref count, in case
  1460      * 'split' was actually returned by the join call above, in a
  1461      * single-element optimisation when nplen == 1.
  1462      */
  1463     Tcl_IncrRefCount(split);
  1464     Tcl_DecrRefCount(split);
  1465 
  1466     /* This has a refCount of 1 for the caller */
  1467     return retVal;
  1468 }
  1469 
  1470 /*
  1471  *---------------------------------------------------------------------------
  1472  *
  1473  * TclFSNormalizeToUniquePath --
  1474  *
  1475  * Description:
  1476  *	Takes a path specification containing no ../, ./ sequences,
  1477  *	and converts it into a unique path for the given platform.
  1478  *      On MacOS, Unix, this means the path must be free of
  1479  *	symbolic links/aliases, and on Windows it means we want the
  1480  *	long form, with that long form's case-dependence (which gives
  1481  *	us a unique, case-dependent path).
  1482  *
  1483  * Results:
  1484  *	The pathPtr is modified in place.  The return value is
  1485  *	the last byte offset which was recognised in the path
  1486  *	string.
  1487  *
  1488  * Side effects:
  1489  *	None (beyond the memory allocation for the result).
  1490  *
  1491  * Special notes:
  1492  *	If the filesystem-specific normalizePathProcs can re-introduce
  1493  *	../, ./ sequences into the path, then this function will
  1494  *	not return the correct result.  This may be possible with
  1495  *	symbolic links on unix/macos.
  1496  *
  1497  *      Important assumption: if startAt is non-zero, it must point
  1498  *      to a directory separator that we know exists and is already
  1499  *      normalized (so it is important not to point to the char just
  1500  *      after the separator).
  1501  *---------------------------------------------------------------------------
  1502  */
  1503 int
  1504 TclFSNormalizeToUniquePath(interp, pathPtr, startAt, clientDataPtr)
  1505     Tcl_Interp *interp;
  1506     Tcl_Obj *pathPtr;
  1507     int startAt;
  1508     ClientData *clientDataPtr;
  1509 {
  1510     FilesystemRecord *fsRecPtr, *firstFsRecPtr;
  1511     /* Ignore this variable */
  1512     (void)clientDataPtr;
  1513     
  1514     /*
  1515      * Call each of the "normalise path" functions in succession. This is
  1516      * a special case, in which if we have a native filesystem handler,
  1517      * we call it first.  This is because the root of Tcl's filesystem
  1518      * is always a native filesystem (i.e. '/' on unix is native).
  1519      */
  1520 
  1521     firstFsRecPtr = FsGetFirstFilesystem();
  1522 
  1523     fsRecPtr = firstFsRecPtr;
  1524     while (fsRecPtr != NULL) {
  1525         if (fsRecPtr->fsPtr == &tclNativeFilesystem) {
  1526 	    Tcl_FSNormalizePathProc *proc = fsRecPtr->fsPtr->normalizePathProc;
  1527 	    if (proc != NULL) {
  1528 		startAt = (*proc)(interp, pathPtr, startAt);
  1529 	    }
  1530 	    break;
  1531         }
  1532 	fsRecPtr = fsRecPtr->nextPtr;
  1533     }
  1534     
  1535     fsRecPtr = firstFsRecPtr; 
  1536     while (fsRecPtr != NULL) {
  1537 	/* Skip the native system next time through */
  1538 	if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
  1539 	    Tcl_FSNormalizePathProc *proc = fsRecPtr->fsPtr->normalizePathProc;
  1540 	    if (proc != NULL) {
  1541 		startAt = (*proc)(interp, pathPtr, startAt);
  1542 	    }
  1543 	    /* 
  1544 	     * We could add an efficiency check like this:
  1545 	     * 
  1546 	     *   if (retVal == length-of(pathPtr)) {break;}
  1547 	     * 
  1548 	     * but there's not much benefit.
  1549 	     */
  1550 	}
  1551 	fsRecPtr = fsRecPtr->nextPtr;
  1552     }
  1553 
  1554     return startAt;
  1555 }
  1556 
  1557 /*
  1558  *---------------------------------------------------------------------------
  1559  *
  1560  * TclGetOpenMode --
  1561  *
  1562  * Description:
  1563  *	Computes a POSIX mode mask for opening a file, from a given string,
  1564  *	and also sets a flag to indicate whether the caller should seek to
  1565  *	EOF after opening the file.
  1566  *
  1567  * Results:
  1568  *	On success, returns mode to pass to "open". If an error occurs, the
  1569  *	return value is -1 and if interp is not NULL, sets interp's result
  1570  *	object to an error message.
  1571  *
  1572  * Side effects:
  1573  *	Sets the integer referenced by seekFlagPtr to 1 to tell the caller
  1574  *	to seek to EOF after opening the file.
  1575  *
  1576  * Special note:
  1577  *	This code is based on a prototype implementation contributed
  1578  *	by Mark Diekhans.
  1579  *
  1580  *---------------------------------------------------------------------------
  1581  */
  1582 
  1583 int
  1584 TclGetOpenMode(interp, string, seekFlagPtr)
  1585     Tcl_Interp *interp;			/* Interpreter to use for error
  1586 					 * reporting - may be NULL. */
  1587     CONST char *string;			/* Mode string, e.g. "r+" or
  1588 					 * "RDONLY CREAT". */
  1589     int *seekFlagPtr;			/* Set this to 1 if the caller
  1590                                          * should seek to EOF during the
  1591                                          * opening of the file. */
  1592 {
  1593     int mode, modeArgc, c, i, gotRW;
  1594     CONST char **modeArgv, *flag;
  1595 #define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR)
  1596 
  1597     /*
  1598      * Check for the simpler fopen-like access modes (e.g. "r").  They
  1599      * are distinguished from the POSIX access modes by the presence
  1600      * of a lower-case first letter.
  1601      */
  1602 
  1603     *seekFlagPtr = 0;
  1604     mode = 0;
  1605 
  1606     /*
  1607      * Guard against international characters before using byte oriented
  1608      * routines.
  1609      */
  1610 
  1611     if (!(string[0] & 0x80)
  1612 	    && islower(UCHAR(string[0]))) { /* INTL: ISO only. */
  1613 	switch (string[0]) {
  1614 	    case 'r':
  1615 		mode = O_RDONLY;
  1616 		break;
  1617 	    case 'w':
  1618 		mode = O_WRONLY|O_CREAT|O_TRUNC;
  1619 		break;
  1620 	    case 'a':
  1621 	        /* [Bug 680143].
  1622 		 * Added O_APPEND for proper automatic
  1623 		 * seek-to-end-on-write by the OS.
  1624 		 */
  1625 	        mode = O_WRONLY|O_CREAT|O_APPEND;
  1626                 *seekFlagPtr = 1;
  1627 		break;
  1628 	    default:
  1629 		error:
  1630                 if (interp != (Tcl_Interp *) NULL) {
  1631                     Tcl_AppendResult(interp,
  1632                             "illegal access mode \"", string, "\"",
  1633                             (char *) NULL);
  1634                 }
  1635 		return -1;
  1636 	}
  1637 	if (string[1] == '+') {
  1638 	    mode &= ~(O_RDONLY|O_WRONLY);
  1639 	    mode |= O_RDWR;
  1640 	    if (string[2] != 0) {
  1641 		goto error;
  1642 	    }
  1643 	} else if (string[1] != 0) {
  1644 	    goto error;
  1645 	}
  1646         return mode;
  1647     }
  1648 
  1649     /*
  1650      * The access modes are specified using a list of POSIX modes
  1651      * such as O_CREAT.
  1652      *
  1653      * IMPORTANT NOTE: We rely on Tcl_SplitList working correctly when
  1654      * a NULL interpreter is passed in.
  1655      */
  1656 
  1657     if (Tcl_SplitList(interp, string, &modeArgc, &modeArgv) != TCL_OK) {
  1658         if (interp != (Tcl_Interp *) NULL) {
  1659             Tcl_AddErrorInfo(interp,
  1660                     "\n    while processing open access modes \"");
  1661             Tcl_AddErrorInfo(interp, string);
  1662             Tcl_AddErrorInfo(interp, "\"");
  1663         }
  1664         return -1;
  1665     }
  1666     
  1667     gotRW = 0;
  1668     for (i = 0; i < modeArgc; i++) {
  1669 	flag = modeArgv[i];
  1670 	c = flag[0];
  1671 	if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {
  1672 	    mode = (mode & ~RW_MODES) | O_RDONLY;
  1673 	    gotRW = 1;
  1674 	} else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {
  1675 	    mode = (mode & ~RW_MODES) | O_WRONLY;
  1676 	    gotRW = 1;
  1677 	} else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {
  1678 	    mode = (mode & ~RW_MODES) | O_RDWR;
  1679 	    gotRW = 1;
  1680 	} else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {
  1681 	    mode |= O_APPEND;
  1682             *seekFlagPtr = 1;
  1683 	} else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {
  1684 	    mode |= O_CREAT;
  1685 	} else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {
  1686 	    mode |= O_EXCL;
  1687 	} else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {
  1688 #ifdef O_NOCTTY
  1689 	    mode |= O_NOCTTY;
  1690 #else
  1691 	    if (interp != (Tcl_Interp *) NULL) {
  1692                 Tcl_AppendResult(interp, "access mode \"", flag,
  1693                         "\" not supported by this system", (char *) NULL);
  1694             }
  1695             ckfree((char *) modeArgv);
  1696 	    return -1;
  1697 #endif
  1698 	} else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {
  1699 #if defined(O_NDELAY) || defined(O_NONBLOCK)
  1700 #   ifdef O_NONBLOCK
  1701 	    mode |= O_NONBLOCK;
  1702 #   else
  1703 	    mode |= O_NDELAY;
  1704 #   endif
  1705 #else
  1706             if (interp != (Tcl_Interp *) NULL) {
  1707                 Tcl_AppendResult(interp, "access mode \"", flag,
  1708                         "\" not supported by this system", (char *) NULL);
  1709             }
  1710             ckfree((char *) modeArgv);
  1711 	    return -1;
  1712 #endif
  1713 	} else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {
  1714 	    mode |= O_TRUNC;
  1715 	} else {
  1716             if (interp != (Tcl_Interp *) NULL) {
  1717                 Tcl_AppendResult(interp, "invalid access mode \"", flag,
  1718                         "\": must be RDONLY, WRONLY, RDWR, APPEND, CREAT",
  1719                         " EXCL, NOCTTY, NONBLOCK, or TRUNC", (char *) NULL);
  1720             }
  1721 	    ckfree((char *) modeArgv);
  1722 	    return -1;
  1723 	}
  1724     }
  1725     ckfree((char *) modeArgv);
  1726     if (!gotRW) {
  1727         if (interp != (Tcl_Interp *) NULL) {
  1728             Tcl_AppendResult(interp, "access mode must include either",
  1729                     " RDONLY, WRONLY, or RDWR", (char *) NULL);
  1730         }
  1731 	return -1;
  1732     }
  1733     return mode;
  1734 }
  1735 
  1736 /*
  1737  *----------------------------------------------------------------------
  1738  *
  1739  * Tcl_FSEvalFile --
  1740  *
  1741  *	Read in a file and process the entire file as one gigantic
  1742  *	Tcl command.
  1743  *
  1744  * Results:
  1745  *	A standard Tcl result, which is either the result of executing
  1746  *	the file or an error indicating why the file couldn't be read.
  1747  *
  1748  * Side effects:
  1749  *	Depends on the commands in the file.  During the evaluation
  1750  *	of the contents of the file, iPtr->scriptFile is made to
  1751  *	point to pathPtr (the old value is cached and replaced when
  1752  *	this function returns).
  1753  *
  1754  *----------------------------------------------------------------------
  1755  */
  1756 
  1757 EXPORT_C int
  1758 Tcl_FSEvalFile(interp, pathPtr)
  1759     Tcl_Interp *interp;		/* Interpreter in which to process file. */
  1760     Tcl_Obj *pathPtr;		/* Path of file to process.  Tilde-substitution
  1761 				 * will be performed on this name. */
  1762 {
  1763     int result, length;
  1764     Tcl_StatBuf statBuf;
  1765     Tcl_Obj *oldScriptFile;
  1766     Interp *iPtr;
  1767     char *string;
  1768     Tcl_Channel chan;
  1769     Tcl_Obj *objPtr;
  1770 
  1771     if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
  1772 	return TCL_ERROR;
  1773     }
  1774 
  1775     result = TCL_ERROR;
  1776     objPtr = Tcl_NewObj();
  1777     Tcl_IncrRefCount(objPtr);
  1778 
  1779     if (Tcl_FSStat(pathPtr, &statBuf) == -1) {
  1780         Tcl_SetErrno(errno);
  1781 	Tcl_AppendResult(interp, "couldn't read file \"", 
  1782 		Tcl_GetString(pathPtr),
  1783 		"\": ", Tcl_PosixError(interp), (char *) NULL);
  1784 	goto end;
  1785     }
  1786     chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644);
  1787     if (chan == (Tcl_Channel) NULL) {
  1788         Tcl_ResetResult(interp);
  1789 	Tcl_AppendResult(interp, "couldn't read file \"", 
  1790 		Tcl_GetString(pathPtr),
  1791 		"\": ", Tcl_PosixError(interp), (char *) NULL);
  1792 	goto end;
  1793     }
  1794     /*
  1795      * The eofchar is \32 (^Z).  This is the usual on Windows, but we
  1796      * effect this cross-platform to allow for scripted documents.
  1797      * [Bug: 2040]
  1798      */
  1799     Tcl_SetChannelOption(interp, chan, "-eofchar", "\32");
  1800     if (Tcl_ReadChars(chan, objPtr, -1, 0) < 0) {
  1801         Tcl_Close(interp, chan);
  1802 	Tcl_AppendResult(interp, "couldn't read file \"", 
  1803 		Tcl_GetString(pathPtr),
  1804 		"\": ", Tcl_PosixError(interp), (char *) NULL);
  1805 	goto end;
  1806     }
  1807     if (Tcl_Close(interp, chan) != TCL_OK) {
  1808         goto end;
  1809     }
  1810 
  1811     iPtr = (Interp *) interp;
  1812     oldScriptFile = iPtr->scriptFile;
  1813     iPtr->scriptFile = pathPtr;
  1814     Tcl_IncrRefCount(iPtr->scriptFile);
  1815     string = Tcl_GetStringFromObj(objPtr, &length);
  1816 
  1817 #ifdef TCL_TIP280
  1818     /* TIP #280 Force the evaluator to open a frame for a sourced
  1819      * file. */
  1820     iPtr->evalFlags |= TCL_EVAL_FILE;
  1821 #endif
  1822     result = Tcl_EvalEx(interp, string, length, 0);
  1823     /* 
  1824      * Now we have to be careful; the script may have changed the
  1825      * iPtr->scriptFile value, so we must reset it without
  1826      * assuming it still points to 'pathPtr'.
  1827      */
  1828     if (iPtr->scriptFile != NULL) {
  1829 	Tcl_DecrRefCount(iPtr->scriptFile);
  1830     }
  1831     iPtr->scriptFile = oldScriptFile;
  1832 
  1833     if (result == TCL_RETURN) {
  1834 	result = TclUpdateReturnInfo(iPtr);
  1835     } else if (result == TCL_ERROR) {
  1836 	char msg[200 + TCL_INTEGER_SPACE];
  1837 
  1838 	/*
  1839 	 * Record information telling where the error occurred.
  1840 	 */
  1841 
  1842 	sprintf(msg, "\n    (file \"%.150s\" line %d)", Tcl_GetString(pathPtr),
  1843 		interp->errorLine);
  1844 	Tcl_AddErrorInfo(interp, msg);
  1845     }
  1846 
  1847     end:
  1848     Tcl_DecrRefCount(objPtr);
  1849     return result;
  1850 }
  1851 
  1852 /*
  1853  *----------------------------------------------------------------------
  1854  *
  1855  * Tcl_GetErrno --
  1856  *
  1857  *	Gets the current value of the Tcl error code variable. This is
  1858  *	currently the global variable "errno" but could in the future
  1859  *	change to something else.
  1860  *
  1861  * Results:
  1862  *	The value of the Tcl error code variable.
  1863  *
  1864  * Side effects:
  1865  *	None. Note that the value of the Tcl error code variable is
  1866  *	UNDEFINED if a call to Tcl_SetErrno did not precede this call.
  1867  *
  1868  *----------------------------------------------------------------------
  1869  */
  1870 
  1871 EXPORT_C int
  1872 Tcl_GetErrno()
  1873 {
  1874     return errno;
  1875 }
  1876 
  1877 /*
  1878  *----------------------------------------------------------------------
  1879  *
  1880  * Tcl_SetErrno --
  1881  *
  1882  *	Sets the Tcl error code variable to the supplied value.
  1883  *
  1884  * Results:
  1885  *	None.
  1886  *
  1887  * Side effects:
  1888  *	Modifies the value of the Tcl error code variable.
  1889  *
  1890  *----------------------------------------------------------------------
  1891  */
  1892 
  1893 EXPORT_C void
  1894 Tcl_SetErrno(err)
  1895     int err;			/* The new value. */
  1896 {
  1897     errno = err;
  1898 }
  1899 
  1900 /*
  1901  *----------------------------------------------------------------------
  1902  *
  1903  * Tcl_PosixError --
  1904  *
  1905  *	This procedure is typically called after UNIX kernel calls
  1906  *	return errors.  It stores machine-readable information about
  1907  *	the error in $errorCode returns an information string for
  1908  *	the caller's use.
  1909  *
  1910  * Results:
  1911  *	The return value is a human-readable string describing the
  1912  *	error.
  1913  *
  1914  * Side effects:
  1915  *	The global variable $errorCode is reset.
  1916  *
  1917  *----------------------------------------------------------------------
  1918  */
  1919 
  1920 EXPORT_C CONST char *
  1921 Tcl_PosixError(interp)
  1922     Tcl_Interp *interp;		/* Interpreter whose $errorCode variable
  1923 				 * is to be changed. */
  1924 {
  1925     CONST char *id, *msg;
  1926 
  1927     msg = Tcl_ErrnoMsg(errno);
  1928     id = Tcl_ErrnoId();
  1929     if (interp) {
  1930 	Tcl_SetErrorCode(interp, "POSIX", id, msg, (char *) NULL);
  1931     }
  1932     return msg;
  1933 }
  1934 
  1935 /*
  1936  *----------------------------------------------------------------------
  1937  *
  1938  * Tcl_FSStat --
  1939  *
  1940  *	This procedure replaces the library version of stat and lsat.
  1941  *	
  1942  *	The appropriate function for the filesystem to which pathPtr
  1943  *	belongs will be called.
  1944  *
  1945  * Results:
  1946  *      See stat documentation.
  1947  *
  1948  * Side effects:
  1949  *      See stat documentation.
  1950  *
  1951  *----------------------------------------------------------------------
  1952  */
  1953 
  1954 EXPORT_C int
  1955 Tcl_FSStat(pathPtr, buf)
  1956     Tcl_Obj *pathPtr;		/* Path of file to stat (in current CP). */
  1957     Tcl_StatBuf *buf;		/* Filled with results of stat call. */
  1958 {
  1959     Tcl_Filesystem *fsPtr;
  1960 #ifdef USE_OBSOLETE_FS_HOOKS
  1961     struct stat oldStyleStatBuffer;
  1962     int retVal = -1;
  1963 
  1964     /*
  1965      * Call each of the "stat" function in succession.  A non-return
  1966      * value of -1 indicates the particular function has succeeded.
  1967      */
  1968 
  1969     Tcl_MutexLock(&obsoleteFsHookMutex);
  1970     
  1971     if (statProcList != NULL) {
  1972 	StatProc *statProcPtr;
  1973 	char *path;
  1974 	Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
  1975 	if (transPtr == NULL) {
  1976 	    path = NULL;
  1977 	} else {
  1978 	    path = Tcl_GetString(transPtr);
  1979 	}
  1980 
  1981 	statProcPtr = statProcList;
  1982 	while ((retVal == -1) && (statProcPtr != NULL)) {
  1983 	    retVal = (*statProcPtr->proc)(path, &oldStyleStatBuffer);
  1984 	    statProcPtr = statProcPtr->nextPtr;
  1985 	}
  1986 	if (transPtr != NULL) {
  1987 	    Tcl_DecrRefCount(transPtr);
  1988 	}
  1989     }
  1990     
  1991     Tcl_MutexUnlock(&obsoleteFsHookMutex);
  1992     if (retVal != -1) {
  1993 	/*
  1994 	 * Note that EOVERFLOW is not a problem here, and these
  1995 	 * assignments should all be widening (if not identity.)
  1996 	 */
  1997 	buf->st_mode = oldStyleStatBuffer.st_mode;
  1998 	buf->st_ino = oldStyleStatBuffer.st_ino;
  1999 	buf->st_dev = oldStyleStatBuffer.st_dev;
  2000 	buf->st_rdev = oldStyleStatBuffer.st_rdev;
  2001 	buf->st_nlink = oldStyleStatBuffer.st_nlink;
  2002 	buf->st_uid = oldStyleStatBuffer.st_uid;
  2003 	buf->st_gid = oldStyleStatBuffer.st_gid;
  2004 	buf->st_size = Tcl_LongAsWide(oldStyleStatBuffer.st_size);
  2005 	buf->st_atime = oldStyleStatBuffer.st_atime;
  2006 	buf->st_mtime = oldStyleStatBuffer.st_mtime;
  2007 	buf->st_ctime = oldStyleStatBuffer.st_ctime;
  2008 #ifdef HAVE_ST_BLOCKS
  2009 	buf->st_blksize = oldStyleStatBuffer.st_blksize;
  2010 	buf->st_blocks = Tcl_LongAsWide(oldStyleStatBuffer.st_blocks);
  2011 #endif
  2012         return retVal;
  2013     }
  2014 #endif /* USE_OBSOLETE_FS_HOOKS */
  2015     fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2016     if (fsPtr != NULL) {
  2017 	Tcl_FSStatProc *proc = fsPtr->statProc;
  2018 	if (proc != NULL) {
  2019 	    return (*proc)(pathPtr, buf);
  2020 	}
  2021     }
  2022     Tcl_SetErrno(ENOENT);
  2023     return -1;
  2024 }
  2025 
  2026 /*
  2027  *----------------------------------------------------------------------
  2028  *
  2029  * Tcl_FSLstat --
  2030  *
  2031  *	This procedure replaces the library version of lstat.
  2032  *	The appropriate function for the filesystem to which pathPtr
  2033  *	belongs will be called.  If no 'lstat' function is listed,
  2034  *	but a 'stat' function is, then Tcl will fall back on the
  2035  *	stat function.
  2036  *
  2037  * Results:
  2038  *      See lstat documentation.
  2039  *
  2040  * Side effects:
  2041  *      See lstat documentation.
  2042  *
  2043  *----------------------------------------------------------------------
  2044  */
  2045 
  2046 EXPORT_C int
  2047 Tcl_FSLstat(pathPtr, buf)
  2048     Tcl_Obj *pathPtr;		/* Path of file to stat (in current CP). */
  2049     Tcl_StatBuf *buf;		/* Filled with results of stat call. */
  2050 {
  2051     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2052     if (fsPtr != NULL) {
  2053 	Tcl_FSLstatProc *proc = fsPtr->lstatProc;
  2054 	if (proc != NULL) {
  2055 	    return (*proc)(pathPtr, buf);
  2056 	} else {
  2057 	    Tcl_FSStatProc *sproc = fsPtr->statProc;
  2058 	    if (sproc != NULL) {
  2059 		return (*sproc)(pathPtr, buf);
  2060 	    }
  2061 	}
  2062     }
  2063     Tcl_SetErrno(ENOENT);
  2064     return -1;
  2065 }
  2066 
  2067 /*
  2068  *----------------------------------------------------------------------
  2069  *
  2070  * Tcl_FSAccess --
  2071  *
  2072  *	This procedure replaces the library version of access.
  2073  *	The appropriate function for the filesystem to which pathPtr
  2074  *	belongs will be called.
  2075  *
  2076  * Results:
  2077  *      See access documentation.
  2078  *
  2079  * Side effects:
  2080  *      See access documentation.
  2081  *
  2082  *----------------------------------------------------------------------
  2083  */
  2084 
  2085 EXPORT_C int
  2086 Tcl_FSAccess(pathPtr, mode)
  2087     Tcl_Obj *pathPtr;		/* Path of file to access (in current CP). */
  2088     int mode;                   /* Permission setting. */
  2089 {
  2090     Tcl_Filesystem *fsPtr;
  2091 #ifdef USE_OBSOLETE_FS_HOOKS
  2092     int retVal = -1;
  2093 
  2094     /*
  2095      * Call each of the "access" function in succession.  A non-return
  2096      * value of -1 indicates the particular function has succeeded.
  2097      */
  2098 
  2099     Tcl_MutexLock(&obsoleteFsHookMutex);
  2100 
  2101     if (accessProcList != NULL) {
  2102 	AccessProc *accessProcPtr;
  2103 	char *path;
  2104 	Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
  2105 	if (transPtr == NULL) {
  2106 	    path = NULL;
  2107 	} else {
  2108 	    path = Tcl_GetString(transPtr);
  2109 	}
  2110 
  2111 	accessProcPtr = accessProcList;
  2112 	while ((retVal == -1) && (accessProcPtr != NULL)) {
  2113 	    retVal = (*accessProcPtr->proc)(path, mode);
  2114 	    accessProcPtr = accessProcPtr->nextPtr;
  2115 	}
  2116 	if (transPtr != NULL) {
  2117 	    Tcl_DecrRefCount(transPtr);
  2118 	}
  2119     }
  2120     
  2121     Tcl_MutexUnlock(&obsoleteFsHookMutex);
  2122     if (retVal != -1) {
  2123 	return retVal;
  2124     }
  2125 #endif /* USE_OBSOLETE_FS_HOOKS */
  2126     fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2127     if (fsPtr != NULL) {
  2128 	Tcl_FSAccessProc *proc = fsPtr->accessProc;
  2129 	if (proc != NULL) {
  2130 	    return (*proc)(pathPtr, mode);
  2131 	}
  2132     }
  2133 
  2134     Tcl_SetErrno(ENOENT);
  2135     return -1;
  2136 }
  2137 
  2138 /*
  2139  *----------------------------------------------------------------------
  2140  *
  2141  * Tcl_FSOpenFileChannel --
  2142  *
  2143  *	The appropriate function for the filesystem to which pathPtr
  2144  *	belongs will be called.
  2145  *
  2146  * Results:
  2147  *	The new channel or NULL, if the named file could not be opened.
  2148  *
  2149  * Side effects:
  2150  *	May open the channel and may cause creation of a file on the
  2151  *	file system.
  2152  *
  2153  *----------------------------------------------------------------------
  2154  */
  2155  
  2156 EXPORT_C Tcl_Channel
  2157 Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions)
  2158     Tcl_Interp *interp;                 /* Interpreter for error reporting;
  2159                                          * can be NULL. */
  2160     Tcl_Obj *pathPtr;                   /* Name of file to open. */
  2161     CONST char *modeString;             /* A list of POSIX open modes or
  2162                                          * a string such as "rw". */
  2163     int permissions;                    /* If the open involves creating a
  2164                                          * file, with what modes to create
  2165                                          * it? */
  2166 {
  2167     Tcl_Filesystem *fsPtr;
  2168 #ifdef USE_OBSOLETE_FS_HOOKS
  2169     Tcl_Channel retVal = NULL;
  2170 
  2171     /*
  2172      * Call each of the "Tcl_OpenFileChannel" functions in succession.
  2173      * A non-NULL return value indicates the particular function has
  2174      * succeeded.
  2175      */
  2176 
  2177     Tcl_MutexLock(&obsoleteFsHookMutex);
  2178     if (openFileChannelProcList != NULL) {
  2179 	OpenFileChannelProc *openFileChannelProcPtr;
  2180 	char *path;
  2181 	Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
  2182 	
  2183 	if (transPtr == NULL) {
  2184 	    path = NULL;
  2185 	} else {
  2186 	    path = Tcl_GetString(transPtr);
  2187 	}
  2188 
  2189 	openFileChannelProcPtr = openFileChannelProcList;
  2190 	
  2191 	while ((retVal == NULL) && (openFileChannelProcPtr != NULL)) {
  2192 	    retVal = (*openFileChannelProcPtr->proc)(interp, path,
  2193 						     modeString, permissions);
  2194 	    openFileChannelProcPtr = openFileChannelProcPtr->nextPtr;
  2195 	}
  2196 	if (transPtr != NULL) {
  2197 	    Tcl_DecrRefCount(transPtr);
  2198 	}
  2199     }
  2200     Tcl_MutexUnlock(&obsoleteFsHookMutex);
  2201     if (retVal != NULL) {
  2202 	return retVal;
  2203     }
  2204 #endif /* USE_OBSOLETE_FS_HOOKS */
  2205     
  2206     /* 
  2207      * We need this just to ensure we return the correct error messages
  2208      * under some circumstances.
  2209      */
  2210     if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
  2211         return NULL;
  2212     }
  2213     
  2214     fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2215     if (fsPtr != NULL) {
  2216 	Tcl_FSOpenFileChannelProc *proc = fsPtr->openFileChannelProc;
  2217 	if (proc != NULL) {
  2218 	    int mode, seekFlag;
  2219 	    mode = TclGetOpenMode(interp, modeString, &seekFlag);
  2220 	    if (mode == -1) {
  2221 	        return NULL;
  2222 	    }
  2223 	    retVal = (*proc)(interp, pathPtr, mode, permissions);
  2224 	    if (retVal != NULL) {
  2225 		if (seekFlag) {
  2226 		    if (Tcl_Seek(retVal, (Tcl_WideInt)0, 
  2227 				 SEEK_END) < (Tcl_WideInt)0) {
  2228 			if (interp != (Tcl_Interp *) NULL) {
  2229 			    Tcl_AppendResult(interp,
  2230 			      "could not seek to end of file while opening \"",
  2231 			      Tcl_GetString(pathPtr), "\": ", 
  2232 			      Tcl_PosixError(interp), (char *) NULL);
  2233 			}
  2234 			Tcl_Close(NULL, retVal);
  2235 			return NULL;
  2236 		    }
  2237 		}
  2238 	    }
  2239 	    return retVal;
  2240 	}
  2241     }
  2242     /* File doesn't belong to any filesystem that can open it */
  2243     Tcl_SetErrno(ENOENT);
  2244     if (interp != NULL) {
  2245 	Tcl_AppendResult(interp, "couldn't open \"", 
  2246 			 Tcl_GetString(pathPtr), "\": ",
  2247 			 Tcl_PosixError(interp), (char *) NULL);
  2248     }
  2249     return NULL;
  2250 }
  2251 
  2252 /*
  2253  *----------------------------------------------------------------------
  2254  *
  2255  * Tcl_FSUtime --
  2256  *
  2257  *	This procedure replaces the library version of utime.
  2258  *	The appropriate function for the filesystem to which pathPtr
  2259  *	belongs will be called.
  2260  *
  2261  * Results:
  2262  *      See utime documentation.
  2263  *
  2264  * Side effects:
  2265  *      See utime documentation.
  2266  *
  2267  *----------------------------------------------------------------------
  2268  */
  2269 
  2270 EXPORT_C int 
  2271 Tcl_FSUtime (pathPtr, tval)
  2272     Tcl_Obj *pathPtr;       /* File to change access/modification times */
  2273     struct utimbuf *tval;   /* Structure containing access/modification 
  2274                              * times to use.  Should not be modified. */
  2275 {
  2276     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2277     if (fsPtr != NULL) {
  2278 	Tcl_FSUtimeProc *proc = fsPtr->utimeProc;
  2279 	if (proc != NULL) {
  2280 	    return (*proc)(pathPtr, tval);
  2281 	}
  2282     }
  2283     return -1;
  2284 }
  2285 
  2286 /*
  2287  *----------------------------------------------------------------------
  2288  *
  2289  * NativeFileAttrStrings --
  2290  *
  2291  *	This procedure implements the platform dependent 'file
  2292  *	attributes' subcommand, for the native filesystem, for listing
  2293  *	the set of possible attribute strings.  This function is part
  2294  *	of Tcl's native filesystem support, and is placed here because
  2295  *	it is shared by Unix, MacOS and Windows code.
  2296  *
  2297  * Results:
  2298  *      An array of strings
  2299  *
  2300  * Side effects:
  2301  *      None.
  2302  *
  2303  *----------------------------------------------------------------------
  2304  */
  2305 
  2306 static CONST char**
  2307 NativeFileAttrStrings(pathPtr, objPtrRef)
  2308     Tcl_Obj *pathPtr;
  2309     Tcl_Obj** objPtrRef;
  2310 {
  2311     return tclpFileAttrStrings;
  2312 }
  2313 
  2314 /*
  2315  *----------------------------------------------------------------------
  2316  *
  2317  * NativeFileAttrsGet --
  2318  *
  2319  *	This procedure implements the platform dependent
  2320  *	'file attributes' subcommand, for the native
  2321  *	filesystem, for 'get' operations.  This function is part
  2322  *	of Tcl's native filesystem support, and is placed here
  2323  *	because it is shared by Unix, MacOS and Windows code.
  2324  *
  2325  * Results:
  2326  *      Standard Tcl return code.  The object placed in objPtrRef
  2327  *      (if TCL_OK was returned) is likely to have a refCount of zero.
  2328  *      Either way we must either store it somewhere (e.g. the Tcl 
  2329  *      result), or Incr/Decr its refCount to ensure it is properly
  2330  *      freed.
  2331  *
  2332  * Side effects:
  2333  *      None.
  2334  *
  2335  *----------------------------------------------------------------------
  2336  */
  2337 
  2338 static int
  2339 NativeFileAttrsGet(interp, index, pathPtr, objPtrRef)
  2340     Tcl_Interp *interp;		/* The interpreter for error reporting. */
  2341     int index;			/* index of the attribute command. */
  2342     Tcl_Obj *pathPtr;		/* path of file we are operating on. */
  2343     Tcl_Obj **objPtrRef;	/* for output. */
  2344 {
  2345     return (*tclpFileAttrProcs[index].getProc)(interp, index, 
  2346 					       pathPtr, objPtrRef);
  2347 }
  2348 
  2349 /*
  2350  *----------------------------------------------------------------------
  2351  *
  2352  * NativeFileAttrsSet --
  2353  *
  2354  *	This procedure implements the platform dependent
  2355  *	'file attributes' subcommand, for the native
  2356  *	filesystem, for 'set' operations. This function is part
  2357  *	of Tcl's native filesystem support, and is placed here
  2358  *	because it is shared by Unix, MacOS and Windows code.
  2359  *
  2360  * Results:
  2361  *      Standard Tcl return code.
  2362  *
  2363  * Side effects:
  2364  *      None.
  2365  *
  2366  *----------------------------------------------------------------------
  2367  */
  2368 
  2369 static int
  2370 NativeFileAttrsSet(interp, index, pathPtr, objPtr)
  2371     Tcl_Interp *interp;		/* The interpreter for error reporting. */
  2372     int index;			/* index of the attribute command. */
  2373     Tcl_Obj *pathPtr;		/* path of file we are operating on. */
  2374     Tcl_Obj *objPtr;		/* set to this value. */
  2375 {
  2376     return (*tclpFileAttrProcs[index].setProc)(interp, index,
  2377 					       pathPtr, objPtr);
  2378 }
  2379 
  2380 /*
  2381  *----------------------------------------------------------------------
  2382  *
  2383  * Tcl_FSFileAttrStrings --
  2384  *
  2385  *	This procedure implements part of the hookable 'file
  2386  *	attributes' subcommand.  The appropriate function for the
  2387  *	filesystem to which pathPtr belongs will be called.
  2388  *
  2389  * Results:
  2390  *      The called procedure may either return an array of strings,
  2391  *      or may instead return NULL and place a Tcl list into the 
  2392  *      given objPtrRef.  Tcl will take that list and first increment
  2393  *      its refCount before using it.  On completion of that use, Tcl
  2394  *      will decrement its refCount.  Hence if the list should be
  2395  *      disposed of by Tcl when done, it should have a refCount of zero,
  2396  *      and if the list should not be disposed of, the filesystem
  2397  *      should ensure it retains a refCount on the object.
  2398  *
  2399  * Side effects:
  2400  *      None.
  2401  *
  2402  *----------------------------------------------------------------------
  2403  */
  2404 
  2405 EXPORT_C CONST char **
  2406 Tcl_FSFileAttrStrings(pathPtr, objPtrRef)
  2407     Tcl_Obj* pathPtr;
  2408     Tcl_Obj** objPtrRef;
  2409 {
  2410     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2411     if (fsPtr != NULL) {
  2412 	Tcl_FSFileAttrStringsProc *proc = fsPtr->fileAttrStringsProc;
  2413 	if (proc != NULL) {
  2414 	    return (*proc)(pathPtr, objPtrRef);
  2415 	}
  2416     }
  2417     Tcl_SetErrno(ENOENT);
  2418     return NULL;
  2419 }
  2420 
  2421 /*
  2422  *----------------------------------------------------------------------
  2423  *
  2424  * Tcl_FSFileAttrsGet --
  2425  *
  2426  *	This procedure implements read access for the hookable 'file
  2427  *	attributes' subcommand.  The appropriate function for the
  2428  *	filesystem to which pathPtr belongs will be called.
  2429  *
  2430  * Results:
  2431  *      Standard Tcl return code.  The object placed in objPtrRef
  2432  *      (if TCL_OK was returned) is likely to have a refCount of zero.
  2433  *      Either way we must either store it somewhere (e.g. the Tcl 
  2434  *      result), or Incr/Decr its refCount to ensure it is properly
  2435  *      freed.
  2436 
  2437  *
  2438  * Side effects:
  2439  *      None.
  2440  *
  2441  *----------------------------------------------------------------------
  2442  */
  2443 
  2444 EXPORT_C int
  2445 Tcl_FSFileAttrsGet(interp, index, pathPtr, objPtrRef)
  2446     Tcl_Interp *interp;		/* The interpreter for error reporting. */
  2447     int index;			/* index of the attribute command. */
  2448     Tcl_Obj *pathPtr;		/* filename we are operating on. */
  2449     Tcl_Obj **objPtrRef;	/* for output. */
  2450 {
  2451     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2452     if (fsPtr != NULL) {
  2453 	Tcl_FSFileAttrsGetProc *proc = fsPtr->fileAttrsGetProc;
  2454 	if (proc != NULL) {
  2455 	    return (*proc)(interp, index, pathPtr, objPtrRef);
  2456 	}
  2457     }
  2458     Tcl_SetErrno(ENOENT);
  2459     return -1;
  2460 }
  2461 
  2462 /*
  2463  *----------------------------------------------------------------------
  2464  *
  2465  * Tcl_FSFileAttrsSet --
  2466  *
  2467  *	This procedure implements write access for the hookable 'file
  2468  *	attributes' subcommand.  The appropriate function for the
  2469  *	filesystem to which pathPtr belongs will be called.
  2470  *
  2471  * Results:
  2472  *      Standard Tcl return code.
  2473  *
  2474  * Side effects:
  2475  *      None.
  2476  *
  2477  *----------------------------------------------------------------------
  2478  */
  2479 
  2480 EXPORT_C int
  2481 Tcl_FSFileAttrsSet(interp, index, pathPtr, objPtr)
  2482     Tcl_Interp *interp;		/* The interpreter for error reporting. */
  2483     int index;			/* index of the attribute command. */
  2484     Tcl_Obj *pathPtr;		/* filename we are operating on. */
  2485     Tcl_Obj *objPtr;		/* Input value. */
  2486 {
  2487     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2488     if (fsPtr != NULL) {
  2489 	Tcl_FSFileAttrsSetProc *proc = fsPtr->fileAttrsSetProc;
  2490 	if (proc != NULL) {
  2491 	    return (*proc)(interp, index, pathPtr, objPtr);
  2492 	}
  2493     }
  2494     Tcl_SetErrno(ENOENT);
  2495     return -1;
  2496 }
  2497 
  2498 /*
  2499  *----------------------------------------------------------------------
  2500  *
  2501  * Tcl_FSGetCwd --
  2502  *
  2503  *	This function replaces the library version of getcwd().
  2504  *	
  2505  *	Most VFS's will *not* implement a 'cwdProc'.  Tcl now maintains
  2506  *	its own record (in a Tcl_Obj) of the cwd, and an attempt
  2507  *	is made to synchronise this with the cwd's containing filesystem,
  2508  *	if that filesystem provides a cwdProc (e.g. the native filesystem).
  2509  *	
  2510  *	Note that if Tcl's cwd is not in the native filesystem, then of
  2511  *	course Tcl's cwd and the native cwd are different: extensions
  2512  *	should therefore ensure they only access the cwd through this
  2513  *	function to avoid confusion.
  2514  *	
  2515  *	If a global cwdPathPtr already exists, it is cached in the thread's
  2516  *	private data structures and reference to the cached copy is returned,
  2517  *	subject to a synchronisation attempt in that cwdPathPtr's fs.
  2518  *	
  2519  *	Otherwise, the chain of functions that have been "inserted"
  2520  *	into the filesystem will be called in succession until either a
  2521  *	value other than NULL is returned, or the entire list is
  2522  *	visited.
  2523  *
  2524  * Results:
  2525  *	The result is a pointer to a Tcl_Obj specifying the current
  2526  *	directory, or NULL if the current directory could not be
  2527  *	determined.  If NULL is returned, an error message is left in the
  2528  *	interp's result.  
  2529  *	
  2530  *	The result already has its refCount incremented for the caller.
  2531  *	When it is no longer needed, that refCount should be decremented.
  2532  *
  2533  * Side effects:
  2534  *	Various objects may be freed and allocated.
  2535  *
  2536  *----------------------------------------------------------------------
  2537  */
  2538 
  2539 EXPORT_C Tcl_Obj*
  2540 Tcl_FSGetCwd(interp)
  2541     Tcl_Interp *interp;
  2542 {
  2543     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  2544     
  2545     if (TclFSCwdPointerEquals(NULL)) {
  2546 	FilesystemRecord *fsRecPtr;
  2547 	Tcl_Obj *retVal = NULL;
  2548 
  2549 	/* 
  2550 	 * We've never been called before, try to find a cwd.  Call
  2551 	 * each of the "Tcl_GetCwd" function in succession.  A non-NULL
  2552 	 * return value indicates the particular function has
  2553 	 * succeeded.
  2554 	 */
  2555 
  2556 	fsRecPtr = FsGetFirstFilesystem();
  2557 	while ((retVal == NULL) && (fsRecPtr != NULL)) {
  2558 	    Tcl_FSGetCwdProc *proc = fsRecPtr->fsPtr->getCwdProc;
  2559 	    if (proc != NULL) {
  2560 		retVal = (*proc)(interp);
  2561 	    }
  2562 	    fsRecPtr = fsRecPtr->nextPtr;
  2563 	}
  2564 	/* 
  2565 	 * Now the 'cwd' may NOT be normalized, at least on some
  2566 	 * platforms.  For the sake of efficiency, we want a completely
  2567 	 * normalized cwd at all times.
  2568 	 * 
  2569 	 * Finally, if retVal is NULL, we do not have a cwd, which
  2570 	 * could be problematic.
  2571 	 */
  2572 	if (retVal != NULL) {
  2573 	    Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal, NULL);
  2574 	    if (norm != NULL) {
  2575 		/* 
  2576 		 * We found a cwd, which is now in our global storage.
  2577 		 * We must make a copy. Norm already has a refCount of 1.
  2578 		 * 
  2579 		 * Threading issue: note that multiple threads at system
  2580 		 * startup could in principle call this procedure 
  2581 		 * simultaneously.  They will therefore each set the
  2582 		 * cwdPathPtr independently.  That behaviour is a bit
  2583 		 * peculiar, but should be fine.  Once we have a cwd,
  2584 		 * we'll always be in the 'else' branch below which
  2585 		 * is simpler.
  2586 		 */
  2587 		FsUpdateCwd(norm);
  2588 		Tcl_DecrRefCount(norm);
  2589 	    }
  2590 	    Tcl_DecrRefCount(retVal);
  2591 	}
  2592     } else {
  2593 	/* 
  2594 	 * We already have a cwd cached, but we want to give the
  2595 	 * filesystem it is in a chance to check whether that cwd
  2596 	 * has changed, or is perhaps no longer accessible.  This
  2597 	 * allows an error to be thrown if, say, the permissions on
  2598 	 * that directory have changed.
  2599 	 */
  2600 	Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr);
  2601 	/* 
  2602 	 * If the filesystem couldn't be found, or if no cwd function
  2603 	 * exists for this filesystem, then we simply assume the cached
  2604 	 * cwd is ok.  If we do call a cwd, we must watch for errors
  2605 	 * (if the cwd returns NULL).  This ensures that, say, on Unix
  2606 	 * if the permissions of the cwd change, 'pwd' does actually
  2607 	 * throw the correct error in Tcl.  (This is tested for in the
  2608 	 * test suite on unix).
  2609 	 */
  2610 	if (fsPtr != NULL) {
  2611 	    Tcl_FSGetCwdProc *proc = fsPtr->getCwdProc;
  2612 	    if (proc != NULL) {
  2613 		Tcl_Obj *retVal = (*proc)(interp);
  2614 		if (retVal != NULL) {
  2615 		    Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal, NULL);
  2616 		    /* 
  2617 		     * Check whether cwd has changed from the value
  2618 		     * previously stored in cwdPathPtr.  Really 'norm'
  2619 		     * shouldn't be null, but we are careful.
  2620 		     */
  2621 		    if (norm == NULL) {
  2622 			/* Do nothing */
  2623 		    } else if (Tcl_FSEqualPaths(tsdPtr->cwdPathPtr, norm)) {
  2624 			/* 
  2625 			 * If the paths were equal, we can be more
  2626 			 * efficient and retain the old path object
  2627 			 * which will probably already be shared.  In
  2628 			 * this case we can simply free the normalized
  2629 			 * path we just calculated.
  2630 			 */
  2631 			Tcl_DecrRefCount(norm);
  2632 		    } else {
  2633 			FsUpdateCwd(norm);
  2634 			Tcl_DecrRefCount(norm);
  2635 		    }
  2636 		    Tcl_DecrRefCount(retVal);
  2637 		} else {
  2638 		    /* The 'cwd' function returned an error; reset the cwd */
  2639 		    FsUpdateCwd(NULL);
  2640 		}
  2641 	    }
  2642 	}
  2643     }
  2644     
  2645     if (tsdPtr->cwdPathPtr != NULL) {
  2646 	Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
  2647     }
  2648     
  2649     return tsdPtr->cwdPathPtr; 
  2650 }
  2651 
  2652 /*
  2653  *----------------------------------------------------------------------
  2654  *
  2655  * Tcl_FSChdir --
  2656  *
  2657  *	This function replaces the library version of chdir().
  2658  *	
  2659  *	The path is normalized and then passed to the filesystem
  2660  *	which claims it.
  2661  *
  2662  * Results:
  2663  *	See chdir() documentation.  If successful, we keep a 
  2664  *	record of the successful path in cwdPathPtr for subsequent 
  2665  *	calls to getcwd.
  2666  *
  2667  * Side effects:
  2668  *	See chdir() documentation.  The global cwdPathPtr may 
  2669  *	change value.
  2670  *
  2671  *----------------------------------------------------------------------
  2672  */
  2673 EXPORT_C int
  2674 Tcl_FSChdir(pathPtr)
  2675     Tcl_Obj *pathPtr;
  2676 {
  2677     Tcl_Filesystem *fsPtr;
  2678     int retVal = -1;
  2679     
  2680 #ifdef WIN32
  2681     /*
  2682      * This complete hack addresses the bug tested in winFCmd-16.12,
  2683      * where having your HOME as "C:" (IOW, a seemingly path relative
  2684      * dir) would cause a crash when you cd'd to it and requested 'pwd'.
  2685      * The work-around is to force such a dir into an absolute path by
  2686      * tacking on '/'.
  2687      *
  2688      * We check for '~' specifically because that's what Tcl_CdObjCmd
  2689      * passes in that triggers the bug.  A direct 'cd C:' call will not
  2690      * because that gets the volumerelative pwd.
  2691      *
  2692      * This is not an issue for 8.5 as that has a more elaborate change
  2693      * that requires the use of TCL_FILESYSTEM_VERSION_2.
  2694      */
  2695     Tcl_Obj *objPtr = NULL;
  2696     if (pathPtr->bytes && pathPtr->length == 1 && pathPtr->bytes[0] == '~') {
  2697 	int len;
  2698 	char *str;
  2699 
  2700 	objPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
  2701 	if (objPtr == NULL) {
  2702 	    Tcl_SetErrno(ENOENT);
  2703 	    return -1;
  2704 	}
  2705 	Tcl_IncrRefCount(objPtr);
  2706 	str = Tcl_GetStringFromObj(objPtr, &len);
  2707 	if (len == 2 && str[1] == ':') {
  2708 	    pathPtr = Tcl_NewStringObj(str, len);
  2709 	    Tcl_AppendToObj(pathPtr, "/", 1);
  2710 	    Tcl_IncrRefCount(pathPtr);
  2711 	    Tcl_DecrRefCount(objPtr);
  2712 	    objPtr = pathPtr;
  2713 	} else {
  2714 	    Tcl_DecrRefCount(objPtr);
  2715 	    objPtr = NULL;
  2716 	}
  2717     }
  2718 #endif
  2719     if (Tcl_FSGetNormalizedPath(NULL, pathPtr) == NULL) {
  2720 #ifdef WIN32
  2721 	if (objPtr) { Tcl_DecrRefCount(objPtr); }
  2722 #endif
  2723 	Tcl_SetErrno(ENOENT);
  2724         return -1;
  2725     }
  2726     
  2727     fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2728     if (fsPtr != NULL) {
  2729 	Tcl_FSChdirProc *proc = fsPtr->chdirProc;
  2730 	if (proc != NULL) {
  2731 	    retVal = (*proc)(pathPtr);
  2732 	} else {
  2733 	    /* Fallback on stat-based implementation */
  2734 	    Tcl_StatBuf buf;
  2735 	    /* If the file can be stat'ed and is a directory and
  2736 	     * is readable, then we can chdir. */
  2737 	    if ((Tcl_FSStat(pathPtr, &buf) == 0) 
  2738 	      && (S_ISDIR(buf.st_mode))
  2739 	      && (Tcl_FSAccess(pathPtr, R_OK) == 0)) {
  2740 		/* We allow the chdir */
  2741 		retVal = 0;
  2742 	    }
  2743 	}
  2744     }
  2745 
  2746     if (retVal != -1) {
  2747 	/* 
  2748 	 * The cwd changed, or an error was thrown.  If an error was
  2749 	 * thrown, we can just continue (and that will report the error
  2750 	 * to the user).  If there was no error we must assume that the
  2751 	 * cwd was actually changed to the normalized value we
  2752 	 * calculated above, and we must therefore cache that
  2753 	 * information.
  2754 	 */
  2755 	if (retVal == 0) {
  2756 	    /* 
  2757 	     * Note that this normalized path may be different to what
  2758 	     * we found above (or at least a different object), if the
  2759 	     * filesystem epoch changed recently.  This can actually
  2760 	     * happen with scripted documents very easily.  Therefore
  2761 	     * we ask for the normalized path again (the correct value
  2762 	     * will have been cached as a result of the
  2763 	     * Tcl_FSGetFileSystemForPath call above anyway).
  2764 	     */
  2765 	    Tcl_Obj *normDirName = Tcl_FSGetNormalizedPath(NULL, pathPtr);
  2766 	    if (normDirName == NULL) {
  2767 #ifdef WIN32
  2768 		if (objPtr) { Tcl_DecrRefCount(objPtr); }
  2769 #endif
  2770 		Tcl_SetErrno(ENOENT);
  2771 	        return -1;
  2772 	    }
  2773 	    FsUpdateCwd(normDirName);
  2774 	}
  2775     } else {
  2776 	Tcl_SetErrno(ENOENT);
  2777     }
  2778     
  2779 #ifdef WIN32
  2780     if (objPtr) { Tcl_DecrRefCount(objPtr); }
  2781 #endif
  2782     return (retVal);
  2783 }
  2784 
  2785 /*
  2786  *----------------------------------------------------------------------
  2787  *
  2788  * Tcl_FSLoadFile --
  2789  *
  2790  *	Dynamically loads a binary code file into memory and returns
  2791  *	the addresses of two procedures within that file, if they are
  2792  *	defined.  The appropriate function for the filesystem to which
  2793  *	pathPtr belongs will be called.
  2794  *	
  2795  *	Note that the native filesystem doesn't actually assume
  2796  *	'pathPtr' is a path.  Rather it assumes filename is either
  2797  *	a path or just the name of a file which can be found somewhere
  2798  *	in the environment's loadable path.  This behaviour is not
  2799  *	very compatible with virtual filesystems (and has other problems
  2800  *	documented in the load man-page), so it is advised that full
  2801  *	paths are always used.
  2802  *
  2803  * Results:
  2804  *	A standard Tcl completion code.  If an error occurs, an error
  2805  *	message is left in the interp's result.
  2806  *
  2807  * Side effects:
  2808  *	New code suddenly appears in memory.  This may later be
  2809  *	unloaded by passing the clientData to the unloadProc.
  2810  *
  2811  *----------------------------------------------------------------------
  2812  */
  2813 
  2814 EXPORT_C int
  2815 Tcl_FSLoadFile(interp, pathPtr, sym1, sym2, proc1Ptr, proc2Ptr, 
  2816 	       handlePtr, unloadProcPtr)
  2817     Tcl_Interp *interp;		/* Used for error reporting. */
  2818     Tcl_Obj *pathPtr;		/* Name of the file containing the desired
  2819 				 * code. */
  2820     CONST char *sym1, *sym2;	/* Names of two procedures to look up in
  2821 				 * the file's symbol table. */
  2822     Tcl_PackageInitProc **proc1Ptr, **proc2Ptr;
  2823 				/* Where to return the addresses corresponding
  2824 				 * to sym1 and sym2. */
  2825     Tcl_LoadHandle *handlePtr;	/* Filled with token for dynamically loaded
  2826 				 * file which will be passed back to 
  2827 				 * (*unloadProcPtr)() to unload the file. */
  2828     Tcl_FSUnloadFileProc **unloadProcPtr;	
  2829                                 /* Filled with address of Tcl_FSUnloadFileProc
  2830                                  * function which should be used for
  2831                                  * this file. */
  2832 {
  2833     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  2834     if (fsPtr != NULL) {
  2835 	Tcl_FSLoadFileProc *proc = fsPtr->loadFileProc;
  2836 	if (proc != NULL) {
  2837 	    int retVal = (*proc)(interp, pathPtr, handlePtr, unloadProcPtr);
  2838 	    if (retVal != TCL_OK) {
  2839 		return retVal;
  2840 	    }
  2841 	    if (*handlePtr == NULL) {
  2842 		return TCL_ERROR;
  2843 	    }
  2844 	    if (sym1 != NULL) {
  2845 	        *proc1Ptr = TclpFindSymbol(interp, *handlePtr, sym1);
  2846 	    }
  2847 	    if (sym2 != NULL) {
  2848 	        *proc2Ptr = TclpFindSymbol(interp, *handlePtr, sym2);
  2849 	    }
  2850 	    return retVal;
  2851 	} else {
  2852 	    Tcl_Filesystem *copyFsPtr;
  2853 	    Tcl_Obj *copyToPtr;
  2854 	    
  2855 	    /* First check if it is readable -- and exists! */
  2856 	    if (Tcl_FSAccess(pathPtr, R_OK) != 0) {
  2857 		Tcl_AppendResult(interp, "couldn't load library \"",
  2858 				 Tcl_GetString(pathPtr), "\": ", 
  2859 				 Tcl_PosixError(interp), (char *) NULL);
  2860 		return TCL_ERROR;
  2861 	    }
  2862 	    
  2863 #ifdef TCL_LOAD_FROM_MEMORY
  2864 	/* 
  2865 	 * The platform supports loading code from memory, so ask for a
  2866 	 * buffer of the appropriate size, read the file into it and 
  2867 	 * load the code from the buffer:
  2868 	 */
  2869 	do {
  2870             int ret, size;
  2871             void *buffer;
  2872             Tcl_StatBuf statBuf;
  2873             Tcl_Channel data;
  2874             
  2875             ret = Tcl_FSStat(pathPtr, &statBuf);
  2876             if (ret < 0) {
  2877                 break;
  2878             }
  2879             size = (int) statBuf.st_size;
  2880             /* Tcl_Read takes an int: check that file size isn't wide */
  2881             if (size != (Tcl_WideInt)statBuf.st_size) {
  2882                 break;
  2883             }
  2884 	    data = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0666);
  2885             if (!data) {
  2886                 break;
  2887             }
  2888             buffer = TclpLoadMemoryGetBuffer(interp, size);
  2889             if (!buffer) {
  2890                 Tcl_Close(interp, data);
  2891                 break;
  2892             }
  2893             Tcl_SetChannelOption(interp, data, "-translation", "binary");
  2894             ret = Tcl_Read(data, buffer, size);
  2895             Tcl_Close(interp, data);
  2896             ret = TclpLoadMemory(interp, buffer, size, ret, handlePtr, unloadProcPtr);
  2897             if (ret == TCL_OK) {
  2898 		if (*handlePtr == NULL) {
  2899 		    break;
  2900 		}
  2901                 if (sym1 != NULL) {
  2902                     *proc1Ptr = TclpFindSymbol(interp, *handlePtr, sym1);
  2903                 }
  2904                 if (sym2 != NULL) {
  2905                     *proc2Ptr = TclpFindSymbol(interp, *handlePtr, sym2);
  2906                 }
  2907 		return TCL_OK;
  2908 	    }
  2909 	} while (0); 
  2910 	Tcl_ResetResult(interp);
  2911 #endif
  2912 
  2913 	    /* 
  2914 	     * Get a temporary filename to use, first to
  2915 	     * copy the file into, and then to load. 
  2916 	     */
  2917 	    copyToPtr = TclpTempFileName();
  2918 	    if (copyToPtr == NULL) {
  2919 	        return -1;
  2920 	    }
  2921 	    Tcl_IncrRefCount(copyToPtr);
  2922 	    
  2923 	    copyFsPtr = Tcl_FSGetFileSystemForPath(copyToPtr);
  2924 	    if ((copyFsPtr == NULL) || (copyFsPtr == fsPtr)) {
  2925 		/* 
  2926 		 * We already know we can't use Tcl_FSLoadFile from 
  2927 		 * this filesystem, and we must avoid a possible
  2928 		 * infinite loop.  Try to delete the file we
  2929 		 * probably created, and then exit.
  2930 		 */
  2931 		Tcl_FSDeleteFile(copyToPtr);
  2932 		Tcl_DecrRefCount(copyToPtr);
  2933 		return -1;
  2934 	    }
  2935 	    
  2936 	    if (TclCrossFilesystemCopy(interp, pathPtr, 
  2937 				       copyToPtr) == TCL_OK) {
  2938 		Tcl_LoadHandle newLoadHandle = NULL;
  2939 		Tcl_FSUnloadFileProc *newUnloadProcPtr = NULL;
  2940 		FsDivertLoad *tvdlPtr;
  2941 		int retVal;
  2942 
  2943 #if !defined(__WIN32__) && !defined(MAC_TCL)
  2944 		/* 
  2945 		 * Do we need to set appropriate permissions 
  2946 		 * on the file?  This may be required on some
  2947 		 * systems.  On Unix we could loop over
  2948 		 * the file attributes, and set any that are
  2949 		 * called "-permissions" to 0700.  However,
  2950 		 * we just do this directly, like this:
  2951 		 */
  2952 		
  2953 		Tcl_Obj* perm = Tcl_NewStringObj("0700",-1);
  2954 		Tcl_IncrRefCount(perm);
  2955 		Tcl_FSFileAttrsSet(NULL, 2, copyToPtr, perm);
  2956 		Tcl_DecrRefCount(perm);
  2957 #endif
  2958 		
  2959 		/* 
  2960 		 * We need to reset the result now, because the cross-
  2961 		 * filesystem copy may have stored the number of bytes
  2962 		 * in the result
  2963 		 */
  2964 		Tcl_ResetResult(interp);
  2965 		
  2966 		retVal = Tcl_FSLoadFile(interp, copyToPtr, sym1, sym2,
  2967 					proc1Ptr, proc2Ptr, 
  2968 					&newLoadHandle,
  2969 					&newUnloadProcPtr);
  2970 	        if (retVal != TCL_OK) {
  2971 		    /* The file didn't load successfully */
  2972 		    Tcl_FSDeleteFile(copyToPtr);
  2973 		    Tcl_DecrRefCount(copyToPtr);
  2974 		    return retVal;
  2975 		}
  2976 		/* 
  2977 		 * Try to delete the file immediately -- this is
  2978 		 * possible in some OSes, and avoids any worries
  2979 		 * about leaving the copy laying around on exit. 
  2980 		 */
  2981 		if (Tcl_FSDeleteFile(copyToPtr) == TCL_OK) {
  2982 		    Tcl_DecrRefCount(copyToPtr);
  2983 		    /* 
  2984 		     * We tell our caller about the real shared
  2985 		     * library which was loaded.  Note that this
  2986 		     * does mean that the package list maintained
  2987 		     * by 'load' will store the original (vfs)
  2988 		     * path alongside the temporary load handle
  2989 		     * and unload proc ptr.
  2990 		     */
  2991 		    (*handlePtr) = newLoadHandle;
  2992 		    (*unloadProcPtr) = newUnloadProcPtr;
  2993 		    return TCL_OK;
  2994 		}
  2995 		/* 
  2996 		 * When we unload this file, we need to divert the 
  2997 		 * unloading so we can unload and cleanup the 
  2998 		 * temporary file correctly.
  2999 		 */
  3000 		tvdlPtr = (FsDivertLoad*) ckalloc(sizeof(FsDivertLoad));
  3001 
  3002 		/* 
  3003 		 * Remember three pieces of information.  This allows
  3004 		 * us to cleanup the diverted load completely, on
  3005 		 * platforms which allow proper unloading of code.
  3006 		 */
  3007 		tvdlPtr->loadHandle = newLoadHandle;
  3008 		tvdlPtr->unloadProcPtr = newUnloadProcPtr;
  3009 
  3010 		if (copyFsPtr != &tclNativeFilesystem) {
  3011 		    /* copyToPtr is already incremented for this reference */
  3012 		    tvdlPtr->divertedFile = copyToPtr;
  3013 
  3014 		    /* 
  3015 		     * This is the filesystem we loaded it into.  Since
  3016 		     * we have a reference to 'copyToPtr', we already
  3017 		     * have a refCount on this filesystem, so we don't
  3018 		     * need to worry about it disappearing on us.
  3019 		     */
  3020 		    tvdlPtr->divertedFilesystem = copyFsPtr;
  3021 		    tvdlPtr->divertedFileNativeRep = NULL;
  3022 		} else {
  3023 		    /* We need the native rep */
  3024 		    tvdlPtr->divertedFileNativeRep = 
  3025 		      TclNativeDupInternalRep(Tcl_FSGetInternalRep(copyToPtr, 
  3026 								copyFsPtr));
  3027 		    /* 
  3028 		     * We don't need or want references to the copied
  3029 		     * Tcl_Obj or the filesystem if it is the native
  3030 		     * one.
  3031 		     */
  3032 		    tvdlPtr->divertedFile = NULL;
  3033 		    tvdlPtr->divertedFilesystem = NULL;
  3034 		    Tcl_DecrRefCount(copyToPtr);
  3035 		}
  3036 
  3037 		copyToPtr = NULL;
  3038 		(*handlePtr) = (Tcl_LoadHandle) tvdlPtr;
  3039 		(*unloadProcPtr) = &FSUnloadTempFile;
  3040 		return retVal;
  3041 	    } else {
  3042 		/* Cross-platform copy failed */
  3043 		Tcl_FSDeleteFile(copyToPtr);
  3044 		Tcl_DecrRefCount(copyToPtr);
  3045 		return TCL_ERROR;
  3046 	    }
  3047 	}
  3048     }
  3049     Tcl_SetErrno(ENOENT);
  3050     return -1;
  3051 }
  3052 /* 
  3053  * This function used to be in the platform specific directories, but it
  3054  * has now been made to work cross-platform
  3055  */
  3056 int
  3057 TclpLoadFile(interp, pathPtr, sym1, sym2, proc1Ptr, proc2Ptr, 
  3058 	     clientDataPtr, unloadProcPtr)
  3059     Tcl_Interp *interp;		/* Used for error reporting. */
  3060     Tcl_Obj *pathPtr;		/* Name of the file containing the desired
  3061 				 * code (UTF-8). */
  3062     CONST char *sym1, *sym2;	/* Names of two procedures to look up in
  3063 				 * the file's symbol table. */
  3064     Tcl_PackageInitProc **proc1Ptr, **proc2Ptr;
  3065 				/* Where to return the addresses corresponding
  3066 				 * to sym1 and sym2. */
  3067     ClientData *clientDataPtr;	/* Filled with token for dynamically loaded
  3068 				 * file which will be passed back to 
  3069 				 * (*unloadProcPtr)() to unload the file. */
  3070     Tcl_FSUnloadFileProc **unloadProcPtr;	
  3071 				/* Filled with address of Tcl_FSUnloadFileProc
  3072 				 * function which should be used for
  3073 				 * this file. */
  3074 {
  3075     Tcl_LoadHandle handle = NULL;
  3076     int res;
  3077     
  3078     res = TclpDlopen(interp, pathPtr, &handle, unloadProcPtr);
  3079     
  3080     if (res != TCL_OK) {
  3081         return res;
  3082     }
  3083 
  3084     if (handle == NULL) {
  3085 	return TCL_ERROR;
  3086     }
  3087     
  3088     *clientDataPtr = (ClientData)handle;
  3089     
  3090     *proc1Ptr = TclpFindSymbol(interp, handle, sym1);
  3091     *proc2Ptr = TclpFindSymbol(interp, handle, sym2);
  3092     return TCL_OK;
  3093 }
  3094 
  3095 /*
  3096  *---------------------------------------------------------------------------
  3097  *
  3098  * FSUnloadTempFile --
  3099  *
  3100  *	This function is called when we loaded a library of code via
  3101  *	an intermediate temporary file.  This function ensures
  3102  *	the library is correctly unloaded and the temporary file
  3103  *	is correctly deleted.
  3104  *
  3105  * Results:
  3106  *	None.
  3107  *
  3108  * Side effects:
  3109  *	The effects of the 'unload' function called, and of course
  3110  *	the temporary file will be deleted.
  3111  *
  3112  *---------------------------------------------------------------------------
  3113  */
  3114 static void 
  3115 FSUnloadTempFile(loadHandle)
  3116     Tcl_LoadHandle loadHandle; /* loadHandle returned by a previous call
  3117 			       * to Tcl_FSLoadFile().  The loadHandle is 
  3118 			       * a token that represents the loaded 
  3119 			       * file. */
  3120 {
  3121     FsDivertLoad *tvdlPtr = (FsDivertLoad*)loadHandle;
  3122     /* 
  3123      * This test should never trigger, since we give
  3124      * the client data in the function above.
  3125      */
  3126     if (tvdlPtr == NULL) { return; }
  3127     
  3128     /* 
  3129      * Call the real 'unloadfile' proc we actually used. It is very
  3130      * important that we call this first, so that the shared library
  3131      * is actually unloaded by the OS.  Otherwise, the following
  3132      * 'delete' may well fail because the shared library is still in
  3133      * use.
  3134      */
  3135     if (tvdlPtr->unloadProcPtr != NULL) {
  3136 	(*tvdlPtr->unloadProcPtr)(tvdlPtr->loadHandle);
  3137     }
  3138     
  3139     if (tvdlPtr->divertedFilesystem == NULL) {
  3140 	/* 
  3141 	 * It was the native filesystem, and we have a special
  3142 	 * function available just for this purpose, which we 
  3143 	 * know works even at this late stage.
  3144 	 */
  3145 	TclpDeleteFile(tvdlPtr->divertedFileNativeRep);
  3146 	NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep);
  3147     } else {
  3148 	/* 
  3149 	 * Remove the temporary file we created.  Note, we may crash
  3150 	 * here because encodings have been taken down already.
  3151 	 */
  3152 	if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile)
  3153 	    != TCL_OK) {
  3154 	    /* 
  3155 	     * The above may have failed because the filesystem, or something
  3156 	     * it depends upon (e.g. encodings) have been taken down because
  3157 	     * Tcl is exiting.
  3158 	     * 
  3159 	     * We may need to work out how to delete this file more
  3160 	     * robustly (or give the filesystem the information it needs
  3161 	     * to delete the file more robustly).
  3162 	     * 
  3163 	     * In particular, one problem might be that the filesystem
  3164 	     * cannot extract the information it needs from the above
  3165 	     * path object because Tcl's entire filesystem apparatus
  3166 	     * (the code in this file) has been finalized, and it
  3167 	     * refuses to pass the internal representation to the
  3168 	     * filesystem.
  3169 	     */
  3170 	}
  3171 	
  3172 	/* 
  3173 	 * And free up the allocations.  This will also of course remove
  3174 	 * a refCount from the Tcl_Filesystem to which this file belongs,
  3175 	 * which could then free up the filesystem if we are exiting.
  3176 	 */
  3177 	Tcl_DecrRefCount(tvdlPtr->divertedFile);
  3178     }
  3179 
  3180     ckfree((char*)tvdlPtr);
  3181 }
  3182 
  3183 /*
  3184  *---------------------------------------------------------------------------
  3185  *
  3186  * Tcl_FSLink --
  3187  *
  3188  *	This function replaces the library version of readlink() and
  3189  *	can also be used to make links.  The appropriate function for
  3190  *	the filesystem to which pathPtr belongs will be called.
  3191  *
  3192  * Results:
  3193  *      If toPtr is NULL, then the result is a Tcl_Obj specifying the 
  3194  *      contents of the symbolic link given by 'pathPtr', or NULL if
  3195  *      the symbolic link could not be read.  The result is owned by
  3196  *      the caller, which should call Tcl_DecrRefCount when the result
  3197  *      is no longer needed.
  3198  *      
  3199  *      If toPtr is non-NULL, then the result is toPtr if the link action
  3200  *      was successful, or NULL if not.  In this case the result has no
  3201  *      additional reference count, and need not be freed.  The actual
  3202  *      action to perform is given by the 'linkAction' flags, which is
  3203  *      an or'd combination of:
  3204  *      
  3205  *        TCL_CREATE_SYMBOLIC_LINK
  3206  *        TCL_CREATE_HARD_LINK
  3207  *      
  3208  *      Note that most filesystems will not support linking across
  3209  *      to different filesystems, so this function will usually
  3210  *      fail unless toPtr is in the same FS as pathPtr.
  3211  *      
  3212  * Side effects:
  3213  *	See readlink() documentation.  A new filesystem link 
  3214  *	object may appear
  3215  *
  3216  *---------------------------------------------------------------------------
  3217  */
  3218 
  3219 EXPORT_C Tcl_Obj *
  3220 Tcl_FSLink(pathPtr, toPtr, linkAction)
  3221     Tcl_Obj *pathPtr;		/* Path of file to readlink or link */
  3222     Tcl_Obj *toPtr;		/* NULL or path to be linked to */
  3223     int linkAction;             /* Action to perform */
  3224 {
  3225     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  3226     if (fsPtr != NULL) {
  3227 	Tcl_FSLinkProc *proc = fsPtr->linkProc;
  3228 	if (proc != NULL) {
  3229 	    return (*proc)(pathPtr, toPtr, linkAction);
  3230 	}
  3231     }
  3232     /*
  3233      * If S_IFLNK isn't defined it means that the machine doesn't
  3234      * support symbolic links, so the file can't possibly be a
  3235      * symbolic link.  Generate an EINVAL error, which is what
  3236      * happens on machines that do support symbolic links when
  3237      * you invoke readlink on a file that isn't a symbolic link.
  3238      */
  3239 #ifndef S_IFLNK
  3240     errno = EINVAL;
  3241 #else
  3242     Tcl_SetErrno(ENOENT);
  3243 #endif /* S_IFLNK */
  3244     return NULL;
  3245 }
  3246 
  3247 /*
  3248  *---------------------------------------------------------------------------
  3249  *
  3250  * Tcl_FSListVolumes --
  3251  *
  3252  *	Lists the currently mounted volumes.  The chain of functions
  3253  *	that have been "inserted" into the filesystem will be called in
  3254  *	succession; each may return a list of volumes, all of which are
  3255  *	added to the result until all mounted file systems are listed.
  3256  *	
  3257  *	Notice that we assume the lists returned by each filesystem
  3258  *	(if non NULL) have been given a refCount for us already.
  3259  *	However, we are NOT allowed to hang on to the list itself
  3260  *	(it belongs to the filesystem we called).  Therefore we
  3261  *	quite naturally add its contents to the result we are
  3262  *	building, and then decrement the refCount.
  3263  *
  3264  * Results:
  3265  *	The list of volumes, in an object which has refCount 0.
  3266  *
  3267  * Side effects:
  3268  *	None
  3269  *
  3270  *---------------------------------------------------------------------------
  3271  */
  3272 
  3273 EXPORT_C Tcl_Obj*
  3274 Tcl_FSListVolumes(void)
  3275 {
  3276     FilesystemRecord *fsRecPtr;
  3277     Tcl_Obj *resultPtr = Tcl_NewObj();
  3278     
  3279     /*
  3280      * Call each of the "listVolumes" function in succession.
  3281      * A non-NULL return value indicates the particular function has
  3282      * succeeded.  We call all the functions registered, since we want
  3283      * a list of all drives from all filesystems.
  3284      */
  3285 
  3286     fsRecPtr = FsGetFirstFilesystem();
  3287     while (fsRecPtr != NULL) {
  3288 	Tcl_FSListVolumesProc *proc = fsRecPtr->fsPtr->listVolumesProc;
  3289 	if (proc != NULL) {
  3290 	    Tcl_Obj *thisFsVolumes = (*proc)();
  3291 	    if (thisFsVolumes != NULL) {
  3292 		Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes);
  3293 		Tcl_DecrRefCount(thisFsVolumes);
  3294 	    }
  3295 	}
  3296 	fsRecPtr = fsRecPtr->nextPtr;
  3297     }
  3298     
  3299     return resultPtr;
  3300 }
  3301 
  3302 /*
  3303  *---------------------------------------------------------------------------
  3304  *
  3305  * FsListMounts --
  3306  *
  3307  *	List all mounts within the given directory, which match the
  3308  *	given pattern.
  3309  *
  3310  * Results:
  3311  *	The list of mounts, in a list object which has refCount 0, or
  3312  *	NULL if we didn't even find any filesystems to try to list
  3313  *	mounts.
  3314  *
  3315  * Side effects:
  3316  *	None
  3317  *
  3318  *---------------------------------------------------------------------------
  3319  */
  3320 
  3321 static Tcl_Obj*
  3322 FsListMounts(pathPtr, pattern)
  3323     Tcl_Obj *pathPtr;	        /* Contains path to directory to search. */
  3324     CONST char *pattern;	/* Pattern to match against. */
  3325 {
  3326     FilesystemRecord *fsRecPtr;
  3327     Tcl_GlobTypeData mountsOnly = { TCL_GLOB_TYPE_MOUNT, 0, NULL, NULL };
  3328     Tcl_Obj *resultPtr = NULL;
  3329     
  3330     /*
  3331      * Call each of the "listMounts" functions in succession.
  3332      * A non-NULL return value indicates the particular function has
  3333      * succeeded.  We call all the functions registered, since we want
  3334      * a list from each filesystems.
  3335      */
  3336 
  3337     fsRecPtr = FsGetFirstFilesystem();
  3338     while (fsRecPtr != NULL) {
  3339 	if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
  3340 	    Tcl_FSMatchInDirectoryProc *proc = 
  3341 				  fsRecPtr->fsPtr->matchInDirectoryProc;
  3342 	    if (proc != NULL) {
  3343 		if (resultPtr == NULL) {
  3344 		    resultPtr = Tcl_NewObj();
  3345 		}
  3346 		(*proc)(NULL, resultPtr, pathPtr, pattern, &mountsOnly);
  3347 	    }
  3348 	}
  3349 	fsRecPtr = fsRecPtr->nextPtr;
  3350     }
  3351     
  3352     return resultPtr;
  3353 }
  3354 
  3355 /*
  3356  *---------------------------------------------------------------------------
  3357  *
  3358  * Tcl_FSSplitPath --
  3359  *
  3360  *      This function takes the given Tcl_Obj, which should be a valid
  3361  *      path, and returns a Tcl List object containing each segment of
  3362  *      that path as an element.
  3363  *
  3364  * Results:
  3365  *      Returns list object with refCount of zero.  If the passed in
  3366  *      lenPtr is non-NULL, we use it to return the number of elements
  3367  *      in the returned list.
  3368  *
  3369  * Side effects:
  3370  *	None.
  3371  *
  3372  *---------------------------------------------------------------------------
  3373  */
  3374 
  3375 EXPORT_C Tcl_Obj* 
  3376 Tcl_FSSplitPath(pathPtr, lenPtr)
  3377     Tcl_Obj *pathPtr;		/* Path to split. */
  3378     int *lenPtr;		/* int to store number of path elements. */
  3379 {
  3380     Tcl_Obj *result = NULL;  /* Needed only to prevent gcc warnings. */
  3381     Tcl_Filesystem *fsPtr;
  3382     char separator = '/';
  3383     int driveNameLength;
  3384     char *p;
  3385     
  3386     /*
  3387      * Perform platform specific splitting. 
  3388      */
  3389 
  3390     if (FSGetPathType(pathPtr, &fsPtr, &driveNameLength) 
  3391 	== TCL_PATH_ABSOLUTE) {
  3392 	if (fsPtr == &tclNativeFilesystem) {
  3393 	    return TclpNativeSplitPath(pathPtr, lenPtr);
  3394 	}
  3395     } else {
  3396 	return TclpNativeSplitPath(pathPtr, lenPtr);
  3397     }
  3398 
  3399     /* We assume separators are single characters */
  3400     if (fsPtr->filesystemSeparatorProc != NULL) {
  3401 	Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(pathPtr);
  3402 	if (sep != NULL) {
  3403 	    separator = Tcl_GetString(sep)[0];
  3404 	}
  3405     }
  3406     
  3407     /* 
  3408      * Place the drive name as first element of the
  3409      * result list.  The drive name may contain strange
  3410      * characters, like colons and multiple forward slashes
  3411      * (for example 'ftp://' is a valid vfs drive name)
  3412      */
  3413     result = Tcl_NewObj();
  3414     p = Tcl_GetString(pathPtr);
  3415     Tcl_ListObjAppendElement(NULL, result, 
  3416 			     Tcl_NewStringObj(p, driveNameLength));
  3417     p+= driveNameLength;
  3418     			
  3419     /* Add the remaining path elements to the list */
  3420     for (;;) {
  3421 	char *elementStart = p;
  3422 	int length;
  3423 	while ((*p != '\0') && (*p != separator)) {
  3424 	    p++;
  3425 	}
  3426 	length = p - elementStart;
  3427 	if (length > 0) {
  3428 	    Tcl_Obj *nextElt;
  3429 	    if (elementStart[0] == '~') {
  3430 		nextElt = Tcl_NewStringObj("./",2);
  3431 		Tcl_AppendToObj(nextElt, elementStart, length);
  3432 	    } else {
  3433 		nextElt = Tcl_NewStringObj(elementStart, length);
  3434 	    }
  3435 	    Tcl_ListObjAppendElement(NULL, result, nextElt);
  3436 	}
  3437 	if (*p++ == '\0') {
  3438 	    break;
  3439 	}
  3440     }
  3441 			     
  3442     /*
  3443      * Compute the number of elements in the result.
  3444      */
  3445 
  3446     if (lenPtr != NULL) {
  3447 	Tcl_ListObjLength(NULL, result, lenPtr);
  3448     }
  3449     return result;
  3450 }
  3451 
  3452 /* Simple helper function */
  3453 Tcl_Obj* 
  3454 TclFSInternalToNormalized(fromFilesystem, clientData, fsRecPtrPtr)
  3455     Tcl_Filesystem *fromFilesystem;
  3456     ClientData clientData;
  3457     FilesystemRecord **fsRecPtrPtr;
  3458 {
  3459     FilesystemRecord *fsRecPtr = FsGetFirstFilesystem();
  3460 
  3461     while (fsRecPtr != NULL) {
  3462 	if (fsRecPtr->fsPtr == fromFilesystem) {
  3463 	    *fsRecPtrPtr = fsRecPtr;
  3464 	    break;
  3465 	}
  3466 	fsRecPtr = fsRecPtr->nextPtr;
  3467     }
  3468     
  3469     if ((fsRecPtr != NULL) 
  3470       && (fromFilesystem->internalToNormalizedProc != NULL)) {
  3471 	return (*fromFilesystem->internalToNormalizedProc)(clientData);
  3472     } else {
  3473 	return NULL;
  3474     }
  3475 }
  3476 
  3477 /*
  3478  *----------------------------------------------------------------------
  3479  *
  3480  * GetPathType --
  3481  *
  3482  *	Helper function used by FSGetPathType.
  3483  *
  3484  * Results:
  3485  *	Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
  3486  *	TCL_PATH_VOLUME_RELATIVE.  The filesystem reference will
  3487  *	be set if and only if it is non-NULL and the function's 
  3488  *	return value is TCL_PATH_ABSOLUTE.
  3489  *
  3490  * Side effects:
  3491  *	None.
  3492  *
  3493  *----------------------------------------------------------------------
  3494  */
  3495 
  3496 static Tcl_PathType
  3497 GetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr, driveNameRef)
  3498     Tcl_Obj *pathObjPtr;
  3499     Tcl_Filesystem **filesystemPtrPtr;
  3500     int *driveNameLengthPtr;
  3501     Tcl_Obj **driveNameRef;
  3502 {
  3503     FilesystemRecord *fsRecPtr;
  3504     int pathLen;
  3505     char *path;
  3506     Tcl_PathType type = TCL_PATH_RELATIVE;
  3507     
  3508     path = Tcl_GetStringFromObj(pathObjPtr, &pathLen);
  3509 
  3510     /*
  3511      * Call each of the "listVolumes" function in succession, checking
  3512      * whether the given path is an absolute path on any of the volumes
  3513      * returned (this is done by checking whether the path's prefix
  3514      * matches).
  3515      */
  3516 
  3517     fsRecPtr = FsGetFirstFilesystem();
  3518     while (fsRecPtr != NULL) {
  3519 	Tcl_FSListVolumesProc *proc = fsRecPtr->fsPtr->listVolumesProc;
  3520 	/* 
  3521 	 * We want to skip the native filesystem in this loop because
  3522 	 * otherwise we won't necessarily pass all the Tcl testsuite --
  3523 	 * this is because some of the tests artificially change the
  3524 	 * current platform (between mac, win, unix) but the list
  3525 	 * of volumes we get by calling (*proc) will reflect the current
  3526 	 * (real) platform only and this may cause some tests to fail.
  3527 	 * In particular, on unix '/' will match the beginning of 
  3528 	 * certain absolute Windows paths starting '//' and those tests
  3529 	 * will go wrong.
  3530 	 * 
  3531 	 * Besides these test-suite issues, there is one other reason
  3532 	 * to skip the native filesystem --- since the tclFilename.c
  3533 	 * code has nice fast 'absolute path' checkers, we don't want
  3534 	 * to waste time repeating that effort here, and this 
  3535 	 * function is actually called quite often, so if we can
  3536 	 * save the overhead of the native filesystem returning us
  3537 	 * a list of volumes all the time, it is better.
  3538 	 */
  3539 	if ((fsRecPtr->fsPtr != &tclNativeFilesystem) && (proc != NULL)) {
  3540 	    int numVolumes;
  3541 	    Tcl_Obj *thisFsVolumes = (*proc)();
  3542 	    if (thisFsVolumes != NULL) {
  3543 		if (Tcl_ListObjLength(NULL, thisFsVolumes, 
  3544 				      &numVolumes) != TCL_OK) {
  3545 		    /* 
  3546 		     * This is VERY bad; the Tcl_FSListVolumesProc
  3547 		     * didn't return a valid list.  Set numVolumes to
  3548 		     * -1 so that we skip the while loop below and just
  3549 		     * return with the current value of 'type'.
  3550 		     * 
  3551 		     * It would be better if we could signal an error
  3552 		     * here (but panic seems a bit excessive).
  3553 		     */
  3554 		    numVolumes = -1;
  3555 		}
  3556 		while (numVolumes > 0) {
  3557 		    Tcl_Obj *vol;
  3558 		    int len;
  3559 		    char *strVol;
  3560 
  3561 		    numVolumes--;
  3562 		    Tcl_ListObjIndex(NULL, thisFsVolumes, numVolumes, &vol);
  3563 		    strVol = Tcl_GetStringFromObj(vol,&len);
  3564 		    if (pathLen < len) {
  3565 			continue;
  3566 		    }
  3567 		    if (strncmp(strVol, path, (size_t) len) == 0) {
  3568 			type = TCL_PATH_ABSOLUTE;
  3569 			if (filesystemPtrPtr != NULL) {
  3570 			    *filesystemPtrPtr = fsRecPtr->fsPtr;
  3571 			}
  3572 			if (driveNameLengthPtr != NULL) {
  3573 			    *driveNameLengthPtr = len;
  3574 			}
  3575 			if (driveNameRef != NULL) {
  3576 			    *driveNameRef = vol;
  3577 			    Tcl_IncrRefCount(vol);
  3578 			}
  3579 			break;
  3580 		    }
  3581 		}
  3582 		Tcl_DecrRefCount(thisFsVolumes);
  3583 		if (type == TCL_PATH_ABSOLUTE) {
  3584 		    /* We don't need to examine any more filesystems */
  3585 		    break;
  3586 		}
  3587 	    }
  3588 	}
  3589 	fsRecPtr = fsRecPtr->nextPtr;
  3590     }
  3591     
  3592     if (type != TCL_PATH_ABSOLUTE) {
  3593 	type = TclpGetNativePathType(pathObjPtr, driveNameLengthPtr, 
  3594 				     driveNameRef);
  3595 	if ((type == TCL_PATH_ABSOLUTE) && (filesystemPtrPtr != NULL)) {
  3596 	    *filesystemPtrPtr = &tclNativeFilesystem;
  3597 	}
  3598     }
  3599     return type;
  3600 }
  3601 
  3602 /*
  3603  *---------------------------------------------------------------------------
  3604  *
  3605  * Tcl_FSRenameFile --
  3606  *
  3607  *	If the two paths given belong to the same filesystem, we call
  3608  *	that filesystems rename function.  Otherwise we simply
  3609  *	return the posix error 'EXDEV', and -1.
  3610  *
  3611  * Results:
  3612  *      Standard Tcl error code if a function was called.
  3613  *
  3614  * Side effects:
  3615  *	A file may be renamed.
  3616  *
  3617  *---------------------------------------------------------------------------
  3618  */
  3619 
  3620 EXPORT_C int
  3621 Tcl_FSRenameFile(srcPathPtr, destPathPtr)
  3622     Tcl_Obj* srcPathPtr;	/* Pathname of file or dir to be renamed
  3623 				 * (UTF-8). */
  3624     Tcl_Obj *destPathPtr;	/* New pathname of file or directory
  3625 				 * (UTF-8). */
  3626 {
  3627     int retVal = -1;
  3628     Tcl_Filesystem *fsPtr, *fsPtr2;
  3629     fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
  3630     fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
  3631 
  3632     if (fsPtr == fsPtr2 && fsPtr != NULL) {
  3633 	Tcl_FSRenameFileProc *proc = fsPtr->renameFileProc;
  3634 	if (proc != NULL) {
  3635 	    retVal =  (*proc)(srcPathPtr, destPathPtr);
  3636 	}
  3637     }
  3638     if (retVal == -1) {
  3639 	Tcl_SetErrno(EXDEV);
  3640     }
  3641     return retVal;
  3642 }
  3643 
  3644 /*
  3645  *---------------------------------------------------------------------------
  3646  *
  3647  * Tcl_FSCopyFile --
  3648  *
  3649  *	If the two paths given belong to the same filesystem, we call
  3650  *	that filesystem's copy function.  Otherwise we simply
  3651  *	return the posix error 'EXDEV', and -1.
  3652  *	
  3653  *	Note that in the native filesystems, 'copyFileProc' is defined
  3654  *	to copy soft links (i.e. it copies the links themselves, not
  3655  *	the things they point to).
  3656  *
  3657  * Results:
  3658  *      Standard Tcl error code if a function was called.
  3659  *
  3660  * Side effects:
  3661  *	A file may be copied.
  3662  *
  3663  *---------------------------------------------------------------------------
  3664  */
  3665 
  3666 EXPORT_C int 
  3667 Tcl_FSCopyFile(srcPathPtr, destPathPtr)
  3668     Tcl_Obj* srcPathPtr;	/* Pathname of file to be copied (UTF-8). */
  3669     Tcl_Obj *destPathPtr;	/* Pathname of file to copy to (UTF-8). */
  3670 {
  3671     int retVal = -1;
  3672     Tcl_Filesystem *fsPtr, *fsPtr2;
  3673     fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
  3674     fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
  3675 
  3676     if (fsPtr == fsPtr2 && fsPtr != NULL) {
  3677 	Tcl_FSCopyFileProc *proc = fsPtr->copyFileProc;
  3678 	if (proc != NULL) {
  3679 	    retVal = (*proc)(srcPathPtr, destPathPtr);
  3680 	}
  3681     }
  3682     if (retVal == -1) {
  3683 	Tcl_SetErrno(EXDEV);
  3684     }
  3685     return retVal;
  3686 }
  3687 
  3688 /*
  3689  *---------------------------------------------------------------------------
  3690  *
  3691  * TclCrossFilesystemCopy --
  3692  *
  3693  *	Helper for above function, and for Tcl_FSLoadFile, to copy
  3694  *	files from one filesystem to another.  This function will
  3695  *	overwrite the target file if it already exists.
  3696  *
  3697  * Results:
  3698  *      Standard Tcl error code.
  3699  *
  3700  * Side effects:
  3701  *	A file may be created.
  3702  *
  3703  *---------------------------------------------------------------------------
  3704  */
  3705 int 
  3706 TclCrossFilesystemCopy(interp, source, target) 
  3707     Tcl_Interp *interp; /* For error messages */
  3708     Tcl_Obj *source;	/* Pathname of file to be copied (UTF-8). */
  3709     Tcl_Obj *target;	/* Pathname of file to copy to (UTF-8). */
  3710 {
  3711     int result = TCL_ERROR;
  3712     int prot = 0666;
  3713     
  3714     Tcl_Channel out = Tcl_FSOpenFileChannel(interp, target, "w", prot);
  3715     if (out != NULL) {
  3716 	/* It looks like we can copy it over */
  3717 	Tcl_Channel in = Tcl_FSOpenFileChannel(interp, source, 
  3718 					       "r", prot);
  3719 	if (in == NULL) {
  3720 	    /* This is very strange, we checked this above */
  3721 	    Tcl_Close(interp, out);
  3722 	} else {
  3723 	    Tcl_StatBuf sourceStatBuf;
  3724 	    struct utimbuf tval;
  3725 	    /* 
  3726 	     * Copy it synchronously.  We might wish to add an
  3727 	     * asynchronous option to support vfs's which are
  3728 	     * slow (e.g. network sockets).
  3729 	     */
  3730 	    Tcl_SetChannelOption(interp, in, "-translation", "binary");
  3731 	    Tcl_SetChannelOption(interp, out, "-translation", "binary");
  3732 	    
  3733 	    if (TclCopyChannel(interp, in, out, -1, NULL) == TCL_OK) {
  3734 		result = TCL_OK;
  3735 	    }
  3736 	    /* 
  3737 	     * If the copy failed, assume that copy channel left
  3738 	     * a good error message.
  3739 	     */
  3740 	    Tcl_Close(interp, in);
  3741 	    Tcl_Close(interp, out);
  3742 	    
  3743 	    /* Set modification date of copied file */
  3744 	    if (Tcl_FSLstat(source, &sourceStatBuf) == 0) {
  3745 		tval.actime = sourceStatBuf.st_atime;
  3746 		tval.modtime = sourceStatBuf.st_mtime;
  3747 		Tcl_FSUtime(target, &tval);
  3748 	    }
  3749 	}
  3750     }
  3751     return result;
  3752 }
  3753 
  3754 /*
  3755  *---------------------------------------------------------------------------
  3756  *
  3757  * Tcl_FSDeleteFile --
  3758  *
  3759  *	The appropriate function for the filesystem to which pathPtr
  3760  *	belongs will be called.
  3761  *
  3762  * Results:
  3763  *      Standard Tcl error code.
  3764  *
  3765  * Side effects:
  3766  *	A file may be deleted.
  3767  *
  3768  *---------------------------------------------------------------------------
  3769  */
  3770 
  3771 EXPORT_C int
  3772 Tcl_FSDeleteFile(pathPtr)
  3773     Tcl_Obj *pathPtr;		/* Pathname of file to be removed (UTF-8). */
  3774 {
  3775     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  3776     if (fsPtr != NULL) {
  3777 	Tcl_FSDeleteFileProc *proc = fsPtr->deleteFileProc;
  3778 	if (proc != NULL) {
  3779 	    return (*proc)(pathPtr);
  3780 	}
  3781     }
  3782     Tcl_SetErrno(ENOENT);
  3783     return -1;
  3784 }
  3785 
  3786 /*
  3787  *---------------------------------------------------------------------------
  3788  *
  3789  * Tcl_FSCreateDirectory --
  3790  *
  3791  *	The appropriate function for the filesystem to which pathPtr
  3792  *	belongs will be called.
  3793  *
  3794  * Results:
  3795  *      Standard Tcl error code.
  3796  *
  3797  * Side effects:
  3798  *	A directory may be created.
  3799  *
  3800  *---------------------------------------------------------------------------
  3801  */
  3802 
  3803 EXPORT_C int
  3804 Tcl_FSCreateDirectory(pathPtr)
  3805     Tcl_Obj *pathPtr;		/* Pathname of directory to create (UTF-8). */
  3806 {
  3807     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  3808     if (fsPtr != NULL) {
  3809 	Tcl_FSCreateDirectoryProc *proc = fsPtr->createDirectoryProc;
  3810 	if (proc != NULL) {
  3811 	    return (*proc)(pathPtr);
  3812 	}
  3813     }
  3814     Tcl_SetErrno(ENOENT);
  3815     return -1;
  3816 }
  3817 
  3818 /*
  3819  *---------------------------------------------------------------------------
  3820  *
  3821  * Tcl_FSCopyDirectory --
  3822  *
  3823  *	If the two paths given belong to the same filesystem, we call
  3824  *	that filesystems copy-directory function.  Otherwise we simply
  3825  *	return the posix error 'EXDEV', and -1.
  3826  *
  3827  * Results:
  3828  *      Standard Tcl error code if a function was called.
  3829  *
  3830  * Side effects:
  3831  *	A directory may be copied.
  3832  *
  3833  *---------------------------------------------------------------------------
  3834  */
  3835 
  3836 EXPORT_C int
  3837 Tcl_FSCopyDirectory(srcPathPtr, destPathPtr, errorPtr)
  3838     Tcl_Obj* srcPathPtr;	/* Pathname of directory to be copied
  3839 				 * (UTF-8). */
  3840     Tcl_Obj *destPathPtr;	/* Pathname of target directory (UTF-8). */
  3841     Tcl_Obj **errorPtr;	        /* If non-NULL, then will be set to a
  3842                        	         * new object containing name of file
  3843                        	         * causing error, with refCount 1. */
  3844 {
  3845     int retVal = -1;
  3846     Tcl_Filesystem *fsPtr, *fsPtr2;
  3847     fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
  3848     fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
  3849 
  3850     if (fsPtr == fsPtr2 && fsPtr != NULL) {
  3851 	Tcl_FSCopyDirectoryProc *proc = fsPtr->copyDirectoryProc;
  3852 	if (proc != NULL) {
  3853 	    retVal = (*proc)(srcPathPtr, destPathPtr, errorPtr);
  3854 	}
  3855     }
  3856     if (retVal == -1) {
  3857 	Tcl_SetErrno(EXDEV);
  3858     }
  3859     return retVal;
  3860 }
  3861 
  3862 /*
  3863  *---------------------------------------------------------------------------
  3864  *
  3865  * Tcl_FSRemoveDirectory --
  3866  *
  3867  *	The appropriate function for the filesystem to which pathPtr
  3868  *	belongs will be called.
  3869  *
  3870  * Results:
  3871  *      Standard Tcl error code.
  3872  *
  3873  * Side effects:
  3874  *	A directory may be deleted.
  3875  *
  3876  *---------------------------------------------------------------------------
  3877  */
  3878 
  3879 EXPORT_C int
  3880 Tcl_FSRemoveDirectory(pathPtr, recursive, errorPtr)
  3881     Tcl_Obj *pathPtr;		/* Pathname of directory to be removed
  3882 				 * (UTF-8). */
  3883     int recursive;		/* If non-zero, removes directories that
  3884 				 * are nonempty.  Otherwise, will only remove
  3885 				 * empty directories. */
  3886     Tcl_Obj **errorPtr;	        /* If non-NULL, then will be set to a
  3887 				 * new object containing name of file
  3888 				 * causing error, with refCount 1. */
  3889 {
  3890     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
  3891     if (fsPtr != NULL) {
  3892 	Tcl_FSRemoveDirectoryProc *proc = fsPtr->removeDirectoryProc;
  3893 	if (proc != NULL) {
  3894 	    if (recursive) {
  3895 	        /* 
  3896 	         * We check whether the cwd lies inside this directory
  3897 	         * and move it if it does.
  3898 	         */
  3899 		Tcl_Obj *cwdPtr = Tcl_FSGetCwd(NULL);
  3900 		if (cwdPtr != NULL) {
  3901 		    char *cwdStr, *normPathStr;
  3902 		    int cwdLen, normLen;
  3903 		    Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr);
  3904 		    if (normPath != NULL) {
  3905 		        normPathStr = Tcl_GetStringFromObj(normPath, &normLen);
  3906 			cwdStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen);
  3907 			if ((cwdLen >= normLen) && (strncmp(normPathStr, 
  3908 					cwdStr, (size_t) normLen) == 0)) {
  3909 			    /* 
  3910 			     * the cwd is inside the directory, so we
  3911 			     * perform a 'cd [file dirname $path]'
  3912 			     */
  3913 			    Tcl_Obj *dirPtr = TclFileDirname(NULL, pathPtr);
  3914 			    Tcl_FSChdir(dirPtr);
  3915 			    Tcl_DecrRefCount(dirPtr);
  3916 			}
  3917 		    }
  3918 		    Tcl_DecrRefCount(cwdPtr);
  3919 		}
  3920 	    }
  3921 	    return (*proc)(pathPtr, recursive, errorPtr);
  3922 	}
  3923     }
  3924     Tcl_SetErrno(ENOENT);
  3925     return -1;
  3926 }
  3927 
  3928 /*
  3929  *---------------------------------------------------------------------------
  3930  *
  3931  * Tcl_FSGetFileSystemForPath --
  3932  *
  3933  *      This function determines which filesystem to use for a
  3934  *      particular path object, and returns the filesystem which
  3935  *      accepts this file.  If no filesystem will accept this object
  3936  *      as a valid file path, then NULL is returned.
  3937  *
  3938  * Results:
  3939 .*      NULL or a filesystem which will accept this path.
  3940  *
  3941  * Side effects:
  3942  *	The object may be converted to a path type.
  3943  *
  3944  *---------------------------------------------------------------------------
  3945  */
  3946 
  3947 EXPORT_C Tcl_Filesystem*
  3948 Tcl_FSGetFileSystemForPath(pathObjPtr)
  3949     Tcl_Obj* pathObjPtr;
  3950 {
  3951     FilesystemRecord *fsRecPtr;
  3952     Tcl_Filesystem* retVal = NULL;
  3953     
  3954     /* 
  3955      * If the object has a refCount of zero, we reject it.  This
  3956      * is to avoid possible segfaults or nondeterministic memory
  3957      * leaks (i.e. the user doesn't know if they should decrement
  3958      * the ref count on return or not).
  3959      */
  3960     
  3961     if (pathObjPtr->refCount == 0) {
  3962 	panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0");
  3963 	return NULL;
  3964     }
  3965     
  3966     /* 
  3967      * Check if the filesystem has changed in some way since
  3968      * this object's internal representation was calculated.
  3969      * Before doing that, assure we have the most up-to-date
  3970      * copy of the master filesystem. This is accomplished
  3971      * by the FsGetFirstFilesystem() call.
  3972      */
  3973 
  3974     fsRecPtr = FsGetFirstFilesystem();
  3975 
  3976     if (TclFSEnsureEpochOk(pathObjPtr, &retVal) != TCL_OK) {
  3977 	return NULL;
  3978     }
  3979 
  3980     /*
  3981      * Call each of the "pathInFilesystem" functions in succession.  A
  3982      * non-return value of -1 indicates the particular function has
  3983      * succeeded.
  3984      */
  3985 
  3986     while ((retVal == NULL) && (fsRecPtr != NULL)) {
  3987 	Tcl_FSPathInFilesystemProc *proc = fsRecPtr->fsPtr->pathInFilesystemProc;
  3988 	if (proc != NULL) {
  3989 	    ClientData clientData = NULL;
  3990 	    int ret = (*proc)(pathObjPtr, &clientData);
  3991 	    if (ret != -1) {
  3992 		/* 
  3993 		 * We assume the type of pathObjPtr hasn't been changed 
  3994 		 * by the above call to the pathInFilesystemProc.
  3995 		 */
  3996 		TclFSSetPathDetails(pathObjPtr, fsRecPtr, clientData);
  3997 		retVal = fsRecPtr->fsPtr;
  3998 	    }
  3999 	}
  4000 	fsRecPtr = fsRecPtr->nextPtr;
  4001     }
  4002 
  4003     return retVal;
  4004 }
  4005 
  4006 /*
  4007  *---------------------------------------------------------------------------
  4008  *
  4009  * Tcl_FSGetNativePath --
  4010  *
  4011  *      This function is for use by the Win/Unix/MacOS native filesystems,
  4012  *      so that they can easily retrieve the native (char* or TCHAR*)
  4013  *      representation of a path.  Other filesystems will probably
  4014  *      want to implement similar functions.  They basically act as a 
  4015  *      safety net around Tcl_FSGetInternalRep.  Normally your file-
  4016  *      system procedures will always be called with path objects
  4017  *      already converted to the correct filesystem, but if for 
  4018  *      some reason they are called directly (i.e. by procedures 
  4019  *      not in this file), then one cannot necessarily guarantee that
  4020  *      the path object pointer is from the correct filesystem.
  4021  *      
  4022  *      Note: in the future it might be desireable to have separate
  4023  *      versions of this function with different signatures, for
  4024  *      example Tcl_FSGetNativeMacPath, Tcl_FSGetNativeUnixPath etc.
  4025  *      Right now, since native paths are all string based, we use just
  4026  *      one function.  On MacOS we could possibly use an FSSpec or
  4027  *      FSRef as the native representation.
  4028  *
  4029  * Results:
  4030  *      NULL or a valid native path.
  4031  *
  4032  * Side effects:
  4033  *	See Tcl_FSGetInternalRep.
  4034  *
  4035  *---------------------------------------------------------------------------
  4036  */
  4037 
  4038 EXPORT_C CONST char *
  4039 Tcl_FSGetNativePath(pathObjPtr)
  4040     Tcl_Obj *pathObjPtr;
  4041 {
  4042     return (CONST char *)Tcl_FSGetInternalRep(pathObjPtr, &tclNativeFilesystem);
  4043 }
  4044 
  4045 /*
  4046  *---------------------------------------------------------------------------
  4047  *
  4048  * NativeCreateNativeRep --
  4049  *
  4050  *      Create a native representation for the given path.
  4051  *
  4052  * Results:
  4053  *      None.
  4054  *
  4055  * Side effects:
  4056  *	None.
  4057  *
  4058  *---------------------------------------------------------------------------
  4059  */
  4060 static ClientData 
  4061 NativeCreateNativeRep(pathObjPtr)
  4062     Tcl_Obj* pathObjPtr;
  4063 {
  4064     char *nativePathPtr;
  4065     Tcl_DString ds;
  4066     Tcl_Obj* validPathObjPtr;
  4067     int len;
  4068     char *str;
  4069 
  4070     /* Make sure the normalized path is set */
  4071     validPathObjPtr = Tcl_FSGetNormalizedPath(NULL, pathObjPtr);
  4072     if (validPathObjPtr == NULL) {
  4073 	return NULL;
  4074     }
  4075 
  4076     str = Tcl_GetStringFromObj(validPathObjPtr, &len);
  4077 #ifdef __WIN32__
  4078     Tcl_WinUtfToTChar(str, len, &ds);
  4079     if (tclWinProcs->useWide) {
  4080 	len = Tcl_DStringLength(&ds) + sizeof(WCHAR);
  4081     } else {
  4082 	len = Tcl_DStringLength(&ds) + sizeof(char);
  4083     }
  4084 #else
  4085     Tcl_UtfToExternalDString(NULL, str, len, &ds);
  4086     len = Tcl_DStringLength(&ds) + sizeof(char);
  4087 #endif
  4088     nativePathPtr = ckalloc((unsigned) len);
  4089     memcpy((VOID*)nativePathPtr, (VOID*)Tcl_DStringValue(&ds), (size_t) len);
  4090 
  4091     Tcl_DStringFree(&ds);
  4092     return (ClientData)nativePathPtr;
  4093 }
  4094 
  4095 /*
  4096  *---------------------------------------------------------------------------
  4097  *
  4098  * TclpNativeToNormalized --
  4099  *
  4100  *      Convert native format to a normalized path object, with refCount
  4101  *      of zero.
  4102  *
  4103  * Results:
  4104  *      A valid normalized path.
  4105  *
  4106  * Side effects:
  4107  *	None.
  4108  *
  4109  *---------------------------------------------------------------------------
  4110  */
  4111 Tcl_Obj* 
  4112 TclpNativeToNormalized(clientData)
  4113     ClientData clientData;
  4114 {
  4115     Tcl_DString ds;
  4116     Tcl_Obj *objPtr;
  4117     CONST char *copy;
  4118     int len;
  4119     
  4120 #ifdef __WIN32__
  4121     Tcl_WinTCharToUtf((CONST char*)clientData, -1, &ds);
  4122 #else
  4123     Tcl_ExternalToUtfDString(NULL, (CONST char*)clientData, -1, &ds);
  4124 #endif
  4125     
  4126     copy = Tcl_DStringValue(&ds);
  4127     len = Tcl_DStringLength(&ds);
  4128 
  4129 #ifdef __WIN32__
  4130     /* 
  4131      * Certain native path representations on Windows have this special
  4132      * prefix to indicate that they are to be treated specially.  For
  4133      * example extremely long paths, or symlinks 
  4134      */
  4135     if (*copy == '\\') {
  4136         if (0 == strncmp(copy,"\\??\\",4)) {
  4137 	    copy += 4;
  4138 	    len -= 4;
  4139 	} else if (0 == strncmp(copy,"\\\\?\\",4)) {
  4140 	    copy += 4;
  4141 	    len -= 4;
  4142 	}
  4143     }
  4144 #endif
  4145 
  4146     objPtr = Tcl_NewStringObj(copy,len);
  4147     Tcl_DStringFree(&ds);
  4148     
  4149     return objPtr;
  4150 }
  4151 
  4152 
  4153 /*
  4154  *---------------------------------------------------------------------------
  4155  *
  4156  * TclNativeDupInternalRep --
  4157  *
  4158  *      Duplicate the native representation.
  4159  *
  4160  * Results:
  4161  *      The copied native representation, or NULL if it is not possible
  4162  *      to copy the representation.
  4163  *
  4164  * Side effects:
  4165  *	None.
  4166  *
  4167  *---------------------------------------------------------------------------
  4168  */
  4169 ClientData 
  4170 TclNativeDupInternalRep(clientData)
  4171     ClientData clientData;
  4172 {
  4173     ClientData copy;
  4174     size_t len;
  4175 
  4176     if (clientData == NULL) {
  4177 	return NULL;
  4178     }
  4179 
  4180 #ifdef __WIN32__
  4181     if (tclWinProcs->useWide) {
  4182 	/* unicode representation when running on NT/2K/XP */
  4183 	len = sizeof(WCHAR) + (wcslen((CONST WCHAR*)clientData) * sizeof(WCHAR));
  4184     } else {
  4185 	/* ansi representation when running on 95/98/ME */
  4186 	len = sizeof(char) + (strlen((CONST char*)clientData) * sizeof(char));
  4187     }
  4188 #else
  4189     /* ansi representation when running on Unix/MacOS */
  4190     len = sizeof(char) + (strlen((CONST char*)clientData) * sizeof(char));
  4191 #endif
  4192     
  4193     copy = (ClientData) ckalloc(len);
  4194     memcpy((VOID*)copy, (VOID*)clientData, len);
  4195     return copy;
  4196 }
  4197 
  4198 /*
  4199  *---------------------------------------------------------------------------
  4200  *
  4201  * NativeFreeInternalRep --
  4202  *
  4203  *      Free a native internal representation, which will be non-NULL.
  4204  *
  4205  * Results:
  4206  *      None.
  4207  *
  4208  * Side effects:
  4209  *	Memory is released.
  4210  *
  4211  *---------------------------------------------------------------------------
  4212  */
  4213 static void 
  4214 NativeFreeInternalRep(clientData)
  4215     ClientData clientData;
  4216 {
  4217     ckfree((char*)clientData);
  4218 }
  4219 
  4220 /*
  4221  *---------------------------------------------------------------------------
  4222  *
  4223  * Tcl_FSFileSystemInfo --
  4224  *
  4225  *      This function returns a list of two elements.  The first
  4226  *      element is the name of the filesystem (e.g. "native" or "vfs"),
  4227  *      and the second is the particular type of the given path within
  4228  *      that filesystem.
  4229  *
  4230  * Results:
  4231  *      A list of two elements.
  4232  *
  4233  * Side effects:
  4234  *	The object may be converted to a path type.
  4235  *
  4236  *---------------------------------------------------------------------------
  4237  */
  4238 EXPORT_C Tcl_Obj*
  4239 Tcl_FSFileSystemInfo(pathObjPtr)
  4240     Tcl_Obj* pathObjPtr;
  4241 {
  4242     Tcl_Obj *resPtr;
  4243     Tcl_FSFilesystemPathTypeProc *proc;
  4244     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathObjPtr);
  4245     
  4246     if (fsPtr == NULL) {
  4247 	return NULL;
  4248     }
  4249     
  4250     resPtr = Tcl_NewListObj(0,NULL);
  4251     
  4252     Tcl_ListObjAppendElement(NULL, resPtr, 
  4253 			     Tcl_NewStringObj(fsPtr->typeName,-1));
  4254 
  4255     proc = fsPtr->filesystemPathTypeProc;
  4256     if (proc != NULL) {
  4257 	Tcl_Obj *typePtr = (*proc)(pathObjPtr);
  4258 	if (typePtr != NULL) {
  4259 	    Tcl_ListObjAppendElement(NULL, resPtr, typePtr);
  4260 	}
  4261     }
  4262     
  4263     return resPtr;
  4264 }
  4265 
  4266 /*
  4267  *---------------------------------------------------------------------------
  4268  *
  4269  * Tcl_FSPathSeparator --
  4270  *
  4271  *      This function returns the separator to be used for a given
  4272  *      path.  The object returned should have a refCount of zero
  4273  *
  4274  * Results:
  4275  *      A Tcl object, with a refCount of zero.  If the caller
  4276  *      needs to retain a reference to the object, it should
  4277  *      call Tcl_IncrRefCount.
  4278  *
  4279  * Side effects:
  4280  *	The path object may be converted to a path type.
  4281  *
  4282  *---------------------------------------------------------------------------
  4283  */
  4284 EXPORT_C Tcl_Obj*
  4285 Tcl_FSPathSeparator(pathObjPtr)
  4286     Tcl_Obj* pathObjPtr;
  4287 {
  4288     Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathObjPtr);
  4289     
  4290     if (fsPtr == NULL) {
  4291 	return NULL;
  4292     }
  4293     if (fsPtr->filesystemSeparatorProc != NULL) {
  4294 	return (*fsPtr->filesystemSeparatorProc)(pathObjPtr);
  4295     }
  4296     
  4297     return NULL;
  4298 }
  4299 
  4300 /*
  4301  *---------------------------------------------------------------------------
  4302  *
  4303  * NativeFilesystemSeparator --
  4304  *
  4305  *      This function is part of the native filesystem support, and
  4306  *      returns the separator for the given path.
  4307  *
  4308  * Results:
  4309  *      String object containing the separator character.
  4310  *
  4311  * Side effects:
  4312  *	None.
  4313  *
  4314  *---------------------------------------------------------------------------
  4315  */
  4316 static Tcl_Obj*
  4317 NativeFilesystemSeparator(pathObjPtr)
  4318     Tcl_Obj* pathObjPtr;
  4319 {
  4320     char *separator = NULL; /* lint */
  4321     switch (tclPlatform) {
  4322 	case TCL_PLATFORM_UNIX:
  4323 	    separator = "/";
  4324 	    break;
  4325 	case TCL_PLATFORM_WINDOWS:
  4326 	    separator = "\\";
  4327 	    break;
  4328 	case TCL_PLATFORM_MAC:
  4329 	    separator = ":";
  4330 	    break;
  4331     }
  4332     return Tcl_NewStringObj(separator,1);
  4333 }
  4334 
  4335 /* Everything from here on is contained in this obsolete ifdef */
  4336 #ifdef USE_OBSOLETE_FS_HOOKS
  4337 
  4338 /*
  4339  *----------------------------------------------------------------------
  4340  *
  4341  * TclStatInsertProc --
  4342  *
  4343  *	Insert the passed procedure pointer at the head of the list of
  4344  *	functions which are used during a call to 'TclStat(...)'. The
  4345  *	passed function should behave exactly like 'TclStat' when called
  4346  *	during that time (see 'TclStat(...)' for more information).
  4347  *	The function will be added even if it already in the list.
  4348  *
  4349  * Results:
  4350  *      Normally TCL_OK; TCL_ERROR if memory for a new node in the list
  4351  *	could not be allocated.
  4352  *
  4353  * Side effects:
  4354  *      Memory allocated and modifies the link list for 'TclStat'
  4355  *	functions.
  4356  *
  4357  *----------------------------------------------------------------------
  4358  */
  4359 
  4360 int
  4361 TclStatInsertProc (proc)
  4362     TclStatProc_ *proc;
  4363 {
  4364     int retVal = TCL_ERROR;
  4365 
  4366     if (proc != NULL) {
  4367 	StatProc *newStatProcPtr;
  4368 
  4369 	newStatProcPtr = (StatProc *)ckalloc(sizeof(StatProc));
  4370 
  4371 	if (newStatProcPtr != NULL) {
  4372 	    newStatProcPtr->proc = proc;
  4373 	    Tcl_MutexLock(&obsoleteFsHookMutex);
  4374 	    newStatProcPtr->nextPtr = statProcList;
  4375 	    statProcList = newStatProcPtr;
  4376 	    Tcl_MutexUnlock(&obsoleteFsHookMutex);
  4377 
  4378 	    retVal = TCL_OK;
  4379 	}
  4380     }
  4381 
  4382     return retVal;
  4383 }
  4384 
  4385 /*
  4386  *----------------------------------------------------------------------
  4387  *
  4388  * TclStatDeleteProc --
  4389  *
  4390  *	Removed the passed function pointer from the list of 'TclStat'
  4391  *	functions.  Ensures that the built-in stat function is not
  4392  *	removvable.
  4393  *
  4394  * Results:
  4395  *      TCL_OK if the procedure pointer was successfully removed,
  4396  *	TCL_ERROR otherwise.
  4397  *
  4398  * Side effects:
  4399  *      Memory is deallocated and the respective list updated.
  4400  *
  4401  *----------------------------------------------------------------------
  4402  */
  4403 
  4404 int
  4405 TclStatDeleteProc (proc)
  4406     TclStatProc_ *proc;
  4407 {
  4408     int retVal = TCL_ERROR;
  4409     StatProc *tmpStatProcPtr;
  4410     StatProc *prevStatProcPtr = NULL;
  4411 
  4412     Tcl_MutexLock(&obsoleteFsHookMutex);
  4413     tmpStatProcPtr = statProcList;
  4414     /*
  4415      * Traverse the 'statProcList' looking for the particular node
  4416      * whose 'proc' member matches 'proc' and remove that one from
  4417      * the list.  Ensure that the "default" node cannot be removed.
  4418      */
  4419 
  4420     while ((retVal == TCL_ERROR) && (tmpStatProcPtr != NULL)) {
  4421 	if (tmpStatProcPtr->proc == proc) {
  4422 	    if (prevStatProcPtr == NULL) {
  4423 		statProcList = tmpStatProcPtr->nextPtr;
  4424 	    } else {
  4425 		prevStatProcPtr->nextPtr = tmpStatProcPtr->nextPtr;
  4426 	    }
  4427 
  4428 	    ckfree((char *)tmpStatProcPtr);
  4429 
  4430 	    retVal = TCL_OK;
  4431 	} else {
  4432 	    prevStatProcPtr = tmpStatProcPtr;
  4433 	    tmpStatProcPtr = tmpStatProcPtr->nextPtr;
  4434 	}
  4435     }
  4436 
  4437     Tcl_MutexUnlock(&obsoleteFsHookMutex);
  4438 
  4439     return retVal;
  4440 }
  4441 
  4442 /*
  4443  *----------------------------------------------------------------------
  4444  *
  4445  * TclAccessInsertProc --
  4446  *
  4447  *	Insert the passed procedure pointer at the head of the list of
  4448  *	functions which are used during a call to 'TclAccess(...)'.
  4449  *	The passed function should behave exactly like 'TclAccess' when
  4450  *	called during that time (see 'TclAccess(...)' for more
  4451  *	information).  The function will be added even if it already in
  4452  *	the list.
  4453  *
  4454  * Results:
  4455  *      Normally TCL_OK; TCL_ERROR if memory for a new node in the list
  4456  *	could not be allocated.
  4457  *
  4458  * Side effects:
  4459  *      Memory allocated and modifies the link list for 'TclAccess'
  4460  *	functions.
  4461  *
  4462  *----------------------------------------------------------------------
  4463  */
  4464 
  4465 int
  4466 TclAccessInsertProc(proc)
  4467     TclAccessProc_ *proc;
  4468 {
  4469     int retVal = TCL_ERROR;
  4470 
  4471     if (proc != NULL) {
  4472 	AccessProc *newAccessProcPtr;
  4473 
  4474 	newAccessProcPtr = (AccessProc *)ckalloc(sizeof(AccessProc));
  4475 
  4476 	if (newAccessProcPtr != NULL) {
  4477 	    newAccessProcPtr->proc = proc;
  4478 	    Tcl_MutexLock(&obsoleteFsHookMutex);
  4479 	    newAccessProcPtr->nextPtr = accessProcList;
  4480 	    accessProcList = newAccessProcPtr;
  4481 	    Tcl_MutexUnlock(&obsoleteFsHookMutex);
  4482 
  4483 	    retVal = TCL_OK;
  4484 	}
  4485     }
  4486 
  4487     return retVal;
  4488 }
  4489 
  4490 /*
  4491  *----------------------------------------------------------------------
  4492  *
  4493  * TclAccessDeleteProc --
  4494  *
  4495  *	Removed the passed function pointer from the list of 'TclAccess'
  4496  *	functions.  Ensures that the built-in access function is not
  4497  *	removvable.
  4498  *
  4499  * Results:
  4500  *      TCL_OK if the procedure pointer was successfully removed,
  4501  *	TCL_ERROR otherwise.
  4502  *
  4503  * Side effects:
  4504  *      Memory is deallocated and the respective list updated.
  4505  *
  4506  *----------------------------------------------------------------------
  4507  */
  4508 
  4509 int
  4510 TclAccessDeleteProc(proc)
  4511     TclAccessProc_ *proc;
  4512 {
  4513     int retVal = TCL_ERROR;
  4514     AccessProc *tmpAccessProcPtr;
  4515     AccessProc *prevAccessProcPtr = NULL;
  4516 
  4517     /*
  4518      * Traverse the 'accessProcList' looking for the particular node
  4519      * whose 'proc' member matches 'proc' and remove that one from
  4520      * the list.  Ensure that the "default" node cannot be removed.
  4521      */
  4522 
  4523     Tcl_MutexLock(&obsoleteFsHookMutex);
  4524     tmpAccessProcPtr = accessProcList;
  4525     while ((retVal == TCL_ERROR) && (tmpAccessProcPtr != NULL)) {
  4526 	if (tmpAccessProcPtr->proc == proc) {
  4527 	    if (prevAccessProcPtr == NULL) {
  4528 		accessProcList = tmpAccessProcPtr->nextPtr;
  4529 	    } else {
  4530 		prevAccessProcPtr->nextPtr = tmpAccessProcPtr->nextPtr;
  4531 	    }
  4532 
  4533 	    ckfree((char *)tmpAccessProcPtr);
  4534 
  4535 	    retVal = TCL_OK;
  4536 	} else {
  4537 	    prevAccessProcPtr = tmpAccessProcPtr;
  4538 	    tmpAccessProcPtr = tmpAccessProcPtr->nextPtr;
  4539 	}
  4540     }
  4541     Tcl_MutexUnlock(&obsoleteFsHookMutex);
  4542 
  4543     return retVal;
  4544 }
  4545 
  4546 /*
  4547  *----------------------------------------------------------------------
  4548  *
  4549  * TclOpenFileChannelInsertProc --
  4550  *
  4551  *	Insert the passed procedure pointer at the head of the list of
  4552  *	functions which are used during a call to
  4553  *	'Tcl_OpenFileChannel(...)'. The passed function should behave
  4554  *	exactly like 'Tcl_OpenFileChannel' when called during that time
  4555  *	(see 'Tcl_OpenFileChannel(...)' for more information). The
  4556  *	function will be added even if it already in the list.
  4557  *
  4558  * Results:
  4559  *      Normally TCL_OK; TCL_ERROR if memory for a new node in the list
  4560  *	could not be allocated.
  4561  *
  4562  * Side effects:
  4563  *      Memory allocated and modifies the link list for
  4564  *	'Tcl_OpenFileChannel' functions.
  4565  *
  4566  *----------------------------------------------------------------------
  4567  */
  4568 
  4569 int
  4570 TclOpenFileChannelInsertProc(proc)
  4571     TclOpenFileChannelProc_ *proc;
  4572 {
  4573     int retVal = TCL_ERROR;
  4574 
  4575     if (proc != NULL) {
  4576 	OpenFileChannelProc *newOpenFileChannelProcPtr;
  4577 
  4578 	newOpenFileChannelProcPtr =
  4579 		(OpenFileChannelProc *)ckalloc(sizeof(OpenFileChannelProc));
  4580 
  4581 	if (newOpenFileChannelProcPtr != NULL) {
  4582 	    newOpenFileChannelProcPtr->proc = proc;
  4583 	    Tcl_MutexLock(&obsoleteFsHookMutex);
  4584 	    newOpenFileChannelProcPtr->nextPtr = openFileChannelProcList;
  4585 	    openFileChannelProcList = newOpenFileChannelProcPtr;
  4586 	    Tcl_MutexUnlock(&obsoleteFsHookMutex);
  4587 
  4588 	    retVal = TCL_OK;
  4589 	}
  4590     }
  4591 
  4592     return retVal;
  4593 }
  4594 
  4595 /*
  4596  *----------------------------------------------------------------------
  4597  *
  4598  * TclOpenFileChannelDeleteProc --
  4599  *
  4600  *	Removed the passed function pointer from the list of
  4601  *	'Tcl_OpenFileChannel' functions.  Ensures that the built-in
  4602  *	open file channel function is not removable.
  4603  *
  4604  * Results:
  4605  *      TCL_OK if the procedure pointer was successfully removed,
  4606  *	TCL_ERROR otherwise.
  4607  *
  4608  * Side effects:
  4609  *      Memory is deallocated and the respective list updated.
  4610  *
  4611  *----------------------------------------------------------------------
  4612  */
  4613 
  4614 int
  4615 TclOpenFileChannelDeleteProc(proc)
  4616     TclOpenFileChannelProc_ *proc;
  4617 {
  4618     int retVal = TCL_ERROR;
  4619     OpenFileChannelProc *tmpOpenFileChannelProcPtr = openFileChannelProcList;
  4620     OpenFileChannelProc *prevOpenFileChannelProcPtr = NULL;
  4621 
  4622     /*
  4623      * Traverse the 'openFileChannelProcList' looking for the particular
  4624      * node whose 'proc' member matches 'proc' and remove that one from
  4625      * the list.  
  4626      */
  4627 
  4628     Tcl_MutexLock(&obsoleteFsHookMutex);
  4629     tmpOpenFileChannelProcPtr = openFileChannelProcList;
  4630     while ((retVal == TCL_ERROR) &&
  4631 	    (tmpOpenFileChannelProcPtr != NULL)) {
  4632 	if (tmpOpenFileChannelProcPtr->proc == proc) {
  4633 	    if (prevOpenFileChannelProcPtr == NULL) {
  4634 		openFileChannelProcList = tmpOpenFileChannelProcPtr->nextPtr;
  4635 	    } else {
  4636 		prevOpenFileChannelProcPtr->nextPtr =
  4637 			tmpOpenFileChannelProcPtr->nextPtr;
  4638 	    }
  4639 
  4640 	    ckfree((char *)tmpOpenFileChannelProcPtr);
  4641 
  4642 	    retVal = TCL_OK;
  4643 	} else {
  4644 	    prevOpenFileChannelProcPtr = tmpOpenFileChannelProcPtr;
  4645 	    tmpOpenFileChannelProcPtr = tmpOpenFileChannelProcPtr->nextPtr;
  4646 	}
  4647     }
  4648     Tcl_MutexUnlock(&obsoleteFsHookMutex);
  4649 
  4650     return retVal;
  4651 }
  4652 #endif /* USE_OBSOLETE_FS_HOOKS */
  4653 
  4654 
  4655 /*
  4656  * Prototypes for procedures defined later in this file.
  4657  */
  4658 
  4659 static void		DupFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr,
  4660 			    Tcl_Obj *copyPtr));
  4661 static void		FreeFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *listPtr));
  4662 static void             UpdateStringOfFsPath _ANSI_ARGS_((Tcl_Obj *objPtr));
  4663 static int		SetFsPathFromAny _ANSI_ARGS_((Tcl_Interp *interp,
  4664 			    Tcl_Obj *objPtr));
  4665 static int 		FindSplitPos _ANSI_ARGS_((char *path, char *separator));
  4666 
  4667 
  4668 
  4669 /*
  4670  * Define the 'path' object type, which Tcl uses to represent
  4671  * file paths internally.
  4672  */
  4673 static Tcl_ObjType tclFsPathType = {
  4674     "path",				/* name */
  4675     FreeFsPathInternalRep,		/* freeIntRepProc */
  4676     DupFsPathInternalRep,	        /* dupIntRepProc */
  4677     UpdateStringOfFsPath,		/* updateStringProc */
  4678     SetFsPathFromAny			/* setFromAnyProc */
  4679 };
  4680 
  4681 /* 
  4682  * struct FsPath --
  4683  * 
  4684  * Internal representation of a Tcl_Obj of "path" type.  This
  4685  * can be used to represent relative or absolute paths, and has
  4686  * certain optimisations when used to represent paths which are
  4687  * already normalized and absolute.
  4688  * 
  4689  * Note that 'normPathPtr' can be a circular reference to the
  4690  * container Tcl_Obj of this FsPath.
  4691  */
  4692 typedef struct FsPath {
  4693     Tcl_Obj *translatedPathPtr; /* Name without any ~user sequences.
  4694 				 * If this is NULL, then this is a 
  4695 				 * pure normalized, absolute path
  4696 				 * object, in which the parent Tcl_Obj's
  4697 				 * string rep is already both translated
  4698 				 * and normalized. */
  4699     Tcl_Obj *normPathPtr;       /* Normalized absolute path, without 
  4700 				 * ., .. or ~user sequences. If the 
  4701 				 * Tcl_Obj containing 
  4702 				 * this FsPath is already normalized, 
  4703 				 * this may be a circular reference back
  4704 				 * to the container.  If that is NOT the
  4705 				 * case, we have a refCount on the object. */
  4706     Tcl_Obj *cwdPtr;            /* If null, path is absolute, else
  4707 				 * this points to the cwd object used
  4708 				 * for this path.  We have a refCount
  4709 				 * on the object. */
  4710     int flags;                  /* Flags to describe interpretation */
  4711     ClientData nativePathPtr;   /* Native representation of this path,
  4712 				 * which is filesystem dependent. */
  4713     int filesystemEpoch;        /* Used to ensure the path representation
  4714 				 * was generated during the correct
  4715 				 * filesystem epoch.  The epoch changes
  4716 				 * when filesystem-mounts are changed. */ 
  4717     struct FilesystemRecord *fsRecPtr;
  4718 				/* Pointer to the filesystem record 
  4719 				 * entry to use for this path. */
  4720 } FsPath;
  4721 
  4722 /* 
  4723  * Define some macros to give us convenient access to path-object
  4724  * specific fields.
  4725  */
  4726 #define PATHOBJ(objPtr) (objPtr->internalRep.otherValuePtr)
  4727 #define PATHFLAGS(objPtr) \
  4728  (((FsPath*)(objPtr->internalRep.otherValuePtr))->flags)
  4729 
  4730 #define TCLPATH_APPENDED 1
  4731 #define TCLPATH_RELATIVE 2
  4732 
  4733 /*
  4734  *----------------------------------------------------------------------
  4735  *
  4736  * Tcl_FSGetPathType --
  4737  *
  4738  *	Determines whether a given path is relative to the current
  4739  *	directory, relative to the current volume, or absolute.  
  4740  *
  4741  * Results:
  4742  *	Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
  4743  *	TCL_PATH_VOLUME_RELATIVE.
  4744  *
  4745  * Side effects:
  4746  *	None.
  4747  *
  4748  *----------------------------------------------------------------------
  4749  */
  4750 
  4751 EXPORT_C Tcl_PathType
  4752 Tcl_FSGetPathType(pathObjPtr)
  4753     Tcl_Obj *pathObjPtr;
  4754 {
  4755     return FSGetPathType(pathObjPtr, NULL, NULL);
  4756 }
  4757 
  4758 /*
  4759  *----------------------------------------------------------------------
  4760  *
  4761  * FSGetPathType --
  4762  *
  4763  *	Determines whether a given path is relative to the current
  4764  *	directory, relative to the current volume, or absolute.  If the
  4765  *	caller wishes to know which filesystem claimed the path (in the
  4766  *	case for which the path is absolute), then a reference to a
  4767  *	filesystem pointer can be passed in (but passing NULL is
  4768  *	acceptable).
  4769  *
  4770  * Results:
  4771  *	Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
  4772  *	TCL_PATH_VOLUME_RELATIVE.  The filesystem reference will
  4773  *	be set if and only if it is non-NULL and the function's 
  4774  *	return value is TCL_PATH_ABSOLUTE.
  4775  *
  4776  * Side effects:
  4777  *	None.
  4778  *
  4779  *----------------------------------------------------------------------
  4780  */
  4781 
  4782 static Tcl_PathType
  4783 FSGetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr)
  4784     Tcl_Obj *pathObjPtr;
  4785     Tcl_Filesystem **filesystemPtrPtr;
  4786     int *driveNameLengthPtr;
  4787 {
  4788     if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) {
  4789 	return GetPathType(pathObjPtr, filesystemPtrPtr, 
  4790 			   driveNameLengthPtr, NULL);
  4791     } else {
  4792 	FsPath *fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  4793 	if (fsPathPtr->cwdPtr != NULL) {
  4794 	    if (PATHFLAGS(pathObjPtr) == 0) {
  4795 		return TCL_PATH_RELATIVE;
  4796 	    }
  4797 	    return FSGetPathType(fsPathPtr->cwdPtr, filesystemPtrPtr, 
  4798 				 driveNameLengthPtr);
  4799 	} else {
  4800 	    return GetPathType(pathObjPtr, filesystemPtrPtr, 
  4801 			       driveNameLengthPtr, NULL);
  4802 	}
  4803     }
  4804 }
  4805 
  4806 /*
  4807  *---------------------------------------------------------------------------
  4808  *
  4809  * Tcl_FSJoinPath --
  4810  *
  4811  *      This function takes the given Tcl_Obj, which should be a valid
  4812  *      list, and returns the path object given by considering the
  4813  *      first 'elements' elements as valid path segments.  If elements < 0,
  4814  *      we use the entire list.
  4815  *      
  4816  * Results:
  4817  *      Returns object with refCount of zero, (or if non-zero, it has
  4818  *      references elsewhere in Tcl).  Either way, the caller must
  4819  *      increment its refCount before use.
  4820  *
  4821  * Side effects:
  4822  *	None.
  4823  *
  4824  *---------------------------------------------------------------------------
  4825  */
  4826 EXPORT_C Tcl_Obj* 
  4827 Tcl_FSJoinPath(listObj, elements)
  4828     Tcl_Obj *listObj;
  4829     int elements;
  4830 {
  4831     Tcl_Obj *res;
  4832     int i;
  4833     Tcl_Filesystem *fsPtr = NULL;
  4834     
  4835     if (elements < 0) {
  4836 	if (Tcl_ListObjLength(NULL, listObj, &elements) != TCL_OK) {
  4837 	    return NULL;
  4838 	}
  4839     } else {
  4840 	/* Just make sure it is a valid list */
  4841 	int listTest;
  4842 	if (Tcl_ListObjLength(NULL, listObj, &listTest) != TCL_OK) {
  4843 	    return NULL;
  4844 	}
  4845 	/* 
  4846 	 * Correct this if it is too large, otherwise we will
  4847 	 * waste our time joining null elements to the path 
  4848 	 */
  4849 	if (elements > listTest) {
  4850 	    elements = listTest;
  4851 	}
  4852     }
  4853     
  4854     res = Tcl_NewObj();
  4855     
  4856     for (i = 0; i < elements; i++) {
  4857 	Tcl_Obj *elt;
  4858 	int driveNameLength;
  4859 	Tcl_PathType type;
  4860 	char *strElt;
  4861 	int strEltLen;
  4862 	int length;
  4863 	char *ptr;
  4864 	Tcl_Obj *driveName = NULL;
  4865 	
  4866 	Tcl_ListObjIndex(NULL, listObj, i, &elt);
  4867 	
  4868 	/* 
  4869 	 * This is a special case where we can be much more
  4870 	 * efficient, where we are joining a single relative path
  4871 	 * onto an object that is already of path type.  The 
  4872 	 * 'TclNewFSPathObj' call below creates an object which
  4873 	 * can be normalized more efficiently.  Currently we only
  4874 	 * use the special case when we have exactly two elements,
  4875 	 * but we could expand that in the future.
  4876 	 */
  4877 	if ((i == (elements-2)) && (i == 0) && (elt->typePtr == &tclFsPathType)
  4878 	  && !(elt->bytes != NULL && (elt->bytes[0] == '\0'))) {
  4879 	    Tcl_Obj *tail;
  4880 	    Tcl_PathType type;
  4881 	    Tcl_ListObjIndex(NULL, listObj, i+1, &tail);
  4882 	    type = GetPathType(tail, NULL, NULL, NULL);
  4883 	    if (type == TCL_PATH_RELATIVE) {
  4884 		CONST char *str;
  4885 		int len;
  4886 		str = Tcl_GetStringFromObj(tail,&len);
  4887 		if (len == 0) {
  4888 		    /* 
  4889 		     * This happens if we try to handle the root volume
  4890 		     * '/'.  There's no need to return a special path
  4891 		     * object, when the base itself is just fine!
  4892 		     */
  4893 		    Tcl_DecrRefCount(res);
  4894 		    return elt;
  4895 		}
  4896 		/* 
  4897 		 * If it doesn't begin with '.'  and is a mac or unix
  4898 		 * path or it a windows path without backslashes, then we
  4899 		 * can be very efficient here.  (In fact even a windows
  4900 		 * path with backslashes can be joined efficiently, but
  4901 		 * the path object would not have forward slashes only,
  4902 		 * and this would therefore contradict our 'file join'
  4903 		 * documentation).
  4904 		 */
  4905 		if (str[0] != '.' && ((tclPlatform != TCL_PLATFORM_WINDOWS) 
  4906 				      || (strchr(str, '\\') == NULL))) {
  4907 		    /* 
  4908 		     * Finally, on Windows, 'file join' is defined to 
  4909 		     * convert all backslashes to forward slashes,
  4910 		     * so the base part cannot have backslashes either.
  4911 		     */
  4912 		    if ((tclPlatform != TCL_PLATFORM_WINDOWS)
  4913 			|| (strchr(Tcl_GetString(elt), '\\') == NULL)) {
  4914 			if (res != NULL) {
  4915 			    TclDecrRefCount(res);
  4916 			}
  4917 			return TclNewFSPathObj(elt, str, len);
  4918 		    }
  4919 		}
  4920 		/* 
  4921 		 * Otherwise we don't have an easy join, and
  4922 		 * we must let the more general code below handle
  4923 		 * things
  4924 		 */
  4925 	    } else {
  4926 		if (tclPlatform == TCL_PLATFORM_UNIX) {
  4927 		    Tcl_DecrRefCount(res);
  4928 		    return tail;
  4929 		} else {
  4930 		    CONST char *str;
  4931 		    int len;
  4932 		    str = Tcl_GetStringFromObj(tail,&len);
  4933 		    if (tclPlatform == TCL_PLATFORM_WINDOWS) {
  4934 			if (strchr(str, '\\') == NULL) {
  4935 			    Tcl_DecrRefCount(res);
  4936 			    return tail;
  4937 			}
  4938 		    } else if (tclPlatform == TCL_PLATFORM_MAC) {
  4939 			if (strchr(str, '/') == NULL) {
  4940 			    Tcl_DecrRefCount(res);
  4941 			    return tail;
  4942 			}
  4943 		    }
  4944 		}
  4945 	    }
  4946 	}
  4947 	strElt = Tcl_GetStringFromObj(elt, &strEltLen);
  4948 	type = GetPathType(elt, &fsPtr, &driveNameLength, &driveName);
  4949 	if (type != TCL_PATH_RELATIVE) {
  4950 	    /* Zero out the current result */
  4951 	    Tcl_DecrRefCount(res);
  4952 	    if (driveName != NULL) {
  4953 		res = Tcl_DuplicateObj(driveName);
  4954 		Tcl_DecrRefCount(driveName);
  4955 	    } else {
  4956 		res = Tcl_NewStringObj(strElt, driveNameLength);
  4957 	    }
  4958 	    strElt += driveNameLength;
  4959 	}
  4960 	
  4961 	ptr = Tcl_GetStringFromObj(res, &length);
  4962 	
  4963 	/* 
  4964 	 * Strip off any './' before a tilde, unless this is the
  4965 	 * beginning of the path.
  4966 	 */
  4967 	if (length > 0 && strEltLen > 0) {
  4968 	    if ((strElt[0] == '.') && (strElt[1] == '/') 
  4969 	      && (strElt[2] == '~')) {
  4970 		strElt += 2;
  4971 	    }
  4972 	}
  4973 
  4974 	/* 
  4975 	 * A NULL value for fsPtr at this stage basically means
  4976 	 * we're trying to join a relative path onto something
  4977 	 * which is also relative (or empty).  There's nothing
  4978 	 * particularly wrong with that.
  4979 	 */
  4980 	if (*strElt == '\0') continue;
  4981 	
  4982 	if (fsPtr == &tclNativeFilesystem || fsPtr == NULL) {
  4983 	    TclpNativeJoinPath(res, strElt);
  4984 	} else {
  4985 	    char separator = '/';
  4986 	    int needsSep = 0;
  4987 	    
  4988 	    if (fsPtr->filesystemSeparatorProc != NULL) {
  4989 		Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(res);
  4990 		if (sep != NULL) {
  4991 		    separator = Tcl_GetString(sep)[0];
  4992 		}
  4993 	    }
  4994 
  4995 	    if (length > 0 && ptr[length -1] != '/') {
  4996 		Tcl_AppendToObj(res, &separator, 1);
  4997 		length++;
  4998 	    }
  4999 	    Tcl_SetObjLength(res, length + (int) strlen(strElt));
  5000 	    
  5001 	    ptr = Tcl_GetString(res) + length;
  5002 	    for (; *strElt != '\0'; strElt++) {
  5003 		if (*strElt == separator) {
  5004 		    while (strElt[1] == separator) {
  5005 			strElt++;
  5006 		    }
  5007 		    if (strElt[1] != '\0') {
  5008 			if (needsSep) {
  5009 			    *ptr++ = separator;
  5010 			}
  5011 		    }
  5012 		} else {
  5013 		    *ptr++ = *strElt;
  5014 		    needsSep = 1;
  5015 		}
  5016 	    }
  5017 	    length = ptr - Tcl_GetString(res);
  5018 	    Tcl_SetObjLength(res, length);
  5019 	}
  5020     }
  5021     return res;
  5022 }
  5023 
  5024 /*
  5025  *---------------------------------------------------------------------------
  5026  *
  5027  * Tcl_FSConvertToPathType --
  5028  *
  5029  *      This function tries to convert the given Tcl_Obj to a valid
  5030  *      Tcl path type, taking account of the fact that the cwd may
  5031  *      have changed even if this object is already supposedly of
  5032  *      the correct type.
  5033  *      
  5034  *      The filename may begin with "~" (to indicate current user's
  5035  *      home directory) or "~<user>" (to indicate any user's home
  5036  *      directory).
  5037  *
  5038  * Results:
  5039  *      Standard Tcl error code.
  5040  *
  5041  * Side effects:
  5042  *	The old representation may be freed, and new memory allocated.
  5043  *
  5044  *---------------------------------------------------------------------------
  5045  */
  5046 EXPORT_C int 
  5047 Tcl_FSConvertToPathType(interp, objPtr)
  5048     Tcl_Interp *interp;		/* Interpreter in which to store error
  5049 				 * message (if necessary). */
  5050     Tcl_Obj *objPtr;		/* Object to convert to a valid, current
  5051 				 * path type. */
  5052 {
  5053     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  5054 
  5055     /* 
  5056      * While it is bad practice to examine an object's type directly,
  5057      * this is actually the best thing to do here.  The reason is that
  5058      * if we are converting this object to FsPath type for the first
  5059      * time, we don't need to worry whether the 'cwd' has changed.
  5060      * On the other hand, if this object is already of FsPath type,
  5061      * and is a relative path, we do have to worry about the cwd.
  5062      * If the cwd has changed, we must recompute the path.
  5063      */
  5064     if (objPtr->typePtr == &tclFsPathType) {
  5065 	FsPath *fsPathPtr = (FsPath*) PATHOBJ(objPtr);
  5066 	if (fsPathPtr->filesystemEpoch != tsdPtr->filesystemEpoch) {
  5067 	    if (objPtr->bytes == NULL) {
  5068 		UpdateStringOfFsPath(objPtr);
  5069 	    }
  5070 	    FreeFsPathInternalRep(objPtr);
  5071 	    objPtr->typePtr = NULL;
  5072 	    return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
  5073 	}
  5074 	return TCL_OK;
  5075     } else {
  5076 	return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
  5077     }
  5078 }
  5079 
  5080 /* 
  5081  * Helper function for SetFsPathFromAny.  Returns position of first
  5082  * directory delimiter in the path.
  5083  */
  5084 static int
  5085 FindSplitPos(path, separator)
  5086     char *path;
  5087     char *separator;
  5088 {
  5089     int count = 0;
  5090     switch (tclPlatform) {
  5091 	case TCL_PLATFORM_UNIX:
  5092 	case TCL_PLATFORM_MAC:
  5093 	    while (path[count] != 0) {
  5094 		if (path[count] == *separator) {
  5095 		    return count;
  5096 		}
  5097 		count++;
  5098 	    }
  5099 	    break;
  5100 
  5101 	case TCL_PLATFORM_WINDOWS:
  5102 	    while (path[count] != 0) {
  5103 		if (path[count] == *separator || path[count] == '\\') {
  5104 		    return count;
  5105 		}
  5106 		count++;
  5107 	    }
  5108 	    break;
  5109     }
  5110     return count;
  5111 }
  5112 
  5113 /*
  5114  *---------------------------------------------------------------------------
  5115  *
  5116  * TclNewFSPathObj --
  5117  *
  5118  *      Creates a path object whose string representation is 
  5119  *      '[file join dirPtr addStrRep]', but does so in a way that
  5120  *      allows for more efficient caching of normalized paths.
  5121  *      
  5122  * Assumptions:
  5123  *      'dirPtr' must be an absolute path.  
  5124  *      'len' may not be zero.
  5125  *      
  5126  * Results:
  5127  *      The new Tcl object, with refCount zero.
  5128  *
  5129  * Side effects:
  5130  *	Memory is allocated.  'dirPtr' gets an additional refCount.
  5131  *
  5132  *---------------------------------------------------------------------------
  5133  */
  5134 
  5135 Tcl_Obj*
  5136 TclNewFSPathObj(Tcl_Obj *dirPtr, CONST char *addStrRep, int len)
  5137 {
  5138     FsPath *fsPathPtr;
  5139     Tcl_Obj *objPtr;
  5140     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  5141     
  5142     objPtr = Tcl_NewObj();
  5143     fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
  5144     
  5145     if (tclPlatform == TCL_PLATFORM_MAC) { 
  5146 	/* 
  5147 	 * Mac relative paths may begin with a directory separator ':'. 
  5148 	 * If present, we need to skip this ':' because we assume that 
  5149 	 * we can join dirPtr and addStrRep by concatenating them as 
  5150 	 * strings (and we ensure that dirPtr is terminated by a ':'). 
  5151 	 */ 
  5152 	if (addStrRep[0] == ':') { 
  5153 	    addStrRep++; 
  5154 	    len--; 
  5155 	} 
  5156     } 
  5157     /* Setup the path */
  5158     fsPathPtr->translatedPathPtr = NULL;
  5159     fsPathPtr->normPathPtr = Tcl_NewStringObj(addStrRep, len);
  5160     Tcl_IncrRefCount(fsPathPtr->normPathPtr);
  5161     fsPathPtr->cwdPtr = dirPtr;
  5162     Tcl_IncrRefCount(dirPtr);
  5163     fsPathPtr->nativePathPtr = NULL;
  5164     fsPathPtr->fsRecPtr = NULL;
  5165     fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
  5166 
  5167     PATHOBJ(objPtr) = (VOID *) fsPathPtr;
  5168     PATHFLAGS(objPtr) = TCLPATH_RELATIVE | TCLPATH_APPENDED;
  5169     objPtr->typePtr = &tclFsPathType;
  5170     objPtr->bytes = NULL;
  5171     objPtr->length = 0;
  5172 
  5173     return objPtr;
  5174 }
  5175 
  5176 /*
  5177  *---------------------------------------------------------------------------
  5178  *
  5179  * TclFSMakePathRelative --
  5180  *
  5181  *      Only for internal use.
  5182  *      
  5183  *      Takes a path and a directory, where we _assume_ both path and
  5184  *      directory are absolute, normalized and that the path lies
  5185  *      inside the directory.  Returns a Tcl_Obj representing filename 
  5186  *      of the path relative to the directory.
  5187  *      
  5188  *      In the case where the resulting path would start with a '~', we
  5189  *      take special care to return an ordinary string.  This means to
  5190  *      use that path (and not have it interpreted as a user name),
  5191  *      one must prepend './'.  This may seem strange, but that is how
  5192  *      'glob' is currently defined.
  5193  *      
  5194  * Results:
  5195  *      NULL on error, otherwise a valid object, typically with
  5196  *      refCount of zero, which it is assumed the caller will
  5197  *      increment.
  5198  *
  5199  * Side effects:
  5200  *	The old representation may be freed, and new memory allocated.
  5201  *
  5202  *---------------------------------------------------------------------------
  5203  */
  5204 
  5205 Tcl_Obj*
  5206 TclFSMakePathRelative(interp, objPtr, cwdPtr)
  5207     Tcl_Interp *interp;		/* Used for error reporting if not NULL. */
  5208     Tcl_Obj *objPtr;		/* The object we have. */
  5209     Tcl_Obj *cwdPtr;		/* Make it relative to this. */
  5210 {
  5211     int cwdLen, len;
  5212     CONST char *tempStr;
  5213     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  5214     
  5215     if (objPtr->typePtr == &tclFsPathType) {
  5216 	FsPath *fsPathPtr = (FsPath*) PATHOBJ(objPtr);
  5217 	if (PATHFLAGS(objPtr) != 0 
  5218 		&& fsPathPtr->cwdPtr == cwdPtr) {
  5219 	    objPtr = fsPathPtr->normPathPtr;
  5220 	    /* Free old representation */
  5221 	    if (objPtr->typePtr != NULL) {
  5222 		if (objPtr->bytes == NULL) {
  5223 		    if (objPtr->typePtr->updateStringProc == NULL) {
  5224 			if (interp != NULL) {
  5225 			    Tcl_ResetResult(interp);
  5226 			    Tcl_AppendResult(interp, "can't find object",
  5227 					     "string representation", (char *) NULL);
  5228 			}
  5229 			return NULL;
  5230 		    }
  5231 		    objPtr->typePtr->updateStringProc(objPtr);
  5232 		}
  5233 		if ((objPtr->typePtr->freeIntRepProc) != NULL) {
  5234 		    (*objPtr->typePtr->freeIntRepProc)(objPtr);
  5235 		}
  5236 	    }
  5237 	    /* Now objPtr is a string object */
  5238 	    
  5239 	    if (Tcl_GetString(objPtr)[0] == '~') {
  5240 		/* 
  5241 		 * If the first character of the path is a tilde,
  5242 		 * we must just return the path as is, to agree
  5243 		 * with the defined behaviour of 'glob'.
  5244 		 */
  5245 		return objPtr;
  5246 	    }
  5247 
  5248 	    fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
  5249 
  5250 	    /* Circular reference, by design */
  5251 	    fsPathPtr->translatedPathPtr = objPtr;
  5252 	    fsPathPtr->normPathPtr = NULL;
  5253 	    fsPathPtr->cwdPtr = cwdPtr;
  5254 	    Tcl_IncrRefCount(cwdPtr);
  5255 	    fsPathPtr->nativePathPtr = NULL;
  5256 	    fsPathPtr->fsRecPtr = NULL;
  5257 	    fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
  5258 
  5259 	    PATHOBJ(objPtr) = (VOID *) fsPathPtr;
  5260 	    PATHFLAGS(objPtr) = 0;
  5261 	    objPtr->typePtr = &tclFsPathType;
  5262 
  5263 	    return objPtr;
  5264 	}
  5265     }
  5266     /* 
  5267      * We know the cwd is a normalised object which does
  5268      * not end in a directory delimiter, unless the cwd
  5269      * is the name of a volume, in which case it will
  5270      * end in a delimiter!  We handle this situation here.
  5271      * A better test than the '!= sep' might be to simply
  5272      * check if 'cwd' is a root volume.
  5273      * 
  5274      * Note that if we get this wrong, we will strip off
  5275      * either too much or too little below, leading to
  5276      * wrong answers returned by glob.
  5277      */
  5278     tempStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen);
  5279     /* 
  5280      * Should we perhaps use 'Tcl_FSPathSeparator'?
  5281      * But then what about the Windows special case?
  5282      * Perhaps we should just check if cwd is a root
  5283      * volume.
  5284      */
  5285     switch (tclPlatform) {
  5286 	case TCL_PLATFORM_UNIX:
  5287 	    if (tempStr[cwdLen-1] != '/') {
  5288 		cwdLen++;
  5289 	    }
  5290 	    break;
  5291 	case TCL_PLATFORM_WINDOWS:
  5292 	    if (tempStr[cwdLen-1] != '/' 
  5293 		    && tempStr[cwdLen-1] != '\\') {
  5294 		cwdLen++;
  5295 	    }
  5296 	    break;
  5297 	case TCL_PLATFORM_MAC:
  5298 	    if (tempStr[cwdLen-1] != ':') {
  5299 		cwdLen++;
  5300 	    }
  5301 	    break;
  5302     }
  5303     tempStr = Tcl_GetStringFromObj(objPtr, &len);
  5304 
  5305     return Tcl_NewStringObj(tempStr + cwdLen, len - cwdLen);
  5306 }
  5307 
  5308 /*
  5309  *---------------------------------------------------------------------------
  5310  *
  5311  * TclFSMakePathFromNormalized --
  5312  *
  5313  *      Like SetFsPathFromAny, but assumes the given object is an
  5314  *      absolute normalized path. Only for internal use.
  5315  *      
  5316  * Results:
  5317  *      Standard Tcl error code.
  5318  *
  5319  * Side effects:
  5320  *	The old representation may be freed, and new memory allocated.
  5321  *
  5322  *---------------------------------------------------------------------------
  5323  */
  5324 
  5325 int
  5326 TclFSMakePathFromNormalized(interp, objPtr, nativeRep)
  5327     Tcl_Interp *interp;		/* Used for error reporting if not NULL. */
  5328     Tcl_Obj *objPtr;		/* The object to convert. */
  5329     ClientData nativeRep;	/* The native rep for the object, if known
  5330 				 * else NULL. */
  5331 {
  5332     FsPath *fsPathPtr;
  5333     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  5334 
  5335     if (objPtr->typePtr == &tclFsPathType) {
  5336 	return TCL_OK;
  5337     }
  5338     
  5339     /* Free old representation */
  5340     if (objPtr->typePtr != NULL) {
  5341 	if (objPtr->bytes == NULL) {
  5342 	    if (objPtr->typePtr->updateStringProc == NULL) {
  5343 		if (interp != NULL) {
  5344 		    Tcl_ResetResult(interp);
  5345 		    Tcl_AppendResult(interp, "can't find object",
  5346 				     "string representation", (char *) NULL);
  5347 		}
  5348 		return TCL_ERROR;
  5349 	    }
  5350 	    objPtr->typePtr->updateStringProc(objPtr);
  5351 	}
  5352 	if ((objPtr->typePtr->freeIntRepProc) != NULL) {
  5353 	    (*objPtr->typePtr->freeIntRepProc)(objPtr);
  5354 	}
  5355     }
  5356 
  5357     fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
  5358     /* It's a pure normalized absolute path */
  5359     fsPathPtr->translatedPathPtr = NULL;
  5360     fsPathPtr->normPathPtr = objPtr;
  5361     fsPathPtr->cwdPtr = NULL;
  5362     fsPathPtr->nativePathPtr = nativeRep;
  5363     fsPathPtr->fsRecPtr = NULL;
  5364     fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
  5365 
  5366     PATHOBJ(objPtr) = (VOID *) fsPathPtr;
  5367     PATHFLAGS(objPtr) = 0;
  5368     objPtr->typePtr = &tclFsPathType;
  5369 
  5370     return TCL_OK;
  5371 }
  5372 
  5373 /*
  5374  *---------------------------------------------------------------------------
  5375  *
  5376  * Tcl_FSNewNativePath --
  5377  *
  5378  *      This function performs the something like that reverse of the 
  5379  *      usual obj->path->nativerep conversions.  If some code retrieves
  5380  *      a path in native form (from, e.g. readlink or a native dialog),
  5381  *      and that path is to be used at the Tcl level, then calling
  5382  *      this function is an efficient way of creating the appropriate
  5383  *      path object type.
  5384  *      
  5385  *      Any memory which is allocated for 'clientData' should be retained
  5386  *      until clientData is passed to the filesystem's freeInternalRepProc
  5387  *      when it can be freed.  The built in platform-specific filesystems
  5388  *      use 'ckalloc' to allocate clientData, and ckfree to free it.
  5389  *
  5390  * Results:
  5391  *      NULL or a valid path object pointer, with refCount zero.
  5392  *
  5393  * Side effects:
  5394  *	New memory may be allocated.
  5395  *
  5396  *---------------------------------------------------------------------------
  5397  */
  5398 
  5399 EXPORT_C Tcl_Obj *
  5400 Tcl_FSNewNativePath(fromFilesystem, clientData)
  5401     Tcl_Filesystem* fromFilesystem;
  5402     ClientData clientData;
  5403 {
  5404     Tcl_Obj *objPtr;
  5405     FsPath *fsPathPtr;
  5406 
  5407     FilesystemRecord *fsFromPtr;
  5408     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  5409     
  5410     objPtr = TclFSInternalToNormalized(fromFilesystem, clientData, &fsFromPtr);
  5411     if (objPtr == NULL) {
  5412 	return NULL;
  5413     }
  5414     
  5415     /* 
  5416      * Free old representation; shouldn't normally be any,
  5417      * but best to be safe. 
  5418      */
  5419     if (objPtr->typePtr != NULL) {
  5420 	if (objPtr->bytes == NULL) {
  5421 	    if (objPtr->typePtr->updateStringProc == NULL) {
  5422 		return NULL;
  5423 	    }
  5424 	    objPtr->typePtr->updateStringProc(objPtr);
  5425 	}
  5426 	if ((objPtr->typePtr->freeIntRepProc) != NULL) {
  5427 	    (*objPtr->typePtr->freeIntRepProc)(objPtr);
  5428 	}
  5429     }
  5430     
  5431     fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
  5432 
  5433     fsPathPtr->translatedPathPtr = NULL;
  5434     /* Circular reference, by design */
  5435     fsPathPtr->normPathPtr = objPtr;
  5436     fsPathPtr->cwdPtr = NULL;
  5437     fsPathPtr->nativePathPtr = clientData;
  5438     fsPathPtr->fsRecPtr = fsFromPtr;
  5439     fsPathPtr->fsRecPtr->fileRefCount++;
  5440     fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
  5441 
  5442     PATHOBJ(objPtr) = (VOID *) fsPathPtr;
  5443     PATHFLAGS(objPtr) = 0;
  5444     objPtr->typePtr = &tclFsPathType;
  5445 
  5446     return objPtr;
  5447 }
  5448 
  5449 /*
  5450  *---------------------------------------------------------------------------
  5451  *
  5452  * Tcl_FSGetTranslatedPath --
  5453  *
  5454  *      This function attempts to extract the translated path
  5455  *      from the given Tcl_Obj.  If the translation succeeds (i.e. the
  5456  *      object is a valid path), then it is returned.  Otherwise NULL
  5457  *      will be returned, and an error message may be left in the
  5458  *      interpreter (if it is non-NULL)
  5459  *
  5460  * Results:
  5461  *      NULL or a valid Tcl_Obj pointer.
  5462  *
  5463  * Side effects:
  5464  *	Only those of 'Tcl_FSConvertToPathType'
  5465  *
  5466  *---------------------------------------------------------------------------
  5467  */
  5468 
  5469 EXPORT_C Tcl_Obj* 
  5470 Tcl_FSGetTranslatedPath(interp, pathPtr)
  5471     Tcl_Interp *interp;
  5472     Tcl_Obj* pathPtr;
  5473 {
  5474     Tcl_Obj *retObj = NULL;
  5475     FsPath *srcFsPathPtr;
  5476 
  5477     if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) {
  5478 	return NULL;
  5479     }
  5480     srcFsPathPtr = (FsPath*) PATHOBJ(pathPtr);
  5481     if (srcFsPathPtr->translatedPathPtr == NULL) {
  5482 	if (PATHFLAGS(pathPtr) != 0) {
  5483 	    retObj = Tcl_FSGetNormalizedPath(interp, pathPtr);
  5484 	} else {
  5485 	    /* 
  5486 	     * It is a pure absolute, normalized path object.
  5487 	     * This is something like being a 'pure list'.  The
  5488 	     * object's string, translatedPath and normalizedPath
  5489 	     * are all identical.
  5490 	     */
  5491 	    retObj = srcFsPathPtr->normPathPtr;
  5492 	}
  5493     } else {
  5494 	/* It is an ordinary path object */
  5495 	retObj = srcFsPathPtr->translatedPathPtr;
  5496     }
  5497 
  5498     if (retObj) {
  5499 	Tcl_IncrRefCount(retObj);
  5500     }
  5501     return retObj;
  5502 }
  5503 
  5504 /*
  5505  *---------------------------------------------------------------------------
  5506  *
  5507  * Tcl_FSGetTranslatedStringPath --
  5508  *
  5509  *      This function attempts to extract the translated path
  5510  *      from the given Tcl_Obj.  If the translation succeeds (i.e. the
  5511  *      object is a valid path), then the path is returned.  Otherwise NULL
  5512  *      will be returned, and an error message may be left in the
  5513  *      interpreter (if it is non-NULL)
  5514  *
  5515  * Results:
  5516  *      NULL or a valid string.
  5517  *
  5518  * Side effects:
  5519  *	Only those of 'Tcl_FSConvertToPathType'
  5520  *
  5521  *---------------------------------------------------------------------------
  5522  */
  5523 EXPORT_C CONST char*
  5524 Tcl_FSGetTranslatedStringPath(interp, pathPtr)
  5525     Tcl_Interp *interp;
  5526     Tcl_Obj* pathPtr;
  5527 {
  5528     Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
  5529 
  5530     if (transPtr != NULL) {
  5531 	int len;
  5532 	CONST char *result, *orig;
  5533 	orig = Tcl_GetStringFromObj(transPtr, &len);
  5534 	result = (char*) ckalloc((unsigned)(len+1));
  5535 	memcpy((VOID*) result, (VOID*) orig, (size_t) (len+1));
  5536 	Tcl_DecrRefCount(transPtr);
  5537 	return result;
  5538     }
  5539 
  5540     return NULL;
  5541 }
  5542 
  5543 /*
  5544  *---------------------------------------------------------------------------
  5545  *
  5546  * Tcl_FSGetNormalizedPath --
  5547  *
  5548  *      This important function attempts to extract from the given Tcl_Obj
  5549  *      a unique normalised path representation, whose string value can
  5550  *      be used as a unique identifier for the file.
  5551  *
  5552  * Results:
  5553  *      NULL or a valid path object pointer.
  5554  *
  5555  * Side effects:
  5556  *	New memory may be allocated.  The Tcl 'errno' may be modified
  5557  *      in the process of trying to examine various path possibilities.
  5558  *
  5559  *---------------------------------------------------------------------------
  5560  */
  5561 
  5562 EXPORT_C Tcl_Obj* 
  5563 Tcl_FSGetNormalizedPath(interp, pathObjPtr)
  5564     Tcl_Interp *interp;
  5565     Tcl_Obj* pathObjPtr;
  5566 {
  5567     FsPath *fsPathPtr;
  5568 
  5569     if (Tcl_FSConvertToPathType(interp, pathObjPtr) != TCL_OK) {
  5570 	return NULL;
  5571     }
  5572     fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  5573 
  5574     if (PATHFLAGS(pathObjPtr) != 0) {
  5575 	/* 
  5576 	 * This is a special path object which is the result of
  5577 	 * something like 'file join' 
  5578 	 */
  5579 	Tcl_Obj *dir, *copy;
  5580 	int cwdLen;
  5581 	int pathType;
  5582 	CONST char *cwdStr;
  5583 	ClientData clientData = NULL;
  5584 	
  5585 	pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr);
  5586 	dir = Tcl_FSGetNormalizedPath(interp, fsPathPtr->cwdPtr);
  5587 	if (dir == NULL) {
  5588 	    return NULL;
  5589 	}
  5590 	if (pathObjPtr->bytes == NULL) {
  5591 	    UpdateStringOfFsPath(pathObjPtr);
  5592 	}
  5593 	copy = Tcl_DuplicateObj(dir);
  5594 	Tcl_IncrRefCount(copy);
  5595 	Tcl_IncrRefCount(dir);
  5596 	/* We now own a reference on both 'dir' and 'copy' */
  5597 	
  5598 	cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
  5599 	/* 
  5600 	 * Should we perhaps use 'Tcl_FSPathSeparator'?
  5601 	 * But then what about the Windows special case?
  5602 	 * Perhaps we should just check if cwd is a root volume.
  5603 	 * We should never get cwdLen == 0 in this code path.
  5604 	 */
  5605 	switch (tclPlatform) {
  5606 	    case TCL_PLATFORM_UNIX:
  5607 		if (cwdStr[cwdLen-1] != '/') {
  5608 		    Tcl_AppendToObj(copy, "/", 1);
  5609 		    cwdLen++;
  5610 		}
  5611 		break;
  5612 	    case TCL_PLATFORM_WINDOWS:
  5613 		if (cwdStr[cwdLen-1] != '/' 
  5614 			&& cwdStr[cwdLen-1] != '\\') {
  5615 		    Tcl_AppendToObj(copy, "/", 1);
  5616 		    cwdLen++;
  5617 		}
  5618 		break;
  5619 	    case TCL_PLATFORM_MAC:
  5620 		if (cwdStr[cwdLen-1] != ':') {
  5621 		    Tcl_AppendToObj(copy, ":", 1);
  5622 		    cwdLen++;
  5623 		}
  5624 		break;
  5625 	}
  5626 	Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
  5627 	/* 
  5628 	 * Normalize the combined string, but only starting after
  5629 	 * the end of the previously normalized 'dir'.  This should
  5630 	 * be much faster!  We use 'cwdLen-1' so that we are
  5631 	 * already pointing at the dir-separator that we know about.
  5632 	 * The normalization code will actually start off directly
  5633 	 * after that separator.
  5634 	 */
  5635 	TclFSNormalizeToUniquePath(interp, copy, cwdLen-1, 
  5636 	  (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
  5637 	/* Now we need to construct the new path object */
  5638 	
  5639 	if (pathType == TCL_PATH_RELATIVE) {
  5640 	    FsPath* origDirFsPathPtr;
  5641 	    Tcl_Obj *origDir = fsPathPtr->cwdPtr;
  5642 	    origDirFsPathPtr = (FsPath*) PATHOBJ(origDir);
  5643 	    
  5644 	    fsPathPtr->cwdPtr = origDirFsPathPtr->cwdPtr;
  5645 	    Tcl_IncrRefCount(fsPathPtr->cwdPtr);
  5646 	    
  5647 	    Tcl_DecrRefCount(fsPathPtr->normPathPtr);
  5648 	    fsPathPtr->normPathPtr = copy;
  5649 	    /* That's our reference to copy used */
  5650 	    Tcl_DecrRefCount(dir);
  5651 	    Tcl_DecrRefCount(origDir);
  5652 	} else {
  5653 	    Tcl_DecrRefCount(fsPathPtr->cwdPtr);
  5654 	    fsPathPtr->cwdPtr = NULL;
  5655 	    Tcl_DecrRefCount(fsPathPtr->normPathPtr);
  5656 	    fsPathPtr->normPathPtr = copy;
  5657 	    /* That's our reference to copy used */
  5658 	    Tcl_DecrRefCount(dir);
  5659 	}
  5660 	if (clientData != NULL) {
  5661 	    fsPathPtr->nativePathPtr = clientData;
  5662 	}
  5663 	PATHFLAGS(pathObjPtr) = 0;
  5664     }
  5665     /* Ensure cwd hasn't changed */
  5666     if (fsPathPtr->cwdPtr != NULL) {
  5667 	if (!TclFSCwdPointerEquals(fsPathPtr->cwdPtr)) {
  5668 	    if (pathObjPtr->bytes == NULL) {
  5669 		UpdateStringOfFsPath(pathObjPtr);
  5670 	    }
  5671 	    FreeFsPathInternalRep(pathObjPtr);
  5672 	    pathObjPtr->typePtr = NULL;
  5673 	    if (Tcl_ConvertToType(interp, pathObjPtr, 
  5674 				  &tclFsPathType) != TCL_OK) {
  5675 		return NULL;
  5676 	    }
  5677 	    fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  5678 	} else if (fsPathPtr->normPathPtr == NULL) {
  5679 	    int cwdLen;
  5680 	    Tcl_Obj *copy;
  5681 	    CONST char *cwdStr;
  5682 	    ClientData clientData = NULL;
  5683 	    
  5684 	    copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
  5685 	    Tcl_IncrRefCount(copy);
  5686 	    cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
  5687 	    /* 
  5688 	     * Should we perhaps use 'Tcl_FSPathSeparator'?
  5689 	     * But then what about the Windows special case?
  5690 	     * Perhaps we should just check if cwd is a root volume.
  5691 	     * We should never get cwdLen == 0 in this code path.
  5692 	     */
  5693 	    switch (tclPlatform) {
  5694 		case TCL_PLATFORM_UNIX:
  5695 		    if (cwdStr[cwdLen-1] != '/') {
  5696 			Tcl_AppendToObj(copy, "/", 1);
  5697 			cwdLen++;
  5698 		    }
  5699 		    break;
  5700 		case TCL_PLATFORM_WINDOWS:
  5701 		    if (cwdStr[cwdLen-1] != '/' 
  5702 			    && cwdStr[cwdLen-1] != '\\') {
  5703 			Tcl_AppendToObj(copy, "/", 1);
  5704 			cwdLen++;
  5705 		    }
  5706 		    break;
  5707 		case TCL_PLATFORM_MAC:
  5708 		    if (cwdStr[cwdLen-1] != ':') {
  5709 			Tcl_AppendToObj(copy, ":", 1);
  5710 			cwdLen++;
  5711 		    }
  5712 		    break;
  5713 	    }
  5714 	    Tcl_AppendObjToObj(copy, pathObjPtr);
  5715 	    /* 
  5716 	     * Normalize the combined string, but only starting after
  5717 	     * the end of the previously normalized 'dir'.  This should
  5718 	     * be much faster!
  5719 	     */
  5720 	    TclFSNormalizeToUniquePath(interp, copy, cwdLen-1, 
  5721 	      (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
  5722 	    fsPathPtr->normPathPtr = copy;
  5723 	    if (clientData != NULL) {
  5724 		fsPathPtr->nativePathPtr = clientData;
  5725 	    }
  5726 	}
  5727     }
  5728     if (fsPathPtr->normPathPtr == NULL) {
  5729 	ClientData clientData = NULL;
  5730 	Tcl_Obj *useThisCwd = NULL;
  5731 	/* 
  5732 	 * Since normPathPtr is NULL, but this is a valid path
  5733 	 * object, we know that the translatedPathPtr cannot be NULL.
  5734 	 */
  5735 	Tcl_Obj *absolutePath = fsPathPtr->translatedPathPtr;
  5736 	char *path = Tcl_GetString(absolutePath);
  5737 	
  5738 	/* 
  5739 	 * We have to be a little bit careful here to avoid infinite loops
  5740 	 * we're asking Tcl_FSGetPathType to return the path's type, but
  5741 	 * that call can actually result in a lot of other filesystem
  5742 	 * action, which might loop back through here.
  5743 	 */
  5744 	if (path[0] != '\0') {
  5745 	    Tcl_PathType type = Tcl_FSGetPathType(pathObjPtr);
  5746 	    if (type == TCL_PATH_RELATIVE) {
  5747 		useThisCwd = Tcl_FSGetCwd(interp);
  5748 
  5749 		if (useThisCwd == NULL) return NULL;
  5750 
  5751 		absolutePath = Tcl_FSJoinToPath(useThisCwd, 1, &absolutePath);
  5752 		Tcl_IncrRefCount(absolutePath);
  5753 		/* We have a refCount on the cwd */
  5754 #ifdef __WIN32__
  5755 	    } else if (type == TCL_PATH_VOLUME_RELATIVE) {
  5756 		/* 
  5757 		 * Only Windows has volume-relative paths.  These
  5758 		 * paths are rather rare, but is is nice if Tcl can
  5759 		 * handle them.  It is much better if we can
  5760 		 * handle them here, rather than in the native fs code,
  5761 		 * because we really need to have a real absolute path
  5762 		 * just below.
  5763 		 * 
  5764 		 * We do not let this block compile on non-Windows
  5765 		 * platforms because the test suite's manual forcing
  5766 		 * of tclPlatform can otherwise cause this code path
  5767 		 * to be executed, causing various errors because
  5768 		 * volume-relative paths really do not exist.
  5769 		 */
  5770 		useThisCwd = Tcl_FSGetCwd(interp);
  5771 		if (useThisCwd == NULL) return NULL;
  5772 		
  5773 		if (path[0] == '/') {
  5774 		    /* 
  5775 		     * Path of form /foo/bar which is a path in the
  5776 		     * root directory of the current volume.
  5777 		     */
  5778 		    CONST char *drive = Tcl_GetString(useThisCwd);
  5779 		    absolutePath = Tcl_NewStringObj(drive,2);
  5780 		    Tcl_AppendToObj(absolutePath, path, -1);
  5781 		    Tcl_IncrRefCount(absolutePath);
  5782 		    /* We have a refCount on the cwd */
  5783 		} else {
  5784 		    /* 
  5785 		     * Path of form C:foo/bar, but this only makes
  5786 		     * sense if the cwd is also on drive C.
  5787 		     */
  5788 		    CONST char *drive = Tcl_GetString(useThisCwd);
  5789 		    char drive_c = path[0];
  5790 		    if (drive_c >= 'a') {
  5791 			drive_c -= ('a' - 'A');
  5792 		    }
  5793 		    if (drive[0] == drive_c) {
  5794 			absolutePath = Tcl_DuplicateObj(useThisCwd);
  5795 			/* We have a refCount on the cwd */
  5796 		    } else {
  5797 			Tcl_DecrRefCount(useThisCwd);
  5798 			useThisCwd = NULL;
  5799 			/* 
  5800 			 * The path is not in the current drive, but
  5801 			 * is volume-relative.  The way Tcl 8.3 handles
  5802 			 * this is that it treats such a path as
  5803 			 * relative to the root of the drive.  We
  5804 			 * therefore behave the same here.
  5805 			 */
  5806 			absolutePath = Tcl_NewStringObj(path, 2);
  5807 		    }
  5808 		    Tcl_IncrRefCount(absolutePath);
  5809 		    Tcl_AppendToObj(absolutePath, "/", 1);
  5810 		    Tcl_AppendToObj(absolutePath, path+2, -1);
  5811 		}
  5812 #endif /* __WIN32__ */
  5813 	    }
  5814 	}
  5815 	/* Already has refCount incremented */
  5816 	fsPathPtr->normPathPtr = TclFSNormalizeAbsolutePath(interp, absolutePath, 
  5817 		       (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
  5818 	if (0 && (clientData != NULL)) {
  5819 	    fsPathPtr->nativePathPtr = 
  5820 	      (*fsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc)(clientData);
  5821 	}
  5822 	if (!strcmp(Tcl_GetString(fsPathPtr->normPathPtr),
  5823 		    Tcl_GetString(pathObjPtr))) {
  5824 	    /* 
  5825 	     * The path was already normalized.  
  5826 	     * Get rid of the duplicate.
  5827 	     */
  5828 	    Tcl_DecrRefCount(fsPathPtr->normPathPtr);
  5829 	    /* 
  5830 	     * We do *not* increment the refCount for 
  5831 	     * this circular reference 
  5832 	     */
  5833 	    fsPathPtr->normPathPtr = pathObjPtr;
  5834 	}
  5835 	if (useThisCwd != NULL) {
  5836 	    /* This was returned by Tcl_FSJoinToPath above */
  5837 	    Tcl_DecrRefCount(absolutePath);
  5838 	    fsPathPtr->cwdPtr = useThisCwd;
  5839 	}
  5840     }
  5841 
  5842     return fsPathPtr->normPathPtr;
  5843 }
  5844 
  5845 /*
  5846  *---------------------------------------------------------------------------
  5847  *
  5848  * Tcl_FSGetInternalRep --
  5849  *
  5850  *      Extract the internal representation of a given path object,
  5851  *      in the given filesystem.  If the path object belongs to a
  5852  *      different filesystem, we return NULL.
  5853  *      
  5854  *      If the internal representation is currently NULL, we attempt
  5855  *      to generate it, by calling the filesystem's 
  5856  *      'Tcl_FSCreateInternalRepProc'.
  5857  *
  5858  * Results:
  5859  *      NULL or a valid internal representation.
  5860  *
  5861  * Side effects:
  5862  *	An attempt may be made to convert the object.
  5863  *
  5864  *---------------------------------------------------------------------------
  5865  */
  5866 
  5867 EXPORT_C ClientData 
  5868 Tcl_FSGetInternalRep(pathObjPtr, fsPtr)
  5869     Tcl_Obj* pathObjPtr;
  5870     Tcl_Filesystem *fsPtr;
  5871 {
  5872     FsPath *srcFsPathPtr;
  5873     
  5874     if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) {
  5875 	return NULL;
  5876     }
  5877     srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  5878     
  5879     /* 
  5880      * We will only return the native representation for the caller's
  5881      * filesystem.  Otherwise we will simply return NULL. This means
  5882      * that there must be a unique bi-directional mapping between paths
  5883      * and filesystems, and that this mapping will not allow 'remapped'
  5884      * files -- files which are in one filesystem but mapped into
  5885      * another.  Another way of putting this is that 'stacked'
  5886      * filesystems are not allowed.  We recognise that this is a
  5887      * potentially useful feature for the future.
  5888      * 
  5889      * Even something simple like a 'pass through' filesystem which
  5890      * logs all activity and passes the calls onto the native system
  5891      * would be nice, but not easily achievable with the current
  5892      * implementation.
  5893      */
  5894     if (srcFsPathPtr->fsRecPtr == NULL) {
  5895 	/* 
  5896 	 * This only usually happens in wrappers like TclpStat which
  5897 	 * create a string object and pass it to TclpObjStat.  Code
  5898 	 * which calls the Tcl_FS..  functions should always have a
  5899 	 * filesystem already set.  Whether this code path is legal or
  5900 	 * not depends on whether we decide to allow external code to
  5901 	 * call the native filesystem directly.  It is at least safer
  5902 	 * to allow this sub-optimal routing.
  5903 	 */
  5904 	Tcl_FSGetFileSystemForPath(pathObjPtr);
  5905 	
  5906 	/* 
  5907 	 * If we fail through here, then the path is probably not a
  5908 	 * valid path in the filesystsem, and is most likely to be a
  5909 	 * use of the empty path "" via a direct call to one of the
  5910 	 * objectified interfaces (e.g. from the Tcl testsuite).
  5911 	 */
  5912 	srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  5913 	if (srcFsPathPtr->fsRecPtr == NULL) {
  5914 	    return NULL;
  5915 	}
  5916     }
  5917 
  5918     if (fsPtr != srcFsPathPtr->fsRecPtr->fsPtr) {
  5919 	/* 
  5920 	 * There is still one possibility we should consider; if the
  5921 	 * file belongs to a different filesystem, perhaps it is
  5922 	 * actually linked through to a file in our own filesystem
  5923 	 * which we do care about.  The way we can check for this
  5924 	 * is we ask what filesystem this path belongs to.
  5925 	 */
  5926 	Tcl_Filesystem *actualFs = Tcl_FSGetFileSystemForPath(pathObjPtr);
  5927 	if (actualFs == fsPtr) {
  5928 	    return Tcl_FSGetInternalRep(pathObjPtr, fsPtr);
  5929 	}
  5930 	return NULL;
  5931     }
  5932 
  5933     if (srcFsPathPtr->nativePathPtr == NULL) {
  5934 	Tcl_FSCreateInternalRepProc *proc;
  5935 	char *nativePathPtr;
  5936 
  5937 	proc = srcFsPathPtr->fsRecPtr->fsPtr->createInternalRepProc;
  5938 	if (proc == NULL) {
  5939 	    return NULL;
  5940 	}
  5941 
  5942 	nativePathPtr = (*proc)(pathObjPtr);
  5943 	srcFsPathPtr  = (FsPath*) PATHOBJ(pathObjPtr);
  5944 	srcFsPathPtr->nativePathPtr = nativePathPtr;
  5945     }
  5946 
  5947     return srcFsPathPtr->nativePathPtr;
  5948 }
  5949 
  5950 /*
  5951  *---------------------------------------------------------------------------
  5952  *
  5953  * TclFSEnsureEpochOk --
  5954  *
  5955  *      This will ensure the pathObjPtr is up to date and can be
  5956  *      converted into a "path" type, and that we are able to generate a
  5957  *      complete normalized path which is used to determine the
  5958  *      filesystem match.
  5959  *
  5960  * Results:
  5961  *      Standard Tcl return code.
  5962  *
  5963  * Side effects:
  5964  *	An attempt may be made to convert the object.
  5965  *
  5966  *---------------------------------------------------------------------------
  5967  */
  5968 
  5969 int 
  5970 TclFSEnsureEpochOk(pathObjPtr, fsPtrPtr)
  5971     Tcl_Obj* pathObjPtr;
  5972     Tcl_Filesystem **fsPtrPtr;
  5973 {
  5974     FsPath *srcFsPathPtr;
  5975     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  5976 
  5977     /* 
  5978      * SHOULD BE ABLE TO IMPROVE EFFICIENCY HERE.
  5979      */
  5980 
  5981     if (Tcl_FSGetNormalizedPath(NULL, pathObjPtr) == NULL) {
  5982 	return TCL_ERROR;
  5983     }
  5984 
  5985     srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  5986 
  5987     /* 
  5988      * Check if the filesystem has changed in some way since
  5989      * this object's internal representation was calculated.
  5990      */
  5991     if (srcFsPathPtr->filesystemEpoch != tsdPtr->filesystemEpoch) {
  5992 	/* 
  5993 	 * We have to discard the stale representation and 
  5994 	 * recalculate it 
  5995 	 */
  5996 	if (pathObjPtr->bytes == NULL) {
  5997 	    UpdateStringOfFsPath(pathObjPtr);
  5998 	}
  5999 	FreeFsPathInternalRep(pathObjPtr);
  6000 	pathObjPtr->typePtr = NULL;
  6001 	if (SetFsPathFromAny(NULL, pathObjPtr) != TCL_OK) {
  6002 	    return TCL_ERROR;
  6003 	}
  6004 	srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  6005     }
  6006     /* Check whether the object is already assigned to a fs */
  6007     if (srcFsPathPtr->fsRecPtr != NULL) {
  6008 	*fsPtrPtr = srcFsPathPtr->fsRecPtr->fsPtr;
  6009     }
  6010 
  6011     return TCL_OK;
  6012 }
  6013 
  6014 void 
  6015 TclFSSetPathDetails(pathObjPtr, fsRecPtr, clientData) 
  6016     Tcl_Obj *pathObjPtr;
  6017     FilesystemRecord *fsRecPtr;
  6018     ClientData clientData;
  6019 {
  6020     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  6021     /* We assume pathObjPtr is already of the correct type */
  6022     FsPath *srcFsPathPtr;
  6023     
  6024     srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  6025     srcFsPathPtr->fsRecPtr = fsRecPtr;
  6026     srcFsPathPtr->nativePathPtr = clientData;
  6027     srcFsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
  6028     fsRecPtr->fileRefCount++;
  6029 }
  6030 
  6031 /*
  6032  *---------------------------------------------------------------------------
  6033  *
  6034  * Tcl_FSEqualPaths --
  6035  *
  6036  *      This function tests whether the two paths given are equal path
  6037  *      objects.  If either or both is NULL, 0 is always returned.
  6038  *
  6039  * Results:
  6040  *      1 or 0.
  6041  *
  6042  * Side effects:
  6043  *	None.
  6044  *
  6045  *---------------------------------------------------------------------------
  6046  */
  6047 
  6048 EXPORT_C int 
  6049 Tcl_FSEqualPaths(firstPtr, secondPtr)
  6050     Tcl_Obj* firstPtr;
  6051     Tcl_Obj* secondPtr;
  6052 {
  6053     if (firstPtr == secondPtr) {
  6054 	return 1;
  6055     } else {
  6056 	char *firstStr, *secondStr;
  6057 	int firstLen, secondLen, tempErrno;
  6058 
  6059 	if (firstPtr == NULL || secondPtr == NULL) {
  6060 	    return 0;
  6061 	}
  6062 	firstStr  = Tcl_GetStringFromObj(firstPtr, &firstLen);
  6063 	secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
  6064 	if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
  6065 	    return 1;
  6066 	}
  6067 	/* 
  6068 	 * Try the most thorough, correct method of comparing fully
  6069 	 * normalized paths
  6070 	 */
  6071 
  6072 	tempErrno = Tcl_GetErrno();
  6073 	firstPtr = Tcl_FSGetNormalizedPath(NULL, firstPtr);
  6074 	secondPtr = Tcl_FSGetNormalizedPath(NULL, secondPtr);
  6075 	Tcl_SetErrno(tempErrno);
  6076 
  6077 	if (firstPtr == NULL || secondPtr == NULL) {
  6078 	    return 0;
  6079 	}
  6080 	firstStr  = Tcl_GetStringFromObj(firstPtr, &firstLen);
  6081 	secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
  6082 	if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
  6083 	    return 1;
  6084 	}
  6085     }
  6086 
  6087     return 0;
  6088 }
  6089 
  6090 /*
  6091  *---------------------------------------------------------------------------
  6092  *
  6093  * SetFsPathFromAny --
  6094  *
  6095  *      This function tries to convert the given Tcl_Obj to a valid
  6096  *      Tcl path type.
  6097  *      
  6098  *      The filename may begin with "~" (to indicate current user's
  6099  *      home directory) or "~<user>" (to indicate any user's home
  6100  *      directory).
  6101  *
  6102  * Results:
  6103  *      Standard Tcl error code.
  6104  *
  6105  * Side effects:
  6106  *	The old representation may be freed, and new memory allocated.
  6107  *
  6108  *---------------------------------------------------------------------------
  6109  */
  6110 
  6111 static int
  6112 SetFsPathFromAny(interp, objPtr)
  6113     Tcl_Interp *interp;		/* Used for error reporting if not NULL. */
  6114     Tcl_Obj *objPtr;		/* The object to convert. */
  6115 {
  6116     int len;
  6117     FsPath *fsPathPtr;
  6118     Tcl_Obj *transPtr;
  6119     char *name;
  6120     ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
  6121     
  6122     if (objPtr->typePtr == &tclFsPathType) {
  6123 	return TCL_OK;
  6124     }
  6125     
  6126     /* 
  6127      * First step is to translate the filename.  This is similar to
  6128      * Tcl_TranslateFilename, but shouldn't convert everything to
  6129      * windows backslashes on that platform.  The current
  6130      * implementation of this piece is a slightly optimised version
  6131      * of the various Tilde/Split/Join stuff to avoid multiple
  6132      * split/join operations.
  6133      * 
  6134      * We remove any trailing directory separator.
  6135      * 
  6136      * However, the split/join routines are quite complex, and
  6137      * one has to make sure not to break anything on Unix, Win
  6138      * or MacOS (fCmd.test, fileName.test and cmdAH.test exercise
  6139      * most of the code).
  6140      */
  6141     name = Tcl_GetStringFromObj(objPtr,&len);
  6142 
  6143     /*
  6144      * Handle tilde substitutions, if needed.
  6145      */
  6146     if (name[0] == '~') {
  6147 	char *expandedUser;
  6148 	Tcl_DString temp;
  6149 	int split;
  6150 	char separator='/';
  6151 	
  6152 	if (tclPlatform==TCL_PLATFORM_MAC) {
  6153 	    if (strchr(name, ':') != NULL) separator = ':';
  6154 	}
  6155 	
  6156 	split = FindSplitPos(name, &separator);
  6157 	if (split != len) {
  6158 	    /* We have multiple pieces '~user/foo/bar...' */
  6159 	    name[split] = '\0';
  6160 	}
  6161 	/* Do some tilde substitution */
  6162 	if (name[1] == '\0') {
  6163 	    /* We have just '~' */
  6164 	    CONST char *dir;
  6165 	    Tcl_DString dirString;
  6166 	    if (split != len) { name[split] = separator; }
  6167 	    
  6168 	    dir = TclGetEnv("HOME", &dirString);
  6169 	    if (dir == NULL) {
  6170 		if (interp) {
  6171 		    Tcl_ResetResult(interp);
  6172 		    Tcl_AppendResult(interp, "couldn't find HOME environment ",
  6173 			    "variable to expand path", (char *) NULL);
  6174 		}
  6175 		return TCL_ERROR;
  6176 	    }
  6177 	    Tcl_DStringInit(&temp);
  6178 	    Tcl_JoinPath(1, &dir, &temp);
  6179 	    Tcl_DStringFree(&dirString);
  6180 	} else {
  6181 	    /* We have a user name '~user' */
  6182 	    Tcl_DStringInit(&temp);
  6183 	    if (TclpGetUserHome(name+1, &temp) == NULL) {	
  6184 		if (interp != NULL) {
  6185 		    Tcl_ResetResult(interp);
  6186 		    Tcl_AppendResult(interp, "user \"", (name+1), 
  6187 				     "\" doesn't exist", (char *) NULL);
  6188 		}
  6189 		Tcl_DStringFree(&temp);
  6190 		if (split != len) { name[split] = separator; }
  6191 		return TCL_ERROR;
  6192 	    }
  6193 	    if (split != len) { name[split] = separator; }
  6194 	}
  6195 	
  6196 	expandedUser = Tcl_DStringValue(&temp);
  6197 	transPtr = Tcl_NewStringObj(expandedUser, Tcl_DStringLength(&temp));
  6198 
  6199 	if (split != len) {
  6200 	    /* Join up the tilde substitution with the rest */
  6201 	    if (name[split+1] == separator) {
  6202 
  6203 		/*
  6204 		 * Somewhat tricky case like ~//foo/bar.
  6205 		 * Make use of Split/Join machinery to get it right.
  6206 		 * Assumes all paths beginning with ~ are part of the
  6207 		 * native filesystem.
  6208 		 */
  6209 
  6210 		int objc;
  6211 		Tcl_Obj **objv;
  6212 		Tcl_Obj *parts = TclpNativeSplitPath(objPtr, NULL);
  6213 		Tcl_ListObjGetElements(NULL, parts, &objc, &objv);
  6214 		/* Skip '~'.  It's replaced by its expansion */
  6215 		objc--; objv++;
  6216 		while (objc--) {
  6217 		    TclpNativeJoinPath(transPtr, Tcl_GetString(*objv++));
  6218 		}
  6219 		Tcl_DecrRefCount(parts);
  6220 	    } else {
  6221 		/* Simple case. "rest" is relative path.  Just join it. */
  6222 		Tcl_Obj *rest = Tcl_NewStringObj(name+split+1,-1);
  6223 		transPtr = Tcl_FSJoinToPath(transPtr, 1, &rest);
  6224 	    }
  6225 	}
  6226 	Tcl_DStringFree(&temp);
  6227     } else {
  6228 	transPtr = Tcl_FSJoinToPath(objPtr,0,NULL);
  6229     }
  6230 
  6231 #if defined(__CYGWIN__) && defined(__WIN32__)
  6232     {
  6233     extern int cygwin_conv_to_win32_path 
  6234 	_ANSI_ARGS_((CONST char *, char *));
  6235     char winbuf[MAX_PATH+1];
  6236 
  6237     /*
  6238      * In the Cygwin world, call conv_to_win32_path in order to use the
  6239      * mount table to translate the file name into something Windows will
  6240      * understand.  Take care when converting empty strings!
  6241      */
  6242     name = Tcl_GetStringFromObj(transPtr, &len);
  6243     if (len > 0) {
  6244 	cygwin_conv_to_win32_path(name, winbuf);
  6245 	TclWinNoBackslash(winbuf);
  6246 	Tcl_SetStringObj(transPtr, winbuf, -1);
  6247     }
  6248     }
  6249 #endif /* __CYGWIN__ && __WIN32__ */
  6250 
  6251     /* 
  6252      * Now we have a translated filename in 'transPtr'.  This will have
  6253      * forward slashes on Windows, and will not contain any ~user
  6254      * sequences.
  6255      */
  6256     
  6257     fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
  6258 
  6259     fsPathPtr->translatedPathPtr = transPtr;
  6260     Tcl_IncrRefCount(fsPathPtr->translatedPathPtr);
  6261     fsPathPtr->normPathPtr = NULL;
  6262     fsPathPtr->cwdPtr = NULL;
  6263     fsPathPtr->nativePathPtr = NULL;
  6264     fsPathPtr->fsRecPtr = NULL;
  6265     fsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
  6266 
  6267     /*
  6268      * Free old representation before installing our new one.
  6269      */
  6270     if (objPtr->typePtr != NULL && objPtr->typePtr->freeIntRepProc != NULL) {
  6271 	(objPtr->typePtr->freeIntRepProc)(objPtr);
  6272     }
  6273     PATHOBJ(objPtr) = (VOID *) fsPathPtr;
  6274     PATHFLAGS(objPtr) = 0;
  6275     objPtr->typePtr = &tclFsPathType;
  6276 
  6277     return TCL_OK;
  6278 }
  6279 
  6280 static void
  6281 FreeFsPathInternalRep(pathObjPtr)
  6282     Tcl_Obj *pathObjPtr;	/* Path object with internal rep to free. */
  6283 {
  6284     FsPath *fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
  6285 
  6286     if (fsPathPtr->translatedPathPtr != NULL) {
  6287 	if (fsPathPtr->translatedPathPtr != pathObjPtr) {
  6288 	    Tcl_DecrRefCount(fsPathPtr->translatedPathPtr);
  6289 	}
  6290     }
  6291     if (fsPathPtr->normPathPtr != NULL) {
  6292 	if (fsPathPtr->normPathPtr != pathObjPtr) {
  6293 	    Tcl_DecrRefCount(fsPathPtr->normPathPtr);
  6294 	}
  6295 	fsPathPtr->normPathPtr = NULL;
  6296     }
  6297     if (fsPathPtr->cwdPtr != NULL) {
  6298 	Tcl_DecrRefCount(fsPathPtr->cwdPtr);
  6299     }
  6300     if (fsPathPtr->nativePathPtr != NULL) {
  6301 	if (fsPathPtr->fsRecPtr != NULL) {
  6302 	    if (fsPathPtr->fsRecPtr->fsPtr->freeInternalRepProc != NULL) {
  6303 		(*fsPathPtr->fsRecPtr->fsPtr
  6304 		   ->freeInternalRepProc)(fsPathPtr->nativePathPtr);
  6305 		fsPathPtr->nativePathPtr = NULL;
  6306 	    }
  6307 	}
  6308     }
  6309     if (fsPathPtr->fsRecPtr != NULL) {
  6310 	fsPathPtr->fsRecPtr->fileRefCount--;
  6311 	if (fsPathPtr->fsRecPtr->fileRefCount <= 0) {
  6312 	    /* It has been unregistered already, so simply free it */
  6313 	    ckfree((char *)fsPathPtr->fsRecPtr);
  6314 	}
  6315     }
  6316 
  6317     ckfree((char*) fsPathPtr);
  6318 }
  6319 
  6320 
  6321 static void
  6322 DupFsPathInternalRep(srcPtr, copyPtr)
  6323     Tcl_Obj *srcPtr;		/* Path obj with internal rep to copy. */
  6324     Tcl_Obj *copyPtr;		/* Path obj with internal rep to set. */
  6325 {
  6326     FsPath *srcFsPathPtr = (FsPath*) PATHOBJ(srcPtr);
  6327     FsPath *copyFsPathPtr = (FsPath*) ckalloc((unsigned)sizeof(FsPath));
  6328       
  6329     Tcl_FSDupInternalRepProc *dupProc;
  6330     
  6331     PATHOBJ(copyPtr) = (VOID *) copyFsPathPtr;
  6332 
  6333     if (srcFsPathPtr->translatedPathPtr != NULL) {
  6334 	copyFsPathPtr->translatedPathPtr = srcFsPathPtr->translatedPathPtr;
  6335 	if (copyFsPathPtr->translatedPathPtr != copyPtr) {
  6336 	    Tcl_IncrRefCount(copyFsPathPtr->translatedPathPtr);
  6337 	}
  6338     } else {
  6339 	copyFsPathPtr->translatedPathPtr = NULL;
  6340     }
  6341     
  6342     if (srcFsPathPtr->normPathPtr != NULL) {
  6343 	copyFsPathPtr->normPathPtr = srcFsPathPtr->normPathPtr;
  6344 	if (copyFsPathPtr->normPathPtr != copyPtr) {
  6345 	    Tcl_IncrRefCount(copyFsPathPtr->normPathPtr);
  6346 	}
  6347     } else {
  6348 	copyFsPathPtr->normPathPtr = NULL;
  6349     }
  6350     
  6351     if (srcFsPathPtr->cwdPtr != NULL) {
  6352 	copyFsPathPtr->cwdPtr = srcFsPathPtr->cwdPtr;
  6353 	Tcl_IncrRefCount(copyFsPathPtr->cwdPtr);
  6354     } else {
  6355 	copyFsPathPtr->cwdPtr = NULL;
  6356     }
  6357 
  6358     copyFsPathPtr->flags = srcFsPathPtr->flags;
  6359     
  6360     if (srcFsPathPtr->fsRecPtr != NULL 
  6361       && srcFsPathPtr->nativePathPtr != NULL) {
  6362 	dupProc = srcFsPathPtr->fsRecPtr->fsPtr->dupInternalRepProc;
  6363 	if (dupProc != NULL) {
  6364 	    copyFsPathPtr->nativePathPtr = 
  6365 	      (*dupProc)(srcFsPathPtr->nativePathPtr);
  6366 	} else {
  6367 	    copyFsPathPtr->nativePathPtr = NULL;
  6368 	}
  6369     } else {
  6370 	copyFsPathPtr->nativePathPtr = NULL;
  6371     }
  6372     copyFsPathPtr->fsRecPtr = srcFsPathPtr->fsRecPtr;
  6373     copyFsPathPtr->filesystemEpoch = srcFsPathPtr->filesystemEpoch;
  6374     if (copyFsPathPtr->fsRecPtr != NULL) {
  6375 	copyFsPathPtr->fsRecPtr->fileRefCount++;
  6376     }
  6377 
  6378     copyPtr->typePtr = &tclFsPathType;
  6379 }
  6380 
  6381 /*
  6382  *---------------------------------------------------------------------------
  6383  *
  6384  * UpdateStringOfFsPath --
  6385  *
  6386  *      Gives an object a valid string rep.
  6387  *      
  6388  * Results:
  6389  *      None.
  6390  *
  6391  * Side effects:
  6392  *	Memory may be allocated.
  6393  *
  6394  *---------------------------------------------------------------------------
  6395  */
  6396 
  6397 static void
  6398 UpdateStringOfFsPath(objPtr)
  6399     register Tcl_Obj *objPtr;	/* path obj with string rep to update. */
  6400 {
  6401     FsPath *fsPathPtr = (FsPath*) PATHOBJ(objPtr);
  6402     CONST char *cwdStr;
  6403     int cwdLen;
  6404     Tcl_Obj *copy;
  6405     
  6406     if (PATHFLAGS(objPtr) == 0 || fsPathPtr->cwdPtr == NULL) {
  6407 	panic("Called UpdateStringOfFsPath with invalid object");
  6408     }
  6409     
  6410     copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
  6411     Tcl_IncrRefCount(copy);
  6412     
  6413     cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
  6414     /* 
  6415      * Should we perhaps use 'Tcl_FSPathSeparator'?
  6416      * But then what about the Windows special case?
  6417      * Perhaps we should just check if cwd is a root volume.
  6418      * We should never get cwdLen == 0 in this code path.
  6419      */
  6420     switch (tclPlatform) {
  6421 	case TCL_PLATFORM_UNIX:
  6422 	    if (cwdStr[cwdLen-1] != '/') {
  6423 		Tcl_AppendToObj(copy, "/", 1);
  6424 		cwdLen++;
  6425 	    }
  6426 	    break;
  6427 	case TCL_PLATFORM_WINDOWS:
  6428 	    /* 
  6429 	     * We need the extra 'cwdLen != 2', and ':' checks because 
  6430 	     * a volume relative path doesn't get a '/'.  For example 
  6431 	     * 'glob C:*cat*.exe' will return 'C:cat32.exe'
  6432 	     */
  6433 	    if (cwdStr[cwdLen-1] != '/'
  6434 		    && cwdStr[cwdLen-1] != '\\') {
  6435 		if (cwdLen != 2 || cwdStr[1] != ':') {
  6436 		    Tcl_AppendToObj(copy, "/", 1);
  6437 		    cwdLen++;
  6438 		}
  6439 	    }
  6440 	    break;
  6441 	case TCL_PLATFORM_MAC:
  6442 	    if (cwdStr[cwdLen-1] != ':') {
  6443 		Tcl_AppendToObj(copy, ":", 1);
  6444 		cwdLen++;
  6445 	    }
  6446 	    break;
  6447     }
  6448     Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
  6449     objPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen);
  6450     objPtr->length = cwdLen;
  6451     copy->bytes = tclEmptyStringRep;
  6452     copy->length = 0;
  6453     Tcl_DecrRefCount(copy);
  6454 }
  6455 
  6456 /*
  6457  *---------------------------------------------------------------------------
  6458  *
  6459  * NativePathInFilesystem --
  6460  *
  6461  *      Any path object is acceptable to the native filesystem, by
  6462  *      default (we will throw errors when illegal paths are actually
  6463  *      tried to be used).
  6464  *      
  6465  *      However, this behavior means the native filesystem must be
  6466  *      the last filesystem in the lookup list (otherwise it will
  6467  *      claim all files belong to it, and other filesystems will
  6468  *      never get a look in).
  6469  *
  6470  * Results:
  6471  *      TCL_OK, to indicate 'yes', -1 to indicate no.
  6472  *
  6473  * Side effects:
  6474  *	None.
  6475  *
  6476  *---------------------------------------------------------------------------
  6477  */
  6478 static int 
  6479 NativePathInFilesystem(pathPtr, clientDataPtr)
  6480     Tcl_Obj *pathPtr;
  6481     ClientData *clientDataPtr;
  6482 {
  6483     /* 
  6484      * A special case is required to handle the empty path "". 
  6485      * This is a valid path (i.e. the user should be able
  6486      * to do 'file exists ""' without throwing an error), but
  6487      * equally the path doesn't exist.  Those are the semantics
  6488      * of Tcl (at present anyway), so we have to abide by them
  6489      * here.
  6490      */
  6491     if (pathPtr->typePtr == &tclFsPathType) {
  6492 	if (pathPtr->bytes != NULL && pathPtr->bytes[0] == '\0') {
  6493 	    /* We reject the empty path "" */
  6494 	    return -1;
  6495 	}
  6496 	/* Otherwise there is no way this path can be empty */
  6497     } else {
  6498 	/* 
  6499 	 * It is somewhat unusual to reach this code path without
  6500 	 * the object being of tclFsPathType.  However, we do
  6501 	 * our best to deal with the situation.
  6502 	 */
  6503 	int len;
  6504 	Tcl_GetStringFromObj(pathPtr,&len);
  6505 	if (len == 0) {
  6506 	    /* We reject the empty path "" */
  6507 	    return -1;
  6508 	}
  6509     }
  6510     /* 
  6511      * Path is of correct type, or is of non-zero length, 
  6512      * so we accept it.
  6513      */
  6514     return TCL_OK;
  6515 }