#include "tclWinInt.h"
#define FILE_PENDING (1<<0)
#define FILE_ASYNC (1<<1)
#define FILE_APPEND (1<<2)
#define FILE_TYPE_SERIAL (FILE_TYPE_PIPE+1)
#define FILE_TYPE_CONSOLE (FILE_TYPE_PIPE+2)
typedef struct FileInfo {
Tcl_Channel channel;
int validMask;
int watchMask;
int flags;
HANDLE handle;
struct FileInfo *nextPtr;
int dirty;
} FileInfo;
typedef struct ThreadSpecificData {
FileInfo *firstFilePtr;
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;
typedef struct FileEvent {
Tcl_Event header;
FileInfo *infoPtr;
} FileEvent;
static int FileBlockProc _ANSI_ARGS_((ClientData instanceData,
int mode));
static void FileChannelExitHandler _ANSI_ARGS_((
ClientData clientData));
static void FileCheckProc _ANSI_ARGS_((ClientData clientData,
int flags));
static int FileCloseProc _ANSI_ARGS_((ClientData instanceData,
Tcl_Interp *interp));
static int FileEventProc _ANSI_ARGS_((Tcl_Event *evPtr,
int flags));
static int FileGetHandleProc _ANSI_ARGS_((ClientData instanceData,
int direction, ClientData *handlePtr));
static ThreadSpecificData *FileInit _ANSI_ARGS_((void));
static int FileInputProc _ANSI_ARGS_((ClientData instanceData,
char *buf, int toRead, int *errorCode));
static int FileOutputProc _ANSI_ARGS_((ClientData instanceData,
CONST char *buf, int toWrite, int *errorCode));
static int FileSeekProc _ANSI_ARGS_((ClientData instanceData,
long offset, int mode, int *errorCode));
static Tcl_WideInt FileWideSeekProc _ANSI_ARGS_((ClientData instanceData,
Tcl_WideInt offset, int mode, int *errorCode));
static void FileSetupProc _ANSI_ARGS_((ClientData clientData,
int flags));
static void FileWatchProc _ANSI_ARGS_((ClientData instanceData,
int mask));
static Tcl_ChannelType fileChannelType = {
"file",
TCL_CHANNEL_VERSION_3,
FileCloseProc,
FileInputProc,
FileOutputProc,
FileSeekProc,
NULL,
NULL,
FileWatchProc,
FileGetHandleProc,
NULL,
FileBlockProc,
NULL,
NULL,
FileWideSeekProc,
};
#ifdef HAVE_NO_SEH
static void *ESP;
static void *EBP;
#endif
static ThreadSpecificData *
FileInit()
{
ThreadSpecificData *tsdPtr =
(ThreadSpecificData *)TclThreadDataKeyGet(&dataKey);
if (tsdPtr == NULL) {
tsdPtr = TCL_TSD_INIT(&dataKey);
tsdPtr->firstFilePtr = NULL;
Tcl_CreateEventSource(FileSetupProc, FileCheckProc, NULL);
Tcl_CreateThreadExitHandler(FileChannelExitHandler, NULL);
}
return tsdPtr;
}
static void
FileChannelExitHandler(clientData)
ClientData clientData;
{
Tcl_DeleteEventSource(FileSetupProc, FileCheckProc, NULL);
}
void
FileSetupProc(data, flags)
ClientData data;
int flags;
{
FileInfo *infoPtr;
Tcl_Time blockTime = { 0, 0 };
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
if (!(flags & TCL_FILE_EVENTS)) {
return;
}
for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
infoPtr = infoPtr->nextPtr) {
if (infoPtr->watchMask) {
Tcl_SetMaxBlockTime(&blockTime);
break;
}
}
}
static void
FileCheckProc(data, flags)
ClientData data;
int flags;
{
FileEvent *evPtr;
FileInfo *infoPtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
if (!(flags & TCL_FILE_EVENTS)) {
return;
}
for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
infoPtr = infoPtr->nextPtr) {
if (infoPtr->watchMask && !(infoPtr->flags & FILE_PENDING)) {
infoPtr->flags |= FILE_PENDING;
evPtr = (FileEvent *) ckalloc(sizeof(FileEvent));
evPtr->header.proc = FileEventProc;
evPtr->infoPtr = infoPtr;
Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL);
}
}
}
static int
FileEventProc(evPtr, flags)
Tcl_Event *evPtr;
int flags;
{
FileEvent *fileEvPtr = (FileEvent *)evPtr;
FileInfo *infoPtr;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
if (!(flags & TCL_FILE_EVENTS)) {
return 0;
}
for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
infoPtr = infoPtr->nextPtr) {
if (fileEvPtr->infoPtr == infoPtr) {
infoPtr->flags &= ~(FILE_PENDING);
Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask);
break;
}
}
return 1;
}
static int
FileBlockProc(instanceData, mode)
ClientData instanceData;
int mode;
{
FileInfo *infoPtr = (FileInfo *) instanceData;
if (mode == TCL_MODE_NONBLOCKING) {
infoPtr->flags |= FILE_ASYNC;
} else {
infoPtr->flags &= ~(FILE_ASYNC);
}
return 0;
}
static int
FileCloseProc(instanceData, interp)
ClientData instanceData;
Tcl_Interp *interp;
{
FileInfo *fileInfoPtr = (FileInfo *) instanceData;
FileInfo **nextPtrPtr;
int errorCode = 0;
ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey);
FileWatchProc(instanceData, 0);
if (!TclInExit()
|| ((GetStdHandle(STD_INPUT_HANDLE) != fileInfoPtr->handle)
&& (GetStdHandle(STD_OUTPUT_HANDLE) != fileInfoPtr->handle)
&& (GetStdHandle(STD_ERROR_HANDLE) != fileInfoPtr->handle))) {
if (CloseHandle(fileInfoPtr->handle) == FALSE) {
TclWinConvertError(GetLastError());
errorCode = errno;
}
}
for (nextPtrPtr = &(tsdPtr->firstFilePtr); (*nextPtrPtr) != NULL;
nextPtrPtr = &((*nextPtrPtr)->nextPtr)) {
if ((*nextPtrPtr) == fileInfoPtr) {
(*nextPtrPtr) = fileInfoPtr->nextPtr;
break;
}
}
ckfree((char *)fileInfoPtr);
return errorCode;
}
static int
FileSeekProc(instanceData, offset, mode, errorCodePtr)
ClientData instanceData;
long offset;
int mode;
int *errorCodePtr;
{
FileInfo *infoPtr = (FileInfo *) instanceData;
DWORD moveMethod;
DWORD newPos, newPosHigh;
DWORD oldPos, oldPosHigh;
*errorCodePtr = 0;
if (mode == SEEK_SET) {
moveMethod = FILE_BEGIN;
} else if (mode == SEEK_CUR) {
moveMethod = FILE_CURRENT;
} else {
moveMethod = FILE_END;
}
oldPosHigh = (DWORD)0;
oldPos = SetFilePointer(infoPtr->handle, (LONG)0, &oldPosHigh,
FILE_CURRENT);
if (oldPos == INVALID_SET_FILE_POINTER) {
int winError = GetLastError();
if (winError != NO_ERROR) {
TclWinConvertError(winError);
*errorCodePtr = errno;
return -1;
}
}
newPosHigh = (DWORD)(offset < 0 ? -1 : 0);
newPos = SetFilePointer(infoPtr->handle, (LONG) offset, &newPosHigh,
moveMethod);
if (newPos == INVALID_SET_FILE_POINTER) {
int winError = GetLastError();
if (winError != NO_ERROR) {
TclWinConvertError(winError);
*errorCodePtr = errno;
return -1;
}
}
if (newPosHigh != 0) {
*errorCodePtr = EOVERFLOW;
SetFilePointer(infoPtr->handle, (LONG)oldPos, &oldPosHigh, FILE_BEGIN);
return -1;
}
return (int) newPos;
}
static Tcl_WideInt
FileWideSeekProc(instanceData, offset, mode, errorCodePtr)
ClientData instanceData;
Tcl_WideInt offset;
int mode;
int *errorCodePtr;
{
FileInfo *infoPtr = (FileInfo *) instanceData;
DWORD moveMethod;
DWORD newPos, newPosHigh;
*errorCodePtr = 0;
if (mode == SEEK_SET) {
moveMethod = FILE_BEGIN;
} else if (mode == SEEK_CUR) {
moveMethod = FILE_CURRENT;
} else {
moveMethod = FILE_END;
}
newPosHigh = (DWORD)(offset >> 32);
newPos = SetFilePointer(infoPtr->handle, (LONG) offset, &newPosHigh,
moveMethod);
if (newPos == INVALID_SET_FILE_POINTER) {
int winError = GetLastError();
if (winError != NO_ERROR) {
TclWinConvertError(winError);
*errorCodePtr = errno;
return -1;
}
}
return ((Tcl_WideInt) newPos) | (((Tcl_WideInt) newPosHigh) << 32);
}
static int
FileInputProc(instanceData, buf, bufSize, errorCode)
ClientData instanceData;
char *buf;
int bufSize;
int *errorCode;
{
FileInfo *infoPtr;
DWORD bytesRead;
*errorCode = 0;
infoPtr = (FileInfo *) instanceData;
if (ReadFile(infoPtr->handle, (LPVOID) buf, (DWORD) bufSize, &bytesRead,
(LPOVERLAPPED) NULL) != FALSE) {
return bytesRead;
}
TclWinConvertError(GetLastError());
*errorCode = errno;
if (errno == EPIPE) {
return 0;
}
return -1;
}
static int
FileOutputProc(instanceData, buf, toWrite, errorCode)
ClientData instanceData;
CONST char *buf;
int toWrite;
int *errorCode;
{
FileInfo *infoPtr = (FileInfo *) instanceData;
DWORD bytesWritten;
*errorCode = 0;
if (infoPtr->flags & FILE_APPEND) {
SetFilePointer(infoPtr->handle, 0, NULL, FILE_END);
}
if (WriteFile(infoPtr->handle, (LPVOID) buf, (DWORD) toWrite, &bytesWritten,
(LPOVERLAPPED) NULL) == FALSE) {
TclWinConvertError(GetLastError());
*errorCode = errno;
return -1;
}
infoPtr->dirty = 1;
return bytesWritten;
}
static void
FileWatchProc(instanceData, mask)
ClientData instanceData;
int mask;
{
FileInfo *infoPtr = (FileInfo *) instanceData;
Tcl_Time blockTime = { 0, 0 };
infoPtr->watchMask = mask & infoPtr->validMask;
if (infoPtr->watchMask) {
Tcl_SetMaxBlockTime(&blockTime);
}
}
static int
FileGetHandleProc(instanceData, direction, handlePtr)
ClientData instanceData;
int direction;
ClientData *handlePtr;
{
FileInfo *infoPtr = (FileInfo *) instanceData;
if (direction & infoPtr->validMask) {
*handlePtr = (ClientData) infoPtr->handle;
return TCL_OK;
} else {
return TCL_ERROR;
}
}
Tcl_Channel
TclpOpenFileChannel(interp, pathPtr, mode, permissions)
Tcl_Interp *interp;
Tcl_Obj *pathPtr;
int mode;
int permissions;
{
Tcl_Channel channel = 0;
int channelPermissions;
DWORD accessMode, createMode, shareMode, flags, consoleParams, type;
CONST TCHAR *nativeName;
DCB dcb;
HANDLE handle;
char channelName[16 + TCL_INTEGER_SPACE];
TclFile readFile = NULL;
TclFile writeFile = NULL;
nativeName = (TCHAR*) Tcl_FSGetNativePath(pathPtr);
if (nativeName == NULL) {
return NULL;
}
switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) {
case O_RDONLY:
accessMode = GENERIC_READ;
channelPermissions = TCL_READABLE;
break;
case O_WRONLY:
accessMode = GENERIC_WRITE;
channelPermissions = TCL_WRITABLE;
break;
case O_RDWR:
accessMode = (GENERIC_READ | GENERIC_WRITE);
channelPermissions = (TCL_READABLE | TCL_WRITABLE);
break;
default:
panic("TclpOpenFileChannel: invalid mode value");
break;
}
switch (mode & (O_CREAT | O_EXCL | O_TRUNC)) {
case (O_CREAT | O_EXCL):
case (O_CREAT | O_EXCL | O_TRUNC):
createMode = CREATE_NEW;
break;
case (O_CREAT | O_TRUNC):
createMode = CREATE_ALWAYS;
break;
case O_CREAT:
createMode = OPEN_ALWAYS;
break;
case O_TRUNC:
case (O_TRUNC | O_EXCL):
createMode = TRUNCATE_EXISTING;
break;
default:
createMode = OPEN_EXISTING;
break;
}
if (mode & O_CREAT) {
if (permissions & S_IWRITE) {
flags = FILE_ATTRIBUTE_NORMAL;
} else {
flags = FILE_ATTRIBUTE_READONLY;
}
} else {
flags = (*tclWinProcs->getFileAttributesProc)(nativeName);
if (flags == 0xFFFFFFFF) {
flags = 0;
}
}
shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
handle = (*tclWinProcs->createFileProc)(nativeName, accessMode,
shareMode, NULL, createMode, flags, (HANDLE) NULL);
if (handle == INVALID_HANDLE_VALUE) {
DWORD err;
err = GetLastError();
if ((err & 0xffffL) == ERROR_OPEN_FAILED) {
err = (mode & O_CREAT) ? ERROR_FILE_EXISTS : ERROR_FILE_NOT_FOUND;
}
TclWinConvertError(err);
if (interp != (Tcl_Interp *) NULL) {
Tcl_AppendResult(interp, "couldn't open \"",
Tcl_GetString(pathPtr), "\": ",
Tcl_PosixError(interp), (char *) NULL);
}
return NULL;
}
type = GetFileType(handle);
if (type == FILE_TYPE_CHAR) {
if (GetConsoleMode(handle, &consoleParams)) {
type = FILE_TYPE_CONSOLE;
} else {
dcb.DCBlength = sizeof( DCB ) ;
if (GetCommState(handle, &dcb)) {
type = FILE_TYPE_SERIAL;
}
}
}
channel = NULL;
switch (type) {
case FILE_TYPE_SERIAL:
handle = TclWinSerialReopen(handle, nativeName, accessMode);
if (handle == INVALID_HANDLE_VALUE) {
TclWinConvertError(GetLastError());
if (interp != (Tcl_Interp *) NULL) {
Tcl_AppendResult(interp, "couldn't reopen serial \"",
Tcl_GetString(pathPtr), "\": ",
Tcl_PosixError(interp), (char *) NULL);
}
return NULL;
}
channel = TclWinOpenSerialChannel(handle, channelName,
channelPermissions);
break;
case FILE_TYPE_CONSOLE:
channel = TclWinOpenConsoleChannel(handle, channelName,
channelPermissions);
break;
case FILE_TYPE_PIPE:
if (channelPermissions & TCL_READABLE) {
readFile = TclWinMakeFile(handle);
}
if (channelPermissions & TCL_WRITABLE) {
writeFile = TclWinMakeFile(handle);
}
channel = TclpCreateCommandChannel(readFile, writeFile, NULL, 0, NULL);
break;
case FILE_TYPE_CHAR:
case FILE_TYPE_DISK:
case FILE_TYPE_UNKNOWN:
channel = TclWinOpenFileChannel(handle, channelName,
channelPermissions,
(mode & O_APPEND) ? FILE_APPEND : 0);
break;
default:
channel = NULL;
Tcl_AppendResult(interp, "couldn't open \"",
Tcl_GetString(pathPtr), "\": ",
"bad file type", (char *) NULL);
break;
}
return channel;
}
Tcl_Channel
Tcl_MakeFileChannel(rawHandle, mode)
ClientData rawHandle;
int mode;
{
char channelName[16 + TCL_INTEGER_SPACE];
Tcl_Channel channel = NULL;
HANDLE handle = (HANDLE) rawHandle;
HANDLE dupedHandle;
DCB dcb;
DWORD consoleParams, type;
TclFile readFile = NULL;
TclFile writeFile = NULL;
BOOL result;
if (mode == 0) {
return NULL;
}
type = GetFileType(handle);
if (type == FILE_TYPE_CHAR) {
if (GetConsoleMode(handle, &consoleParams)) {
type = FILE_TYPE_CONSOLE;
} else {
dcb.DCBlength = sizeof( DCB ) ;
if (GetCommState(handle, &dcb)) {
type = FILE_TYPE_SERIAL;
}
}
}
switch (type)
{
case FILE_TYPE_SERIAL:
channel = TclWinOpenSerialChannel(handle, channelName, mode);
break;
case FILE_TYPE_CONSOLE:
channel = TclWinOpenConsoleChannel(handle, channelName, mode);
break;
case FILE_TYPE_PIPE:
if (mode & TCL_READABLE)
{
readFile = TclWinMakeFile(handle);
}
if (mode & TCL_WRITABLE)
{
writeFile = TclWinMakeFile(handle);
}
channel = TclpCreateCommandChannel(readFile, writeFile, NULL, 0, NULL);
break;
case FILE_TYPE_DISK:
case FILE_TYPE_CHAR:
channel = TclWinOpenFileChannel(handle, channelName, mode, 0);
break;
case FILE_TYPE_UNKNOWN:
default:
result = DuplicateHandle(GetCurrentProcess(), handle,
GetCurrentProcess(), &dupedHandle, 0, FALSE,
DUPLICATE_SAME_ACCESS);
if (result != 0) {
return NULL;
}
#ifdef HAVE_NO_SEH
__asm__ __volatile__ (
"movl %esp, _ESP" "\n\t"
"movl %ebp, _EBP");
__asm__ __volatile__ (
"pushl $__except_makefilechannel_handler" "\n\t"
"pushl %fs:0" "\n\t"
"mov %esp, %fs:0");
result = 0;
#else
__try {
#endif
CloseHandle(dupedHandle);
#ifdef HAVE_NO_SEH
__asm__ __volatile__ (
"jmp makefilechannel_pop" "\n"
"makefilechannel_reentry:" "\n\t"
"movl _ESP, %esp" "\n\t"
"movl _EBP, %ebp");
result = 1;
__asm__ __volatile__ (
"makefilechannel_pop:" "\n\t"
"mov (%esp), %eax" "\n\t"
"mov %eax, %fs:0" "\n\t"
"add $8, %esp");
if (result)
return NULL;
#else
}
__except (EXCEPTION_EXECUTE_HANDLER) {
return NULL;
}
#endif
channel = TclWinOpenFileChannel(handle, channelName, mode, 0);
}
return channel;
}
#ifdef HAVE_NO_SEH
static
__attribute__ ((cdecl))
EXCEPTION_DISPOSITION
_except_makefilechannel_handler(
struct _EXCEPTION_RECORD *ExceptionRecord,
void *EstablisherFrame,
struct _CONTEXT *ContextRecord,
void *DispatcherContext)
{
__asm__ __volatile__ (
"jmp makefilechannel_reentry");
return 0;
}
#endif
Tcl_Channel
TclpGetDefaultStdChannel(type)
int type;
{
Tcl_Channel channel;
HANDLE handle;
int mode;
char *bufMode;
DWORD handleId;
switch (type) {
case TCL_STDIN:
handleId = STD_INPUT_HANDLE;
mode = TCL_READABLE;
bufMode = "line";
break;
case TCL_STDOUT:
handleId = STD_OUTPUT_HANDLE;
mode = TCL_WRITABLE;
bufMode = "line";
break;
case TCL_STDERR:
handleId = STD_ERROR_HANDLE;
mode = TCL_WRITABLE;
bufMode = "none";
break;
default:
panic("TclGetDefaultStdChannel: Unexpected channel type");
break;
}
handle = GetStdHandle(handleId);
if ((handle == INVALID_HANDLE_VALUE) || (handle == 0)) {
return (Tcl_Channel) NULL;
}
channel = Tcl_MakeFileChannel(handle, mode);
if (channel == NULL) {
return (Tcl_Channel) NULL;
}
if ((Tcl_SetChannelOption((Tcl_Interp *) NULL, channel, "-translation",
"auto") == TCL_ERROR)
|| (Tcl_SetChannelOption((Tcl_Interp *) NULL, channel, "-eofchar",
"\032 {}") == TCL_ERROR)
|| (Tcl_SetChannelOption((Tcl_Interp *) NULL, channel,
"-buffering", bufMode) == TCL_ERROR)) {
Tcl_Close((Tcl_Interp *) NULL, channel);
return (Tcl_Channel) NULL;
}
return channel;
}
Tcl_Channel
TclWinOpenFileChannel(handle, channelName, permissions, appendMode)
HANDLE handle;
char *channelName;
int permissions;
int appendMode;
{
FileInfo *infoPtr;
ThreadSpecificData *tsdPtr;
tsdPtr = FileInit();
for (infoPtr = tsdPtr->firstFilePtr; infoPtr != NULL;
infoPtr = infoPtr->nextPtr) {
if (infoPtr->handle == (HANDLE) handle) {
return (permissions == infoPtr->validMask) ? infoPtr->channel : NULL;
}
}
infoPtr = (FileInfo *) ckalloc((unsigned) sizeof(FileInfo));
infoPtr->nextPtr = tsdPtr->firstFilePtr;
tsdPtr->firstFilePtr = infoPtr;
infoPtr->validMask = permissions;
infoPtr->watchMask = 0;
infoPtr->flags = appendMode;
infoPtr->handle = handle;
infoPtr->dirty = 0;
wsprintfA(channelName, "file%lx", (int) infoPtr);
infoPtr->channel = Tcl_CreateChannel(&fileChannelType, channelName,
(ClientData) infoPtr, permissions);
Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto");
Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}");
return infoPtr->channel;
}
void
TclWinFlushDirtyChannels ()
{
FileInfo *infoPtr;
ThreadSpecificData *tsdPtr;
tsdPtr = FileInit();
for (infoPtr = tsdPtr->firstFilePtr;
infoPtr != NULL;
infoPtr = infoPtr->nextPtr) {
if (infoPtr->dirty) {
FlushFileBuffers(infoPtr->handle);
infoPtr->dirty = 0;
}
}
}