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

