This msvcrt-A?? series patch implements the _popen/_wpopen/_pclose functions used to read and write to process streams. License: LGPL Changelog: * dlls/msvcrt/file.c, dlls/msvcrt/msvcrt.spec: Jaco Greeff <jaco@puxedo.org> - Implementation of the _popen/_wpopen and _pclose functions --[ inline patch ]-- diff -aurN msvcrt-A03/dlls/msvcrt/file.c msvcrt-A04/dlls/msvcrt/file.c --- msvcrt-A03/dlls/msvcrt/file.c 2002-11-02 19:45:39.000000000 +0000 +++ msvcrt-A04/dlls/msvcrt/file.c 2002-11-03 14:11:35.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,22 @@ #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" + +typedef struct +{ + MSVCRT_FILE *fProcess; + HANDLE hProcess; + HANDLE hStream; +} 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 +200,35 @@ /* 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; + POPEN_Files[i].hStream = NULL; + } } /* free everything on process exit */ @@ -2289,3 +2316,375 @@ 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("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("Cannot set both read and write open flags, ignoring write flag\n"); + else + nFlags = nFlags|MSVCRT__IOWRT; + break; + case 'b': + case 't': + FIXME("%c mode flag not implemented, ignoring\n", *szMode); + break; + default: + TRACE("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); + if (!CreatePipe(&hNewOutRead, &hNewOutWrite, &saSecurity, 0)) + TRACE("Creation of pipe failed\n"); + if (!SetStdHandle(STD_OUTPUT_HANDLE, hNewOutWrite)) + TRACE("Redirection of stream failed"); + if (DuplicateHandle(GetCurrentProcess(), hNewOutRead, + GetCurrentProcess(), &hNewOutReadDup, 0, + FALSE, DUPLICATE_SAME_ACCESS)) + CloseHandle(hNewOutRead); + else + TRACE("Duplication of handle failed\n"); + + hOrigIn = GetStdHandle(STD_INPUT_HANDLE); + if (!CreatePipe(&hNewInRead, &hNewInWrite, &saSecurity, 0)) + TRACE("Creation of pipe failed\n"); + if (!SetStdHandle(STD_INPUT_HANDLE, hNewInRead)) + TRACE("Redirection of stream failed"); + if (DuplicateHandle(GetCurrentProcess(), hNewInWrite, + GetCurrentProcess(), &hNewInWriteDup, 0, + FALSE, DUPLICATE_SAME_ACCESS)) + CloseHandle(hNewInWrite); + else + TRACE("Duplication of handle failed\n"); + + /* _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 *)MSVCRT_malloc(strlen(POPEN_WCMD_EXEC)+strlen(" ")+strlen(szCommand)+1))) + { + SetStdHandle(STD_INPUT_HANDLE, hOrigIn); + SetStdHandle(STD_OUTPUT_HANDLE, hOrigOut); + CloseHandle(hNewOutWrite); + CloseHandle(hNewOutReadDup); + CloseHandle(hNewInRead); + CloseHandle(hNewInWriteDup); + + 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; + + 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; + + /* We don't want to go about leaking handles, so store + * the one we cannot close now, closing the rest + */ + if (nFlags & MSVCRT__IOREAD) + { + CloseHandle(hNewInRead); + CloseHandle(hNewInWriteDup); + POPEN_Files[nPos].hStream = hNewOutWrite; + } + else + { + CloseHandle(hNewOutWrite); + CloseHandle(hNewOutReadDup); + POPEN_Files[nPos].hStream = hNewInRead; + } + } + else + { + if (fProcess) + { + MSVCRT_fclose(fProcess); + fProcess = NULL; + } + TerminateProcess(piInfo.hProcess, 0); + CloseHandle(hNewOutWrite); + CloseHandle(hNewOutReadDup); + CloseHandle(hNewInRead); + CloseHandle(hNewInWriteDup); + + 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 + */ + SetStdHandle(STD_INPUT_HANDLE, hOrigIn); + SetStdHandle(STD_OUTPUT_HANDLE, hOrigOut); + MSVCRT_free(szExec); + + return fProcess; +} + +/********************************************************************* + * POPEN_LPCWSTRToLPSTR (internal) + * + * Description + * Convert a Unicode string to ASCII + */ +static CHAR *POPEN_LPCWSTRToLPSTR(LPCWSTR wszIn) +{ + INT nIn, nOut; + CHAR *szOut = NULL; + + TRACE("(wszIn == %s)\n", debugstr_w(wszIn)); + + nIn = strlenW(wszIn); + nOut = WideCharToMultiByte(CP_ACP, 0, wszIn, nIn, NULL, 0, NULL, NULL); + if ((szOut = (CHAR *)MSVCRT_malloc((nOut+1)*sizeof(CHAR)))) + { + WideCharToMultiByte(CP_ACP, 0, wszIn, nIn, szOut, nOut+1, NULL, NULL); + szOut[nOut] = '\0'; + } + else + TRACE("Unable to allocate memory for Unicode to ASCII conversion\n"); + + return szOut; +} + +/********************************************************************* + * 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; + + 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; + } + + if (!(szCommand = POPEN_LPCWSTRToLPSTR(wszCommand))) + { + if (!(szMode = POPEN_LPCWSTRToLPSTR(wszMode))) + { + fProcess = MSVCRT_popen(szCommand, szMode); + MSVCRT_free(szMode); + } + MSVCRT_free(szCommand); + } + + 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 and the prcess stream + * handles as to make sure we are not leaking anything + */ + if (!CloseHandle(POPEN_Files[nPos].hProcess)) + TRACE("Unable to close the process handle\n"); + if (!CloseHandle(POPEN_Files[nPos].hStream)) + TRACE("Unable to close the process stream handle\n"); + } + return nRet; +} diff -aurN msvcrt-A03/dlls/msvcrt/msvcrt.spec msvcrt-A04/dlls/msvcrt/msvcrt.spec --- msvcrt-A03/dlls/msvcrt/msvcrt.spec 2002-10-31 22:04:53.000000000 +0000 +++ msvcrt-A04/dlls/msvcrt/msvcrt.spec 2002-11-02 22:57:24.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