summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPablo Galindo <Pablogsal@gmail.com>2018-01-29 01:56:10 (GMT)
committerGregory P. Smith <greg@krypto.org>2018-01-29 01:56:10 (GMT)
commit6c6ddf97c402709713d668d0ed53836a7749ba99 (patch)
treee403bfbc134daea26026db43b207bbdb203019fa
parentf5b04a360e44aa9733f7a92dd66d2292d6c52955 (diff)
downloadcpython-6c6ddf97c402709713d668d0ed53836a7749ba99.zip
cpython-6c6ddf97c402709713d668d0ed53836a7749ba99.tar.gz
cpython-6c6ddf97c402709713d668d0ed53836a7749ba99.tar.bz2
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.
-rw-r--r--Lib/test/test_posix.py17
-rw-r--r--Misc/NEWS.d/next/Core and Builtins/2018-01-06-01-14-53.bpo-20104.9DkKb8.rst1
-rw-r--r--Modules/clinic/posixmodule.c.h54
-rw-r--r--Modules/posixmodule.c202
-rwxr-xr-xconfigure2
-rw-r--r--configure.ac2
-rw-r--r--pyconfig.h.in3
7 files changed, 277 insertions, 4 deletions
diff --git a/Lib/test/test_posix.py b/Lib/test/test_posix.py
index a7f3d34..8ada0e3 100644
--- a/Lib/test/test_posix.py
+++ b/Lib/test/test_posix.py
@@ -176,6 +176,23 @@ class PosixTester(unittest.TestCase):
finally:
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 f432437..d6af15f 100644
--- a/Modules/clinic/posixmodule.c.h
+++ b/Modules/clinic/posixmodule.c.h
@@ -1721,6 +1721,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__,
@@ -6137,6 +6185,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) */
@@ -6528,4 +6580,4 @@ exit:
#ifndef OS_GETRANDOM_METHODDEF
#define OS_GETRANDOM_METHODDEF
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
-/*[clinic end generated code: output=06ace805893aa10c input=a9049054013a1b77]*/
+/*[clinic end generated code: output=8e5d4a01257b6292 input=a9049054013a1b77]*/
diff --git a/Modules/posixmodule.c b/Modules/posixmodule.c
index ceea855..4c0392e 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 */
@@ -5097,6 +5102,194 @@ 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]
@@ -5189,7 +5382,6 @@ os_spawnv_impl(PyObject *module, int mode, path_t *path, PyObject *argv)
return Py_BuildValue(_Py_PARSE_INTPTR, spawnval);
}
-
/*[clinic input]
os.spawnve
@@ -12610,6 +12802,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,
@@ -13164,6 +13357,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;
diff --git a/configure b/configure
index 6ce0728a..f94d16b 100755
--- a/configure
+++ b/configure
@@ -11197,7 +11197,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
initgroups kill killpg lchmod 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 pread preadv preadv2 \
+ posix_fallocate posix_fadvise posix_spawn 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 b901f80..6524863 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3431,7 +3431,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
initgroups kill killpg lchmod 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 pread preadv preadv2 \
+ posix_fallocate posix_fadvise posix_spawn 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 27fe136..a18e3ca 100644
--- a/pyconfig.h.in
+++ b/pyconfig.h.in
@@ -707,6 +707,9 @@
/* Define to 1 if you have the `posix_fallocate' function. */
#undef HAVE_POSIX_FALLOCATE
+/* Define to 1 if you have the `posix_spawn' function. */
+#undef HAVE_POSIX_SPAWN
+
/* Define to 1 if you have the `pread' function. */
#undef HAVE_PREAD