diff options
Diffstat (limited to 'win/tclWinPipe.c')
-rw-r--r-- | win/tclWinPipe.c | 1588 |
1 files changed, 1143 insertions, 445 deletions
diff --git a/win/tclWinPipe.c b/win/tclWinPipe.c index 8194f64..d91a860 100644 --- a/win/tclWinPipe.c +++ b/win/tclWinPipe.c @@ -9,7 +9,7 @@ * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * - * RCS: @(#) $Id: tclWinPipe.c,v 1.4 1999/03/11 00:19:24 stanton Exp $ + * RCS: @(#) $Id: tclWinPipe.c,v 1.5 1999/04/16 00:48:09 stanton Exp $ */ #include "tclWinInt.h" @@ -27,6 +27,14 @@ static int initialized = 0; /* + * The pipeMutex locks around access to the initialized and procList variables, + * and it is used to protect background threads from being terminated while + * they are using APIs that hold locks. + */ + +TCL_DECLARE_MUTEX(pipeMutex) + +/* * The following defines identify the various types of applications that * run under windows. There is special case code for the various types. */ @@ -98,17 +106,25 @@ typedef struct ProcInfo { static ProcInfo *procList; /* - * State flags used in the PipeInfo structure below. + * Bit masks used in the flags field of the PipeInfo structure below. */ #define PIPE_PENDING (1<<0) /* Message is pending in the queue. */ #define PIPE_ASYNC (1<<1) /* Channel is non-blocking. */ /* + * Bit masks used in the sharedFlags field of the PipeInfo structure below. + */ + +#define PIPE_EOF (1<<2) /* Pipe has reached EOF. */ +#define PIPE_EXTRABYTE (1<<3) /* The reader thread has consumed one byte. */ + +/* * This structure describes per-instance data for a pipe based channel. */ typedef struct PipeInfo { + struct PipeInfo *nextPtr; /* Pointer to next registered pipe. */ Tcl_Channel channel; /* Pointer to channel structure. */ int validMask; /* OR'ed combination of TCL_READABLE, * TCL_WRITABLE, or TCL_EXCEPTION: indicates @@ -122,15 +138,56 @@ typedef struct PipeInfo { TclFile errorFile; /* Error output from pipe. */ int numPids; /* Number of processes attached to pipe. */ Tcl_Pid *pidPtr; /* Pids of attached processes. */ - struct PipeInfo *nextPtr; /* Pointer to next registered pipe. */ + Tcl_ThreadId threadId; /* Thread to which events should be reported. + * This value is used by the reader/writer + * threads. */ + HANDLE writeThread; /* Handle to writer thread. */ + HANDLE readThread; /* Handle to reader thread. */ + HANDLE writable; /* Manual-reset event to signal when the + * writer thread has finished waiting for + * the current buffer to be written. */ + HANDLE readable; /* Manual-reset event to signal when the + * reader thread has finished waiting for + * input. */ + HANDLE startWriter; /* Auto-reset event used by the main thread to + * signal when the writer thread should attempt + * to write to the pipe. */ + HANDLE startReader; /* Auto-reset event used by the main thread to + * signal when the reader thread should attempt + * to read from the pipe. */ + DWORD writeError; /* An error caused by the last background + * write. Set to 0 if no error has been + * detected. This word is shared with the + * writer thread so access must be + * synchronized with the writable object. + */ + char *writeBuf; /* Current background output buffer. + * Access is synchronized with the writable + * object. */ + int writeBufLen; /* Size of write buffer. Access is + * synchronized with the writable + * object. */ + int toWrite; /* Current amount to be written. Access is + * synchronized with the writable object. */ + int readFlags; /* Flags that are shared with the reader + * thread. Access is synchronized with the + * readable object. */ + char extraByte; /* Buffer for extra character consumed by + * reader thread. This byte is shared with + * the reader thread so access must be + * synchronized with the readable object. */ } PipeInfo; -/* - * The following pointer refers to the head of the list of pipes - * that are being watched for file events. - */ +typedef struct ThreadSpecificData { + /* + * The following pointer refers to the head of the list of pipes + * that are being watched for file events. + */ + + PipeInfo *firstPipePtr; +} ThreadSpecificData; -static PipeInfo *firstPipePtr; +static Tcl_ThreadDataKey dataKey; /* * The following structure is what is added to the Tcl event queue when @@ -150,30 +207,33 @@ typedef struct PipeEvent { * Declarations for functions used only in this file. */ -static int ApplicationType(Tcl_Interp *interp, const char *fileName, - char *fullName); -static void BuildCommandLine(int argc, char **argv, Tcl_DString *linePtr); -static void CopyChannel(HANDLE dst, HANDLE src); -static BOOL HasConsole(void); -static TclFile MakeFile(HANDLE handle); -static char * MakeTempFile(Tcl_DString *namePtr); -static int PipeBlockModeProc(ClientData instanceData, int mode); -static void PipeCheckProc _ANSI_ARGS_((ClientData clientData, - int flags)); -static int PipeCloseProc(ClientData instanceData, Tcl_Interp *interp); -static int PipeEventProc(Tcl_Event *evPtr, int flags); -static void PipeExitHandler(ClientData clientData); -static int PipeGetHandleProc(ClientData instanceData, int direction, - ClientData *handlePtr); -static void PipeInit(void); -static int PipeInputProc(ClientData instanceData, char *buf, int toRead, - int *errorCode); -static int PipeOutputProc(ClientData instanceData, char *buf, int toWrite, - int *errorCode); -static void PipeWatchProc(ClientData instanceData, int mask); -static void PipeSetupProc _ANSI_ARGS_((ClientData clientData, - int flags)); -static int TempFileName(char name[MAX_PATH]); +static int ApplicationType(Tcl_Interp *interp, + const char *fileName, char *fullName); +static void BuildCommandLine(const char *executable, int argc, + char **argv, Tcl_DString *linePtr); +static void CopyChannel(HANDLE dst, HANDLE src); +static BOOL HasConsole(void); +static char * MakeTempFile(Tcl_DString *namePtr); +static int PipeBlockModeProc(ClientData instanceData, int mode); +static void PipeCheckProc(ClientData clientData, int flags); +static int PipeClose2Proc(ClientData instanceData, + Tcl_Interp *interp, int flags); +static int PipeEventProc(Tcl_Event *evPtr, int flags); +static void PipeExitHandler(ClientData clientData); +static int PipeGetHandleProc(ClientData instanceData, + int direction, ClientData *handlePtr); +static void PipeInit(void); +static int PipeInputProc(ClientData instanceData, char *buf, + int toRead, int *errorCode); +static int PipeOutputProc(ClientData instanceData, char *buf, + int toWrite, int *errorCode); +static DWORD WINAPI PipeReaderThread(LPVOID arg); +static void PipeSetupProc(ClientData clientData, int flags); +static void PipeWatchProc(ClientData instanceData, int mask); +static DWORD WINAPI PipeWriterThread(LPVOID arg); +static void ProcExitHandler(ClientData clientData); +static int TempFileName(WCHAR name[MAX_PATH]); +static int WaitForRead(PipeInfo *infoPtr, int blocking); /* * This structure describes the channel type structure for command pipe @@ -183,7 +243,7 @@ static int TempFileName(char name[MAX_PATH]); static Tcl_ChannelType pipeChannelType = { "pipe", /* Type name. */ PipeBlockModeProc, /* Set blocking or non-blocking mode.*/ - PipeCloseProc, /* Close proc. */ + TCL_CLOSE2PROC, /* Close proc. */ PipeInputProc, /* Input proc. */ PipeOutputProc, /* Output proc. */ NULL, /* Seek proc. */ @@ -191,6 +251,7 @@ static Tcl_ChannelType pipeChannelType = { NULL, /* Get option proc. */ PipeWatchProc, /* Set up notifier to watch the channel. */ PipeGetHandleProc, /* Get an OS handle from channel. */ + PipeClose2Proc }; /* @@ -212,11 +273,30 @@ static Tcl_ChannelType pipeChannelType = { static void PipeInit() { - initialized = 1; - firstPipePtr = NULL; - procList = NULL; - Tcl_CreateEventSource(PipeSetupProc, PipeCheckProc, NULL); - Tcl_CreateExitHandler(PipeExitHandler, NULL); + ThreadSpecificData *tsdPtr; + + /* + * Check the initialized flag first, then check again in the mutex. + * This is a speed enhancement. + */ + + if (!initialized) { + Tcl_MutexLock(&pipeMutex); + if (!initialized) { + initialized = 1; + procList = NULL; + Tcl_CreateExitHandler(ProcExitHandler, NULL); + } + Tcl_MutexUnlock(&pipeMutex); + } + + tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); + if (tsdPtr == NULL) { + tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->firstPipePtr = NULL; + Tcl_CreateEventSource(PipeSetupProc, PipeCheckProc, NULL); + Tcl_CreateThreadExitHandler(PipeExitHandler, NULL); + } } /* @@ -237,11 +317,36 @@ PipeInit() */ static void -PipeExitHandler(clientData) - ClientData clientData; /* Old window proc */ +PipeExitHandler( + ClientData clientData) /* Old window proc */ { Tcl_DeleteEventSource(PipeSetupProc, PipeCheckProc, NULL); +} + +/* + *---------------------------------------------------------------------- + * + * ProcExitHandler -- + * + * This function is called to cleanup the process list before + * Tcl is unloaded. + * + * Results: + * None. + * + * Side effects: + * Resets the process list. + * + *---------------------------------------------------------------------- + */ + +static void +ProcExitHandler( + ClientData clientData) /* Old window proc */ +{ + Tcl_MutexLock(&pipeMutex); initialized = 0; + Tcl_MutexUnlock(&pipeMutex); } /* @@ -262,27 +367,45 @@ PipeExitHandler(clientData) */ void -PipeSetupProc(data, flags) - ClientData data; /* Not used. */ - int flags; /* Event flags as passed to Tcl_DoOneEvent. */ +PipeSetupProc( + ClientData data, /* Not used. */ + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { PipeInfo *infoPtr; Tcl_Time blockTime = { 0, 0 }; + int block = 1; + WinFile *filePtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!(flags & TCL_FILE_EVENTS)) { return; } /* - * Check to see if there is a watched pipe. If so, poll. + * Look to see if any events are already pending. If they are, poll. */ - for (infoPtr = firstPipePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { - if (infoPtr->watchMask) { - Tcl_SetMaxBlockTime(&blockTime); - break; + for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->watchMask & TCL_WRITABLE) { + filePtr = (WinFile*) infoPtr->writeFile; + if ((filePtr->type == WIN32S_PIPE) + || (WaitForSingleObject(infoPtr->writable, 0) + != WAIT_TIMEOUT)) { + block = 0; + } + } + if (infoPtr->watchMask & TCL_READABLE) { + filePtr = (WinFile*) infoPtr->readFile; + if ((filePtr->type == WIN32S_PIPE) + || (WaitForRead(infoPtr, 0) >= 0)) { + block = 0; + } } } + if (!block) { + Tcl_SetMaxBlockTime(&blockTime); + } } /* @@ -303,24 +426,54 @@ PipeSetupProc(data, flags) */ static void -PipeCheckProc(data, flags) - ClientData data; /* Not used. */ - int flags; /* Event flags as passed to Tcl_DoOneEvent. */ +PipeCheckProc( + ClientData data, /* Not used. */ + int flags) /* Event flags as passed to Tcl_DoOneEvent. */ { PipeInfo *infoPtr; PipeEvent *evPtr; + WinFile *filePtr; + int needEvent; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!(flags & TCL_FILE_EVENTS)) { return; } /* - * Queue events for any watched pipes that don't already have events + * Queue events for any ready pipes that don't already have events * queued. */ - for (infoPtr = firstPipePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { - if (infoPtr->watchMask && !(infoPtr->flags & PIPE_PENDING)) { + for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { + if (infoPtr->flags & PIPE_PENDING) { + continue; + } + + /* + * Queue an event if the pipe is signaled for reading or writing. + */ + + needEvent = 0; + filePtr = (WinFile*) infoPtr->writeFile; + if (infoPtr->watchMask & TCL_WRITABLE) { + if ((filePtr->type == WIN32S_PIPE) + || (WaitForSingleObject(infoPtr->writable, 0) + != WAIT_TIMEOUT)) { + needEvent = 1; + } + } + + filePtr = (WinFile*) infoPtr->readFile; + if (infoPtr->watchMask & TCL_READABLE) { + if ((filePtr->type == WIN32S_PIPE) + || (WaitForRead(infoPtr, 0) >= 0)) { + needEvent = 1; + } + } + + if (needEvent) { infoPtr->flags |= PIPE_PENDING; evPtr = (PipeEvent *) ckalloc(sizeof(PipeEvent)); evPtr->header.proc = PipeEventProc; @@ -333,7 +486,7 @@ PipeCheckProc(data, flags) /* *---------------------------------------------------------------------- * - * MakeFile -- + * TclWinMakeFile -- * * This function constructs a new TclFile from a given data and * type value. @@ -347,9 +500,9 @@ PipeCheckProc(data, flags) *---------------------------------------------------------------------- */ -static TclFile -MakeFile(handle) - HANDLE handle; /* Type-specific data. */ +TclFile +TclWinMakeFile( + HANDLE handle) /* Type-specific data. */ { WinFile *filePtr; @@ -363,37 +516,6 @@ MakeFile(handle) /* *---------------------------------------------------------------------- * - * TclpMakeFile -- - * - * Make a TclFile from a channel. - * - * Results: - * Returns a new TclFile or NULL on failure. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -TclFile -TclpMakeFile(channel, direction) - Tcl_Channel channel; /* Channel to get file from. */ - int direction; /* Either TCL_READABLE or TCL_WRITABLE. */ -{ - HANDLE handle; - - if (Tcl_GetChannelHandle(channel, direction, - (ClientData *) &handle) == TCL_OK) { - return MakeFile(handle); - } else { - return (TclFile) NULL; - } -} - -/* - *---------------------------------------------------------------------- - * * TempFileName -- * * Gets a temporary file name and deals with the fact that the @@ -414,117 +536,58 @@ TclpMakeFile(channel, direction) static int TempFileName(name) - char name[MAX_PATH]; /* Buffer in which name for temporary + WCHAR name[MAX_PATH]; /* Buffer in which name for temporary * file gets stored. */ { - if ((GetTempPath(MAX_PATH, name) == 0) || - (GetTempFileName(name, "TCL", 0, name) == 0)) { - name[0] = '.'; - name[1] = '\0'; - if (GetTempFileName(name, "TCL", 0, name) == 0) { - return 0; + TCHAR *prefix; + + prefix = (tclWinProcs->useWide) ? (TCHAR *) L"TCL" : (TCHAR *) "TCL"; + if ((*tclWinProcs->getTempPathProc)(MAX_PATH, name) != 0) { + if ((*tclWinProcs->getTempFileNameProc)((TCHAR *) name, prefix, 0, + name) != 0) { + return 1; } } - return 1; + if (tclWinProcs->useWide) { + ((WCHAR *) name)[0] = '.'; + ((WCHAR *) name)[1] = '\0'; + } else { + ((char *) name)[0] = '.'; + ((char *) name)[1] = '\0'; + } + return (*tclWinProcs->getTempFileNameProc)((TCHAR *) name, prefix, 0, + name); } /* *---------------------------------------------------------------------- * - * TclpCreateTempFile -- + * TclpMakeFile -- * - * This function opens a unique file with the property that it - * will be deleted when its file handle is closed. The temporary - * file is created in the system temporary directory. + * Make a TclFile from a channel. * * Results: - * Returns a valid TclFile, or NULL on failure. + * Returns a new TclFile or NULL on failure. * * Side effects: - * Creates a new temporary file. + * None. * *---------------------------------------------------------------------- */ TclFile -TclpCreateTempFile(contents, namePtr) - char *contents; /* String to write into temp file, or NULL. */ - Tcl_DString *namePtr; /* If non-NULL, pointer to initialized - * DString that is filled with the name of - * the temp file that was created. */ +TclpMakeFile(channel, direction) + Tcl_Channel channel; /* Channel to get file from. */ + int direction; /* Either TCL_READABLE or TCL_WRITABLE. */ { - char name[MAX_PATH]; HANDLE handle; - if (TempFileName(name) == 0) { - return NULL; - } - - handle = CreateFile(name, GENERIC_READ | GENERIC_WRITE, 0, NULL, - CREATE_ALWAYS, FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, - NULL); - if (handle == INVALID_HANDLE_VALUE) { - goto error; - } - - /* - * Write the file out, doing line translations on the way. - */ - - if (contents != NULL) { - DWORD result, length; - char *p; - - for (p = contents; *p != '\0'; p++) { - if (*p == '\n') { - length = p - contents; - if (length > 0) { - if (!WriteFile(handle, contents, length, &result, NULL)) { - goto error; - } - } - if (!WriteFile(handle, "\r\n", 2, &result, NULL)) { - goto error; - } - contents = p+1; - } - } - length = p - contents; - if (length > 0) { - if (!WriteFile(handle, contents, length, &result, NULL)) { - goto error; - } - } - } - - if (SetFilePointer(handle, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF) { - goto error; - } - - if (namePtr != NULL) { - Tcl_DStringAppend(namePtr, name, -1); - } - - /* - * Under Win32s a file created with FILE_FLAG_DELETE_ON_CLOSE won't - * actually be deleted when it is closed, so we have to do it ourselves. - */ - - if (TclWinGetPlatformId() == VER_PLATFORM_WIN32s) { - TmpFile *tmpFilePtr = (TmpFile *) ckalloc(sizeof(TmpFile)); - tmpFilePtr->file.type = WIN32S_TMPFILE; - tmpFilePtr->file.handle = handle; - strcpy(tmpFilePtr->name, name); - return (TclFile)tmpFilePtr; + if (Tcl_GetChannelHandle(channel, direction, + (ClientData *) &handle) == TCL_OK) { + return TclWinMakeFile(handle); } else { - return MakeFile(handle); + return (TclFile) NULL; } - - error: - TclWinConvertError(GetLastError()); - CloseHandle(handle); - DeleteFile(name); - return NULL; } /* @@ -546,13 +609,14 @@ TclpCreateTempFile(contents, namePtr) TclFile TclpOpenFile(path, mode) - char *path; - int mode; + CONST char *path; /* The name of the file to open. */ + int mode; /* In what mode to open the file? */ { HANDLE handle; DWORD accessMode, createMode, shareMode, flags; - SECURITY_ATTRIBUTES sec; - + Tcl_DString ds; + TCHAR *nativePath; + /* * Map the access bits to the NT access mode. */ @@ -596,28 +660,21 @@ TclpOpenFile(path, mode) break; } + nativePath = Tcl_WinUtfToTChar(path, -1, &ds); + /* * If the file is not being created, use the existing file attributes. */ flags = 0; if (!(mode & O_CREAT)) { - flags = GetFileAttributes(path); + flags = (*tclWinProcs->getFileAttributesProc)(nativePath); if (flags == 0xFFFFFFFF) { flags = 0; } } /* - * Set up the security attributes so this file is not inherited by - * child processes. - */ - - sec.nLength = sizeof(sec); - sec.lpSecurityDescriptor = NULL; - sec.bInheritHandle = 0; - - /* * Set up the file sharing mode. We want to allow simultaneous access. */ @@ -627,10 +684,14 @@ TclpOpenFile(path, mode) * Now we get to create the file. */ - handle = CreateFile(path, accessMode, shareMode, &sec, createMode, flags, - (HANDLE) NULL); + handle = (*tclWinProcs->createFileProc)(nativePath, accessMode, + shareMode, NULL, createMode, flags, NULL); + Tcl_DStringFree(&ds); + if (handle == INVALID_HANDLE_VALUE) { - DWORD err = GetLastError(); + DWORD err; + + err = GetLastError(); if ((err & 0xffffL) == ERROR_OPEN_FAILED) { err = (mode & O_CREAT) ? ERROR_FILE_EXISTS : ERROR_FILE_NOT_FOUND; } @@ -646,7 +707,98 @@ TclpOpenFile(path, mode) SetFilePointer(handle, 0, NULL, FILE_END); } - return MakeFile(handle); + return TclWinMakeFile(handle); +} + +/* + *---------------------------------------------------------------------- + * + * TclpCreateTempFile -- + * + * This function opens a unique file with the property that it + * will be deleted when its file handle is closed. The temporary + * file is created in the system temporary directory. + * + * Results: + * Returns a valid TclFile, or NULL on failure. + * + * Side effects: + * Creates a new temporary file. + * + *---------------------------------------------------------------------- + */ + +TclFile +TclpCreateTempFile(contents) + CONST char *contents; /* String to write into temp file, or NULL. */ +{ + WCHAR name[MAX_PATH]; + HANDLE handle; + + if (TempFileName(name) == 0) { + return NULL; + } + + handle = (*tclWinProcs->createFileProc)((TCHAR *) name, + GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, + FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, NULL); + if (handle == INVALID_HANDLE_VALUE) { + goto error; + } + + /* + * Write the file out, doing line translations on the way. + */ + + if (contents != NULL) { + DWORD result, length; + CONST char *p; + + for (p = contents; *p != '\0'; p++) { + if (*p == '\n') { + length = p - contents; + if (length > 0) { + if (!WriteFile(handle, contents, length, &result, NULL)) { + goto error; + } + } + if (!WriteFile(handle, "\r\n", 2, &result, NULL)) { + goto error; + } + contents = p+1; + } + } + length = p - contents; + if (length > 0) { + if (!WriteFile(handle, contents, length, &result, NULL)) { + goto error; + } + } + if (SetFilePointer(handle, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF) { + goto error; + } + } + + /* + * Under Win32s a file created with FILE_FLAG_DELETE_ON_CLOSE won't + * actually be deleted when it is closed, so we have to do it ourselves. + */ + + if (TclWinGetPlatformId() == VER_PLATFORM_WIN32s) { + TmpFile *tmpFilePtr = (TmpFile *) ckalloc(sizeof(TmpFile)); + tmpFilePtr->file.type = WIN32S_TMPFILE; + tmpFilePtr->file.handle = handle; + lstrcpyA(tmpFilePtr->name, (char *) name); + return (TclFile) tmpFilePtr; + } else { + return TclWinMakeFile(handle); + } + + error: + TclWinConvertError(GetLastError()); + CloseHandle(handle); + (*tclWinProcs->deleteFileProc)((TCHAR *) name); + return NULL; } /* @@ -667,39 +819,42 @@ TclpOpenFile(path, mode) */ int -TclpCreatePipe(readPipe, writePipe) - TclFile *readPipe; /* Location to store file handle for +TclpCreatePipe( + TclFile *readPipe, /* Location to store file handle for * read side of pipe. */ - TclFile *writePipe; /* Location to store file handle for + TclFile *writePipe) /* Location to store file handle for * write side of pipe. */ { HANDLE readHandle, writeHandle; if (CreatePipe(&readHandle, &writeHandle, NULL, 0) != 0) { - *readPipe = MakeFile(readHandle); - *writePipe = MakeFile(writeHandle); + *readPipe = TclWinMakeFile(readHandle); + *writePipe = TclWinMakeFile(writeHandle); return 1; } if (TclWinGetPlatformId() == VER_PLATFORM_WIN32s) { WinPipe *readPipePtr, *writePipePtr; char buf[MAX_PATH]; + int bytes; - if (TempFileName(buf) != 0) { + if (TempFileName((WCHAR *) buf) != 0) { + bytes = strlen((char *) buf) + 1; readPipePtr = (WinPipe *) ckalloc(sizeof(WinPipe)); writePipePtr = (WinPipe *) ckalloc(sizeof(WinPipe)); readPipePtr->file.type = WIN32S_PIPE; readPipePtr->otherPtr = writePipePtr; - readPipePtr->fileName = strcpy(ckalloc(strlen(buf) + 1), buf); + readPipePtr->fileName = (char *) ckalloc(bytes); + lstrcpyA(readPipePtr->fileName, buf); readPipePtr->file.handle = INVALID_HANDLE_VALUE; writePipePtr->file.type = WIN32S_PIPE; writePipePtr->otherPtr = readPipePtr; writePipePtr->fileName = readPipePtr->fileName; writePipePtr->file.handle = INVALID_HANDLE_VALUE; - *readPipe = (TclFile)readPipePtr; - *writePipe = (TclFile)writePipePtr; + *readPipe = (TclFile) readPipePtr; + *writePipe = (TclFile) writePipePtr; return 1; } @@ -727,8 +882,8 @@ TclpCreatePipe(readPipe, writePipe) */ int -TclpCloseFile(file) - TclFile file; /* The file to close. */ +TclpCloseFile( + TclFile file) /* The file to close. */ { WinFile *filePtr = (WinFile *) file; WinPipe *pipePtr; @@ -736,17 +891,28 @@ TclpCloseFile(file) switch (filePtr->type) { case WIN_FILE: case WIN32S_TMPFILE: - if (CloseHandle(filePtr->handle) == FALSE) { - TclWinConvertError(GetLastError()); - ckfree((char *) filePtr); - return -1; + /* + * Don't close the Win32 handle if the handle is a standard channel + * during the exit process. Otherwise, one thread may kill the stdio + * of another. + */ + + if (!TclInExit() + || ((GetStdHandle(STD_INPUT_HANDLE) != filePtr->handle) + && (GetStdHandle(STD_OUTPUT_HANDLE) != filePtr->handle) + && (GetStdHandle(STD_ERROR_HANDLE) != filePtr->handle))) { + if (CloseHandle(filePtr->handle) == FALSE) { + TclWinConvertError(GetLastError()); + ckfree((char *) filePtr); + return -1; + } } /* * Simulate deleting the file on close for Win32s. */ if (filePtr->type == WIN32S_TMPFILE) { - DeleteFile(((TmpFile*)filePtr)->name); + DeleteFileA(((TmpFile *) filePtr)->name); } break; @@ -759,13 +925,13 @@ TclpCloseFile(file) if (pipePtr->file.handle != INVALID_HANDLE_VALUE) { CloseHandle(pipePtr->file.handle); } - DeleteFile(pipePtr->fileName); + DeleteFileA(pipePtr->fileName); ckfree((char *) pipePtr->fileName); } break; default: - panic("Tcl_CloseFile: unexpected file type"); + panic("TclpCloseFile: unexpected file type"); } ckfree((char *) filePtr); @@ -792,16 +958,19 @@ TclpCloseFile(file) */ unsigned long -TclpGetPid(pid) - Tcl_Pid pid; /* The HANDLE of the child process. */ +TclpGetPid( + Tcl_Pid pid) /* The HANDLE of the child process. */ { ProcInfo *infoPtr; - + + Tcl_MutexLock(&pipeMutex); for (infoPtr = procList; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { if (infoPtr->hProcess == (HANDLE) pid) { + Tcl_MutexUnlock(&pipeMutex); return infoPtr->dwProcessId; } } + Tcl_MutexUnlock(&pipeMutex); return (unsigned long) -1; } @@ -823,7 +992,7 @@ TclpGetPid(pid) * * Results: * The return value is TCL_ERROR and an error message is left in - * interp->result if there was a problem creating the child + * the interp's result if there was a problem creating the child * process. Otherwise, the return value is TCL_OK and *pidPtr is * filled with the process id of the child process. * @@ -834,56 +1003,50 @@ TclpGetPid(pid) */ int -TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, - pidPtr) - Tcl_Interp *interp; /* Interpreter in which to leave errors that +TclpCreateProcess( + Tcl_Interp *interp, /* Interpreter in which to leave errors that * occurred when creating the child process. * Error messages from the child process * itself are sent to errorFile. */ - int argc; /* Number of arguments in following array. */ - char **argv; /* Array of argument strings. argv[0] + int argc, /* Number of arguments in following array. */ + char **argv, /* Array of argument strings. argv[0] * contains the name of the executable * converted to native format (using the * Tcl_TranslateFileName call). Additional * arguments have not been converted. */ - TclFile inputFile; /* If non-NULL, gives the file to use as + TclFile inputFile, /* If non-NULL, gives the file to use as * input for the child process. If inputFile * file is not readable or is NULL, the child * will receive no standard input. */ - TclFile outputFile; /* If non-NULL, gives the file that + TclFile outputFile, /* If non-NULL, gives the file that * receives output from the child process. If * outputFile file is not writeable or is * NULL, output from the child will be * discarded. */ - TclFile errorFile; /* If non-NULL, gives the file that + TclFile errorFile, /* If non-NULL, gives the file that * receives errors from the child process. If * errorFile file is not writeable or is NULL, * errors from the child will be discarded. * errorFile may be the same as outputFile. */ - Tcl_Pid *pidPtr; /* If this procedure is successful, pidPtr + Tcl_Pid *pidPtr) /* If this procedure is successful, pidPtr * is filled with the process id of the child * process. */ { int result, applType, createFlags; - Tcl_DString cmdLine; - STARTUPINFO startInfo; + Tcl_DString cmdLine; /* Complete command line (TCHAR). */ + STARTUPINFOA startInfo; PROCESS_INFORMATION procInfo; SECURITY_ATTRIBUTES secAtts; HANDLE hProcess, h, inputHandle, outputHandle, errorHandle; - char execPath[MAX_PATH]; - char *originalName; + char execPath[MAX_PATH * TCL_UTF_MAX]; WinFile *filePtr; - if (!initialized) { - PipeInit(); - } + PipeInit(); applType = ApplicationType(interp, argv[0], execPath); if (applType == APPL_NONE) { return TCL_ERROR; } - originalName = argv[0]; - argv[0] = execPath; result = TCL_ERROR; Tcl_DStringInit(&cmdLine); @@ -903,7 +1066,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, char *inputFileName, *outputFileName; Tcl_DString inputTempFile, outputTempFile; - BuildCommandLine(argc, argv, &cmdLine); + BuildCommandLine(execPath, argc, argv, &cmdLine); ZeroMemory(&startInfo, sizeof(startInfo)); startInfo.cb = sizeof(startInfo); @@ -922,8 +1085,9 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, h = INVALID_HANDLE_VALUE; inputFileName = MakeTempFile(&inputTempFile); if (inputFileName != NULL) { - h = CreateFile(inputFileName, GENERIC_WRITE, 0, - NULL, CREATE_ALWAYS, 0, NULL); + h = CreateFileA((char *) inputFileName, + GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, 0, + NULL); } if (h == INVALID_HANDLE_VALUE) { Tcl_AppendResult(interp, "couldn't duplicate input handle: ", @@ -935,7 +1099,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, break; } case WIN32S_PIPE: { - inputFileName = ((WinPipe*)inputFile)->fileName; + inputFileName = (char *) ((WinPipe *) inputFile)->fileName; break; } } @@ -944,7 +1108,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, inputFileName = "nul"; } if (outputFile != NULL) { - filePtr = (WinFile *)outputFile; + filePtr = (WinFile *) outputFile; if (filePtr->type == WIN_FILE) { outputFileName = MakeTempFile(&outputTempFile); if (outputFileName == NULL) { @@ -954,7 +1118,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, } outputHandle = filePtr->handle; } else if (filePtr->type == WIN32S_PIPE) { - outputFileName = ((WinPipe*)outputFile)->fileName; + outputFileName = (char *) ((WinPipe *) outputFile)->fileName; } } if (outputFileName == NULL) { @@ -980,9 +1144,9 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, result = TCL_OK; } } else { - if (CreateProcess(NULL, Tcl_DStringValue(&cmdLine), NULL, NULL, - FALSE, DETACHED_PROCESS, NULL, NULL, &startInfo, - &procInfo) != 0) { + if (CreateProcessA(NULL, Tcl_DStringValue(&cmdLine), + NULL, NULL, FALSE, DETACHED_PROCESS, NULL, NULL, + &startInfo, &procInfo) != 0) { CloseHandle(procInfo.hThread); while (1) { if (GetExitCodeProcess(procInfo.hProcess, &status) == FALSE) { @@ -1005,7 +1169,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, } if (result != TCL_OK) { TclWinConvertError(GetLastError()); - Tcl_AppendResult(interp, "couldn't execute \"", originalName, + Tcl_AppendResult(interp, "couldn't execute \"", argv[0], "\": ", Tcl_PosixError(interp), (char *) NULL); } @@ -1017,7 +1181,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, * file owned by the caller. */ - h = CreateFile(outputFileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, + h = CreateFileA(outputFileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, 0, NULL); if (h != INVALID_HANDLE_VALUE) { CopyChannel(outputHandle, h); @@ -1026,11 +1190,11 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, } if (inputFileName == Tcl_DStringValue(&inputTempFile)) { - DeleteFile(inputFileName); + DeleteFileA(inputFileName); } if (outputFileName == Tcl_DStringValue(&outputTempFile)) { - DeleteFile(outputFileName); + DeleteFileA(outputFileName); } Tcl_DStringFree(&inputTempFile); @@ -1140,7 +1304,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, CloseHandle(h); } } else { - startInfo.hStdOutput = CreateFile("NUL:", GENERIC_WRITE, 0, + startInfo.hStdOutput = CreateFileA("NUL:", GENERIC_WRITE, 0, &secAtts, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); } } else { @@ -1160,7 +1324,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, * deep sink. */ - startInfo.hStdError = CreateFile("NUL:", GENERIC_WRITE, 0, + startInfo.hStdError = CreateFileA("NUL:", GENERIC_WRITE, 0, &secAtts, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); } else { DuplicateHandle(hProcess, errorHandle, hProcess, &startInfo.hStdError, @@ -1271,16 +1435,22 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, * using ab~1.def instead of "a b.default"). */ - BuildCommandLine(argc, argv, &cmdLine); + BuildCommandLine(execPath, argc, argv, &cmdLine); - if (!CreateProcess(NULL, Tcl_DStringValue(&cmdLine), NULL, NULL, TRUE, - createFlags, NULL, NULL, &startInfo, &procInfo)) { + if ((*tclWinProcs->createProcessProc)(NULL, + (TCHAR *) Tcl_DStringValue(&cmdLine), NULL, NULL, TRUE, + createFlags, NULL, NULL, &startInfo, &procInfo) == 0) { TclWinConvertError(GetLastError()); - Tcl_AppendResult(interp, "couldn't execute \"", originalName, + Tcl_AppendResult(interp, "couldn't execute \"", argv[0], "\": ", Tcl_PosixError(interp), (char *) NULL); goto end; } + /* + * This wait is used to force the OS to give some time to the DOS + * process. + */ + if (applType == APPL_DOS) { WaitForSingleObject(hProcess, 50); } @@ -1338,7 +1508,9 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, static BOOL HasConsole() { - HANDLE handle = CreateFile("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, + HANDLE handle; + + handle = CreateFileA("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (handle != INVALID_HANDLE_VALUE) { @@ -1386,18 +1558,22 @@ HasConsole() */ static int -ApplicationType(interp, originalName, fullPath) +ApplicationType(interp, originalName, fullName) Tcl_Interp *interp; /* Interp, for error message. */ const char *originalName; /* Name of the application to find. */ - char fullPath[MAX_PATH]; /* Filled with complete path to + char fullName[]; /* Filled with complete path to * application. */ { - int applType, i; + int applType, i, nameLen, found; HANDLE hFile; - char *ext, *rest; + TCHAR *rest; + char *ext; char buf[2]; - DWORD read; + DWORD attr, read; IMAGE_DOS_HEADER header; + Tcl_DString nameBuf, ds; + TCHAR *nativeName; + WCHAR nativeFullPath[MAX_PATH]; static char extensions[][5] = {"", ".com", ".exe", ".bat"}; /* Look for the program as an external program. First try the name @@ -1414,29 +1590,43 @@ ApplicationType(interp, originalName, fullPath) */ applType = APPL_NONE; + Tcl_DStringInit(&nameBuf); + Tcl_DStringAppend(&nameBuf, originalName, -1); + nameLen = Tcl_DStringLength(&nameBuf); + for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) { - lstrcpyn(fullPath, originalName, MAX_PATH - 5); - lstrcat(fullPath, extensions[i]); - - SearchPath(NULL, fullPath, NULL, MAX_PATH, fullPath, &rest); + Tcl_DStringSetLength(&nameBuf, nameLen); + Tcl_DStringAppend(&nameBuf, extensions[i], -1); + nativeName = Tcl_WinUtfToTChar(Tcl_DStringValue(&nameBuf), + Tcl_DStringLength(&nameBuf), &ds); + found = (*tclWinProcs->searchPathProc)(NULL, nativeName, NULL, + MAX_PATH, nativeFullPath, &rest); + Tcl_DStringFree(&ds); + if (found == 0) { + continue; + } /* * Ignore matches on directories or data files, return if identified * a known type. */ - if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) { + attr = (*tclWinProcs->getFileAttributesProc)((TCHAR *) nativeFullPath); + if ((attr == 0xffffffff) || (attr & FILE_ATTRIBUTE_DIRECTORY)) { continue; } + strcpy(fullName, Tcl_WinTCharToUtf((TCHAR *) nativeFullPath, -1, &ds)); + Tcl_DStringFree(&ds); - ext = strrchr(fullPath, '.'); - if ((ext != NULL) && (strcmpi(ext, ".bat") == 0)) { + ext = strrchr(fullName, '.'); + if ((ext != NULL) && (stricmp(ext, ".bat") == 0)) { applType = APPL_DOS; break; } - - hFile = CreateFile(fullPath, GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + + hFile = (*tclWinProcs->createFileProc)((TCHAR *) nativeFullPath, + GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { continue; } @@ -1453,7 +1643,7 @@ ApplicationType(interp, originalName, fullPath) */ CloseHandle(hFile); - if ((ext != NULL) && (strcmpi(ext, ".com") == 0)) { + if ((ext != NULL) && (strcmp(ext, ".com") == 0)) { applType = APPL_DOS; break; } @@ -1497,6 +1687,7 @@ ApplicationType(interp, originalName, fullPath) } break; } + Tcl_DStringFree(&nameBuf); if (applType == APPL_NONE) { TclWinConvertError(GetLastError()); @@ -1513,7 +1704,10 @@ ApplicationType(interp, originalName, fullPath) * application name from the arguments. */ - GetShortPathName(fullPath, fullPath, MAX_PATH); + (*tclWinProcs->getShortPathNameProc)((TCHAR *) nativeFullPath, + nativeFullPath, MAX_PATH); + strcpy(fullName, Tcl_WinTCharToUtf((TCHAR *) nativeFullPath, -1, &ds)); + Tcl_DStringFree(&ds); } return applType; } @@ -1538,18 +1732,26 @@ ApplicationType(interp, originalName, fullPath) */ static void -BuildCommandLine(argc, argv, linePtr) - int argc; /* Number of arguments. */ - char **argv; /* Argument strings. */ - Tcl_DString *linePtr; /* Initialized Tcl_DString that receives the - * command line. */ +BuildCommandLine( + CONST char *executable, /* Full path of executable (including + * extension). Replacement for argv[0]. */ + int argc, /* Number of arguments. */ + char **argv, /* Argument strings in UTF. */ + Tcl_DString *linePtr) /* Initialized Tcl_DString that receives the + * command line (TCHAR). */ { - char *start, *special; + CONST char *arg, *start, *special; int quote, i; + Tcl_DString ds; + + Tcl_DStringInit(&ds); for (i = 0; i < argc; i++) { - if (i > 0) { - Tcl_DStringAppend(linePtr, " ", 1); + if (i == 0) { + arg = executable; + } else { + arg = argv[i]; + Tcl_DStringAppend(&ds, " ", 1); } quote = 0; @@ -1557,21 +1759,21 @@ BuildCommandLine(argc, argv, linePtr) quote = 1; } else { for (start = argv[i]; *start != '\0'; start++) { - if (isspace(*start)) { + if (isspace(*start)) { /* INTL: ISO space. */ quote = 1; break; } } } if (quote) { - Tcl_DStringAppend(linePtr, "\"", 1); + Tcl_DStringAppend(&ds, "\"", 1); } - start = argv[i]; - for (special = argv[i]; ; ) { + start = arg; + for (special = arg; ; ) { if ((*special == '\\') && (special[1] == '\\' || special[1] == '"')) { - Tcl_DStringAppend(linePtr, start, special - start); + Tcl_DStringAppend(&ds, start, special - start); start = special; while (1) { special++; @@ -1581,19 +1783,19 @@ BuildCommandLine(argc, argv, linePtr) * N * 2 + 1 backslashes then a quote. */ - Tcl_DStringAppend(linePtr, start, special - start); + Tcl_DStringAppend(&ds, start, special - start); break; } if (*special != '\\') { break; } } - Tcl_DStringAppend(linePtr, start, special - start); + Tcl_DStringAppend(&ds, start, special - start); start = special; } if (*special == '"') { - Tcl_DStringAppend(linePtr, start, special - start); - Tcl_DStringAppend(linePtr, "\\\"", 2); + Tcl_DStringAppend(&ds, start, special - start); + Tcl_DStringAppend(&ds, "\\\"", 2); start = special + 1; } if (*special == '\0') { @@ -1601,11 +1803,13 @@ BuildCommandLine(argc, argv, linePtr) } special++; } - Tcl_DStringAppend(linePtr, start, special - start); + Tcl_DStringAppend(&ds, start, special - start); if (quote) { - Tcl_DStringAppend(linePtr, "\"", 1); + Tcl_DStringAppend(&ds, "\"", 1); } } + Tcl_WinUtfToTChar(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds), linePtr); + Tcl_DStringFree(&ds); } /* @@ -1636,7 +1840,7 @@ MakeTempFile(namePtr) { char name[MAX_PATH]; - if (TempFileName(name) == 0) { + if (TempFileName((WCHAR *) name) == 0) { return NULL; } @@ -1665,9 +1869,9 @@ MakeTempFile(namePtr) */ static void -CopyChannel(dst, src) - HANDLE dst; /* Destination file. */ - HANDLE src; /* Source file. */ +CopyChannel( + HANDLE dst, /* Destination file. */ + HANDLE src) /* Source file. */ { char buf[8192]; DWORD dwRead, dwWrite; @@ -1701,29 +1905,42 @@ CopyChannel(dst, src) */ Tcl_Channel -TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) - TclFile readFile; /* If non-null, gives the file for reading. */ - TclFile writeFile; /* If non-null, gives the file for writing. */ - TclFile errorFile; /* If non-null, gives the file where errors +TclpCreateCommandChannel( + TclFile readFile, /* If non-null, gives the file for reading. */ + TclFile writeFile, /* If non-null, gives the file for writing. */ + TclFile errorFile, /* If non-null, gives the file where errors * can be read. */ - int numPids; /* The number of pids in the pid array. */ - Tcl_Pid *pidPtr; /* An array of process identifiers. */ + int numPids, /* The number of pids in the pid array. */ + Tcl_Pid *pidPtr) /* An array of process identifiers. */ { - char channelName[20]; + char channelName[16 + TCL_INTEGER_SPACE]; int channelId; + DWORD id; PipeInfo *infoPtr = (PipeInfo *) ckalloc((unsigned) sizeof(PipeInfo)); + OSVERSIONINFO os; + int useThreads; - if (!initialized) { - PipeInit(); - } + /* + * Fetch the OS version info. + */ + + os.dwOSVersionInfoSize = sizeof(os); + GetVersionEx(&os); + useThreads = (os.dwPlatformId != VER_PLATFORM_WIN32s); + + PipeInit(); infoPtr->watchMask = 0; infoPtr->flags = 0; + infoPtr->readFlags = 0; infoPtr->readFile = readFile; infoPtr->writeFile = writeFile; infoPtr->errorFile = errorFile; infoPtr->numPids = numPids; infoPtr->pidPtr = pidPtr; + infoPtr->writeBuf = 0; + infoPtr->writeBufLen = 0; + infoPtr->writeError = 0; /* * Use one of the fds associated with the channel as the @@ -1734,7 +1951,7 @@ TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) WinPipe *pipePtr = (WinPipe *) readFile; if (pipePtr->file.type == WIN32S_PIPE && pipePtr->file.handle == INVALID_HANDLE_VALUE) { - pipePtr->file.handle = CreateFile(pipePtr->fileName, GENERIC_READ, + pipePtr->file.handle = CreateFileA(pipePtr->fileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); } channelId = (int) pipePtr->file.handle; @@ -1747,10 +1964,41 @@ TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) } infoPtr->validMask = 0; + + infoPtr->threadId = Tcl_GetCurrentThread(); + if (readFile != NULL) { + if (useThreads) { + /* + * Start the background reader thread. + */ + + infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL); + infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->readThread = CreateThread(NULL, 8000, PipeReaderThread, + infoPtr, 0, &id); + SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); + } else { + infoPtr->readThread = 0; + } infoPtr->validMask |= TCL_READABLE; + } else { + infoPtr->readThread = 0; } if (writeFile != NULL) { + if (useThreads) { + /* + * Start the background writeer thwrite. + */ + + infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL); + infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->writeThread = CreateThread(NULL, 8000, PipeWriterThread, + infoPtr, 0, &id); + SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); + } else { + infoPtr->writeThread = 0; + } infoPtr->validMask |= TCL_WRITABLE; } @@ -1758,9 +2006,11 @@ TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) * For backward compatibility with previous versions of Tcl, we * use "file%d" as the base name for pipes even though it would * be more natural to use "pipe%d". + * Use the pointer to keep the channel names unique, in case + * channels share handles (stdin/stdout). */ - sprintf(channelName, "file%d", channelId); + wsprintfA(channelName, "file%lx", infoPtr); infoPtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName, (ClientData) infoPtr, infoPtr->validMask); @@ -1783,26 +2033,26 @@ TclpCreateCommandChannel(readFile, writeFile, errorFile, numPids, pidPtr) * TclGetAndDetachPids -- * * Stores a list of the command PIDs for a command channel in - * interp->result. + * the interp's result. * * Results: * None. * * Side effects: - * Modifies interp->result. + * Modifies the interp's result. * *---------------------------------------------------------------------- */ void -TclGetAndDetachPids(interp, chan) - Tcl_Interp *interp; - Tcl_Channel chan; +TclGetAndDetachPids( + Tcl_Interp *interp, + Tcl_Channel chan) { PipeInfo *pipePtr; Tcl_ChannelType *chanTypePtr; int i; - char buf[20]; + char buf[TCL_INTEGER_SPACE]; /* * Punt if the channel is not a command channel. @@ -1815,7 +2065,7 @@ TclGetAndDetachPids(interp, chan) pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan); for (i = 0; i < pipePtr->numPids; i++) { - sprintf(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i])); + wsprintfA(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i])); Tcl_AppendElement(interp, buf); Tcl_DetachPids(1, &(pipePtr->pidPtr[i])); } @@ -1842,9 +2092,9 @@ TclGetAndDetachPids(interp, chan) */ static int -PipeBlockModeProc(instanceData, mode) - ClientData instanceData; /* Instance data for channel. */ - int mode; /* TCL_MODE_BLOCKING or +PipeBlockModeProc( + ClientData instanceData, /* Instance data for channel. */ + int mode) /* TCL_MODE_BLOCKING or * TCL_MODE_NONBLOCKING. */ { PipeInfo *infoPtr = (PipeInfo *) instanceData; @@ -1867,7 +2117,7 @@ PipeBlockModeProc(instanceData, mode) /* *---------------------------------------------------------------------- * - * PipeCloseProc -- + * PipeClose2Proc -- * * Closes a pipe based IO channel. * @@ -1881,41 +2131,127 @@ PipeBlockModeProc(instanceData, mode) */ static int -PipeCloseProc(instanceData, interp) - ClientData instanceData; /* Pointer to PipeInfo structure. */ - Tcl_Interp *interp; /* For error reporting. */ +PipeClose2Proc( + ClientData instanceData, /* Pointer to PipeInfo structure. */ + Tcl_Interp *interp, /* For error reporting. */ + int flags) /* Flags that indicate which side to close. */ { PipeInfo *pipePtr = (PipeInfo *) instanceData; Tcl_Channel errChan; int errorCode, result; PipeInfo *infoPtr, **nextPtrPtr; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); - /* - * Remove the file from the list of watched files. - */ + errorCode = 0; + if ((!flags || (flags == TCL_CLOSE_READ)) + && (pipePtr->readFile != NULL)) { + /* + * Clean up the background thread if necessary. Note that this + * must be done before we can close the file, since the + * thread may be blocking trying to read from the pipe. + */ - for (nextPtrPtr = &firstPipePtr, infoPtr = *nextPtrPtr; infoPtr != NULL; - nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { - if (infoPtr == (PipeInfo *)pipePtr) { - *nextPtrPtr = infoPtr->nextPtr; - break; - } - } + if (pipePtr->readThread) { + /* + * Forcibly terminate the background thread. We cannot rely on the + * thread to cleanly terminate itself because we have no way of + * closing the pipe handle without blocking in the case where the + * thread is in the middle of an I/O operation. Note that we need + * to guard against terminating the thread while it is in the + * middle of Tcl_ThreadAlert because it won't be able to release + * the notifier lock. + */ - errorCode = 0; - if (pipePtr->readFile != NULL) { + Tcl_MutexLock(&pipeMutex); + TerminateThread(pipePtr->readThread, 0); + Tcl_MutexUnlock(&pipeMutex); + + /* + * Wait for the thread to terminate. This ensures that we are + * completely cleaned up before we leave this function. + */ + + WaitForSingleObject(pipePtr->readThread, INFINITE); + CloseHandle(pipePtr->readThread); + CloseHandle(pipePtr->readable); + CloseHandle(pipePtr->startReader); + pipePtr->readThread = NULL; + } if (TclpCloseFile(pipePtr->readFile) != 0) { errorCode = errno; } + pipePtr->validMask &= ~TCL_READABLE; + pipePtr->readFile = NULL; } - if (pipePtr->writeFile != NULL) { + if ((!flags || (flags & TCL_CLOSE_WRITE)) + && (pipePtr->writeFile != NULL)) { + /* + * Wait for the writer thread to finish the current buffer, then + * terminate the thread and close the handles. If the channel is + * nonblocking, there should be no pending write operations. + */ + + if (pipePtr->writeThread) { + WaitForSingleObject(pipePtr->writable, INFINITE); + + /* + * Forcibly terminate the background thread. We cannot rely on the + * thread to cleanly terminate itself because we have no way of + * closing the pipe handle without blocking in the case where the + * thread is in the middle of an I/O operation. Note that we need + * to guard against terminating the thread while it is in the + * middle of Tcl_ThreadAlert because it won't be able to release + * the notifier lock. + */ + + Tcl_MutexLock(&pipeMutex); + TerminateThread(pipePtr->writeThread, 0); + Tcl_MutexUnlock(&pipeMutex); + + + /* + * Wait for the thread to terminate. This ensures that we are + * completely cleaned up before we leave this function. + */ + + WaitForSingleObject(pipePtr->writeThread, INFINITE); + CloseHandle(pipePtr->writeThread); + CloseHandle(pipePtr->writable); + CloseHandle(pipePtr->startWriter); + pipePtr->writeThread = NULL; + } if (TclpCloseFile(pipePtr->writeFile) != 0) { if (errorCode == 0) { errorCode = errno; } } + pipePtr->validMask &= ~TCL_WRITABLE; + pipePtr->writeFile = NULL; } - + + pipePtr->watchMask &= pipePtr->validMask; + + /* + * Don't free the channel if any of the flags were set. + */ + + if (flags) { + return errorCode; + } + + /* + * Remove the file from the list of watched files. + */ + + for (nextPtrPtr = &(tsdPtr->firstPipePtr), infoPtr = *nextPtrPtr; + infoPtr != NULL; + nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { + if (infoPtr == (PipeInfo *)pipePtr) { + *nextPtrPtr = infoPtr->nextPtr; + break; + } + } + /* * Wrap the error file into a channel and give it to the cleanup * routine. If we are running in Win32s, just delete the error file @@ -1935,15 +2271,23 @@ PipeCloseProc(instanceData, interp) filePtr = (WinFile*)pipePtr->errorFile; errChan = Tcl_MakeFileChannel((ClientData) filePtr->handle, TCL_READABLE); + ckfree((char *) filePtr); } } else { errChan = NULL; } + result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr, errChan); + if (pipePtr->numPids > 0) { ckfree((char *) pipePtr->pidPtr); } + + if (pipePtr->writeBuf != NULL) { + ckfree(pipePtr->writeBuf); + } + ckfree((char*) pipePtr); if (errorCode == 0) { @@ -1971,17 +2315,17 @@ PipeCloseProc(instanceData, interp) */ static int -PipeInputProc(instanceData, buf, bufSize, errorCode) - ClientData instanceData; /* Pipe state. */ - char *buf; /* Where to store data read. */ - int bufSize; /* How much space is available +PipeInputProc( + ClientData instanceData, /* Pipe state. */ + char *buf, /* Where to store data read. */ + int bufSize, /* How much space is available * in the buffer? */ - int *errorCode; /* Where to store error code. */ + int *errorCode) /* Where to store error code. */ { PipeInfo *infoPtr = (PipeInfo *) instanceData; WinFile *filePtr = (WinFile*) infoPtr->readFile; - DWORD count; - DWORD bytesRead; + DWORD count, bytesRead = 0; + int result; *errorCode = 0; if (filePtr->type == WIN32S_PIPE) { @@ -1989,7 +2333,7 @@ PipeInputProc(instanceData, buf, bufSize, errorCode) panic("PipeInputProc: child process isn't finished writing"); } if (filePtr->handle == INVALID_HANDLE_VALUE) { - filePtr->handle = CreateFile(((WinPipe *)filePtr)->fileName, + filePtr->handle = CreateFileA(((WinPipe *)filePtr)->fileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); } @@ -1998,50 +2342,63 @@ PipeInputProc(instanceData, buf, bufSize, errorCode) } } else { /* - * Pipes will block until the requested number of bytes has been - * read. To avoid blocking unnecessarily, we look ahead and only - * read as much as is available. + * Synchronize with the reader thread. */ - if (PeekNamedPipe(filePtr->handle, (LPVOID) NULL, (DWORD) 0, - (LPDWORD) NULL, &count, (LPDWORD) NULL) == TRUE) { - if ((count != 0) && ((DWORD) bufSize > count)) { - bufSize = (int) count; + result = WaitForRead(infoPtr, (infoPtr->flags & PIPE_ASYNC) ? 0 : 1); - /* - * This code is commented out because on Win95 we don't get - * notifier of eof on a pipe unless we try to read it. - * The correct solution is to move to threads. - */ + /* + * If an error occurred, return immediately. + */ -/* } else if ((count == 0) && (infoPtr->flags & PIPE_ASYNC)) { */ -/* errno = *errorCode = EAGAIN; */ -/* return -1; */ - } else if ((count == 0) && !(infoPtr->flags & PIPE_ASYNC)) { - bufSize = 1; + if (result == -1) { + *errorCode = errno; + return -1; + } + + if (infoPtr->readFlags & PIPE_EXTRABYTE) { + /* + * The reader thread consumed 1 byte as a side effect of + * waiting so we need to move it into the buffer. + */ + + *buf = infoPtr->extraByte; + infoPtr->readFlags &= ~PIPE_EXTRABYTE; + buf++; + bufSize--; + bytesRead = 1; + + /* + * If further read attempts would block, return what we have. + */ + + if (result == 0) { + return bytesRead; } - } else { - goto error; } } /* - * Note that we will block on reads from a console buffer until a - * full line has been entered. The only way I know of to get - * around this is to write a console driver. We should probably - * do this at some point, but for now, we just block. + * Attempt to read bufSize bytes. The read will return immediately + * if there is any data available. Otherwise it will block until + * at least one byte is available or an EOF occurs. */ - if (ReadFile(filePtr->handle, (LPVOID) buf, (DWORD) bufSize, &bytesRead, - (LPOVERLAPPED) NULL) == FALSE) { - goto error; + if (ReadFile(filePtr->handle, (LPVOID) buf, (DWORD) bufSize, &count, + (LPOVERLAPPED) NULL) == TRUE) { + return bytesRead + count; + } else if (bytesRead) { + /* + * Ignore errors if we have data to return. + */ + + return bytesRead; } - - return bytesRead; error: TclWinConvertError(GetLastError()); if (errno == EPIPE) { + infoPtr->readFlags |= PIPE_EOF; return 0; } *errorCode = errno; @@ -2067,27 +2424,78 @@ PipeInputProc(instanceData, buf, bufSize, errorCode) */ static int -PipeOutputProc(instanceData, buf, toWrite, errorCode) - ClientData instanceData; /* Pipe state. */ - char *buf; /* The data buffer. */ - int toWrite; /* How many bytes to write? */ - int *errorCode; /* Where to store error code. */ +PipeOutputProc( + ClientData instanceData, /* Pipe state. */ + char *buf, /* The data buffer. */ + int toWrite, /* How many bytes to write? */ + int *errorCode) /* Where to store error code. */ { PipeInfo *infoPtr = (PipeInfo *) instanceData; WinFile *filePtr = (WinFile*) infoPtr->writeFile; - DWORD bytesWritten; + DWORD bytesWritten, timeout; *errorCode = 0; - if (WriteFile(filePtr->handle, (LPVOID) buf, (DWORD) toWrite, - &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) { - TclWinConvertError(GetLastError()); - if (errno == EPIPE) { - return 0; - } - *errorCode = errno; - return -1; + timeout = (infoPtr->flags & PIPE_ASYNC) ? 0 : INFINITE; + if (WaitForSingleObject(infoPtr->writable, timeout) == WAIT_TIMEOUT) { + /* + * The writer thread is blocked waiting for a write to complete + * and the channel is in non-blocking mode. + */ + + errno = EAGAIN; + goto error; + } + + /* + * Check for a background error on the last write. + */ + + if (infoPtr->writeError) { + TclWinConvertError(infoPtr->writeError); + infoPtr->writeError = 0; + goto error; + } + + if (infoPtr->flags & PIPE_ASYNC) { + /* + * The pipe is non-blocking, so copy the data into the output + * buffer and restart the writer thread. + */ + + if (toWrite > infoPtr->writeBufLen) { + /* + * Reallocate the buffer to be large enough to hold the data. + */ + + if (infoPtr->writeBuf) { + ckfree(infoPtr->writeBuf); + } + infoPtr->writeBufLen = toWrite; + infoPtr->writeBuf = ckalloc(toWrite); + } + memcpy(infoPtr->writeBuf, buf, toWrite); + infoPtr->toWrite = toWrite; + ResetEvent(infoPtr->writable); + SetEvent(infoPtr->startWriter); + bytesWritten = toWrite; + } else { + /* + * In the blocking case, just try to write the buffer directly. + * This avoids an unnecessary copy. + */ + + if (WriteFile(filePtr->handle, (LPVOID) buf, (DWORD) toWrite, + &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) { + TclWinConvertError(GetLastError()); + goto error; + } } return bytesWritten; + + error: + *errorCode = errno; + return -1; + } /* @@ -2112,16 +2520,16 @@ PipeOutputProc(instanceData, buf, toWrite, errorCode) */ static int -PipeEventProc(evPtr, flags) - Tcl_Event *evPtr; /* Event to service. */ - int flags; /* Flags that indicate what events to +PipeEventProc( + Tcl_Event *evPtr, /* Event to service. */ + int flags) /* Flags that indicate what events to * handle, such as TCL_FILE_EVENTS. */ { PipeEvent *pipeEvPtr = (PipeEvent *)evPtr; PipeInfo *infoPtr; WinFile *filePtr; int mask; -/* DWORD count;*/ + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); if (!(flags & TCL_FILE_EVENTS)) { return 0; @@ -2134,7 +2542,8 @@ PipeEventProc(evPtr, flags) * event is in the queue. */ - for (infoPtr = firstPipePtr; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { + for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; + infoPtr = infoPtr->nextPtr) { if (pipeEvPtr->infoPtr == infoPtr) { infoPtr->flags &= ~(PIPE_PENDING); break; @@ -2152,36 +2561,29 @@ PipeEventProc(evPtr, flags) /* * If we aren't on Win32s, check to see if the pipe is readable. Note * that we can't tell if a pipe is writable, so we always report it - * as being writable. + * as being writable unless we have detected EOF. */ - filePtr = (WinFile*) ((PipeInfo*)infoPtr)->readFile; - if (filePtr->type != WIN32S_PIPE) { - - /* - * On windows 95, PeekNamedPipe returns 0 on eof so we can't - * distinguish underflow from eof. The correct solution is to - * switch to the threaded implementation. - */ - mask = TCL_WRITABLE|TCL_READABLE; -/* if (PeekNamedPipe(filePtr->handle, (LPVOID) NULL, (DWORD) 0, */ -/* (LPDWORD) NULL, &count, (LPDWORD) NULL) == TRUE) { */ -/* if (count != 0) { */ -/* mask |= TCL_READABLE; */ -/* } */ -/* } else { */ - - /* - * If the pipe has been closed by the other side, then - * mark the pipe as readable, but not writable. - */ + filePtr = (WinFile*) ((PipeInfo*)infoPtr)->writeFile; + mask = 0; + if (infoPtr->watchMask & TCL_WRITABLE) { + if ((filePtr->type == WIN32S_PIPE) + || (WaitForSingleObject(infoPtr->writable, 0) + != WAIT_TIMEOUT)) { + mask = TCL_WRITABLE; + } + } -/* if (GetLastError() == ERROR_BROKEN_PIPE) { */ -/* mask = TCL_READABLE; */ -/* } */ -/* } */ - } else { - mask = TCL_READABLE | TCL_WRITABLE; + filePtr = (WinFile*) ((PipeInfo*)infoPtr)->readFile; + if (infoPtr->watchMask & TCL_READABLE) { + if ((filePtr->type == WIN32S_PIPE) + || (WaitForRead(infoPtr, 0) >= 0)) { + if (infoPtr->readFlags & PIPE_EOF) { + mask = TCL_READABLE; + } else { + mask |= TCL_READABLE; + } + } } /* @@ -2210,27 +2612,29 @@ PipeEventProc(evPtr, flags) */ static void -PipeWatchProc(instanceData, mask) - ClientData instanceData; /* Pipe state. */ - int mask; /* What events to watch for; OR-ed +PipeWatchProc( + ClientData instanceData, /* Pipe state. */ + int mask) /* What events to watch for, OR-ed * combination of TCL_READABLE, * TCL_WRITABLE and TCL_EXCEPTION. */ { PipeInfo **nextPtrPtr, *ptr; PipeInfo *infoPtr = (PipeInfo *) instanceData; int oldMask = infoPtr->watchMask; + ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); /* - * For now, we just send a message to ourselves so we can poll the - * channel for readable events. + * Since most of the work is handled by the background threads, + * we just need to update the watchMask and then force the notifier + * to poll once. */ infoPtr->watchMask = mask & infoPtr->validMask; if (infoPtr->watchMask) { Tcl_Time blockTime = { 0, 0 }; if (!oldMask) { - infoPtr->nextPtr = firstPipePtr; - firstPipePtr = infoPtr; + infoPtr->nextPtr = tsdPtr->firstPipePtr; + tsdPtr->firstPipePtr = infoPtr; } Tcl_SetMaxBlockTime(&blockTime); } else { @@ -2239,7 +2643,7 @@ PipeWatchProc(instanceData, mask) * Remove the pipe from the list of watched pipes. */ - for (nextPtrPtr = &firstPipePtr, ptr = *nextPtrPtr; + for (nextPtrPtr = &(tsdPtr->firstPipePtr), ptr = *nextPtrPtr; ptr != NULL; nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { if (infoPtr == ptr) { @@ -2270,10 +2674,10 @@ PipeWatchProc(instanceData, mask) */ static int -PipeGetHandleProc(instanceData, direction, handlePtr) - ClientData instanceData; /* The pipe state. */ - int direction; /* TCL_READABLE or TCL_WRITABLE */ - ClientData *handlePtr; /* Where to store the handle. */ +PipeGetHandleProc( + ClientData instanceData, /* The pipe state. */ + int direction, /* TCL_READABLE or TCL_WRITABLE */ + ClientData *handlePtr) /* Where to store the handle. */ { PipeInfo *infoPtr = (PipeInfo *) instanceData; WinFile *filePtr; @@ -2282,7 +2686,7 @@ PipeGetHandleProc(instanceData, direction, handlePtr) filePtr = (WinFile*) infoPtr->readFile; if (filePtr->type == WIN32S_PIPE) { if (filePtr->handle == INVALID_HANDLE_VALUE) { - filePtr->handle = CreateFile(((WinPipe *)filePtr)->fileName, + filePtr->handle = CreateFileA(((WinPipe *)filePtr)->fileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); } @@ -2321,19 +2725,17 @@ PipeGetHandleProc(instanceData, direction, handlePtr) */ Tcl_Pid -Tcl_WaitPid(pid, statPtr, options) - Tcl_Pid pid; - int *statPtr; - int options; +Tcl_WaitPid( + Tcl_Pid pid, + int *statPtr, + int options) { ProcInfo *infoPtr, **prevPtrPtr; int flags; Tcl_Pid result; DWORD ret; - if (!initialized) { - PipeInit(); - } + PipeInit(); /* * If no pid is specified, do nothing. @@ -2348,6 +2750,7 @@ Tcl_WaitPid(pid, statPtr, options) * Find the process on the process list. */ + Tcl_MutexLock(&pipeMutex); prevPtrPtr = &procList; for (infoPtr = procList; infoPtr != NULL; prevPtrPtr = &infoPtr->nextPtr, infoPtr = infoPtr->nextPtr) { @@ -2355,12 +2758,13 @@ Tcl_WaitPid(pid, statPtr, options) break; } } + Tcl_MutexUnlock(&pipeMutex); /* * If the pid is not one of the processes we know about (we started it) * then do nothing. */ - + if (infoPtr == NULL) { *statPtr = 0; return 0; @@ -2431,8 +2835,10 @@ TclWinAddProcess(hProcess, id) ProcInfo *procPtr = (ProcInfo *) ckalloc(sizeof(ProcInfo)); procPtr->hProcess = hProcess; procPtr->dwProcessId = id; + Tcl_MutexLock(&pipeMutex); procPtr->nextPtr = procList; procList = procPtr; + Tcl_MutexUnlock(&pipeMutex); } /* @@ -2454,18 +2860,18 @@ TclWinAddProcess(hProcess, id) /* ARGSUSED */ int -Tcl_PidObjCmd(dummy, interp, objc, objv) - ClientData dummy; /* Not used. */ - Tcl_Interp *interp; /* Current interpreter. */ - int objc; /* Number of arguments. */ - Tcl_Obj *CONST *objv; /* Argument strings. */ +Tcl_PidObjCmd( + ClientData dummy, /* Not used. */ + Tcl_Interp *interp, /* Current interpreter. */ + int objc, /* Number of arguments. */ + Tcl_Obj *CONST *objv) /* Argument strings. */ { Tcl_Channel chan; Tcl_ChannelType *chanTypePtr; PipeInfo *pipePtr; int i; Tcl_Obj *resultPtr; - char buf[20]; + char buf[TCL_INTEGER_SPACE]; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?channelId?"); @@ -2473,7 +2879,7 @@ Tcl_PidObjCmd(dummy, interp, objc, objv) } if (objc == 1) { resultPtr = Tcl_GetObjResult(interp); - sprintf(buf, "%lu", (unsigned long) getpid()); + wsprintfA(buf, "%lu", (unsigned long) getpid()); Tcl_SetStringObj(resultPtr, buf, -1); } else { chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL), @@ -2489,10 +2895,302 @@ Tcl_PidObjCmd(dummy, interp, objc, objv) pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan); resultPtr = Tcl_GetObjResult(interp); for (i = 0; i < pipePtr->numPids; i++) { - sprintf(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i])); + wsprintfA(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i])); Tcl_ListObjAppendElement(/*interp*/ NULL, resultPtr, Tcl_NewStringObj(buf, -1)); } } return TCL_OK; } + +/* + *---------------------------------------------------------------------- + * + * WaitForRead -- + * + * Wait until some data is available, the pipe is at + * EOF or the reader thread is blocked waiting for data (if the + * channel is in non-blocking mode). + * + * Results: + * Returns 1 if pipe is readable. Returns 0 if there is no data + * on the pipe, but there is buffered data. Returns -1 if an + * error occurred. If an error occurred, the threads may not + * be synchronized. + * + * Side effects: + * Updates the shared state flags and may consume 1 byte of data + * from the pipe. If no error occurred, the reader thread is + * blocked waiting for a signal from the main thread. + * + *---------------------------------------------------------------------- + */ + +static int +WaitForRead( + PipeInfo *infoPtr, /* Pipe state. */ + int blocking) /* Indicates whether call should be + * blocking or not. */ +{ + DWORD timeout, count; + HANDLE *handle = ((WinFile *) infoPtr->readFile)->handle; + + while (1) { + /* + * Synchronize with the reader thread. + */ + + timeout = blocking ? INFINITE : 0; + if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) { + /* + * The reader thread is blocked waiting for data and the channel + * is in non-blocking mode. + */ + + errno = EAGAIN; + return -1; + } + + /* + * At this point, the two threads are synchronized, so it is safe + * to access shared state. + */ + + + /* + * If the pipe has hit EOF, it is always readable. + */ + + if (infoPtr->readFlags & PIPE_EOF) { + return 1; + } + + /* + * Check to see if there is any data sitting in the pipe. + */ + + if (PeekNamedPipe(handle, (LPVOID) NULL, (DWORD) 0, + (LPDWORD) NULL, &count, (LPDWORD) NULL) != TRUE) { + TclWinConvertError(GetLastError()); + /* + * Check to see if the peek failed because of EOF. + */ + + if (errno == EPIPE) { + infoPtr->readFlags |= PIPE_EOF; + return 1; + } + + /* + * Ignore errors if there is data in the buffer. + */ + + if (infoPtr->readFlags & PIPE_EXTRABYTE) { + return 0; + } else { + return -1; + } + } + + /* + * We found some data in the pipe, so it must be readable. + */ + + if (count > 0) { + return 1; + } + + /* + * The pipe isn't readable, but there is some data sitting + * in the buffer, so return immediately. + */ + + if (infoPtr->readFlags & PIPE_EXTRABYTE) { + return 0; + } + + /* + * There wasn't any data available, so reset the thread and + * try again. + */ + + ResetEvent(infoPtr->readable); + SetEvent(infoPtr->startReader); + } +} + +/* + *---------------------------------------------------------------------- + * + * PipeReaderThread -- + * + * This function runs in a separate thread and waits for input + * to become available on a pipe. + * + * Results: + * None. + * + * Side effects: + * Signals the main thread when input become available. May + * cause the main thread to wake up by posting a message. May + * consume one byte from the pipe for each wait operation. + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +PipeReaderThread(LPVOID arg) +{ + PipeInfo *infoPtr = (PipeInfo *)arg; + HANDLE *handle = ((WinFile *) infoPtr->readFile)->handle; + DWORD count, err; + int done = 0; + + while (!done) { + /* + * Wait for the main thread to signal before attempting to wait. + */ + + WaitForSingleObject(infoPtr->startReader, INFINITE); + + /* + * Try waiting for 0 bytes. This will block until some data is + * available on NT, but will return immediately on Win 95. So, + * if no data is available after the first read, we block until + * we can read a single byte off of the pipe. + */ + + if ((ReadFile(handle, NULL, 0, &count, NULL) == FALSE) + || (PeekNamedPipe(handle, NULL, 0, NULL, &count, + NULL) == FALSE)) { + /* + * The error is a result of an EOF condition, so set the + * EOF bit before signalling the main thread. + */ + + err = GetLastError(); + if (err == ERROR_BROKEN_PIPE) { + infoPtr->readFlags |= PIPE_EOF; + done = 1; + } else if (err == ERROR_INVALID_HANDLE) { + break; + } + } else if (count == 0) { + if (ReadFile(handle, &(infoPtr->extraByte), 1, &count, NULL) + != FALSE) { + /* + * One byte was consumed as a side effect of waiting + * for the pipe to become readable. + */ + + infoPtr->readFlags |= PIPE_EXTRABYTE; + } else { + err = GetLastError(); + if (err == ERROR_BROKEN_PIPE) { + /* + * The error is a result of an EOF condition, so set the + * EOF bit before signalling the main thread. + */ + + infoPtr->readFlags |= PIPE_EOF; + done = 1; + } else if (err == ERROR_INVALID_HANDLE) { + break; + } + } + } + + + /* + * Signal the main thread by signalling the readable event and + * then waking up the notifier thread. + */ + + SetEvent(infoPtr->readable); + + /* + * Alert the foreground thread. Note that we need to treat this like + * a critical section so the foreground thread does not terminate + * this thread while we are holding a mutex in the notifier code. + */ + + Tcl_MutexLock(&pipeMutex); + Tcl_ThreadAlert(infoPtr->threadId); + Tcl_MutexUnlock(&pipeMutex); + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * PipeWriterThread -- + * + * This function runs in a separate thread and writes data + * onto a pipe. + * + * Results: + * Always returns 0. + * + * Side effects: + * Signals the main thread when an output operation is completed. + * May cause the main thread to wake up by posting a message. + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +PipeWriterThread(LPVOID arg) +{ + + PipeInfo *infoPtr = (PipeInfo *)arg; + HANDLE *handle = ((WinFile *) infoPtr->writeFile)->handle; + DWORD count, toWrite; + char *buf; + int done = 0; + + while (!done) { + /* + * Wait for the main thread to signal before attempting to write. + */ + + WaitForSingleObject(infoPtr->startWriter, INFINITE); + + buf = infoPtr->writeBuf; + toWrite = infoPtr->toWrite; + + /* + * Loop until all of the bytes are written or an error occurs. + */ + + while (toWrite > 0) { + if (WriteFile(handle, buf, toWrite, &count, NULL) == FALSE) { + infoPtr->writeError = GetLastError(); + done = 1; + break; + } else { + toWrite -= count; + buf += count; + } + } + + /* + * Signal the main thread by signalling the writable event and + * then waking up the notifier thread. + */ + + SetEvent(infoPtr->writable); + + /* + * Alert the foreground thread. Note that we need to treat this like + * a critical section so the foreground thread does not terminate + * this thread while we are holding a mutex in the notifier code. + */ + + Tcl_MutexLock(&pipeMutex); + Tcl_ThreadAlert(infoPtr->threadId); + Tcl_MutexUnlock(&pipeMutex); + } + return 0; +} + |