sl@0: /* sl@0: * tclWinPipe.c -- sl@0: * sl@0: * This file implements the Windows-specific exec pipeline functions, sl@0: * the "pipe" channel driver, and the "pid" Tcl command. sl@0: * sl@0: * Copyright (c) 1996-1997 by Sun Microsystems, Inc. sl@0: * sl@0: * See the file "license.terms" for information on usage and redistribution sl@0: * of this file, and for a DISCLAIMER OF ALL WARRANTIES. sl@0: * sl@0: * RCS: @(#) $Id: tclWinPipe.c,v 1.33.2.17 2006/03/14 20:36:39 andreas_kupries Exp $ sl@0: */ sl@0: sl@0: #include "tclWinInt.h" sl@0: sl@0: #include sl@0: #include sl@0: #include sl@0: sl@0: /* sl@0: * The following variable is used to tell whether this module has been sl@0: * initialized. sl@0: */ sl@0: sl@0: static int initialized = 0; sl@0: sl@0: /* sl@0: * The pipeMutex locks around access to the initialized and procList variables, sl@0: * and it is used to protect background threads from being terminated while sl@0: * they are using APIs that hold locks. sl@0: */ sl@0: sl@0: TCL_DECLARE_MUTEX(pipeMutex) sl@0: sl@0: /* sl@0: * The following defines identify the various types of applications that sl@0: * run under windows. There is special case code for the various types. sl@0: */ sl@0: sl@0: #define APPL_NONE 0 sl@0: #define APPL_DOS 1 sl@0: #define APPL_WIN3X 2 sl@0: #define APPL_WIN32 3 sl@0: sl@0: /* sl@0: * The following constants and structures are used to encapsulate the state sl@0: * of various types of files used in a pipeline. sl@0: * This used to have a 1 && 2 that supported Win32s. sl@0: */ sl@0: sl@0: #define WIN_FILE 3 /* Basic Win32 file. */ sl@0: sl@0: /* sl@0: * This structure encapsulates the common state associated with all file sl@0: * types used in a pipeline. sl@0: */ sl@0: sl@0: typedef struct WinFile { sl@0: int type; /* One of the file types defined above. */ sl@0: HANDLE handle; /* Open file handle. */ sl@0: } WinFile; sl@0: sl@0: /* sl@0: * This list is used to map from pids to process handles. sl@0: */ sl@0: sl@0: typedef struct ProcInfo { sl@0: HANDLE hProcess; sl@0: DWORD dwProcessId; sl@0: struct ProcInfo *nextPtr; sl@0: } ProcInfo; sl@0: sl@0: static ProcInfo *procList; sl@0: sl@0: /* sl@0: * Bit masks used in the flags field of the PipeInfo structure below. sl@0: */ sl@0: sl@0: #define PIPE_PENDING (1<<0) /* Message is pending in the queue. */ sl@0: #define PIPE_ASYNC (1<<1) /* Channel is non-blocking. */ sl@0: sl@0: /* sl@0: * Bit masks used in the sharedFlags field of the PipeInfo structure below. sl@0: */ sl@0: sl@0: #define PIPE_EOF (1<<2) /* Pipe has reached EOF. */ sl@0: #define PIPE_EXTRABYTE (1<<3) /* The reader thread has consumed one byte. */ sl@0: sl@0: /* sl@0: * This structure describes per-instance data for a pipe based channel. sl@0: */ sl@0: sl@0: typedef struct PipeInfo { sl@0: struct PipeInfo *nextPtr; /* Pointer to next registered pipe. */ sl@0: Tcl_Channel channel; /* Pointer to channel structure. */ sl@0: int validMask; /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, or TCL_EXCEPTION: indicates sl@0: * which operations are valid on the file. */ sl@0: int watchMask; /* OR'ed combination of TCL_READABLE, sl@0: * TCL_WRITABLE, or TCL_EXCEPTION: indicates sl@0: * which events should be reported. */ sl@0: int flags; /* State flags, see above for a list. */ sl@0: TclFile readFile; /* Output from pipe. */ sl@0: TclFile writeFile; /* Input from pipe. */ sl@0: TclFile errorFile; /* Error output from pipe. */ sl@0: int numPids; /* Number of processes attached to pipe. */ sl@0: Tcl_Pid *pidPtr; /* Pids of attached processes. */ sl@0: Tcl_ThreadId threadId; /* Thread to which events should be reported. sl@0: * This value is used by the reader/writer sl@0: * threads. */ sl@0: HANDLE writeThread; /* Handle to writer thread. */ sl@0: HANDLE readThread; /* Handle to reader thread. */ sl@0: HANDLE writable; /* Manual-reset event to signal when the sl@0: * writer thread has finished waiting for sl@0: * the current buffer to be written. */ sl@0: HANDLE readable; /* Manual-reset event to signal when the sl@0: * reader thread has finished waiting for sl@0: * input. */ sl@0: HANDLE startWriter; /* Auto-reset event used by the main thread to sl@0: * signal when the writer thread should attempt sl@0: * to write to the pipe. */ sl@0: HANDLE stopWriter; /* Manual-reset event used to alert the reader sl@0: * thread to fall-out and exit */ sl@0: HANDLE startReader; /* Auto-reset event used by the main thread to sl@0: * signal when the reader thread should attempt sl@0: * to read from the pipe. */ sl@0: HANDLE stopReader; /* Manual-reset event used to alert the reader sl@0: * thread to fall-out and exit */ sl@0: DWORD writeError; /* An error caused by the last background sl@0: * write. Set to 0 if no error has been sl@0: * detected. This word is shared with the sl@0: * writer thread so access must be sl@0: * synchronized with the writable object. sl@0: */ sl@0: char *writeBuf; /* Current background output buffer. sl@0: * Access is synchronized with the writable sl@0: * object. */ sl@0: int writeBufLen; /* Size of write buffer. Access is sl@0: * synchronized with the writable sl@0: * object. */ sl@0: int toWrite; /* Current amount to be written. Access is sl@0: * synchronized with the writable object. */ sl@0: int readFlags; /* Flags that are shared with the reader sl@0: * thread. Access is synchronized with the sl@0: * readable object. */ sl@0: char extraByte; /* Buffer for extra character consumed by sl@0: * reader thread. This byte is shared with sl@0: * the reader thread so access must be sl@0: * synchronized with the readable object. */ sl@0: } PipeInfo; sl@0: sl@0: typedef struct ThreadSpecificData { sl@0: /* sl@0: * The following pointer refers to the head of the list of pipes sl@0: * that are being watched for file events. sl@0: */ sl@0: sl@0: PipeInfo *firstPipePtr; sl@0: } ThreadSpecificData; sl@0: sl@0: static Tcl_ThreadDataKey dataKey; sl@0: sl@0: /* sl@0: * The following structure is what is added to the Tcl event queue when sl@0: * pipe events are generated. sl@0: */ sl@0: sl@0: typedef struct PipeEvent { sl@0: Tcl_Event header; /* Information that is standard for sl@0: * all events. */ sl@0: PipeInfo *infoPtr; /* Pointer to pipe info structure. Note sl@0: * that we still have to verify that the sl@0: * pipe exists before dereferencing this sl@0: * pointer. */ sl@0: } PipeEvent; sl@0: sl@0: /* sl@0: * Declarations for functions used only in this file. sl@0: */ sl@0: sl@0: static int ApplicationType(Tcl_Interp *interp, sl@0: const char *fileName, char *fullName); sl@0: static void BuildCommandLine(const char *executable, int argc, sl@0: CONST char **argv, Tcl_DString *linePtr); sl@0: static BOOL HasConsole(void); sl@0: static int PipeBlockModeProc(ClientData instanceData, int mode); sl@0: static void PipeCheckProc(ClientData clientData, int flags); sl@0: static int PipeClose2Proc(ClientData instanceData, sl@0: Tcl_Interp *interp, int flags); sl@0: static int PipeEventProc(Tcl_Event *evPtr, int flags); sl@0: static int PipeGetHandleProc(ClientData instanceData, sl@0: int direction, ClientData *handlePtr); sl@0: static void PipeInit(void); sl@0: static int PipeInputProc(ClientData instanceData, char *buf, sl@0: int toRead, int *errorCode); sl@0: static int PipeOutputProc(ClientData instanceData, sl@0: CONST char *buf, int toWrite, int *errorCode); sl@0: static DWORD WINAPI PipeReaderThread(LPVOID arg); sl@0: static void PipeSetupProc(ClientData clientData, int flags); sl@0: static void PipeWatchProc(ClientData instanceData, int mask); sl@0: static DWORD WINAPI PipeWriterThread(LPVOID arg); sl@0: static int TempFileName(WCHAR name[MAX_PATH]); sl@0: static int WaitForRead(PipeInfo *infoPtr, int blocking); sl@0: sl@0: static void PipeThreadActionProc _ANSI_ARGS_ (( sl@0: ClientData instanceData, int action)); sl@0: sl@0: /* sl@0: * This structure describes the channel type structure for command pipe sl@0: * based IO. sl@0: */ sl@0: sl@0: static Tcl_ChannelType pipeChannelType = { sl@0: "pipe", /* Type name. */ sl@0: TCL_CHANNEL_VERSION_4, /* v4 channel */ sl@0: TCL_CLOSE2PROC, /* Close proc. */ sl@0: PipeInputProc, /* Input proc. */ sl@0: PipeOutputProc, /* Output proc. */ sl@0: NULL, /* Seek proc. */ sl@0: NULL, /* Set option proc. */ sl@0: NULL, /* Get option proc. */ sl@0: PipeWatchProc, /* Set up notifier to watch the channel. */ sl@0: PipeGetHandleProc, /* Get an OS handle from channel. */ sl@0: PipeClose2Proc, /* close2proc */ sl@0: PipeBlockModeProc, /* Set blocking or non-blocking mode.*/ sl@0: NULL, /* flush proc. */ sl@0: NULL, /* handler proc. */ sl@0: NULL, /* wide seek proc */ sl@0: PipeThreadActionProc, /* thread action proc */ sl@0: }; sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeInit -- sl@0: * sl@0: * This function initializes the static variables for this file. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Creates a new event source. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: PipeInit() sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: /* sl@0: * Check the initialized flag first, then check again in the mutex. sl@0: * This is a speed enhancement. sl@0: */ sl@0: sl@0: if (!initialized) { sl@0: Tcl_MutexLock(&pipeMutex); sl@0: if (!initialized) { sl@0: initialized = 1; sl@0: procList = NULL; sl@0: } sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: } sl@0: sl@0: tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); sl@0: if (tsdPtr == NULL) { sl@0: tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: tsdPtr->firstPipePtr = NULL; sl@0: Tcl_CreateEventSource(PipeSetupProc, PipeCheckProc, NULL); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpFinalizePipes -- sl@0: * sl@0: * This function is called from Tcl_FinalizeThread to finalize the sl@0: * platform specific pipe subsystem. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Removes the pipe event source. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: TclpFinalizePipes() sl@0: { sl@0: ThreadSpecificData *tsdPtr; sl@0: sl@0: tsdPtr = (ThreadSpecificData *)TclThreadDataKeyGet(&dataKey); sl@0: if (tsdPtr != NULL) { sl@0: Tcl_DeleteEventSource(PipeSetupProc, PipeCheckProc, NULL); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeSetupProc -- sl@0: * sl@0: * This procedure is invoked before Tcl_DoOneEvent blocks waiting sl@0: * for an event. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Adjusts the block time if needed. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: PipeSetupProc( sl@0: ClientData data, /* Not used. */ sl@0: int flags) /* Event flags as passed to Tcl_DoOneEvent. */ sl@0: { sl@0: PipeInfo *infoPtr; sl@0: Tcl_Time blockTime = { 0, 0 }; sl@0: int block = 1; sl@0: WinFile *filePtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Look to see if any events are already pending. If they are, poll. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: if (infoPtr->watchMask & TCL_WRITABLE) { sl@0: filePtr = (WinFile*) infoPtr->writeFile; sl@0: if (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT) { sl@0: block = 0; sl@0: } sl@0: } sl@0: if (infoPtr->watchMask & TCL_READABLE) { sl@0: filePtr = (WinFile*) infoPtr->readFile; sl@0: if (WaitForRead(infoPtr, 0) >= 0) { sl@0: block = 0; sl@0: } sl@0: } sl@0: } sl@0: if (!block) { sl@0: Tcl_SetMaxBlockTime(&blockTime); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeCheckProc -- sl@0: * sl@0: * This procedure is called by Tcl_DoOneEvent to check the pipe sl@0: * event source for events. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * May queue an event. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: PipeCheckProc( sl@0: ClientData data, /* Not used. */ sl@0: int flags) /* Event flags as passed to Tcl_DoOneEvent. */ sl@0: { sl@0: PipeInfo *infoPtr; sl@0: PipeEvent *evPtr; sl@0: WinFile *filePtr; sl@0: int needEvent; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return; sl@0: } sl@0: sl@0: /* sl@0: * Queue events for any ready pipes that don't already have events sl@0: * queued. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: if (infoPtr->flags & PIPE_PENDING) { sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * Queue an event if the pipe is signaled for reading or writing. sl@0: */ sl@0: sl@0: needEvent = 0; sl@0: filePtr = (WinFile*) infoPtr->writeFile; sl@0: if ((infoPtr->watchMask & TCL_WRITABLE) && sl@0: (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT)) { sl@0: needEvent = 1; sl@0: } sl@0: sl@0: filePtr = (WinFile*) infoPtr->readFile; sl@0: if ((infoPtr->watchMask & TCL_READABLE) && sl@0: (WaitForRead(infoPtr, 0) >= 0)) { sl@0: needEvent = 1; sl@0: } sl@0: sl@0: if (needEvent) { sl@0: infoPtr->flags |= PIPE_PENDING; sl@0: evPtr = (PipeEvent *) ckalloc(sizeof(PipeEvent)); sl@0: evPtr->header.proc = PipeEventProc; sl@0: evPtr->infoPtr = infoPtr; sl@0: Tcl_QueueEvent((Tcl_Event *) evPtr, TCL_QUEUE_TAIL); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclWinMakeFile -- sl@0: * sl@0: * This function constructs a new TclFile from a given data and sl@0: * type value. sl@0: * sl@0: * Results: sl@0: * Returns a newly allocated WinFile as a TclFile. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: TclFile sl@0: TclWinMakeFile( sl@0: HANDLE handle) /* Type-specific data. */ sl@0: { sl@0: WinFile *filePtr; sl@0: sl@0: filePtr = (WinFile *) ckalloc(sizeof(WinFile)); sl@0: filePtr->type = WIN_FILE; sl@0: filePtr->handle = handle; sl@0: sl@0: return (TclFile)filePtr; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TempFileName -- sl@0: * sl@0: * Gets a temporary file name and deals with the fact that the sl@0: * temporary file path provided by Windows may not actually exist sl@0: * if the TMP or TEMP environment variables refer to a sl@0: * non-existent directory. sl@0: * sl@0: * Results: sl@0: * 0 if error, non-zero otherwise. If non-zero is returned, the sl@0: * name buffer will be filled with a name that can be used to sl@0: * construct a temporary file. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: TempFileName(name) sl@0: WCHAR name[MAX_PATH]; /* Buffer in which name for temporary sl@0: * file gets stored. */ sl@0: { sl@0: TCHAR *prefix; sl@0: sl@0: prefix = (tclWinProcs->useWide) ? (TCHAR *) L"TCL" : (TCHAR *) "TCL"; sl@0: if ((*tclWinProcs->getTempPathProc)(MAX_PATH, name) != 0) { sl@0: if ((*tclWinProcs->getTempFileNameProc)((TCHAR *) name, prefix, 0, sl@0: name) != 0) { sl@0: return 1; sl@0: } sl@0: } sl@0: if (tclWinProcs->useWide) { sl@0: ((WCHAR *) name)[0] = '.'; sl@0: ((WCHAR *) name)[1] = '\0'; sl@0: } else { sl@0: ((char *) name)[0] = '.'; sl@0: ((char *) name)[1] = '\0'; sl@0: } sl@0: return (*tclWinProcs->getTempFileNameProc)((TCHAR *) name, prefix, 0, sl@0: name); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpMakeFile -- sl@0: * sl@0: * Make a TclFile from a channel. sl@0: * sl@0: * Results: sl@0: * Returns a new TclFile or NULL on failure. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: TclFile sl@0: TclpMakeFile(channel, direction) sl@0: Tcl_Channel channel; /* Channel to get file from. */ sl@0: int direction; /* Either TCL_READABLE or TCL_WRITABLE. */ sl@0: { sl@0: HANDLE handle; sl@0: sl@0: if (Tcl_GetChannelHandle(channel, direction, sl@0: (ClientData *) &handle) == TCL_OK) { sl@0: return TclWinMakeFile(handle); sl@0: } else { sl@0: return (TclFile) NULL; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpOpenFile -- sl@0: * sl@0: * This function opens files for use in a pipeline. sl@0: * sl@0: * Results: sl@0: * Returns a newly allocated TclFile structure containing the sl@0: * file handle. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: TclFile sl@0: TclpOpenFile(path, mode) sl@0: CONST char *path; /* The name of the file to open. */ sl@0: int mode; /* In what mode to open the file? */ sl@0: { sl@0: HANDLE handle; sl@0: DWORD accessMode, createMode, shareMode, flags; sl@0: Tcl_DString ds; sl@0: CONST TCHAR *nativePath; sl@0: sl@0: /* sl@0: * Map the access bits to the NT access mode. sl@0: */ sl@0: sl@0: switch (mode & (O_RDONLY | O_WRONLY | O_RDWR)) { sl@0: case O_RDONLY: sl@0: accessMode = GENERIC_READ; sl@0: break; sl@0: case O_WRONLY: sl@0: accessMode = GENERIC_WRITE; sl@0: break; sl@0: case O_RDWR: sl@0: accessMode = (GENERIC_READ | GENERIC_WRITE); sl@0: break; sl@0: default: sl@0: TclWinConvertError(ERROR_INVALID_FUNCTION); sl@0: return NULL; sl@0: } sl@0: sl@0: /* sl@0: * Map the creation flags to the NT create mode. sl@0: */ sl@0: sl@0: switch (mode & (O_CREAT | O_EXCL | O_TRUNC)) { sl@0: case (O_CREAT | O_EXCL): sl@0: case (O_CREAT | O_EXCL | O_TRUNC): sl@0: createMode = CREATE_NEW; sl@0: break; sl@0: case (O_CREAT | O_TRUNC): sl@0: createMode = CREATE_ALWAYS; sl@0: break; sl@0: case O_CREAT: sl@0: createMode = OPEN_ALWAYS; sl@0: break; sl@0: case O_TRUNC: sl@0: case (O_TRUNC | O_EXCL): sl@0: createMode = TRUNCATE_EXISTING; sl@0: break; sl@0: default: sl@0: createMode = OPEN_EXISTING; sl@0: break; sl@0: } sl@0: sl@0: nativePath = Tcl_WinUtfToTChar(path, -1, &ds); sl@0: sl@0: /* sl@0: * If the file is not being created, use the existing file attributes. sl@0: */ sl@0: sl@0: flags = 0; sl@0: if (!(mode & O_CREAT)) { sl@0: flags = (*tclWinProcs->getFileAttributesProc)(nativePath); sl@0: if (flags == 0xFFFFFFFF) { sl@0: flags = 0; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Set up the file sharing mode. We want to allow simultaneous access. sl@0: */ sl@0: sl@0: shareMode = FILE_SHARE_READ | FILE_SHARE_WRITE; sl@0: sl@0: /* sl@0: * Now we get to create the file. sl@0: */ sl@0: sl@0: handle = (*tclWinProcs->createFileProc)(nativePath, accessMode, sl@0: shareMode, NULL, createMode, flags, NULL); sl@0: Tcl_DStringFree(&ds); sl@0: sl@0: if (handle == INVALID_HANDLE_VALUE) { sl@0: DWORD err; sl@0: sl@0: err = GetLastError(); sl@0: if ((err & 0xffffL) == ERROR_OPEN_FAILED) { sl@0: err = (mode & O_CREAT) ? ERROR_FILE_EXISTS : ERROR_FILE_NOT_FOUND; sl@0: } sl@0: TclWinConvertError(err); sl@0: return NULL; sl@0: } sl@0: sl@0: /* sl@0: * Seek to the end of file if we are writing. sl@0: */ sl@0: sl@0: if (mode & (O_WRONLY|O_APPEND)) { sl@0: SetFilePointer(handle, 0, NULL, FILE_END); sl@0: } sl@0: sl@0: return TclWinMakeFile(handle); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpCreateTempFile -- sl@0: * sl@0: * This function opens a unique file with the property that it sl@0: * will be deleted when its file handle is closed. The temporary sl@0: * file is created in the system temporary directory. sl@0: * sl@0: * Results: sl@0: * Returns a valid TclFile, or NULL on failure. sl@0: * sl@0: * Side effects: sl@0: * Creates a new temporary file. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: TclFile sl@0: TclpCreateTempFile(contents) sl@0: CONST char *contents; /* String to write into temp file, or NULL. */ sl@0: { sl@0: WCHAR name[MAX_PATH]; sl@0: CONST char *native; sl@0: Tcl_DString dstring; sl@0: HANDLE handle; sl@0: sl@0: if (TempFileName(name) == 0) { sl@0: return NULL; sl@0: } sl@0: sl@0: handle = (*tclWinProcs->createFileProc)((TCHAR *) name, sl@0: GENERIC_READ | GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, sl@0: FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, NULL); sl@0: if (handle == INVALID_HANDLE_VALUE) { sl@0: goto error; sl@0: } sl@0: sl@0: /* sl@0: * Write the file out, doing line translations on the way. sl@0: */ sl@0: sl@0: if (contents != NULL) { sl@0: DWORD result, length; sl@0: CONST char *p; sl@0: sl@0: /* sl@0: * Convert the contents from UTF to native encoding sl@0: */ sl@0: native = Tcl_UtfToExternalDString(NULL, contents, -1, &dstring); sl@0: sl@0: for (p = native; *p != '\0'; p++) { sl@0: if (*p == '\n') { sl@0: length = p - native; sl@0: if (length > 0) { sl@0: if (!WriteFile(handle, native, length, &result, NULL)) { sl@0: goto error; sl@0: } sl@0: } sl@0: if (!WriteFile(handle, "\r\n", 2, &result, NULL)) { sl@0: goto error; sl@0: } sl@0: native = p+1; sl@0: } sl@0: } sl@0: length = p - native; sl@0: if (length > 0) { sl@0: if (!WriteFile(handle, native, length, &result, NULL)) { sl@0: goto error; sl@0: } sl@0: } sl@0: Tcl_DStringFree(&dstring); sl@0: if (SetFilePointer(handle, 0, NULL, FILE_BEGIN) == 0xFFFFFFFF) { sl@0: goto error; sl@0: } sl@0: } sl@0: sl@0: return TclWinMakeFile(handle); sl@0: sl@0: error: sl@0: /* Free the native representation of the contents if necessary */ sl@0: if (contents != NULL) { sl@0: Tcl_DStringFree(&dstring); sl@0: } sl@0: sl@0: TclWinConvertError(GetLastError()); sl@0: CloseHandle(handle); sl@0: (*tclWinProcs->deleteFileProc)((TCHAR *) name); sl@0: return NULL; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpTempFileName -- sl@0: * sl@0: * This function returns a unique filename. sl@0: * sl@0: * Results: sl@0: * Returns a valid Tcl_Obj* with refCount 0, or NULL on failure. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: Tcl_Obj* sl@0: TclpTempFileName() sl@0: { sl@0: WCHAR fileName[MAX_PATH]; sl@0: sl@0: if (TempFileName(fileName) == 0) { sl@0: return NULL; sl@0: } sl@0: sl@0: return TclpNativeToNormalized((ClientData) fileName); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpCreatePipe -- sl@0: * sl@0: * Creates an anonymous pipe. sl@0: * sl@0: * Results: sl@0: * Returns 1 on success, 0 on failure. sl@0: * sl@0: * Side effects: sl@0: * Creates a pipe. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TclpCreatePipe( sl@0: TclFile *readPipe, /* Location to store file handle for sl@0: * read side of pipe. */ sl@0: TclFile *writePipe) /* Location to store file handle for sl@0: * write side of pipe. */ sl@0: { sl@0: HANDLE readHandle, writeHandle; sl@0: sl@0: if (CreatePipe(&readHandle, &writeHandle, NULL, 0) != 0) { sl@0: *readPipe = TclWinMakeFile(readHandle); sl@0: *writePipe = TclWinMakeFile(writeHandle); sl@0: return 1; sl@0: } sl@0: sl@0: TclWinConvertError(GetLastError()); sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpCloseFile -- sl@0: * sl@0: * Closes a pipeline file handle. These handles are created by sl@0: * TclpOpenFile, TclpCreatePipe, or TclpMakeFile. sl@0: * sl@0: * Results: sl@0: * 0 on success, -1 on failure. sl@0: * sl@0: * Side effects: sl@0: * The file is closed and deallocated. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TclpCloseFile( sl@0: TclFile file) /* The file to close. */ sl@0: { sl@0: WinFile *filePtr = (WinFile *) file; sl@0: sl@0: switch (filePtr->type) { sl@0: case WIN_FILE: sl@0: /* sl@0: * Don't close the Win32 handle if the handle is a standard channel sl@0: * during the thread exit process. Otherwise, one thread may kill sl@0: * the stdio of another. sl@0: */ sl@0: sl@0: if (!TclInThreadExit() sl@0: || ((GetStdHandle(STD_INPUT_HANDLE) != filePtr->handle) sl@0: && (GetStdHandle(STD_OUTPUT_HANDLE) != filePtr->handle) sl@0: && (GetStdHandle(STD_ERROR_HANDLE) != filePtr->handle))) { sl@0: if (filePtr->handle != NULL && sl@0: CloseHandle(filePtr->handle) == FALSE) { sl@0: TclWinConvertError(GetLastError()); sl@0: ckfree((char *) filePtr); sl@0: return -1; sl@0: } sl@0: } sl@0: break; sl@0: sl@0: default: sl@0: panic("TclpCloseFile: unexpected file type"); sl@0: } sl@0: sl@0: ckfree((char *) filePtr); sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *-------------------------------------------------------------------------- sl@0: * sl@0: * TclpGetPid -- sl@0: * sl@0: * Given a HANDLE to a child process, return the process id for that sl@0: * child process. sl@0: * sl@0: * Results: sl@0: * Returns the process id for the child process. If the pid was not sl@0: * known by Tcl, either because the pid was not created by Tcl or the sl@0: * child process has already been reaped, -1 is returned. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *-------------------------------------------------------------------------- sl@0: */ sl@0: sl@0: unsigned long sl@0: TclpGetPid( sl@0: Tcl_Pid pid) /* The HANDLE of the child process. */ sl@0: { sl@0: ProcInfo *infoPtr; sl@0: sl@0: PipeInit(); sl@0: sl@0: Tcl_MutexLock(&pipeMutex); sl@0: for (infoPtr = procList; infoPtr != NULL; infoPtr = infoPtr->nextPtr) { sl@0: if (infoPtr->hProcess == (HANDLE) pid) { sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: return infoPtr->dwProcessId; sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: return (unsigned long) -1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpCreateProcess -- sl@0: * sl@0: * Create a child process that has the specified files as its sl@0: * standard input, output, and error. The child process runs sl@0: * asynchronously under Windows NT and Windows 9x, and runs sl@0: * with the same environment variables as the creating process. sl@0: * sl@0: * The complete Windows search path is searched to find the specified sl@0: * executable. If an executable by the given name is not found, sl@0: * automatically tries appending ".com", ".exe", and ".bat" to the sl@0: * executable name. sl@0: * sl@0: * Results: sl@0: * The return value is TCL_ERROR and an error message is left in sl@0: * the interp's result if there was a problem creating the child sl@0: * process. Otherwise, the return value is TCL_OK and *pidPtr is sl@0: * filled with the process id of the child process. sl@0: * sl@0: * Side effects: sl@0: * A process is created. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: int sl@0: TclpCreateProcess( sl@0: Tcl_Interp *interp, /* Interpreter in which to leave errors that sl@0: * occurred when creating the child process. sl@0: * Error messages from the child process sl@0: * itself are sent to errorFile. */ sl@0: int argc, /* Number of arguments in following array. */ sl@0: CONST char **argv, /* Array of argument strings. argv[0] sl@0: * contains the name of the executable sl@0: * converted to native format (using the sl@0: * Tcl_TranslateFileName call). Additional sl@0: * arguments have not been converted. */ sl@0: TclFile inputFile, /* If non-NULL, gives the file to use as sl@0: * input for the child process. If inputFile sl@0: * file is not readable or is NULL, the child sl@0: * will receive no standard input. */ sl@0: TclFile outputFile, /* If non-NULL, gives the file that sl@0: * receives output from the child process. If sl@0: * outputFile file is not writeable or is sl@0: * NULL, output from the child will be sl@0: * discarded. */ sl@0: TclFile errorFile, /* If non-NULL, gives the file that sl@0: * receives errors from the child process. If sl@0: * errorFile file is not writeable or is NULL, sl@0: * errors from the child will be discarded. sl@0: * errorFile may be the same as outputFile. */ sl@0: Tcl_Pid *pidPtr) /* If this procedure is successful, pidPtr sl@0: * is filled with the process id of the child sl@0: * process. */ sl@0: { sl@0: int result, applType, createFlags; sl@0: Tcl_DString cmdLine; /* Complete command line (TCHAR). */ sl@0: STARTUPINFOA startInfo; sl@0: PROCESS_INFORMATION procInfo; sl@0: SECURITY_ATTRIBUTES secAtts; sl@0: HANDLE hProcess, h, inputHandle, outputHandle, errorHandle; sl@0: char execPath[MAX_PATH * TCL_UTF_MAX]; sl@0: WinFile *filePtr; sl@0: sl@0: PipeInit(); sl@0: sl@0: applType = ApplicationType(interp, argv[0], execPath); sl@0: if (applType == APPL_NONE) { sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: result = TCL_ERROR; sl@0: Tcl_DStringInit(&cmdLine); sl@0: hProcess = GetCurrentProcess(); sl@0: sl@0: /* sl@0: * STARTF_USESTDHANDLES must be used to pass handles to child process. sl@0: * Using SetStdHandle() and/or dup2() only works when a console mode sl@0: * parent process is spawning an attached console mode child process. sl@0: */ sl@0: sl@0: ZeroMemory(&startInfo, sizeof(startInfo)); sl@0: startInfo.cb = sizeof(startInfo); sl@0: startInfo.dwFlags = STARTF_USESTDHANDLES; sl@0: startInfo.hStdInput = INVALID_HANDLE_VALUE; sl@0: startInfo.hStdOutput= INVALID_HANDLE_VALUE; sl@0: startInfo.hStdError = INVALID_HANDLE_VALUE; sl@0: sl@0: secAtts.nLength = sizeof(SECURITY_ATTRIBUTES); sl@0: secAtts.lpSecurityDescriptor = NULL; sl@0: secAtts.bInheritHandle = TRUE; sl@0: sl@0: /* sl@0: * We have to check the type of each file, since we cannot duplicate sl@0: * some file types. sl@0: */ sl@0: sl@0: inputHandle = INVALID_HANDLE_VALUE; sl@0: if (inputFile != NULL) { sl@0: filePtr = (WinFile *)inputFile; sl@0: if (filePtr->type == WIN_FILE) { sl@0: inputHandle = filePtr->handle; sl@0: } sl@0: } sl@0: outputHandle = INVALID_HANDLE_VALUE; sl@0: if (outputFile != NULL) { sl@0: filePtr = (WinFile *)outputFile; sl@0: if (filePtr->type == WIN_FILE) { sl@0: outputHandle = filePtr->handle; sl@0: } sl@0: } sl@0: errorHandle = INVALID_HANDLE_VALUE; sl@0: if (errorFile != NULL) { sl@0: filePtr = (WinFile *)errorFile; sl@0: if (filePtr->type == WIN_FILE) { sl@0: errorHandle = filePtr->handle; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Duplicate all the handles which will be passed off as stdin, stdout sl@0: * and stderr of the child process. The duplicate handles are set to sl@0: * be inheritable, so the child process can use them. sl@0: */ sl@0: sl@0: if (inputHandle == INVALID_HANDLE_VALUE) { sl@0: /* sl@0: * If handle was not set, stdin should return immediate EOF. sl@0: * Under Windows95, some applications (both 16 and 32 bit!) sl@0: * cannot read from the NUL device; they read from console sl@0: * instead. When running tk, this is fatal because the child sl@0: * process would hang forever waiting for EOF from the unmapped sl@0: * console window used by the helper application. sl@0: * sl@0: * Fortunately, the helper application detects a closed pipe sl@0: * as an immediate EOF and can pass that information to the sl@0: * child process. sl@0: */ sl@0: sl@0: if (CreatePipe(&startInfo.hStdInput, &h, &secAtts, 0) != FALSE) { sl@0: CloseHandle(h); sl@0: } sl@0: } else { sl@0: DuplicateHandle(hProcess, inputHandle, hProcess, &startInfo.hStdInput, sl@0: 0, TRUE, DUPLICATE_SAME_ACCESS); sl@0: } sl@0: if (startInfo.hStdInput == INVALID_HANDLE_VALUE) { sl@0: TclWinConvertError(GetLastError()); sl@0: Tcl_AppendResult(interp, "couldn't duplicate input handle: ", sl@0: Tcl_PosixError(interp), (char *) NULL); sl@0: goto end; sl@0: } sl@0: sl@0: if (outputHandle == INVALID_HANDLE_VALUE) { sl@0: /* sl@0: * If handle was not set, output should be sent to an infinitely sl@0: * deep sink. Under Windows 95, some 16 bit applications cannot sl@0: * have stdout redirected to NUL; they send their output to sl@0: * the console instead. Some applications, like "more" or "dir /p", sl@0: * when outputting multiple pages to the console, also then try and sl@0: * read from the console to go the next page. When running tk, this sl@0: * is fatal because the child process would hang forever waiting sl@0: * for input from the unmapped console window used by the helper sl@0: * application. sl@0: * sl@0: * Fortunately, the helper application will detect a closed pipe sl@0: * as a sink. sl@0: */ sl@0: sl@0: if ((TclWinGetPlatformId() == VER_PLATFORM_WIN32_WINDOWS) sl@0: && (applType == APPL_DOS)) { sl@0: if (CreatePipe(&h, &startInfo.hStdOutput, &secAtts, 0) != FALSE) { sl@0: CloseHandle(h); sl@0: } sl@0: } else { sl@0: startInfo.hStdOutput = CreateFileA("NUL:", GENERIC_WRITE, 0, sl@0: &secAtts, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); sl@0: } sl@0: } else { sl@0: DuplicateHandle(hProcess, outputHandle, hProcess, &startInfo.hStdOutput, sl@0: 0, TRUE, DUPLICATE_SAME_ACCESS); sl@0: } sl@0: if (startInfo.hStdOutput == INVALID_HANDLE_VALUE) { sl@0: TclWinConvertError(GetLastError()); sl@0: Tcl_AppendResult(interp, "couldn't duplicate output handle: ", sl@0: Tcl_PosixError(interp), (char *) NULL); sl@0: goto end; sl@0: } sl@0: sl@0: if (errorHandle == INVALID_HANDLE_VALUE) { sl@0: /* sl@0: * If handle was not set, errors should be sent to an infinitely sl@0: * deep sink. sl@0: */ sl@0: sl@0: startInfo.hStdError = CreateFileA("NUL:", GENERIC_WRITE, 0, sl@0: &secAtts, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); sl@0: } else { sl@0: DuplicateHandle(hProcess, errorHandle, hProcess, &startInfo.hStdError, sl@0: 0, TRUE, DUPLICATE_SAME_ACCESS); sl@0: } sl@0: if (startInfo.hStdError == INVALID_HANDLE_VALUE) { sl@0: TclWinConvertError(GetLastError()); sl@0: Tcl_AppendResult(interp, "couldn't duplicate error handle: ", sl@0: Tcl_PosixError(interp), (char *) NULL); sl@0: goto end; sl@0: } sl@0: /* sl@0: * If we do not have a console window, then we must run DOS and sl@0: * WIN32 console mode applications as detached processes. This tells sl@0: * the loader that the child application should not inherit the sl@0: * console, and that it should not create a new console window for sl@0: * the child application. The child application should get its stdio sl@0: * from the redirection handles provided by this application, and run sl@0: * in the background. sl@0: * sl@0: * If we are starting a GUI process, they don't automatically get a sl@0: * console, so it doesn't matter if they are started as foreground or sl@0: * detached processes. The GUI window will still pop up to the sl@0: * foreground. sl@0: */ sl@0: sl@0: if (TclWinGetPlatformId() == VER_PLATFORM_WIN32_NT) { sl@0: if (HasConsole()) { sl@0: createFlags = 0; sl@0: } else if (applType == APPL_DOS) { sl@0: /* sl@0: * Under NT, 16-bit DOS applications will not run unless they sl@0: * can be attached to a console. If we are running without a sl@0: * console, run the 16-bit program as an normal process inside sl@0: * of a hidden console application, and then run that hidden sl@0: * console as a detached process. sl@0: */ sl@0: sl@0: startInfo.wShowWindow = SW_HIDE; sl@0: startInfo.dwFlags |= STARTF_USESHOWWINDOW; sl@0: createFlags = CREATE_NEW_CONSOLE; sl@0: Tcl_DStringAppend(&cmdLine, "cmd.exe /c", -1); sl@0: } else { sl@0: createFlags = DETACHED_PROCESS; sl@0: } sl@0: } else { sl@0: if (HasConsole()) { sl@0: createFlags = 0; sl@0: } else { sl@0: createFlags = DETACHED_PROCESS; sl@0: } sl@0: sl@0: if (applType == APPL_DOS) { sl@0: /* sl@0: * Under Windows 95, 16-bit DOS applications do not work well sl@0: * with pipes: sl@0: * sl@0: * 1. EOF on a pipe between a detached 16-bit DOS application sl@0: * and another application is not seen at the other sl@0: * end of the pipe, so the listening process blocks forever on sl@0: * reads. This inablity to detect EOF happens when either a sl@0: * 16-bit app or the 32-bit app is the listener. sl@0: * sl@0: * 2. If a 16-bit DOS application (detached or not) blocks when sl@0: * writing to a pipe, it will never wake up again, and it sl@0: * eventually brings the whole system down around it. sl@0: * sl@0: * The 16-bit application is run as a normal process inside sl@0: * of a hidden helper console app, and this helper may be run sl@0: * as a detached process. If any of the stdio handles is sl@0: * a pipe, the helper application accumulates information sl@0: * into temp files and forwards it to or from the DOS sl@0: * application as appropriate. This means that DOS apps sl@0: * must receive EOF from a stdin pipe before they will actually sl@0: * begin, and must finish generating stdout or stderr before sl@0: * the data will be sent to the next stage of the pipe. sl@0: * sl@0: * The helper app should be located in the same directory as sl@0: * the tcl dll. sl@0: */ sl@0: sl@0: if (createFlags != 0) { sl@0: startInfo.wShowWindow = SW_HIDE; sl@0: startInfo.dwFlags |= STARTF_USESHOWWINDOW; sl@0: createFlags = CREATE_NEW_CONSOLE; sl@0: } sl@0: sl@0: { sl@0: Tcl_Obj *tclExePtr, *pipeDllPtr; sl@0: int i, fileExists; sl@0: char *start,*end; sl@0: Tcl_DString pipeDll; sl@0: Tcl_DStringInit(&pipeDll); sl@0: Tcl_DStringAppend(&pipeDll, TCL_PIPE_DLL, -1); sl@0: tclExePtr = Tcl_NewStringObj(TclpFindExecutable(""), -1); sl@0: start = Tcl_GetStringFromObj(tclExePtr, &i); sl@0: for (end = start + (i-1); end > start; end--) { sl@0: if (*end == '/') sl@0: break; sl@0: } sl@0: if (*end != '/') sl@0: panic("no / in executable path name"); sl@0: i = (end - start) + 1; sl@0: pipeDllPtr = Tcl_NewStringObj(start, i); sl@0: Tcl_AppendToObj(pipeDllPtr, Tcl_DStringValue(&pipeDll), -1); sl@0: Tcl_IncrRefCount(pipeDllPtr); sl@0: if (Tcl_FSConvertToPathType(interp, pipeDllPtr) != TCL_OK) sl@0: panic("Tcl_FSConvertToPathType failed"); sl@0: fileExists = (Tcl_FSAccess(pipeDllPtr, F_OK) == 0); sl@0: if (!fileExists) { sl@0: panic("Tcl pipe dll \"%s\" not found", sl@0: Tcl_DStringValue(&pipeDll)); sl@0: } sl@0: Tcl_DStringAppend(&cmdLine, Tcl_DStringValue(&pipeDll), -1); sl@0: Tcl_DecrRefCount(tclExePtr); sl@0: Tcl_DecrRefCount(pipeDllPtr); sl@0: Tcl_DStringFree(&pipeDll); sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * cmdLine gets the full command line used to invoke the executable, sl@0: * including the name of the executable itself. The command line sl@0: * arguments in argv[] are stored in cmdLine separated by spaces. sl@0: * Special characters in individual arguments from argv[] must be sl@0: * quoted when being stored in cmdLine. sl@0: * sl@0: * When calling any application, bear in mind that arguments that sl@0: * specify a path name are not converted. If an argument contains sl@0: * forward slashes as path separators, it may or may not be sl@0: * recognized as a path name, depending on the program. In general, sl@0: * most applications accept forward slashes only as option sl@0: * delimiters and backslashes only as paths. sl@0: * sl@0: * Additionally, when calling a 16-bit dos or windows application, sl@0: * all path names must use the short, cryptic, path format (e.g., sl@0: * using ab~1.def instead of "a b.default"). sl@0: */ sl@0: sl@0: BuildCommandLine(execPath, argc, argv, &cmdLine); sl@0: sl@0: if ((*tclWinProcs->createProcessProc)(NULL, sl@0: (TCHAR *) Tcl_DStringValue(&cmdLine), NULL, NULL, TRUE, sl@0: (DWORD) createFlags, NULL, NULL, &startInfo, &procInfo) == 0) { sl@0: TclWinConvertError(GetLastError()); sl@0: Tcl_AppendResult(interp, "couldn't execute \"", argv[0], sl@0: "\": ", Tcl_PosixError(interp), (char *) NULL); sl@0: goto end; sl@0: } sl@0: sl@0: /* sl@0: * This wait is used to force the OS to give some time to the DOS sl@0: * process. sl@0: */ sl@0: sl@0: if (applType == APPL_DOS) { sl@0: WaitForSingleObject(procInfo.hProcess, 50); sl@0: } sl@0: sl@0: /* sl@0: * "When an application spawns a process repeatedly, a new thread sl@0: * instance will be created for each process but the previous sl@0: * instances may not be cleaned up. This results in a significant sl@0: * virtual memory loss each time the process is spawned. If there sl@0: * is a WaitForInputIdle() call between CreateProcess() and sl@0: * CloseHandle(), the problem does not occur." PSS ID Number: Q124121 sl@0: */ sl@0: sl@0: WaitForInputIdle(procInfo.hProcess, 5000); sl@0: CloseHandle(procInfo.hThread); sl@0: sl@0: *pidPtr = (Tcl_Pid) procInfo.hProcess; sl@0: if (*pidPtr != 0) { sl@0: TclWinAddProcess(procInfo.hProcess, procInfo.dwProcessId); sl@0: } sl@0: result = TCL_OK; sl@0: sl@0: end: sl@0: Tcl_DStringFree(&cmdLine); sl@0: if (startInfo.hStdInput != INVALID_HANDLE_VALUE) { sl@0: CloseHandle(startInfo.hStdInput); sl@0: } sl@0: if (startInfo.hStdOutput != INVALID_HANDLE_VALUE) { sl@0: CloseHandle(startInfo.hStdOutput); sl@0: } sl@0: if (startInfo.hStdError != INVALID_HANDLE_VALUE) { sl@0: CloseHandle(startInfo.hStdError); sl@0: } sl@0: return result; sl@0: } sl@0: sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * HasConsole -- sl@0: * sl@0: * Determines whether the current application is attached to a sl@0: * console. sl@0: * sl@0: * Results: sl@0: * Returns TRUE if this application has a console, else FALSE. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static BOOL sl@0: HasConsole() sl@0: { sl@0: HANDLE handle; sl@0: sl@0: handle = CreateFileA("CONOUT$", GENERIC_WRITE, FILE_SHARE_WRITE, sl@0: NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); sl@0: sl@0: if (handle != INVALID_HANDLE_VALUE) { sl@0: CloseHandle(handle); sl@0: return TRUE; sl@0: } else { sl@0: return FALSE; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *-------------------------------------------------------------------- sl@0: * sl@0: * ApplicationType -- sl@0: * sl@0: * Search for the specified program and identify if it refers to a DOS, sl@0: * Windows 3.X, or Win32 program. Used to determine how to invoke sl@0: * a program, or if it can even be invoked. sl@0: * sl@0: * It is possible to almost positively identify DOS and Windows sl@0: * applications that contain the appropriate magic numbers. However, sl@0: * DOS .com files do not seem to contain a magic number; if the program sl@0: * name ends with .com and could not be identified as a Windows .com sl@0: * file, it will be assumed to be a DOS application, even if it was sl@0: * just random data. If the program name does not end with .com, no sl@0: * such assumption is made. sl@0: * sl@0: * The Win32 procedure GetBinaryType incorrectly identifies any sl@0: * junk file that ends with .exe as a dos executable and some sl@0: * executables that don't end with .exe as not executable. Plus it sl@0: * doesn't exist under win95, so I won't feel bad about reimplementing sl@0: * functionality. sl@0: * sl@0: * Results: sl@0: * The return value is one of APPL_DOS, APPL_WIN3X, or APPL_WIN32 sl@0: * if the filename referred to the corresponding application type. sl@0: * If the file name could not be found or did not refer to any known sl@0: * application type, APPL_NONE is returned and an error message is sl@0: * left in interp. .bat files are identified as APPL_DOS. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: ApplicationType(interp, originalName, fullName) sl@0: Tcl_Interp *interp; /* Interp, for error message. */ sl@0: const char *originalName; /* Name of the application to find. */ sl@0: char fullName[]; /* Filled with complete path to sl@0: * application. */ sl@0: { sl@0: int applType, i, nameLen, found; sl@0: HANDLE hFile; sl@0: TCHAR *rest; sl@0: char *ext; sl@0: char buf[2]; sl@0: DWORD attr, read; sl@0: IMAGE_DOS_HEADER header; sl@0: Tcl_DString nameBuf, ds; sl@0: CONST TCHAR *nativeName; sl@0: WCHAR nativeFullPath[MAX_PATH]; sl@0: static char extensions[][5] = {"", ".com", ".exe", ".bat"}; sl@0: sl@0: /* Look for the program as an external program. First try the name sl@0: * as it is, then try adding .com, .exe, and .bat, in that order, to sl@0: * the name, looking for an executable. sl@0: * sl@0: * Using the raw SearchPath() procedure doesn't do quite what is sl@0: * necessary. If the name of the executable already contains a '.' sl@0: * character, it will not try appending the specified extension when sl@0: * searching (in other words, SearchPath will not find the program sl@0: * "a.b.exe" if the arguments specified "a.b" and ".exe"). sl@0: * So, first look for the file as it is named. Then manually append sl@0: * the extensions, looking for a match. sl@0: */ sl@0: sl@0: applType = APPL_NONE; sl@0: Tcl_DStringInit(&nameBuf); sl@0: Tcl_DStringAppend(&nameBuf, originalName, -1); sl@0: nameLen = Tcl_DStringLength(&nameBuf); sl@0: sl@0: for (i = 0; i < (int) (sizeof(extensions) / sizeof(extensions[0])); i++) { sl@0: Tcl_DStringSetLength(&nameBuf, nameLen); sl@0: Tcl_DStringAppend(&nameBuf, extensions[i], -1); sl@0: nativeName = Tcl_WinUtfToTChar(Tcl_DStringValue(&nameBuf), sl@0: Tcl_DStringLength(&nameBuf), &ds); sl@0: found = (*tclWinProcs->searchPathProc)(NULL, nativeName, NULL, sl@0: MAX_PATH, nativeFullPath, &rest); sl@0: Tcl_DStringFree(&ds); sl@0: if (found == 0) { sl@0: continue; sl@0: } sl@0: sl@0: /* sl@0: * Ignore matches on directories or data files, return if identified sl@0: * a known type. sl@0: */ sl@0: sl@0: attr = (*tclWinProcs->getFileAttributesProc)((TCHAR *) nativeFullPath); sl@0: if ((attr == 0xffffffff) || (attr & FILE_ATTRIBUTE_DIRECTORY)) { sl@0: continue; sl@0: } sl@0: strcpy(fullName, Tcl_WinTCharToUtf((TCHAR *) nativeFullPath, -1, &ds)); sl@0: Tcl_DStringFree(&ds); sl@0: sl@0: ext = strrchr(fullName, '.'); sl@0: if ((ext != NULL) && (stricmp(ext, ".bat") == 0)) { sl@0: applType = APPL_DOS; sl@0: break; sl@0: } sl@0: sl@0: hFile = (*tclWinProcs->createFileProc)((TCHAR *) nativeFullPath, sl@0: GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, sl@0: FILE_ATTRIBUTE_NORMAL, NULL); sl@0: if (hFile == INVALID_HANDLE_VALUE) { sl@0: continue; sl@0: } sl@0: sl@0: header.e_magic = 0; sl@0: ReadFile(hFile, (void *) &header, sizeof(header), &read, NULL); sl@0: if (header.e_magic != IMAGE_DOS_SIGNATURE) { sl@0: /* sl@0: * Doesn't have the magic number for relocatable executables. If sl@0: * filename ends with .com, assume it's a DOS application anyhow. sl@0: * Note that we didn't make this assumption at first, because some sl@0: * supposed .com files are really 32-bit executables with all the sl@0: * magic numbers and everything. sl@0: */ sl@0: sl@0: CloseHandle(hFile); sl@0: if ((ext != NULL) && (stricmp(ext, ".com") == 0)) { sl@0: applType = APPL_DOS; sl@0: break; sl@0: } sl@0: continue; sl@0: } sl@0: if (header.e_lfarlc != sizeof(header)) { sl@0: /* sl@0: * All Windows 3.X and Win32 and some DOS programs have this value sl@0: * set here. If it doesn't, assume that since it already had the sl@0: * other magic number it was a DOS application. sl@0: */ sl@0: sl@0: CloseHandle(hFile); sl@0: applType = APPL_DOS; sl@0: break; sl@0: } sl@0: sl@0: /* sl@0: * The DWORD at header.e_lfanew points to yet another magic number. sl@0: */ sl@0: sl@0: buf[0] = '\0'; sl@0: SetFilePointer(hFile, header.e_lfanew, NULL, FILE_BEGIN); sl@0: ReadFile(hFile, (void *) buf, 2, &read, NULL); sl@0: CloseHandle(hFile); sl@0: sl@0: if ((buf[0] == 'N') && (buf[1] == 'E')) { sl@0: applType = APPL_WIN3X; sl@0: } else if ((buf[0] == 'P') && (buf[1] == 'E')) { sl@0: applType = APPL_WIN32; sl@0: } else { sl@0: /* sl@0: * Strictly speaking, there should be a test that there sl@0: * is an 'L' and 'E' at buf[0..1], to identify the type as sl@0: * DOS, but of course we ran into a DOS executable that sl@0: * _doesn't_ have the magic number -- specifically, one sl@0: * compiled using the Lahey Fortran90 compiler. sl@0: */ sl@0: sl@0: applType = APPL_DOS; sl@0: } sl@0: break; sl@0: } sl@0: Tcl_DStringFree(&nameBuf); sl@0: sl@0: if (applType == APPL_NONE) { sl@0: TclWinConvertError(GetLastError()); sl@0: Tcl_AppendResult(interp, "couldn't execute \"", originalName, sl@0: "\": ", Tcl_PosixError(interp), (char *) NULL); sl@0: return APPL_NONE; sl@0: } sl@0: sl@0: if ((applType == APPL_DOS) || (applType == APPL_WIN3X)) { sl@0: /* sl@0: * Replace long path name of executable with short path name for sl@0: * 16-bit applications. Otherwise the application may not be able sl@0: * to correctly parse its own command line to separate off the sl@0: * application name from the arguments. sl@0: */ sl@0: sl@0: (*tclWinProcs->getShortPathNameProc)((TCHAR *) nativeFullPath, sl@0: nativeFullPath, MAX_PATH); sl@0: strcpy(fullName, Tcl_WinTCharToUtf((TCHAR *) nativeFullPath, -1, &ds)); sl@0: Tcl_DStringFree(&ds); sl@0: } sl@0: return applType; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * BuildCommandLine -- sl@0: * sl@0: * The command line arguments are stored in linePtr separated sl@0: * by spaces, in a form that CreateProcess() understands. Special sl@0: * characters in individual arguments from argv[] must be quoted sl@0: * when being stored in cmdLine. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: BuildCommandLine( sl@0: CONST char *executable, /* Full path of executable (including sl@0: * extension). Replacement for argv[0]. */ sl@0: int argc, /* Number of arguments. */ sl@0: CONST char **argv, /* Argument strings in UTF. */ sl@0: Tcl_DString *linePtr) /* Initialized Tcl_DString that receives the sl@0: * command line (TCHAR). */ sl@0: { sl@0: CONST char *arg, *start, *special; sl@0: int quote, i; sl@0: Tcl_DString ds; sl@0: sl@0: Tcl_DStringInit(&ds); sl@0: sl@0: /* sl@0: * Prime the path. Add a space separator if we were primed with sl@0: * something. sl@0: */ sl@0: sl@0: Tcl_DStringAppend(&ds, Tcl_DStringValue(linePtr), -1); sl@0: if (Tcl_DStringLength(&ds) > 0) Tcl_DStringAppend(&ds, " ", 1); sl@0: sl@0: for (i = 0; i < argc; i++) { sl@0: if (i == 0) { sl@0: arg = executable; sl@0: } else { sl@0: arg = argv[i]; sl@0: Tcl_DStringAppend(&ds, " ", 1); sl@0: } sl@0: sl@0: quote = 0; sl@0: if (arg[0] == '\0') { sl@0: quote = 1; sl@0: } else { sl@0: int count; sl@0: Tcl_UniChar ch; sl@0: for (start = arg; *start != '\0'; start += count) { sl@0: count = Tcl_UtfToUniChar(start, &ch); sl@0: if (Tcl_UniCharIsSpace(ch)) { /* INTL: ISO space. */ sl@0: quote = 1; sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: if (quote) { sl@0: Tcl_DStringAppend(&ds, "\"", 1); sl@0: } sl@0: start = arg; sl@0: for (special = arg; ; ) { sl@0: if ((*special == '\\') && sl@0: (special[1] == '\\' || special[1] == '"' || (quote && special[1] == '\0'))) { sl@0: Tcl_DStringAppend(&ds, start, (int) (special - start)); sl@0: start = special; sl@0: while (1) { sl@0: special++; sl@0: if (*special == '"' || (quote && *special == '\0')) { sl@0: /* sl@0: * N backslashes followed a quote -> insert sl@0: * N * 2 + 1 backslashes then a quote. sl@0: */ sl@0: sl@0: Tcl_DStringAppend(&ds, start, sl@0: (int) (special - start)); sl@0: break; sl@0: } sl@0: if (*special != '\\') { sl@0: break; sl@0: } sl@0: } sl@0: Tcl_DStringAppend(&ds, start, (int) (special - start)); sl@0: start = special; sl@0: } sl@0: if (*special == '"') { sl@0: Tcl_DStringAppend(&ds, start, (int) (special - start)); sl@0: Tcl_DStringAppend(&ds, "\\\"", 2); sl@0: start = special + 1; sl@0: } sl@0: if (*special == '\0') { sl@0: break; sl@0: } sl@0: special++; sl@0: } sl@0: Tcl_DStringAppend(&ds, start, (int) (special - start)); sl@0: if (quote) { sl@0: Tcl_DStringAppend(&ds, "\"", 1); sl@0: } sl@0: } sl@0: Tcl_DStringFree(linePtr); sl@0: Tcl_WinUtfToTChar(Tcl_DStringValue(&ds), Tcl_DStringLength(&ds), linePtr); sl@0: Tcl_DStringFree(&ds); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclpCreateCommandChannel -- sl@0: * sl@0: * This function is called by Tcl_OpenCommandChannel to perform sl@0: * the platform specific channel initialization for a command sl@0: * channel. sl@0: * sl@0: * Results: sl@0: * Returns a new channel or NULL on failure. sl@0: * sl@0: * Side effects: sl@0: * Allocates a new channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: Tcl_Channel sl@0: TclpCreateCommandChannel( sl@0: TclFile readFile, /* If non-null, gives the file for reading. */ sl@0: TclFile writeFile, /* If non-null, gives the file for writing. */ sl@0: TclFile errorFile, /* If non-null, gives the file where errors sl@0: * can be read. */ sl@0: int numPids, /* The number of pids in the pid array. */ sl@0: Tcl_Pid *pidPtr) /* An array of process identifiers. */ sl@0: { sl@0: char channelName[16 + TCL_INTEGER_SPACE]; sl@0: int channelId; sl@0: DWORD id; sl@0: PipeInfo *infoPtr = (PipeInfo *) ckalloc((unsigned) sizeof(PipeInfo)); sl@0: sl@0: PipeInit(); sl@0: sl@0: infoPtr->watchMask = 0; sl@0: infoPtr->flags = 0; sl@0: infoPtr->readFlags = 0; sl@0: infoPtr->readFile = readFile; sl@0: infoPtr->writeFile = writeFile; sl@0: infoPtr->errorFile = errorFile; sl@0: infoPtr->numPids = numPids; sl@0: infoPtr->pidPtr = pidPtr; sl@0: infoPtr->writeBuf = 0; sl@0: infoPtr->writeBufLen = 0; sl@0: infoPtr->writeError = 0; sl@0: infoPtr->channel = (Tcl_Channel) NULL; sl@0: sl@0: /* sl@0: * Use one of the fds associated with the channel as the sl@0: * channel id. sl@0: */ sl@0: sl@0: if (readFile) { sl@0: channelId = (int) ((WinFile*)readFile)->handle; sl@0: } else if (writeFile) { sl@0: channelId = (int) ((WinFile*)writeFile)->handle; sl@0: } else if (errorFile) { sl@0: channelId = (int) ((WinFile*)errorFile)->handle; sl@0: } else { sl@0: channelId = 0; sl@0: } sl@0: sl@0: infoPtr->validMask = 0; sl@0: sl@0: infoPtr->threadId = Tcl_GetCurrentThread(); sl@0: sl@0: if (readFile != NULL) { sl@0: /* sl@0: * Start the background reader thread. sl@0: */ sl@0: sl@0: infoPtr->readable = CreateEvent(NULL, TRUE, TRUE, NULL); sl@0: infoPtr->startReader = CreateEvent(NULL, FALSE, FALSE, NULL); sl@0: infoPtr->stopReader = CreateEvent(NULL, TRUE, FALSE, NULL); sl@0: infoPtr->readThread = CreateThread(NULL, 256, PipeReaderThread, sl@0: infoPtr, 0, &id); sl@0: SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); sl@0: infoPtr->validMask |= TCL_READABLE; sl@0: } else { sl@0: infoPtr->readThread = 0; sl@0: } sl@0: if (writeFile != NULL) { sl@0: /* sl@0: * Start the background writer thread. sl@0: */ sl@0: sl@0: infoPtr->writable = CreateEvent(NULL, TRUE, TRUE, NULL); sl@0: infoPtr->startWriter = CreateEvent(NULL, FALSE, FALSE, NULL); sl@0: infoPtr->stopWriter = CreateEvent(NULL, TRUE, FALSE, NULL); sl@0: infoPtr->writeThread = CreateThread(NULL, 256, PipeWriterThread, sl@0: infoPtr, 0, &id); sl@0: SetThreadPriority(infoPtr->readThread, THREAD_PRIORITY_HIGHEST); sl@0: infoPtr->validMask |= TCL_WRITABLE; sl@0: } sl@0: sl@0: /* sl@0: * For backward compatibility with previous versions of Tcl, we sl@0: * use "file%d" as the base name for pipes even though it would sl@0: * be more natural to use "pipe%d". sl@0: * Use the pointer to keep the channel names unique, in case sl@0: * channels share handles (stdin/stdout). sl@0: */ sl@0: sl@0: wsprintfA(channelName, "file%lx", infoPtr); sl@0: infoPtr->channel = Tcl_CreateChannel(&pipeChannelType, channelName, sl@0: (ClientData) infoPtr, infoPtr->validMask); sl@0: sl@0: /* sl@0: * Pipes have AUTO translation mode on Windows and ^Z eof char, which sl@0: * means that a ^Z will be appended to them at close. This is needed sl@0: * for Windows programs that expect a ^Z at EOF. sl@0: */ sl@0: sl@0: Tcl_SetChannelOption((Tcl_Interp *) NULL, infoPtr->channel, sl@0: "-translation", "auto"); sl@0: Tcl_SetChannelOption((Tcl_Interp *) NULL, infoPtr->channel, sl@0: "-eofchar", "\032 {}"); sl@0: return infoPtr->channel; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclGetAndDetachPids -- sl@0: * sl@0: * Stores a list of the command PIDs for a command channel in sl@0: * the interp's result. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Modifies the interp's result. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: TclGetAndDetachPids( sl@0: Tcl_Interp *interp, sl@0: Tcl_Channel chan) sl@0: { sl@0: PipeInfo *pipePtr; sl@0: Tcl_ChannelType *chanTypePtr; sl@0: int i; sl@0: char buf[TCL_INTEGER_SPACE]; sl@0: sl@0: /* sl@0: * Punt if the channel is not a command channel. sl@0: */ sl@0: sl@0: chanTypePtr = Tcl_GetChannelType(chan); sl@0: if (chanTypePtr != &pipeChannelType) { sl@0: return; sl@0: } sl@0: sl@0: pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan); sl@0: for (i = 0; i < pipePtr->numPids; i++) { sl@0: wsprintfA(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i])); sl@0: Tcl_AppendElement(interp, buf); sl@0: Tcl_DetachPids(1, &(pipePtr->pidPtr[i])); sl@0: } sl@0: if (pipePtr->numPids > 0) { sl@0: ckfree((char *) pipePtr->pidPtr); sl@0: pipePtr->numPids = 0; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeBlockModeProc -- sl@0: * sl@0: * Set blocking or non-blocking mode on channel. sl@0: * sl@0: * Results: sl@0: * 0 if successful, errno when failed. sl@0: * sl@0: * Side effects: sl@0: * Sets the device into blocking or non-blocking mode. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: PipeBlockModeProc( sl@0: ClientData instanceData, /* Instance data for channel. */ sl@0: int mode) /* TCL_MODE_BLOCKING or sl@0: * TCL_MODE_NONBLOCKING. */ sl@0: { sl@0: PipeInfo *infoPtr = (PipeInfo *) instanceData; sl@0: sl@0: /* sl@0: * Pipes on Windows can not be switched between blocking and nonblocking, sl@0: * hence we have to emulate the behavior. This is done in the input sl@0: * function by checking against a bit in the state. We set or unset the sl@0: * bit here to cause the input function to emulate the correct behavior. sl@0: */ sl@0: sl@0: if (mode == TCL_MODE_NONBLOCKING) { sl@0: infoPtr->flags |= PIPE_ASYNC; sl@0: } else { sl@0: infoPtr->flags &= ~(PIPE_ASYNC); sl@0: } sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeClose2Proc -- sl@0: * sl@0: * Closes a pipe based IO channel. sl@0: * sl@0: * Results: sl@0: * 0 on success, errno otherwise. sl@0: * sl@0: * Side effects: sl@0: * Closes the physical channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: PipeClose2Proc( sl@0: ClientData instanceData, /* Pointer to PipeInfo structure. */ sl@0: Tcl_Interp *interp, /* For error reporting. */ sl@0: int flags) /* Flags that indicate which side to close. */ sl@0: { sl@0: PipeInfo *pipePtr = (PipeInfo *) instanceData; sl@0: Tcl_Channel errChan; sl@0: int errorCode, result; sl@0: PipeInfo *infoPtr, **nextPtrPtr; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: DWORD exitCode; sl@0: sl@0: errorCode = 0; sl@0: if ((!flags || (flags == TCL_CLOSE_READ)) sl@0: && (pipePtr->readFile != NULL)) { sl@0: /* sl@0: * Clean up the background thread if necessary. Note that this sl@0: * must be done before we can close the file, since the sl@0: * thread may be blocking trying to read from the pipe. sl@0: */ sl@0: sl@0: if (pipePtr->readThread) { sl@0: /* sl@0: * The thread may already have closed on it's own. Check it's sl@0: * exit code. sl@0: */ sl@0: sl@0: GetExitCodeThread(pipePtr->readThread, &exitCode); sl@0: sl@0: if (exitCode == STILL_ACTIVE) { sl@0: /* sl@0: * Set the stop event so that if the reader thread is blocked sl@0: * in PipeReaderThread on WaitForMultipleEvents, it will exit sl@0: * cleanly. sl@0: */ sl@0: sl@0: SetEvent(pipePtr->stopReader); sl@0: sl@0: /* sl@0: * Wait at most 20 milliseconds for the reader thread to close. sl@0: */ sl@0: sl@0: if (WaitForSingleObject(pipePtr->readThread, 20) sl@0: == WAIT_TIMEOUT) { sl@0: /* sl@0: * The thread must be blocked waiting for the pipe to sl@0: * become readable in ReadFile(). There isn't a clean way sl@0: * to exit the thread from this condition. We should sl@0: * terminate the child process instead to get the reader sl@0: * thread to fall out of ReadFile with a FALSE. (below) is sl@0: * not the correct way to do this, but will stay here until sl@0: * a better solution is found. sl@0: * sl@0: * Note that we need to guard against terminating the sl@0: * thread while it is in the middle of Tcl_ThreadAlert sl@0: * because it won't be able to release the notifier lock. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&pipeMutex); sl@0: sl@0: /* BUG: this leaks memory */ sl@0: TerminateThread(pipePtr->readThread, 0); sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: } sl@0: } sl@0: sl@0: CloseHandle(pipePtr->readThread); sl@0: CloseHandle(pipePtr->readable); sl@0: CloseHandle(pipePtr->startReader); sl@0: CloseHandle(pipePtr->stopReader); sl@0: pipePtr->readThread = NULL; sl@0: } sl@0: if (TclpCloseFile(pipePtr->readFile) != 0) { sl@0: errorCode = errno; sl@0: } sl@0: pipePtr->validMask &= ~TCL_READABLE; sl@0: pipePtr->readFile = NULL; sl@0: } sl@0: if ((!flags || (flags & TCL_CLOSE_WRITE)) sl@0: && (pipePtr->writeFile != NULL)) { sl@0: sl@0: if (pipePtr->writeThread) { sl@0: /* sl@0: * Wait for the writer thread to finish the current buffer, sl@0: * then terminate the thread and close the handles. If the sl@0: * channel is nonblocking, there should be no pending write sl@0: * operations. sl@0: */ sl@0: sl@0: WaitForSingleObject(pipePtr->writable, INFINITE); sl@0: sl@0: /* sl@0: * The thread may already have closed on it's own. Check it's sl@0: * exit code. sl@0: */ sl@0: sl@0: GetExitCodeThread(pipePtr->writeThread, &exitCode); sl@0: sl@0: if (exitCode == STILL_ACTIVE) { sl@0: /* sl@0: * Set the stop event so that if the reader thread is blocked sl@0: * in PipeReaderThread on WaitForMultipleEvents, it will exit sl@0: * cleanly. sl@0: */ sl@0: sl@0: SetEvent(pipePtr->stopWriter); sl@0: sl@0: /* sl@0: * Wait at most 20 milliseconds for the reader thread to close. sl@0: */ sl@0: sl@0: if (WaitForSingleObject(pipePtr->writeThread, 20) sl@0: == WAIT_TIMEOUT) { sl@0: /* sl@0: * The thread must be blocked waiting for the pipe to sl@0: * consume input in WriteFile(). There isn't a clean way sl@0: * to exit the thread from this condition. We should sl@0: * terminate the child process instead to get the writer sl@0: * thread to fall out of WriteFile with a FALSE. (below) is sl@0: * not the correct way to do this, but will stay here until sl@0: * a better solution is found. sl@0: * sl@0: * Note that we need to guard against terminating the sl@0: * thread while it is in the middle of Tcl_ThreadAlert sl@0: * because it won't be able to release the notifier lock. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&pipeMutex); sl@0: sl@0: /* BUG: this leaks memory */ sl@0: TerminateThread(pipePtr->writeThread, 0); sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: } sl@0: } sl@0: sl@0: CloseHandle(pipePtr->writeThread); sl@0: CloseHandle(pipePtr->writable); sl@0: CloseHandle(pipePtr->startWriter); sl@0: CloseHandle(pipePtr->stopWriter); sl@0: pipePtr->writeThread = NULL; sl@0: } sl@0: if (TclpCloseFile(pipePtr->writeFile) != 0) { sl@0: if (errorCode == 0) { sl@0: errorCode = errno; sl@0: } sl@0: } sl@0: pipePtr->validMask &= ~TCL_WRITABLE; sl@0: pipePtr->writeFile = NULL; sl@0: } sl@0: sl@0: pipePtr->watchMask &= pipePtr->validMask; sl@0: sl@0: /* sl@0: * Don't free the channel if any of the flags were set. sl@0: */ sl@0: sl@0: if (flags) { sl@0: return errorCode; sl@0: } sl@0: sl@0: /* sl@0: * Remove the file from the list of watched files. sl@0: */ sl@0: sl@0: for (nextPtrPtr = &(tsdPtr->firstPipePtr), infoPtr = *nextPtrPtr; sl@0: infoPtr != NULL; sl@0: nextPtrPtr = &infoPtr->nextPtr, infoPtr = *nextPtrPtr) { sl@0: if (infoPtr == (PipeInfo *)pipePtr) { sl@0: *nextPtrPtr = infoPtr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: sl@0: if ((pipePtr->flags & PIPE_ASYNC) || TclInExit()) { sl@0: /* sl@0: * If the channel is non-blocking or Tcl is being cleaned up, sl@0: * just detach the children PIDs, reap them (important if we are sl@0: * in a dynamic load module), and discard the errorFile. sl@0: */ sl@0: sl@0: Tcl_DetachPids(pipePtr->numPids, pipePtr->pidPtr); sl@0: Tcl_ReapDetachedProcs(); sl@0: sl@0: if (pipePtr->errorFile) { sl@0: if (TclpCloseFile(pipePtr->errorFile) != 0) { sl@0: if ( errorCode == 0 ) { sl@0: errorCode = errno; sl@0: } sl@0: } sl@0: } sl@0: result = 0; sl@0: } else { sl@0: /* sl@0: * Wrap the error file into a channel and give it to the cleanup sl@0: * routine. sl@0: */ sl@0: sl@0: if (pipePtr->errorFile) { sl@0: WinFile *filePtr; sl@0: sl@0: filePtr = (WinFile*)pipePtr->errorFile; sl@0: errChan = Tcl_MakeFileChannel((ClientData) filePtr->handle, sl@0: TCL_READABLE); sl@0: ckfree((char *) filePtr); sl@0: } else { sl@0: errChan = NULL; sl@0: } sl@0: sl@0: result = TclCleanupChildren(interp, pipePtr->numPids, sl@0: pipePtr->pidPtr, errChan); sl@0: } sl@0: sl@0: if (pipePtr->numPids > 0) { sl@0: ckfree((char *) pipePtr->pidPtr); sl@0: } sl@0: sl@0: if (pipePtr->writeBuf != NULL) { sl@0: ckfree(pipePtr->writeBuf); sl@0: } sl@0: sl@0: ckfree((char*) pipePtr); sl@0: sl@0: if (errorCode == 0) { sl@0: return result; sl@0: } sl@0: return errorCode; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeInputProc -- sl@0: * sl@0: * Reads input from the IO channel into the buffer given. Returns sl@0: * count of how many bytes were actually read, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many bytes were read is returned and an error sl@0: * indication is returned in an output argument. sl@0: * sl@0: * Side effects: sl@0: * Reads input from the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: PipeInputProc( sl@0: ClientData instanceData, /* Pipe state. */ sl@0: char *buf, /* Where to store data read. */ sl@0: int bufSize, /* How much space is available sl@0: * in the buffer? */ sl@0: int *errorCode) /* Where to store error code. */ sl@0: { sl@0: PipeInfo *infoPtr = (PipeInfo *) instanceData; sl@0: WinFile *filePtr = (WinFile*) infoPtr->readFile; sl@0: DWORD count, bytesRead = 0; sl@0: int result; sl@0: sl@0: *errorCode = 0; sl@0: /* sl@0: * Synchronize with the reader thread. sl@0: */ sl@0: sl@0: result = WaitForRead(infoPtr, (infoPtr->flags & PIPE_ASYNC) ? 0 : 1); sl@0: sl@0: /* sl@0: * If an error occurred, return immediately. sl@0: */ sl@0: sl@0: if (result == -1) { sl@0: *errorCode = errno; sl@0: return -1; sl@0: } sl@0: sl@0: if (infoPtr->readFlags & PIPE_EXTRABYTE) { sl@0: /* sl@0: * The reader thread consumed 1 byte as a side effect of sl@0: * waiting so we need to move it into the buffer. sl@0: */ sl@0: sl@0: *buf = infoPtr->extraByte; sl@0: infoPtr->readFlags &= ~PIPE_EXTRABYTE; sl@0: buf++; sl@0: bufSize--; sl@0: bytesRead = 1; sl@0: sl@0: /* sl@0: * If further read attempts would block, return what we have. sl@0: */ sl@0: sl@0: if (result == 0) { sl@0: return bytesRead; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Attempt to read bufSize bytes. The read will return immediately sl@0: * if there is any data available. Otherwise it will block until sl@0: * at least one byte is available or an EOF occurs. sl@0: */ sl@0: sl@0: if (ReadFile(filePtr->handle, (LPVOID) buf, (DWORD) bufSize, &count, sl@0: (LPOVERLAPPED) NULL) == TRUE) { sl@0: return bytesRead + count; sl@0: } else if (bytesRead) { sl@0: /* sl@0: * Ignore errors if we have data to return. sl@0: */ sl@0: sl@0: return bytesRead; sl@0: } sl@0: sl@0: TclWinConvertError(GetLastError()); sl@0: if (errno == EPIPE) { sl@0: infoPtr->readFlags |= PIPE_EOF; sl@0: return 0; sl@0: } sl@0: *errorCode = errno; sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeOutputProc -- sl@0: * sl@0: * Writes the given output on the IO channel. Returns count of how sl@0: * many characters were actually written, and an error indication. sl@0: * sl@0: * Results: sl@0: * A count of how many characters were written is returned and an sl@0: * error indication is returned in an output argument. sl@0: * sl@0: * Side effects: sl@0: * Writes output on the actual channel. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: PipeOutputProc( sl@0: ClientData instanceData, /* Pipe state. */ sl@0: CONST char *buf, /* The data buffer. */ sl@0: int toWrite, /* How many bytes to write? */ sl@0: int *errorCode) /* Where to store error code. */ sl@0: { sl@0: PipeInfo *infoPtr = (PipeInfo *) instanceData; sl@0: WinFile *filePtr = (WinFile*) infoPtr->writeFile; sl@0: DWORD bytesWritten, timeout; sl@0: sl@0: *errorCode = 0; sl@0: timeout = (infoPtr->flags & PIPE_ASYNC) ? 0 : INFINITE; sl@0: if (WaitForSingleObject(infoPtr->writable, timeout) == WAIT_TIMEOUT) { sl@0: /* sl@0: * The writer thread is blocked waiting for a write to complete sl@0: * and the channel is in non-blocking mode. sl@0: */ sl@0: sl@0: errno = EAGAIN; sl@0: goto error; sl@0: } sl@0: sl@0: /* sl@0: * Check for a background error on the last write. sl@0: */ sl@0: sl@0: if (infoPtr->writeError) { sl@0: TclWinConvertError(infoPtr->writeError); sl@0: infoPtr->writeError = 0; sl@0: goto error; sl@0: } sl@0: sl@0: if (infoPtr->flags & PIPE_ASYNC) { sl@0: /* sl@0: * The pipe is non-blocking, so copy the data into the output sl@0: * buffer and restart the writer thread. sl@0: */ sl@0: sl@0: if (toWrite > infoPtr->writeBufLen) { sl@0: /* sl@0: * Reallocate the buffer to be large enough to hold the data. sl@0: */ sl@0: sl@0: if (infoPtr->writeBuf) { sl@0: ckfree(infoPtr->writeBuf); sl@0: } sl@0: infoPtr->writeBufLen = toWrite; sl@0: infoPtr->writeBuf = ckalloc((unsigned int) toWrite); sl@0: } sl@0: memcpy(infoPtr->writeBuf, buf, (size_t) toWrite); sl@0: infoPtr->toWrite = toWrite; sl@0: ResetEvent(infoPtr->writable); sl@0: SetEvent(infoPtr->startWriter); sl@0: bytesWritten = toWrite; sl@0: } else { sl@0: /* sl@0: * In the blocking case, just try to write the buffer directly. sl@0: * This avoids an unnecessary copy. sl@0: */ sl@0: sl@0: if (WriteFile(filePtr->handle, (LPVOID) buf, (DWORD) toWrite, sl@0: &bytesWritten, (LPOVERLAPPED) NULL) == FALSE) { sl@0: TclWinConvertError(GetLastError()); sl@0: goto error; sl@0: } sl@0: } sl@0: return bytesWritten; sl@0: sl@0: error: sl@0: *errorCode = errno; sl@0: return -1; sl@0: sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeEventProc -- sl@0: * sl@0: * This function is invoked by Tcl_ServiceEvent when a file event sl@0: * reaches the front of the event queue. This procedure invokes sl@0: * Tcl_NotifyChannel on the pipe. sl@0: * sl@0: * Results: sl@0: * Returns 1 if the event was handled, meaning it should be removed sl@0: * from the queue. Returns 0 if the event was not handled, meaning sl@0: * it should stay on the queue. The only time the event isn't sl@0: * handled is if the TCL_FILE_EVENTS flag bit isn't set. sl@0: * sl@0: * Side effects: sl@0: * Whatever the notifier callback does. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: PipeEventProc( sl@0: Tcl_Event *evPtr, /* Event to service. */ sl@0: int flags) /* Flags that indicate what events to sl@0: * handle, such as TCL_FILE_EVENTS. */ sl@0: { sl@0: PipeEvent *pipeEvPtr = (PipeEvent *)evPtr; sl@0: PipeInfo *infoPtr; sl@0: WinFile *filePtr; sl@0: int mask; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: if (!(flags & TCL_FILE_EVENTS)) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Search through the list of watched pipes for the one whose handle sl@0: * matches the event. We do this rather than simply dereferencing sl@0: * the handle in the event so that pipes can be deleted while the sl@0: * event is in the queue. sl@0: */ sl@0: sl@0: for (infoPtr = tsdPtr->firstPipePtr; infoPtr != NULL; sl@0: infoPtr = infoPtr->nextPtr) { sl@0: if (pipeEvPtr->infoPtr == infoPtr) { sl@0: infoPtr->flags &= ~(PIPE_PENDING); sl@0: break; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Remove stale events. sl@0: */ sl@0: sl@0: if (!infoPtr) { sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * Check to see if the pipe is readable. Note sl@0: * that we can't tell if a pipe is writable, so we always report it sl@0: * as being writable unless we have detected EOF. sl@0: */ sl@0: sl@0: filePtr = (WinFile*) ((PipeInfo*)infoPtr)->writeFile; sl@0: mask = 0; sl@0: if ((infoPtr->watchMask & TCL_WRITABLE) && sl@0: (WaitForSingleObject(infoPtr->writable, 0) != WAIT_TIMEOUT)) { sl@0: mask = TCL_WRITABLE; sl@0: } sl@0: sl@0: filePtr = (WinFile*) ((PipeInfo*)infoPtr)->readFile; sl@0: if ((infoPtr->watchMask & TCL_READABLE) && sl@0: (WaitForRead(infoPtr, 0) >= 0)) { sl@0: if (infoPtr->readFlags & PIPE_EOF) { sl@0: mask = TCL_READABLE; sl@0: } else { sl@0: mask |= TCL_READABLE; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Inform the channel of the events. sl@0: */ sl@0: sl@0: Tcl_NotifyChannel(infoPtr->channel, infoPtr->watchMask & mask); sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeWatchProc -- sl@0: * sl@0: * Called by the notifier to set up to watch for events on this sl@0: * channel. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: PipeWatchProc( sl@0: ClientData instanceData, /* Pipe state. */ sl@0: int mask) /* What events to watch for, OR-ed sl@0: * combination of TCL_READABLE, sl@0: * TCL_WRITABLE and TCL_EXCEPTION. */ sl@0: { sl@0: PipeInfo **nextPtrPtr, *ptr; sl@0: PipeInfo *infoPtr = (PipeInfo *) instanceData; sl@0: int oldMask = infoPtr->watchMask; sl@0: ThreadSpecificData *tsdPtr = TCL_TSD_INIT(&dataKey); sl@0: sl@0: /* sl@0: * Since most of the work is handled by the background threads, sl@0: * we just need to update the watchMask and then force the notifier sl@0: * to poll once. sl@0: */ sl@0: sl@0: infoPtr->watchMask = mask & infoPtr->validMask; sl@0: if (infoPtr->watchMask) { sl@0: Tcl_Time blockTime = { 0, 0 }; sl@0: if (!oldMask) { sl@0: infoPtr->nextPtr = tsdPtr->firstPipePtr; sl@0: tsdPtr->firstPipePtr = infoPtr; sl@0: } sl@0: Tcl_SetMaxBlockTime(&blockTime); sl@0: } else { sl@0: if (oldMask) { sl@0: /* sl@0: * Remove the pipe from the list of watched pipes. sl@0: */ sl@0: sl@0: for (nextPtrPtr = &(tsdPtr->firstPipePtr), ptr = *nextPtrPtr; sl@0: ptr != NULL; sl@0: nextPtrPtr = &ptr->nextPtr, ptr = *nextPtrPtr) { sl@0: if (infoPtr == ptr) { sl@0: *nextPtrPtr = ptr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeGetHandleProc -- sl@0: * sl@0: * Called from Tcl_GetChannelHandle to retrieve OS handles from sl@0: * inside a command pipeline based channel. sl@0: * sl@0: * Results: sl@0: * Returns TCL_OK with the fd in handlePtr, or TCL_ERROR if sl@0: * there is no handle for the specified direction. sl@0: * sl@0: * Side effects: sl@0: * None. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: PipeGetHandleProc( sl@0: ClientData instanceData, /* The pipe state. */ sl@0: int direction, /* TCL_READABLE or TCL_WRITABLE */ sl@0: ClientData *handlePtr) /* Where to store the handle. */ sl@0: { sl@0: PipeInfo *infoPtr = (PipeInfo *) instanceData; sl@0: WinFile *filePtr; sl@0: sl@0: if (direction == TCL_READABLE && infoPtr->readFile) { sl@0: filePtr = (WinFile*) infoPtr->readFile; sl@0: *handlePtr = (ClientData) filePtr->handle; sl@0: return TCL_OK; sl@0: } sl@0: if (direction == TCL_WRITABLE && infoPtr->writeFile) { sl@0: filePtr = (WinFile*) infoPtr->writeFile; sl@0: *handlePtr = (ClientData) filePtr->handle; sl@0: return TCL_OK; sl@0: } sl@0: return TCL_ERROR; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_WaitPid -- sl@0: * sl@0: * Emulates the waitpid system call. sl@0: * sl@0: * Results: sl@0: * Returns 0 if the process is still alive, -1 on an error, or sl@0: * the pid on a clean close. sl@0: * sl@0: * Side effects: sl@0: * Unless WNOHANG is set and the wait times out, the process sl@0: * information record will be deleted and the process handle sl@0: * will be closed. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: Tcl_Pid sl@0: Tcl_WaitPid( sl@0: Tcl_Pid pid, sl@0: int *statPtr, sl@0: int options) sl@0: { sl@0: ProcInfo *infoPtr = NULL, **prevPtrPtr; sl@0: DWORD flags; sl@0: Tcl_Pid result; sl@0: DWORD ret, exitCode; sl@0: sl@0: PipeInit(); sl@0: sl@0: /* sl@0: * If no pid is specified, do nothing. sl@0: */ sl@0: sl@0: if (pid == 0) { sl@0: *statPtr = 0; sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Find the process and cut it from the process list. sl@0: * SF Tcl Bug 859820, Backport of its fix. sl@0: * SF Tcl Bug 1381436, asking for the backport. sl@0: * sl@0: * [x] Cutting the infoPtr after the closehandle allows the sl@0: * pointer to become stale. We do it here, and compensate if the sl@0: * process was not done yet. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&pipeMutex); sl@0: prevPtrPtr = &procList; sl@0: for (infoPtr = procList; infoPtr != NULL; sl@0: prevPtrPtr = &infoPtr->nextPtr, infoPtr = infoPtr->nextPtr) { sl@0: if (infoPtr->hProcess == (HANDLE) pid) { sl@0: *prevPtrPtr = infoPtr->nextPtr; sl@0: break; sl@0: } sl@0: } sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: sl@0: /* sl@0: * If the pid is not one of the processes we know about (we started it) sl@0: * then do nothing. sl@0: */ sl@0: sl@0: if (infoPtr == NULL) { sl@0: *statPtr = 0; sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * Officially "wait" for it to finish. We either poll (WNOHANG) or sl@0: * wait for an infinite amount of time. sl@0: */ sl@0: sl@0: if (options & WNOHANG) { sl@0: flags = 0; sl@0: } else { sl@0: flags = INFINITE; sl@0: } sl@0: ret = WaitForSingleObject(infoPtr->hProcess, flags); sl@0: if (ret == WAIT_TIMEOUT) { sl@0: *statPtr = 0; sl@0: if (options & WNOHANG) { sl@0: /* sl@0: * Re-insert the cut infoPtr back on the list. sl@0: * See [x] for explanation. sl@0: */ sl@0: Tcl_MutexLock(&pipeMutex); sl@0: infoPtr->nextPtr = procList; sl@0: procList = infoPtr; sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: return 0; sl@0: } else { sl@0: result = 0; sl@0: } sl@0: } else if (ret == WAIT_OBJECT_0) { sl@0: GetExitCodeProcess(infoPtr->hProcess, &exitCode); sl@0: if (exitCode & 0xC0000000) { sl@0: /* sl@0: * A fatal exception occured. sl@0: */ sl@0: switch (exitCode) { sl@0: case EXCEPTION_FLT_DENORMAL_OPERAND: sl@0: case EXCEPTION_FLT_DIVIDE_BY_ZERO: sl@0: case EXCEPTION_FLT_INEXACT_RESULT: sl@0: case EXCEPTION_FLT_INVALID_OPERATION: sl@0: case EXCEPTION_FLT_OVERFLOW: sl@0: case EXCEPTION_FLT_STACK_CHECK: sl@0: case EXCEPTION_FLT_UNDERFLOW: sl@0: case EXCEPTION_INT_DIVIDE_BY_ZERO: sl@0: case EXCEPTION_INT_OVERFLOW: sl@0: *statPtr = 0xC0000000 | SIGFPE; sl@0: break; sl@0: sl@0: case EXCEPTION_PRIV_INSTRUCTION: sl@0: case EXCEPTION_ILLEGAL_INSTRUCTION: sl@0: *statPtr = 0xC0000000 | SIGILL; sl@0: break; sl@0: sl@0: case EXCEPTION_ACCESS_VIOLATION: sl@0: case EXCEPTION_DATATYPE_MISALIGNMENT: sl@0: case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: sl@0: case EXCEPTION_STACK_OVERFLOW: sl@0: case EXCEPTION_NONCONTINUABLE_EXCEPTION: sl@0: case EXCEPTION_INVALID_DISPOSITION: sl@0: case EXCEPTION_GUARD_PAGE: sl@0: case EXCEPTION_INVALID_HANDLE: sl@0: *statPtr = 0xC0000000 | SIGSEGV; sl@0: break; sl@0: sl@0: case CONTROL_C_EXIT: sl@0: *statPtr = 0xC0000000 | SIGINT; sl@0: break; sl@0: sl@0: default: sl@0: *statPtr = 0xC0000000 | SIGABRT; sl@0: break; sl@0: } sl@0: } else { sl@0: *statPtr = exitCode; sl@0: } sl@0: result = pid; sl@0: } else { sl@0: errno = ECHILD; sl@0: *statPtr = 0xC0000000 | ECHILD; sl@0: result = (Tcl_Pid) -1; sl@0: } sl@0: sl@0: /* sl@0: * Officially close the process handle. sl@0: */ sl@0: sl@0: CloseHandle(infoPtr->hProcess); sl@0: ckfree((char*)infoPtr); sl@0: sl@0: return result; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * TclWinAddProcess -- sl@0: * sl@0: * Add a process to the process list so that we can use sl@0: * Tcl_WaitPid on the process. sl@0: * sl@0: * Results: sl@0: * None sl@0: * sl@0: * Side effects: sl@0: * Adds the specified process handle to the process list so sl@0: * Tcl_WaitPid knows about it. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: void sl@0: TclWinAddProcess(hProcess, id) sl@0: HANDLE hProcess; /* Handle to process */ sl@0: DWORD id; /* Global process identifier */ sl@0: { sl@0: ProcInfo *procPtr = (ProcInfo *) ckalloc(sizeof(ProcInfo)); sl@0: sl@0: PipeInit(); sl@0: sl@0: procPtr->hProcess = hProcess; sl@0: procPtr->dwProcessId = id; sl@0: Tcl_MutexLock(&pipeMutex); sl@0: procPtr->nextPtr = procList; sl@0: procList = procPtr; sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * Tcl_PidObjCmd -- sl@0: * sl@0: * This procedure is invoked to process the "pid" Tcl command. sl@0: * See the user documentation for details on what it does. sl@0: * sl@0: * Results: sl@0: * A standard Tcl result. sl@0: * sl@0: * Side effects: sl@0: * See the user documentation. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: /* ARGSUSED */ sl@0: int sl@0: Tcl_PidObjCmd( sl@0: ClientData dummy, /* Not used. */ sl@0: Tcl_Interp *interp, /* Current interpreter. */ sl@0: int objc, /* Number of arguments. */ sl@0: Tcl_Obj *CONST *objv) /* Argument strings. */ sl@0: { sl@0: Tcl_Channel chan; sl@0: Tcl_ChannelType *chanTypePtr; sl@0: PipeInfo *pipePtr; sl@0: int i; sl@0: Tcl_Obj *resultPtr; sl@0: char buf[TCL_INTEGER_SPACE]; sl@0: sl@0: if (objc > 2) { sl@0: Tcl_WrongNumArgs(interp, 1, objv, "?channelId?"); sl@0: return TCL_ERROR; sl@0: } sl@0: if (objc == 1) { sl@0: resultPtr = Tcl_GetObjResult(interp); sl@0: wsprintfA(buf, "%lu", (unsigned long) getpid()); sl@0: Tcl_SetStringObj(resultPtr, buf, -1); sl@0: } else { sl@0: chan = Tcl_GetChannel(interp, Tcl_GetStringFromObj(objv[1], NULL), sl@0: NULL); sl@0: if (chan == (Tcl_Channel) NULL) { sl@0: return TCL_ERROR; sl@0: } sl@0: chanTypePtr = Tcl_GetChannelType(chan); sl@0: if (chanTypePtr != &pipeChannelType) { sl@0: return TCL_OK; sl@0: } sl@0: sl@0: pipePtr = (PipeInfo *) Tcl_GetChannelInstanceData(chan); sl@0: resultPtr = Tcl_GetObjResult(interp); sl@0: for (i = 0; i < pipePtr->numPids; i++) { sl@0: wsprintfA(buf, "%lu", TclpGetPid(pipePtr->pidPtr[i])); sl@0: Tcl_ListObjAppendElement(/*interp*/ NULL, resultPtr, sl@0: Tcl_NewStringObj(buf, -1)); sl@0: } sl@0: } sl@0: return TCL_OK; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * WaitForRead -- sl@0: * sl@0: * Wait until some data is available, the pipe is at sl@0: * EOF or the reader thread is blocked waiting for data (if the sl@0: * channel is in non-blocking mode). sl@0: * sl@0: * Results: sl@0: * Returns 1 if pipe is readable. Returns 0 if there is no data sl@0: * on the pipe, but there is buffered data. Returns -1 if an sl@0: * error occurred. If an error occurred, the threads may not sl@0: * be synchronized. sl@0: * sl@0: * Side effects: sl@0: * Updates the shared state flags and may consume 1 byte of data sl@0: * from the pipe. If no error occurred, the reader thread is sl@0: * blocked waiting for a signal from the main thread. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static int sl@0: WaitForRead( sl@0: PipeInfo *infoPtr, /* Pipe state. */ sl@0: int blocking) /* Indicates whether call should be sl@0: * blocking or not. */ sl@0: { sl@0: DWORD timeout, count; sl@0: HANDLE *handle = ((WinFile *) infoPtr->readFile)->handle; sl@0: sl@0: while (1) { sl@0: /* sl@0: * Synchronize with the reader thread. sl@0: */ sl@0: sl@0: timeout = blocking ? INFINITE : 0; sl@0: if (WaitForSingleObject(infoPtr->readable, timeout) == WAIT_TIMEOUT) { sl@0: /* sl@0: * The reader thread is blocked waiting for data and the channel sl@0: * is in non-blocking mode. sl@0: */ sl@0: sl@0: errno = EAGAIN; sl@0: return -1; sl@0: } sl@0: sl@0: /* sl@0: * At this point, the two threads are synchronized, so it is safe sl@0: * to access shared state. sl@0: */ sl@0: sl@0: sl@0: /* sl@0: * If the pipe has hit EOF, it is always readable. sl@0: */ sl@0: sl@0: if (infoPtr->readFlags & PIPE_EOF) { sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * Check to see if there is any data sitting in the pipe. sl@0: */ sl@0: sl@0: if (PeekNamedPipe(handle, (LPVOID) NULL, (DWORD) 0, sl@0: (LPDWORD) NULL, &count, (LPDWORD) NULL) != TRUE) { sl@0: TclWinConvertError(GetLastError()); sl@0: /* sl@0: * Check to see if the peek failed because of EOF. sl@0: */ sl@0: sl@0: if (errno == EPIPE) { sl@0: infoPtr->readFlags |= PIPE_EOF; sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * Ignore errors if there is data in the buffer. sl@0: */ sl@0: sl@0: if (infoPtr->readFlags & PIPE_EXTRABYTE) { sl@0: return 0; sl@0: } else { sl@0: return -1; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * We found some data in the pipe, so it must be readable. sl@0: */ sl@0: sl@0: if (count > 0) { sl@0: return 1; sl@0: } sl@0: sl@0: /* sl@0: * The pipe isn't readable, but there is some data sitting sl@0: * in the buffer, so return immediately. sl@0: */ sl@0: sl@0: if (infoPtr->readFlags & PIPE_EXTRABYTE) { sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: * There wasn't any data available, so reset the thread and sl@0: * try again. sl@0: */ sl@0: sl@0: ResetEvent(infoPtr->readable); sl@0: SetEvent(infoPtr->startReader); sl@0: } sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeReaderThread -- sl@0: * sl@0: * This function runs in a separate thread and waits for input sl@0: * to become available on a pipe. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Signals the main thread when input become available. May sl@0: * cause the main thread to wake up by posting a message. May sl@0: * consume one byte from the pipe for each wait operation. Will sl@0: * cause a memory leak of ~4k, if forcefully terminated with sl@0: * TerminateThread(). sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static DWORD WINAPI sl@0: PipeReaderThread(LPVOID arg) sl@0: { sl@0: PipeInfo *infoPtr = (PipeInfo *)arg; sl@0: HANDLE *handle = ((WinFile *) infoPtr->readFile)->handle; sl@0: DWORD count, err; sl@0: int done = 0; sl@0: HANDLE wEvents[2]; sl@0: DWORD waitResult; sl@0: sl@0: wEvents[0] = infoPtr->stopReader; sl@0: wEvents[1] = infoPtr->startReader; sl@0: sl@0: while (!done) { sl@0: /* sl@0: * Wait for the main thread to signal before attempting to wait sl@0: * on the pipe becoming readable. sl@0: */ sl@0: sl@0: waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); sl@0: sl@0: if (waitResult != (WAIT_OBJECT_0 + 1)) { sl@0: /* sl@0: * The start event was not signaled. It might be the stop event sl@0: * or an error, so exit. sl@0: */ sl@0: sl@0: break; sl@0: } sl@0: sl@0: /* sl@0: * Try waiting for 0 bytes. This will block until some data is sl@0: * available on NT, but will return immediately on Win 95. So, sl@0: * if no data is available after the first read, we block until sl@0: * we can read a single byte off of the pipe. sl@0: */ sl@0: sl@0: if ((ReadFile(handle, NULL, 0, &count, NULL) == FALSE) sl@0: || (PeekNamedPipe(handle, NULL, 0, NULL, &count, sl@0: NULL) == FALSE)) { sl@0: /* sl@0: * The error is a result of an EOF condition, so set the sl@0: * EOF bit before signalling the main thread. sl@0: */ sl@0: sl@0: err = GetLastError(); sl@0: if (err == ERROR_BROKEN_PIPE) { sl@0: infoPtr->readFlags |= PIPE_EOF; sl@0: done = 1; sl@0: } else if (err == ERROR_INVALID_HANDLE) { sl@0: break; sl@0: } sl@0: } else if (count == 0) { sl@0: if (ReadFile(handle, &(infoPtr->extraByte), 1, &count, NULL) sl@0: != FALSE) { sl@0: /* sl@0: * One byte was consumed as a side effect of waiting sl@0: * for the pipe to become readable. sl@0: */ sl@0: sl@0: infoPtr->readFlags |= PIPE_EXTRABYTE; sl@0: } else { sl@0: err = GetLastError(); sl@0: if (err == ERROR_BROKEN_PIPE) { sl@0: /* sl@0: * The error is a result of an EOF condition, so set the sl@0: * EOF bit before signalling the main thread. sl@0: */ sl@0: sl@0: infoPtr->readFlags |= PIPE_EOF; sl@0: done = 1; sl@0: } else if (err == ERROR_INVALID_HANDLE) { sl@0: break; sl@0: } sl@0: } sl@0: } sl@0: sl@0: sl@0: /* sl@0: * Signal the main thread by signalling the readable event and sl@0: * then waking up the notifier thread. sl@0: */ sl@0: sl@0: SetEvent(infoPtr->readable); sl@0: sl@0: /* sl@0: * Alert the foreground thread. Note that we need to treat this like sl@0: * a critical section so the foreground thread does not terminate sl@0: * this thread while we are holding a mutex in the notifier code. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&pipeMutex); sl@0: if (infoPtr->threadId != NULL) { sl@0: /* TIP #218. When in flight ignore the event, no one will receive it anyway */ sl@0: Tcl_ThreadAlert(infoPtr->threadId); sl@0: } sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: } sl@0: sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeWriterThread -- sl@0: * sl@0: * This function runs in a separate thread and writes data sl@0: * onto a pipe. sl@0: * sl@0: * Results: sl@0: * Always returns 0. sl@0: * sl@0: * Side effects: sl@0: * Signals the main thread when an output operation is completed. sl@0: * May cause the main thread to wake up by posting a message. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static DWORD WINAPI sl@0: PipeWriterThread(LPVOID arg) sl@0: { sl@0: sl@0: PipeInfo *infoPtr = (PipeInfo *)arg; sl@0: HANDLE *handle = ((WinFile *) infoPtr->writeFile)->handle; sl@0: DWORD count, toWrite; sl@0: char *buf; sl@0: int done = 0; sl@0: HANDLE wEvents[2]; sl@0: DWORD waitResult; sl@0: sl@0: wEvents[0] = infoPtr->stopWriter; sl@0: wEvents[1] = infoPtr->startWriter; sl@0: sl@0: while (!done) { sl@0: /* sl@0: * Wait for the main thread to signal before attempting to write. sl@0: */ sl@0: sl@0: waitResult = WaitForMultipleObjects(2, wEvents, FALSE, INFINITE); sl@0: sl@0: if (waitResult != (WAIT_OBJECT_0 + 1)) { sl@0: /* sl@0: * The start event was not signaled. It might be the stop event sl@0: * or an error, so exit. sl@0: */ sl@0: sl@0: break; sl@0: } sl@0: sl@0: buf = infoPtr->writeBuf; sl@0: toWrite = infoPtr->toWrite; sl@0: sl@0: /* sl@0: * Loop until all of the bytes are written or an error occurs. sl@0: */ sl@0: sl@0: while (toWrite > 0) { sl@0: if (WriteFile(handle, buf, toWrite, &count, NULL) == FALSE) { sl@0: infoPtr->writeError = GetLastError(); sl@0: done = 1; sl@0: break; sl@0: } else { sl@0: toWrite -= count; sl@0: buf += count; sl@0: } sl@0: } sl@0: sl@0: /* sl@0: * Signal the main thread by signalling the writable event and sl@0: * then waking up the notifier thread. sl@0: */ sl@0: sl@0: SetEvent(infoPtr->writable); sl@0: sl@0: /* sl@0: * Alert the foreground thread. Note that we need to treat this like sl@0: * a critical section so the foreground thread does not terminate sl@0: * this thread while we are holding a mutex in the notifier code. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&pipeMutex); sl@0: if (infoPtr->threadId != NULL) { sl@0: /* TIP #218. When in flight ignore the event, no one will receive it anyway */ sl@0: Tcl_ThreadAlert(infoPtr->threadId); sl@0: } sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: } sl@0: sl@0: return 0; sl@0: } sl@0: sl@0: /* sl@0: *---------------------------------------------------------------------- sl@0: * sl@0: * PipeThreadActionProc -- sl@0: * sl@0: * Insert or remove any thread local refs to this channel. sl@0: * sl@0: * Results: sl@0: * None. sl@0: * sl@0: * Side effects: sl@0: * Changes thread local list of valid channels. sl@0: * sl@0: *---------------------------------------------------------------------- sl@0: */ sl@0: sl@0: static void sl@0: PipeThreadActionProc (instanceData, action) sl@0: ClientData instanceData; sl@0: int action; sl@0: { sl@0: PipeInfo *infoPtr = (PipeInfo *) instanceData; sl@0: sl@0: /* We do not access firstPipePtr in the thread structures. This is sl@0: * not for all pipes managed by the thread, but only those we are sl@0: * watching. Removal of the filevent handlers before transfer thus sl@0: * takes care of this structure. sl@0: */ sl@0: sl@0: Tcl_MutexLock(&pipeMutex); sl@0: if (action == TCL_CHANNEL_THREAD_INSERT) { sl@0: /* We can't copy the thread information from the channel when sl@0: * the channel is created. At this time the channel back sl@0: * pointer has not been set yet. However in that case the sl@0: * threadId has already been set by TclpCreateCommandChannel sl@0: * itself, so the structure is still good. sl@0: */ sl@0: sl@0: PipeInit (); sl@0: if (infoPtr->channel != NULL) { sl@0: infoPtr->threadId = Tcl_GetChannelThread (infoPtr->channel); sl@0: } sl@0: } else { sl@0: infoPtr->threadId = NULL; sl@0: } sl@0: Tcl_MutexUnlock(&pipeMutex); sl@0: }