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:
parent
5e922658fb
commit
254a4663d8
|
@ -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
|
||||
|
||||
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
Added support for the `setpgroup`, `resetids`, `setsigmask`, `setsigdef` and
|
||||
`scheduler` parameters of `posix_spawn`. Patch by Pablo Galindo.
|
|
@ -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]*/
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue