diff options
Diffstat (limited to 'libio')
-rw-r--r-- | libio/iopopen.c | 132 |
1 files changed, 85 insertions, 47 deletions
diff --git a/libio/iopopen.c b/libio/iopopen.c index 2eff45b4c8..c768295180 100644 --- a/libio/iopopen.c +++ b/libio/iopopen.c @@ -34,7 +34,8 @@ #include <not-cancel.h> #include <sys/types.h> #include <sys/wait.h> -#include <kernel-features.h> +#include <spawn.h> +#include <paths.h> struct _IO_proc_file { @@ -59,13 +60,60 @@ unlock (void *not_used) } #endif +/* POSIX states popen shall ensure that any streams from previous popen() + calls that remain open in the parent process should be closed in the new + child process. + To avoid a race-condition between checking which file descriptors need to + be close (by transversing the proc_file_chain list) and the insertion of a + new one after a successful posix_spawn this function should be called + with proc_file_chain_lock acquired. */ +static bool +spawn_process (posix_spawn_file_actions_t *fa, FILE *fp, const char *command, + int do_cloexec, int pipe_fds[2], int parent_end, int child_end, + int child_pipe_fd) +{ + + for (struct _IO_proc_file *p = proc_file_chain; p; p = p->next) + { + int fd = _IO_fileno ((FILE *) p); + + /* If any stream from previous popen() calls has fileno + child_pipe_fd, it has been already closed by the adddup2 action + above. */ + if (fd != child_pipe_fd + && __posix_spawn_file_actions_addclose (fa, fd) != 0) + return false; + } + + if (__posix_spawn (&((_IO_proc_file *) fp)->pid, _PATH_BSHELL, fa, 0, + (char *const[]){ (char*) "sh", (char*) "-c", + (char *) command, NULL }, __environ) != 0) + return false; + + __close_nocancel (pipe_fds[child_end]); + + if (!do_cloexec) + /* Undo the effects of the pipe2 call which set the + close-on-exec flag. */ + __fcntl (pipe_fds[parent_end], F_SETFD, 0); + + _IO_fileno (fp) = pipe_fds[parent_end]; + + ((_IO_proc_file *) fp)->next = proc_file_chain; + proc_file_chain = (_IO_proc_file *) fp; + + return true; +} + FILE * _IO_new_proc_open (FILE *fp, const char *command, const char *mode) { int read_or_write; + /* These are indexes for pipe_fds. */ int parent_end, child_end; int pipe_fds[2]; - pid_t child_pid; + int child_pipe_fd; + bool spawn_ok; int do_read = 0; int do_write = 0; @@ -108,72 +156,62 @@ _IO_new_proc_open (FILE *fp, const char *command, const char *mode) if (do_read) { - parent_end = pipe_fds[0]; - child_end = pipe_fds[1]; + parent_end = 0; + child_end = 1; read_or_write = _IO_NO_WRITES; + child_pipe_fd = 1; } else { - parent_end = pipe_fds[1]; - child_end = pipe_fds[0]; + parent_end = 1; + child_end = 0; read_or_write = _IO_NO_READS; + child_pipe_fd = 0; } - ((_IO_proc_file *) fp)->pid = child_pid = __fork (); - if (child_pid == 0) - { - int child_std_end = do_read ? 1 : 0; - struct _IO_proc_file *p; - - if (child_end != child_std_end) - __dup2 (child_end, child_std_end); - else - /* The descriptor is already the one we will use. But it must - not be marked close-on-exec. Undo the effects. */ - __fcntl (child_end, F_SETFD, 0); - /* POSIX.2: "popen() shall ensure that any streams from previous - popen() calls that remain open in the parent process are closed - in the new child process." */ - for (p = proc_file_chain; p; p = p->next) - { - int fd = _IO_fileno ((FILE *) p); + posix_spawn_file_actions_t fa; + /* posix_spawn_file_actions_init does not fail. */ + __posix_spawn_file_actions_init (&fa); - /* If any stream from previous popen() calls has fileno - child_std_end, it has been already closed by the dup2 syscall - above. */ - if (fd != child_std_end) - __close_nocancel (fd); - } - - execl ("/bin/sh", "sh", "-c", command, (char *) 0); - _exit (127); - } - __close_nocancel (child_end); - if (child_pid < 0) + /* The descriptor is already the one the child will use. In this case + it must be moved to another one otherwise, there is no safe way to + remove the close-on-exec flag in the child without creating a FD leak + race in the parent. */ + if (pipe_fds[child_end] == child_pipe_fd) { - __close_nocancel (parent_end); - return NULL; + int tmp = __fcntl (child_pipe_fd, F_DUPFD_CLOEXEC, 0); + if (tmp < 0) + goto spawn_failure; + __close_nocancel (pipe_fds[child_end]); + pipe_fds[child_end] = tmp; } - if (!do_cloexec) - /* Undo the effects of the pipe2 call which set the - close-on-exec flag. */ - __fcntl (parent_end, F_SETFD, 0); + if (__posix_spawn_file_actions_adddup2 (&fa, pipe_fds[child_end], + child_pipe_fd) != 0) + goto spawn_failure; - _IO_fileno (fp) = parent_end; - - /* Link into proc_file_chain. */ #ifdef _IO_MTSAFE_IO _IO_cleanup_region_start_noarg (unlock); _IO_lock_lock (proc_file_chain_lock); #endif - ((_IO_proc_file *) fp)->next = proc_file_chain; - proc_file_chain = (_IO_proc_file *) fp; + spawn_ok = spawn_process (&fa, fp, command, do_cloexec, pipe_fds, + parent_end, child_end, child_pipe_fd); #ifdef _IO_MTSAFE_IO _IO_lock_unlock (proc_file_chain_lock); _IO_cleanup_region_end (0); #endif + __posix_spawn_file_actions_destroy (&fa); + + if (!spawn_ok) + { + spawn_failure: + __close_nocancel (pipe_fds[child_end]); + __close_nocancel (pipe_fds[parent_end]); + __set_errno (ENOMEM); + return NULL; + } + _IO_mask_flags (fp, read_or_write, _IO_NO_READS|_IO_NO_WRITES); return fp; } |