Hi, This second round of the _popen/_wpopen/_pclose implementation uses CreateProcess with pipes as per Alexandre's suggestion. This patch will not work without the wcmd-1 patch sent earlier, since earlier versions of wcmd create new STDIN and STDOUT handles by allocating a new console, invalidating the pipes created by the _popen command. Test case to follow... License: LGPL Changelog: * dlls/msvcrt/msvcrt.spec, dlls/msvcrt/file.c: Jaco Greeff <jaco@puxedo.org> - Implementation of the _popen/_wpopen/_pclose commands -- [ inline patch ]-- diff -aurN msvcrt-popen.orig/dlls/msvcrt/file.c msvcrt-popen.new/dlls/msvcrt/file.c --- msvcrt-popen.orig/dlls/msvcrt/file.c 2002-11-02 12:07:23.000000000 +0000 +++ msvcrt-popen.new/dlls/msvcrt/file.c 2002-11-02 17:17:51.000000000 +0000 @@ -5,6 +5,7 @@ * Copyright 1996 Jukka Iivonen * Copyright 1997,2000 Uwe Bonnes * Copyright 2000 Jon Griffiths + * Copyright 2002 Jaco Greeff * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -96,6 +97,62 @@ #define LOCK_FILES EnterCriticalSection(&MSVCRT_file_cs) #define UNLOCK_FILES LeaveCriticalSection(&MSVCRT_file_cs) +/* FIXME: Don't know what this value should be, 10 seems enough as + * the maximum number of simultaneous _popen'ed processes + */ +#define POPEN_MAX_FILES 10 +#define POPEN_FILE_IN_USE 0x0001 + +#define POPEN_WCMD_EXEC "C:\\Windows\\System32\\cmd.exe /c" + +#define LPCWSTR_TO_LPSTR(wszIn, nIn, szOut, nOut) \ + *nOut = WideCharToMultiByte(CP_ACP, 0, wszIn, nIn, NULL, 0, NULL, NULL); \ + szOut = (CHAR *)MSVCRT_malloc((*nOut+1)*sizeof(CHAR)); \ + if (szOut) \ + { \ + WideCharToMultiByte(CP_ACP, 0, wszIn, nIn, szOut, *nOut+1, NULL, NULL); \ + szOut[nLen] = '\0'; \ + } \ + else \ + *nOut = 0; + +#define CREATE_PIPE(hRead, hWrite, security) \ + if (!CreatePipe(&hRead, &hWrite, &security, 0)) \ + { \ + TRACE("Creation of pipe failed\n"); \ + return NULL; \ + } + +#define REDIRECT_STREAM(type, handle)\ + if (!SetStdHandle(type, handle)) \ + { \ + TRACE("Redirection of stream failed"); \ + return NULL; \ + } + +#define RESTORE_STREAM(type, handle) \ + if (!SetStdHandle(type, handle)) \ + TRACE("Restore of stream failed\n"); + +#define DUPLICATE_HANDLE(hHandle, hDup, type, hOrig) \ + if (!DuplicateHandle(GetCurrentProcess(), hHandle, \ + GetCurrentProcess(), &hDup, 0, \ + FALSE, DUPLICATE_SAME_ACCESS)) \ + { \ + TRACE("Duplication of piped handle failed\n"); \ + RESTORE_STREAM(type, hOrig); \ + return NULL; \ + } \ + else \ + CloseHandle(hHandle); + +typedef struct +{ + MSVCRT_FILE *fProcess; + HANDLE hProcess; +} POPEN_FILE; + +static POPEN_FILE POPEN_Files[POPEN_MAX_FILES]; /* INTERNAL: Get the HANDLE for a fd */ static HANDLE msvcrt_fdtoh(int fd) @@ -183,25 +240,34 @@ /* INTERNAL: Set up stdin, stderr and stdout */ void msvcrt_init_io(void) { - int i; - memset(MSVCRT__iob,0,3*sizeof(MSVCRT_FILE)); - MSVCRT_handles[0] = GetStdHandle(STD_INPUT_HANDLE); - MSVCRT_flags[0] = MSVCRT__iob[0]._flag = MSVCRT__IOREAD; - MSVCRT_handles[1] = GetStdHandle(STD_OUTPUT_HANDLE); - MSVCRT_flags[1] = MSVCRT__iob[1]._flag = MSVCRT__IOWRT; - MSVCRT_handles[2] = GetStdHandle(STD_ERROR_HANDLE); - MSVCRT_flags[2] = MSVCRT__iob[2]._flag = MSVCRT__IOWRT; - - TRACE(":handles (%p)(%p)(%p)\n",MSVCRT_handles[0], - MSVCRT_handles[1],MSVCRT_handles[2]); - - for (i = 0; i < 3; i++) - { - /* FILE structs for stdin/out/err are static and never deleted */ - MSVCRT_files[i] = &MSVCRT__iob[i]; - MSVCRT__iob[i]._file = i; - MSVCRT_tempfiles[i] = NULL; - } + int i; + memset(MSVCRT__iob,0,3*sizeof(MSVCRT_FILE)); + MSVCRT_handles[0] = GetStdHandle(STD_INPUT_HANDLE); + MSVCRT_flags[0] = MSVCRT__iob[0]._flag = MSVCRT__IOREAD; + MSVCRT_handles[1] = GetStdHandle(STD_OUTPUT_HANDLE); + MSVCRT_flags[1] = MSVCRT__iob[1]._flag = MSVCRT__IOWRT; + MSVCRT_handles[2] = GetStdHandle(STD_ERROR_HANDLE); + MSVCRT_flags[2] = MSVCRT__iob[2]._flag = MSVCRT__IOWRT; + + TRACE(":handles (%p)(%p)(%p)\n",MSVCRT_handles[0], + MSVCRT_handles[1],MSVCRT_handles[2]); + + for (i = 0; i < 3; i++) + { + /* FILE structs for stdin/out/err are static and never deleted */ + MSVCRT_files[i] = &MSVCRT__iob[i]; + MSVCRT__iob[i]._file = i; + MSVCRT_tempfiles[i] = NULL; + } + + for (i = 0; i < POPEN_MAX_FILES; i++) + { + /* clear everything that is used to hold the process + * file handles by setting it to zero. + */ + POPEN_Files[i].hProcess = NULL; + POPEN_Files[i].fProcess = NULL; + } } /* free everything on process exit */ @@ -2342,3 +2408,325 @@ va_end(valist); return res; } + +/********************************************************************* + * POPEN_parseModeFlags (internal) + * + * Description + * Parses the flags passed to the _popne/_wpopen function and + * setups up the correct binary flags for usage via the + * _popen/_wpopen commands + * + * FIXME: We are not currently catering for the "t"|"b" flags, rather + * only allowing for the read and write + */ +static INT POPEN_parseModeFlags(const CHAR *szMode) +{ + INT nFlags = 0; + while (szMode && *szMode) + { + switch (*szMode) + { + case 'r': + { + if (nFlags & MSVCRT__IOWRT) + TRACE(": _popen: Cannot set both read and write open flags, ignoring read flag\n"); + else + nFlags = nFlags|MSVCRT__IOREAD; + break; + } + case 'w': + { + if (nFlags & MSVCRT__IOREAD) + TRACE(": _popen: Cannot set both read and write open flags, ignoring write flag\n"); + else + nFlags = nFlags|MSVCRT__IOWRT; + break; + } + case 'b': + case 't': + { + FIXME(": _popen: %c mode flag not implemented, ignoring\n", *szMode); + break; + } + default: + { + TRACE(": _popen: unknown mode flag %c, ignoring\n", *szMode); + break; + } + } + szMode++; + } + return nFlags; +} + +/********************************************************************* + * POPEN_getOpenFileSlotPos (internal) + * + * Description + * Return the first available file slot for storing file data + */ +static int POPEN_getOpenFileSlotPos(VOID) +{ + int i = 0; + for (i = 0; i < POPEN_MAX_FILES; i++) + { + if (!POPEN_Files[i].fProcess) + { + POPEN_Files[i].fProcess = (MSVCRT_FILE *)POPEN_FILE_IN_USE; + return i; + } + } + ERR("Cannot find any available _popen handles, increase the value of POPEN_MAX_FILES\n"); + return -1; +} + +/********************************************************************* + * POPEN_findFileSlotPos (internal) + * + * Description + * Return the first available file slot for storing file data + */ +static int POPEN_findFileSlotPos(MSVCRT_FILE *fProcess) +{ + int i = 0; + for (i = 0; i < POPEN_MAX_FILES; i++) + { + if (POPEN_Files[i].fProcess == fProcess) + return i; + } + TRACE("Invalid process handle, fProcess == %p\n", fProcess); + return -1; +} + +/********************************************************************* + * MSVCRT_popen (MSVCRT.@) + * + * Description + * + * Input + * szCommand - The command to execute, this gets executed as + * "cmd.exe /C szCommand" under Windows + * szMode - The file mode to read/write from the process. This + * can be "r", "w" with optional "t"|"b" specifiers + * + * Output + * Returns a valid MSVCRT_FILE to the opened stream for reading or + * writing to the process. If the call failed, returns NULL + * + * FIXME: We are not currently catering for the "t"|"b" flags, rather + * only allowing for the read and write + */ +MSVCRT_FILE *MSVCRT_popen(const CHAR *szCommand, const CHAR *szMode) +{ + MSVCRT_FILE *fProcess = NULL; + INT nFlags = 0; + CHAR *szExec = NULL; + PROCESS_INFORMATION piInfo; + STARTUPINFOA siStartup; + SECURITY_ATTRIBUTES saSecurity; + HANDLE hOrigIn, hOrigOut; + HANDLE hNewOutRead, hNewOutWrite, hNewOutReadDup; + HANDLE hNewInRead, hNewInWrite, hNewInWriteDup; + HANDLE hProcess; + + TRACE("(szCommand == %s, szMode == %s)\n", debugstr_a(szCommand), debugstr_a(szMode)); + + if (!szCommand || !szMode) + { + TRACE("Invalid wszCommand and/or wszMode specified\n"); + return NULL; + } + + nFlags = POPEN_parseModeFlags(szMode); + if (!(nFlags & (MSVCRT__IOREAD|MSVCRT__IOWRT))) + { + TRACE("No open mode flag r or w specified\n"); + return NULL; + } + + /* setup the security attributes for the child pipes we + * are about to create such that it inherts the handles + * of our main process + */ + ZeroMemory(&saSecurity, sizeof(SECURITY_ATTRIBUTES)); + saSecurity.nLength = sizeof(SECURITY_ATTRIBUTES); + saSecurity.bInheritHandle = TRUE; + saSecurity.lpSecurityDescriptor = NULL; + + /* create duplicate streams for both reading and writing to + * the actual child process (potentially this allows us to + * open a process "rw", but the call only allows one of these + * flags at a time) + */ + hOrigOut = GetStdHandle(STD_OUTPUT_HANDLE); + CREATE_PIPE(hNewOutRead, hNewOutWrite, saSecurity); + REDIRECT_STREAM(STD_OUTPUT_HANDLE, hNewOutWrite); + DUPLICATE_HANDLE(hNewOutRead, hNewOutReadDup, STD_OUTPUT_HANDLE, hOrigOut); + + hOrigIn = GetStdHandle(STD_INPUT_HANDLE); + CREATE_PIPE(hNewInRead, hNewInWrite, saSecurity); + REDIRECT_STREAM(STD_INPUT_HANDLE, hNewInRead); + DUPLICATE_HANDLE(hNewInWrite, hNewInWriteDup, STD_INPUT_HANDLE, hOrigIn); + + /* _popen/_wpopen executes the required command via the command + * processor, either command.com (Win 95/98) or cmd.exe (Win NT/2000/XP). + * wcmd acts as a replacement, so we will be launching it instead. + */ + if (!(szExec = (CHAR *)malloc(strlen(POPEN_WCMD_EXEC)+strlen(" ")+strlen(szCommand)+1))) + { + RESTORE_STREAM(STD_INPUT_HANDLE, hOrigIn); + RESTORE_STREAM(STD_OUTPUT_HANDLE, hOrigOut); + TRACE("Unable to allocate memory for process command line\n"); + return NULL; + } + sprintf(szExec, "%s %s", POPEN_WCMD_EXEC, szCommand); + + /* create the process with our attributes, using the + * redirected stream we just setup + */ + ZeroMemory(&siStartup, sizeof(STARTUPINFOA)); + siStartup.cb = sizeof(STARTUPINFOA); + if (CreateProcessA(NULL, szExec, NULL, NULL, TRUE, 0, + NULL, NULL, &siStartup, &piInfo)) + { + int nPos = -1, fd = -1; + DWORD dwLen; + + /* wait for our newly created process to make it's + * appearance + */ + WaitForSingleObject(piInfo.hProcess, INFINITE); + TRACE("Process created, hProcess = %p, hThread == %p\n", + piInfo.hProcess, piInfo.hThread); + + /* create a MSVCRT_FILE * to the actual file stream and save + * this together with our process handle so we can close + * both when requested + */ + hProcess = (nFlags & MSVCRT__IOREAD) ? hNewOutReadDup : hNewInWriteDup; + if ((fd = msvcrt_alloc_fd(hProcess, nFlags)) && + (fProcess = msvcrt_alloc_fp(fd)) && + ((nPos = POPEN_getOpenFileSlotPos()) != -1)) + { + POPEN_Files[nPos].fProcess = fProcess; + POPEN_Files[nPos].hProcess = piInfo.hProcess; + } + else + { + if (fProcess) + { + MSVCRT_fclose(fProcess); + fProcess = NULL; + } + TerminateProcess(piInfo.hProcess, 0); + TRACE("Unable to create MSVCRT_FILE * from process handle\n"); + } + } + else + TRACE("Unable to create child process, %s\n", szExec); + + /* here we clean up everything we have allocated: restore our original + * STDOUT and STDIN to the state it was before we created the child pipes + * and free allocated memory for the command buffer + */ + RESTORE_STREAM(STD_INPUT_HANDLE, hOrigIn); + RESTORE_STREAM(STD_OUTPUT_HANDLE, hOrigOut); + MSVCRT_free(szExec); + + return fProcess; +} + +/********************************************************************* + * MSVCRT_wpopen (MSVCRT.@) + * + * Description + * See MSVCRT_popen + */ +MSVCRT_FILE *MSVCRT_wpopen(const WCHAR *wszCommand, const WCHAR *wszMode) +{ + MSVCRT_FILE *fProcess = NULL; + CHAR *szCommand; + CHAR *szMode; + INT nLen; + + TRACE("(wszCommand == %s, wszMode == %s)\n", debugstr_w(wszCommand), debugstr_w(wszMode)); + + if (!wszCommand || !wszMode) + { + TRACE("Invalid wszCommand and/or wszMode specified\n"); + return NULL; + } + + LPCWSTR_TO_LPSTR(wszCommand, strlenW(wszCommand), szCommand, &nLen); + if (szCommand) + { + LPCWSTR_TO_LPSTR(wszMode, strlenW(wszMode), szMode, &nLen); + if (szMode) + { + fProcess = MSVCRT_popen(szCommand, szMode); + MSVCRT_free(szMode); + } + else + TRACE("Unable to allocate memory for Unicode mode conversion\n"); + MSVCRT_free(szCommand); + } + else + TRACE("Unable to allocate memory for Unicode command conversion\n"); + + return fProcess; +} + +/********************************************************************* + * MSVCRT_pclose (MSVCRT.@) + * + * Description + * Closes the file stream and process handle of the process + * previously created by a clall to _popen/_wpopen + * + * Input + * fProcess - The process handle and previously returned by + * a clall to _popen/_wpopen + * + * Output + * Returns 0 on success, -1 on error + */ +int MSVCRT_pclose(MSVCRT_FILE *fProcess) +{ + int nRet = -1; + int nPos; + DWORD dwCode; + + TRACE("(fProcess == %p)\n", fProcess); + + if ((nPos = POPEN_findFileSlotPos(fProcess)) != -1) + { + /* Close the file stream used previously to read from + * or write to the process + */ + if ((nRet = MSVCRT_fclose(POPEN_Files[nPos].fProcess))) + TRACE("Unable to close the process file stream\n"); + + /* If the process is still running, try to terminate + * it. If it is not running, just do a trace on the + * actual exit code + */ + if (GetExitCodeProcess(POPEN_Files[nPos].hProcess, &dwCode)) + { + if (dwCode == STILL_ACTIVE) + { + if (TerminateProcess(POPEN_Files[nPos].hProcess, 0)) + TRACE("Unable to terminate the process\n"); + } + else + TRACE("Process terminated with exitcode 0x%x\n", (INT)dwCode); + } + else + TRACE("No process exit code available\n"); + + /* Close the actual process handle */ + if (!CloseHandle(POPEN_Files[nPos].hProcess)) + TRACE("Unable to close the process handle\n"); + } + return nRet; +} diff -aurN msvcrt-popen.orig/dlls/msvcrt/msvcrt.spec msvcrt-popen.new/dlls/msvcrt/msvcrt.spec --- msvcrt-popen.orig/dlls/msvcrt/msvcrt.spec 2002-11-02 10:43:54.000000000 +0000 +++ msvcrt-popen.new/dlls/msvcrt/msvcrt.spec 2002-11-01 20:52:10.000000000 +0000 @@ -401,11 +401,11 @@ @ stub _outp #(long long) @ stub _outpd #(long long) @ stub _outpw #(long long) -@ stub _pclose #(ptr) +@ cdecl _pclose(ptr) MSVCRT_pclose @ extern _pctype MSVCRT__pctype @ stub _pgmptr @ stub _pipe #(ptr long long) -@ stub _popen #(str str) +@ cdecl _popen(str str) MSVCRT_popen @ cdecl _purecall() _purecall @ cdecl _putch(long) _putch @ cdecl _putenv(str) _putenv @@ -535,7 +535,7 @@ @ varargs _wopen(wstr long) _wopen @ stub _wperror #(wstr) @ stub _wpgmptr -@ stub _wpopen #(wstr wstr) +@ cdecl _wpopen(wstr wstr) MSVCRT_popen @ cdecl _wputenv(wstr) _wputenv @ cdecl _wremove(wstr) _wremove @ cdecl _wrename(wstr wstr) _wrename