@@ -138,16 +138,17 @@ _sanity_check_python_fd_sequence(PyObject *fd_sequence)
138138
139139/* Is fd found in the sorted Python Sequence? */
140140static int
141- _is_fd_in_sorted_fd_sequence (int fd , PyObject * fd_sequence )
141+ _is_fd_in_sorted_fd_sequence (int fd , int * fd_sequence ,
142+ Py_ssize_t fd_sequence_len )
142143{
143144 /* Binary search. */
144145 Py_ssize_t search_min = 0 ;
145- Py_ssize_t search_max = PyTuple_GET_SIZE ( fd_sequence ) - 1 ;
146+ Py_ssize_t search_max = fd_sequence_len - 1 ;
146147 if (search_max < 0 )
147148 return 0 ;
148149 do {
149150 long middle = (search_min + search_max ) / 2 ;
150- long middle_fd = PyLong_AsLong ( PyTuple_GET_ITEM ( fd_sequence , middle )) ;
151+ long middle_fd = fd_sequence [ middle ] ;
151152 if (fd == middle_fd )
152153 return 1 ;
153154 if (fd > middle_fd )
@@ -158,24 +159,56 @@ _is_fd_in_sorted_fd_sequence(int fd, PyObject *fd_sequence)
158159 return 0 ;
159160}
160161
162+ /*
163+ * Do all the Python C API calls in the parent process to turn the pass_fds
164+ * "py_fds_to_keep" tuple into a C array. The caller owns allocation and
165+ * freeing of the array.
166+ *
167+ * On error an unknown number of array elements may have been filled in.
168+ * A Python exception has been set when an error is returned.
169+ *
170+ * Returns: -1 on error, 0 on success.
171+ */
161172static int
162- make_inheritable (PyObject * py_fds_to_keep , int errpipe_write )
173+ convert_fds_to_keep_to_c (PyObject * py_fds_to_keep , int * c_fds_to_keep )
163174{
164175 Py_ssize_t i , len ;
165176
166177 len = PyTuple_GET_SIZE (py_fds_to_keep );
167178 for (i = 0 ; i < len ; ++ i ) {
168179 PyObject * fdobj = PyTuple_GET_ITEM (py_fds_to_keep , i );
169180 long fd = PyLong_AsLong (fdobj );
170- assert (!PyErr_Occurred ());
171- assert (0 <= fd && fd <= INT_MAX );
181+ if (fd == -1 && PyErr_Occurred ()) {
182+ return -1 ;
183+ }
184+ if (fd < 0 || fd > INT_MAX ) {
185+ PyErr_SetString (PyExc_ValueError ,
186+ "fd out of range in fds_to_keep." );
187+ return -1 ;
188+ }
189+ c_fds_to_keep [i ] = (int )fd ;
190+ }
191+ return 0 ;
192+ }
193+
194+
195+ /* This function must be async-signal-safe as it is called from child_exec()
196+ * after fork() or vfork().
197+ */
198+ static int
199+ make_inheritable (int * c_fds_to_keep , Py_ssize_t len , int errpipe_write )
200+ {
201+ Py_ssize_t i ;
202+
203+ for (i = 0 ; i < len ; ++ i ) {
204+ int fd = c_fds_to_keep [i ];
172205 if (fd == errpipe_write ) {
173- /* errpipe_write is part of py_fds_to_keep . It must be closed at
206+ /* errpipe_write is part of fds_to_keep . It must be closed at
174207 exec(), but kept open in the child process until exec() is
175208 called. */
176209 continue ;
177210 }
178- if (_Py_set_inheritable_async_safe (( int ) fd , 1 , NULL ) < 0 )
211+ if (_Py_set_inheritable_async_safe (fd , 1 , NULL ) < 0 )
179212 return -1 ;
180213 }
181214 return 0 ;
@@ -211,7 +244,7 @@ safe_get_max_fd(void)
211244
212245
213246/* Close all file descriptors in the given range except for those in
214- * py_fds_to_keep by invoking closer on each subrange.
247+ * fds_to_keep by invoking closer on each subrange.
215248 *
216249 * If end_fd == -1, it's guessed via safe_get_max_fd(), but it isn't
217250 * possible to know for sure what the max fd to go up to is for
@@ -221,19 +254,18 @@ safe_get_max_fd(void)
221254static int
222255_close_range_except (int start_fd ,
223256 int end_fd ,
224- PyObject * py_fds_to_keep ,
257+ int * fds_to_keep ,
258+ Py_ssize_t fds_to_keep_len ,
225259 int (* closer )(int , int ))
226260{
227261 if (end_fd == -1 ) {
228262 end_fd = Py_MIN (safe_get_max_fd (), INT_MAX );
229263 }
230- Py_ssize_t num_fds_to_keep = PyTuple_GET_SIZE (py_fds_to_keep );
231264 Py_ssize_t keep_seq_idx ;
232- /* As py_fds_to_keep is sorted we can loop through the list closing
265+ /* As fds_to_keep is sorted we can loop through the list closing
233266 * fds in between any in the keep list falling within our range. */
234- for (keep_seq_idx = 0 ; keep_seq_idx < num_fds_to_keep ; ++ keep_seq_idx ) {
235- PyObject * py_keep_fd = PyTuple_GET_ITEM (py_fds_to_keep , keep_seq_idx );
236- int keep_fd = PyLong_AsLong (py_keep_fd );
267+ for (keep_seq_idx = 0 ; keep_seq_idx < fds_to_keep_len ; ++ keep_seq_idx ) {
268+ int keep_fd = fds_to_keep [keep_seq_idx ];
237269 if (keep_fd < start_fd )
238270 continue ;
239271 if (closer (start_fd , keep_fd - 1 ) != 0 )
@@ -273,7 +305,7 @@ _brute_force_closer(int first, int last)
273305}
274306
275307/* Close all open file descriptors in the range from start_fd and higher
276- * Do not close any in the sorted py_fds_to_keep list.
308+ * Do not close any in the sorted fds_to_keep list.
277309 *
278310 * This version is async signal safe as it does not make any unsafe C library
279311 * calls, malloc calls or handle any locks. It is _unfortunate_ to be forced
@@ -288,14 +320,16 @@ _brute_force_closer(int first, int last)
288320 * it with some cpp #define magic to work on other OSes as well if you want.
289321 */
290322static void
291- _close_open_fds_safe (int start_fd , PyObject * py_fds_to_keep )
323+ _close_open_fds_safe (int start_fd , int * fds_to_keep , Py_ssize_t fds_to_keep_len )
292324{
293325 int fd_dir_fd ;
294326
295327 fd_dir_fd = _Py_open_noraise (FD_DIR , O_RDONLY );
296328 if (fd_dir_fd == -1 ) {
297329 /* No way to get a list of open fds. */
298- _close_range_except (start_fd , -1 , py_fds_to_keep , _brute_force_closer );
330+ _close_range_except (start_fd , -1 ,
331+ fds_to_keep , fds_to_keep_len ,
332+ _brute_force_closer );
299333 return ;
300334 } else {
301335 char buffer [sizeof (struct linux_dirent64 )];
@@ -314,7 +348,8 @@ _close_open_fds_safe(int start_fd, PyObject* py_fds_to_keep)
314348 if ((fd = _pos_int_from_ascii (entry -> d_name )) < 0 )
315349 continue ; /* Not a number. */
316350 if (fd != fd_dir_fd && fd >= start_fd &&
317- !_is_fd_in_sorted_fd_sequence (fd , py_fds_to_keep )) {
351+ !_is_fd_in_sorted_fd_sequence (fd , fds_to_keep ,
352+ fds_to_keep_len )) {
318353 close (fd );
319354 }
320355 }
@@ -335,7 +370,7 @@ _unsafe_closer(int first, int last)
335370}
336371
337372/* Close all open file descriptors from start_fd and higher.
338- * Do not close any in the sorted py_fds_to_keep tuple.
373+ * Do not close any in the sorted fds_to_keep tuple.
339374 *
340375 * This function violates the strict use of async signal safe functions. :(
341376 * It calls opendir(), readdir() and closedir(). Of these, the one most
@@ -348,11 +383,13 @@ _unsafe_closer(int first, int last)
348383 * http://womble.decadent.org.uk/readdir_r-advisory.html
349384 */
350385static void
351- _close_open_fds_maybe_unsafe (int start_fd , PyObject * py_fds_to_keep )
386+ _close_open_fds_maybe_unsafe (int start_fd , int * fds_to_keep ,
387+ Py_ssize_t fds_to_keep_len )
352388{
353389 DIR * proc_fd_dir ;
354390#ifndef HAVE_DIRFD
355- while (_is_fd_in_sorted_fd_sequence (start_fd , py_fds_to_keep )) {
391+ while (_is_fd_in_sorted_fd_sequence (start_fd , fds_to_keep ,
392+ fds_to_keep_len )) {
356393 ++ start_fd ;
357394 }
358395 /* Close our lowest fd before we call opendir so that it is likely to
@@ -371,7 +408,8 @@ _close_open_fds_maybe_unsafe(int start_fd, PyObject* py_fds_to_keep)
371408 proc_fd_dir = opendir (FD_DIR );
372409 if (!proc_fd_dir ) {
373410 /* No way to get a list of open fds. */
374- _close_range_except (start_fd , -1 , py_fds_to_keep , _unsafe_closer );
411+ _close_range_except (start_fd , -1 , fds_to_keep , fds_to_keep_len ,
412+ _unsafe_closer );
375413 } else {
376414 struct dirent * dir_entry ;
377415#ifdef HAVE_DIRFD
@@ -385,14 +423,16 @@ _close_open_fds_maybe_unsafe(int start_fd, PyObject* py_fds_to_keep)
385423 if ((fd = _pos_int_from_ascii (dir_entry -> d_name )) < 0 )
386424 continue ; /* Not a number. */
387425 if (fd != fd_used_by_opendir && fd >= start_fd &&
388- !_is_fd_in_sorted_fd_sequence (fd , py_fds_to_keep )) {
426+ !_is_fd_in_sorted_fd_sequence (fd , fds_to_keep ,
427+ fds_to_keep_len )) {
389428 close (fd );
390429 }
391430 errno = 0 ;
392431 }
393432 if (errno ) {
394433 /* readdir error, revert behavior. Highly Unlikely. */
395- _close_range_except (start_fd , -1 , py_fds_to_keep , _unsafe_closer );
434+ _close_range_except (start_fd , -1 , fds_to_keep , fds_to_keep_len ,
435+ _unsafe_closer );
396436 }
397437 closedir (proc_fd_dir );
398438 }
@@ -420,16 +460,16 @@ _close_range_closer(int first, int last)
420460#endif
421461
422462static void
423- _close_open_fds (int start_fd , PyObject * py_fds_to_keep )
463+ _close_open_fds (int start_fd , int * fds_to_keep , Py_ssize_t fds_to_keep_len )
424464{
425465#ifdef HAVE_ASYNC_SAFE_CLOSE_RANGE
426466 if (_close_range_except (
427- start_fd , INT_MAX , py_fds_to_keep ,
467+ start_fd , INT_MAX , fds_to_keep , fds_to_keep_len ,
428468 _close_range_closer ) == 0 ) {
429469 return ;
430470 }
431471#endif
432- _close_open_fds_fallback (start_fd , py_fds_to_keep );
472+ _close_open_fds_fallback (start_fd , fds_to_keep , fds_to_keep_len );
433473}
434474
435475#ifdef VFORK_USABLE
@@ -522,7 +562,7 @@ child_exec(char *const exec_array[],
522562 int call_setgroups , size_t groups_size , const gid_t * groups ,
523563 int call_setuid , uid_t uid , int child_umask ,
524564 const void * child_sigmask ,
525- PyObject * py_fds_to_keep ,
565+ int * fds_to_keep , Py_ssize_t fds_to_keep_len ,
526566 PyObject * preexec_fn ,
527567 PyObject * preexec_fn_args_tuple )
528568{
@@ -532,7 +572,7 @@ child_exec(char *const exec_array[],
532572 /* Buffer large enough to hold a hex integer. We can't malloc. */
533573 char hex_errno [sizeof (saved_errno )* 2 + 1 ];
534574
535- if (make_inheritable (py_fds_to_keep , errpipe_write ) < 0 )
575+ if (make_inheritable (fds_to_keep , fds_to_keep_len , errpipe_write ) < 0 )
536576 goto error ;
537577
538578 /* Close parent's pipe ends. */
@@ -652,7 +692,7 @@ child_exec(char *const exec_array[],
652692 /* close FDs after executing preexec_fn, which might open FDs */
653693 if (close_fds ) {
654694 /* TODO HP-UX could use pstat_getproc() if anyone cares about it. */
655- _close_open_fds (3 , py_fds_to_keep );
695+ _close_open_fds (3 , fds_to_keep , fds_to_keep_len );
656696 }
657697
658698 /* This loop matches the Lib/os.py _execvpe()'s PATH search when */
@@ -726,7 +766,7 @@ do_fork_exec(char *const exec_array[],
726766 int call_setgroups , size_t groups_size , const gid_t * groups ,
727767 int call_setuid , uid_t uid , int child_umask ,
728768 const void * child_sigmask ,
729- PyObject * py_fds_to_keep ,
769+ int * fds_to_keep , Py_ssize_t fds_to_keep_len ,
730770 PyObject * preexec_fn ,
731771 PyObject * preexec_fn_args_tuple )
732772{
@@ -777,7 +817,8 @@ do_fork_exec(char *const exec_array[],
777817 close_fds , restore_signals , call_setsid , pgid_to_set ,
778818 call_setgid , gid , call_setgroups , groups_size , groups ,
779819 call_setuid , uid , child_umask , child_sigmask ,
780- py_fds_to_keep , preexec_fn , preexec_fn_args_tuple );
820+ fds_to_keep , fds_to_keep_len ,
821+ preexec_fn , preexec_fn_args_tuple );
781822 _exit (255 );
782823 return 0 ; /* Dead code to avoid a potential compiler warning. */
783824}
@@ -810,6 +851,7 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
810851 int need_after_fork = 0 ;
811852 int saved_errno = 0 ;
812853 int allow_vfork ;
854+ int * c_fds_to_keep = NULL ;
813855
814856 if (!PyArg_ParseTuple (
815857 args , "OOpO!OOiiiiiiiiii" _Py_PARSE_PID "OOOiOp:fork_exec" ,
@@ -983,6 +1025,16 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
9831025#endif /* HAVE_SETREUID */
9841026 }
9851027
1028+ Py_ssize_t fds_to_keep_len = PyTuple_GET_SIZE (py_fds_to_keep );
1029+ c_fds_to_keep = PyMem_Malloc (fds_to_keep_len * sizeof (int ));
1030+ if (c_fds_to_keep == NULL ) {
1031+ PyErr_SetString (PyExc_MemoryError , "failed to malloc c_fds_to_keep" );
1032+ goto cleanup ;
1033+ }
1034+ if (convert_fds_to_keep_to_c (py_fds_to_keep , c_fds_to_keep ) < 0 ) {
1035+ goto cleanup ;
1036+ }
1037+
9861038 /* This must be the last thing done before fork() because we do not
9871039 * want to call PyOS_BeforeFork() if there is any chance of another
9881040 * error leading to the cleanup: code without calling fork(). */
@@ -1025,7 +1077,8 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
10251077 close_fds , restore_signals , call_setsid , pgid_to_set ,
10261078 call_setgid , gid , call_setgroups , num_groups , groups ,
10271079 call_setuid , uid , child_umask , old_sigmask ,
1028- py_fds_to_keep , preexec_fn , preexec_fn_args_tuple );
1080+ c_fds_to_keep , fds_to_keep_len ,
1081+ preexec_fn , preexec_fn_args_tuple );
10291082
10301083 /* Parent (original) process */
10311084 if (pid == -1 ) {
@@ -1055,6 +1108,10 @@ subprocess_fork_exec(PyObject *module, PyObject *args)
10551108 PyOS_AfterFork_Parent ();
10561109
10571110cleanup :
1111+ if (c_fds_to_keep != NULL ) {
1112+ PyMem_Free (c_fds_to_keep );
1113+ }
1114+
10581115 if (saved_errno != 0 ) {
10591116 errno = saved_errno ;
10601117 /* We can't call this above as PyOS_AfterFork_Parent() calls back
0 commit comments