@@ -160,16 +160,17 @@ _sanity_check_python_fd_sequence(PyObject *fd_sequence)
160160
161161/* Is fd found in the sorted Python Sequence? */
162162static int
163- _is_fd_in_sorted_fd_sequence (int fd , PyObject * fd_sequence )
163+ _is_fd_in_sorted_fd_sequence (int fd , int * fd_sequence ,
164+ Py_ssize_t fd_sequence_len )
164165{
165166 /* Binary search. */
166167 Py_ssize_t search_min = 0 ;
167- Py_ssize_t search_max = PyTuple_GET_SIZE ( fd_sequence ) - 1 ;
168+ Py_ssize_t search_max = fd_sequence_len - 1 ;
168169 if (search_max < 0 )
169170 return 0 ;
170171 do {
171172 long middle = (search_min + search_max ) / 2 ;
172- long middle_fd = PyLong_AsLong ( PyTuple_GET_ITEM ( fd_sequence , middle )) ;
173+ long middle_fd = fd_sequence [ middle ] ;
173174 if (fd == middle_fd )
174175 return 1 ;
175176 if (fd > middle_fd )
@@ -180,24 +181,56 @@ _is_fd_in_sorted_fd_sequence(int fd, PyObject *fd_sequence)
180181 return 0 ;
181182}
182183
184+ /*
185+ * Do all the Python C API calls in the parent process to turn the pass_fds
186+ * "py_fds_to_keep" tuple into a C array. The caller owns allocation and
187+ * freeing of the array.
188+ *
189+ * On error an unknown number of array elements may have been filled in.
190+ * A Python exception has been set when an error is returned.
191+ *
192+ * Returns: -1 on error, 0 on success.
193+ */
183194static int
184- make_inheritable (PyObject * py_fds_to_keep , int errpipe_write )
195+ convert_fds_to_keep_to_c (PyObject * py_fds_to_keep , int * c_fds_to_keep )
185196{
186197 Py_ssize_t i , len ;
187198
188199 len = PyTuple_GET_SIZE (py_fds_to_keep );
189200 for (i = 0 ; i < len ; ++ i ) {
190201 PyObject * fdobj = PyTuple_GET_ITEM (py_fds_to_keep , i );
191202 long fd = PyLong_AsLong (fdobj );
192- assert (!PyErr_Occurred ());
193- assert (0 <= fd && fd <= INT_MAX );
203+ if (PyErr_Occurred ()) {
204+ return -1 ;
205+ }
206+ if (fd < 0 || fd > INT_MAX ) {
207+ PyErr_SetString (PyExc_ValueError ,
208+ "fd out of range in fds_to_keep." );
209+ return -1 ;
210+ }
211+ c_fds_to_keep [i ] = (int )fd ;
212+ }
213+ return 0 ;
214+ }
215+
216+
217+ /* This function must be async-signal-safe as it is called from child_exec()
218+ * after fork() or vfork().
219+ */
220+ static int
221+ make_inheritable (int * c_fds_to_keep , Py_ssize_t len , int errpipe_write )
222+ {
223+ Py_ssize_t i ;
224+
225+ for (i = 0 ; i < len ; ++ i ) {
226+ int fd = c_fds_to_keep [i ];
194227 if (fd == errpipe_write ) {
195- /* errpipe_write is part of py_fds_to_keep . It must be closed at
228+ /* errpipe_write is part of fds_to_keep . It must be closed at
196229 exec(), but kept open in the child process until exec() is
197230 called. */
198231 continue ;
199232 }
200- if (_Py_set_inheritable_async_safe (( int ) fd , 1 , NULL ) < 0 )
233+ if (_Py_set_inheritable_async_safe (fd , 1 , NULL ) < 0 )
201234 return -1 ;
202235 }
203236 return 0 ;
@@ -233,7 +266,7 @@ safe_get_max_fd(void)
233266
234267
235268/* Close all file descriptors in the given range except for those in
236- * py_fds_to_keep by invoking closer on each subrange.
269+ * fds_to_keep by invoking closer on each subrange.
237270 *
238271 * If end_fd == -1, it's guessed via safe_get_max_fd(), but it isn't
239272 * possible to know for sure what the max fd to go up to is for
@@ -243,19 +276,18 @@ safe_get_max_fd(void)
243276static int
244277_close_range_except (int start_fd ,
245278 int end_fd ,
246- PyObject * py_fds_to_keep ,
279+ int * fds_to_keep ,
280+ Py_ssize_t fds_to_keep_len ,
247281 int (* closer )(int , int ))
248282{
249283 if (end_fd == -1 ) {
250284 end_fd = Py_MIN (safe_get_max_fd (), INT_MAX );
251285 }
252- Py_ssize_t num_fds_to_keep = PyTuple_GET_SIZE (py_fds_to_keep );
253286 Py_ssize_t keep_seq_idx ;
254- /* As py_fds_to_keep is sorted we can loop through the list closing
287+ /* As fds_to_keep is sorted we can loop through the list closing
255288 * fds in between any in the keep list falling within our range. */
256- for (keep_seq_idx = 0 ; keep_seq_idx < num_fds_to_keep ; ++ keep_seq_idx ) {
257- PyObject * py_keep_fd = PyTuple_GET_ITEM (py_fds_to_keep , keep_seq_idx );
258- int keep_fd = PyLong_AsLong (py_keep_fd );
289+ for (keep_seq_idx = 0 ; keep_seq_idx < fds_to_keep_len ; ++ keep_seq_idx ) {
290+ int keep_fd = fds_to_keep [keep_seq_idx ];
259291 if (keep_fd < start_fd )
260292 continue ;
261293 if (closer (start_fd , keep_fd - 1 ) != 0 )
@@ -295,7 +327,7 @@ _brute_force_closer(int first, int last)
295327}
296328
297329/* Close all open file descriptors in the range from start_fd and higher
298- * Do not close any in the sorted py_fds_to_keep list.
330+ * Do not close any in the sorted fds_to_keep list.
299331 *
300332 * This version is async signal safe as it does not make any unsafe C library
301333 * calls, malloc calls or handle any locks. It is _unfortunate_ to be forced
@@ -310,14 +342,16 @@ _brute_force_closer(int first, int last)
310342 * it with some cpp #define magic to work on other OSes as well if you want.
311343 */
312344static void
313- _close_open_fds_safe (int start_fd , PyObject * py_fds_to_keep )
345+ _close_open_fds_safe (int start_fd , int * fds_to_keep , Py_ssize_t fds_to_keep_len )
314346{
315347 int fd_dir_fd ;
316348
317349 fd_dir_fd = _Py_open_noraise (FD_DIR , O_RDONLY );
318350 if (fd_dir_fd == -1 ) {
319351 /* No way to get a list of open fds. */
320- _close_range_except (start_fd , -1 , py_fds_to_keep , _brute_force_closer );
352+ _close_range_except (start_fd , -1 ,
353+ fds_to_keep , fds_to_keep_len ,
354+ _brute_force_closer );
321355 return ;
322356 } else {
323357 char buffer [sizeof (struct linux_dirent64 )];
@@ -336,7 +370,8 @@ _close_open_fds_safe(int start_fd, PyObject* py_fds_to_keep)
336370 if ((fd = _pos_int_from_ascii (entry -> d_name )) < 0 )
337371 continue ; /* Not a number. */
338372 if (fd != fd_dir_fd && fd >= start_fd &&
339- !_is_fd_in_sorted_fd_sequence (fd , py_fds_to_keep )) {
373+ !_is_fd_in_sorted_fd_sequence (fd , fds_to_keep ,
374+ fds_to_keep_len )) {
340375 close (fd );
341376 }
342377 }
@@ -357,7 +392,7 @@ _unsafe_closer(int first, int last)
357392}
358393
359394/* Close all open file descriptors from start_fd and higher.
360- * Do not close any in the sorted py_fds_to_keep tuple.
395+ * Do not close any in the sorted fds_to_keep tuple.
361396 *
362397 * This function violates the strict use of async signal safe functions. :(
363398 * It calls opendir(), readdir() and closedir(). Of these, the one most
@@ -370,11 +405,13 @@ _unsafe_closer(int first, int last)
370405 * http://womble.decadent.org.uk/readdir_r-advisory.html
371406 */
372407static void
373- _close_open_fds_maybe_unsafe (int start_fd , PyObject * py_fds_to_keep )
408+ _close_open_fds_maybe_unsafe (int start_fd , int * fds_to_keep ,
409+ Py_ssize_t fds_to_keep_len )
374410{
375411 DIR * proc_fd_dir ;
376412#ifndef HAVE_DIRFD
377- while (_is_fd_in_sorted_fd_sequence (start_fd , py_fds_to_keep )) {
413+ while (_is_fd_in_sorted_fd_sequence (start_fd , fds_to_keep ,
414+ fds_to_keep_len )) {
378415 ++ start_fd ;
379416 }
380417 /* Close our lowest fd before we call opendir so that it is likely to
@@ -393,7 +430,8 @@ _close_open_fds_maybe_unsafe(int start_fd, PyObject* py_fds_to_keep)
393430 proc_fd_dir = opendir (FD_DIR );
394431 if (!proc_fd_dir ) {
395432 /* No way to get a list of open fds. */
396- _close_range_except (start_fd , -1 , py_fds_to_keep , _unsafe_closer );
433+ _close_range_except (start_fd , -1 , fds_to_keep , fds_to_keep_len ,
434+ _unsafe_closer );
397435 } else {
398436 struct dirent * dir_entry ;
399437#ifdef HAVE_DIRFD
@@ -407,14 +445,16 @@ _close_open_fds_maybe_unsafe(int start_fd, PyObject* py_fds_to_keep)
407445 if ((fd = _pos_int_from_ascii (dir_entry -> d_name )) < 0 )
408446 continue ; /* Not a number. */
409447 if (fd != fd_used_by_opendir && fd >= start_fd &&
410- !_is_fd_in_sorted_fd_sequence (fd , py_fds_to_keep )) {
448+ !_is_fd_in_sorted_fd_sequence (fd , fds_to_keep ,
449+ fds_to_keep_len )) {
411450 close (fd );
412451 }
413452 errno = 0 ;
414453 }
415454 if (errno ) {
416455 /* readdir error, revert behavior. Highly Unlikely. */
417- _close_range_except (start_fd , -1 , py_fds_to_keep , _unsafe_closer );
456+ _close_range_except (start_fd , -1 , fds_to_keep , fds_to_keep_len ,
457+ _unsafe_closer );
418458 }
419459 closedir (proc_fd_dir );
420460 }
@@ -442,16 +482,16 @@ _close_range_closer(int first, int last)
442482#endif
443483
444484static void
445- _close_open_fds (int start_fd , PyObject * py_fds_to_keep )
485+ _close_open_fds (int start_fd , int * fds_to_keep , Py_ssize_t fds_to_keep_len )
446486{
447487#ifdef HAVE_ASYNC_SAFE_CLOSE_RANGE
448488 if (_close_range_except (
449- start_fd , INT_MAX , py_fds_to_keep ,
489+ start_fd , INT_MAX , fds_to_keep , fds_to_keep_len ,
450490 _close_range_closer ) == 0 ) {
451491 return ;
452492 }
453493#endif
454- _close_open_fds_fallback (start_fd , py_fds_to_keep );
494+ _close_open_fds_fallback (start_fd , fds_to_keep , fds_to_keep_len );
455495}
456496
457497#ifdef VFORK_USABLE
@@ -544,7 +584,7 @@ child_exec(char *const exec_array[],
544584 Py_ssize_t extra_group_size , const gid_t * extra_groups ,
545585 uid_t uid , int child_umask ,
546586 const void * child_sigmask ,
547- PyObject * py_fds_to_keep ,
587+ int * fds_to_keep , Py_ssize_t fds_to_keep_len ,
548588 PyObject * preexec_fn ,
549589 PyObject * preexec_fn_args_tuple )
550590{
@@ -554,7 +594,7 @@ child_exec(char *const exec_array[],
554594 /* Buffer large enough to hold a hex integer. We can't malloc. */
555595 char hex_errno [sizeof (saved_errno )* 2 + 1 ];
556596
557- if (make_inheritable (py_fds_to_keep , errpipe_write ) < 0 )
597+ if (make_inheritable (fds_to_keep , fds_to_keep_len , errpipe_write ) < 0 )
558598 goto error ;
559599
560600 /* Close parent's pipe ends. */
@@ -676,7 +716,7 @@ child_exec(char *const exec_array[],
676716 /* close FDs after executing preexec_fn, which might open FDs */
677717 if (close_fds ) {
678718 /* TODO HP-UX could use pstat_getproc() if anyone cares about it. */
679- _close_open_fds (3 , py_fds_to_keep );
719+ _close_open_fds (3 , fds_to_keep , fds_to_keep_len );
680720 }
681721
682722 /* This loop matches the Lib/os.py _execvpe()'s PATH search when */
@@ -750,7 +790,7 @@ do_fork_exec(char *const exec_array[],
750790 Py_ssize_t extra_group_size , const gid_t * extra_groups ,
751791 uid_t uid , int child_umask ,
752792 const void * child_sigmask ,
753- PyObject * py_fds_to_keep ,
793+ int * fds_to_keep , Py_ssize_t fds_to_keep_len ,
754794 PyObject * preexec_fn ,
755795 PyObject * preexec_fn_args_tuple )
756796{
@@ -801,7 +841,8 @@ do_fork_exec(char *const exec_array[],
801841 close_fds , restore_signals , call_setsid , pgid_to_set ,
802842 gid , extra_group_size , extra_groups ,
803843 uid , child_umask , child_sigmask ,
804- py_fds_to_keep , preexec_fn , preexec_fn_args_tuple );
844+ fds_to_keep , fds_to_keep_len ,
845+ preexec_fn , preexec_fn_args_tuple );
805846 _exit (255 );
806847 return 0 ; /* Dead code to avoid a potential compiler warning. */
807848}
@@ -881,6 +922,8 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
881922 Py_ssize_t extra_group_size = 0 ;
882923 int need_after_fork = 0 ;
883924 int saved_errno = 0 ;
925+ int * c_fds_to_keep = NULL ;
926+ Py_ssize_t fds_to_keep_len = PyTuple_GET_SIZE (py_fds_to_keep );
884927
885928 PyInterpreterState * interp = PyInterpreterState_Get ();
886929 if ((preexec_fn != Py_None ) && (interp != PyInterpreterState_Main ())) {
@@ -1031,6 +1074,15 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
10311074#endif /* HAVE_SETREUID */
10321075 }
10331076
1077+ c_fds_to_keep = PyMem_RawMalloc (fds_to_keep_len * sizeof (int ));
1078+ if (c_fds_to_keep == NULL ) {
1079+ PyErr_SetString (PyExc_MemoryError , "failed to malloc c_fds_to_keep" );
1080+ goto cleanup ;
1081+ }
1082+ if (convert_fds_to_keep_to_c (py_fds_to_keep , c_fds_to_keep ) < 0 ) {
1083+ goto cleanup ;
1084+ }
1085+
10341086 /* This must be the last thing done before fork() because we do not
10351087 * want to call PyOS_BeforeFork() if there is any chance of another
10361088 * error leading to the cleanup: code without calling fork(). */
@@ -1073,7 +1125,8 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
10731125 close_fds , restore_signals , call_setsid , pgid_to_set ,
10741126 gid , extra_group_size , extra_groups ,
10751127 uid , child_umask , old_sigmask ,
1076- py_fds_to_keep , preexec_fn , preexec_fn_args_tuple );
1128+ c_fds_to_keep , fds_to_keep_len ,
1129+ preexec_fn , preexec_fn_args_tuple );
10771130
10781131 /* Parent (original) process */
10791132 if (pid == (pid_t )- 1 ) {
@@ -1103,6 +1156,10 @@ subprocess_fork_exec_impl(PyObject *module, PyObject *process_args,
11031156 PyOS_AfterFork_Parent ();
11041157
11051158cleanup :
1159+ if (c_fds_to_keep != NULL ) {
1160+ PyMem_RawFree (c_fds_to_keep );
1161+ }
1162+
11061163 if (saved_errno != 0 ) {
11071164 errno = saved_errno ;
11081165 /* We can't call this above as PyOS_AfterFork_Parent() calls back
0 commit comments