static int make_inheritable(PyObject *py_fds_to_keep, int errpipe_write) { Py_ssize_t i, len; len = PySequence_Length(py_fds_to_keep); for (i = 0; i < len; ++i) { PyObject* fdobj = PySequence_Fast_GET_ITEM(py_fds_to_keep, i); long fd = PyLong_AsLong(fdobj); assert(!PyErr_Occurred()); assert(0 <= fd && fd <= INT_MAX); if (fd == errpipe_write) { /* errpipe_write is part of py_fds_to_keep. It must be closed at exec(), but kept open in the child process until exec() is called. */ continue; } if (_Py_set_inheritable((int)fd, 1, NULL) < 0) return -1; } return 0; }
/* Duplicate a file descriptor. The new file descriptor is created as non-inheritable. Return a new file descriptor on success, raise an OSError exception and return -1 on error. The GIL is released to call dup(). The caller must hold the GIL. */ int _Py_dup(int fd) { #ifdef MS_WINDOWS HANDLE handle; DWORD ftype; #endif if (!_PyVerify_fd(fd)) { PyErr_SetFromErrno(PyExc_OSError); return -1; } #ifdef MS_WINDOWS handle = (HANDLE)_get_osfhandle(fd); if (handle == INVALID_HANDLE_VALUE) { PyErr_SetFromWindowsErr(0); return -1; } /* get the file type, ignore the error if it failed */ ftype = GetFileType(handle); Py_BEGIN_ALLOW_THREADS fd = dup(fd); Py_END_ALLOW_THREADS if (fd < 0) { PyErr_SetFromErrno(PyExc_OSError); return -1; } /* Character files like console cannot be make non-inheritable */ if (ftype != FILE_TYPE_CHAR) { if (_Py_set_inheritable(fd, 0, NULL) < 0) { close(fd); return -1; } } #elif defined(HAVE_FCNTL_H) && defined(F_DUPFD_CLOEXEC) Py_BEGIN_ALLOW_THREADS fd = fcntl(fd, F_DUPFD_CLOEXEC, 0); Py_END_ALLOW_THREADS if (fd < 0) { PyErr_SetFromErrno(PyExc_OSError); return -1; } #else Py_BEGIN_ALLOW_THREADS fd = dup(fd); Py_END_ALLOW_THREADS if (fd < 0) { PyErr_SetFromErrno(PyExc_OSError); return -1; } if (_Py_set_inheritable(fd, 0, NULL) < 0) { close(fd); return -1; } #endif return fd; }
/* * This function is code executed in the child process immediately after fork * to set things up and call exec(). * * All of the code in this function must only use async-signal-safe functions, * listed at `man 7 signal` or * http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html. * * This restriction is documented at * http://www.opengroup.org/onlinepubs/009695399/functions/fork.html. */ static void child_exec(char *const exec_array[], char *const argv[], char *const envp[], const char *cwd, int p2cread, int p2cwrite, int c2pread, int c2pwrite, int errread, int errwrite, int errpipe_read, int errpipe_write, int close_fds, int restore_signals, int call_setsid, PyObject *py_fds_to_keep, PyObject *preexec_fn, PyObject *preexec_fn_args_tuple) { int i, saved_errno, reached_preexec = 0; PyObject *result; const char* err_msg = ""; /* Buffer large enough to hold a hex integer. We can't malloc. */ char hex_errno[sizeof(saved_errno)*2+1]; if (make_inheritable(py_fds_to_keep, errpipe_write) < 0) goto error; /* Close parent's pipe ends. */ if (p2cwrite != -1) POSIX_CALL(close(p2cwrite)); if (c2pread != -1) POSIX_CALL(close(c2pread)); if (errread != -1) POSIX_CALL(close(errread)); POSIX_CALL(close(errpipe_read)); /* When duping fds, if there arises a situation where one of the fds is either 0, 1 or 2, it is possible that it is overwritten (#12607). */ if (c2pwrite == 0) POSIX_CALL(c2pwrite = dup(c2pwrite)); if (errwrite == 0 || errwrite == 1) POSIX_CALL(errwrite = dup(errwrite)); /* Dup fds for child. dup2() removes the CLOEXEC flag but we must do it ourselves if dup2() would be a no-op (issue #10806). */ if (p2cread == 0) { if (_Py_set_inheritable(p2cread, 1, NULL) < 0) goto error; } else if (p2cread != -1) POSIX_CALL(dup2(p2cread, 0)); /* stdin */ if (c2pwrite == 1) { if (_Py_set_inheritable(c2pwrite, 1, NULL) < 0) goto error; } else if (c2pwrite != -1) POSIX_CALL(dup2(c2pwrite, 1)); /* stdout */ if (errwrite == 2) { if (_Py_set_inheritable(errwrite, 1, NULL) < 0) goto error; } else if (errwrite != -1) POSIX_CALL(dup2(errwrite, 2)); /* stderr */ /* Close pipe fds. Make sure we don't close the same fd more than */ /* once, or standard fds. */ if (p2cread > 2) POSIX_CALL(close(p2cread)); if (c2pwrite > 2 && c2pwrite != p2cread) POSIX_CALL(close(c2pwrite)); if (errwrite != c2pwrite && errwrite != p2cread && errwrite > 2) POSIX_CALL(close(errwrite)); if (cwd) POSIX_CALL(chdir(cwd)); if (restore_signals) _Py_RestoreSignals(); #ifdef HAVE_SETSID if (call_setsid) POSIX_CALL(setsid()); #endif reached_preexec = 1; if (preexec_fn != Py_None && preexec_fn_args_tuple) { /* This is where the user has asked us to deadlock their program. */ result = PyObject_Call(preexec_fn, preexec_fn_args_tuple, NULL); if (result == NULL) { /* Stringifying the exception or traceback would involve * memory allocation and thus potential for deadlock. * We've already faced potential deadlock by calling back * into Python in the first place, so it probably doesn't * matter but we avoid it to minimize the possibility. */ err_msg = "Exception occurred in preexec_fn."; errno = 0; /* We don't want to report an OSError. */ goto error; } /* Py_DECREF(result); - We're about to exec so why bother? */ } /* close FDs after executing preexec_fn, which might open FDs */ if (close_fds) { /* TODO HP-UX could use pstat_getproc() if anyone cares about it. */ _close_open_fds(3, 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; for (i = 0; exec_array[i] != NULL; ++i) { const char *executable = exec_array[i]; if (envp) { execve(executable, argv, envp); } else { execv(executable, argv); } if (errno != ENOENT && errno != ENOTDIR && saved_errno == 0) { saved_errno = errno; } } /* Report the first exec error, not the last. */ if (saved_errno) errno = saved_errno; error: saved_errno = errno; /* Report the posix error to our parent process. */ /* We ignore all write() return values as the total size of our writes is less than PIPEBUF and we cannot do anything about an error anyways. Use _Py_write_noraise() to retry write() if it is interrupted by a signal (fails with EINTR). */ if (saved_errno) { char *cur; _Py_write_noraise(errpipe_write, "OSError:", 8); cur = hex_errno + sizeof(hex_errno); while (saved_errno != 0 && cur > hex_errno) { *--cur = Py_hexdigits[saved_errno % 16]; saved_errno /= 16; } _Py_write_noraise(errpipe_write, cur, hex_errno + sizeof(hex_errno) - cur); _Py_write_noraise(errpipe_write, ":", 1); if (!reached_preexec) { /* Indicate to the parent that the error happened before exec(). */ _Py_write_noraise(errpipe_write, "noexec", 6); } /* We can't call strerror(saved_errno). It is not async signal safe. * The parent process will look the error message up. */ } else { _Py_write_noraise(errpipe_write, "SubprocessError:0:", 18); _Py_write_noraise(errpipe_write, err_msg, strlen(err_msg)); } }