diff --git a/src/exec.c b/src/exec.c index 2d966ca28..ecc699fcf 100644 --- a/src/exec.c +++ b/src/exec.c @@ -17,17 +17,11 @@ #include #include -#ifdef HAVE_SYS_SYSMACROS_H -# include -#endif #include #include #include #include #include -#ifdef HAVE_SYS_SELECT_H -# include -#endif /* HAVE_SYS_SELECT_H */ #include #ifdef STDC_HEADERS # include @@ -56,25 +50,38 @@ #include "sudo.h" #include "sudo_exec.h" +#include "sudo_event.h" #include "sudo_plugin.h" #include "sudo_plugin_int.h" +struct exec_closure { + pid_t child; + bool log_io; + sigset_t omask; + struct command_status *cstat; + struct command_details *details; + struct sudo_event_base *evbase; +}; + /* We keep a tailq of signals to forward to child. */ struct sigforward { struct sigforward *prev, *next; int signo; }; -TQ_DECLARE(sigforward) -static struct sigforward_list sigfwd_list; +static struct { + struct sigforward *first, *last; + struct sudo_event *event; +} sigfwd_list; +static struct sudo_event *signal_event; +static struct sudo_event *backchannel_event; static pid_t ppgrp = -1; volatile pid_t cmnd_pid = -1; -static int dispatch_signals(int sv[2], pid_t child, int log_io, - struct command_status *cstat); +static void signal_pipe_cb(int fd, int what, void *v); static int dispatch_pending_signals(struct command_status *cstat); -static void forward_signals(int fd); -static void schedule_signal(int signo); +static void forward_signals(int fd, int what, void *v); +static void schedule_signal(struct sudo_event_base *evbase, int signo); #ifdef SA_SIGINFO static void handler_user_only(int s, siginfo_t *info, void *context); #endif @@ -194,6 +201,134 @@ exec_cmnd(struct command_details *details, struct command_status *cstat, debug_return; } +static void +backchannel_cb(int fd, int what, void *v) +{ + struct exec_closure *ec = v; + ssize_t n; + debug_decl(backchannel_cb, SUDO_DEBUG_EXEC) + + /* read child status */ + n = recv(fd, ec->cstat, sizeof(struct command_status), 0); + if (n != sizeof(struct command_status)) { + if (n == -1) { + switch (errno) { + case EINTR: + /* got a signal, restart loop to service it. */ + sudo_ev_loopcontinue(ec->evbase); + break; + case EAGAIN: + /* not ready after all... */ + break; + default: + ec->cstat->type = CMD_ERRNO; + ec->cstat->val = errno; + sudo_debug_printf(SUDO_DEBUG_ERROR, + "failed to read child status: %s", strerror(errno)); + sudo_ev_loopbreak(ec->evbase); + break; + } + } else { + /* Short read or EOF. */ + sudo_debug_printf(SUDO_DEBUG_ERROR, + "failed to read child status: %s", n ? "short read" : "EOF"); + /* + * If not logging I/O we may get EOF when the command is + * executed and sv is closed. It is safe to ignore this. + */ + if (ec->log_io || n != 0) { + /* XXX - need new CMD_ type for monitor errors. */ + errno = n ? EIO : ECONNRESET; + ec->cstat->type = CMD_ERRNO; + ec->cstat->val = errno; + sudo_ev_loopbreak(ec->evbase); + } + } + debug_return; + } + switch (ec->cstat->type) { + case CMD_PID: + /* + * Once we know the command's pid we can unblock + * signals which ere blocked in fork_pty(). This + * avoids a race between exec of the command and + * receipt of a fatal signal from it. + */ + cmnd_pid = ec->cstat->val; + sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", + ec->details->command, (int)cmnd_pid); + if (ec->log_io) + sigprocmask(SIG_SETMASK, &ec->omask, NULL); + break; + case CMD_WSTATUS: + if (WIFSTOPPED(ec->cstat->val)) { + /* Suspend parent and tell child how to resume on return. */ + sudo_debug_printf(SUDO_DEBUG_INFO, + "child stopped, suspending parent"); + n = suspend_parent(WSTOPSIG(ec->cstat->val)); + schedule_signal(ec->evbase, n); + /* Re-enable I/O events and restart event loop to service signal. */ + add_io_events(ec->evbase); + sudo_ev_loopcontinue(ec->evbase); + } else { + /* Child exited or was killed, either way we are done. */ + sudo_debug_printf(SUDO_DEBUG_INFO, "child exited or was killed"); + sudo_ev_loopexit(ec->evbase); + } + break; + case CMD_ERRNO: + /* Child was unable to execute command or broken pipe. */ + sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s", + strerror(ec->cstat->val)); + sudo_ev_loopbreak(ec->evbase); + break; + } + debug_return; +} + +/* + * Setup initial exec events. + * Allocates events for the signal pipe and backchannel. + * Forwarded signals on the backchannel are enabled on demand. + */ +static struct sudo_event_base * +exec_event_setup(int backchannel, struct exec_closure *ec) +{ + struct sudo_event_base *evbase; + debug_decl(exec_event_setup, SUDO_DEBUG_EXEC) + + evbase = sudo_ev_base_alloc(); + if (evbase == NULL) + fatal(NULL); + + /* Event for incoming signals via signal_pipe. */ + signal_event = sudo_ev_alloc(signal_pipe[0], + SUDO_EV_READ|SUDO_EV_PERSIST, signal_pipe_cb, ec); + if (signal_event == NULL) + fatal(NULL); + if (sudo_ev_add(evbase, signal_event, false) == -1) + fatal(_("unable to add event to queue")); + + /* Event for command status via backchannel. */ + backchannel_event = sudo_ev_alloc(backchannel, + SUDO_EV_READ|SUDO_EV_PERSIST, backchannel_cb, ec); + if (backchannel_event == NULL) + fatal(NULL); + if (sudo_ev_add(evbase, backchannel_event, false) == -1) + fatal(_("unable to add event to queue")); + + /* The signal forwarding event gets added on demand. */ + sigfwd_list.event = sudo_ev_alloc(backchannel, + SUDO_EV_WRITE, forward_signals, NULL); + if (sigfwd_list.event == NULL) + fatal(NULL); + + sudo_debug_printf(SUDO_DEBUG_INFO, "signal pipe fd %d\n", signal_pipe[0]); + sudo_debug_printf(SUDO_DEBUG_INFO, "backchannel fd %d\n", backchannel); + + debug_return_ptr(evbase); +} + /* * Execute a command, potentially in a pty with I/O loggging, and * wait for it to finish. @@ -203,12 +338,12 @@ exec_cmnd(struct command_details *details, struct command_status *cstat, int sudo_execute(struct command_details *details, struct command_status *cstat) { - int maxfd, n, nready, sv[2]; + int sv[2]; const char *utmp_user = NULL; + struct sudo_event_base *evbase; + struct exec_closure ec; bool log_io = false; - fd_set *fdsr, *fdsw; sigaction_t sa; - sigset_t omask; pid_t child; debug_decl(sudo_execute, SUDO_DEBUG_EXEC) @@ -296,15 +431,12 @@ sudo_execute(struct command_details *details, struct command_status *cstat) sudo_sigaction(SIGINT, &sa, NULL); sudo_sigaction(SIGQUIT, &sa, NULL); - /* Max fd we will be selecting on. */ - maxfd = MAX(sv[0], signal_pipe[0]); - /* * Child will run the command in the pty, parent will pass data - * to and from pty. Adjusts maxfd as needed. + * to and from pty. */ if (log_io) - child = fork_pty(details, sv, &maxfd, &omask); + child = fork_pty(details, sv, &ec.omask); else child = fork_cmnd(details, sv); close(sv[1]); @@ -319,125 +451,33 @@ sudo_execute(struct command_details *details, struct command_status *cstat) */ setlocale(LC_ALL, "C"); + /* + * Allocate event base and two persistent events: + * the signal pipe and the child process's backchannel. + */ + evbase = exec_event_setup(sv[0], &ec); + + /* + * Generic exec closure used for signal_pipe and backchannel callbacks. + * Note ec.omask is set earlier. + */ + ec.child = child; + ec.log_io = log_io; + ec.cstat = cstat; + ec.evbase = evbase; + ec.details = details; + /* * In the event loop we pass input from user tty to master * and pass output from master to stdout and IO plugin. */ - fdsr = emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); - fdsw = emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); - for (;;) { - memset(fdsw, 0, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); - memset(fdsr, 0, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); - - FD_SET(signal_pipe[0], fdsr); - FD_SET(sv[0], fdsr); - if (!tq_empty(&sigfwd_list)) - FD_SET(sv[0], fdsw); - if (log_io) - fd_set_iobs(fdsr, fdsw); /* XXX - better name */ - nready = select(maxfd + 1, fdsr, fdsw, NULL, NULL); - sudo_debug_printf(SUDO_DEBUG_DEBUG, "select returns %d", nready); - if (nready == -1) { - if (errno == EINTR || errno == ENOMEM) - continue; - if (errno == EBADF || errno == EIO) { - /* One of the ttys must have gone away. */ - goto do_tty_io; - } - warning(_("select failed")); - sudo_debug_printf(SUDO_DEBUG_ERROR, - "select failure, terminating child"); - schedule_signal(SIGKILL); - forward_signals(sv[0]); - break; - } - if (FD_ISSET(sv[0], fdsw)) { - forward_signals(sv[0]); - } - if (FD_ISSET(signal_pipe[0], fdsr)) { - n = dispatch_signals(sv, child, log_io, cstat); - if (n == 0) { - /* Child has exited, cstat is set, we are done. */ - break; - } - if (n == -1) { - /* Error reading signal_pipe[0], should not happen. */ - break; - } - /* Restart event loop so signals get sent to child immediately. */ - continue; - } - if (FD_ISSET(sv[0], fdsr)) { - /* read child status */ - n = recv(sv[0], cstat, sizeof(*cstat), 0); - if (n != sizeof(*cstat)) { - if (n == -1) { - if (errno == EINTR) - continue; - if (errno != EAGAIN) { - cstat->type = CMD_ERRNO; - cstat->val = errno; - break; - } - sudo_debug_printf(SUDO_DEBUG_ERROR, - "failed to read child status: %s", strerror(errno)); - } else { - /* Short read or EOF. */ - sudo_debug_printf(SUDO_DEBUG_ERROR, - "failed to read child status: %s", - n ? "short read" : "EOF"); - /* - * If not logging I/O we may get EOF when the command is - * executed and sv is closed. It is safe to ignore this. - */ - if (log_io || n != 0) { - /* XXX - need new CMD_ type for monitor errors. */ - cstat->type = CMD_ERRNO; - cstat->val = n ? EIO : ECONNRESET; - break; - } - } - } - if (cstat->type == CMD_PID) { - /* - * Once we know the command's pid we can unblock - * signals which ere blocked in fork_pty(). This - * avoids a race between exec of the command and - * receipt of a fatal signal from it. - */ - cmnd_pid = cstat->val; - sudo_debug_printf(SUDO_DEBUG_INFO, "executed %s, pid %d", - details->command, (int)cmnd_pid); - if (log_io) - sigprocmask(SIG_SETMASK, &omask, NULL); - } else if (cstat->type == CMD_WSTATUS) { - if (WIFSTOPPED(cstat->val)) { - /* Suspend parent and tell child how to resume on return. */ - sudo_debug_printf(SUDO_DEBUG_INFO, - "child stopped, suspending parent"); - n = suspend_parent(WSTOPSIG(cstat->val)); - schedule_signal(n); - continue; - } else { - /* Child exited or was killed, either way we are done. */ - sudo_debug_printf(SUDO_DEBUG_INFO, "child exited or was killed"); - break; - } - } else if (cstat->type == CMD_ERRNO) { - /* Child was unable to execute command or broken pipe. */ - sudo_debug_printf(SUDO_DEBUG_INFO, "errno from child: %s", - strerror(cstat->val)); - break; - } - } -do_tty_io: - if (perform_io(fdsr, fdsw, cstat) != 0) { - /* I/O error, kill child if still alive and finish. */ - sudo_debug_printf(SUDO_DEBUG_ERROR, "I/O error, terminating child"); - schedule_signal(SIGKILL); - forward_signals(sv[0]); - break; - } + if (log_io) + add_io_events(evbase); + if (sudo_ev_loop(evbase, 0) == -1) + warning(_("error in event loop")); + if (sudo_ev_got_break(evbase)) { + /* error from callback */ + sudo_debug_printf(SUDO_DEBUG_ERROR, "event loop exited prematurely"); } if (log_io) { @@ -453,141 +493,224 @@ do_tty_io: } #endif - efree(fdsr); - efree(fdsw); + /* Free things up. */ while (!tq_empty(&sigfwd_list)) { struct sigforward *sigfwd = tq_first(&sigfwd_list); tq_remove(&sigfwd_list, sigfwd); efree(sigfwd); } + sudo_ev_free(sigfwd_list.event); + sudo_ev_free(signal_event); + sudo_ev_free(backchannel_event); + sudo_ev_base_free(evbase); done: debug_return_int(cstat->type == CMD_ERRNO ? -1 : 0); } /* - * Read signals on signal_pipe written by handler(). - * Returns -1 on error, 0 on child exit, else 1. + * Forward a signal to the command (non-pty version). */ static int -dispatch_signals(int sv[2], pid_t child, int log_io, struct command_status *cstat) +dispatch_signal(struct sudo_event_base *evbase, pid_t child, + int signo, char *signame, struct command_status *cstat) { + int rc = 1; + debug_decl(dispatch_signal, SUDO_DEBUG_EXEC) + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: evbase %p, child: %d, signo %s(%d), cstat %p", + __func__, evbase, (int)child, signame, signo, cstat); + + if (signo == SIGCHLD) { + pid_t pid; + int status; + /* + * The command stopped or exited. + */ + do { + pid = waitpid(child, &status, WUNTRACED|WNOHANG); + } while (pid == -1 && errno == EINTR); + if (pid == child) { + if (WIFSTOPPED(status)) { + /* + * Save the controlling terminal's process group + * so we can restore it after we resume, if needed. + * Most well-behaved shells change the pgrp back to + * its original value before suspending so we must + * not try to restore in that case, lest we race with + * the child upon resume, potentially stopping sudo + * with SIGTTOU while the command continues to run. + */ + sigaction_t sa, osa; + pid_t saved_pgrp = (pid_t)-1; + int signo = WSTOPSIG(status); + int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0); + if (fd != -1) { + saved_pgrp = tcgetpgrp(fd); + /* + * Child was stopped trying to access controlling + * terminal. If the child has a different pgrp + * and we own the controlling terminal, give it + * to the child's pgrp and let it continue. + */ + if (signo == SIGTTOU || signo == SIGTTIN) { + if (saved_pgrp == ppgrp) { + pid_t child_pgrp = getpgid(child); + if (child_pgrp != ppgrp) { + if (tcsetpgrp(fd, child_pgrp) == 0) { + if (killpg(child_pgrp, SIGCONT) != 0) { + warning("kill(%d, SIGCONT)", + (int)child_pgrp); + } + close(fd); + goto done; + } + } + } + } + } + if (signo == SIGTSTP) { + memset(&sa, 0, sizeof(sa)); + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART; + sa.sa_handler = SIG_DFL; + sudo_sigaction(SIGTSTP, &sa, &osa); + } + if (kill(getpid(), signo) != 0) + warning("kill(%d, SIG%s)", (int)getpid(), signame); + if (signo == SIGTSTP) + sudo_sigaction(SIGTSTP, &osa, NULL); + if (fd != -1) { + /* + * Restore command's process group if different. + * Otherwise, we cannot resume some shells. + */ + if (saved_pgrp != ppgrp) + (void)tcsetpgrp(fd, saved_pgrp); + close(fd); + } + } else { + /* Child has exited or been killed, we are done. */ + cstat->type = CMD_WSTATUS; + cstat->val = status; + sudo_ev_loopexit(evbase); + goto done; + } + } + } else { + /* Send signal to child. */ + if (signo == SIGALRM) { + terminate_command(child, false); + } else if (kill(child, signo) != 0) { + warning("kill(%d, SIG%s)", (int)child, signame); + } + } + rc = 0; +done: + debug_return_int(rc); +} + +/* + * Forward a signal to the monitory (pty version). + */ +static int +dispatch_signal_pty(struct sudo_event_base *evbase, pid_t child, + int signo, char *signame, struct command_status *cstat) +{ + int rc = 1; + debug_decl(dispatch_signal_pty, SUDO_DEBUG_EXEC) + + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: evbase %p, child: %d, signo %s(%d), cstat %p", + __func__, evbase, (int)child, signame, signo, cstat); + + if (signo == SIGCHLD) { + int n, status; + pid_t pid; + /* + * Monitor process was signaled; wait for it as needed. + */ + do { + pid = waitpid(child, &status, WUNTRACED|WNOHANG); + } while (pid == -1 && errno == EINTR); + if (pid == child) { + /* + * If the monitor dies we get notified via backchannel_cb(). + * If it was stopped, we should stop too (the command keeps + * running in its pty) and continue it when we come back. + */ + if (WIFSTOPPED(status)) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "monitor stopped, suspending parent"); + n = suspend_parent(WSTOPSIG(status)); + kill(pid, SIGCONT); + schedule_signal(evbase, n); + /* Re-enable I/O events and restart event loop. */ + add_io_events(evbase); + sudo_ev_loopcontinue(evbase); + goto done; + } else if (WIFSIGNALED(status)) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "monitor killed, signal %d", WTERMSIG(status)); + } else { + sudo_debug_printf(SUDO_DEBUG_INFO, + "monitor exited, status %d", WEXITSTATUS(status)); + } + } + } else { + /* Schedule signo to be forwared to the child. */ + schedule_signal(evbase, signo); + /* Restart event loop to service signal immediately. */ + sudo_ev_loopcontinue(evbase); + } + rc = 0; +done: + debug_return_int(rc); +} + +/* Signal pipe callback */ +static void +signal_pipe_cb(int fd, int what, void *v) +{ + struct exec_closure *ec = v; char signame[SIG2STR_MAX]; unsigned char signo; ssize_t nread; - int status; - pid_t pid; - debug_decl(dispatch_signals, SUDO_DEBUG_EXEC) + int rc = 0; + debug_decl(signal_pipe_cb, SUDO_DEBUG_EXEC) - for (;;) { + do { /* read signal pipe */ - nread = read(signal_pipe[0], &signo, sizeof(signo)); + nread = read(fd, &signo, sizeof(signo)); if (nread <= 0) { - /* It should not be possible to get EOF but just in case. */ + /* It should not be possible to get EOF but just in case... */ if (nread == 0) errno = ECONNRESET; /* Restart if interrupted by signal so the pipe doesn't fill. */ if (errno == EINTR) continue; - /* If pipe is empty, we are done. */ - if (errno == EAGAIN) - break; - sudo_debug_printf(SUDO_DEBUG_ERROR, "error reading signal pipe %s", - strerror(errno)); - cstat->type = CMD_ERRNO; - cstat->val = errno; - debug_return_int(-1); + /* On error, store errno and break out of the event loop. */ + if (errno != EAGAIN) { + sudo_debug_printf(SUDO_DEBUG_ERROR, + "error reading signal pipe %s", strerror(errno)); + ec->cstat->type = CMD_ERRNO; + ec->cstat->val = errno; + sudo_ev_loopbreak(ec->evbase); + } + break; } if (sig2str(signo, signame) == -1) snprintf(signame, sizeof(signame), "%d", signo); sudo_debug_printf(SUDO_DEBUG_DIAG, "received SIG%s", signame); - if (signo == SIGCHLD) { - /* - * If logging I/O, child is the intermediate process, - * otherwise it is the command itself. - */ - do { - pid = waitpid(child, &status, WUNTRACED|WNOHANG); - } while (pid == -1 && errno == EINTR); - if (pid == child && !log_io) { - if (WIFSTOPPED(status)) { - /* - * Save the controlling terminal's process group - * so we can restore it after we resume, if needed. - * Most well-behaved shells change the pgrp back to - * its original value before suspending so we must - * not try to restore in that case, lest we race with - * the child upon resume, potentially stopping sudo - * with SIGTTOU while the command continues to run. - */ - sigaction_t sa, osa; - pid_t saved_pgrp = (pid_t)-1; - int signo = WSTOPSIG(status); - int fd = open(_PATH_TTY, O_RDWR|O_NOCTTY, 0); - if (fd != -1) { - saved_pgrp = tcgetpgrp(fd); - /* - * Child was stopped trying to access controlling - * terminal. If the child has a different pgrp - * and we own the controlling terminal, give it - * to the child's pgrp and let it continue. - */ - if (signo == SIGTTOU || signo == SIGTTIN) { - if (saved_pgrp == ppgrp) { - pid_t child_pgrp = getpgid(child); - if (child_pgrp != ppgrp) { - if (tcsetpgrp(fd, child_pgrp) == 0) { - if (killpg(child_pgrp, SIGCONT) != 0) { - warning("kill(%d, SIGCONT)", - (int)child_pgrp); - } - close(fd); - debug_return_int(1); - } - } - } - } - } - if (signo == SIGTSTP) { - memset(&sa, 0, sizeof(sa)); - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESTART; - sa.sa_handler = SIG_DFL; - sudo_sigaction(SIGTSTP, &sa, &osa); - } - if (kill(getpid(), signo) != 0) - warning("kill(%d, SIG%s)", (int)getpid(), signame); - if (signo == SIGTSTP) - sudo_sigaction(SIGTSTP, &osa, NULL); - if (fd != -1) { - /* - * Restore command's process group if different. - * Otherwise, we cannot resume some shells. - */ - if (saved_pgrp != ppgrp) - (void)tcsetpgrp(fd, saved_pgrp); - close(fd); - } - } else { - /* Child has exited or been killed, we are done. */ - cstat->type = CMD_WSTATUS; - cstat->val = status; - debug_return_int(0); - } - } + if (ec->log_io) { + rc = dispatch_signal_pty(ec->evbase, ec->child, signo, signame, + ec->cstat); } else { - if (log_io) { - /* Schedule signo to be forwared to the child. */ - schedule_signal(signo); - } else { - /* Nothing listening on sv[0], send directly. */ - if (signo == SIGALRM) - terminate_command(child, false); - else if (kill(child, signo) != 0) - warning("kill(%d, SIG%s)", (int)child, signame); - } + rc = dispatch_signal(ec->evbase, ec->child, signo, signame, + ec->cstat); } - } - debug_return_int(1); + } while (rc == 0); + debug_return; } /* @@ -651,7 +774,7 @@ dispatch_pending_signals(struct command_status *cstat) * Forward signals in sigfwd_list to child listening on fd. */ static void -forward_signals(int sock) +forward_signals(int sock, int what, void *v) { char signame[SIG2STR_MAX]; struct sigforward *sigfwd; @@ -691,14 +814,13 @@ forward_signals(int sock) break; } } - debug_return; } /* - * Schedule a signal to be forwared. + * Schedule a signal to be forwarded. */ static void -schedule_signal(int signo) +schedule_signal(struct sudo_event_base *evbase, int signo) { struct sigforward *sigfwd; char signame[SIG2STR_MAX]; @@ -718,6 +840,9 @@ schedule_signal(int signo) sigfwd->signo = signo; tq_append(&sigfwd_list, sigfwd); + if (sudo_ev_add(evbase, sigfwd_list.event, true) == -1) + fatal(_("unable to add event to queue")); + debug_return; } diff --git a/src/exec_pty.c b/src/exec_pty.c index 3b7feac66..ed83e4e5d 100644 --- a/src/exec_pty.c +++ b/src/exec_pty.c @@ -57,6 +57,7 @@ #include #include "sudo.h" +#include "sudo_event.h" #include "sudo_exec.h" #include "sudo_plugin.h" #include "sudo_plugin_int.h" @@ -68,6 +69,9 @@ #define SFD_SLAVE 4 #define SFD_USERTTY 5 +/* Evaluates to true if the event has /dev/tty as its fd. */ +#define USERTTY_EVENT(_ev) (sudo_ev_get_fd((_ev)) == io_fds[SFD_USERTTY]) + #define TERM_COOKED 0 #define TERM_RAW 1 @@ -80,23 +84,28 @@ struct io_buffer { struct io_buffer *next; + struct sudo_event *revent; + struct sudo_event *wevent; + bool (*action)(const char *buf, unsigned int len); int len; /* buffer length (how much produced) */ int off; /* write position (how much already consumed) */ - int rfd; /* reader (producer) */ - int wfd; /* writer (consumer) */ - bool (*action)(const char *buf, unsigned int len); char buf[32 * 1024]; }; +struct io_buffer_list { + struct io_buffer *first; + /* XXX - stash cstat too? */ +}; + static char slavename[PATH_MAX]; static bool foreground, pipeline, tty_initialized; static int io_fds[6] = { -1, -1, -1, -1, -1, -1}; static int ttymode = TERM_COOKED; static pid_t ppgrp, cmnd_pgrp, mon_pgrp; static sigset_t ttyblock; -static struct io_buffer *iobufs; +static struct io_buffer_list iobufs; -static void flush_output(void); +static void del_io_events(void); static int exec_monitor(struct command_details *details, int backchannel); static void exec_pty(struct command_details *details, struct command_status *cstat, int *errfd); @@ -362,8 +371,8 @@ suspend_parent(int signo) /* FALLTHROUGH */ case SIGSTOP: case SIGTSTP: - /* Flush any remaining output before suspending. */ - flush_output(); + /* Flush any remaining output and deschedule I/O events. */ + del_io_events(); /* Restore original tty mode before suspending. */ if (ttymode != TERM_COOKED) { @@ -454,102 +463,172 @@ terminate_command(pid_t pid, bool use_pgrp) debug_return; } -static struct io_buffer * -io_buf_new(int rfd, int wfd, bool (*action)(const char *, unsigned int), - struct io_buffer *head) +/* + * Read/write an iobuf that is ready. + */ +static void +io_callback(int fd, int what, void *v) { + struct io_buffer *iob = v; + struct sudo_event_base *evbase; + int n; + debug_decl(io_callback, SUDO_DEBUG_EXEC); + + if (ISSET(what, SUDO_EV_READ)) { + evbase = sudo_ev_get_base(iob->revent); + do { + n = read(fd, iob->buf + iob->len, sizeof(iob->buf) - iob->len); + } while (n == -1 && errno == EINTR); + switch (n) { + case -1: + if (errno == EAGAIN) + break; + /* treat read error as fatal and close the fd */ + sudo_debug_printf(SUDO_DEBUG_ERROR, + "error reading fd %d: %s", fd, strerror(errno)); + /* FALLTHROUGH */ + case 0: + /* got EOF or pty has gone away */ + if (n == 0) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "read EOF from fd %d", fd); + } + (void) sudo_ev_del(evbase, iob->revent); + safe_close(fd); + sudo_ev_free(iob->revent); + iob->revent = NULL; + /* If writer already consumed the buffer, close it too. */ + if (iob->wevent != NULL && iob->off == iob->len) { + (void) sudo_ev_del(evbase, iob->wevent); + safe_close(sudo_ev_get_fd(iob->wevent)); + sudo_ev_free(iob->wevent); + iob->wevent = NULL; + iob->off = iob->len = 0; + } + break; + default: + sudo_debug_printf(SUDO_DEBUG_INFO, + "read %d bytes from fd %d", n, fd); + if (!iob->action(iob->buf + iob->len, n)) + terminate_command(cmnd_pid, true); + iob->len += n; + /* Enable writer if not /dev/tty or we are foreground pgrp. */ + if (iob->wevent != NULL && + (foreground || !USERTTY_EVENT(iob->wevent))) { + if (sudo_ev_add(evbase, iob->wevent, false) == -1) + fatal(_("unable to add event to queue")); + } + /* Re-enable reader if buffer is not full. */ + if (iob->len != sizeof(iob->buf)) { + if (sudo_ev_add(evbase, iob->revent, false) == -1) + fatal(_("unable to add event to queue")); + } + break; + } + } + if (ISSET(what, SUDO_EV_WRITE)) { + evbase = sudo_ev_get_base(iob->wevent); + do { + n = write(fd, iob->buf + iob->off, iob->len - iob->off); + } while (n == -1 && errno == EINTR); + if (n == -1) { + switch (errno) { + case EPIPE: + case ENXIO: + case EIO: + case EBADF: + /* other end of pipe closed or pty revoked */ + sudo_debug_printf(SUDO_DEBUG_INFO, + "unable to write %d bytes to fd %d", + iob->len - iob->off, fd); + if (iob->revent != NULL) { + (void) sudo_ev_del(evbase, iob->revent); + safe_close(sudo_ev_get_fd(iob->revent)); + sudo_ev_free(iob->revent); + iob->revent = NULL; + } + (void) sudo_ev_del(evbase, iob->wevent); + safe_close(fd); + sudo_ev_free(iob->wevent); + iob->wevent = NULL; + break; + case EAGAIN: + /* not an error */ + break; + default: +#if 0 /* XXX -- how to set cstat? stash in iobufs instead? */ + if (cstat != NULL) { + cstat->type = CMD_ERRNO; + cstat->val = errno; + } +#endif /* XXX */ + sudo_debug_printf(SUDO_DEBUG_ERROR, + "error writing fd %d: %s", fd, strerror(errno)); + sudo_ev_loopbreak(evbase); + break; + } + } else { + sudo_debug_printf(SUDO_DEBUG_INFO, + "wrote %d bytes to fd %d", n, fd); + iob->off += n; + /* Reset buffer if fully consumed. */ + if (iob->off == iob->len) { + iob->off = iob->len = 0; + /* Forward the EOF from reader to writer. */ + if (iob->revent == NULL) { + (void) sudo_ev_del(evbase, iob->wevent); + safe_close(fd); + sudo_ev_free(iob->wevent); + iob->wevent = NULL; + } + } + /* Re-enable writer if buffer is not empty. */ + if (iob->len > iob->off) { + if (sudo_ev_add(evbase, iob->wevent, false) == -1) + fatal(_("unable to add event to queue")); + } + /* Enable reader if buffer is not full. */ + if (iob->revent != NULL && + (ttymode == TERM_RAW || !USERTTY_EVENT(iob->revent))) { + if (iob->len != sizeof(iob->buf)) { + if (sudo_ev_add(evbase, iob->revent, false) == -1) + fatal(_("unable to add event to queue")); + } + } + } + } +} + +static void +io_buf_new(int rfd, int wfd, bool (*action)(const char *, unsigned int), + struct io_buffer_list *head) +{ + int n; struct io_buffer *iob; debug_decl(io_buf_new, SUDO_DEBUG_EXEC); - iob = ecalloc(1, sizeof(*iob)); - iob->rfd = rfd; - iob->wfd = wfd; + /* Set non-blocking mode. */ + n = fcntl(rfd, F_GETFL, 0); + if (n != -1 && !ISSET(n, O_NONBLOCK)) + (void) fcntl(rfd, F_SETFL, n | O_NONBLOCK); + n = fcntl(wfd, F_GETFL, 0); + if (n != -1 && !ISSET(n, O_NONBLOCK)) + (void) fcntl(wfd, F_SETFL, n | O_NONBLOCK); + + /* Add to head of list. */ + iob = emalloc(sizeof(*iob)); + iob->next = head->first; + iob->revent = sudo_ev_alloc(rfd, SUDO_EV_READ, io_callback, iob); + iob->wevent = sudo_ev_alloc(wfd, SUDO_EV_WRITE, io_callback, iob); + iob->len = 0; + iob->off = 0; iob->action = action; - iob->next = head; + iob->buf[0] = '\0'; + if (iob->revent == NULL || iob->wevent == NULL) + fatal(NULL); + head->first = iob; - debug_return_ptr(iob); -} - -/* - * Read/write iobufs depending on fdsr and fdsw. - * Returns the number of errors. - */ -int -perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat) -{ - struct io_buffer *iob; - int n, errors = 0; - debug_decl(perform_io, SUDO_DEBUG_EXEC); - - for (iob = iobufs; iob; iob = iob->next) { - if (iob->rfd != -1 && FD_ISSET(iob->rfd, fdsr)) { - do { - n = read(iob->rfd, iob->buf + iob->len, - sizeof(iob->buf) - iob->len); - } while (n == -1 && errno == EINTR); - switch (n) { - case -1: - if (errno != EAGAIN) { - /* treat read error as fatal and close the fd */ - sudo_debug_printf(SUDO_DEBUG_ERROR, - "error reading fd %d: %s", iob->rfd, - strerror(errno)); - safe_close(iob->rfd); - iob->rfd = -1; - } - break; - case 0: - /* got EOF or pty has gone away */ - sudo_debug_printf(SUDO_DEBUG_INFO, - "read EOF from fd %d", iob->rfd); - safe_close(iob->rfd); - iob->rfd = -1; - break; - default: - sudo_debug_printf(SUDO_DEBUG_INFO, - "read %d bytes from fd %d", n, iob->rfd); - if (!iob->action(iob->buf + iob->len, n)) - terminate_command(cmnd_pid, true); - iob->len += n; - break; - } - } - if (iob->wfd != -1 && FD_ISSET(iob->wfd, fdsw)) { - do { - n = write(iob->wfd, iob->buf + iob->off, - iob->len - iob->off); - } while (n == -1 && errno == EINTR); - if (n == -1) { - if (errno == EPIPE || errno == ENXIO || errno == EIO || errno == EBADF) { - sudo_debug_printf(SUDO_DEBUG_INFO, - "unable to write %d bytes to fd %d", - iob->len - iob->off, iob->wfd); - /* other end of pipe closed or pty revoked */ - if (iob->rfd != -1) { - safe_close(iob->rfd); - iob->rfd = -1; - } - safe_close(iob->wfd); - iob->wfd = -1; - continue; - } - if (errno != EAGAIN) { - errors++; - sudo_debug_printf(SUDO_DEBUG_ERROR, - "error writing fd %d: %s", iob->wfd, strerror(errno)); - } - } else { - sudo_debug_printf(SUDO_DEBUG_INFO, - "wrote %d bytes to fd %d", n, iob->wfd); - iob->off += n; - } - } - } - if (errors && cstat != NULL) { - cstat->type = CMD_ERRNO; - cstat->val = errno; - } - debug_return_int(errors); + debug_return; } /* @@ -558,10 +637,9 @@ perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat) * Returns the child pid. */ int -fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask) +fork_pty(struct command_details *details, int sv[], sigset_t *omask) { struct command_status cstat; - struct io_buffer *iob; int io_pipe[3][2], n; sigaction_t sa; sigset_t mask; @@ -599,13 +677,13 @@ fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask) if (io_fds[SFD_USERTTY] != -1) { /* Read from /dev/tty, write to pty master */ if (!ISSET(details->flags, CD_BACKGROUND)) { - iobufs = io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER], - log_ttyin, iobufs); + io_buf_new(io_fds[SFD_USERTTY], io_fds[SFD_MASTER], + log_ttyin, &iobufs); } /* Read from pty master, write to /dev/tty */ - iobufs = io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY], - log_ttyout, iobufs); + io_buf_new(io_fds[SFD_MASTER], io_fds[SFD_USERTTY], + log_ttyout, &iobufs); /* Are we the foreground process? */ foreground = tcgetpgrp(io_fds[SFD_USERTTY]) == ppgrp; @@ -621,8 +699,8 @@ fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask) pipeline = true; if (pipe(io_pipe[STDIN_FILENO]) != 0) fatal(_("unable to create pipe")); - iobufs = io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1], - log_stdin, iobufs); + io_buf_new(STDIN_FILENO, io_pipe[STDIN_FILENO][1], + log_stdin, &iobufs); io_fds[SFD_STDIN] = io_pipe[STDIN_FILENO][0]; } if (io_fds[SFD_STDOUT] == -1 || !isatty(STDOUT_FILENO)) { @@ -630,16 +708,16 @@ fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask) pipeline = true; if (pipe(io_pipe[STDOUT_FILENO]) != 0) fatal(_("unable to create pipe")); - iobufs = io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO, - log_stdout, iobufs); + io_buf_new(io_pipe[STDOUT_FILENO][0], STDOUT_FILENO, + log_stdout, &iobufs); io_fds[SFD_STDOUT] = io_pipe[STDOUT_FILENO][1]; } if (io_fds[SFD_STDERR] == -1 || !isatty(STDERR_FILENO)) { sudo_debug_printf(SUDO_DEBUG_INFO, "stderr not a tty, creating a pipe"); if (pipe(io_pipe[STDERR_FILENO]) != 0) fatal(_("unable to create pipe")); - iobufs = io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO, - log_stderr, iobufs); + io_buf_new(io_pipe[STDERR_FILENO][0], STDERR_FILENO, + log_stderr, &iobufs); io_fds[SFD_STDERR] = io_pipe[STDERR_FILENO][1]; } @@ -729,28 +807,13 @@ fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask) if (io_pipe[STDERR_FILENO][1]) close(io_pipe[STDERR_FILENO][1]); - for (iob = iobufs; iob; iob = iob->next) { - /* Determine maxfd */ - if (iob->rfd > *maxfd) - *maxfd = iob->rfd; - if (iob->wfd > *maxfd) - *maxfd = iob->wfd; - - /* Set non-blocking mode. */ - n = fcntl(iob->rfd, F_GETFL, 0); - if (n != -1 && !ISSET(n, O_NONBLOCK)) - (void) fcntl(iob->rfd, F_SETFL, n | O_NONBLOCK); - n = fcntl(iob->wfd, F_GETFL, 0); - if (n != -1 && !ISSET(n, O_NONBLOCK)) - (void) fcntl(iob->wfd, F_SETFL, n | O_NONBLOCK); - } - debug_return_int(child); } void pty_close(struct command_status *cstat) { + struct io_buffer *iob; int n; debug_decl(pty_close, SUDO_DEBUG_EXEC); @@ -762,8 +825,15 @@ pty_close(struct command_status *cstat) (void) fcntl(io_fds[SFD_USERTTY], F_SETFL, n); } } - flush_output(); + del_io_events(); + /* Free I/O buffers. */ + while ((iob = iobufs.first) != NULL) { + iobufs.first = iob->next; + efree(iob); + } + + /* Restore terminal settings. */ if (io_fds[SFD_USERTTY] != -1) { check_foreground(); if (foreground) { @@ -793,41 +863,111 @@ pty_close(struct command_status *cstat) } /* - * Fill in fdsr and fdsw based on the io buffers list. - * Called prior to select(). + * Schedule I/O events before starting the main event loop or + * resuming from suspend. */ void -fd_set_iobs(fd_set *fdsr, fd_set *fdsw) +add_io_events(struct sudo_event_base *evbase) { struct io_buffer *iob; - debug_decl(fd_set_iobs, SUDO_DEBUG_EXEC); + debug_decl(add_io_events, SUDO_DEBUG_EXEC); - for (iob = iobufs; iob; iob = iob->next) { - if (iob->rfd == -1 && iob->wfd == -1) - continue; - if (iob->off == iob->len) { - iob->off = iob->len = 0; - /* Forward the EOF from reader to writer. */ - if (iob->rfd == -1) { - safe_close(iob->wfd); - iob->wfd = -1; + /* + * Schedule all readers as long as the buffer is not full. + * Schedule writers that contain buffered data. + * Normally, write buffers are added on demand when data is read. + */ + for (iob = iobufs.first; iob != NULL; iob = iob->next) { + /* Don't read/write from /dev/tty if we are not in the foreground. */ + if (iob->revent != NULL && + (ttymode == TERM_RAW || !USERTTY_EVENT(iob->revent))) { + if (iob->len != sizeof(iob->buf)) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "added I/O revent %p, fd %d, events %d", + iob->revent, iob->revent->fd, iob->revent->events); + if (sudo_ev_add(evbase, iob->revent, false) == -1) + fatal(_("unable to add event to queue")); } } - /* Don't read/write /dev/tty if we are not in the foreground. */ - if (iob->rfd != -1 && - (ttymode == TERM_RAW || iob->rfd != io_fds[SFD_USERTTY])) { - if (iob->len != sizeof(iob->buf)) - FD_SET(iob->rfd, fdsr); - } - if (iob->wfd != -1 && - (foreground || iob->wfd != io_fds[SFD_USERTTY])) { - if (iob->len > iob->off) - FD_SET(iob->wfd, fdsw); + if (iob->wevent != NULL && + (foreground || !USERTTY_EVENT(iob->wevent))) { + if (iob->len > iob->off) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "added I/O wevent %p, fd %d, events %d", + iob->wevent, iob->wevent->fd, iob->wevent->events); + if (sudo_ev_add(evbase, iob->wevent, false) == -1) + fatal(_("unable to add event to queue")); + } } } debug_return; } +/* + * Flush any output buffered in iobufs or readable from fds other + * than /dev/tty. Removes I/O events from the event base when done. + */ +static void +del_io_events(void) +{ + struct io_buffer *iob; + struct sudo_event_base *evbase; + debug_decl(del_io_events, SUDO_DEBUG_EXEC); + + /* Remove iobufs from existing event base. */ + for (iob = iobufs.first; iob != NULL; iob = iob->next) { + if (iob->revent != NULL) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "deleted I/O revent %p, fd %d, events %d", + iob->revent, iob->revent->fd, iob->revent->events); + sudo_ev_del(NULL, iob->revent); + } + if (iob->wevent != NULL) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "deleted I/O wevent %p, fd %d, events %d", + iob->wevent, iob->wevent->fd, iob->wevent->events); + sudo_ev_del(NULL, iob->wevent); + } + } + + /* Create temporary event base for flushing. */ + evbase = sudo_ev_base_alloc(); + if (evbase == NULL) + fatal(NULL); + + /* Avoid reading from /dev/tty, just flush existing data. */ + for (iob = iobufs.first; iob != NULL; iob = iob->next) { + /* Don't read from /dev/tty while flushing. */ + if (iob->revent != NULL && !USERTTY_EVENT(iob->revent)) { + if (iob->len != sizeof(iob->buf)) { + if (sudo_ev_add(evbase, iob->revent, false) == -1) + fatal(_("unable to add event to queue")); + } + } + /* Flush any write buffers with data in them. */ + if (iob->wevent != NULL && + (foreground || !USERTTY_EVENT(iob->wevent))) { + if (iob->len > iob->off) { + if (sudo_ev_add(evbase, iob->wevent, false) == -1) + fatal(_("unable to add event to queue")); + } + } + } + + (void) sudo_ev_loop(evbase, SUDO_EVLOOP_NONBLOCK); + + /* Free temporary event base. */ + for (iob = iobufs.first; iob != NULL; iob = iob->next) { + if (iob->revent != NULL) + sudo_ev_del(evbase, iob->revent); + if (iob->wevent != NULL) + sudo_ev_del(evbase, iob->wevent); + } + sudo_ev_base_free(evbase); + + debug_return; +} + static void deliver_signal(pid_t pid, int signo, bool from_parent) { @@ -1205,81 +1345,6 @@ bad: debug_return_int(errno); } -/* - * Flush any output buffered in iobufs or readable from the fds. - * Does not read from /dev/tty. - */ -static void -flush_output(void) -{ - struct io_buffer *iob; - struct timeval tv; - fd_set *fdsr, *fdsw; - int nready, nwriters, maxfd = -1; - debug_decl(flush_output, SUDO_DEBUG_EXEC); - - /* Determine maxfd */ - for (iob = iobufs; iob; iob = iob->next) { - if (iob->rfd > maxfd) - maxfd = iob->rfd; - if (iob->wfd > maxfd) - maxfd = iob->wfd; - } - if (maxfd == -1) - debug_return; - - fdsr = emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); - fdsw = emalloc2(howmany(maxfd + 1, NFDBITS), sizeof(fd_mask)); - for (;;) { - memset(fdsw, 0, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); - memset(fdsr, 0, howmany(maxfd + 1, NFDBITS) * sizeof(fd_mask)); - - nwriters = 0; - for (iob = iobufs; iob; iob = iob->next) { - /* Don't read from /dev/tty while flushing. */ - if (io_fds[SFD_USERTTY] != -1 && iob->rfd == io_fds[SFD_USERTTY]) - continue; - if (iob->rfd == -1 && iob->wfd == -1) - continue; - if (iob->off == iob->len) { - iob->off = iob->len = 0; - /* Forward the EOF from reader to writer. */ - if (iob->rfd == -1) { - safe_close(iob->wfd); - iob->wfd = -1; - } - } - if (iob->rfd != -1) { - if (iob->len != sizeof(iob->buf)) - FD_SET(iob->rfd, fdsr); - } - if (iob->wfd != -1) { - if (iob->len > iob->off) { - nwriters++; - FD_SET(iob->wfd, fdsw); - } - } - } - - /* Don't sleep in select if there are no buffers that need writing. */ - tv.tv_sec = 0; - tv.tv_usec = 0; - nready = select(maxfd + 1, fdsr, fdsw, NULL, nwriters ? NULL : &tv); - if (nready <= 0) { - if (nready == 0) - break; /* all I/O flushed */ - if (errno == EINTR || errno == ENOMEM) - continue; - warning(_("select failed")); - } - if (perform_io(fdsr, fdsw, NULL) != 0 || nready == -1) - break; - } - efree(fdsr); - efree(fdsw); - debug_return; -} - /* * Sets up std{in,out,err} and executes the actual command. * Returns only if execve() fails. @@ -1360,17 +1425,40 @@ sigwinch(int s) /* * Only close the fd if it is not /dev/tty or std{in,out,err}. - * Return value is the same as send(2). + * Return value is the same as close(2). */ static int safe_close(int fd) { + struct io_buffer *iob; debug_decl(safe_close, SUDO_DEBUG_EXEC); /* Avoid closing /dev/tty or std{in,out,err}. */ if (fd < 3 || fd == io_fds[SFD_USERTTY]) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: not closing fd %d (/dev/tty)", __func__, fd); errno = EINVAL; debug_return_int(-1); } + /* Deschedule any other users of the fd. */ + for (iob = iobufs.first; iob != NULL; iob = iob->next) { + if (iob->revent != NULL) { + if (sudo_ev_get_fd(iob->revent) == fd) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: deleting revent %p due to shared fd %d", + __func__, iob->revent, fd); + sudo_ev_del(NULL, iob->revent); + } + } + if (iob->wevent != NULL) { + if (sudo_ev_get_fd(iob->wevent) == fd) { + sudo_debug_printf(SUDO_DEBUG_INFO, + "%s: deleting wevent %p due to shared fd %d", + __func__, iob->wevent, fd); + sudo_ev_del(NULL, iob->wevent); + } + } + } + sudo_debug_printf(SUDO_DEBUG_INFO, "%s: closing fd %d", __func__, fd); debug_return_int(close(fd)); } diff --git a/src/sudo_exec.h b/src/sudo_exec.h index 9238fda87..3b5a5f7d4 100644 --- a/src/sudo_exec.h +++ b/src/sudo_exec.h @@ -45,18 +45,18 @@ */ /* exec.c */ +struct sudo_event_base; int sudo_execve(const char *path, char *const argv[], char *const envp[], int noexec); extern volatile pid_t cmnd_pid; /* exec_pty.c */ struct command_details; struct command_status; -int fork_pty(struct command_details *details, int sv[], int *maxfd, sigset_t *omask); -int perform_io(fd_set *fdsr, fd_set *fdsw, struct command_status *cstat); +int fork_pty(struct command_details *details, int sv[], sigset_t *omask); int suspend_parent(int signo); void exec_cmnd(struct command_details *details, struct command_status *cstat, int *errfd); -void fd_set_iobs(fd_set *fdsr, fd_set *fdsw); +void add_io_events(struct sudo_event_base *evbase); #ifdef SA_SIGINFO void handler(int s, siginfo_t *info, void *context); #else