Hi, I would appreciate comments on the following implementation of _popen/_wpopen/_pclose. For me there is one uncertainty and one issue: 1. Calling the popen function returns a FILE* which needs to be converted to a MSVCRT_FILE*. The apprach taken here is potentially not the best route but the only one I could think of. Is it suffiecient? 2. I only allow for one process to be open at any single time. As a first pass, it will problably do the job, I just need to find the most efficient approach to store all the open handles to allow the functions to do the concersions from MSVCRT_FILE to FILE as used in _pclose. Here it is as a first pass, please be gentle but keep the comments comming... License: LGPL Changelog: * dlls/msvcrt/process.c: Jaco Greeff <jaco@puxedo.org> - Implementation of the _popen/_wpopen/_pclose functions diff -aurN msvcrt-popen.orig/dlls/msvcrt/msvcrt.spec msvcrt-popen.new/dlls/msvcrt/msvcrt.spec --- msvcrt-popen.orig/dlls/msvcrt/msvcrt.spec Fri Nov 1 11:44:09 2002 +++ msvcrt-popen.new/dlls/msvcrt/msvcrt.spec Fri Nov 1 08:23:27 2002 @@ -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 diff -aurN msvcrt-popen.orig/dlls/msvcrt/process.c msvcrt-popen.new/dlls/msvcrt/process.c --- msvcrt-popen.orig/dlls/msvcrt/process.c Fri Nov 1 11:44:10 2002 +++ msvcrt-popen.new/dlls/msvcrt/process.c Fri Nov 1 11:57:41 2002 @@ -1,10 +1,11 @@ /* - * msvcrt.dll spawn/exec functions + * msvcrt.dll spawn/exec/popen functions * * Copyright 1996,1998 Marcus Meissner * 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 @@ -28,6 +29,7 @@ #include "config.h" #include <stdarg.h> +#include <stdio.h> #include "msvcrt.h" #include "msvcrt/errno.h" @@ -36,10 +38,45 @@ #include "msvcrt/stdlib.h" #include "msvcrt/string.h" +#include "msvcrt/stdio.h" +#include "wine/unicode.h" + #include "wine/debug.h" WINE_DEFAULT_DEBUG_CHANNEL(msvcrt); +#define POPEN_FLAG_READ 0x0001 +#define POPEN_FLAG_WRITE 0x0002 +#define POPEN_FLAG_TEXT 0x0004 +#define POPEN_FLAG_BINARY 0x0008 + +#define POPEN_MAX_FILES 10 +#define POPEN_WCMD_EXEC "wcmd /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; + +typedef struct +{ + FILE *fSysProcess; + MSVCRT_FILE *fProcess; + int fd; +} POPEN_FILE; + +/* FIXME: we can only spawn one process at a time presently, this + * needs to be extended to allow for any number. As a first pass, + * this might be good enough... + */ +static POPEN_FILE *pPOPEN_File = NULL; + /* FIXME: Check file extensions for app to run */ static const unsigned int EXE = 'e' << 16 | 'x' << 8 | 'e'; static const unsigned int BAT = 'b' << 16 | 'a' << 8 | 't'; @@ -469,4 +506,180 @@ MSVCRT__set_errno(err); return err; } +} + +/********************************************************************* + * POPEN_parseModeFlags (internal) + * + * Parses the flags passed to the _popne/_wpopen function and sets up + * the correct binary flags for usage via these commands + */ +INT POPEN_parseModeFlags(const CHAR *szMode) +{ + INT nFlags = 0; + while (szMode && *szMode) + { + switch (*szMode) + { + case 'r': + { + if (nFlags & POPEN_FLAG_WRITE) + WARN(": _popen: Cannot set both read and write open flags, ignoring read flag\n"); + else + nFlags = nFlags & POPEN_FLAG_READ; + break; + } + case 'w': + { + if (nFlags & POPEN_FLAG_READ) + WARN(": _popen: Cannot set both read and write open flags, ignoring write flag\n"); + else + nFlags = nFlags & POPEN_FLAG_WRITE; + break; + } + case 'b': + case 't': + { + FIXME(": _popen: %c mode flag not implemented, ignoring\n", *szMode); + break; + } + default: + { + WARN(": _popen: unknown mode flag %c, ignoring\n", *szMode); + break; + } + } + } + return nFlags; +} + +/********************************************************************* + * MSVCRT_popen (MSVCRT.@) + */ +MSVCRT_FILE *MSVCRT_popen(const CHAR *szCommand, const CHAR *szMode) +{ + MSVCRT_FILE *fProcess = NULL; + FILE *fIntl = NULL; + INT nFlags = 0; + CHAR *szExec = NULL; + + TRACE("(szCommand == %s, szMode == %s)\n", debugstr_a(szCommand), debugstr_a(szMode)); + + if (!szCommand || !szMode) + return NULL; + + nFlags = POPEN_parseModeFlags(szMode); + if (!(nFlags & (POPEN_FLAG_READ|POPEN_FLAG_WRITE))) + { + ERR("No open mode flag r or w specified\n"); + return NULL; + } + + /* _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, launch it instead... + */ + if ((szExec = (CHAR *)malloc(strlen(POPEN_WCMD_EXEC)+strlen(szCommand)+1))) + { + sprintf(szExec, "%s%s", POPEN_WCMD_EXEC, szCommand); + + /* convert from the specific FILE* to the MSVCRT_FILE*. We need + * to make doubly sure that in the process we are not leaking + * handles since we go from File -> fd -> MSVCRT_FILE. (The route + * back in _pclose is even more interresting which means that we + * need to keep track of the original FILE* so we can close it...) + * + * FIXME: We only have one available slot at present so we can only + * have one _popen command active at any time. + */ + if (!pPOPEN_File) + { + FIXME("Allowance for multiple _popen streams to be made\n"); + + if ((pPOPEN_File = (POPEN_FILE *)MSVCRT_malloc(sizeof(POPEN_FILE))) && + (fIntl = popen(szExec, (nFlags & POPEN_FLAG_READ) ? "r" : "w"))) + { + int fd = fileno(fIntl); + fProcess = _fdopen(fd, (nFlags & POPEN_FLAG_READ) ? "r" : "w"); + + pPOPEN_File->fSysProcess = fIntl; + pPOPEN_File->fProcess = fProcess; + pPOPEN_File->fd = fd; + } + else + { + if (pPOPEN_File) + { + MSVCRT_free(pPOPEN_File); + pPOPEN_File = NULL; + } + ERR("Execution of %s failed\n", debugstr_a(szExec)); + } + } + else + FIXME("Attempt to use multiple _popen streams, failing\n"); + MSVCRT_free(szExec); + } + + return fProcess; +} + +/********************************************************************* + * MSVCRT_wpopen (MSVCRT.@) + */ +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) + 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); + } + MSVCRT_free(szCommand); + } + + return fProcess; +} + +/********************************************************************* + * MSVCRT_pclose (MSVCRT.@) + */ +int MSVCRT_pclose(MSVCRT_FILE *fProcess) +{ + int nRet = -1; + + TRACE("(fProcess == %p)\n", fProcess); + + /* FIXME: single process time again, we must allow for multiple streams + * here and in the _popen function (read comments in _popen) + */ + if (pPOPEN_File && fProcess && pPOPEN_File->fProcess == fProcess) + { + nRet = pclose(pPOPEN_File->fSysProcess); + + /* FIXME: we need to make doubly sure that we are not leaking handles. Since + * all references refer to the same file, we should be safe. We are closing + * fProcess/pPOPEN_File->fProcess just to clean up the internal msvcrt stuff. + */ + MSVCRT_fclose(fProcess); + MSVCRT_free(pPOPEN_File); + pPOPEN_File = NULL; + } + else + ERR("%p is not a valid process handle\n", fProcess); + + return nRet; }