Skip to content

Commit 6c6ddf9

Browse files
pablogsalgpshead
authored andcommitted
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.
1 parent f5b04a3 commit 6c6ddf9

File tree

7 files changed

+277
-4
lines changed

7 files changed

+277
-4
lines changed

Lib/test/test_posix.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,23 @@ def test_fexecve(self):
176176
finally:
177177
os.close(fp)
178178

179+
180+
@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
181+
def test_posix_spawn(self):
182+
pid = posix.posix_spawn(sys.executable, [sys.executable, "-c", "pass"], os.environ,[])
183+
self.assertEqual(os.waitpid(pid,0),(pid,0))
184+
185+
186+
@unittest.skipUnless(hasattr(os, 'posix_spawn'), "test needs os.posix_spawn")
187+
def test_posix_spawn_file_actions(self):
188+
file_actions = []
189+
file_actions.append((0,3,os.path.realpath(__file__),0,0))
190+
file_actions.append((os.POSIX_SPAWN_CLOSE,2))
191+
file_actions.append((os.POSIX_SPAWN_DUP2,1,4))
192+
pid = posix.posix_spawn(sys.executable, [sys.executable, "-c", "pass"], os.environ, file_actions)
193+
self.assertEqual(os.waitpid(pid,0),(pid,0))
194+
195+
179196
@unittest.skipUnless(hasattr(posix, 'waitid'), "test needs posix.waitid()")
180197
@unittest.skipUnless(hasattr(os, 'fork'), "test needs os.fork()")
181198
def test_waitid(self):
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Expose posix_spawn as a low level API in the os module.

Modules/clinic/posixmodule.c.h

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1721,6 +1721,54 @@ os_execve(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *k
17211721

17221722
#endif /* defined(HAVE_EXECV) */
17231723

1724+
#if defined(HAVE_POSIX_SPAWN)
1725+
1726+
PyDoc_STRVAR(os_posix_spawn__doc__,
1727+
"posix_spawn($module, path, argv, env, file_actions=None, /)\n"
1728+
"--\n"
1729+
"\n"
1730+
"Execute the program specified by path in a new process.\n"
1731+
"\n"
1732+
" path\n"
1733+
" Path of executable file.\n"
1734+
" argv\n"
1735+
" Tuple or list of strings.\n"
1736+
" env\n"
1737+
" Dictionary of strings mapping to strings.\n"
1738+
" file_actions\n"
1739+
" FileActions object.");
1740+
1741+
#define OS_POSIX_SPAWN_METHODDEF \
1742+
{"posix_spawn", (PyCFunction)os_posix_spawn, METH_FASTCALL, os_posix_spawn__doc__},
1743+
1744+
static PyObject *
1745+
os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
1746+
PyObject *env, PyObject *file_actions);
1747+
1748+
static PyObject *
1749+
os_posix_spawn(PyObject *module, PyObject *const *args, Py_ssize_t nargs)
1750+
{
1751+
PyObject *return_value = NULL;
1752+
path_t path = PATH_T_INITIALIZE("posix_spawn", "path", 0, 0);
1753+
PyObject *argv;
1754+
PyObject *env;
1755+
PyObject *file_actions = Py_None;
1756+
1757+
if (!_PyArg_ParseStack(args, nargs, "O&OO|O:posix_spawn",
1758+
path_converter, &path, &argv, &env, &file_actions)) {
1759+
goto exit;
1760+
}
1761+
return_value = os_posix_spawn_impl(module, &path, argv, env, file_actions);
1762+
1763+
exit:
1764+
/* Cleanup for path */
1765+
path_cleanup(&path);
1766+
1767+
return return_value;
1768+
}
1769+
1770+
#endif /* defined(HAVE_POSIX_SPAWN) */
1771+
17241772
#if (defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV))
17251773

17261774
PyDoc_STRVAR(os_spawnv__doc__,
@@ -6137,6 +6185,10 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
61376185
#define OS_EXECVE_METHODDEF
61386186
#endif /* !defined(OS_EXECVE_METHODDEF) */
61396187

6188+
#ifndef OS_POSIX_SPAWN_METHODDEF
6189+
#define OS_POSIX_SPAWN_METHODDEF
6190+
#endif /* !defined(OS_POSIX_SPAWN_METHODDEF) */
6191+
61406192
#ifndef OS_SPAWNV_METHODDEF
61416193
#define OS_SPAWNV_METHODDEF
61426194
#endif /* !defined(OS_SPAWNV_METHODDEF) */
@@ -6528,4 +6580,4 @@ os_getrandom(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject
65286580
#ifndef OS_GETRANDOM_METHODDEF
65296581
#define OS_GETRANDOM_METHODDEF
65306582
#endif /* !defined(OS_GETRANDOM_METHODDEF) */
6531-
/*[clinic end generated code: output=06ace805893aa10c input=a9049054013a1b77]*/
6583+
/*[clinic end generated code: output=8e5d4a01257b6292 input=a9049054013a1b77]*/

Modules/posixmodule.c

Lines changed: 201 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,7 @@ corresponding Unix manual entries for more information on calls.");
176176
#else
177177
/* Unix functions that the configure script doesn't check for */
178178
#define HAVE_EXECV 1
179+
#define HAVE_POSIX_SPAWN 1
179180
#define HAVE_FORK 1
180181
#if defined(__USLC__) && defined(__SCO_VERSION__) /* SCO UDK Compiler */
181182
#define HAVE_FORK1 1
@@ -246,6 +247,10 @@ extern int lstat(const char *, struct stat *);
246247

247248
#endif /* !_MSC_VER */
248249

250+
#ifdef HAVE_POSIX_SPAWN
251+
#include <spawn.h>
252+
#endif
253+
249254
#ifdef HAVE_UTIME_H
250255
#include <utime.h>
251256
#endif /* HAVE_UTIME_H */
@@ -5097,6 +5102,194 @@ os_execve_impl(PyObject *module, path_t *path, PyObject *argv, PyObject *env)
50975102

50985103
#endif /* HAVE_EXECV */
50995104

5105+
#ifdef HAVE_POSIX_SPAWN
5106+
5107+
enum posix_spawn_file_actions_identifier {
5108+
POSIX_SPAWN_OPEN,
5109+
POSIX_SPAWN_CLOSE,
5110+
POSIX_SPAWN_DUP2
5111+
};
5112+
5113+
/*[clinic input]
5114+
5115+
os.posix_spawn
5116+
path: path_t
5117+
Path of executable file.
5118+
argv: object
5119+
Tuple or list of strings.
5120+
env: object
5121+
Dictionary of strings mapping to strings.
5122+
file_actions: object = None
5123+
FileActions object.
5124+
/
5125+
5126+
Execute the program specified by path in a new process.
5127+
[clinic start generated code]*/
5128+
5129+
static PyObject *
5130+
os_posix_spawn_impl(PyObject *module, path_t *path, PyObject *argv,
5131+
PyObject *env, PyObject *file_actions)
5132+
/*[clinic end generated code: output=d023521f541c709c input=0ec9f1cfdc890be5]*/
5133+
{
5134+
EXECV_CHAR **argvlist = NULL;
5135+
EXECV_CHAR **envlist;
5136+
Py_ssize_t argc, envc;
5137+
5138+
/* posix_spawn has three arguments: (path, argv, env), where
5139+
argv is a list or tuple of strings and env is a dictionary
5140+
like posix.environ. */
5141+
5142+
if (!PySequence_Check(argv)){
5143+
PyErr_SetString(PyExc_TypeError,
5144+
"posix_spawn: argv must be a tuple or list");
5145+
goto fail;
5146+
}
5147+
argc = PySequence_Size(argv);
5148+
if (argc < 1) {
5149+
PyErr_SetString(PyExc_ValueError, "posix_spawn: argv must not be empty");
5150+
return NULL;
5151+
}
5152+
5153+
if (!PyMapping_Check(env)) {
5154+
PyErr_SetString(PyExc_TypeError,
5155+
"posix_spawn: environment must be a mapping object");
5156+
goto fail;
5157+
}
5158+
5159+
argvlist = parse_arglist(argv, &argc);
5160+
if (argvlist == NULL) {
5161+
goto fail;
5162+
}
5163+
if (!argvlist[0][0]) {
5164+
PyErr_SetString(PyExc_ValueError,
5165+
"posix_spawn: argv first element cannot be empty");
5166+
goto fail;
5167+
}
5168+
5169+
envlist = parse_envlist(env, &envc);
5170+
if (envlist == NULL)
5171+
goto fail;
5172+
5173+
pid_t pid;
5174+
posix_spawn_file_actions_t *file_actionsp = NULL;
5175+
if (file_actions != NULL && file_actions != Py_None){
5176+
posix_spawn_file_actions_t _file_actions;
5177+
if(posix_spawn_file_actions_init(&_file_actions) != 0){
5178+
PyErr_SetString(PyExc_TypeError,
5179+
"Error initializing file actions");
5180+
goto fail;
5181+
}
5182+
5183+
5184+
file_actionsp = &_file_actions;
5185+
5186+
5187+
PyObject* seq = PySequence_Fast(file_actions, "file_actions must be a sequence");
5188+
if(seq == NULL){
5189+
goto fail;
5190+
}
5191+
PyObject* file_actions_obj;
5192+
PyObject* mode_obj;
5193+
5194+
for (int i = 0; i < PySequence_Fast_GET_SIZE(seq); ++i) {
5195+
file_actions_obj = PySequence_Fast_GET_ITEM(seq, i);
5196+
5197+
if(!PySequence_Check(file_actions_obj) | !PySequence_Size(file_actions_obj)){
5198+
PyErr_SetString(PyExc_TypeError,"Each file_action element must be a non empty sequence");
5199+
goto fail;
5200+
}
5201+
5202+
5203+
mode_obj = PySequence_Fast_GET_ITEM(file_actions_obj, 0);
5204+
int mode = PyLong_AsLong(mode_obj);
5205+
5206+
/* Populate the file_actions object */
5207+
5208+
switch(mode) {
5209+
5210+
case POSIX_SPAWN_OPEN:
5211+
if(PySequence_Size(file_actions_obj) != 5){
5212+
PyErr_SetString(PyExc_TypeError,"A open file_action object must have 5 elements");
5213+
goto fail;
5214+
}
5215+
5216+
long open_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
5217+
if(PyErr_Occurred()) {
5218+
goto fail;
5219+
}
5220+
const char* open_path = PyUnicode_AsUTF8(PySequence_GetItem(file_actions_obj, 2));
5221+
if(open_path == NULL){
5222+
goto fail;
5223+
}
5224+
long open_oflag = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 3));
5225+
if(PyErr_Occurred()) {
5226+
goto fail;
5227+
}
5228+
long open_mode = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 4));
5229+
if(PyErr_Occurred()) {
5230+
goto fail;
5231+
}
5232+
posix_spawn_file_actions_addopen(file_actionsp, open_fd, open_path, open_oflag, open_mode);
5233+
break;
5234+
5235+
case POSIX_SPAWN_CLOSE:
5236+
if(PySequence_Size(file_actions_obj) != 2){
5237+
PyErr_SetString(PyExc_TypeError,"A close file_action object must have 2 elements");
5238+
goto fail;
5239+
}
5240+
5241+
long close_fd = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
5242+
if(PyErr_Occurred()) {
5243+
goto fail;
5244+
}
5245+
posix_spawn_file_actions_addclose(file_actionsp, close_fd);
5246+
break;
5247+
5248+
case POSIX_SPAWN_DUP2:
5249+
if(PySequence_Size(file_actions_obj) != 3){
5250+
PyErr_SetString(PyExc_TypeError,"A dup2 file_action object must have 3 elements");
5251+
goto fail;
5252+
}
5253+
5254+
long fd1 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 1));
5255+
if(PyErr_Occurred()) {
5256+
goto fail;
5257+
}
5258+
long fd2 = PyLong_AsLong(PySequence_GetItem(file_actions_obj, 2));
5259+
if(PyErr_Occurred()) {
5260+
goto fail;
5261+
}
5262+
posix_spawn_file_actions_adddup2(file_actionsp, fd1, fd2);
5263+
break;
5264+
5265+
default:
5266+
PyErr_SetString(PyExc_TypeError,"Unknown file_actions identifier");
5267+
goto fail;
5268+
}
5269+
}
5270+
Py_DECREF(seq);
5271+
}
5272+
5273+
_Py_BEGIN_SUPPRESS_IPH
5274+
posix_spawn(&pid, path->narrow, file_actionsp, NULL, argvlist, envlist);
5275+
return PyLong_FromPid(pid);
5276+
_Py_END_SUPPRESS_IPH
5277+
5278+
path_error(path);
5279+
5280+
free_string_array(envlist, envc);
5281+
5282+
fail:
5283+
5284+
if (argvlist) {
5285+
free_string_array(argvlist, argc);
5286+
}
5287+
return NULL;
5288+
5289+
5290+
}
5291+
#endif /* HAVE_POSIX_SPAWN */
5292+
51005293

51015294
#if defined(HAVE_SPAWNV) || defined(HAVE_WSPAWNV)
51025295
/*[clinic input]
@@ -5189,7 +5382,6 @@ os_spawnv_impl(PyObject *module, int mode, path_t *path, PyObject *argv)
51895382
return Py_BuildValue(_Py_PARSE_INTPTR, spawnval);
51905383
}
51915384

5192-
51935385
/*[clinic input]
51945386
os.spawnve
51955387
@@ -12610,6 +12802,7 @@ static PyMethodDef posix_methods[] = {
1261012802
OS_NICE_METHODDEF
1261112803
OS_GETPRIORITY_METHODDEF
1261212804
OS_SETPRIORITY_METHODDEF
12805+
OS_POSIX_SPAWN_METHODDEF
1261312806
#ifdef HAVE_READLINK
1261412807
{"readlink", (PyCFunction)posix_readlink,
1261512808
METH_VARARGS | METH_KEYWORDS,
@@ -13164,6 +13357,13 @@ all_ins(PyObject *m)
1316413357
if (PyModule_AddIntConstant(m, "RWF_NOWAIT", RWF_NOWAIT)) return -1;
1316513358
#endif
1316613359

13360+
/* constants for posix_spawn */
13361+
#ifdef HAVE_POSIX_SPAWN
13362+
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_OPEN", POSIX_SPAWN_OPEN)) return -1;
13363+
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_CLOSE", POSIX_SPAWN_CLOSE)) return -1;
13364+
if (PyModule_AddIntConstant(m, "POSIX_SPAWN_DUP2", POSIX_SPAWN_DUP2)) return -1;
13365+
#endif
13366+
1316713367
#ifdef HAVE_SPAWNV
1316813368
if (PyModule_AddIntConstant(m, "P_WAIT", _P_WAIT)) return -1;
1316913369
if (PyModule_AddIntConstant(m, "P_NOWAIT", _P_NOWAIT)) return -1;

configure

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11197,7 +11197,7 @@ for ac_func in alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
1119711197
initgroups kill killpg lchmod lchown lockf linkat lstat lutimes mmap \
1119811198
memrchr mbrtowc mkdirat mkfifo \
1119911199
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
11200-
posix_fallocate posix_fadvise pread preadv preadv2 \
11200+
posix_fallocate posix_fadvise posix_spawn pread preadv preadv2 \
1120111201
pthread_init pthread_kill putenv pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
1120211202
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
1120311203
setgid sethostname \

configure.ac

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3431,7 +3431,7 @@ AC_CHECK_FUNCS(alarm accept4 setitimer getitimer bind_textdomain_codeset chown \
34313431
initgroups kill killpg lchmod lchown lockf linkat lstat lutimes mmap \
34323432
memrchr mbrtowc mkdirat mkfifo \
34333433
mkfifoat mknod mknodat mktime mremap nice openat pathconf pause pipe2 plock poll \
3434-
posix_fallocate posix_fadvise pread preadv preadv2 \
3434+
posix_fallocate posix_fadvise posix_spawn pread preadv preadv2 \
34353435
pthread_init pthread_kill putenv pwrite pwritev pwritev2 readlink readlinkat readv realpath renameat \
34363436
sem_open sem_timedwait sem_getvalue sem_unlink sendfile setegid seteuid \
34373437
setgid sethostname \

pyconfig.h.in

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,9 @@
707707
/* Define to 1 if you have the `posix_fallocate' function. */
708708
#undef HAVE_POSIX_FALLOCATE
709709

710+
/* Define to 1 if you have the `posix_spawn' function. */
711+
#undef HAVE_POSIX_SPAWN
712+
710713
/* Define to 1 if you have the `pread' function. */
711714
#undef HAVE_PREAD
712715

0 commit comments

Comments
 (0)