Thanks to visit codestin.com
Credit goes to github.com

Skip to content

bpo-35674: Expose posix_spawnp #11554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 16, 2019
Merged
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
17 changes: 17 additions & 0 deletions Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3396,6 +3396,10 @@ written in Python, such as a mail server's external command delivery program.
The positional-only arguments *path*, *args*, and *env* are similar to
:func:`execve`.

The *path* parameter is the path to the executable file.The *path* should
contain a directory.Use :func:`posix_spawnp` to pass an executable file
without directory.

The *file_actions* argument may be a sequence of tuples describing actions
to take on specific file descriptors in the child process between the C
library implementation's :c:func:`fork` and :c:func:`exec` steps.
Expand Down Expand Up @@ -3459,6 +3463,19 @@ written in Python, such as a mail server's external command delivery program.
.. versionadded:: 3.7


.. function:: posix_spawnp(path, argv, env, *, file_actions=None, \
setpgroup=None, resetids=False, setsigmask=(), \
setsigdef=(), scheduler=None)

Wraps the :c:func:`posix_spawnp` C library API for use from Python.

Similar to :func:`posix_spawn` except that the system searches
for the *executable* file in the list of directories specified by the
:envvar:`PATH` environment variable (in the same way as for ``execvp(3)``).

.. versionadded:: 3.8


.. function:: register_at_fork(*, before=None, after_in_parent=None, \
after_in_child=None)

Expand Down
190 changes: 119 additions & 71 deletions Lib/test/test_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -1489,10 +1489,10 @@ def test_setgroups(self):
self.assertListEqual(groups, posix.getgroups())


@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
class TestPosixSpawn(unittest.TestCase):
# Program which does nothing and exit with status 0 (success)
class _PosixSpawnMixin:
# Program which does nothing and exits with status 0 (success)
NOOP_PROGRAM = (sys.executable, '-I', '-S', '-c', 'pass')
spawn_func = None

def python_args(self, *args):
# Disable site module to avoid side effects. For example,
Expand All @@ -1511,17 +1511,17 @@ def test_returns_pid(self):
pidfile.write(str(os.getpid()))
"""
args = self.python_args('-c', script)
pid = posix.posix_spawn(args[0], args, os.environ)
pid = self.spawn_func(args[0], args, os.environ)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(pidfile) as f:
self.assertEqual(f.read(), str(pid))

def test_no_such_executable(self):
no_such_executable = 'no_such_executable'
try:
pid = posix.posix_spawn(no_such_executable,
[no_such_executable],
os.environ)
pid = self.spawn_func(no_such_executable,
[no_such_executable],
os.environ)
except FileNotFoundError as exc:
self.assertEqual(exc.filename, no_such_executable)
else:
Expand All @@ -1538,14 +1538,14 @@ def test_specify_environment(self):
envfile.write(os.environ['foo'])
"""
args = self.python_args('-c', script)
pid = posix.posix_spawn(args[0], args,
{**os.environ, 'foo': 'bar'})
pid = self.spawn_func(args[0], args,
{**os.environ, 'foo': 'bar'})
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(envfile) as f:
self.assertEqual(f.read(), 'bar')

def test_empty_file_actions(self):
pid = posix.posix_spawn(
pid = self.spawn_func(
self.NOOP_PROGRAM[0],
self.NOOP_PROGRAM,
os.environ,
Expand All @@ -1554,7 +1554,7 @@ def test_empty_file_actions(self):
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

def test_resetids_explicit_default(self):
pid = posix.posix_spawn(
pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', 'pass'],
os.environ,
Expand All @@ -1563,7 +1563,7 @@ def test_resetids_explicit_default(self):
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

def test_resetids(self):
pid = posix.posix_spawn(
pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', 'pass'],
os.environ,
Expand All @@ -1573,12 +1573,12 @@ def test_resetids(self):

def test_resetids_wrong_type(self):
with self.assertRaises(TypeError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, resetids=None)
self.spawn_func(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, resetids=None)

def test_setpgroup(self):
pid = posix.posix_spawn(
pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', 'pass'],
os.environ,
Expand All @@ -1588,9 +1588,9 @@ def test_setpgroup(self):

def test_setpgroup_wrong_type(self):
with self.assertRaises(TypeError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setpgroup="023")
self.spawn_func(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setpgroup="023")

@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
Expand All @@ -1599,7 +1599,7 @@ def test_setsigmask(self):
import signal
signal.raise_signal(signal.SIGUSR1)""")

pid = posix.posix_spawn(
pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', code],
os.environ,
Expand All @@ -1609,18 +1609,18 @@ def test_setsigmask(self):

def test_setsigmask_wrong_type(self):
with self.assertRaises(TypeError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigmask=34)
self.spawn_func(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigmask=34)
with self.assertRaises(TypeError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigmask=["j"])
self.spawn_func(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigmask=["j"])
with self.assertRaises(ValueError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigmask=[signal.NSIG,
signal.NSIG+1])
self.spawn_func(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigmask=[signal.NSIG,
signal.NSIG+1])

@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
Expand All @@ -1630,7 +1630,7 @@ def test_setsigdef(self):
import signal
signal.raise_signal(signal.SIGUSR1)""")
try:
pid = posix.posix_spawn(
pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', code],
os.environ,
Expand All @@ -1646,17 +1646,17 @@ def test_setsigdef(self):

def test_setsigdef_wrong_type(self):
with self.assertRaises(TypeError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigdef=34)
self.spawn_func(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigdef=34)
with self.assertRaises(TypeError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigdef=["j"])
self.spawn_func(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigdef=["j"])
with self.assertRaises(ValueError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])
self.spawn_func(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])

@requires_sched
@unittest.skipIf(sys.platform.startswith(('freebsd', 'netbsd')),
Expand All @@ -1670,7 +1670,7 @@ def test_setscheduler_only_param(self):
sys.exit(101)
if os.sched_getparam(0).sched_priority != {priority}:
sys.exit(102)""")
pid = posix.posix_spawn(
pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', code],
os.environ,
Expand All @@ -1690,7 +1690,7 @@ def test_setscheduler_with_policy(self):
sys.exit(101)
if os.sched_getparam(0).sched_priority != {priority}:
sys.exit(102)""")
pid = posix.posix_spawn(
pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', code],
os.environ,
Expand All @@ -1704,40 +1704,40 @@ def test_multiple_file_actions(self):
(os.POSIX_SPAWN_CLOSE, 0),
(os.POSIX_SPAWN_DUP2, 1, 4),
]
pid = posix.posix_spawn(self.NOOP_PROGRAM[0],
self.NOOP_PROGRAM,
os.environ,
file_actions=file_actions)
pid = self.spawn_func(self.NOOP_PROGRAM[0],
self.NOOP_PROGRAM,
os.environ,
file_actions=file_actions)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

def test_bad_file_actions(self):
args = self.NOOP_PROGRAM
with self.assertRaises(TypeError):
posix.posix_spawn(args[0], args, os.environ,
file_actions=[None])
self.spawn_func(args[0], args, os.environ,
file_actions=[None])
with self.assertRaises(TypeError):
posix.posix_spawn(args[0], args, os.environ,
file_actions=[()])
self.spawn_func(args[0], args, os.environ,
file_actions=[()])
with self.assertRaises(TypeError):
posix.posix_spawn(args[0], args, os.environ,
file_actions=[(None,)])
self.spawn_func(args[0], args, os.environ,
file_actions=[(None,)])
with self.assertRaises(TypeError):
posix.posix_spawn(args[0], args, os.environ,
file_actions=[(12345,)])
self.spawn_func(args[0], args, os.environ,
file_actions=[(12345,)])
with self.assertRaises(TypeError):
posix.posix_spawn(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_CLOSE,)])
self.spawn_func(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_CLOSE,)])
with self.assertRaises(TypeError):
posix.posix_spawn(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_CLOSE, 1, 2)])
self.spawn_func(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_CLOSE, 1, 2)])
with self.assertRaises(TypeError):
posix.posix_spawn(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_CLOSE, None)])
self.spawn_func(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_CLOSE, None)])
with self.assertRaises(ValueError):
posix.posix_spawn(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_OPEN,
3, __file__ + '\0',
os.O_RDONLY, 0)])
self.spawn_func(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_OPEN,
3, __file__ + '\0',
os.O_RDONLY, 0)])

def test_open_file(self):
outfile = support.TESTFN
Expand All @@ -1752,8 +1752,8 @@ def test_open_file(self):
stat.S_IRUSR | stat.S_IWUSR),
]
args = self.python_args('-c', script)
pid = posix.posix_spawn(args[0], args, os.environ,
file_actions=file_actions)
pid = self.spawn_func(args[0], args, os.environ,
file_actions=file_actions)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(outfile) as f:
self.assertEqual(f.read(), 'hello')
Expand All @@ -1770,8 +1770,8 @@ def test_close_file(self):
closefile.write('is closed %d' % e.errno)
"""
args = self.python_args('-c', script)
pid = posix.posix_spawn(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_CLOSE, 0),])
pid = self.spawn_func(args[0], args, os.environ,
file_actions=[(os.POSIX_SPAWN_CLOSE, 0)])
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(closefile) as f:
self.assertEqual(f.read(), 'is closed %d' % errno.EBADF)
Expand All @@ -1788,16 +1788,64 @@ def test_dup2(self):
(os.POSIX_SPAWN_DUP2, childfile.fileno(), 1),
]
args = self.python_args('-c', script)
pid = posix.posix_spawn(args[0], args, os.environ,
file_actions=file_actions)
pid = self.spawn_func(args[0], args, os.environ,
file_actions=file_actions)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(dupfile) as f:
self.assertEqual(f.read(), 'hello')


@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
class TestPosixSpawn(unittest.TestCase, _PosixSpawnMixin):
spawn_func = getattr(posix, 'posix_spawn', None)


@unittest.skipUnless(hasattr(os, 'posix_spawnp'), "test needs os.posix_spawnp")
class TestPosixSpawnP(unittest.TestCase, _PosixSpawnMixin):
spawn_func = getattr(posix, 'posix_spawnp', None)

@support.skip_unless_symlink
def test_posix_spawnp(self):
# Use a symlink to create a program in its own temporary directory
temp_dir = tempfile.mkdtemp()
self.addCleanup(support.rmtree, temp_dir)

program = 'posix_spawnp_test_program.exe'
program_fullpath = os.path.join(temp_dir, program)
os.symlink(sys.executable, program_fullpath)

try:
path = os.pathsep.join((temp_dir, os.environ['PATH']))
except KeyError:
path = temp_dir # PATH is not set

spawn_args = (program, '-I', '-S', '-c', 'pass')
code = textwrap.dedent("""
import os
args = %a
pid = os.posix_spawnp(args[0], args, os.environ)
pid2, status = os.waitpid(pid, 0)
if pid2 != pid:
raise Exception(f"pid {pid2} != {pid}")
if status != 0:
raise Exception(f"status {status} != 0")
""" % (spawn_args,))

# Use a subprocess to test os.posix_spawnp() with a modified PATH
# environment variable: posix_spawnp() uses the current environment
# to locate the program, not its environment argument.
args = ('-c', code)
assert_python_ok(*args, PATH=path)


def test_main():
try:
support.run_unittest(PosixTester, PosixGroupsTester, TestPosixSpawn)
support.run_unittest(
PosixTester,
PosixGroupsTester,
TestPosixSpawn,
TestPosixSpawnP,
)
finally:
support.reap_children()

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Add a new :func:`os.posix_spawnp` function.
Patch by Joannah Nanjekye.
Loading