From a4faef5368c5a15a2a4bb83aee451708ed622aee Mon Sep 17 00:00:00 2001 From: Pablo Galindo 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 --- 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 +#endif + #ifdef HAVE_UTIME_H #include #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