bpo-20104: Add flag capabilities to posix_spawn (GH-6693)

Implement the "attributes objects" parameter of `os.posix_spawn` to complete the implementation and fully cover the underlying API.
This commit is contained in:
Pablo Galindo 2018-09-07 16:44:24 +01:00 committed by GitHub
parent 5e922658fb
commit 254a4663d8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 344 additions and 14 deletions

View File

@ -3394,7 +3394,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, /, *, \
setpgroup=None, resetids=False, setsigmask=(), \
setsigdef=(), scheduler=None)
Wraps the :c:func:`posix_spawn` C library API for use from Python.
@ -3432,6 +3434,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

View File

@ -8,6 +8,7 @@ posix = support.import_module('posix')
import errno
import sys
import signal
import time
import os
import platform
@ -16,6 +17,7 @@ import stat
import tempfile
import unittest
import warnings
import textwrap
_DUMMY_SYMLINK = os.path.join(tempfile.gettempdir(),
support.TESTFN + '-dummy-symlink')
@ -1540,6 +1542,147 @@ class TestPosixSpawn(unittest.TestCase):
)
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),

View File

@ -0,0 +1,2 @@
Added support for the `setpgroup`, `resetids`, `setsigmask`, `setsigdef` and
`scheduler` parameters of `posix_spawn`. Patch by Pablo Galindo.

View File

@ -1730,7 +1730,9 @@ exit:
#if defined(HAVE_POSIX_SPAWN)
PyDoc_STRVAR(os_posix_spawn__doc__,
"posix_spawn($module, path, argv, env, file_actions=None, /)\n"
"posix_spawn($module, path, argv, env, file_actions=None, /, *,\n"
" setpgroup=None, resetids=False, setsigmask=(),\n"
" setsigdef=(), scheduler=None)\n"
"--\n"
"\n"
"Execute the program specified by path in a new process.\n"
@ -1742,29 +1744,48 @@ PyDoc_STRVAR(os_posix_spawn__doc__,
" env\n"
" Dictionary of strings mapping to strings.\n"
" file_actions\n"
" A sequence of file action tuples.");
" A sequence of file action tuples.\n"
" setpgroup\n"
" The pgroup to use with the POSIX_SPAWN_SETPGROUP flag.\n"
" resetids\n"
" If the value is `True` the POSIX_SPAWN_RESETIDS will be activated.\n"
" setsigmask\n"
" The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag.\n"
" setsigdef\n"
" The sigmask to use with the POSIX_SPAWN_SETSIGDEF flag.\n"
" scheduler\n"
" A tuple with the scheduler policy (optional) and parameters.");
#define OS_POSIX_SPAWN_METHODDEF \
{"posix_spawn", (PyCFunction)os_posix_spawn, METH_FASTCALL, os_posix_spawn__doc__},
{"posix_spawn", (PyCFunction)os_posix_spawn, METH_FASTCALL|METH_KEYWORDS, os_posix_spawn__doc__},
static PyObject *
os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
PyObject *env, PyObject *file_actions);
PyObject *env, PyObject *file_actions,
PyObject *setpgroup, int resetids, PyObject *setsigmask,
PyObject *setsigdef, PyObject *scheduler);
static PyObject *
os_posix_spawn(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
os_posix_spawn(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
static const char * const _keywords[] = {"", "", "", "", "setpgroup", "resetids", "setsigmask", "setsigdef", "scheduler", NULL};
static _PyArg_Parser _parser = {"O&OO|O$OiOOO:posix_spawn", _keywords, 0};
path_t path = PATH_T_INITIALIZE("posix_spawn", "path", 0, 0);
PyObject *argv;
PyObject *env;
PyObject *file_actions = Py_None;
PyObject *setpgroup = NULL;
int resetids = 0;
PyObject *setsigmask = NULL;
PyObject *setsigdef = NULL;
PyObject *scheduler = NULL;
if (!_PyArg_ParseStack(args, nargs, "O&OO|O:posix_spawn",
path_converter, &path, &argv, &env, &file_actions)) {
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
path_converter, &path, &argv, &env, &file_actions, &setpgroup, &resetids, &setsigmask, &setsigdef, &scheduler)) {
goto exit;
}
return_value = os_posix_spawn_impl(module, &path, argv, env, file_actions);
return_value = os_posix_spawn_impl(module, &path, argv, env, file_actions, setpgroup, resetids, setsigmask, setsigdef, scheduler);
exit:
/* Cleanup for path */
@ -6627,4 +6648,4 @@ exit:
#ifndef OS_GETRANDOM_METHODDEF
#define OS_GETRANDOM_METHODDEF
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
/*[clinic end generated code: output=47fb6a3e88cba6d9 input=a9049054013a1b77]*/
/*[clinic end generated code: output=ef78384ae88712e1 input=a9049054013a1b77]*/

View File

@ -5177,6 +5177,114 @@ enum posix_spawn_file_actions_identifier {
POSIX_SPAWN_DUP2
};
static int
convert_sched_param(PyObject *param, struct sched_param *res);
static int
parse_posix_spawn_flags(PyObject *setpgroup, int resetids, PyObject *setsigmask,
PyObject *setsigdef, PyObject *scheduler,
posix_spawnattr_t *attrp)
{
long all_flags = 0;
errno = posix_spawnattr_init(attrp);
if (errno) {
posix_error();
return -1;
}
if (setpgroup) {
pid_t pgid = PyLong_AsPid(setpgroup);
if (pgid == (pid_t)-1 && PyErr_Occurred()) {
goto fail;
}
errno = posix_spawnattr_setpgroup(attrp, pgid);
if (errno) {
posix_error();
goto fail;
}
all_flags |= POSIX_SPAWN_SETPGROUP;
}
if (resetids) {
all_flags |= POSIX_SPAWN_RESETIDS;
}
if (setsigmask) {
sigset_t set;
if (!_Py_Sigset_Converter(setsigmask, &set)) {
goto fail;
}
errno = posix_spawnattr_setsigmask(attrp, &set);
if (errno) {
posix_error();
goto fail;
}
all_flags |= POSIX_SPAWN_SETSIGMASK;
}
if (setsigdef) {
sigset_t set;
if (!_Py_Sigset_Converter(setsigdef, &set)) {
goto fail;
}
errno = posix_spawnattr_setsigdefault(attrp, &set);
if (errno) {
posix_error();
goto fail;
}
all_flags |= POSIX_SPAWN_SETSIGDEF;
}
if (scheduler) {
#ifdef POSIX_SPAWN_SETSCHEDULER
PyObject *py_schedpolicy;
struct sched_param schedparam;
if (!PyArg_ParseTuple(scheduler, "OO&"
";A scheduler tuple must have two elements",
&py_schedpolicy, convert_sched_param, &schedparam)) {
goto fail;
}
if (py_schedpolicy != Py_None) {
int schedpolicy = _PyLong_AsInt(py_schedpolicy);
if (schedpolicy == -1 && PyErr_Occurred()) {
goto fail;
}
errno = posix_spawnattr_setschedpolicy(attrp, schedpolicy);
if (errno) {
posix_error();
goto fail;
}
all_flags |= POSIX_SPAWN_SETSCHEDULER;
}
errno = posix_spawnattr_setschedparam(attrp, &schedparam);
if (errno) {
posix_error();
goto fail;
}
all_flags |= POSIX_SPAWN_SETSCHEDPARAM;
#else
PyErr_SetString(PyExc_NotImplementedError,
"The scheduler option is not supported in this system.");
goto fail;
#endif
}
errno = posix_spawnattr_setflags(attrp, all_flags);
if (errno) {
posix_error();
goto fail;
}
return 0;
fail:
(void)posix_spawnattr_destroy(attrp);
return -1;
}
static int
parse_file_actions(PyObject *file_actions,
posix_spawn_file_actions_t *file_actionsp,
@ -5277,6 +5385,7 @@ parse_file_actions(PyObject *file_actions,
}
Py_DECREF(file_action);
}
Py_DECREF(seq);
return 0;
@ -5299,19 +5408,33 @@ os.posix_spawn
file_actions: object = None
A sequence of file action tuples.
/
*
setpgroup: object = NULL
The pgroup to use with the POSIX_SPAWN_SETPGROUP flag.
resetids: bool(accept={int}) = False
If the value is `True` the POSIX_SPAWN_RESETIDS will be activated.
setsigmask: object(c_default='NULL') = ()
The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag.
setsigdef: object(c_default='NULL') = ()
The sigmask to use with the POSIX_SPAWN_SETSIGDEF flag.
scheduler: object = NULL
A tuple with the scheduler policy (optional) and parameters.
Execute the program specified by path in a new process.
[clinic start generated code]*/
static PyObject *
os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
PyObject *env, PyObject *file_actions)
/*[clinic end generated code: output=d023521f541c709c input=a3db1021d33230dc]*/
PyObject *env, PyObject *file_actions,
PyObject *setpgroup, int resetids, PyObject *setsigmask,
PyObject *setsigdef, PyObject *scheduler)
/*[clinic end generated code: output=45dfa4c515d09f2c input=2d7a7578430a90f0]*/
{
EXECV_CHAR **argvlist = NULL;
EXECV_CHAR **envlist = NULL;
posix_spawn_file_actions_t file_actions_buf;
posix_spawn_file_actions_t *file_actionsp = NULL;
posix_spawnattr_t attr;
posix_spawnattr_t *attrp = NULL;
Py_ssize_t argc, envc;
PyObject *result = NULL;
PyObject *temp_buffer = NULL;
@ -5373,9 +5496,15 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
file_actionsp = &file_actions_buf;
}
if (parse_posix_spawn_flags(setpgroup, resetids, setsigmask,
setsigdef, scheduler, &attr)) {
goto exit;
}
attrp = &attr;
_Py_BEGIN_SUPPRESS_IPH
err_code = posix_spawn(&pid, path->narrow,
file_actionsp, NULL, argvlist, envlist);
file_actionsp, attrp, argvlist, envlist);
_Py_END_SUPPRESS_IPH
if (err_code) {
errno = err_code;
@ -5388,6 +5517,9 @@ exit:
if (file_actionsp) {
(void)posix_spawn_file_actions_destroy(file_actionsp);
}
if (attrp) {
(void)posix_spawnattr_destroy(attrp);
}
if (envlist) {
free_string_array(envlist, envc);
}