/* * tclIOUtil.c -- * * Provides an interface for managing filesystems in Tcl, and also for * creating a filesystem interface in Tcl arbitrary facilities. All * filesystem operations are performed via this interface. Vince Darley * is the primary author. Other signifiant contributors are Karl * Lehenbauer, Mark Diekhans and Peter da Silva. * * Copyright (c) 1991-1994 The Regents of the University of California. * Copyright (c) 1994-1997 Sun Microsystems, Inc. * Copyright (c) 2001-2004 Vincent Darley. * * See the file "license.terms" for information on usage and redistribution of * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclInt.h" #ifdef _WIN32 # include "tclWinInt.h" #endif #include "tclFileSystem.h" #ifdef TCL_TEMPLOAD_NO_UNLINK #ifndef NO_FSTATFS #include #endif #endif /* * struct FilesystemRecord -- * * An item in a linked list of registered filesystems */ typedef struct FilesystemRecord { ClientData clientData; /* Client-specific data for the filesystem * (can be NULL) */ const Tcl_Filesystem *fsPtr;/* Pointer to filesystem dispatch table. */ struct FilesystemRecord *nextPtr; /* The next registered filesystem, or NULL to * indicate the end of the list. */ struct FilesystemRecord *prevPtr; /* The previous filesystem, or NULL to indicate * the ned of the list */ } FilesystemRecord; /* */ typedef struct { int initialized; size_t cwdPathEpoch; /* Compared with the global cwdPathEpoch to * determine whether cwdPathPtr is stale. */ size_t filesystemEpoch; Tcl_Obj *cwdPathPtr; /* A private copy of cwdPathPtr. Updated when * the value is accessed and cwdPathEpoch has * changed. */ ClientData cwdClientData; FilesystemRecord *filesystemList; size_t claims; } ThreadSpecificData; /* * Forward declarations. */ static Tcl_NRPostProc EvalFileCallback; static FilesystemRecord*FsGetFirstFilesystem(void); static void FsThrExitProc(ClientData cd); static Tcl_Obj * FsListMounts(Tcl_Obj *pathPtr, const char *pattern); static void FsAddMountsToGlobResult(Tcl_Obj *resultPtr, Tcl_Obj *pathPtr, const char *pattern, Tcl_GlobTypeData *types); static void FsUpdateCwd(Tcl_Obj *cwdObj, ClientData clientData); static void FsRecacheFilesystemList(void); static void Claim(void); static void Disclaim(void); static void * DivertFindSymbol(Tcl_Interp *interp, Tcl_LoadHandle loadHandle, const char *symbol); static void DivertUnloadFile(Tcl_LoadHandle loadHandle); /* * Functions that provide native filesystem support. They are private and * should be used only here. They should be called instead of calling Tclp... * native filesystem functions. Others should use the Tcl_FS... functions * which ensure correct and complete virtual filesystem support. */ static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator; static Tcl_FSFreeInternalRepProc NativeFreeInternalRep; static Tcl_FSFileAttrStringsProc NativeFileAttrStrings; static Tcl_FSFileAttrsGetProc NativeFileAttrsGet; static Tcl_FSFileAttrsSetProc NativeFileAttrsSet; /* * Functions that support the native filesystem functions listed above. They * are the same for win/unix, and not in tclInt.h because they are and should * be used only here. */ MODULE_SCOPE const char *const tclpFileAttrStrings[]; MODULE_SCOPE const TclFileAttrProcs tclpFileAttrProcs[]; /* * These these functions are not static either because routines in the native * (win/unix) directories call them or they are actually implemented in those * directories. They should be called from outside Tcl's native filesystem * routines. If we ever built the native filesystem support into a separate * code library, this could actually be enforced. */ Tcl_FSFilesystemPathTypeProc TclpFilesystemPathType; Tcl_FSInternalToNormalizedProc TclpNativeToNormalized; Tcl_FSStatProc TclpObjStat; Tcl_FSAccessProc TclpObjAccess; Tcl_FSMatchInDirectoryProc TclpMatchInDirectory; Tcl_FSChdirProc TclpObjChdir; Tcl_FSLstatProc TclpObjLstat; Tcl_FSCopyFileProc TclpObjCopyFile; Tcl_FSDeleteFileProc TclpObjDeleteFile; Tcl_FSRenameFileProc TclpObjRenameFile; Tcl_FSCreateDirectoryProc TclpObjCreateDirectory; Tcl_FSCopyDirectoryProc TclpObjCopyDirectory; Tcl_FSRemoveDirectoryProc TclpObjRemoveDirectory; Tcl_FSLinkProc TclpObjLink; Tcl_FSListVolumesProc TclpObjListVolumes; /* * The native filesystem dispatch table. This could me made public but it * should only be accessed by the functions it points to, or perhaps * subordinate helper functions. */ const Tcl_Filesystem tclNativeFilesystem = { "native", sizeof(Tcl_Filesystem), TCL_FILESYSTEM_VERSION_2, TclNativePathInFilesystem, TclNativeDupInternalRep, NativeFreeInternalRep, TclpNativeToNormalized, TclNativeCreateNativeRep, TclpObjNormalizePath, TclpFilesystemPathType, NativeFilesystemSeparator, TclpObjStat, TclpObjAccess, TclpOpenFileChannel, TclpMatchInDirectory, TclpUtime, #ifndef S_IFLNK NULL, #else TclpObjLink, #endif /* S_IFLNK */ TclpObjListVolumes, NativeFileAttrStrings, NativeFileAttrsGet, NativeFileAttrsSet, TclpObjCreateDirectory, TclpObjRemoveDirectory, TclpObjDeleteFile, TclpObjCopyFile, TclpObjRenameFile, TclpObjCopyDirectory, TclpObjLstat, /* Needs casts since we're using version_2. */ (Tcl_FSLoadFileProc *)(void *) TclpDlopen, (Tcl_FSGetCwdProc *) TclpGetNativeCwd, TclpObjChdir }; /* * An initial record in the linked list for the native filesystem. Remains at * the tail of the list and is never freed. Currently the native filesystem is * hard-coded. It may make sense to modify this to accomodate unconventional * uses of Tcl that provide no native filesystem. */ static FilesystemRecord nativeFilesystemRecord = { NULL, &tclNativeFilesystem, NULL, NULL }; /* * Incremented each time the linked list of filesystems is modified. For * multithreaded builds, invalidates all cached filesystem internal * representations. */ static size_t theFilesystemEpoch = 1; /* * The linked list of filesystems. To minimize locking each thread maintains a * local copy of this list. * */ static FilesystemRecord *filesystemList = &nativeFilesystemRecord; TCL_DECLARE_MUTEX(filesystemMutex) /* * A files-system indepent sense of the current directory. */ static Tcl_Obj *cwdPathPtr = NULL; static size_t cwdPathEpoch = 0; /* The pathname of the current directory */ static ClientData cwdClientData = NULL; TCL_DECLARE_MUTEX(cwdMutex) static Tcl_ThreadDataKey fsDataKey; /* * When a temporary copy of a file is created on the native filesystem in order * to load the file, an FsDivertLoad structure is created to track both the * actual unloadProc/clientData combination which was used, and the original and * modified filenames. This makes it possible to correctly undo the entire * operation in order to unload the library. */ typedef struct { Tcl_LoadHandle loadHandle; Tcl_FSUnloadFileProc *unloadProcPtr; Tcl_Obj *divertedFile; const Tcl_Filesystem *divertedFilesystem; ClientData divertedFileNativeRep; } FsDivertLoad; /* * Obsolete string-based APIs that should be removed in a future release, * perhaps in Tcl 9. */ /* Obsolete */ int Tcl_Stat( const char *path, /* Pathname of file to stat (in current CP). */ struct stat *oldStyleBuf) /* Filled with results of stat call. */ { int ret; Tcl_StatBuf buf; Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSStat(pathPtr, &buf); Tcl_DecrRefCount(pathPtr); if (ret != -1) { #ifndef TCL_WIDE_INT_IS_LONG Tcl_WideInt tmp1, tmp2, tmp3 = 0; # define OUT_OF_RANGE(x) \ (((Tcl_WideInt)(x)) < LONG_MIN || \ ((Tcl_WideInt)(x)) > LONG_MAX) # define OUT_OF_URANGE(x) \ (((Tcl_WideUInt)(x)) > ((Tcl_WideUInt)ULONG_MAX)) /* * Perform the result-buffer overflow check manually. * * Note that ino_t/ino64_t is unsigned... * * Workaround gcc warning of "comparison is always false due to * limited range of data type" by assigning to tmp var of type * Tcl_WideInt. */ tmp1 = (Tcl_WideInt) buf.st_ino; tmp2 = (Tcl_WideInt) buf.st_size; #ifdef HAVE_STRUCT_STAT_ST_BLOCKS tmp3 = (Tcl_WideInt) buf.st_blocks; #endif if (OUT_OF_URANGE(tmp1) || OUT_OF_RANGE(tmp2) || OUT_OF_RANGE(tmp3)) { #if defined(EFBIG) errno = EFBIG; #elif defined(EOVERFLOW) errno = EOVERFLOW; #else #error "What status should be returned for file size out of range?" #endif return -1; } # undef OUT_OF_RANGE # undef OUT_OF_URANGE #endif /* !TCL_WIDE_INT_IS_LONG */ /* * Copy across all supported fields, with possible type coercions on * those fields that change between the normal and lf64 versions of * the stat structure (on Solaris at least). This is slow when the * structure sizes coincide, but that's what you get for using an * obsolete interface. */ oldStyleBuf->st_mode = buf.st_mode; oldStyleBuf->st_ino = (ino_t) buf.st_ino; oldStyleBuf->st_dev = buf.st_dev; oldStyleBuf->st_rdev = buf.st_rdev; oldStyleBuf->st_nlink = buf.st_nlink; oldStyleBuf->st_uid = buf.st_uid; oldStyleBuf->st_gid = buf.st_gid; oldStyleBuf->st_size = (off_t) buf.st_size; oldStyleBuf->st_atime = Tcl_GetAccessTimeFromStat(&buf); oldStyleBuf->st_mtime = Tcl_GetModificationTimeFromStat(&buf); oldStyleBuf->st_ctime = Tcl_GetChangeTimeFromStat(&buf); #ifdef HAVE_STRUCT_STAT_ST_BLKSIZE oldStyleBuf->st_blksize = buf.st_blksize; #endif #ifdef HAVE_STRUCT_STAT_ST_BLOCKS #ifdef HAVE_BLKCNT_T oldStyleBuf->st_blocks = (blkcnt_t) buf.st_blocks; #else oldStyleBuf->st_blocks = (unsigned long) buf.st_blocks; #endif #endif } return ret; } /* Obsolete */ int Tcl_Access( const char *path, /* Pathname of file to access (in current CP). */ int mode) /* Permission setting. */ { int ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSAccess(pathPtr,mode); Tcl_DecrRefCount(pathPtr); return ret; } /* Obsolete */ Tcl_Channel Tcl_OpenFileChannel( Tcl_Interp *interp, /* Interpreter for error reporting. May be * NULL. */ const char *path, /* Pathname of file to open. */ const char *modeString, /* A list of POSIX open modes or a string such * as "rw". */ int permissions) /* The modes to use if creating a new file. */ { Tcl_Channel ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions); Tcl_DecrRefCount(pathPtr); return ret; } /* Obsolete */ int Tcl_Chdir( const char *dirName) { int ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(dirName,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSChdir(pathPtr); Tcl_DecrRefCount(pathPtr); return ret; } /* Obsolete */ char * Tcl_GetCwd( Tcl_Interp *interp, Tcl_DString *cwdPtr) { Tcl_Obj *cwd = Tcl_FSGetCwd(interp); if (cwd == NULL) { return NULL; } Tcl_DStringInit(cwdPtr); TclDStringAppendObj(cwdPtr, cwd); Tcl_DecrRefCount(cwd); return Tcl_DStringValue(cwdPtr); } int Tcl_EvalFile( Tcl_Interp *interp, /* Interpreter in which to evaluate the script. */ const char *fileName) /* Pathname of the file containing the script. * Performs Tilde-substitution on this * pathaname. */ { int ret; Tcl_Obj *pathPtr = Tcl_NewStringObj(fileName,-1); Tcl_IncrRefCount(pathPtr); ret = Tcl_FSEvalFile(interp, pathPtr); Tcl_DecrRefCount(pathPtr); return ret; } /* * The basic filesystem implementation. */ static void FsThrExitProc( ClientData cd) { ThreadSpecificData *tsdPtr = (ThreadSpecificData *)cd; FilesystemRecord *fsRecPtr = NULL, *tmpFsRecPtr = NULL; /* * Discard the cwd copy. */ if (tsdPtr->cwdPathPtr != NULL) { Tcl_DecrRefCount(tsdPtr->cwdPathPtr); tsdPtr->cwdPathPtr = NULL; } if (tsdPtr->cwdClientData != NULL) { NativeFreeInternalRep(tsdPtr->cwdClientData); } /* * Discard the filesystems cache. */ fsRecPtr = tsdPtr->filesystemList; while (fsRecPtr != NULL) { tmpFsRecPtr = fsRecPtr->nextPtr; fsRecPtr->fsPtr = NULL; Tcl_Free(fsRecPtr); fsRecPtr = tmpFsRecPtr; } tsdPtr->filesystemList = NULL; tsdPtr->initialized = 0; } int TclFSCwdIsNative(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); if (tsdPtr->cwdClientData != NULL) { return 1; } else { return 0; } } /* *---------------------------------------------------------------------- * * TclFSCwdPointerEquals -- * Determine whether the given pathname is equal to the current working * directory. * * Results: * 1 if equal, 0 otherwise. * * Side effects: * Updates TSD if needed. * * Stores a pointer to the current directory in *pathPtrPtr if it is not * already there and the current directory is not NULL. * * If *pathPtrPtr is not null its reference count is decremented * before it is replaced. *---------------------------------------------------------------------- */ int TclFSCwdPointerEquals( Tcl_Obj **pathPtrPtr) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); Tcl_MutexLock(&cwdMutex); if (tsdPtr->cwdPathPtr == NULL || tsdPtr->cwdPathEpoch != cwdPathEpoch) { if (tsdPtr->cwdPathPtr != NULL) { Tcl_DecrRefCount(tsdPtr->cwdPathPtr); } if (tsdPtr->cwdClientData != NULL) { NativeFreeInternalRep(tsdPtr->cwdClientData); } if (cwdPathPtr == NULL) { tsdPtr->cwdPathPtr = NULL; } else { tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr); Tcl_IncrRefCount(tsdPtr->cwdPathPtr); } if (cwdClientData == NULL) { tsdPtr->cwdClientData = NULL; } else { tsdPtr->cwdClientData = TclNativeDupInternalRep(cwdClientData); } tsdPtr->cwdPathEpoch = cwdPathEpoch; } Tcl_MutexUnlock(&cwdMutex); if (tsdPtr->initialized == 0) { Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr); tsdPtr->initialized = 1; } if (pathPtrPtr == NULL) { return (tsdPtr->cwdPathPtr == NULL); } if (tsdPtr->cwdPathPtr == *pathPtrPtr) { return 1; } else { size_t len1, len2; const char *str1, *str2; str1 = TclGetStringFromObj(tsdPtr->cwdPathPtr, &len1); str2 = TclGetStringFromObj(*pathPtrPtr, &len2); if ((len1 == len2) && !memcmp(str1, str2, len1)) { /* * The values are equal but the objects are different. Cache the * current structure in place of the old one. */ Tcl_DecrRefCount(*pathPtrPtr); *pathPtrPtr = tsdPtr->cwdPathPtr; Tcl_IncrRefCount(*pathPtrPtr); return 1; } else { return 0; } } } static void FsRecacheFilesystemList(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); FilesystemRecord *fsRecPtr, *tmpFsRecPtr = NULL, *toFree = NULL, *list; /* * Trash the current cache. */ fsRecPtr = tsdPtr->filesystemList; while (fsRecPtr != NULL) { tmpFsRecPtr = fsRecPtr->nextPtr; fsRecPtr->nextPtr = toFree; toFree = fsRecPtr; fsRecPtr = tmpFsRecPtr; } /* * Locate tail of the global filesystem list. */ Tcl_MutexLock(&filesystemMutex); fsRecPtr = filesystemList; while (fsRecPtr != NULL) { tmpFsRecPtr = fsRecPtr; fsRecPtr = fsRecPtr->nextPtr; } /* * Refill the cache, honouring the order. */ list = NULL; fsRecPtr = tmpFsRecPtr; while (fsRecPtr != NULL) { tmpFsRecPtr = (FilesystemRecord *)Tcl_Alloc(sizeof(FilesystemRecord)); *tmpFsRecPtr = *fsRecPtr; tmpFsRecPtr->nextPtr = list; tmpFsRecPtr->prevPtr = NULL; list = tmpFsRecPtr; fsRecPtr = fsRecPtr->prevPtr; } tsdPtr->filesystemList = list; tsdPtr->filesystemEpoch = theFilesystemEpoch; Tcl_MutexUnlock(&filesystemMutex); while (toFree) { FilesystemRecord *next = toFree->nextPtr; toFree->fsPtr = NULL; Tcl_Free(toFree); toFree = next; } /* * Make sure the above gets released on thread exit. */ if (tsdPtr->initialized == 0) { Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr); tsdPtr->initialized = 1; } } static FilesystemRecord * FsGetFirstFilesystem(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); if (tsdPtr->filesystemList == NULL || ((tsdPtr->claims == 0) && (tsdPtr->filesystemEpoch != theFilesystemEpoch))) { FsRecacheFilesystemList(); } return tsdPtr->filesystemList; } /* * The epoch can is changed when a filesystems is added or removed, when * "system encoding" changes, and when env(HOME) changes. */ int TclFSEpochOk( size_t filesystemEpoch) { return (filesystemEpoch == 0 || filesystemEpoch == theFilesystemEpoch); } static void Claim(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); tsdPtr->claims++; } static void Disclaim(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); tsdPtr->claims--; } size_t TclFSEpoch(void) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); return tsdPtr->filesystemEpoch; } /* * If non-NULL, take posession of clientData and free it later. */ static void FsUpdateCwd( Tcl_Obj *cwdObj, ClientData clientData) { size_t len = 0; const char *str = NULL; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); if (cwdObj != NULL) { str = TclGetStringFromObj(cwdObj, &len); } Tcl_MutexLock(&cwdMutex); if (cwdPathPtr != NULL) { Tcl_DecrRefCount(cwdPathPtr); } if (cwdClientData != NULL) { NativeFreeInternalRep(cwdClientData); } if (cwdObj == NULL) { cwdPathPtr = NULL; cwdClientData = NULL; } else { /* * This must be stored as a string obj! */ cwdPathPtr = Tcl_NewStringObj(str, len); Tcl_IncrRefCount(cwdPathPtr); cwdClientData = TclNativeDupInternalRep(clientData); } if (++cwdPathEpoch == 0) { ++cwdPathEpoch; } tsdPtr->cwdPathEpoch = cwdPathEpoch; Tcl_MutexUnlock(&cwdMutex); if (tsdPtr->cwdPathPtr) { Tcl_DecrRefCount(tsdPtr->cwdPathPtr); } if (tsdPtr->cwdClientData) { NativeFreeInternalRep(tsdPtr->cwdClientData); } if (cwdObj == NULL) { tsdPtr->cwdPathPtr = NULL; tsdPtr->cwdClientData = NULL; } else { tsdPtr->cwdPathPtr = Tcl_NewStringObj(str, len); tsdPtr->cwdClientData = clientData; Tcl_IncrRefCount(tsdPtr->cwdPathPtr); } } /* *---------------------------------------------------------------------- * * TclFinalizeFilesystem -- * * Clean up the filesystem. After this, any call to a Tcl_FS... function * fails. * * If TclResetFilesystem is called later, it restores the filesystem to a * pristine state. * * Results: * None. * * Side effects: * Frees memory allocated for the filesystem. * *---------------------------------------------------------------------- */ void TclFinalizeFilesystem(void) { FilesystemRecord *fsRecPtr; /* * Assume that only one thread is active. Otherwise mutexes would be needed * around this code. * TO DO: This assumption is false, isn't it? */ if (cwdPathPtr != NULL) { Tcl_DecrRefCount(cwdPathPtr); cwdPathPtr = NULL; cwdPathEpoch = 0; } if (cwdClientData != NULL) { NativeFreeInternalRep(cwdClientData); cwdClientData = NULL; } /* * Remove all filesystems, freeing any allocated memory that is no longer * needed. */ fsRecPtr = filesystemList; while (fsRecPtr != NULL) { FilesystemRecord *tmpFsRecPtr = fsRecPtr->nextPtr; /* * The native filesystem is static, so don't free it. */ if (fsRecPtr != &nativeFilesystemRecord) { Tcl_Free(fsRecPtr); } fsRecPtr = tmpFsRecPtr; } if (++theFilesystemEpoch == 0) { ++theFilesystemEpoch; } filesystemList = NULL; /* * filesystemList is now NULL. Any attempt to use the filesystem is likely * to fail. */ #ifdef _WIN32 TclWinEncodingsCleanup(); #endif } /* *---------------------------------------------------------------------- * * TclResetFilesystem -- * * Restore the filesystem to a pristine state. * * Results: * None. * * Side effects: * None. * *---------------------------------------------------------------------- */ void TclResetFilesystem(void) { filesystemList = &nativeFilesystemRecord; if (++theFilesystemEpoch == 0) { ++theFilesystemEpoch; } } /* *---------------------------------------------------------------------- * * Tcl_FSRegister -- * * Prepends to the list of registered fileystems a new FilesystemRecord * for the given Tcl_Filesystem, which is added even if it is already in * the list. To determine whether the filesystem is already in the list, * use Tcl_FSData(). * * Functions that use the list generally process it from head to tail and * use the first filesystem that is suitable. Therefore, when adding a * diagnostic filsystem (one which simply reports all fs activity), it * must be at the head of the list. I.e. it must be the last one * registered. * * Results: * TCL_OK, or TCL_ERROR if memory for a new node in the list could * not be allocated. * * Side effects: * Allocates memory for a filesystem record and modifies the list of * registered filesystems. * *---------------------------------------------------------------------- */ int Tcl_FSRegister( ClientData clientData, /* Client-specific data for this filesystem. */ const Tcl_Filesystem *fsPtr)/* The filesystem record for the new fs. */ { FilesystemRecord *newFilesystemPtr; if (fsPtr == NULL) { return TCL_ERROR; } newFilesystemPtr = (FilesystemRecord *)Tcl_Alloc(sizeof(FilesystemRecord)); newFilesystemPtr->clientData = clientData; newFilesystemPtr->fsPtr = fsPtr; Tcl_MutexLock(&filesystemMutex); newFilesystemPtr->nextPtr = filesystemList; newFilesystemPtr->prevPtr = NULL; if (filesystemList) { filesystemList->prevPtr = newFilesystemPtr; } filesystemList = newFilesystemPtr; /* * Increment the filesystem epoch counter since existing pathnames might * conceivably now belong to different filesystems. */ if (++theFilesystemEpoch == 0) { ++theFilesystemEpoch; } Tcl_MutexUnlock(&filesystemMutex); return TCL_OK; } /* *---------------------------------------------------------------------- * * Tcl_FSUnregister -- * * Removes the record for given filesystem from the list of registered * filesystems. Refuses to remove the built-in (native) filesystem. This * might be changed in the future to allow a smaller Tcl core in which the * native filesystem is not used at all, e.g. initializing Tcl over a * network connection. * * Results: * TCL_OK if the function pointer was successfully removed, or TCL_ERROR * otherwise. * * Side effects: * The list of registered filesystems is updated. Memory for the * corresponding FilesystemRecord is eventually freed. * *---------------------------------------------------------------------- */ int Tcl_FSUnregister( const Tcl_Filesystem *fsPtr)/* The filesystem record to remove. */ { int retVal = TCL_ERROR; FilesystemRecord *fsRecPtr; Tcl_MutexLock(&filesystemMutex); /* * Traverse filesystemList in search of the record whose * 'fsPtr' member matches 'fsPtr' and remove that record from the list. * Do not revmoe the record for the native filesystem. */ fsRecPtr = filesystemList; while ((retVal == TCL_ERROR) && (fsRecPtr != &nativeFilesystemRecord)) { if (fsRecPtr->fsPtr == fsPtr) { if (fsRecPtr->prevPtr) { fsRecPtr->prevPtr->nextPtr = fsRecPtr->nextPtr; } else { filesystemList = fsRecPtr->nextPtr; } if (fsRecPtr->nextPtr) { fsRecPtr->nextPtr->prevPtr = fsRecPtr->prevPtr; } /* * Each cached pathname could now belong to a different filesystem, * so increment the filesystem epoch counter to ensure that cached * information about the removed filesystem is not used. */ if (++theFilesystemEpoch == 0) { ++theFilesystemEpoch; } Tcl_Free(fsRecPtr); retVal = TCL_OK; } else { fsRecPtr = fsRecPtr->nextPtr; } } Tcl_MutexUnlock(&filesystemMutex); return retVal; } /* *---------------------------------------------------------------------- * * Tcl_FSMatchInDirectory -- * * Search in the given pathname for files matching the given pattern. * Used by [glob]. Processes just one pattern for one directory. Callers * such as TclGlob and DoGlob implement manage the searching of multiple * directories in cases such as * glob -dir $dir -join * pkgIndex.tcl * * Results: * * TCL_OK, or TCL_ERROR * * Side effects: * resultPtr is populated, or in the case of an TCL_ERROR, an error message is * set in the interpreter. * *---------------------------------------------------------------------- */ int Tcl_FSMatchInDirectory( Tcl_Interp *interp, /* Interpreter to receive error messages, or * NULL */ Tcl_Obj *resultPtr, /* List that results are added to. */ Tcl_Obj *pathPtr, /* Pathname of directory to search. If NULL, * the current working directory is used. */ const char *pattern, /* Pattern to match. If NULL, pathPtr must be * a fully-specified pathname of a single * file/directory which already exists and is * of the correct type. */ Tcl_GlobTypeData *types) /* Specifies acceptable types. * May be NULL. The directory flag is * particularly significant. */ { const Tcl_Filesystem *fsPtr; Tcl_Obj *cwd, *tmpResultPtr, **elemsPtr; int resLength, i, ret = -1; if (types != NULL && (types->type & TCL_GLOB_TYPE_MOUNT)) { /* * Currently external callers may not query mounts, which would be a * valuable future step. This is the only routine that knows about * mounts, so we're being called recursively by ourself. Return no * matches. */ return TCL_OK; } if (pathPtr != NULL) { fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); } else { fsPtr = NULL; } if (fsPtr != NULL) { /* * A corresponding filesystem was found. Search within it. */ if (fsPtr->matchInDirectoryProc == NULL) { Tcl_SetErrno(ENOENT); return -1; } ret = fsPtr->matchInDirectoryProc(interp, resultPtr, pathPtr, pattern, types); if (ret == TCL_OK && pattern != NULL) { FsAddMountsToGlobResult(resultPtr, pathPtr, pattern, types); } return ret; } if (pathPtr != NULL && TclGetString(pathPtr)[0] != '\0') { /* * There is a pathname but it belongs to no known filesystem. Mayday! */ Tcl_SetErrno(ENOENT); return -1; } /* * The pathname is empty or NULL so search in the current working * directory. matchInDirectoryProc prefixes each result with this * directory, so trim it from each result. Deal with this here in the * generic code because otherwise every filesystem implementation of * Tcl_FSMatchInDirectory has to do it. */ cwd = Tcl_FSGetCwd(NULL); if (cwd == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "glob couldn't determine the current working directory", -1)); } return TCL_ERROR; } fsPtr = Tcl_FSGetFileSystemForPath(cwd); if (fsPtr != NULL && fsPtr->matchInDirectoryProc != NULL) { TclNewObj(tmpResultPtr); Tcl_IncrRefCount(tmpResultPtr); ret = fsPtr->matchInDirectoryProc(interp, tmpResultPtr, cwd, pattern, types); if (ret == TCL_OK) { FsAddMountsToGlobResult(tmpResultPtr, cwd, pattern, types); /* * resultPtr and tmpResultPtr are guaranteed to be distinct. */ ret = Tcl_ListObjGetElements(interp, tmpResultPtr, &resLength, &elemsPtr); for (i=0 ; ret==TCL_OK && itype & TCL_GLOB_TYPE_DIR)); Tcl_Obj *mounts = FsListMounts(pathPtr, pattern); if (mounts == NULL) { return; } if (Tcl_ListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) { goto endOfMounts; } if (Tcl_ListObjLength(NULL, resultPtr, &gLength) != TCL_OK) { goto endOfMounts; } for (i=0 ; ifsPtr == fsPtr) { retVal = fsRecPtr->clientData; } fsRecPtr = fsRecPtr->nextPtr; } return retVal; } /* *--------------------------------------------------------------------------- * * TclFSNormalizeToUniquePath -- * * Converts the given pathname, containing no ../, ./ components, into a * unique pathname for the given platform. On Unix the resulting pathname * is free of symbolic links/aliases, and on Windows it is the long * case-preserving form. * * * Results: * Stores the resulting pathname in pathPtr and returns the offset of the * last byte processed in pathPtr. * * Side effects: * None (beyond the memory allocation for the result). * * Special notes: * If the filesystem-specific normalizePathProcs can re-introduce ../, ./ * components into the pathname, this function does not return the correct * result. This may be possible with symbolic links on unix. * * *--------------------------------------------------------------------------- */ int TclFSNormalizeToUniquePath( Tcl_Interp *interp, /* Used for error messages. */ Tcl_Obj *pathPtr, /* An Pathname to normalize in-place. Must be * unshared. */ int startAt) /* Offset the string of pathPtr to start at. * Must either be 0 or offset of a directory * separator at the end of a pathname part that * is already normalized, I.e. not the index of * the byte just after the separator. */ { FilesystemRecord *fsRecPtr, *firstFsRecPtr; size_t i; int isVfsPath = 0; const char *path; /* * Pathnames starting with a UNC prefix and ending with a colon character * are reserved for VFS use. These names can not conflict with real UNC * pathnames per https://msdn.microsoft.com/en-us/library/gg465305.aspx and * rfc3986's definition of reg-name. * * We check these first to avoid useless calls to the native filesystem's * normalizePathProc. */ path = TclGetStringFromObj(pathPtr, &i); if ( (i >= 3) && ( (path[0] == '/' && path[1] == '/') || (path[0] == '\\' && path[1] == '\\') ) ) { for ( i = 2; ; i++) { if (path[i] == '\0') break; if (path[i] == path[0]) break; } --i; if (path[i] == ':') isVfsPath = 1; } /* * Call the the normalizePathProc routine of each registered filesystem. */ firstFsRecPtr = FsGetFirstFilesystem(); Claim(); if (!isVfsPath) { /* * Find and call the native filesystem handler first if there is one * because the root of Tcl's filesystem is always a native filesystem * (i.e., '/' on unix is native). */ for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) { if (fsRecPtr->fsPtr != &tclNativeFilesystem) { continue; } /* * TODO: Always call the normalizePathProc here because it should * always exist. */ if (fsRecPtr->fsPtr->normalizePathProc != NULL) { startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr, startAt); } break; } } for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) { if (fsRecPtr->fsPtr == &tclNativeFilesystem) { /* * Skip the native system this time through. */ continue; } if (fsRecPtr->fsPtr->normalizePathProc != NULL) { startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr, startAt); } /* * This efficiency check could be added: * if (retVal == length-of(pathPtr)) {break;} * but there's not much benefit. */ } Disclaim(); return startAt; } /* *--------------------------------------------------------------------------- * * TclGetOpenMode -- * * Obsolete. A limited version of TclGetOpenModeEx() which exists only to * satisfy any extensions imprudently using it via Tcl's internal stubs * table. * * Results: * See TclGetOpenModeEx(). * * Side effects: * See TclGetOpenModeEx(). * *--------------------------------------------------------------------------- */ int TclGetOpenMode( Tcl_Interp *interp, /* Interpreter to use for error reporting. May * be NULL. */ const char *modeString, /* e.g. "r+" or "RDONLY CREAT". */ int *seekFlagPtr) /* Sets this to 1 to tell the caller to seek to EOF after opening the file, and * 0 otherwise. */ { int binary = 0; return TclGetOpenModeEx(interp, modeString, seekFlagPtr, &binary); } /* *--------------------------------------------------------------------------- * * TclGetOpenModeEx -- * * Computes a POSIX mode mask for opening a file. * * Results: * The mode to pass to "open", or -1 if an error occurs. * * Side effects: * Sets *seekFlagPtr to 1 to tell the caller to * seek to EOF after opening the file, or to 0 otherwise. * * Sets *binaryPtr to 1 to tell the caller to configure the channel as a * binary channel, or to 0 otherwise. * * If there is an error and interp is not NULL, sets interpreter result to * an error message. * * Special note: * Based on a prototype implementation contributed by Mark Diekhans. * *--------------------------------------------------------------------------- */ int TclGetOpenModeEx( Tcl_Interp *interp, /* Interpreter, possibly NULL, to use for * error reporting. */ const char *modeString, /* Mode string, e.g. "r+" or "RDONLY CREAT" */ int *seekFlagPtr, /* Sets this to 1 to tell the the caller to seek to * EOF after opening the file, and 0 otherwise. */ int *binaryPtr) /* Sets this to 1 to tell the caller to * configure the channel for binary * operations after opening the file. */ { int mode, modeArgc, c, i, gotRW; const char **modeArgv, *flag; #define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR) /* * Check for the simpler fopen-like access modes like "r" which are * distinguished from the POSIX access modes by the presence of a * lower-case first letter. */ *seekFlagPtr = 0; *binaryPtr = 0; mode = 0; /* * Guard against wide characters before using byte-oriented routines. */ if (!(modeString[0] & 0x80) && islower(UCHAR(modeString[0]))) { /* INTL: ISO only. */ switch (modeString[0]) { case 'r': mode = O_RDONLY; break; case 'w': mode = O_WRONLY|O_CREAT|O_TRUNC; break; case 'a': /* * Add O_APPEND for proper automatic seek-to-end-on-write by the * OS. [Bug 680143] */ mode = O_WRONLY|O_CREAT|O_APPEND; *seekFlagPtr = 1; break; default: goto error; } i = 1; while (i<3 && modeString[i]) { if (modeString[i] == modeString[i-1]) { goto error; } switch (modeString[i++]) { case '+': /* * Remove O_APPEND so that the seek command works. [Bug * 1773127] */ mode &= ~(O_RDONLY|O_WRONLY|O_APPEND); mode |= O_RDWR; break; case 'b': *binaryPtr = 1; break; default: goto error; } } if (modeString[i] != 0) { goto error; } return mode; error: *seekFlagPtr = 0; *binaryPtr = 0; if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "illegal access mode \"%s\"", modeString)); } return -1; } /* * The access modes are specified as a list of POSIX modes like O_CREAT. * * Tcl_SplitList must work correctly when interp is NULL. */ if (Tcl_SplitList(interp, modeString, &modeArgc, &modeArgv) != TCL_OK) { if (interp != NULL) { Tcl_AddErrorInfo(interp, "\n while processing open access modes \""); Tcl_AddErrorInfo(interp, modeString); Tcl_AddErrorInfo(interp, "\""); } return -1; } gotRW = 0; for (i = 0; i < modeArgc; i++) { flag = modeArgv[i]; c = flag[0]; if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) { mode = (mode & ~RW_MODES) | O_RDONLY; gotRW = 1; } else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) { mode = (mode & ~RW_MODES) | O_WRONLY; gotRW = 1; } else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) { mode = (mode & ~RW_MODES) | O_RDWR; gotRW = 1; } else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) { mode |= O_APPEND; *seekFlagPtr = 1; } else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) { mode |= O_CREAT; } else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) { mode |= O_EXCL; } else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) { #ifdef O_NOCTTY mode |= O_NOCTTY; #else if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "access mode \"%s\" not supported by this system", flag)); } Tcl_Free((void *)modeArgv); return -1; #endif } else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) { #ifdef O_NONBLOCK mode |= O_NONBLOCK; #else if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "access mode \"%s\" not supported by this system", flag)); } Tcl_Free((void *)modeArgv); return -1; #endif } else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) { mode |= O_TRUNC; } else if ((c == 'B') && (strcmp(flag, "BINARY") == 0)) { *binaryPtr = 1; } else { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "invalid access mode \"%s\": must be RDONLY, WRONLY, " "RDWR, APPEND, BINARY, CREAT, EXCL, NOCTTY, NONBLOCK," " or TRUNC", flag)); } Tcl_Free((void *)modeArgv); return -1; } } Tcl_Free((void *)modeArgv); if (!gotRW) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "access mode must include either RDONLY, WRONLY, or RDWR", -1)); } return -1; } return mode; } /* *---------------------------------------------------------------------- * * Tcl_FSEvalFile, Tcl_FSEvalFileEx, TclNREvalFile -- * * Reads a file and evaluates it as a script. * * Tcl_FSEvalFile is Tcl_FSEvalFileEx without the encoding argument. * * TclNREvalFile is an NRE-enabled version of Tcl_FSEvalFileEx. * * Results: * A standard Tcl result, which is either the result of executing the * file or an error indicating why the file couldn't be read. * * Side effects: * Arbitrary, depending on the contents of the script. While the script * is evaluated iPtr->scriptFile is a reference to pathPtr, and after the * evaluation completes, has its original value restored again. * *---------------------------------------------------------------------- */ int Tcl_FSEvalFile( Tcl_Interp *interp, /* Interpreter that evaluates the script. */ Tcl_Obj *pathPtr) /* Pathname of file containing the script. * Tilde-substitution is performed on this * pathname. */ { return Tcl_FSEvalFileEx(interp, pathPtr, NULL); } int Tcl_FSEvalFileEx( Tcl_Interp *interp, /* Interpreter that evaluates the script. */ Tcl_Obj *pathPtr, /* Pathname of the file to process. * Tilde-substitution is performed on this * pathname. */ const char *encodingName) /* Either the name of an encoding or NULL to use the utf-8 encoding. */ { size_t length; int result = TCL_ERROR; Tcl_StatBuf statBuf; Tcl_Obj *oldScriptFile; Interp *iPtr; const char *string; Tcl_Channel chan; Tcl_Obj *objPtr; if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) { return result; } if (Tcl_FSStat(pathPtr, &statBuf) == -1) { Tcl_SetErrno(errno); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); return result; } chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644); if (chan == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); return result; } /* * The eof character is \32 (^Z). This is standard on Windows, and Tcl * uses it on every platform to allow for scripted documents. [Bug: 2040] */ Tcl_SetChannelOption(interp, chan, "-eofchar", "\32 {}"); /* * If the encoding is specified, set the channel to that encoding. * Otherwise use utf-8. If the encoding is unknown report an error. */ if (encodingName == NULL) { encodingName = "utf-8"; } if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName) != TCL_OK) { Tcl_CloseEx(interp,chan,0); return result; } TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); /* * Read first character of stream to check for utf-8 BOM */ if (Tcl_ReadChars(chan, objPtr, 1, 0) == TCL_IO_FAILURE) { Tcl_CloseEx(interp, chan, 0); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); goto end; } string = TclGetString(objPtr); /* * If first character is not a BOM, append the remaining characters. * Otherwise, replace them. [Bug 3466099] */ if (Tcl_ReadChars(chan, objPtr, -1, memcmp(string, "\xEF\xBB\xBF", 3)) == TCL_IO_FAILURE) { Tcl_CloseEx(interp, chan, 0); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); goto end; } if (Tcl_CloseEx(interp, chan, 0) != TCL_OK) { goto end; } iPtr = (Interp *) interp; oldScriptFile = iPtr->scriptFile; iPtr->scriptFile = pathPtr; Tcl_IncrRefCount(iPtr->scriptFile); string = TclGetStringFromObj(objPtr, &length); /* * TIP #280: Open a frame for the evaluated script. */ iPtr->evalFlags |= TCL_EVAL_FILE; result = TclEvalEx(interp, string, length, 0, 1, NULL, string); /* * Restore the original iPtr->scriptFile value, but because the value may * have hanged during evaluation, don't assume it currently points to * pathPtr. */ if (iPtr->scriptFile != NULL) { Tcl_DecrRefCount(iPtr->scriptFile); } iPtr->scriptFile = oldScriptFile; if (result == TCL_RETURN) { result = TclUpdateReturnInfo(iPtr); } else if (result == TCL_ERROR) { /* * Record information about where the error occurred. */ const char *pathString = TclGetStringFromObj(pathPtr, &length); unsigned limit = 150; int overflow = (length > limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (file \"%.*s%s\" line %d)", (overflow ? limit : (unsigned)length), pathString, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } end: Tcl_DecrRefCount(objPtr); return result; } int TclNREvalFile( Tcl_Interp *interp, /* Interpreter in which to evaluate the script. */ Tcl_Obj *pathPtr, /* Pathname of a file containing the script to * evaluate. Tilde-substitution is performed on * this pathname. */ const char *encodingName) /* The name of an encoding to use, or NULL to * use the utf-8 encoding. */ { Tcl_StatBuf statBuf; Tcl_Obj *oldScriptFile, *objPtr; Interp *iPtr; Tcl_Channel chan; const char *string; if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) { return TCL_ERROR; } if (Tcl_FSStat(pathPtr, &statBuf) == -1) { Tcl_SetErrno(errno); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); return TCL_ERROR; } chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644); if (chan == NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); return TCL_ERROR; } TclPkgFileSeen(interp, TclGetString(pathPtr)); /* * The eof character is \32 (^Z). This is standard on Windows, and Tcl * uses it on every platform to allow for scripted documents. [Bug: 2040] */ Tcl_SetChannelOption(interp, chan, "-eofchar", "\32 {}"); /* * If the encoding is specified, set the channel to that encoding. * Otherwise use utf-8. If the encoding is unknown report an error. */ if (encodingName == NULL) { encodingName = "utf-8"; } if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName) != TCL_OK) { Tcl_CloseEx(interp, chan, 0); return TCL_ERROR; } TclNewObj(objPtr); Tcl_IncrRefCount(objPtr); /* * Read first character of stream to check for utf-8 BOM */ if (Tcl_ReadChars(chan, objPtr, 1, 0) == TCL_IO_FAILURE) { Tcl_CloseEx(interp, chan, 0); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); Tcl_DecrRefCount(objPtr); return TCL_ERROR; } string = TclGetString(objPtr); /* * If first character is not a BOM, append the remaining characters. * Otherwise, replace them. [Bug 3466099] */ if (Tcl_ReadChars(chan, objPtr, -1, memcmp(string, "\xEF\xBB\xBF", 3)) == TCL_IO_FAILURE) { Tcl_CloseEx(interp, chan, 0); Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't read file \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); Tcl_DecrRefCount(objPtr); return TCL_ERROR; } if (Tcl_CloseEx(interp, chan, 0) != TCL_OK) { Tcl_DecrRefCount(objPtr); return TCL_ERROR; } iPtr = (Interp *) interp; oldScriptFile = iPtr->scriptFile; iPtr->scriptFile = pathPtr; Tcl_IncrRefCount(iPtr->scriptFile); /* * TIP #280: Open a frame for the evaluated script. */ iPtr->evalFlags |= TCL_EVAL_FILE; TclNRAddCallback(interp, EvalFileCallback, oldScriptFile, pathPtr, objPtr, NULL); return TclNREvalObjEx(interp, objPtr, 0, NULL, INT_MIN); } static int EvalFileCallback( ClientData data[], Tcl_Interp *interp, int result) { Interp *iPtr = (Interp *) interp; Tcl_Obj *oldScriptFile = (Tcl_Obj *)data[0]; Tcl_Obj *pathPtr = (Tcl_Obj *)data[1]; Tcl_Obj *objPtr = (Tcl_Obj *)data[2]; /* * Restore the original iPtr->scriptFile value, but because the value may * have hanged during evaluation, don't assume it currently points to * pathPtr. */ if (iPtr->scriptFile != NULL) { Tcl_DecrRefCount(iPtr->scriptFile); } iPtr->scriptFile = oldScriptFile; if (result == TCL_RETURN) { result = TclUpdateReturnInfo(iPtr); } else if (result == TCL_ERROR) { /* * Record information about where the error occurred. */ size_t length; const char *pathString = TclGetStringFromObj(pathPtr, &length); const unsigned int limit = 150; int overflow = (length > limit); Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf( "\n (file \"%.*s%s\" line %d)", (overflow ? limit : (unsigned int)length), pathString, (overflow ? "..." : ""), Tcl_GetErrorLine(interp))); } Tcl_DecrRefCount(objPtr); return result; } /* *---------------------------------------------------------------------- * * Tcl_GetErrno -- * * Currently the global variable "errno", but could in the future change * to something else. * * Results: * The current Tcl error number. * * Side effects: * None. The value of the Tcl error code variable is only defined if it * was set by a previous call to Tcl_SetErrno. * *---------------------------------------------------------------------- */ int Tcl_GetErrno(void) { /* * On some platforms errno is thread-local, as implemented by the C * library. */ return errno; } /* *---------------------------------------------------------------------- * * Tcl_SetErrno -- * * Sets the Tcl error code to the given value. On some saner platforms * this is implemented in the C library as a thread-local value , but this * is *really* unsafe to assume! * * Results: * None. * * Side effects: * Modifies the the Tcl error code value. * *---------------------------------------------------------------------- */ void Tcl_SetErrno( int err) /* The new value. */ { /* * On some platforms, errno is implemented by the C library as a thread * local value */ errno = err; } /* *---------------------------------------------------------------------- * * Tcl_PosixError -- * * Typically called after a UNIX kernel call returns an error. Sets the * interpreter errorCode to machine-parsable information about the error. * * Results: * A human-readable sring describing the error. * * Side effects: * Sets the errorCode value of the interpreter. * *---------------------------------------------------------------------- */ const char * Tcl_PosixError( Tcl_Interp *interp) /* Interpreter to set the errorCode of */ { const char *id, *msg; msg = Tcl_ErrnoMsg(errno); id = Tcl_ErrnoId(); if (interp) { Tcl_SetErrorCode(interp, "POSIX", id, msg, NULL); } return msg; } /* *---------------------------------------------------------------------- * * Tcl_FSStat -- * Calls 'statProc' of the filesystem corresponding to pathPtr. * * Replaces the standard library routines stat. * * * Results: * See stat documentation. * * Side effects: * See stat documentation. * *---------------------------------------------------------------------- */ int Tcl_FSStat( Tcl_Obj *pathPtr, /* Pathname of the file to call stat on (in * current CP). */ Tcl_StatBuf *buf) /* A buffer to hold the results of the call to * stat. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->statProc != NULL) { return fsPtr->statProc(pathPtr, buf); } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSLstat -- * Calls the 'lstatProc' of the filesystem corresponding to pathPtr. * * Replaces the library version of lstat. If the filesystem doesn't * provide lstatProc but does provide statProc, Tcl falls back to * statProc. * * Results: * See lstat documentation. * * Side effects: * See lstat documentation. * *---------------------------------------------------------------------- */ int Tcl_FSLstat( Tcl_Obj *pathPtr, /* Pathname of the file to call stat on (in current CP). */ Tcl_StatBuf *buf) /* Filled with results of that call to stat. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL) { if (fsPtr->lstatProc != NULL) { return fsPtr->lstatProc(pathPtr, buf); } if (fsPtr->statProc != NULL) { return fsPtr->statProc(pathPtr, buf); } } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSAccess -- * * Calls 'accessProc' of the filesystem corresponding to pathPtr. * * Replaces the library version of access. * * Results: * See access documentation. * * Side effects: * See access documentation. * *---------------------------------------------------------------------- */ int Tcl_FSAccess( Tcl_Obj *pathPtr, /* Pathname of file to access (in current CP). */ int mode) /* Permission setting. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->accessProc != NULL) { return fsPtr->accessProc(pathPtr, mode); } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSOpenFileChannel -- * * Calls 'openfileChannelProc' of the filesystem corresponding to * pathPtr. * * Results: * The new channel, or NULL if the named file could not be opened. * * Side effects: * Opens a channel, possibly creating the corresponding the file on the * filesystem. * *---------------------------------------------------------------------- */ Tcl_Channel Tcl_FSOpenFileChannel( Tcl_Interp *interp, /* Interpreter for error reporting, or NULL */ Tcl_Obj *pathPtr, /* Pathname of file to open. */ const char *modeString, /* A list of POSIX open modes or a string such * as "rw". */ int permissions) /* What modes to use if opening the file involves creating it. */ { const Tcl_Filesystem *fsPtr; Tcl_Channel retVal = NULL; if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) { /* * Return the correct error message. */ return NULL; } fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->openFileChannelProc != NULL) { int mode, seekFlag, binary; /* * Parse the mode to determine whether to seek at the outset * and/or set the channel into binary mode. */ mode = TclGetOpenModeEx(interp, modeString, &seekFlag, &binary); if (mode == -1) { return NULL; } /* * Open the file. */ retVal = fsPtr->openFileChannelProc(interp, pathPtr, mode, permissions); if (retVal == NULL) { return NULL; } /* * Seek and/or set binary mode as determined above. */ if (seekFlag && Tcl_Seek(retVal, (Tcl_WideInt) 0, SEEK_END) < (Tcl_WideInt) 0) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "could not seek to end of file while opening \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); } Tcl_CloseEx(NULL, retVal, 0); return NULL; } if (binary) { Tcl_SetChannelOption(interp, retVal, "-translation", "binary"); } return retVal; } /* * File doesn't belong to any filesystem that can open it. */ Tcl_SetErrno(ENOENT); if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't open \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); } return NULL; } /* *---------------------------------------------------------------------- * * Tcl_FSUtime -- * * Calls 'uTimeProc' of the filesystem corresponding to the given * pathname. * * Replaces the library version of utime. * * Results: * See utime documentation. * * Side effects: * See utime documentation. * *---------------------------------------------------------------------- */ int Tcl_FSUtime( Tcl_Obj *pathPtr, /* Pathaname of file to call uTimeProc on */ struct utimbuf *tval) /* Specifies the access/modification * times to use. Should not be modified. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->utimeProc != NULL) { return fsPtr->utimeProc(pathPtr, tval); } /* TODO: set errno here? Tcl_SetErrno(ENOENT); */ return -1; } /* *---------------------------------------------------------------------- * * NativeFileAttrStrings -- * * Implements the platform-dependent 'file attributes' subcommand for the * native filesystem, for listing the set of possible attribute strings. * Part of Tcl's native filesystem support. Placed here because it is used * under both Unix and Windows. * * Results: * An array of strings * * Side effects: * None. * *---------------------------------------------------------------------- */ static const char *const * NativeFileAttrStrings( TCL_UNUSED(Tcl_Obj *), TCL_UNUSED(Tcl_Obj **)) { return tclpFileAttrStrings; } /* *---------------------------------------------------------------------- * * NativeFileAttrsGet -- * * Implements the platform-dependent 'file attributes' subcommand for the * native filesystem for 'get' operations. Part of Tcl's native * filesystem support. Defined here because it is used under both Unix * and Windows. * * Results: * Standard Tcl return code. * * If there was no error, stores in objPtrRef a pointer to a new object * having a refCount of zero and holding the result. The caller should * store it somewhere, e.g. as the Tcl result, or decrement its refCount * to free it. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NativeFileAttrsGet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* index of the attribute command. */ Tcl_Obj *pathPtr, /* Pathname of the file */ Tcl_Obj **objPtrRef) /* Where to store the a pointer to the result. */ { return tclpFileAttrProcs[index].getProc(interp, index, pathPtr,objPtrRef); } /* *---------------------------------------------------------------------- * * NativeFileAttrsSet -- * * Implements the platform-dependent 'file attributes' subcommand for the * native filesystem for 'set' operations. A part of Tcl's native * filesystem support, it is defined here because it is used under both * Unix and Windows. * * Results: * A standard Tcl return code. * * Side effects: * None. * *---------------------------------------------------------------------- */ static int NativeFileAttrsSet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* index of the attribute command. */ Tcl_Obj *pathPtr, /* Pathname of the file */ Tcl_Obj *objPtr) /* The value to set. */ { return tclpFileAttrProcs[index].setProc(interp, index, pathPtr, objPtr); } /* *---------------------------------------------------------------------- * * Tcl_FSFileAttrStrings -- * * Implements part of the hookable 'file attributes' * subcommand. * * Calls 'fileAttrStringsProc' of the filesystem corresponding to the * given pathname. * * Results: * Returns an array of strings, or returns NULL and stores in objPtrRef * a pointer to a new Tcl list having a refCount of zero, and containing * the file attribute strings. * * Side effects: * None. * *---------------------------------------------------------------------- */ const char *const * Tcl_FSFileAttrStrings( Tcl_Obj *pathPtr, Tcl_Obj **objPtrRef) { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->fileAttrStringsProc != NULL) { return fsPtr->fileAttrStringsProc(pathPtr, objPtrRef); } Tcl_SetErrno(ENOENT); return NULL; } /* *---------------------------------------------------------------------- * * TclFSFileAttrIndex -- * * Given an attribute name, determines the index of the attribute in the * attribute table. * * Results: * A standard Tcl result code. * * If there is no error, stores the index in *indexPtr. * * Side effects: * None. * *---------------------------------------------------------------------- */ int TclFSFileAttrIndex( Tcl_Obj *pathPtr, /* Pathname of the file. */ const char *attributeName, /* The name of the attribute. */ int *indexPtr) /* A place to store the result. */ { Tcl_Obj *listObj = NULL; const char *const *attrTable; /* * Get the attribute table for the file. */ attrTable = Tcl_FSFileAttrStrings(pathPtr, &listObj); if (listObj != NULL) { Tcl_IncrRefCount(listObj); } if (attrTable != NULL) { /* * It's a constant attribute table, so use T_GIFO. */ Tcl_Obj *tmpObj = Tcl_NewStringObj(attributeName, -1); int result; result = Tcl_GetIndexFromObj(NULL, tmpObj, attrTable, NULL, TCL_EXACT, indexPtr); TclDecrRefCount(tmpObj); if (listObj != NULL) { TclDecrRefCount(listObj); } return result; } else if (listObj != NULL) { /* * It's a non-constant attribute list, so do a literal search. */ int i, objc; Tcl_Obj **objv; if (Tcl_ListObjGetElements(NULL, listObj, &objc, &objv) != TCL_OK) { TclDecrRefCount(listObj); return TCL_ERROR; } for (i=0 ; ifileAttrsGetProc != NULL) { return fsPtr->fileAttrsGetProc(interp, index, pathPtr, objPtrRef); } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSFileAttrsSet -- * * Implements write access for the hookable 'file * attributes' subcommand. * * Calls 'fileAttrsSetProc' for the filesystem corresponding to the given * pathname. * * Results: * A standard Tcl return code. * * Side effects: * None. * *---------------------------------------------------------------------- */ int Tcl_FSFileAttrsSet( Tcl_Interp *interp, /* The interpreter for error reporting. */ int index, /* The index of the attribute command. */ Tcl_Obj *pathPtr, /* The pathname of the file. */ Tcl_Obj *objPtr) /* A place to store the result. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->fileAttrsSetProc != NULL) { return fsPtr->fileAttrsSetProc(interp, index, pathPtr, objPtr); } Tcl_SetErrno(ENOENT); return -1; } /* *---------------------------------------------------------------------- * * Tcl_FSGetCwd -- * * Replaces the library version of getcwd(). * * Most virtual filesystems do not implement cwdProc. Tcl maintains its * own record of the current directory which it keeps synchronized with * the filesystem corresponding to the pathname of the current directory * if the filesystem provides a cwdProc (the native filesystem does). * * If Tcl's current directory is not in the native filesystem, Tcl's * current directory and the current directory of the process are * different. To avoid confusion, extensions should call Tcl_FSGetCwd to * obtain the current directory from Tcl rather than from the operating * system. * * Results: * Returns a pointer to a Tcl_Obj having a refCount of 1 and containing * the current thread's local copy of the global cwdPathPtr value. * * Returns NULL if the current directory could not be determined, and * leaves an error message in the interpreter's result. * * Side effects: * Various objects may be freed and allocated. * *---------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSGetCwd( Tcl_Interp *interp) { ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); if (TclFSCwdPointerEquals(NULL)) { FilesystemRecord *fsRecPtr; Tcl_Obj *retVal = NULL; /* * This is the first time this routine has been called. Call * 'getCwdProc' for each registered filsystems until one returns * something other than NULL, which is a pointer to the pathname of the * current directory. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); for (; (retVal == NULL) && (fsRecPtr != NULL); fsRecPtr = fsRecPtr->nextPtr) { ClientData retCd; TclFSGetCwdProc2 *proc2; if (fsRecPtr->fsPtr->getCwdProc == NULL) { continue; } if (fsRecPtr->fsPtr->version == TCL_FILESYSTEM_VERSION_1) { retVal = fsRecPtr->fsPtr->getCwdProc(interp); continue; } proc2 = (TclFSGetCwdProc2 *) fsRecPtr->fsPtr->getCwdProc; retCd = proc2(NULL); if (retCd != NULL) { Tcl_Obj *norm; /* * Found the pathname of the current directory. */ retVal = fsRecPtr->fsPtr->internalToNormalizedProc(retCd); Tcl_IncrRefCount(retVal); norm = TclFSNormalizeAbsolutePath(interp,retVal); if (norm != NULL) { /* * Assign to global storage the pathname of the current directory * and copy it into thread-local storage as well. * * At system startup multiple threads could in principle * call this function simultaneously, which is a little * peculiar, but should be fine given the mutex locks in * FSUPdateCWD. Once some value is assigned to the global * variable the 'else' branch below is always taken, which * is simpler. */ FsUpdateCwd(norm, retCd); Tcl_DecrRefCount(norm); } else { fsRecPtr->fsPtr->freeInternalRepProc(retCd); } Tcl_DecrRefCount(retVal); retVal = NULL; Disclaim(); goto cdDidNotChange; } else if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error getting working directory name: %s", Tcl_PosixError(interp))); } } Disclaim(); if (retVal != NULL) { /* * On some platforms the pathname of the current directory might * not be normalized. For efficiency, ensure that it is * normalized. For the sake of efficiency, we want a completely * normalized current working directory at all times. */ Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal); if (norm != NULL) { /* * We found a current working directory, which is now in our * global storage. We must make a copy. Norm already has a * refCount of 1. * * Threading issue: Multiple threads at system startup could in * principle call this function simultaneously. They will * therefore each set the cwdPathPtr independently, which is a * bit peculiar, but should be fine. Once we have a cwd, we'll * always be in the 'else' branch below which is simpler. */ void *cd = (void *) Tcl_FSGetNativePath(norm); FsUpdateCwd(norm, TclNativeDupInternalRep(cd)); Tcl_DecrRefCount(norm); } Tcl_DecrRefCount(retVal); } else { /* * retVal is NULL. There is no current directory, which could be * problematic. */ } } else { /* * There is a thread-local value for the pathname of the current * directory. Give corresponding filesystem a chance update the value * if it is out-of-date. This allows an error to be thrown if, for * example, the permissions on the current working directory have * changed. */ const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr); ClientData retCd = NULL; Tcl_Obj *retVal, *norm; if (fsPtr == NULL || fsPtr->getCwdProc == NULL) { /* * There is no corresponding filesystem or the filesystem does not * have a getCwd routine. Just assume current local value is ok. */ goto cdDidNotChange; } if (fsPtr->version == TCL_FILESYSTEM_VERSION_1) { retVal = fsPtr->getCwdProc(interp); } else { /* * New API. */ TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc; retCd = proc2(tsdPtr->cwdClientData); if (retCd == NULL && interp != NULL) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "error getting working directory name: %s", Tcl_PosixError(interp))); } if (retCd == tsdPtr->cwdClientData) { goto cdDidNotChange; } /* * Looks like a new current directory. */ retVal = fsPtr->internalToNormalizedProc(retCd); Tcl_IncrRefCount(retVal); } if (retVal == NULL) { /* * The current directory could not not determined. Reset the * current direcory to ensure, for example, that 'pwd' does actually * throw the correct error in Tcl. This is tested for in the test * suite on unix. */ FsUpdateCwd(NULL, NULL); goto cdDidNotChange; } norm = TclFSNormalizeAbsolutePath(interp, retVal); if (norm == NULL) { /* * 'norm' shouldn't ever be NULL, but we are careful. */ /* Do nothing */ if (retCd != NULL) { fsPtr->freeInternalRepProc(retCd); } } else if (norm == tsdPtr->cwdPathPtr) { goto cdEqual; } else { /* * Determine whether the filesystem's answer is the same as the * cached local value. Since both 'norm' and 'tsdPtr->cwdPathPtr' * are normalized pathnames, do something more efficient than * calling 'Tcl_FSEqualPaths', and in addition avoid a nasty * infinite loop bug when trying to normalize tsdPtr->cwdPathPtr. */ size_t len1, len2; const char *str1, *str2; str1 = TclGetStringFromObj(tsdPtr->cwdPathPtr, &len1); str2 = TclGetStringFromObj(norm, &len2); if ((len1 == len2) && (strcmp(str1, str2) == 0)) { /* * The pathname values are equal so retain the old pathname * object which is probably already shared and free the * normalized pathname that was just produced. */ cdEqual: Tcl_DecrRefCount(norm); if (retCd != NULL) { fsPtr->freeInternalRepProc(retCd); } } else { /* * The pathname of the current directory is not the same as * this thread's local cached value. Replace the local value. */ FsUpdateCwd(norm, retCd); Tcl_DecrRefCount(norm); } } Tcl_DecrRefCount(retVal); } cdDidNotChange: if (tsdPtr->cwdPathPtr != NULL) { Tcl_IncrRefCount(tsdPtr->cwdPathPtr); } return tsdPtr->cwdPathPtr; } /* *---------------------------------------------------------------------- * * Tcl_FSChdir -- * * Replaces the library version of chdir(). * * Calls 'chdirProc' of the filesystem that corresponds to the given * pathname. * * Results: * See chdir() documentation. * * Side effects: * See chdir() documentation. * * On success stores in cwdPathPtr the pathname of the new current * directory. * *---------------------------------------------------------------------- */ int Tcl_FSChdir( Tcl_Obj *pathPtr) { const Tcl_Filesystem *fsPtr, *oldFsPtr = NULL; ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey); int retVal = -1; if (tsdPtr->cwdPathPtr != NULL) { oldFsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr); } if (Tcl_FSGetNormalizedPath(NULL, pathPtr) == NULL) { Tcl_SetErrno(ENOENT); return retVal; } fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL) { if (fsPtr->chdirProc != NULL) { /* * If this fails Tcl_SetErrno() has already been called. */ retVal = fsPtr->chdirProc(pathPtr); } else { /* * Fallback to stat-based implementation. */ Tcl_StatBuf buf; if ((Tcl_FSStat(pathPtr, &buf) == 0) && (S_ISDIR(buf.st_mode)) && (Tcl_FSAccess(pathPtr, R_OK) == 0)) { /* * stat was successful, and the file is a directory and is * readable. Can proceed to change the current directory. */ retVal = 0; } else { /* * 'Tcl_SetErrno()' has already been called. */ } } } else { Tcl_SetErrno(ENOENT); } if (retVal == 0) { /* Assume that the cwd was actually changed to the normalized value * just calculated, and cache that information. */ /* * If the filesystem epoch changed recently, the normalized pathname or * its internal handle may be different from what was found above. * This can easily be the case with scripted documents . Therefore get * the normalized pathname again. The correct value will have been * cached as a result of the Tcl_FSGetFileSystemForPath call, above. */ Tcl_Obj *normDirName = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (normDirName == NULL) { /* Not really true, but what else to do? */ Tcl_SetErrno(ENOENT); return -1; } if (fsPtr == &tclNativeFilesystem) { ClientData cd; ClientData oldcd = tsdPtr->cwdClientData; /* * Assume that the native filesystem has a getCwdProc and that it * is at version 2. */ TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc; cd = proc2(oldcd); if (cd != oldcd) { /* * Call getCwdProc() and store the resulting internal handle to * compare things with it later. This might might not be * exactly the same string as that of the fully normalized * pathname. For example, for the Windows internal handle the * separator is the backslash character. On Unix it might well * be true that the internal handle is the fully normalized * pathname and one could simply use: * cd = Tcl_FSGetNativePath(pathPtr); * but this can't be guaranteed in the general case. In fact, * the internal handle could be any value the filesystem * decides to use to identify a node. */ FsUpdateCwd(normDirName, cd); } } else { /* * Tcl_FSGetCwd() synchronizes the file-global cwdPathPtr if * needed. However, if there is no 'getCwdProc', cwdPathPtr must be * updated right now because there won't be another chance. This * block of code is currently executed whether or not the * filesystem provides a getCwdProc, but it should in principle * work to only call this block if fsPtr->getCwdProc == NULL. */ FsUpdateCwd(normDirName, NULL); } if (oldFsPtr != NULL && fsPtr != oldFsPtr) { /* * The filesystem of the current directory is not the same as the * filesystem of the previous current directory. Invalidate All * FsPath objects. */ Tcl_FSMountsChanged(NULL); } } else { /* * The current directory is now changed or an error occurred and an * error message is now set. Just continue. */ } return retVal; } /* *---------------------------------------------------------------------- * * Tcl_FSLoadFile -- * * Loads a dynamic shared object by passing the given pathname unmodified * to Tcl_LoadFile, and provides pointers to the functions named by 'sym1' * and 'sym2', and another pointer to a function that unloads the object. * * Results: * A standard Tcl completion code. If an error occurs, sets the * interpreter's result to an error message. * * Side effects: * A dynamic shared object is loaded into memory. This may later be * unloaded by passing the handlePtr to *unloadProcPtr. * *---------------------------------------------------------------------- */ int Tcl_FSLoadFile( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Pathname of the file containing the dynamic shared object. */ const char *sym1, const char *sym2, /* Names of two functions to find in the * dynamic shared object. */ Tcl_PackageInitProc **proc1Ptr, Tcl_PackageInitProc **proc2Ptr, /* Places to store pointers to the functions * named by sym1 and sym2. */ Tcl_LoadHandle *handlePtr, /* A place to store the token for the loaded * object. Can be passed to * (*unloadProcPtr)() to unload the file. */ TCL_UNUSED(Tcl_FSUnloadFileProc **)) { const char *symbols[3]; void *procPtrs[2]; int res; symbols[0] = sym1; symbols[1] = sym2; symbols[2] = NULL; res = Tcl_LoadFile(interp, pathPtr, symbols, 0, procPtrs, handlePtr); if (res == TCL_OK) { *proc1Ptr = (Tcl_PackageInitProc *) procPtrs[0]; *proc2Ptr = (Tcl_PackageInitProc *) procPtrs[1]; } else { *proc1Ptr = *proc2Ptr = NULL; } return res; } /* *---------------------------------------------------------------------- * * Tcl_LoadFile -- * * Load a dynamic shared object by calling 'loadFileProc' of the * filesystem corresponding to the given pathname, and then finds within * the loaded object the functions named in symbols[]. * * The given pathname is passed unmodified to `loadFileProc`, which * decides how to resolve it. On POSIX systems the native filesystem * passes the given pathname to dlopen(), which resolves the filename * according to its own set of rules. This behaviour is not very * compatible with virtual filesystems, and has other problems as * documented for [load], so it is recommended to use an absolute * pathname. * * Results: * A standard Tcl completion code. If an error occurs, sets the * interpreter result to an error message. * * Side effects: * Memory is allocated for the new object. May be freed by calling * TclFS_UnloadFile. * *---------------------------------------------------------------------- */ /* * Modern HPUX allows the unlink (no ETXTBSY error) yet somehow trashes some * internal data structures, preventing any additional dynamic shared objects * from getting properly loaded. Only the first is ok. Work around the issue * by not unlinking, i.e., emulating the behaviour of the older HPUX which * denied removal. * * Doing the unlink is also an issue within docker containers, whose AUFS * bungles this as well, see * https://github.com/dotcloud/docker/issues/1911 * */ static int skipUnlink( Tcl_Obj *shlibFile) { /* * Unlinking is not performed in the following cases: * * 1. The operating system is HPUX. * * 2. If the environment variable TCL_TEMPLOAD_NO_UNLINK is present and * set to true (an integer > 0) * * 3. TCL_TEMPLOAD_NO_UNLINK is not true (an integer > 0) and AUFS filesystem can be detected (using statfs, if available). * */ #ifdef hpux (void)shlibFile; return 1; #else char *skipstr = getenv("TCL_TEMPLOAD_NO_UNLINK"); if (skipstr && (skipstr[0] != '\0')) { return atoi(skipstr); } #ifndef TCL_TEMPLOAD_NO_UNLINK (void)shlibFile; #else /* At built time TCL_TEMPLOAD_NO_UNLINK can be set manually to control whether * this automatic overriding of unlink is included. */ #ifndef NO_FSTATFS { struct statfs fs; /* * Have fstatfs. May not have the AUFS super magic ... Indeed our build * box is too old to have it directly in the headers. Define taken from * http://mooon.googlecode.com/svn/trunk/linux_include/linux/aufs_type.h * http://aufs.sourceforge.net/ * Better reference will be gladly accepted. */ #ifndef AUFS_SUPER_MAGIC /* AUFS_SUPER_MAGIC can disable/override the AUFS detection, i.e. for * testing if a newer AUFS does not have the bug any more. */ #define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's') #endif /* AUFS_SUPER_MAGIC */ if ((statfs(TclGetString(shlibFile), &fs) == 0) && (fs.f_type == AUFS_SUPER_MAGIC)) { return 1; } } #endif /* ... NO_FSTATFS */ #endif /* ... TCL_TEMPLOAD_NO_UNLINK */ /* * No HPUX, environment variable override, or AUFS detected. Perform * unlink. */ return 0; #endif /* hpux */ } int Tcl_LoadFile( Tcl_Interp *interp, /* Used for error reporting. */ Tcl_Obj *pathPtr, /* Pathname of the file containing the dynamic * shared object. */ const char *const symbols[],/* A null-terminated array of names of * functions to find in the loaded object. */ int flags, /* Flags */ void *procVPtrs, /* A place to store pointers to the functions * named by symbols[]. */ Tcl_LoadHandle *handlePtr) /* A place to hold a token for the loaded object. * Can be used by TclpFindSymbol. */ { void **procPtrs = (void **) procVPtrs; const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); const Tcl_Filesystem *copyFsPtr; Tcl_FSUnloadFileProc *unloadProcPtr; Tcl_Obj *copyToPtr; Tcl_LoadHandle newLoadHandle = NULL; Tcl_LoadHandle divertedLoadHandle = NULL; Tcl_FSUnloadFileProc *newUnloadProcPtr = NULL; FsDivertLoad *tvdlPtr; int retVal; int i; if (fsPtr == NULL) { Tcl_SetErrno(ENOENT); return TCL_ERROR; } if (fsPtr->loadFileProc != NULL) { retVal = ((Tcl_FSLoadFileProc2 *)(void *)(fsPtr->loadFileProc)) (interp, pathPtr, handlePtr, &unloadProcPtr, flags); if (retVal == TCL_OK) { if (*handlePtr == NULL) { return TCL_ERROR; } if (interp) { Tcl_ResetResult(interp); } goto resolveSymbols; } if (Tcl_GetErrno() != EXDEV) { return retVal; } } /* * The filesystem doesn't support 'load'. Fall to the following: */ /* * Make sure the file is accessible. */ if (Tcl_FSAccess(pathPtr, R_OK) != 0) { if (interp) { Tcl_SetObjResult(interp, Tcl_ObjPrintf( "couldn't load library \"%s\": %s", TclGetString(pathPtr), Tcl_PosixError(interp))); } return TCL_ERROR; } #ifdef TCL_LOAD_FROM_MEMORY /* * The platform supports loading a dynamic shared object from memory. * Create a sufficiently large buffer, read the file into it, and then load * the dynamic shared object from the buffer: */ { int ret, size; void *buffer; Tcl_StatBuf statBuf; Tcl_Channel data; ret = Tcl_FSStat(pathPtr, &statBuf); if (ret < 0) { goto mustCopyToTempAnyway; } size = (int) statBuf.st_size; /* * Tcl_Read takes an int: Determine whether the file size is wide. */ if (size != (Tcl_WideInt) statBuf.st_size) { goto mustCopyToTempAnyway; } data = Tcl_FSOpenFileChannel(interp, pathPtr, "rb", 0666); if (!data) { goto mustCopyToTempAnyway; } buffer = TclpLoadMemoryGetBuffer(interp, size); if (!buffer) { Tcl_CloseEx(interp, data, 0); goto mustCopyToTempAnyway; } ret = Tcl_Read(data, (char *)buffer, size); Tcl_CloseEx(interp, data, 0); ret = TclpLoadMemory(interp, buffer, size, ret, handlePtr, &unloadProcPtr, flags); if (ret == TCL_OK && *handlePtr != NULL) { goto resolveSymbols; } } mustCopyToTempAnyway: if (interp) { Tcl_ResetResult(interp); } #endif /* TCL_LOAD_FROM_MEMORY */ /* * Get a temporary filename, first to copy the file into, and then to load. */ copyToPtr = TclpTempFileNameForLibrary(interp, pathPtr); if (copyToPtr == NULL) { return TCL_ERROR; } Tcl_IncrRefCount(copyToPtr); copyFsPtr = Tcl_FSGetFileSystemForPath(copyToPtr); if ((copyFsPtr == NULL) || (copyFsPtr == fsPtr)) { /* * Tcl_FSLoadFile isn't available for the filesystem of the temporary * file. In order to avoid a possible infinite loop, do not attempt to * load further. */ /* * Try to delete the file we probably created and then exit. */ Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); if (interp) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "couldn't load from current filesystem", -1)); } return TCL_ERROR; } if (TclCrossFilesystemCopy(interp, pathPtr, copyToPtr) != TCL_OK) { Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); return TCL_ERROR; } #ifndef _WIN32 /* * It might be necessary on some systems to set the appropriate permissions * on the file. On Unix we could loop over the file attributes and set any * that are called "-permissions" to 0700, but just do it directly instead: */ { int index; Tcl_Obj *perm; TclNewLiteralStringObj(perm, "0700"); Tcl_IncrRefCount(perm); if (TclFSFileAttrIndex(copyToPtr, "-permissions", &index) == TCL_OK) { Tcl_FSFileAttrsSet(NULL, index, copyToPtr, perm); } Tcl_DecrRefCount(perm); } #endif /* * The cross-filesystem copy may have stored the number of bytes in the * result, so reset the result now. */ if (interp) { Tcl_ResetResult(interp); } retVal = Tcl_LoadFile(interp, copyToPtr, symbols, flags, procPtrs, &newLoadHandle); if (retVal != TCL_OK) { Tcl_FSDeleteFile(copyToPtr); Tcl_DecrRefCount(copyToPtr); return retVal; } /* * Try to delete the file immediately. Some operatings systems allow this, * and it avoids leaving the copy laying around after exit. */ if (!skipUnlink(copyToPtr) && (Tcl_FSDeleteFile(copyToPtr) == TCL_OK)) { Tcl_DecrRefCount(copyToPtr); /* * Tell the caller all the details: The package list maintained by * 'load' stores the original (vfs) pathname, the handle of object * loaded from the temporary file, and the unloadProcPtr. */ *handlePtr = newLoadHandle; if (interp) { Tcl_ResetResult(interp); } return TCL_OK; } /* * Divert the unloading in order to unload and cleanup the temporary file. */ tvdlPtr = (FsDivertLoad *)Tcl_Alloc(sizeof(FsDivertLoad)); /* * Remember three pieces of information in order to clean up the diverted * load completely on platforms which allow proper unloading of code. */ tvdlPtr->loadHandle = newLoadHandle; tvdlPtr->unloadProcPtr = newUnloadProcPtr; if (copyFsPtr != &tclNativeFilesystem) { /* refCount of copyToPtr is already incremented. */ tvdlPtr->divertedFile = copyToPtr; /* * This is the filesystem for the temporary file the object was loaded * from. A reference to copyToPtr is already stored in * tvdlPtr->divertedFile, so need need to increment the refCount again. */ tvdlPtr->divertedFilesystem = copyFsPtr; tvdlPtr->divertedFileNativeRep = NULL; } else { /* * Grab the native representation. */ tvdlPtr->divertedFileNativeRep = TclNativeDupInternalRep( Tcl_FSGetInternalRep(copyToPtr, copyFsPtr)); /* * Don't keeep a reference to the Tcl_Obj or the native filesystem. */ tvdlPtr->divertedFile = NULL; tvdlPtr->divertedFilesystem = NULL; Tcl_DecrRefCount(copyToPtr); } copyToPtr = NULL; divertedLoadHandle = (Tcl_LoadHandle)Tcl_Alloc(sizeof(struct Tcl_LoadHandle_)); divertedLoadHandle->clientData = tvdlPtr; divertedLoadHandle->findSymbolProcPtr = DivertFindSymbol; divertedLoadHandle->unloadFileProcPtr = DivertUnloadFile; *handlePtr = divertedLoadHandle; if (interp) { Tcl_ResetResult(interp); } return retVal; resolveSymbols: /* * handlePtr now contains a token for the loaded object. * Resolve the symbols. */ if (symbols != NULL) { for (i=0 ; symbols[i] != NULL; i++) { procPtrs[i] = Tcl_FindSymbol(interp, *handlePtr, symbols[i]); if (procPtrs[i] == NULL) { /* * At least one symbol in the list was not found. Unload the * file and return an error code. Tcl_FindSymbol should have * already left an appropriate error message. */ (*handlePtr)->unloadFileProcPtr(*handlePtr); *handlePtr = NULL; return TCL_ERROR; } } } return TCL_OK; } /* *---------------------------------------------------------------------- * * DivertFindSymbol -- * * Find a symbol in a shared library loaded by making a copying a file * from the virtual filesystem to a native filesystem. * *---------------------------------------------------------------------- */ static void * DivertFindSymbol( Tcl_Interp *interp, /* The relevant interpreter. */ Tcl_LoadHandle loadHandle, /* A handle to the diverted module. */ const char *symbol) /* The name of symbol to resolve. */ { FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData; Tcl_LoadHandle originalHandle = tvdlPtr->loadHandle; return originalHandle->findSymbolProcPtr(interp, originalHandle, symbol); } /* *---------------------------------------------------------------------- * * DivertUnloadFile -- * * Unloads an object that was loaded from a temporary file copied from the * virtual filesystem the native filesystem. * *---------------------------------------------------------------------- */ static void DivertUnloadFile( Tcl_LoadHandle loadHandle) /* A handle for the loaded object. */ { FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData; Tcl_LoadHandle originalHandle; if (tvdlPtr == NULL) { /* * tvdlPtr was provided by Tcl_LoadFile so it should not be NULL here. */ return; } originalHandle = tvdlPtr->loadHandle; /* * Call the real 'unloadfile' proc. This must be called first so that the * shared library is actually unloaded by the OS. Otherwise, the following * 'delete' may fail because the shared library is still in use. */ originalHandle->unloadFileProcPtr(originalHandle); /* * Determine which filesystem contains the temporary copy of the file. */ if (tvdlPtr->divertedFilesystem == NULL) { /* * Use the function for the native filsystem, which works works even at * this late stage. */ TclpDeleteFile(tvdlPtr->divertedFileNativeRep); NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep); } else { /* * Remove the temporary file. If encodings have been cleaned up * already, this may crash. */ if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile) != TCL_OK) { /* * This may have happened because Tcl is exiting, and encodings may * have already been deleted or something else the filesystem * depends on may be gone. * * TO DO: Figure out how to delete this file more robustly, or * give the filesystem the information it needs to delete the file * more robustly. One problem might be that the filesystem cannot * extract the information it needs from the above pathname object * because Tcl's entire filesystem apparatus (the code in this * file) has been finalized and there is no way to get the native * handle of the file. */ } /* * This also decrements the refCount of the Tcl_Filesystem * corresponding to this file. which might cause the filesystem to be * deallocated if Tcl is exiting. */ Tcl_DecrRefCount(tvdlPtr->divertedFile); } Tcl_Free(tvdlPtr); Tcl_Free(loadHandle); } /* *---------------------------------------------------------------------- * * Tcl_FindSymbol -- * * Find a symbol in a loaded object. * * Previously filesystem-specific, but has been made portable by having * TclpDlopen return a structure that includes procedure pointers. * * Results: * Returns a pointer to the symbol if found. Otherwise, sets * an error message in the interpreter result and returns NULL. * *---------------------------------------------------------------------- */ void * Tcl_FindSymbol( Tcl_Interp *interp, /* The relevant interpreter. */ Tcl_LoadHandle loadHandle, /* A handle for the loaded object. */ const char *symbol) /* The name name of the symbol to resolve. */ { return loadHandle->findSymbolProcPtr(interp, loadHandle, symbol); } /* *---------------------------------------------------------------------- * * Tcl_FSUnloadFile -- * * Unloads a loaded object if unloading is supported for the object. * *---------------------------------------------------------------------- */ int Tcl_FSUnloadFile( Tcl_Interp *interp, /* The relevant interpreter. */ Tcl_LoadHandle handle) /* A handle for the object to unload. */ { if (handle->unloadFileProcPtr == NULL) { if (interp != NULL) { Tcl_SetObjResult(interp, Tcl_NewStringObj( "cannot unload: filesystem does not support unloading", -1)); } return TCL_ERROR; } if (handle->unloadFileProcPtr != NULL) { handle->unloadFileProcPtr(handle); } return TCL_OK; } /* *---------------------------------------------------------------------- * * TclFSUnloadTempFile -- * * Unloads an object loaded via temporary file from a virtual filesystem * to a native filesystem. * * Results: * None. * * Side effects: * Frees resources for the loaded object and deletes the temporary file. * *---------------------------------------------------------------------- */ void TclFSUnloadTempFile( Tcl_LoadHandle loadHandle) /* A handle for the object, as provided by a * previous call to Tcl_FSLoadFile(). */ { FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle; if (tvdlPtr == NULL) { /* * tvdlPtr was provided by Tcl_LoadFile so it should not be NULL here. */ return; } if (tvdlPtr->unloadProcPtr != NULL) { /* * 'unloadProcPtr' must be called first so that the shared library is * actually unloaded by the OS. Otherwise, the following 'delete' may * well fail because the shared library is still in use. */ tvdlPtr->unloadProcPtr(tvdlPtr->loadHandle); } if (tvdlPtr->divertedFilesystem == NULL) { /* * Call the function for the native fileystem, which works even at this * late stage. */ TclpDeleteFile(tvdlPtr->divertedFileNativeRep); NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep); } else { /* * Remove the temporary file that was created. If encodings have * already been freed because the interpreter is exiting this may * crash. */ if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile) != TCL_OK) { /* * This may have happened because Tcl is exiting and encodings may * have already been deleted, or something else the filesystem * depends on may be gone. * * TO DO: Figure out how to delete this file more robustly, or * give the filesystem the information it needs to delete the file * more robustly. One problem might be that the filesystem cannot * extract the information it needs from the above pathname object * because Tcl's entire filesystem apparatus (the code in this * file) has been finalized and there is no way to get the native * handle of the file. */ } /* * This also decrements the refCount of the Tcl_Filesystem * corresponding to this file. which might case filesystem to be freed * if Tcl is exiting. */ Tcl_DecrRefCount(tvdlPtr->divertedFile); } Tcl_Free(tvdlPtr); } /* *--------------------------------------------------------------------------- * * Tcl_FSLink -- * * Creates or inspects a link by calling 'linkProc' of the filesystem * corresponding to the given pathname. Replaces the library version of * readlink(). * * Results: * If toPtr is NULL, a Tcl_Obj containing the value the symbolic link for * 'pathPtr', or NULL if a symbolic link was not accessible. The caller * should Tcl_DecrRefCount on the result to release it. Otherwise NULL. * * In this case the result has no additional reference count and need not * be freed. The actual action to perform is given by the 'linkAction' * flags, which is a combination of: * * TCL_CREATE_SYMBOLIC_LINK * TCL_CREATE_HARD_LINK * * Most filesystems do not support linking across to different * filesystems, so this function usually fails if the filesystem * corresponding to toPtr is not the same as the filesystem corresponding * to pathPtr. * * Side effects: * Creates or sets a link if toPtr is not NULL. * * See readlink(). * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSLink( Tcl_Obj *pathPtr, /* Pathaname of file. */ Tcl_Obj *toPtr, /* * NULL or the pathname of a file to link to. */ int linkAction) /* Action to perform. */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->linkProc != NULL) { return fsPtr->linkProc(pathPtr, toPtr, linkAction); } /* * If S_IFLNK isn't defined the machine doesn't support symbolic links, so * the file can't possibly be a symbolic link. Generate an EINVAL error, * which is what happens on machines that do support symbolic links when * readlink is called for a file that isn't a symbolic link. */ #ifndef S_IFLNK errno = EINVAL; /* TODO: Change to Tcl_SetErrno()? */ #else Tcl_SetErrno(ENOENT); #endif /* S_IFLNK */ return NULL; } /* *--------------------------------------------------------------------------- * * Tcl_FSListVolumes -- * * Lists the currently mounted volumes by calling `listVolumesProc` of * each registered filesystem, and combining the results to form a list of * volumes. * * Results: * The list of volumes, in an object which has refCount 0. * * Side effects: * None * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSListVolumes(void) { FilesystemRecord *fsRecPtr; Tcl_Obj *resultPtr; /* * Call each "listVolumes" function of each registered filesystem in * succession. A non-NULL return value indicates the particular function * has succeeded. */ TclNewObj(resultPtr); fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { if (fsRecPtr->fsPtr->listVolumesProc != NULL) { Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc(); if (thisFsVolumes != NULL) { Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes); /* The refCount of each list returned by a `listVolumesProc` is * already incremented. Do not hang onto the list, though. It * belongs to the filesystem. Add its contents to * the result * we are building, and then decrement the refCount. */ Tcl_DecrRefCount(thisFsVolumes); } } fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); return resultPtr; } /* *--------------------------------------------------------------------------- * * FsListMounts -- * * Lists the mounts mathing the given pattern in the given directory. * * Results: * A list, having a refCount of 0, of the matching mounts, or NULL if no * search was performed because no filesystem provided a search routine. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static Tcl_Obj * FsListMounts( Tcl_Obj *pathPtr, /* Pathname of directory to search. */ const char *pattern) /* Pattern to match against. */ { FilesystemRecord *fsRecPtr; Tcl_GlobTypeData mountsOnly = { TCL_GLOB_TYPE_MOUNT, 0, NULL, NULL }; Tcl_Obj *resultPtr = NULL; /* * Call the matchInDirectory function of each registered filesystem, * passing it 'mountsOnly'. Results accumulate in resultPtr. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { if (fsRecPtr->fsPtr != &tclNativeFilesystem && fsRecPtr->fsPtr->matchInDirectoryProc != NULL) { if (resultPtr == NULL) { TclNewObj(resultPtr); } fsRecPtr->fsPtr->matchInDirectoryProc(NULL, resultPtr, pathPtr, pattern, &mountsOnly); } fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); return resultPtr; } /* *--------------------------------------------------------------------------- * * Tcl_FSSplitPath -- * * Splits a pathname into its components. * * Results: * A list with refCount of zero. * * Side effects: * If lenPtr is not null, sets it to the number of elements in the result. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSSplitPath( Tcl_Obj *pathPtr, /* The pathname to split. */ int *lenPtr) /* A place to hold the number of pathname * elements. */ { Tcl_Obj *result = NULL; /* Just to squelch gcc warnings. */ const Tcl_Filesystem *fsPtr; char separator = '/'; int driveNameLength; const char *p; /* * Perform platform-specific splitting. */ if (TclFSGetPathType(pathPtr, &fsPtr, &driveNameLength) == TCL_PATH_ABSOLUTE) { if (fsPtr == &tclNativeFilesystem) { return TclpNativeSplitPath(pathPtr, lenPtr); } } else { return TclpNativeSplitPath(pathPtr, lenPtr); } /* Assume each separator is a single character. */ if (fsPtr->filesystemSeparatorProc != NULL) { Tcl_Obj *sep = fsPtr->filesystemSeparatorProc(pathPtr); if (sep != NULL) { Tcl_IncrRefCount(sep); separator = TclGetString(sep)[0]; Tcl_DecrRefCount(sep); } } /* * Add the drive name as first element of the result. The drive name may * contain strange characters like colons and sequences of forward slashes * For example, 'ftp://' is a valid drive name. */ TclNewObj(result); p = TclGetString(pathPtr); Tcl_ListObjAppendElement(NULL, result, Tcl_NewStringObj(p, driveNameLength)); p += driveNameLength; /* * Add the remaining pathname elements to the list. */ for (;;) { const char *elementStart = p; int length; while ((*p != '\0') && (*p != separator)) { p++; } length = p - elementStart; if (length > 0) { Tcl_Obj *nextElt; if (elementStart[0] == '~') { TclNewLiteralStringObj(nextElt, "./"); Tcl_AppendToObj(nextElt, elementStart, length); } else { nextElt = Tcl_NewStringObj(elementStart, length); } Tcl_ListObjAppendElement(NULL, result, nextElt); } if (*p++ == '\0') { break; } } if (lenPtr != NULL) { TclListObjLength(NULL, result, lenPtr); } return result; } /* *---------------------------------------------------------------------- * * TclGetPathType -- * * Helper function used by TclFSGetPathType and TclJoinPath. * * Results: * One of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or * TCL_PATH_VOLUME_RELATIVE. * * Side effects: * See **filesystemPtrptr, *driveNameLengthPtr and **driveNameRef, * *---------------------------------------------------------------------- */ Tcl_PathType TclGetPathType( Tcl_Obj *pathPtr, /* Pathname to determine type of. */ const Tcl_Filesystem **filesystemPtrPtr, /* If not NULL, a place in which to store a * pointer to the filesystem for this pathname * if it is absolute. */ int *driveNameLengthPtr, /* If not NULL, a place in which to store the * length of the volume name. */ Tcl_Obj **driveNameRef) /* If not NULL, for an absolute pathname, a * place to store a pointer to an object with a * refCount of 1, and whose value is the name * of the volume. */ { size_t pathLen; const char *path = TclGetStringFromObj(pathPtr, &pathLen); Tcl_PathType type; type = TclFSNonnativePathType(path, pathLen, filesystemPtrPtr, driveNameLengthPtr, driveNameRef); if (type != TCL_PATH_ABSOLUTE) { type = TclpGetNativePathType(pathPtr, driveNameLengthPtr, driveNameRef); if ((type == TCL_PATH_ABSOLUTE) && (filesystemPtrPtr != NULL)) { *filesystemPtrPtr = &tclNativeFilesystem; } } return type; } /* *---------------------------------------------------------------------- * * TclFSNonnativePathType -- * * Helper function used by TclGetPathType. Checks whether the given * pathname starts with a string which corresponds to a file volume in * some registered filesystem other than the native one. For speed and * historical reasons the native filesystem has special hard-coded checks * dotted here and there in the filesystem code. * * Results: * One of TCL_PATH_ABSOLUTE or TCL_PATH_RELATIVE. The filesystem * reference will be set if and only if it is non-NULL and the function's * return value is TCL_PATH_ABSOLUTE. * * Side effects: * None. * *---------------------------------------------------------------------- */ Tcl_PathType TclFSNonnativePathType( const char *path, /* Pathname to determine the type of. */ int pathLen, /* Length of the pathname. */ const Tcl_Filesystem **filesystemPtrPtr, /* If not NULL, a place to store a pointer to * the filesystem for this pathname when it is * an absolute pathname. */ int *driveNameLengthPtr, /* If not NULL, a place to store the length of * the volume name if the pathname is absolute. */ Tcl_Obj **driveNameRef) /* If not NULL, a place to store a pointer to * an object having its its refCount already * incremented, and contining the name of the * volume if the pathname is absolute. */ { FilesystemRecord *fsRecPtr; Tcl_PathType type = TCL_PATH_RELATIVE; /* * Determine whether the given pathname is an absolute pathname on some * filesystem other than the native filesystem. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); while (fsRecPtr != NULL) { /* * Skip the the native filesystem because otherwise some of the tests * in the Tcl testsuite might fail because some of the tests * artificially change the current platform (between win, unix) but the * list of volumes obtained by calling fsRecPtr->fsPtr->listVolumesProc * reflects the current (real) platform only. In particular, on Unix * '/' matchs the beginning of certain absolute Windows pathnames * starting '//' and those tests go wrong. * * There is another reason to skip the native filesystem: Since the * tclFilename.c code has nice fast 'absolute path' checkers, there is * no reason to waste time doing that in this frequently-called * function. It is better to save the overhead of the native * filesystem continuously returning a list of volumes. */ if ((fsRecPtr->fsPtr != &tclNativeFilesystem) && (fsRecPtr->fsPtr->listVolumesProc != NULL)) { int numVolumes; Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc(); if (thisFsVolumes != NULL) { if (Tcl_ListObjLength(NULL, thisFsVolumes, &numVolumes) != TCL_OK) { /* * This is VERY bad; the listVolumesProc didn't return a * valid list. Set numVolumes to -1 to skip the loop below * and just return with the current value of 'type'. * * It would be better to signal an error here, but * Tcl_Panic seems a bit excessive. */ numVolumes = -1; } while (numVolumes > 0) { Tcl_Obj *vol; size_t len; const char *strVol; numVolumes--; Tcl_ListObjIndex(NULL, thisFsVolumes, numVolumes, &vol); strVol = TclGetStringFromObj(vol,&len); if ((size_t) pathLen < len) { continue; } if (strncmp(strVol, path, len) == 0) { type = TCL_PATH_ABSOLUTE; if (filesystemPtrPtr != NULL) { *filesystemPtrPtr = fsRecPtr->fsPtr; } if (driveNameLengthPtr != NULL) { *driveNameLengthPtr = len; } if (driveNameRef != NULL) { *driveNameRef = vol; Tcl_IncrRefCount(vol); } break; } } Tcl_DecrRefCount(thisFsVolumes); if (type == TCL_PATH_ABSOLUTE) { /* * No need to to examine additional filesystems. */ break; } } } fsRecPtr = fsRecPtr->nextPtr; } Disclaim(); return type; } /* *--------------------------------------------------------------------------- * * Tcl_FSRenameFile -- * * If the two pathnames correspond to the same filesystem, call * 'renameFileProc' of that filesystem. Otherwise return the POSIX error * 'EXDEV', and -1. * * Results: * A standard Tcl error code if a rename function was called, or -1 * otherwise. * * Side effects: * A file may be renamed. * *--------------------------------------------------------------------------- */ int Tcl_FSRenameFile( Tcl_Obj *srcPathPtr, /* The pathname of a file or directory to be renamed. */ Tcl_Obj *destPathPtr) /* The new pathname for the file. */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); if ((fsPtr == fsPtr2) && (fsPtr != NULL) && (fsPtr->renameFileProc != NULL)) { retVal = fsPtr->renameFileProc(srcPathPtr, destPathPtr); } if (retVal == -1) { Tcl_SetErrno(EXDEV); } return retVal; } /* *--------------------------------------------------------------------------- * * Tcl_FSCopyFile -- * * If both pathnames correspond to the same filesystem, calls * 'copyFileProc' of that filesystem. * * In the native filesystems, 'copyFileProc' copies a link itself, not the * thing the link points to. * * Results: * A standard Tcl return code if a copyFileProc was called, or -1 * otherwise. * * Side effects: * A file might be copied. The POSIX error 'EXDEV' is set if a copy * function was not called. * *--------------------------------------------------------------------------- */ int Tcl_FSCopyFile( Tcl_Obj *srcPathPtr, /* The pathname of file to be copied. */ Tcl_Obj *destPathPtr) /* The new pathname to copy the file to. */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); if (fsPtr == fsPtr2 && fsPtr != NULL && fsPtr->copyFileProc != NULL) { retVal = fsPtr->copyFileProc(srcPathPtr, destPathPtr); } if (retVal == -1) { Tcl_SetErrno(EXDEV); } return retVal; } /* *--------------------------------------------------------------------------- * * TclCrossFilesystemCopy -- * * Helper for Tcl_FSCopyFile and Tcl_FSLoadFile. Copies a file from one * filesystem to another, overwiting any file that already exists. * * Results: * A standard Tcl return code. * * Side effects: * A file may be copied. * *--------------------------------------------------------------------------- */ int TclCrossFilesystemCopy( Tcl_Interp *interp, /* For error messages. */ Tcl_Obj *source, /* Pathname of file to be copied. */ Tcl_Obj *target) /* Pathname to copy the file to. */ { int result = TCL_ERROR; int prot = 0666; Tcl_Channel in, out; Tcl_StatBuf sourceStatBuf; struct utimbuf tval; out = Tcl_FSOpenFileChannel(interp, target, "wb", prot); if (out == NULL) { /* * Failed to open an output channel. Bail out. */ goto done; } in = Tcl_FSOpenFileChannel(interp, source, "rb", prot); if (in == NULL) { /* * Could not open an input channel. Why didn't the caller check this? */ Tcl_CloseEx(interp, out, 0); goto done; } /* * Copy the file synchronously. TO DO: Maybe add an asynchronous option * to support virtual filesystems that are slow (e.g. network sockets). */ if (TclCopyChannel(interp, in, out, -1, NULL) == TCL_OK) { result = TCL_OK; } /* * If the copy failed, assume that copy channel left an error message. */ Tcl_CloseEx(interp, in, 0); Tcl_CloseEx(interp, out, 0); /* * Set modification date of copied file. */ if (Tcl_FSLstat(source, &sourceStatBuf) == 0) { tval.actime = Tcl_GetAccessTimeFromStat(&sourceStatBuf); tval.modtime = Tcl_GetModificationTimeFromStat(&sourceStatBuf); Tcl_FSUtime(target, &tval); } done: return result; } /* *--------------------------------------------------------------------------- * * Tcl_FSDeleteFile -- * * Calls 'deleteFileProc' of the filesystem corresponding to the given * pathname. * * Results: * A standard Tcl return code. * * Side effects: * A file may be deleted. * *--------------------------------------------------------------------------- */ int Tcl_FSDeleteFile( Tcl_Obj *pathPtr) /* Pathname of file to be removed (UTF-8). */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->deleteFileProc != NULL) { return fsPtr->deleteFileProc(pathPtr); } Tcl_SetErrno(ENOENT); return -1; } /* *--------------------------------------------------------------------------- * * Tcl_FSCreateDirectory -- * * Calls 'createDirectoryProc' of the filesystem corresponding to the * given pathname. * * Results: * A standard Tcl return code, or -1 if no createDirectoryProc is found. * * Side effects: * A directory may be created. POSIX error 'ENOENT' is set if no * createDirectoryProc is found. * *--------------------------------------------------------------------------- */ int Tcl_FSCreateDirectory( Tcl_Obj *pathPtr) /* Pathname of directory to create (UTF-8). */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr != NULL && fsPtr->createDirectoryProc != NULL) { return fsPtr->createDirectoryProc(pathPtr); } Tcl_SetErrno(ENOENT); return -1; } /* *--------------------------------------------------------------------------- * * Tcl_FSCopyDirectory -- * * If both pathnames correspond to the the same filesystem, calls * 'copyDirectoryProc' of that filesystem. * * Results: * A standard Tcl return code, or -1 if no 'copyDirectoryProc' is found. * * Side effects: * A directory may be copied. POSIX error 'EXDEV' is set if no * copyDirectoryProc is found. * *--------------------------------------------------------------------------- */ int Tcl_FSCopyDirectory( Tcl_Obj *srcPathPtr, /* * The pathname of the directory to be copied. */ Tcl_Obj *destPathPtr, /* The pathname of the target directory. */ Tcl_Obj **errorPtr) /* If not NULL, and there is an error, a place * to store a pointer to a new object, with * its refCount already incremented, and * containing the pathname name of file * causing the error. */ { int retVal = -1; const Tcl_Filesystem *fsPtr, *fsPtr2; fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr); fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr); if (fsPtr == fsPtr2 && fsPtr != NULL && fsPtr->copyDirectoryProc != NULL){ retVal = fsPtr->copyDirectoryProc(srcPathPtr, destPathPtr, errorPtr); } if (retVal == -1) { Tcl_SetErrno(EXDEV); } return retVal; } /* *--------------------------------------------------------------------------- * * Tcl_FSRemoveDirectory -- * * Calls 'removeDirectoryProc' of the filesystem corresponding to remove * pathPtr. * * Results: * A standard Tcl return code, or -1 if no removeDirectoryProc is found. * * Side effects: * A directory may be removed. POSIX error 'ENOENT' is set if no * removeDirectoryProc is found. * *--------------------------------------------------------------------------- */ int Tcl_FSRemoveDirectory( Tcl_Obj *pathPtr, /* The pathname of the directory to be removed. */ int recursive, /* If zero, removes only an empty directory. * Otherwise, removes the directory and all its * contents. */ Tcl_Obj **errorPtr) /* If not NULL and an error occurs, stores a * place to store a a pointer to a new * object having a refCount of 1 and containing * the name of the file that produced an error. * */ { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr == NULL || fsPtr->removeDirectoryProc == NULL) { Tcl_SetErrno(ENOENT); return -1; } if (recursive) { Tcl_Obj *cwdPtr = Tcl_FSGetCwd(NULL); if (cwdPtr != NULL) { const char *cwdStr, *normPathStr; size_t cwdLen, normLen; Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr); if (normPath != NULL) { normPathStr = TclGetStringFromObj(normPath, &normLen); cwdStr = TclGetStringFromObj(cwdPtr, &cwdLen); if ((cwdLen >= normLen) && (strncmp(normPathStr, cwdStr, normLen) == 0)) { /* * The cwd is inside the directory to be removed. Change * the cwd to [file dirname $path]. */ Tcl_Obj *dirPtr = TclPathPart(NULL, pathPtr, TCL_PATH_DIRNAME); Tcl_FSChdir(dirPtr); Tcl_DecrRefCount(dirPtr); } } Tcl_DecrRefCount(cwdPtr); } } return fsPtr->removeDirectoryProc(pathPtr, recursive, errorPtr); } /* *--------------------------------------------------------------------------- * * Tcl_FSGetFileSystemForPath -- * * Produces the filesystem that corresponds to the given pathname. * * Results: * The corresponding Tcl_Filesystem, or NULL if the pathname is invalid. * * Side effects: * The internal representation of fsPtrPtr is converted to fsPathType if * needed, and that internal representation is updated as needed. * *--------------------------------------------------------------------------- */ const Tcl_Filesystem * Tcl_FSGetFileSystemForPath( Tcl_Obj *pathPtr) { FilesystemRecord *fsRecPtr; const Tcl_Filesystem *retVal = NULL; if (pathPtr == NULL) { Tcl_Panic("Tcl_FSGetFileSystemForPath called with NULL object"); return NULL; } if (pathPtr->refCount == 0) { /* * Avoid possible segfaults or nondeterministic memory leaks where the * reference count has been incorreclty managed. */ Tcl_Panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0"); return NULL; } /* Start with an up-to-date copy of the filesystem. */ fsRecPtr = FsGetFirstFilesystem(); Claim(); /* * Ensure that pathPtr is a valid pathname. */ if (TclFSEnsureEpochOk(pathPtr, &retVal) != TCL_OK) { /* not a valid pathname */ Disclaim(); return NULL; } else if (retVal != NULL) { /* * Found the filesystem in the internal representation of pathPtr. */ Disclaim(); return retVal; } /* * Call each of the "pathInFilesystem" functions in succession until the * corresponding filesystem is found. */ for (; fsRecPtr!=NULL ; fsRecPtr=fsRecPtr->nextPtr) { ClientData clientData = NULL; if (fsRecPtr->fsPtr->pathInFilesystemProc == NULL) { continue; } if (fsRecPtr->fsPtr->pathInFilesystemProc(pathPtr, &clientData)!=-1) { /* This is the filesystem for pathPtr. Assume the type of pathPtr * hasn't been changed by the above call to the * pathInFilesystemProc, and cache this result in the internal * representation of pathPtr. */ TclFSSetPathDetails(pathPtr, fsRecPtr->fsPtr, clientData); Disclaim(); return fsRecPtr->fsPtr; } } Disclaim(); return NULL; } /* *--------------------------------------------------------------------------- * * Tcl_FSGetNativePath -- * * See Tcl_FSGetInternalRep. * *--------------------------------------------------------------------------- */ const void * Tcl_FSGetNativePath( Tcl_Obj *pathPtr) { return Tcl_FSGetInternalRep(pathPtr, &tclNativeFilesystem); } /* *--------------------------------------------------------------------------- * * NativeFreeInternalRep -- * * Free a native internal representation. * * Results: * None. * * Side effects: * Memory is released. * *--------------------------------------------------------------------------- */ static void NativeFreeInternalRep( ClientData clientData) { Tcl_Free(clientData); } /* *--------------------------------------------------------------------------- * * Tcl_FSFileSystemInfo -- * Produce the type of a pathname and the type of its filesystem. * * * Results: * A list where the first item is the name of the filesystem (e.g. * "native" or "vfs"), and the second item is the type of the given * pathname within that filesystem. * * Side effects: * The internal representation of pathPtr may be converted to a * fsPathType. * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSFileSystemInfo( Tcl_Obj *pathPtr) { Tcl_Obj *resPtr; const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); if (fsPtr == NULL) { return NULL; } resPtr = Tcl_NewListObj(0, NULL); Tcl_ListObjAppendElement(NULL, resPtr, Tcl_NewStringObj(fsPtr->typeName, -1)); if (fsPtr->filesystemPathTypeProc != NULL) { Tcl_Obj *typePtr = fsPtr->filesystemPathTypeProc(pathPtr); if (typePtr != NULL) { Tcl_ListObjAppendElement(NULL, resPtr, typePtr); } } return resPtr; } /* *--------------------------------------------------------------------------- * * Tcl_FSPathSeparator -- * * Produces the separator for given pathname. * * Results: * A Tcl object having a refCount of zero. * * Side effects: * The internal representation of pathPtr may be converted to a fsPathType * *--------------------------------------------------------------------------- */ Tcl_Obj * Tcl_FSPathSeparator( Tcl_Obj *pathPtr) { const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr); Tcl_Obj *resultObj; if (fsPtr == NULL) { return NULL; } if (fsPtr->filesystemSeparatorProc != NULL) { return fsPtr->filesystemSeparatorProc(pathPtr); } /* * Use the standard forward slash character if filesystem does not to * provide a filesystemSeparatorProc. */ TclNewLiteralStringObj(resultObj, "/"); return resultObj; } /* *--------------------------------------------------------------------------- * * NativeFilesystemSeparator -- * * This function, part of the native filesystem support, returns the * separator for the given pathname. * * Results: * The separator character. * * Side effects: * None. * *--------------------------------------------------------------------------- */ static Tcl_Obj * NativeFilesystemSeparator( TCL_UNUSED(Tcl_Obj *) /*pathPtr*/) { const char *separator = NULL; switch (tclPlatform) { case TCL_PLATFORM_UNIX: separator = "/"; break; case TCL_PLATFORM_WINDOWS: separator = "\\"; break; } return Tcl_NewStringObj(separator,1); } /* * Local Variables: * mode: c * c-basic-offset: 4 * fill-column: 78 * End: */