python3: expose posix_spawn in os module

This commit is contained in:
hanxinke 2021-05-25 05:10:34 -04:00
parent 83a8b47fb3
commit 046f4dd6a3
33 changed files with 7439 additions and 1 deletions

View File

@ -0,0 +1,460 @@
From 52628b241a1559840479e72c32214cf4a6b4a92f Mon Sep 17 00:00:00 2001
From: Pablo Galindo <Pablogsal@gmail.com>
Date: Fri, 7 Sep 2018 16:44:24 +0100
Subject: [PATCH] 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.
Conflict:NA
Reference:https://github.com/python/cpython/commit/254a4663d8c5970ae2928185c50ebaa6c7e62c80
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 143 ++++++++++++++++++
.../2018-05-05-23-26-58.bpo-20104.tDBciE.rst | 2 +
Modules/clinic/posixmodule.c.h | 37 ++++-
Modules/posixmodule.c | 140 ++++++++++++++++-
4 files changed, 310 insertions(+), 12 deletions(-)
create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-05-05-23-26-58.bpo-20104.tDBciE.rst
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index a22baac..1103ff3 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -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')
@@ -1558,6 +1560,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),
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-05-05-23-26-58.bpo-20104.tDBciE.rst b/Misc/NEWS.d/next/Core and Builtins/2018-05-05-23-26-58.bpo-20104.tDBciE.rst
new file mode 100644
index 0000000..1d725ba
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2018-05-05-23-26-58.bpo-20104.tDBciE.rst
@@ -0,0 +1,2 @@
+Added support for the `setpgroup`, `resetids`, `setsigmask`, `setsigdef` and
+`scheduler` parameters of `posix_spawn`. Patch by Pablo Galindo.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 1371ef6..a3b5a8b 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -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 */
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 441c82e..a1808f6 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5138,6 +5138,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,
@@ -5238,6 +5346,7 @@ parse_file_actions(PyObject *file_actions,
}
Py_DECREF(file_action);
}
+
Py_DECREF(seq);
return 0;
@@ -5260,19 +5369,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;
@@ -5334,9 +5457,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;
@@ -5349,6 +5478,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);
}
--
2.23.0

View File

@ -0,0 +1,209 @@
From 80c7ad17e8004096e508db5fc78d3b69b51e354e Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Sat, 8 Sep 2018 14:48:18 +0300
Subject: [PATCH] bpo-20104: Change the file_actions parameter of
os.posix_spawn(). (GH-6725)
* Make its default value an empty tuple instead of None.
* Make it a keyword-only parameter.
Conflict:NA
Reference:https://github.com/python/cpython/commit/d700f97b627989d41cd4629dc02969f9a6b56d2f
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 57 +++++++++++++++++-----------------
Modules/clinic/posixmodule.c.h | 8 ++---
Modules/posixmodule.c | 9 +++---
3 files changed, 37 insertions(+), 37 deletions(-)
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 77fedb1..ee3c5f0 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1527,8 +1527,7 @@ class TestPosixSpawn(unittest.TestCase):
pidfile.write(str(os.getpid()))
"""
args = self.python_args('-c', script)
- pid = posix.posix_spawn(args[0], args,
- os.environ)
+ pid = posix.posix_spawn(args[0], args, os.environ)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(pidfile) as f:
self.assertEqual(f.read(), str(pid))
@@ -1566,7 +1565,7 @@ class TestPosixSpawn(unittest.TestCase):
self.NOOP_PROGRAM[0],
self.NOOP_PROGRAM,
os.environ,
- []
+ file_actions=[]
)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
@@ -1719,37 +1718,38 @@ class TestPosixSpawn(unittest.TestCase):
]
pid = posix.posix_spawn(self.NOOP_PROGRAM[0],
self.NOOP_PROGRAM,
- os.environ, file_actions)
+ 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, [None])
+ posix.posix_spawn(args[0], args, os.environ,
+ file_actions=[None])
with self.assertRaises(TypeError):
- posix.posix_spawn(args[0], args,
- os.environ, [()])
+ posix.posix_spawn(args[0], args, os.environ,
+ file_actions=[()])
with self.assertRaises(TypeError):
- posix.posix_spawn(args[0], args,
- os.environ, [(None,)])
+ posix.posix_spawn(args[0], args, os.environ,
+ file_actions=[(None,)])
with self.assertRaises(TypeError):
- posix.posix_spawn(args[0], args,
- os.environ, [(12345,)])
+ posix.posix_spawn(args[0], args, os.environ,
+ file_actions=[(12345,)])
with self.assertRaises(TypeError):
- posix.posix_spawn(args[0], args,
- os.environ, [(os.POSIX_SPAWN_CLOSE,)])
+ posix.posix_spawn(args[0], args, os.environ,
+ file_actions=[(os.POSIX_SPAWN_CLOSE,)])
with self.assertRaises(TypeError):
- posix.posix_spawn(args[0], args,
- os.environ, [(os.POSIX_SPAWN_CLOSE, 1, 2)])
+ posix.posix_spawn(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, [(os.POSIX_SPAWN_CLOSE, None)])
+ posix.posix_spawn(args[0], args, os.environ,
+ file_actions=[(os.POSIX_SPAWN_CLOSE, None)])
with self.assertRaises(ValueError):
- posix.posix_spawn(args[0], args,
- os.environ,
- [(os.POSIX_SPAWN_OPEN, 3, __file__ + '\0',
- os.O_RDONLY, 0)])
+ posix.posix_spawn(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
@@ -1764,8 +1764,8 @@ class TestPosixSpawn(unittest.TestCase):
stat.S_IRUSR | stat.S_IWUSR),
]
args = self.python_args('-c', script)
- pid = posix.posix_spawn(args[0], args,
- os.environ, file_actions)
+ pid = posix.posix_spawn(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')
@@ -1782,9 +1782,8 @@ class TestPosixSpawn(unittest.TestCase):
closefile.write('is closed %d' % e.errno)
"""
args = self.python_args('-c', script)
- pid = posix.posix_spawn(args[0], args,
- os.environ,
- [(os.POSIX_SPAWN_CLOSE, 0),])
+ pid = posix.posix_spawn(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)
@@ -1801,8 +1800,8 @@ class TestPosixSpawn(unittest.TestCase):
(os.POSIX_SPAWN_DUP2, childfile.fileno(), 1),
]
args = self.python_args('-c', script)
- pid = posix.posix_spawn(args[0], args,
- os.environ, file_actions)
+ pid = posix.posix_spawn(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')
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index a3b5a8b..133abd7 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -1730,7 +1730,7 @@ 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=(),\n"
" setpgroup=None, resetids=False, setsigmask=(),\n"
" setsigdef=(), scheduler=None)\n"
"--\n"
@@ -1769,12 +1769,12 @@ static PyObject *
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};
+ static const char * const _keywords[] = {"", "", "", "file_actions", "setpgroup", "resetids", "setsigmask", "setsigdef", "scheduler", NULL};
+ static _PyArg_Parser _parser = {"O&OO|$OOiOOO: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 *file_actions = NULL;
PyObject *setpgroup = NULL;
int resetids = 0;
PyObject *setsigmask = NULL;
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index a1808f6..dc6a22f 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5366,10 +5366,10 @@ os.posix_spawn
Tuple or list of strings.
env: object
Dictionary of strings mapping to strings.
- file_actions: object = None
- A sequence of file action tuples.
/
*
+ file_actions: object(c_default='NULL') = ()
+ A sequence of file action tuples.
setpgroup: object = NULL
The pgroup to use with the POSIX_SPAWN_SETPGROUP flag.
resetids: bool(accept={int}) = False
@@ -5380,6 +5380,7 @@ os.posix_spawn
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]*/
@@ -5388,7 +5389,7 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
PyObject *env, PyObject *file_actions,
PyObject *setpgroup, int resetids, PyObject *setsigmask,
PyObject *setsigdef, PyObject *scheduler)
-/*[clinic end generated code: output=45dfa4c515d09f2c input=2d7a7578430a90f0]*/
+/*[clinic end generated code: output=45dfa4c515d09f2c input=2891c2f1d457e39b]*/
{
EXECV_CHAR **argvlist = NULL;
EXECV_CHAR **envlist = NULL;
@@ -5438,7 +5439,7 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
goto exit;
}
- if (file_actions != Py_None) {
+ if (file_actions != NULL) {
/* There is a bug in old versions of glibc that makes some of the
* helper functions for manipulating file actions not copy the provided
* buffers. The problem is that posix_spawn_file_actions_addopen does not
--
2.23.0

View File

@ -0,0 +1,369 @@
From a4faef5368c5a15a2a4bb83aee451708ed622aee Mon Sep 17 00:00:00 2001
From: Pablo Galindo <Pablogsal@gmail.com>
Date: Mon, 29 Jan 2018 01:56:10 +0000
Subject: [PATCH] bpo-20104: Expose `posix_spawn` in the os module (GH-5109)
Add os.posix_spawn to wrap the low level POSIX API of the same name.
Contributed by Pablo Galindo.
Conflict:NA
Reference:https://github.com/python/cpython/commit/6c6ddf97c402709713d668d0ed53836a7749ba99
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 16 ++
.../2018-01-06-01-14-53.bpo-20104.9DkKb8.rst | 1 +
Modules/clinic/posixmodule.c.h | 52 +++++
Modules/posixmodule.c | 202 ++++++++++++++++++
4 files changed, 271 insertions(+)
create mode 100644 Misc/NEWS.d/next/Core and Builtins/2018-01-06-01-14-53.bpo-20104.9DkKb8.rst
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 08306b2..c67087c 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -192,6 +192,22 @@ class PosixTester(unittest.TestCase):
os.close(fp)
+ @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
+ def test_posix_spawn(self):
+ pid = posix.posix_spawn(sys.executable, [sys.executable, "-c", "pass"], os.environ,[])
+ self.assertEqual(os.waitpid(pid,0),(pid,0))
+
+
+ @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
+ def test_posix_spawn_file_actions(self):
+ file_actions = []
+ file_actions.append((0,3,os.path.realpath(__file__),0,0))
+ file_actions.append((os.POSIX_SPAWN_CLOSE,2))
+ file_actions.append((os.POSIX_SPAWN_DUP2,1,4))
+ pid = posix.posix_spawn(sys.executable, [sys.executable, "-c", "pass"], os.environ, file_actions)
+ self.assertEqual(os.waitpid(pid,0),(pid,0))
+
+
@unittest.skipUnless(hasattr(posix, 'waitid'), "test needs posix.waitid()")
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
def test_waitid(self):
diff --git a/Misc/NEWS.d/next/Core and Builtins/2018-01-06-01-14-53.bpo-20104.9DkKb8.rst b/Misc/NEWS.d/next/Core and Builtins/2018-01-06-01-14-53.bpo-20104.9DkKb8.rst
new file mode 100644
index 0000000..cb69f32
--- /dev/null
+++ b/Misc/NEWS.d/next/Core and Builtins/2018-01-06-01-14-53.bpo-20104.9DkKb8.rst
@@ -0,0 +1 @@
+Expose posix_spawn as a low level API in the os module.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 22eef68..07be916 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -1727,6 +1727,54 @@ exit:
#endif /* defined(HAVE_EXECV) */
+#if defined(HAVE_POSIX_SPAWN)
+
+PyDoc_STRVAR(os_posix_spawn__doc__,
+"posix_spawn($module, path, argv, env, file_actions=None, /)\n"
+"--\n"
+"\n"
+"Execute the program specified by path in a new process.\n"
+"\n"
+" path\n"
+" Path of executable file.\n"
+" argv\n"
+" Tuple or list of strings.\n"
+" env\n"
+" Dictionary of strings mapping to strings.\n"
+" file_actions\n"
+" FileActions object.");
+
+#define OS_POSIX_SPAWN_METHODDEF \
+ {"posix_spawn", (PyCFunction)os_posix_spawn, METH_FASTCALL, os_posix_spawn__doc__},
+
+static PyObject *
+os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
+ PyObject *env, PyObject *file_actions);
+
+static PyObject *
+os_posix_spawn(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
+{
+ PyObject *return_value = NULL;
+ path_t path = PATH_T_INITIALIZE("posix_spawn", "path", 0, 0);
+ PyObject *argv;
+ PyObject *env;
+ PyObject *file_actions = Py_None;
+
+ if (!_PyArg_ParseStack(args, nargs, "O&OO|O:posix_spawn",
+ path_converter, &path, &argv, &env, &file_actions)) {
+ goto exit;
+ }
+ return_value = os_posix_spawn_impl(module, &path, argv, env, file_actions);
+
+exit:
+ /* Cleanup for path */
+ path_cleanup(&path);
+
+ return return_value;
+}
+
+#endif /* defined(HAVE_POSIX_SPAWN) */
+
#if (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV))
PyDoc_STRVAR(os_spawnv__doc__,
@@ -6147,6 +6195,10 @@ exit:
#define OS_EXECVE_METHODDEF
#endif /* !defined(OS_EXECVE_METHODDEF) */
+#ifndef OS_POSIX_SPAWN_METHODDEF
+ #define OS_POSIX_SPAWN_METHODDEF
+#endif /* !defined(OS_POSIX_SPAWN_METHODDEF) */
+
#ifndef OS_SPAWNV_METHODDEF
#define OS_SPAWNV_METHODDEF
#endif /* !defined(OS_SPAWNV_METHODDEF) */
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 43d4302..fcb22c2 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -176,6 +176,7 @@ corresponding Unix manual entries for more information on calls.");
#else
/* Unix functions that the configure script doesn't check for */
#define HAVE_EXECV 1
+#define HAVE_POSIX_SPAWN 1
#define HAVE_FORK 1
#if defined(__USLC__) && defined(__SCO_VERSION__) /* SCO UDK Compiler */
#define HAVE_FORK1 1
@@ -246,6 +247,10 @@ extern int lstat(const char *, struct stat *);
#endif /* !_MSC_VER */
+#ifdef HAVE_POSIX_SPAWN
+#include <spawn.h>
+#endif
+
#ifdef HAVE_UTIME_H
#include <utime.h>
#endif /* HAVE_UTIME_H */
@@ -5126,6 +5131,195 @@ os_execve_impl(PyObject *module, path_t *path, PyObject *argv, PyObject *env)
#endif /* HAVE_EXECV */
+#ifdef HAVE_POSIX_SPAWN
+
+enum posix_spawn_file_actions_identifier {
+ POSIX_SPAWN_OPEN,
+ POSIX_SPAWN_CLOSE,
+ POSIX_SPAWN_DUP2
+};
+
+/*[clinic input]
+
+os.posix_spawn
+ path: path_t
+ Path of executable file.
+ argv: object
+ Tuple or list of strings.
+ env: object
+ Dictionary of strings mapping to strings.
+ file_actions: object = None
+ FileActions object.
+ /
+
+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=0ec9f1cfdc890be5]*/
+{
+ EXECV_CHAR **argvlist = NULL;
+ EXECV_CHAR **envlist;
+ Py_ssize_t argc, envc;
+
+ /* posix_spawn has three arguments: (path, argv, env), where
+ argv is a list or tuple of strings and env is a dictionary
+ like posix.environ. */
+
+ if (!PySequence_Check(argv)){
+ PyErr_SetString(PyExc_TypeError,
+ "posix_spawn: argv must be a tuple or list");
+ goto fail;
+ }
+ argc = PySequence_Size(argv);
+ if (argc < 1) {
+ PyErr_SetString(PyExc_ValueError, "posix_spawn: argv must not be empty");
+ return NULL;
+ }
+
+ if (!PyMapping_Check(env)) {
+ PyErr_SetString(PyExc_TypeError,
+ "posix_spawn: environment must be a mapping object");
+ goto fail;
+ }
+
+ argvlist = parse_arglist(argv, &argc);
+ if (argvlist == NULL) {
+ goto fail;
+ }
+ if (!argvlist[0][0]) {
+ PyErr_SetString(PyExc_ValueError,
+ "posix_spawn: argv first element cannot be empty");
+ goto fail;
+ }
+
+ envlist = parse_envlist(env, &envc);
+ if (envlist == NULL)
+ goto fail;
+
+ pid_t pid;
+ posix_spawn_file_actions_t *file_actionsp = NULL;
+ if (file_actions != NULL && file_actions != Py_None){
+ posix_spawn_file_actions_t _file_actions;
+ if(posix_spawn_file_actions_init(&_file_actions) != 0){
+ PyErr_SetString(PyExc_TypeError,
+ "Error initializing file actions");
+ goto fail;
+ }
+
+
+ file_actionsp = &_file_actions;
+
+
+ PyObject* seq = PySequence_Fast(file_actions, "file_actions must be a sequence");
+ if(seq == NULL){
+ goto fail;
+ }
+ PyObject* file_actions_obj;
+ PyObject* mode_obj;
+
+ for (int i = 0; i < PySequence_Fast_GET_SIZE(seq); ++i) {
+ file_actions_obj = PySequence_Fast_GET_ITEM(seq, i);
+
+ if(!PySequence_Check(file_actions_obj) | !PySequence_Size(file_actions_obj)){
+ PyErr_SetString(PyExc_TypeError,"Each file_action element must be a non empty sequence");
+ goto fail;
+ }
+
+
+ mode_obj = PySequence_Fast_GET_ITEM(file_actions_obj, 0);
+ int mode = PyLong_AsLong(mode_obj);
+
+ /* Populate the file_actions object */
+
+ switch(mode) {
+
+ case POSIX_SPAWN_OPEN:
+ if(PySequence_Size(file_actions_obj) != 5){
+ PyErr_SetString(PyExc_TypeError,"A open file_action object must have 5 elements");
+ goto fail;
+ }
+
+ long open_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
+ if(PyErr_Occurred()) {
+ goto fail;
+ }
+ const char* open_path = PyUnicode_AsUTF8(PySequence_GetItem(file_actions_obj, 2));
+ if(open_path == NULL){
+ goto fail;
+ }
+ long open_oflag = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 3));
+ if(PyErr_Occurred()) {
+ goto fail;
+ }
+ long open_mode = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 4));
+ if(PyErr_Occurred()) {
+ goto fail;
+ }
+ posix_spawn_file_actions_addopen(file_actionsp, open_fd, open_path, open_oflag, open_mode);
+ break;
+
+ case POSIX_SPAWN_CLOSE:
+ if(PySequence_Size(file_actions_obj) != 2){
+ PyErr_SetString(PyExc_TypeError,"A close file_action object must have 2 elements");
+ goto fail;
+ }
+
+ long close_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
+ if(PyErr_Occurred()) {
+ goto fail;
+ }
+ posix_spawn_file_actions_addclose(file_actionsp, close_fd);
+ break;
+
+ case POSIX_SPAWN_DUP2:
+ if(PySequence_Size(file_actions_obj) != 3){
+ PyErr_SetString(PyExc_TypeError,"A dup2 file_action object must have 3 elements");
+ goto fail;
+ }
+
+ long fd1 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
+ if(PyErr_Occurred()) {
+ goto fail;
+ }
+ long fd2 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 2));
+ if(PyErr_Occurred()) {
+ goto fail;
+ }
+ posix_spawn_file_actions_adddup2(file_actionsp, fd1, fd2);
+ break;
+
+ default:
+ PyErr_SetString(PyExc_TypeError,"Unknown file_actions identifier");
+ goto fail;
+ }
+ }
+ Py_DECREF(seq);
+}
+
+ _Py_BEGIN_SUPPRESS_IPH
+ posix_spawn(&pid, path->narrow, file_actionsp, NULL, argvlist, envlist);
+ return PyLong_FromPid(pid);
+ _Py_END_SUPPRESS_IPH
+
+ path_error(path);
+
+ free_string_array(envlist, envc);
+
+fail:
+
+ if (argvlist) {
+ free_string_array(argvlist, argc);
+ }
+ return NULL;
+
+
+}
+#endif /* HAVE_POSIX_SPAWN */
+
+
#if defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)
/*[clinic input]
os.spawnv
@@ -12687,6 +12881,7 @@ static PyMethodDef posix_methods[] = {
OS_NICE_METHODDEF
OS_GETPRIORITY_METHODDEF
OS_SETPRIORITY_METHODDEF
+ OS_POSIX_SPAWN_METHODDEF
#ifdef HAVE_READLINK
{"readlink", (PyCFunction)posix_readlink,
METH_VARARGS | METH_KEYWORDS,
@@ -13241,6 +13436,13 @@ all_ins(PyObject *m)
if (PyModule_AddIntConstant(m, "RWF_NOWAIT", RWF_NOWAIT)) return -1;
#endif
+/* constants for posix_spawn */
+#ifdef HAVE_POSIX_SPAWN
+ if (PyModule_AddIntConstant(m, "POSIX_SPAWN_OPEN", POSIX_SPAWN_OPEN)) return -1;
+ if (PyModule_AddIntConstant(m, "POSIX_SPAWN_CLOSE", POSIX_SPAWN_CLOSE)) return -1;
+ if (PyModule_AddIntConstant(m, "POSIX_SPAWN_DUP2", POSIX_SPAWN_DUP2)) return -1;
+#endif
+
#ifdef HAVE_SPAWNV
if (PyModule_AddIntConstant(m, "P_WAIT", _P_WAIT)) return -1;
if (PyModule_AddIntConstant(m, "P_NOWAIT", _P_NOWAIT)) return -1;
--
2.23.0

View File

@ -0,0 +1,257 @@
From 94083ce4d60abd390fbba615de333bdec07edc2c Mon Sep 17 00:00:00 2001
From: Pablo Galindo <Pablogsal@gmail.com>
Date: Mon, 29 Jan 2018 20:34:42 +0000
Subject: [PATCH] bpo-20104: Fix leaks and errors in new os.posix_spawn
(GH-5418)
* Fix memory leaks and error handling in posix spawn
* Improve error handling when destroying the file_actions object
* Py_DECREF the result of PySequence_Fast on error
* Handle uninitialized pid
* Use OSError if file actions fails to initialize
* Move _file_actions to outer scope to avoid undefined behaviour
* Remove HAVE_POSIX_SPAWN define in Modules/posixmodule.c
* Unshadow exception and clean error message
Conflict:NA
Reference:https://github.com/python/cpython/commit/0cd6bca65519109a8a7862d38ba1b8924e432a16
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Modules/posixmodule.c | 111 +++++++++++++++++++++++++-----------------
1 file changed, 66 insertions(+), 45 deletions(-)
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index fcb22c2..b5636f5 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -176,7 +176,6 @@ corresponding Unix manual entries for more information on calls.");
#else
/* Unix functions that the configure script doesn't check for */
#define HAVE_EXECV 1
-#define HAVE_POSIX_SPAWN 1
#define HAVE_FORK 1
#if defined(__USLC__) && defined(__SCO_VERSION__) /* SCO UDK Compiler */
#define HAVE_FORK1 1
@@ -5161,17 +5160,22 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
/*[clinic end generated code: output=d023521f541c709c input=0ec9f1cfdc890be5]*/
{
EXECV_CHAR **argvlist = NULL;
- EXECV_CHAR **envlist;
+ EXECV_CHAR **envlist = NULL;
+ posix_spawn_file_actions_t _file_actions;
+ posix_spawn_file_actions_t *file_actionsp = NULL;
Py_ssize_t argc, envc;
+ PyObject* result = NULL;
+ PyObject* seq = NULL;
+
/* posix_spawn has three arguments: (path, argv, env), where
argv is a list or tuple of strings and env is a dictionary
like posix.environ. */
- if (!PySequence_Check(argv)){
+ if (!PySequence_Check(argv)) {
PyErr_SetString(PyExc_TypeError,
"posix_spawn: argv must be a tuple or list");
- goto fail;
+ goto exit;
}
argc = PySequence_Size(argv);
if (argc < 1) {
@@ -5182,40 +5186,37 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
if (!PyMapping_Check(env)) {
PyErr_SetString(PyExc_TypeError,
"posix_spawn: environment must be a mapping object");
- goto fail;
+ goto exit;
}
argvlist = parse_arglist(argv, &argc);
if (argvlist == NULL) {
- goto fail;
+ goto exit;
}
if (!argvlist[0][0]) {
PyErr_SetString(PyExc_ValueError,
"posix_spawn: argv first element cannot be empty");
- goto fail;
+ goto exit;
}
envlist = parse_envlist(env, &envc);
- if (envlist == NULL)
- goto fail;
+ if (envlist == NULL) {
+ goto exit;
+ }
pid_t pid;
- posix_spawn_file_actions_t *file_actionsp = NULL;
- if (file_actions != NULL && file_actions != Py_None){
- posix_spawn_file_actions_t _file_actions;
+ if (file_actions != NULL && file_actions != Py_None) {
if(posix_spawn_file_actions_init(&_file_actions) != 0){
- PyErr_SetString(PyExc_TypeError,
+ PyErr_SetString(PyExc_OSError,
"Error initializing file actions");
- goto fail;
+ goto exit;
}
-
file_actionsp = &_file_actions;
-
- PyObject* seq = PySequence_Fast(file_actions, "file_actions must be a sequence");
- if(seq == NULL){
- goto fail;
+ seq = PySequence_Fast(file_actions, "file_actions must be a sequence");
+ if(seq == NULL) {
+ goto exit;
}
PyObject* file_actions_obj;
PyObject* mode_obj;
@@ -5223,9 +5224,9 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
for (int i = 0; i < PySequence_Fast_GET_SIZE(seq); ++i) {
file_actions_obj = PySequence_Fast_GET_ITEM(seq, i);
- if(!PySequence_Check(file_actions_obj) | !PySequence_Size(file_actions_obj)){
+ if(!PySequence_Check(file_actions_obj) | !PySequence_Size(file_actions_obj)) {
PyErr_SetString(PyExc_TypeError,"Each file_action element must be a non empty sequence");
- goto fail;
+ goto exit;
}
@@ -5237,83 +5238,103 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
switch(mode) {
case POSIX_SPAWN_OPEN:
- if(PySequence_Size(file_actions_obj) != 5){
+ if(PySequence_Size(file_actions_obj) != 5) {
PyErr_SetString(PyExc_TypeError,"A open file_action object must have 5 elements");
- goto fail;
+ goto exit;
}
long open_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
if(PyErr_Occurred()) {
- goto fail;
+ goto exit;
}
const char* open_path = PyUnicode_AsUTF8(PySequence_GetItem(file_actions_obj, 2));
- if(open_path == NULL){
- goto fail;
+ if(open_path == NULL) {
+ goto exit;
}
long open_oflag = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 3));
if(PyErr_Occurred()) {
- goto fail;
+ goto exit;
}
long open_mode = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 4));
if(PyErr_Occurred()) {
- goto fail;
+ goto exit;
+ }
+ if(posix_spawn_file_actions_addopen(file_actionsp, open_fd, open_path, open_oflag, open_mode)) {
+ PyErr_SetString(PyExc_OSError,"Failed to add open file action");
+ goto exit;
}
- posix_spawn_file_actions_addopen(file_actionsp, open_fd, open_path, open_oflag, open_mode);
+
break;
case POSIX_SPAWN_CLOSE:
if(PySequence_Size(file_actions_obj) != 2){
PyErr_SetString(PyExc_TypeError,"A close file_action object must have 2 elements");
- goto fail;
+ goto exit;
}
long close_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
if(PyErr_Occurred()) {
- goto fail;
+ goto exit;
+ }
+ if(posix_spawn_file_actions_addclose(file_actionsp, close_fd)) {
+ PyErr_SetString(PyExc_OSError,"Failed to add close file action");
+ goto exit;
}
- posix_spawn_file_actions_addclose(file_actionsp, close_fd);
break;
case POSIX_SPAWN_DUP2:
if(PySequence_Size(file_actions_obj) != 3){
PyErr_SetString(PyExc_TypeError,"A dup2 file_action object must have 3 elements");
- goto fail;
+ goto exit;
}
long fd1 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
if(PyErr_Occurred()) {
- goto fail;
+ goto exit;
}
long fd2 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 2));
if(PyErr_Occurred()) {
- goto fail;
+ goto exit;
+ }
+ if(posix_spawn_file_actions_adddup2(file_actionsp, fd1, fd2)) {
+ PyErr_SetString(PyExc_OSError,"Failed to add dup2 file action");
+ goto exit;
}
- posix_spawn_file_actions_adddup2(file_actionsp, fd1, fd2);
break;
default:
PyErr_SetString(PyExc_TypeError,"Unknown file_actions identifier");
- goto fail;
+ goto exit;
}
}
- Py_DECREF(seq);
-}
+ }
_Py_BEGIN_SUPPRESS_IPH
- posix_spawn(&pid, path->narrow, file_actionsp, NULL, argvlist, envlist);
- return PyLong_FromPid(pid);
+ int err_code = posix_spawn(&pid, path->narrow, file_actionsp, NULL, argvlist, envlist);
_Py_END_SUPPRESS_IPH
+ if(err_code) {
+ PyErr_SetString(PyExc_OSError,"posix_spawn call failed");
+ goto exit;
+ }
+ result = PyLong_FromPid(pid);
- path_error(path);
+exit:
- free_string_array(envlist, envc);
+ Py_XDECREF(seq);
-fail:
+ if(file_actionsp) {
+ posix_spawn_file_actions_destroy(file_actionsp);
+ }
+
+ if (envlist) {
+ free_string_array(envlist, envc);
+ }
if (argvlist) {
free_string_array(argvlist, argc);
}
- return NULL;
+
+ return result;
}
--
2.23.0

View File

@ -0,0 +1,543 @@
From bebc98800f4a760d03c3ee9fe49e2943132b47c6 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Tue, 1 May 2018 16:45:04 +0300
Subject: [PATCH] bpo-20104: Improve error handling and fix a reference leak in
os.posix_spawn(). (#6332)
Conflict:NA
Reference:https://github.com/python/cpython/commit/ef347535f289baad22c0601e12a36b2dcd155c3a
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 177 +++++++++++--
.../2018-04-01-19-21-04.bpo-20104.-AKcGa.rst | 1 +
Modules/clinic/posixmodule.c.h | 2 +-
Modules/posixmodule.c | 245 +++++++++---------
4 files changed, 285 insertions(+), 140 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2018-04-01-19-21-04.bpo-20104.-AKcGa.rst
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index c67087c..a22baac 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -192,22 +192,6 @@ class PosixTester(unittest.TestCase):
os.close(fp)
- @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
- def test_posix_spawn(self):
- pid = posix.posix_spawn(sys.executable, [sys.executable, "-c", "pass"], os.environ,[])
- self.assertEqual(os.waitpid(pid,0),(pid,0))
-
-
- @unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
- def test_posix_spawn_file_actions(self):
- file_actions = []
- file_actions.append((0,3,os.path.realpath(__file__),0,0))
- file_actions.append((os.POSIX_SPAWN_CLOSE,2))
- file_actions.append((os.POSIX_SPAWN_DUP2,1,4))
- pid = posix.posix_spawn(sys.executable, [sys.executable, "-c", "pass"], os.environ, file_actions)
- self.assertEqual(os.waitpid(pid,0),(pid,0))
-
-
@unittest.skipUnless(hasattr(posix, 'waitid'), "test needs posix.waitid()")
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
def test_waitid(self):
@@ -1519,9 +1503,168 @@ class PosixGroupsTester(unittest.TestCase):
posix.setgroups(groups)
self.assertListEqual(groups, posix.getgroups())
+
+@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
+class TestPosixSpawn(unittest.TestCase):
+ def test_returns_pid(self):
+ pidfile = support.TESTFN
+ self.addCleanup(support.unlink, pidfile)
+ script = f"""if 1:
+ import os
+ with open({pidfile!r}, "w") as pidfile:
+ pidfile.write(str(os.getpid()))
+ """
+ pid = posix.posix_spawn(sys.executable,
+ [sys.executable, '-c', script],
+ 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)
+ except FileNotFoundError as exc:
+ self.assertEqual(exc.filename, no_such_executable)
+ else:
+ pid2, status = os.waitpid(pid, 0)
+ self.assertEqual(pid2, pid)
+ self.assertNotEqual(status, 0)
+
+ def test_specify_environment(self):
+ envfile = support.TESTFN
+ self.addCleanup(support.unlink, envfile)
+ script = f"""if 1:
+ import os
+ with open({envfile!r}, "w") as envfile:
+ envfile.write(os.environ['foo'])
+ """
+ pid = posix.posix_spawn(sys.executable,
+ [sys.executable, '-c', script],
+ {'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(
+ sys.executable,
+ [sys.executable, '-c', 'pass'],
+ os.environ,
+ []
+ )
+ 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),
+ (os.POSIX_SPAWN_CLOSE, 0),
+ (os.POSIX_SPAWN_DUP2, 1, 4),
+ ]
+ pid = posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ, file_actions)
+ self.assertEqual(os.waitpid(pid, 0), (pid, 0))
+
+ def test_bad_file_actions(self):
+ with self.assertRaises(TypeError):
+ posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ, [None])
+ with self.assertRaises(TypeError):
+ posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ, [()])
+ with self.assertRaises(TypeError):
+ posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ, [(None,)])
+ with self.assertRaises(TypeError):
+ posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ, [(12345,)])
+ with self.assertRaises(TypeError):
+ posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ, [(os.POSIX_SPAWN_CLOSE,)])
+ with self.assertRaises(TypeError):
+ posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ, [(os.POSIX_SPAWN_CLOSE, 1, 2)])
+ with self.assertRaises(TypeError):
+ posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ, [(os.POSIX_SPAWN_CLOSE, None)])
+ with self.assertRaises(ValueError):
+ posix.posix_spawn(sys.executable,
+ [sys.executable, "-c", "pass"],
+ os.environ,
+ [(os.POSIX_SPAWN_OPEN, 3, __file__ + '\0',
+ os.O_RDONLY, 0)])
+
+ def test_open_file(self):
+ outfile = support.TESTFN
+ self.addCleanup(support.unlink, outfile)
+ script = """if 1:
+ import sys
+ sys.stdout.write("hello")
+ """
+ file_actions = [
+ (os.POSIX_SPAWN_OPEN, 1, outfile,
+ os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
+ stat.S_IRUSR | stat.S_IWUSR),
+ ]
+ pid = posix.posix_spawn(sys.executable,
+ [sys.executable, '-c', script],
+ os.environ, file_actions)
+ self.assertEqual(os.waitpid(pid, 0), (pid, 0))
+ with open(outfile) as f:
+ self.assertEqual(f.read(), 'hello')
+
+ def test_close_file(self):
+ closefile = support.TESTFN
+ self.addCleanup(support.unlink, closefile)
+ script = f"""if 1:
+ import os
+ try:
+ os.fstat(0)
+ except OSError as e:
+ with open({closefile!r}, 'w') as closefile:
+ closefile.write('is closed %d' % e.errno)
+ """
+ pid = posix.posix_spawn(sys.executable,
+ [sys.executable, '-c', script],
+ os.environ,
+ [(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)
+
+ def test_dup2(self):
+ dupfile = support.TESTFN
+ self.addCleanup(support.unlink, dupfile)
+ script = """if 1:
+ import sys
+ sys.stdout.write("hello")
+ """
+ with open(dupfile, "wb") as childfile:
+ file_actions = [
+ (os.POSIX_SPAWN_DUP2, childfile.fileno(), 1),
+ ]
+ pid = posix.posix_spawn(sys.executable,
+ [sys.executable, '-c', script],
+ os.environ, file_actions)
+ self.assertEqual(os.waitpid(pid, 0), (pid, 0))
+ with open(dupfile) as f:
+ self.assertEqual(f.read(), 'hello')
+
+
def test_main():
try:
- support.run_unittest(PosixTester, PosixGroupsTester)
+ support.run_unittest(PosixTester, PosixGroupsTester, TestPosixSpawn)
finally:
support.reap_children()
diff --git a/Misc/NEWS.d/next/Library/2018-04-01-19-21-04.bpo-20104.-AKcGa.rst b/Misc/NEWS.d/next/Library/2018-04-01-19-21-04.bpo-20104.-AKcGa.rst
new file mode 100644
index 0000000..150401d
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-04-01-19-21-04.bpo-20104.-AKcGa.rst
@@ -0,0 +1 @@
+Improved error handling and fixed a reference leak in :func:`os.posix_spawn()`.
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 07be916..1371ef6 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -1742,7 +1742,7 @@ PyDoc_STRVAR(os_posix_spawn__doc__,
" env\n"
" Dictionary of strings mapping to strings.\n"
" file_actions\n"
-" FileActions object.");
+" A sequence of file action tuples.");
#define OS_POSIX_SPAWN_METHODDEF \
{"posix_spawn", (PyCFunction)os_posix_spawn, METH_FASTCALL, os_posix_spawn__doc__},
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index b5636f5..f10fe2b 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5138,6 +5138,111 @@ enum posix_spawn_file_actions_identifier {
POSIX_SPAWN_DUP2
};
+static int
+parse_file_actions(PyObject *file_actions,
+ posix_spawn_file_actions_t *file_actionsp)
+{
+ PyObject *seq;
+ PyObject *file_action = NULL;
+ PyObject *tag_obj;
+
+ seq = PySequence_Fast(file_actions,
+ "file_actions must be a sequence or None");
+ if (seq == NULL) {
+ return -1;
+ }
+
+ errno = posix_spawn_file_actions_init(file_actionsp);
+ if (errno) {
+ posix_error();
+ Py_DECREF(seq);
+ return -1;
+ }
+
+ for (int i = 0; i < PySequence_Fast_GET_SIZE(seq); ++i) {
+ file_action = PySequence_Fast_GET_ITEM(seq, i);
+ Py_INCREF(file_action);
+ if (!PyTuple_Check(file_action) || !PyTuple_GET_SIZE(file_action)) {
+ PyErr_SetString(PyExc_TypeError,
+ "Each file_actions element must be a non-empty tuple");
+ goto fail;
+ }
+ long tag = PyLong_AsLong(PyTuple_GET_ITEM(file_action, 0));
+ if (tag == -1 && PyErr_Occurred()) {
+ goto fail;
+ }
+
+ /* Populate the file_actions object */
+ switch (tag) {
+ case POSIX_SPAWN_OPEN: {
+ int fd, oflag;
+ PyObject *path;
+ unsigned long mode;
+ if (!PyArg_ParseTuple(file_action, "OiO&ik"
+ ";A open file_action tuple must have 5 elements",
+ &tag_obj, &fd, PyUnicode_FSConverter, &path,
+ &oflag, &mode))
+ {
+ goto fail;
+ }
+ errno = posix_spawn_file_actions_addopen(file_actionsp,
+ fd, PyBytes_AS_STRING(path), oflag, (mode_t)mode);
+ Py_DECREF(path); /* addopen copied it. */
+ if (errno) {
+ posix_error();
+ goto fail;
+ }
+ break;
+ }
+ case POSIX_SPAWN_CLOSE: {
+ int fd;
+ if (!PyArg_ParseTuple(file_action, "Oi"
+ ";A close file_action tuple must have 2 elements",
+ &tag_obj, &fd))
+ {
+ goto fail;
+ }
+ errno = posix_spawn_file_actions_addclose(file_actionsp, fd);
+ if (errno) {
+ posix_error();
+ goto fail;
+ }
+ break;
+ }
+ case POSIX_SPAWN_DUP2: {
+ int fd1, fd2;
+ if (!PyArg_ParseTuple(file_action, "Oii"
+ ";A dup2 file_action tuple must have 3 elements",
+ &tag_obj, &fd1, &fd2))
+ {
+ goto fail;
+ }
+ errno = posix_spawn_file_actions_adddup2(file_actionsp,
+ fd1, fd2);
+ if (errno) {
+ posix_error();
+ goto fail;
+ }
+ break;
+ }
+ default: {
+ PyErr_SetString(PyExc_TypeError,
+ "Unknown file_actions identifier");
+ goto fail;
+ }
+ }
+ Py_DECREF(file_action);
+ }
+ Py_DECREF(seq);
+ return 0;
+
+fail:
+ Py_DECREF(seq);
+ Py_DECREF(file_action);
+ (void)posix_spawn_file_actions_destroy(file_actionsp);
+ return -1;
+}
+
/*[clinic input]
os.posix_spawn
@@ -5148,7 +5253,7 @@ os.posix_spawn
env: object
Dictionary of strings mapping to strings.
file_actions: object = None
- FileActions object.
+ A sequence of file action tuples.
/
Execute the program specified by path in a new process.
@@ -5157,22 +5262,22 @@ Execute the program specified by path in a new process.
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=0ec9f1cfdc890be5]*/
+/*[clinic end generated code: output=d023521f541c709c input=a3db1021d33230dc]*/
{
EXECV_CHAR **argvlist = NULL;
EXECV_CHAR **envlist = NULL;
- posix_spawn_file_actions_t _file_actions;
+ posix_spawn_file_actions_t file_actions_buf;
posix_spawn_file_actions_t *file_actionsp = NULL;
Py_ssize_t argc, envc;
- PyObject* result = NULL;
- PyObject* seq = NULL;
-
+ PyObject *result = NULL;
+ pid_t pid;
+ int err_code;
/* posix_spawn has three arguments: (path, argv, env), where
- argv is a list or tuple of strings and env is a dictionary
+ argv is a list or tuple of strings and env is a dictionary
like posix.environ. */
- if (!PySequence_Check(argv)) {
+ if (!PyList_Check(argv) && !PyTuple_Check(argv)) {
PyErr_SetString(PyExc_TypeError,
"posix_spawn: argv must be a tuple or list");
goto exit;
@@ -5204,139 +5309,35 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
goto exit;
}
- pid_t pid;
- if (file_actions != NULL && file_actions != Py_None) {
- if(posix_spawn_file_actions_init(&_file_actions) != 0){
- PyErr_SetString(PyExc_OSError,
- "Error initializing file actions");
- goto exit;
- }
-
- file_actionsp = &_file_actions;
-
- seq = PySequence_Fast(file_actions, "file_actions must be a sequence");
- if(seq == NULL) {
+ if (file_actions != Py_None) {
+ if (parse_file_actions(file_actions, &file_actions_buf)) {
goto exit;
}
- PyObject* file_actions_obj;
- PyObject* mode_obj;
-
- for (int i = 0; i < PySequence_Fast_GET_SIZE(seq); ++i) {
- file_actions_obj = PySequence_Fast_GET_ITEM(seq, i);
-
- if(!PySequence_Check(file_actions_obj) | !PySequence_Size(file_actions_obj)) {
- PyErr_SetString(PyExc_TypeError,"Each file_action element must be a non empty sequence");
- goto exit;
- }
-
-
- mode_obj = PySequence_Fast_GET_ITEM(file_actions_obj, 0);
- int mode = PyLong_AsLong(mode_obj);
-
- /* Populate the file_actions object */
-
- switch(mode) {
-
- case POSIX_SPAWN_OPEN:
- if(PySequence_Size(file_actions_obj) != 5) {
- PyErr_SetString(PyExc_TypeError,"A open file_action object must have 5 elements");
- goto exit;
- }
-
- long open_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
- if(PyErr_Occurred()) {
- goto exit;
- }
- const char* open_path = PyUnicode_AsUTF8(PySequence_GetItem(file_actions_obj, 2));
- if(open_path == NULL) {
- goto exit;
- }
- long open_oflag = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 3));
- if(PyErr_Occurred()) {
- goto exit;
- }
- long open_mode = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 4));
- if(PyErr_Occurred()) {
- goto exit;
- }
- if(posix_spawn_file_actions_addopen(file_actionsp, open_fd, open_path, open_oflag, open_mode)) {
- PyErr_SetString(PyExc_OSError,"Failed to add open file action");
- goto exit;
- }
-
- break;
-
- case POSIX_SPAWN_CLOSE:
- if(PySequence_Size(file_actions_obj) != 2){
- PyErr_SetString(PyExc_TypeError,"A close file_action object must have 2 elements");
- goto exit;
- }
-
- long close_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
- if(PyErr_Occurred()) {
- goto exit;
- }
- if(posix_spawn_file_actions_addclose(file_actionsp, close_fd)) {
- PyErr_SetString(PyExc_OSError,"Failed to add close file action");
- goto exit;
- }
- break;
-
- case POSIX_SPAWN_DUP2:
- if(PySequence_Size(file_actions_obj) != 3){
- PyErr_SetString(PyExc_TypeError,"A dup2 file_action object must have 3 elements");
- goto exit;
- }
-
- long fd1 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
- if(PyErr_Occurred()) {
- goto exit;
- }
- long fd2 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 2));
- if(PyErr_Occurred()) {
- goto exit;
- }
- if(posix_spawn_file_actions_adddup2(file_actionsp, fd1, fd2)) {
- PyErr_SetString(PyExc_OSError,"Failed to add dup2 file action");
- goto exit;
- }
- break;
-
- default:
- PyErr_SetString(PyExc_TypeError,"Unknown file_actions identifier");
- goto exit;
- }
- }
+ file_actionsp = &file_actions_buf;
}
_Py_BEGIN_SUPPRESS_IPH
- int err_code = posix_spawn(&pid, path->narrow, file_actionsp, NULL, argvlist, envlist);
+ err_code = posix_spawn(&pid, path->narrow,
+ file_actionsp, NULL, argvlist, envlist);
_Py_END_SUPPRESS_IPH
- if(err_code) {
- PyErr_SetString(PyExc_OSError,"posix_spawn call failed");
+ if (err_code) {
+ errno = err_code;
+ PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path->object);
goto exit;
}
result = PyLong_FromPid(pid);
exit:
-
- Py_XDECREF(seq);
-
- if(file_actionsp) {
- posix_spawn_file_actions_destroy(file_actionsp);
+ if (file_actionsp) {
+ (void)posix_spawn_file_actions_destroy(file_actionsp);
}
-
if (envlist) {
free_string_array(envlist, envc);
}
-
if (argvlist) {
free_string_array(argvlist, argc);
}
-
return result;
-
-
}
#endif /* HAVE_POSIX_SPAWN */
--
2.23.0

View File

@ -0,0 +1,458 @@
From 2d0f8adba1764f0b242b15c4f0c67a791fb11e46 Mon Sep 17 00:00:00 2001
From: Antoine Pitrou <pitrou@free.fr>
Date: Fri, 4 May 2018 13:00:50 +0200
Subject: [PATCH] bpo-33332: Add signal.valid_signals() (GH-6581)
Conflict:NA
Reference:https://github.com/python/cpython/commit/9d3627e311211a1b4abcda29c36fe4afe2c46532
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Doc/library/signal.rst | 9 ++-
Lib/asyncio/unix_events.py | 4 +-
Lib/multiprocessing/resource_sharer.py | 2 +-
Lib/signal.py | 10 ++-
Lib/test/support/__init__.py | 2 +-
Lib/test/test_asyncio/test_unix_events.py | 12 ++++
Lib/test/test_signal.py | 30 +++++++++
.../2018-04-23-21-41-30.bpo-33332.Y6OZ8Z.rst | 2 +
Modules/clinic/signalmodule.c.h | 29 +++++++++
Modules/signalmodule.c | 64 +++++++++++++++++--
configure | 2 +-
configure.ac | 2 +-
pyconfig.h.in | 3 +
13 files changed, 156 insertions(+), 15 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2018-04-23-21-41-30.bpo-33332.Y6OZ8Z.rst
diff --git a/Doc/library/signal.rst b/Doc/library/signal.rst
index 7f48f8c..f39e0b2 100644
--- a/Doc/library/signal.rst
+++ b/Doc/library/signal.rst
@@ -313,6 +313,11 @@ The :mod:`signal` module defines the following functions:
previously in use, and ``None`` means that the previous signal handler was not
installed from Python.
+.. function:: valid_signals()
+
+ Return the set of valid signal numbers on this platform. This can be
+ less than ``range(1, NSIG)`` if some signals are reserved by the system
+ for internal use.
.. function:: pause()
@@ -368,8 +373,8 @@ The :mod:`signal` module defines the following functions:
argument.
*mask* is a set of signal numbers (e.g. {:const:`signal.SIGINT`,
- :const:`signal.SIGTERM`}). Use ``range(1, signal.NSIG)`` for a full mask
- including all signals.
+ :const:`signal.SIGTERM`}). Use :func:`~signal.valid_signals` for a full
+ mask including all signals.
For example, ``signal.pthread_sigmask(signal.SIG_BLOCK, [])`` reads the
signal mask of the calling thread.
diff --git a/Lib/asyncio/unix_events.py b/Lib/asyncio/unix_events.py
index e037e12..4c1bf40 100644
--- a/Lib/asyncio/unix_events.py
+++ b/Lib/asyncio/unix_events.py
@@ -168,8 +168,8 @@ class _UnixSelectorEventLoop(selector_events.BaseSelectorEventLoop):
if not isinstance(sig, int):
raise TypeError(f'sig must be an int, not {sig!r}')
- if not (1 <= sig < signal.NSIG):
- raise ValueError(f'sig {sig} out of range(1, {signal.NSIG})')
+ if sig not in signal.valid_signals():
+ raise ValueError(f'invalid signal number {sig}')
def _make_read_pipe_transport(self, pipe, protocol, waiter=None,
extra=None):
diff --git a/Lib/multiprocessing/resource_sharer.py b/Lib/multiprocessing/resource_sharer.py
index c8f18ea..8d5c990 100644
--- a/Lib/multiprocessing/resource_sharer.py
+++ b/Lib/multiprocessing/resource_sharer.py
@@ -136,7 +136,7 @@ class _ResourceSharer(object):
def _serve(self):
if hasattr(signal, 'pthread_sigmask'):
- signal.pthread_sigmask(signal.SIG_BLOCK, range(1, signal.NSIG))
+ signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
while 1:
try:
with self._listener.accept() as conn:
diff --git a/Lib/signal.py b/Lib/signal.py
index 9f05c91..826b62c 100644
--- a/Lib/signal.py
+++ b/Lib/signal.py
@@ -65,8 +65,7 @@ if 'pthread_sigmask' in _globals:
if 'sigpending' in _globals:
@_wraps(_signal.sigpending)
def sigpending():
- sigs = _signal.sigpending()
- return set(_int_to_enum(x, Signals) for x in sigs)
+ return {_int_to_enum(x, Signals) for x in _signal.sigpending()}
if 'sigwait' in _globals:
@@ -76,4 +75,11 @@ if 'sigwait' in _globals:
return _int_to_enum(retsig, Signals)
sigwait.__doc__ = _signal.sigwait
+
+if 'valid_signals' in _globals:
+ @_wraps(_signal.valid_signals)
+ def valid_signals():
+ return {_int_to_enum(x, Signals) for x in _signal.valid_signals()}
+
+
del _globals, _wraps
diff --git a/Lib/test/support/__init__.py b/Lib/test/support/__init__.py
index b78451b..f0c6327 100644
--- a/Lib/test/support/__init__.py
+++ b/Lib/test/support/__init__.py
@@ -2901,7 +2901,7 @@ class SaveSignals:
def __init__(self):
import signal
self.signal = signal
- self.signals = list(range(1, signal.NSIG))
+ self.signals = signal.valid_signals()
# SIGKILL and SIGSTOP signals cannot be ignored nor caught
for signame in ('SIGKILL', 'SIGSTOP'):
try:
diff --git a/Lib/test/test_asyncio/test_unix_events.py b/Lib/test/test_asyncio/test_unix_events.py
index 66a6bc1..bbdfd16 100644
--- a/Lib/test/test_asyncio/test_unix_events.py
+++ b/Lib/test/test_asyncio/test_unix_events.py
@@ -69,6 +69,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.unix_events.signal')
def test_add_signal_handler_setup_error(self, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
m_signal.set_wakeup_fd.side_effect = ValueError
self.assertRaises(
@@ -96,6 +97,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.unix_events.signal')
def test_add_signal_handler(self, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
cb = lambda: True
self.loop.add_signal_handler(signal.SIGHUP, cb)
@@ -106,6 +108,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.unix_events.signal')
def test_add_signal_handler_install_error(self, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
def set_wakeup_fd(fd):
if fd == -1:
@@ -125,6 +128,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.base_events.logger')
def test_add_signal_handler_install_error2(self, m_logging, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
class Err(OSError):
errno = errno.EINVAL
@@ -145,6 +149,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
errno = errno.EINVAL
m_signal.signal.side_effect = Err
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
self.assertRaises(
RuntimeError,
@@ -156,6 +161,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.unix_events.signal')
def test_remove_signal_handler(self, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
@@ -170,6 +176,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
def test_remove_signal_handler_2(self, m_signal):
m_signal.NSIG = signal.NSIG
m_signal.SIGINT = signal.SIGINT
+ m_signal.valid_signals = signal.valid_signals
self.loop.add_signal_handler(signal.SIGINT, lambda: True)
self.loop._signal_handlers[signal.SIGHUP] = object()
@@ -187,6 +194,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.base_events.logger')
def test_remove_signal_handler_cleanup_error(self, m_logging, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
m_signal.set_wakeup_fd.side_effect = ValueError
@@ -197,6 +205,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.unix_events.signal')
def test_remove_signal_handler_error(self, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
m_signal.signal.side_effect = OSError
@@ -207,6 +216,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.unix_events.signal')
def test_remove_signal_handler_error2(self, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
class Err(OSError):
@@ -219,6 +229,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.unix_events.signal')
def test_close(self, m_signal):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
self.loop.add_signal_handler(signal.SIGCHLD, lambda: True)
@@ -236,6 +247,7 @@ class SelectorEventLoopSignalTests(test_utils.TestCase):
@mock.patch('asyncio.unix_events.signal')
def test_close_on_finalizing(self, m_signal, m_sys):
m_signal.NSIG = signal.NSIG
+ m_signal.valid_signals = signal.valid_signals
self.loop.add_signal_handler(signal.SIGHUP, lambda: True)
self.assertEqual(len(self.loop._signal_handlers), 1)
diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
index 406684b..0244650 100644
--- a/Lib/test/test_signal.py
+++ b/Lib/test/test_signal.py
@@ -61,9 +61,28 @@ class PosixTests(unittest.TestCase):
script = os.path.join(dirname, 'signalinterproctester.py')
assert_python_ok(script)
+ def test_valid_signals(self):
+ s = signal.valid_signals()
+ self.assertIsInstance(s, set)
+ self.assertIn(signal.Signals.SIGINT, s)
+ self.assertIn(signal.Signals.SIGALRM, s)
+ self.assertNotIn(0, s)
+ self.assertNotIn(signal.NSIG, s)
+ self.assertLess(len(s), signal.NSIG)
+
@unittest.skipUnless(sys.platform == "win32", "Windows specific")
class WindowsSignalTests(unittest.TestCase):
+
+ def test_valid_signals(self):
+ s = signal.valid_signals()
+ self.assertIsInstance(s, set)
+ self.assertGreaterEqual(len(s), 6)
+ self.assertIn(signal.Signals.SIGINT, s)
+ self.assertNotIn(0, s)
+ self.assertNotIn(signal.NSIG, s)
+ self.assertLess(len(s), signal.NSIG)
+
def test_issue9324(self):
# Updated for issue #10003, adding SIGBREAK
handler = lambda x, y: None
@@ -941,6 +960,17 @@ class PendingSignalsTests(unittest.TestCase):
self.assertRaises(TypeError, signal.pthread_sigmask, 1)
self.assertRaises(TypeError, signal.pthread_sigmask, 1, 2, 3)
self.assertRaises(OSError, signal.pthread_sigmask, 1700, [])
+ with self.assertRaises(ValueError):
+ signal.pthread_sigmask(signal.SIG_BLOCK, [signal.NSIG])
+
+ @unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
+ 'need signal.pthread_sigmask()')
+ def test_pthread_sigmask_valid_signals(self):
+ s = signal.pthread_sigmask(signal.SIG_BLOCK, signal.valid_signals())
+ self.addCleanup(signal.pthread_sigmask, signal.SIG_SETMASK, s)
+ # Get current blocked set
+ s = signal.pthread_sigmask(signal.SIG_UNBLOCK, signal.valid_signals())
+ self.assertLessEqual(s, signal.valid_signals())
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
diff --git a/Misc/NEWS.d/next/Library/2018-04-23-21-41-30.bpo-33332.Y6OZ8Z.rst b/Misc/NEWS.d/next/Library/2018-04-23-21-41-30.bpo-33332.Y6OZ8Z.rst
new file mode 100644
index 0000000..05dca54
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-04-23-21-41-30.bpo-33332.Y6OZ8Z.rst
@@ -0,0 +1,2 @@
+Add ``signal.valid_signals()`` to expose the POSIX sigfillset()
+functionality.
diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h
index dc3aadf..ac97e80 100644
--- a/Modules/clinic/signalmodule.c.h
+++ b/Modules/clinic/signalmodule.c.h
@@ -311,6 +311,31 @@ PyDoc_STRVAR(signal_sigwait__doc__,
#endif /* defined(HAVE_SIGWAIT) */
+#if (defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS))
+
+PyDoc_STRVAR(signal_valid_signals__doc__,
+"valid_signals($module, /)\n"
+"--\n"
+"\n"
+"Return a set of valid signal numbers on this platform.\n"
+"\n"
+"The signal numbers returned by this function can be safely passed to\n"
+"functions like `pthread_sigmask`.");
+
+#define SIGNAL_VALID_SIGNALS_METHODDEF \
+ {"valid_signals", (PyCFunction)signal_valid_signals, METH_NOARGS, signal_valid_signals__doc__},
+
+static PyObject *
+signal_valid_signals_impl(PyObject *module);
+
+static PyObject *
+signal_valid_signals(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+ return signal_valid_signals_impl(module);
+}
+
+#endif /* (defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS)) */
+
#if defined(HAVE_SIGWAITINFO)
PyDoc_STRVAR(signal_sigwaitinfo__doc__,
@@ -429,6 +454,10 @@ exit:
#define SIGNAL_SIGWAIT_METHODDEF
#endif /* !defined(SIGNAL_SIGWAIT_METHODDEF) */
+#ifndef SIGNAL_VALID_SIGNALS_METHODDEF
+ #define SIGNAL_VALID_SIGNALS_METHODDEF
+#endif /* !defined(SIGNAL_VALID_SIGNALS_METHODDEF) */
+
#ifndef SIGNAL_SIGWAITINFO_METHODDEF
#define SIGNAL_SIGWAITINFO_METHODDEF
#endif /* !defined(SIGNAL_SIGWAITINFO_METHODDEF) */
diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c
index a0722b7..b6b5bf1 100644
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -774,11 +774,21 @@ iterable_to_sigset(PyObject *iterable, sigset_t *mask)
if (signum == -1 && PyErr_Occurred())
goto error;
if (0 < signum && signum < NSIG) {
- /* bpo-33329: ignore sigaddset() return value as it can fail
- * for some reserved signals, but we want the `range(1, NSIG)`
- * idiom to allow selecting all valid signals.
- */
- (void) sigaddset(mask, (int)signum);
+ if (sigaddset(mask, (int)signum)) {
+ if (errno != EINVAL) {
+ /* Probably impossible */
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto error;
+ }
+ /* For backwards compatibility, allow idioms such as
+ * `range(1, NSIG)` but warn about invalid signal numbers
+ */
+ const char *msg =
+ "invalid signal number %ld, please use valid_signals()";
+ if (PyErr_WarnFormat(PyExc_RuntimeWarning, 1, msg, signum)) {
+ goto error;
+ }
+ }
}
else {
PyErr_Format(PyExc_ValueError,
@@ -934,6 +944,47 @@ signal_sigwait(PyObject *module, PyObject *sigset)
#endif /* #ifdef HAVE_SIGWAIT */
+#if defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS)
+
+/*[clinic input]
+signal.valid_signals
+
+Return a set of valid signal numbers on this platform.
+
+The signal numbers returned by this function can be safely passed to
+functions like `pthread_sigmask`.
+[clinic start generated code]*/
+
+static PyObject *
+signal_valid_signals_impl(PyObject *module)
+/*[clinic end generated code: output=1609cffbcfcf1314 input=86a3717ff25288f2]*/
+{
+#ifdef MS_WINDOWS
+#ifdef SIGBREAK
+ PyObject *tup = Py_BuildValue("(iiiiiii)", SIGABRT, SIGBREAK, SIGFPE,
+ SIGILL, SIGINT, SIGSEGV, SIGTERM);
+#else
+ PyObject *tup = Py_BuildValue("(iiiiii)", SIGABRT, SIGFPE, SIGILL,
+ SIGINT, SIGSEGV, SIGTERM);
+#endif
+ if (tup == NULL) {
+ return NULL;
+ }
+ PyObject *set = PySet_New(tup);
+ Py_DECREF(tup);
+ return set;
+#else
+ sigset_t mask;
+ if (sigemptyset(&mask) || sigfillset(&mask)) {
+ return PyErr_SetFromErrno(PyExc_OSError);
+ }
+ return sigset_to_set(mask);
+#endif
+}
+
+#endif /* #if defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS) */
+
+
#if defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT)
static int initialized;
static PyStructSequence_Field struct_siginfo_fields[] = {
@@ -1157,6 +1208,9 @@ static PyMethodDef signal_methods[] = {
SIGNAL_SIGWAIT_METHODDEF
SIGNAL_SIGWAITINFO_METHODDEF
SIGNAL_SIGTIMEDWAIT_METHODDEF
+#if defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS)
+ SIGNAL_VALID_SIGNALS_METHODDEF
+#endif
{NULL, NULL} /* sentinel */
};
diff --git a/configure b/configure
index 829dd69..d30ccf5 100755
--- a/configure
+++ b/configure
@@ -11506,7 +11506,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
sched_get_priority_max sched_setaffinity sched_setscheduler sched_setparam \
sched_rr_get_interval \
- sigaction sigaltstack siginterrupt sigpending sigrelse \
+ sigaction sigaltstack sigfillset siginterrupt sigpending sigrelse \
sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
diff --git a/configure.ac b/configure.ac
index f1cc8e9..9d4c889 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3590,7 +3590,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
setlocale setregid setreuid setresuid setresgid setsid setpgid setpgrp setpriority setuid setvbuf \
sched_get_priority_max sched_setaffinity sched_setscheduler sched_setparam \
sched_rr_get_interval \
- sigaction sigaltstack siginterrupt sigpending sigrelse \
+ sigaction sigaltstack sigfillset siginterrupt sigpending sigrelse \
sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
diff --git a/pyconfig.h.in b/pyconfig.h.in
index ebab5ff..70cead7 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -899,6 +899,9 @@
/* Define to 1 if you have the `sigaltstack' function. */
#undef HAVE_SIGALTSTACK
+/* Define to 1 if you have the `sigfillset' function. */
+#undef HAVE_SIGFILLSET
+
/* Define to 1 if `si_band' is a member of `siginfo_t'. */
#undef HAVE_SIGINFO_T_SI_BAND
--
2.23.0

View File

@ -0,0 +1,459 @@
From cc17fe8bed98cae0bcb7739c953933d1f379dd12 Mon Sep 17 00:00:00 2001
From: Serhiy Storchaka <storchaka@gmail.com>
Date: Tue, 8 May 2018 07:48:50 +0300
Subject: [PATCH] bpo-33441: Make the sigset_t converter available in other
modules. (GH-6720)
* Expose the sigset_t converter via private API _Py_Sigset_Converter().
* Use Argument Clinic for parsing sigset_t in signalmodule.c.
* Raise ValueError instead OverflowError for integers out of
the C long range.
Based on patch by Pablo Galindo Salgado.
Conflict:NA
Reference:https://github.com/python/cpython/commit/d54cfb160c626626394e2f171d3ccfe03309f34e
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_signal.py | 4 ++
Modules/clinic/signalmodule.c.h | 53 +++++++++++---
Modules/posixmodule.c | 59 ++++++++++++++++
Modules/posixmodule.h | 9 +++
Modules/signalmodule.c | 120 +++++++-------------------------
5 files changed, 141 insertions(+), 104 deletions(-)
diff --git a/Lib/test/test_signal.py b/Lib/test/test_signal.py
index 0244650..152f803 100644
--- a/Lib/test/test_signal.py
+++ b/Lib/test/test_signal.py
@@ -962,6 +962,10 @@ class PendingSignalsTests(unittest.TestCase):
self.assertRaises(OSError, signal.pthread_sigmask, 1700, [])
with self.assertRaises(ValueError):
signal.pthread_sigmask(signal.SIG_BLOCK, [signal.NSIG])
+ with self.assertRaises(ValueError):
+ signal.pthread_sigmask(signal.SIG_BLOCK, [0])
+ with self.assertRaises(ValueError):
+ signal.pthread_sigmask(signal.SIG_BLOCK, [1<<1000])
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
diff --git a/Modules/clinic/signalmodule.c.h b/Modules/clinic/signalmodule.c.h
index ac97e80..811b252 100644
--- a/Modules/clinic/signalmodule.c.h
+++ b/Modules/clinic/signalmodule.c.h
@@ -248,17 +248,17 @@ PyDoc_STRVAR(signal_pthread_sigmask__doc__,
{"pthread_sigmask", (PyCFunction)signal_pthread_sigmask, METH_FASTCALL, signal_pthread_sigmask__doc__},
static PyObject *
-signal_pthread_sigmask_impl(PyObject *module, int how, PyObject *mask);
+signal_pthread_sigmask_impl(PyObject *module, int how, sigset_t mask);
static PyObject *
signal_pthread_sigmask(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
int how;
- PyObject *mask;
+ sigset_t mask;
- if (!_PyArg_ParseStack(args, nargs, "iO:pthread_sigmask",
- &how, &mask)) {
+ if (!_PyArg_ParseStack(args, nargs, "iO&:pthread_sigmask",
+ &how, _Py_Sigset_Converter, &mask)) {
goto exit;
}
return_value = signal_pthread_sigmask_impl(module, how, mask);
@@ -309,6 +309,24 @@ PyDoc_STRVAR(signal_sigwait__doc__,
#define SIGNAL_SIGWAIT_METHODDEF \
{"sigwait", (PyCFunction)signal_sigwait, METH_O, signal_sigwait__doc__},
+static PyObject *
+signal_sigwait_impl(PyObject *module, sigset_t sigset);
+
+static PyObject *
+signal_sigwait(PyObject *module, PyObject *arg)
+{
+ PyObject *return_value = NULL;
+ sigset_t sigset;
+
+ if (!PyArg_Parse(arg, "O&:sigwait", _Py_Sigset_Converter, &sigset)) {
+ goto exit;
+ }
+ return_value = signal_sigwait_impl(module, sigset);
+
+exit:
+ return return_value;
+}
+
#endif /* defined(HAVE_SIGWAIT) */
#if (defined(HAVE_SIGFILLSET) || defined(MS_WINDOWS))
@@ -349,6 +367,24 @@ PyDoc_STRVAR(signal_sigwaitinfo__doc__,
#define SIGNAL_SIGWAITINFO_METHODDEF \
{"sigwaitinfo", (PyCFunction)signal_sigwaitinfo, METH_O, signal_sigwaitinfo__doc__},
+static PyObject *
+signal_sigwaitinfo_impl(PyObject *module, sigset_t sigset);
+
+static PyObject *
+signal_sigwaitinfo(PyObject *module, PyObject *arg)
+{
+ PyObject *return_value = NULL;
+ sigset_t sigset;
+
+ if (!PyArg_Parse(arg, "O&:sigwaitinfo", _Py_Sigset_Converter, &sigset)) {
+ goto exit;
+ }
+ return_value = signal_sigwaitinfo_impl(module, sigset);
+
+exit:
+ return return_value;
+}
+
#endif /* defined(HAVE_SIGWAITINFO) */
#if defined(HAVE_SIGTIMEDWAIT)
@@ -365,19 +401,18 @@ PyDoc_STRVAR(signal_sigtimedwait__doc__,
{"sigtimedwait", (PyCFunction)signal_sigtimedwait, METH_FASTCALL, signal_sigtimedwait__doc__},
static PyObject *
-signal_sigtimedwait_impl(PyObject *module, PyObject *sigset,
+signal_sigtimedwait_impl(PyObject *module, sigset_t sigset,
PyObject *timeout_obj);
static PyObject *
signal_sigtimedwait(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
{
PyObject *return_value = NULL;
- PyObject *sigset;
+ sigset_t sigset;
PyObject *timeout_obj;
- if (!_PyArg_UnpackStack(args, nargs, "sigtimedwait",
- 2, 2,
- &sigset, &timeout_obj)) {
+ if (!_PyArg_ParseStack(args, nargs, "O&O:sigtimedwait",
+ _Py_Sigset_Converter, &sigset, &timeout_obj)) {
goto exit;
}
return_value = signal_sigtimedwait_impl(module, sigset, timeout_obj);
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 441c82e..5689f93 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -1278,6 +1278,65 @@ PyLong_FromPy_off_t(Py_off_t offset)
#endif
}
+#ifdef HAVE_SIGSET_T
+/* Convert an iterable of integers to a sigset.
+ Return 1 on success, return 0 and raise an exception on error. */
+int
+_Py_Sigset_Converter(PyObject *obj, void *addr)
+{
+ sigset_t *mask = (sigset_t *)addr;
+ PyObject *iterator, *item;
+ long signum;
+ int overflow;
+
+ if (sigemptyset(mask)) {
+ /* Probably only if mask == NULL. */
+ PyErr_SetFromErrno(PyExc_OSError);
+ return 0;
+ }
+
+ iterator = PyObject_GetIter(obj);
+ if (iterator == NULL) {
+ return 0;
+ }
+
+ while ((item = PyIter_Next(iterator)) != NULL) {
+ signum = PyLong_AsLongAndOverflow(item, &overflow);
+ Py_DECREF(item);
+ if (signum <= 0 || signum >= NSIG) {
+ if (overflow || signum != -1 || !PyErr_Occurred()) {
+ PyErr_Format(PyExc_ValueError,
+ "signal number %ld out of range", signum);
+ }
+ goto error;
+ }
+ if (sigaddset(mask, (int)signum)) {
+ if (errno != EINVAL) {
+ /* Probably impossible */
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto error;
+ }
+ /* For backwards compatibility, allow idioms such as
+ * `range(1, NSIG)` but warn about invalid signal numbers
+ */
+ const char msg[] =
+ "invalid signal number %ld, please use valid_signals()";
+ if (PyErr_WarnFormat(PyExc_RuntimeWarning, 1, msg, signum)) {
+ goto error;
+ }
+ }
+ }
+ if (!PyErr_Occurred()) {
+ Py_DECREF(iterator);
+ return 1;
+ }
+
+error:
+ Py_DECREF(iterator);
+ return 0;
+}
+#endif /* HAVE_SIGSET_T */
+
#ifdef MS_WINDOWS
static int
diff --git a/Modules/posixmodule.h b/Modules/posixmodule.h
index 1ec1833..1e00562 100644
--- a/Modules/posixmodule.h
+++ b/Modules/posixmodule.h
@@ -17,8 +17,17 @@ PyAPI_FUNC(PyObject *) _PyLong_FromGid(gid_t);
PyAPI_FUNC(int) _Py_Uid_Converter(PyObject *, void *);
PyAPI_FUNC(int) _Py_Gid_Converter(PyObject *, void *);
#endif /* MS_WINDOWS */
+
+#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGWAIT) || \
+ defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT)
+# define HAVE_SIGSET_T
#endif
+#ifdef HAVE_SIGSET_T
+PyAPI_FUNC(int) _Py_Sigset_Converter(PyObject *, void *);
+#endif /* HAVE_SIGSET_T */
+#endif /* Py_LIMITED_API */
+
#ifdef __cplusplus
}
#endif
diff --git a/Modules/signalmodule.c b/Modules/signalmodule.c
index b6b5bf1..0a5f190 100644
--- a/Modules/signalmodule.c
+++ b/Modules/signalmodule.c
@@ -59,6 +59,14 @@ module signal
[clinic start generated code]*/
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=b0301a3bde5fe9d3]*/
+/*[python input]
+
+class sigset_t_converter(CConverter):
+ type = 'sigset_t'
+ converter = '_Py_Sigset_Converter'
+
+[python start generated code]*/
+/*[python end generated code: output=da39a3ee5e6b4b0d input=b5689d14466b6823]*/
/*
NOTES ON THE INTERACTION BETWEEN SIGNALS AND THREADS
@@ -741,69 +749,6 @@ signal_getitimer_impl(PyObject *module, int which)
#endif
-#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGWAIT) || \
- defined(HAVE_SIGWAITINFO) || defined(HAVE_SIGTIMEDWAIT)
-/* Convert an iterable to a sigset.
- Return 0 on success, return -1 and raise an exception on error. */
-
-static int
-iterable_to_sigset(PyObject *iterable, sigset_t *mask)
-{
- int result = -1;
- PyObject *iterator, *item;
- long signum;
-
- sigemptyset(mask);
-
- iterator = PyObject_GetIter(iterable);
- if (iterator == NULL)
- goto error;
-
- while (1)
- {
- item = PyIter_Next(iterator);
- if (item == NULL) {
- if (PyErr_Occurred())
- goto error;
- else
- break;
- }
-
- signum = PyLong_AsLong(item);
- Py_DECREF(item);
- if (signum == -1 && PyErr_Occurred())
- goto error;
- if (0 < signum && signum < NSIG) {
- if (sigaddset(mask, (int)signum)) {
- if (errno != EINVAL) {
- /* Probably impossible */
- PyErr_SetFromErrno(PyExc_OSError);
- goto error;
- }
- /* For backwards compatibility, allow idioms such as
- * `range(1, NSIG)` but warn about invalid signal numbers
- */
- const char *msg =
- "invalid signal number %ld, please use valid_signals()";
- if (PyErr_WarnFormat(PyExc_RuntimeWarning, 1, msg, signum)) {
- goto error;
- }
- }
- }
- else {
- PyErr_Format(PyExc_ValueError,
- "signal number %ld out of range", signum);
- goto error;
- }
- }
- result = 0;
-
-error:
- Py_XDECREF(iterator);
- return result;
-}
-#endif
-
#if defined(PYPTHREAD_SIGMASK) || defined(HAVE_SIGPENDING)
static PyObject*
sigset_to_set(sigset_t mask)
@@ -846,23 +791,20 @@ sigset_to_set(sigset_t mask)
signal.pthread_sigmask
how: int
- mask: object
+ mask: sigset_t
/
Fetch and/or change the signal mask of the calling thread.
[clinic start generated code]*/
static PyObject *
-signal_pthread_sigmask_impl(PyObject *module, int how, PyObject *mask)
-/*[clinic end generated code: output=ff640fe092bc9181 input=f3b7d7a61b7b8283]*/
+signal_pthread_sigmask_impl(PyObject *module, int how, sigset_t mask)
+/*[clinic end generated code: output=0562c0fb192981a8 input=85bcebda442fa77f]*/
{
- sigset_t newmask, previous;
+ sigset_t previous;
int err;
- if (iterable_to_sigset(mask, &newmask))
- return NULL;
-
- err = pthread_sigmask(how, &newmask, &previous);
+ err = pthread_sigmask(how, &mask, &previous);
if (err != 0) {
errno = err;
PyErr_SetFromErrno(PyExc_OSError);
@@ -910,7 +852,7 @@ signal_sigpending_impl(PyObject *module)
/*[clinic input]
signal.sigwait
- sigset: object
+ sigset: sigset_t
/
Wait for a signal.
@@ -921,17 +863,13 @@ and returns the signal number.
[clinic start generated code]*/
static PyObject *
-signal_sigwait(PyObject *module, PyObject *sigset)
-/*[clinic end generated code: output=557173647424f6e4 input=11af2d82d83c2e94]*/
+signal_sigwait_impl(PyObject *module, sigset_t sigset)
+/*[clinic end generated code: output=f43770699d682f96 input=a6fbd47b1086d119]*/
{
- sigset_t set;
int err, signum;
- if (iterable_to_sigset(sigset, &set))
- return NULL;
-
Py_BEGIN_ALLOW_THREADS
- err = sigwait(&set, &signum);
+ err = sigwait(&sigset, &signum);
Py_END_ALLOW_THREADS
if (err) {
errno = err;
@@ -1046,7 +984,7 @@ fill_siginfo(siginfo_t *si)
/*[clinic input]
signal.sigwaitinfo
- sigset: object
+ sigset: sigset_t
/
Wait synchronously until one of the signals in *sigset* is delivered.
@@ -1055,20 +993,16 @@ Returns a struct_siginfo containing information about the signal.
[clinic start generated code]*/
static PyObject *
-signal_sigwaitinfo(PyObject *module, PyObject *sigset)
-/*[clinic end generated code: output=c40f27b269cd2309 input=f3779a74a991e171]*/
+signal_sigwaitinfo_impl(PyObject *module, sigset_t sigset)
+/*[clinic end generated code: output=1eb2f1fa236fdbca input=3d1a7e1f27fc664c]*/
{
- sigset_t set;
siginfo_t si;
int err;
int async_err = 0;
- if (iterable_to_sigset(sigset, &set))
- return NULL;
-
do {
Py_BEGIN_ALLOW_THREADS
- err = sigwaitinfo(&set, &si);
+ err = sigwaitinfo(&sigset, &si);
Py_END_ALLOW_THREADS
} while (err == -1
&& errno == EINTR && !(async_err = PyErr_CheckSignals()));
@@ -1085,7 +1019,7 @@ signal_sigwaitinfo(PyObject *module, PyObject *sigset)
/*[clinic input]
signal.sigtimedwait
- sigset: object
+ sigset: sigset_t
timeout as timeout_obj: object
/
@@ -1095,12 +1029,11 @@ The timeout is specified in seconds, with floating point numbers allowed.
[clinic start generated code]*/
static PyObject *
-signal_sigtimedwait_impl(PyObject *module, PyObject *sigset,
+signal_sigtimedwait_impl(PyObject *module, sigset_t sigset,
PyObject *timeout_obj)
-/*[clinic end generated code: output=f7eff31e679f4312 input=53fd4ea3e3724eb8]*/
+/*[clinic end generated code: output=59c8971e8ae18a64 input=87fd39237cf0b7ba]*/
{
struct timespec ts;
- sigset_t set;
siginfo_t si;
int res;
_PyTime_t timeout, deadline, monotonic;
@@ -1114,9 +1047,6 @@ signal_sigtimedwait_impl(PyObject *module, PyObject *sigset,
return NULL;
}
- if (iterable_to_sigset(sigset, &set))
- return NULL;
-
deadline = _PyTime_GetMonotonicClock() + timeout;
do {
@@ -1124,7 +1054,7 @@ signal_sigtimedwait_impl(PyObject *module, PyObject *sigset,
return NULL;
Py_BEGIN_ALLOW_THREADS
- res = sigtimedwait(&set, &si, &ts);
+ res = sigtimedwait(&sigset, &si, &ts);
Py_END_ALLOW_THREADS
if (res != -1)
--
2.23.0

View File

@ -0,0 +1,35 @@
From af40fb24e5ae3a462da9ea43529030eb05f487c8 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Miro=20Hron=C4=8Dok?= <miro@hroncok.cz>
Date: Fri, 11 May 2018 07:40:43 +0200
Subject: [PATCH] bpo-33455: Pass os.environ in
test_posix::test_specify_environment. (GH-6753)
Pass os.environ's copy to new process created at test_posix:
test_specify_environment. Otherwise important variables such as
LD_LIBRARY_PATH are not set and the child process might not work at all
in an environment where such variables are required for Python to function.
Conflict:NA
Reference:https://github.com/python/cpython/commit/7ec8f28656ea9d84048e9b5655375c6a74a59f53
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 5063a56..e2cda33 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1545,7 +1545,7 @@ class TestPosixSpawn(unittest.TestCase):
"""
pid = posix.posix_spawn(sys.executable,
[sys.executable, '-c', script],
- {'foo': 'bar'})
+ {**os.environ, 'foo': 'bar'})
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(envfile) as f:
self.assertEqual(f.read(), 'bar')
--
2.23.0

View File

@ -0,0 +1,83 @@
From d2b779f242d19fa0f2c1d8d55014f8dd0e7ca1cf Mon Sep 17 00:00:00 2001
From: Pablo Galindo <Pablogsal@gmail.com>
Date: Tue, 19 Jun 2018 09:19:50 +0100
Subject: [PATCH] bpo-33630: Fix using of freed memory in old versions of glicb
for posix_spawn(). (GH-7685)
Conflict:NA
Reference:https://github.com/python/cpython/commit/cb970730e3ca2522e9b1700dcaf0a06b7e898db6
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Modules/posixmodule.c | 25 ++++++++++++++++++++++---
1 file changed, 22 insertions(+), 3 deletions(-)
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index f10fe2b..441c82e 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5140,7 +5140,8 @@ enum posix_spawn_file_actions_identifier {
static int
parse_file_actions(PyObject *file_actions,
- posix_spawn_file_actions_t *file_actionsp)
+ posix_spawn_file_actions_t *file_actionsp,
+ PyObject *temp_buffer)
{
PyObject *seq;
PyObject *file_action = NULL;
@@ -5185,9 +5186,13 @@ parse_file_actions(PyObject *file_actions,
{
goto fail;
}
+ if (PyList_Append(temp_buffer, path)) {
+ Py_DECREF(path);
+ goto fail;
+ }
errno = posix_spawn_file_actions_addopen(file_actionsp,
fd, PyBytes_AS_STRING(path), oflag, (mode_t)mode);
- Py_DECREF(path); /* addopen copied it. */
+ Py_DECREF(path);
if (errno) {
posix_error();
goto fail;
@@ -5270,6 +5275,7 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
posix_spawn_file_actions_t *file_actionsp = NULL;
Py_ssize_t argc, envc;
PyObject *result = NULL;
+ PyObject *temp_buffer = NULL;
pid_t pid;
int err_code;
@@ -5310,7 +5316,19 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
}
if (file_actions != Py_None) {
- if (parse_file_actions(file_actions, &file_actions_buf)) {
+ /* There is a bug in old versions of glibc that makes some of the
+ * helper functions for manipulating file actions not copy the provided
+ * buffers. The problem is that posix_spawn_file_actions_addopen does not
+ * copy the value of path for some old versions of glibc (<2.20).
+ * The use of temp_buffer here is a workaround that keeps the
+ * python objects that own the buffers alive until posix_spawn gets called.
+ * Check https://bugs.python.org/issue33630 and
+ * https://sourceware.org/bugzilla/show_bug.cgi?id=17048 for more info.*/
+ temp_buffer = PyList_New(0);
+ if (!temp_buffer) {
+ goto exit;
+ }
+ if (parse_file_actions(file_actions, &file_actions_buf, temp_buffer)) {
goto exit;
}
file_actionsp = &file_actions_buf;
@@ -5337,6 +5355,7 @@ exit:
if (argvlist) {
free_string_array(argvlist, argc);
}
+ Py_XDECREF(temp_buffer);
return result;
}
#endif /* HAVE_POSIX_SPAWN */
--
2.23.0

View File

@ -0,0 +1,93 @@
From 26c316254e347dffe64944dcf9ffeb6ee981dc0d Mon Sep 17 00:00:00 2001
From: William Orr <will@worrbase.com>
Date: Mon, 1 Oct 2018 22:19:56 -0700
Subject: [PATCH] closes bpo-34862: Guard definition of convert_sched_param
with POSIX_SPAWN_SETSCHEDULER. (GH-9658)
Fixes broken build on OpenBSD-current.
Conflict:NA
Reference:https://github.com/python/cpython/commit/81574b80e92554adf75c13fa42415beb8be383cb
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Modules/clinic/posixmodule.c.h | 4 ++--
Modules/posixmodule.c | 10 ++++++----
2 files changed, 8 insertions(+), 6 deletions(-)
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 0448aa5..51b30d9 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -2154,7 +2154,7 @@ exit:
#endif /* defined(HAVE_SCHED_H) && defined(HAVE_SCHED_SETSCHEDULER) */
-#if defined(HAVE_SCHED_H) && (defined(HAVE_SCHED_SETSCHEDULER) || defined(HAVE_SCHED_SETPARAM))
+#if defined(HAVE_SCHED_H) && (defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM))
PyDoc_STRVAR(os_sched_param__doc__,
"sched_param(sched_priority)\n"
@@ -2186,7 +2186,7 @@ exit:
return return_value;
}
-#endif /* defined(HAVE_SCHED_H) && (defined(HAVE_SCHED_SETSCHEDULER) || defined(HAVE_SCHED_SETPARAM)) */
+#endif /* defined(HAVE_SCHED_H) && (defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)) */
#if defined(HAVE_SCHED_H) && defined(HAVE_SCHED_SETSCHEDULER)
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 8d0e312..ba7fbef 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -1937,7 +1937,7 @@ static PyTypeObject WaitidResultType;
static int initialized;
static PyTypeObject StatResultType;
static PyTypeObject StatVFSResultType;
-#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER)
+#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)
static PyTypeObject SchedParamType;
#endif
static newfunc structseq_new;
@@ -5138,8 +5138,10 @@ enum posix_spawn_file_actions_identifier {
POSIX_SPAWN_DUP2
};
+#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)
static int
convert_sched_param(PyObject *param, struct sched_param *res);
+#endif
static int
parse_posix_spawn_flags(PyObject *setpgroup, int resetids, PyObject *setsigmask,
@@ -5961,7 +5963,7 @@ os_sched_getscheduler_impl(PyObject *module, pid_t pid)
#endif /* HAVE_SCHED_SETSCHEDULER */
-#if defined(HAVE_SCHED_SETSCHEDULER) || defined(HAVE_SCHED_SETPARAM)
+#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)
/*[clinic input]
class os.sched_param "PyObject *" "&SchedParamType"
@@ -6022,7 +6024,7 @@ convert_sched_param(PyObject *param, struct sched_param *res)
res->sched_priority = Py_SAFE_DOWNCAST(priority, long, int);
return 1;
}
-#endif /* defined(HAVE_SCHED_SETSCHEDULER) || defined(HAVE_SCHED_SETPARAM) */
+#endif /* defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM) */
#ifdef HAVE_SCHED_SETSCHEDULER
@@ -13977,7 +13979,7 @@ INITFUNC(void)
# endif
#endif
-#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER)
+#if defined(HAVE_SCHED_SETPARAM) || defined(HAVE_SCHED_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDULER) || defined(POSIX_SPAWN_SETSCHEDPARAM)
sched_param_desc.name = MODNAME ".sched_param";
if (PyStructSequence_InitType2(&SchedParamType, &sched_param_desc) < 0)
return NULL;
--
2.23.0

View File

@ -0,0 +1,298 @@
From 28e39f4fff94e3f17ba49dce6b6f812a60f3d9dc Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye <33177550+nanjekyejoannah@users.noreply.github.com>
Date: Fri, 1 Feb 2019 13:05:22 +0300
Subject: [PATCH] bpo-35537: Add setsid parameter to os.posix_spawn() and
os.posix_spawnp() (GH-11608)
Conflict:NA
Reference:https://github.com/python/cpython/commit/80c5dfe74b4402d0a220c9227f262ec6fde1d7fc
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 16 +++++++
.../2019-01-18-13-44-13.bpo-35537.R1lbTl.rst | 1 +
Modules/clinic/posixmodule.c.h | 42 +++++++++++-------
Modules/posixmodule.c | 44 +++++++++++++------
4 files changed, 73 insertions(+), 30 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-01-18-13-44-13.bpo-35537.R1lbTl.rst
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index ec72f9e..ccc16b5 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1638,6 +1638,22 @@ class _PosixSpawnMixin:
os.environ, setsigmask=[signal.NSIG,
signal.NSIG+1])
+ def test_start_new_session(self):
+ # For code coverage of calling setsid(). We don't care if we get an
+ # EPERM error from it depending on the test execution environment, that
+ # still indicates that it was called.
+ code = "import os; print(os.getpgid(os.getpid()))"
+ try:
+ self.spawn_func(sys.executable,
+ [sys.executable, "-c", code],
+ os.environ, setsid=True)
+ except NotImplementedError as exc:
+ self.skipTest("setsid is not supported: %s" % exc)
+ else:
+ parent_pgid = os.getpgid(os.getpid())
+ child_pgid = int(output)
+ self.assertNotEqual(parent_pgid, child_pgid)
+
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
def test_setsigdef(self):
diff --git a/Misc/NEWS.d/next/Library/2019-01-18-13-44-13.bpo-35537.R1lbTl.rst b/Misc/NEWS.d/next/Library/2019-01-18-13-44-13.bpo-35537.R1lbTl.rst
new file mode 100644
index 0000000..56f23a1
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-01-18-13-44-13.bpo-35537.R1lbTl.rst
@@ -0,0 +1 @@
+:func:`os.posix_spawn` and :func:`os.posix_spawnp` now have a *setsid* parameter.
\ No newline at end of file
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 51b30d9..e06ddec 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -1731,8 +1731,8 @@ exit:
PyDoc_STRVAR(os_posix_spawn__doc__,
"posix_spawn($module, path, argv, env, /, *, file_actions=(),\n"
-" setpgroup=None, resetids=False, setsigmask=(),\n"
-" setsigdef=(), scheduler=None)\n"
+" setpgroup=None, resetids=False, setsid=False,\n"
+" setsigmask=(), setsigdef=(), scheduler=None)\n"
"--\n"
"\n"
"Execute the program specified by path in a new process.\n"
@@ -1748,7 +1748,9 @@ PyDoc_STRVAR(os_posix_spawn__doc__,
" 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"
+" If the value is `true` the POSIX_SPAWN_RESETIDS will be activated.\n"
+" setsid\n"
+" If the value is `true` the POSIX_SPAWN_SETSID or POSIX_SPAWN_SETSID_NP will be activated.\n"
" setsigmask\n"
" The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag.\n"
" setsigdef\n"
@@ -1762,30 +1764,32 @@ PyDoc_STRVAR(os_posix_spawn__doc__,
static PyObject *
os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
PyObject *env, PyObject *file_actions,
- PyObject *setpgroup, int resetids, PyObject *setsigmask,
- PyObject *setsigdef, PyObject *scheduler);
+ PyObject *setpgroup, int resetids, int setsid,
+ PyObject *setsigmask, PyObject *setsigdef,
+ PyObject *scheduler);
static PyObject *
os_posix_spawn(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
- static const char * const _keywords[] = {"", "", "", "file_actions", "setpgroup", "resetids", "setsigmask", "setsigdef", "scheduler", NULL};
- static _PyArg_Parser _parser = {"O&OO|$OOiOOO:posix_spawn", _keywords, 0};
+ static const char * const _keywords[] = {"", "", "", "file_actions", "setpgroup", "resetids", "setsid", "setsigmask", "setsigdef", "scheduler", NULL};
+ static _PyArg_Parser _parser = {"O&OO|$OOiiOOO:posix_spawn", _keywords, 0};
path_t path = PATH_T_INITIALIZE("posix_spawn", "path", 0, 0);
PyObject *argv;
PyObject *env;
PyObject *file_actions = NULL;
PyObject *setpgroup = NULL;
int resetids = 0;
+ int setsid = 0;
PyObject *setsigmask = NULL;
PyObject *setsigdef = NULL;
PyObject *scheduler = NULL;
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
- path_converter, &path, &argv, &env, &file_actions, &setpgroup, &resetids, &setsigmask, &setsigdef, &scheduler)) {
+ path_converter, &path, &argv, &env, &file_actions, &setpgroup, &resetids, &setsid, &setsigmask, &setsigdef, &scheduler)) {
goto exit;
}
- return_value = os_posix_spawn_impl(module, &path, argv, env, file_actions, setpgroup, resetids, setsigmask, setsigdef, scheduler);
+ return_value = os_posix_spawn_impl(module, &path, argv, env, file_actions, setpgroup, resetids, setsid, setsigmask, setsigdef, scheduler);
exit:
/* Cleanup for path */
@@ -1800,8 +1804,8 @@ exit:
PyDoc_STRVAR(os_posix_spawnp__doc__,
"posix_spawnp($module, path, argv, env, /, *, file_actions=(),\n"
-" setpgroup=None, resetids=False, setsigmask=(),\n"
-" setsigdef=(), scheduler=None)\n"
+" setpgroup=None, resetids=False, setsid=False,\n"
+" setsigmask=(), setsigdef=(), scheduler=None)\n"
"--\n"
"\n"
"Execute the program specified by path in a new process.\n"
@@ -1818,6 +1822,8 @@ PyDoc_STRVAR(os_posix_spawnp__doc__,
" 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"
+" setsid\n"
+" If the value is `True` the POSIX_SPAWN_SETSID or POSIX_SPAWN_SETSID_NP will be activated.\n"
" setsigmask\n"
" The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag.\n"
" setsigdef\n"
@@ -1831,30 +1837,32 @@ PyDoc_STRVAR(os_posix_spawnp__doc__,
static PyObject *
os_posix_spawnp_impl(PyObject *module, path_t *path, PyObject *argv,
PyObject *env, PyObject *file_actions,
- PyObject *setpgroup, int resetids, PyObject *setsigmask,
- PyObject *setsigdef, PyObject *scheduler);
+ PyObject *setpgroup, int resetids, int setsid,
+ PyObject *setsigmask, PyObject *setsigdef,
+ PyObject *scheduler);
static PyObject *
os_posix_spawnp(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
{
PyObject *return_value = NULL;
- static const char * const _keywords[] = {"", "", "", "file_actions", "setpgroup", "resetids", "setsigmask", "setsigdef", "scheduler", NULL};
- static _PyArg_Parser _parser = {"O&OO|$OOiOOO:posix_spawnp", _keywords, 0};
+ static const char * const _keywords[] = {"", "", "", "file_actions", "setpgroup", "resetids", "setsid", "setsigmask", "setsigdef", "scheduler", NULL};
+ static _PyArg_Parser _parser = {"O&OO|$OOiiOOO:posix_spawnp", _keywords, 0};
path_t path = PATH_T_INITIALIZE("posix_spawnp", "path", 0, 0);
PyObject *argv;
PyObject *env;
PyObject *file_actions = NULL;
PyObject *setpgroup = NULL;
int resetids = 0;
+ int setsid = 0;
PyObject *setsigmask = NULL;
PyObject *setsigdef = NULL;
PyObject *scheduler = NULL;
if (!_PyArg_ParseStackAndKeywords(args, nargs, kwnames, &_parser,
- path_converter, &path, &argv, &env, &file_actions, &setpgroup, &resetids, &setsigmask, &setsigdef, &scheduler)) {
+ path_converter, &path, &argv, &env, &file_actions, &setpgroup, &resetids, &setsid, &setsigmask, &setsigdef, &scheduler)) {
goto exit;
}
- return_value = os_posix_spawnp_impl(module, &path, argv, env, file_actions, setpgroup, resetids, setsigmask, setsigdef, scheduler);
+ return_value = os_posix_spawnp_impl(module, &path, argv, env, file_actions, setpgroup, resetids, setsid, setsigmask, setsigdef, scheduler);
exit:
/* Cleanup for path */
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index ba7fbef..834ead4 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5144,10 +5144,11 @@ convert_sched_param(PyObject *param, struct sched_param *res);
#endif
static int
-parse_posix_spawn_flags(PyObject *setpgroup, int resetids, PyObject *setsigmask,
+parse_posix_spawn_flags(PyObject *setpgroup, int resetids, int setsid, PyObject *setsigmask,
PyObject *setsigdef, PyObject *scheduler,
posix_spawnattr_t *attrp)
{
+ const char *func_name = "posix_spawnp";
long all_flags = 0;
errno = posix_spawnattr_init(attrp);
@@ -5173,6 +5174,17 @@ parse_posix_spawn_flags(PyObject *setpgroup, int resetids, PyObject *setsigmask,
all_flags |= POSIX_SPAWN_RESETIDS;
}
+ if (setsid) {
+#ifdef POSIX_SPAWN_SETSID
+ all_flags |= POSIX_SPAWN_SETSID;
+#elif defined(POSIX_SPAWN_SETSID_NP)
+ all_flags |= POSIX_SPAWN_SETSID_NP;
+#else
+ argument_unavailable_error(func_name, "setsid");
+ return -1;
+#endif
+ }
+
if (setsigmask) {
sigset_t set;
if (!_Py_Sigset_Converter(setsigmask, &set)) {
@@ -5363,7 +5375,7 @@ fail:
static PyObject *
py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *argv,
PyObject *env, PyObject *file_actions,
- PyObject *setpgroup, int resetids, PyObject *setsigmask,
+ PyObject *setpgroup, int resetids, int setsid, PyObject *setsigmask,
PyObject *setsigdef, PyObject *scheduler)
{
EXECV_CHAR **argvlist = NULL;
@@ -5378,7 +5390,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
pid_t pid;
int err_code;
- /* posix_spawn has three arguments: (path, argv, env), where
+ /* posix_spawn and posix_spawnp have three arguments: (path, argv, env), where
argv is a list or tuple of strings and env is a dictionary
like posix.environ. */
@@ -5433,7 +5445,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
file_actionsp = &file_actions_buf;
}
- if (parse_posix_spawn_flags(setpgroup, resetids, setsigmask,
+ if (parse_posix_spawn_flags(setpgroup, resetids, setsid, setsigmask,
setsigdef, scheduler, &attr)) {
goto exit;
}
@@ -5495,6 +5507,8 @@ os.posix_spawn
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.
+ setsid: bool(accept={int}) = False
+ If the value is `True` the POSIX_SPAWN_SETSID or POSIX_SPAWN_SETSID_NP will be activated.
setsigmask: object(c_default='NULL') = ()
The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag.
setsigdef: object(c_default='NULL') = ()
@@ -5508,12 +5522,13 @@ Execute the program specified by path in a new process.
static PyObject *
os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
PyObject *env, PyObject *file_actions,
- PyObject *setpgroup, int resetids, PyObject *setsigmask,
- PyObject *setsigdef, PyObject *scheduler)
-/*[clinic end generated code: output=45dfa4c515d09f2c input=2891c2f1d457e39b]*/
+ PyObject *setpgroup, int resetids, int setsid,
+ PyObject *setsigmask, PyObject *setsigdef,
+ PyObject *scheduler)
+/*[clinic end generated code: output=14a1098c566bc675 input=8c6305619a00ad04]*/
{
return py_posix_spawn(0, module, path, argv, env, file_actions,
- setpgroup, resetids, setsigmask, setsigdef,
+ setpgroup, resetids, setsid, setsigmask, setsigdef,
scheduler);
}
#endif /* HAVE_POSIX_SPAWN */
@@ -5537,7 +5552,9 @@ os.posix_spawnp
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.
+ If the value is `true` the POSIX_SPAWN_RESETIDS will be activated.
+ setsid: bool(accept={int}) = False
+ If the value is `true` the POSIX_SPAWN_SETSID or POSIX_SPAWN_SETSID_NP will be activated.
setsigmask: object(c_default='NULL') = ()
The sigmask to use with the POSIX_SPAWN_SETSIGMASK flag.
setsigdef: object(c_default='NULL') = ()
@@ -5551,12 +5568,13 @@ Execute the program specified by path in a new process.
static PyObject *
os_posix_spawnp_impl(PyObject *module, path_t *path, PyObject *argv,
PyObject *env, PyObject *file_actions,
- PyObject *setpgroup, int resetids, PyObject *setsigmask,
- PyObject *setsigdef, PyObject *scheduler)
-/*[clinic end generated code: output=7955dc0edc82b8c3 input=b7576eb25b1ed9eb]*/
+ PyObject *setpgroup, int resetids, int setsid,
+ PyObject *setsigmask, PyObject *setsigdef,
+ PyObject *scheduler)
+/*[clinic end generated code: output=7b9aaefe3031238d input=c1911043a22028da]*/
{
return py_posix_spawn(1, module, path, argv, env, file_actions,
- setpgroup, resetids, setsigmask, setsigdef,
+ setpgroup, resetids, setsid, setsigmask, setsigdef,
scheduler);
}
#endif /* HAVE_POSIX_SPAWNP */
--
2.23.0

View File

@ -0,0 +1,91 @@
From e601dbfea5dc592618863bd77cf7ae2dafda466b Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@redhat.com>
Date: Fri, 1 Feb 2019 15:47:24 +0100
Subject: [PATCH] bpo-35537: Fix function name in os.posix_spawnp() errors
(GH-11719)
Conflict:NA
Reference:https://github.com/python/cpython/commit/325e4bac5ab49f47ec60242d3242647605193a2e
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Modules/posixmodule.c | 24 +++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 834ead4..cc1f0be 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5144,11 +5144,11 @@ convert_sched_param(PyObject *param, struct sched_param *res);
#endif
static int
-parse_posix_spawn_flags(PyObject *setpgroup, int resetids, int setsid, PyObject *setsigmask,
+parse_posix_spawn_flags(const char *func_name, PyObject *setpgroup,
+ int resetids, int setsid, PyObject *setsigmask,
PyObject *setsigdef, PyObject *scheduler,
posix_spawnattr_t *attrp)
{
- const char *func_name = "posix_spawnp";
long all_flags = 0;
errno = posix_spawnattr_init(attrp);
@@ -5378,6 +5378,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
PyObject *setpgroup, int resetids, int setsid, PyObject *setsigmask,
PyObject *setsigdef, PyObject *scheduler)
{
+ const char *func_name = use_posix_spawnp ? "posix_spawnp" : "posix_spawn";
EXECV_CHAR **argvlist = NULL;
EXECV_CHAR **envlist = NULL;
posix_spawn_file_actions_t file_actions_buf;
@@ -5395,19 +5396,20 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
like posix.environ. */
if (!PyList_Check(argv) && !PyTuple_Check(argv)) {
- PyErr_SetString(PyExc_TypeError,
- "posix_spawn: argv must be a tuple or list");
+ PyErr_Format(PyExc_TypeError,
+ "%s: argv must be a tuple or list", func_name);
goto exit;
}
argc = PySequence_Size(argv);
if (argc < 1) {
- PyErr_SetString(PyExc_ValueError, "posix_spawn: argv must not be empty");
+ PyErr_Format(PyExc_ValueError,
+ "%s: argv must not be empty", func_name);
return NULL;
}
if (!PyMapping_Check(env)) {
- PyErr_SetString(PyExc_TypeError,
- "posix_spawn: environment must be a mapping object");
+ PyErr_Format(PyExc_TypeError,
+ "%s: environment must be a mapping object", func_name);
goto exit;
}
@@ -5416,8 +5418,8 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
goto exit;
}
if (!argvlist[0][0]) {
- PyErr_SetString(PyExc_ValueError,
- "posix_spawn: argv first element cannot be empty");
+ PyErr_Format(PyExc_ValueError,
+ "%s: argv first element cannot be empty", func_name);
goto exit;
}
@@ -5445,8 +5447,8 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
file_actionsp = &file_actions_buf;
}
- if (parse_posix_spawn_flags(setpgroup, resetids, setsid, setsigmask,
- setsigdef, scheduler, &attr)) {
+ if (parse_posix_spawn_flags(func_name, setpgroup, resetids, setsid,
+ setsigmask, setsigdef, scheduler, &attr)) {
goto exit;
}
attrp = &attr;
--
2.23.0

View File

@ -0,0 +1,101 @@
From 7940f5c6e508ccb5a4d647094bf40f7a2f57b097 Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@redhat.com>
Date: Fri, 14 Jun 2019 19:31:43 +0200
Subject: [PATCH] bpo-35537: Rewrite setsid test for os.posix_spawn (GH-11721)
bpo-35537, bpo-35876: Fix also test_start_new_session() of
test_subprocess: use os.getsid() rather than os.getpgid().
Conflict:NA
Reference:https://github.com/python/cpython/commit/5884043252473ac733aba1d3251d4debe72511e5
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 44 +++++++++++++++++++++++--------------
Lib/test/test_subprocess.py | 9 ++++----
2 files changed, 32 insertions(+), 21 deletions(-)
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 83accd4..e816946 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1638,23 +1638,35 @@ class _PosixSpawnMixin:
os.environ, setsigmask=[signal.NSIG,
signal.NSIG+1])
- @unittest.skipIf(True,
- "FIXME: bpo-35537: test fails is setsid is supported")
- def test_start_new_session(self):
- # For code coverage of calling setsid(). We don't care if we get an
- # EPERM error from it depending on the test execution environment, that
- # still indicates that it was called.
- code = "import os; print(os.getpgid(os.getpid()))"
+ def test_setsid(self):
+ rfd, wfd = os.pipe()
+ self.addCleanup(os.close, rfd)
try:
- self.spawn_func(sys.executable,
- [sys.executable, "-c", code],
- os.environ, setsid=True)
- except NotImplementedError as exc:
- self.skipTest("setsid is not supported: %s" % exc)
- else:
- parent_pgid = os.getpgid(os.getpid())
- child_pgid = int(output)
- self.assertNotEqual(parent_pgid, child_pgid)
+ os.set_inheritable(wfd, True)
+
+ code = textwrap.dedent(f"""
+ import os
+ fd = {wfd}
+ sid = os.getsid(0)
+ os.write(fd, str(sid).encode())
+ """)
+
+ try:
+ pid = self.spawn_func(sys.executable,
+ [sys.executable, "-c", code],
+ os.environ, setsid=True)
+ except NotImplementedError as exc:
+ self.skipTest(f"setsid is not supported: {exc!r}")
+ except PermissionError as exc:
+ self.skipTest(f"setsid failed with: {exc!r}")
+ finally:
+ os.close(wfd)
+
+ self.assertEqual(os.waitpid(pid, 0), (pid, 0))
+ output = os.read(rfd, 100)
+ child_sid = int(output)
+ parent_sid = os.getsid(os.getpid())
+ self.assertNotEqual(parent_sid, child_sid)
@unittest.skipUnless(hasattr(signal, 'pthread_sigmask'),
'need signal.pthread_sigmask()')
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 89dcb8f..eebd348 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1671,16 +1671,15 @@ class POSIXProcessTestCase(BaseTestCase):
# still indicates that it was called.
try:
output = subprocess.check_output(
- [sys.executable, "-c",
- "import os; print(os.getpgid(os.getpid()))"],
+ [sys.executable, "-c", "import os; print(os.getsid(0))"],
start_new_session=True)
except OSError as e:
if e.errno != errno.EPERM:
raise
else:
- parent_pgid = os.getpgid(os.getpid())
- child_pgid = int(output)
- self.assertNotEqual(parent_pgid, child_pgid)
+ parent_sid = os.getsid(0)
+ child_sid = int(output)
+ self.assertNotEqual(parent_sid, child_sid)
def test_run_abort(self):
# returncode handles signal termination
--
2.23.0

View File

@ -0,0 +1,32 @@
From 41c5fb1900f673029d1b6ecf24c743ac1ce4114f Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@redhat.com>
Date: Fri, 1 Feb 2019 11:40:26 +0100
Subject: [PATCH] bpo-35537: Skip test_start_new_session() of posix_spawn
(GH-11718)
The test fails. Skip the test until a fix can be found.
Conflict:NA
Reference:https://github.com/python/cpython/commit/1e39b83f24ffade3e0ca1a8004b461354f64ae08
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index ccc16b5..83accd4 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1638,6 +1638,8 @@ class _PosixSpawnMixin:
os.environ, setsigmask=[signal.NSIG,
signal.NSIG+1])
+ @unittest.skipIf(True,
+ "FIXME: bpo-35537: test fails is setsid is supported")
def test_start_new_session(self):
# For code coverage of calling setsid(). We don't care if we get an
# EPERM error from it depending on the test execution environment, that
--
2.23.0

View File

@ -0,0 +1,162 @@
From e2f485341b9b99e33a10738191cc933f68509096 Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@redhat.com>
Date: Wed, 23 Jan 2019 19:00:39 +0100
Subject: [PATCH] bpo-35537: subprocess can use posix_spawn with pipes
(GH-11575)
* subprocess.Popen can now also use os.posix_spawn() with pipes,
but only if pipe file descriptors are greater than 2.
* Fix Popen._posix_spawn(): set '_child_created' attribute to True.
* Add Popen._close_pipe_fds() helper function to factorize the code.
Conflict:NA
Reference:https://github.com/python/cpython/commit/f6243ac1e4828299fe5a8e943d7bd41cab1f34cd
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/subprocess.py | 92 ++++++++++++++++++++++++++++++++---------------
1 file changed, 64 insertions(+), 28 deletions(-)
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index d711ddb..b02b701 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -1085,6 +1085,34 @@ class Popen(object):
pass
raise # resume the KeyboardInterrupt
+ def _close_pipe_fds(self,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite):
+ # self._devnull is not always defined.
+ devnull_fd = getattr(self, '_devnull', None)
+
+ if _mswindows:
+ if p2cread != -1:
+ p2cread.Close()
+ if c2pwrite != -1:
+ c2pwrite.Close()
+ if errwrite != -1:
+ errwrite.Close()
+ else:
+ if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
+ os.close(p2cread)
+ if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
+ os.close(c2pwrite)
+ if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
+ os.close(errwrite)
+
+ if devnull_fd is not None:
+ os.close(devnull_fd)
+
+ # Prevent a double close of these handles/fds from __init__ on error.
+ self._closed_child_pipe_fds = True
+
if _mswindows:
#
@@ -1263,17 +1291,9 @@ class Popen(object):
# output pipe are maintained in this process or else the
# pipe will not close when the child process exits and the
# ReadFile will hang.
- if p2cread != -1:
- p2cread.Close()
- if c2pwrite != -1:
- c2pwrite.Close()
- if errwrite != -1:
- errwrite.Close()
- if hasattr(self, '_devnull'):
- os.close(self._devnull)
- # Prevent a double close of these handles/fds from __init__
- # on error.
- self._closed_child_pipe_fds = True
+ self._close_pipe_fds(p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite)
# Retain the process handle, but close the thread handle
self._child_created = True
@@ -1460,7 +1480,10 @@ class Popen(object):
errread, errwrite)
- def _posix_spawn(self, args, executable, env, restore_signals):
+ def _posix_spawn(self, args, executable, env, restore_signals,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite):
"""Execute program using os.posix_spawn()."""
if env is None:
env = os.environ
@@ -1475,7 +1498,26 @@ class Popen(object):
sigset.append(signum)
kwargs['setsigdef'] = sigset
+ file_actions = []
+ for fd in (p2cwrite, c2pread, errread):
+ if fd != -1:
+ file_actions.append((os.POSIX_SPAWN_CLOSE, fd))
+ for fd, fd2 in (
+ (p2cread, 0),
+ (c2pwrite, 1),
+ (errwrite, 2),
+ ):
+ if fd != -1:
+ file_actions.append((os.POSIX_SPAWN_DUP2, fd, fd2))
+ if file_actions:
+ kwargs['file_actions'] = file_actions
+
self.pid = os.posix_spawn(executable, args, env, **kwargs)
+ self._child_created = True
+
+ self._close_pipe_fds(p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite)
def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env,
@@ -1508,11 +1550,14 @@ class Popen(object):
and not close_fds
and not pass_fds
and cwd is None
- and p2cread == p2cwrite == -1
- and c2pread == c2pwrite == -1
- and errread == errwrite == -1
+ and (p2cread == -1 or p2cread > 2)
+ and (c2pwrite == -1 or c2pwrite > 2)
+ and (errwrite == -1 or errwrite > 2)
and not start_new_session):
- self._posix_spawn(args, executable, env, restore_signals)
+ self._posix_spawn(args, executable, env, restore_signals,
+ p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite)
return
orig_executable = executable
@@ -1567,18 +1612,9 @@ class Popen(object):
# be sure the FD is closed no matter what
os.close(errpipe_write)
- # self._devnull is not always defined.
- devnull_fd = getattr(self, '_devnull', None)
- if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
- os.close(p2cread)
- if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
- os.close(c2pwrite)
- if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
- os.close(errwrite)
- if devnull_fd is not None:
- os.close(devnull_fd)
- # Prevent a double close of these fds from __init__ on error.
- self._closed_child_pipe_fds = True
+ self._close_pipe_fds(p2cread, p2cwrite,
+ c2pread, c2pwrite,
+ errread, errwrite)
# Wait for exec to fail or succeed; possibly raising an
# exception (limited in size)
--
2.23.0

View File

@ -0,0 +1,144 @@
From 41bd027d617de09a2c58972312b36f7be2779db6 Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@redhat.com>
Date: Wed, 16 Jan 2019 00:02:35 +0100
Subject: [PATCH] bpo-35537: subprocess uses os.posix_spawn in some cases
(GH-11452)
The subprocess module can now use the os.posix_spawn() function
in some cases for better performance. Currently, it is only used on macOS
and Linux (using glibc 2.24 or newer) if all these conditions are met:
* executable path contains a directory
* close_fds=False
* preexec_fn, pass_fds, cwd, stdin, stdout, stderr
and start_new_session parameters are not set
Conflict:NA
Reference:https://github.com/python/cpython/commit/9daecf37a571e98aaf43a387bcc9e41a7132f477
Co-authored-by: Joannah Nanjekye <nanjekyejoannah@gmail.com>
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/subprocess.py | 82 +++++++++++++++++++
.../2018-12-20-16-24-51.bpo-35537.z4E7aA.rst | 2 +
2 files changed, 84 insertions(+)
create mode 100644 Misc/NEWS.d/next/Library/2018-12-20-16-24-51.bpo-35537.z4E7aA.rst
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 3f99be5..d711ddb 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -630,6 +630,57 @@ def getoutput(cmd):
return getstatusoutput(cmd)[1]
+def _use_posix_spawn():
+ """Check is posix_spawn() can be used for subprocess.
+
+ subprocess requires a posix_spawn() implementation that reports properly
+ errors to the parent process, set errno on the following failures:
+
+ * process attribute actions failed
+ * file actions failed
+ * exec() failed
+
+ Prefer an implementation which can use vfork in some cases for best
+ performances.
+ """
+ if _mswindows or not hasattr(os, 'posix_spawn'):
+ # os.posix_spawn() is not available
+ return False
+
+ if sys.platform == 'darwin':
+ # posix_spawn() is a syscall on macOS and properly reports errors
+ return True
+
+ # Check libc name and runtime libc version
+ try:
+ ver = os.confstr('CS_GNU_LIBC_VERSION')
+ # parse 'glibc 2.28' as ('glibc', (2, 28))
+ parts = ver.split(maxsplit=1)
+ if len(parts) != 2:
+ # reject unknown format
+ raise ValueError
+ libc = parts[0]
+ version = tuple(map(int, parts[1].split('.')))
+
+ if sys.platform == 'linux' and libc == 'glibc' and version >= (2, 24):
+ # glibc 2.24 has a new Linux posix_spawn implementation using vfork
+ # which properly reports errors to the parent process.
+ return True
+ # Note: Don't use the POSIX implementation of glibc because it doesn't
+ # use vfork (even if glibc 2.26 added a pipe to properly report errors
+ # to the parent process).
+ except (AttributeError, ValueError, OSError):
+ # os.confstr() or CS_GNU_LIBC_VERSION value not available
+ pass
+
+ # By default, consider that the implementation does not properly report
+ # errors.
+ return False
+
+
+_USE_POSIX_SPAWN = _use_posix_spawn()
+
+
class Popen(object):
""" Execute a child program in a new process.
@@ -1409,6 +1460,23 @@ class Popen(object):
errread, errwrite)
+ def _posix_spawn(self, args, executable, env, restore_signals):
+ """Execute program using os.posix_spawn()."""
+ if env is None:
+ env = os.environ
+
+ kwargs = {}
+ if restore_signals:
+ # See _Py_RestoreSignals() in Python/pylifecycle.c
+ sigset = []
+ for signame in ('SIGPIPE', 'SIGXFZ', 'SIGXFSZ'):
+ signum = getattr(signal, signame, None)
+ if signum is not None:
+ sigset.append(signum)
+ kwargs['setsigdef'] = sigset
+
+ self.pid = os.posix_spawn(executable, args, env, **kwargs)
+
def _execute_child(self, args, executable, preexec_fn, close_fds,
pass_fds, cwd, env,
startupinfo, creationflags, shell,
@@ -1433,6 +1501,20 @@ class Popen(object):
if executable is None:
executable = args[0]
+
+ if (_USE_POSIX_SPAWN
+ and os.path.dirname(executable)
+ and preexec_fn is None
+ and not close_fds
+ and not pass_fds
+ and cwd is None
+ and p2cread == p2cwrite == -1
+ and c2pread == c2pwrite == -1
+ and errread == errwrite == -1
+ and not start_new_session):
+ self._posix_spawn(args, executable, env, restore_signals)
+ return
+
orig_executable = executable
# For transferring possible exec failure from child to parent.
diff --git a/Misc/NEWS.d/next/Library/2018-12-20-16-24-51.bpo-35537.z4E7aA.rst b/Misc/NEWS.d/next/Library/2018-12-20-16-24-51.bpo-35537.z4E7aA.rst
new file mode 100644
index 0000000..b14d749
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-12-20-16-24-51.bpo-35537.z4E7aA.rst
@@ -0,0 +1,2 @@
+The :mod:`subprocess` module can now use the :func:`os.posix_spawn` function in
+some cases for better performance.
--
2.23.0

View File

@ -0,0 +1,686 @@
From cfe32a7f819ed8d802141fcad2842bbbf356670b Mon Sep 17 00:00:00 2001
From: Joannah Nanjekye <33177550+nanjekyejoannah@users.noreply.github.com>
Date: Wed, 16 Jan 2019 16:29:26 +0300
Subject: [PATCH] bpo-35674: Add os.posix_spawnp() (GH-11554)
Add a new os.posix_spawnp() function.
Conflict:NA
Reference:https://github.com/python/cpython/commit/92b8322e7ea04b239cb1cb87b78d952f13ddfebb
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 190 +++++++++++-------
.../2019-01-14-14-13-08.bpo-35674.kamWqz.rst | 2 +
Modules/clinic/posixmodule.c.h | 73 +++++++
Modules/posixmodule.c | 135 +++++++++----
configure | 2 +-
configure.ac | 2 +-
pyconfig.h.in | 3 +
7 files changed, 300 insertions(+), 107 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-01-14-14-13-08.bpo-35674.kamWqz.rst
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index ee3c5f0..ec72f9e 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1505,10 +1505,10 @@ class PosixGroupsTester(unittest.TestCase):
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,
@@ -1527,7 +1527,7 @@ class TestPosixSpawn(unittest.TestCase):
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))
@@ -1535,9 +1535,9 @@ class TestPosixSpawn(unittest.TestCase):
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:
@@ -1554,14 +1554,14 @@ class TestPosixSpawn(unittest.TestCase):
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,
@@ -1570,7 +1570,7 @@ class TestPosixSpawn(unittest.TestCase):
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,
@@ -1579,7 +1579,7 @@ class TestPosixSpawn(unittest.TestCase):
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,
@@ -1589,12 +1589,12 @@ class TestPosixSpawn(unittest.TestCase):
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,
@@ -1604,9 +1604,9 @@ class TestPosixSpawn(unittest.TestCase):
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()')
@@ -1615,7 +1615,7 @@ class TestPosixSpawn(unittest.TestCase):
import _testcapi, signal
_testcapi.raise_signal(signal.SIGUSR1)""")
- pid = posix.posix_spawn(
+ pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', code],
os.environ,
@@ -1625,18 +1625,18 @@ class TestPosixSpawn(unittest.TestCase):
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()')
@@ -1646,7 +1646,7 @@ class TestPosixSpawn(unittest.TestCase):
import _testcapi, signal
_testcapi.raise_signal(signal.SIGUSR1)""")
try:
- pid = posix.posix_spawn(
+ pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', code],
os.environ,
@@ -1662,17 +1662,17 @@ class TestPosixSpawn(unittest.TestCase):
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])
@unittest.skipUnless(hasattr(posix, 'sched_setscheduler'), "can't change scheduler")
def test_setscheduler_only_param(self):
@@ -1684,7 +1684,7 @@ class TestPosixSpawn(unittest.TestCase):
os.exit(101)
if os.sched_getparam(0).sched_priority != {priority}:
os.exit(102)""")
- pid = posix.posix_spawn(
+ pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', code],
os.environ,
@@ -1702,7 +1702,7 @@ class TestPosixSpawn(unittest.TestCase):
os.exit(101)
if os.sched_getparam(0).sched_priority != {priority}:
os.exit(102)""")
- pid = posix.posix_spawn(
+ pid = self.spawn_func(
sys.executable,
[sys.executable, '-c', code],
os.environ,
@@ -1716,40 +1716,40 @@ class TestPosixSpawn(unittest.TestCase):
(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
@@ -1764,8 +1764,8 @@ class TestPosixSpawn(unittest.TestCase):
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')
@@ -1782,8 +1782,8 @@ class TestPosixSpawn(unittest.TestCase):
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)
@@ -1800,16 +1800,64 @@ class TestPosixSpawn(unittest.TestCase):
(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()
diff --git a/Misc/NEWS.d/next/Library/2019-01-14-14-13-08.bpo-35674.kamWqz.rst b/Misc/NEWS.d/next/Library/2019-01-14-14-13-08.bpo-35674.kamWqz.rst
new file mode 100644
index 0000000..02d170e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-01-14-14-13-08.bpo-35674.kamWqz.rst
@@ -0,0 +1,2 @@
+Add a new :func:`os.posix_spawnp` function.
+Patch by Joannah Nanjekye.
\ No newline at end of file
diff --git a/Modules/clinic/posixmodule.c.h b/Modules/clinic/posixmodule.c.h
index 133abd7..0448aa5 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -1796,6 +1796,75 @@ exit:
#endif /* defined(HAVE_POSIX_SPAWN) */
+#if defined(HAVE_POSIX_SPAWNP)
+
+PyDoc_STRVAR(os_posix_spawnp__doc__,
+"posix_spawnp($module, path, argv, env, /, *, file_actions=(),\n"
+" setpgroup=None, resetids=False, setsigmask=(),\n"
+" setsigdef=(), scheduler=None)\n"
+"--\n"
+"\n"
+"Execute the program specified by path in a new process.\n"
+"\n"
+" path\n"
+" Path of executable file.\n"
+" argv\n"
+" Tuple or list of strings.\n"
+" env\n"
+" Dictionary of strings mapping to strings.\n"
+" file_actions\n"
+" 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_SPAWNP_METHODDEF \
+ {"posix_spawnp", (PyCFunction)(void(*)(void))os_posix_spawnp, METH_FASTCALL|METH_KEYWORDS, os_posix_spawnp__doc__},
+
+static PyObject *
+os_posix_spawnp_impl(PyObject *module, path_t *path, PyObject *argv,
+ PyObject *env, PyObject *file_actions,
+ PyObject *setpgroup, int resetids, PyObject *setsigmask,
+ PyObject *setsigdef, PyObject *scheduler);
+
+static PyObject *
+os_posix_spawnp(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames)
+{
+ PyObject *return_value = NULL;
+ static const char * const _keywords[] = {"", "", "", "file_actions", "setpgroup", "resetids", "setsigmask", "setsigdef", "scheduler", NULL};
+ static _PyArg_Parser _parser = {"O&OO|$OOiOOO:posix_spawnp", _keywords, 0};
+ path_t path = PATH_T_INITIALIZE("posix_spawnp", "path", 0, 0);
+ PyObject *argv;
+ PyObject *env;
+ PyObject *file_actions = NULL;
+ PyObject *setpgroup = NULL;
+ int resetids = 0;
+ PyObject *setsigmask = NULL;
+ PyObject *setsigdef = NULL;
+ PyObject *scheduler = NULL;
+
+ 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_spawnp_impl(module, &path, argv, env, file_actions, setpgroup, resetids, setsigmask, setsigdef, scheduler);
+
+exit:
+ /* Cleanup for path */
+ path_cleanup(&path);
+
+ return return_value;
+}
+
+#endif /* defined(HAVE_POSIX_SPAWNP) */
+
#if (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV))
PyDoc_STRVAR(os_spawnv__doc__,
@@ -6220,6 +6289,10 @@ exit:
#define OS_POSIX_SPAWN_METHODDEF
#endif /* !defined(OS_POSIX_SPAWN_METHODDEF) */
+#ifndef OS_POSIX_SPAWNP_METHODDEF
+ #define OS_POSIX_SPAWNP_METHODDEF
+#endif /* !defined(OS_POSIX_SPAWNP_METHODDEF) */
+
#ifndef OS_SPAWNV_METHODDEF
#define OS_SPAWNV_METHODDEF
#endif /* !defined(OS_SPAWNV_METHODDEF) */
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index dc6a22f..8d0e312 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5357,39 +5357,12 @@ fail:
return -1;
}
-/*[clinic input]
-
-os.posix_spawn
- path: path_t
- Path of executable file.
- argv: object
- Tuple or list of strings.
- env: object
- Dictionary of strings mapping to strings.
- /
- *
- file_actions: object(c_default='NULL') = ()
- 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,
- PyObject *setpgroup, int resetids, PyObject *setsigmask,
- PyObject *setsigdef, PyObject *scheduler)
-/*[clinic end generated code: output=45dfa4c515d09f2c input=2891c2f1d457e39b]*/
+py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *argv,
+ PyObject *env, PyObject *file_actions,
+ PyObject *setpgroup, int resetids, PyObject *setsigmask,
+ PyObject *setsigdef, PyObject *scheduler)
{
EXECV_CHAR **argvlist = NULL;
EXECV_CHAR **envlist = NULL;
@@ -5465,9 +5438,19 @@ os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
attrp = &attr;
_Py_BEGIN_SUPPRESS_IPH
- err_code = posix_spawn(&pid, path->narrow,
- file_actionsp, attrp, argvlist, envlist);
+#ifdef HAVE_POSIX_SPAWNP
+ if (use_posix_spawnp) {
+ err_code = posix_spawnp(&pid, path->narrow,
+ file_actionsp, attrp, argvlist, envlist);
+ }
+ else
+#endif /* HAVE_POSIX_SPAWNP */
+ {
+ err_code = posix_spawn(&pid, path->narrow,
+ file_actionsp, attrp, argvlist, envlist);
+ }
_Py_END_SUPPRESS_IPH
+
if (err_code) {
errno = err_code;
PyErr_SetFromErrnoWithFilenameObject(PyExc_OSError, path->object);
@@ -5491,7 +5474,90 @@ exit:
Py_XDECREF(temp_buffer);
return result;
}
-#endif /* HAVE_POSIX_SPAWN */
+
+
+/*[clinic input]
+
+os.posix_spawn
+ path: path_t
+ Path of executable file.
+ argv: object
+ Tuple or list of strings.
+ env: object
+ Dictionary of strings mapping to strings.
+ /
+ *
+ file_actions: object(c_default='NULL') = ()
+ 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,
+ PyObject *setpgroup, int resetids, PyObject *setsigmask,
+ PyObject *setsigdef, PyObject *scheduler)
+/*[clinic end generated code: output=45dfa4c515d09f2c input=2891c2f1d457e39b]*/
+{
+ return py_posix_spawn(0, module, path, argv, env, file_actions,
+ setpgroup, resetids, setsigmask, setsigdef,
+ scheduler);
+}
+ #endif /* HAVE_POSIX_SPAWN */
+
+
+
+#ifdef HAVE_POSIX_SPAWNP
+/*[clinic input]
+
+os.posix_spawnp
+ path: path_t
+ Path of executable file.
+ argv: object
+ Tuple or list of strings.
+ env: object
+ Dictionary of strings mapping to strings.
+ /
+ *
+ file_actions: object(c_default='NULL') = ()
+ 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_spawnp_impl(PyObject *module, path_t *path, PyObject *argv,
+ PyObject *env, PyObject *file_actions,
+ PyObject *setpgroup, int resetids, PyObject *setsigmask,
+ PyObject *setsigdef, PyObject *scheduler)
+/*[clinic end generated code: output=7955dc0edc82b8c3 input=b7576eb25b1ed9eb]*/
+{
+ return py_posix_spawn(1, module, path, argv, env, file_actions,
+ setpgroup, resetids, setsigmask, setsigdef,
+ scheduler);
+}
+#endif /* HAVE_POSIX_SPAWNP */
#if defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)
@@ -13056,6 +13122,7 @@ static PyMethodDef posix_methods[] = {
OS_GETPRIORITY_METHODDEF
OS_SETPRIORITY_METHODDEF
OS_POSIX_SPAWN_METHODDEF
+ OS_POSIX_SPAWNP_METHODDEF
#ifdef HAVE_READLINK
{"readlink", (PyCFunction)posix_readlink,
METH_VARARGS | METH_KEYWORDS,
diff --git a/configure b/configure
index 829dd69..820fa5e 100755
--- a/configure
+++ b/configure
@@ -11499,7 +11499,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
initgroups kill killpg lchown lockf linkat lstat lutimes mmap \
memrchr mbrtowc mkdirat mkfifo \
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
- posix_fallocate posix_fadvise posix_spawn pread preadv preadv2 \
+ posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
pthread_init pthread_kill putenv pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
setgid sethostname \
diff --git a/configure.ac b/configure.ac
index f1cc8e9..493d28f 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3583,7 +3583,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
initgroups kill killpg lchown lockf linkat lstat lutimes mmap \
memrchr mbrtowc mkdirat mkfifo \
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
- posix_fallocate posix_fadvise posix_spawn pread preadv preadv2 \
+ posix_fallocate posix_fadvise posix_spawn posix_spawnp pread preadv preadv2 \
pthread_init pthread_kill putenv pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
setgid sethostname \
diff --git a/pyconfig.h.in b/pyconfig.h.in
index ebab5ff..d6ab0b5 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -713,6 +713,9 @@
/* Define to 1 if you have the `posix_spawn' function. */
#undef HAVE_POSIX_SPAWN
+/* Define to 1 if you have the `posix_spawnp' function. */
+#undef HAVE_POSIX_SPAWNP
+
/* Define to 1 if you have the `pread' function. */
#undef HAVE_PREAD
--
2.23.0

View File

@ -0,0 +1,56 @@
From 27f95e26df82f2a9cfd3bdf517eeeb0449606538 Mon Sep 17 00:00:00 2001
From: "Gregory P. Smith" <greg@krypto.org>
Date: Sat, 24 Oct 2020 12:07:35 -0700
Subject: [PATCH] bpo-35823: Allow setsid() after vfork() on Linux. (GH-22945)
It should just be a syscall updating a couple of fields in the kernel side
process info. Confirming, in glibc is appears to be a shim for the setsid
syscall (based on not finding any code implementing anything special for it)
and in uclibc (*much* easier to read) it is clearly just a setsid syscall shim.
A breadcrumb _suggesting_ that it is not allowed on Darwin/macOS comes from
a commit in emacs: https://lists.gnu.org/archive/html/bug-gnu-emacs/2017-04/msg00297.html
but I don't have a way to verify if that is true or not.
As we are not supporting vfork on macOS today I just left a note in a comment.
Conflict:NA
Reference:https://github.com/python/cpython/commit/be3c3a0e468237430ad7d19a33c60d306199a7f2
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Modules/_posixsubprocess.c | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index 3caa8f0..5845445 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -37,6 +37,8 @@
#if defined(__linux__) && defined(HAVE_VFORK) && defined(HAVE_SIGNAL_H) && \
defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
+/* If this is ever expanded to non-Linux platforms, verify what calls are
+ * allowed after vfork(). Ex: setsid() may be disallowed on macOS? */
# include <signal.h>
# define VFORK_USABLE 1
#endif
@@ -699,7 +701,6 @@ do_fork_exec(char *const exec_array[],
#ifdef VFORK_USABLE
if (child_sigmask) {
/* These are checked by our caller; verify them in debug builds. */
- assert(!call_setsid);
assert(!call_setuid);
assert(!call_setgid);
assert(!call_setgroups);
@@ -969,7 +970,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
/* Use vfork() only if it's safe. See the comment above child_exec(). */
sigset_t old_sigs;
if (preexec_fn == Py_None &&
- !call_setuid && !call_setgid && !call_setgroups && !call_setsid) {
+ !call_setuid && !call_setgid && !call_setgroups) {
/* Block all signals to ensure that no signal handlers are run in the
* child process while it shares memory with us. Note that signals
* used internally by C libraries won't be blocked by
--
2.23.0

View File

@ -0,0 +1,68 @@
From a51a04b531a7f5b7ce4079dc17cc19b7e5cec2ed Mon Sep 17 00:00:00 2001
From: Alexey Izbyshev <izbyshev@ispras.ru>
Date: Sat, 24 Oct 2020 20:47:38 +0300
Subject: [PATCH] bpo-35823: subprocess: Fix handling of pthread_sigmask()
errors (GH-22944)
Using POSIX_CALL() is incorrect since pthread_sigmask() returns
the error number instead of setting errno.
Also handle failure of the first call to pthread_sigmask()
in the parent process, and explain why we don't handle failure
of the second call in a comment.
Conflict:NA
Reference:https://github.com/python/cpython/commit/473db47747bb8bc986d88ad81799bcbd88153ac5
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Modules/_posixsubprocess.c | 19 +++++++++++++++----
1 file changed, 15 insertions(+), 4 deletions(-)
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index a8fb851..3caa8f0 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -568,7 +568,9 @@ child_exec(char *const exec_array[],
#ifdef VFORK_USABLE
if (child_sigmask) {
reset_signal_handlers(child_sigmask);
- POSIX_CALL(pthread_sigmask(SIG_SETMASK, child_sigmask, NULL));
+ if ((errno = pthread_sigmask(SIG_SETMASK, child_sigmask, NULL))) {
+ goto error;
+ }
}
#endif
@@ -979,7 +981,11 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
*/
sigset_t all_sigs;
sigfillset(&all_sigs);
- pthread_sigmask(SIG_BLOCK, &all_sigs, &old_sigs);
+ if ((saved_errno = pthread_sigmask(SIG_BLOCK, &all_sigs, &old_sigs))) {
+ errno = saved_errno;
+ PyErr_SetFromErrno(PyExc_OSError);
+ goto cleanup;
+ }
old_sigmask = &old_sigs;
}
#endif
@@ -1006,8 +1012,13 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
* Note that in environments where vfork() is implemented as fork(),
* such as QEMU user-mode emulation, the parent won't be blocked,
* but it won't share the address space with the child,
- * so it's still safe to unblock the signals. */
- pthread_sigmask(SIG_SETMASK, old_sigmask, NULL);
+ * so it's still safe to unblock the signals.
+ *
+ * We don't handle errors here because this call can't fail
+ * if valid arguments are given, and because there is no good
+ * way for the caller to deal with a failure to restore
+ * the thread signal mask. */
+ (void) pthread_sigmask(SIG_SETMASK, old_sigmask, NULL);
}
#endif
--
2.23.0

View File

@ -0,0 +1,421 @@
From d227db75c726e3881c538de4723400ba740af69a Mon Sep 17 00:00:00 2001
From: Alexey Izbyshev <izbyshev@ispras.ru>
Date: Sat, 24 Oct 2020 03:47:01 +0300
Subject: [PATCH] bpo-35823: subprocess: Use vfork() instead of fork() on Linux
when safe (GH-11671)
* bpo-35823: subprocess: Use vfork() instead of fork() on Linux when safe
When used to run a new executable image, fork() is not a good choice
for process creation, especially if the parent has a large working set:
fork() needs to copy page tables, which is slow, and may fail on systems
where overcommit is disabled, despite that the child is not going to
touch most of its address space.
Currently, subprocess is capable of using posix_spawn() instead, which
normally provides much better performance. However, posix_spawn() does not
support many of child setup operations exposed by subprocess.Popen().
Most notably, it's not possible to express `close_fds=True`, which
happens to be the default, via posix_spawn(). As a result, most users
can't benefit from faster process creation, at least not without
changing their code.
However, Linux provides vfork() system call, which creates a new process
without copying the address space of the parent, and which is actually
used by C libraries to efficiently implement posix_spawn(). Due to sharing
of the address space and even the stack with the parent, extreme care
is required to use vfork(). At least the following restrictions must hold:
* No signal handlers must execute in the child process. Otherwise, they
might clobber memory shared with the parent, potentially confusing it.
* Any library function called after vfork() in the child must be
async-signal-safe (as for fork()), but it must also not interact with any
library state in a way that might break due to address space sharing
and/or lack of any preparations performed by libraries on normal fork().
POSIX.1 permits to call only execve() and _exit(), and later revisions
remove vfork() specification entirely. In practice, however, almost all
operations needed by subprocess.Popen() can be safely implemented on
Linux.
* Due to sharing of the stack with the parent, the child must be careful
not to clobber local variables that are alive across vfork() call.
Compilers are normally aware of this and take extra care with vfork()
(and setjmp(), which has a similar problem).
* In case the parent is privileged, special attention must be paid to vfork()
use, because sharing an address space across different privilege domains
is insecure[1].
This patch adds support for using vfork() instead of fork() on Linux
when it's possible to do safely given the above. In particular:
* vfork() is not used if credential switch is requested. The reverse case
(simple subprocess.Popen() but another application thread switches
credentials concurrently) is not possible for pure-Python apps because
subprocess.Popen() and functions like os.setuid() are mutually excluded
via GIL. We might also consider to add a way to opt-out of vfork() (and
posix_spawn() on platforms where it might be implemented via vfork()) in
a future PR.
* vfork() is not used if `preexec_fn != None`.
With this change, subprocess will still use posix_spawn() if possible, but
will fallback to vfork() on Linux in most cases, and, failing that,
to fork().
[1] https://ewontfix.com/7
Conflict:NA
Reference:https://github.com/python/cpython/commit/976da903a746a5455998e9ca45fbc4d3ad3479d8
Co-authored-by: Gregory P. Smith [Google LLC] <gps@google.com>
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
.../2020-10-16-07-45-35.bpo-35823.SNQo56.rst | 2 +
Modules/_posixsubprocess.c | 224 +++++++++++++++---
configure | 2 +-
configure.ac | 2 +-
pyconfig.h.in | 3 +
5 files changed, 204 insertions(+), 29 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2020-10-16-07-45-35.bpo-35823.SNQo56.rst
diff --git a/Misc/NEWS.d/next/Library/2020-10-16-07-45-35.bpo-35823.SNQo56.rst b/Misc/NEWS.d/next/Library/2020-10-16-07-45-35.bpo-35823.SNQo56.rst
new file mode 100644
index 0000000..cd428d3
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-10-16-07-45-35.bpo-35823.SNQo56.rst
@@ -0,0 +1,2 @@
+Use ``vfork()`` instead of ``fork()`` for :func:`subprocess.Popen` on Linux
+to improve performance in cases where it is deemed safe.
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index 9e5e7c6..a8fb851 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -35,6 +35,12 @@
# define SYS_getdents64 __NR_getdents64
#endif
+#if defined(__linux__) && defined(HAVE_VFORK) && defined(HAVE_SIGNAL_H) && \
+ defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
+# include <signal.h>
+# define VFORK_USABLE 1
+#endif
+
#if defined(__sun) && defined(__SVR4)
/* readdir64 is used to work around Solaris 9 bug 6395699. */
# define readdir readdir64
@@ -394,9 +400,53 @@ _close_open_fds_maybe_unsafe(long start_fd, PyObject* py_fds_to_keep)
#endif /* else NOT (defined(__linux__) && defined(HAVE_SYS_SYSCALL_H)) */
+#ifdef VFORK_USABLE
+/* Reset dispositions for all signals to SIG_DFL except for ignored
+ * signals. This way we ensure that no signal handlers can run
+ * after we unblock signals in a child created by vfork().
+ */
+static void
+reset_signal_handlers(const sigset_t *child_sigmask)
+{
+ struct sigaction sa_dfl = {.sa_handler = SIG_DFL};
+ for (int sig = 1; sig < _NSIG; sig++) {
+ /* Dispositions for SIGKILL and SIGSTOP can't be changed. */
+ if (sig == SIGKILL || sig == SIGSTOP) {
+ continue;
+ }
+
+ /* There is no need to reset the disposition of signals that will
+ * remain blocked across execve() since the kernel will do it. */
+ if (sigismember(child_sigmask, sig) == 1) {
+ continue;
+ }
+
+ struct sigaction sa;
+ /* C libraries usually return EINVAL for signals used
+ * internally (e.g. for thread cancellation), so simply
+ * skip errors here. */
+ if (sigaction(sig, NULL, &sa) == -1) {
+ continue;
+ }
+
+ /* void *h works as these fields are both pointer types already. */
+ void *h = (sa.sa_flags & SA_SIGINFO ? (void *)sa.sa_sigaction :
+ (void *)sa.sa_handler);
+ if (h == SIG_IGN || h == SIG_DFL) {
+ continue;
+ }
+
+ /* This call can't reasonably fail, but if it does, terminating
+ * the child seems to be too harsh, so ignore errors. */
+ (void) sigaction(sig, &sa_dfl, NULL);
+ }
+}
+#endif /* VFORK_USABLE */
+
+
/*
- * This function is code executed in the child process immediately after fork
- * to set things up and call exec().
+ * This function is code executed in the child process immediately after
+ * (v)fork to set things up and call exec().
*
* All of the code in this function must only use async-signal-safe functions,
* listed at `man 7 signal` or
@@ -404,8 +454,28 @@ _close_open_fds_maybe_unsafe(long start_fd, PyObject* py_fds_to_keep)
*
* This restriction is documented at
* http://www.opengroup.org/onlinepubs/009695399/functions/fork.html.
+ *
+ * If this function is called after vfork(), even more care must be taken.
+ * The lack of preparations that C libraries normally take on fork(),
+ * as well as sharing the address space with the parent, might make even
+ * async-signal-safe functions vfork-unsafe. In particular, on Linux,
+ * set*id() and setgroups() library functions must not be called, since
+ * they have to interact with the library-level thread list and send
+ * library-internal signals to implement per-process credentials semantics
+ * required by POSIX but not supported natively on Linux. Another reason to
+ * avoid this family of functions is that sharing an address space between
+ * processes running with different privileges is inherently insecure.
+ * See bpo-35823 for further discussion and references.
+ *
+ * In some C libraries, setrlimit() has the same thread list/signalling
+ * behavior since resource limits were per-thread attributes before
+ * Linux 2.6.10. Musl, as of 1.2.1, is known to have this issue
+ * (https://www.openwall.com/lists/musl/2020/10/15/6).
+ *
+ * If vfork-unsafe functionality is desired after vfork(), consider using
+ * syscall() to obtain it.
*/
-static void
+_Py_NO_INLINE static void
child_exec(char *const exec_array[],
char *const argv[],
char *const envp[],
@@ -419,6 +489,7 @@ child_exec(char *const exec_array[],
int call_setgid, gid_t gid,
int call_setgroups, size_t groups_size, const gid_t *groups,
int call_setuid, uid_t uid, int child_umask,
+ const void *child_sigmask,
PyObject *py_fds_to_keep,
PyObject *preexec_fn,
PyObject *preexec_fn_args_tuple)
@@ -494,6 +565,13 @@ child_exec(char *const exec_array[],
if (restore_signals)
_Py_RestoreSignals();
+#ifdef VFORK_USABLE
+ if (child_sigmask) {
+ reset_signal_handlers(child_sigmask);
+ POSIX_CALL(pthread_sigmask(SIG_SETMASK, child_sigmask, NULL));
+ }
+#endif
+
#ifdef HAVE_SETSID
if (call_setsid)
POSIX_CALL(setsid());
@@ -586,6 +664,81 @@ error:
}
+/* The main purpose of this wrapper function is to isolate vfork() from both
+ * subprocess_fork_exec() and child_exec(). A child process created via
+ * vfork() executes on the same stack as the parent process while the latter is
+ * suspended, so this function should not be inlined to avoid compiler bugs
+ * that might clobber data needed by the parent later. Additionally,
+ * child_exec() should not be inlined to avoid spurious -Wclobber warnings from
+ * GCC (see bpo-35823).
+ */
+_Py_NO_INLINE static pid_t
+do_fork_exec(char *const exec_array[],
+ char *const argv[],
+ char *const envp[],
+ const char *cwd,
+ int p2cread, int p2cwrite,
+ int c2pread, int c2pwrite,
+ int errread, int errwrite,
+ int errpipe_read, int errpipe_write,
+ int close_fds, int restore_signals,
+ int call_setsid,
+ int call_setgid, gid_t gid,
+ int call_setgroups, size_t groups_size, const gid_t *groups,
+ int call_setuid, uid_t uid, int child_umask,
+ const void *child_sigmask,
+ PyObject *py_fds_to_keep,
+ PyObject *preexec_fn,
+ PyObject *preexec_fn_args_tuple)
+{
+
+ pid_t pid;
+
+#ifdef VFORK_USABLE
+ if (child_sigmask) {
+ /* These are checked by our caller; verify them in debug builds. */
+ assert(!call_setsid);
+ assert(!call_setuid);
+ assert(!call_setgid);
+ assert(!call_setgroups);
+ assert(preexec_fn == Py_None);
+
+ pid = vfork();
+ } else
+#endif
+ {
+ pid = fork();
+ }
+
+ if (pid != 0) {
+ return pid;
+ }
+
+ /* Child process.
+ * See the comment above child_exec() for restrictions imposed on
+ * the code below.
+ */
+
+ if (preexec_fn != Py_None) {
+ /* We'll be calling back into Python later so we need to do this.
+ * This call may not be async-signal-safe but neither is calling
+ * back into Python. The user asked us to use hope as a strategy
+ * to avoid deadlock... */
+ PyOS_AfterFork_Child();
+ }
+
+ child_exec(exec_array, argv, envp, cwd,
+ p2cread, p2cwrite, c2pread, c2pwrite,
+ errread, errwrite, errpipe_read, errpipe_write,
+ close_fds, restore_signals, call_setsid,
+ call_setgid, gid, call_setgroups, groups_size, groups,
+ call_setuid, uid, child_umask, child_sigmask,
+ py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
+ _exit(255);
+ return 0; /* Dead code to avoid a potential compiler warning. */
+}
+
+
static PyObject *
subprocess_fork_exec(PyObject* self, PyObject *args)
{
@@ -808,39 +961,56 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
need_after_fork = 1;
}
- pid = fork();
- if (pid == 0) {
- /* Child process */
- /*
- * Code from here to _exit() must only use async-signal-safe functions,
- * listed at `man 7 signal` or
- * http://www.opengroup.org/onlinepubs/009695399/functions/xsh_chap02_04.html.
+ /* NOTE: When old_sigmask is non-NULL, do_fork_exec() may use vfork(). */
+ const void *old_sigmask = NULL;
+#ifdef VFORK_USABLE
+ /* Use vfork() only if it's safe. See the comment above child_exec(). */
+ sigset_t old_sigs;
+ if (preexec_fn == Py_None &&
+ !call_setuid && !call_setgid && !call_setgroups && !call_setsid) {
+ /* Block all signals to ensure that no signal handlers are run in the
+ * child process while it shares memory with us. Note that signals
+ * used internally by C libraries won't be blocked by
+ * pthread_sigmask(), but signal handlers installed by C libraries
+ * normally service only signals originating from *within the process*,
+ * so it should be sufficient to consider any library function that
+ * might send such a signal to be vfork-unsafe and do not call it in
+ * the child.
*/
+ sigset_t all_sigs;
+ sigfillset(&all_sigs);
+ pthread_sigmask(SIG_BLOCK, &all_sigs, &old_sigs);
+ old_sigmask = &old_sigs;
+ }
+#endif
- if (preexec_fn != Py_None) {
- /* We'll be calling back into Python later so we need to do this.
- * This call may not be async-signal-safe but neither is calling
- * back into Python. The user asked us to use hope as a strategy
- * to avoid deadlock... */
- PyOS_AfterFork_Child();
- }
+ pid = do_fork_exec(exec_array, argv, envp, cwd,
+ p2cread, p2cwrite, c2pread, c2pwrite,
+ errread, errwrite, errpipe_read, errpipe_write,
+ close_fds, restore_signals, call_setsid,
+ call_setgid, gid, call_setgroups, num_groups, groups,
+ call_setuid, uid, child_umask, old_sigmask,
+ py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
- child_exec(exec_array, argv, envp, cwd,
- p2cread, p2cwrite, c2pread, c2pwrite,
- errread, errwrite, errpipe_read, errpipe_write,
- close_fds, restore_signals, call_setsid,
- call_setgid, gid, call_setgroups, num_groups, groups,
- call_setuid, uid, child_umask,
- py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
- _exit(255);
- return NULL; /* Dead code to avoid a potential compiler warning. */
- }
/* Parent (original) process */
if (pid == -1) {
/* Capture errno for the exception. */
saved_errno = errno;
}
+#ifdef VFORK_USABLE
+ if (old_sigmask) {
+ /* vfork() semantics guarantees that the parent is blocked
+ * until the child performs _exit() or execve(), so it is safe
+ * to unblock signals once we're here.
+ * Note that in environments where vfork() is implemented as fork(),
+ * such as QEMU user-mode emulation, the parent won't be blocked,
+ * but it won't share the address space with the child,
+ * so it's still safe to unblock the signals. */
+ pthread_sigmask(SIG_SETMASK, old_sigmask, NULL);
+ }
+#endif
+
Py_XDECREF(cwd_obj2);
if (need_after_fork)
diff --git a/configure b/configure
index 93615b6..35b4d8a 100755
--- a/configure
+++ b/configure
@@ -11509,7 +11509,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
sigaction sigaltstack sigfillset siginterrupt sigpending sigrelse \
sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
- truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
+ truncate uname unlinkat unsetenv utimensat utimes vfork waitid waitpid wait3 wait4 \
wcscoll wcsftime wcsxfrm wmemcmp writev _getpty
do :
as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`
diff --git a/configure.ac b/configure.ac
index c5ec7a9..c2e9fbb 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3593,7 +3593,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
sigaction sigaltstack sigfillset siginterrupt sigpending sigrelse \
sigtimedwait sigwait sigwaitinfo snprintf strftime strlcpy symlinkat sync \
sysconf tcgetpgrp tcsetpgrp tempnam timegm times tmpfile tmpnam tmpnam_r \
- truncate uname unlinkat unsetenv utimensat utimes waitid waitpid wait3 wait4 \
+ truncate uname unlinkat unsetenv utimensat utimes vfork waitid waitpid wait3 wait4 \
wcscoll wcsftime wcsxfrm wmemcmp writev _getpty)
# Force lchmod off for Linux. Linux disallows changing the mode of symbolic
diff --git a/pyconfig.h.in b/pyconfig.h.in
index 55f3fca..5ef1a34 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -1233,6 +1233,9 @@
/* Define to 1 if you have the <uuid/uuid.h> header file. */
#undef HAVE_UUID_UUID_H
+/* Define to 1 if you have the `vfork' function. */
+#undef HAVE_VFORK
+
/* Define to 1 if you have the `wait3' function. */
#undef HAVE_WAIT3
--
2.23.0

View File

@ -0,0 +1,739 @@
From df76b86c86a24a0b7faefac03e22d702359eb6b5 Mon Sep 17 00:00:00 2001
From: Patrick McLean <47801044+patrick-mclean@users.noreply.github.com>
Date: Thu, 12 Sep 2019 10:15:44 -0700
Subject: [PATCH] bpo-36046: Add user and group parameters to subprocess
(GH-11950)
* subprocess: Add user, group and extra_groups paremeters to subprocess.Popen
This adds a `user` parameter to the Popen constructor that will call
setreuid() in the child before calling exec(). This allows processes
running as root to safely drop privileges before running the subprocess
without having to use a preexec_fn.
This also adds a `group` parameter that will call setregid() in
the child process before calling exec().
Finally an `extra_groups` parameter was added that will call
setgroups() to set the supplimental groups.
Conflict:NA
Reference:https://github.com/python/cpython/commit/2b2ead74382513d0bb9ef34504e283a71e6a706f
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Doc/library/subprocess.rst | 32 +++-
Lib/multiprocessing/util.py | 2 +-
Lib/subprocess.py | 106 +++++++++++-
Lib/test/test_capi.py | 6 +-
Lib/test/test_subprocess.py | 163 +++++++++++++++++-
.../2019-02-19-17-32-45.bpo-36046.fX9OPj.rst | 2 +
Modules/_posixsubprocess.c | 136 ++++++++++++++-
7 files changed, 428 insertions(+), 19 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-02-19-17-32-45.bpo-36046.fX9OPj.rst
diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
index 9f2a056..3461297 100644
--- a/Doc/library/subprocess.rst
+++ b/Doc/library/subprocess.rst
@@ -339,8 +339,9 @@ functions.
stderr=None, preexec_fn=None, close_fds=True, shell=False, \
cwd=None, env=None, universal_newlines=None, \
startupinfo=None, creationflags=0, restore_signals=True, \
- start_new_session=False, pass_fds=(), *, \
- encoding=None, errors=None, text=None)
+ start_new_session=False, pass_fds=(), *, group=None, \
+ extra_groups=None, user=None, encoding=None, errors=None, \
+ text=None)
Execute a child program in a new process. On POSIX, the class uses
:meth:`os.execvp`-like behavior to execute the child program. On Windows,
@@ -520,6 +521,33 @@ functions.
.. versionchanged:: 3.2
*start_new_session* was added.
+ If *group* is not ``None``, the setregid() system call will be made in the
+ child process prior to the execution of the subprocess. If the provided
+ value is a string, it will be looked up via :func:`grp.getgrnam()` and
+ the value in ``gr_gid`` will be used. If the value is an integer, it
+ will be passed verbatim. (POSIX only)
+
+ .. availability:: POSIX
+ .. versionadded:: 3.9
+
+ If *extra_groups* is not ``None``, the setgroups() system call will be
+ made in the child process prior to the execution of the subprocess.
+ Strings provided in *extra_groups* will be looked up via
+ :func:`grp.getgrnam()` and the values in ``gr_gid`` will be used.
+ Integer values will be passed verbatim. (POSIX only)
+
+ .. availability:: POSIX
+ .. versionadded:: 3.9
+
+ If *user* is not ``None``, the setreuid() system call will be made in the
+ child process prior to the execution of the subprocess. If the provided
+ value is a string, it will be looked up via :func:`pwd.getpwnam()` and
+ the value in ``pw_uid`` will be used. If the value is an integer, it will
+ be passed verbatim. (POSIX only)
+
+ .. availability:: POSIX
+ .. versionadded:: 3.9
+
If *env* is not ``None``, it must be a mapping that defines the environment
variables for the new process; these are used instead of the default
behavior of inheriting the current process' environment.
diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py
index 327fe42..c7e9b85 100644
--- a/Lib/multiprocessing/util.py
+++ b/Lib/multiprocessing/util.py
@@ -452,7 +452,7 @@ def spawnv_passfds(path, args, passfds):
return _posixsubprocess.fork_exec(
args, [os.fsencode(path)], True, passfds, None, None,
-1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
- False, False, None)
+ False, False, None, None, None, None)
finally:
os.close(errpipe_read)
os.close(errpipe_write)
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 332c19f..12ca9ef 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -54,6 +54,15 @@ import errno
import contextlib
from time import monotonic as _time
+try:
+ import pwd
+except ImportError:
+ pwd = None
+try:
+ import grp
+except ImportError:
+ grp = None
+
# Exception classes used by this module.
class SubprocessError(Exception): pass
@@ -720,6 +729,12 @@ class Popen(object):
start_new_session (POSIX only)
+ group (POSIX only)
+
+ extra_groups (POSIX only)
+
+ user (POSIX only)
+
pass_fds (POSIX only)
encoding and errors: Text mode encoding and error handling to use for
@@ -736,7 +751,8 @@ class Popen(object):
shell=False, cwd=None, env=None, universal_newlines=None,
startupinfo=None, creationflags=0,
restore_signals=True, start_new_session=False,
- pass_fds=(), *, encoding=None, errors=None, text=None):
+ pass_fds=(), *, user=None, group=None, extra_groups=None,
+ encoding=None, errors=None, text=None):
"""Create new Popen instance."""
_cleanup()
# Held while anything is calling waitpid before returncode has been
@@ -825,6 +841,78 @@ class Popen(object):
self._closed_child_pipe_fds = False
+ gid = None
+ if group is not None:
+ if not hasattr(os, 'setregid'):
+ raise ValueError("The 'group' parameter is not supported on the "
+ "current platform")
+
+ elif isinstance(group, str):
+ if grp is None:
+ raise ValueError("The group parameter cannot be a string "
+ "on systems without the grp module")
+
+ gid = grp.getgrnam(group).gr_gid
+ elif isinstance(group, int):
+ gid = group
+ else:
+ raise TypeError("Group must be a string or an integer, not {}"
+ .format(type(group)))
+
+ if gid < 0:
+ raise ValueError(f"Group ID cannot be negative, got {gid}")
+
+ gids = None
+ if extra_groups is not None:
+ if not hasattr(os, 'setgroups'):
+ raise ValueError("The 'extra_groups' parameter is not "
+ "supported on the current platform")
+
+ elif isinstance(extra_groups, str):
+ raise ValueError("Groups must be a list, not a string")
+
+ gids = []
+ for extra_group in extra_groups:
+ if isinstance(extra_group, str):
+ if grp is None:
+ raise ValueError("Items in extra_groups cannot be "
+ "strings on systems without the "
+ "grp module")
+
+ gids.append(grp.getgrnam(extra_group).gr_gid)
+ elif isinstance(extra_group, int):
+ gids.append(extra_group)
+ else:
+ raise TypeError("Items in extra_groups must be a string "
+ "or integer, not {}"
+ .format(type(extra_group)))
+
+ # make sure that the gids are all positive here so we can do less
+ # checking in the C code
+ for gid_check in gids:
+ if gid_check < 0:
+ raise ValueError(f"Group ID cannot be negative, got {gid_check}")
+
+ uid = None
+ if user is not None:
+ if not hasattr(os, 'setreuid'):
+ raise ValueError("The 'user' parameter is not supported on "
+ "the current platform")
+
+ elif isinstance(user, str):
+ if pwd is None:
+ raise ValueError("The user parameter cannot be a string "
+ "on systems without the pwd module")
+
+ uid = pwd.getpwnam(user).pw_uid
+ elif isinstance(user, int):
+ uid = user
+ else:
+ raise TypeError("User must be a string or an integer")
+
+ if uid < 0:
+ raise ValueError(f"User ID cannot be negative, got {uid}")
+
try:
if p2cwrite != -1:
self.stdin = io.open(p2cwrite, 'wb', bufsize)
@@ -849,7 +937,9 @@ class Popen(object):
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite,
- restore_signals, start_new_session)
+ restore_signals,
+ gid, gids, uid,
+ start_new_session)
except:
# Cleanup if the child failed starting.
for f in filter(None, (self.stdin, self.stdout, self.stderr)):
@@ -1219,7 +1309,9 @@ class Popen(object):
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite,
- unused_restore_signals, unused_start_new_session):
+ unused_restore_signals,
+ unused_gid, unused_gids, unused_uid,
+ unused_start_new_session):
"""Execute program (MS Windows version)"""
assert not pass_fds, "pass_fds not supported on Windows."
@@ -1526,7 +1618,9 @@ class Popen(object):
p2cread, p2cwrite,
c2pread, c2pwrite,
errread, errwrite,
- restore_signals, start_new_session):
+ restore_signals,
+ gid, gids, uid,
+ start_new_session):
"""Execute program (POSIX version)"""
if isinstance(args, (str, bytes)):
@@ -1607,7 +1701,9 @@ class Popen(object):
p2cread, p2cwrite, c2pread, c2pwrite,
errread, errwrite,
errpipe_read, errpipe_write,
- restore_signals, start_new_session, preexec_fn)
+ restore_signals, start_new_session,
+ gid, gids, uid,
+ preexec_fn)
self._child_created = True
finally:
# be sure the FD is closed no matter what
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index 3ed2263..dcff095 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -96,7 +96,7 @@ class CAPITest(unittest.TestCase):
def __len__(self):
return 1
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
- 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17)
+ 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
# Issue #15736: overflow in _PySequence_BytesToCharpArray()
class Z(object):
def __len__(self):
@@ -104,7 +104,7 @@ class CAPITest(unittest.TestCase):
def __getitem__(self, i):
return b'x'
self.assertRaises(MemoryError, _posixsubprocess.fork_exec,
- 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17)
+ 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
@unittest.skipUnless(_posixsubprocess, '_posixsubprocess required for this test.')
def test_subprocess_fork_exec(self):
@@ -114,7 +114,7 @@ class CAPITest(unittest.TestCase):
# Issue #15738: crash in subprocess_fork_exec()
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
- Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17)
+ Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
@unittest.skipIf(MISSING_C_DOCSTRINGS,
"Signature information for builtins requires docstrings")
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index eebd348..6c2fd61 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -19,6 +19,7 @@ import shutil
import threading
import gc
import textwrap
+import json
from test.support import FakePath
try:
@@ -33,6 +34,15 @@ try:
except ImportError:
_testcapi = None
+try:
+ import pwd
+except ImportError:
+ pwd = None
+try:
+ import grp
+except ImportError:
+ grp = None
+
if support.PGO:
raise unittest.SkipTest("test is not helpful for PGO")
@@ -1681,6 +1691,139 @@ class POSIXProcessTestCase(BaseTestCase):
child_sid = int(output)
self.assertNotEqual(parent_sid, child_sid)
+ @unittest.skipUnless(hasattr(os, 'setreuid'), 'no setreuid on platform')
+ def test_user(self):
+ # For code coverage of the user parameter. We don't care if we get an
+ # EPERM error from it depending on the test execution environment, that
+ # still indicates that it was called.
+
+ uid = os.geteuid()
+ test_users = [65534 if uid != 65534 else 65533, uid]
+ name_uid = "nobody" if sys.platform != 'darwin' else "unknown"
+
+ if pwd is not None:
+ test_users.append(name_uid)
+
+ for user in test_users:
+ with self.subTest(user=user):
+ try:
+ output = subprocess.check_output(
+ [sys.executable, "-c",
+ "import os; print(os.getuid())"],
+ user=user)
+ except OSError as e:
+ if e.errno != errno.EPERM:
+ raise
+ else:
+ if isinstance(user, str):
+ user_uid = pwd.getpwnam(user).pw_uid
+ else:
+ user_uid = user
+ child_user = int(output)
+ self.assertEqual(child_user, user_uid)
+
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"], user=-1)
+
+ if pwd is None:
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"], user=name_uid)
+
+ @unittest.skipIf(hasattr(os, 'setreuid'), 'setreuid() available on platform')
+ def test_user_error(self):
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"], user=65535)
+
+ @unittest.skipUnless(hasattr(os, 'setregid'), 'no setregid() on platform')
+ def test_group(self):
+ gid = os.getegid()
+ group_list = [65534 if gid != 65534 else 65533]
+ name_group = "nogroup" if sys.platform != 'darwin' else "staff"
+
+ if grp is not None:
+ group_list.append(name_group)
+
+ for group in group_list + [gid]:
+ with self.subTest(group=group):
+ try:
+ output = subprocess.check_output(
+ [sys.executable, "-c",
+ "import os; print(os.getgid())"],
+ group=group)
+ except OSError as e:
+ if e.errno != errno.EPERM:
+ raise
+ else:
+ if isinstance(group, str):
+ group_gid = grp.getgrnam(group).gr_gid
+ else:
+ group_gid = group
+
+ child_group = int(output)
+ self.assertEqual(child_group, group_gid)
+
+ # make sure we bomb on negative values
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"], group=-1)
+
+ if grp is None:
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"], group=name_group)
+
+ @unittest.skipIf(hasattr(os, 'setregid'), 'setregid() available on platform')
+ def test_group_error(self):
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"], group=65535)
+
+ @unittest.skipUnless(hasattr(os, 'setgroups'), 'no setgroups() on platform')
+ def test_extra_groups(self):
+ gid = os.getegid()
+ group_list = [65534 if gid != 65534 else 65533]
+ name_group = "nogroup" if sys.platform != 'darwin' else "staff"
+ perm_error = False
+
+ if grp is not None:
+ group_list.append(name_group)
+
+ try:
+ output = subprocess.check_output(
+ [sys.executable, "-c",
+ "import os, sys, json; json.dump(os.getgroups(), sys.stdout)"],
+ extra_groups=group_list)
+ except OSError as ex:
+ if ex.errno != errno.EPERM:
+ raise
+ perm_error = True
+
+ else:
+ parent_groups = os.getgroups()
+ child_groups = json.loads(output)
+
+ if grp is not None:
+ desired_gids = [grp.getgrnam(g).gr_gid if isinstance(g, str) else g
+ for g in group_list]
+ else:
+ desired_gids = group_list
+
+ if perm_error:
+ self.assertEqual(set(child_groups), set(parent_groups))
+ else:
+ self.assertEqual(set(desired_gids), set(child_groups))
+
+ # make sure we bomb on negative values
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"], extra_groups=[-1])
+
+ if grp is None:
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"],
+ extra_groups=[name_group])
+
+ @unittest.skipIf(hasattr(os, 'setgroups'), 'setgroups() available on platform')
+ def test_extra_groups_error(self):
+ with self.assertRaises(ValueError):
+ subprocess.check_call([sys.executable, "-c", "pass"], extra_groups=[])
+
def test_run_abort(self):
# returncode handles signal termination
with support.SuppressCrashReport():
@@ -2730,13 +2873,23 @@ class POSIXProcessTestCase(BaseTestCase):
([b"arg"], [b"exe"], 123, [b"env"]),
([b"arg"], [b"exe"], None, 123),
):
- with self.assertRaises(TypeError):
+ with self.assertRaises(TypeError) as err:
_posixsubprocess.fork_exec(
args, exe_list,
True, (), cwd, env_list,
-1, -1, -1, -1,
1, 2, 3, 4,
- True, True, func)
+ True, True,
+ False, [], 0,
+ func)
+ # Attempt to prevent
+ # "TypeError: fork_exec() takes exactly N arguments (M given)"
+ # from passing the test. More refactoring to have us start
+ # with a valid *args list, confirm a good call with that works
+ # before mutating it in various ways to ensure that bad calls
+ # with individual arg type errors raise a typeerror would be
+ # ideal. Saving that for a future PR...
+ self.assertNotIn('takes exactly', str(err.exception))
finally:
if not gc_enabled:
gc.disable()
@@ -2775,7 +2928,9 @@ class POSIXProcessTestCase(BaseTestCase):
True, fds_to_keep, None, [b"env"],
-1, -1, -1, -1,
1, 2, 3, 4,
- True, True, None)
+ True, True,
+ None, None, None,
+ None)
self.assertIn('fds_to_keep', str(c.exception))
finally:
if not gc_enabled:
@@ -3198,7 +3353,7 @@ class MiscTests(unittest.TestCase):
def test__all__(self):
"""Ensure that __all__ is populated properly."""
- intentionally_excluded = {"list2cmdline", "Handle"}
+ intentionally_excluded = {"list2cmdline", "Handle", "pwd", "grp"}
exported = set(subprocess.__all__)
possible_exports = set()
import types
diff --git a/Misc/NEWS.d/next/Library/2019-02-19-17-32-45.bpo-36046.fX9OPj.rst b/Misc/NEWS.d/next/Library/2019-02-19-17-32-45.bpo-36046.fX9OPj.rst
new file mode 100644
index 0000000..5e809d6
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-02-19-17-32-45.bpo-36046.fX9OPj.rst
@@ -0,0 +1,2 @@
+Added ``user``, ``group`` and ``extra_groups`` parameters to the
+subprocess.Popen constructor. Patch by Patrick McLean.
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index 3cf0683..caa8d7a 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -20,6 +20,11 @@
#ifdef HAVE_DIRENT_H
#include <dirent.h>
#endif
+#ifdef HAVE_GRP_H
+#include <grp.h>
+#endif /* HAVE_GRP_H */
+
+#include "posixmodule.h"
#ifdef _Py_MEMORY_SANITIZER
# include <sanitizer/msan_interface.h>
@@ -47,6 +52,12 @@
# define FD_DIR "/proc/self/fd"
#endif
+#ifdef NGROUPS_MAX
+#define MAX_GROUPS NGROUPS_MAX
+#else
+#define MAX_GROUPS 64
+#endif
+
#define POSIX_CALL(call) do { if ((call) == -1) goto error; } while (0)
@@ -405,6 +416,9 @@ child_exec(char *const exec_array[],
int errpipe_read, int errpipe_write,
int close_fds, int restore_signals,
int call_setsid,
+ int call_setgid, gid_t gid,
+ int call_setgroups, size_t groups_size, const gid_t *groups,
+ int call_setuid, uid_t uid,
PyObject *py_fds_to_keep,
PyObject *preexec_fn,
PyObject *preexec_fn_args_tuple)
@@ -482,6 +496,22 @@ child_exec(char *const exec_array[],
POSIX_CALL(setsid());
#endif
+#ifdef HAVE_SETGROUPS
+ if (call_setgroups)
+ POSIX_CALL(setgroups(groups_size, groups));
+#endif /* HAVE_SETGROUPS */
+
+#ifdef HAVE_SETREGID
+ if (call_setgid)
+ POSIX_CALL(setregid(gid, gid));
+#endif /* HAVE_SETREGID */
+
+#ifdef HAVE_SETREUID
+ if (call_setuid)
+ POSIX_CALL(setreuid(uid, uid));
+#endif /* HAVE_SETREUID */
+
+
reached_preexec = 1;
if (preexec_fn != Py_None && preexec_fn_args_tuple) {
/* This is where the user has asked us to deadlock their program. */
@@ -561,26 +591,33 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
PyObject *env_list, *preexec_fn;
PyObject *process_args, *converted_args = NULL, *fast_args = NULL;
PyObject *preexec_fn_args_tuple = NULL;
+ PyObject *groups_list;
+ PyObject *uid_object, *gid_object;
int p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite;
int errpipe_read, errpipe_write, close_fds, restore_signals;
int call_setsid;
+ int call_setgid = 0, call_setgroups = 0, call_setuid = 0;
+ uid_t uid;
+ gid_t gid, *groups = NULL;
PyObject *cwd_obj, *cwd_obj2;
const char *cwd;
pid_t pid;
int need_to_reenable_gc = 0;
char *const *exec_array, *const *argv = NULL, *const *envp = NULL;
- Py_ssize_t arg_num;
+ Py_ssize_t arg_num, num_groups = 0;
int need_after_fork = 0;
int saved_errno = 0;
if (!PyArg_ParseTuple(
- args, "OOpO!OOiiiiiiiiiiO:fork_exec",
+ args, "OOpO!OOiiiiiiiiiiOOOO:fork_exec",
&process_args, &executable_list,
&close_fds, &PyTuple_Type, &py_fds_to_keep,
&cwd_obj, &env_list,
&p2cread, &p2cwrite, &c2pread, &c2pwrite,
&errread, &errwrite, &errpipe_read, &errpipe_write,
- &restore_signals, &call_setsid, &preexec_fn))
+ &restore_signals, &call_setsid,
+ &gid_object, &groups_list, &uid_object,
+ &preexec_fn))
return NULL;
if (close_fds && errpipe_write < 3) { /* precondition */
@@ -672,6 +709,90 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
cwd_obj2 = NULL;
}
+ if (groups_list != Py_None) {
+#ifdef HAVE_SETGROUPS
+ Py_ssize_t i;
+ unsigned long gid;
+
+ if (!PyList_Check(groups_list)) {
+ PyErr_SetString(PyExc_TypeError,
+ "setgroups argument must be a list");
+ goto cleanup;
+ }
+ num_groups = PySequence_Size(groups_list);
+
+ if (num_groups < 0)
+ goto cleanup;
+
+ if (num_groups > MAX_GROUPS) {
+ PyErr_SetString(PyExc_ValueError, "too many groups");
+ goto cleanup;
+ }
+
+ if ((groups = PyMem_RawMalloc(num_groups * sizeof(gid_t))) == NULL) {
+ PyErr_SetString(PyExc_MemoryError,
+ "failed to allocate memory for group list");
+ goto cleanup;
+ }
+
+ for (i = 0; i < num_groups; i++) {
+ PyObject *elem;
+ elem = PySequence_GetItem(groups_list, i);
+ if (!elem)
+ goto cleanup;
+ if (!PyLong_Check(elem)) {
+ PyErr_SetString(PyExc_TypeError,
+ "groups must be integers");
+ Py_DECREF(elem);
+ goto cleanup;
+ } else {
+ /* In posixmodule.c UnsignedLong is used as a fallback value
+ * if the value provided does not fit in a Long. Since we are
+ * already doing the bounds checking on the Python side, we
+ * can go directly to an UnsignedLong here. */
+ if (!_Py_Gid_Converter(elem, &gid)) {
+ Py_DECREF(elem);
+ PyErr_SetString(PyExc_ValueError, "invalid group id");
+ goto cleanup;
+ }
+ groups[i] = gid;
+ }
+ Py_DECREF(elem);
+ }
+ call_setgroups = 1;
+
+#else /* HAVE_SETGROUPS */
+ PyErr_BadInternalCall();
+ goto cleanup;
+#endif /* HAVE_SETGROUPS */
+ }
+
+ if (gid_object != Py_None) {
+#ifdef HAVE_SETREGID
+ if (!_Py_Gid_Converter(gid_object, &gid))
+ goto cleanup;
+
+ call_setgid = 1;
+
+#else /* HAVE_SETREGID */
+ PyErr_BadInternalCall();
+ goto cleanup;
+#endif /* HAVE_SETREUID */
+ }
+
+ if (uid_object != Py_None) {
+#ifdef HAVE_SETREUID
+ if (!_Py_Uid_Converter(uid_object, &uid))
+ goto cleanup;
+
+ call_setuid = 1;
+
+#else /* HAVE_SETREUID */
+ PyErr_BadInternalCall();
+ goto cleanup;
+#endif /* HAVE_SETREUID */
+ }
+
/* This must be the last thing done before fork() because we do not
* want to call PyOS_BeforeFork() if there is any chance of another
* error leading to the cleanup: code without calling fork(). */
@@ -704,6 +825,8 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
p2cread, p2cwrite, c2pread, c2pwrite,
errread, errwrite, errpipe_read, errpipe_write,
close_fds, restore_signals, call_setsid,
+ call_setgid, gid, call_setgroups, num_groups, groups,
+ call_setuid, uid,
py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
_exit(255);
return NULL; /* Dead code to avoid a potential compiler warning. */
@@ -748,6 +871,8 @@ cleanup:
_Py_FreeCharPArray(argv);
if (exec_array)
_Py_FreeCharPArray(exec_array);
+
+ PyMem_RawFree(groups);
Py_XDECREF(converted_args);
Py_XDECREF(fast_args);
Py_XDECREF(preexec_fn_args_tuple);
@@ -761,7 +886,10 @@ PyDoc_STRVAR(subprocess_fork_exec_doc,
"fork_exec(args, executable_list, close_fds, cwd, env,\n\
p2cread, p2cwrite, c2pread, c2pwrite,\n\
errread, errwrite, errpipe_read, errpipe_write,\n\
- restore_signals, call_setsid, preexec_fn)\n\
+ restore_signals, call_setsid,\n\
+ call_setgid, gid, groups_size, gids,\n\
+ call_setuid, uid,\n\
+ preexec_fn)\n\
\n\
Forks a child process, closes parent file descriptors as appropriate in the\n\
child and dups the few that are needed before calling exec() in the child\n\
--
2.23.0

View File

@ -0,0 +1,71 @@
From f0deaf10e67b96413be55e18c768b897de02dea2 Mon Sep 17 00:00:00 2001
From: "Gregory P. Smith" <greg@krypto.org>
Date: Fri, 13 Sep 2019 14:43:35 +0100
Subject: [PATCH] bpo-36046: Fix buildbot failures (GH-16091)
Varying user/group/permission check needs on platforms.
Conflict:NA
Reference:https://github.com/python/cpython/commit/693aa80a434590ea7dcd35c000209e53d01b9425
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_subprocess.py | 20 +++++++++++++++++---
1 file changed, 17 insertions(+), 3 deletions(-)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 6c2fd61..aa2f539 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1539,6 +1539,18 @@ class RunFuncTestCase(BaseTestCase):
f"{stacks}```")
+def _get_test_grp_name():
+ for name_group in ('staff', 'nogroup', 'grp'):
+ if grp:
+ try:
+ grp.getgrnam(name_group)
+ except KeyError:
+ continue
+ return name_group
+ else:
+ raise unittest.SkipTest('No identified group name to use for this test on this platform.')
+
+
@unittest.skipIf(mswindows, "POSIX specific tests")
class POSIXProcessTestCase(BaseTestCase):
@@ -1711,8 +1723,10 @@ class POSIXProcessTestCase(BaseTestCase):
[sys.executable, "-c",
"import os; print(os.getuid())"],
user=user)
+ except PermissionError: # errno.EACCES
+ pass
except OSError as e:
- if e.errno != errno.EPERM:
+ if e.errno not in (errno.EACCES, errno.EPERM):
raise
else:
if isinstance(user, str):
@@ -1738,7 +1752,7 @@ class POSIXProcessTestCase(BaseTestCase):
def test_group(self):
gid = os.getegid()
group_list = [65534 if gid != 65534 else 65533]
- name_group = "nogroup" if sys.platform != 'darwin' else "staff"
+ name_group = _get_test_grp_name()
if grp is not None:
group_list.append(name_group)
@@ -1779,7 +1793,7 @@ class POSIXProcessTestCase(BaseTestCase):
def test_extra_groups(self):
gid = os.getegid()
group_list = [65534 if gid != 65534 else 65533]
- name_group = "nogroup" if sys.platform != 'darwin' else "staff"
+ name_group = _get_test_grp_name()
perm_error = False
if grp is not None:
--
2.23.0

View File

@ -0,0 +1,136 @@
From 39129f265d74f4ed4aa424b8bc54075621622d07 Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@redhat.com>
Date: Wed, 25 Sep 2019 15:52:49 +0200
Subject: [PATCH] bpo-36046: posix_spawn() doesn't support uid/gid (GH-16384)
* subprocess.Popen now longer uses posix_spawn() if uid, gid or gids are set.
* test_subprocess: add "nobody" and "nfsnobody" group names for test_group().
* test_subprocess: test_user() and test_group() are now also tested with close_fds=False.
Conflict:NA
Reference:https://github.com/python/cpython/commit/faca8553425c231d867dcabf6a69a9dd21118b6c
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/subprocess.py | 5 ++-
Lib/test/test_subprocess.py | 71 ++++++++++++++++++++-----------------
2 files changed, 42 insertions(+), 34 deletions(-)
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index 6e0eaf9..c80d07e 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -1646,7 +1646,10 @@ class Popen(object):
and (p2cread == -1 or p2cread > 2)
and (c2pwrite == -1 or c2pwrite > 2)
and (errwrite == -1 or errwrite > 2)
- and not start_new_session):
+ and not start_new_session
+ and gid is None
+ and gids is None
+ and uid is None):
self._posix_spawn(args, executable, env, restore_signals,
p2cread, p2cwrite,
c2pread, c2pwrite,
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index aa2f539..8360c6f 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1540,7 +1540,7 @@ class RunFuncTestCase(BaseTestCase):
def _get_test_grp_name():
- for name_group in ('staff', 'nogroup', 'grp'):
+ for name_group in ('staff', 'nogroup', 'grp', 'nobody', 'nfsnobody'):
if grp:
try:
grp.getgrnam(name_group)
@@ -1717,24 +1717,27 @@ class POSIXProcessTestCase(BaseTestCase):
test_users.append(name_uid)
for user in test_users:
- with self.subTest(user=user):
- try:
- output = subprocess.check_output(
- [sys.executable, "-c",
- "import os; print(os.getuid())"],
- user=user)
- except PermissionError: # errno.EACCES
- pass
- except OSError as e:
- if e.errno not in (errno.EACCES, errno.EPERM):
- raise
- else:
- if isinstance(user, str):
- user_uid = pwd.getpwnam(user).pw_uid
+ # posix_spawn() may be used with close_fds=False
+ for close_fds in (False, True):
+ with self.subTest(user=user, close_fds=close_fds):
+ try:
+ output = subprocess.check_output(
+ [sys.executable, "-c",
+ "import os; print(os.getuid())"],
+ user=user,
+ close_fds=close_fds)
+ except PermissionError: # (EACCES, EPERM)
+ pass
+ except OSError as e:
+ if e.errno not in (errno.EACCES, errno.EPERM):
+ raise
else:
- user_uid = user
- child_user = int(output)
- self.assertEqual(child_user, user_uid)
+ if isinstance(user, str):
+ user_uid = pwd.getpwnam(user).pw_uid
+ else:
+ user_uid = user
+ child_user = int(output)
+ self.assertEqual(child_user, user_uid)
with self.assertRaises(ValueError):
subprocess.check_call([sys.executable, "-c", "pass"], user=-1)
@@ -1758,23 +1761,25 @@ class POSIXProcessTestCase(BaseTestCase):
group_list.append(name_group)
for group in group_list + [gid]:
- with self.subTest(group=group):
- try:
- output = subprocess.check_output(
- [sys.executable, "-c",
- "import os; print(os.getgid())"],
- group=group)
- except OSError as e:
- if e.errno != errno.EPERM:
- raise
- else:
- if isinstance(group, str):
- group_gid = grp.getgrnam(group).gr_gid
+ # posix_spawn() may be used with close_fds=False
+ for close_fds in (False, True):
+ with self.subTest(group=group, close_fds=close_fds):
+ try:
+ output = subprocess.check_output(
+ [sys.executable, "-c",
+ "import os; print(os.getgid())"],
+ group=group,
+ close_fds=close_fds)
+ except PermissionError: # (EACCES, EPERM)
+ pass
else:
- group_gid = group
+ if isinstance(group, str):
+ group_gid = grp.getgrnam(group).gr_gid
+ else:
+ group_gid = group
- child_group = int(output)
- self.assertEqual(child_group, group_gid)
+ child_group = int(output)
+ self.assertEqual(child_group, group_gid)
# make sure we bomb on negative values
with self.assertRaises(ValueError):
--
2.23.0

View File

@ -0,0 +1,63 @@
From e63da81ac5d562dfad72fded544fd08566f58de8 Mon Sep 17 00:00:00 2001
From: Anthony Shaw <anthony.p.shaw@gmail.com>
Date: Fri, 10 May 2019 12:00:06 +1000
Subject: [PATCH] bpo-36814: ensure os.posix_spawn() handles None (GH-13144)
Fix an issue where os.posix_spawn() would incorrectly raise a TypeError
when file_actions is None.
Conflict:NA
Reference:https://github.com/python/cpython/commit/948ed8c96b6912541a608591efe3e00fb520429a
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 9 +++++++++
.../Library/2019-05-06-23-13-26.bpo-36814.dSeMz_.rst | 1 +
Modules/posixmodule.c | 2 +-
3 files changed, 11 insertions(+), 1 deletion(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-05-06-23-13-26.bpo-36814.dSeMz_.rst
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index 83accd4..9440083 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1560,6 +1560,15 @@ class _PosixSpawnMixin:
with open(envfile) as f:
self.assertEqual(f.read(), 'bar')
+ def test_none_file_actions(self):
+ pid = self.spawn_func(
+ self.NOOP_PROGRAM[0],
+ self.NOOP_PROGRAM,
+ os.environ,
+ file_actions=None
+ )
+ self.assertEqual(os.waitpid(pid, 0), (pid, 0))
+
def test_empty_file_actions(self):
pid = self.spawn_func(
self.NOOP_PROGRAM[0],
diff --git a/Misc/NEWS.d/next/Library/2019-05-06-23-13-26.bpo-36814.dSeMz_.rst b/Misc/NEWS.d/next/Library/2019-05-06-23-13-26.bpo-36814.dSeMz_.rst
new file mode 100644
index 0000000..3f40011
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-06-23-13-26.bpo-36814.dSeMz_.rst
@@ -0,0 +1 @@
+Fix an issue where os.posix_spawnp() would incorrectly raise a TypeError when file_actions is None.
\ No newline at end of file
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index 129de76..f1ab030 100644
--- a/Modules/posixmodule.c
+++ b/Modules/posixmodule.c
@@ -5487,7 +5487,7 @@ py_posix_spawn(int use_posix_spawnp, PyObject *module, path_t *path, PyObject *a
goto exit;
}
- if (file_actions != NULL) {
+ if (file_actions != NULL && file_actions != Py_None) {
/* There is a bug in old versions of glibc that makes some of the
* helper functions for manipulating file actions not copy the provided
* buffers. The problem is that posix_spawn_file_actions_addopen does not
--
2.23.0

View File

@ -0,0 +1,291 @@
From 7ea255a0878c2486a02d0982fea1cb8e72ebd52f Mon Sep 17 00:00:00 2001
From: "Gregory P. Smith" <greg@krypto.org>
Date: Sat, 12 Oct 2019 13:24:56 -0700
Subject: [PATCH] bpo-38417: Add umask support to subprocess (GH-16726)
On POSIX systems, allow the umask to be set in the child process before we exec.
Conflict:NA
Reference:https://github.com/python/cpython/commit/f3751efb5c8b53b37efbbf75d9422c1d11c01646
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Doc/library/subprocess.rst | 12 ++++++---
Lib/multiprocessing/util.py | 2 +-
Lib/subprocess.py | 14 ++++++----
Lib/test/test_capi.py | 6 ++---
Lib/test/test_subprocess.py | 26 +++++++++++++++++--
.../2019-10-12-00-13-47.bpo-38417.W7x_aS.rst | 2 ++
Modules/_posixsubprocess.c | 14 ++++++----
7 files changed, 57 insertions(+), 19 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-10-12-00-13-47.bpo-38417.W7x_aS.rst
diff --git a/Doc/library/subprocess.rst b/Doc/library/subprocess.rst
index 3461297..009de63 100644
--- a/Doc/library/subprocess.rst
+++ b/Doc/library/subprocess.rst
@@ -339,9 +339,9 @@ functions.
stderr=None, preexec_fn=None, close_fds=True, shell=False, \
cwd=None, env=None, universal_newlines=None, \
startupinfo=None, creationflags=0, restore_signals=True, \
- start_new_session=False, pass_fds=(), *, group=None, \
- extra_groups=None, user=None, encoding=None, errors=None, \
- text=None)
+ start_new_session=False, pass_fds=(), \*, group=None, \
+ extra_groups=None, user=None, umask=-1, \
+ encoding=None, errors=None, text=None)
Execute a child program in a new process. On POSIX, the class uses
:meth:`os.execvp`-like behavior to execute the child program. On Windows,
@@ -548,6 +548,12 @@ functions.
.. availability:: POSIX
.. versionadded:: 3.9
+ If *umask* is not negative, the umask() system call will be made in the
+ child process prior to the execution of the subprocess.
+
+ .. availability:: POSIX
+ .. versionadded:: 3.9
+
If *env* is not ``None``, it must be a mapping that defines the environment
variables for the new process; these are used instead of the default
behavior of inheriting the current process' environment.
diff --git a/Lib/multiprocessing/util.py b/Lib/multiprocessing/util.py
index c7e9b85..ff8b4ec 100644
--- a/Lib/multiprocessing/util.py
+++ b/Lib/multiprocessing/util.py
@@ -452,7 +452,7 @@ def spawnv_passfds(path, args, passfds):
return _posixsubprocess.fork_exec(
args, [os.fsencode(path)], True, passfds, None, None,
-1, -1, -1, -1, -1, -1, errpipe_read, errpipe_write,
- False, False, None, None, None, None)
+ False, False, None, None, None, -1, None)
finally:
os.close(errpipe_read)
os.close(errpipe_write)
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index c80d07e..300ad58 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -734,6 +734,8 @@ class Popen(object):
user (POSIX only)
+ umask (POSIX only)
+
pass_fds (POSIX only)
encoding and errors: Text mode encoding and error handling to use for
@@ -751,7 +753,7 @@ class Popen(object):
startupinfo=None, creationflags=0,
restore_signals=True, start_new_session=False,
pass_fds=(), *, user=None, group=None, extra_groups=None,
- encoding=None, errors=None, text=None):
+ encoding=None, errors=None, text=None, umask=-1):
"""Create new Popen instance."""
_cleanup()
# Held while anything is calling waitpid before returncode has been
@@ -936,7 +938,7 @@ class Popen(object):
c2pread, c2pwrite,
errread, errwrite,
restore_signals,
- gid, gids, uid,
+ gid, gids, uid, umask,
start_new_session)
except:
# Cleanup if the child failed starting.
@@ -1309,6 +1311,7 @@ class Popen(object):
errread, errwrite,
unused_restore_signals,
unused_gid, unused_gids, unused_uid,
+ unused_umask,
unused_start_new_session):
"""Execute program (MS Windows version)"""
@@ -1617,7 +1620,7 @@ class Popen(object):
c2pread, c2pwrite,
errread, errwrite,
restore_signals,
- gid, gids, uid,
+ gid, gids, uid, umask,
start_new_session):
"""Execute program (POSIX version)"""
@@ -1649,7 +1652,8 @@ class Popen(object):
and not start_new_session
and gid is None
and gids is None
- and uid is None):
+ and uid is None
+ and umask < 0):
self._posix_spawn(args, executable, env, restore_signals,
p2cread, p2cwrite,
c2pread, c2pwrite,
@@ -1703,7 +1707,7 @@ class Popen(object):
errread, errwrite,
errpipe_read, errpipe_write,
restore_signals, start_new_session,
- gid, gids, uid,
+ gid, gids, uid, umask,
preexec_fn)
self._child_created = True
finally:
diff --git a/Lib/test/test_capi.py b/Lib/test/test_capi.py
index dcff095..ebcb692 100644
--- a/Lib/test/test_capi.py
+++ b/Lib/test/test_capi.py
@@ -96,7 +96,7 @@ class CAPITest(unittest.TestCase):
def __len__(self):
return 1
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
- 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
+ 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
# Issue #15736: overflow in _PySequence_BytesToCharpArray()
class Z(object):
def __len__(self):
@@ -104,7 +104,7 @@ class CAPITest(unittest.TestCase):
def __getitem__(self, i):
return b'x'
self.assertRaises(MemoryError, _posixsubprocess.fork_exec,
- 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
+ 1,Z(),3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
@unittest.skipUnless(_posixsubprocess, '_posixsubprocess required for this test.')
def test_subprocess_fork_exec(self):
@@ -114,7 +114,7 @@ class CAPITest(unittest.TestCase):
# Issue #15738: crash in subprocess_fork_exec()
self.assertRaises(TypeError, _posixsubprocess.fork_exec,
- Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20)
+ Z(),[b'1'],3,(1, 2),5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21)
@unittest.skipIf(MISSING_C_DOCSTRINGS,
"Signature information for builtins requires docstrings")
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 8360c6f..059a007 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1843,6 +1843,28 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(ValueError):
subprocess.check_call([sys.executable, "-c", "pass"], extra_groups=[])
+ @unittest.skipIf(mswindows or not hasattr(os, 'umask'),
+ 'POSIX umask() is not available.')
+ def test_umask(self):
+ tmpdir = None
+ try:
+ tmpdir = tempfile.mkdtemp()
+ name = os.path.join(tmpdir, "beans")
+ # We set an unusual umask in the child so as a unique mode
+ # for us to test the child's touched file for.
+ subprocess.check_call(
+ [sys.executable, "-c", f"open({name!r}, 'w')"], # touch
+ umask=0o053)
+ # Ignore execute permissions entirely in our test,
+ # filesystems could be mounted to ignore or force that.
+ st_mode = os.stat(name).st_mode & 0o666
+ expected_mode = 0o624
+ self.assertEqual(expected_mode, st_mode,
+ msg=f'{oct(expected_mode)} != {oct(st_mode)}')
+ finally:
+ if tmpdir is not None:
+ shutil.rmtree(tmpdir)
+
def test_run_abort(self):
# returncode handles signal termination
with support.SuppressCrashReport():
@@ -2899,7 +2921,7 @@ class POSIXProcessTestCase(BaseTestCase):
-1, -1, -1, -1,
1, 2, 3, 4,
True, True,
- False, [], 0,
+ False, [], 0, -1,
func)
# Attempt to prevent
# "TypeError: fork_exec() takes exactly N arguments (M given)"
@@ -2948,7 +2970,7 @@ class POSIXProcessTestCase(BaseTestCase):
-1, -1, -1, -1,
1, 2, 3, 4,
True, True,
- None, None, None,
+ None, None, None, -1,
None)
self.assertIn('fds_to_keep', str(c.exception))
finally:
diff --git a/Misc/NEWS.d/next/Library/2019-10-12-00-13-47.bpo-38417.W7x_aS.rst b/Misc/NEWS.d/next/Library/2019-10-12-00-13-47.bpo-38417.W7x_aS.rst
new file mode 100644
index 0000000..c2356dd
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-10-12-00-13-47.bpo-38417.W7x_aS.rst
@@ -0,0 +1,2 @@
+Added support for setting the umask in the child process to the subprocess
+module on POSIX systems.
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index caa8d7a..9e5e7c6 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -8,7 +8,7 @@
#ifdef HAVE_SYS_TYPES_H
#include <sys/types.h>
#endif
-#if defined(HAVE_SYS_STAT_H) && defined(__FreeBSD__)
+#if defined(HAVE_SYS_STAT_H)
#include <sys/stat.h>
#endif
#ifdef HAVE_SYS_SYSCALL_H
@@ -418,7 +418,7 @@ child_exec(char *const exec_array[],
int call_setsid,
int call_setgid, gid_t gid,
int call_setgroups, size_t groups_size, const gid_t *groups,
- int call_setuid, uid_t uid,
+ int call_setuid, uid_t uid, int child_umask,
PyObject *py_fds_to_keep,
PyObject *preexec_fn,
PyObject *preexec_fn_args_tuple)
@@ -488,6 +488,9 @@ child_exec(char *const exec_array[],
if (cwd)
POSIX_CALL(chdir(cwd));
+ if (child_umask >= 0)
+ umask(child_umask); /* umask() always succeeds. */
+
if (restore_signals)
_Py_RestoreSignals();
@@ -599,6 +602,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
int call_setgid = 0, call_setgroups = 0, call_setuid = 0;
uid_t uid;
gid_t gid, *groups = NULL;
+ int child_umask;
PyObject *cwd_obj, *cwd_obj2;
const char *cwd;
pid_t pid;
@@ -609,14 +613,14 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
int saved_errno = 0;
if (!PyArg_ParseTuple(
- args, "OOpO!OOiiiiiiiiiiOOOO:fork_exec",
+ args, "OOpO!OOiiiiiiiiiiOOOiO:fork_exec",
&process_args, &executable_list,
&close_fds, &PyTuple_Type, &py_fds_to_keep,
&cwd_obj, &env_list,
&p2cread, &p2cwrite, &c2pread, &c2pwrite,
&errread, &errwrite, &errpipe_read, &errpipe_write,
&restore_signals, &call_setsid,
- &gid_object, &groups_list, &uid_object,
+ &gid_object, &groups_list, &uid_object, &child_umask,
&preexec_fn))
return NULL;
@@ -826,7 +830,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
errread, errwrite, errpipe_read, errpipe_write,
close_fds, restore_signals, call_setsid,
call_setgid, gid, call_setgroups, num_groups, groups,
- call_setuid, uid,
+ call_setuid, uid, child_umask,
py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
_exit(255);
return NULL; /* Dead code to avoid a potential compiler warning. */
--
2.23.0

View File

@ -0,0 +1,30 @@
From 70975d9a05f6d957c669a521071f207dca6002bc Mon Sep 17 00:00:00 2001
From: Pablo Galindo <Pablogsal@gmail.com>
Date: Sun, 13 Oct 2019 02:40:24 +0100
Subject: [PATCH] bpo-38456: Handle the case when there is no 'true' command
(GH-16739)
Conflict:NA
Reference:https://github.com/python/cpython/commit/46113e0cf32748f66cf64cd633984d143b433cd1
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_subprocess.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 9820507..ac45436 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -67,6 +67,8 @@ ZERO_RETURN_CMD = (sys.executable, '-c', 'pass')
def setUpModule():
shell_true = shutil.which('true')
+ if shell_true is None:
+ return
if (os.access(shell_true, os.X_OK) and
subprocess.run([shell_true]).returncode == 0):
global ZERO_RETURN_CMD
--
2.23.0

View File

@ -0,0 +1,422 @@
From 080b02c44bb09cdcf0af439681250d8c71a4f245 Mon Sep 17 00:00:00 2001
From: "Gregory P. Smith" <greg@krypto.org>
Date: Sat, 12 Oct 2019 16:35:53 -0700
Subject: [PATCH] bpo-38456: Use /bin/true in test_subprocess (GH-16736)
* bpo-38456: Use /bin/true in test_subprocess.
Instead of sys.executable, "-c", "pass" or "import sys; sys.exit(0)"
use /bin/true when it is available. On a reasonable machine this
shaves up to two seconds wall time off the otherwise ~40sec execution
on a --with-pydebug build. It should be more notable on many
buildbots or overloaded slower I/O systems (CI, etc).
Conflict:NA
Reference:https://github.com/python/cpython/commit/67b93f80c764bca01c81c989d74a99df208bea4d
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_subprocess.py | 108 ++++++++++++++++++++----------------
1 file changed, 59 insertions(+), 49 deletions(-)
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index 059a007..9820507 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -62,6 +62,16 @@ NONEXISTING_CMD = ('nonexisting_i_hope',)
# Ignore errors that indicate the command was not found
NONEXISTING_ERRORS = (FileNotFoundError, NotADirectoryError, PermissionError)
+ZERO_RETURN_CMD = (sys.executable, '-c', 'pass')
+
+
+def setUpModule():
+ shell_true = shutil.which('true')
+ if (os.access(shell_true, os.X_OK) and
+ subprocess.run([shell_true]).returncode == 0):
+ global ZERO_RETURN_CMD
+ ZERO_RETURN_CMD = (shell_true,) # Faster than Python startup.
+
class BaseTestCase(unittest.TestCase):
def setUp(self):
@@ -106,7 +116,7 @@ class PopenExecuteChildRaises(subprocess.Popen):
class ProcessTestCase(BaseTestCase):
def test_io_buffered_by_default(self):
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
try:
@@ -120,7 +130,7 @@ class ProcessTestCase(BaseTestCase):
p.wait()
def test_io_unbuffered_works(self):
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, bufsize=0)
try:
@@ -150,8 +160,7 @@ class ProcessTestCase(BaseTestCase):
def test_check_call_zero(self):
# check_call() function with zero return code
- rc = subprocess.check_call([sys.executable, "-c",
- "import sys; sys.exit(0)"])
+ rc = subprocess.check_call(ZERO_RETURN_CMD)
self.assertEqual(rc, 0)
def test_check_call_nonzero(self):
@@ -689,19 +698,19 @@ class ProcessTestCase(BaseTestCase):
newenv = os.environ.copy()
newenv["FRUIT\0VEGETABLE"] = "cabbage"
with self.assertRaises(ValueError):
- subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
+ subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
# null character in the environment variable value
newenv = os.environ.copy()
newenv["FRUIT"] = "orange\0VEGETABLE=cabbage"
with self.assertRaises(ValueError):
- subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
+ subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
# equal character in the environment variable name
newenv = os.environ.copy()
newenv["FRUIT=ORANGE"] = "lemon"
with self.assertRaises(ValueError):
- subprocess.Popen([sys.executable, "-c", "pass"], env=newenv)
+ subprocess.Popen(ZERO_RETURN_CMD, env=newenv)
# equal character in the environment variable value
newenv = os.environ.copy()
@@ -802,7 +811,7 @@ class ProcessTestCase(BaseTestCase):
options['stderr'] = subprocess.PIPE
if not options:
continue
- p = subprocess.Popen((sys.executable, "-c", "pass"), **options)
+ p = subprocess.Popen(ZERO_RETURN_CMD, **options)
p.communicate()
if p.stdin is not None:
self.assertTrue(p.stdin.closed)
@@ -941,7 +950,7 @@ class ProcessTestCase(BaseTestCase):
#
# We set stdout to PIPE because, as of this writing, a different
# code path is tested when the number of pipes is zero or one.
- p = subprocess.Popen([sys.executable, "-c", "pass"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
universal_newlines=True)
@@ -1089,7 +1098,7 @@ class ProcessTestCase(BaseTestCase):
self.assertEqual(p.poll(), 0)
def test_wait(self):
- p = subprocess.Popen([sys.executable, "-c", "pass"])
+ p = subprocess.Popen(ZERO_RETURN_CMD)
self.assertEqual(p.wait(), 0)
# Subsequent invocations should just return the returncode
self.assertEqual(p.wait(), 0)
@@ -1108,14 +1117,14 @@ class ProcessTestCase(BaseTestCase):
# an invalid type of the bufsize argument should raise
# TypeError.
with self.assertRaises(TypeError):
- subprocess.Popen([sys.executable, "-c", "pass"], "orange")
+ subprocess.Popen(ZERO_RETURN_CMD, "orange")
def test_bufsize_is_none(self):
# bufsize=None should be the same as bufsize=0.
- p = subprocess.Popen([sys.executable, "-c", "pass"], None)
+ p = subprocess.Popen(ZERO_RETURN_CMD, None)
self.assertEqual(p.wait(), 0)
# Again with keyword arg
- p = subprocess.Popen([sys.executable, "-c", "pass"], bufsize=None)
+ p = subprocess.Popen(ZERO_RETURN_CMD, bufsize=None)
self.assertEqual(p.wait(), 0)
def _test_bufsize_equal_one(self, line, expected, universal_newlines):
@@ -1319,7 +1328,7 @@ class ProcessTestCase(BaseTestCase):
def test_communicate_epipe(self):
# Issue 10963: communicate() should hide EPIPE
- p = subprocess.Popen([sys.executable, "-c", 'pass'],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
@@ -1330,7 +1339,7 @@ class ProcessTestCase(BaseTestCase):
def test_communicate_epipe_only_stdin(self):
# Issue 10963: communicate() should hide EPIPE
- p = subprocess.Popen([sys.executable, "-c", 'pass'],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
stdin=subprocess.PIPE)
self.addCleanup(p.stdin.close)
p.wait()
@@ -1369,7 +1378,7 @@ class ProcessTestCase(BaseTestCase):
fds_before_popen = os.listdir(fd_directory)
with self.assertRaises(PopenTestException):
PopenExecuteChildRaises(
- [sys.executable, '-c', 'pass'], stdin=subprocess.PIPE,
+ ZERO_RETURN_CMD, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
# NOTE: This test doesn't verify that the real _execute_child
@@ -1412,7 +1421,7 @@ class RunFuncTestCase(BaseTestCase):
def test_check_zero(self):
# check_returncode shouldn't raise when returncode is zero
- cp = self.run_python("import sys; sys.exit(0)", check=True)
+ cp = subprocess.run(ZERO_RETURN_CMD, check=True)
self.assertEqual(cp.returncode, 0)
def test_timeout(self):
@@ -1740,16 +1749,16 @@ class POSIXProcessTestCase(BaseTestCase):
self.assertEqual(child_user, user_uid)
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"], user=-1)
+ subprocess.check_call(ZERO_RETURN_CMD, user=-1)
if pwd is None:
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"], user=name_uid)
+ subprocess.check_call(ZERO_RETURN_CMD, user=name_uid)
@unittest.skipIf(hasattr(os, 'setreuid'), 'setreuid() available on platform')
def test_user_error(self):
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"], user=65535)
+ subprocess.check_call(ZERO_RETURN_CMD, user=65535)
@unittest.skipUnless(hasattr(os, 'setregid'), 'no setregid() on platform')
def test_group(self):
@@ -1783,16 +1792,16 @@ class POSIXProcessTestCase(BaseTestCase):
# make sure we bomb on negative values
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"], group=-1)
+ subprocess.check_call(ZERO_RETURN_CMD, group=-1)
if grp is None:
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"], group=name_group)
+ subprocess.check_call(ZERO_RETURN_CMD, group=name_group)
@unittest.skipIf(hasattr(os, 'setregid'), 'setregid() available on platform')
def test_group_error(self):
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"], group=65535)
+ subprocess.check_call(ZERO_RETURN_CMD, group=65535)
@unittest.skipUnless(hasattr(os, 'setgroups'), 'no setgroups() on platform')
def test_extra_groups(self):
@@ -1831,17 +1840,17 @@ class POSIXProcessTestCase(BaseTestCase):
# make sure we bomb on negative values
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"], extra_groups=[-1])
+ subprocess.check_call(ZERO_RETURN_CMD, extra_groups=[-1])
if grp is None:
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"],
+ subprocess.check_call(ZERO_RETURN_CMD,
extra_groups=[name_group])
@unittest.skipIf(hasattr(os, 'setgroups'), 'setgroups() available on platform')
def test_extra_groups_error(self):
with self.assertRaises(ValueError):
- subprocess.check_call([sys.executable, "-c", "pass"], extra_groups=[])
+ subprocess.check_call(ZERO_RETURN_CMD, extra_groups=[])
@unittest.skipIf(mswindows or not hasattr(os, 'umask'),
'POSIX umask() is not available.')
@@ -1853,7 +1862,7 @@ class POSIXProcessTestCase(BaseTestCase):
# We set an unusual umask in the child so as a unique mode
# for us to test the child's touched file for.
subprocess.check_call(
- [sys.executable, "-c", f"open({name!r}, 'w')"], # touch
+ [sys.executable, "-c", f"open({name!r}, 'w').close()"],
umask=0o053)
# Ignore execute permissions entirely in our test,
# filesystems could be mounted to ignore or force that.
@@ -1956,7 +1965,7 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(subprocess.SubprocessError):
self._TestExecuteChildPopen(
- self, [sys.executable, "-c", "pass"],
+ self, ZERO_RETURN_CMD,
stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, preexec_fn=raise_it)
@@ -2413,7 +2422,7 @@ class POSIXProcessTestCase(BaseTestCase):
try:
subprocess.call(
- [sys.executable, "-c", "pass"],
+ ZERO_RETURN_CMD,
preexec_fn=prepare)
except ValueError as err:
# Pure Python implementations keeps the message
@@ -2456,29 +2465,30 @@ class POSIXProcessTestCase(BaseTestCase):
self.assertEqual(stdout.decode('ascii'), ascii(encoded_value))
def test_bytes_program(self):
- abs_program = os.fsencode(sys.executable)
- path, program = os.path.split(sys.executable)
+ abs_program = os.fsencode(ZERO_RETURN_CMD[0])
+ args = list(ZERO_RETURN_CMD[1:])
+ path, program = os.path.split(ZERO_RETURN_CMD[0])
program = os.fsencode(program)
# absolute bytes path
- exitcode = subprocess.call([abs_program, "-c", "pass"])
+ exitcode = subprocess.call([abs_program]+args)
self.assertEqual(exitcode, 0)
# absolute bytes path as a string
- cmd = b"'" + abs_program + b"' -c pass"
+ cmd = b"'%s' %s" % (abs_program, " ".join(args).encode("utf-8"))
exitcode = subprocess.call(cmd, shell=True)
self.assertEqual(exitcode, 0)
# bytes program, unicode PATH
env = os.environ.copy()
env["PATH"] = path
- exitcode = subprocess.call([program, "-c", "pass"], env=env)
+ exitcode = subprocess.call([program]+args, env=env)
self.assertEqual(exitcode, 0)
# bytes program, bytes PATH
envb = os.environb.copy()
envb[b"PATH"] = os.fsencode(path)
- exitcode = subprocess.call([program, "-c", "pass"], env=envb)
+ exitcode = subprocess.call([program]+args, env=envb)
self.assertEqual(exitcode, 0)
def test_pipe_cloexec(self):
@@ -2706,7 +2716,7 @@ class POSIXProcessTestCase(BaseTestCase):
# pass_fds overrides close_fds with a warning.
with self.assertWarns(RuntimeWarning) as context:
self.assertFalse(subprocess.call(
- [sys.executable, "-c", "import sys; sys.exit(0)"],
+ ZERO_RETURN_CMD,
close_fds=False, pass_fds=(fd, )))
self.assertIn('overriding close_fds', str(context.warning))
@@ -2768,19 +2778,19 @@ class POSIXProcessTestCase(BaseTestCase):
def test_stdout_stdin_are_single_inout_fd(self):
with io.open(os.devnull, "r+") as inout:
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
stdout=inout, stdin=inout)
p.wait()
def test_stdout_stderr_are_single_inout_fd(self):
with io.open(os.devnull, "r+") as inout:
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
stdout=inout, stderr=inout)
p.wait()
def test_stderr_stdin_are_single_inout_fd(self):
with io.open(os.devnull, "r+") as inout:
- p = subprocess.Popen([sys.executable, "-c", "import sys; sys.exit(0)"],
+ p = subprocess.Popen(ZERO_RETURN_CMD,
stderr=inout, stdin=inout)
p.wait()
@@ -2980,7 +2990,7 @@ class POSIXProcessTestCase(BaseTestCase):
def test_communicate_BrokenPipeError_stdin_close(self):
# By not setting stdout or stderr or a timeout we force the fast path
# that just calls _stdin_write() internally due to our mock.
- proc = subprocess.Popen([sys.executable, '-c', 'pass'])
+ proc = subprocess.Popen(ZERO_RETURN_CMD)
with proc, mock.patch.object(proc, 'stdin') as mock_proc_stdin:
mock_proc_stdin.close.side_effect = BrokenPipeError
proc.communicate() # Should swallow BrokenPipeError from close.
@@ -2989,7 +2999,7 @@ class POSIXProcessTestCase(BaseTestCase):
def test_communicate_BrokenPipeError_stdin_write(self):
# By not setting stdout or stderr or a timeout we force the fast path
# that just calls _stdin_write() internally due to our mock.
- proc = subprocess.Popen([sys.executable, '-c', 'pass'])
+ proc = subprocess.Popen(ZERO_RETURN_CMD)
with proc, mock.patch.object(proc, 'stdin') as mock_proc_stdin:
mock_proc_stdin.write.side_effect = BrokenPipeError
proc.communicate(b'stuff') # Should swallow the BrokenPipeError.
@@ -3028,7 +3038,7 @@ class POSIXProcessTestCase(BaseTestCase):
'need _testcapi.W_STOPCODE')
def test_stopped(self):
"""Test wait() behavior when waitpid returns WIFSTOPPED; issue29335."""
- args = [sys.executable, '-c', 'pass']
+ args = ZERO_RETURN_CMD
proc = subprocess.Popen(args)
# Wait until the real process completes to avoid zombie process
@@ -3069,7 +3079,7 @@ class Win32ProcessTestCase(BaseTestCase):
# Since Python is a console process, it won't be affected
# by wShowWindow, but the argument should be silently
# ignored
- subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ subprocess.call(ZERO_RETURN_CMD,
startupinfo=startupinfo)
def test_startupinfo_keywords(self):
@@ -3085,7 +3095,7 @@ class Win32ProcessTestCase(BaseTestCase):
# Since Python is a console process, it won't be affected
# by wShowWindow, but the argument should be silently
# ignored
- subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ subprocess.call(ZERO_RETURN_CMD,
startupinfo=startupinfo)
def test_startupinfo_copy(self):
@@ -3097,7 +3107,7 @@ class Win32ProcessTestCase(BaseTestCase):
# Call Popen() twice with the same startupinfo object to make sure
# that it's not modified
for _ in range(2):
- cmd = [sys.executable, "-c", "pass"]
+ cmd = ZERO_RETURN_CMD
with open(os.devnull, 'w') as null:
proc = subprocess.Popen(cmd,
stdout=null,
@@ -3137,7 +3147,7 @@ class Win32ProcessTestCase(BaseTestCase):
class BadEnv(dict):
keys = None
with self.assertRaises(TypeError):
- subprocess.Popen([sys.executable, "-c", "pass"], env=BadEnv())
+ subprocess.Popen(ZERO_RETURN_CMD, env=BadEnv())
def test_close_fds(self):
# close file descriptors
@@ -3198,13 +3208,13 @@ class Win32ProcessTestCase(BaseTestCase):
def test_empty_attribute_list(self):
startupinfo = subprocess.STARTUPINFO()
startupinfo.lpAttributeList = {}
- subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ subprocess.call(ZERO_RETURN_CMD,
startupinfo=startupinfo)
def test_empty_handle_list(self):
startupinfo = subprocess.STARTUPINFO()
startupinfo.lpAttributeList = {"handle_list": []}
- subprocess.call([sys.executable, "-c", "import sys; sys.exit(0)"],
+ subprocess.call(ZERO_RETURN_CMD,
startupinfo=startupinfo)
def test_shell_sequence(self):
@@ -3503,7 +3513,7 @@ class ContextManagerTests(BaseTestCase):
def test_broken_pipe_cleanup(self):
"""Broken pipe error should not prevent wait() (Issue 21619)"""
- proc = subprocess.Popen([sys.executable, '-c', 'pass'],
+ proc = subprocess.Popen(ZERO_RETURN_CMD,
stdin=subprocess.PIPE,
bufsize=support.PIPE_MAX_SIZE*2)
proc = proc.__enter__()
--
2.23.0

View File

@ -0,0 +1,59 @@
From c70f250a25f240cb6595a8eb8ff80389ae472e45 Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@python.org>
Date: Thu, 5 Mar 2020 14:28:40 +0100
Subject: [PATCH] bpo-39855: Fix test_subprocess if nobody user doesn't exist
(GH-18781)
test_subprocess.test_user() now skips the test on an user name if the
user name doesn't exist. For example, skip the test if the user
"nobody" doesn't exist on Linux.
Conflict:NA
Reference:https://github.com/python/cpython/commit/f7b5d419bf871d9cc898982c7b6b4c043f7d5e9d
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_subprocess.py | 9 +++++++--
.../next/Tests/2020-03-04-23-03-01.bpo-39855.Ql5xv8.rst | 3 +++
2 files changed, 10 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Tests/2020-03-04-23-03-01.bpo-39855.Ql5xv8.rst
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index ac45436..bced1e7 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1725,7 +1725,12 @@ class POSIXProcessTestCase(BaseTestCase):
name_uid = "nobody" if sys.platform != 'darwin' else "unknown"
if pwd is not None:
- test_users.append(name_uid)
+ try:
+ pwd.getpwnam(name_uid)
+ test_users.append(name_uid)
+ except KeyError:
+ # unknown user name
+ name_uid = None
for user in test_users:
# posix_spawn() may be used with close_fds=False
@@ -1753,7 +1758,7 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, user=-1)
- if pwd is None:
+ if pwd is None and name_uid is not None:
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, user=name_uid)
diff --git a/Misc/NEWS.d/next/Tests/2020-03-04-23-03-01.bpo-39855.Ql5xv8.rst b/Misc/NEWS.d/next/Tests/2020-03-04-23-03-01.bpo-39855.Ql5xv8.rst
new file mode 100644
index 0000000..0601241
--- /dev/null
+++ b/Misc/NEWS.d/next/Tests/2020-03-04-23-03-01.bpo-39855.Ql5xv8.rst
@@ -0,0 +1,3 @@
+test_subprocess.test_user() now skips the test on an user name if the user
+name doesn't exist. For example, skip the test if the user "nobody" doesn't
+exist on Linux.
--
2.23.0

View File

@ -0,0 +1,103 @@
From ee0f0b4f066eb9e44e0e0270eba23ddb60e8b44a Mon Sep 17 00:00:00 2001
From: Alexey Izbyshev <izbyshev@ispras.ru>
Date: Mon, 26 Oct 2020 03:09:32 +0300
Subject: [PATCH] bpo-42146: Fix memory leak in subprocess.Popen() in case of
uid/gid overflow (GH-22966)
Fix memory leak in subprocess.Popen() in case of uid/gid overflow
Also add a test that would catch this leak with `--huntrleaks`.
Alas, the test for `extra_groups` also exposes an inconsistency
in our error reporting: we use a custom ValueError for `extra_groups`,
but propagate OverflowError for `user` and `group`.
Conflict:NA
Reference:https://github.com/python/cpython/commit/c0590c0033e86f98cdf5f2ca6898656f98ab4053
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_subprocess.py | 13 +++++++++++++
.../2020-10-25-19-25-02.bpo-42146.6A8uvS.rst | 2 ++
Modules/_posixsubprocess.c | 4 ++--
3 files changed, 17 insertions(+), 2 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2020-10-25-19-25-02.bpo-42146.6A8uvS.rst
diff --git a/Lib/test/test_subprocess.py b/Lib/test/test_subprocess.py
index bced1e7..e401656 100644
--- a/Lib/test/test_subprocess.py
+++ b/Lib/test/test_subprocess.py
@@ -1758,6 +1758,10 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, user=-1)
+ with self.assertRaises(OverflowError):
+ subprocess.check_call(ZERO_RETURN_CMD,
+ cwd=os.curdir, env=os.environ, user=2**64)
+
if pwd is None and name_uid is not None:
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, user=name_uid)
@@ -1801,6 +1805,10 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, group=-1)
+ with self.assertRaises(OverflowError):
+ subprocess.check_call(ZERO_RETURN_CMD,
+ cwd=os.curdir, env=os.environ, group=2**64)
+
if grp is None:
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, group=name_group)
@@ -1849,6 +1857,11 @@ class POSIXProcessTestCase(BaseTestCase):
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD, extra_groups=[-1])
+ with self.assertRaises(ValueError):
+ subprocess.check_call(ZERO_RETURN_CMD,
+ cwd=os.curdir, env=os.environ,
+ extra_groups=[2**64])
+
if grp is None:
with self.assertRaises(ValueError):
subprocess.check_call(ZERO_RETURN_CMD,
diff --git a/Misc/NEWS.d/next/Library/2020-10-25-19-25-02.bpo-42146.6A8uvS.rst b/Misc/NEWS.d/next/Library/2020-10-25-19-25-02.bpo-42146.6A8uvS.rst
new file mode 100644
index 0000000..0418098
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2020-10-25-19-25-02.bpo-42146.6A8uvS.rst
@@ -0,0 +1,2 @@
+Fix memory leak in :func:`subprocess.Popen` in case an uid (gid) specified in
+`user` (`group`, `extra_groups`) overflows `uid_t` (`gid_t`).
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index 5845445..2981253 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -759,7 +759,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
uid_t uid;
gid_t gid, *groups = NULL;
int child_umask;
- PyObject *cwd_obj, *cwd_obj2;
+ PyObject *cwd_obj, *cwd_obj2 = NULL;
const char *cwd;
pid_t pid;
int need_to_reenable_gc = 0;
@@ -866,7 +866,6 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
cwd = PyBytes_AsString(cwd_obj2);
} else {
cwd = NULL;
- cwd_obj2 = NULL;
}
if (groups_list != Py_None) {
@@ -1051,6 +1050,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
return PyLong_FromPid(pid);
cleanup:
+ Py_XDECREF(cwd_obj2);
if (envp)
_Py_FreeCharPArray(envp);
if (argv)
--
2.23.0

View File

@ -0,0 +1,139 @@
From e8aadd0680a208a4963aa801a5a9d505b4af7add Mon Sep 17 00:00:00 2001
From: Alexey Izbyshev <izbyshev@ispras.ru>
Date: Sun, 1 Nov 2020 08:33:08 +0300
Subject: [PATCH] bpo-42146: Unify cleanup in subprocess_fork_exec() (GH-22970)
* bpo-42146: Unify cleanup in subprocess_fork_exec()
Also ignore errors from _enable_gc():
* They are always suppressed by the current code due to a bug.
* _enable_gc() is only used if `preexec_fn != None`, which is unsafe.
* We don't have a good way to handle errors in case we successfully
created a child process.
Conflict:NA
Reference:https://github.com/python/cpython/commit/d3b4e068077dd26927ae7485bd0303e09d962c02
Co-authored-by: Gregory P. Smith <greg@krypto.org>
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Modules/_posixsubprocess.c | 52 +++++++++++++-------------------------
1 file changed, 18 insertions(+), 34 deletions(-)
diff --git a/Modules/_posixsubprocess.c b/Modules/_posixsubprocess.c
index 2981253..fe801e9 100644
--- a/Modules/_posixsubprocess.c
+++ b/Modules/_posixsubprocess.c
@@ -69,8 +69,8 @@
#define POSIX_CALL(call) do { if ((call) == -1) goto error; } while (0)
-/* If gc was disabled, call gc.enable(). Return 0 on success. */
-static int
+/* If gc was disabled, call gc.enable(). Ignore errors. */
+static void
_enable_gc(int need_to_reenable_gc, PyObject *gc_module)
{
PyObject *result;
@@ -80,15 +80,17 @@ _enable_gc(int need_to_reenable_gc, PyObject *gc_module)
if (need_to_reenable_gc) {
PyErr_Fetch(&exctype, &val, &tb);
result = _PyObject_CallMethodId(gc_module, &PyId_enable, NULL);
+ if (result == NULL) {
+ /* We might have created a child process at this point, we
+ * we have no good way to handle a failure to reenable GC
+ * and return information about the child process. */
+ PyErr_Print();
+ }
+ Py_XDECREF(result);
if (exctype != NULL) {
PyErr_Restore(exctype, val, tb);
}
- if (result == NULL) {
- return 1;
- }
- Py_DECREF(result);
}
- return 0;
}
@@ -761,7 +763,7 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
int child_umask;
PyObject *cwd_obj, *cwd_obj2 = NULL;
const char *cwd;
- pid_t pid;
+ pid_t pid = -1;
int need_to_reenable_gc = 0;
char *const *exec_array, *const *argv = NULL, *const *envp = NULL;
Py_ssize_t arg_num, num_groups = 0;
@@ -982,8 +984,6 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
sigset_t all_sigs;
sigfillset(&all_sigs);
if ((saved_errno = pthread_sigmask(SIG_BLOCK, &all_sigs, &old_sigs))) {
- errno = saved_errno;
- PyErr_SetFromErrno(PyExc_OSError);
goto cleanup;
}
old_sigmask = &old_sigs;
@@ -1022,49 +1022,33 @@ subprocess_fork_exec(PyObject* self, PyObject *args)
}
#endif
- Py_XDECREF(cwd_obj2);
-
if (need_after_fork)
PyOS_AfterFork_Parent();
- if (envp)
- _Py_FreeCharPArray(envp);
- if (argv)
- _Py_FreeCharPArray(argv);
- _Py_FreeCharPArray(exec_array);
- /* Reenable gc in the parent process (or if fork failed). */
- if (_enable_gc(need_to_reenable_gc, gc_module)) {
- pid = -1;
- }
- Py_XDECREF(preexec_fn_args_tuple);
- Py_XDECREF(gc_module);
-
- if (pid == -1) {
+cleanup:
+ if (saved_errno != 0) {
errno = saved_errno;
/* We can't call this above as PyOS_AfterFork_Parent() calls back
* into Python code which would see the unreturned error. */
PyErr_SetFromErrno(PyExc_OSError);
- return NULL; /* fork() failed. */
}
- return PyLong_FromPid(pid);
-
-cleanup:
+ Py_XDECREF(preexec_fn_args_tuple);
+ PyMem_RawFree(groups);
Py_XDECREF(cwd_obj2);
if (envp)
_Py_FreeCharPArray(envp);
+ Py_XDECREF(converted_args);
+ Py_XDECREF(fast_args);
if (argv)
_Py_FreeCharPArray(argv);
if (exec_array)
_Py_FreeCharPArray(exec_array);
- PyMem_RawFree(groups);
- Py_XDECREF(converted_args);
- Py_XDECREF(fast_args);
- Py_XDECREF(preexec_fn_args_tuple);
_enable_gc(need_to_reenable_gc, gc_module);
Py_XDECREF(gc_module);
- return NULL;
+
+ return pid == -1 ? NULL : PyLong_FromPid(pid);
}
--
2.23.0

View File

@ -0,0 +1,166 @@
From 34014f7bcfd422c7fd7d3077f9c65dc50e0210c9 Mon Sep 17 00:00:00 2001
From: Victor Stinner <vstinner@redhat.com>
Date: Thu, 30 Aug 2018 01:21:11 +0200
Subject: [PATCH] Fix TestPosixSpawn.test_close_file() (GH-8992)
Modify TestPosixSpawn to run Python using -I and -S options.
Disable site module to avoid side effects. For example, on Fedora 28,
if the HOME environment variable is not set, site._getuserbase()
calls pwd.getpwuid() which opens /var/lib/sss/mc/passwd, but then
leaves the file open which makes test_close_file() to fail.
Conflict:NA
Reference:https://github.com/python/cpython/commit/0382406fccbb31aa993de118b60e7fd4ec264968
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/test/test_posix.py | 64 ++++++++++++++++++++++--------------------
1 file changed, 34 insertions(+), 30 deletions(-)
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index e2cda33..77fedb1 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -1507,6 +1507,17 @@ class PosixGroupsTester(unittest.TestCase):
@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)
+ NOOP_PROGRAM = (sys.executable, '-I', '-S', '-c', 'pass')
+
+ def python_args(self, *args):
+ # Disable site module to avoid side effects. For example,
+ # on Fedora 28, if the HOME environment variable is not set,
+ # site._getuserbase() calls pwd.getpwuid() which opens
+ # /var/lib/sss/mc/passwd but then leaves the file open which makes
+ # test_close_file() to fail.
+ return (sys.executable, '-I', '-S', *args)
+
def test_returns_pid(self):
pidfile = support.TESTFN
self.addCleanup(support.unlink, pidfile)
@@ -1515,8 +1526,8 @@ class TestPosixSpawn(unittest.TestCase):
with open({pidfile!r}, "w") as pidfile:
pidfile.write(str(os.getpid()))
"""
- pid = posix.posix_spawn(sys.executable,
- [sys.executable, '-c', script],
+ args = self.python_args('-c', script)
+ pid = posix.posix_spawn(args[0], args,
os.environ)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(pidfile) as f:
@@ -1543,8 +1554,8 @@ class TestPosixSpawn(unittest.TestCase):
with open({envfile!r}, "w") as envfile:
envfile.write(os.environ['foo'])
"""
- pid = posix.posix_spawn(sys.executable,
- [sys.executable, '-c', script],
+ args = self.python_args('-c', script)
+ pid = posix.posix_spawn(args[0], args,
{**os.environ, 'foo': 'bar'})
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(envfile) as f:
@@ -1552,8 +1563,8 @@ class TestPosixSpawn(unittest.TestCase):
def test_empty_file_actions(self):
pid = posix.posix_spawn(
- sys.executable,
- [sys.executable, '-c', 'pass'],
+ self.NOOP_PROGRAM[0],
+ self.NOOP_PROGRAM,
os.environ,
[]
)
@@ -1706,43 +1717,36 @@ class TestPosixSpawn(unittest.TestCase):
(os.POSIX_SPAWN_CLOSE, 0),
(os.POSIX_SPAWN_DUP2, 1, 4),
]
- pid = posix.posix_spawn(sys.executable,
- [sys.executable, "-c", "pass"],
+ pid = posix.posix_spawn(self.NOOP_PROGRAM[0],
+ self.NOOP_PROGRAM,
os.environ, 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(sys.executable,
- [sys.executable, "-c", "pass"],
+ posix.posix_spawn(args[0], args,
os.environ, [None])
with self.assertRaises(TypeError):
- posix.posix_spawn(sys.executable,
- [sys.executable, "-c", "pass"],
+ posix.posix_spawn(args[0], args,
os.environ, [()])
with self.assertRaises(TypeError):
- posix.posix_spawn(sys.executable,
- [sys.executable, "-c", "pass"],
+ posix.posix_spawn(args[0], args,
os.environ, [(None,)])
with self.assertRaises(TypeError):
- posix.posix_spawn(sys.executable,
- [sys.executable, "-c", "pass"],
+ posix.posix_spawn(args[0], args,
os.environ, [(12345,)])
with self.assertRaises(TypeError):
- posix.posix_spawn(sys.executable,
- [sys.executable, "-c", "pass"],
+ posix.posix_spawn(args[0], args,
os.environ, [(os.POSIX_SPAWN_CLOSE,)])
with self.assertRaises(TypeError):
- posix.posix_spawn(sys.executable,
- [sys.executable, "-c", "pass"],
+ posix.posix_spawn(args[0], args,
os.environ, [(os.POSIX_SPAWN_CLOSE, 1, 2)])
with self.assertRaises(TypeError):
- posix.posix_spawn(sys.executable,
- [sys.executable, "-c", "pass"],
+ posix.posix_spawn(args[0], args,
os.environ, [(os.POSIX_SPAWN_CLOSE, None)])
with self.assertRaises(ValueError):
- posix.posix_spawn(sys.executable,
- [sys.executable, "-c", "pass"],
+ posix.posix_spawn(args[0], args,
os.environ,
[(os.POSIX_SPAWN_OPEN, 3, __file__ + '\0',
os.O_RDONLY, 0)])
@@ -1759,8 +1763,8 @@ class TestPosixSpawn(unittest.TestCase):
os.O_WRONLY | os.O_CREAT | os.O_TRUNC,
stat.S_IRUSR | stat.S_IWUSR),
]
- pid = posix.posix_spawn(sys.executable,
- [sys.executable, '-c', script],
+ args = self.python_args('-c', script)
+ pid = posix.posix_spawn(args[0], args,
os.environ, file_actions)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(outfile) as f:
@@ -1777,8 +1781,8 @@ class TestPosixSpawn(unittest.TestCase):
with open({closefile!r}, 'w') as closefile:
closefile.write('is closed %d' % e.errno)
"""
- pid = posix.posix_spawn(sys.executable,
- [sys.executable, '-c', script],
+ args = self.python_args('-c', script)
+ pid = posix.posix_spawn(args[0], args,
os.environ,
[(os.POSIX_SPAWN_CLOSE, 0),])
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
@@ -1796,8 +1800,8 @@ class TestPosixSpawn(unittest.TestCase):
file_actions = [
(os.POSIX_SPAWN_DUP2, childfile.fileno(), 1),
]
- pid = posix.posix_spawn(sys.executable,
- [sys.executable, '-c', script],
+ args = self.python_args('-c', script)
+ pid = posix.posix_spawn(args[0], args,
os.environ, file_actions)
self.assertEqual(os.waitpid(pid, 0), (pid, 0))
with open(dupfile) as f:
--
2.23.0

View File

@ -0,0 +1,91 @@
From 5b3807f6215fc2c5e14963bb48665426a53580f5 Mon Sep 17 00:00:00 2001
From: Giampaolo Rodola <g.rodola@gmail.com>
Date: Tue, 29 Jan 2019 22:14:24 +0100
Subject: [PATCH] subprocess: close pipes/fds by using ExitStack (GH-11686)
Close pipes/fds in subprocess by using ExitStack.
"In case of premature failure on X.Close() or os.close(X) the remaining pipes/fds will remain "open". Perhaps it makes sense to use contextlib.ExitStack."
- Rationale: https://github.com/python/cpython/pull/11575#discussion_r250288394
Conflict:NA
Reference:https://github.com/python/cpython/commit/bafa8487f77fa076de3a06755399daf81cb75598
Signed-off-by: hanxinke <hanxinke@huawei.com>
---
Lib/subprocess.py | 35 ++++++++++---------
.../2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst | 4 +++
2 files changed, 22 insertions(+), 17 deletions(-)
create mode 100644 Misc/NEWS.d/next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst
diff --git a/Lib/subprocess.py b/Lib/subprocess.py
index b02b701..332c19f 100644
--- a/Lib/subprocess.py
+++ b/Lib/subprocess.py
@@ -51,6 +51,7 @@ import signal
import builtins
import warnings
import errno
+import contextlib
from time import monotonic as _time
# Exception classes used by this module.
@@ -1092,28 +1093,28 @@ class Popen(object):
# self._devnull is not always defined.
devnull_fd = getattr(self, '_devnull', None)
- if _mswindows:
- if p2cread != -1:
- p2cread.Close()
- if c2pwrite != -1:
- c2pwrite.Close()
- if errwrite != -1:
- errwrite.Close()
- else:
- if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
- os.close(p2cread)
- if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
- os.close(c2pwrite)
- if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
- os.close(errwrite)
+ with contextlib.ExitStack() as stack:
+ if _mswindows:
+ if p2cread != -1:
+ stack.callback(p2cread.Close)
+ if c2pwrite != -1:
+ stack.callback(c2pwrite.Close)
+ if errwrite != -1:
+ stack.callback(errwrite.Close)
+ else:
+ if p2cread != -1 and p2cwrite != -1 and p2cread != devnull_fd:
+ stack.callback(os.close, p2cread)
+ if c2pwrite != -1 and c2pread != -1 and c2pwrite != devnull_fd:
+ stack.callback(os.close, c2pwrite)
+ if errwrite != -1 and errread != -1 and errwrite != devnull_fd:
+ stack.callback(os.close, errwrite)
- if devnull_fd is not None:
- os.close(devnull_fd)
+ if devnull_fd is not None:
+ stack.callback(os.close, devnull_fd)
# Prevent a double close of these handles/fds from __init__ on error.
self._closed_child_pipe_fds = True
-
if _mswindows:
#
# Windows methods
diff --git a/Misc/NEWS.d/next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst b/Misc/NEWS.d/next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst
new file mode 100644
index 0000000..2a9588e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-01-29-17-24-52.bpo-35537.Q0ktFC.rst
@@ -0,0 +1,4 @@
+An ExitStack is now used internally within subprocess.POpen to clean up pipe
+file handles. No behavior change in normal operation. But if closing one
+handle were ever to cause an exception, the others will now be closed
+instead of leaked. (patch by Giampaolo Rodola)
--
2.23.0

View File

@ -3,7 +3,7 @@ Summary: Interpreter of the Python3 programming language
URL: https://www.python.org/
Version: 3.7.9
Release: 11
Release: 12
License: Python
%global branchversion 3.7
@ -108,6 +108,39 @@ Patch320: CVE-2020-27619.patch
Patch6000: CVE-2021-3177.patch
Patch6001: backport-CVE-2021-23336.patch
Patch6003: backport-20104-Expose-posix_spawn-in-the-os-module-GH-510.patch
Patch6004: backport-20104-Fix-leaks-and-errors-in-new-os.posix_spawn.patch
Patch6005: backport-20104-Improve-error-handling-and-fix-a-reference.patch
Patch6006: backport-33630-Fix-using-of-freed-memory-in-old-versions-.patch
Patch6007: backport-33332-Add-signal.valid_signals-GH-6581.patch
Patch6008: backport-33441-Make-the-sigset_t-converter-available-in-o.patch
Patch6009: backport-20104-Add-flag-capabilities-to-posix_spawn-GH-66.patch
Patch6010: backport-33455-Pass-os.environ-in-test_posix-test_specify.patch
Patch6011: backport-Fix-TestPosixSpawn.test_close_file-GH-8992.patch
Patch6012: backport-20104-Change-the-file_actions-parameter-of-os.po.patch
Patch6013: backport-35537-subprocess-uses-os.posix_spawn-in-some-cas.patch
Patch6014: backport-35674-Add-os.posix_spawnp-GH-11554.patch
Patch6015: backport-35537-subprocess-can-use-posix_spawn-with-pipes-.patch
Patch6016: backport-subprocess-close-pipes-fds-by-using-ExitStack-GH-116.patch
Patch6017: backport-34862-Guard-definition-of-convert_sched_p.patch
Patch6018: backport-35537-Add-setsid-parameter-to-os.posix_spawn-and.patch
Patch6019: backport-35537-Skip-test_start_new_session-of-posix_spawn.patch
Patch6020: backport-35537-Fix-function-name-in-os.posix_spawnp-error.patch
Patch6021: backport-36814-ensure-os.posix_spawn-handles-None-GH-1314.patch
Patch6022: backport-35537-Rewrite-setsid-test-for-os.posix_spawn-GH-.patch
Patch6023: backport-36046-Add-user-and-group-parameters-to-subproces.patch
Patch6024: backport-36046-Fix-buildbot-failures-GH-16091.patch
Patch6025: backport-36046-posix_spawn-doesn-t-support-uid-gid-GH-163.patch
Patch6026: backport-38417-Add-umask-support-to-subprocess-GH-16726.patch
Patch6027: backport-38456-Use-bin-true-in-test_subprocess-GH-16736.patch
Patch6028: backport-38456-Handle-the-case-when-there-is-no-true-comm.patch
Patch6029: backport-39855-Fix-test_subprocess-if-nobody-user-doesn-t.patch
Patch6030: backport-35823-subprocess-Use-vfork-instead-of-fork-on-Li.patch
Patch6031: backport-35823-subprocess-Fix-handling-of-pthread_sigmask.patch
Patch6032: backport-35823-Allow-setsid-after-vfork-on-Linux.-GH-2294.patch
Patch6033: backport-42146-Fix-memory-leak-in-subprocess.Popen-in-cas.patch
Patch6034: backport-42146-Unify-cleanup-in-subprocess_fork_exec-GH-2.patch
Recommends: %{name}-help = %{version}-%{release}
Provides: python%{branchversion} = %{version}-%{release}
Provides: python(abi) = %{branchversion}
@ -201,6 +234,39 @@ rm Lib/ensurepip/_bundled/*.whl
%patch6000 -p1
%patch6001 -p1
%patch6003 -p1
%patch6004 -p1
%patch6005 -p1
%patch6006 -p1
%patch6007 -p1
%patch6008 -p1
%patch6009 -p1
%patch6010 -p1
%patch6011 -p1
%patch6012 -p1
%patch6013 -p1
%patch6014 -p1
%patch6015 -p1
%patch6016 -p1
%patch6017 -p1
%patch6018 -p1
%patch6019 -p1
%patch6020 -p1
%patch6021 -p1
%patch6022 -p1
%patch6023 -p1
%patch6024 -p1
%patch6025 -p1
%patch6026 -p1
%patch6027 -p1
%patch6028 -p1
%patch6029 -p1
%patch6030 -p1
%patch6031 -p1
%patch6032 -p1
%patch6033 -p1
%patch6034 -p1
sed -i "s/generic_os/%{_vendor}/g" Lib/platform.py
rm configure pyconfig.h.in
@ -801,6 +867,43 @@ export BEP_GTDLIST="$BEP_GTDLIST_TMP"
%{_mandir}/*/*
%changelog
* Tue May 25 2021 hanxinke<hanxinke@huawei.com> - 3.7.9-12
- Type:enhancement
- CVE:NA
- SUG:NA
- DESC:expose posix_spawn in os module
fix leaks and errors in new os.posix_spawn
improve error handing and fix a reference
fix using of freed memory in old versions
add signal.valid_signals
make the sigset_t converter available
add flag capabilities to posix_spawn
pass os.environ in test_posix test_specify
fix TestPosixSpawn.test_close_file
change the file_actions parameter of os.posix_spawn
subprocess uses os.posix_spawn in some case
add os.posix_spawnp
subprocess can use posix_spawn with pipes
subprocess close pipes fds by using ExitStack
guard definition of convert_sched_param with POSIX_SPAWN_SETSCHEDULER
add setsid parameter to os.posix_spawn() and os.posix_spawnp()
skip test_start_new_session() of posix_spawn
fix function name in os.posix_spawnp() errors
ensure os.posix_spawn() handles None
rewrite setsid test for os.posix_spawn
add user and group parameters to subproces
fix buildbot failures
posix_spawn doesn't support uid gid
add umask support to subprocess
use bin true in test_subprocess
handle the case when there is no 'true' command
fix test_subprocess if nobody user doesn't exist
subprocess: Use vfork() instead of fork() on Linux when safe
subprocess: Fix handling of pthread_sigmask() errors
allow setsid() after vfork() on Linux
fix memory leak in subprocess.Popen() in case of uid/gid overflow
Unify cleanup in subprocess_fork_exec()
* Mon May 24 2021 hehuazhen<hehuazhen@huawei.com> - 3.7.9-11
- Type:bugfix
- ID:NA