@@ -2117,6 +2117,7 @@ posix_popen(PyObject *self, PyObject *args)
21172117 *
21182118 * Written by Bill Tutt <[email protected] >. Minor tweaks 21192119 * and 2.0 integration by Fredrik Lundh <[email protected] > 2120+ * Return code handling by David Bolen.
21202121 */
21212122
21222123#include <malloc.h>
@@ -2130,6 +2131,15 @@ posix_popen(PyObject *self, PyObject *args)
21302131#define POPEN_4 4
21312132
21322133static PyObject * _PyPopen (char * , int , int );
2134+ static int _PyPclose (FILE * file );
2135+
2136+ /*
2137+ * Internal dictionary mapping popen* file pointers to process handles,
2138+ * in order to maintain a link to the process handle until the file is
2139+ * closed, at which point the process exit code is returned to the caller.
2140+ */
2141+ static PyObject * _PyPopenProcs = NULL ;
2142+
21332143
21342144/* popen that works from a GUI.
21352145 *
@@ -2285,7 +2295,7 @@ win32_popen4(PyObject *self, PyObject *args)
22852295}
22862296
22872297static int
2288- _PyPopenCreateProcess (char * cmdstring ,
2298+ _PyPopenCreateProcess (char * cmdstring , FILE * file ,
22892299 HANDLE hStdin ,
22902300 HANDLE hStdout ,
22912301 HANDLE hStderr )
@@ -2361,8 +2371,28 @@ _PyPopenCreateProcess(char *cmdstring,
23612371 & siStartInfo ,
23622372 & piProcInfo ) ) {
23632373 /* Close the handles now so anyone waiting is woken. */
2364- CloseHandle (piProcInfo .hProcess );
23652374 CloseHandle (piProcInfo .hThread );
2375+
2376+ /*
2377+ * Try to insert our process handle into the internal
2378+ * dictionary so we can find it later when trying
2379+ * to close this file.
2380+ */
2381+ if (!_PyPopenProcs )
2382+ _PyPopenProcs = PyDict_New ();
2383+ if (_PyPopenProcs ) {
2384+ PyObject * hProcessObj , * fileObj ;
2385+
2386+ hProcessObj = PyLong_FromVoidPtr (piProcInfo .hProcess );
2387+ fileObj = PyLong_FromVoidPtr (file );
2388+
2389+ if (!hProcessObj || !fileObj ||
2390+ PyDict_SetItem (_PyPopenProcs ,
2391+ fileObj , hProcessObj ) < 0 ) {
2392+ /* Insert failure - close handle to prevent leak */
2393+ CloseHandle (piProcInfo .hProcess );
2394+ }
2395+ }
23662396 return TRUE;
23672397 }
23682398 return FALSE;
@@ -2439,7 +2469,7 @@ _PyPopen(char *cmdstring, int mode, int n)
24392469 /* Case for writing to child Stdin in text mode. */
24402470 fd1 = _open_osfhandle ((long )hChildStdinWrDup , mode );
24412471 f1 = _fdopen (fd1 , "w" );
2442- f = PyFile_FromFile (f1 , cmdstring , "w" , fclose );
2472+ f = PyFile_FromFile (f1 , cmdstring , "w" , _PyPclose );
24432473 PyFile_SetBufSize (f , 0 );
24442474 /* We don't care about these pipes anymore, so close them. */
24452475 CloseHandle (hChildStdoutRdDup );
@@ -2450,7 +2480,7 @@ _PyPopen(char *cmdstring, int mode, int n)
24502480 /* Case for reading from child Stdout in text mode. */
24512481 fd1 = _open_osfhandle ((long )hChildStdoutRdDup , mode );
24522482 f1 = _fdopen (fd1 , "r" );
2453- f = PyFile_FromFile (f1 , cmdstring , "r" , fclose );
2483+ f = PyFile_FromFile (f1 , cmdstring , "r" , _PyPclose );
24542484 PyFile_SetBufSize (f , 0 );
24552485 /* We don't care about these pipes anymore, so close them. */
24562486 CloseHandle (hChildStdinWrDup );
@@ -2461,7 +2491,7 @@ _PyPopen(char *cmdstring, int mode, int n)
24612491 /* Case for readinig from child Stdout in binary mode. */
24622492 fd1 = _open_osfhandle ((long )hChildStdoutRdDup , mode );
24632493 f1 = _fdopen (fd1 , "rb" );
2464- f = PyFile_FromFile (f1 , cmdstring , "rb" , fclose );
2494+ f = PyFile_FromFile (f1 , cmdstring , "rb" , _PyPclose );
24652495 PyFile_SetBufSize (f , 0 );
24662496 /* We don't care about these pipes anymore, so close them. */
24672497 CloseHandle (hChildStdinWrDup );
@@ -2472,7 +2502,7 @@ _PyPopen(char *cmdstring, int mode, int n)
24722502 /* Case for writing to child Stdin in binary mode. */
24732503 fd1 = _open_osfhandle ((long )hChildStdinWrDup , mode );
24742504 f1 = _fdopen (fd1 , "wb" );
2475- f = PyFile_FromFile (f1 , cmdstring , "wb" , fclose );
2505+ f = PyFile_FromFile (f1 , cmdstring , "wb" , _PyPclose );
24762506 PyFile_SetBufSize (f , 0 );
24772507 /* We don't care about these pipes anymore, so close them. */
24782508 CloseHandle (hChildStdoutRdDup );
@@ -2499,7 +2529,7 @@ _PyPopen(char *cmdstring, int mode, int n)
24992529 f1 = _fdopen (fd1 , m2 );
25002530 fd2 = _open_osfhandle ((long )hChildStdoutRdDup , mode );
25012531 f2 = _fdopen (fd2 , m1 );
2502- p1 = PyFile_FromFile (f1 , cmdstring , m2 , fclose );
2532+ p1 = PyFile_FromFile (f1 , cmdstring , m2 , _PyPclose );
25032533 PyFile_SetBufSize (p1 , 0 );
25042534 p2 = PyFile_FromFile (f2 , cmdstring , m1 , fclose );
25052535 PyFile_SetBufSize (p2 , 0 );
@@ -2530,7 +2560,7 @@ _PyPopen(char *cmdstring, int mode, int n)
25302560 f2 = _fdopen (fd2 , m1 );
25312561 fd3 = _open_osfhandle ((long )hChildStderrRdDup , mode );
25322562 f3 = _fdopen (fd3 , m1 );
2533- p1 = PyFile_FromFile (f1 , cmdstring , m2 , fclose );
2563+ p1 = PyFile_FromFile (f1 , cmdstring , m2 , _PyPclose );
25342564 p2 = PyFile_FromFile (f2 , cmdstring , m1 , fclose );
25352565 p3 = PyFile_FromFile (f3 , cmdstring , m1 , fclose );
25362566 PyFile_SetBufSize (p1 , 0 );
@@ -2543,13 +2573,15 @@ _PyPopen(char *cmdstring, int mode, int n)
25432573
25442574 if (n == POPEN_4 ) {
25452575 if (!_PyPopenCreateProcess (cmdstring ,
2576+ f1 ,
25462577 hChildStdinRd ,
25472578 hChildStdoutWr ,
25482579 hChildStdoutWr ))
25492580 return win32_error ("CreateProcess" , NULL );
25502581 }
25512582 else {
25522583 if (!_PyPopenCreateProcess (cmdstring ,
2584+ f1 ,
25532585 hChildStdinRd ,
25542586 hChildStdoutWr ,
25552587 hChildStderrWr ))
@@ -2573,6 +2605,56 @@ _PyPopen(char *cmdstring, int mode, int n)
25732605
25742606 return f ;
25752607}
2608+
2609+ /*
2610+ * Wrapper for fclose() to use for popen* files, so we can retrieve the
2611+ * exit code for the child process and return as a result of the close.
2612+ */
2613+ static int _PyPclose (FILE * file )
2614+ {
2615+ int result = 0 ;
2616+ DWORD exit_code ;
2617+ HANDLE hProcess ;
2618+ PyObject * hProcessObj , * fileObj ;
2619+
2620+ if (_PyPopenProcs ) {
2621+ fileObj = PyLong_FromVoidPtr (file );
2622+ if (fileObj ) {
2623+ hProcessObj = PyDict_GetItem (_PyPopenProcs , fileObj );
2624+ if (hProcessObj ) {
2625+ hProcess = PyLong_AsVoidPtr (hProcessObj );
2626+ if (GetExitCodeProcess (hProcess , & exit_code )) {
2627+ /* Possible truncation here in 16-bit environments, but
2628+ * real exit codes are just the lower byte in any event.
2629+ */
2630+ result = exit_code ;
2631+ if (result == STILL_ACTIVE )
2632+ result = 0 ; /* Minimize confusion */
2633+ } else {
2634+ /* No good way to bubble up an error, so instead we just
2635+ * return the Windows last error shifted above standard
2636+ * exit codes. This will truncate in 16-bits but should
2637+ * be fine in 32 and at least distinguishes the problem.
2638+ */
2639+ result = (GetLastError () << 8 );
2640+ }
2641+
2642+ /* Free up the native handle at this point */
2643+ CloseHandle (hProcess );
2644+
2645+ /* Remove from dictionary and flush dictionary if empty */
2646+ PyDict_DelItem (_PyPopenProcs , fileObj );
2647+ if (PyDict_Size (_PyPopenProcs ) == 0 ) {
2648+ Py_DECREF (_PyPopenProcs );
2649+ _PyPopenProcs = NULL ;
2650+ }
2651+ } /* if hProcessObj */
2652+ } /* if fileObj */
2653+ } /* if _PyPopenProcs */
2654+
2655+ fclose (file );
2656+ return result ;
2657+ }
25762658#else
25772659static PyObject *
25782660posix_popen (PyObject * self , PyObject * args )
0 commit comments