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.
This commit is contained in:
parent
f5b04a360e
commit
6c6ddf97c4
|
@ -176,6 +176,23 @@ class PosixTester(unittest.TestCase):
|
||||||
finally:
|
finally:
|
||||||
os.close(fp)
|
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(posix, 'waitid'), "test needs posix.waitid()")
|
||||||
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
|
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
|
||||||
def test_waitid(self):
|
def test_waitid(self):
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
Expose posix_spawn as a low level API in the os module.
|
|
@ -1721,6 +1721,54 @@ exit:
|
||||||
|
|
||||||
#endif /* defined(HAVE_EXECV) */
|
#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))
|
#if (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV))
|
||||||
|
|
||||||
PyDoc_STRVAR(os_spawnv__doc__,
|
PyDoc_STRVAR(os_spawnv__doc__,
|
||||||
|
@ -6137,6 +6185,10 @@ exit:
|
||||||
#define OS_EXECVE_METHODDEF
|
#define OS_EXECVE_METHODDEF
|
||||||
#endif /* !defined(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
|
#ifndef OS_SPAWNV_METHODDEF
|
||||||
#define OS_SPAWNV_METHODDEF
|
#define OS_SPAWNV_METHODDEF
|
||||||
#endif /* !defined(OS_SPAWNV_METHODDEF) */
|
#endif /* !defined(OS_SPAWNV_METHODDEF) */
|
||||||
|
@ -6528,4 +6580,4 @@ exit:
|
||||||
#ifndef OS_GETRANDOM_METHODDEF
|
#ifndef OS_GETRANDOM_METHODDEF
|
||||||
#define OS_GETRANDOM_METHODDEF
|
#define OS_GETRANDOM_METHODDEF
|
||||||
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
|
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
|
||||||
/*[clinic end generated code: output=06ace805893aa10c input=a9049054013a1b77]*/
|
/*[clinic end generated code: output=8e5d4a01257b6292 input=a9049054013a1b77]*/
|
||||||
|
|
|
@ -176,6 +176,7 @@ corresponding Unix manual entries for more information on calls.");
|
||||||
#else
|
#else
|
||||||
/* Unix functions that the configure script doesn't check for */
|
/* Unix functions that the configure script doesn't check for */
|
||||||
#define HAVE_EXECV 1
|
#define HAVE_EXECV 1
|
||||||
|
#define HAVE_POSIX_SPAWN 1
|
||||||
#define HAVE_FORK 1
|
#define HAVE_FORK 1
|
||||||
#if defined(__USLC__) && defined(__SCO_VERSION__) /* SCO UDK Compiler */
|
#if defined(__USLC__) && defined(__SCO_VERSION__) /* SCO UDK Compiler */
|
||||||
#define HAVE_FORK1 1
|
#define HAVE_FORK1 1
|
||||||
|
@ -246,6 +247,10 @@ extern int lstat(const char *, struct stat *);
|
||||||
|
|
||||||
#endif /* !_MSC_VER */
|
#endif /* !_MSC_VER */
|
||||||
|
|
||||||
|
#ifdef HAVE_POSIX_SPAWN
|
||||||
|
#include <spawn.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_UTIME_H
|
#ifdef HAVE_UTIME_H
|
||||||
#include <utime.h>
|
#include <utime.h>
|
||||||
#endif /* HAVE_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 */
|
#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)
|
#if defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)
|
||||||
/*[clinic input]
|
/*[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);
|
return Py_BuildValue(_Py_PARSE_INTPTR, spawnval);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*[clinic input]
|
/*[clinic input]
|
||||||
os.spawnve
|
os.spawnve
|
||||||
|
|
||||||
|
@ -12610,6 +12802,7 @@ static PyMethodDef posix_methods[] = {
|
||||||
OS_NICE_METHODDEF
|
OS_NICE_METHODDEF
|
||||||
OS_GETPRIORITY_METHODDEF
|
OS_GETPRIORITY_METHODDEF
|
||||||
OS_SETPRIORITY_METHODDEF
|
OS_SETPRIORITY_METHODDEF
|
||||||
|
OS_POSIX_SPAWN_METHODDEF
|
||||||
#ifdef HAVE_READLINK
|
#ifdef HAVE_READLINK
|
||||||
{"readlink", (PyCFunction)posix_readlink,
|
{"readlink", (PyCFunction)posix_readlink,
|
||||||
METH_VARARGS | METH_KEYWORDS,
|
METH_VARARGS | METH_KEYWORDS,
|
||||||
|
@ -13164,6 +13357,13 @@ all_ins(PyObject *m)
|
||||||
if (PyModule_AddIntConstant(m, "RWF_NOWAIT", RWF_NOWAIT)) return -1;
|
if (PyModule_AddIntConstant(m, "RWF_NOWAIT", RWF_NOWAIT)) return -1;
|
||||||
#endif
|
#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
|
#ifdef HAVE_SPAWNV
|
||||||
if (PyModule_AddIntConstant(m, "P_WAIT", _P_WAIT)) return -1;
|
if (PyModule_AddIntConstant(m, "P_WAIT", _P_WAIT)) return -1;
|
||||||
if (PyModule_AddIntConstant(m, "P_NOWAIT", _P_NOWAIT)) 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 \
|
initgroups kill killpg lchmod lchown lockf linkat lstat lutimes mmap \
|
||||||
memrchr mbrtowc mkdirat mkfifo \
|
memrchr mbrtowc mkdirat mkfifo \
|
||||||
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
|
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 \
|
pthread_init pthread_kill putenv pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
|
||||||
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
|
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
|
||||||
setgid sethostname \
|
setgid sethostname \
|
||||||
|
|
|
@ -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 \
|
initgroups kill killpg lchmod lchown lockf linkat lstat lutimes mmap \
|
||||||
memrchr mbrtowc mkdirat mkfifo \
|
memrchr mbrtowc mkdirat mkfifo \
|
||||||
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
|
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 \
|
pthread_init pthread_kill putenv pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
|
||||||
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
|
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
|
||||||
setgid sethostname \
|
setgid sethostname \
|
||||||
|
|
|
@ -707,6 +707,9 @@
|
||||||
/* Define to 1 if you have the `posix_fallocate' function. */
|
/* Define to 1 if you have the `posix_fallocate' function. */
|
||||||
#undef HAVE_POSIX_FALLOCATE
|
#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. */
|
/* Define to 1 if you have the `pread' function. */
|
||||||
#undef HAVE_PREAD
|
#undef HAVE_PREAD
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue