msvcrt-B03: _popen/_wpopen/_pclose

Jaco Greeff jaco at puxedo.org
Tue Nov 5 05:22:01 CST 2002


Updated to make sure that the A?? patches applies cleanly to current CVS.

License:
LGPL

Changelog:
* dlls/msvcrt/msvcrt.spec, dlls/msvcrt/file.c: Jaco Greeff <jaco at puxedo.org>
- Full implementation of the _popen/_wpopen and _pclose functions


-------------- next part --------------
diff -aurN msvcrt-B02/dlls/msvcrt/file.c msvcrt-B03/dlls/msvcrt/file.c
--- msvcrt-B02/dlls/msvcrt/file.c	Tue Nov  5 11:46:50 2002
+++ msvcrt-B03/dlls/msvcrt/file.c	Tue Nov  5 11:49:52 2002
@@ -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,25 @@
 #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
+
+/* If we cannot find the interpretedr pointed to by COMSPEC,
+ * this is the interpreter we will try to use
+ */
+#define POPEN_WCMD_EXEC     "wcmd.exe"
+
+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 +203,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 */
@@ -2288,4 +2318,424 @@
     res = MSVCRT_vwprintf(format, valist);
     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;
+}
+
+
+/*********************************************************************
+ *      POPEN_getInterpreterCmd (internal)
+ *
+ * Description
+ *     Finds an interpreter to use and combines this with the command
+ *     we are to execute via _popen. If COMSPEC is set an a valid
+ *     interpreter is found for it, it will be used, else it will
+ *     use POPEN_WCMD_EXEC
+ *
+ * Input
+ *     szCommand - Command to execute via the interpreter, as passed
+ *                 to _popen/_wpopen
+ *
+ * Output
+ *     The full command string or NULL on failure
+ */
+static CHAR *POPEN_getInterpreterCmd(const CHAR *szCommand)
+{
+    CHAR szInterpreter[1024+1];
+    CHAR *szExec = NULL;
+
+    /* Get the value of COMSPEC, if not available, fall back to
+     * our hard-coded default
+     */
+     if (!(GetEnvironmentVariableA("COMSPEC", szInterpreter, 1024)) ||
+         (GetFileAttributesA(szInterpreter) == -1))
+        snprintf(szInterpreter, 1024, "%s", POPEN_WCMD_EXEC);
+        
+     TRACE("Using interpreter == %s\n", szInterpreter);
+
+     /* _popen/_wpopen executes the required command via the command
+      * processor, so we will need to set the interpreter to execute
+      * and return, the "/c" flag
+      */
+     if ((szExec = (CHAR *)MSVCRT_malloc(strlen(szInterpreter)+strlen(" /c ")+strlen(szCommand)+1)))
+        sprintf(szExec, "%s /c %s", szInterpreter, szCommand);
+     else
+        TRACE("Unable to allocate memory for process command line\n");
+
+     return szExec;
+}
+
+
+/*********************************************************************
+ *      MSVCRT_popen (MSVCRT.@)
+ *
+ * Description
+ *     Opens a process, returning a FILE structure to allow reading
+ *     from or writing to the process.
+ *
+ * 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");
+
+
+    /* Try to get the system interpreter, if this fails,
+     * gracefully clean up and exit
+     */
+    if (!(szExec = POPEN_getInterpreterCmd(szCommand)))
+    {
+        SetStdHandle(STD_INPUT_HANDLE, hOrigIn);
+        SetStdHandle(STD_OUTPUT_HANDLE, hOrigOut);
+        CloseHandle(hNewOutWrite);
+        CloseHandle(hNewOutReadDup);
+        CloseHandle(hNewInRead);
+        CloseHandle(hNewInWriteDup);
+        return NULL;
+    }
+
+    /* 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-B02/dlls/msvcrt/msvcrt.spec msvcrt-B03/dlls/msvcrt/msvcrt.spec
--- msvcrt-B02/dlls/msvcrt/msvcrt.spec	Tue Nov  5 01:53:44 2002
+++ msvcrt-B03/dlls/msvcrt/msvcrt.spec	Tue Nov  5 11:49:35 2002
@@ -400,11 +400,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
@@ -534,7 +534,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


More information about the wine-patches mailing list