os/persistentdata/persistentstorage/sqlite3api/TEST/TCL/tcldistribution/generic/tclIOUtil.c
Update contrib.
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.
11 * Parts of this file are based on code contributed by Karl
12 * Lehenbauer, Mark Diekhans and Peter da Silva.
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.
18 * See the file "license.terms" for information on usage and redistribution
19 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
21 * RCS: @(#) $Id: tclIOUtil.c,v 1.77.2.34 2007/02/19 23:49:05 hobbs Exp $
27 #include "tclMacInt.h"
30 /* for tclWinProcs->useWide */
31 #include "tclWinInt.h"
33 #if defined(__SYMBIAN32__) && defined(__WINSCW__)
34 #include "tclSymbianGlobals.h"
35 #define dataKey getdataKey(4)
39 * struct FilesystemRecord --
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.
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
52 int fileRefCount; /* How many Tcl_Obj's use this
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. */
63 * The internal TclFS API provides routines for handling and
64 * manipulating paths efficiently, taking direct advantage of
65 * the "path" Tcl_Obj type.
67 * These functions are not exported at all at present.
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));
86 * Private variables for use in this file
88 extern Tcl_Filesystem tclNativeFilesystem;
89 extern int theFilesystemEpoch;
92 * Private functions for use in this file
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));
105 * Prototypes for procedures defined later in this file.
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));
116 static void FsRecacheFilesystemList(void);
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
126 extern CONST char * tclpFileAttrStrings[];
127 extern CONST TclFileAttrProcs tclpFileAttrProcs[];
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).
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. */
142 Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
144 Tcl_IncrRefCount(pathPtr);
145 ret = Tcl_FSStat(pathPtr, &buf);
146 Tcl_DecrRefCount(pathPtr);
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
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:
158 # define OUT_OF_URANGE(x) \
159 ((((Tcl_WideUInt)(~ (__typeof__(x)) 0)) > (Tcl_WideUInt)ULONG_MAX) && \
160 (((Tcl_WideUInt)(x)) & ~(Tcl_WideUInt)ULONG_MAX))
162 # define OUT_OF_URANGE(x) \
163 (((Tcl_WideUInt)(x)) > (Tcl_WideUInt)ULONG_MAX)
167 * Perform the result-buffer overflow check manually.
169 * Note that ino_t/ino64_t is unsigned...
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)
183 # error "What status should be returned for file size out of range?"
190 # undef OUT_OF_URANGE
191 #endif /* !TCL_WIDE_INT_IS_LONG */
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.
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;
222 Tcl_Access(path, mode)
223 CONST char *path; /* Path of file to access (in current CP). */
224 int mode; /* Permission setting. */
227 Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
228 Tcl_IncrRefCount(pathPtr);
229 ret = Tcl_FSAccess(pathPtr,mode);
230 Tcl_DecrRefCount(pathPtr);
236 Tcl_OpenFileChannel(interp, path, modeString, permissions)
237 Tcl_Interp *interp; /* Interpreter for error reporting;
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
247 Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
248 Tcl_IncrRefCount(pathPtr);
249 ret = Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions);
250 Tcl_DecrRefCount(pathPtr);
261 Tcl_Obj *pathPtr = Tcl_NewStringObj(dirName,-1);
262 Tcl_IncrRefCount(pathPtr);
263 ret = Tcl_FSChdir(pathPtr);
264 Tcl_DecrRefCount(pathPtr);
270 Tcl_GetCwd(interp, cwdPtr)
275 cwd = Tcl_FSGetCwd(interp);
279 Tcl_DStringInit(cwdPtr);
280 Tcl_DStringAppend(cwdPtr, Tcl_GetString(cwd), -1);
281 Tcl_DecrRefCount(cwd);
282 return Tcl_DStringValue(cwdPtr);
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. */
294 Tcl_Obj *pathPtr = Tcl_NewStringObj(fileName,-1);
295 Tcl_IncrRefCount(pathPtr);
296 ret = Tcl_FSEvalFile(interp, pathPtr);
297 Tcl_DecrRefCount(pathPtr);
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.
311 * As soon as prowrap and mktclapp are updated to use the full
312 * filesystem support, I suggest all these hooks are removed.
314 #define USE_OBSOLETE_FS_HOOKS
317 #ifdef USE_OBSOLETE_FS_HOOKS
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.
325 typedef struct StatProc {
326 TclStatProc_ *proc; /* Function to process a 'stat()' call */
327 struct StatProc *nextPtr; /* The next 'stat()' function to call */
330 typedef struct AccessProc {
331 TclAccessProc_ *proc; /* Function to process a 'access()' call */
332 struct AccessProc *nextPtr; /* The next 'access()' function to call */
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;
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.
349 * The "delete" functions (e.g. 'TclStatDeleteProc(...)') ensure that
350 * these statically declared list entry cannot be inadvertently removed.
352 * This method avoids the need to call any sort of "initialization"
355 * All three lists are protected by a global obsoleteFsHookMutex.
358 static StatProc *statProcList = NULL;
359 static AccessProc *accessProcList = NULL;
360 static OpenFileChannelProc *openFileChannelProcList = NULL;
362 TCL_DECLARE_MUTEX(obsoleteFsHookMutex)
364 #endif /* USE_OBSOLETE_FS_HOOKS */
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.
373 * The correct API to use now is the Tcl_FS... set of functions,
374 * which ensure correct and complete virtual filesystem support.
376 * We cannot make all of these static, since some of them
377 * are implemented in the platform-specific directories.
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;
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
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;
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
422 Tcl_Filesystem tclNativeFilesystem = {
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,
436 &TclpOpenFileChannel,
437 &TclpMatchInDirectory,
445 &NativeFileAttrStrings,
448 &TclpObjCreateDirectory,
449 &TclpObjRemoveDirectory,
453 &TclpObjCopyDirectory,
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.
466 * We initialize the record so that it thinks one file uses it. This
467 * means it will never be freed.
469 static FilesystemRecord nativeFilesystemRecord = {
471 &tclNativeFilesystem,
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.
483 int theFilesystemEpoch = 0;
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.
490 static FilesystemRecord *filesystemList = &nativeFilesystemRecord;
492 TCL_DECLARE_MUTEX(filesystemMutex)
494 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)
496 * Used to implement Tcl_FSGetCwd in a file-system independent way.
498 static Tcl_Obj* cwdPathPtr = NULL;
499 static int cwdPathEpoch = 0;
501 TCL_DECLARE_MUTEX(cwdMutex)
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.
511 typedef struct ThreadSpecificData {
516 FilesystemRecord *filesystemList;
517 } ThreadSpecificData;
519 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)
520 static Tcl_ThreadDataKey dataKey;
524 * Declare fallback support function and
525 * information for Tcl_FSLoadFile
527 static Tcl_FSUnloadFileProc FSUnloadTempFile;
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.
537 typedef struct FsDivertLoad {
538 Tcl_LoadHandle loadHandle;
539 Tcl_FSUnloadFileProc *unloadProcPtr;
540 Tcl_Obj *divertedFile;
541 Tcl_Filesystem *divertedFilesystem;
542 ClientData divertedFileNativeRep;
545 /* Now move on to the basic filesystem implementation */
551 ThreadSpecificData *tsdPtr = (ThreadSpecificData*)cd;
552 FilesystemRecord *fsRecPtr = NULL, *tmpFsRecPtr = NULL;
554 /* Trash the cwd copy */
555 if (tsdPtr->cwdPathPtr != NULL) {
556 Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
557 tsdPtr->cwdPathPtr = NULL;
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);
566 fsRecPtr = tmpFsRecPtr;
568 tsdPtr->initialized = 0;
572 TclFSCwdPointerEquals(objPtr)
575 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
577 Tcl_MutexLock(&cwdMutex);
578 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)
579 if (tsdPtr->cwdPathPtr == NULL) {
580 if (cwdPathPtr == NULL) {
581 tsdPtr->cwdPathPtr = NULL;
583 tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr);
584 Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
586 tsdPtr->cwdPathEpoch = cwdPathEpoch;
587 } else if (tsdPtr->cwdPathEpoch != cwdPathEpoch) {
588 Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
589 if (cwdPathPtr == NULL) {
590 tsdPtr->cwdPathPtr = NULL;
592 tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr);
593 Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
597 if (tsdPtr->cwdPathPtr == NULL) {
598 if (glcwdPathPtr == NULL) {
599 tsdPtr->cwdPathPtr = NULL;
601 tsdPtr->cwdPathPtr = Tcl_DuplicateObj(glcwdPathPtr);
602 Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
604 tsdPtr->cwdPathEpoch = glcwdPathEpoch;
605 } else if (tsdPtr->cwdPathEpoch != glcwdPathEpoch) {
606 Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
607 if (glcwdPathPtr == NULL) {
608 tsdPtr->cwdPathPtr = NULL;
610 tsdPtr->cwdPathPtr = Tcl_DuplicateObj(glcwdPathPtr);
611 Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
615 Tcl_MutexUnlock(&cwdMutex);
617 if (tsdPtr->initialized == 0) {
618 Tcl_CreateThreadExitHandler(FsThrExitProc, (ClientData)tsdPtr);
619 tsdPtr->initialized = 1;
621 return (tsdPtr->cwdPathPtr == objPtr);
626 FsRecacheFilesystemList(void)
628 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
629 FilesystemRecord *fsRecPtr, *tmpFsRecPtr = NULL;
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);
638 fsRecPtr = tmpFsRecPtr;
640 tsdPtr->filesystemList = NULL;
643 * Code below operates on shared data. We
644 * are already called under mutex lock so
645 * we can safely proceed.
648 /* Locate tail of the global filesystem list */
649 fsRecPtr = filesystemList;
650 while (fsRecPtr != NULL) {
651 tmpFsRecPtr = fsRecPtr;
652 fsRecPtr = fsRecPtr->nextPtr;
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;
665 tsdPtr->filesystemList = tmpFsRecPtr;
666 fsRecPtr = fsRecPtr->prevPtr;
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;
677 static FilesystemRecord *
678 FsGetFirstFilesystem(void) {
679 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
680 FilesystemRecord *fsRecPtr;
682 tsdPtr->filesystemEpoch = theFilesystemEpoch;
683 fsRecPtr = filesystemList;
685 Tcl_MutexLock(&filesystemMutex);
686 if (tsdPtr->filesystemList == NULL
687 || (tsdPtr->filesystemEpoch != theFilesystemEpoch)) {
688 FsRecacheFilesystemList();
689 tsdPtr->filesystemEpoch = theFilesystemEpoch;
691 Tcl_MutexUnlock(&filesystemMutex);
692 fsRecPtr = tsdPtr->filesystemList;
703 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
705 if (cwdObj != NULL) {
706 str = Tcl_GetStringFromObj(cwdObj, &len);
709 Tcl_MutexLock(&cwdMutex);
710 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)
711 if (cwdPathPtr != NULL) {
712 Tcl_DecrRefCount(cwdPathPtr);
714 if (cwdObj == NULL) {
717 /* This MUST be stored as string object! */
718 cwdPathPtr = Tcl_NewStringObj(str, len);
719 Tcl_IncrRefCount(cwdPathPtr);
722 tsdPtr->cwdPathEpoch = cwdPathEpoch;
724 if (glcwdPathPtr != NULL) {
725 Tcl_DecrRefCount(glcwdPathPtr);
727 if (cwdObj == NULL) {
730 /* This MUST be stored as string object! */
731 glcwdPathPtr = Tcl_NewStringObj(str, len);
732 Tcl_IncrRefCount(glcwdPathPtr);
735 tsdPtr->cwdPathEpoch = glcwdPathEpoch;
737 Tcl_MutexUnlock(&cwdMutex);
739 if (tsdPtr->cwdPathPtr) {
740 Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
742 if (cwdObj == NULL) {
743 tsdPtr->cwdPathPtr = NULL;
745 tsdPtr->cwdPathPtr = Tcl_NewStringObj(str, len);
746 Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
751 *----------------------------------------------------------------------
753 * TclFinalizeFilesystem --
755 * Clean up the filesystem. After this, calls to all Tcl_FS...
756 * functions will fail.
758 * We will later call TclResetFilesystem to restore the FS
759 * to a pristine state.
765 * Frees any memory allocated by the filesystem.
767 *----------------------------------------------------------------------
771 TclFinalizeFilesystem()
773 FilesystemRecord *fsRecPtr;
776 * Assumption that only one thread is active now. Otherwise
777 * we would need to put various mutexes around this code.
779 #if !defined(__SYMBIAN32__) || !defined(__WINSCW__)
780 if (cwdPathPtr != NULL) {
781 Tcl_DecrRefCount(cwdPathPtr);
785 if (glcwdPathPtr != NULL) {
786 Tcl_DecrRefCount(glcwdPathPtr);
793 * Remove all filesystems, freeing any allocated memory
794 * that is no longer needed
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);
806 fsRecPtr = tmpFsRecPtr;
808 filesystemList = NULL;
811 * Now filesystemList is NULL. This means that any attempt
812 * to use the filesystem is likely to fail.
816 accessProcList = NULL;
817 openFileChannelProcList = NULL;
819 TclWinEncodingsCleanup();
824 *----------------------------------------------------------------------
826 * TclResetFilesystem --
828 * Restore the filesystem to a pristine state.
836 *----------------------------------------------------------------------
842 filesystemList = &nativeFilesystemRecord;
845 * Note, at this point, I believe nativeFilesystemRecord ->
846 * fileRefCount should equal 1 and if not, we should try to track
852 * Cleans up the win32 API filesystem proc lookup table. This must
853 * happen very late in finalization so that deleting of copied
856 TclWinResetInterfaces();
861 *----------------------------------------------------------------------
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
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.
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.
882 * Normally TCL_OK; TCL_ERROR if memory for a new node in the list
883 * could not be allocated.
886 * Memory allocated and modifies the link list for filesystems.
888 *----------------------------------------------------------------------
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. */
896 FilesystemRecord *newFilesystemPtr;
902 newFilesystemPtr = (FilesystemRecord *) ckalloc(sizeof(FilesystemRecord));
904 newFilesystemPtr->clientData = clientData;
905 newFilesystemPtr->fsPtr = fsPtr;
907 * We start with a refCount of 1. If this drops to zero, then
908 * anyone is welcome to ckfree us.
910 newFilesystemPtr->fileRefCount = 1;
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.
922 * However, since registering and unregistering filesystems is
923 * a very rare action, this is not a very important point.
925 Tcl_MutexLock(&filesystemMutex);
927 newFilesystemPtr->nextPtr = filesystemList;
928 newFilesystemPtr->prevPtr = NULL;
929 if (filesystemList) {
930 filesystemList->prevPtr = newFilesystemPtr;
932 filesystemList = newFilesystemPtr;
935 * Increment the filesystem epoch counter, since existing paths
936 * might conceivably now belong to different filesystems.
938 theFilesystemEpoch++;
939 Tcl_MutexUnlock(&filesystemMutex);
945 *----------------------------------------------------------------------
947 * Tcl_FSUnregister --
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
958 * TCL_OK if the procedure pointer was successfully removed,
959 * TCL_ERROR otherwise.
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.
966 *----------------------------------------------------------------------
970 Tcl_FSUnregister(fsPtr)
971 Tcl_Filesystem *fsPtr; /* The filesystem record to remove. */
973 int retVal = TCL_ERROR;
974 FilesystemRecord *fsRecPtr;
976 Tcl_MutexLock(&filesystemMutex);
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.
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;
990 filesystemList = fsRecPtr->nextPtr;
992 if (fsRecPtr->nextPtr) {
993 fsRecPtr->nextPtr->prevPtr = fsRecPtr->prevPtr;
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).
1003 theFilesystemEpoch++;
1005 fsRecPtr->fileRefCount--;
1006 if (fsRecPtr->fileRefCount <= 0) {
1007 ckfree((char *)fsRecPtr);
1012 fsRecPtr = fsRecPtr->nextPtr;
1016 Tcl_MutexUnlock(&filesystemMutex);
1021 *----------------------------------------------------------------------
1023 * Tcl_FSMatchInDirectory --
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.
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.
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.
1048 * Recursive searches, e.g.
1050 * glob -dir $dir -join * pkgIndex.tcl
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.
1058 * The interpreter may have an error message inserted into it.
1060 *----------------------------------------------------------------------
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. */
1073 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
1074 if (fsPtr != NULL) {
1075 Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc;
1077 int ret = (*proc)(interp, result, pathPtr, pattern, types);
1078 if (ret == TCL_OK && pattern != NULL) {
1079 result = FsAddMountsToGlobResult(result, pathPtr,
1087 if (pathPtr != NULL) {
1089 Tcl_GetStringFromObj(pathPtr,&len);
1092 * We have no idea how to match files in a directory
1093 * which belongs to no known filesystem
1095 Tcl_SetErrno(ENOENT);
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.
1109 cwd = Tcl_FSGetCwd(NULL);
1111 if (interp != NULL) {
1112 Tcl_SetResult(interp, "glob couldn't determine "
1113 "the current working directory", TCL_STATIC);
1117 fsPtr = Tcl_FSGetFileSystemForPath(cwd);
1118 if (fsPtr != NULL) {
1119 Tcl_FSMatchInDirectoryProc *proc = fsPtr->matchInDirectoryProc;
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) {
1127 tmpResultPtr = FsAddMountsToGlobResult(tmpResultPtr, cwd,
1130 ret = Tcl_ListObjLength(interp, tmpResultPtr, &resLength);
1131 if (ret == TCL_OK) {
1134 for (i = 0; i < resLength; i++) {
1137 Tcl_ListObjIndex(interp, tmpResultPtr, i, &elt);
1138 Tcl_ListObjAppendElement(interp, result,
1139 TclFSMakePathRelative(interp, elt, cwd));
1143 Tcl_DecrRefCount(tmpResultPtr);
1146 Tcl_DecrRefCount(cwd);
1149 Tcl_SetErrno(ENOENT);
1154 *----------------------------------------------------------------------
1156 * FsAddMountsToGlobResult --
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.
1165 * The passed in 'result' may be modified (in place, if
1166 * necessary), and the correct list is returned.
1171 *----------------------------------------------------------------------
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;
1180 int mLength, gLength, i;
1181 int dir = (types == NULL || (types->type & TCL_GLOB_TYPE_DIR));
1182 Tcl_Obj *mounts = FsListMounts(pathPtr, pattern);
1184 if (mounts == NULL) return result;
1186 if (Tcl_ListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) {
1189 if (Tcl_ListObjLength(NULL, result, &gLength) != TCL_OK) {
1192 for (i = 0; i < mLength; i++) {
1197 Tcl_ListObjIndex(NULL, mounts, i, &mElt);
1199 for (j = 0; j < gLength; j++) {
1201 Tcl_ListObjIndex(NULL, result, j, &gElt);
1202 if (Tcl_FSEqualPaths(mElt, gElt)) {
1205 /* We don't want to list this */
1206 if (Tcl_IsShared(result)) {
1208 newList = Tcl_DuplicateObj(result);
1209 Tcl_DecrRefCount(result);
1212 Tcl_ListObjReplace(NULL, result, j, 1, 0, NULL);
1215 /* Break out of for loop */
1219 if (!found && dir) {
1220 if (Tcl_IsShared(result)) {
1222 newList = Tcl_DuplicateObj(result);
1223 Tcl_DecrRefCount(result);
1226 Tcl_ListObjAppendElement(NULL, result, mElt);
1228 * No need to increment gLength, since we
1229 * don't want to compare mounts against
1235 Tcl_DecrRefCount(mounts);
1240 *----------------------------------------------------------------------
1242 * Tcl_FSMountsChanged --
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.
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:
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.
1265 * (2) when additional mount points are established inside any
1266 * existing filesystem (except the native fs)
1268 * (3) when any filesystem (except the native fs) changes the list
1269 * of available volumes.
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.
1277 * Tcl has no control over (2) and (3), so any registered filesystem
1278 * must make sure it calls this function when those situations
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).
1286 *----------------------------------------------------------------------
1290 Tcl_FSMountsChanged(fsPtr)
1291 Tcl_Filesystem *fsPtr;
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.
1300 * Increment the filesystem epoch counter, since existing paths
1301 * might now belong to different filesystems.
1303 Tcl_MutexLock(&filesystemMutex);
1304 theFilesystemEpoch++;
1305 Tcl_MutexUnlock(&filesystemMutex);
1309 *----------------------------------------------------------------------
1313 * Retrieve the clientData field for the filesystem given,
1314 * or NULL if that filesystem is not registered.
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.
1324 *----------------------------------------------------------------------
1329 Tcl_Filesystem *fsPtr; /* The filesystem record to query. */
1331 ClientData retVal = NULL;
1332 FilesystemRecord *fsRecPtr = FsGetFirstFilesystem();
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.
1340 while ((retVal == NULL) && (fsRecPtr != NULL)) {
1341 if (fsRecPtr->fsPtr == fsPtr) {
1342 retVal = fsRecPtr->clientData;
1344 fsRecPtr = fsRecPtr->nextPtr;
1351 *---------------------------------------------------------------------------
1353 * TclFSNormalizeAbsolutePath --
1356 * Takes an absolute path specification and computes a 'normalized'
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).
1366 * The behaviour of this function if passed a non-absolute path
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.
1375 * None (beyond the memory allocation for the result).
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.
1382 *---------------------------------------------------------------------------
1385 TclFSNormalizeAbsolutePath(interp, pathPtr, clientDataPtr)
1386 Tcl_Interp* interp; /* Interpreter to use */
1387 Tcl_Obj *pathPtr; /* Absolute path to normalize */
1388 ClientData *clientDataPtr;
1390 int splen = 0, nplen, eltLen, i;
1396 /* Split has refCount zero */
1397 split = Tcl_FSSplitPath(pathPtr, &splen);
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.
1405 for (i = 0; i < splen; i++) {
1406 Tcl_ListObjIndex(NULL, split, nplen, &elt);
1407 eltName = Tcl_GetStringFromObj(elt, &eltLen);
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] == '.')) {
1415 Tcl_ListObjReplace(NULL, split, nplen, 2, 0, NULL);
1417 Tcl_ListObjReplace(NULL, split, nplen, 1, 0, NULL);
1424 ClientData clientData = NULL;
1426 retVal = Tcl_FSJoinPath(split, nplen);
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.
1435 * Virtual file systems which may be registered may have
1436 * other criteria for normalizing a path.
1438 Tcl_IncrRefCount(retVal);
1439 TclFSNormalizeToUniquePath(interp, retVal, 0, &clientData);
1441 * Since we know it is a normalized path, we can
1442 * actually convert this object into an "path" object for
1443 * greater efficiency
1445 TclFSMakePathFromNormalized(interp, retVal, clientData);
1446 if (clientDataPtr != NULL) {
1447 *clientDataPtr = clientData;
1450 /* Init to an empty string */
1451 retVal = Tcl_NewStringObj("",0);
1452 Tcl_IncrRefCount(retVal);
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.
1463 Tcl_IncrRefCount(split);
1464 Tcl_DecrRefCount(split);
1466 /* This has a refCount of 1 for the caller */
1471 *---------------------------------------------------------------------------
1473 * TclFSNormalizeToUniquePath --
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).
1484 * The pathPtr is modified in place. The return value is
1485 * the last byte offset which was recognised in the path
1489 * None (beyond the memory allocation for the result).
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.
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 *---------------------------------------------------------------------------
1504 TclFSNormalizeToUniquePath(interp, pathPtr, startAt, clientDataPtr)
1508 ClientData *clientDataPtr;
1510 FilesystemRecord *fsRecPtr, *firstFsRecPtr;
1511 /* Ignore this variable */
1512 (void)clientDataPtr;
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).
1521 firstFsRecPtr = FsGetFirstFilesystem();
1523 fsRecPtr = firstFsRecPtr;
1524 while (fsRecPtr != NULL) {
1525 if (fsRecPtr->fsPtr == &tclNativeFilesystem) {
1526 Tcl_FSNormalizePathProc *proc = fsRecPtr->fsPtr->normalizePathProc;
1528 startAt = (*proc)(interp, pathPtr, startAt);
1532 fsRecPtr = fsRecPtr->nextPtr;
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;
1541 startAt = (*proc)(interp, pathPtr, startAt);
1544 * We could add an efficiency check like this:
1546 * if (retVal == length-of(pathPtr)) {break;}
1548 * but there's not much benefit.
1551 fsRecPtr = fsRecPtr->nextPtr;
1558 *---------------------------------------------------------------------------
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.
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.
1573 * Sets the integer referenced by seekFlagPtr to 1 to tell the caller
1574 * to seek to EOF after opening the file.
1577 * This code is based on a prototype implementation contributed
1580 *---------------------------------------------------------------------------
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. */
1593 int mode, modeArgc, c, i, gotRW;
1594 CONST char **modeArgv, *flag;
1595 #define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR)
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.
1607 * Guard against international characters before using byte oriented
1611 if (!(string[0] & 0x80)
1612 && islower(UCHAR(string[0]))) { /* INTL: ISO only. */
1613 switch (string[0]) {
1618 mode = O_WRONLY|O_CREAT|O_TRUNC;
1622 * Added O_APPEND for proper automatic
1623 * seek-to-end-on-write by the OS.
1625 mode = O_WRONLY|O_CREAT|O_APPEND;
1630 if (interp != (Tcl_Interp *) NULL) {
1631 Tcl_AppendResult(interp,
1632 "illegal access mode \"", string, "\"",
1637 if (string[1] == '+') {
1638 mode &= ~(O_RDONLY|O_WRONLY);
1640 if (string[2] != 0) {
1643 } else if (string[1] != 0) {
1650 * The access modes are specified using a list of POSIX modes
1653 * IMPORTANT NOTE: We rely on Tcl_SplitList working correctly when
1654 * a NULL interpreter is passed in.
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, "\"");
1668 for (i = 0; i < modeArgc; i++) {
1671 if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {
1672 mode = (mode & ~RW_MODES) | O_RDONLY;
1674 } else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {
1675 mode = (mode & ~RW_MODES) | O_WRONLY;
1677 } else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {
1678 mode = (mode & ~RW_MODES) | O_RDWR;
1680 } else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {
1683 } else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {
1685 } else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {
1687 } else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {
1691 if (interp != (Tcl_Interp *) NULL) {
1692 Tcl_AppendResult(interp, "access mode \"", flag,
1693 "\" not supported by this system", (char *) NULL);
1695 ckfree((char *) modeArgv);
1698 } else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {
1699 #if defined(O_NDELAY) || defined(O_NONBLOCK)
1706 if (interp != (Tcl_Interp *) NULL) {
1707 Tcl_AppendResult(interp, "access mode \"", flag,
1708 "\" not supported by this system", (char *) NULL);
1710 ckfree((char *) modeArgv);
1713 } else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {
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);
1721 ckfree((char *) modeArgv);
1725 ckfree((char *) modeArgv);
1727 if (interp != (Tcl_Interp *) NULL) {
1728 Tcl_AppendResult(interp, "access mode must include either",
1729 " RDONLY, WRONLY, or RDWR", (char *) NULL);
1737 *----------------------------------------------------------------------
1741 * Read in a file and process the entire file as one gigantic
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.
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).
1754 *----------------------------------------------------------------------
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. */
1764 Tcl_StatBuf statBuf;
1765 Tcl_Obj *oldScriptFile;
1771 if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
1776 objPtr = Tcl_NewObj();
1777 Tcl_IncrRefCount(objPtr);
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);
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);
1795 * The eofchar is \32 (^Z). This is the usual on Windows, but we
1796 * effect this cross-platform to allow for scripted documents.
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);
1807 if (Tcl_Close(interp, chan) != TCL_OK) {
1811 iPtr = (Interp *) interp;
1812 oldScriptFile = iPtr->scriptFile;
1813 iPtr->scriptFile = pathPtr;
1814 Tcl_IncrRefCount(iPtr->scriptFile);
1815 string = Tcl_GetStringFromObj(objPtr, &length);
1818 /* TIP #280 Force the evaluator to open a frame for a sourced
1820 iPtr->evalFlags |= TCL_EVAL_FILE;
1822 result = Tcl_EvalEx(interp, string, length, 0);
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'.
1828 if (iPtr->scriptFile != NULL) {
1829 Tcl_DecrRefCount(iPtr->scriptFile);
1831 iPtr->scriptFile = oldScriptFile;
1833 if (result == TCL_RETURN) {
1834 result = TclUpdateReturnInfo(iPtr);
1835 } else if (result == TCL_ERROR) {
1836 char msg[200 + TCL_INTEGER_SPACE];
1839 * Record information telling where the error occurred.
1842 sprintf(msg, "\n (file \"%.150s\" line %d)", Tcl_GetString(pathPtr),
1844 Tcl_AddErrorInfo(interp, msg);
1848 Tcl_DecrRefCount(objPtr);
1853 *----------------------------------------------------------------------
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.
1862 * The value of the Tcl error code variable.
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.
1868 *----------------------------------------------------------------------
1878 *----------------------------------------------------------------------
1882 * Sets the Tcl error code variable to the supplied value.
1888 * Modifies the value of the Tcl error code variable.
1890 *----------------------------------------------------------------------
1895 int err; /* The new value. */
1901 *----------------------------------------------------------------------
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
1911 * The return value is a human-readable string describing the
1915 * The global variable $errorCode is reset.
1917 *----------------------------------------------------------------------
1920 EXPORT_C CONST char *
1921 Tcl_PosixError(interp)
1922 Tcl_Interp *interp; /* Interpreter whose $errorCode variable
1923 * is to be changed. */
1925 CONST char *id, *msg;
1927 msg = Tcl_ErrnoMsg(errno);
1930 Tcl_SetErrorCode(interp, "POSIX", id, msg, (char *) NULL);
1936 *----------------------------------------------------------------------
1940 * This procedure replaces the library version of stat and lsat.
1942 * The appropriate function for the filesystem to which pathPtr
1943 * belongs will be called.
1946 * See stat documentation.
1949 * See stat documentation.
1951 *----------------------------------------------------------------------
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. */
1959 Tcl_Filesystem *fsPtr;
1960 #ifdef USE_OBSOLETE_FS_HOOKS
1961 struct stat oldStyleStatBuffer;
1965 * Call each of the "stat" function in succession. A non-return
1966 * value of -1 indicates the particular function has succeeded.
1969 Tcl_MutexLock(&obsoleteFsHookMutex);
1971 if (statProcList != NULL) {
1972 StatProc *statProcPtr;
1974 Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
1975 if (transPtr == NULL) {
1978 path = Tcl_GetString(transPtr);
1981 statProcPtr = statProcList;
1982 while ((retVal == -1) && (statProcPtr != NULL)) {
1983 retVal = (*statProcPtr->proc)(path, &oldStyleStatBuffer);
1984 statProcPtr = statProcPtr->nextPtr;
1986 if (transPtr != NULL) {
1987 Tcl_DecrRefCount(transPtr);
1991 Tcl_MutexUnlock(&obsoleteFsHookMutex);
1994 * Note that EOVERFLOW is not a problem here, and these
1995 * assignments should all be widening (if not identity.)
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);
2014 #endif /* USE_OBSOLETE_FS_HOOKS */
2015 fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2016 if (fsPtr != NULL) {
2017 Tcl_FSStatProc *proc = fsPtr->statProc;
2019 return (*proc)(pathPtr, buf);
2022 Tcl_SetErrno(ENOENT);
2027 *----------------------------------------------------------------------
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
2038 * See lstat documentation.
2041 * See lstat documentation.
2043 *----------------------------------------------------------------------
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. */
2051 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2052 if (fsPtr != NULL) {
2053 Tcl_FSLstatProc *proc = fsPtr->lstatProc;
2055 return (*proc)(pathPtr, buf);
2057 Tcl_FSStatProc *sproc = fsPtr->statProc;
2058 if (sproc != NULL) {
2059 return (*sproc)(pathPtr, buf);
2063 Tcl_SetErrno(ENOENT);
2068 *----------------------------------------------------------------------
2072 * This procedure replaces the library version of access.
2073 * The appropriate function for the filesystem to which pathPtr
2074 * belongs will be called.
2077 * See access documentation.
2080 * See access documentation.
2082 *----------------------------------------------------------------------
2086 Tcl_FSAccess(pathPtr, mode)
2087 Tcl_Obj *pathPtr; /* Path of file to access (in current CP). */
2088 int mode; /* Permission setting. */
2090 Tcl_Filesystem *fsPtr;
2091 #ifdef USE_OBSOLETE_FS_HOOKS
2095 * Call each of the "access" function in succession. A non-return
2096 * value of -1 indicates the particular function has succeeded.
2099 Tcl_MutexLock(&obsoleteFsHookMutex);
2101 if (accessProcList != NULL) {
2102 AccessProc *accessProcPtr;
2104 Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
2105 if (transPtr == NULL) {
2108 path = Tcl_GetString(transPtr);
2111 accessProcPtr = accessProcList;
2112 while ((retVal == -1) && (accessProcPtr != NULL)) {
2113 retVal = (*accessProcPtr->proc)(path, mode);
2114 accessProcPtr = accessProcPtr->nextPtr;
2116 if (transPtr != NULL) {
2117 Tcl_DecrRefCount(transPtr);
2121 Tcl_MutexUnlock(&obsoleteFsHookMutex);
2125 #endif /* USE_OBSOLETE_FS_HOOKS */
2126 fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2127 if (fsPtr != NULL) {
2128 Tcl_FSAccessProc *proc = fsPtr->accessProc;
2130 return (*proc)(pathPtr, mode);
2134 Tcl_SetErrno(ENOENT);
2139 *----------------------------------------------------------------------
2141 * Tcl_FSOpenFileChannel --
2143 * The appropriate function for the filesystem to which pathPtr
2144 * belongs will be called.
2147 * The new channel or NULL, if the named file could not be opened.
2150 * May open the channel and may cause creation of a file on the
2153 *----------------------------------------------------------------------
2156 EXPORT_C Tcl_Channel
2157 Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions)
2158 Tcl_Interp *interp; /* Interpreter for error reporting;
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
2167 Tcl_Filesystem *fsPtr;
2168 #ifdef USE_OBSOLETE_FS_HOOKS
2169 Tcl_Channel retVal = NULL;
2172 * Call each of the "Tcl_OpenFileChannel" functions in succession.
2173 * A non-NULL return value indicates the particular function has
2177 Tcl_MutexLock(&obsoleteFsHookMutex);
2178 if (openFileChannelProcList != NULL) {
2179 OpenFileChannelProc *openFileChannelProcPtr;
2181 Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
2183 if (transPtr == NULL) {
2186 path = Tcl_GetString(transPtr);
2189 openFileChannelProcPtr = openFileChannelProcList;
2191 while ((retVal == NULL) && (openFileChannelProcPtr != NULL)) {
2192 retVal = (*openFileChannelProcPtr->proc)(interp, path,
2193 modeString, permissions);
2194 openFileChannelProcPtr = openFileChannelProcPtr->nextPtr;
2196 if (transPtr != NULL) {
2197 Tcl_DecrRefCount(transPtr);
2200 Tcl_MutexUnlock(&obsoleteFsHookMutex);
2201 if (retVal != NULL) {
2204 #endif /* USE_OBSOLETE_FS_HOOKS */
2207 * We need this just to ensure we return the correct error messages
2208 * under some circumstances.
2210 if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
2214 fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2215 if (fsPtr != NULL) {
2216 Tcl_FSOpenFileChannelProc *proc = fsPtr->openFileChannelProc;
2219 mode = TclGetOpenMode(interp, modeString, &seekFlag);
2223 retVal = (*proc)(interp, pathPtr, mode, permissions);
2224 if (retVal != NULL) {
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);
2234 Tcl_Close(NULL, retVal);
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);
2253 *----------------------------------------------------------------------
2257 * This procedure replaces the library version of utime.
2258 * The appropriate function for the filesystem to which pathPtr
2259 * belongs will be called.
2262 * See utime documentation.
2265 * See utime documentation.
2267 *----------------------------------------------------------------------
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. */
2276 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2277 if (fsPtr != NULL) {
2278 Tcl_FSUtimeProc *proc = fsPtr->utimeProc;
2280 return (*proc)(pathPtr, tval);
2287 *----------------------------------------------------------------------
2289 * NativeFileAttrStrings --
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.
2298 * An array of strings
2303 *----------------------------------------------------------------------
2307 NativeFileAttrStrings(pathPtr, objPtrRef)
2309 Tcl_Obj** objPtrRef;
2311 return tclpFileAttrStrings;
2315 *----------------------------------------------------------------------
2317 * NativeFileAttrsGet --
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.
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
2335 *----------------------------------------------------------------------
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. */
2345 return (*tclpFileAttrProcs[index].getProc)(interp, index,
2346 pathPtr, objPtrRef);
2350 *----------------------------------------------------------------------
2352 * NativeFileAttrsSet --
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.
2361 * Standard Tcl return code.
2366 *----------------------------------------------------------------------
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. */
2376 return (*tclpFileAttrProcs[index].setProc)(interp, index,
2381 *----------------------------------------------------------------------
2383 * Tcl_FSFileAttrStrings --
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.
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.
2402 *----------------------------------------------------------------------
2405 EXPORT_C CONST char **
2406 Tcl_FSFileAttrStrings(pathPtr, objPtrRef)
2408 Tcl_Obj** objPtrRef;
2410 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2411 if (fsPtr != NULL) {
2412 Tcl_FSFileAttrStringsProc *proc = fsPtr->fileAttrStringsProc;
2414 return (*proc)(pathPtr, objPtrRef);
2417 Tcl_SetErrno(ENOENT);
2422 *----------------------------------------------------------------------
2424 * Tcl_FSFileAttrsGet --
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.
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
2441 *----------------------------------------------------------------------
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. */
2451 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2452 if (fsPtr != NULL) {
2453 Tcl_FSFileAttrsGetProc *proc = fsPtr->fileAttrsGetProc;
2455 return (*proc)(interp, index, pathPtr, objPtrRef);
2458 Tcl_SetErrno(ENOENT);
2463 *----------------------------------------------------------------------
2465 * Tcl_FSFileAttrsSet --
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.
2472 * Standard Tcl return code.
2477 *----------------------------------------------------------------------
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. */
2487 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2488 if (fsPtr != NULL) {
2489 Tcl_FSFileAttrsSetProc *proc = fsPtr->fileAttrsSetProc;
2491 return (*proc)(interp, index, pathPtr, objPtr);
2494 Tcl_SetErrno(ENOENT);
2499 *----------------------------------------------------------------------
2503 * This function replaces the library version of getcwd().
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).
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.
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.
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
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
2530 * The result already has its refCount incremented for the caller.
2531 * When it is no longer needed, that refCount should be decremented.
2534 * Various objects may be freed and allocated.
2536 *----------------------------------------------------------------------
2540 Tcl_FSGetCwd(interp)
2543 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
2545 if (TclFSCwdPointerEquals(NULL)) {
2546 FilesystemRecord *fsRecPtr;
2547 Tcl_Obj *retVal = NULL;
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
2556 fsRecPtr = FsGetFirstFilesystem();
2557 while ((retVal == NULL) && (fsRecPtr != NULL)) {
2558 Tcl_FSGetCwdProc *proc = fsRecPtr->fsPtr->getCwdProc;
2560 retVal = (*proc)(interp);
2562 fsRecPtr = fsRecPtr->nextPtr;
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.
2569 * Finally, if retVal is NULL, we do not have a cwd, which
2570 * could be problematic.
2572 if (retVal != NULL) {
2573 Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal, NULL);
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.
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
2588 Tcl_DecrRefCount(norm);
2590 Tcl_DecrRefCount(retVal);
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.
2600 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr);
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).
2610 if (fsPtr != NULL) {
2611 Tcl_FSGetCwdProc *proc = fsPtr->getCwdProc;
2613 Tcl_Obj *retVal = (*proc)(interp);
2614 if (retVal != NULL) {
2615 Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal, NULL);
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.
2623 } else if (Tcl_FSEqualPaths(tsdPtr->cwdPathPtr, norm)) {
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.
2631 Tcl_DecrRefCount(norm);
2634 Tcl_DecrRefCount(norm);
2636 Tcl_DecrRefCount(retVal);
2638 /* The 'cwd' function returned an error; reset the cwd */
2645 if (tsdPtr->cwdPathPtr != NULL) {
2646 Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
2649 return tsdPtr->cwdPathPtr;
2653 *----------------------------------------------------------------------
2657 * This function replaces the library version of chdir().
2659 * The path is normalized and then passed to the filesystem
2663 * See chdir() documentation. If successful, we keep a
2664 * record of the successful path in cwdPathPtr for subsequent
2668 * See chdir() documentation. The global cwdPathPtr may
2671 *----------------------------------------------------------------------
2674 Tcl_FSChdir(pathPtr)
2677 Tcl_Filesystem *fsPtr;
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
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.
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.
2695 Tcl_Obj *objPtr = NULL;
2696 if (pathPtr->bytes && pathPtr->length == 1 && pathPtr->bytes[0] == '~') {
2700 objPtr = Tcl_FSGetTranslatedPath(NULL, pathPtr);
2701 if (objPtr == NULL) {
2702 Tcl_SetErrno(ENOENT);
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);
2714 Tcl_DecrRefCount(objPtr);
2719 if (Tcl_FSGetNormalizedPath(NULL, pathPtr) == NULL) {
2721 if (objPtr) { Tcl_DecrRefCount(objPtr); }
2723 Tcl_SetErrno(ENOENT);
2727 fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2728 if (fsPtr != NULL) {
2729 Tcl_FSChdirProc *proc = fsPtr->chdirProc;
2731 retVal = (*proc)(pathPtr);
2733 /* Fallback on stat-based implementation */
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 */
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
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).
2765 Tcl_Obj *normDirName = Tcl_FSGetNormalizedPath(NULL, pathPtr);
2766 if (normDirName == NULL) {
2768 if (objPtr) { Tcl_DecrRefCount(objPtr); }
2770 Tcl_SetErrno(ENOENT);
2773 FsUpdateCwd(normDirName);
2776 Tcl_SetErrno(ENOENT);
2780 if (objPtr) { Tcl_DecrRefCount(objPtr); }
2786 *----------------------------------------------------------------------
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.
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.
2804 * A standard Tcl completion code. If an error occurs, an error
2805 * message is left in the interp's result.
2808 * New code suddenly appears in memory. This may later be
2809 * unloaded by passing the clientData to the unloadProc.
2811 *----------------------------------------------------------------------
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
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
2833 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
2834 if (fsPtr != NULL) {
2835 Tcl_FSLoadFileProc *proc = fsPtr->loadFileProc;
2837 int retVal = (*proc)(interp, pathPtr, handlePtr, unloadProcPtr);
2838 if (retVal != TCL_OK) {
2841 if (*handlePtr == NULL) {
2845 *proc1Ptr = TclpFindSymbol(interp, *handlePtr, sym1);
2848 *proc2Ptr = TclpFindSymbol(interp, *handlePtr, sym2);
2852 Tcl_Filesystem *copyFsPtr;
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);
2863 #ifdef TCL_LOAD_FROM_MEMORY
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:
2872 Tcl_StatBuf statBuf;
2875 ret = Tcl_FSStat(pathPtr, &statBuf);
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) {
2884 data = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0666);
2888 buffer = TclpLoadMemoryGetBuffer(interp, size);
2890 Tcl_Close(interp, data);
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) {
2902 *proc1Ptr = TclpFindSymbol(interp, *handlePtr, sym1);
2905 *proc2Ptr = TclpFindSymbol(interp, *handlePtr, sym2);
2910 Tcl_ResetResult(interp);
2914 * Get a temporary filename to use, first to
2915 * copy the file into, and then to load.
2917 copyToPtr = TclpTempFileName();
2918 if (copyToPtr == NULL) {
2921 Tcl_IncrRefCount(copyToPtr);
2923 copyFsPtr = Tcl_FSGetFileSystemForPath(copyToPtr);
2924 if ((copyFsPtr == NULL) || (copyFsPtr == fsPtr)) {
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.
2931 Tcl_FSDeleteFile(copyToPtr);
2932 Tcl_DecrRefCount(copyToPtr);
2936 if (TclCrossFilesystemCopy(interp, pathPtr,
2937 copyToPtr) == TCL_OK) {
2938 Tcl_LoadHandle newLoadHandle = NULL;
2939 Tcl_FSUnloadFileProc *newUnloadProcPtr = NULL;
2940 FsDivertLoad *tvdlPtr;
2943 #if !defined(__WIN32__) && !defined(MAC_TCL)
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:
2953 Tcl_Obj* perm = Tcl_NewStringObj("0700",-1);
2954 Tcl_IncrRefCount(perm);
2955 Tcl_FSFileAttrsSet(NULL, 2, copyToPtr, perm);
2956 Tcl_DecrRefCount(perm);
2960 * We need to reset the result now, because the cross-
2961 * filesystem copy may have stored the number of bytes
2964 Tcl_ResetResult(interp);
2966 retVal = Tcl_FSLoadFile(interp, copyToPtr, sym1, sym2,
2970 if (retVal != TCL_OK) {
2971 /* The file didn't load successfully */
2972 Tcl_FSDeleteFile(copyToPtr);
2973 Tcl_DecrRefCount(copyToPtr);
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.
2981 if (Tcl_FSDeleteFile(copyToPtr) == TCL_OK) {
2982 Tcl_DecrRefCount(copyToPtr);
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.
2991 (*handlePtr) = newLoadHandle;
2992 (*unloadProcPtr) = newUnloadProcPtr;
2996 * When we unload this file, we need to divert the
2997 * unloading so we can unload and cleanup the
2998 * temporary file correctly.
3000 tvdlPtr = (FsDivertLoad*) ckalloc(sizeof(FsDivertLoad));
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.
3007 tvdlPtr->loadHandle = newLoadHandle;
3008 tvdlPtr->unloadProcPtr = newUnloadProcPtr;
3010 if (copyFsPtr != &tclNativeFilesystem) {
3011 /* copyToPtr is already incremented for this reference */
3012 tvdlPtr->divertedFile = copyToPtr;
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.
3020 tvdlPtr->divertedFilesystem = copyFsPtr;
3021 tvdlPtr->divertedFileNativeRep = NULL;
3023 /* We need the native rep */
3024 tvdlPtr->divertedFileNativeRep =
3025 TclNativeDupInternalRep(Tcl_FSGetInternalRep(copyToPtr,
3028 * We don't need or want references to the copied
3029 * Tcl_Obj or the filesystem if it is the native
3032 tvdlPtr->divertedFile = NULL;
3033 tvdlPtr->divertedFilesystem = NULL;
3034 Tcl_DecrRefCount(copyToPtr);
3038 (*handlePtr) = (Tcl_LoadHandle) tvdlPtr;
3039 (*unloadProcPtr) = &FSUnloadTempFile;
3042 /* Cross-platform copy failed */
3043 Tcl_FSDeleteFile(copyToPtr);
3044 Tcl_DecrRefCount(copyToPtr);
3049 Tcl_SetErrno(ENOENT);
3053 * This function used to be in the platform specific directories, but it
3054 * has now been made to work cross-platform
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
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
3075 Tcl_LoadHandle handle = NULL;
3078 res = TclpDlopen(interp, pathPtr, &handle, unloadProcPtr);
3080 if (res != TCL_OK) {
3084 if (handle == NULL) {
3088 *clientDataPtr = (ClientData)handle;
3090 *proc1Ptr = TclpFindSymbol(interp, handle, sym1);
3091 *proc2Ptr = TclpFindSymbol(interp, handle, sym2);
3096 *---------------------------------------------------------------------------
3098 * FSUnloadTempFile --
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.
3109 * The effects of the 'unload' function called, and of course
3110 * the temporary file will be deleted.
3112 *---------------------------------------------------------------------------
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
3121 FsDivertLoad *tvdlPtr = (FsDivertLoad*)loadHandle;
3123 * This test should never trigger, since we give
3124 * the client data in the function above.
3126 if (tvdlPtr == NULL) { return; }
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
3135 if (tvdlPtr->unloadProcPtr != NULL) {
3136 (*tvdlPtr->unloadProcPtr)(tvdlPtr->loadHandle);
3139 if (tvdlPtr->divertedFilesystem == NULL) {
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.
3145 TclpDeleteFile(tvdlPtr->divertedFileNativeRep);
3146 NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep);
3149 * Remove the temporary file we created. Note, we may crash
3150 * here because encodings have been taken down already.
3152 if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile)
3155 * The above may have failed because the filesystem, or something
3156 * it depends upon (e.g. encodings) have been taken down because
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).
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
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.
3177 Tcl_DecrRefCount(tvdlPtr->divertedFile);
3180 ckfree((char*)tvdlPtr);
3184 *---------------------------------------------------------------------------
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.
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.
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:
3205 * TCL_CREATE_SYMBOLIC_LINK
3206 * TCL_CREATE_HARD_LINK
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.
3213 * See readlink() documentation. A new filesystem link
3216 *---------------------------------------------------------------------------
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 */
3225 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
3226 if (fsPtr != NULL) {
3227 Tcl_FSLinkProc *proc = fsPtr->linkProc;
3229 return (*proc)(pathPtr, toPtr, linkAction);
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.
3242 Tcl_SetErrno(ENOENT);
3243 #endif /* S_IFLNK */
3248 *---------------------------------------------------------------------------
3250 * Tcl_FSListVolumes --
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.
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.
3265 * The list of volumes, in an object which has refCount 0.
3270 *---------------------------------------------------------------------------
3274 Tcl_FSListVolumes(void)
3276 FilesystemRecord *fsRecPtr;
3277 Tcl_Obj *resultPtr = Tcl_NewObj();
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.
3286 fsRecPtr = FsGetFirstFilesystem();
3287 while (fsRecPtr != NULL) {
3288 Tcl_FSListVolumesProc *proc = fsRecPtr->fsPtr->listVolumesProc;
3290 Tcl_Obj *thisFsVolumes = (*proc)();
3291 if (thisFsVolumes != NULL) {
3292 Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes);
3293 Tcl_DecrRefCount(thisFsVolumes);
3296 fsRecPtr = fsRecPtr->nextPtr;
3303 *---------------------------------------------------------------------------
3307 * List all mounts within the given directory, which match the
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
3318 *---------------------------------------------------------------------------
3322 FsListMounts(pathPtr, pattern)
3323 Tcl_Obj *pathPtr; /* Contains path to directory to search. */
3324 CONST char *pattern; /* Pattern to match against. */
3326 FilesystemRecord *fsRecPtr;
3327 Tcl_GlobTypeData mountsOnly = { TCL_GLOB_TYPE_MOUNT, 0, NULL, NULL };
3328 Tcl_Obj *resultPtr = NULL;
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.
3337 fsRecPtr = FsGetFirstFilesystem();
3338 while (fsRecPtr != NULL) {
3339 if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
3340 Tcl_FSMatchInDirectoryProc *proc =
3341 fsRecPtr->fsPtr->matchInDirectoryProc;
3343 if (resultPtr == NULL) {
3344 resultPtr = Tcl_NewObj();
3346 (*proc)(NULL, resultPtr, pathPtr, pattern, &mountsOnly);
3349 fsRecPtr = fsRecPtr->nextPtr;
3356 *---------------------------------------------------------------------------
3358 * Tcl_FSSplitPath --
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.
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.
3372 *---------------------------------------------------------------------------
3376 Tcl_FSSplitPath(pathPtr, lenPtr)
3377 Tcl_Obj *pathPtr; /* Path to split. */
3378 int *lenPtr; /* int to store number of path elements. */
3380 Tcl_Obj *result = NULL; /* Needed only to prevent gcc warnings. */
3381 Tcl_Filesystem *fsPtr;
3382 char separator = '/';
3383 int driveNameLength;
3387 * Perform platform specific splitting.
3390 if (FSGetPathType(pathPtr, &fsPtr, &driveNameLength)
3391 == TCL_PATH_ABSOLUTE) {
3392 if (fsPtr == &tclNativeFilesystem) {
3393 return TclpNativeSplitPath(pathPtr, lenPtr);
3396 return TclpNativeSplitPath(pathPtr, lenPtr);
3399 /* We assume separators are single characters */
3400 if (fsPtr->filesystemSeparatorProc != NULL) {
3401 Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(pathPtr);
3403 separator = Tcl_GetString(sep)[0];
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)
3413 result = Tcl_NewObj();
3414 p = Tcl_GetString(pathPtr);
3415 Tcl_ListObjAppendElement(NULL, result,
3416 Tcl_NewStringObj(p, driveNameLength));
3417 p+= driveNameLength;
3419 /* Add the remaining path elements to the list */
3421 char *elementStart = p;
3423 while ((*p != '\0') && (*p != separator)) {
3426 length = p - elementStart;
3429 if (elementStart[0] == '~') {
3430 nextElt = Tcl_NewStringObj("./",2);
3431 Tcl_AppendToObj(nextElt, elementStart, length);
3433 nextElt = Tcl_NewStringObj(elementStart, length);
3435 Tcl_ListObjAppendElement(NULL, result, nextElt);
3443 * Compute the number of elements in the result.
3446 if (lenPtr != NULL) {
3447 Tcl_ListObjLength(NULL, result, lenPtr);
3452 /* Simple helper function */
3454 TclFSInternalToNormalized(fromFilesystem, clientData, fsRecPtrPtr)
3455 Tcl_Filesystem *fromFilesystem;
3456 ClientData clientData;
3457 FilesystemRecord **fsRecPtrPtr;
3459 FilesystemRecord *fsRecPtr = FsGetFirstFilesystem();
3461 while (fsRecPtr != NULL) {
3462 if (fsRecPtr->fsPtr == fromFilesystem) {
3463 *fsRecPtrPtr = fsRecPtr;
3466 fsRecPtr = fsRecPtr->nextPtr;
3469 if ((fsRecPtr != NULL)
3470 && (fromFilesystem->internalToNormalizedProc != NULL)) {
3471 return (*fromFilesystem->internalToNormalizedProc)(clientData);
3478 *----------------------------------------------------------------------
3482 * Helper function used by FSGetPathType.
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.
3493 *----------------------------------------------------------------------
3497 GetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr, driveNameRef)
3498 Tcl_Obj *pathObjPtr;
3499 Tcl_Filesystem **filesystemPtrPtr;
3500 int *driveNameLengthPtr;
3501 Tcl_Obj **driveNameRef;
3503 FilesystemRecord *fsRecPtr;
3506 Tcl_PathType type = TCL_PATH_RELATIVE;
3508 path = Tcl_GetStringFromObj(pathObjPtr, &pathLen);
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
3517 fsRecPtr = FsGetFirstFilesystem();
3518 while (fsRecPtr != NULL) {
3519 Tcl_FSListVolumesProc *proc = fsRecPtr->fsPtr->listVolumesProc;
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
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.
3539 if ((fsRecPtr->fsPtr != &tclNativeFilesystem) && (proc != NULL)) {
3541 Tcl_Obj *thisFsVolumes = (*proc)();
3542 if (thisFsVolumes != NULL) {
3543 if (Tcl_ListObjLength(NULL, thisFsVolumes,
3544 &numVolumes) != TCL_OK) {
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'.
3551 * It would be better if we could signal an error
3552 * here (but panic seems a bit excessive).
3556 while (numVolumes > 0) {
3562 Tcl_ListObjIndex(NULL, thisFsVolumes, numVolumes, &vol);
3563 strVol = Tcl_GetStringFromObj(vol,&len);
3564 if (pathLen < len) {
3567 if (strncmp(strVol, path, (size_t) len) == 0) {
3568 type = TCL_PATH_ABSOLUTE;
3569 if (filesystemPtrPtr != NULL) {
3570 *filesystemPtrPtr = fsRecPtr->fsPtr;
3572 if (driveNameLengthPtr != NULL) {
3573 *driveNameLengthPtr = len;
3575 if (driveNameRef != NULL) {
3576 *driveNameRef = vol;
3577 Tcl_IncrRefCount(vol);
3582 Tcl_DecrRefCount(thisFsVolumes);
3583 if (type == TCL_PATH_ABSOLUTE) {
3584 /* We don't need to examine any more filesystems */
3589 fsRecPtr = fsRecPtr->nextPtr;
3592 if (type != TCL_PATH_ABSOLUTE) {
3593 type = TclpGetNativePathType(pathObjPtr, driveNameLengthPtr,
3595 if ((type == TCL_PATH_ABSOLUTE) && (filesystemPtrPtr != NULL)) {
3596 *filesystemPtrPtr = &tclNativeFilesystem;
3603 *---------------------------------------------------------------------------
3605 * Tcl_FSRenameFile --
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.
3612 * Standard Tcl error code if a function was called.
3615 * A file may be renamed.
3617 *---------------------------------------------------------------------------
3621 Tcl_FSRenameFile(srcPathPtr, destPathPtr)
3622 Tcl_Obj* srcPathPtr; /* Pathname of file or dir to be renamed
3624 Tcl_Obj *destPathPtr; /* New pathname of file or directory
3628 Tcl_Filesystem *fsPtr, *fsPtr2;
3629 fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
3630 fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
3632 if (fsPtr == fsPtr2 && fsPtr != NULL) {
3633 Tcl_FSRenameFileProc *proc = fsPtr->renameFileProc;
3635 retVal = (*proc)(srcPathPtr, destPathPtr);
3639 Tcl_SetErrno(EXDEV);
3645 *---------------------------------------------------------------------------
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.
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).
3658 * Standard Tcl error code if a function was called.
3661 * A file may be copied.
3663 *---------------------------------------------------------------------------
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). */
3672 Tcl_Filesystem *fsPtr, *fsPtr2;
3673 fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
3674 fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
3676 if (fsPtr == fsPtr2 && fsPtr != NULL) {
3677 Tcl_FSCopyFileProc *proc = fsPtr->copyFileProc;
3679 retVal = (*proc)(srcPathPtr, destPathPtr);
3683 Tcl_SetErrno(EXDEV);
3689 *---------------------------------------------------------------------------
3691 * TclCrossFilesystemCopy --
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.
3698 * Standard Tcl error code.
3701 * A file may be created.
3703 *---------------------------------------------------------------------------
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). */
3711 int result = TCL_ERROR;
3714 Tcl_Channel out = Tcl_FSOpenFileChannel(interp, target, "w", prot);
3716 /* It looks like we can copy it over */
3717 Tcl_Channel in = Tcl_FSOpenFileChannel(interp, source,
3720 /* This is very strange, we checked this above */
3721 Tcl_Close(interp, out);
3723 Tcl_StatBuf sourceStatBuf;
3724 struct utimbuf tval;
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).
3730 Tcl_SetChannelOption(interp, in, "-translation", "binary");
3731 Tcl_SetChannelOption(interp, out, "-translation", "binary");
3733 if (TclCopyChannel(interp, in, out, -1, NULL) == TCL_OK) {
3737 * If the copy failed, assume that copy channel left
3738 * a good error message.
3740 Tcl_Close(interp, in);
3741 Tcl_Close(interp, out);
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);
3755 *---------------------------------------------------------------------------
3757 * Tcl_FSDeleteFile --
3759 * The appropriate function for the filesystem to which pathPtr
3760 * belongs will be called.
3763 * Standard Tcl error code.
3766 * A file may be deleted.
3768 *---------------------------------------------------------------------------
3772 Tcl_FSDeleteFile(pathPtr)
3773 Tcl_Obj *pathPtr; /* Pathname of file to be removed (UTF-8). */
3775 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
3776 if (fsPtr != NULL) {
3777 Tcl_FSDeleteFileProc *proc = fsPtr->deleteFileProc;
3779 return (*proc)(pathPtr);
3782 Tcl_SetErrno(ENOENT);
3787 *---------------------------------------------------------------------------
3789 * Tcl_FSCreateDirectory --
3791 * The appropriate function for the filesystem to which pathPtr
3792 * belongs will be called.
3795 * Standard Tcl error code.
3798 * A directory may be created.
3800 *---------------------------------------------------------------------------
3804 Tcl_FSCreateDirectory(pathPtr)
3805 Tcl_Obj *pathPtr; /* Pathname of directory to create (UTF-8). */
3807 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
3808 if (fsPtr != NULL) {
3809 Tcl_FSCreateDirectoryProc *proc = fsPtr->createDirectoryProc;
3811 return (*proc)(pathPtr);
3814 Tcl_SetErrno(ENOENT);
3819 *---------------------------------------------------------------------------
3821 * Tcl_FSCopyDirectory --
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.
3828 * Standard Tcl error code if a function was called.
3831 * A directory may be copied.
3833 *---------------------------------------------------------------------------
3837 Tcl_FSCopyDirectory(srcPathPtr, destPathPtr, errorPtr)
3838 Tcl_Obj* srcPathPtr; /* Pathname of directory to be copied
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. */
3846 Tcl_Filesystem *fsPtr, *fsPtr2;
3847 fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
3848 fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
3850 if (fsPtr == fsPtr2 && fsPtr != NULL) {
3851 Tcl_FSCopyDirectoryProc *proc = fsPtr->copyDirectoryProc;
3853 retVal = (*proc)(srcPathPtr, destPathPtr, errorPtr);
3857 Tcl_SetErrno(EXDEV);
3863 *---------------------------------------------------------------------------
3865 * Tcl_FSRemoveDirectory --
3867 * The appropriate function for the filesystem to which pathPtr
3868 * belongs will be called.
3871 * Standard Tcl error code.
3874 * A directory may be deleted.
3876 *---------------------------------------------------------------------------
3880 Tcl_FSRemoveDirectory(pathPtr, recursive, errorPtr)
3881 Tcl_Obj *pathPtr; /* Pathname of directory to be removed
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. */
3890 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
3891 if (fsPtr != NULL) {
3892 Tcl_FSRemoveDirectoryProc *proc = fsPtr->removeDirectoryProc;
3896 * We check whether the cwd lies inside this directory
3897 * and move it if it does.
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)) {
3910 * the cwd is inside the directory, so we
3911 * perform a 'cd [file dirname $path]'
3913 Tcl_Obj *dirPtr = TclFileDirname(NULL, pathPtr);
3914 Tcl_FSChdir(dirPtr);
3915 Tcl_DecrRefCount(dirPtr);
3918 Tcl_DecrRefCount(cwdPtr);
3921 return (*proc)(pathPtr, recursive, errorPtr);
3924 Tcl_SetErrno(ENOENT);
3929 *---------------------------------------------------------------------------
3931 * Tcl_FSGetFileSystemForPath --
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.
3939 .* NULL or a filesystem which will accept this path.
3942 * The object may be converted to a path type.
3944 *---------------------------------------------------------------------------
3947 EXPORT_C Tcl_Filesystem*
3948 Tcl_FSGetFileSystemForPath(pathObjPtr)
3949 Tcl_Obj* pathObjPtr;
3951 FilesystemRecord *fsRecPtr;
3952 Tcl_Filesystem* retVal = NULL;
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).
3961 if (pathObjPtr->refCount == 0) {
3962 panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0");
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.
3974 fsRecPtr = FsGetFirstFilesystem();
3976 if (TclFSEnsureEpochOk(pathObjPtr, &retVal) != TCL_OK) {
3981 * Call each of the "pathInFilesystem" functions in succession. A
3982 * non-return value of -1 indicates the particular function has
3986 while ((retVal == NULL) && (fsRecPtr != NULL)) {
3987 Tcl_FSPathInFilesystemProc *proc = fsRecPtr->fsPtr->pathInFilesystemProc;
3989 ClientData clientData = NULL;
3990 int ret = (*proc)(pathObjPtr, &clientData);
3993 * We assume the type of pathObjPtr hasn't been changed
3994 * by the above call to the pathInFilesystemProc.
3996 TclFSSetPathDetails(pathObjPtr, fsRecPtr, clientData);
3997 retVal = fsRecPtr->fsPtr;
4000 fsRecPtr = fsRecPtr->nextPtr;
4007 *---------------------------------------------------------------------------
4009 * Tcl_FSGetNativePath --
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.
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.
4030 * NULL or a valid native path.
4033 * See Tcl_FSGetInternalRep.
4035 *---------------------------------------------------------------------------
4038 EXPORT_C CONST char *
4039 Tcl_FSGetNativePath(pathObjPtr)
4040 Tcl_Obj *pathObjPtr;
4042 return (CONST char *)Tcl_FSGetInternalRep(pathObjPtr, &tclNativeFilesystem);
4046 *---------------------------------------------------------------------------
4048 * NativeCreateNativeRep --
4050 * Create a native representation for the given path.
4058 *---------------------------------------------------------------------------
4061 NativeCreateNativeRep(pathObjPtr)
4062 Tcl_Obj* pathObjPtr;
4064 char *nativePathPtr;
4066 Tcl_Obj* validPathObjPtr;
4070 /* Make sure the normalized path is set */
4071 validPathObjPtr = Tcl_FSGetNormalizedPath(NULL, pathObjPtr);
4072 if (validPathObjPtr == NULL) {
4076 str = Tcl_GetStringFromObj(validPathObjPtr, &len);
4078 Tcl_WinUtfToTChar(str, len, &ds);
4079 if (tclWinProcs->useWide) {
4080 len = Tcl_DStringLength(&ds) + sizeof(WCHAR);
4082 len = Tcl_DStringLength(&ds) + sizeof(char);
4085 Tcl_UtfToExternalDString(NULL, str, len, &ds);
4086 len = Tcl_DStringLength(&ds) + sizeof(char);
4088 nativePathPtr = ckalloc((unsigned) len);
4089 memcpy((VOID*)nativePathPtr, (VOID*)Tcl_DStringValue(&ds), (size_t) len);
4091 Tcl_DStringFree(&ds);
4092 return (ClientData)nativePathPtr;
4096 *---------------------------------------------------------------------------
4098 * TclpNativeToNormalized --
4100 * Convert native format to a normalized path object, with refCount
4104 * A valid normalized path.
4109 *---------------------------------------------------------------------------
4112 TclpNativeToNormalized(clientData)
4113 ClientData clientData;
4121 Tcl_WinTCharToUtf((CONST char*)clientData, -1, &ds);
4123 Tcl_ExternalToUtfDString(NULL, (CONST char*)clientData, -1, &ds);
4126 copy = Tcl_DStringValue(&ds);
4127 len = Tcl_DStringLength(&ds);
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
4135 if (*copy == '\\') {
4136 if (0 == strncmp(copy,"\\??\\",4)) {
4139 } else if (0 == strncmp(copy,"\\\\?\\",4)) {
4146 objPtr = Tcl_NewStringObj(copy,len);
4147 Tcl_DStringFree(&ds);
4154 *---------------------------------------------------------------------------
4156 * TclNativeDupInternalRep --
4158 * Duplicate the native representation.
4161 * The copied native representation, or NULL if it is not possible
4162 * to copy the representation.
4167 *---------------------------------------------------------------------------
4170 TclNativeDupInternalRep(clientData)
4171 ClientData clientData;
4176 if (clientData == NULL) {
4181 if (tclWinProcs->useWide) {
4182 /* unicode representation when running on NT/2K/XP */
4183 len = sizeof(WCHAR) + (wcslen((CONST WCHAR*)clientData) * sizeof(WCHAR));
4185 /* ansi representation when running on 95/98/ME */
4186 len = sizeof(char) + (strlen((CONST char*)clientData) * sizeof(char));
4189 /* ansi representation when running on Unix/MacOS */
4190 len = sizeof(char) + (strlen((CONST char*)clientData) * sizeof(char));
4193 copy = (ClientData) ckalloc(len);
4194 memcpy((VOID*)copy, (VOID*)clientData, len);
4199 *---------------------------------------------------------------------------
4201 * NativeFreeInternalRep --
4203 * Free a native internal representation, which will be non-NULL.
4209 * Memory is released.
4211 *---------------------------------------------------------------------------
4214 NativeFreeInternalRep(clientData)
4215 ClientData clientData;
4217 ckfree((char*)clientData);
4221 *---------------------------------------------------------------------------
4223 * Tcl_FSFileSystemInfo --
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
4231 * A list of two elements.
4234 * The object may be converted to a path type.
4236 *---------------------------------------------------------------------------
4239 Tcl_FSFileSystemInfo(pathObjPtr)
4240 Tcl_Obj* pathObjPtr;
4243 Tcl_FSFilesystemPathTypeProc *proc;
4244 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathObjPtr);
4246 if (fsPtr == NULL) {
4250 resPtr = Tcl_NewListObj(0,NULL);
4252 Tcl_ListObjAppendElement(NULL, resPtr,
4253 Tcl_NewStringObj(fsPtr->typeName,-1));
4255 proc = fsPtr->filesystemPathTypeProc;
4257 Tcl_Obj *typePtr = (*proc)(pathObjPtr);
4258 if (typePtr != NULL) {
4259 Tcl_ListObjAppendElement(NULL, resPtr, typePtr);
4267 *---------------------------------------------------------------------------
4269 * Tcl_FSPathSeparator --
4271 * This function returns the separator to be used for a given
4272 * path. The object returned should have a refCount of zero
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.
4280 * The path object may be converted to a path type.
4282 *---------------------------------------------------------------------------
4285 Tcl_FSPathSeparator(pathObjPtr)
4286 Tcl_Obj* pathObjPtr;
4288 Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathObjPtr);
4290 if (fsPtr == NULL) {
4293 if (fsPtr->filesystemSeparatorProc != NULL) {
4294 return (*fsPtr->filesystemSeparatorProc)(pathObjPtr);
4301 *---------------------------------------------------------------------------
4303 * NativeFilesystemSeparator --
4305 * This function is part of the native filesystem support, and
4306 * returns the separator for the given path.
4309 * String object containing the separator character.
4314 *---------------------------------------------------------------------------
4317 NativeFilesystemSeparator(pathObjPtr)
4318 Tcl_Obj* pathObjPtr;
4320 char *separator = NULL; /* lint */
4321 switch (tclPlatform) {
4322 case TCL_PLATFORM_UNIX:
4325 case TCL_PLATFORM_WINDOWS:
4328 case TCL_PLATFORM_MAC:
4332 return Tcl_NewStringObj(separator,1);
4335 /* Everything from here on is contained in this obsolete ifdef */
4336 #ifdef USE_OBSOLETE_FS_HOOKS
4339 *----------------------------------------------------------------------
4341 * TclStatInsertProc --
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.
4350 * Normally TCL_OK; TCL_ERROR if memory for a new node in the list
4351 * could not be allocated.
4354 * Memory allocated and modifies the link list for 'TclStat'
4357 *----------------------------------------------------------------------
4361 TclStatInsertProc (proc)
4364 int retVal = TCL_ERROR;
4367 StatProc *newStatProcPtr;
4369 newStatProcPtr = (StatProc *)ckalloc(sizeof(StatProc));
4371 if (newStatProcPtr != NULL) {
4372 newStatProcPtr->proc = proc;
4373 Tcl_MutexLock(&obsoleteFsHookMutex);
4374 newStatProcPtr->nextPtr = statProcList;
4375 statProcList = newStatProcPtr;
4376 Tcl_MutexUnlock(&obsoleteFsHookMutex);
4386 *----------------------------------------------------------------------
4388 * TclStatDeleteProc --
4390 * Removed the passed function pointer from the list of 'TclStat'
4391 * functions. Ensures that the built-in stat function is not
4395 * TCL_OK if the procedure pointer was successfully removed,
4396 * TCL_ERROR otherwise.
4399 * Memory is deallocated and the respective list updated.
4401 *----------------------------------------------------------------------
4405 TclStatDeleteProc (proc)
4408 int retVal = TCL_ERROR;
4409 StatProc *tmpStatProcPtr;
4410 StatProc *prevStatProcPtr = NULL;
4412 Tcl_MutexLock(&obsoleteFsHookMutex);
4413 tmpStatProcPtr = statProcList;
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.
4420 while ((retVal == TCL_ERROR) && (tmpStatProcPtr != NULL)) {
4421 if (tmpStatProcPtr->proc == proc) {
4422 if (prevStatProcPtr == NULL) {
4423 statProcList = tmpStatProcPtr->nextPtr;
4425 prevStatProcPtr->nextPtr = tmpStatProcPtr->nextPtr;
4428 ckfree((char *)tmpStatProcPtr);
4432 prevStatProcPtr = tmpStatProcPtr;
4433 tmpStatProcPtr = tmpStatProcPtr->nextPtr;
4437 Tcl_MutexUnlock(&obsoleteFsHookMutex);
4443 *----------------------------------------------------------------------
4445 * TclAccessInsertProc --
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
4455 * Normally TCL_OK; TCL_ERROR if memory for a new node in the list
4456 * could not be allocated.
4459 * Memory allocated and modifies the link list for 'TclAccess'
4462 *----------------------------------------------------------------------
4466 TclAccessInsertProc(proc)
4467 TclAccessProc_ *proc;
4469 int retVal = TCL_ERROR;
4472 AccessProc *newAccessProcPtr;
4474 newAccessProcPtr = (AccessProc *)ckalloc(sizeof(AccessProc));
4476 if (newAccessProcPtr != NULL) {
4477 newAccessProcPtr->proc = proc;
4478 Tcl_MutexLock(&obsoleteFsHookMutex);
4479 newAccessProcPtr->nextPtr = accessProcList;
4480 accessProcList = newAccessProcPtr;
4481 Tcl_MutexUnlock(&obsoleteFsHookMutex);
4491 *----------------------------------------------------------------------
4493 * TclAccessDeleteProc --
4495 * Removed the passed function pointer from the list of 'TclAccess'
4496 * functions. Ensures that the built-in access function is not
4500 * TCL_OK if the procedure pointer was successfully removed,
4501 * TCL_ERROR otherwise.
4504 * Memory is deallocated and the respective list updated.
4506 *----------------------------------------------------------------------
4510 TclAccessDeleteProc(proc)
4511 TclAccessProc_ *proc;
4513 int retVal = TCL_ERROR;
4514 AccessProc *tmpAccessProcPtr;
4515 AccessProc *prevAccessProcPtr = NULL;
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.
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;
4530 prevAccessProcPtr->nextPtr = tmpAccessProcPtr->nextPtr;
4533 ckfree((char *)tmpAccessProcPtr);
4537 prevAccessProcPtr = tmpAccessProcPtr;
4538 tmpAccessProcPtr = tmpAccessProcPtr->nextPtr;
4541 Tcl_MutexUnlock(&obsoleteFsHookMutex);
4547 *----------------------------------------------------------------------
4549 * TclOpenFileChannelInsertProc --
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.
4559 * Normally TCL_OK; TCL_ERROR if memory for a new node in the list
4560 * could not be allocated.
4563 * Memory allocated and modifies the link list for
4564 * 'Tcl_OpenFileChannel' functions.
4566 *----------------------------------------------------------------------
4570 TclOpenFileChannelInsertProc(proc)
4571 TclOpenFileChannelProc_ *proc;
4573 int retVal = TCL_ERROR;
4576 OpenFileChannelProc *newOpenFileChannelProcPtr;
4578 newOpenFileChannelProcPtr =
4579 (OpenFileChannelProc *)ckalloc(sizeof(OpenFileChannelProc));
4581 if (newOpenFileChannelProcPtr != NULL) {
4582 newOpenFileChannelProcPtr->proc = proc;
4583 Tcl_MutexLock(&obsoleteFsHookMutex);
4584 newOpenFileChannelProcPtr->nextPtr = openFileChannelProcList;
4585 openFileChannelProcList = newOpenFileChannelProcPtr;
4586 Tcl_MutexUnlock(&obsoleteFsHookMutex);
4596 *----------------------------------------------------------------------
4598 * TclOpenFileChannelDeleteProc --
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.
4605 * TCL_OK if the procedure pointer was successfully removed,
4606 * TCL_ERROR otherwise.
4609 * Memory is deallocated and the respective list updated.
4611 *----------------------------------------------------------------------
4615 TclOpenFileChannelDeleteProc(proc)
4616 TclOpenFileChannelProc_ *proc;
4618 int retVal = TCL_ERROR;
4619 OpenFileChannelProc *tmpOpenFileChannelProcPtr = openFileChannelProcList;
4620 OpenFileChannelProc *prevOpenFileChannelProcPtr = NULL;
4623 * Traverse the 'openFileChannelProcList' looking for the particular
4624 * node whose 'proc' member matches 'proc' and remove that one from
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;
4636 prevOpenFileChannelProcPtr->nextPtr =
4637 tmpOpenFileChannelProcPtr->nextPtr;
4640 ckfree((char *)tmpOpenFileChannelProcPtr);
4644 prevOpenFileChannelProcPtr = tmpOpenFileChannelProcPtr;
4645 tmpOpenFileChannelProcPtr = tmpOpenFileChannelProcPtr->nextPtr;
4648 Tcl_MutexUnlock(&obsoleteFsHookMutex);
4652 #endif /* USE_OBSOLETE_FS_HOOKS */
4656 * Prototypes for procedures defined later in this file.
4659 static void DupFsPathInternalRep _ANSI_ARGS_((Tcl_Obj *srcPtr,
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,
4665 static int FindSplitPos _ANSI_ARGS_((char *path, char *separator));
4670 * Define the 'path' object type, which Tcl uses to represent
4671 * file paths internally.
4673 static Tcl_ObjType tclFsPathType = {
4675 FreeFsPathInternalRep, /* freeIntRepProc */
4676 DupFsPathInternalRep, /* dupIntRepProc */
4677 UpdateStringOfFsPath, /* updateStringProc */
4678 SetFsPathFromAny /* setFromAnyProc */
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.
4689 * Note that 'normPathPtr' can be a circular reference to the
4690 * container Tcl_Obj of this FsPath.
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
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. */
4723 * Define some macros to give us convenient access to path-object
4726 #define PATHOBJ(objPtr) (objPtr->internalRep.otherValuePtr)
4727 #define PATHFLAGS(objPtr) \
4728 (((FsPath*)(objPtr->internalRep.otherValuePtr))->flags)
4730 #define TCLPATH_APPENDED 1
4731 #define TCLPATH_RELATIVE 2
4734 *----------------------------------------------------------------------
4736 * Tcl_FSGetPathType --
4738 * Determines whether a given path is relative to the current
4739 * directory, relative to the current volume, or absolute.
4742 * Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
4743 * TCL_PATH_VOLUME_RELATIVE.
4748 *----------------------------------------------------------------------
4751 EXPORT_C Tcl_PathType
4752 Tcl_FSGetPathType(pathObjPtr)
4753 Tcl_Obj *pathObjPtr;
4755 return FSGetPathType(pathObjPtr, NULL, NULL);
4759 *----------------------------------------------------------------------
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
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.
4779 *----------------------------------------------------------------------
4783 FSGetPathType(pathObjPtr, filesystemPtrPtr, driveNameLengthPtr)
4784 Tcl_Obj *pathObjPtr;
4785 Tcl_Filesystem **filesystemPtrPtr;
4786 int *driveNameLengthPtr;
4788 if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) {
4789 return GetPathType(pathObjPtr, filesystemPtrPtr,
4790 driveNameLengthPtr, NULL);
4792 FsPath *fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
4793 if (fsPathPtr->cwdPtr != NULL) {
4794 if (PATHFLAGS(pathObjPtr) == 0) {
4795 return TCL_PATH_RELATIVE;
4797 return FSGetPathType(fsPathPtr->cwdPtr, filesystemPtrPtr,
4798 driveNameLengthPtr);
4800 return GetPathType(pathObjPtr, filesystemPtrPtr,
4801 driveNameLengthPtr, NULL);
4807 *---------------------------------------------------------------------------
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.
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.
4824 *---------------------------------------------------------------------------
4827 Tcl_FSJoinPath(listObj, elements)
4833 Tcl_Filesystem *fsPtr = NULL;
4836 if (Tcl_ListObjLength(NULL, listObj, &elements) != TCL_OK) {
4840 /* Just make sure it is a valid list */
4842 if (Tcl_ListObjLength(NULL, listObj, &listTest) != TCL_OK) {
4846 * Correct this if it is too large, otherwise we will
4847 * waste our time joining null elements to the path
4849 if (elements > listTest) {
4850 elements = listTest;
4856 for (i = 0; i < elements; i++) {
4858 int driveNameLength;
4864 Tcl_Obj *driveName = NULL;
4866 Tcl_ListObjIndex(NULL, listObj, i, &elt);
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.
4877 if ((i == (elements-2)) && (i == 0) && (elt->typePtr == &tclFsPathType)
4878 && !(elt->bytes != NULL && (elt->bytes[0] == '\0'))) {
4881 Tcl_ListObjIndex(NULL, listObj, i+1, &tail);
4882 type = GetPathType(tail, NULL, NULL, NULL);
4883 if (type == TCL_PATH_RELATIVE) {
4886 str = Tcl_GetStringFromObj(tail,&len);
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!
4893 Tcl_DecrRefCount(res);
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'
4905 if (str[0] != '.' && ((tclPlatform != TCL_PLATFORM_WINDOWS)
4906 || (strchr(str, '\\') == NULL))) {
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.
4912 if ((tclPlatform != TCL_PLATFORM_WINDOWS)
4913 || (strchr(Tcl_GetString(elt), '\\') == NULL)) {
4915 TclDecrRefCount(res);
4917 return TclNewFSPathObj(elt, str, len);
4921 * Otherwise we don't have an easy join, and
4922 * we must let the more general code below handle
4926 if (tclPlatform == TCL_PLATFORM_UNIX) {
4927 Tcl_DecrRefCount(res);
4932 str = Tcl_GetStringFromObj(tail,&len);
4933 if (tclPlatform == TCL_PLATFORM_WINDOWS) {
4934 if (strchr(str, '\\') == NULL) {
4935 Tcl_DecrRefCount(res);
4938 } else if (tclPlatform == TCL_PLATFORM_MAC) {
4939 if (strchr(str, '/') == NULL) {
4940 Tcl_DecrRefCount(res);
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);
4956 res = Tcl_NewStringObj(strElt, driveNameLength);
4958 strElt += driveNameLength;
4961 ptr = Tcl_GetStringFromObj(res, &length);
4964 * Strip off any './' before a tilde, unless this is the
4965 * beginning of the path.
4967 if (length > 0 && strEltLen > 0) {
4968 if ((strElt[0] == '.') && (strElt[1] == '/')
4969 && (strElt[2] == '~')) {
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.
4980 if (*strElt == '\0') continue;
4982 if (fsPtr == &tclNativeFilesystem || fsPtr == NULL) {
4983 TclpNativeJoinPath(res, strElt);
4985 char separator = '/';
4988 if (fsPtr->filesystemSeparatorProc != NULL) {
4989 Tcl_Obj *sep = (*fsPtr->filesystemSeparatorProc)(res);
4991 separator = Tcl_GetString(sep)[0];
4995 if (length > 0 && ptr[length -1] != '/') {
4996 Tcl_AppendToObj(res, &separator, 1);
4999 Tcl_SetObjLength(res, length + (int) strlen(strElt));
5001 ptr = Tcl_GetString(res) + length;
5002 for (; *strElt != '\0'; strElt++) {
5003 if (*strElt == separator) {
5004 while (strElt[1] == separator) {
5007 if (strElt[1] != '\0') {
5017 length = ptr - Tcl_GetString(res);
5018 Tcl_SetObjLength(res, length);
5025 *---------------------------------------------------------------------------
5027 * Tcl_FSConvertToPathType --
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
5034 * The filename may begin with "~" (to indicate current user's
5035 * home directory) or "~<user>" (to indicate any user's home
5039 * Standard Tcl error code.
5042 * The old representation may be freed, and new memory allocated.
5044 *---------------------------------------------------------------------------
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
5053 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
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.
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);
5070 FreeFsPathInternalRep(objPtr);
5071 objPtr->typePtr = NULL;
5072 return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
5076 return Tcl_ConvertToType(interp, objPtr, &tclFsPathType);
5081 * Helper function for SetFsPathFromAny. Returns position of first
5082 * directory delimiter in the path.
5085 FindSplitPos(path, separator)
5090 switch (tclPlatform) {
5091 case TCL_PLATFORM_UNIX:
5092 case TCL_PLATFORM_MAC:
5093 while (path[count] != 0) {
5094 if (path[count] == *separator) {
5101 case TCL_PLATFORM_WINDOWS:
5102 while (path[count] != 0) {
5103 if (path[count] == *separator || path[count] == '\\') {
5114 *---------------------------------------------------------------------------
5116 * TclNewFSPathObj --
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.
5123 * 'dirPtr' must be an absolute path.
5124 * 'len' may not be zero.
5127 * The new Tcl object, with refCount zero.
5130 * Memory is allocated. 'dirPtr' gets an additional refCount.
5132 *---------------------------------------------------------------------------
5136 TclNewFSPathObj(Tcl_Obj *dirPtr, CONST char *addStrRep, int len)
5140 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
5142 objPtr = Tcl_NewObj();
5143 fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
5145 if (tclPlatform == TCL_PLATFORM_MAC) {
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 ':').
5152 if (addStrRep[0] == ':') {
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;
5167 PATHOBJ(objPtr) = (VOID *) fsPathPtr;
5168 PATHFLAGS(objPtr) = TCLPATH_RELATIVE | TCLPATH_APPENDED;
5169 objPtr->typePtr = &tclFsPathType;
5170 objPtr->bytes = NULL;
5177 *---------------------------------------------------------------------------
5179 * TclFSMakePathRelative --
5181 * Only for internal use.
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.
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.
5195 * NULL on error, otherwise a valid object, typically with
5196 * refCount of zero, which it is assumed the caller will
5200 * The old representation may be freed, and new memory allocated.
5202 *---------------------------------------------------------------------------
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. */
5212 CONST char *tempStr;
5213 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
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);
5231 objPtr->typePtr->updateStringProc(objPtr);
5233 if ((objPtr->typePtr->freeIntRepProc) != NULL) {
5234 (*objPtr->typePtr->freeIntRepProc)(objPtr);
5237 /* Now objPtr is a string object */
5239 if (Tcl_GetString(objPtr)[0] == '~') {
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'.
5248 fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
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;
5259 PATHOBJ(objPtr) = (VOID *) fsPathPtr;
5260 PATHFLAGS(objPtr) = 0;
5261 objPtr->typePtr = &tclFsPathType;
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.
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.
5278 tempStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen);
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
5285 switch (tclPlatform) {
5286 case TCL_PLATFORM_UNIX:
5287 if (tempStr[cwdLen-1] != '/') {
5291 case TCL_PLATFORM_WINDOWS:
5292 if (tempStr[cwdLen-1] != '/'
5293 && tempStr[cwdLen-1] != '\\') {
5297 case TCL_PLATFORM_MAC:
5298 if (tempStr[cwdLen-1] != ':') {
5303 tempStr = Tcl_GetStringFromObj(objPtr, &len);
5305 return Tcl_NewStringObj(tempStr + cwdLen, len - cwdLen);
5309 *---------------------------------------------------------------------------
5311 * TclFSMakePathFromNormalized --
5313 * Like SetFsPathFromAny, but assumes the given object is an
5314 * absolute normalized path. Only for internal use.
5317 * Standard Tcl error code.
5320 * The old representation may be freed, and new memory allocated.
5322 *---------------------------------------------------------------------------
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
5333 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
5335 if (objPtr->typePtr == &tclFsPathType) {
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);
5350 objPtr->typePtr->updateStringProc(objPtr);
5352 if ((objPtr->typePtr->freeIntRepProc) != NULL) {
5353 (*objPtr->typePtr->freeIntRepProc)(objPtr);
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;
5366 PATHOBJ(objPtr) = (VOID *) fsPathPtr;
5367 PATHFLAGS(objPtr) = 0;
5368 objPtr->typePtr = &tclFsPathType;
5374 *---------------------------------------------------------------------------
5376 * Tcl_FSNewNativePath --
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
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.
5391 * NULL or a valid path object pointer, with refCount zero.
5394 * New memory may be allocated.
5396 *---------------------------------------------------------------------------
5400 Tcl_FSNewNativePath(fromFilesystem, clientData)
5401 Tcl_Filesystem* fromFilesystem;
5402 ClientData clientData;
5407 FilesystemRecord *fsFromPtr;
5408 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
5410 objPtr = TclFSInternalToNormalized(fromFilesystem, clientData, &fsFromPtr);
5411 if (objPtr == NULL) {
5416 * Free old representation; shouldn't normally be any,
5417 * but best to be safe.
5419 if (objPtr->typePtr != NULL) {
5420 if (objPtr->bytes == NULL) {
5421 if (objPtr->typePtr->updateStringProc == NULL) {
5424 objPtr->typePtr->updateStringProc(objPtr);
5426 if ((objPtr->typePtr->freeIntRepProc) != NULL) {
5427 (*objPtr->typePtr->freeIntRepProc)(objPtr);
5431 fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
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;
5442 PATHOBJ(objPtr) = (VOID *) fsPathPtr;
5443 PATHFLAGS(objPtr) = 0;
5444 objPtr->typePtr = &tclFsPathType;
5450 *---------------------------------------------------------------------------
5452 * Tcl_FSGetTranslatedPath --
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)
5461 * NULL or a valid Tcl_Obj pointer.
5464 * Only those of 'Tcl_FSConvertToPathType'
5466 *---------------------------------------------------------------------------
5470 Tcl_FSGetTranslatedPath(interp, pathPtr)
5474 Tcl_Obj *retObj = NULL;
5475 FsPath *srcFsPathPtr;
5477 if (Tcl_FSConvertToPathType(interp, pathPtr) != TCL_OK) {
5480 srcFsPathPtr = (FsPath*) PATHOBJ(pathPtr);
5481 if (srcFsPathPtr->translatedPathPtr == NULL) {
5482 if (PATHFLAGS(pathPtr) != 0) {
5483 retObj = Tcl_FSGetNormalizedPath(interp, pathPtr);
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.
5491 retObj = srcFsPathPtr->normPathPtr;
5494 /* It is an ordinary path object */
5495 retObj = srcFsPathPtr->translatedPathPtr;
5499 Tcl_IncrRefCount(retObj);
5505 *---------------------------------------------------------------------------
5507 * Tcl_FSGetTranslatedStringPath --
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)
5516 * NULL or a valid string.
5519 * Only those of 'Tcl_FSConvertToPathType'
5521 *---------------------------------------------------------------------------
5523 EXPORT_C CONST char*
5524 Tcl_FSGetTranslatedStringPath(interp, pathPtr)
5528 Tcl_Obj *transPtr = Tcl_FSGetTranslatedPath(interp, pathPtr);
5530 if (transPtr != NULL) {
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);
5544 *---------------------------------------------------------------------------
5546 * Tcl_FSGetNormalizedPath --
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.
5553 * NULL or a valid path object pointer.
5556 * New memory may be allocated. The Tcl 'errno' may be modified
5557 * in the process of trying to examine various path possibilities.
5559 *---------------------------------------------------------------------------
5563 Tcl_FSGetNormalizedPath(interp, pathObjPtr)
5565 Tcl_Obj* pathObjPtr;
5569 if (Tcl_FSConvertToPathType(interp, pathObjPtr) != TCL_OK) {
5572 fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
5574 if (PATHFLAGS(pathObjPtr) != 0) {
5576 * This is a special path object which is the result of
5577 * something like 'file join'
5579 Tcl_Obj *dir, *copy;
5583 ClientData clientData = NULL;
5585 pathType = Tcl_FSGetPathType(fsPathPtr->cwdPtr);
5586 dir = Tcl_FSGetNormalizedPath(interp, fsPathPtr->cwdPtr);
5590 if (pathObjPtr->bytes == NULL) {
5591 UpdateStringOfFsPath(pathObjPtr);
5593 copy = Tcl_DuplicateObj(dir);
5594 Tcl_IncrRefCount(copy);
5595 Tcl_IncrRefCount(dir);
5596 /* We now own a reference on both 'dir' and 'copy' */
5598 cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
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.
5605 switch (tclPlatform) {
5606 case TCL_PLATFORM_UNIX:
5607 if (cwdStr[cwdLen-1] != '/') {
5608 Tcl_AppendToObj(copy, "/", 1);
5612 case TCL_PLATFORM_WINDOWS:
5613 if (cwdStr[cwdLen-1] != '/'
5614 && cwdStr[cwdLen-1] != '\\') {
5615 Tcl_AppendToObj(copy, "/", 1);
5619 case TCL_PLATFORM_MAC:
5620 if (cwdStr[cwdLen-1] != ':') {
5621 Tcl_AppendToObj(copy, ":", 1);
5626 Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
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.
5635 TclFSNormalizeToUniquePath(interp, copy, cwdLen-1,
5636 (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
5637 /* Now we need to construct the new path object */
5639 if (pathType == TCL_PATH_RELATIVE) {
5640 FsPath* origDirFsPathPtr;
5641 Tcl_Obj *origDir = fsPathPtr->cwdPtr;
5642 origDirFsPathPtr = (FsPath*) PATHOBJ(origDir);
5644 fsPathPtr->cwdPtr = origDirFsPathPtr->cwdPtr;
5645 Tcl_IncrRefCount(fsPathPtr->cwdPtr);
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);
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);
5660 if (clientData != NULL) {
5661 fsPathPtr->nativePathPtr = clientData;
5663 PATHFLAGS(pathObjPtr) = 0;
5665 /* Ensure cwd hasn't changed */
5666 if (fsPathPtr->cwdPtr != NULL) {
5667 if (!TclFSCwdPointerEquals(fsPathPtr->cwdPtr)) {
5668 if (pathObjPtr->bytes == NULL) {
5669 UpdateStringOfFsPath(pathObjPtr);
5671 FreeFsPathInternalRep(pathObjPtr);
5672 pathObjPtr->typePtr = NULL;
5673 if (Tcl_ConvertToType(interp, pathObjPtr,
5674 &tclFsPathType) != TCL_OK) {
5677 fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
5678 } else if (fsPathPtr->normPathPtr == NULL) {
5682 ClientData clientData = NULL;
5684 copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
5685 Tcl_IncrRefCount(copy);
5686 cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
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.
5693 switch (tclPlatform) {
5694 case TCL_PLATFORM_UNIX:
5695 if (cwdStr[cwdLen-1] != '/') {
5696 Tcl_AppendToObj(copy, "/", 1);
5700 case TCL_PLATFORM_WINDOWS:
5701 if (cwdStr[cwdLen-1] != '/'
5702 && cwdStr[cwdLen-1] != '\\') {
5703 Tcl_AppendToObj(copy, "/", 1);
5707 case TCL_PLATFORM_MAC:
5708 if (cwdStr[cwdLen-1] != ':') {
5709 Tcl_AppendToObj(copy, ":", 1);
5714 Tcl_AppendObjToObj(copy, pathObjPtr);
5716 * Normalize the combined string, but only starting after
5717 * the end of the previously normalized 'dir'. This should
5720 TclFSNormalizeToUniquePath(interp, copy, cwdLen-1,
5721 (fsPathPtr->nativePathPtr == NULL ? &clientData : NULL));
5722 fsPathPtr->normPathPtr = copy;
5723 if (clientData != NULL) {
5724 fsPathPtr->nativePathPtr = clientData;
5728 if (fsPathPtr->normPathPtr == NULL) {
5729 ClientData clientData = NULL;
5730 Tcl_Obj *useThisCwd = NULL;
5732 * Since normPathPtr is NULL, but this is a valid path
5733 * object, we know that the translatedPathPtr cannot be NULL.
5735 Tcl_Obj *absolutePath = fsPathPtr->translatedPathPtr;
5736 char *path = Tcl_GetString(absolutePath);
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.
5744 if (path[0] != '\0') {
5745 Tcl_PathType type = Tcl_FSGetPathType(pathObjPtr);
5746 if (type == TCL_PATH_RELATIVE) {
5747 useThisCwd = Tcl_FSGetCwd(interp);
5749 if (useThisCwd == NULL) return NULL;
5751 absolutePath = Tcl_FSJoinToPath(useThisCwd, 1, &absolutePath);
5752 Tcl_IncrRefCount(absolutePath);
5753 /* We have a refCount on the cwd */
5755 } else if (type == TCL_PATH_VOLUME_RELATIVE) {
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
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.
5770 useThisCwd = Tcl_FSGetCwd(interp);
5771 if (useThisCwd == NULL) return NULL;
5773 if (path[0] == '/') {
5775 * Path of form /foo/bar which is a path in the
5776 * root directory of the current volume.
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 */
5785 * Path of form C:foo/bar, but this only makes
5786 * sense if the cwd is also on drive C.
5788 CONST char *drive = Tcl_GetString(useThisCwd);
5789 char drive_c = path[0];
5790 if (drive_c >= 'a') {
5791 drive_c -= ('a' - 'A');
5793 if (drive[0] == drive_c) {
5794 absolutePath = Tcl_DuplicateObj(useThisCwd);
5795 /* We have a refCount on the cwd */
5797 Tcl_DecrRefCount(useThisCwd);
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.
5806 absolutePath = Tcl_NewStringObj(path, 2);
5808 Tcl_IncrRefCount(absolutePath);
5809 Tcl_AppendToObj(absolutePath, "/", 1);
5810 Tcl_AppendToObj(absolutePath, path+2, -1);
5812 #endif /* __WIN32__ */
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);
5822 if (!strcmp(Tcl_GetString(fsPathPtr->normPathPtr),
5823 Tcl_GetString(pathObjPtr))) {
5825 * The path was already normalized.
5826 * Get rid of the duplicate.
5828 Tcl_DecrRefCount(fsPathPtr->normPathPtr);
5830 * We do *not* increment the refCount for
5831 * this circular reference
5833 fsPathPtr->normPathPtr = pathObjPtr;
5835 if (useThisCwd != NULL) {
5836 /* This was returned by Tcl_FSJoinToPath above */
5837 Tcl_DecrRefCount(absolutePath);
5838 fsPathPtr->cwdPtr = useThisCwd;
5842 return fsPathPtr->normPathPtr;
5846 *---------------------------------------------------------------------------
5848 * Tcl_FSGetInternalRep --
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.
5854 * If the internal representation is currently NULL, we attempt
5855 * to generate it, by calling the filesystem's
5856 * 'Tcl_FSCreateInternalRepProc'.
5859 * NULL or a valid internal representation.
5862 * An attempt may be made to convert the object.
5864 *---------------------------------------------------------------------------
5868 Tcl_FSGetInternalRep(pathObjPtr, fsPtr)
5869 Tcl_Obj* pathObjPtr;
5870 Tcl_Filesystem *fsPtr;
5872 FsPath *srcFsPathPtr;
5874 if (Tcl_FSConvertToPathType(NULL, pathObjPtr) != TCL_OK) {
5877 srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
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.
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
5894 if (srcFsPathPtr->fsRecPtr == NULL) {
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.
5904 Tcl_FSGetFileSystemForPath(pathObjPtr);
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).
5912 srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
5913 if (srcFsPathPtr->fsRecPtr == NULL) {
5918 if (fsPtr != srcFsPathPtr->fsRecPtr->fsPtr) {
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.
5926 Tcl_Filesystem *actualFs = Tcl_FSGetFileSystemForPath(pathObjPtr);
5927 if (actualFs == fsPtr) {
5928 return Tcl_FSGetInternalRep(pathObjPtr, fsPtr);
5933 if (srcFsPathPtr->nativePathPtr == NULL) {
5934 Tcl_FSCreateInternalRepProc *proc;
5935 char *nativePathPtr;
5937 proc = srcFsPathPtr->fsRecPtr->fsPtr->createInternalRepProc;
5942 nativePathPtr = (*proc)(pathObjPtr);
5943 srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
5944 srcFsPathPtr->nativePathPtr = nativePathPtr;
5947 return srcFsPathPtr->nativePathPtr;
5951 *---------------------------------------------------------------------------
5953 * TclFSEnsureEpochOk --
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
5961 * Standard Tcl return code.
5964 * An attempt may be made to convert the object.
5966 *---------------------------------------------------------------------------
5970 TclFSEnsureEpochOk(pathObjPtr, fsPtrPtr)
5971 Tcl_Obj* pathObjPtr;
5972 Tcl_Filesystem **fsPtrPtr;
5974 FsPath *srcFsPathPtr;
5975 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
5978 * SHOULD BE ABLE TO IMPROVE EFFICIENCY HERE.
5981 if (Tcl_FSGetNormalizedPath(NULL, pathObjPtr) == NULL) {
5985 srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
5988 * Check if the filesystem has changed in some way since
5989 * this object's internal representation was calculated.
5991 if (srcFsPathPtr->filesystemEpoch != tsdPtr->filesystemEpoch) {
5993 * We have to discard the stale representation and
5996 if (pathObjPtr->bytes == NULL) {
5997 UpdateStringOfFsPath(pathObjPtr);
5999 FreeFsPathInternalRep(pathObjPtr);
6000 pathObjPtr->typePtr = NULL;
6001 if (SetFsPathFromAny(NULL, pathObjPtr) != TCL_OK) {
6004 srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
6006 /* Check whether the object is already assigned to a fs */
6007 if (srcFsPathPtr->fsRecPtr != NULL) {
6008 *fsPtrPtr = srcFsPathPtr->fsRecPtr->fsPtr;
6015 TclFSSetPathDetails(pathObjPtr, fsRecPtr, clientData)
6016 Tcl_Obj *pathObjPtr;
6017 FilesystemRecord *fsRecPtr;
6018 ClientData clientData;
6020 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
6021 /* We assume pathObjPtr is already of the correct type */
6022 FsPath *srcFsPathPtr;
6024 srcFsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
6025 srcFsPathPtr->fsRecPtr = fsRecPtr;
6026 srcFsPathPtr->nativePathPtr = clientData;
6027 srcFsPathPtr->filesystemEpoch = tsdPtr->filesystemEpoch;
6028 fsRecPtr->fileRefCount++;
6032 *---------------------------------------------------------------------------
6034 * Tcl_FSEqualPaths --
6036 * This function tests whether the two paths given are equal path
6037 * objects. If either or both is NULL, 0 is always returned.
6045 *---------------------------------------------------------------------------
6049 Tcl_FSEqualPaths(firstPtr, secondPtr)
6053 if (firstPtr == secondPtr) {
6056 char *firstStr, *secondStr;
6057 int firstLen, secondLen, tempErrno;
6059 if (firstPtr == NULL || secondPtr == NULL) {
6062 firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen);
6063 secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
6064 if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
6068 * Try the most thorough, correct method of comparing fully
6072 tempErrno = Tcl_GetErrno();
6073 firstPtr = Tcl_FSGetNormalizedPath(NULL, firstPtr);
6074 secondPtr = Tcl_FSGetNormalizedPath(NULL, secondPtr);
6075 Tcl_SetErrno(tempErrno);
6077 if (firstPtr == NULL || secondPtr == NULL) {
6080 firstStr = Tcl_GetStringFromObj(firstPtr, &firstLen);
6081 secondStr = Tcl_GetStringFromObj(secondPtr, &secondLen);
6082 if ((firstLen == secondLen) && (strcmp(firstStr, secondStr) == 0)) {
6091 *---------------------------------------------------------------------------
6093 * SetFsPathFromAny --
6095 * This function tries to convert the given Tcl_Obj to a valid
6098 * The filename may begin with "~" (to indicate current user's
6099 * home directory) or "~<user>" (to indicate any user's home
6103 * Standard Tcl error code.
6106 * The old representation may be freed, and new memory allocated.
6108 *---------------------------------------------------------------------------
6112 SetFsPathFromAny(interp, objPtr)
6113 Tcl_Interp *interp; /* Used for error reporting if not NULL. */
6114 Tcl_Obj *objPtr; /* The object to convert. */
6120 ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
6122 if (objPtr->typePtr == &tclFsPathType) {
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.
6134 * We remove any trailing directory separator.
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).
6141 name = Tcl_GetStringFromObj(objPtr,&len);
6144 * Handle tilde substitutions, if needed.
6146 if (name[0] == '~') {
6152 if (tclPlatform==TCL_PLATFORM_MAC) {
6153 if (strchr(name, ':') != NULL) separator = ':';
6156 split = FindSplitPos(name, &separator);
6158 /* We have multiple pieces '~user/foo/bar...' */
6161 /* Do some tilde substitution */
6162 if (name[1] == '\0') {
6163 /* We have just '~' */
6165 Tcl_DString dirString;
6166 if (split != len) { name[split] = separator; }
6168 dir = TclGetEnv("HOME", &dirString);
6171 Tcl_ResetResult(interp);
6172 Tcl_AppendResult(interp, "couldn't find HOME environment ",
6173 "variable to expand path", (char *) NULL);
6177 Tcl_DStringInit(&temp);
6178 Tcl_JoinPath(1, &dir, &temp);
6179 Tcl_DStringFree(&dirString);
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);
6189 Tcl_DStringFree(&temp);
6190 if (split != len) { name[split] = separator; }
6193 if (split != len) { name[split] = separator; }
6196 expandedUser = Tcl_DStringValue(&temp);
6197 transPtr = Tcl_NewStringObj(expandedUser, Tcl_DStringLength(&temp));
6200 /* Join up the tilde substitution with the rest */
6201 if (name[split+1] == separator) {
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.
6212 Tcl_Obj *parts = TclpNativeSplitPath(objPtr, NULL);
6213 Tcl_ListObjGetElements(NULL, parts, &objc, &objv);
6214 /* Skip '~'. It's replaced by its expansion */
6217 TclpNativeJoinPath(transPtr, Tcl_GetString(*objv++));
6219 Tcl_DecrRefCount(parts);
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);
6226 Tcl_DStringFree(&temp);
6228 transPtr = Tcl_FSJoinToPath(objPtr,0,NULL);
6231 #if defined(__CYGWIN__) && defined(__WIN32__)
6233 extern int cygwin_conv_to_win32_path
6234 _ANSI_ARGS_((CONST char *, char *));
6235 char winbuf[MAX_PATH+1];
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!
6242 name = Tcl_GetStringFromObj(transPtr, &len);
6244 cygwin_conv_to_win32_path(name, winbuf);
6245 TclWinNoBackslash(winbuf);
6246 Tcl_SetStringObj(transPtr, winbuf, -1);
6249 #endif /* __CYGWIN__ && __WIN32__ */
6252 * Now we have a translated filename in 'transPtr'. This will have
6253 * forward slashes on Windows, and will not contain any ~user
6257 fsPathPtr = (FsPath*)ckalloc((unsigned)sizeof(FsPath));
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;
6268 * Free old representation before installing our new one.
6270 if (objPtr->typePtr != NULL && objPtr->typePtr->freeIntRepProc != NULL) {
6271 (objPtr->typePtr->freeIntRepProc)(objPtr);
6273 PATHOBJ(objPtr) = (VOID *) fsPathPtr;
6274 PATHFLAGS(objPtr) = 0;
6275 objPtr->typePtr = &tclFsPathType;
6281 FreeFsPathInternalRep(pathObjPtr)
6282 Tcl_Obj *pathObjPtr; /* Path object with internal rep to free. */
6284 FsPath *fsPathPtr = (FsPath*) PATHOBJ(pathObjPtr);
6286 if (fsPathPtr->translatedPathPtr != NULL) {
6287 if (fsPathPtr->translatedPathPtr != pathObjPtr) {
6288 Tcl_DecrRefCount(fsPathPtr->translatedPathPtr);
6291 if (fsPathPtr->normPathPtr != NULL) {
6292 if (fsPathPtr->normPathPtr != pathObjPtr) {
6293 Tcl_DecrRefCount(fsPathPtr->normPathPtr);
6295 fsPathPtr->normPathPtr = NULL;
6297 if (fsPathPtr->cwdPtr != NULL) {
6298 Tcl_DecrRefCount(fsPathPtr->cwdPtr);
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;
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);
6317 ckfree((char*) fsPathPtr);
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. */
6326 FsPath *srcFsPathPtr = (FsPath*) PATHOBJ(srcPtr);
6327 FsPath *copyFsPathPtr = (FsPath*) ckalloc((unsigned)sizeof(FsPath));
6329 Tcl_FSDupInternalRepProc *dupProc;
6331 PATHOBJ(copyPtr) = (VOID *) copyFsPathPtr;
6333 if (srcFsPathPtr->translatedPathPtr != NULL) {
6334 copyFsPathPtr->translatedPathPtr = srcFsPathPtr->translatedPathPtr;
6335 if (copyFsPathPtr->translatedPathPtr != copyPtr) {
6336 Tcl_IncrRefCount(copyFsPathPtr->translatedPathPtr);
6339 copyFsPathPtr->translatedPathPtr = NULL;
6342 if (srcFsPathPtr->normPathPtr != NULL) {
6343 copyFsPathPtr->normPathPtr = srcFsPathPtr->normPathPtr;
6344 if (copyFsPathPtr->normPathPtr != copyPtr) {
6345 Tcl_IncrRefCount(copyFsPathPtr->normPathPtr);
6348 copyFsPathPtr->normPathPtr = NULL;
6351 if (srcFsPathPtr->cwdPtr != NULL) {
6352 copyFsPathPtr->cwdPtr = srcFsPathPtr->cwdPtr;
6353 Tcl_IncrRefCount(copyFsPathPtr->cwdPtr);
6355 copyFsPathPtr->cwdPtr = NULL;
6358 copyFsPathPtr->flags = srcFsPathPtr->flags;
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);
6367 copyFsPathPtr->nativePathPtr = NULL;
6370 copyFsPathPtr->nativePathPtr = NULL;
6372 copyFsPathPtr->fsRecPtr = srcFsPathPtr->fsRecPtr;
6373 copyFsPathPtr->filesystemEpoch = srcFsPathPtr->filesystemEpoch;
6374 if (copyFsPathPtr->fsRecPtr != NULL) {
6375 copyFsPathPtr->fsRecPtr->fileRefCount++;
6378 copyPtr->typePtr = &tclFsPathType;
6382 *---------------------------------------------------------------------------
6384 * UpdateStringOfFsPath --
6386 * Gives an object a valid string rep.
6392 * Memory may be allocated.
6394 *---------------------------------------------------------------------------
6398 UpdateStringOfFsPath(objPtr)
6399 register Tcl_Obj *objPtr; /* path obj with string rep to update. */
6401 FsPath *fsPathPtr = (FsPath*) PATHOBJ(objPtr);
6406 if (PATHFLAGS(objPtr) == 0 || fsPathPtr->cwdPtr == NULL) {
6407 panic("Called UpdateStringOfFsPath with invalid object");
6410 copy = Tcl_DuplicateObj(fsPathPtr->cwdPtr);
6411 Tcl_IncrRefCount(copy);
6413 cwdStr = Tcl_GetStringFromObj(copy, &cwdLen);
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.
6420 switch (tclPlatform) {
6421 case TCL_PLATFORM_UNIX:
6422 if (cwdStr[cwdLen-1] != '/') {
6423 Tcl_AppendToObj(copy, "/", 1);
6427 case TCL_PLATFORM_WINDOWS:
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'
6433 if (cwdStr[cwdLen-1] != '/'
6434 && cwdStr[cwdLen-1] != '\\') {
6435 if (cwdLen != 2 || cwdStr[1] != ':') {
6436 Tcl_AppendToObj(copy, "/", 1);
6441 case TCL_PLATFORM_MAC:
6442 if (cwdStr[cwdLen-1] != ':') {
6443 Tcl_AppendToObj(copy, ":", 1);
6448 Tcl_AppendObjToObj(copy, fsPathPtr->normPathPtr);
6449 objPtr->bytes = Tcl_GetStringFromObj(copy, &cwdLen);
6450 objPtr->length = cwdLen;
6451 copy->bytes = tclEmptyStringRep;
6453 Tcl_DecrRefCount(copy);
6457 *---------------------------------------------------------------------------
6459 * NativePathInFilesystem --
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).
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).
6471 * TCL_OK, to indicate 'yes', -1 to indicate no.
6476 *---------------------------------------------------------------------------
6479 NativePathInFilesystem(pathPtr, clientDataPtr)
6481 ClientData *clientDataPtr;
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
6491 if (pathPtr->typePtr == &tclFsPathType) {
6492 if (pathPtr->bytes != NULL && pathPtr->bytes[0] == '\0') {
6493 /* We reject the empty path "" */
6496 /* Otherwise there is no way this path can be empty */
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.
6504 Tcl_GetStringFromObj(pathPtr,&len);
6506 /* We reject the empty path "" */
6511 * Path is of correct type, or is of non-zero length,