diff --git a/Doc/library/os.rst b/Doc/library/os.rst index 5037ae89ae3154..dd10a07abf9145 100644 --- a/Doc/library/os.rst +++ b/Doc/library/os.rst @@ -4757,11 +4757,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 @@ -4805,8 +4812,11 @@ written in Python, such as a mail server's external command delivery program. .. versionchanged:: 3.13 *env* parameter accepts ``None``. - ``os.POSIX_SPAWN_CLOSEFROM`` is available on platforms where - :c:func:`!posix_spawn_file_actions_addclosefrom_np` exists. + + .. versionchanged:: 3.14 + ``os.POSIX_SPAWN_CLOSEFROM`` and ``os.POSIX_SPAWN_CHDIR`` are available + on platforms where :c:func:`!posix_spawn_file_actions_addclosefrom_np` + and :c:func:`!posix_spawn_file_actions_addchdir_np` exist. .. availability:: Unix, not WASI, not Android, not iOS. diff --git a/Lib/subprocess.py b/Lib/subprocess.py index 749c728db729ae..4c6633e35f7be4 100644 --- a/Lib/subprocess.py +++ b/Lib/subprocess.py @@ -749,6 +749,7 @@ def _use_posix_spawn(): # 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') @@ -1757,7 +1758,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): @@ -1784,6 +1785,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)) @@ -1836,7 +1840,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) @@ -1846,7 +1850,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) diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py index 81d97a88f07bdd..fbb8084150e3e5 100644 --- a/Lib/test/test_subprocess.py +++ b/Lib/test/test_subprocess.py @@ -1841,6 +1841,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() diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c index f3ce1fb632226e..27093f95ea890a 100644 --- a/Modules/posixmodule.c +++ b/Modules/posixmodule.c @@ -7127,6 +7127,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) @@ -7277,7 +7280,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) { PyObject *seq; PyObject *file_action = NULL; @@ -7384,6 +7387,27 @@ 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; + } + Py_XDECREF(*cwd); + *cwd = path; + break; + } #endif default: { PyErr_SetString(PyExc_TypeError, @@ -7421,6 +7445,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 = NULL; pid_t pid; int err_code; @@ -7486,7 +7511,7 @@ 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)) { + if (parse_file_actions(file_actions, &file_actions_buf, temp_buffer, &cwd)) { goto exit; } file_actionsp = &file_actions_buf; @@ -7518,6 +7543,17 @@ 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 + if (errno == ENOENT && cwd != NULL) { + /* ENOENT can occur when either the path of the executable or the + * cwd given via file_actions doesn't exist. Since it's not feasible + * to determine which of those paths caused the problem, we return + * an exception with both. */ + PyErr_Format(PyExc_FileNotFoundError, "Either '%S' or '%s' doesn't exist.", + path->object, PyBytes_AS_STRING(cwd)); + goto exit; + } +#endif PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path->object); goto exit; } @@ -7539,6 +7575,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); Py_XDECREF(temp_buffer); return result; } @@ -17576,6 +17613,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) diff --git a/configure b/configure index a058553480ca5a..d2a29f9313722f 100755 --- a/configure +++ b/configure @@ -19554,6 +19554,12 @@ if test "x$ac_cv_func_posix_spawnp" = xyes then : printf "%s\n" "#define HAVE_POSIX_SPAWNP 1" >>confdefs.h +fi +ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addchdir_np" "ac_cv_func_posix_spawn_file_actions_addchdir_np" +if test "x$ac_cv_func_posix_spawn_file_actions_addchdir_np" = xyes +then : + printf "%s\n" "#define HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR_NP 1" >>confdefs.h + fi ac_fn_c_check_func "$LINENO" "posix_spawn_file_actions_addclosefrom_np" "ac_cv_func_posix_spawn_file_actions_addclosefrom_np" if test "x$ac_cv_func_posix_spawn_file_actions_addclosefrom_np" = xyes diff --git a/configure.ac b/configure.ac index 23bd81ed4431b9..6de0500581d05f 100644 --- a/configure.ac +++ b/configure.ac @@ -5144,7 +5144,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 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 diff --git a/pyconfig.h.in b/pyconfig.h.in index dbf7865447bc2e..808dcad06ace24 100644 --- a/pyconfig.h.in +++ b/pyconfig.h.in @@ -949,6 +949,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