- improved os.popen support for windows, based on win32pipe

by Bill Tutt.

  note: to run this on Windows 95/98, you need to have the
  w9xpopen.exe helper in the same directory as the python DLL.
This commit is contained in:
Fredrik Lundh 2000-07-09 14:49:51 +00:00
parent ce81d59c0c
commit ffb9c770f8
1 changed files with 462 additions and 6 deletions

View File

@ -222,6 +222,7 @@ extern int lstat(const char *, struct stat *);
#include <direct.h>
#include <io.h>
#include <process.h>
#define WINDOWS_LEAN_AND_MEAN
#include <windows.h>
#ifdef MS_WIN32
#define popen _popen
@ -353,6 +354,18 @@ posix_error_with_filename(char* name)
return PyErr_SetFromErrnoWithFilename(PyExc_OSError, name);
}
#ifdef MS_WIN32
static PyObject *
win32_error(char* function, char* filename)
{
/* XXX this could be improved */
errno = GetLastError();
if (filename)
return PyErr_SetFromErrnoWithFilename(PyExc_OSError, filename);
else
return PyErr_SetFromErrno(PyExc_OSError);
}
#endif
#if defined(PYOS_OS2)
/**********************************************************************
@ -845,7 +858,7 @@ posix_listdir(PyObject *self, PyObject *args)
errno = GetLastError();
if (errno == ERROR_FILE_NOT_FOUND)
return PyList_New(0);
return posix_error_with_filename(name);
return win32_error("FindFirstFile", name);
}
do {
if (FileData.cFileName[0] == '.' &&
@ -868,10 +881,8 @@ posix_listdir(PyObject *self, PyObject *args)
Py_DECREF(v);
} while (FindNextFile(hFindFile, &FileData) == TRUE);
if (FindClose(hFindFile) == FALSE) {
errno = GetLastError();
return posix_error_with_filename(name);
}
if (FindClose(hFindFile) == FALSE)
return win32_error("FindClose", name);
return d;
@ -2108,6 +2119,446 @@ posix_popen(PyObject *self, PyObject *args)
return f;
}
#elif defined(MS_WIN32)
/*
* Portable 'popen' replacement for Win32.
*
* Written by Bill Tutt <billtut@microsoft.com>. Minor tweaks
* and 2.0 integration by Fredrik Lundh <fredrik@pythonware.com>
*/
#include <malloc.h>
#include <io.h>
#include <fcntl.h>
/* These tell _PyPopen() wether to return 1, 2, or 3 file objects. */
#define POPEN_1 1
#define POPEN_2 2
#define POPEN_3 3
#define POPEN_4 4
static PyObject *_PyPopen(char *, int, int);
/* popen that works from a GUI.
*
* The result of this function is a pipe (file) connected to the
* processes stdin or stdout, depending on the requested mode.
*/
static PyObject *
posix_popen(PyObject *self, PyObject *args)
{
int bufsize = -1;
PyObject *f, *s;
int tm = 0;
char *cmdstring;
char *mode = "r";
if (!PyArg_ParseTuple(args, "s|s:popen", &cmdstring, &mode))
return NULL;
s = PyTuple_New(0);
if (*mode == 'r')
tm = _O_RDONLY;
else if (*mode != 'w') {
PyErr_SetString(PyExc_ValueError, "mode must be 'r' or 'w'");
return NULL;
} else
tm = _O_WRONLY;
if (*(mode+1) == 't')
f = _PyPopen(cmdstring, tm | _O_TEXT , POPEN_1);
else if (*(mode+1) == 'b')
f = _PyPopen(cmdstring, tm | _O_BINARY , POPEN_1);
else
f = _PyPopen(cmdstring, tm | _O_TEXT, POPEN_1);
return f;
}
/* Variation on win32pipe.popen
*
* The result of this function is a pipe (file) connected to the
* process's stdin, and a pipe connected to the process's stdout.
*/
static PyObject *
win32_popen2(PyObject *self, PyObject *args)
{
PyObject *f;
int tm=0;
char *cmdstring;
char *mode = "t";
if (!PyArg_ParseTuple(args, "s|s:popen2", &cmdstring, &mode))
return NULL;
if (*mode == 't')
tm = _O_TEXT;
else if (*mode != 'b') {
PyErr_SetString(PyExc_ValueError, "mode must be 't' or 'b'");
return NULL;
} else
tm = _O_BINARY;
f = _PyPopen(cmdstring, tm , POPEN_2);
return f;
}
/*
* Variation on <om win32pipe.popen>
* The result of this function is 3 pipes - the process's stdin,
* stdout and stderr
*
*/
static PyObject *
win32_popen3(PyObject *self, PyObject *args)
{
PyObject *f;
int tm = 0;
char *cmdstring;
char *mode = "t";
if (!PyArg_ParseTuple(args, "s|s:Popen3", &cmdstring, &mode))
return NULL;
if (*mode == 't')
tm = _O_TEXT;
else if (*mode != 'b') {
PyErr_SetString(PyExc_ValueError, "mode must be 't' or 'b'");
return NULL;
} else
tm = _O_BINARY;
f = _PyPopen(cmdstring, tm, POPEN_3);
return f;
}
/*
* Variation on win32pipe.popen
*
* The result of this function is 2 pipes - the processes stdin,
* and stdout+stderr combined as a single pipe.
*/
static PyObject *
win32_popen4(PyObject *self, PyObject *args)
{
PyObject *f;
int tm = 0;
char *cmdstring;
char *mode = "t";
if (!PyArg_ParseTuple(args, "s|s:popen4", &cmdstring, &mode))
return NULL;
if (*mode == 't')
tm = _O_TEXT;
else if (*mode != 'b') {
PyErr_SetString(PyExc_ValueError, "mode must be 't' or 'b'");
return NULL;
} else
tm = _O_BINARY;
f = _PyPopen(cmdstring, tm , POPEN_4);
return f;
}
static int
_PyPopenCreateProcess(char *cmdstring,
HANDLE hStdin,
HANDLE hStdout,
HANDLE hStderr)
{
PROCESS_INFORMATION piProcInfo;
STARTUPINFO siStartInfo;
char *s1,*s2, *s3 = " /c ";
const char *szConsoleSpawn = "w9xpopen.exe \"";
int i;
int x;
if (i = GetEnvironmentVariable("COMSPEC",NULL,0)) {
s1 = (char *)_alloca(i);
if (!(x = GetEnvironmentVariable("COMSPEC", s1, i)))
return x;
if (GetVersion() < 0x80000000) {
/*
* NT/2000
*/
x = i + strlen(s3) + strlen(cmdstring) + 1;
s2 = (char *)_alloca(x);
ZeroMemory(s2, x);
sprintf(s2, "%s%s%s", s1, s3, cmdstring);
}
else {
/*
* Oh gag, we're on Win9x. Use the workaround listed in
* KB: Q150956
*/
char modulepath[256];
GetModuleFileName(NULL, modulepath, sizeof(modulepath));
for (i = x = 0; modulepath[i]; i++)
if (modulepath[i] == '\\')
x = i+1;
modulepath[x] = '\0';
x = i + strlen(s3) + strlen(cmdstring) + 1 +
strlen(modulepath) +
strlen(szConsoleSpawn) + 1;
s2 = (char *)_alloca(x);
ZeroMemory(s2, x);
sprintf(
s2,
"%s%s%s%s%s\"",
modulepath,
szConsoleSpawn,
s1,
s3,
cmdstring);
}
}
/* Could be an else here to try cmd.exe / command.com in the path
Now we'll just error out.. */
else
return -1;
ZeroMemory(&siStartInfo, sizeof(STARTUPINFO));
siStartInfo.cb = sizeof(STARTUPINFO);
siStartInfo.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
siStartInfo.hStdInput = hStdin;
siStartInfo.hStdOutput = hStdout;
siStartInfo.hStdError = hStderr;
siStartInfo.wShowWindow = SW_HIDE;
if (CreateProcess(NULL,
s2,
NULL,
NULL,
TRUE,
CREATE_NEW_CONSOLE,
NULL,
NULL,
&siStartInfo,
&piProcInfo) ) {
/* Close the handles now so anyone waiting is woken. */
CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread);
return TRUE;
}
return FALSE;
}
/* The following code is based off of KB: Q190351 */
static PyObject *
_PyPopen(char *cmdstring, int mode, int n)
{
HANDLE hChildStdinRd, hChildStdinWr, hChildStdoutRd, hChildStdoutWr,
hChildStderrRd, hChildStderrWr, hChildStdinWrDup, hChildStdoutRdDup,
hChildStderrRdDup; /* hChildStdoutWrDup; */
SECURITY_ATTRIBUTES saAttr;
BOOL fSuccess;
int fd1, fd2, fd3;
FILE *f1, *f2, *f3;
PyObject *f;
saAttr.nLength = sizeof(SECURITY_ATTRIBUTES);
saAttr.bInheritHandle = TRUE;
saAttr.lpSecurityDescriptor = NULL;
if (!CreatePipe(&hChildStdinRd, &hChildStdinWr, &saAttr, 0))
return win32_error("CreatePipe", NULL);
/* Create new output read handle and the input write handle. Set
* the inheritance properties to FALSE. Otherwise, the child inherits
* the these handles; resulting in non-closeable handles to the pipes
* being created. */
fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdinWr,
GetCurrentProcess(), &hChildStdinWrDup, 0,
FALSE,
DUPLICATE_SAME_ACCESS);
if (!fSuccess)
return win32_error("DuplicateHandle", NULL);
/* Close the inheritable version of ChildStdin
that we're using. */
CloseHandle(hChildStdinWr);
if (!CreatePipe(&hChildStdoutRd, &hChildStdoutWr, &saAttr, 0))
return win32_error("CreatePipe", NULL);
fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStdoutRd,
GetCurrentProcess(), &hChildStdoutRdDup, 0,
FALSE,
DUPLICATE_SAME_ACCESS);
if (!fSuccess)
return win32_error("DuplicateHandle", NULL);
/* Close the inheritable version of ChildStdout
that we're using. */
CloseHandle(hChildStdoutRd);
if (n != POPEN_4) {
if (!CreatePipe(&hChildStderrRd, &hChildStderrWr, &saAttr, 0))
return win32_error("CreatePipe", NULL);
fSuccess = DuplicateHandle(GetCurrentProcess(), hChildStderrRd,
GetCurrentProcess(), &hChildStderrRdDup, 0,
FALSE,
DUPLICATE_SAME_ACCESS);
if (!fSuccess)
return win32_error("DuplicateHandle", NULL);
/* Close the inheritable version of ChildStdErr that we're using. */
CloseHandle(hChildStderrRd);
}
switch (n) {
case POPEN_1:
switch (mode & (_O_RDONLY | _O_TEXT | _O_BINARY | _O_WRONLY)) {
case _O_WRONLY | _O_TEXT:
/* Case for writing to child Stdin in text mode. */
fd1 = _open_osfhandle((long)hChildStdinWrDup, mode);
f1 = _fdopen(fd1, "w");
f = PyFile_FromFile(f1, cmdstring, "w", fclose);
PyFile_SetBufSize(f, 0);
/* We don't care about these pipes anymore, so close them. */
CloseHandle(hChildStdoutRdDup);
CloseHandle(hChildStderrRdDup);
break;
case _O_RDONLY | _O_TEXT:
/* Case for reading from child Stdout in text mode. */
fd1 = _open_osfhandle((long)hChildStdoutRdDup, mode);
f1 = _fdopen(fd1, "r");
f = PyFile_FromFile(f1, cmdstring, "r", fclose);
PyFile_SetBufSize(f, 0);
/* We don't care about these pipes anymore, so close them. */
CloseHandle(hChildStdinWrDup);
CloseHandle(hChildStderrRdDup);
break;
case _O_RDONLY | _O_BINARY:
/* Case for readinig from child Stdout in binary mode. */
fd1 = _open_osfhandle((long)hChildStdoutRdDup, mode);
f1 = _fdopen(fd1, "rb");
f = PyFile_FromFile(f1, cmdstring, "rb", fclose);
PyFile_SetBufSize(f, 0);
/* We don't care about these pipes anymore, so close them. */
CloseHandle(hChildStdinWrDup);
CloseHandle(hChildStderrRdDup);
break;
case _O_WRONLY | _O_BINARY:
/* Case for writing to child Stdin in binary mode. */
fd1 = _open_osfhandle((long)hChildStdinWrDup, mode);
f1 = _fdopen(fd1, "wb");
f = PyFile_FromFile(f1, cmdstring, "wb", fclose);
PyFile_SetBufSize(f, 0);
/* We don't care about these pipes anymore, so close them. */
CloseHandle(hChildStdoutRdDup);
CloseHandle(hChildStderrRdDup);
break;
}
break;
case POPEN_2:
case POPEN_4:
{
char *m1, *m2;
PyObject *p1, *p2;
if (mode && _O_TEXT) {
m1 = "r";
m2 = "w";
} else {
m1 = "rb";
m2 = "wb";
}
fd1 = _open_osfhandle((long)hChildStdinWrDup, mode);
f1 = _fdopen(fd1, m2);
fd2 = _open_osfhandle((long)hChildStdoutRdDup, mode);
f2 = _fdopen(fd2, m1);
p1 = PyFile_FromFile(f1, cmdstring, m2, fclose);
PyFile_SetBufSize(p1, 0);
p2 = PyFile_FromFile(f2, cmdstring, m1, fclose);
PyFile_SetBufSize(p2, 0);
if (n != 4)
CloseHandle(hChildStderrRdDup);
f = Py_BuildValue("OO",p1,p2);
break;
}
case POPEN_3:
{
char *m1, *m2;
PyObject *p1, *p2, *p3;
if (mode && _O_TEXT) {
m1 = "r";
m2 = "w";
} else {
m1 = "rb";
m2 = "wb";
}
fd1 = _open_osfhandle((long)hChildStdinWrDup, mode);
f1 = _fdopen(fd1, m2);
fd2 = _open_osfhandle((long)hChildStdoutRdDup, mode);
f2 = _fdopen(fd2, m1);
fd3 = _open_osfhandle((long)hChildStderrRdDup, mode);
f3 = _fdopen(fd3, m1);
p1 = PyFile_FromFile(f1, cmdstring, m2, fclose);
p2 = PyFile_FromFile(f2, cmdstring, m1, fclose);
p3 = PyFile_FromFile(f3, cmdstring, m1, fclose);
PyFile_SetBufSize(p1, 0);
PyFile_SetBufSize(p2, 0);
PyFile_SetBufSize(p3, 0);
f = Py_BuildValue("OOO",p1,p2,p3);
break;
}
}
if (n == POPEN_4) {
if (!_PyPopenCreateProcess(cmdstring,
hChildStdinRd,
hChildStdoutWr,
hChildStdoutWr))
return win32_error("CreateProcess", NULL);
}
else {
if (!_PyPopenCreateProcess(cmdstring,
hChildStdinRd,
hChildStdoutWr,
hChildStderrWr))
return win32_error("CreateProcess", NULL);
}
/* Child is launched. Close the parents copy of those pipe
* handles that only the child should have open. You need to
* make sure that no handles to the write end of the output pipe
* are maintained in this process or else the pipe will not close
* when the child process exits and the ReadFile will hang. */
if (!CloseHandle(hChildStdinRd))
return win32_error("CloseHandle", NULL);
if (!CloseHandle(hChildStdoutWr))
return win32_error("CloseHandle", NULL);
if ((n != 4) && (!CloseHandle(hChildStderrWr)))
return win32_error("CloseHandle", NULL);
return f;
}
#else
static PyObject *
posix_popen(PyObject *self, PyObject *args)
@ -2728,7 +3179,7 @@ posix_pipe(PyObject *self, PyObject *args)
ok = CreatePipe(&read, &write, NULL, 0);
Py_END_ALLOW_THREADS
if (!ok)
return posix_error();
return win32_error("CreatePipe", NULL);
read_fd = _open_osfhandle((intptr_t)read, 0);
write_fd = _open_osfhandle((intptr_t)write, 1);
return Py_BuildValue("(ii)", read_fd, write_fd);
@ -4423,6 +4874,11 @@ static PyMethodDef posix_methods[] = {
#endif /* HAVE_PLOCK */
#ifdef HAVE_POPEN
{"popen", posix_popen, METH_VARARGS, posix_popen__doc__},
#ifdef MS_WIN32
{"popen2", win32_popen2, METH_VARARGS},
{"popen3", win32_popen3, METH_VARARGS},
{"popen4", win32_popen4, METH_VARARGS},
#endif
#endif /* HAVE_POPEN */
#ifdef HAVE_SETUID
{"setuid", posix_setuid, METH_VARARGS, posix_setuid__doc__},