summaryrefslogtreecommitdiffstats
path: root/Modules/_posixsubprocess.c
diff options
context:
space:
mode:
Diffstat (limited to 'Modules/_posixsubprocess.c')
-rw-r--r--Modules/_posixsubprocess.c115
1 files changed, 74 insertions, 41 deletions
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index 19ca31f..0196ca2 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -47,7 +47,9 @@ static int
_enable_gc(PyObject *gc_module)
{
PyObject *result;
- result = PyObject_CallMethod(gc_module, "enable", NULL);
+ _Py_IDENTIFIER(enable);
+
+ result = _PyObject_CallMethodId(gc_module, &PyId_enable, NULL);
if (result == NULL)
return 1;
Py_DECREF(result);
@@ -78,7 +80,7 @@ _pos_int_from_ascii(char *name)
* that properly supports /dev/fd.
*/
static int
-_is_fdescfs_mounted_on_dev_fd()
+_is_fdescfs_mounted_on_dev_fd(void)
{
struct stat dev_stat;
struct stat dev_fd_stat;
@@ -174,17 +176,11 @@ _close_fds_by_brute_force(int start_fd, int end_fd, PyObject *py_fds_to_keep)
* This structure is very old and stable: It will not change unless the kernel
* chooses to break compatibility with all existing binaries. Highly Unlikely.
*/
-struct linux_dirent {
-#if defined(__x86_64__) && defined(__ILP32__)
- /* Support the wacky x32 ABI (fake 32-bit userspace speaking to x86_64
- * kernel interfaces) - https://sites.google.com/site/x32abi/ */
+struct linux_dirent64 {
unsigned long long d_ino;
- unsigned long long d_off;
-#else
- unsigned long d_ino; /* Inode number */
- unsigned long d_off; /* Offset to next linux_dirent */
-#endif
+ long long d_off;
unsigned short d_reclen; /* Length of this linux_dirent */
+ unsigned char d_type;
char d_name[256]; /* Filename (null-terminated) */
};
@@ -226,16 +222,16 @@ _close_open_fd_range_safe(int start_fd, int end_fd, PyObject* py_fds_to_keep)
_close_fds_by_brute_force(start_fd, end_fd, py_fds_to_keep);
return;
} else {
- char buffer[sizeof(struct linux_dirent)];
+ char buffer[sizeof(struct linux_dirent64)];
int bytes;
- while ((bytes = syscall(SYS_getdents, fd_dir_fd,
- (struct linux_dirent *)buffer,
+ while ((bytes = syscall(SYS_getdents64, fd_dir_fd,
+ (struct linux_dirent64 *)buffer,
sizeof(buffer))) > 0) {
- struct linux_dirent *entry;
+ struct linux_dirent64 *entry;
int offset;
for (offset = 0; offset < bytes; offset += entry->d_reclen) {
int fd;
- entry = (struct linux_dirent *)(buffer + offset);
+ entry = (struct linux_dirent64 *)(buffer + offset);
if ((fd = _pos_int_from_ascii(entry->d_name)) < 0)
continue; /* Not a number. */
if (fd != fd_dir_fd && fd >= start_fd && fd < end_fd &&
@@ -416,17 +412,6 @@ child_exec(char *const exec_array[],
POSIX_CALL(close(errwrite));
}
- if (close_fds) {
- int local_max_fd = max_fd;
-#if defined(__NetBSD__)
- local_max_fd = fcntl(0, F_MAXFD);
- if (local_max_fd < 0)
- local_max_fd = max_fd;
-#endif
- /* TODO HP-UX could use pstat_getproc() if anyone cares about it. */
- _close_open_fd_range(3, local_max_fd, py_fds_to_keep);
- }
-
if (cwd)
POSIX_CALL(chdir(cwd));
@@ -455,6 +440,18 @@ child_exec(char *const exec_array[],
/* Py_DECREF(result); - We're about to exec so why bother? */
}
+ /* close FDs after executing preexec_fn, which might open FDs */
+ if (close_fds) {
+ int local_max_fd = max_fd;
+#if defined(__NetBSD__)
+ local_max_fd = fcntl(0, F_MAXFD);
+ if (local_max_fd < 0)
+ local_max_fd = max_fd;
+#endif
+ /* TODO HP-UX could use pstat_getproc() if anyone cares about it. */
+ _close_open_fd_range(3, local_max_fd, py_fds_to_keep);
+ }
+
/* This loop matches the Lib/os.py _execvpe()'s PATH search when */
/* given the executable_list generated by Lib/subprocess.py. */
saved_errno = 0;
@@ -506,7 +503,7 @@ static PyObject *
subprocess_fork_exec(PyObject* self, PyObject *args)
{
PyObject *gc_module = NULL;
- PyObject *executable_list, *py_close_fds, *py_fds_to_keep;
+ PyObject *executable_list, *py_fds_to_keep;
PyObject *env_list, *preexec_fn;
PyObject *process_args, *converted_args = NULL, *fast_args = NULL;
PyObject *preexec_fn_args_tuple = NULL;
@@ -521,17 +518,14 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
Py_ssize_t arg_num;
if (!PyArg_ParseTuple(
- args, "OOOOOOiiiiiiiiiiO:fork_exec",
- &process_args, &executable_list, &py_close_fds, &py_fds_to_keep,
+ args, "OOpOOOiiiiiiiiiiO:fork_exec",
+ &process_args, &executable_list, &close_fds, &py_fds_to_keep,
&cwd_obj, &env_list,
&p2cread, &p2cwrite, &c2pread, &c2pwrite,
&errread, &errwrite, &errpipe_read, &errpipe_write,
&restore_signals, &call_setsid, &preexec_fn))
return NULL;
- close_fds = PyObject_IsTrue(py_close_fds);
- if (close_fds < 0)
- return NULL;
if (close_fds && errpipe_write < 3) { /* precondition */
PyErr_SetString(PyExc_ValueError, "errpipe_write must be >= 3");
return NULL;
@@ -548,10 +542,13 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
/* We need to call gc.disable() when we'll be calling preexec_fn */
if (preexec_fn != Py_None) {
PyObject *result;
+ _Py_IDENTIFIER(isenabled);
+ _Py_IDENTIFIER(disable);
+
gc_module = PyImport_ImportModule("gc");
if (gc_module == NULL)
return NULL;
- result = PyObject_CallMethod(gc_module, "isenabled", NULL);
+ result = _PyObject_CallMethodId(gc_module, &PyId_isenabled, NULL);
if (result == NULL) {
Py_DECREF(gc_module);
return NULL;
@@ -562,7 +559,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
Py_DECREF(gc_module);
return NULL;
}
- result = PyObject_CallMethod(gc_module, "disable", NULL);
+ result = _PyObject_CallMethodId(gc_module, &PyId_disable, NULL);
if (result == NULL) {
Py_DECREF(gc_module);
return NULL;
@@ -726,26 +723,24 @@ Raises: Only on an error in the parent process.\n\
PyDoc_STRVAR(subprocess_cloexec_pipe_doc,
"cloexec_pipe() -> (read_end, write_end)\n\n\
-Create a pipe whose ends have the cloexec flag set.");
+Create a pipe whose ends have the cloexec flag set; write_end will be >= 3.");
static PyObject *
subprocess_cloexec_pipe(PyObject *self, PyObject *noargs)
{
int fds[2];
- int res;
+ int res, saved_errno;
+ long oldflags;
#ifdef HAVE_PIPE2
Py_BEGIN_ALLOW_THREADS
res = pipe2(fds, O_CLOEXEC);
Py_END_ALLOW_THREADS
if (res != 0 && errno == ENOSYS)
{
- {
#endif
/* We hold the GIL which offers some protection from other code calling
* fork() before the CLOEXEC flags have been set but we can't guarantee
* anything without pipe2(). */
- long oldflags;
-
res = pipe(fds);
if (res == 0) {
@@ -762,9 +757,47 @@ subprocess_cloexec_pipe(PyObject *self, PyObject *noargs)
if (res == 0)
res = fcntl(fds[1], F_SETFD, oldflags | FD_CLOEXEC);
#ifdef HAVE_PIPE2
- }
}
#endif
+ if (res == 0 && fds[1] < 3) {
+ /* We always want the write end of the pipe to avoid fds 0, 1 and 2
+ * as our child may claim those for stdio connections. */
+ int write_fd = fds[1];
+ int fds_to_close[3] = {-1, -1, -1};
+ int fds_to_close_idx = 0;
+#ifdef F_DUPFD_CLOEXEC
+ fds_to_close[fds_to_close_idx++] = write_fd;
+ write_fd = fcntl(write_fd, F_DUPFD_CLOEXEC, 3);
+ if (write_fd < 0) /* We don't support F_DUPFD_CLOEXEC / other error */
+#endif
+ {
+ /* Use dup a few times until we get a desirable fd. */
+ for (; fds_to_close_idx < 3; ++fds_to_close_idx) {
+ fds_to_close[fds_to_close_idx] = write_fd;
+ write_fd = dup(write_fd);
+ if (write_fd >= 3)
+ break;
+ /* We may dup a few extra times if it returns an error but
+ * that is okay. Repeat calls should return the same error. */
+ }
+ if (write_fd < 0) res = write_fd;
+ if (res == 0) {
+ oldflags = fcntl(write_fd, F_GETFD, 0);
+ if (oldflags < 0) res = oldflags;
+ if (res == 0)
+ res = fcntl(write_fd, F_SETFD, oldflags | FD_CLOEXEC);
+ }
+ }
+ saved_errno = errno;
+ /* Close fds we tried for the write end that were too low. */
+ for (fds_to_close_idx=0; fds_to_close_idx < 3; ++fds_to_close_idx) {
+ int temp_fd = fds_to_close[fds_to_close_idx];
+ while (temp_fd >= 0 && close(temp_fd) < 0 && errno == EINTR);
+ }
+ errno = saved_errno; /* report dup or fcntl errors, not close. */
+ fds[1] = write_fd;
+ } /* end if write fd was too small */
+
if (res != 0)
return PyErr_SetFromErrno(PyExc_OSError);
return Py_BuildValue("(ii)", fds[0], fds[1]);