Skip to content

Commit dfc5c41

Browse files
arhadthedevgpshead
andauthored
gh-94518: Port 23-argument _posixsubprocess.fork_exec to Argument Clinic (#94519)
Convert fork_exec to pre-inlined-argparser Argument Clinic Co-authored-by: Gregory P. Smith <[email protected]>
1 parent 0421ed4 commit dfc5c41

File tree

3 files changed

+256
-66
lines changed

3 files changed

+256
-66
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Convert private :meth:`_posixsubprocess.fork_exec` to use Argument Clinic.

Modules/_posixsubprocess.c

Lines changed: 93 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,28 @@
7575

7676
static struct PyModuleDef _posixsubprocessmodule;
7777

78+
/*[clinic input]
79+
module _posixsubprocess
80+
[clinic start generated code]*/
81+
/*[clinic end generated code: output=da39a3ee5e6b4b0d input=c62211df27cf7334]*/
82+
83+
/*[python input]
84+
class pid_t_converter(CConverter):
85+
type = 'pid_t'
86+
format_unit = '" _Py_PARSE_PID "'
87+
88+
def parse_arg(self, argname, displayname):
89+
return """
90+
{paramname} = PyLong_AsPid({argname});
91+
if ({paramname} == -1 && PyErr_Occurred()) {{{{
92+
goto exit;
93+
}}}}
94+
""".format(argname=argname, paramname=self.parser_name)
95+
[python start generated code]*/
96+
/*[python end generated code: output=da39a3ee5e6b4b0d input=5af1c116d56cbb5a]*/
97+
98+
#include "clinic/_posixsubprocess.c.h"
99+
78100
/* Convert ASCII to a positive int, no libc call. no overflow. -1 on error. */
79101
static int
80102
_pos_int_from_ascii(const char *name)
@@ -744,7 +766,7 @@ do_fork_exec(char *const exec_array[],
744766
assert(preexec_fn == Py_None);
745767

746768
pid = vfork();
747-
if (pid == -1) {
769+
if (pid == (pid_t)-1) {
748770
/* If vfork() fails, fall back to using fork(). When it isn't
749771
* allowed in a process by the kernel, vfork can return -1
750772
* with errno EINVAL. https://bugs.python.org/issue47151. */
@@ -784,44 +806,81 @@ do_fork_exec(char *const exec_array[],
784806
return 0; /* Dead code to avoid a potential compiler warning. */
785807
}
786808

809+
/*[clinic input]
810+
_posixsubprocess.fork_exec as subprocess_fork_exec
811+
args as process_args: object
812+
executable_list: object
813+
close_fds: bool
814+
pass_fds as py_fds_to_keep: object(subclass_of='&PyTuple_Type')
815+
cwd as cwd_obj: object
816+
env as env_list: object
817+
p2cread: int
818+
p2cwrite: int
819+
c2pread: int
820+
c2pwrite: int
821+
errread: int
822+
errwrite: int
823+
errpipe_read: int
824+
errpipe_write: int
825+
restore_signals: bool
826+
call_setsid: bool
827+
pgid_to_set: pid_t
828+
gid as gid_object: object
829+
extra_groups as extra_groups_packed: object
830+
uid as uid_object: object
831+
child_umask: int
832+
preexec_fn: object
833+
allow_vfork: bool
834+
/
835+
836+
Spawn a fresh new child process.
837+
838+
Fork a child process, close parent file descriptors as appropriate in the
839+
child and duplicate the few that are needed before calling exec() in the
840+
child process.
841+
842+
If close_fds is True, close file descriptors 3 and higher, except those listed
843+
in the sorted tuple pass_fds.
844+
845+
The preexec_fn, if supplied, will be called immediately before closing file
846+
descriptors and exec.
847+
848+
WARNING: preexec_fn is NOT SAFE if your application uses threads.
849+
It may trigger infrequent, difficult to debug deadlocks.
850+
851+
If an error occurs in the child process before the exec, it is
852+
serialized and written to the errpipe_write fd per subprocess.py.
853+
854+
Returns: the child process's PID.
855+
856+
Raises: Only on an error in the parent process.
857+
[clinic start generated code]*/
787858

788859
static PyObject *
789-
subprocess_fork_exec(PyObject *module, PyObject *args)
860+
subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
861+
PyObject *executable_list, int close_fds,
862+
PyObject *py_fds_to_keep, PyObject *cwd_obj,
863+
PyObject *env_list, int p2cread, int p2cwrite,
864+
int c2pread, int c2pwrite, int errread,
865+
int errwrite, int errpipe_read, int errpipe_write,
866+
int restore_signals, int call_setsid,
867+
pid_t pgid_to_set, PyObject *gid_object,
868+
PyObject *extra_groups_packed,
869+
PyObject *uid_object, int child_umask,
870+
PyObject *preexec_fn, int allow_vfork)
871+
/*[clinic end generated code: output=7ee4f6ee5cf22b5b input=51757287ef266ffa]*/
790872
{
791-
PyObject *gc_module = NULL;
792-
PyObject *executable_list, *py_fds_to_keep;
793-
PyObject *env_list, *preexec_fn;
794-
PyObject *process_args, *converted_args = NULL, *fast_args = NULL;
873+
PyObject *converted_args = NULL, *fast_args = NULL;
795874
PyObject *preexec_fn_args_tuple = NULL;
796-
PyObject *extra_groups_packed;
797-
PyObject *uid_object, *gid_object;
798-
int p2cread, p2cwrite, c2pread, c2pwrite, errread, errwrite;
799-
int errpipe_read, errpipe_write, close_fds, restore_signals;
800-
int call_setsid;
801-
pid_t pgid_to_set = -1;
802875
gid_t *extra_groups = NULL;
803-
int child_umask;
804-
PyObject *cwd_obj, *cwd_obj2 = NULL;
805-
const char *cwd;
876+
PyObject *cwd_obj2 = NULL;
877+
const char *cwd = NULL;
806878
pid_t pid = -1;
807879
int need_to_reenable_gc = 0;
808-
char *const *exec_array, *const *argv = NULL, *const *envp = NULL;
809-
Py_ssize_t arg_num, extra_group_size = 0;
880+
char *const *argv = NULL, *const *envp = NULL;
881+
Py_ssize_t extra_group_size = 0;
810882
int need_after_fork = 0;
811883
int saved_errno = 0;
812-
int allow_vfork;
813-
814-
if (!PyArg_ParseTuple(
815-
args, "OOpO!OOiiiiiiiipp" _Py_PARSE_PID "OOOiOp:fork_exec",
816-
&process_args, &executable_list,
817-
&close_fds, &PyTuple_Type, &py_fds_to_keep,
818-
&cwd_obj, &env_list,
819-
&p2cread, &p2cwrite, &c2pread, &c2pwrite,
820-
&errread, &errwrite, &errpipe_read, &errpipe_write,
821-
&restore_signals, &call_setsid, &pgid_to_set,
822-
&gid_object, &extra_groups_packed, &uid_object, &child_umask,
823-
&preexec_fn, &allow_vfork))
824-
return NULL;
825884

826885
PyInterpreterState *interp = PyInterpreterState_Get();
827886
if ((preexec_fn != Py_None) && (interp != PyInterpreterState_Main())) {
@@ -844,7 +903,7 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
844903
need_to_reenable_gc = PyGC_Disable();
845904
}
846905

847-
exec_array = _PySequence_BytesToCharpArray(executable_list);
906+
char *const *exec_array = _PySequence_BytesToCharpArray(executable_list);
848907
if (!exec_array)
849908
goto cleanup;
850909

@@ -862,7 +921,7 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
862921
converted_args = PyTuple_New(num_args);
863922
if (converted_args == NULL)
864923
goto cleanup;
865-
for (arg_num = 0; arg_num < num_args; ++arg_num) {
924+
for (Py_ssize_t arg_num = 0; arg_num < num_args; ++arg_num) {
866925
PyObject *borrowed_arg, *converted_arg;
867926
if (PySequence_Fast_GET_SIZE(fast_args) != num_args) {
868927
PyErr_SetString(PyExc_RuntimeError, "args changed during iteration");
@@ -891,8 +950,6 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
891950
if (PyUnicode_FSConverter(cwd_obj, &cwd_obj2) == 0)
892951
goto cleanup;
893952
cwd = PyBytes_AsString(cwd_obj2);
894-
} else {
895-
cwd = NULL;
896953
}
897954

898955
if (extra_groups_packed != Py_None) {
@@ -1019,7 +1076,7 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
10191076
py_fds_to_keep, preexec_fn, preexec_fn_args_tuple);
10201077

10211078
/* Parent (original) process */
1022-
if (pid == -1) {
1079+
if (pid == (pid_t)-1) {
10231080
/* Capture errno for the exception. */
10241081
saved_errno = errno;
10251082
}
@@ -1068,47 +1125,17 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
10681125
if (need_to_reenable_gc) {
10691126
PyGC_Enable();
10701127
}
1071-
Py_XDECREF(gc_module);
10721128

10731129
return pid == -1 ? NULL : PyLong_FromPid(pid);
10741130
}
10751131

1076-
1077-
PyDoc_STRVAR(subprocess_fork_exec_doc,
1078-
"fork_exec(args, executable_list, close_fds, pass_fds, cwd, env,\n\
1079-
p2cread, p2cwrite, c2pread, c2pwrite,\n\
1080-
errread, errwrite, errpipe_read, errpipe_write,\n\
1081-
restore_signals, call_setsid, pgid_to_set,\n\
1082-
gid, extra_groups, uid,\n\
1083-
preexec_fn)\n\
1084-
\n\
1085-
Forks a child process, closes parent file descriptors as appropriate in the\n\
1086-
child and dups the few that are needed before calling exec() in the child\n\
1087-
process.\n\
1088-
\n\
1089-
If close_fds is true, close file descriptors 3 and higher, except those listed\n\
1090-
in the sorted tuple pass_fds.\n\
1091-
\n\
1092-
The preexec_fn, if supplied, will be called immediately before closing file\n\
1093-
descriptors and exec.\n\
1094-
WARNING: preexec_fn is NOT SAFE if your application uses threads.\n\
1095-
It may trigger infrequent, difficult to debug deadlocks.\n\
1096-
\n\
1097-
If an error occurs in the child process before the exec, it is\n\
1098-
serialized and written to the errpipe_write fd per subprocess.py.\n\
1099-
\n\
1100-
Returns: the child process's PID.\n\
1101-
\n\
1102-
Raises: Only on an error in the parent process.\n\
1103-
");
1104-
11051132
/* module level code ********************************************************/
11061133

11071134
PyDoc_STRVAR(module_doc,
11081135
"A POSIX helper for the subprocess module.");
11091136

11101137
static PyMethodDef module_methods[] = {
1111-
{"fork_exec", subprocess_fork_exec, METH_VARARGS, subprocess_fork_exec_doc},
1138+
SUBPROCESS_FORK_EXEC_METHODDEF
11121139
{NULL, NULL} /* sentinel */
11131140
};
11141141

Modules/clinic/_posixsubprocess.c.h

Lines changed: 162 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)