diff options
author | Pablo Galindo <Pablogsal@gmail.com> | 2018-01-29 01:56:10 (GMT) |
---|---|---|
committer | Gregory P. Smith <greg@krypto.org> | 2018-01-29 01:56:10 (GMT) |
commit | 6c6ddf97c402709713d668d0ed53836a7749ba99 (patch) | |
tree | e403bfbc134daea26026db43b207bbdb203019fa | |
parent | f5b04a360e44aa9733f7a92dd66d2292d6c52955 (diff) | |
download | cpython-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.py | 17 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Core and Builtins/2018-01-06-01-14-53.bpo-20104.9DkKb8.rst | 1 | ||||
-rw-r--r-- | Modules/clinic/posixmodule.c.h | 54 | ||||
-rw-r--r-- | Modules/posixmodule.c | 202 | ||||
-rwxr-xr-x | configure | 2 | ||||
-rw-r--r-- | configure.ac | 2 | ||||
-rw-r--r-- | pyconfig.h.in | 3 |
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; @@ -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 |