diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst index e7844587e908b7..39abab6c24395c 100644 --- a/Doc/library/subprocess.rst +++ b/Doc/library/subprocess.rst @@ -394,6 +394,8 @@ functions. Popen(['/bin/sh', '-c', args[0], args[1], ...]) + VxWorks does not support shell argument. + On Windows with ``shell=True``, the :envvar:`COMSPEC` environment variable specifies the default shell. The only time you need to specify ``shell=True`` on Windows is when the command you wish to execute is built @@ -449,6 +451,8 @@ functions. child process just before the child is executed. (POSIX only) + VxWorks does not support preexec_fn argument. + .. warning:: The *preexec_fn* parameter is not safe to use in the presence of threads @@ -509,6 +513,8 @@ functions. If *start_new_session* is true the setsid() system call will be made in the child process prior to the execution of the subprocess. (POSIX only) + VxWorks does not support start_new_session argument. + .. versionchanged:: 3.2 *start_new_session* was added. diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 0496b447e8ea03..aed765ae4130c5 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -66,7 +66,11 @@ _mswindows = True except ModuleNotFoundError: _mswindows = False - import _posixsubprocess + _vxworks = (sys.platform == "vxworks") + if _vxworks: + import _vxwapi + else: + import _posixsubprocess import select import selectors else: @@ -733,6 +737,14 @@ def __init__(self, args, bufsize=-1, executable=None, raise ValueError("preexec_fn is not supported on Windows " "platforms") else: + if _vxworks: + if shell: + raise ValueError("shell is not supported on VxWorks") + if preexec_fn is not None: + raise ValueError("preexec_fn is not supported on VxWorks") + if start_new_session: + raise ValueError("start_new_session is not supported" + "on VxWorks") # POSIX if pass_fds and not close_fds: warnings.warn("pass_fds overriding close_fds.", RuntimeWarning) @@ -1580,7 +1592,18 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, for dir in os.get_exec_path(env)) fds_to_keep = set(pass_fds) fds_to_keep.add(errpipe_write) - self.pid = _posixsubprocess.fork_exec( + if _vxworks: + self.pid = _vxwapi.rtp_spawn( + args, executable_list, + close_fds, tuple(sorted(map(int, fds_to_keep))), + cwd, env_list, + p2cread, p2cwrite, c2pread, c2pwrite, + errread, errwrite, + errpipe_read, errpipe_write) + if self.pid != -1: + self._child_created = True + else: + self.pid = _posixsubprocess.fork_exec( args, executable_list, close_fds, tuple(sorted(map(int, fds_to_keep))), cwd, env_list, @@ -1588,7 +1611,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds, errread, errwrite, errpipe_read, errpipe_write, restore_signals, start_new_session, preexec_fn) - self._child_created = True + self._child_created = True finally: # be sure the FD is closed no matter what os.close(errpipe_write) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index b0b6b06e92759e..88542ee145b3e6 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -36,6 +36,21 @@ raise unittest.SkipTest("test is not helpful for PGO") mswindows = (sys.platform == "win32") +vxworks = (sys.platform == "vxworks") + +no_shell = False +no_preexec_fn = False + +if vxworks: + mock_fork_exec_or_spawn_fn_name = "subprocess._vxwapi.rtp_spawn" + platform_specific_fork_exec_or_spawn = subprocess._vxwapi.rtp_spawn + + no_shell = True + no_preexec_fn = True +else: + mock_fork_exec_or_spawn_fn_name = "subprocess._posixsubprocess.fork_exec" + if not mswindows: + platform_specific_fork_exec_or_spawn = subprocess._posixsubprocess.fork_exec # # Depends on the following external programs: Python @@ -315,6 +330,7 @@ def test_executable_takes_precedence(self): executable=NONEXISTING_CMD[0]) @unittest.skipIf(mswindows, "executable argument replaces shell") + @unittest.skipIf(no_shell, "shell argument is not supported") def test_executable_replaces_shell(self): # Check that the executable argument replaces the default shell # when shell=True. @@ -1581,8 +1597,8 @@ class PopenNoDestructor(subprocess.Popen): def __del__(self): pass - @mock.patch("subprocess._posixsubprocess.fork_exec") - def test_exception_errpipe_normal(self, fork_exec): + @mock.patch(mock_fork_exec_or_spawn_fn_name) + def test_exception_errpipe_normal(self, platform_specific_fork_exec_or_spawn): """Test error passing done through errpipe_write in the good case""" def proper_error(*args): errpipe_write = args[13] @@ -1591,15 +1607,15 @@ def proper_error(*args): os.write(errpipe_write, b"OSError:" + err_code + b":") return 0 - fork_exec.side_effect = proper_error + platform_specific_fork_exec_or_spawn.side_effect = proper_error with mock.patch("subprocess.os.waitpid", side_effect=ChildProcessError): with self.assertRaises(IsADirectoryError): self.PopenNoDestructor(["non_existent_command"]) - @mock.patch("subprocess._posixsubprocess.fork_exec") - def test_exception_errpipe_bad_data(self, fork_exec): + @mock.patch(mock_fork_exec_or_spawn_fn_name) + def test_exception_errpipe_bad_data(self, platform_specific_fork_exec_or_spawn): """Test error passing done through errpipe_write where its not in the expected format""" error_data = b"\xFF\x00\xDE\xAD" @@ -1611,7 +1627,7 @@ def bad_error(*args): os.write(errpipe_write, error_data) return 0 - fork_exec.side_effect = bad_error + platform_specific_fork_exec_or_spawn.side_effect = bad_error with mock.patch("subprocess.os.waitpid", side_effect=ChildProcessError): @@ -1644,6 +1660,8 @@ def test_restore_signals(self): msg="restore_signals=True should've unblocked " "SIGPIPE and friends.") + @unittest.skipUnless(os is not None and hasattr(os, 'setsid'), + 'need os.setsid') def test_start_new_session(self): # For code coverage of calling setsid(). We don't care if we get an # EPERM error from it depending on the test execution environment, that @@ -1690,6 +1708,7 @@ def test_CalledProcessError_str_non_zero(self): error_string = str(err) self.assertIn("non-zero exit status 2.", error_string) + @unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported") def test_preexec(self): # DISCLAIMER: Setting environment variables is *not* a good use # of a preexec_fn. This is merely a test. @@ -1701,6 +1720,7 @@ def test_preexec(self): with p: self.assertEqual(p.stdout.read(), b"apple") + @unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported") def test_preexec_exception(self): def raise_it(): raise ValueError("What if two swallows carried a coconut?") @@ -1743,6 +1763,7 @@ def _execute_child(self, *args, **kwargs): os.close(fd) @unittest.skipIf(not os.path.exists("/dev/zero"), "/dev/zero required.") + @unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported") def test_preexec_errpipe_does_not_double_close_pipes(self): """Issue16140: Don't double close pipes on preexec error.""" @@ -1756,6 +1777,7 @@ def raise_it(): stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=raise_it) + @unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported") def test_preexec_gc_module_failure(self): # This tests the code that disables garbage collection if the child # process will execute any Python. @@ -1795,6 +1817,7 @@ def raise_runtime_error(): @unittest.skipIf( sys.platform == 'darwin', 'setrlimit() seems to fail on OS X') + @unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported") def test_preexec_fork_failure(self): # The internal code did not preserve the previous exception when # re-enabling garbage collection @@ -1840,6 +1863,7 @@ def test_invalid_args(self): "import sys; sys.exit(47)"], creationflags=47) + @unittest.skipIf(no_shell, "shell argument is not supported") def test_shell_sequence(self): # Run command through the shell (sequence) newenv = os.environ.copy() @@ -1850,6 +1874,7 @@ def test_shell_sequence(self): with p: self.assertEqual(p.stdout.read().strip(b" \t\r\n\f"), b"apple") + @unittest.skipIf(no_shell, "shell argument is not supported") def test_shell_string(self): # Run command through the shell (string) newenv = os.environ.copy() @@ -1873,6 +1898,7 @@ def test_call_string(self): os.remove(fname) self.assertEqual(rc, 47) + @unittest.skipIf(no_shell, "shell argument is not supported") def test_specific_shell(self): # Issue #9265: Incorrect name passed as arg[0]. shells = [] @@ -2203,6 +2229,7 @@ def test_swap_std_fds_with_one_closed(self): for to_fds in itertools.permutations(range(3), 2): self._check_swap_std_fds_with_one_closed(from_fds, to_fds) + @unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported") def test_surrogates_error_message(self): def prepare(): raise ValueError("surrogate:\uDCff") @@ -2251,6 +2278,7 @@ def test_undecodable_env(self): stdout = stdout.rstrip(b'\n\r') self.assertEqual(stdout.decode('ascii'), ascii(encoded_value)) + @unittest.skipIf(no_shell, "shell argument is not supported") def test_bytes_program(self): abs_program = os.fsencode(sys.executable) path, program = os.path.split(sys.executable) @@ -2662,6 +2690,7 @@ def test_leak_fast_process_del_killed(self): self.assertRaises(OSError, os.waitpid, pid, 0) self.assertNotIn(ident, [id(o) for o in subprocess._active]) + @unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported") def test_close_fds_after_preexec(self): fd_status = support.findfile("fd_status.py", subdir="subprocessdata") @@ -2680,6 +2709,7 @@ def test_close_fds_after_preexec(self): self.assertNotIn(fd, remaining_fds) @support.cpython_only + @unittest.skipIf(no_preexec_fn, "preexec_fn argument is not supported") def test_fork_exec(self): # Issue #22290: fork_exec() must not crash on memory allocation failure # or other errors @@ -2712,7 +2742,6 @@ def test_fork_exec(self): @support.cpython_only def test_fork_exec_sorted_fd_sanity_check(self): # Issue #23564: sanity check the fork_exec() fds_to_keep sanity check. - import _posixsubprocess class BadInt: first = True def __init__(self, value): @@ -2738,12 +2767,19 @@ def __int__(self): with self.assertRaises( ValueError, msg='fds_to_keep={}'.format(fds_to_keep)) as c: - _posixsubprocess.fork_exec( - [b"false"], [b"false"], - True, fds_to_keep, None, [b"env"], - -1, -1, -1, -1, - 1, 2, 3, 4, - True, True, None) + if vxworks: + platform_specific_fork_exec_or_spawn( + [b"false"], [b"false"], + True, fds_to_keep, None, [b"env"], + -1, -1, -1, -1, + 1, 2, 3, 4) + else: + platform_specific_fork_exec_or_spawn( + [b"false"], [b"false"], + True, fds_to_keep, None, [b"env"], + -1, -1, -1, -1, + 1, 2, 3, 4, + True, True, None) self.assertIn('fds_to_keep', str(c.exception)) finally: if not gc_enabled: @@ -3134,6 +3170,7 @@ def popen_via_context_manager(*args, **kwargs): raise KeyboardInterrupt # Test how __exit__ handles ^C. self._test_keyboardinterrupt_no_kill(popen_via_context_manager) + @unittest.skipIf(no_shell, "shell argument is not supported") def test_getoutput(self): self.assertEqual(subprocess.getoutput('echo xyzzy'), 'xyzzy') self.assertEqual(subprocess.getstatusoutput('echo xyzzy'), diff --git a/Misc/NEWS.d/next/Library/2019-03-04-14-57-25.bpo-31904.28djD8.rst b/Misc/NEWS.d/next/Library/2019-03-04-14-57-25.bpo-31904.28djD8.rst new file mode 100644 index 00000000000000..959b7a18a8f027 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-03-04-14-57-25.bpo-31904.28djD8.rst @@ -0,0 +1 @@ +Support subprocess module on VxWorks. diff --git a/Modules/_vxwapi.c b/Modules/_vxwapi.c new file mode 100644 index 00000000000000..984ce6487657ea --- /dev/null +++ b/Modules/_vxwapi.c @@ -0,0 +1,504 @@ +/* + * VxWorks Compatibility Wrapper + * + * Python interface to VxWorks methods + * + * Author: wenyan.xin@windriver.com + * + ************************************************/ +#include +#include + +#include +#include +#include +#include + +#include "clinic/_vxwapi.c.h" + +/*[clinic input] +module _vxwapi +[clinic start generated code]*/ +/*[clinic end generated code: output=da39a3ee5e6b4b0d input=6efcf3b26a262ef1]*/ + +/* Returns 1 if there is a problem with fd_sequence, 0 otherwise. */ +static int +_sanity_check_python_fd_sequence(PyObject *fd_sequence) +{ + Py_ssize_t seq_idx; + long prev_fd = -1; + for (seq_idx = 0; seq_idx < PyTuple_GET_SIZE(fd_sequence); ++seq_idx) { + PyObject* py_fd = PyTuple_GET_ITEM(fd_sequence, seq_idx); + long iter_fd; + if (!PyLong_Check(py_fd)) { + return 1; + } + iter_fd = PyLong_AsLong(py_fd); + if (iter_fd < 0 || iter_fd <= prev_fd || iter_fd > INT_MAX) { + /* Negative, overflow, unsorted, too big for a fd. */ + return 1; + } + prev_fd = iter_fd; + } + return 0; +} + +/* Is fd found in the sorted Python Sequence? */ +static int +_is_fd_in_sorted_fd_sequence(int fd, PyObject *fd_sequence) +{ + /* Binary search. */ + Py_ssize_t search_min = 0; + Py_ssize_t search_max = PyTuple_GET_SIZE(fd_sequence) - 1; + if (search_max < 0) + return 0; + do { + long middle = (search_min + search_max) / 2; + long middle_fd = PyLong_AsLong(PyTuple_GET_ITEM(fd_sequence, middle)); + if (fd == middle_fd) + return 1; + if (fd > middle_fd) + search_min = middle + 1; + else + search_max = middle - 1; + } while (search_min <= search_max); + return 0; +} + + +static void _close_open_fds(long start_fd, PyObject* py_fds_to_keep) +{ + int fd; + int ret; + + for (fd = start_fd; fd < FD_SETSIZE; ++fd) { + ret = fcntl (fd, F_GETFD); + if (ret < 0) + continue; + + if (_is_fd_in_sorted_fd_sequence(fd, py_fds_to_keep)) + continue; + + _Py_set_inheritable_async_safe(fd, 0, NULL); + } +} + + +static int +make_inheritable(PyObject *py_fds_to_keep, int errpipe_write) +{ + Py_ssize_t i, len; + + len = PyTuple_GET_SIZE(py_fds_to_keep); + for (i = 0; i < len; ++i) { + PyObject* fdobj = PyTuple_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_async_safe((int)fd, 1, NULL) < 0) + return -1; + } + return 0; +} + +static PyObject * +rtp_spawn_impl( + PyObject *executable_list, + char *const argvp[], + char *const envpp[], + PyObject *cwd_obj, + int p2cread, int p2cwrite, + int c2pread, int c2pwrite, + int errread, int errwrite, + int errpipe_read, int errpipe_write, + int close_fds, PyObject *py_fds_to_keep) +{ + int priority = 0; + unsigned int uStackSize = 0; + int options = 0; + int taskOptions = VX_FP_TASK; + char pwdbuf[PATH_MAX]={0}; + const char *cwd = NULL; + PyObject *cwd_obj2; + char *const *exec_array; + + int p2cread_bk = -1; + int c2pwrite_bk = -1; + int errwrite_bk = -1; + + if (make_inheritable(py_fds_to_keep, errpipe_write) < 0) + goto error; + + /* 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) { + c2pwrite = dup(c2pwrite); + if (_Py_set_inheritable_async_safe(c2pwrite, 0, NULL) < 0) { + goto error; + } + c2pwrite_bk = c2pwrite; + } + + if (c2pwrite_bk == -1 && p2cread == 1) { + p2cread = dup(p2cread); + if (_Py_set_inheritable_async_safe(p2cread, 0, NULL) < 0) { + goto error; + } + p2cread_bk = p2cread; + } + + while (errwrite == 0 || errwrite == 1) { + errwrite = dup(errwrite); + if (_Py_set_inheritable_async_safe(errwrite, 0, NULL) < 0) { + goto error; + } + errwrite_bk = errwrite; + } + + exec_array = _PySequence_BytesToCharpArray(executable_list); + if (!exec_array) + goto error; + + if (cwd_obj != Py_None) { + if (PyUnicode_FSConverter(cwd_obj, &cwd_obj2) == 0) + goto error; + cwd = PyBytes_AsString(cwd_obj2); + } else { + cwd = NULL; + cwd_obj2 = NULL; + } + + int pid = RTP_ID_ERROR; + int stdin_bk = -1, stdout_bk = -1, stderr_bk = -1; + int saved_errno, reached_preexec = 0; + const char* err_msg = ""; + char hex_errno[sizeof(saved_errno)*2+1]; + + if (-1 != p2cwrite && + _Py_set_inheritable_async_safe(p2cwrite, 0, NULL) < 0) + goto error; + + if (-1 != c2pread && + _Py_set_inheritable_async_safe(c2pread, 0, NULL) < 0) + goto error; + + if (-1 != errread && + _Py_set_inheritable_async_safe(errread, 0, NULL) < 0) + goto error; + + if (-1 != errpipe_read && + _Py_set_inheritable_async_safe(errpipe_read, 0, NULL) < 0) + goto error; + + if (-1 != errpipe_write && + _Py_set_inheritable_async_safe(errpipe_write, 0, NULL) < 0) + goto error; + + if (p2cread == 0) { + if (_Py_set_inheritable_async_safe(p2cread, 1, NULL) < 0) + goto error; + } + else if (p2cread != -1) { + stdin_bk = dup(0); + if (dup2(p2cread, 0) == -1) /* stdin */ + goto error; + } + + if (c2pwrite == 1) { + if (_Py_set_inheritable_async_safe(c2pwrite, 1, NULL) < 0) + goto error; + } + else if (c2pwrite != -1) { + stdout_bk = dup(1); + if (dup2(c2pwrite, 1) == -1) /* stdout */ + goto error; + } + + if (errwrite == 2) { + if (_Py_set_inheritable_async_safe(errwrite, 1, NULL) < 0) + goto error; + } + else if (errwrite != -1) { + stderr_bk = dup(2); + if (dup2(errwrite, 2) == -1) /* stderr */ + goto error; + } + + char *cwd_bk = getcwd(pwdbuf, sizeof(pwdbuf)); + if (cwd) { + if (chdir(cwd) == -1) { + if (ENODEV == errno) { + errno = ENOENT; + } + goto error; + } + } + + reached_preexec = 1; + + if (close_fds) { + _close_open_fds(3, py_fds_to_keep); + } + + (void)taskPriorityGet (taskIdSelf(), &priority); + (void)taskStackSizeGet (taskIdSelf(), &uStackSize); + + saved_errno = 0; + for (int i = 0; exec_array[i] != NULL; ++i) { + const char *executable = exec_array[i]; + pid = rtpSpawn (executable, (const char **)argvp, + (const char**)envpp, priority, uStackSize, options, taskOptions); + + if (RTP_ID_ERROR == pid && saved_errno == 0) { + if (ENODEV == errno) { + errno = ENOENT; + } + saved_errno = errno; + } + } + + if (p2cread_bk != -1 ) + close(p2cread_bk); + + if (c2pwrite_bk != -1 ) + close(c2pwrite_bk); + + if (errwrite_bk != -1 ) + close(errwrite_bk); + + if (exec_array) + _Py_FreeCharPArray(exec_array); + + if (stdin_bk >= 0) { + if (dup2(stdin_bk, 0) == -1) + goto error; + close(stdin_bk); + } + if (stdout_bk >= 0) { + if (dup2(stdout_bk, 1) == -1) + goto error; + close(stdout_bk); + } + if (stderr_bk >= 0) { + if (dup2(stderr_bk,2) == -1) + goto error; + close(stderr_bk); + } + + if (cwd && cwd_bk) + chdir(cwd_bk); + + if (RTP_ID_ERROR != pid) { + return Py_BuildValue("i", pid); + } + + /* 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 rtpSpawn(). */ + _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)); + } + + return Py_BuildValue("i", pid); +} + + +static void _save_fds(int saved_fd[]) +{ + int fd; + int flags; + + if (!saved_fd) return; + + for (fd = 0; fd < FD_SETSIZE; ++fd) { + flags = fcntl (fd, F_GETFD); + if (flags < 0) + continue; + + saved_fd[fd] = !(flags & FD_CLOEXEC); + } +} + +static void _restore_fds(int saved_fd[]) +{ + int fd; + int flags; + + if (!saved_fd) return; + + for (fd = 0; fd < FD_SETSIZE; ++fd) { + flags = fcntl (fd, F_GETFD); + if (flags < 0) + continue; + + if (-1 != saved_fd[fd]) { + _Py_set_inheritable_async_safe(fd, saved_fd[fd], NULL); + } + } +} + +/*[clinic input] +_vxwapi.rtp_spawn + + process_args: object + executable_list: object + close_fds: int + py_fds_to_keep: object(subclass_of='&PyTuple_Type') + cwd_obj: object + env_list: object + p2cread: int + p2cwrite: int + c2pread: int + c2pwrite: int + errread: int + errwrite: int + errpipe_read: int + errpipe_write: int + / + +Spawn a real time process in the vxWorks OS +[clinic start generated code]*/ + +static PyObject * +_vxwapi_rtp_spawn_impl(PyObject *module, PyObject *process_args, + PyObject *executable_list, int close_fds, + PyObject *py_fds_to_keep, PyObject *cwd_obj, + PyObject *env_list, int p2cread, int p2cwrite, + int c2pread, int c2pwrite, int errread, int errwrite, + int errpipe_read, int errpipe_write) +/*[clinic end generated code: output=e398e7eafdf8ce1e input=76a69d261378fad9]*/ + +{ + PyObject *converted_args = NULL, *fast_args = NULL; + char *const *argv = NULL, *const *envp = NULL; + int saved_fd[FD_SETSIZE] = {-1}; + + PyObject *return_value = NULL; + if (_PyInterpreterState_Get() != PyInterpreterState_Main()) { + PyErr_SetString(PyExc_RuntimeError, "fork not supported for subinterpreters"); + return NULL; + } + + if (close_fds && errpipe_write < 3) { /* precondition */ + PyErr_SetString(PyExc_ValueError, "errpipe_write must be >= 3"); + return NULL; + } + + if (_sanity_check_python_fd_sequence(py_fds_to_keep)) { + PyErr_SetString(PyExc_ValueError, "bad value(s) in fds_to_keep"); + return NULL; + } + + /* Convert args and env into appropriate arguments */ + /* These conversions are done in the parent process to avoid allocating + or freeing memory in the child process. */ + if (process_args != Py_None) { + Py_ssize_t num_args; + Py_ssize_t arg_num; + /* Equivalent to: */ + /* tuple(PyUnicode_FSConverter(arg) for arg in process_args) */ + fast_args = PySequence_Fast(process_args, "argv must be a tuple"); + if (fast_args == NULL) + goto cleanup; + num_args = PySequence_Fast_GET_SIZE(fast_args); + converted_args = PyTuple_New(num_args); + if (converted_args == NULL) + goto cleanup; + for (arg_num = 0; arg_num < num_args; ++arg_num) { + PyObject *borrowed_arg, *converted_arg; + if (PySequence_Fast_GET_SIZE(fast_args) != num_args) { + PyErr_SetString(PyExc_RuntimeError, + "args changed during iteration"); + goto cleanup; + } + borrowed_arg = PySequence_Fast_GET_ITEM(fast_args, arg_num); + if (PyUnicode_FSConverter(borrowed_arg, &converted_arg) == 0) + goto cleanup; + PyTuple_SET_ITEM(converted_args, arg_num, converted_arg); + } + + argv = _PySequence_BytesToCharpArray(converted_args); + Py_CLEAR(converted_args); + Py_CLEAR(fast_args); + if (!argv) + goto cleanup; + } + + if (env_list != Py_None) { + envp = _PySequence_BytesToCharpArray(env_list); + if (!envp) + goto cleanup; + } + + _save_fds(saved_fd); + + return_value = rtp_spawn_impl( + executable_list, argv, envp, cwd_obj, + p2cread, p2cwrite, c2pread, c2pwrite, + errread, errwrite, errpipe_read, errpipe_write, + close_fds, py_fds_to_keep); + + _restore_fds (saved_fd); + +cleanup: + if (envp) + _Py_FreeCharPArray(envp); + if (argv) + _Py_FreeCharPArray(argv); + Py_XDECREF(converted_args); + Py_XDECREF(fast_args); + return return_value; +} + + + +static PyMethodDef _vxwapiMethods[] = { + _VXWAPI_RTP_SPAWN_METHODDEF + { NULL, NULL } +}; + +static struct PyModuleDef _vxwapimodule = { + PyModuleDef_HEAD_INIT, + "_vxwapi", + NULL, + -1, + _vxwapiMethods +}; + +PyMODINIT_FUNC +PyInit__vxwapi(void) +{ + return PyModule_Create(&_vxwapimodule); +} + diff --git a/Modules/clinic/_vxwapi.c.h b/Modules/clinic/_vxwapi.c.h new file mode 100644 index 00000000000000..8c4029121f0042 --- /dev/null +++ b/Modules/clinic/_vxwapi.c.h @@ -0,0 +1,142 @@ +/*[clinic input] +preserve +[clinic start generated code]*/ + +PyDoc_STRVAR(_vxwapi_rtp_spawn__doc__, +"rtp_spawn($module, process_args, executable_list, close_fds,\n" +" py_fds_to_keep, cwd_obj, env_list, p2cread, p2cwrite,\n" +" c2pread, c2pwrite, errread, errwrite, errpipe_read,\n" +" errpipe_write, /)\n" +"--\n" +"\n" +"Spawn a real time process in the vxWorks OS"); + +#define _VXWAPI_RTP_SPAWN_METHODDEF \ + {"rtp_spawn", (PyCFunction)(void(*)(void))_vxwapi_rtp_spawn, METH_FASTCALL, _vxwapi_rtp_spawn__doc__}, + +static PyObject * +_vxwapi_rtp_spawn_impl(PyObject *module, PyObject *process_args, + PyObject *executable_list, int close_fds, + PyObject *py_fds_to_keep, PyObject *cwd_obj, + PyObject *env_list, int p2cread, int p2cwrite, + int c2pread, int c2pwrite, int errread, int errwrite, + int errpipe_read, int errpipe_write); + +static PyObject * +_vxwapi_rtp_spawn(PyObject *module, PyObject *const *args, Py_ssize_t nargs) +{ + PyObject *return_value = NULL; + PyObject *process_args; + PyObject *executable_list; + int close_fds; + PyObject *py_fds_to_keep; + PyObject *cwd_obj; + PyObject *env_list; + int p2cread; + int p2cwrite; + int c2pread; + int c2pwrite; + int errread; + int errwrite; + int errpipe_read; + int errpipe_write; + + if (!_PyArg_CheckPositional("rtp_spawn", nargs, 14, 14)) { + goto exit; + } + process_args = args[0]; + executable_list = args[1]; + if (PyFloat_Check(args[2])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + close_fds = _PyLong_AsInt(args[2]); + if (close_fds == -1 && PyErr_Occurred()) { + goto exit; + } + if (!PyTuple_Check(args[3])) { + _PyArg_BadArgument("rtp_spawn", 4, "tuple", args[3]); + goto exit; + } + py_fds_to_keep = args[3]; + cwd_obj = args[4]; + env_list = args[5]; + if (PyFloat_Check(args[6])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + p2cread = _PyLong_AsInt(args[6]); + if (p2cread == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyFloat_Check(args[7])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + p2cwrite = _PyLong_AsInt(args[7]); + if (p2cwrite == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyFloat_Check(args[8])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + c2pread = _PyLong_AsInt(args[8]); + if (c2pread == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyFloat_Check(args[9])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + c2pwrite = _PyLong_AsInt(args[9]); + if (c2pwrite == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyFloat_Check(args[10])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + errread = _PyLong_AsInt(args[10]); + if (errread == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyFloat_Check(args[11])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + errwrite = _PyLong_AsInt(args[11]); + if (errwrite == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyFloat_Check(args[12])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + errpipe_read = _PyLong_AsInt(args[12]); + if (errpipe_read == -1 && PyErr_Occurred()) { + goto exit; + } + if (PyFloat_Check(args[13])) { + PyErr_SetString(PyExc_TypeError, + "integer argument expected, got float" ); + goto exit; + } + errpipe_write = _PyLong_AsInt(args[13]); + if (errpipe_write == -1 && PyErr_Occurred()) { + goto exit; + } + return_value = _vxwapi_rtp_spawn_impl(module, process_args, executable_list, close_fds, py_fds_to_keep, cwd_obj, env_list, p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite, errpipe_read, errpipe_write); + +exit: + return return_value; +} +/*[clinic end generated code: output=44ceb0e8de454bd6 input=a9049054013a1b77]*/ diff --git a/setup.py b/setup.py index c278f08b8e6d2f..bf54dbcb681a44 100644 --- a/setup.py +++ b/setup.py @@ -807,7 +807,10 @@ def detect_simple_extensions(self): self.add(Extension('_csv', ['_csv.c'])) # POSIX subprocess module helper. - self.add(Extension('_posixsubprocess', ['_posixsubprocess.c'])) + if VXWORKS: + self.add(Extension('_vxwapi', ['_vxwapi.c'])) + else: + self.add(Extension('_posixsubprocess', ['_posixsubprocess.c'])) def detect_test_extensions(self): # Python C API test module