Skip to content
Draft
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 = PyList_GET_SIZE(cwd_buffer);
if (errno == ENOENT && cwd_size > 0) {
/* 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 @@ -5335,7 +5335,7 @@ AC_CHECK_FUNCS([ \
lockf lstat lutimes madvise mbrtowc memrchr mkdirat mkfifo mkfifoat \
mknod mknodat mktime mmap mremap nice openat opendir pathconf pause pipe \
pipe2 plock poll ppoll posix_fadvise posix_fallocate posix_openpt posix_spawn posix_spawnp \
posix_spawn_file_actions_addclosefrom_np \
posix_spawn_file_actions_addchdir_np posix_spawn_file_actions_addclosefrom_np \
pread preadv preadv2 process_vm_readv \
pthread_cond_timedwait_relative_np pthread_condattr_setclock pthread_init \
pthread_kill pthread_get_name_np pthread_getname_np pthread_set_name_np \
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