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