Skip to content

bpo-20104: Add flag capabilities to posix_spawn #6693

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 11 commits into from
Sep 7, 2018
34 changes: 33 additions & 1 deletion Doc/library/os.rst
Original file line number Diff line number Diff line change
Expand Up @@ -3392,7 +3392,9 @@ written in Python, such as a mail server's external command delivery program.
subprocesses.


.. function:: posix_spawn(path, argv, env, file_actions=None)
.. function:: posix_spawn(path, argv, env, file_actions=None, /, *, \
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't believe this / should be here.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #6725.

setpgroup=None, resetids=False, setsigmask=(), \
setsigdef=(), scheduler=None)

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

Expand Down Expand Up @@ -3430,6 +3432,36 @@ written in Python, such as a mail server's external command delivery program.
:c:func:`posix_spawn_file_actions_adddup2` 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
specified. If the value specified is 0, the child's process group ID will be
made the same as its process ID. If the value of *setpgroup* is not set, the
child will inherit the parent's process group ID. This argument corresponds
to the C library :c:data:`POSIX_SPAWN_SETPGROUP` flag.

If the *resetids* argument is ``True`` it will reset the effective UID and
GID of the child to the real UID and GID of the parent process. If the
argument is ``False``, then the child retains the effective UID and GID of
the parent. In either case, if the set-user-ID and set-group-ID permission
bits are enabled on the executable file, their effect will override the
setting of the effective UID and GID. This argument corresponds to the C
library :c:data:`POSIX_SPAWN_RESETIDS` flag.

The *setsigmask* argument will set the signal mask to the signal set
specified. If the parameter is not used, then the child inherits the
parent's signal mask. This argument corresponds to the C library
:c:data:`POSIX_SPAWN_SETSIGMASK` flag.

The *sigdef* argument will reset the disposition of all signals in the set
specified. This argument corresponds to the C library
:c:data:`POSIX_SPAWN_SETSIGDEF` flag.

The *scheduler* argument must be a tuple containing the (optional) scheduler
policy and an instance of :class:`sched_param` with the scheduler parameters.
A value of ``None`` in the place of the scheduler policy indicates that is
not being provided. This argument is a combination of the C library
:c:data:`POSIX_SPAWN_SETSCHEDPARAM` and :c:data:`POSIX_SPAWN_SETSCHEDULER`
flags.

.. versionadded:: 3.7


Expand Down
143 changes: 143 additions & 0 deletions Lib/test/test_posix.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import errno
import sys
import signal
import time
import os
import platform
Expand All @@ -16,6 +17,7 @@
import tempfile
import unittest
import warnings
import textwrap

_DUMMY_SYMLINK = os.path.join(tempfile.gettempdir(),
support.TESTFN + '-dummy-symlink')
Expand Down Expand Up @@ -1476,6 +1478,147 @@ def test_empty_file_actions(self):
)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

def test_resetids_explicit_default(self):
pid = posix.posix_spawn(
sys.executable,
[sys.executable, '-c', 'pass'],
os.environ,
resetids=False
)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

def test_resetids(self):
pid = posix.posix_spawn(
sys.executable,
[sys.executable, '-c', 'pass'],
os.environ,
resetids=True
)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

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

def test_setpgroup(self):
pid = posix.posix_spawn(
sys.executable,
[sys.executable, '-c', 'pass'],
os.environ,
setpgroup=os.getpgrp()
)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

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

@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
def test_setsigmask(self):
code = textwrap.dedent("""\
import _testcapi, signal
_testcapi.raise_signal(signal.SIGUSR1)""")

pid = posix.posix_spawn(
sys.executable,
[sys.executable, '-c', code],
os.environ,
setsigmask=[signal.SIGUSR1]
)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

def test_setsigmask_wrong_type(self):
with self.assertRaises(TypeError):
posix.posix_spawn(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"])
with self.assertRaises(ValueError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigmask=[signal.NSIG,
signal.NSIG+1])

@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
def test_setsigdef(self):
original_handler = signal.signal(signal.SIGUSR1, signal.SIG_IGN)
code = textwrap.dedent("""\
import _testcapi, signal
_testcapi.raise_signal(signal.SIGUSR1)""")
try:
pid = posix.posix_spawn(
sys.executable,
[sys.executable, '-c', code],
os.environ,
setsigdef=[signal.SIGUSR1]
)
finally:
signal.signal(signal.SIGUSR1, original_handler)

pid2, status = os.waitpid(pid, 0)
self.assertEqual(pid2, pid)
self.assertTrue(os.WIFSIGNALED(status), status)
self.assertEqual(os.WTERMSIG(status), signal.SIGUSR1)

def test_setsigdef_wrong_type(self):
with self.assertRaises(TypeError):
posix.posix_spawn(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"])
with self.assertRaises(ValueError):
posix.posix_spawn(sys.executable,
[sys.executable, "-c", "pass"],
os.environ, setsigdef=[signal.NSIG, signal.NSIG+1])

@unittest.skipUnless(hasattr(posix, 'sched_setscheduler'), "can't change scheduler")
def test_setscheduler_only_param(self):
policy = os.sched_getscheduler(0)
priority = os.sched_get_priority_min(policy)
code = textwrap.dedent(f"""\
import os
if os.sched_getscheduler(0) != {policy}:
os.exit(101)
if os.sched_getparam(0).sched_priority != {priority}:
os.exit(102)""")
pid = posix.posix_spawn(
sys.executable,
[sys.executable, '-c', code],
os.environ,
scheduler=(None, os.sched_param(priority))
)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

@unittest.skipUnless(hasattr(posix, 'sched_setscheduler'), "can't change scheduler")
def test_setscheduler_with_policy(self):
policy = os.sched_getscheduler(0)
priority = os.sched_get_priority_min(policy)
code = textwrap.dedent(f"""\
import os
if os.sched_getscheduler(0) != {policy}:
os.exit(101)
if os.sched_getparam(0).sched_priority != {priority}:
os.exit(102)""")
pid = posix.posix_spawn(
sys.executable,
[sys.executable, '-c', code],
os.environ,
scheduler=(policy, os.sched_param(priority))
)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))

def test_multiple_file_actions(self):
file_actions = [
(os.POSIX_SPAWN_OPEN, 3, os.path.realpath(__file__), os.O_RDONLY, 0),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Added support for the `setpgroup`, `resetids`, `setsigmask`, `setsigdef` and
`scheduler` parameters of `posix_spawn`. Patch by Pablo Galindo.
39 changes: 30 additions & 9 deletions Modules/clinic/posixmodule.c.h

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

Loading