diff options
Diffstat (limited to 'win/tclWinPipe.c')
| -rw-r--r-- | win/tclWinPipe.c | 3092 |
1 files changed, 1891 insertions, 1201 deletions
diff --git a/win/tclWinPipe.c b/win/tclWinPipe.c index 76695e0..a9eec6d 100644 --- a/win/tclWinPipe.c +++ b/win/tclWinPipe.c @@ -1,24 +1,17 @@ -/* +/* * tclWinPipe.c -- * - * This file implements the Windows-specific exec pipeline functions, - * the "pipe" channel driver, and the "pid" Tcl command. + * This file implements the Windows-specific exec pipeline functions, the + * "pipe" channel driver, and the "pid" Tcl command. * * Copyright (c) 1996-1997 by Sun Microsystems, Inc. * - * 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.3 1998/09/14 18:40:20 stanton Exp $ + * See the file "license.terms" for information on usage and redistribution of + * this file, and for a DISCLAIMER OF ALL WARRANTIES. */ #include "tclWinInt.h" -#include <dos.h> -#include <fcntl.h> -#include <io.h> -#include <sys/stat.h> - /* * The following variable is used to tell whether this module has been * initialized. @@ -27,8 +20,16 @@ static int initialized = 0; /* - * The following defines identify the various types of applications that - * run under windows. There is special case code for the various types. + * 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. */ #define APPL_NONE 0 @@ -37,17 +38,16 @@ static int initialized = 0; #define APPL_WIN32 3 /* - * The following constants and structures are used to encapsulate the state - * of various types of files used in a pipeline. + * The following constants and structures are used to encapsulate the state of + * various types of files used in a pipeline. This used to have a 1 && 2 that + * supported Win32s. */ -#define WIN32S_PIPE 1 /* Win32s emulated pipe. */ -#define WIN32S_TMPFILE 2 /* Win32s emulated temporary file. */ -#define WIN_FILE 3 /* Basic Win32 file. */ +#define WIN_FILE 3 /* Basic Win32 file. */ /* - * This structure encapsulates the common state associated with all file - * types used in a pipeline. + * This structure encapsulates the common state associated with all file types + * used in a pipeline. */ typedef struct WinFile { @@ -56,36 +56,6 @@ typedef struct WinFile { } WinFile; /* - * The following structure is used to keep track of temporary files under - * Win32s and delete the disk file when the open handle is closed. - * The type field will be WIN32S_TMPFILE. - */ - -typedef struct TmpFile { - WinFile file; /* Common part. */ - char name[MAX_PATH]; /* Name of temp file. */ -} TmpFile; - -/* - * The following structure represents a synchronous pipe under Win32s. - * The type field will be WIN32S_PIPE. The handle field will refer to - * an open file when Tcl is reading from the "pipe", otherwise it is - * INVALID_HANDLE_VALUE. - */ - -typedef struct WinPipe { - WinFile file; /* Common part. */ - struct WinPipe *otherPtr; /* Pointer to the WinPipe structure that - * corresponds to the other end of this - * pipe. */ - char *fileName; /* The name of the staging file that gets - * the data written to this pipe. Malloc'd. - * and shared by both ends of the pipe. Only - * when both ends are freed will fileName be - * freed and the file it refers to deleted. */ -} WinPipe; - -/* * This list is used to map from pids to process handles. */ @@ -98,17 +68,31 @@ 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. */ + +/* + * TODO: It appears the whole EXTRABYTE machinery is in place to support + * outdated Win 95 systems. If this can be confirmed, much code can be + * deleted. + */ + +/* * 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,27 +106,70 @@ 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 stopWriter; /* Manual-reset event used to alert the reader + * thread to fall-out and exit */ + HANDLE startReader; /* Auto-reset event used by the main thread to + * signal when the reader thread should + * attempt to read from the pipe. */ + HANDLE stopReader; /* Manual-reset event used to alert the reader + * thread to fall-out and exit */ + 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 - * pipe events are generated. + * The following structure is what is added to the Tcl event queue when pipe + * events are generated. */ typedef struct PipeEvent { - Tcl_Event header; /* Information that is standard for - * all events. */ - PipeInfo *infoPtr; /* Pointer to pipe info structure. Note - * that we still have to verify that the - * pipe exists before dereferencing this + Tcl_Event header; /* Information that is standard for all + * events. */ + PipeInfo *infoPtr; /* Pointer to pipe info structure. Note that + * we still have to verify that the pipe + * exists before dereferencing this * pointer. */ } PipeEvent; @@ -150,40 +177,41 @@ 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, + const char **argv, Tcl_DString *linePtr); +static BOOL HasConsole(void); +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 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, + const 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 int TempFileName(TCHAR name[MAX_PATH]); +static int WaitForRead(PipeInfo *infoPtr, int blocking); +static void PipeThreadActionProc(ClientData instanceData, + int action); /* - * This structure describes the channel type structure for command pipe - * based IO. + * This structure describes the channel type structure for command pipe based + * I/O. */ -static Tcl_ChannelType pipeChannelType = { +static const Tcl_ChannelType pipeChannelType = { "pipe", /* Type name. */ - PipeBlockModeProc, /* Set blocking or non-blocking mode.*/ - PipeCloseProc, /* Close proc. */ + TCL_CHANNEL_VERSION_5, /* v5 channel */ + TCL_CLOSE2PROC, /* Close proc. */ PipeInputProc, /* Input proc. */ PipeOutputProc, /* Output proc. */ NULL, /* Seek proc. */ @@ -191,6 +219,13 @@ static Tcl_ChannelType pipeChannelType = { NULL, /* Get option proc. */ PipeWatchProc, /* Set up notifier to watch the channel. */ PipeGetHandleProc, /* Get an OS handle from channel. */ + PipeClose2Proc, /* close2proc */ + PipeBlockModeProc, /* Set blocking or non-blocking mode.*/ + NULL, /* flush proc. */ + NULL, /* handler proc. */ + NULL, /* wide seek proc */ + PipeThreadActionProc, /* thread action proc */ + NULL /* truncate */ }; /* @@ -210,22 +245,39 @@ static Tcl_ChannelType pipeChannelType = { */ static void -PipeInit() +PipeInit(void) { - 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_MutexUnlock(&pipeMutex); + } + + tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); + if (tsdPtr == NULL) { + tsdPtr = TCL_TSD_INIT(&dataKey); + tsdPtr->firstPipePtr = NULL; + Tcl_CreateEventSource(PipeSetupProc, PipeCheckProc, NULL); + } } /* *---------------------------------------------------------------------- * - * PipeExitHandler -- + * TclpFinalizePipes -- * - * This function is called to cleanup the pipe module before - * Tcl is unloaded. + * This function is called from Tcl_FinalizeThread to finalize the + * platform specific pipe subsystem. * * Results: * None. @@ -236,12 +288,15 @@ PipeInit() *---------------------------------------------------------------------- */ -static void -PipeExitHandler(clientData) - ClientData clientData; /* Old window proc */ +void +TclpFinalizePipes(void) { - Tcl_DeleteEventSource(PipeSetupProc, PipeCheckProc, NULL); - initialized = 0; + ThreadSpecificData *tsdPtr; + + tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); + if (tsdPtr != NULL) { + Tcl_DeleteEventSource(PipeSetupProc, PipeCheckProc, NULL); + } } /* @@ -249,8 +304,8 @@ PipeExitHandler(clientData) * * PipeSetupProc -- * - * This procedure is invoked before Tcl_DoOneEvent blocks waiting - * for an event. + * This function is invoked before Tcl_DoOneEvent blocks waiting for an + * event. * * Results: * None. @@ -262,26 +317,38 @@ 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; + 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) { + if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { + block = 0; + } } + if (infoPtr->watchMask & TCL_READABLE) { + if (WaitForRead(infoPtr, 0) >= 0) { + block = 0; + } + } + } + if (!block) { + Tcl_SetMaxBlockTime(&blockTime); } } @@ -290,8 +357,8 @@ PipeSetupProc(data, flags) * * PipeCheckProc -- * - * This procedure is called by Tcl_DoOneEvent to check the pipe - * event source for events. + * This function is called by Tcl_DoOneEvent to check the pipe event + * source for events. * * Results: * None. @@ -303,26 +370,47 @@ 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; + 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 - * queued. + * 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; + if ((infoPtr->watchMask & TCL_WRITABLE) && + (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT)) { + needEvent = 1; + } + + if ((infoPtr->watchMask & TCL_READABLE) && + (WaitForRead(infoPtr, 0) >= 0)) { + needEvent = 1; + } + + if (needEvent) { infoPtr->flags |= PIPE_PENDING; - evPtr = (PipeEvent *) ckalloc(sizeof(PipeEvent)); + evPtr = ckalloc(sizeof(PipeEvent)); evPtr->header.proc = PipeEventProc; evPtr->infoPtr = infoPtr; Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); @@ -333,10 +421,10 @@ PipeCheckProc(data, flags) /* *---------------------------------------------------------------------- * - * MakeFile -- + * TclWinMakeFile -- * - * This function constructs a new TclFile from a given data and - * type value. + * This function constructs a new TclFile from a given data and type + * value. * * Results: * Returns a newly allocated WinFile as a TclFile. @@ -347,13 +435,13 @@ PipeCheckProc(data, flags) *---------------------------------------------------------------------- */ -static TclFile -MakeFile(handle) - HANDLE handle; /* Type-specific data. */ +TclFile +TclWinMakeFile( + HANDLE handle) /* Type-specific data. */ { WinFile *filePtr; - filePtr = (WinFile *) ckalloc(sizeof(WinFile)); + filePtr = ckalloc(sizeof(WinFile)); filePtr->type = WIN_FILE; filePtr->handle = handle; @@ -363,6 +451,42 @@ MakeFile(handle) /* *---------------------------------------------------------------------- * + * TempFileName -- + * + * Gets a temporary file name and deals with the fact that the temporary + * file path provided by Windows may not actually exist if the TMP or + * TEMP environment variables refer to a non-existent directory. + * + * Results: + * 0 if error, non-zero otherwise. If non-zero is returned, the name + * buffer will be filled with a name that can be used to construct a + * temporary file. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +TempFileName( + TCHAR name[MAX_PATH]) /* Buffer in which name for temporary file + * gets stored. */ +{ + const TCHAR *prefix = TEXT("TCL"); + if (GetTempPath(MAX_PATH, name) != 0) { + if (GetTempFileName(name, prefix, 0, name) != 0) { + return 1; + } + } + name[0] = '.'; + name[1] = '\0'; + return GetTempFileName(name, prefix, 0, name); +} + +/* + *---------------------------------------------------------------------- + * * TclpMakeFile -- * * Make a TclFile from a channel. @@ -377,15 +501,15 @@ MakeFile(handle) */ TclFile -TclpMakeFile(channel, direction) - Tcl_Channel channel; /* Channel to get file from. */ - int direction; /* Either TCL_READABLE or TCL_WRITABLE. */ +TclpMakeFile( + Tcl_Channel channel, /* Channel to get file from. */ + int direction) /* Either TCL_READABLE or TCL_WRITABLE. */ { HANDLE handle; - if (Tcl_GetChannelHandle(channel, direction, + if (Tcl_GetChannelHandle(channel, direction, (ClientData *) &handle) == TCL_OK) { - return MakeFile(handle); + return TclWinMakeFile(handle); } else { return (TclFile) NULL; } @@ -394,17 +518,13 @@ TclpMakeFile(channel, direction) /* *---------------------------------------------------------------------- * - * TempFileName -- + * TclpOpenFile -- * - * Gets a temporary file name and deals with the fact that the - * temporary file path provided by Windows may not actually exist - * if the TMP or TEMP environment variables refer to a - * non-existent directory. + * This function opens files for use in a pipeline. * - * Results: - * 0 if error, non-zero otherwise. If non-zero is returned, the - * name buffer will be filled with a name that can be used to - * construct a temporary file. + * Results: + * Returns a newly allocated TclFile structure containing the file + * handle. * * Side effects: * None. @@ -412,20 +532,107 @@ TclpMakeFile(channel, direction) *---------------------------------------------------------------------- */ -static int -TempFileName(name) - char name[MAX_PATH]; /* Buffer in which name for temporary - * file gets stored. */ +TclFile +TclpOpenFile( + const char *path, /* The name of the file to open. */ + int mode) /* In what mode to open the file? */ { - 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; + HANDLE handle; + DWORD accessMode, createMode, shareMode, flags; + Tcl_DString ds; + const TCHAR *nativePath; + + /* + * Map the access bits to the NT access mode. + */ + + switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) { + case O_RDONLY: + accessMode = GENERIC_READ; + break; + case O_WRONLY: + accessMode = GENERIC_WRITE; + break; + case O_RDWR: + accessMode = (GENERIC_READ | GENERIC_WRITE); + break; + default: + TclWinConvertError(ERROR_INVALID_FUNCTION); + return NULL; + } + + /* + * Map the creation flags to the NT create mode. + */ + + 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; + } + + 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(nativePath); + if (flags == 0xFFFFFFFF) { + flags = 0; } } - return 1; + + /* + * Set up the file sharing mode. We want to allow simultaneous access. + */ + + shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; + + /* + * Now we get to create the file. + */ + + handle = CreateFile(nativePath, accessMode, shareMode, + NULL, createMode, flags, NULL); + Tcl_DStringFree(&ds); + + 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); + return NULL; + } + + /* + * Seek to the end of file if we are writing. + */ + + if (mode & (O_WRONLY|O_APPEND)) { + SetFilePointer(handle, 0, NULL, FILE_END); + } + + return TclWinMakeFile(handle); } /* @@ -433,9 +640,9 @@ TempFileName(name) * * 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. + * 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. @@ -447,22 +654,21 @@ TempFileName(name) */ 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. */ +TclpCreateTempFile( + const char *contents) /* String to write into temp file, or NULL. */ { - char name[MAX_PATH]; + TCHAR name[MAX_PATH]; + const char *native; + Tcl_DString dstring; 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); + 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; } @@ -473,54 +679,53 @@ TclpCreateTempFile(contents, namePtr) if (contents != NULL) { DWORD result, length; - char *p; - - for (p = contents; *p != '\0'; p++) { + const char *p; + int toCopy; + + /* + * Convert the contents from UTF to native encoding + */ + + native = Tcl_UtfToExternalDString(NULL, contents, -1, &dstring); + + toCopy = Tcl_DStringLength(&dstring); + for (p = native; toCopy > 0; p++, toCopy--) { if (*p == '\n') { - length = p - contents; + length = p - native; if (length > 0) { - if (!WriteFile(handle, contents, length, &result, NULL)) { + if (!WriteFile(handle, native, length, &result, NULL)) { goto error; } } if (!WriteFile(handle, "\r\n", 2, &result, NULL)) { goto error; } - contents = p+1; + native = p+1; } } - length = p - contents; + length = p - native; if (length > 0) { - if (!WriteFile(handle, contents, length, &result, NULL)) { + if (!WriteFile(handle, native, length, &result, NULL)) { goto error; } } + Tcl_DStringFree(&dstring); + if (SetFilePointer(handle, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF) { + goto error; + } } - if (SetFilePointer(handle, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF) { - goto error; - } - - if (namePtr != NULL) { - Tcl_DStringAppend(namePtr, name, -1); - } + return TclWinMakeFile(handle); + 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. + * Free the native representation of the contents if necessary. */ - 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; - } else { - return MakeFile(handle); + if (contents != NULL) { + Tcl_DStringFree(&dstring); } - error: TclWinConvertError(GetLastError()); CloseHandle(handle); DeleteFile(name); @@ -530,13 +735,12 @@ TclpCreateTempFile(contents, namePtr) /* *---------------------------------------------------------------------- * - * TclpOpenFile -- + * TclpTempFileName -- * - * This function opens files for use in a pipeline. + * This function returns a unique filename. * * Results: - * Returns a newly allocated TclFile structure containing the - * file handle. + * Returns a valid Tcl_Obj* with refCount 0, or NULL on failure. * * Side effects: * None. @@ -544,109 +748,16 @@ TclpCreateTempFile(contents, namePtr) *---------------------------------------------------------------------- */ -TclFile -TclpOpenFile(path, mode) - char *path; - int mode; +Tcl_Obj * +TclpTempFileName(void) { - HANDLE handle; - DWORD accessMode, createMode, shareMode, flags; - SECURITY_ATTRIBUTES sec; - - /* - * Map the access bits to the NT access mode. - */ - - switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) { - case O_RDONLY: - accessMode = GENERIC_READ; - break; - case O_WRONLY: - accessMode = GENERIC_WRITE; - break; - case O_RDWR: - accessMode = (GENERIC_READ | GENERIC_WRITE); - break; - default: - TclWinConvertError(ERROR_INVALID_FUNCTION); - return NULL; - } - - /* - * Map the creation flags to the NT create mode. - */ - - 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 the file is not being created, use the existing file attributes. - */ - - flags = 0; - if (!(mode & O_CREAT)) { - flags = GetFileAttributes(path); - 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. - */ - - shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; - - /* - * Now we get to create the file. - */ - - handle = CreateFile(path, accessMode, shareMode, &sec, createMode, flags, - (HANDLE) NULL); - if (handle == INVALID_HANDLE_VALUE) { - DWORD err = GetLastError(); - if ((err & 0xffffL) == ERROR_OPEN_FAILED) { - err = (mode & O_CREAT) ? ERROR_FILE_EXISTS : ERROR_FILE_NOT_FOUND; - } - TclWinConvertError(err); - return NULL; - } - - /* - * Seek to the end of file if we are writing. - */ + TCHAR fileName[MAX_PATH]; - if (mode & O_WRONLY) { - SetFilePointer(handle, 0, NULL, FILE_END); + if (TempFileName(fileName) == 0) { + return NULL; } - return MakeFile(handle); + return TclpNativeToNormalized(fileName); } /* @@ -654,57 +765,32 @@ TclpOpenFile(path, mode) * * TclpCreatePipe -- * - * Creates an anonymous pipe. Under Win32s, creates a temp file - * that is used to simulate a pipe. + * Creates an anonymous pipe. * * Results: - * Returns 1 on success, 0 on failure. + * Returns 1 on success, 0 on failure. * * Side effects: - * Creates a pipe. + * Creates a pipe. * *---------------------------------------------------------------------- */ int -TclpCreatePipe(readPipe, writePipe) - TclFile *readPipe; /* Location to store file handle for - * read side of pipe. */ - TclFile *writePipe; /* Location to store file handle for - * write side of pipe. */ +TclpCreatePipe( + TclFile *readPipe, /* Location to store file handle for read side + * of pipe. */ + 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]; - - if (TempFileName(buf) != 0) { - 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->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; - - return 1; - } - } - TclWinConvertError(GetLastError()); return 0; } @@ -714,7 +800,7 @@ TclpCreatePipe(readPipe, writePipe) * * TclpCloseFile -- * - * Closes a pipeline file handle. These handles are created by + * Closes a pipeline file handle. These handles are created by * TclpOpenFile, TclpCreatePipe, or TclpMakeFile. * * Results: @@ -727,48 +813,37 @@ 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; switch (filePtr->type) { - case WIN_FILE: - case WIN32S_TMPFILE: - if (CloseHandle(filePtr->handle) == FALSE) { + case WIN_FILE: + /* + * Don't close the Win32 handle if the handle is a standard channel + * during the thread exit process. Otherwise, one thread may kill the + * stdio of another. + */ + + if (!TclInThreadExit() + || ((GetStdHandle(STD_INPUT_HANDLE) != filePtr->handle) + && (GetStdHandle(STD_OUTPUT_HANDLE) != filePtr->handle) + && (GetStdHandle(STD_ERROR_HANDLE) != filePtr->handle))) { + if (filePtr->handle != NULL && + CloseHandle(filePtr->handle) == FALSE) { TclWinConvertError(GetLastError()); - ckfree((char *) filePtr); + ckfree(filePtr); return -1; } - /* - * Simulate deleting the file on close for Win32s. - */ - - if (filePtr->type == WIN32S_TMPFILE) { - DeleteFile(((TmpFile*)filePtr)->name); - } - break; - - case WIN32S_PIPE: - pipePtr = (WinPipe *) file; - - if (pipePtr->otherPtr != NULL) { - pipePtr->otherPtr->otherPtr = NULL; - } else { - if (pipePtr->file.handle != INVALID_HANDLE_VALUE) { - CloseHandle(pipePtr->file.handle); - } - DeleteFile(pipePtr->fileName); - ckfree((char *) pipePtr->fileName); - } - break; + } + break; - default: - panic("Tcl_CloseFile: unexpected file type"); + default: + Tcl_Panic("TclpCloseFile: unexpected file type"); } - ckfree((char *) filePtr); + ckfree(filePtr); return 0; } @@ -781,9 +856,9 @@ TclpCloseFile(file) * child process. * * Results: - * Returns the process id for the child process. If the pid was not - * known by Tcl, either because the pid was not created by Tcl or the - * child process has already been reaped, -1 is returned. + * Returns the process id for the child process. If the pid was not known + * by Tcl, either because the pid was not created by Tcl or the child + * process has already been reaped, -1 is returned. * * Side effects: * None. @@ -791,17 +866,22 @@ TclpCloseFile(file) *-------------------------------------------------------------------------- */ -unsigned long -TclpGetPid(pid) - Tcl_Pid pid; /* The HANDLE of the child process. */ +int +TclpGetPid( + Tcl_Pid pid) /* The HANDLE of the child process. */ { ProcInfo *infoPtr; - + + PipeInit(); + + 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; } @@ -810,249 +890,87 @@ TclpGetPid(pid) * * TclpCreateProcess -- * - * Create a child process that has the specified files as its - * standard input, output, and error. The child process runs - * synchronously under Win32s and asynchronously under Windows NT - * and Windows 95, and runs with the same environment variables - * as the creating process. + * Create a child process that has the specified files as its standard + * input, output, and error. The child process runs asynchronously under + * Windows NT and Windows 9x, and runs with the same environment + * variables as the creating process. * - * The complete Windows search path is searched to find the specified - * executable. If an executable by the given name is not found, - * automatically tries appending ".com", ".exe", and ".bat" to the + * The complete Windows search path is searched to find the specified + * executable. If an executable by the given name is not found, + * automatically tries appending ".com", ".exe", and ".bat" to the * executable name. * * Results: - * The return value is TCL_ERROR and an error message is left in - * interp->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. - * + * The return value is TCL_ERROR and an error message is left in 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. + * * Side effects: * A process is created. - * + * *---------------------------------------------------------------------- */ 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] - * contains the name of the executable - * converted to native format (using the - * Tcl_TranslateFileName call). Additional + int argc, /* Number of arguments in following array. */ + const 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 - * 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 - * receives output from the child process. If + 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 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 - * 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 - * is filled with the process id of the child + 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 function is successful, pidPtr is + * filled with the process id of the child * process. */ { int result, applType, createFlags; - Tcl_DString cmdLine; + Tcl_DString cmdLine; /* Complete command line (TCHAR). */ STARTUPINFO 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); - - if (TclWinGetPlatformId() == VER_PLATFORM_WIN32s) { - /* - * Under Win32s, there are no pipes. In order to simulate pipe - * behavior, the child processes are run synchronously and their - * I/O is redirected from/to temporary files before the next - * stage of the pipeline is started. - */ - - MSG msg; - DWORD status; - DWORD args[4]; - void *trans[5]; - char *inputFileName, *outputFileName; - Tcl_DString inputTempFile, outputTempFile; - - BuildCommandLine(argc, argv, &cmdLine); - - ZeroMemory(&startInfo, sizeof(startInfo)); - startInfo.cb = sizeof(startInfo); - - Tcl_DStringInit(&inputTempFile); - Tcl_DStringInit(&outputTempFile); - outputHandle = INVALID_HANDLE_VALUE; - - inputFileName = NULL; - outputFileName = NULL; - if (inputFile != NULL) { - filePtr = (WinFile *) inputFile; - switch (filePtr->type) { - case WIN_FILE: - case WIN32S_TMPFILE: { - h = INVALID_HANDLE_VALUE; - inputFileName = MakeTempFile(&inputTempFile); - if (inputFileName != NULL) { - h = CreateFile(inputFileName, GENERIC_WRITE, 0, - NULL, CREATE_ALWAYS, 0, NULL); - } - if (h == INVALID_HANDLE_VALUE) { - Tcl_AppendResult(interp, "couldn't duplicate input handle: ", - Tcl_PosixError(interp), (char *) NULL); - goto end32s; - } - CopyChannel(h, filePtr->handle); - CloseHandle(h); - break; - } - case WIN32S_PIPE: { - inputFileName = ((WinPipe*)inputFile)->fileName; - break; - } - } - } - if (inputFileName == NULL) { - inputFileName = "nul"; - } - if (outputFile != NULL) { - filePtr = (WinFile *)outputFile; - if (filePtr->type == WIN_FILE) { - outputFileName = MakeTempFile(&outputTempFile); - if (outputFileName == NULL) { - Tcl_AppendResult(interp, "couldn't duplicate output handle: ", - Tcl_PosixError(interp), (char *) NULL); - goto end32s; - } - outputHandle = filePtr->handle; - } else if (filePtr->type == WIN32S_PIPE) { - outputFileName = ((WinPipe*)outputFile)->fileName; - } - } - if (outputFileName == NULL) { - outputFileName = "nul"; - } - - if (applType == APPL_DOS) { - args[0] = (DWORD) Tcl_DStringValue(&cmdLine); - args[1] = (DWORD) inputFileName; - args[2] = (DWORD) outputFileName; - trans[0] = &args[0]; - trans[1] = &args[1]; - trans[2] = &args[2]; - trans[3] = NULL; - if (TclWinSynchSpawn(args, 0, trans, pidPtr) != 0) { - result = TCL_OK; - } - } else if (applType == APPL_WIN3X) { - args[0] = (DWORD) Tcl_DStringValue(&cmdLine); - trans[0] = &args[0]; - trans[1] = NULL; - if (TclWinSynchSpawn(args, 1, trans, pidPtr) != 0) { - result = TCL_OK; - } - } else { - if (CreateProcess(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) { - break; - } - if (status != STILL_ACTIVE) { - break; - } - if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE) == TRUE) { - TranslateMessage(&msg); - DispatchMessage(&msg); - } - } - *pidPtr = (Tcl_Pid) procInfo.hProcess; - if (*pidPtr != 0) { - ProcInfo *procPtr = (ProcInfo *) ckalloc(sizeof(ProcInfo)); - procPtr->hProcess = procInfo.hProcess; - procPtr->dwProcessId = procInfo.dwProcessId; - procPtr->nextPtr = procList; - procList = procPtr; - } - result = TCL_OK; - } - } - if (result != TCL_OK) { - TclWinConvertError(GetLastError()); - Tcl_AppendResult(interp, "couldn't execute \"", originalName, - "\": ", Tcl_PosixError(interp), (char *) NULL); - } - - end32s: - if (outputHandle != INVALID_HANDLE_VALUE) { - /* - * Now copy stuff from temp file to actual output handle. Don't - * close outputHandle because it is associated with the output - * file owned by the caller. - */ - - h = CreateFile(outputFileName, GENERIC_READ, 0, NULL, OPEN_ALWAYS, - 0, NULL); - if (h != INVALID_HANDLE_VALUE) { - CopyChannel(outputHandle, h); - } - CloseHandle(h); - } - - if (inputFileName == Tcl_DStringValue(&inputTempFile)) { - DeleteFile(inputFileName); - } - - if (outputFileName == Tcl_DStringValue(&outputTempFile)) { - DeleteFile(outputFileName); - } - - Tcl_DStringFree(&inputTempFile); - Tcl_DStringFree(&outputTempFile); - Tcl_DStringFree(&cmdLine); - return result; - } hProcess = GetCurrentProcess(); /* * STARTF_USESTDHANDLES must be used to pass handles to child process. - * Using SetStdHandle() and/or dup2() only works when a console mode + * Using SetStdHandle() and/or dup2() only works when a console mode * parent process is spawning an attached console mode child process. */ ZeroMemory(&startInfo, sizeof(startInfo)); startInfo.cb = sizeof(startInfo); - startInfo.dwFlags = STARTF_USESTDHANDLES; + startInfo.dwFlags = STARTF_USESTDHANDLES; startInfo.hStdInput = INVALID_HANDLE_VALUE; startInfo.hStdOutput= INVALID_HANDLE_VALUE; startInfo.hStdError = INVALID_HANDLE_VALUE; @@ -1062,8 +980,8 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, secAtts.bInheritHandle = TRUE; /* - * We have to check the type of each file, since we cannot duplicate - * some file types. + * We have to check the type of each file, since we cannot duplicate some + * file types. */ inputHandle = INVALID_HANDLE_VALUE; @@ -1089,23 +1007,22 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, } /* - * Duplicate all the handles which will be passed off as stdin, stdout - * and stderr of the child process. The duplicate handles are set to - * be inheritable, so the child process can use them. + * Duplicate all the handles which will be passed off as stdin, stdout and + * stderr of the child process. The duplicate handles are set to be + * inheritable, so the child process can use them. */ if (inputHandle == INVALID_HANDLE_VALUE) { - /* - * If handle was not set, stdin should return immediate EOF. - * Under Windows95, some applications (both 16 and 32 bit!) - * cannot read from the NUL device; they read from console - * instead. When running tk, this is fatal because the child - * process would hang forever waiting for EOF from the unmapped - * console window used by the helper application. + /* + * If handle was not set, stdin should return immediate EOF. Under + * Windows95, some applications (both 16 and 32 bit!) cannot read from + * the NUL device; they read from console instead. When running tk, + * this is fatal because the child process would hang forever waiting + * for EOF from the unmapped console window used by the helper + * application. * - * Fortunately, the helper application detects a closed pipe - * as an immediate EOF and can pass that information to the - * child process. + * Fortunately, the helper application detects a closed pipe as an + * immediate EOF and can pass that information to the child process. */ if (CreatePipe(&startInfo.hStdInput, &h, &secAtts, 0) != FALSE) { @@ -1117,78 +1034,72 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, } if (startInfo.hStdInput == INVALID_HANDLE_VALUE) { TclWinConvertError(GetLastError()); - Tcl_AppendResult(interp, "couldn't duplicate input handle: ", - Tcl_PosixError(interp), (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't duplicate input handle: %s", + Tcl_PosixError(interp))); goto end; } if (outputHandle == INVALID_HANDLE_VALUE) { /* - * If handle was not set, output should be sent to an infinitely - * deep sink. Under Windows 95, some 16 bit applications cannot - * have stdout redirected to NUL; they send their output to - * the console instead. Some applications, like "more" or "dir /p", - * when outputting multiple pages to the console, also then try and - * read from the console to go the next page. When running tk, this - * is fatal because the child process would hang forever waiting - * for input from the unmapped console window used by the helper - * application. + * If handle was not set, output should be sent to an infinitely deep + * sink. Under Windows 95, some 16 bit applications cannot have stdout + * redirected to NUL; they send their output to the console instead. + * Some applications, like "more" or "dir /p", when outputting + * multiple pages to the console, also then try and read from the + * console to go the next page. When running tk, this is fatal because + * the child process would hang forever waiting for input from the + * unmapped console window used by the helper application. * - * Fortunately, the helper application will detect a closed pipe - * as a sink. + * Fortunately, the helper application will detect a closed pipe as a + * sink. */ - if ((TclWinGetPlatformId() == VER_PLATFORM_WIN32_WINDOWS) - && (applType == APPL_DOS)) { - if (CreatePipe(&h, &startInfo.hStdOutput, &secAtts, 0) != FALSE) { - CloseHandle(h); - } - } else { - startInfo.hStdOutput = CreateFile("NUL:", GENERIC_WRITE, 0, - &secAtts, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } + startInfo.hStdOutput = CreateFile(TEXT("NUL:"), GENERIC_WRITE, 0, + &secAtts, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); } else { - DuplicateHandle(hProcess, outputHandle, hProcess, &startInfo.hStdOutput, - 0, TRUE, DUPLICATE_SAME_ACCESS); + DuplicateHandle(hProcess, outputHandle, hProcess, + &startInfo.hStdOutput, 0, TRUE, DUPLICATE_SAME_ACCESS); } if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) { TclWinConvertError(GetLastError()); - Tcl_AppendResult(interp, "couldn't duplicate output handle: ", - Tcl_PosixError(interp), (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't duplicate output handle: %s", + Tcl_PosixError(interp))); goto end; } if (errorHandle == INVALID_HANDLE_VALUE) { /* - * If handle was not set, errors should be sent to an infinitely - * deep sink. + * If handle was not set, errors should be sent to an infinitely deep + * sink. */ - startInfo.hStdError = CreateFile("NUL:", GENERIC_WRITE, 0, + startInfo.hStdError = CreateFile(TEXT("NUL:"), GENERIC_WRITE, 0, &secAtts, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); } else { - DuplicateHandle(hProcess, errorHandle, hProcess, &startInfo.hStdError, + DuplicateHandle(hProcess, errorHandle, hProcess, &startInfo.hStdError, 0, TRUE, DUPLICATE_SAME_ACCESS); - } + } if (startInfo.hStdError == INVALID_HANDLE_VALUE) { TclWinConvertError(GetLastError()); - Tcl_AppendResult(interp, "couldn't duplicate error handle: ", - Tcl_PosixError(interp), (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "couldn't duplicate error handle: %s", + Tcl_PosixError(interp))); goto end; } - /* - * If we do not have a console window, then we must run DOS and - * WIN32 console mode applications as detached processes. This tells - * the loader that the child application should not inherit the - * console, and that it should not create a new console window for - * the child application. The child application should get its stdio - * from the redirection handles provided by this application, and run - * in the background. + + /* + * If we do not have a console window, then we must run DOS and WIN32 + * console mode applications as detached processes. This tells the loader + * that the child application should not inherit the console, and that it + * should not create a new console window for the child application. The + * child application should get its stdio from the redirection handles + * provided by this application, and run in the background. * - * If we are starting a GUI process, they don't automatically get a + * If we are starting a GUI process, they don't automatically get a * console, so it doesn't matter if they are started as foreground or - * detached processes. The GUI window will still pop up to the - * foreground. + * detached processes. The GUI window will still pop up to the foreground. */ if (TclWinGetPlatformId() == VER_PLATFORM_WIN32_NT) { @@ -1196,106 +1107,81 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, createFlags = 0; } else if (applType == APPL_DOS) { /* - * Under NT, 16-bit DOS applications will not run unless they - * can be attached to a console. If we are running without a - * console, run the 16-bit program as an normal process inside - * of a hidden console application, and then run that hidden - * console as a detached process. + * Under NT, 16-bit DOS applications will not run unless they can + * be attached to a console. If we are running without a console, + * run the 16-bit program as an normal process inside of a hidden + * console application, and then run that hidden console as a + * detached process. */ startInfo.wShowWindow = SW_HIDE; startInfo.dwFlags |= STARTF_USESHOWWINDOW; createFlags = CREATE_NEW_CONSOLE; - Tcl_DStringAppend(&cmdLine, "cmd.exe /c ", -1); + TclDStringAppendLiteral(&cmdLine, "cmd.exe /c"); } else { createFlags = DETACHED_PROCESS; - } + } } else { if (HasConsole()) { createFlags = 0; } else { createFlags = DETACHED_PROCESS; } - - if (applType == APPL_DOS) { - /* - * Under Windows 95, 16-bit DOS applications do not work well - * with pipes: - * - * 1. EOF on a pipe between a detached 16-bit DOS application - * and another application is not seen at the other - * end of the pipe, so the listening process blocks forever on - * reads. This inablity to detect EOF happens when either a - * 16-bit app or the 32-bit app is the listener. - * - * 2. If a 16-bit DOS application (detached or not) blocks when - * writing to a pipe, it will never wake up again, and it - * eventually brings the whole system down around it. - * - * The 16-bit application is run as a normal process inside - * of a hidden helper console app, and this helper may be run - * as a detached process. If any of the stdio handles is - * a pipe, the helper application accumulates information - * into temp files and forwards it to or from the DOS - * application as appropriate. This means that DOS apps - * must receive EOF from a stdin pipe before they will actually - * begin, and must finish generating stdout or stderr before - * the data will be sent to the next stage of the pipe. - * - * The helper app should be located in the same directory as - * the tcl dll. - */ - if (createFlags != 0) { - startInfo.wShowWindow = SW_HIDE; - startInfo.dwFlags |= STARTF_USESHOWWINDOW; - createFlags = CREATE_NEW_CONSOLE; - } - Tcl_DStringAppend(&cmdLine, "tclpip" STRINGIFY(TCL_MAJOR_VERSION) - STRINGIFY(TCL_MINOR_VERSION) ".dll ", -1); + if (applType == APPL_DOS) { + Tcl_SetObjResult(interp, Tcl_NewStringObj( + "DOS application process not supported on this platform", + -1)); + Tcl_SetErrorCode(interp, "TCL", "OPERATION", "EXEC", "DOS_APP", + NULL); + goto end; } } - + /* * cmdLine gets the full command line used to invoke the executable, - * including the name of the executable itself. The command line - * arguments in argv[] are stored in cmdLine separated by spaces. - * Special characters in individual arguments from argv[] must be - * quoted when being stored in cmdLine. + * including the name of the executable itself. The command line arguments + * in argv[] are stored in cmdLine separated by spaces. Special characters + * in individual arguments from argv[] must be quoted when being stored in + * cmdLine. * - * When calling any application, bear in mind that arguments that - * specify a path name are not converted. If an argument contains - * forward slashes as path separators, it may or may not be - * recognized as a path name, depending on the program. In general, - * most applications accept forward slashes only as option - * delimiters and backslashes only as paths. + * When calling any application, bear in mind that arguments that specify + * a path name are not converted. If an argument contains forward slashes + * as path separators, it may or may not be recognized as a path name, + * depending on the program. In general, most applications accept forward + * slashes only as option delimiters and backslashes only as paths. * - * Additionally, when calling a 16-bit dos or windows application, - * all path names must use the short, cryptic, path format (e.g., - * using ab~1.def instead of "a b.default"). + * Additionally, when calling a 16-bit dos or windows application, all + * path names must use the short, cryptic, path format (e.g., 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 (CreateProcess(NULL, (TCHAR *) Tcl_DStringValue(&cmdLine), + NULL, NULL, TRUE, (DWORD) createFlags, NULL, NULL, &startInfo, + &procInfo) == 0) { TclWinConvertError(GetLastError()); - Tcl_AppendResult(interp, "couldn't execute \"", originalName, - "\": ", Tcl_PosixError(interp), (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("couldn't execute \"%s\": %s", + argv[0], Tcl_PosixError(interp))); 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); + WaitForSingleObject(procInfo.hProcess, 50); } - /* - * "When an application spawns a process repeatedly, a new thread - * instance will be created for each process but the previous - * instances may not be cleaned up. This results in a significant - * virtual memory loss each time the process is spawned. If there - * is a WaitForInputIdle() call between CreateProcess() and - * CloseHandle(), the problem does not occur." PSS ID Number: Q124121 + /* + * "When an application spawns a process repeatedly, a new thread instance + * will be created for each process but the previous instances may not be + * cleaned up. This results in a significant virtual memory loss each time + * the process is spawned. If there is a WaitForInputIdle() call between + * CreateProcess() and CloseHandle(), the problem does not occur." PSS ID + * Number: Q124121 */ WaitForInputIdle(procInfo.hProcess, 5000); @@ -1303,21 +1189,17 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, *pidPtr = (Tcl_Pid) procInfo.hProcess; if (*pidPtr != 0) { - ProcInfo *procPtr = (ProcInfo *) ckalloc(sizeof(ProcInfo)); - procPtr->hProcess = procInfo.hProcess; - procPtr->dwProcessId = procInfo.dwProcessId; - procPtr->nextPtr = procList; - procList = procPtr; + TclWinAddProcess(procInfo.hProcess, procInfo.dwProcessId); } result = TCL_OK; - end: + end: Tcl_DStringFree(&cmdLine); if (startInfo.hStdInput != INVALID_HANDLE_VALUE) { - CloseHandle(startInfo.hStdInput); + CloseHandle(startInfo.hStdInput); } if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) { - CloseHandle(startInfo.hStdOutput); + CloseHandle(startInfo.hStdOutput); } if (startInfo.hStdError != INVALID_HANDLE_VALUE) { CloseHandle(startInfo.hStdError); @@ -1331,8 +1213,7 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, * * HasConsole -- * - * Determines whether the current application is attached to a - * console. + * Determines whether the current application is attached to a console. * * Results: * Returns TRUE if this application has a console, else FALSE. @@ -1344,16 +1225,18 @@ TclpCreateProcess(interp, argc, argv, inputFile, outputFile, errorFile, */ static BOOL -HasConsole() +HasConsole(void) { - 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) { - CloseHandle(handle); + CloseHandle(handle); return TRUE; } else { - return FALSE; + return FALSE; } } @@ -1363,29 +1246,28 @@ HasConsole() * ApplicationType -- * * Search for the specified program and identify if it refers to a DOS, - * Windows 3.X, or Win32 program. Used to determine how to invoke - * a program, or if it can even be invoked. - * - * It is possible to almost positively identify DOS and Windows - * applications that contain the appropriate magic numbers. However, - * DOS .com files do not seem to contain a magic number; if the program - * name ends with .com and could not be identified as a Windows .com - * file, it will be assumed to be a DOS application, even if it was - * just random data. If the program name does not end with .com, no - * such assumption is made. - * - * The Win32 procedure GetBinaryType incorrectly identifies any - * junk file that ends with .exe as a dos executable and some - * executables that don't end with .exe as not executable. Plus it - * doesn't exist under win95, so I won't feel bad about reimplementing - * functionality. + * Windows 3.X, or Win32 program. Used to determine how to invoke a + * program, or if it can even be invoked. + * + * It is possible to almost positively identify DOS and Windows + * applications that contain the appropriate magic numbers. However, DOS + * .com files do not seem to contain a magic number; if the program name + * ends with .com and could not be identified as a Windows .com file, it + * will be assumed to be a DOS application, even if it was just random + * data. If the program name does not end with .com, no such assumption + * is made. + * + * The Win32 function GetBinaryType incorrectly identifies any junk file + * that ends with .exe as a dos executable and some executables that + * don't end with .exe as not executable. Plus it doesn't exist under + * win95, so I won't feel bad about reimplementing functionality. * * Results: - * The return value is one of APPL_DOS, APPL_WIN3X, or APPL_WIN32 - * if the filename referred to the corresponding application type. - * If the file name could not be found or did not refer to any known - * application type, APPL_NONE is returned and an error message is - * left in interp. .bat files are identified as APPL_DOS. + * The return value is one of APPL_DOS, APPL_WIN3X, or APPL_WIN32 if the + * filename referred to the corresponding application type. If the file + * name could not be found or did not refer to any known application + * type, APPL_NONE is returned and an error message is left in interp. + * .bat files are identified as APPL_DOS. * * Side effects: * None. @@ -1394,57 +1276,75 @@ HasConsole() */ static int -ApplicationType(interp, originalName, fullPath) - 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 +ApplicationType( + Tcl_Interp *interp, /* Interp, for error message. */ + const char *originalName, /* Name of the application to find. */ + 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; - static char extensions[][5] = {"", ".com", ".exe", ".bat"}; + Tcl_DString nameBuf, ds; + const TCHAR *nativeName; + TCHAR nativeFullPath[MAX_PATH]; + static const char extensions[][5] = {"", ".com", ".exe", ".bat"}; - /* Look for the program as an external program. First try the name - * as it is, then try adding .com, .exe, and .bat, in that order, to - * the name, looking for an executable. + /* + * Look for the program as an external program. First try the name as it + * is, then try adding .com, .exe, and .bat, in that order, to the name, + * looking for an executable. * - * Using the raw SearchPath() procedure doesn't do quite what is - * necessary. If the name of the executable already contains a '.' - * character, it will not try appending the specified extension when - * searching (in other words, SearchPath will not find the program - * "a.b.exe" if the arguments specified "a.b" and ".exe"). - * So, first look for the file as it is named. Then manually append - * the extensions, looking for a match. + * Using the raw SearchPath() function doesn't do quite what is necessary. + * If the name of the executable already contains a '.' character, it will + * not try appending the specified extension when searching (in other + * words, SearchPath will not find the program "a.b.exe" if the arguments + * specified "a.b" and ".exe"). So, first look for the file as it is + * named. Then manually append the extensions, looking for a match. */ 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 = SearchPath(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. + * Ignore matches on directories or data files, return if identified a + * known type. */ - if (GetFileAttributes(fullPath) & FILE_ATTRIBUTE_DIRECTORY) { + attr = GetFileAttributes(nativeFullPath); + if ((attr == 0xffffffff) || (attr & FILE_ATTRIBUTE_DIRECTORY)) { continue; } + strcpy(fullName, Tcl_WinTCharToUtf(nativeFullPath, -1, &ds)); + Tcl_DStringFree(&ds); - ext = strrchr(fullPath, '.'); - if ((ext != NULL) && (strcmpi(ext, ".bat") == 0)) { + ext = strrchr(fullName, '.'); + if ((ext != NULL) && (strcasecmp(ext, ".bat") == 0)) { applType = APPL_DOS; break; } - hFile = CreateFile(fullPath, GENERIC_READ, FILE_SHARE_READ, NULL, - OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + hFile = CreateFile(nativeFullPath, + GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, NULL); if (hFile == INVALID_HANDLE_VALUE) { continue; } @@ -1452,25 +1352,25 @@ ApplicationType(interp, originalName, fullPath) header.e_magic = 0; ReadFile(hFile, (void *) &header, sizeof(header), &read, NULL); if (header.e_magic != IMAGE_DOS_SIGNATURE) { - /* - * Doesn't have the magic number for relocatable executables. If + /* + * Doesn't have the magic number for relocatable executables. If * filename ends with .com, assume it's a DOS application anyhow. * Note that we didn't make this assumption at first, because some * supposed .com files are really 32-bit executables with all the - * magic numbers and everything. + * magic numbers and everything. */ CloseHandle(hFile); - if ((ext != NULL) && (strcmpi(ext, ".com") == 0)) { + if ((ext != NULL) && (strcasecmp(ext, ".com") == 0)) { applType = APPL_DOS; break; } continue; } if (header.e_lfarlc != sizeof(header)) { - /* + /* * All Windows 3.X and Win32 and some DOS programs have this value - * set here. If it doesn't, assume that since it already had the + * set here. If it doesn't, assume that since it already had the * other magic number it was a DOS application. */ @@ -1479,7 +1379,7 @@ ApplicationType(interp, originalName, fullPath) break; } - /* + /* * The DWORD at header.e_lfanew points to yet another magic number. */ @@ -1494,47 +1394,50 @@ ApplicationType(interp, originalName, fullPath) applType = APPL_WIN32; } else { /* - * Strictly speaking, there should be a test that there - * is an 'L' and 'E' at buf[0..1], to identify the type as - * DOS, but of course we ran into a DOS executable that - * _doesn't_ have the magic number -- specifically, one - * compiled using the Lahey Fortran90 compiler. + * Strictly speaking, there should be a test that there is an 'L' + * and 'E' at buf[0..1], to identify the type as DOS, but of + * course we ran into a DOS executable that _doesn't_ have the + * magic number - specifically, one compiled using the Lahey + * Fortran90 compiler. */ applType = APPL_DOS; } break; } + Tcl_DStringFree(&nameBuf); if (applType == APPL_NONE) { TclWinConvertError(GetLastError()); - Tcl_AppendResult(interp, "couldn't execute \"", originalName, - "\": ", Tcl_PosixError(interp), (char *) NULL); + Tcl_SetObjResult(interp, Tcl_ObjPrintf("couldn't execute \"%s\": %s", + originalName, Tcl_PosixError(interp))); return APPL_NONE; } if ((applType == APPL_DOS) || (applType == APPL_WIN3X)) { - /* - * Replace long path name of executable with short path name for - * 16-bit applications. Otherwise the application may not be able - * to correctly parse its own command line to separate off the + /* + * Replace long path name of executable with short path name for + * 16-bit applications. Otherwise the application may not be able to + * correctly parse its own command line to separate off the * application name from the arguments. */ - GetShortPathName(fullPath, fullPath, MAX_PATH); + GetShortPathName(nativeFullPath, nativeFullPath, MAX_PATH); + strcpy(fullName, Tcl_WinTCharToUtf(nativeFullPath, -1, &ds)); + Tcl_DStringFree(&ds); } return applType; } -/* +/* *---------------------------------------------------------------------- * * BuildCommandLine -- * - * The command line arguments are stored in linePtr separated - * by spaces, in a form that CreateProcess() understands. Special - * characters in individual arguments from argv[] must be quoted - * when being stored in cmdLine. + * The command line arguments are stored in linePtr separated by spaces, + * in a form that CreateProcess() understands. Special characters in + * individual arguments from argv[] must be quoted when being stored in + * cmdLine. * * Results: * None. @@ -1546,62 +1449,83 @@ 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. */ + const 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); + + /* + * Prime the path. Add a space separator if we were primed with something. + */ + + TclDStringAppendDString(&ds, linePtr); + if (Tcl_DStringLength(linePtr) > 0) { + TclDStringAppendLiteral(&ds, " "); + } for (i = 0; i < argc; i++) { - if (i > 0) { - Tcl_DStringAppend(linePtr, " ", 1); + if (i == 0) { + arg = executable; + } else { + arg = argv[i]; + TclDStringAppendLiteral(&ds, " "); } quote = 0; - if (argv[i][0] == '\0') { + if (arg[0] == '\0') { quote = 1; } else { - for (start = argv[i]; *start != '\0'; start++) { - if (isspace(*start)) { + int count; + Tcl_UniChar ch; + + for (start = arg; *start != '\0'; start += count) { + count = Tcl_UtfToUniChar(start, &ch); + if (Tcl_UniCharIsSpace(ch)) { /* INTL: ISO space. */ quote = 1; break; } } } if (quote) { - Tcl_DStringAppend(linePtr, "\"", 1); + TclDStringAppendLiteral(&ds, "\""); } - - start = argv[i]; - for (special = argv[i]; ; ) { - if ((*special == '\\') && - (special[1] == '\\' || special[1] == '"')) { - Tcl_DStringAppend(linePtr, start, special - start); + start = arg; + for (special = arg; ; ) { + if ((*special == '\\') && (special[1] == '\\' || + special[1] == '"' || (quote && special[1] == '\0'))) { + Tcl_DStringAppend(&ds, start, (int) (special - start)); start = special; while (1) { special++; - if (*special == '"') { - /* - * N backslashes followed a quote -> insert - * N * 2 + 1 backslashes then a quote. + if (*special == '"' || (quote && *special == '\0')) { + /* + * N backslashes followed a quote -> insert N * 2 + 1 + * backslashes then a quote. */ - Tcl_DStringAppend(linePtr, start, special - start); + Tcl_DStringAppend(&ds, start, + (int) (special - start)); break; } if (*special != '\\') { break; } } - Tcl_DStringAppend(linePtr, start, special - start); + Tcl_DStringAppend(&ds, start, (int) (special - start)); start = special; } if (*special == '"') { - Tcl_DStringAppend(linePtr, start, special - start); - Tcl_DStringAppend(linePtr, "\\\"", 2); + Tcl_DStringAppend(&ds, start, (int) (special - start)); + TclDStringAppendLiteral(&ds, "\\\""); start = special + 1; } if (*special == '\0') { @@ -1609,85 +1533,14 @@ BuildCommandLine(argc, argv, linePtr) } special++; } - Tcl_DStringAppend(linePtr, start, special - start); + Tcl_DStringAppend(&ds, start, (int) (special - start)); if (quote) { - Tcl_DStringAppend(linePtr, "\"", 1); - } - } -} - -/* - *---------------------------------------------------------------------- - * - * MakeTempFile -- - * - * Helper function for TclpCreateProcess under Win32s. Makes a - * temporary file that _won't_ go away automatically when it's file - * handle is closed. Used for simulated pipes, which are written - * in one pass and reopened and read in the next pass. - * - * Results: - * namePtr is filled with the name of the temporary file. - * - * Side effects: - * A temporary file with the name specified by namePtr is created. - * The caller is responsible for deleting this temporary file. - * - *---------------------------------------------------------------------- - */ - -static char * -MakeTempFile(namePtr) - Tcl_DString *namePtr; /* Initialized Tcl_DString that is filled - * with the name of the temporary file that - * was created. */ -{ - char name[MAX_PATH]; - - if (TempFileName(name) == 0) { - return NULL; - } - - Tcl_DStringAppend(namePtr, name, -1); - return Tcl_DStringValue(namePtr); -} - -/* - *---------------------------------------------------------------------- - * - * CopyChannel -- - * - * Helper function used by TclpCreateProcess under Win32s. Copies - * what remains of source file to destination file; source file - * pointer need not be positioned at the beginning of the file if - * all of source file is not desired, but data is copied up to end - * of source file. - * - * Results: - * None. - * - * Side effects: - * None. - * - *---------------------------------------------------------------------- - */ - -static void -CopyChannel(dst, src) - HANDLE dst; /* Destination file. */ - HANDLE src; /* Source file. */ -{ - char buf[8192]; - DWORD dwRead, dwWrite; - - while (ReadFile(src, buf, sizeof(buf), &dwRead, NULL) != FALSE) { - if (dwRead == 0) { - break; - } - if (WriteFile(dst, buf, dwRead, &dwWrite, NULL) == FALSE) { - break; + TclDStringAppendLiteral(&ds, "\""); } } + Tcl_DStringFree(linePtr); + Tcl_WinUtfToTChar(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds), linePtr); + Tcl_DStringFree(&ds); } /* @@ -1695,9 +1548,8 @@ CopyChannel(dst, src) * * TclpCreateCommandChannel -- * - * This function is called by Tcl_OpenCommandChannel to perform - * the platform specific channel initialization for a command - * channel. + * This function is called by Tcl_OpenCommandChannel to perform the + * platform specific channel initialization for a command channel. * * Results: * Returns a new channel or NULL on failure. @@ -1709,108 +1561,158 @@ 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]; - int channelId; - PipeInfo *infoPtr = (PipeInfo *) ckalloc((unsigned) sizeof(PipeInfo)); + char channelName[16 + TCL_INTEGER_SPACE]; + DWORD id; + PipeInfo *infoPtr = ckalloc(sizeof(PipeInfo)); - if (!initialized) { - PipeInit(); - } + 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; + infoPtr->channel = NULL; - /* - * Use one of the fds associated with the channel as the - * channel id. - */ + infoPtr->validMask = 0; - if (readFile) { - WinPipe *pipePtr = (WinPipe *) readFile; - if (pipePtr->file.type == WIN32S_PIPE - && pipePtr->file.handle == INVALID_HANDLE_VALUE) { - pipePtr->file.handle = CreateFile(pipePtr->fileName, GENERIC_READ, - 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - } - channelId = (int) pipePtr->file.handle; - } else if (writeFile) { - channelId = (int) ((WinFile*)writeFile)->handle; - } else if (errorFile) { - channelId = (int) ((WinFile*)errorFile)->handle; - } else { - channelId = 0; - } + infoPtr->threadId = Tcl_GetCurrentThread(); - infoPtr->validMask = 0; if (readFile != NULL) { - infoPtr->validMask |= TCL_READABLE; + /* + * Start the background reader thread. + */ + + infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL); + infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->stopReader = CreateEvent(NULL, TRUE, FALSE, NULL); + infoPtr->readThread = CreateThread(NULL, 256, PipeReaderThread, + infoPtr, 0, &id); + SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); + infoPtr->validMask |= TCL_READABLE; + } else { + infoPtr->readThread = 0; } if (writeFile != NULL) { - infoPtr->validMask |= TCL_WRITABLE; + /* + * Start the background writer thread. + */ + + infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL); + infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL); + infoPtr->stopWriter = CreateEvent(NULL, TRUE, FALSE, NULL); + infoPtr->writeThread = CreateThread(NULL, 256, PipeWriterThread, + infoPtr, 0, &id); + SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); + infoPtr->validMask |= TCL_WRITABLE; } /* - * 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". + * 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); + sprintf(channelName, "file%" TCL_I_MODIFIER "x", (size_t) infoPtr); infoPtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName, - (ClientData) infoPtr, infoPtr->validMask); + infoPtr, infoPtr->validMask); /* * Pipes have AUTO translation mode on Windows and ^Z eof char, which - * means that a ^Z will be appended to them at close. This is needed - * for Windows programs that expect a ^Z at EOF. + * means that a ^Z will be appended to them at close. This is needed for + * Windows programs that expect a ^Z at EOF. */ - Tcl_SetChannelOption((Tcl_Interp *) NULL, infoPtr->channel, - "-translation", "auto"); - Tcl_SetChannelOption((Tcl_Interp *) NULL, infoPtr->channel, - "-eofchar", "\032 {}"); + Tcl_SetChannelOption(NULL, infoPtr->channel, "-translation", "auto"); + Tcl_SetChannelOption(NULL, infoPtr->channel, "-eofchar", "\032 {}"); return infoPtr->channel; } /* *---------------------------------------------------------------------- * + * Tcl_CreatePipe -- + * + * System dependent interface to create a pipe for the [chan pipe] + * command. Stolen from TclX. + * + * Results: + * TCL_OK or TCL_ERROR. + * + *---------------------------------------------------------------------- + */ + +int +Tcl_CreatePipe( + Tcl_Interp *interp, /* Errors returned in result.*/ + Tcl_Channel *rchan, /* Where to return the read side. */ + Tcl_Channel *wchan, /* Where to return the write side. */ + int flags) /* Reserved for future use. */ +{ + HANDLE readHandle, writeHandle; + SECURITY_ATTRIBUTES sec; + + sec.nLength = sizeof(SECURITY_ATTRIBUTES); + sec.lpSecurityDescriptor = NULL; + sec.bInheritHandle = FALSE; + + if (!CreatePipe(&readHandle, &writeHandle, &sec, 0)) { + TclWinConvertError(GetLastError()); + Tcl_SetObjResult(interp, Tcl_ObjPrintf( + "pipe creation failed: %s", Tcl_PosixError(interp))); + return TCL_ERROR; + } + + *rchan = Tcl_MakeFileChannel((ClientData) readHandle, TCL_READABLE); + Tcl_RegisterChannel(interp, *rchan); + + *wchan = Tcl_MakeFileChannel((ClientData) writeHandle, TCL_WRITABLE); + Tcl_RegisterChannel(interp, *wchan); + + return TCL_OK; +} + +/* + *---------------------------------------------------------------------- + * * TclGetAndDetachPids -- * - * Stores a list of the command PIDs for a command channel in - * interp->result. + * Stores a list of the command PIDs for a command channel in 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; + const Tcl_ChannelType *chanTypePtr; + Tcl_Obj *pidsObj; int i; - char buf[20]; /* * Punt if the channel is not a command channel. @@ -1818,18 +1720,21 @@ TclGetAndDetachPids(interp, chan) chanTypePtr = Tcl_GetChannelType(chan); if (chanTypePtr != &pipeChannelType) { - return; + return; } - pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan); + pipePtr = Tcl_GetChannelInstanceData(chan); + TclNewObj(pidsObj); for (i = 0; i < pipePtr->numPids; i++) { - sprintf(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i])); - Tcl_AppendElement(interp, buf); - Tcl_DetachPids(1, &(pipePtr->pidPtr[i])); + Tcl_ListObjAppendElement(NULL, pidsObj, + Tcl_NewWideIntObj((unsigned) + TclpGetPid(pipePtr->pidPtr[i]))); + Tcl_DetachPids(1, &pipePtr->pidPtr[i]); } + Tcl_SetObjResult(interp, pidsObj); if (pipePtr->numPids > 0) { - ckfree((char *) pipePtr->pidPtr); - pipePtr->numPids = 0; + ckfree(pipePtr->pidPtr); + pipePtr->numPids = 0; } } @@ -1850,13 +1755,13 @@ TclGetAndDetachPids(interp, chan) */ static int -PipeBlockModeProc(instanceData, mode) - ClientData instanceData; /* Instance data for channel. */ - int mode; /* TCL_MODE_BLOCKING or - * TCL_MODE_NONBLOCKING. */ +PipeBlockModeProc( + ClientData instanceData, /* Instance data for channel. */ + int mode) /* TCL_MODE_BLOCKING or + * TCL_MODE_NONBLOCKING. */ { PipeInfo *infoPtr = (PipeInfo *) instanceData; - + /* * Pipes on Windows can not be switched between blocking and nonblocking, * hence we have to emulate the behavior. This is done in the input @@ -1875,7 +1780,7 @@ PipeBlockModeProc(instanceData, mode) /* *---------------------------------------------------------------------- * - * PipeCloseProc -- + * PipeClose2Proc -- * * Closes a pipe based IO channel. * @@ -1889,73 +1794,244 @@ 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); + DWORD exitCode; - /* - * Remove the file from the list of watched files. - */ + errorCode = 0; + result = 0; - for (nextPtrPtr = &firstPipePtr, infoPtr = *nextPtrPtr; infoPtr != NULL; - nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { - if (infoPtr == (PipeInfo *)pipePtr) { - *nextPtrPtr = infoPtr->nextPtr; - break; - } - } + 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. + */ - errorCode = 0; - if (pipePtr->readFile != NULL) { + if (pipePtr->readThread) { + /* + * The thread may already have closed on its own. Check its exit + * code. + */ + + GetExitCodeThread(pipePtr->readThread, &exitCode); + + if (exitCode == STILL_ACTIVE) { + /* + * Set the stop event so that if the reader thread is blocked + * in PipeReaderThread on WaitForMultipleEvents, it will exit + * cleanly. + */ + + SetEvent(pipePtr->stopReader); + + /* + * Wait at most 20 milliseconds for the reader thread to + * close. + */ + + if (WaitForSingleObject(pipePtr->readThread, + 20) == WAIT_TIMEOUT) { + /* + * The thread must be blocked waiting for the pipe to + * become readable in ReadFile(). There isn't a clean way + * to exit the thread from this condition. We should + * terminate the child process instead to get the reader + * thread to fall out of ReadFile with a FALSE. (below) is + * not the correct way to do this, but will stay here + * until a better solution is found. + * + * 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); + + /* BUG: this leaks memory */ + TerminateThread(pipePtr->readThread, 0); + Tcl_MutexUnlock(&pipeMutex); + } + } + + CloseHandle(pipePtr->readThread); + CloseHandle(pipePtr->readable); + CloseHandle(pipePtr->startReader); + CloseHandle(pipePtr->stopReader); + 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)) { + if (pipePtr->writeThread) { + /* + * Wait for the writer thread to finish the current buffer, then + * terminate the thread and close the handles. If the channel is + * nonblocking but blocked during exit, bail out since the worker + * thread is not interruptible and we want TIP#398-fast-exit. + */ + if (TclInExit() + && (pipePtr->flags & PIPE_ASYNC)) { + + /* give it a chance to leave honorably */ + SetEvent(pipePtr->stopWriter); + + if (WaitForSingleObject(pipePtr->writable, 0) == WAIT_TIMEOUT) { + return EWOULDBLOCK; + } + + } else { + + WaitForSingleObject(pipePtr->writable, INFINITE); + + } + + /* + * The thread may already have closed on it's own. Check its exit + * code. + */ + + GetExitCodeThread(pipePtr->writeThread, &exitCode); + + if (exitCode == STILL_ACTIVE) { + /* + * Set the stop event so that if the reader thread is blocked + * in PipeReaderThread on WaitForMultipleEvents, it will exit + * cleanly. + */ + + SetEvent(pipePtr->stopWriter); + + /* + * Wait at most 20 milliseconds for the reader thread to + * close. + */ + + if (WaitForSingleObject(pipePtr->writeThread, + 20) == WAIT_TIMEOUT) { + /* + * The thread must be blocked waiting for the pipe to + * consume input in WriteFile(). There isn't a clean way + * to exit the thread from this condition. We should + * terminate the child process instead to get the writer + * thread to fall out of WriteFile with a FALSE. (below) + * is not the correct way to do this, but will stay here + * until a better solution is found. + * + * 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); + + /* BUG: this leaks memory */ + TerminateThread(pipePtr->writeThread, 0); + Tcl_MutexUnlock(&pipeMutex); + } + } + + CloseHandle(pipePtr->writeThread); + CloseHandle(pipePtr->writable); + CloseHandle(pipePtr->startWriter); + CloseHandle(pipePtr->stopWriter); + pipePtr->writeThread = NULL; + } if (TclpCloseFile(pipePtr->writeFile) != 0) { if (errorCode == 0) { errorCode = errno; } } + pipePtr->validMask &= ~TCL_WRITABLE; + pipePtr->writeFile = NULL; } - + + pipePtr->watchMask &= pipePtr->validMask; + /* - * 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 - * immediately, because it was never used. + * Don't free the channel if any of the flags were set. */ - if (pipePtr->errorFile) { - WinFile *filePtr; - OSVERSIONINFO os; + 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; + } + } + + if ((pipePtr->flags & PIPE_ASYNC) || TclInExit()) { + /* + * If the channel is non-blocking or Tcl is being cleaned up, just + * detach the children PIDs, reap them (important if we are in a + * dynamic load module), and discard the errorFile. + */ + + Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr); + Tcl_ReapDetachedProcs(); + + if (pipePtr->errorFile) { + if (TclpCloseFile(pipePtr->errorFile) != 0) { + if (errorCode == 0) { + errorCode = errno; + } + } + } + result = 0; + } else { + /* + * Wrap the error file into a channel and give it to the cleanup + * routine. + */ + + if (pipePtr->errorFile) { + WinFile *filePtr = (WinFile *) pipePtr->errorFile; - os.dwOSVersionInfoSize = sizeof(os); - GetVersionEx(&os); - if (os.dwPlatformId == VER_PLATFORM_WIN32s) { - TclpCloseFile(pipePtr->errorFile); - errChan = NULL; - } else { - filePtr = (WinFile*)pipePtr->errorFile; errChan = Tcl_MakeFileChannel((ClientData) filePtr->handle, TCL_READABLE); + ckfree(filePtr); + } else { + errChan = NULL; } - } else { - errChan = NULL; + + result = TclCleanupChildren(interp, pipePtr->numPids, + pipePtr->pidPtr, errChan); } - result = TclCleanupChildren(interp, pipePtr->numPids, pipePtr->pidPtr, - errChan); + if (pipePtr->numPids > 0) { - ckfree((char *) pipePtr->pidPtr); + ckfree(pipePtr->pidPtr); + } + + if (pipePtr->writeBuf != NULL) { + ckfree(pipePtr->writeBuf); } - ckfree((char*) pipePtr); + + ckfree(pipePtr); if (errorCode == 0) { - return result; + return result; } return errorCode; } @@ -1965,8 +2041,8 @@ PipeCloseProc(instanceData, interp) * * PipeInputProc -- * - * Reads input from the IO channel into the buffer given. Returns - * count of how many bytes were actually read, and an error indication. + * Reads input from the IO channel into the buffer given. Returns count + * of how many bytes were actually read, and an error indication. * * Results: * A count of how many bytes were read is returned and an error @@ -1979,77 +2055,75 @@ 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 - * in the buffer? */ - int *errorCode; /* Where to store error code. */ +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. */ { 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) { - if (((WinPipe *)filePtr)->otherPtr != NULL) { - panic("PipeInputProc: child process isn't finished writing"); - } - if (filePtr->handle == INVALID_HANDLE_VALUE) { - filePtr->handle = CreateFile(((WinPipe *)filePtr)->fileName, - GENERIC_READ, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, - NULL); - } - if (filePtr->handle == INVALID_HANDLE_VALUE) { - goto error; - } - } else { + /* + * Synchronize with the reader thread. + */ + + result = WaitForRead(infoPtr, (infoPtr->flags & PIPE_ASYNC) ? 0 : 1); + + /* + * If an error occurred, return immediately. + */ + + if (result == -1) { + *errorCode = errno; + return -1; + } + + if (infoPtr->readFlags & PIPE_EXTRABYTE) { /* - * 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. + * The reader thread consumed 1 byte as a side effect of waiting so we + * need to move it into the buffer. */ - if (PeekNamedPipe(filePtr->handle, (LPVOID) NULL, (DWORD) 0, - (LPDWORD) NULL, &count, (LPDWORD) NULL) == TRUE) { - if ((count != 0) && ((DWORD) bufSize > count)) { - bufSize = (int) count; + *buf = infoPtr->extraByte; + infoPtr->readFlags &= ~PIPE_EXTRABYTE; + buf++; + bufSize--; + bytesRead = 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 further read attempts would block, return what we have. + */ -/* } else if ((count == 0) && (infoPtr->flags & PIPE_ASYNC)) { */ -/* errno = *errorCode = EAGAIN; */ -/* return -1; */ - } else if ((count == 0) && !(infoPtr->flags & PIPE_ASYNC)) { - bufSize = 1; - } - } else { - goto error; + if (result == 0) { + return bytesRead; } } /* - * 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; @@ -2061,12 +2135,12 @@ PipeInputProc(instanceData, buf, bufSize, errorCode) * * PipeOutputProc -- * - * Writes the given output on the IO channel. Returns count of how - * many characters were actually written, and an error indication. + * Writes the given output on the IO channel. Returns count of how many + * characters were actually written, and an error indication. * * Results: - * A count of how many characters were written is returned and an - * error indication is returned in an output argument. + * A count of how many characters were written is returned and an error + * indication is returned in an output argument. * * Side effects: * Writes output on the actual channel. @@ -2075,27 +2149,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. */ + const 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 = EWOULDBLOCK; + 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, (size_t) 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; + } /* @@ -2103,15 +2228,15 @@ PipeOutputProc(instanceData, buf, toWrite, errorCode) * * PipeEventProc -- * - * This function is invoked by Tcl_ServiceEvent when a file event - * reaches the front of the event queue. This procedure invokes - * Tcl_NotifyChannel on the pipe. + * This function is invoked by Tcl_ServiceEvent when a file event reaches + * the front of the event queue. This function invokes Tcl_NotifyChannel + * on the pipe. * * Results: - * Returns 1 if the event was handled, meaning it should be removed - * from the queue. Returns 0 if the event was not handled, meaning - * it should stay on the queue. The only time the event isn't - * handled is if the TCL_FILE_EVENTS flag bit isn't set. + * Returns 1 if the event was handled, meaning it should be removed from + * the queue. Returns 0 if the event was not handled, meaning it should + * stay on the queue. The only time the event isn't handled is if the + * TCL_FILE_EVENTS flag bit isn't set. * * Side effects: * Whatever the notifier callback does. @@ -2120,16 +2245,15 @@ 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; @@ -2137,12 +2261,13 @@ PipeEventProc(evPtr, flags) /* * Search through the list of watched pipes for the one whose handle - * matches the event. We do this rather than simply dereferencing - * the handle in the event so that pipes can be deleted while the - * event is in the queue. + * matches the event. We do this rather than simply dereferencing the + * handle in the event so that pipes can be deleted while the 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; @@ -2158,38 +2283,23 @@ 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. + * 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 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. - */ + mask = 0; + if ((infoPtr->watchMask & TCL_WRITABLE) && + (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT)) { + mask = TCL_WRITABLE; + } -/* if (GetLastError() == ERROR_BROKEN_PIPE) { */ -/* mask = TCL_READABLE; */ -/* } */ -/* } */ - } else { - mask = TCL_READABLE | TCL_WRITABLE; + if ((infoPtr->watchMask & TCL_READABLE) && (WaitForRead(infoPtr,0) >= 0)) { + if (infoPtr->readFlags & PIPE_EOF) { + mask = TCL_READABLE; + } else { + mask |= TCL_READABLE; + } } /* @@ -2205,8 +2315,7 @@ PipeEventProc(evPtr, flags) * * PipeWatchProc -- * - * Called by the notifier to set up to watch for events on this - * channel. + * Called by the notifier to set up to watch for events on this channel. * * Results: * None. @@ -2218,27 +2327,28 @@ PipeEventProc(evPtr, flags) */ static void -PipeWatchProc(instanceData, mask) - ClientData instanceData; /* Pipe state. */ - int mask; /* What events to watch for; OR-ed - * combination of TCL_READABLE, - * TCL_WRITABLE and TCL_EXCEPTION. */ +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 { @@ -2247,9 +2357,9 @@ PipeWatchProc(instanceData, mask) * Remove the pipe from the list of watched pipes. */ - for (nextPtrPtr = &firstPipePtr, ptr = *nextPtrPtr; - ptr != NULL; - nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { + for (nextPtrPtr = &(tsdPtr->firstPipePtr), ptr = *nextPtrPtr; + ptr != NULL; + nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { if (infoPtr == ptr) { *nextPtrPtr = ptr->nextPtr; break; @@ -2264,12 +2374,12 @@ PipeWatchProc(instanceData, mask) * * PipeGetHandleProc -- * - * Called from Tcl_GetChannelHandle to retrieve OS handles from - * inside a command pipeline based channel. + * Called from Tcl_GetChannelHandle to retrieve OS handles from inside a + * command pipeline based channel. * * Results: - * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if - * there is no handle for the specified direction. + * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if there is no + * handle for the specified direction. * * Side effects: * None. @@ -2278,26 +2388,16 @@ 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; + WinFile *filePtr; if (direction == TCL_READABLE && infoPtr->readFile) { filePtr = (WinFile*) infoPtr->readFile; - if (filePtr->type == WIN32S_PIPE) { - if (filePtr->handle == INVALID_HANDLE_VALUE) { - filePtr->handle = CreateFile(((WinPipe *)filePtr)->fileName, - GENERIC_READ, 0, NULL, OPEN_ALWAYS, - FILE_ATTRIBUTE_NORMAL, NULL); - } - if (filePtr->handle == INVALID_HANDLE_VALUE) { - return TCL_ERROR; - } - } *handlePtr = (ClientData) filePtr->handle; return TCL_OK; } @@ -2317,68 +2417,68 @@ PipeGetHandleProc(instanceData, direction, handlePtr) * Emulates the waitpid system call. * * Results: - * Returns 0 if the process is still alive, -1 on an error, or - * the pid on a clean close. + * Returns 0 if the process is still alive, -1 on an error, or the pid on + * a clean close. * * Side effects: - * Unless WNOHANG is set and the wait times out, the process - * information record will be deleted and the process handle - * will be closed. + * Unless WNOHANG is set and the wait times out, the process information + * record will be deleted and the process handle will be closed. * *---------------------------------------------------------------------- */ 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; + ProcInfo *infoPtr = NULL, **prevPtrPtr; + DWORD flags; Tcl_Pid result; - DWORD ret; + DWORD ret, exitCode; - if (!initialized) { - PipeInit(); - } + PipeInit(); /* * If no pid is specified, do nothing. */ - + if (pid == 0) { *statPtr = 0; return 0; } /* - * Find the process on the process list. + * Find the process and cut it from the process list. */ + Tcl_MutexLock(&pipeMutex); prevPtrPtr = &procList; for (infoPtr = procList; infoPtr != NULL; prevPtrPtr = &infoPtr->nextPtr, infoPtr = infoPtr->nextPtr) { if (infoPtr->hProcess == (HANDLE) pid) { + *prevPtrPtr = infoPtr->nextPtr; 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; + *statPtr = 0; return 0; } /* - * Officially "wait" for it to finish. We either poll (WNOHANG) or - * wait for an infinite amount of time. + * Officially "wait" for it to finish. We either poll (WNOHANG) or wait + * for an infinite amount of time. */ - + if (options & WNOHANG) { flags = 0; } else { @@ -2388,27 +2488,94 @@ Tcl_WaitPid(pid, statPtr, options) if (ret == WAIT_TIMEOUT) { *statPtr = 0; if (options & WNOHANG) { + /* + * Re-insert this infoPtr back on the list. + */ + + Tcl_MutexLock(&pipeMutex); + infoPtr->nextPtr = procList; + procList = infoPtr; + Tcl_MutexUnlock(&pipeMutex); return 0; } else { result = 0; } - } else if (ret != WAIT_FAILED) { - GetExitCodeProcess(infoPtr->hProcess, (DWORD*)statPtr); - *statPtr = ((*statPtr << 8) & 0xff00); + } else if (ret == WAIT_OBJECT_0) { + GetExitCodeProcess(infoPtr->hProcess, &exitCode); + + /* + * Does the exit code look like one of the exception codes? + */ + + switch (exitCode) { + case EXCEPTION_FLT_DENORMAL_OPERAND: + case EXCEPTION_FLT_DIVIDE_BY_ZERO: + case EXCEPTION_FLT_INEXACT_RESULT: + case EXCEPTION_FLT_INVALID_OPERATION: + case EXCEPTION_FLT_OVERFLOW: + case EXCEPTION_FLT_STACK_CHECK: + case EXCEPTION_FLT_UNDERFLOW: + case EXCEPTION_INT_DIVIDE_BY_ZERO: + case EXCEPTION_INT_OVERFLOW: + *statPtr = 0xC0000000 | SIGFPE; + break; + + case EXCEPTION_PRIV_INSTRUCTION: + case EXCEPTION_ILLEGAL_INSTRUCTION: + *statPtr = 0xC0000000 | SIGILL; + break; + + case EXCEPTION_ACCESS_VIOLATION: + case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: + case EXCEPTION_STACK_OVERFLOW: + case EXCEPTION_NONCONTINUABLE_EXCEPTION: + case EXCEPTION_INVALID_DISPOSITION: + case EXCEPTION_GUARD_PAGE: + case EXCEPTION_INVALID_HANDLE: + *statPtr = 0xC0000000 | SIGSEGV; + break; + + case EXCEPTION_DATATYPE_MISALIGNMENT: + *statPtr = 0xC0000000 | SIGBUS; + break; + + case EXCEPTION_BREAKPOINT: + case EXCEPTION_SINGLE_STEP: + *statPtr = 0xC0000000 | SIGTRAP; + break; + + case CONTROL_C_EXIT: + *statPtr = 0xC0000000 | SIGINT; + break; + + default: + /* + * Non-exceptional, normal, exit code. Note that the exit code is + * truncated to a signed short range [-32768,32768) whether it + * fits into this range or not. + * + * BUG: Even though the exit code is a DWORD, it is understood by + * convention to be a signed integer, yet there isn't enough room + * to fit this into the POSIX style waitstatus mask without + * truncating it. + */ + + *statPtr = exitCode; + break; + } result = pid; } else { errno = ECHILD; - *statPtr = ECHILD; + *statPtr = 0xC0000000 | ECHILD; result = (Tcl_Pid) -1; } /* - * Remove the process from the process list and close the process handle. + * Officially close the process handle. */ CloseHandle(infoPtr->hProcess); - *prevPtrPtr = infoPtr->nextPtr; - ckfree((char*)infoPtr); + ckfree(infoPtr); return result; } @@ -2416,10 +2583,45 @@ Tcl_WaitPid(pid, statPtr, options) /* *---------------------------------------------------------------------- * + * TclWinAddProcess -- + * + * Add a process to the process list so that we can use Tcl_WaitPid on + * the process. + * + * Results: + * None + * + * Side effects: + * Adds the specified process handle to the process list so Tcl_WaitPid + * knows about it. + * + *---------------------------------------------------------------------- + */ + +void +TclWinAddProcess( + void *hProcess, /* Handle to process */ + unsigned long id) /* Global process identifier */ +{ + ProcInfo *procPtr = ckalloc(sizeof(ProcInfo)); + + PipeInit(); + + procPtr->hProcess = hProcess; + procPtr->dwProcessId = id; + Tcl_MutexLock(&pipeMutex); + procPtr->nextPtr = procList; + procList = procPtr; + Tcl_MutexUnlock(&pipeMutex); +} + +/* + *---------------------------------------------------------------------- + * * Tcl_PidObjCmd -- * - * This procedure is invoked to process the "pid" Tcl command. - * See the user documentation for details on what it does. + * This function is invoked to process the "pid" Tcl command. See the + * user documentation for details on what it does. * * Results: * A standard Tcl result. @@ -2432,31 +2634,28 @@ Tcl_WaitPid(pid, statPtr, options) /* 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; + const Tcl_ChannelType *chanTypePtr; PipeInfo *pipePtr; int i; Tcl_Obj *resultPtr; - char buf[20]; if (objc > 2) { Tcl_WrongNumArgs(interp, 1, objv, "?channelId?"); return TCL_ERROR; } if (objc == 1) { - resultPtr = Tcl_GetObjResult(interp); - sprintf(buf, "%lu", (unsigned long) getpid()); - Tcl_SetStringObj(resultPtr, buf, -1); + Tcl_SetObjResult(interp, Tcl_NewWideIntObj((unsigned) getpid())); } else { - chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL), + chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL), NULL); - if (chan == (Tcl_Channel) NULL) { + if (chan == (Tcl_Channel) NULL) { return TCL_ERROR; } chanTypePtr = Tcl_GetChannelType(chan); @@ -2464,13 +2663,504 @@ Tcl_PidObjCmd(dummy, interp, objc, objv) return TCL_OK; } - pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan); - resultPtr = Tcl_GetObjResult(interp); - for (i = 0; i < pipePtr->numPids; i++) { - sprintf(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i])); + pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan); + resultPtr = Tcl_NewObj(); + for (i = 0; i < pipePtr->numPids; i++) { Tcl_ListObjAppendElement(/*interp*/ NULL, resultPtr, - Tcl_NewStringObj(buf, -1)); + Tcl_NewWideIntObj((unsigned) + TclpGetPid(pipePtr->pidPtr[i]))); } + Tcl_SetObjResult(interp, resultPtr); } 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 = EWOULDBLOCK; + 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. Will cause a memory leak of ~4k, if + * forcefully terminated with TerminateThread(). + * + *---------------------------------------------------------------------- + */ + +static DWORD WINAPI +PipeReaderThread( + LPVOID arg) +{ + PipeInfo *infoPtr = (PipeInfo *)arg; + HANDLE *handle = ((WinFile *) infoPtr->readFile)->handle; + DWORD count, err; + int done = 0; + HANDLE wEvents[2]; + DWORD waitResult; + + wEvents[0] = infoPtr->stopReader; + wEvents[1] = infoPtr->startReader; + + while (!done) { + /* + * Wait for the main thread to signal before attempting to wait on the + * pipe becoming readable. + */ + + waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); + + if (waitResult != (WAIT_OBJECT_0 + 1)) { + /* + * The start event was not signaled. It might be the stop event or + * an error, so exit. + */ + + break; + } + + /* + * 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); + if (infoPtr->threadId != NULL) { + /* + * TIP #218. When in flight ignore the event, no one will receive + * it anyway. + */ + + 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; + HANDLE wEvents[2]; + DWORD waitResult; + + wEvents[0] = infoPtr->stopWriter; + wEvents[1] = infoPtr->startWriter; + + while (!done) { + /* + * Wait for the main thread to signal before attempting to write. + */ + + waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); + + if (waitResult != (WAIT_OBJECT_0 + 1)) { + /* + * The start event was not signaled. It might be the stop event or + * an error, so exit. + */ + + if (waitResult == WAIT_OBJECT_0) { + SetEvent(infoPtr->writable); + } + + break; + } + + 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); + if (infoPtr->threadId != NULL) { + /* + * TIP #218. When in flight ignore the event, no one will receive + * it anyway. + */ + + Tcl_ThreadAlert(infoPtr->threadId); + } + Tcl_MutexUnlock(&pipeMutex); + } + + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * PipeThreadActionProc -- + * + * Insert or remove any thread local refs to this channel. + * + * Results: + * None. + * + * Side effects: + * Changes thread local list of valid channels. + * + *---------------------------------------------------------------------- + */ + +static void +PipeThreadActionProc( + ClientData instanceData, + int action) +{ + PipeInfo *infoPtr = (PipeInfo *) instanceData; + + /* + * We do not access firstPipePtr in the thread structures. This is not for + * all pipes managed by the thread, but only those we are watching. + * Removal of the filevent handlers before transfer thus takes care of + * this structure. + */ + + Tcl_MutexLock(&pipeMutex); + if (action == TCL_CHANNEL_THREAD_INSERT) { + /* + * We can't copy the thread information from the channel when the + * channel is created. At this time the channel back pointer has not + * been set yet. However in that case the threadId has already been + * set by TclpCreateCommandChannel itself, so the structure is still + * good. + */ + + PipeInit(); + if (infoPtr->channel != NULL) { + infoPtr->threadId = Tcl_GetChannelThread(infoPtr->channel); + } + } else { + infoPtr->threadId = NULL; + } + Tcl_MutexUnlock(&pipeMutex); +} + +/* + *---------------------------------------------------------------------- + * + * TclpOpenTemporaryFile -- + * + * Creates a temporary file, possibly based on the supplied bits and + * pieces of template supplied in the first three arguments. If the + * fourth argument is non-NULL, it contains a Tcl_Obj to store the name + * of the temporary file in (and it is caller's responsibility to clean + * up). If the fourth argument is NULL, try to arrange for the temporary + * file to go away once it is no longer needed. + * + * Results: + * A read-write Tcl Channel open on the file. + * + *---------------------------------------------------------------------- + */ + +Tcl_Channel +TclpOpenTemporaryFile( + Tcl_Obj *dirObj, + Tcl_Obj *basenameObj, + Tcl_Obj *extensionObj, + Tcl_Obj *resultingNameObj) +{ + TCHAR name[MAX_PATH]; + char *namePtr; + HANDLE handle; + DWORD flags = FILE_ATTRIBUTE_TEMPORARY; + int length, counter, counter2; + Tcl_DString buf; + + if (!resultingNameObj) { + flags |= FILE_FLAG_DELETE_ON_CLOSE; + } + + namePtr = (char *) name; + length = GetTempPath(MAX_PATH, name); + if (length == 0) { + goto gotError; + } + namePtr += length * sizeof(TCHAR); + if (basenameObj) { + const char *string = Tcl_GetStringFromObj(basenameObj, &length); + + Tcl_WinUtfToTChar(string, length, &buf); + memcpy(namePtr, Tcl_DStringValue(&buf), Tcl_DStringLength(&buf)); + namePtr += Tcl_DStringLength(&buf); + Tcl_DStringFree(&buf); + } else { + const TCHAR *baseStr = TEXT("TCL"); + int length = 3 * sizeof(TCHAR); + + memcpy(namePtr, baseStr, length); + namePtr += length; + } + counter = TclpGetClicks() % 65533; + counter2 = 1024; /* Only try this many times! Prevents + * an infinite loop. */ + + do { + char number[TCL_INTEGER_SPACE + 4]; + + sprintf(number, "%d.TMP", counter); + counter = (unsigned short) (counter + 1); + Tcl_WinUtfToTChar(number, strlen(number), &buf); + Tcl_DStringSetLength(&buf, Tcl_DStringLength(&buf) + 1); + memcpy(namePtr, Tcl_DStringValue(&buf), Tcl_DStringLength(&buf) + 1); + Tcl_DStringFree(&buf); + + handle = CreateFile(name, + GENERIC_READ|GENERIC_WRITE, 0, NULL, CREATE_NEW, flags, NULL); + } while (handle == INVALID_HANDLE_VALUE + && --counter2 > 0 + && GetLastError() == ERROR_FILE_EXISTS); + if (handle == INVALID_HANDLE_VALUE) { + goto gotError; + } + + if (resultingNameObj) { + Tcl_Obj *tmpObj = TclpNativeToNormalized(name); + + Tcl_AppendObjToObj(resultingNameObj, tmpObj); + TclDecrRefCount(tmpObj); + } + + return Tcl_MakeFileChannel((ClientData) handle, + TCL_READABLE|TCL_WRITABLE); + + gotError: + TclWinConvertError(GetLastError()); + return NULL; +} + +/* + * Local Variables: + * mode: c + * c-basic-offset: 4 + * fill-column: 78 + * End: + */ |
