###############################################################################
# Launch a subprocess using forkexec and make sure only the needed fd are
# shared in the two process.
#
# author: Thomas Moreau and Olivier Grisel
#
import sys
import os
import subprocess
def fork_exec(cmd, keep_fds, env=None):
import _posixsubprocess
# Encoded command args as bytes:
cmd = [os.fsencode(arg) for arg in cmd]
# Copy the environment variables to set in the child process (also encoded
# as bytes).
env = env or {}
env = {**os.environ, **env}
encoded_env = []
for key, value in env.items():
encoded_env.append(os.fsencode(f"{key}={value}"))
# Fds with fileno larger than 3 (stdin=0, stdout=1, stderr=2) are be closed
# in the child process, except for those passed in keep_fds.
keep_fds = tuple(sorted(map(int, keep_fds)))
errpipe_read, errpipe_write = os.pipe()
if sys.version_info >= (3, 14):
# Python >= 3.14 removed allow_vfork from _posixsubprocess.fork_exec,
# see https://github.com/python/cpython/pull/121383
pgid_to_set = [-1]
allow_vfork = []
elif sys.version_info >= (3, 11):
# Python 3.11 - 3.13 has allow_vfork in _posixsubprocess.fork_exec
pgid_to_set = [-1]
allow_vfork = [subprocess._USE_VFORK]
else:
# Python < 3.11
pgid_to_set = []
allow_vfork = []
try:
return _posixsubprocess.fork_exec(
cmd, # args
cmd[0:1], # executable_list
True, # close_fds
keep_fds, # pass_fds
None, # cwd
encoded_env, # env
-1, # p2cread
-1, # p2cwrite
-1, # c2pread
-1, # c2pwrite
-1, # errread
-1, # errwrite
errpipe_read, # errpipe_read
errpipe_write, # errpipe_write
False, # restore_signal
False, # call_setsid
*pgid_to_set, # pgid_to_set
None, # gid
None, # extra_groups
None, # uid
-1, # child_umask
None, # preexec_fn
*allow_vfork, # extra flag if vfork is available
)
finally:
os.close(errpipe_read)
os.close(errpipe_write)