4860 lines
133 KiB
C
4860 lines
133 KiB
C
|
/*
|
|||
|
* tclIOUtil.c --
|
|||
|
*
|
|||
|
* This file contains the implementation of Tcl's generic filesystem
|
|||
|
* code, which supports a pluggable filesystem architecture allowing both
|
|||
|
* platform specific filesystems and 'virtual filesystems'. All
|
|||
|
* filesystem access should go through the functions defined in this
|
|||
|
* file. Most of this code was contributed by Vince Darley.
|
|||
|
*
|
|||
|
* Parts of this file are based on code contributed by Karl Lehenbauer,
|
|||
|
* Mark Diekhans and Peter da Silva.
|
|||
|
*
|
|||
|
* Copyright (c) 1991-1994 The Regents of the University of California.
|
|||
|
* Copyright (c) 1994-1997 Sun Microsystems, Inc.
|
|||
|
* Copyright (c) 2001-2004 Vincent Darley.
|
|||
|
*
|
|||
|
* See the file "license.terms" for information on usage and redistribution of
|
|||
|
* this file, and for a DISCLAIMER OF ALL WARRANTIES.
|
|||
|
*/
|
|||
|
|
|||
|
#include "tclInt.h"
|
|||
|
#ifdef _WIN32
|
|||
|
# include "tclWinInt.h"
|
|||
|
#endif
|
|||
|
#include "tclFileSystem.h"
|
|||
|
|
|||
|
#ifdef TCL_TEMPLOAD_NO_UNLINK
|
|||
|
#ifndef NO_FSTATFS
|
|||
|
#include <sys/statfs.h>
|
|||
|
#endif
|
|||
|
#endif
|
|||
|
|
|||
|
/*
|
|||
|
* struct FilesystemRecord --
|
|||
|
*
|
|||
|
* A filesystem record is used to keep track of each filesystem currently
|
|||
|
* registered with the core, in a linked list.
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct FilesystemRecord {
|
|||
|
ClientData clientData; /* Client specific data for the new filesystem
|
|||
|
* (can be NULL) */
|
|||
|
const Tcl_Filesystem *fsPtr;/* Pointer to filesystem dispatch table. */
|
|||
|
struct FilesystemRecord *nextPtr;
|
|||
|
/* The next filesystem registered to Tcl, or
|
|||
|
* NULL if no more. */
|
|||
|
struct FilesystemRecord *prevPtr;
|
|||
|
/* The previous filesystem registered to Tcl,
|
|||
|
* or NULL if no more. */
|
|||
|
} FilesystemRecord;
|
|||
|
|
|||
|
/*
|
|||
|
* This structure holds per-thread private copy of the current directory
|
|||
|
* maintained by the global cwdPathPtr. This structure holds per-thread
|
|||
|
* private copies of some global data. This way we avoid most of the
|
|||
|
* synchronization calls which boosts performance, at cost of having to update
|
|||
|
* this information each time the corresponding epoch counter changes.
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct ThreadSpecificData {
|
|||
|
int initialized;
|
|||
|
size_t cwdPathEpoch;
|
|||
|
size_t filesystemEpoch;
|
|||
|
Tcl_Obj *cwdPathPtr;
|
|||
|
ClientData cwdClientData;
|
|||
|
FilesystemRecord *filesystemList;
|
|||
|
size_t claims;
|
|||
|
} ThreadSpecificData;
|
|||
|
|
|||
|
/*
|
|||
|
* Prototypes for functions defined later in this file.
|
|||
|
*/
|
|||
|
|
|||
|
static Tcl_NRPostProc EvalFileCallback;
|
|||
|
static FilesystemRecord*FsGetFirstFilesystem(void);
|
|||
|
static void FsThrExitProc(ClientData cd);
|
|||
|
static Tcl_Obj * FsListMounts(Tcl_Obj *pathPtr, const char *pattern);
|
|||
|
static void FsAddMountsToGlobResult(Tcl_Obj *resultPtr,
|
|||
|
Tcl_Obj *pathPtr, const char *pattern,
|
|||
|
Tcl_GlobTypeData *types);
|
|||
|
static void FsUpdateCwd(Tcl_Obj *cwdObj, ClientData clientData);
|
|||
|
static void FsRecacheFilesystemList(void);
|
|||
|
static void Claim(void);
|
|||
|
static void Disclaim(void);
|
|||
|
|
|||
|
static void * DivertFindSymbol(Tcl_Interp *interp,
|
|||
|
Tcl_LoadHandle loadHandle, const char *symbol);
|
|||
|
static void DivertUnloadFile(Tcl_LoadHandle loadHandle);
|
|||
|
|
|||
|
/*
|
|||
|
* These form part of the native filesystem support. They are needed here
|
|||
|
* because we have a few native filesystem functions (which are the same for
|
|||
|
* win/unix) in this file. There is no need to place them in tclInt.h, because
|
|||
|
* they are not (and should not be) used anywhere else.
|
|||
|
*/
|
|||
|
|
|||
|
MODULE_SCOPE const char *const tclpFileAttrStrings[];
|
|||
|
MODULE_SCOPE const TclFileAttrProcs tclpFileAttrProcs[];
|
|||
|
|
|||
|
/*
|
|||
|
* Declare the native filesystem support. These functions should be considered
|
|||
|
* private to Tcl, and should really not be called directly by any code other
|
|||
|
* than this file (i.e. neither by Tcl's core nor by extensions). Similarly,
|
|||
|
* the old string-based Tclp... native filesystem functions should not be
|
|||
|
* called.
|
|||
|
*
|
|||
|
* The correct API to use now is the Tcl_FS... set of functions, which ensure
|
|||
|
* correct and complete virtual filesystem support.
|
|||
|
*
|
|||
|
* We cannot make all of these static, since some of them are implemented in
|
|||
|
* the platform-specific directories.
|
|||
|
*/
|
|||
|
|
|||
|
static Tcl_FSFilesystemSeparatorProc NativeFilesystemSeparator;
|
|||
|
static Tcl_FSFreeInternalRepProc NativeFreeInternalRep;
|
|||
|
static Tcl_FSFileAttrStringsProc NativeFileAttrStrings;
|
|||
|
static Tcl_FSFileAttrsGetProc NativeFileAttrsGet;
|
|||
|
static Tcl_FSFileAttrsSetProc NativeFileAttrsSet;
|
|||
|
|
|||
|
/*
|
|||
|
* The only reason these functions are not static is that they are either
|
|||
|
* called by code in the native (win/unix) directories or they are actually
|
|||
|
* implemented in those directories. They should simply not be called by code
|
|||
|
* outside Tcl's native filesystem core i.e. they should be considered
|
|||
|
* 'static' to Tcl's filesystem code (if we ever built the native filesystem
|
|||
|
* support into a separate code library, this could actually be enforced).
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_FSFilesystemPathTypeProc TclpFilesystemPathType;
|
|||
|
Tcl_FSInternalToNormalizedProc TclpNativeToNormalized;
|
|||
|
Tcl_FSStatProc TclpObjStat;
|
|||
|
Tcl_FSAccessProc TclpObjAccess;
|
|||
|
Tcl_FSMatchInDirectoryProc TclpMatchInDirectory;
|
|||
|
Tcl_FSChdirProc TclpObjChdir;
|
|||
|
Tcl_FSLstatProc TclpObjLstat;
|
|||
|
Tcl_FSCopyFileProc TclpObjCopyFile;
|
|||
|
Tcl_FSDeleteFileProc TclpObjDeleteFile;
|
|||
|
Tcl_FSRenameFileProc TclpObjRenameFile;
|
|||
|
Tcl_FSCreateDirectoryProc TclpObjCreateDirectory;
|
|||
|
Tcl_FSCopyDirectoryProc TclpObjCopyDirectory;
|
|||
|
Tcl_FSRemoveDirectoryProc TclpObjRemoveDirectory;
|
|||
|
Tcl_FSLinkProc TclpObjLink;
|
|||
|
Tcl_FSListVolumesProc TclpObjListVolumes;
|
|||
|
|
|||
|
/*
|
|||
|
* Define the native filesystem dispatch table. If necessary, it is ok to make
|
|||
|
* this non-static, but it should only be accessed by the functions actually
|
|||
|
* listed within it (or perhaps other helper functions of them). Anything
|
|||
|
* which is not part of this 'native filesystem implementation' should not be
|
|||
|
* delving inside here!
|
|||
|
*/
|
|||
|
|
|||
|
const Tcl_Filesystem tclNativeFilesystem = {
|
|||
|
"native",
|
|||
|
sizeof(Tcl_Filesystem),
|
|||
|
TCL_FILESYSTEM_VERSION_2,
|
|||
|
TclNativePathInFilesystem,
|
|||
|
TclNativeDupInternalRep,
|
|||
|
NativeFreeInternalRep,
|
|||
|
TclpNativeToNormalized,
|
|||
|
TclNativeCreateNativeRep,
|
|||
|
TclpObjNormalizePath,
|
|||
|
TclpFilesystemPathType,
|
|||
|
NativeFilesystemSeparator,
|
|||
|
TclpObjStat,
|
|||
|
TclpObjAccess,
|
|||
|
TclpOpenFileChannel,
|
|||
|
TclpMatchInDirectory,
|
|||
|
TclpUtime,
|
|||
|
#ifndef S_IFLNK
|
|||
|
NULL,
|
|||
|
#else
|
|||
|
TclpObjLink,
|
|||
|
#endif /* S_IFLNK */
|
|||
|
TclpObjListVolumes,
|
|||
|
NativeFileAttrStrings,
|
|||
|
NativeFileAttrsGet,
|
|||
|
NativeFileAttrsSet,
|
|||
|
TclpObjCreateDirectory,
|
|||
|
TclpObjRemoveDirectory,
|
|||
|
TclpObjDeleteFile,
|
|||
|
TclpObjCopyFile,
|
|||
|
TclpObjRenameFile,
|
|||
|
TclpObjCopyDirectory,
|
|||
|
TclpObjLstat,
|
|||
|
/* Needs casts since we're using version_2. */
|
|||
|
(Tcl_FSLoadFileProc *)(void *) TclpDlopen,
|
|||
|
(Tcl_FSGetCwdProc *) TclpGetNativeCwd,
|
|||
|
TclpObjChdir
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* Define the tail of the linked list. Note that for unconventional uses of
|
|||
|
* Tcl without a native filesystem, we may in the future wish to modify the
|
|||
|
* current approach of hard-coding the native filesystem in the lookup list
|
|||
|
* 'filesystemList' below.
|
|||
|
*
|
|||
|
* We initialize the record so that it thinks one file uses it. This means it
|
|||
|
* will never be freed.
|
|||
|
*/
|
|||
|
|
|||
|
static FilesystemRecord nativeFilesystemRecord = {
|
|||
|
NULL,
|
|||
|
&tclNativeFilesystem,
|
|||
|
NULL,
|
|||
|
NULL
|
|||
|
};
|
|||
|
|
|||
|
/*
|
|||
|
* This is incremented each time we modify the linked list of filesystems. Any
|
|||
|
* time it changes, all cached filesystem representations are suspect and must
|
|||
|
* be freed. For multithreading builds, change of the filesystem epoch will
|
|||
|
* trigger cache cleanup in all threads.
|
|||
|
*/
|
|||
|
|
|||
|
static size_t theFilesystemEpoch = 1;
|
|||
|
|
|||
|
/*
|
|||
|
* Stores the linked list of filesystems. A 1:1 copy of this list is also
|
|||
|
* maintained in the TSD for each thread. This is to avoid synchronization
|
|||
|
* issues.
|
|||
|
*/
|
|||
|
|
|||
|
static FilesystemRecord *filesystemList = &nativeFilesystemRecord;
|
|||
|
TCL_DECLARE_MUTEX(filesystemMutex)
|
|||
|
|
|||
|
/*
|
|||
|
* Used to implement Tcl_FSGetCwd in a file-system independent way.
|
|||
|
*/
|
|||
|
|
|||
|
static Tcl_Obj *cwdPathPtr = NULL;
|
|||
|
static size_t cwdPathEpoch = 0;
|
|||
|
static ClientData cwdClientData = NULL;
|
|||
|
TCL_DECLARE_MUTEX(cwdMutex)
|
|||
|
|
|||
|
static Tcl_ThreadDataKey fsDataKey;
|
|||
|
|
|||
|
/*
|
|||
|
* One of these structures is used each time we successfully load a file from
|
|||
|
* a file system by way of making a temporary copy of the file on the native
|
|||
|
* filesystem. We need to store both the actual unloadProc/clientData
|
|||
|
* combination which was used, and the original and modified filenames, so
|
|||
|
* that we can correctly undo the entire operation when we want to unload the
|
|||
|
* code.
|
|||
|
*/
|
|||
|
|
|||
|
typedef struct FsDivertLoad {
|
|||
|
Tcl_LoadHandle loadHandle;
|
|||
|
Tcl_FSUnloadFileProc *unloadProcPtr;
|
|||
|
Tcl_Obj *divertedFile;
|
|||
|
const Tcl_Filesystem *divertedFilesystem;
|
|||
|
ClientData divertedFileNativeRep;
|
|||
|
} FsDivertLoad;
|
|||
|
|
|||
|
/*
|
|||
|
* The following functions are obsolete string based APIs, and should be
|
|||
|
* removed in a future release (Tcl 9 would be a good time).
|
|||
|
*/
|
|||
|
|
|||
|
/* Obsolete */
|
|||
|
int
|
|||
|
Tcl_Stat(
|
|||
|
const char *path, /* Path of file to stat (in current CP). */
|
|||
|
struct stat *oldStyleBuf) /* Filled with results of stat call. */
|
|||
|
{
|
|||
|
int ret;
|
|||
|
Tcl_StatBuf buf;
|
|||
|
Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
|
|||
|
|
|||
|
Tcl_IncrRefCount(pathPtr);
|
|||
|
ret = Tcl_FSStat(pathPtr, &buf);
|
|||
|
Tcl_DecrRefCount(pathPtr);
|
|||
|
if (ret != -1) {
|
|||
|
#ifndef TCL_WIDE_INT_IS_LONG
|
|||
|
Tcl_WideInt tmp1, tmp2, tmp3 = 0;
|
|||
|
|
|||
|
# define OUT_OF_RANGE(x) \
|
|||
|
(((Tcl_WideInt)(x)) < Tcl_LongAsWide(LONG_MIN) || \
|
|||
|
((Tcl_WideInt)(x)) > Tcl_LongAsWide(LONG_MAX))
|
|||
|
# define OUT_OF_URANGE(x) \
|
|||
|
(((Tcl_WideUInt)(x)) > ((Tcl_WideUInt)ULONG_MAX))
|
|||
|
|
|||
|
/*
|
|||
|
* Perform the result-buffer overflow check manually.
|
|||
|
*
|
|||
|
* Note that ino_t/ino64_t is unsigned...
|
|||
|
*
|
|||
|
* Workaround gcc warning of "comparison is always false due to
|
|||
|
* limited range of data type" by assigning to tmp var of type
|
|||
|
* Tcl_WideInt.
|
|||
|
*/
|
|||
|
|
|||
|
tmp1 = (Tcl_WideInt) buf.st_ino;
|
|||
|
tmp2 = (Tcl_WideInt) buf.st_size;
|
|||
|
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
|
|||
|
tmp3 = (Tcl_WideInt) buf.st_blocks;
|
|||
|
#endif
|
|||
|
|
|||
|
if (OUT_OF_URANGE(tmp1) || OUT_OF_RANGE(tmp2) || OUT_OF_RANGE(tmp3)) {
|
|||
|
#if defined(EFBIG)
|
|||
|
errno = EFBIG;
|
|||
|
#elif defined(EOVERFLOW)
|
|||
|
errno = EOVERFLOW;
|
|||
|
#else
|
|||
|
#error "What status should be returned for file size out of range?"
|
|||
|
#endif
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
# undef OUT_OF_RANGE
|
|||
|
# undef OUT_OF_URANGE
|
|||
|
#endif /* !TCL_WIDE_INT_IS_LONG */
|
|||
|
|
|||
|
/*
|
|||
|
* Copy across all supported fields, with possible type coercions on
|
|||
|
* those fields that change between the normal and lf64 versions of
|
|||
|
* the stat structure (on Solaris at least). This is slow when the
|
|||
|
* structure sizes coincide, but that's what you get for using an
|
|||
|
* obsolete interface.
|
|||
|
*/
|
|||
|
|
|||
|
oldStyleBuf->st_mode = buf.st_mode;
|
|||
|
oldStyleBuf->st_ino = (ino_t) buf.st_ino;
|
|||
|
oldStyleBuf->st_dev = buf.st_dev;
|
|||
|
oldStyleBuf->st_rdev = buf.st_rdev;
|
|||
|
oldStyleBuf->st_nlink = buf.st_nlink;
|
|||
|
oldStyleBuf->st_uid = buf.st_uid;
|
|||
|
oldStyleBuf->st_gid = buf.st_gid;
|
|||
|
oldStyleBuf->st_size = (off_t) buf.st_size;
|
|||
|
oldStyleBuf->st_atime = Tcl_GetAccessTimeFromStat(&buf);
|
|||
|
oldStyleBuf->st_mtime = Tcl_GetModificationTimeFromStat(&buf);
|
|||
|
oldStyleBuf->st_ctime = Tcl_GetChangeTimeFromStat(&buf);
|
|||
|
#ifdef HAVE_STRUCT_STAT_ST_BLKSIZE
|
|||
|
oldStyleBuf->st_blksize = buf.st_blksize;
|
|||
|
#endif
|
|||
|
#ifdef HAVE_STRUCT_STAT_ST_BLOCKS
|
|||
|
#ifdef HAVE_BLKCNT_T
|
|||
|
oldStyleBuf->st_blocks = (blkcnt_t) buf.st_blocks;
|
|||
|
#else
|
|||
|
oldStyleBuf->st_blocks = (unsigned long) buf.st_blocks;
|
|||
|
#endif
|
|||
|
#endif
|
|||
|
}
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
/* Obsolete */
|
|||
|
int
|
|||
|
Tcl_Access(
|
|||
|
const char *path, /* Path of file to access (in current CP). */
|
|||
|
int mode) /* Permission setting. */
|
|||
|
{
|
|||
|
int ret;
|
|||
|
Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
|
|||
|
|
|||
|
Tcl_IncrRefCount(pathPtr);
|
|||
|
ret = Tcl_FSAccess(pathPtr,mode);
|
|||
|
Tcl_DecrRefCount(pathPtr);
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
/* Obsolete */
|
|||
|
Tcl_Channel
|
|||
|
Tcl_OpenFileChannel(
|
|||
|
Tcl_Interp *interp, /* Interpreter for error reporting; can be
|
|||
|
* NULL. */
|
|||
|
const char *path, /* Name of file to open. */
|
|||
|
const char *modeString, /* A list of POSIX open modes or a string such
|
|||
|
* as "rw". */
|
|||
|
int permissions) /* If the open involves creating a file, with
|
|||
|
* what modes to create it? */
|
|||
|
{
|
|||
|
Tcl_Channel ret;
|
|||
|
Tcl_Obj *pathPtr = Tcl_NewStringObj(path,-1);
|
|||
|
|
|||
|
Tcl_IncrRefCount(pathPtr);
|
|||
|
ret = Tcl_FSOpenFileChannel(interp, pathPtr, modeString, permissions);
|
|||
|
Tcl_DecrRefCount(pathPtr);
|
|||
|
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
/* 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 process file. */
|
|||
|
const char *fileName) /* Name of file to process. Tilde-substitution
|
|||
|
* will be performed on this name. */
|
|||
|
{
|
|||
|
int ret;
|
|||
|
Tcl_Obj *pathPtr = Tcl_NewStringObj(fileName,-1);
|
|||
|
|
|||
|
Tcl_IncrRefCount(pathPtr);
|
|||
|
ret = Tcl_FSEvalFile(interp, pathPtr);
|
|||
|
Tcl_DecrRefCount(pathPtr);
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Now move on to the basic filesystem implementation.
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
FsThrExitProc(
|
|||
|
ClientData cd)
|
|||
|
{
|
|||
|
ThreadSpecificData *tsdPtr = cd;
|
|||
|
FilesystemRecord *fsRecPtr = NULL, *tmpFsRecPtr = NULL;
|
|||
|
|
|||
|
/*
|
|||
|
* Trash the cwd copy.
|
|||
|
*/
|
|||
|
|
|||
|
if (tsdPtr->cwdPathPtr != NULL) {
|
|||
|
Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
|
|||
|
tsdPtr->cwdPathPtr = NULL;
|
|||
|
}
|
|||
|
if (tsdPtr->cwdClientData != NULL) {
|
|||
|
NativeFreeInternalRep(tsdPtr->cwdClientData);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Trash the filesystems cache.
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = tsdPtr->filesystemList;
|
|||
|
while (fsRecPtr != NULL) {
|
|||
|
tmpFsRecPtr = fsRecPtr->nextPtr;
|
|||
|
fsRecPtr->fsPtr = NULL;
|
|||
|
ckfree(fsRecPtr);
|
|||
|
fsRecPtr = tmpFsRecPtr;
|
|||
|
}
|
|||
|
tsdPtr->filesystemList = NULL;
|
|||
|
tsdPtr->initialized = 0;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
TclFSCwdIsNative(void)
|
|||
|
{
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
|
|||
|
if (tsdPtr->cwdClientData != NULL) {
|
|||
|
return 1;
|
|||
|
} else {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclFSCwdPointerEquals --
|
|||
|
*
|
|||
|
* Check whether the current working directory is equal to the path
|
|||
|
* given.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* 1 (equal) or 0 (un-equal) as appropriate.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* If the paths are equal, but are not the same object, this method will
|
|||
|
* modify the given pathPtrPtr to refer to the same object. In this case
|
|||
|
* the object pointed to by pathPtrPtr will have its refCount
|
|||
|
* decremented, and it will be adjusted to point to the cwd (with a new
|
|||
|
* refCount).
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclFSCwdPointerEquals(
|
|||
|
Tcl_Obj **pathPtrPtr)
|
|||
|
{
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
|
|||
|
Tcl_MutexLock(&cwdMutex);
|
|||
|
if (tsdPtr->cwdPathPtr == NULL
|
|||
|
|| tsdPtr->cwdPathEpoch != cwdPathEpoch) {
|
|||
|
if (tsdPtr->cwdPathPtr != NULL) {
|
|||
|
Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
|
|||
|
}
|
|||
|
if (tsdPtr->cwdClientData != NULL) {
|
|||
|
NativeFreeInternalRep(tsdPtr->cwdClientData);
|
|||
|
}
|
|||
|
if (cwdPathPtr == NULL) {
|
|||
|
tsdPtr->cwdPathPtr = NULL;
|
|||
|
} else {
|
|||
|
tsdPtr->cwdPathPtr = Tcl_DuplicateObj(cwdPathPtr);
|
|||
|
Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
|
|||
|
}
|
|||
|
if (cwdClientData == NULL) {
|
|||
|
tsdPtr->cwdClientData = NULL;
|
|||
|
} else {
|
|||
|
tsdPtr->cwdClientData = TclNativeDupInternalRep(cwdClientData);
|
|||
|
}
|
|||
|
tsdPtr->cwdPathEpoch = cwdPathEpoch;
|
|||
|
}
|
|||
|
Tcl_MutexUnlock(&cwdMutex);
|
|||
|
|
|||
|
if (tsdPtr->initialized == 0) {
|
|||
|
Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr);
|
|||
|
tsdPtr->initialized = 1;
|
|||
|
}
|
|||
|
|
|||
|
if (pathPtrPtr == NULL) {
|
|||
|
return (tsdPtr->cwdPathPtr == NULL);
|
|||
|
}
|
|||
|
|
|||
|
if (tsdPtr->cwdPathPtr == *pathPtrPtr) {
|
|||
|
return 1;
|
|||
|
} else {
|
|||
|
int len1, len2;
|
|||
|
const char *str1, *str2;
|
|||
|
|
|||
|
str1 = Tcl_GetStringFromObj(tsdPtr->cwdPathPtr, &len1);
|
|||
|
str2 = Tcl_GetStringFromObj(*pathPtrPtr, &len2);
|
|||
|
if ((len1 == len2) && !memcmp(str1, str2, len1)) {
|
|||
|
/*
|
|||
|
* They are equal, but different objects. Update so they will be
|
|||
|
* the same object in the future.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_DecrRefCount(*pathPtrPtr);
|
|||
|
*pathPtrPtr = tsdPtr->cwdPathPtr;
|
|||
|
Tcl_IncrRefCount(*pathPtrPtr);
|
|||
|
return 1;
|
|||
|
} else {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
FsRecacheFilesystemList(void)
|
|||
|
{
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
FilesystemRecord *fsRecPtr, *tmpFsRecPtr = NULL, *toFree = NULL, *list;
|
|||
|
|
|||
|
/*
|
|||
|
* Trash the current cache.
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = tsdPtr->filesystemList;
|
|||
|
while (fsRecPtr != NULL) {
|
|||
|
tmpFsRecPtr = fsRecPtr->nextPtr;
|
|||
|
fsRecPtr->nextPtr = toFree;
|
|||
|
toFree = fsRecPtr;
|
|||
|
fsRecPtr = tmpFsRecPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Locate tail of the global filesystem list.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_MutexLock(&filesystemMutex);
|
|||
|
fsRecPtr = filesystemList;
|
|||
|
while (fsRecPtr != NULL) {
|
|||
|
tmpFsRecPtr = fsRecPtr;
|
|||
|
fsRecPtr = fsRecPtr->nextPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Refill the cache honouring the order.
|
|||
|
*/
|
|||
|
|
|||
|
list = NULL;
|
|||
|
fsRecPtr = tmpFsRecPtr;
|
|||
|
while (fsRecPtr != NULL) {
|
|||
|
tmpFsRecPtr = ckalloc(sizeof(FilesystemRecord));
|
|||
|
*tmpFsRecPtr = *fsRecPtr;
|
|||
|
tmpFsRecPtr->nextPtr = list;
|
|||
|
tmpFsRecPtr->prevPtr = NULL;
|
|||
|
list = tmpFsRecPtr;
|
|||
|
fsRecPtr = fsRecPtr->prevPtr;
|
|||
|
}
|
|||
|
tsdPtr->filesystemList = list;
|
|||
|
tsdPtr->filesystemEpoch = theFilesystemEpoch;
|
|||
|
Tcl_MutexUnlock(&filesystemMutex);
|
|||
|
|
|||
|
while (toFree) {
|
|||
|
FilesystemRecord *next = toFree->nextPtr;
|
|||
|
toFree->fsPtr = NULL;
|
|||
|
ckfree(toFree);
|
|||
|
toFree = next;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Make sure the above gets released on thread exit.
|
|||
|
*/
|
|||
|
|
|||
|
if (tsdPtr->initialized == 0) {
|
|||
|
Tcl_CreateThreadExitHandler(FsThrExitProc, tsdPtr);
|
|||
|
tsdPtr->initialized = 1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
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 be changed by filesystems being added or removed, by changing
|
|||
|
* the "system encoding" and by env(HOME) changing.
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclFSEpochOk(
|
|||
|
size_t filesystemEpoch)
|
|||
|
{
|
|||
|
return (filesystemEpoch == 0 || filesystemEpoch == theFilesystemEpoch);
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
Claim(void)
|
|||
|
{
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
|
|||
|
tsdPtr->claims++;
|
|||
|
}
|
|||
|
|
|||
|
static void
|
|||
|
Disclaim(void)
|
|||
|
{
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
|
|||
|
tsdPtr->claims--;
|
|||
|
}
|
|||
|
|
|||
|
size_t
|
|||
|
TclFSEpoch(void)
|
|||
|
{
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
|
|||
|
return tsdPtr->filesystemEpoch;
|
|||
|
}
|
|||
|
|
|||
|
|
|||
|
/*
|
|||
|
* If non-NULL, clientData is owned by us and must be freed later.
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
FsUpdateCwd(
|
|||
|
Tcl_Obj *cwdObj,
|
|||
|
ClientData clientData)
|
|||
|
{
|
|||
|
int len;
|
|||
|
const char *str = NULL;
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
|
|||
|
if (cwdObj != NULL) {
|
|||
|
str = Tcl_GetStringFromObj(cwdObj, &len);
|
|||
|
}
|
|||
|
|
|||
|
Tcl_MutexLock(&cwdMutex);
|
|||
|
if (cwdPathPtr != NULL) {
|
|||
|
Tcl_DecrRefCount(cwdPathPtr);
|
|||
|
}
|
|||
|
if (cwdClientData != NULL) {
|
|||
|
NativeFreeInternalRep(cwdClientData);
|
|||
|
}
|
|||
|
|
|||
|
if (cwdObj == NULL) {
|
|||
|
cwdPathPtr = NULL;
|
|||
|
cwdClientData = NULL;
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* This must be stored as string obj!
|
|||
|
*/
|
|||
|
|
|||
|
cwdPathPtr = Tcl_NewStringObj(str, len);
|
|||
|
Tcl_IncrRefCount(cwdPathPtr);
|
|||
|
cwdClientData = TclNativeDupInternalRep(clientData);
|
|||
|
}
|
|||
|
|
|||
|
if (++cwdPathEpoch == 0) {
|
|||
|
++cwdPathEpoch;
|
|||
|
}
|
|||
|
tsdPtr->cwdPathEpoch = cwdPathEpoch;
|
|||
|
Tcl_MutexUnlock(&cwdMutex);
|
|||
|
|
|||
|
if (tsdPtr->cwdPathPtr) {
|
|||
|
Tcl_DecrRefCount(tsdPtr->cwdPathPtr);
|
|||
|
}
|
|||
|
if (tsdPtr->cwdClientData) {
|
|||
|
NativeFreeInternalRep(tsdPtr->cwdClientData);
|
|||
|
}
|
|||
|
|
|||
|
if (cwdObj == NULL) {
|
|||
|
tsdPtr->cwdPathPtr = NULL;
|
|||
|
tsdPtr->cwdClientData = NULL;
|
|||
|
} else {
|
|||
|
tsdPtr->cwdPathPtr = Tcl_NewStringObj(str, len);
|
|||
|
tsdPtr->cwdClientData = clientData;
|
|||
|
Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclFinalizeFilesystem --
|
|||
|
*
|
|||
|
* Clean up the filesystem. After this, calls to all Tcl_FS... functions
|
|||
|
* will fail.
|
|||
|
*
|
|||
|
* We will later call TclResetFilesystem to restore the FS to a pristine
|
|||
|
* state.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Frees any memory allocated by the filesystem.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclFinalizeFilesystem(void)
|
|||
|
{
|
|||
|
FilesystemRecord *fsRecPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* Assumption that only one thread is active now. Otherwise we would need
|
|||
|
* to put various mutexes around this code.
|
|||
|
*/
|
|||
|
|
|||
|
if (cwdPathPtr != NULL) {
|
|||
|
Tcl_DecrRefCount(cwdPathPtr);
|
|||
|
cwdPathPtr = NULL;
|
|||
|
cwdPathEpoch = 0;
|
|||
|
}
|
|||
|
if (cwdClientData != NULL) {
|
|||
|
NativeFreeInternalRep(cwdClientData);
|
|||
|
cwdClientData = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Remove all filesystems, freeing any allocated memory that is no longer
|
|||
|
* needed.
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = filesystemList;
|
|||
|
while (fsRecPtr != NULL) {
|
|||
|
FilesystemRecord *tmpFsRecPtr = fsRecPtr->nextPtr;
|
|||
|
|
|||
|
/* The native filesystem is static, so we don't free it. */
|
|||
|
|
|||
|
if (fsRecPtr != &nativeFilesystemRecord) {
|
|||
|
ckfree(fsRecPtr);
|
|||
|
}
|
|||
|
fsRecPtr = tmpFsRecPtr;
|
|||
|
}
|
|||
|
if (++theFilesystemEpoch == 0) {
|
|||
|
++theFilesystemEpoch;
|
|||
|
}
|
|||
|
filesystemList = NULL;
|
|||
|
|
|||
|
/*
|
|||
|
* Now filesystemList is NULL. This means that any attempt to use the
|
|||
|
* filesystem is likely to fail.
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef _WIN32
|
|||
|
TclWinEncodingsCleanup();
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclResetFilesystem --
|
|||
|
*
|
|||
|
* Restore the filesystem to a pristine state.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclResetFilesystem(void)
|
|||
|
{
|
|||
|
filesystemList = &nativeFilesystemRecord;
|
|||
|
if (++theFilesystemEpoch == 0) {
|
|||
|
++theFilesystemEpoch;
|
|||
|
}
|
|||
|
|
|||
|
#ifdef _WIN32
|
|||
|
/*
|
|||
|
* Cleans up the win32 API filesystem proc lookup table. This must happen
|
|||
|
* very late in finalization so that deleting of copied dlls can occur.
|
|||
|
*/
|
|||
|
|
|||
|
TclWinResetInterfaces();
|
|||
|
#endif
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSRegister --
|
|||
|
*
|
|||
|
* Insert the filesystem function table at the head of the list of
|
|||
|
* functions which are used during calls to all file-system operations.
|
|||
|
* The filesystem will be added even if it is already in the list. (You
|
|||
|
* can use Tcl_FSData to check if it is in the list, provided the
|
|||
|
* ClientData used was not NULL).
|
|||
|
*
|
|||
|
* Note that the filesystem handling is head-to-tail of the list. Each
|
|||
|
* filesystem is asked in turn whether it can handle a particular
|
|||
|
* request, until one of them says 'yes'. At that point no further
|
|||
|
* filesystems are asked.
|
|||
|
*
|
|||
|
* In particular this means if you want to add a diagnostic filesystem
|
|||
|
* (which simply reports all fs activity), it must be at the head of the
|
|||
|
* list: i.e. it must be the last registered.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Normally TCL_OK; TCL_ERROR if memory for a new node in the list could
|
|||
|
* not be allocated.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Memory allocated and modifies the link list for filesystems.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSRegister(
|
|||
|
ClientData clientData, /* Client specific data for this fs. */
|
|||
|
const Tcl_Filesystem *fsPtr)/* The filesystem record for the new fs. */
|
|||
|
{
|
|||
|
FilesystemRecord *newFilesystemPtr;
|
|||
|
|
|||
|
if (fsPtr == NULL) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
newFilesystemPtr = ckalloc(sizeof(FilesystemRecord));
|
|||
|
|
|||
|
newFilesystemPtr->clientData = clientData;
|
|||
|
newFilesystemPtr->fsPtr = fsPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* Is this lock and wait strictly speaking necessary? Since any iterators
|
|||
|
* out there will have grabbed a copy of the head of the list and be
|
|||
|
* iterating away from that, if we add a new element to the head of the
|
|||
|
* list, it can't possibly have any effect on any of their loops. In fact
|
|||
|
* it could be better not to wait, since we are adjusting the filesystem
|
|||
|
* epoch, any cached representations calculated by existing iterators are
|
|||
|
* going to have to be thrown away anyway.
|
|||
|
*
|
|||
|
* However, since registering and unregistering filesystems is a very rare
|
|||
|
* action, this is not a very important point.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_MutexLock(&filesystemMutex);
|
|||
|
|
|||
|
newFilesystemPtr->nextPtr = filesystemList;
|
|||
|
newFilesystemPtr->prevPtr = NULL;
|
|||
|
if (filesystemList) {
|
|||
|
filesystemList->prevPtr = newFilesystemPtr;
|
|||
|
}
|
|||
|
filesystemList = newFilesystemPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* Increment the filesystem epoch counter, since existing paths might
|
|||
|
* conceivably now belong to different filesystems.
|
|||
|
*/
|
|||
|
|
|||
|
if (++theFilesystemEpoch == 0) {
|
|||
|
++theFilesystemEpoch;
|
|||
|
}
|
|||
|
Tcl_MutexUnlock(&filesystemMutex);
|
|||
|
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSUnregister --
|
|||
|
*
|
|||
|
* Remove the passed filesystem from the list of filesystem function
|
|||
|
* tables. It also ensures that the built-in (native) filesystem is not
|
|||
|
* removable, although we may wish to change that decision in the future
|
|||
|
* to allow a smaller Tcl core, in which the native filesystem is not
|
|||
|
* used at all (we could, say, initialise Tcl completely over a network
|
|||
|
* connection).
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* TCL_OK if the function pointer was successfully removed, TCL_ERROR
|
|||
|
* otherwise.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Memory may be deallocated (or will be later, once no "path" objects
|
|||
|
* refer to this filesystem), but the list of registered filesystems is
|
|||
|
* updated immediately.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSUnregister(
|
|||
|
const Tcl_Filesystem *fsPtr) /* The filesystem record to remove. */
|
|||
|
{
|
|||
|
int retVal = TCL_ERROR;
|
|||
|
FilesystemRecord *fsRecPtr;
|
|||
|
|
|||
|
Tcl_MutexLock(&filesystemMutex);
|
|||
|
|
|||
|
/*
|
|||
|
* Traverse the 'filesystemList' looking for the particular node whose
|
|||
|
* 'fsPtr' member matches 'fsPtr' and remove that one from the list.
|
|||
|
* Ensure that the "default" node cannot be removed.
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = filesystemList;
|
|||
|
while ((retVal == TCL_ERROR) && (fsRecPtr != &nativeFilesystemRecord)) {
|
|||
|
if (fsRecPtr->fsPtr == fsPtr) {
|
|||
|
if (fsRecPtr->prevPtr) {
|
|||
|
fsRecPtr->prevPtr->nextPtr = fsRecPtr->nextPtr;
|
|||
|
} else {
|
|||
|
filesystemList = fsRecPtr->nextPtr;
|
|||
|
}
|
|||
|
if (fsRecPtr->nextPtr) {
|
|||
|
fsRecPtr->nextPtr->prevPtr = fsRecPtr->prevPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Increment the filesystem epoch counter, since existing paths
|
|||
|
* might conceivably now belong to different filesystems. This
|
|||
|
* should also ensure that paths which have cached the filesystem
|
|||
|
* which is about to be deleted do not reference that filesystem
|
|||
|
* (which would of course lead to memory exceptions).
|
|||
|
*/
|
|||
|
|
|||
|
if (++theFilesystemEpoch == 0) {
|
|||
|
++theFilesystemEpoch;
|
|||
|
}
|
|||
|
|
|||
|
ckfree(fsRecPtr);
|
|||
|
|
|||
|
retVal = TCL_OK;
|
|||
|
} else {
|
|||
|
fsRecPtr = fsRecPtr->nextPtr;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Tcl_MutexUnlock(&filesystemMutex);
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSMatchInDirectory --
|
|||
|
*
|
|||
|
* This routine is used by the globbing code to search a directory for
|
|||
|
* all files which match a given pattern. The appropriate function for
|
|||
|
* the filesystem to which pathPtr belongs will be called. If pathPtr
|
|||
|
* does not belong to any filesystem and if it is NULL or the empty
|
|||
|
* string, then we assume the pattern is to be matched in the current
|
|||
|
* working directory. To avoid have the Tcl_FSMatchInDirectoryProc for
|
|||
|
* each filesystem from having to deal with this issue, we create a
|
|||
|
* pathPtr on the fly (equal to the cwd), and then remove it from the
|
|||
|
* results returned. This makes filesystems easy to write, since they can
|
|||
|
* assume the pathPtr passed to them is an ordinary path. In fact this
|
|||
|
* means we could remove such special case handling from Tcl's native
|
|||
|
* filesystems.
|
|||
|
*
|
|||
|
* If 'pattern' is NULL, then pathPtr is assumed to be a fully specified
|
|||
|
* path of a single file/directory which must be checked for existence
|
|||
|
* and correct type.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
*
|
|||
|
* The return value is a standard Tcl result indicating whether an error
|
|||
|
* occurred in globbing. Error messages are placed in interp, but good
|
|||
|
* results are placed in the resultPtr given.
|
|||
|
*
|
|||
|
* Recursive searches, e.g.
|
|||
|
* glob -dir $dir -join * pkgIndex.tcl
|
|||
|
* which must recurse through each directory matching '*' are handled
|
|||
|
* internally by Tcl, by passing specific flags in a modified 'types'
|
|||
|
* parameter. This means the actual filesystem only ever sees patterns
|
|||
|
* which match in a single directory.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The interpreter may have an error message inserted into it.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSMatchInDirectory(
|
|||
|
Tcl_Interp *interp, /* Interpreter to receive error messages, but
|
|||
|
* may be NULL. */
|
|||
|
Tcl_Obj *resultPtr, /* List object to receive results. */
|
|||
|
Tcl_Obj *pathPtr, /* Contains path to directory to search. */
|
|||
|
const char *pattern, /* Pattern to match against. */
|
|||
|
Tcl_GlobTypeData *types) /* Object containing list of acceptable types.
|
|||
|
* May be NULL. In particular the directory
|
|||
|
* flag is very important. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr;
|
|||
|
Tcl_Obj *cwd, *tmpResultPtr, **elemsPtr;
|
|||
|
int resLength, i, ret = -1;
|
|||
|
|
|||
|
if (types != NULL && (types->type & TCL_GLOB_TYPE_MOUNT)) {
|
|||
|
/*
|
|||
|
* We don't currently allow querying of mounts by external code (a
|
|||
|
* valuable future step), so since we're the only function that
|
|||
|
* actually knows about mounts, this means we're being called
|
|||
|
* recursively by ourself. Return no matches.
|
|||
|
*/
|
|||
|
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
if (pathPtr != NULL) {
|
|||
|
fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
} else {
|
|||
|
fsPtr = NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Check if we've successfully mapped the path to a filesystem within
|
|||
|
* which to search.
|
|||
|
*/
|
|||
|
|
|||
|
if (fsPtr != NULL) {
|
|||
|
if (fsPtr->matchInDirectoryProc == NULL) {
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
ret = fsPtr->matchInDirectoryProc(interp, resultPtr, pathPtr, pattern,
|
|||
|
types);
|
|||
|
if (ret == TCL_OK && pattern != NULL) {
|
|||
|
FsAddMountsToGlobResult(resultPtr, pathPtr, pattern, types);
|
|||
|
}
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the path isn't empty, we have no idea how to match files in a
|
|||
|
* directory which belongs to no known filesystem.
|
|||
|
*/
|
|||
|
|
|||
|
if (pathPtr != NULL && TclGetString(pathPtr)[0] != '\0') {
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* We have an empty or NULL path. This is defined to mean we must search
|
|||
|
* for files within the current 'cwd'. We therefore use that, but then
|
|||
|
* since the proc we call will return results which include the cwd we
|
|||
|
* must then trim it off the front of each path in the result. We choose
|
|||
|
* to deal with this here (in the generic code), since if we don't, every
|
|||
|
* single filesystem's implementation of Tcl_FSMatchInDirectory will have
|
|||
|
* to deal with it for us.
|
|||
|
*/
|
|||
|
|
|||
|
cwd = Tcl_FSGetCwd(NULL);
|
|||
|
if (cwd == NULL) {
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"glob couldn't determine the current working directory",
|
|||
|
-1));
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
fsPtr = Tcl_FSGetFileSystemForPath(cwd);
|
|||
|
if (fsPtr != NULL && fsPtr->matchInDirectoryProc != NULL) {
|
|||
|
TclNewObj(tmpResultPtr);
|
|||
|
Tcl_IncrRefCount(tmpResultPtr);
|
|||
|
ret = fsPtr->matchInDirectoryProc(interp, tmpResultPtr, cwd, pattern,
|
|||
|
types);
|
|||
|
if (ret == TCL_OK) {
|
|||
|
FsAddMountsToGlobResult(tmpResultPtr, cwd, pattern, types);
|
|||
|
|
|||
|
/*
|
|||
|
* Note that we know resultPtr and tmpResultPtr are distinct.
|
|||
|
*/
|
|||
|
|
|||
|
ret = Tcl_ListObjGetElements(interp, tmpResultPtr,
|
|||
|
&resLength, &elemsPtr);
|
|||
|
for (i=0 ; ret==TCL_OK && i<resLength ; i++) {
|
|||
|
ret = Tcl_ListObjAppendElement(interp, resultPtr,
|
|||
|
TclFSMakePathRelative(interp, elemsPtr[i], cwd));
|
|||
|
}
|
|||
|
}
|
|||
|
TclDecrRefCount(tmpResultPtr);
|
|||
|
}
|
|||
|
Tcl_DecrRefCount(cwd);
|
|||
|
return ret;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FsAddMountsToGlobResult --
|
|||
|
*
|
|||
|
* This routine is used by the globbing code to take the results of a
|
|||
|
* directory listing and add any mounted paths to that listing. This is
|
|||
|
* required so that simple things like 'glob *' merge mounts and listings
|
|||
|
* correctly.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Modifies the resultPtr.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
FsAddMountsToGlobResult(
|
|||
|
Tcl_Obj *resultPtr, /* The current list of matching paths; must
|
|||
|
* not be shared! */
|
|||
|
Tcl_Obj *pathPtr, /* The directory in question. */
|
|||
|
const char *pattern, /* Pattern to match against. */
|
|||
|
Tcl_GlobTypeData *types) /* Object containing list of acceptable types.
|
|||
|
* May be NULL. In particular the directory
|
|||
|
* flag is very important. */
|
|||
|
{
|
|||
|
int mLength, gLength, i;
|
|||
|
int dir = (types == NULL || (types->type & TCL_GLOB_TYPE_DIR));
|
|||
|
Tcl_Obj *mounts = FsListMounts(pathPtr, pattern);
|
|||
|
|
|||
|
if (mounts == NULL) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
if (Tcl_ListObjLength(NULL, mounts, &mLength) != TCL_OK || mLength == 0) {
|
|||
|
goto endOfMounts;
|
|||
|
}
|
|||
|
if (Tcl_ListObjLength(NULL, resultPtr, &gLength) != TCL_OK) {
|
|||
|
goto endOfMounts;
|
|||
|
}
|
|||
|
for (i=0 ; i<mLength ; i++) {
|
|||
|
Tcl_Obj *mElt;
|
|||
|
int j;
|
|||
|
int found = 0;
|
|||
|
|
|||
|
Tcl_ListObjIndex(NULL, mounts, i, &mElt);
|
|||
|
|
|||
|
for (j=0 ; j<gLength ; j++) {
|
|||
|
Tcl_Obj *gElt;
|
|||
|
|
|||
|
Tcl_ListObjIndex(NULL, resultPtr, j, &gElt);
|
|||
|
if (Tcl_FSEqualPaths(mElt, gElt)) {
|
|||
|
found = 1;
|
|||
|
if (!dir) {
|
|||
|
/*
|
|||
|
* We don't want to list this.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_ListObjReplace(NULL, resultPtr, j, 1, 0, NULL);
|
|||
|
gLength--;
|
|||
|
}
|
|||
|
break; /* Break out of for loop. */
|
|||
|
}
|
|||
|
}
|
|||
|
if (!found && dir) {
|
|||
|
Tcl_Obj *norm;
|
|||
|
int len, mlen;
|
|||
|
|
|||
|
/*
|
|||
|
* We know mElt is absolute normalized and lies inside pathPtr, so
|
|||
|
* now we must add to the result the right representation of mElt,
|
|||
|
* i.e. the representation which is relative to pathPtr.
|
|||
|
*/
|
|||
|
|
|||
|
norm = Tcl_FSGetNormalizedPath(NULL, pathPtr);
|
|||
|
if (norm != NULL) {
|
|||
|
const char *path, *mount;
|
|||
|
|
|||
|
mount = Tcl_GetStringFromObj(mElt, &mlen);
|
|||
|
path = Tcl_GetStringFromObj(norm, &len);
|
|||
|
if (path[len-1] == '/') {
|
|||
|
/*
|
|||
|
* Deal with the root of the volume.
|
|||
|
*/
|
|||
|
|
|||
|
len--;
|
|||
|
}
|
|||
|
len++; /* account for '/' in the mElt [Bug 1602539] */
|
|||
|
mElt = TclNewFSPathObj(pathPtr, mount + len, mlen - len);
|
|||
|
Tcl_ListObjAppendElement(NULL, resultPtr, mElt);
|
|||
|
}
|
|||
|
/*
|
|||
|
* No need to increment gLength, since we don't want to compare
|
|||
|
* mounts against mounts.
|
|||
|
*/
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
endOfMounts:
|
|||
|
Tcl_DecrRefCount(mounts);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSMountsChanged --
|
|||
|
*
|
|||
|
* Notify the filesystem that the available mounted filesystems (or
|
|||
|
* within any one filesystem type, the number or location of mount
|
|||
|
* points) have changed.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The global filesystem variable 'theFilesystemEpoch' is incremented.
|
|||
|
* The effect of this is to make all cached path representations invalid.
|
|||
|
* Clearly it should only therefore be called when it is really required!
|
|||
|
* There are a few circumstances when it should be called:
|
|||
|
*
|
|||
|
* (1) when a new filesystem is registered or unregistered. Strictly
|
|||
|
* speaking this is only necessary if the new filesystem accepts file
|
|||
|
* paths as is (normally the filesystem itself is really a shell which
|
|||
|
* hasn't yet had any mount points established and so its
|
|||
|
* 'pathInFilesystem' proc will always fail). However, for safety, Tcl
|
|||
|
* always calls this for you in these circumstances.
|
|||
|
*
|
|||
|
* (2) when additional mount points are established inside any existing
|
|||
|
* filesystem (except the native fs)
|
|||
|
*
|
|||
|
* (3) when any filesystem (except the native fs) changes the list of
|
|||
|
* available volumes.
|
|||
|
*
|
|||
|
* (4) when the mapping from a string representation of a file to a full,
|
|||
|
* normalized path changes. For example, if 'env(HOME)' is modified, then
|
|||
|
* any path containing '~' will map to a different filesystem location.
|
|||
|
* Therefore all such paths need to have their internal representation
|
|||
|
* invalidated.
|
|||
|
*
|
|||
|
* Tcl has no control over (2) and (3), so any registered filesystem must
|
|||
|
* make sure it calls this function when those situations occur.
|
|||
|
*
|
|||
|
* (Note: the reason for the exception in 2,3 for the native filesystem
|
|||
|
* is that the native filesystem by default claims all unknown files even
|
|||
|
* if it really doesn't understand them or if they don't exist).
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
Tcl_FSMountsChanged(
|
|||
|
const Tcl_Filesystem *fsPtr)
|
|||
|
{
|
|||
|
/*
|
|||
|
* We currently don't do anything with this parameter. We could in the
|
|||
|
* future only invalidate files for this filesystem or otherwise take more
|
|||
|
* advanced action.
|
|||
|
*/
|
|||
|
|
|||
|
(void)fsPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* Increment the filesystem epoch counter, since existing paths might now
|
|||
|
* belong to different filesystems.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_MutexLock(&filesystemMutex);
|
|||
|
if (++theFilesystemEpoch == 0) {
|
|||
|
++theFilesystemEpoch;
|
|||
|
}
|
|||
|
Tcl_MutexUnlock(&filesystemMutex);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSData --
|
|||
|
*
|
|||
|
* Retrieve the clientData field for the filesystem given, or NULL if
|
|||
|
* that filesystem is not registered.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A clientData value, or NULL. Note that if the filesystem was
|
|||
|
* registered with a NULL clientData field, this function will return
|
|||
|
* that NULL value.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
ClientData
|
|||
|
Tcl_FSData(
|
|||
|
const Tcl_Filesystem *fsPtr) /* The filesystem record to query. */
|
|||
|
{
|
|||
|
ClientData retVal = NULL;
|
|||
|
FilesystemRecord *fsRecPtr = FsGetFirstFilesystem();
|
|||
|
|
|||
|
/*
|
|||
|
* Traverse the list of filesystems look for a particular one. If found,
|
|||
|
* return that filesystem's clientData (originally provided when calling
|
|||
|
* Tcl_FSRegister).
|
|||
|
*/
|
|||
|
|
|||
|
while ((retVal == NULL) && (fsRecPtr != NULL)) {
|
|||
|
if (fsRecPtr->fsPtr == fsPtr) {
|
|||
|
retVal = fsRecPtr->clientData;
|
|||
|
}
|
|||
|
fsRecPtr = fsRecPtr->nextPtr;
|
|||
|
}
|
|||
|
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclFSNormalizeToUniquePath --
|
|||
|
*
|
|||
|
* Takes a path specification containing no ../, ./ sequences, and
|
|||
|
* converts it into a unique path for the given platform. On Unix, this
|
|||
|
* means the path must be free of symbolic links/aliases, and on Windows
|
|||
|
* it means we want the long form, with that long form's case-dependence
|
|||
|
* (which gives us a unique, case-dependent path).
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The pathPtr is modified in place. The return value is the last byte
|
|||
|
* offset which was recognised in the path string.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None (beyond the memory allocation for the result).
|
|||
|
*
|
|||
|
* Special notes:
|
|||
|
* If the filesystem-specific normalizePathProcs can re-introduce ../, ./
|
|||
|
* sequences into the path, then this function will not return the
|
|||
|
* correct result. This may be possible with symbolic links on unix.
|
|||
|
*
|
|||
|
* Important assumption: if startAt is non-zero, it must point to a
|
|||
|
* directory separator that we know exists and is already normalized (so
|
|||
|
* it is important not to point to the char just after the separator).
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclFSNormalizeToUniquePath(
|
|||
|
Tcl_Interp *interp, /* Used for error messages. */
|
|||
|
Tcl_Obj *pathPtr, /* The path to normalize in place. */
|
|||
|
int startAt) /* Start at this char-offset. */
|
|||
|
{
|
|||
|
FilesystemRecord *fsRecPtr, *firstFsRecPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* Call each of the "normalise path" functions in succession. This is a
|
|||
|
* special case, in which if we have a native filesystem handler, we call
|
|||
|
* it first. This is because the root of Tcl's filesystem is always a
|
|||
|
* native filesystem (i.e. '/' on unix is native).
|
|||
|
*/
|
|||
|
|
|||
|
firstFsRecPtr = FsGetFirstFilesystem();
|
|||
|
|
|||
|
Claim();
|
|||
|
for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) {
|
|||
|
if (fsRecPtr->fsPtr != &tclNativeFilesystem) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* TODO: Assume that we always find the native file system; it should
|
|||
|
* always be there...
|
|||
|
*/
|
|||
|
|
|||
|
if (fsRecPtr->fsPtr->normalizePathProc != NULL) {
|
|||
|
startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr,
|
|||
|
startAt);
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
for (fsRecPtr=firstFsRecPtr; fsRecPtr!=NULL; fsRecPtr=fsRecPtr->nextPtr) {
|
|||
|
/*
|
|||
|
* Skip the native system next time through.
|
|||
|
*/
|
|||
|
|
|||
|
if (fsRecPtr->fsPtr == &tclNativeFilesystem) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (fsRecPtr->fsPtr->normalizePathProc != NULL) {
|
|||
|
startAt = fsRecPtr->fsPtr->normalizePathProc(interp, pathPtr,
|
|||
|
startAt);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* We could add an efficiency check like this:
|
|||
|
* if (retVal == length-of(pathPtr)) {break;}
|
|||
|
* but there's not much benefit.
|
|||
|
*/
|
|||
|
}
|
|||
|
Disclaim();
|
|||
|
|
|||
|
return startAt;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclGetOpenMode --
|
|||
|
*
|
|||
|
* This routine is an obsolete, limited version of TclGetOpenModeEx()
|
|||
|
* below. It exists only to satisfy any extensions imprudently using it
|
|||
|
* via Tcl's internal stubs table.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Same as TclGetOpenModeEx().
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Same as TclGetOpenModeEx().
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclGetOpenMode(
|
|||
|
Tcl_Interp *interp, /* Interpreter to use for error reporting -
|
|||
|
* may be NULL. */
|
|||
|
const char *modeString, /* Mode string, e.g. "r+" or "RDONLY CREAT" */
|
|||
|
int *seekFlagPtr) /* Set this to 1 if the caller should seek to
|
|||
|
* EOF during the opening of the file. */
|
|||
|
{
|
|||
|
int binary = 0;
|
|||
|
return TclGetOpenModeEx(interp, modeString, seekFlagPtr, &binary);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclGetOpenModeEx --
|
|||
|
*
|
|||
|
* Computes a POSIX mode mask for opening a file, from a given string,
|
|||
|
* and also sets flags to indicate whether the caller should seek to EOF
|
|||
|
* after opening the file, and whether the caller should configure the
|
|||
|
* channel for binary data.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* On success, returns mode to pass to "open". If an error occurs, the
|
|||
|
* return value is -1 and if interp is not NULL, sets interp's result
|
|||
|
* object to an error message.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Sets the integer referenced by seekFlagPtr to 1 to tell the caller to
|
|||
|
* seek to EOF after opening the file, or to 0 otherwise. Sets the
|
|||
|
* integer referenced by binaryPtr to 1 to tell the caller to seek to
|
|||
|
* configure the channel for binary data, or to 0 otherwise.
|
|||
|
*
|
|||
|
* Special note:
|
|||
|
* This code is based on a prototype implementation contributed by Mark
|
|||
|
* Diekhans.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclGetOpenModeEx(
|
|||
|
Tcl_Interp *interp, /* Interpreter to use for error reporting -
|
|||
|
* may be NULL. */
|
|||
|
const char *modeString, /* Mode string, e.g. "r+" or "RDONLY CREAT" */
|
|||
|
int *seekFlagPtr, /* Set this to 1 if the caller should seek to
|
|||
|
* EOF during the opening of the file. */
|
|||
|
int *binaryPtr) /* Set this to 1 if the caller should
|
|||
|
* configure the opened channel for binary
|
|||
|
* operations. */
|
|||
|
{
|
|||
|
int mode, modeArgc, c, i, gotRW;
|
|||
|
const char **modeArgv, *flag;
|
|||
|
#define RW_MODES (O_RDONLY|O_WRONLY|O_RDWR)
|
|||
|
|
|||
|
/*
|
|||
|
* Check for the simpler fopen-like access modes (e.g. "r"). They are
|
|||
|
* distinguished from the POSIX access modes by the presence of a
|
|||
|
* lower-case first letter.
|
|||
|
*/
|
|||
|
|
|||
|
*seekFlagPtr = 0;
|
|||
|
*binaryPtr = 0;
|
|||
|
mode = 0;
|
|||
|
|
|||
|
/*
|
|||
|
* Guard against international characters before using byte oriented
|
|||
|
* routines.
|
|||
|
*/
|
|||
|
|
|||
|
if (!(modeString[0] & 0x80)
|
|||
|
&& islower(UCHAR(modeString[0]))) { /* INTL: ISO only. */
|
|||
|
switch (modeString[0]) {
|
|||
|
case 'r':
|
|||
|
mode = O_RDONLY;
|
|||
|
break;
|
|||
|
case 'w':
|
|||
|
mode = O_WRONLY|O_CREAT|O_TRUNC;
|
|||
|
break;
|
|||
|
case 'a':
|
|||
|
/*
|
|||
|
* Added O_APPEND for proper automatic seek-to-end-on-write by the
|
|||
|
* OS. [Bug 680143]
|
|||
|
*/
|
|||
|
|
|||
|
mode = O_WRONLY|O_CREAT|O_APPEND;
|
|||
|
*seekFlagPtr = 1;
|
|||
|
break;
|
|||
|
default:
|
|||
|
goto error;
|
|||
|
}
|
|||
|
i = 1;
|
|||
|
while (i<3 && modeString[i]) {
|
|||
|
if (modeString[i] == modeString[i-1]) {
|
|||
|
goto error;
|
|||
|
}
|
|||
|
switch (modeString[i++]) {
|
|||
|
case '+':
|
|||
|
/*
|
|||
|
* Must remove the O_APPEND flag so that the seek command
|
|||
|
* works. [Bug 1773127]
|
|||
|
*/
|
|||
|
|
|||
|
mode &= ~(O_RDONLY|O_WRONLY|O_APPEND);
|
|||
|
mode |= O_RDWR;
|
|||
|
break;
|
|||
|
case 'b':
|
|||
|
*binaryPtr = 1;
|
|||
|
break;
|
|||
|
default:
|
|||
|
goto error;
|
|||
|
}
|
|||
|
}
|
|||
|
if (modeString[i] != 0) {
|
|||
|
goto error;
|
|||
|
}
|
|||
|
return mode;
|
|||
|
|
|||
|
error:
|
|||
|
*seekFlagPtr = 0;
|
|||
|
*binaryPtr = 0;
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"illegal access mode \"%s\"", modeString));
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* The access modes are specified using a list of POSIX modes such as
|
|||
|
* O_CREAT.
|
|||
|
*
|
|||
|
* IMPORTANT NOTE: We rely on Tcl_SplitList working correctly when a NULL
|
|||
|
* interpreter is passed in.
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_SplitList(interp, modeString, &modeArgc, &modeArgv) != TCL_OK) {
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_AddErrorInfo(interp,
|
|||
|
"\n while processing open access modes \"");
|
|||
|
Tcl_AddErrorInfo(interp, modeString);
|
|||
|
Tcl_AddErrorInfo(interp, "\"");
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
gotRW = 0;
|
|||
|
for (i = 0; i < modeArgc; i++) {
|
|||
|
flag = modeArgv[i];
|
|||
|
c = flag[0];
|
|||
|
if ((c == 'R') && (strcmp(flag, "RDONLY") == 0)) {
|
|||
|
mode = (mode & ~RW_MODES) | O_RDONLY;
|
|||
|
gotRW = 1;
|
|||
|
} else if ((c == 'W') && (strcmp(flag, "WRONLY") == 0)) {
|
|||
|
mode = (mode & ~RW_MODES) | O_WRONLY;
|
|||
|
gotRW = 1;
|
|||
|
} else if ((c == 'R') && (strcmp(flag, "RDWR") == 0)) {
|
|||
|
mode = (mode & ~RW_MODES) | O_RDWR;
|
|||
|
gotRW = 1;
|
|||
|
} else if ((c == 'A') && (strcmp(flag, "APPEND") == 0)) {
|
|||
|
mode |= O_APPEND;
|
|||
|
*seekFlagPtr = 1;
|
|||
|
} else if ((c == 'C') && (strcmp(flag, "CREAT") == 0)) {
|
|||
|
mode |= O_CREAT;
|
|||
|
} else if ((c == 'E') && (strcmp(flag, "EXCL") == 0)) {
|
|||
|
mode |= O_EXCL;
|
|||
|
|
|||
|
} else if ((c == 'N') && (strcmp(flag, "NOCTTY") == 0)) {
|
|||
|
#ifdef O_NOCTTY
|
|||
|
mode |= O_NOCTTY;
|
|||
|
#else
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"access mode \"%s\" not supported by this system",
|
|||
|
flag));
|
|||
|
}
|
|||
|
ckfree(modeArgv);
|
|||
|
return -1;
|
|||
|
#endif
|
|||
|
|
|||
|
} else if ((c == 'N') && (strcmp(flag, "NONBLOCK") == 0)) {
|
|||
|
#ifdef O_NONBLOCK
|
|||
|
mode |= O_NONBLOCK;
|
|||
|
#else
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"access mode \"%s\" not supported by this system",
|
|||
|
flag));
|
|||
|
}
|
|||
|
ckfree(modeArgv);
|
|||
|
return -1;
|
|||
|
#endif
|
|||
|
|
|||
|
} else if ((c == 'T') && (strcmp(flag, "TRUNC") == 0)) {
|
|||
|
mode |= O_TRUNC;
|
|||
|
} else if ((c == 'B') && (strcmp(flag, "BINARY") == 0)) {
|
|||
|
*binaryPtr = 1;
|
|||
|
} else {
|
|||
|
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"invalid access mode \"%s\": must be RDONLY, WRONLY, "
|
|||
|
"RDWR, APPEND, BINARY, CREAT, EXCL, NOCTTY, NONBLOCK,"
|
|||
|
" or TRUNC", flag));
|
|||
|
}
|
|||
|
ckfree(modeArgv);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
ckfree(modeArgv);
|
|||
|
|
|||
|
if (!gotRW) {
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"access mode must include either RDONLY, WRONLY, or RDWR",
|
|||
|
-1));
|
|||
|
}
|
|||
|
return -1;
|
|||
|
}
|
|||
|
return mode;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSEvalFile, Tcl_FSEvalFileEx, TclNREvalFile --
|
|||
|
*
|
|||
|
* Read in a file and process the entire file as one gigantic Tcl
|
|||
|
* command. Tcl_FSEvalFile is Tcl_FSEvalFileEx without encoding argument.
|
|||
|
* TclNREvalFile is an NRE-enabled version of Tcl_FSEvalFileEx.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A standard Tcl result, which is either the result of executing the
|
|||
|
* file or an error indicating why the file couldn't be read.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Depends on the commands in the file. During the evaluation of the
|
|||
|
* contents of the file, iPtr->scriptFile is made to point to pathPtr
|
|||
|
* (the old value is cached and replaced when this function returns).
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSEvalFile(
|
|||
|
Tcl_Interp *interp, /* Interpreter in which to process file. */
|
|||
|
Tcl_Obj *pathPtr) /* Path of file to process. Tilde-substitution
|
|||
|
* will be performed on this name. */
|
|||
|
{
|
|||
|
return Tcl_FSEvalFileEx(interp, pathPtr, NULL);
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSEvalFileEx(
|
|||
|
Tcl_Interp *interp, /* Interpreter in which to process file. */
|
|||
|
Tcl_Obj *pathPtr, /* Path of file to process. Tilde-substitution
|
|||
|
* will be performed on this name. */
|
|||
|
const char *encodingName) /* If non-NULL, then use this encoding for the
|
|||
|
* file. NULL means use the system encoding. */
|
|||
|
{
|
|||
|
int length, result = TCL_ERROR;
|
|||
|
Tcl_StatBuf statBuf;
|
|||
|
Tcl_Obj *oldScriptFile;
|
|||
|
Interp *iPtr;
|
|||
|
const char *string;
|
|||
|
Tcl_Channel chan;
|
|||
|
Tcl_Obj *objPtr;
|
|||
|
|
|||
|
if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
if (Tcl_FSStat(pathPtr, &statBuf) == -1) {
|
|||
|
Tcl_SetErrno(errno);
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't read file \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
return result;
|
|||
|
}
|
|||
|
chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644);
|
|||
|
if (chan == NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't read file \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* The eofchar is \32 (^Z). This is the usual on Windows, but we effect
|
|||
|
* this cross-platform to allow for scripted documents. [Bug: 2040]
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_SetChannelOption(interp, chan, "-eofchar", "\32 {}");
|
|||
|
|
|||
|
/*
|
|||
|
* If the encoding is specified, set it for the channel. Else don't touch
|
|||
|
* it (and use the system encoding) Report error on unknown encoding.
|
|||
|
*/
|
|||
|
|
|||
|
if (encodingName != NULL) {
|
|||
|
if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName)
|
|||
|
!= TCL_OK) {
|
|||
|
Tcl_Close(interp,chan);
|
|||
|
return result;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
objPtr = Tcl_NewObj();
|
|||
|
Tcl_IncrRefCount(objPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Try to read first character of stream, so we can check for utf-8 BOM to
|
|||
|
* be handled especially.
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_ReadChars(chan, objPtr, 1, 0) < 0) {
|
|||
|
Tcl_Close(interp, chan);
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't read file \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
goto end;
|
|||
|
}
|
|||
|
string = Tcl_GetString(objPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* If first character is not a BOM, append the remaining characters,
|
|||
|
* otherwise replace them. [Bug 3466099]
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_ReadChars(chan, objPtr, -1,
|
|||
|
memcmp(string, "\xEF\xBB\xBF", 3)) < 0) {
|
|||
|
Tcl_Close(interp, chan);
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't read file \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
goto end;
|
|||
|
}
|
|||
|
|
|||
|
if (Tcl_Close(interp, chan) != TCL_OK) {
|
|||
|
goto end;
|
|||
|
}
|
|||
|
|
|||
|
iPtr = (Interp *) interp;
|
|||
|
oldScriptFile = iPtr->scriptFile;
|
|||
|
iPtr->scriptFile = pathPtr;
|
|||
|
Tcl_IncrRefCount(iPtr->scriptFile);
|
|||
|
string = Tcl_GetStringFromObj(objPtr, &length);
|
|||
|
|
|||
|
/*
|
|||
|
* TIP #280 Force the evaluator to open a frame for a sourced file.
|
|||
|
*/
|
|||
|
|
|||
|
iPtr->evalFlags |= TCL_EVAL_FILE;
|
|||
|
result = TclEvalEx(interp, string, length, 0, 1, NULL, string);
|
|||
|
|
|||
|
/*
|
|||
|
* Now we have to be careful; the script may have changed the
|
|||
|
* iPtr->scriptFile value, so we must reset it without assuming it still
|
|||
|
* points to 'pathPtr'.
|
|||
|
*/
|
|||
|
|
|||
|
if (iPtr->scriptFile != NULL) {
|
|||
|
Tcl_DecrRefCount(iPtr->scriptFile);
|
|||
|
}
|
|||
|
iPtr->scriptFile = oldScriptFile;
|
|||
|
|
|||
|
if (result == TCL_RETURN) {
|
|||
|
result = TclUpdateReturnInfo(iPtr);
|
|||
|
} else if (result == TCL_ERROR) {
|
|||
|
/*
|
|||
|
* Record information telling where the error occurred.
|
|||
|
*/
|
|||
|
|
|||
|
const char *pathString = Tcl_GetStringFromObj(pathPtr, &length);
|
|||
|
int limit = 150;
|
|||
|
int overflow = (length > limit);
|
|||
|
|
|||
|
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
|
|||
|
"\n (file \"%.*s%s\" line %d)",
|
|||
|
(overflow ? limit : length), pathString,
|
|||
|
(overflow ? "..." : ""), Tcl_GetErrorLine(interp)));
|
|||
|
}
|
|||
|
|
|||
|
end:
|
|||
|
Tcl_DecrRefCount(objPtr);
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
TclNREvalFile(
|
|||
|
Tcl_Interp *interp, /* Interpreter in which to process file. */
|
|||
|
Tcl_Obj *pathPtr, /* Path of file to process. Tilde-substitution
|
|||
|
* will be performed on this name. */
|
|||
|
const char *encodingName) /* If non-NULL, then use this encoding for the
|
|||
|
* file. NULL means use the system encoding. */
|
|||
|
{
|
|||
|
Tcl_StatBuf statBuf;
|
|||
|
Tcl_Obj *oldScriptFile, *objPtr;
|
|||
|
Interp *iPtr;
|
|||
|
Tcl_Channel chan;
|
|||
|
const char *string;
|
|||
|
|
|||
|
if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
if (Tcl_FSStat(pathPtr, &statBuf) == -1) {
|
|||
|
Tcl_SetErrno(errno);
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't read file \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
chan = Tcl_FSOpenFileChannel(interp, pathPtr, "r", 0644);
|
|||
|
if (chan == NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't read file \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* The eofchar is \32 (^Z). This is the usual on Windows, but we effect
|
|||
|
* this cross-platform to allow for scripted documents. [Bug: 2040]
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_SetChannelOption(interp, chan, "-eofchar", "\32 {}");
|
|||
|
|
|||
|
/*
|
|||
|
* If the encoding is specified, set it for the channel. Else don't touch
|
|||
|
* it (and use the system encoding) Report error on unknown encoding.
|
|||
|
*/
|
|||
|
|
|||
|
if (encodingName != NULL) {
|
|||
|
if (Tcl_SetChannelOption(interp, chan, "-encoding", encodingName)
|
|||
|
!= TCL_OK) {
|
|||
|
Tcl_Close(interp,chan);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
objPtr = Tcl_NewObj();
|
|||
|
Tcl_IncrRefCount(objPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* Try to read first character of stream, so we can check for utf-8 BOM to
|
|||
|
* be handled especially.
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_ReadChars(chan, objPtr, 1, 0) < 0) {
|
|||
|
Tcl_Close(interp, chan);
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't read file \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
Tcl_DecrRefCount(objPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
string = Tcl_GetString(objPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* If first character is not a BOM, append the remaining characters,
|
|||
|
* otherwise replace them. [Bug 3466099]
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_ReadChars(chan, objPtr, -1,
|
|||
|
memcmp(string, "\xEF\xBB\xBF", 3)) < 0) {
|
|||
|
Tcl_Close(interp, chan);
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't read file \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
Tcl_DecrRefCount(objPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
if (Tcl_Close(interp, chan) != TCL_OK) {
|
|||
|
Tcl_DecrRefCount(objPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
iPtr = (Interp *) interp;
|
|||
|
oldScriptFile = iPtr->scriptFile;
|
|||
|
iPtr->scriptFile = pathPtr;
|
|||
|
Tcl_IncrRefCount(iPtr->scriptFile);
|
|||
|
|
|||
|
/*
|
|||
|
* TIP #280: Force the evaluator to open a frame for a sourced file.
|
|||
|
*/
|
|||
|
|
|||
|
iPtr->evalFlags |= TCL_EVAL_FILE;
|
|||
|
TclNRAddCallback(interp, EvalFileCallback, oldScriptFile, pathPtr, objPtr,
|
|||
|
NULL);
|
|||
|
return TclNREvalObjEx(interp, objPtr, 0, NULL, INT_MIN);
|
|||
|
}
|
|||
|
|
|||
|
static int
|
|||
|
EvalFileCallback(
|
|||
|
ClientData data[],
|
|||
|
Tcl_Interp *interp,
|
|||
|
int result)
|
|||
|
{
|
|||
|
Interp *iPtr = (Interp *) interp;
|
|||
|
Tcl_Obj *oldScriptFile = data[0];
|
|||
|
Tcl_Obj *pathPtr = data[1];
|
|||
|
Tcl_Obj *objPtr = data[2];
|
|||
|
|
|||
|
/*
|
|||
|
* Now we have to be careful; the script may have changed the
|
|||
|
* iPtr->scriptFile value, so we must reset it without assuming it still
|
|||
|
* points to 'pathPtr'.
|
|||
|
*/
|
|||
|
|
|||
|
if (iPtr->scriptFile != NULL) {
|
|||
|
Tcl_DecrRefCount(iPtr->scriptFile);
|
|||
|
}
|
|||
|
iPtr->scriptFile = oldScriptFile;
|
|||
|
|
|||
|
if (result == TCL_RETURN) {
|
|||
|
result = TclUpdateReturnInfo(iPtr);
|
|||
|
} else if (result == TCL_ERROR) {
|
|||
|
/*
|
|||
|
* Record information telling where the error occurred.
|
|||
|
*/
|
|||
|
|
|||
|
int length;
|
|||
|
const char *pathString = Tcl_GetStringFromObj(pathPtr, &length);
|
|||
|
const int limit = 150;
|
|||
|
int overflow = (length > limit);
|
|||
|
|
|||
|
Tcl_AppendObjToErrorInfo(interp, Tcl_ObjPrintf(
|
|||
|
"\n (file \"%.*s%s\" line %d)",
|
|||
|
(overflow ? limit : length), pathString,
|
|||
|
(overflow ? "..." : ""), Tcl_GetErrorLine(interp)));
|
|||
|
}
|
|||
|
|
|||
|
Tcl_DecrRefCount(objPtr);
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_GetErrno --
|
|||
|
*
|
|||
|
* Gets the current value of the Tcl error code variable. This is
|
|||
|
* currently the global variable "errno" but could in the future change
|
|||
|
* to something else.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The value of the Tcl error code variable.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None. Note that the value of the Tcl error code variable is UNDEFINED
|
|||
|
* if a call to Tcl_SetErrno did not precede this call.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_GetErrno(void)
|
|||
|
{
|
|||
|
/*
|
|||
|
* On some platforms, errno is really a thread local (implemented by the C
|
|||
|
* library).
|
|||
|
*/
|
|||
|
|
|||
|
return errno;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_SetErrno --
|
|||
|
*
|
|||
|
* Sets the Tcl error code variable to the supplied value. On some saner
|
|||
|
* platforms this is actually a thread-local (this is implemented in the
|
|||
|
* C library) but this is *really* unsafe to assume!
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Modifies the value of the Tcl error code variable.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
Tcl_SetErrno(
|
|||
|
int err) /* The new value. */
|
|||
|
{
|
|||
|
/*
|
|||
|
* On some platforms, errno is really a thread local (implemented by the C
|
|||
|
* library).
|
|||
|
*/
|
|||
|
|
|||
|
errno = err;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_PosixError --
|
|||
|
*
|
|||
|
* This function is typically called after UNIX kernel calls return
|
|||
|
* errors. It stores machine-readable information about the error in
|
|||
|
* errorCode field of interp and returns an information string for the
|
|||
|
* caller's use.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The return value is a human-readable string describing the error.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The errorCode field of the interp is set.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
const char *
|
|||
|
Tcl_PosixError(
|
|||
|
Tcl_Interp *interp) /* Interpreter whose errorCode field is to be
|
|||
|
* set. */
|
|||
|
{
|
|||
|
const char *id, *msg;
|
|||
|
|
|||
|
msg = Tcl_ErrnoMsg(errno);
|
|||
|
id = Tcl_ErrnoId();
|
|||
|
if (interp) {
|
|||
|
Tcl_SetErrorCode(interp, "POSIX", id, msg, NULL);
|
|||
|
}
|
|||
|
return msg;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSStat --
|
|||
|
*
|
|||
|
* This function replaces the library version of stat and lsat.
|
|||
|
*
|
|||
|
* The appropriate function for the filesystem to which pathPtr belongs
|
|||
|
* will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See stat documentation.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See stat documentation.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSStat(
|
|||
|
Tcl_Obj *pathPtr, /* Path of file to stat (in current CP). */
|
|||
|
Tcl_StatBuf *buf) /* Filled with results of stat call. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->statProc != NULL) {
|
|||
|
return fsPtr->statProc(pathPtr, buf);
|
|||
|
}
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSLstat --
|
|||
|
*
|
|||
|
* This function replaces the library version of lstat. The appropriate
|
|||
|
* function for the filesystem to which pathPtr belongs will be called.
|
|||
|
* If no 'lstat' function is listed, but a 'stat' function is, then Tcl
|
|||
|
* will fall back on the stat function.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See lstat documentation.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See lstat documentation.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSLstat(
|
|||
|
Tcl_Obj *pathPtr, /* Path of file to stat (in current CP). */
|
|||
|
Tcl_StatBuf *buf) /* Filled with results of stat call. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL) {
|
|||
|
if (fsPtr->lstatProc != NULL) {
|
|||
|
return fsPtr->lstatProc(pathPtr, buf);
|
|||
|
}
|
|||
|
if (fsPtr->statProc != NULL) {
|
|||
|
return fsPtr->statProc(pathPtr, buf);
|
|||
|
}
|
|||
|
}
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSAccess --
|
|||
|
*
|
|||
|
* This function replaces the library version of access. The appropriate
|
|||
|
* function for the filesystem to which pathPtr belongs will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See access documentation.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See access documentation.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSAccess(
|
|||
|
Tcl_Obj *pathPtr, /* Path of file to access (in current CP). */
|
|||
|
int mode) /* Permission setting. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->accessProc != NULL) {
|
|||
|
return fsPtr->accessProc(pathPtr, mode);
|
|||
|
}
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSOpenFileChannel --
|
|||
|
*
|
|||
|
* The appropriate function for the filesystem to which pathPtr belongs
|
|||
|
* will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The new channel or NULL, if the named file could not be opened.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* May open the channel and may cause creation of a file on the file
|
|||
|
* system.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Channel
|
|||
|
Tcl_FSOpenFileChannel(
|
|||
|
Tcl_Interp *interp, /* Interpreter for error reporting; can be
|
|||
|
* NULL. */
|
|||
|
Tcl_Obj *pathPtr, /* Name of file to open. */
|
|||
|
const char *modeString, /* A list of POSIX open modes or a string such
|
|||
|
* as "rw". */
|
|||
|
int permissions) /* If the open involves creating a file, with
|
|||
|
* what modes to create it? */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr;
|
|||
|
Tcl_Channel retVal = NULL;
|
|||
|
|
|||
|
/*
|
|||
|
* We need this just to ensure we return the correct error messages under
|
|||
|
* some circumstances.
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_FSGetNormalizedPath(interp, pathPtr) == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
if (fsPtr != NULL && fsPtr->openFileChannelProc != NULL) {
|
|||
|
int mode, seekFlag, binary;
|
|||
|
|
|||
|
/*
|
|||
|
* Parse the mode, picking up whether we want to seek to start with
|
|||
|
* and/or set the channel automatically into binary mode.
|
|||
|
*/
|
|||
|
|
|||
|
mode = TclGetOpenModeEx(interp, modeString, &seekFlag, &binary);
|
|||
|
if (mode == -1) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Do the actual open() call.
|
|||
|
*/
|
|||
|
|
|||
|
retVal = fsPtr->openFileChannelProc(interp, pathPtr, mode,
|
|||
|
permissions);
|
|||
|
if (retVal == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Apply appropriate flags parsed out above.
|
|||
|
*/
|
|||
|
|
|||
|
if (seekFlag && Tcl_Seek(retVal, (Tcl_WideInt) 0, SEEK_END)
|
|||
|
< (Tcl_WideInt) 0) {
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"could not seek to end of file while opening \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
}
|
|||
|
Tcl_Close(NULL, retVal);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
if (binary) {
|
|||
|
Tcl_SetChannelOption(interp, retVal, "-translation", "binary");
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* File doesn't belong to any filesystem that can open it.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't open \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
}
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSUtime --
|
|||
|
*
|
|||
|
* This function replaces the library version of utime. The appropriate
|
|||
|
* function for the filesystem to which pathPtr belongs will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See utime documentation.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See utime documentation.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSUtime(
|
|||
|
Tcl_Obj *pathPtr, /* File to change access/modification
|
|||
|
* times. */
|
|||
|
struct utimbuf *tval) /* Structure containing access/modification
|
|||
|
* times to use. Should not be modified. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->utimeProc != NULL) {
|
|||
|
return fsPtr->utimeProc(pathPtr, tval);
|
|||
|
}
|
|||
|
/* TODO: set errno here? Tcl_SetErrno(ENOENT); */
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* NativeFileAttrStrings --
|
|||
|
*
|
|||
|
* This function implements the platform dependent 'file attributes'
|
|||
|
* subcommand, for the native filesystem, for listing the set of possible
|
|||
|
* attribute strings. This function is part of Tcl's native filesystem
|
|||
|
* support, and is placed here because it is shared by Unix and Windows
|
|||
|
* code.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* An array of strings
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static const char *const *
|
|||
|
NativeFileAttrStrings(
|
|||
|
Tcl_Obj *pathPtr,
|
|||
|
Tcl_Obj **objPtrRef)
|
|||
|
{
|
|||
|
return tclpFileAttrStrings;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* NativeFileAttrsGet --
|
|||
|
*
|
|||
|
* This function implements the platform dependent 'file attributes'
|
|||
|
* subcommand, for the native filesystem, for 'get' operations. This
|
|||
|
* function is part of Tcl's native filesystem support, and is placed
|
|||
|
* here because it is shared by Unix and Windows code.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl return code. The object placed in objPtrRef (if TCL_OK
|
|||
|
* was returned) is likely to have a refCount of zero. Either way we must
|
|||
|
* either store it somewhere (e.g. the Tcl result), or Incr/Decr its
|
|||
|
* refCount to ensure it is properly freed.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
NativeFileAttrsGet(
|
|||
|
Tcl_Interp *interp, /* The interpreter for error reporting. */
|
|||
|
int index, /* index of the attribute command. */
|
|||
|
Tcl_Obj *pathPtr, /* path of file we are operating on. */
|
|||
|
Tcl_Obj **objPtrRef) /* for output. */
|
|||
|
{
|
|||
|
return tclpFileAttrProcs[index].getProc(interp, index, pathPtr,objPtrRef);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* NativeFileAttrsSet --
|
|||
|
*
|
|||
|
* This function implements the platform dependent 'file attributes'
|
|||
|
* subcommand, for the native filesystem, for 'set' operations. This
|
|||
|
* function is part of Tcl's native filesystem support, and is placed
|
|||
|
* here because it is shared by Unix and Windows code.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl return code.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static int
|
|||
|
NativeFileAttrsSet(
|
|||
|
Tcl_Interp *interp, /* The interpreter for error reporting. */
|
|||
|
int index, /* index of the attribute command. */
|
|||
|
Tcl_Obj *pathPtr, /* path of file we are operating on. */
|
|||
|
Tcl_Obj *objPtr) /* set to this value. */
|
|||
|
{
|
|||
|
return tclpFileAttrProcs[index].setProc(interp, index, pathPtr, objPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSFileAttrStrings --
|
|||
|
*
|
|||
|
* This function implements part of the hookable 'file attributes'
|
|||
|
* subcommand. The appropriate function for the filesystem to which
|
|||
|
* pathPtr belongs will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The called function may either return an array of strings, or may
|
|||
|
* instead return NULL and place a Tcl list into the given objPtrRef.
|
|||
|
* Tcl will take that list and first increment its refCount before using
|
|||
|
* it. On completion of that use, Tcl will decrement its refCount. Hence
|
|||
|
* if the list should be disposed of by Tcl when done, it should have a
|
|||
|
* refCount of zero, and if the list should not be disposed of, the
|
|||
|
* filesystem should ensure it retains a refCount on the object.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
const char *const *
|
|||
|
Tcl_FSFileAttrStrings(
|
|||
|
Tcl_Obj *pathPtr,
|
|||
|
Tcl_Obj **objPtrRef)
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->fileAttrStringsProc != NULL) {
|
|||
|
return fsPtr->fileAttrStringsProc(pathPtr, objPtrRef);
|
|||
|
}
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclFSFileAttrIndex --
|
|||
|
*
|
|||
|
* Helper function for converting an attribute name to an index into the
|
|||
|
* attribute table.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Tcl result code, index written to *indexPtr on result==TCL_OK
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclFSFileAttrIndex(
|
|||
|
Tcl_Obj *pathPtr, /* File whose attributes are to be indexed
|
|||
|
* into. */
|
|||
|
const char *attributeName, /* The attribute being looked for. */
|
|||
|
int *indexPtr) /* Where to write the found index. */
|
|||
|
{
|
|||
|
Tcl_Obj *listObj = NULL;
|
|||
|
const char *const *attrTable;
|
|||
|
|
|||
|
/*
|
|||
|
* Get the attribute table for the file.
|
|||
|
*/
|
|||
|
|
|||
|
attrTable = Tcl_FSFileAttrStrings(pathPtr, &listObj);
|
|||
|
if (listObj != NULL) {
|
|||
|
Tcl_IncrRefCount(listObj);
|
|||
|
}
|
|||
|
|
|||
|
if (attrTable != NULL) {
|
|||
|
/*
|
|||
|
* It's a constant attribute table, so use T_GIFO.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *tmpObj = Tcl_NewStringObj(attributeName, -1);
|
|||
|
int result;
|
|||
|
|
|||
|
result = Tcl_GetIndexFromObj(NULL, tmpObj, attrTable, NULL, TCL_EXACT,
|
|||
|
indexPtr);
|
|||
|
TclDecrRefCount(tmpObj);
|
|||
|
if (listObj != NULL) {
|
|||
|
TclDecrRefCount(listObj);
|
|||
|
}
|
|||
|
return result;
|
|||
|
} else if (listObj != NULL) {
|
|||
|
/*
|
|||
|
* It's a non-constant attribute list, so do a literal search.
|
|||
|
*/
|
|||
|
|
|||
|
int i, objc;
|
|||
|
Tcl_Obj **objv;
|
|||
|
|
|||
|
if (Tcl_ListObjGetElements(NULL, listObj, &objc, &objv) != TCL_OK) {
|
|||
|
TclDecrRefCount(listObj);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
for (i=0 ; i<objc ; i++) {
|
|||
|
if (!strcmp(attributeName, TclGetString(objv[i]))) {
|
|||
|
TclDecrRefCount(listObj);
|
|||
|
*indexPtr = i;
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
}
|
|||
|
TclDecrRefCount(listObj);
|
|||
|
return TCL_ERROR;
|
|||
|
} else {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSFileAttrsGet --
|
|||
|
*
|
|||
|
* This function implements read access for the hookable 'file
|
|||
|
* attributes' subcommand. The appropriate function for the filesystem to
|
|||
|
* which pathPtr belongs will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl return code. The object placed in objPtrRef (if TCL_OK
|
|||
|
* was returned) is likely to have a refCount of zero. Either way we must
|
|||
|
* either store it somewhere (e.g. the Tcl result), or Incr/Decr its
|
|||
|
* refCount to ensure it is properly freed.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSFileAttrsGet(
|
|||
|
Tcl_Interp *interp, /* The interpreter for error reporting. */
|
|||
|
int index, /* index of the attribute command. */
|
|||
|
Tcl_Obj *pathPtr, /* filename we are operating on. */
|
|||
|
Tcl_Obj **objPtrRef) /* for output. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->fileAttrsGetProc != NULL) {
|
|||
|
return fsPtr->fileAttrsGetProc(interp, index, pathPtr, objPtrRef);
|
|||
|
}
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSFileAttrsSet --
|
|||
|
*
|
|||
|
* This function implements write access for the hookable 'file
|
|||
|
* attributes' subcommand. The appropriate function for the filesystem to
|
|||
|
* which pathPtr belongs will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl return code.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSFileAttrsSet(
|
|||
|
Tcl_Interp *interp, /* The interpreter for error reporting. */
|
|||
|
int index, /* index of the attribute command. */
|
|||
|
Tcl_Obj *pathPtr, /* filename we are operating on. */
|
|||
|
Tcl_Obj *objPtr) /* Input value. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->fileAttrsSetProc != NULL) {
|
|||
|
return fsPtr->fileAttrsSetProc(interp, index, pathPtr, objPtr);
|
|||
|
}
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSGetCwd --
|
|||
|
*
|
|||
|
* This function replaces the library version of getcwd().
|
|||
|
*
|
|||
|
* Most VFS's will *not* implement a 'cwdProc'. Tcl now maintains its own
|
|||
|
* record (in a Tcl_Obj) of the cwd, and an attempt is made to synch this
|
|||
|
* with the cwd's containing filesystem, if that filesystem provides a
|
|||
|
* cwdProc (e.g. the native filesystem).
|
|||
|
*
|
|||
|
* Note that if Tcl's cwd is not in the native filesystem, then of course
|
|||
|
* Tcl's cwd and the native cwd are different: extensions should
|
|||
|
* therefore ensure they only access the cwd through this function to
|
|||
|
* avoid confusion.
|
|||
|
*
|
|||
|
* If a global cwdPathPtr already exists, it is cached in the thread's
|
|||
|
* private data structures and reference to the cached copy is returned,
|
|||
|
* subject to a synchronisation attempt in that cwdPathPtr's fs.
|
|||
|
*
|
|||
|
* Otherwise, the chain of functions that have been "inserted" into the
|
|||
|
* filesystem will be called in succession until either a value other
|
|||
|
* than NULL is returned, or the entire list is visited.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The result is a pointer to a Tcl_Obj specifying the current directory,
|
|||
|
* or NULL if the current directory could not be determined. If NULL is
|
|||
|
* returned, an error message is left in the interp's result.
|
|||
|
*
|
|||
|
* The result already has its refCount incremented for the caller. When
|
|||
|
* it is no longer needed, that refCount should be decremented.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Various objects may be freed and allocated.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *
|
|||
|
Tcl_FSGetCwd(
|
|||
|
Tcl_Interp *interp)
|
|||
|
{
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
|
|||
|
if (TclFSCwdPointerEquals(NULL)) {
|
|||
|
FilesystemRecord *fsRecPtr;
|
|||
|
Tcl_Obj *retVal = NULL;
|
|||
|
|
|||
|
/*
|
|||
|
* We've never been called before, try to find a cwd. Call each of the
|
|||
|
* "Tcl_GetCwd" function in succession. A non-NULL return value
|
|||
|
* indicates the particular function has succeeded.
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = FsGetFirstFilesystem();
|
|||
|
Claim();
|
|||
|
for (; (retVal == NULL) && (fsRecPtr != NULL);
|
|||
|
fsRecPtr = fsRecPtr->nextPtr) {
|
|||
|
ClientData retCd;
|
|||
|
TclFSGetCwdProc2 *proc2;
|
|||
|
if (fsRecPtr->fsPtr->getCwdProc == NULL) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (fsRecPtr->fsPtr->version == TCL_FILESYSTEM_VERSION_1) {
|
|||
|
retVal = fsRecPtr->fsPtr->getCwdProc(interp);
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
proc2 = (TclFSGetCwdProc2 *) fsRecPtr->fsPtr->getCwdProc;
|
|||
|
retCd = proc2(NULL);
|
|||
|
if (retCd != NULL) {
|
|||
|
Tcl_Obj *norm;
|
|||
|
|
|||
|
/*
|
|||
|
* Looks like a new current directory.
|
|||
|
*/
|
|||
|
|
|||
|
retVal = fsRecPtr->fsPtr->internalToNormalizedProc(retCd);
|
|||
|
Tcl_IncrRefCount(retVal);
|
|||
|
norm = TclFSNormalizeAbsolutePath(interp,retVal);
|
|||
|
if (norm != NULL) {
|
|||
|
/*
|
|||
|
* We found a cwd, which is now in our global storage. We
|
|||
|
* must make a copy. Norm already has a refCount of 1.
|
|||
|
*
|
|||
|
* Threading issue: note that multiple threads at system
|
|||
|
* startup could in principle call this function
|
|||
|
* simultaneously. They will therefore each set the
|
|||
|
* cwdPathPtr independently. That behaviour is a bit
|
|||
|
* peculiar, but should be fine. Once we have a cwd, we'll
|
|||
|
* always be in the 'else' branch below which is simpler.
|
|||
|
*/
|
|||
|
|
|||
|
FsUpdateCwd(norm, retCd);
|
|||
|
Tcl_DecrRefCount(norm);
|
|||
|
} else {
|
|||
|
fsRecPtr->fsPtr->freeInternalRepProc(retCd);
|
|||
|
}
|
|||
|
Tcl_DecrRefCount(retVal);
|
|||
|
retVal = NULL;
|
|||
|
Disclaim();
|
|||
|
goto cdDidNotChange;
|
|||
|
} else if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"error getting working directory name: %s",
|
|||
|
Tcl_PosixError(interp)));
|
|||
|
}
|
|||
|
}
|
|||
|
Disclaim();
|
|||
|
|
|||
|
/*
|
|||
|
* Now the 'cwd' may NOT be normalized, at least on some platforms.
|
|||
|
* For the sake of efficiency, we want a completely normalized cwd at
|
|||
|
* all times.
|
|||
|
*
|
|||
|
* Finally, if retVal is NULL, we do not have a cwd, which could be
|
|||
|
* problematic.
|
|||
|
*/
|
|||
|
|
|||
|
if (retVal != NULL) {
|
|||
|
Tcl_Obj *norm = TclFSNormalizeAbsolutePath(interp, retVal);
|
|||
|
|
|||
|
if (norm != NULL) {
|
|||
|
/*
|
|||
|
* We found a cwd, which is now in our global storage. We must
|
|||
|
* make a copy. Norm already has a refCount of 1.
|
|||
|
*
|
|||
|
* Threading issue: note that multiple threads at system
|
|||
|
* startup could in principle call this function
|
|||
|
* simultaneously. They will therefore each set the cwdPathPtr
|
|||
|
* independently. That behaviour is a bit peculiar, but should
|
|||
|
* be fine. Once we have a cwd, we'll always be in the 'else'
|
|||
|
* branch below which is simpler.
|
|||
|
*/
|
|||
|
|
|||
|
ClientData cd = (ClientData) Tcl_FSGetNativePath(norm);
|
|||
|
|
|||
|
FsUpdateCwd(norm, TclNativeDupInternalRep(cd));
|
|||
|
Tcl_DecrRefCount(norm);
|
|||
|
}
|
|||
|
Tcl_DecrRefCount(retVal);
|
|||
|
}
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* We already have a cwd cached, but we want to give the filesystem it
|
|||
|
* is in a chance to check whether that cwd has changed, or is perhaps
|
|||
|
* no longer accessible. This allows an error to be thrown if, say,
|
|||
|
* the permissions on that directory have changed.
|
|||
|
*/
|
|||
|
|
|||
|
const Tcl_Filesystem *fsPtr =
|
|||
|
Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr);
|
|||
|
ClientData retCd = NULL;
|
|||
|
Tcl_Obj *retVal, *norm;
|
|||
|
|
|||
|
/*
|
|||
|
* If the filesystem couldn't be found, or if no cwd function exists
|
|||
|
* for this filesystem, then we simply assume the cached cwd is ok.
|
|||
|
* If we do call a cwd, we must watch for errors (if the cwd returns
|
|||
|
* NULL). This ensures that, say, on Unix if the permissions of the
|
|||
|
* cwd change, 'pwd' does actually throw the correct error in Tcl.
|
|||
|
* (This is tested for in the test suite on unix).
|
|||
|
*/
|
|||
|
|
|||
|
if (fsPtr == NULL || fsPtr->getCwdProc == NULL) {
|
|||
|
goto cdDidNotChange;
|
|||
|
}
|
|||
|
|
|||
|
if (fsPtr->version == TCL_FILESYSTEM_VERSION_1) {
|
|||
|
retVal = fsPtr->getCwdProc(interp);
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* New API.
|
|||
|
*/
|
|||
|
|
|||
|
TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc;
|
|||
|
|
|||
|
retCd = proc2(tsdPtr->cwdClientData);
|
|||
|
if (retCd == NULL && interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"error getting working directory name: %s",
|
|||
|
Tcl_PosixError(interp)));
|
|||
|
}
|
|||
|
|
|||
|
if (retCd == tsdPtr->cwdClientData) {
|
|||
|
goto cdDidNotChange;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Looks like a new current directory.
|
|||
|
*/
|
|||
|
|
|||
|
retVal = fsPtr->internalToNormalizedProc(retCd);
|
|||
|
Tcl_IncrRefCount(retVal);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Check if the 'cwd' function returned an error; if so, reset the
|
|||
|
* cwd.
|
|||
|
*/
|
|||
|
|
|||
|
if (retVal == NULL) {
|
|||
|
FsUpdateCwd(NULL, NULL);
|
|||
|
goto cdDidNotChange;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Normalize the path.
|
|||
|
*/
|
|||
|
|
|||
|
norm = TclFSNormalizeAbsolutePath(interp, retVal);
|
|||
|
|
|||
|
/*
|
|||
|
* Check whether cwd has changed from the value previously stored in
|
|||
|
* cwdPathPtr. Really 'norm' shouldn't be NULL, but we are careful.
|
|||
|
*/
|
|||
|
|
|||
|
if (norm == NULL) {
|
|||
|
/* Do nothing */
|
|||
|
if (retCd != NULL) {
|
|||
|
fsPtr->freeInternalRepProc(retCd);
|
|||
|
}
|
|||
|
} else if (norm == tsdPtr->cwdPathPtr) {
|
|||
|
goto cdEqual;
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* Note that both 'norm' and 'tsdPtr->cwdPathPtr' are normalized
|
|||
|
* paths. Therefore we can be more efficient than calling
|
|||
|
* 'Tcl_FSEqualPaths', and in addition avoid a nasty infinite loop
|
|||
|
* bug when trying to normalize tsdPtr->cwdPathPtr.
|
|||
|
*/
|
|||
|
|
|||
|
int len1, len2;
|
|||
|
const char *str1, *str2;
|
|||
|
|
|||
|
str1 = Tcl_GetStringFromObj(tsdPtr->cwdPathPtr, &len1);
|
|||
|
str2 = Tcl_GetStringFromObj(norm, &len2);
|
|||
|
if ((len1 == len2) && (strcmp(str1, str2) == 0)) {
|
|||
|
/*
|
|||
|
* If the paths were equal, we can be more efficient and
|
|||
|
* retain the old path object which will probably already be
|
|||
|
* shared. In this case we can simply free the normalized path
|
|||
|
* we just calculated.
|
|||
|
*/
|
|||
|
|
|||
|
cdEqual:
|
|||
|
Tcl_DecrRefCount(norm);
|
|||
|
if (retCd != NULL) {
|
|||
|
fsPtr->freeInternalRepProc(retCd);
|
|||
|
}
|
|||
|
} else {
|
|||
|
FsUpdateCwd(norm, retCd);
|
|||
|
Tcl_DecrRefCount(norm);
|
|||
|
}
|
|||
|
}
|
|||
|
Tcl_DecrRefCount(retVal);
|
|||
|
}
|
|||
|
|
|||
|
cdDidNotChange:
|
|||
|
if (tsdPtr->cwdPathPtr != NULL) {
|
|||
|
Tcl_IncrRefCount(tsdPtr->cwdPathPtr);
|
|||
|
}
|
|||
|
|
|||
|
return tsdPtr->cwdPathPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSChdir --
|
|||
|
*
|
|||
|
* This function replaces the library version of chdir().
|
|||
|
*
|
|||
|
* The path is normalized and then passed to the filesystem which claims
|
|||
|
* it.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* See chdir() documentation. If successful, we keep a record of the
|
|||
|
* successful path in cwdPathPtr for subsequent calls to getcwd.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See chdir() documentation. The global cwdPathPtr may change value.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSChdir(
|
|||
|
Tcl_Obj *pathPtr)
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr, *oldFsPtr = NULL;
|
|||
|
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&fsDataKey);
|
|||
|
int retVal = -1;
|
|||
|
|
|||
|
if (tsdPtr->cwdPathPtr != NULL) {
|
|||
|
oldFsPtr = Tcl_FSGetFileSystemForPath(tsdPtr->cwdPathPtr);
|
|||
|
}
|
|||
|
if (Tcl_FSGetNormalizedPath(NULL, pathPtr) == NULL) {
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
if (fsPtr != NULL) {
|
|||
|
if (fsPtr->chdirProc != NULL) {
|
|||
|
/*
|
|||
|
* If this fails, an appropriate errno will have been stored using
|
|||
|
* 'Tcl_SetErrno()'.
|
|||
|
*/
|
|||
|
|
|||
|
retVal = fsPtr->chdirProc(pathPtr);
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* Fallback on stat-based implementation.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_StatBuf buf;
|
|||
|
|
|||
|
/*
|
|||
|
* If the file can be stat'ed and is a directory and is readable,
|
|||
|
* then we can chdir. If any of these actions fail, then
|
|||
|
* 'Tcl_SetErrno()' should automatically have been called to set
|
|||
|
* an appropriate error code.
|
|||
|
*/
|
|||
|
|
|||
|
if ((Tcl_FSStat(pathPtr, &buf) == 0) && (S_ISDIR(buf.st_mode))
|
|||
|
&& (Tcl_FSAccess(pathPtr, R_OK) == 0)) {
|
|||
|
/*
|
|||
|
* We allow the chdir.
|
|||
|
*/
|
|||
|
|
|||
|
retVal = 0;
|
|||
|
}
|
|||
|
}
|
|||
|
} else {
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* The cwd changed, or an error was thrown. If an error was thrown, we can
|
|||
|
* just continue (and that will report the error to the user). If there
|
|||
|
* was no error we must assume that the cwd was actually changed to the
|
|||
|
* normalized value we calculated above, and we must therefore cache that
|
|||
|
* information.
|
|||
|
*
|
|||
|
* If the filesystem in question has a getCwdProc, then the correct logic
|
|||
|
* which performs the part below is already part of the Tcl_FSGetCwd()
|
|||
|
* call, so no need to replicate it again. This will have a side effect
|
|||
|
* though. The private authoritative representation of the current working
|
|||
|
* directory stored in cwdPathPtr in static memory will be out-of-sync
|
|||
|
* with the real OS-maintained value. The first call to Tcl_FSGetCwd will
|
|||
|
* however recalculate the private copy to match the OS-value so
|
|||
|
* everything will work right.
|
|||
|
*
|
|||
|
* However, if there is no getCwdProc, then we _must_ update our private
|
|||
|
* storage of the cwd, since this is the only opportunity to do that!
|
|||
|
*
|
|||
|
* Note: We currently call this block of code irrespective of whether
|
|||
|
* there was a getCwdProc or not, but the code should all in principle
|
|||
|
* work if we only call this block if fsPtr->getCwdProc == NULL.
|
|||
|
*/
|
|||
|
|
|||
|
if (retVal == 0) {
|
|||
|
/*
|
|||
|
* Note that this normalized path may be different to what we found
|
|||
|
* above (or at least a different object), if the filesystem epoch
|
|||
|
* changed recently. This can actually happen with scripted documents
|
|||
|
* very easily. Therefore we ask for the normalized path again (the
|
|||
|
* correct value will have been cached as a result of the
|
|||
|
* Tcl_FSGetFileSystemForPath call above anyway).
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *normDirName = Tcl_FSGetNormalizedPath(NULL, pathPtr);
|
|||
|
|
|||
|
if (normDirName == NULL) {
|
|||
|
/* Not really true, but what else to do? */
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
if (fsPtr == &tclNativeFilesystem) {
|
|||
|
/*
|
|||
|
* For the native filesystem, we keep a cache of the native
|
|||
|
* representation of the cwd. But, we want to do that for the
|
|||
|
* exact format that is returned by 'getcwd' (so that we can later
|
|||
|
* compare the two representations for equality), which might not
|
|||
|
* be exactly the same char-string as the native representation of
|
|||
|
* the fully normalized path (e.g. on Windows there's a
|
|||
|
* forward-slash vs backslash difference). Hence we ask for this
|
|||
|
* again here. On Unix it might actually be true that we always
|
|||
|
* have the correct form in the native rep in which case we could
|
|||
|
* simply use:
|
|||
|
* cd = Tcl_FSGetNativePath(pathPtr);
|
|||
|
* instead. This should be examined by someone on Unix.
|
|||
|
*/
|
|||
|
|
|||
|
ClientData cd;
|
|||
|
ClientData oldcd = tsdPtr->cwdClientData;
|
|||
|
|
|||
|
/*
|
|||
|
* Assumption we are using a filesystem version 2.
|
|||
|
*/
|
|||
|
|
|||
|
TclFSGetCwdProc2 *proc2 = (TclFSGetCwdProc2 *) fsPtr->getCwdProc;
|
|||
|
|
|||
|
cd = proc2(oldcd);
|
|||
|
if (cd != oldcd) {
|
|||
|
FsUpdateCwd(normDirName, cd);
|
|||
|
}
|
|||
|
} else {
|
|||
|
FsUpdateCwd(normDirName, NULL);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the filesystem changed between old and new cwd
|
|||
|
* force filesystem refresh on path objects.
|
|||
|
*/
|
|||
|
if (oldFsPtr != NULL && fsPtr != oldFsPtr) {
|
|||
|
Tcl_FSMountsChanged(NULL);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSLoadFile --
|
|||
|
*
|
|||
|
* Dynamically loads a binary code file into memory and returns the
|
|||
|
* addresses of two functions within that file, if they are defined. The
|
|||
|
* appropriate function for the filesystem to which pathPtr belongs will
|
|||
|
* be called.
|
|||
|
*
|
|||
|
* Note that the native filesystem doesn't actually assume 'pathPtr' is a
|
|||
|
* path. Rather it assumes pathPtr is either a path or just the name
|
|||
|
* (tail) of a file which can be found somewhere in the environment's
|
|||
|
* loadable path. This behaviour is not very compatible with virtual
|
|||
|
* filesystems (and has other problems documented in the load man-page),
|
|||
|
* so it is advised that full paths are always used.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A standard Tcl completion code. If an error occurs, an error message
|
|||
|
* is left in the interp's result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* New code suddenly appears in memory. This may later be unloaded by
|
|||
|
* passing the clientData to the unloadProc.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSLoadFile(
|
|||
|
Tcl_Interp *interp, /* Used for error reporting. */
|
|||
|
Tcl_Obj *pathPtr, /* Name of the file containing the desired
|
|||
|
* code. */
|
|||
|
const char *sym1, const char *sym2,
|
|||
|
/* Names of two functions to look up in the
|
|||
|
* file's symbol table. */
|
|||
|
Tcl_PackageInitProc **proc1Ptr, Tcl_PackageInitProc **proc2Ptr,
|
|||
|
/* Where to return the addresses corresponding
|
|||
|
* to sym1 and sym2. */
|
|||
|
Tcl_LoadHandle *handlePtr, /* Filled with token for dynamically loaded
|
|||
|
* file which will be passed back to
|
|||
|
* (*unloadProcPtr)() to unload the file. */
|
|||
|
Tcl_FSUnloadFileProc **unloadProcPtr)
|
|||
|
/* Filled with address of Tcl_FSUnloadFileProc
|
|||
|
* function which should be used for this
|
|||
|
* file. */
|
|||
|
{
|
|||
|
const char *symbols[3];
|
|||
|
void *procPtrs[2];
|
|||
|
int res;
|
|||
|
|
|||
|
/*
|
|||
|
* Initialize the arrays.
|
|||
|
*/
|
|||
|
|
|||
|
symbols[0] = sym1;
|
|||
|
symbols[1] = sym2;
|
|||
|
symbols[2] = NULL;
|
|||
|
|
|||
|
/*
|
|||
|
* Perform the load.
|
|||
|
*/
|
|||
|
|
|||
|
res = Tcl_LoadFile(interp, pathPtr, symbols, 0, procPtrs, handlePtr);
|
|||
|
if (res == TCL_OK) {
|
|||
|
*proc1Ptr = (Tcl_PackageInitProc *) procPtrs[0];
|
|||
|
*proc2Ptr = (Tcl_PackageInitProc *) procPtrs[1];
|
|||
|
} else {
|
|||
|
*proc1Ptr = *proc2Ptr = NULL;
|
|||
|
}
|
|||
|
|
|||
|
return res;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_LoadFile --
|
|||
|
*
|
|||
|
* Dynamically loads a binary code file into memory and returns the
|
|||
|
* addresses of a number of given functions within that file, if they are
|
|||
|
* defined. The appropriate function for the filesystem to which pathPtr
|
|||
|
* belongs will be called.
|
|||
|
*
|
|||
|
* Note that the native filesystem doesn't actually assume 'pathPtr' is a
|
|||
|
* path. Rather it assumes pathPtr is either a path or just the name
|
|||
|
* (tail) of a file which can be found somewhere in the environment's
|
|||
|
* loadable path. This behaviour is not very compatible with virtual
|
|||
|
* filesystems (and has other problems documented in the load man-page),
|
|||
|
* so it is advised that full paths are always used.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A standard Tcl completion code. If an error occurs, an error message
|
|||
|
* is left in the interp's result.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* New code suddenly appears in memory. This may later be unloaded by
|
|||
|
* calling TclFS_UnloadFile.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
/*
|
|||
|
* Workaround for issue with modern HPUX which do allow the unlink (no ETXTBSY
|
|||
|
* error) yet somehow trash some internal data structures which prevents the
|
|||
|
* second and further shared libraries from getting properly loaded. Only the
|
|||
|
* first is ok. We try to get around the issue by not unlinking,
|
|||
|
* i.e. emulating the behaviour of the older HPUX which denied removal.
|
|||
|
*
|
|||
|
* Doing the unlink is also an issue within docker containers, whose AUFS
|
|||
|
* bungles this as well, see
|
|||
|
* https://github.com/dotcloud/docker/issues/1911
|
|||
|
*
|
|||
|
* For these situations the change below makes the execution of the unlink
|
|||
|
* semi-controllable at runtime.
|
|||
|
*
|
|||
|
* An AUFS filesystem (if it can be detected) will force avoidance of
|
|||
|
* unlink. The env variable TCL_TEMPLOAD_NO_UNLINK allows detection of a
|
|||
|
* users general request (unlink and not.
|
|||
|
*
|
|||
|
* By default the unlink is done (if not in AUFS). However if the variable is
|
|||
|
* present and set to true (any integer > 0) then the unlink is skipped.
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef _WIN32
|
|||
|
#define getenv(x) _wgetenv(L##x)
|
|||
|
#define atoi(x) _wtoi(x)
|
|||
|
#else
|
|||
|
#define WCHAR char
|
|||
|
#endif
|
|||
|
|
|||
|
static int
|
|||
|
skipUnlink (Tcl_Obj* shlibFile)
|
|||
|
{
|
|||
|
/* Order of testing:
|
|||
|
* 1. On hpux we generally want to skip unlink in general
|
|||
|
*
|
|||
|
* Outside of hpux then:
|
|||
|
* 2. For a general user request (TCL_TEMPLOAD_NO_UNLINK present, non-empty, => int)
|
|||
|
* 3. For general AUFS environment (statfs, if available).
|
|||
|
*
|
|||
|
* Ad 2: This variable can disable/override the AUFS detection, i.e. for
|
|||
|
* testing if a newer AUFS does not have the bug any more.
|
|||
|
*
|
|||
|
* Ad 3: This is conditionally compiled in. Condition currently must be set manually.
|
|||
|
* This part needs proper tests in the configure(.in).
|
|||
|
*/
|
|||
|
|
|||
|
#ifdef hpux
|
|||
|
return 1;
|
|||
|
#else
|
|||
|
WCHAR *skipstr;
|
|||
|
|
|||
|
skipstr = getenv("TCL_TEMPLOAD_NO_UNLINK");
|
|||
|
if (skipstr && (skipstr[0] != '\0')) {
|
|||
|
return atoi(skipstr);
|
|||
|
}
|
|||
|
|
|||
|
#ifdef TCL_TEMPLOAD_NO_UNLINK
|
|||
|
#ifndef NO_FSTATFS
|
|||
|
{
|
|||
|
struct statfs fs;
|
|||
|
/* Have fstatfs. May not have the AUFS super magic ... Indeed our build
|
|||
|
* box is too old to have it directly in the headers. Define taken from
|
|||
|
* http://mooon.googlecode.com/svn/trunk/linux_include/linux/aufs_type.h
|
|||
|
* http://aufs.sourceforge.net/
|
|||
|
* Better reference will be gladly taken.
|
|||
|
*/
|
|||
|
#ifndef AUFS_SUPER_MAGIC
|
|||
|
#define AUFS_SUPER_MAGIC ('a' << 24 | 'u' << 16 | 'f' << 8 | 's')
|
|||
|
#endif /* AUFS_SUPER_MAGIC */
|
|||
|
if ((statfs(Tcl_GetString (shlibFile), &fs) == 0) &&
|
|||
|
(fs.f_type == AUFS_SUPER_MAGIC)) {
|
|||
|
return 1;
|
|||
|
}
|
|||
|
}
|
|||
|
#endif /* ... NO_FSTATFS */
|
|||
|
#endif /* ... TCL_TEMPLOAD_NO_UNLINK */
|
|||
|
|
|||
|
/* Fallback: !hpux, no EV override, no AUFS (detection, nor detected):
|
|||
|
* Don't skip */
|
|||
|
return 0;
|
|||
|
#endif /* hpux */
|
|||
|
}
|
|||
|
|
|||
|
int
|
|||
|
Tcl_LoadFile(
|
|||
|
Tcl_Interp *interp, /* Used for error reporting. */
|
|||
|
Tcl_Obj *pathPtr, /* Name of the file containing the desired
|
|||
|
* code. */
|
|||
|
const char *const symbols[],/* Names of functions to look up in the file's
|
|||
|
* symbol table. */
|
|||
|
int flags, /* Flags */
|
|||
|
void *procVPtrs, /* Where to return the addresses corresponding
|
|||
|
* to symbols[]. */
|
|||
|
Tcl_LoadHandle *handlePtr) /* Filled with token for shared library
|
|||
|
* information which can be used in
|
|||
|
* TclpFindSymbol. */
|
|||
|
{
|
|||
|
void **procPtrs = (void **) procVPtrs;
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
const Tcl_Filesystem *copyFsPtr;
|
|||
|
Tcl_FSUnloadFileProc *unloadProcPtr;
|
|||
|
Tcl_Obj *copyToPtr;
|
|||
|
Tcl_LoadHandle newLoadHandle = NULL;
|
|||
|
Tcl_LoadHandle divertedLoadHandle = NULL;
|
|||
|
Tcl_FSUnloadFileProc *newUnloadProcPtr = NULL;
|
|||
|
FsDivertLoad *tvdlPtr;
|
|||
|
int retVal;
|
|||
|
int i;
|
|||
|
|
|||
|
if (fsPtr == NULL) {
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
if (fsPtr->loadFileProc != NULL) {
|
|||
|
retVal = ((Tcl_FSLoadFileProc2 *)(void *)(fsPtr->loadFileProc))
|
|||
|
(interp, pathPtr, handlePtr, &unloadProcPtr, flags);
|
|||
|
|
|||
|
if (retVal == TCL_OK) {
|
|||
|
if (*handlePtr == NULL) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
if (interp) {
|
|||
|
Tcl_ResetResult(interp);
|
|||
|
}
|
|||
|
goto resolveSymbols;
|
|||
|
}
|
|||
|
if (Tcl_GetErrno() != EXDEV) {
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* The filesystem doesn't support 'load', so we fall back on the following
|
|||
|
* technique:
|
|||
|
*
|
|||
|
* First check if it is readable -- and exists!
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_FSAccess(pathPtr, R_OK) != 0) {
|
|||
|
if (interp) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_ObjPrintf(
|
|||
|
"couldn't load library \"%s\": %s",
|
|||
|
Tcl_GetString(pathPtr), Tcl_PosixError(interp)));
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
#ifdef TCL_LOAD_FROM_MEMORY
|
|||
|
/*
|
|||
|
* The platform supports loading code from memory, so ask for a buffer of
|
|||
|
* the appropriate size, read the file into it and load the code from the
|
|||
|
* buffer:
|
|||
|
*/
|
|||
|
|
|||
|
{
|
|||
|
int ret, size;
|
|||
|
void *buffer;
|
|||
|
Tcl_StatBuf statBuf;
|
|||
|
Tcl_Channel data;
|
|||
|
|
|||
|
ret = Tcl_FSStat(pathPtr, &statBuf);
|
|||
|
if (ret < 0) {
|
|||
|
goto mustCopyToTempAnyway;
|
|||
|
}
|
|||
|
size = (int) statBuf.st_size;
|
|||
|
|
|||
|
/*
|
|||
|
* Tcl_Read takes an int: check that file size isn't wide.
|
|||
|
*/
|
|||
|
|
|||
|
if (size != (Tcl_WideInt) statBuf.st_size) {
|
|||
|
goto mustCopyToTempAnyway;
|
|||
|
}
|
|||
|
data = Tcl_FSOpenFileChannel(interp, pathPtr, "rb", 0666);
|
|||
|
if (!data) {
|
|||
|
goto mustCopyToTempAnyway;
|
|||
|
}
|
|||
|
buffer = TclpLoadMemoryGetBuffer(interp, size);
|
|||
|
if (!buffer) {
|
|||
|
Tcl_Close(interp, data);
|
|||
|
goto mustCopyToTempAnyway;
|
|||
|
}
|
|||
|
ret = Tcl_Read(data, buffer, size);
|
|||
|
Tcl_Close(interp, data);
|
|||
|
ret = TclpLoadMemory(interp, buffer, size, ret, handlePtr,
|
|||
|
&unloadProcPtr, flags);
|
|||
|
if (ret == TCL_OK && *handlePtr != NULL) {
|
|||
|
goto resolveSymbols;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
mustCopyToTempAnyway:
|
|||
|
if (interp) {
|
|||
|
Tcl_ResetResult(interp);
|
|||
|
}
|
|||
|
#endif /* TCL_LOAD_FROM_MEMORY */
|
|||
|
|
|||
|
/*
|
|||
|
* Get a temporary filename to use, first to copy the file into, and then
|
|||
|
* to load.
|
|||
|
*/
|
|||
|
|
|||
|
copyToPtr = TclpTempFileNameForLibrary(interp, pathPtr);
|
|||
|
if (copyToPtr == NULL) {
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
Tcl_IncrRefCount(copyToPtr);
|
|||
|
|
|||
|
copyFsPtr = Tcl_FSGetFileSystemForPath(copyToPtr);
|
|||
|
if ((copyFsPtr == NULL) || (copyFsPtr == fsPtr)) {
|
|||
|
/*
|
|||
|
* We already know we can't use Tcl_FSLoadFile from this filesystem,
|
|||
|
* and we must avoid a possible infinite loop. Try to delete the file
|
|||
|
* we probably created, and then exit.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_FSDeleteFile(copyToPtr);
|
|||
|
Tcl_DecrRefCount(copyToPtr);
|
|||
|
if (interp) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"couldn't load from current filesystem", -1));
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
if (TclCrossFilesystemCopy(interp, pathPtr, copyToPtr) != TCL_OK) {
|
|||
|
/*
|
|||
|
* Cross-platform copy failed.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_FSDeleteFile(copyToPtr);
|
|||
|
Tcl_DecrRefCount(copyToPtr);
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
|
|||
|
#ifndef _WIN32
|
|||
|
/*
|
|||
|
* Do we need to set appropriate permissions on the file? This may be
|
|||
|
* required on some systems. On Unix we could loop over the file
|
|||
|
* attributes, and set any that are called "-permissions" to 0700. However
|
|||
|
* we just do this directly, like this:
|
|||
|
*/
|
|||
|
|
|||
|
{
|
|||
|
int index;
|
|||
|
Tcl_Obj *perm;
|
|||
|
|
|||
|
TclNewLiteralStringObj(perm, "0700");
|
|||
|
Tcl_IncrRefCount(perm);
|
|||
|
if (TclFSFileAttrIndex(copyToPtr, "-permissions", &index) == TCL_OK) {
|
|||
|
Tcl_FSFileAttrsSet(NULL, index, copyToPtr, perm);
|
|||
|
}
|
|||
|
Tcl_DecrRefCount(perm);
|
|||
|
}
|
|||
|
#endif
|
|||
|
|
|||
|
/*
|
|||
|
* We need to reset the result now, because the cross-filesystem copy may
|
|||
|
* have stored the number of bytes in the result.
|
|||
|
*/
|
|||
|
|
|||
|
if (interp) {
|
|||
|
Tcl_ResetResult(interp);
|
|||
|
}
|
|||
|
|
|||
|
retVal = Tcl_LoadFile(interp, copyToPtr, symbols, flags, procPtrs,
|
|||
|
&newLoadHandle);
|
|||
|
if (retVal != TCL_OK) {
|
|||
|
/*
|
|||
|
* The file didn't load successfully.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_FSDeleteFile(copyToPtr);
|
|||
|
Tcl_DecrRefCount(copyToPtr);
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Try to delete the file immediately - this is possible in some OSes, and
|
|||
|
* avoids any worries about leaving the copy laying around on exit.
|
|||
|
*/
|
|||
|
|
|||
|
if (
|
|||
|
!skipUnlink (copyToPtr) &&
|
|||
|
(Tcl_FSDeleteFile(copyToPtr) == TCL_OK)) {
|
|||
|
Tcl_DecrRefCount(copyToPtr);
|
|||
|
|
|||
|
/*
|
|||
|
* We tell our caller about the real shared library which was loaded.
|
|||
|
* Note that this does mean that the package list maintained by 'load'
|
|||
|
* will store the original (vfs) path alongside the temporary load
|
|||
|
* handle and unload proc ptr.
|
|||
|
*/
|
|||
|
|
|||
|
*handlePtr = newLoadHandle;
|
|||
|
if (interp) {
|
|||
|
Tcl_ResetResult(interp);
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* When we unload this file, we need to divert the unloading so we can
|
|||
|
* unload and cleanup the temporary file correctly.
|
|||
|
*/
|
|||
|
|
|||
|
tvdlPtr = ckalloc(sizeof(FsDivertLoad));
|
|||
|
|
|||
|
/*
|
|||
|
* Remember three pieces of information. This allows us to cleanup the
|
|||
|
* diverted load completely, on platforms which allow proper unloading of
|
|||
|
* code.
|
|||
|
*/
|
|||
|
|
|||
|
tvdlPtr->loadHandle = newLoadHandle;
|
|||
|
tvdlPtr->unloadProcPtr = newUnloadProcPtr;
|
|||
|
|
|||
|
if (copyFsPtr != &tclNativeFilesystem) {
|
|||
|
/*
|
|||
|
* copyToPtr is already incremented for this reference.
|
|||
|
*/
|
|||
|
|
|||
|
tvdlPtr->divertedFile = copyToPtr;
|
|||
|
|
|||
|
/*
|
|||
|
* This is the filesystem we loaded it into. Since we have a reference
|
|||
|
* to 'copyToPtr', we already have a refCount on this filesystem, so
|
|||
|
* we don't need to worry about it disappearing on us.
|
|||
|
*/
|
|||
|
|
|||
|
tvdlPtr->divertedFilesystem = copyFsPtr;
|
|||
|
tvdlPtr->divertedFileNativeRep = NULL;
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* We need the native rep.
|
|||
|
*/
|
|||
|
|
|||
|
tvdlPtr->divertedFileNativeRep = TclNativeDupInternalRep(
|
|||
|
Tcl_FSGetInternalRep(copyToPtr, copyFsPtr));
|
|||
|
|
|||
|
/*
|
|||
|
* We don't need or want references to the copied Tcl_Obj or the
|
|||
|
* filesystem if it is the native one.
|
|||
|
*/
|
|||
|
|
|||
|
tvdlPtr->divertedFile = NULL;
|
|||
|
tvdlPtr->divertedFilesystem = NULL;
|
|||
|
Tcl_DecrRefCount(copyToPtr);
|
|||
|
}
|
|||
|
|
|||
|
copyToPtr = NULL;
|
|||
|
|
|||
|
divertedLoadHandle = ckalloc(sizeof(struct Tcl_LoadHandle_));
|
|||
|
divertedLoadHandle->clientData = tvdlPtr;
|
|||
|
divertedLoadHandle->findSymbolProcPtr = DivertFindSymbol;
|
|||
|
divertedLoadHandle->unloadFileProcPtr = DivertUnloadFile;
|
|||
|
*handlePtr = divertedLoadHandle;
|
|||
|
|
|||
|
if (interp) {
|
|||
|
Tcl_ResetResult(interp);
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
|
|||
|
resolveSymbols:
|
|||
|
/*
|
|||
|
* At this point, *handlePtr is already set up to the handle for the
|
|||
|
* loaded library. We now try to resolve the symbols.
|
|||
|
*/
|
|||
|
|
|||
|
if (symbols != NULL) {
|
|||
|
for (i=0 ; symbols[i] != NULL; i++) {
|
|||
|
procPtrs[i] = Tcl_FindSymbol(interp, *handlePtr, symbols[i]);
|
|||
|
if (procPtrs[i] == NULL) {
|
|||
|
/*
|
|||
|
* At least one symbol in the list was not found. Unload the
|
|||
|
* file, and report the problem back to the caller.
|
|||
|
* (Tcl_FindSymbol should already have left an appropriate
|
|||
|
* error message.)
|
|||
|
*/
|
|||
|
|
|||
|
(*handlePtr)->unloadFileProcPtr(*handlePtr);
|
|||
|
*handlePtr = NULL;
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* DivertFindSymbol --
|
|||
|
*
|
|||
|
* Find a symbol in a shared library loaded by copy-from-VFS.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void *
|
|||
|
DivertFindSymbol(
|
|||
|
Tcl_Interp *interp, /* Tcl interpreter */
|
|||
|
Tcl_LoadHandle loadHandle, /* Handle to the diverted module */
|
|||
|
const char *symbol) /* Symbol to resolve */
|
|||
|
{
|
|||
|
FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData;
|
|||
|
Tcl_LoadHandle originalHandle = tvdlPtr->loadHandle;
|
|||
|
|
|||
|
return originalHandle->findSymbolProcPtr(interp, originalHandle, symbol);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* DivertUnloadFile --
|
|||
|
*
|
|||
|
* Unloads a file that has been loaded by copying from VFS to the native
|
|||
|
* filesystem.
|
|||
|
*
|
|||
|
* Parameters:
|
|||
|
* loadHandle -- Handle of the file to unload
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
DivertUnloadFile(
|
|||
|
Tcl_LoadHandle loadHandle)
|
|||
|
{
|
|||
|
FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle->clientData;
|
|||
|
Tcl_LoadHandle originalHandle;
|
|||
|
|
|||
|
/*
|
|||
|
* This test should never trigger, since we give the client data in the
|
|||
|
* function above.
|
|||
|
*/
|
|||
|
|
|||
|
if (tvdlPtr == NULL) {
|
|||
|
return;
|
|||
|
}
|
|||
|
originalHandle = tvdlPtr->loadHandle;
|
|||
|
|
|||
|
/*
|
|||
|
* Call the real 'unloadfile' proc we actually used. It is very important
|
|||
|
* that we call this first, so that the shared library is actually
|
|||
|
* unloaded by the OS. Otherwise, the following 'delete' may well fail
|
|||
|
* because the shared library is still in use.
|
|||
|
*/
|
|||
|
|
|||
|
originalHandle->unloadFileProcPtr(originalHandle);
|
|||
|
|
|||
|
/*
|
|||
|
* What filesystem contains the temp copy of the library?
|
|||
|
*/
|
|||
|
|
|||
|
if (tvdlPtr->divertedFilesystem == NULL) {
|
|||
|
/*
|
|||
|
* It was the native filesystem, and we have a special function
|
|||
|
* available just for this purpose, which we know works even at this
|
|||
|
* late stage.
|
|||
|
*/
|
|||
|
|
|||
|
TclpDeleteFile(tvdlPtr->divertedFileNativeRep);
|
|||
|
NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep);
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* Remove the temporary file we created. Note, we may crash here
|
|||
|
* because encodings have been taken down already.
|
|||
|
*/
|
|||
|
|
|||
|
if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile)
|
|||
|
!= TCL_OK) {
|
|||
|
/*
|
|||
|
* The above may have failed because the filesystem, or something
|
|||
|
* it depends upon (e.g. encodings) have been taken down because
|
|||
|
* Tcl is exiting.
|
|||
|
*
|
|||
|
* We may need to work out how to delete this file more robustly
|
|||
|
* (or give the filesystem the information it needs to delete the
|
|||
|
* file more robustly).
|
|||
|
*
|
|||
|
* In particular, one problem might be that the filesystem cannot
|
|||
|
* extract the information it needs from the above path object
|
|||
|
* because Tcl's entire filesystem apparatus (the code in this
|
|||
|
* file) has been finalized, and it refuses to pass the internal
|
|||
|
* representation to the filesystem.
|
|||
|
*/
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* And free up the allocations. This will also of course remove a
|
|||
|
* refCount from the Tcl_Filesystem to which this file belongs, which
|
|||
|
* could then free up the filesystem if we are exiting.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_DecrRefCount(tvdlPtr->divertedFile);
|
|||
|
}
|
|||
|
|
|||
|
ckfree(tvdlPtr);
|
|||
|
ckfree(loadHandle);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FindSymbol --
|
|||
|
*
|
|||
|
* Find a symbol in a loaded library
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns a pointer to the symbol if found. If not found, returns NULL
|
|||
|
* and leaves an error message in the interpreter result.
|
|||
|
*
|
|||
|
* This function was once filesystem-specific, but has been made portable by
|
|||
|
* having TclpDlopen return a structure that includes procedure pointers.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void *
|
|||
|
Tcl_FindSymbol(
|
|||
|
Tcl_Interp *interp, /* Tcl interpreter */
|
|||
|
Tcl_LoadHandle loadHandle, /* Handle to the loaded library */
|
|||
|
const char *symbol) /* Name of the symbol to resolve */
|
|||
|
{
|
|||
|
return loadHandle->findSymbolProcPtr(interp, loadHandle, symbol);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSUnloadFile --
|
|||
|
*
|
|||
|
* Unloads a library given its handle. Checks first that the library
|
|||
|
* supports unloading.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSUnloadFile(
|
|||
|
Tcl_Interp *interp, /* Tcl interpreter */
|
|||
|
Tcl_LoadHandle handle) /* Handle of the file to unload */
|
|||
|
{
|
|||
|
if (handle->unloadFileProcPtr == NULL) {
|
|||
|
if (interp != NULL) {
|
|||
|
Tcl_SetObjResult(interp, Tcl_NewStringObj(
|
|||
|
"cannot unload: filesystem does not support unloading",
|
|||
|
-1));
|
|||
|
}
|
|||
|
return TCL_ERROR;
|
|||
|
}
|
|||
|
if (handle->unloadFileProcPtr != NULL) {
|
|||
|
handle->unloadFileProcPtr(handle);
|
|||
|
}
|
|||
|
return TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclFSUnloadTempFile --
|
|||
|
*
|
|||
|
* This function is called when we loaded a library of code via an
|
|||
|
* intermediate temporary file. This function ensures the library is
|
|||
|
* correctly unloaded and the temporary file is correctly deleted.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The effects of the 'unload' function called, and of course the
|
|||
|
* temporary file will be deleted.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
void
|
|||
|
TclFSUnloadTempFile(
|
|||
|
Tcl_LoadHandle loadHandle) /* loadHandle returned by a previous call to
|
|||
|
* Tcl_FSLoadFile(). The loadHandle is a token
|
|||
|
* that represents the loaded file. */
|
|||
|
{
|
|||
|
FsDivertLoad *tvdlPtr = (FsDivertLoad *) loadHandle;
|
|||
|
|
|||
|
/*
|
|||
|
* This test should never trigger, since we give the client data in the
|
|||
|
* function above.
|
|||
|
*/
|
|||
|
|
|||
|
if (tvdlPtr == NULL) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Call the real 'unloadfile' proc we actually used. It is very important
|
|||
|
* that we call this first, so that the shared library is actually
|
|||
|
* unloaded by the OS. Otherwise, the following 'delete' may well fail
|
|||
|
* because the shared library is still in use.
|
|||
|
*/
|
|||
|
|
|||
|
if (tvdlPtr->unloadProcPtr != NULL) {
|
|||
|
tvdlPtr->unloadProcPtr(tvdlPtr->loadHandle);
|
|||
|
}
|
|||
|
|
|||
|
if (tvdlPtr->divertedFilesystem == NULL) {
|
|||
|
/*
|
|||
|
* It was the native filesystem, and we have a special function
|
|||
|
* available just for this purpose, which we know works even at this
|
|||
|
* late stage.
|
|||
|
*/
|
|||
|
|
|||
|
TclpDeleteFile(tvdlPtr->divertedFileNativeRep);
|
|||
|
NativeFreeInternalRep(tvdlPtr->divertedFileNativeRep);
|
|||
|
} else {
|
|||
|
/*
|
|||
|
* Remove the temporary file we created. Note, we may crash here
|
|||
|
* because encodings have been taken down already.
|
|||
|
*/
|
|||
|
|
|||
|
if (tvdlPtr->divertedFilesystem->deleteFileProc(tvdlPtr->divertedFile)
|
|||
|
!= TCL_OK) {
|
|||
|
/*
|
|||
|
* The above may have failed because the filesystem, or something
|
|||
|
* it depends upon (e.g. encodings) have been taken down because
|
|||
|
* Tcl is exiting.
|
|||
|
*
|
|||
|
* We may need to work out how to delete this file more robustly
|
|||
|
* (or give the filesystem the information it needs to delete the
|
|||
|
* file more robustly).
|
|||
|
*
|
|||
|
* In particular, one problem might be that the filesystem cannot
|
|||
|
* extract the information it needs from the above path object
|
|||
|
* because Tcl's entire filesystem apparatus (the code in this
|
|||
|
* file) has been finalized, and it refuses to pass the internal
|
|||
|
* representation to the filesystem.
|
|||
|
*/
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* And free up the allocations. This will also of course remove a
|
|||
|
* refCount from the Tcl_Filesystem to which this file belongs, which
|
|||
|
* could then free up the filesystem if we are exiting.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_DecrRefCount(tvdlPtr->divertedFile);
|
|||
|
}
|
|||
|
|
|||
|
ckfree(tvdlPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSLink --
|
|||
|
*
|
|||
|
* This function replaces the library version of readlink() and can also
|
|||
|
* be used to make links. The appropriate function for the filesystem to
|
|||
|
* which pathPtr belongs will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* If toPtr is NULL, then the result is a Tcl_Obj specifying the contents
|
|||
|
* of the symbolic link given by 'pathPtr', or NULL if the symbolic link
|
|||
|
* could not be read. The result is owned by the caller, which should
|
|||
|
* call Tcl_DecrRefCount when the result is no longer needed.
|
|||
|
*
|
|||
|
* If toPtr is non-NULL, then the result is toPtr if the link action was
|
|||
|
* successful, or NULL if not. In this case the result has no additional
|
|||
|
* reference count, and need not be freed. The actual action to perform
|
|||
|
* is given by the 'linkAction' flags, which is an or'd combination of:
|
|||
|
*
|
|||
|
* TCL_CREATE_SYMBOLIC_LINK
|
|||
|
* TCL_CREATE_HARD_LINK
|
|||
|
*
|
|||
|
* Note that most filesystems will not support linking across to
|
|||
|
* different filesystems, so this function will usually fail unless toPtr
|
|||
|
* is in the same FS as pathPtr.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See readlink() documentation. A new filesystem link object may appear.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *
|
|||
|
Tcl_FSLink(
|
|||
|
Tcl_Obj *pathPtr, /* Path of file to readlink or link. */
|
|||
|
Tcl_Obj *toPtr, /* NULL or path to be linked to. */
|
|||
|
int linkAction) /* Action to perform. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->linkProc != NULL) {
|
|||
|
return fsPtr->linkProc(pathPtr, toPtr, linkAction);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If S_IFLNK isn't defined it means that the machine doesn't support
|
|||
|
* symbolic links, so the file can't possibly be a symbolic link. Generate
|
|||
|
* an EINVAL error, which is what happens on machines that do support
|
|||
|
* symbolic links when you invoke readlink on a file that isn't a symbolic
|
|||
|
* link.
|
|||
|
*/
|
|||
|
|
|||
|
#ifndef S_IFLNK
|
|||
|
errno = EINVAL; /* TODO: Change to Tcl_SetErrno()? */
|
|||
|
#else
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
#endif /* S_IFLNK */
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSListVolumes --
|
|||
|
*
|
|||
|
* Lists the currently mounted volumes. The chain of functions that have
|
|||
|
* been "inserted" into the filesystem will be called in succession; each
|
|||
|
* may return a list of volumes, all of which are added to the result
|
|||
|
* until all mounted file systems are listed.
|
|||
|
*
|
|||
|
* Notice that we assume the lists returned by each filesystem (if non
|
|||
|
* NULL) have been given a refCount for us already. However, we are NOT
|
|||
|
* allowed to hang on to the list itself (it belongs to the filesystem we
|
|||
|
* called). Therefore we quite naturally add its contents to the result
|
|||
|
* we are building, and then decrement the refCount.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The list of volumes, in an object which has refCount 0.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *
|
|||
|
Tcl_FSListVolumes(void)
|
|||
|
{
|
|||
|
FilesystemRecord *fsRecPtr;
|
|||
|
Tcl_Obj *resultPtr = Tcl_NewObj();
|
|||
|
|
|||
|
/*
|
|||
|
* Call each of the "listVolumes" function in succession. A non-NULL
|
|||
|
* return value indicates the particular function has succeeded. We call
|
|||
|
* all the functions registered, since we want a list of all drives from
|
|||
|
* all filesystems.
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = FsGetFirstFilesystem();
|
|||
|
Claim();
|
|||
|
while (fsRecPtr != NULL) {
|
|||
|
if (fsRecPtr->fsPtr->listVolumesProc != NULL) {
|
|||
|
Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc();
|
|||
|
|
|||
|
if (thisFsVolumes != NULL) {
|
|||
|
Tcl_ListObjAppendList(NULL, resultPtr, thisFsVolumes);
|
|||
|
Tcl_DecrRefCount(thisFsVolumes);
|
|||
|
}
|
|||
|
}
|
|||
|
fsRecPtr = fsRecPtr->nextPtr;
|
|||
|
}
|
|||
|
Disclaim();
|
|||
|
|
|||
|
return resultPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* FsListMounts --
|
|||
|
*
|
|||
|
* List all mounts within the given directory, which match the given
|
|||
|
* pattern.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* The list of mounts, in a list object which has refCount 0, or NULL if
|
|||
|
* we didn't even find any filesystems to try to list mounts.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static Tcl_Obj *
|
|||
|
FsListMounts(
|
|||
|
Tcl_Obj *pathPtr, /* Contains path to directory to search. */
|
|||
|
const char *pattern) /* Pattern to match against. */
|
|||
|
{
|
|||
|
FilesystemRecord *fsRecPtr;
|
|||
|
Tcl_GlobTypeData mountsOnly = { TCL_GLOB_TYPE_MOUNT, 0, NULL, NULL };
|
|||
|
Tcl_Obj *resultPtr = NULL;
|
|||
|
|
|||
|
/*
|
|||
|
* Call each of the "matchInDirectory" functions in succession, with the
|
|||
|
* specific type information 'mountsOnly'. A non-NULL return value
|
|||
|
* indicates the particular function has succeeded. We call all the
|
|||
|
* functions registered, since we want a list from each filesystems.
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = FsGetFirstFilesystem();
|
|||
|
Claim();
|
|||
|
while (fsRecPtr != NULL) {
|
|||
|
if (fsRecPtr->fsPtr != &tclNativeFilesystem &&
|
|||
|
fsRecPtr->fsPtr->matchInDirectoryProc != NULL) {
|
|||
|
if (resultPtr == NULL) {
|
|||
|
resultPtr = Tcl_NewObj();
|
|||
|
}
|
|||
|
fsRecPtr->fsPtr->matchInDirectoryProc(NULL, resultPtr, pathPtr,
|
|||
|
pattern, &mountsOnly);
|
|||
|
}
|
|||
|
fsRecPtr = fsRecPtr->nextPtr;
|
|||
|
}
|
|||
|
Disclaim();
|
|||
|
|
|||
|
return resultPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSSplitPath --
|
|||
|
*
|
|||
|
* This function takes the given Tcl_Obj, which should be a valid path,
|
|||
|
* and returns a Tcl List object containing each segment of that path as
|
|||
|
* an element.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns list object with refCount of zero. If the passed in lenPtr is
|
|||
|
* non-NULL, we use it to return the number of elements in the returned
|
|||
|
* list.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *
|
|||
|
Tcl_FSSplitPath(
|
|||
|
Tcl_Obj *pathPtr, /* Path to split. */
|
|||
|
int *lenPtr) /* int to store number of path elements. */
|
|||
|
{
|
|||
|
Tcl_Obj *result = NULL; /* Needed only to prevent gcc warnings. */
|
|||
|
const Tcl_Filesystem *fsPtr;
|
|||
|
char separator = '/';
|
|||
|
int driveNameLength;
|
|||
|
const char *p;
|
|||
|
|
|||
|
/*
|
|||
|
* Perform platform specific splitting.
|
|||
|
*/
|
|||
|
|
|||
|
if (TclFSGetPathType(pathPtr, &fsPtr,
|
|||
|
&driveNameLength) == TCL_PATH_ABSOLUTE) {
|
|||
|
if (fsPtr == &tclNativeFilesystem) {
|
|||
|
return TclpNativeSplitPath(pathPtr, lenPtr);
|
|||
|
}
|
|||
|
} else {
|
|||
|
return TclpNativeSplitPath(pathPtr, lenPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* We assume separators are single characters.
|
|||
|
*/
|
|||
|
|
|||
|
if (fsPtr->filesystemSeparatorProc != NULL) {
|
|||
|
Tcl_Obj *sep = fsPtr->filesystemSeparatorProc(pathPtr);
|
|||
|
|
|||
|
if (sep != NULL) {
|
|||
|
Tcl_IncrRefCount(sep);
|
|||
|
separator = Tcl_GetString(sep)[0];
|
|||
|
Tcl_DecrRefCount(sep);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Place the drive name as first element of the result list. The drive
|
|||
|
* name may contain strange characters, like colons and multiple forward
|
|||
|
* slashes (for example 'ftp://' is a valid vfs drive name)
|
|||
|
*/
|
|||
|
|
|||
|
result = Tcl_NewObj();
|
|||
|
p = Tcl_GetString(pathPtr);
|
|||
|
Tcl_ListObjAppendElement(NULL, result,
|
|||
|
Tcl_NewStringObj(p, driveNameLength));
|
|||
|
p += driveNameLength;
|
|||
|
|
|||
|
/*
|
|||
|
* Add the remaining path elements to the list.
|
|||
|
*/
|
|||
|
|
|||
|
for (;;) {
|
|||
|
const char *elementStart = p;
|
|||
|
int length;
|
|||
|
|
|||
|
while ((*p != '\0') && (*p != separator)) {
|
|||
|
p++;
|
|||
|
}
|
|||
|
length = p - elementStart;
|
|||
|
if (length > 0) {
|
|||
|
Tcl_Obj *nextElt;
|
|||
|
|
|||
|
if (elementStart[0] == '~') {
|
|||
|
TclNewLiteralStringObj(nextElt, "./");
|
|||
|
Tcl_AppendToObj(nextElt, elementStart, length);
|
|||
|
} else {
|
|||
|
nextElt = Tcl_NewStringObj(elementStart, length);
|
|||
|
}
|
|||
|
Tcl_ListObjAppendElement(NULL, result, nextElt);
|
|||
|
}
|
|||
|
if (*p++ == '\0') {
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Compute the number of elements in the result.
|
|||
|
*/
|
|||
|
|
|||
|
if (lenPtr != NULL) {
|
|||
|
TclListObjLength(NULL, result, lenPtr);
|
|||
|
}
|
|||
|
return result;
|
|||
|
}
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclGetPathType --
|
|||
|
*
|
|||
|
* Helper function used by FSGetPathType.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns one of TCL_PATH_ABSOLUTE, TCL_PATH_RELATIVE, or
|
|||
|
* TCL_PATH_VOLUME_RELATIVE. The filesystem reference will be set if and
|
|||
|
* only if it is non-NULL and the function's return value is
|
|||
|
* TCL_PATH_ABSOLUTE.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_PathType
|
|||
|
TclGetPathType(
|
|||
|
Tcl_Obj *pathPtr, /* Path to determine type for. */
|
|||
|
const Tcl_Filesystem **filesystemPtrPtr,
|
|||
|
/* If absolute path and this is not NULL, then
|
|||
|
* set to the filesystem which claims this
|
|||
|
* path. */
|
|||
|
int *driveNameLengthPtr, /* If the path is absolute, and this is
|
|||
|
* non-NULL, then set to the length of the
|
|||
|
* driveName. */
|
|||
|
Tcl_Obj **driveNameRef) /* If the path is absolute, and this is
|
|||
|
* non-NULL, then set to the name of the
|
|||
|
* drive, network-volume which contains the
|
|||
|
* path, already with a refCount for the
|
|||
|
* caller. */
|
|||
|
{
|
|||
|
int pathLen;
|
|||
|
const char *path = Tcl_GetStringFromObj(pathPtr, &pathLen);
|
|||
|
Tcl_PathType type;
|
|||
|
|
|||
|
type = TclFSNonnativePathType(path, pathLen, filesystemPtrPtr,
|
|||
|
driveNameLengthPtr, driveNameRef);
|
|||
|
|
|||
|
if (type != TCL_PATH_ABSOLUTE) {
|
|||
|
type = TclpGetNativePathType(pathPtr, driveNameLengthPtr,
|
|||
|
driveNameRef);
|
|||
|
if ((type == TCL_PATH_ABSOLUTE) && (filesystemPtrPtr != NULL)) {
|
|||
|
*filesystemPtrPtr = &tclNativeFilesystem;
|
|||
|
}
|
|||
|
}
|
|||
|
return type;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclFSNonnativePathType --
|
|||
|
*
|
|||
|
* Helper function used by TclGetPathType. Its purpose is to check
|
|||
|
* whether the given path starts with a string which corresponds to a
|
|||
|
* file volume in any registered filesystem except the native one. For
|
|||
|
* speed and historical reasons the native filesystem has special
|
|||
|
* hard-coded checks dotted here and there in the filesystem code.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Returns one of TCL_PATH_ABSOLUTE or TCL_PATH_RELATIVE. The filesystem
|
|||
|
* reference will be set if and only if it is non-NULL and the function's
|
|||
|
* return value is TCL_PATH_ABSOLUTE.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*----------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_PathType
|
|||
|
TclFSNonnativePathType(
|
|||
|
const char *path, /* Path to determine type for. */
|
|||
|
int pathLen, /* Length of the path. */
|
|||
|
const Tcl_Filesystem **filesystemPtrPtr,
|
|||
|
/* If absolute path and this is not NULL, then
|
|||
|
* set to the filesystem which claims this
|
|||
|
* path. */
|
|||
|
int *driveNameLengthPtr, /* If the path is absolute, and this is
|
|||
|
* non-NULL, then set to the length of the
|
|||
|
* driveName. */
|
|||
|
Tcl_Obj **driveNameRef) /* If the path is absolute, and this is
|
|||
|
* non-NULL, then set to the name of the
|
|||
|
* drive, network-volume which contains the
|
|||
|
* path, already with a refCount for the
|
|||
|
* caller. */
|
|||
|
{
|
|||
|
FilesystemRecord *fsRecPtr;
|
|||
|
Tcl_PathType type = TCL_PATH_RELATIVE;
|
|||
|
|
|||
|
/*
|
|||
|
* Call each of the "listVolumes" function in succession, checking whether
|
|||
|
* the given path is an absolute path on any of the volumes returned (this
|
|||
|
* is done by checking whether the path's prefix matches).
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = FsGetFirstFilesystem();
|
|||
|
Claim();
|
|||
|
while (fsRecPtr != NULL) {
|
|||
|
/*
|
|||
|
* We want to skip the native filesystem in this loop because
|
|||
|
* otherwise we won't necessarily pass all the Tcl testsuite - this is
|
|||
|
* because some of the tests artificially change the current platform
|
|||
|
* (between win, unix) but the list of volumes we get by calling
|
|||
|
* fsRecPtr->fsPtr->listVolumesProc will reflect the current (real)
|
|||
|
* platform only and this may cause some tests to fail. In particular,
|
|||
|
* on Unix '/' will match the beginning of certain absolute Windows
|
|||
|
* paths starting '//' and those tests will go wrong.
|
|||
|
*
|
|||
|
* Besides these test-suite issues, there is one other reason to skip
|
|||
|
* the native filesystem - since the tclFilename.c code has nice fast
|
|||
|
* 'absolute path' checkers, we don't want to waste time repeating
|
|||
|
* that effort here, and this function is actually called quite often,
|
|||
|
* so if we can save the overhead of the native filesystem returning
|
|||
|
* us a list of volumes all the time, it is better.
|
|||
|
*/
|
|||
|
|
|||
|
if ((fsRecPtr->fsPtr != &tclNativeFilesystem)
|
|||
|
&& (fsRecPtr->fsPtr->listVolumesProc != NULL)) {
|
|||
|
int numVolumes;
|
|||
|
Tcl_Obj *thisFsVolumes = fsRecPtr->fsPtr->listVolumesProc();
|
|||
|
|
|||
|
if (thisFsVolumes != NULL) {
|
|||
|
if (Tcl_ListObjLength(NULL, thisFsVolumes, &numVolumes)
|
|||
|
!= TCL_OK) {
|
|||
|
/*
|
|||
|
* This is VERY bad; the listVolumesProc didn't return a
|
|||
|
* valid list. Set numVolumes to -1 so that we skip the
|
|||
|
* while loop below and just return with the current value
|
|||
|
* of 'type'.
|
|||
|
*
|
|||
|
* It would be better if we could signal an error here
|
|||
|
* (but Tcl_Panic seems a bit excessive).
|
|||
|
*/
|
|||
|
|
|||
|
numVolumes = -1;
|
|||
|
}
|
|||
|
while (numVolumes > 0) {
|
|||
|
Tcl_Obj *vol;
|
|||
|
int len;
|
|||
|
const char *strVol;
|
|||
|
|
|||
|
numVolumes--;
|
|||
|
Tcl_ListObjIndex(NULL, thisFsVolumes, numVolumes, &vol);
|
|||
|
strVol = Tcl_GetStringFromObj(vol,&len);
|
|||
|
if (pathLen < len) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
if (strncmp(strVol, path, len) == 0) {
|
|||
|
type = TCL_PATH_ABSOLUTE;
|
|||
|
if (filesystemPtrPtr != NULL) {
|
|||
|
*filesystemPtrPtr = fsRecPtr->fsPtr;
|
|||
|
}
|
|||
|
if (driveNameLengthPtr != NULL) {
|
|||
|
*driveNameLengthPtr = len;
|
|||
|
}
|
|||
|
if (driveNameRef != NULL) {
|
|||
|
*driveNameRef = vol;
|
|||
|
Tcl_IncrRefCount(vol);
|
|||
|
}
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
Tcl_DecrRefCount(thisFsVolumes);
|
|||
|
if (type == TCL_PATH_ABSOLUTE) {
|
|||
|
/*
|
|||
|
* We don't need to examine any more filesystems.
|
|||
|
*/
|
|||
|
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
fsRecPtr = fsRecPtr->nextPtr;
|
|||
|
}
|
|||
|
Disclaim();
|
|||
|
return type;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSRenameFile --
|
|||
|
*
|
|||
|
* If the two paths given belong to the same filesystem, we call that
|
|||
|
* filesystems rename function. Otherwise we simply return the POSIX
|
|||
|
* error 'EXDEV', and -1.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl error code if a function was called.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* A file may be renamed.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSRenameFile(
|
|||
|
Tcl_Obj *srcPathPtr, /* Pathname of file or dir to be renamed
|
|||
|
* (UTF-8). */
|
|||
|
Tcl_Obj *destPathPtr) /* New pathname of file or directory
|
|||
|
* (UTF-8). */
|
|||
|
{
|
|||
|
int retVal = -1;
|
|||
|
const Tcl_Filesystem *fsPtr, *fsPtr2;
|
|||
|
|
|||
|
fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
|
|||
|
fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
|
|||
|
|
|||
|
if ((fsPtr == fsPtr2) && (fsPtr != NULL)
|
|||
|
&& (fsPtr->renameFileProc != NULL)) {
|
|||
|
retVal = fsPtr->renameFileProc(srcPathPtr, destPathPtr);
|
|||
|
}
|
|||
|
if (retVal == -1) {
|
|||
|
Tcl_SetErrno(EXDEV);
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSCopyFile --
|
|||
|
*
|
|||
|
* If the two paths given belong to the same filesystem, we call that
|
|||
|
* filesystem's copy function. Otherwise we simply return the POSIX error
|
|||
|
* 'EXDEV', and -1.
|
|||
|
*
|
|||
|
* Note that in the native filesystems, 'copyFileProc' is defined to copy
|
|||
|
* soft links (i.e. it copies the links themselves, not the things they
|
|||
|
* point to).
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl error code if a function was called.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* A file may be copied.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSCopyFile(
|
|||
|
Tcl_Obj *srcPathPtr, /* Pathname of file to be copied (UTF-8). */
|
|||
|
Tcl_Obj *destPathPtr) /* Pathname of file to copy to (UTF-8). */
|
|||
|
{
|
|||
|
int retVal = -1;
|
|||
|
const Tcl_Filesystem *fsPtr, *fsPtr2;
|
|||
|
|
|||
|
fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
|
|||
|
fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
|
|||
|
|
|||
|
if (fsPtr == fsPtr2 && fsPtr != NULL && fsPtr->copyFileProc != NULL) {
|
|||
|
retVal = fsPtr->copyFileProc(srcPathPtr, destPathPtr);
|
|||
|
}
|
|||
|
if (retVal == -1) {
|
|||
|
Tcl_SetErrno(EXDEV);
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* TclCrossFilesystemCopy --
|
|||
|
*
|
|||
|
* Helper for above function, and for Tcl_FSLoadFile, to copy files from
|
|||
|
* one filesystem to another. This function will overwrite the target
|
|||
|
* file if it already exists.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl error code.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* A file may be created.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
TclCrossFilesystemCopy(
|
|||
|
Tcl_Interp *interp, /* For error messages. */
|
|||
|
Tcl_Obj *source, /* Pathname of file to be copied (UTF-8). */
|
|||
|
Tcl_Obj *target) /* Pathname of file to copy to (UTF-8). */
|
|||
|
{
|
|||
|
int result = TCL_ERROR;
|
|||
|
int prot = 0666;
|
|||
|
Tcl_Channel in, out;
|
|||
|
Tcl_StatBuf sourceStatBuf;
|
|||
|
struct utimbuf tval;
|
|||
|
|
|||
|
out = Tcl_FSOpenFileChannel(interp, target, "wb", prot);
|
|||
|
if (out == NULL) {
|
|||
|
/*
|
|||
|
* It looks like we cannot copy it over. Bail out...
|
|||
|
*/
|
|||
|
goto done;
|
|||
|
}
|
|||
|
|
|||
|
in = Tcl_FSOpenFileChannel(interp, source, "rb", prot);
|
|||
|
if (in == NULL) {
|
|||
|
/*
|
|||
|
* This is very strange, caller should have checked this...
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Close(interp, out);
|
|||
|
goto done;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Copy it synchronously. We might wish to add an asynchronous option to
|
|||
|
* support vfs's which are slow (e.g. network sockets).
|
|||
|
*/
|
|||
|
|
|||
|
if (TclCopyChannel(interp, in, out, -1, NULL) == TCL_OK) {
|
|||
|
result = TCL_OK;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the copy failed, assume that copy channel left a good error message.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Close(interp, in);
|
|||
|
Tcl_Close(interp, out);
|
|||
|
|
|||
|
/*
|
|||
|
* Set modification date of copied file.
|
|||
|
*/
|
|||
|
|
|||
|
if (Tcl_FSLstat(source, &sourceStatBuf) == 0) {
|
|||
|
tval.actime = Tcl_GetAccessTimeFromStat(&sourceStatBuf);
|
|||
|
tval.modtime = Tcl_GetModificationTimeFromStat(&sourceStatBuf);
|
|||
|
Tcl_FSUtime(target, &tval);
|
|||
|
}
|
|||
|
|
|||
|
done:
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSDeleteFile --
|
|||
|
*
|
|||
|
* The appropriate function for the filesystem to which pathPtr belongs
|
|||
|
* will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl error code.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* A file may be deleted.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSDeleteFile(
|
|||
|
Tcl_Obj *pathPtr) /* Pathname of file to be removed (UTF-8). */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->deleteFileProc != NULL) {
|
|||
|
return fsPtr->deleteFileProc(pathPtr);
|
|||
|
}
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSCreateDirectory --
|
|||
|
*
|
|||
|
* The appropriate function for the filesystem to which pathPtr belongs
|
|||
|
* will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl error code.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* A directory may be created.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSCreateDirectory(
|
|||
|
Tcl_Obj *pathPtr) /* Pathname of directory to create (UTF-8). */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr != NULL && fsPtr->createDirectoryProc != NULL) {
|
|||
|
return fsPtr->createDirectoryProc(pathPtr);
|
|||
|
}
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSCopyDirectory --
|
|||
|
*
|
|||
|
* If the two paths given belong to the same filesystem, we call that
|
|||
|
* filesystems copy-directory function. Otherwise we simply return the
|
|||
|
* POSIX error 'EXDEV', and -1.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl error code if a function was called.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* A directory may be copied.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSCopyDirectory(
|
|||
|
Tcl_Obj *srcPathPtr, /* Pathname of directory to be copied
|
|||
|
* (UTF-8). */
|
|||
|
Tcl_Obj *destPathPtr, /* Pathname of target directory (UTF-8). */
|
|||
|
Tcl_Obj **errorPtr) /* If non-NULL, then will be set to a new
|
|||
|
* object containing name of file causing
|
|||
|
* error, with refCount 1. */
|
|||
|
{
|
|||
|
int retVal = -1;
|
|||
|
const Tcl_Filesystem *fsPtr, *fsPtr2;
|
|||
|
|
|||
|
fsPtr = Tcl_FSGetFileSystemForPath(srcPathPtr);
|
|||
|
fsPtr2 = Tcl_FSGetFileSystemForPath(destPathPtr);
|
|||
|
|
|||
|
if (fsPtr == fsPtr2 && fsPtr != NULL && fsPtr->copyDirectoryProc != NULL){
|
|||
|
retVal = fsPtr->copyDirectoryProc(srcPathPtr, destPathPtr, errorPtr);
|
|||
|
}
|
|||
|
if (retVal == -1) {
|
|||
|
Tcl_SetErrno(EXDEV);
|
|||
|
}
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSRemoveDirectory --
|
|||
|
*
|
|||
|
* The appropriate function for the filesystem to which pathPtr belongs
|
|||
|
* will be called.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* Standard Tcl error code.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* A directory may be deleted.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
int
|
|||
|
Tcl_FSRemoveDirectory(
|
|||
|
Tcl_Obj *pathPtr, /* Pathname of directory to be removed
|
|||
|
* (UTF-8). */
|
|||
|
int recursive, /* If non-zero, removes directories that are
|
|||
|
* nonempty. Otherwise, will only remove empty
|
|||
|
* directories. */
|
|||
|
Tcl_Obj **errorPtr) /* If non-NULL, then will be set to a new
|
|||
|
* object containing name of file causing
|
|||
|
* error, with refCount 1. */
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr == NULL || fsPtr->removeDirectoryProc == NULL) {
|
|||
|
Tcl_SetErrno(ENOENT);
|
|||
|
return -1;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* When working recursively, we check whether the cwd lies inside this
|
|||
|
* directory and move it if it does.
|
|||
|
*/
|
|||
|
|
|||
|
if (recursive) {
|
|||
|
Tcl_Obj *cwdPtr = Tcl_FSGetCwd(NULL);
|
|||
|
|
|||
|
if (cwdPtr != NULL) {
|
|||
|
const char *cwdStr, *normPathStr;
|
|||
|
int cwdLen, normLen;
|
|||
|
Tcl_Obj *normPath = Tcl_FSGetNormalizedPath(NULL, pathPtr);
|
|||
|
|
|||
|
if (normPath != NULL) {
|
|||
|
normPathStr = Tcl_GetStringFromObj(normPath, &normLen);
|
|||
|
cwdStr = Tcl_GetStringFromObj(cwdPtr, &cwdLen);
|
|||
|
if ((cwdLen >= normLen) && (strncmp(normPathStr, cwdStr,
|
|||
|
(size_t) normLen) == 0)) {
|
|||
|
/*
|
|||
|
* The cwd is inside the directory, so we perform a 'cd
|
|||
|
* [file dirname $path]'.
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *dirPtr = TclPathPart(NULL, pathPtr,
|
|||
|
TCL_PATH_DIRNAME);
|
|||
|
|
|||
|
Tcl_FSChdir(dirPtr);
|
|||
|
Tcl_DecrRefCount(dirPtr);
|
|||
|
}
|
|||
|
}
|
|||
|
Tcl_DecrRefCount(cwdPtr);
|
|||
|
}
|
|||
|
}
|
|||
|
return fsPtr->removeDirectoryProc(pathPtr, recursive, errorPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSGetFileSystemForPath --
|
|||
|
*
|
|||
|
* This function determines which filesystem to use for a particular path
|
|||
|
* object, and returns the filesystem which accepts this file. If no
|
|||
|
* filesystem will accept this object as a valid file path, then NULL is
|
|||
|
* returned.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* NULL or a filesystem which will accept this path.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The object may be converted to a path type.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
const Tcl_Filesystem *
|
|||
|
Tcl_FSGetFileSystemForPath(
|
|||
|
Tcl_Obj *pathPtr)
|
|||
|
{
|
|||
|
FilesystemRecord *fsRecPtr;
|
|||
|
const Tcl_Filesystem *retVal = NULL;
|
|||
|
|
|||
|
if (pathPtr == NULL) {
|
|||
|
Tcl_Panic("Tcl_FSGetFileSystemForPath called with NULL object");
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* If the object has a refCount of zero, we reject it. This is to avoid
|
|||
|
* possible segfaults or nondeterministic memory leaks (i.e. the user
|
|||
|
* doesn't know if they should decrement the ref count on return or not).
|
|||
|
*/
|
|||
|
|
|||
|
if (pathPtr->refCount == 0) {
|
|||
|
Tcl_Panic("Tcl_FSGetFileSystemForPath called with object with refCount == 0");
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Check if the filesystem has changed in some way since this object's
|
|||
|
* internal representation was calculated. Before doing that, assure we
|
|||
|
* have the most up-to-date copy of the first filesystem. This is
|
|||
|
* accomplished by the FsGetFirstFilesystem() call.
|
|||
|
*/
|
|||
|
|
|||
|
fsRecPtr = FsGetFirstFilesystem();
|
|||
|
Claim();
|
|||
|
|
|||
|
if (TclFSEnsureEpochOk(pathPtr, &retVal) != TCL_OK) {
|
|||
|
Disclaim();
|
|||
|
return NULL;
|
|||
|
} else if (retVal != NULL) {
|
|||
|
/* TODO: Can this happen? */
|
|||
|
Disclaim();
|
|||
|
return retVal;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Call each of the "pathInFilesystem" functions in succession. A
|
|||
|
* non-return value of -1 indicates the particular function has succeeded.
|
|||
|
*/
|
|||
|
|
|||
|
for (; fsRecPtr!=NULL ; fsRecPtr=fsRecPtr->nextPtr) {
|
|||
|
ClientData clientData = NULL;
|
|||
|
|
|||
|
if (fsRecPtr->fsPtr->pathInFilesystemProc == NULL) {
|
|||
|
continue;
|
|||
|
}
|
|||
|
|
|||
|
if (fsRecPtr->fsPtr->pathInFilesystemProc(pathPtr, &clientData)!=-1) {
|
|||
|
/*
|
|||
|
* We assume the type of pathPtr hasn't been changed by the above
|
|||
|
* call to the pathInFilesystemProc.
|
|||
|
*/
|
|||
|
|
|||
|
TclFSSetPathDetails(pathPtr, fsRecPtr->fsPtr, clientData);
|
|||
|
Disclaim();
|
|||
|
return fsRecPtr->fsPtr;
|
|||
|
}
|
|||
|
}
|
|||
|
Disclaim();
|
|||
|
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSGetNativePath --
|
|||
|
*
|
|||
|
* This function is for use by the Win/Unix native filesystems, so that
|
|||
|
* they can easily retrieve the native (char* or WCHAR*) representation
|
|||
|
* of a path. Other filesystems will probably want to implement similar
|
|||
|
* functions. They basically act as a safety net around
|
|||
|
* Tcl_FSGetInternalRep. Normally your file-system functions will always
|
|||
|
* be called with path objects already converted to the correct
|
|||
|
* filesystem, but if for some reason they are called directly (i.e. by
|
|||
|
* functions not in this file), then one cannot necessarily guarantee
|
|||
|
* that the path object pointer is from the correct filesystem.
|
|||
|
*
|
|||
|
* Note: in the future it might be desirable to have separate versions
|
|||
|
* of this function with different signatures, for example
|
|||
|
* Tcl_FSGetNativeWinPath, Tcl_FSGetNativeUnixPath etc. Right now, since
|
|||
|
* native paths are all string based, we use just one function.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* NULL or a valid native path.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* See Tcl_FSGetInternalRep.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
const void *
|
|||
|
Tcl_FSGetNativePath(
|
|||
|
Tcl_Obj *pathPtr)
|
|||
|
{
|
|||
|
return Tcl_FSGetInternalRep(pathPtr, &tclNativeFilesystem);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* NativeFreeInternalRep --
|
|||
|
*
|
|||
|
* Free a native internal representation, which will be non-NULL.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* None.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* Memory is released.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static void
|
|||
|
NativeFreeInternalRep(
|
|||
|
ClientData clientData)
|
|||
|
{
|
|||
|
ckfree(clientData);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSFileSystemInfo --
|
|||
|
*
|
|||
|
* This function returns a list of two elements. The first element is the
|
|||
|
* name of the filesystem (e.g. "native" or "vfs"), and the second is the
|
|||
|
* particular type of the given path within that filesystem.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A list of two elements.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The object may be converted to a path type.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *
|
|||
|
Tcl_FSFileSystemInfo(
|
|||
|
Tcl_Obj *pathPtr)
|
|||
|
{
|
|||
|
Tcl_Obj *resPtr;
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
|
|||
|
if (fsPtr == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
resPtr = Tcl_NewListObj(0, NULL);
|
|||
|
Tcl_ListObjAppendElement(NULL, resPtr,
|
|||
|
Tcl_NewStringObj(fsPtr->typeName, -1));
|
|||
|
|
|||
|
if (fsPtr->filesystemPathTypeProc != NULL) {
|
|||
|
Tcl_Obj *typePtr = fsPtr->filesystemPathTypeProc(pathPtr);
|
|||
|
|
|||
|
if (typePtr != NULL) {
|
|||
|
Tcl_ListObjAppendElement(NULL, resPtr, typePtr);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return resPtr;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* Tcl_FSPathSeparator --
|
|||
|
*
|
|||
|
* This function returns the separator to be used for a given path. The
|
|||
|
* object returned should have a refCount of zero
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* A Tcl object, with a refCount of zero. If the caller needs to retain a
|
|||
|
* reference to the object, it should call Tcl_IncrRefCount, and should
|
|||
|
* otherwise free the object.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* The path object may be converted to a path type.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
Tcl_Obj *
|
|||
|
Tcl_FSPathSeparator(
|
|||
|
Tcl_Obj *pathPtr)
|
|||
|
{
|
|||
|
const Tcl_Filesystem *fsPtr = Tcl_FSGetFileSystemForPath(pathPtr);
|
|||
|
Tcl_Obj *resultObj;
|
|||
|
|
|||
|
if (fsPtr == NULL) {
|
|||
|
return NULL;
|
|||
|
}
|
|||
|
|
|||
|
if (fsPtr->filesystemSeparatorProc != NULL) {
|
|||
|
return fsPtr->filesystemSeparatorProc(pathPtr);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Allow filesystems not to provide a filesystemSeparatorProc if they wish
|
|||
|
* to use the standard forward slash.
|
|||
|
*/
|
|||
|
|
|||
|
TclNewLiteralStringObj(resultObj, "/");
|
|||
|
return resultObj;
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*
|
|||
|
* NativeFilesystemSeparator --
|
|||
|
*
|
|||
|
* This function is part of the native filesystem support, and returns
|
|||
|
* the separator for the given path.
|
|||
|
*
|
|||
|
* Results:
|
|||
|
* String object containing the separator character.
|
|||
|
*
|
|||
|
* Side effects:
|
|||
|
* None.
|
|||
|
*
|
|||
|
*---------------------------------------------------------------------------
|
|||
|
*/
|
|||
|
|
|||
|
static Tcl_Obj *
|
|||
|
NativeFilesystemSeparator(
|
|||
|
Tcl_Obj *pathPtr)
|
|||
|
{
|
|||
|
const char *separator = NULL; /* lint */
|
|||
|
|
|||
|
switch (tclPlatform) {
|
|||
|
case TCL_PLATFORM_UNIX:
|
|||
|
separator = "/";
|
|||
|
break;
|
|||
|
case TCL_PLATFORM_WINDOWS:
|
|||
|
separator = "\\";
|
|||
|
break;
|
|||
|
}
|
|||
|
return Tcl_NewStringObj(separator,1);
|
|||
|
}
|
|||
|
|
|||
|
/*
|
|||
|
* Local Variables:
|
|||
|
* mode: c
|
|||
|
* c-basic-offset: 4
|
|||
|
* fill-column: 78
|
|||
|
* End:
|
|||
|
*/
|