Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 13 additions & 2 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5161,11 +5161,18 @@ written in Python, such as a mail server's external command delivery program.

Performs ``os.closerange(fd, INF)``.

.. data:: POSIX_SPAWN_CHDIR

(``os.POSIX_SPAWN_CHDIR``, *path*)

Performs ``os.chdir(path)``.

These tuples correspond to the C library
:c:func:`!posix_spawn_file_actions_addopen`,
:c:func:`!posix_spawn_file_actions_addclose`,
:c:func:`!posix_spawn_file_actions_adddup2`, and
:c:func:`!posix_spawn_file_actions_addclosefrom_np` API calls used to prepare
:c:func:`!posix_spawn_file_actions_adddup2`,
:c:func:`!posix_spawn_file_actions_addclosefrom_np`, and
:c:func:`!posix_spawn_file_actions_addchdir_np` API calls used to prepare
for the :c:func:`!posix_spawn` call itself.

The *setpgroup* argument will set the process group of the child to the value
Expand Down Expand Up @@ -5212,6 +5219,10 @@ written in Python, such as a mail server's external command delivery program.
``os.POSIX_SPAWN_CLOSEFROM`` is available on platforms where
:c:func:`!posix_spawn_file_actions_addclosefrom_np` exists.

.. versionchanged:: 3.15
``os.POSIX_SPAWN_CHDIR`` is available on platforms where
:c:func:`!posix_spawn_file_actions_addchdir_np` exist.

.. availability:: Unix, not WASI, not Android, not iOS.

.. function:: posix_spawnp(path, argv, env, *, file_actions=None, \
Expand Down
11 changes: 8 additions & 3 deletions Lib/subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -806,6 +806,7 @@ def _can_use_kqueue():
# These are primarily fail-safe knobs for negatives. A True value does not
# guarantee the given libc/syscall API will be used.
_USE_POSIX_SPAWN = _use_posix_spawn()
_HAVE_POSIX_SPAWN_CHDIR = hasattr(os, 'POSIX_SPAWN_CHDIR')
_HAVE_POSIX_SPAWN_CLOSEFROM = hasattr(os, 'POSIX_SPAWN_CLOSEFROM')


Expand Down Expand Up @@ -1836,7 +1837,7 @@ def _get_handles(self, stdin, stdout, stderr):
errread, errwrite)


def _posix_spawn(self, args, executable, env, restore_signals, close_fds,
def _posix_spawn(self, args, executable, env, restore_signals, close_fds, cwd,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite):
Expand All @@ -1863,6 +1864,9 @@ def _posix_spawn(self, args, executable, env, restore_signals, close_fds,
if fd != -1:
file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2))

if cwd is not None:
file_actions.append((os.POSIX_SPAWN_CHDIR, cwd))

if close_fds:
file_actions.append((os.POSIX_SPAWN_CLOSEFROM, 3))

Expand Down Expand Up @@ -1915,7 +1919,7 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
and preexec_fn is None
and (not close_fds or _HAVE_POSIX_SPAWN_CLOSEFROM)
and not pass_fds
and cwd is None
and (cwd is None or _HAVE_POSIX_SPAWN_CHDIR)
and (p2cread == -1 or p2cread > 2)
and (c2pwrite == -1 or c2pwrite > 2)
and (errwrite == -1 or errwrite > 2)
Expand All @@ -1925,7 +1929,8 @@ def _execute_child(self, args, executable, preexec_fn, close_fds,
and gids is None
and uid is None
and umask < 0):
self._posix_spawn(args, executable, env, restore_signals, close_fds,
self._posix_spawn(args, executable, env, restore_signals,
close_fds, cwd,
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite)
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_subprocess.py
Original file line number Diff line number Diff line change
Expand Up @@ -2018,6 +2018,7 @@ def _get_chdir_exception(self):
self._nonexistent_dir)
return desired_exception

@mock.patch("subprocess._HAVE_POSIX_SPAWN_CHDIR", new=False)
def test_exception_cwd(self):
"""Test error in the child raised in the parent for a bad cwd."""
desired_exception = self._get_chdir_exception()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function
with ``cwd`` set on platforms where ``posix_spawn_file_actions_addchdir_np``
is available. Patch by Jakub Kulik.
76 changes: 74 additions & 2 deletions Modules/posixmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -7606,6 +7606,9 @@ enum posix_spawn_file_actions_identifier {
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
,POSIX_SPAWN_CLOSEFROM
#endif
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
,POSIX_SPAWN_CHDIR
#endif
};

#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)
Expand Down Expand Up @@ -7757,7 +7760,7 @@ parse_posix_spawn_flags(PyObject *module, const char *func_name, PyObject *setpg
static int
parse_file_actions(PyObject *file_actions,
posix_spawn_file_actions_t *file_actionsp,
PyObject *temp_buffer)
PyObject *temp_buffer, PyObject* cwd_buffer)
{
PyObject *seq;
PyObject *file_action = NULL;
Expand Down Expand Up @@ -7864,6 +7867,29 @@ parse_file_actions(PyObject *file_actions,
}
break;
}
#endif
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
case POSIX_SPAWN_CHDIR: {
PyObject *path;
if (!PyArg_ParseTuple(file_action, "OO&"
";A chdir file_action tuple must have 2 elements",
&tag_obj, PyUnicode_FSConverter, &path))
{
goto fail;
}
errno = posix_spawn_file_actions_addchdir_np(file_actionsp,
PyBytes_AS_STRING(path));
if (errno) {
posix_error();
Py_DECREF(path);
goto fail;
}
if (PyList_Append(cwd_buffer, path)) {
Py_DECREF(path);
goto fail;
}
break;
}
#endif
default: {
PyErr_SetString(PyExc_TypeError,
Expand Down Expand Up @@ -7901,6 +7927,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
Py_ssize_t argc, envc;
PyObject *result = NULL;
PyObject *temp_buffer = NULL;
PyObject *cwd_buffer = NULL;
pid_t pid;
int err_code;

Expand Down Expand Up @@ -7972,7 +7999,13 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
if (!temp_buffer) {
goto exit;
}
if (parse_file_actions(file_actions, &file_actions_buf, temp_buffer)) {
/* Use a list to capture all directories passed via POSIX_SPAWN_CHDIR
* action for potential exception creation below. */
cwd_buffer = PyList_New(0);
if (!cwd_buffer) {
goto exit;
}
if (parse_file_actions(file_actions, &file_actions_buf, temp_buffer, cwd_buffer)) {
goto exit;
}
file_actionsp = &file_actions_buf;
Expand Down Expand Up @@ -8004,6 +8037,41 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a

if (err_code) {
errno = err_code;
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
Py_ssize_t cwd_size;
if (errno == ENOENT && cwd_buffer && (cwd_size = PyList_GET_SIZE(cwd_buffer))) {
/* ENOENT can occur when either the path of the executable or any of
* the cwds given via file_actions doesn't exist. Since it's not
* possible to determine which of those paths caused the problem,
* we return an exception with all of those. */

if (cwd_size == 1) {
PyObject *cwd = PyList_GET_ITEM(cwd_buffer, 0);
PyErr_Format(PyExc_FileNotFoundError, "Either '%S' or '%s' doesn't exist.",
path->object, PyBytes_AS_STRING(cwd));
} else {
/* Multiple POSIX_SPAWN_CHDIR actions were used in a single
* spawn. In this case, we have to build the expection message
* from all possibly missing paths. */
PyObject *separator = PyBytes_FromString(", ");
if (!separator) {
goto exit;
}

PyObject *joined = PyBytes_Join(separator, cwd_buffer);
Py_DECREF(separator);
if (!joined) {
goto exit;
}
PyErr_Format(PyExc_FileNotFoundError,
"Either '%S' or one of (%s) doesn't exist.",
path->object, PyBytes_AS_STRING(joined));

Py_DECREF(joined);
}
goto exit;
}
#endif
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path->object);
goto exit;
}
Expand All @@ -8025,6 +8093,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
if (argvlist) {
free_string_array(argvlist, argc);
}
Py_XDECREF(cwd_buffer);
Py_XDECREF(temp_buffer);
return result;
}
Expand Down Expand Up @@ -18150,6 +18219,9 @@ all_ins(PyObject *m)
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
if (PyModule_AddIntMacro(m, POSIX_SPAWN_CLOSEFROM)) return -1;
#endif
#ifdef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP
if (PyModule_AddIntMacro(m, POSIX_SPAWN_CHDIR)) return -1;
#endif
#endif

#if defined(HAVE_SPAWNV) || defined (HAVE_RTPSPAWN)
Expand Down
6 changes: 6 additions & 0 deletions configure

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -5371,7 +5371,7 @@ fi
# header definition prevents usage - autoconf doesn't use the headers), or
# raise an error if used at runtime. Force these symbols off.
if test "$ac_sys_system" != "iOS" ; then
AC_CHECK_FUNCS([getentropy getgroups system])
AC_CHECK_FUNCS([getentropy getgroups posix_spawn_file_actions_addchdir_np system])
fi

AC_CHECK_DECL([dirfd],
Expand Down
4 changes: 4 additions & 0 deletions pyconfig.h.in
Original file line number Diff line number Diff line change
Expand Up @@ -975,6 +975,10 @@
/* Define to 1 if you have the 'posix_spawnp' function. */
#undef HAVE_POSIX_SPAWNP

/* Define to 1 if you have the 'posix_spawn_file_actions_addchdir_np'
function. */
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP

/* Define to 1 if you have the 'posix_spawn_file_actions_addclosefrom_np'
function. */
#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCLOSEFROM_NP
Expand Down
Loading