From: Todd C. Miller Date: Thu, 27 Jul 2017 15:45:35 +0000 (-0600) Subject: Change to a single event loop in sudoreplay and use signal events. X-Git-Tag: SUDO_1_8_21^2~22 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=2d30c42a03fd558c394f648b75e90666c4998a9f;p=sudo Change to a single event loop in sudoreplay and use signal events. --- diff --git a/plugins/sudoers/sudoreplay.c b/plugins/sudoers/sudoreplay.c index 3504c8166..f4ea7ce50 100644 --- a/plugins/sudoers/sudoreplay.c +++ b/plugins/sudoers/sudoreplay.c @@ -91,27 +91,35 @@ struct log_info { int cols; }; -/* - * I/O log timing entry. - */ -struct iolog_timing { - double seconds; - int idx; - union { - struct { - int rows; - int cols; - } winsize; - size_t nbytes; - } u; -}; - -/* Closure for write_output */ -struct write_closure { - struct sudo_event *wevent; - struct iovec *iov; - unsigned int iovcnt; - size_t nbytes; +struct replay_closure { + struct sudo_event_base *evbase; + struct sudo_event *delay_ev; + struct sudo_event *keyboard_ev; + struct sudo_event *output_ev; + struct sudo_event *sighup_ev; + struct sudo_event *sigint_ev; + struct sudo_event *sigquit_ev; + struct sudo_event *sigterm_ev; + struct sudo_event *sigtstp_ev; + struct timing_closure { + const char *decimal; + double max_delay; + int idx; + union { + struct { + int rows; + int cols; + } winsize; + size_t nbytes; // XXX + } u; + } timing; + bool interactive; + struct io_buffer { + unsigned int len; /* buffer length (how much produced) */ + unsigned int off; /* write position (how much already consumed) */ + unsigned int toread; /* how much remains to be read */ + char buf[64 * 1024]; + } iobuf; }; /* @@ -181,14 +189,13 @@ extern time_t get_date(char *); static int list_sessions(int, char **, const char *, const char *, const char *); static int open_io_fd(char *path, int len, struct io_log_file *iol); static int parse_expr(struct search_node_list *, char **, bool); -static int parse_timing(const char *buf, const char *decimal, struct iolog_timing *timing); +static bool parse_timing(const char *buf, double *seconds, struct timing_closure *timing); static struct log_info *parse_logfile(char *logfile); -static void check_input(int fd, int what, void *v); +static void read_keyboard(int fd, int what, void *v); static void free_log_info(struct log_info *li); static void help(void) __attribute__((__noreturn__)); -static void replay_session(const double max_wait, const char *decimal, bool interactive); +static int replay_session(double max_wait, const char *decimal, bool interactive); static void sudoreplay_cleanup(void); -static void sudoreplay_handler(int); static void usage(int); static void write_output(int fd, int what, void *v); static void restore_terminal_size(void); @@ -219,14 +226,14 @@ main(int argc, char *argv[]) const char *decimal, *id, *user = NULL, *pattern = NULL, *tty = NULL; char *cp, *ep, path[PATH_MAX]; struct log_info *li; - double max_wait = 0; + double max_delay = 0; debug_decl(main, SUDO_DEBUG_MAIN) #if defined(SUDO_DEVEL) && defined(__OpenBSD__) { extern char *malloc_options; malloc_options = "S"; - } + } #endif initprogname(argc > 0 ? argv[0] : "sudoreplay"); @@ -275,7 +282,7 @@ main(int argc, char *argv[]) break; case 'm': errno = 0; - max_wait = strtod(optarg, &ep); + max_delay = strtod(optarg, &ep); if (*ep != '\0' || errno != 0) sudo_fatalx(U_("invalid max wait: %s"), optarg); break; @@ -337,7 +344,7 @@ main(int argc, char *argv[]) /* Open files for replay, applying replay filter for the -f flag. */ for (idx = 0; idx < IOFD_MAX; idx++) { - if (open_io_fd(path, plen, &io_log_files[idx]) == -1) + if (open_io_fd(path, plen, &io_log_files[idx]) == -1) sudo_fatal(U_("unable to open %s"), path); } @@ -360,7 +367,7 @@ main(int argc, char *argv[]) li = NULL; /* Replay session corresponding to io_log_files[]. */ - replay_session(max_wait, decimal, interactive); + exitcode = replay_session(max_delay, decimal, interactive); restore_terminal_size(); sudo_term_restore(ttyfd, true); @@ -393,6 +400,20 @@ io_log_read(int idx, char *buf, size_t nbytes) debug_return_ssize_t(nread); } +static int +io_log_eof(int idx) +{ + int ret; + debug_decl(io_log_eof, SUDO_DEBUG_UTIL) + +#ifdef HAVE_ZLIB_H + ret = gzeof(io_log_files[idx].fd.g); +#else + ret = feof(io_log_files[idx].fd.f); +#endif + debug_return_int(ret); +} + static char * io_log_gets(int idx, char *buf, size_t nbytes) { @@ -608,6 +629,7 @@ xterm_set_size(int rows, int cols) char buf[1024]; debug_decl(xterm_set_size, SUDO_DEBUG_UTIL) + /* XXX - save cursor and position restore after resizing */ len = snprintf(buf, sizeof(buf), setsize_fmt, rows, cols); if (len < 0 || len >= (int)sizeof(buf)) { /* not possible due to size of buf */ @@ -620,6 +642,7 @@ xterm_set_size(int rows, int cols) "%s: error writing xterm resize request", __func__); goto done; } + /* XXX - keyboard input will interfere with this */ if (!xterm_get_size(&new_rows, &new_cols)) goto done; if (rows == new_rows && cols == new_cols) @@ -732,197 +755,277 @@ restore_terminal_size(void) debug_return; } -static void -replay_session(const double max_wait, const char *decimal, bool interactive) +/* + * Read the next record from the timing file and schedule a delay + * event with the specified timeout. + * Return 0 on success, 1 on EOF and -1 on error. + */ +static int +read_timing_record(struct replay_closure *closure) { - struct sudo_event *input_ev, *output_ev; - unsigned int i, iovcnt = 0, iovmax = 0; - struct sudo_event_base *evbase; - struct iovec iovb, *iov = &iovb; - struct write_closure wc; + struct timeval timeout; char buf[LINE_MAX]; - struct sigaction sa; - debug_decl(replay_session, SUDO_DEBUG_UTIL) + double delay; + debug_decl(read_timing_record, SUDO_DEBUG_UTIL) + + /* Read next record from timing file. */ + if (io_log_gets(IOFD_TIMING, buf, sizeof(buf)) == NULL) { + /* EOF or error reading timing file, we are done. */ + debug_return_int(io_log_eof(IOFD_TIMING) ? 1 : -1); + } - /* Restore terminal if interrupted. */ - memset(&sa, 0, sizeof(sa)); - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_RESETHAND; - sa.sa_handler = sudoreplay_handler; - (void) sigaction(SIGINT, &sa, NULL); - (void) sigaction(SIGTERM, &sa, NULL); - (void) sigaction(SIGHUP, &sa, NULL); - (void) sigaction(SIGQUIT, &sa, NULL); - - /* Don't suspend as we cannot restore the screen on resume. */ - sa.sa_flags = SA_RESTART; - sa.sa_handler = SIG_IGN; - (void) sigaction(SIGTSTP, &sa, NULL); + /* Parse timing file record. */ + buf[strcspn(buf, "\n")] = '\0'; + if (!parse_timing(buf, &delay, &closure->timing)) + sudo_fatalx(U_("invalid timing file line: %s"), buf); + + /* Record number bytes to read. */ + /* XXX - remove timing->nbytes? */ + if (closure->timing.idx != IOFD_TIMING) { + closure->iobuf.len = 0; + closure->iobuf.off = 0; + closure->iobuf.toread = closure->timing.u.nbytes; + } + + /* Adjust delay using speed factor and clamp to max_delay */ + delay /= speed_factor; + if (closure->timing.max_delay && delay > closure->timing.max_delay) + delay = closure->timing.max_delay; + + /* Convert delay to a timeval. */ + timeout.tv_sec = delay; + timeout.tv_usec = (delay - timeout.tv_sec) * 1000000.0; + + /* Schedule the delay event. */ + if (sudo_ev_add(closure->evbase, closure->delay_ev, &timeout, false) == -1) + sudo_fatal(U_("unable to add event to queue")); + + debug_return_int(0); +} + +static bool +fill_iobuf(struct replay_closure *closure) +{ + const size_t space = sizeof(closure->iobuf.buf) - closure->iobuf.len; + const struct timing_closure *timing = &closure->timing; + ssize_t nread; + size_t len; + debug_decl(fill_iobuf, SUDO_DEBUG_UTIL) + + if (closure->iobuf.toread != 0 && space != 0) { + len = closure->iobuf.toread < space ? closure->iobuf.toread : space; + nread = io_log_read(timing->idx, + closure->iobuf.buf + closure->iobuf.off, len); + if (nread <= 0) { + if (nread == 0) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "%s: premature EOF, expected %u bytes", + io_log_files[timing->idx].suffix, closure->iobuf.toread); + } else { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO, + "%s: read error", io_log_files[timing->idx].suffix); + } + sudo_warnx(U_("unable to read %s"), + io_log_files[timing->idx].suffix); + debug_return_bool(false); + } + closure->iobuf.toread -= nread; + closure->iobuf.len += nread; + } + + debug_return_bool(true); +} + +/* + * Called when the inter-record delay has expired. + * Depending on the record type, either reads the next + * record or changes window size. + */ +static void +delay_cb(int fd, int what, void *v) +{ + struct replay_closure *closure = v; + const struct timing_closure *timing = &closure->timing; + debug_decl(delay_cb, SUDO_DEBUG_UTIL) + /* Delay done, read I/O log record or change window size. */ + if (timing->idx == IOFD_TIMING) { + resize_terminal(timing->u.winsize.rows, timing->u.winsize.cols); + switch (read_timing_record(closure)) { + case 0: + /* success */ + break; + case 1: + /* EOF */ + sudo_ev_loopexit(closure->evbase); + break; + default: + /* error */ + sudo_ev_loopbreak(closure->evbase); + break; + } + debug_return; + } + + /* Even if we are not replaying, we still have to delay. */ + if (timing->idx >= IOFD_MAX || io_log_files[timing->idx].fd.v == NULL) + debug_return; + + /* Enable write event. */ + if (sudo_ev_add(closure->evbase, closure->output_ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); + + debug_return; +} + +static void +replay_closure_free(struct replay_closure *closure) +{ /* - * Setup event base and input/output events. - * If interactive, take input from and write to /dev/tty. - * If not interactive, delay instead of reading input and write to stdout. + * Free events and event base, then the closure itself. */ - evbase = sudo_ev_base_alloc(); - if (evbase == NULL) - sudo_fatal(NULL); - input_ev = sudo_ev_alloc(ttyfd, interactive ? SUDO_EV_READ : - SUDO_EV_TIMEOUT, check_input, sudo_ev_self_cbarg()); - if (input_ev == NULL) - sudo_fatal(NULL); - output_ev = sudo_ev_alloc(interactive ? ttyfd : STDOUT_FILENO, - SUDO_EV_WRITE, write_output, &wc); - if (output_ev == NULL) - sudo_fatal(NULL); + sudo_ev_free(closure->delay_ev); + sudo_ev_free(closure->keyboard_ev); + sudo_ev_free(closure->output_ev); + sudo_ev_free(closure->sighup_ev); + sudo_ev_free(closure->sigint_ev); + sudo_ev_free(closure->sigquit_ev); + sudo_ev_free(closure->sigterm_ev); + sudo_ev_free(closure->sigtstp_ev); + sudo_ev_base_free(closure->evbase); + free(closure); +} + +static void +signal_cb(int signo, int what, void *v) +{ + struct replay_closure *closure = v; + debug_decl(signal_cb, SUDO_DEBUG_UTIL) + + switch (signo) { + case SIGHUP: + case SIGINT: + case SIGQUIT: + case SIGTERM: + /* Free the event base and restore signal handlers. */ + replay_closure_free(closure); + + /* Restore the terminal and die. */ + sudoreplay_cleanup(); + kill(getpid(), signo); + break; + case SIGTSTP: + /* Ignore ^Z since we have no way to restore the screen. */ + break; + } + + debug_return; +} + +static struct replay_closure * +replay_closure_alloc(double max_delay, const char *decimal, bool interactive) +{ + struct replay_closure *closure; + debug_decl(replay_closure_alloc, SUDO_DEBUG_UTIL) + + if ((closure = calloc(1, sizeof(*closure))) == NULL) + debug_return_ptr(NULL); + + closure->interactive = interactive; + closure->timing.max_delay = max_delay; + closure->timing.decimal = decimal; /* - * Read each line of the timing file, displaying the output streams. + * Setup event base and delay, input and output events. + * If interactive, take input from and write to /dev/tty. + * If not interactive there is no input event. */ - while (io_log_gets(IOFD_TIMING, buf, sizeof(buf)) != NULL) { - size_t len, nread; - double to_wait; - struct timeval timeout; - struct iolog_timing timing; - bool need_nlcr = false; - char last_char = '\0'; - - buf[strcspn(buf, "\n")] = '\0'; - if (!parse_timing(buf, decimal, &timing)) - sudo_fatalx(U_("invalid timing file line: %s"), buf); - - /* Adjust delay using speed factor and clamp to max_wait */ - to_wait = timing.seconds / speed_factor; - if (max_wait && to_wait > max_wait) - to_wait = max_wait; - - /* Convert delay to a timeval. */ - timeout.tv_sec = to_wait; - timeout.tv_usec = (to_wait - timeout.tv_sec) * 1000000.0; - - /* Run event event loop to delay and get keyboard input. */ - if (sudo_ev_add(evbase, input_ev, &timeout, false) == -1) + closure->evbase = sudo_ev_base_alloc(); + if (closure->evbase == NULL) + goto bad; + closure->delay_ev = sudo_ev_alloc(-1, SUDO_EV_TIMEOUT, delay_cb, closure); + if (closure->delay_ev == NULL) + goto bad; + if (interactive) { + closure->keyboard_ev = sudo_ev_alloc(ttyfd, SUDO_EV_READ|SUDO_EV_PERSIST, + read_keyboard, closure); + if (closure->keyboard_ev == NULL) + goto bad; + if (sudo_ev_add(closure->evbase, closure->keyboard_ev, NULL, false) == -1) sudo_fatal(U_("unable to add event to queue")); - sudo_ev_loop(evbase, 0); + } + closure->output_ev = sudo_ev_alloc(interactive ? ttyfd : STDOUT_FILENO, + SUDO_EV_WRITE, write_output, closure); + if (closure->output_ev == NULL) + goto bad; - if (timing.idx == IOFD_TIMING) { - resize_terminal(timing.u.winsize.rows, timing.u.winsize.cols); - continue; - } + /* + * Setup signal events, we need to restore the terminal if killed. + */ + closure->sighup_ev = sudo_ev_alloc(SIGHUP, SUDO_EV_SIGNAL, signal_cb, + closure); + if (closure->sighup_ev == NULL) + goto bad; + if (sudo_ev_add(closure->evbase, closure->sighup_ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); - /* Even if we are not replaying, we still have to delay. */ - if (timing.idx >= IOFD_MAX || io_log_files[timing.idx].fd.v == NULL) - continue; + closure->sigint_ev = sudo_ev_alloc(SIGINT, SUDO_EV_SIGNAL, signal_cb, + closure); + if (closure->sigint_ev == NULL) + goto bad; + if (sudo_ev_add(closure->evbase, closure->sigint_ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); - /* Check whether we need to convert newline to CR LF pairs. */ - if (interactive) - need_nlcr = (timing.idx == IOFD_STDOUT || timing.idx == IOFD_STDERR); + closure->sigquit_ev = sudo_ev_alloc(SIGQUIT, SUDO_EV_SIGNAL, signal_cb, + closure); + if (closure->sigquit_ev == NULL) + goto bad; + if (sudo_ev_add(closure->evbase, closure->sigquit_ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); - /* All output is sent to stdout. */ - /* XXX - assumes no wall clock time spent writing output. */ - while (timing.u.nbytes != 0) { - if (timing.u.nbytes > sizeof(buf)) - len = sizeof(buf); - else - len = timing.u.nbytes; - nread = io_log_read(timing.idx, buf, len); - if (nread <= 0) { - if (nread == 0) { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, - "%s: premature EOF, expected %zu bytes", - io_log_files[timing.idx].suffix, timing.u.nbytes); - } else { - sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO, - "%s: read error", io_log_files[timing.idx].suffix); - } - break; - } - timing.u.nbytes -= nread; - - /* Convert newline to carriage return + linefeed if needed. */ - if (need_nlcr) { - size_t remainder = nread; - size_t linelen; - char *line = buf; - char *nl, *cp = buf; - - /* - * Handle a "\r\n" pair that spans a buffer. - * The newline will be written as part of the next line. - */ - if (last_char == '\r' && *cp == '\n') { - cp++; - remainder--; - } + closure->sigterm_ev = sudo_ev_alloc(SIGTERM, SUDO_EV_SIGNAL, signal_cb, + closure); + if (closure->sigterm_ev == NULL) + goto bad; + if (sudo_ev_add(closure->evbase, closure->sigterm_ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); - iovcnt = 0; - while ((nl = memchr(cp, '\n', remainder)) != NULL) { - /* - * If there is already a carriage return, keep going. - * We'll include it as part of the next line written. - */ - if (cp != nl && nl[-1] == '\r') { - remainder = (size_t)(&buf[nread - 1] - nl); - cp = nl + 1; - continue; - } - - /* Store the line in iov followed by \r\n pair. */ - if (iovcnt + 3 > iovmax) { - iov = iovmax ? - reallocarray(iov, iovmax <<= 1, sizeof(*iov)) : - reallocarray(NULL, iovmax = 32, sizeof(*iov)); - if (iov == NULL) - sudo_fatalx(U_("%s: %s"), __func__, U_("unable to allocate memory")); - } - linelen = (size_t)(nl - line) + 1; - iov[iovcnt].iov_base = line; - iov[iovcnt].iov_len = linelen - 1; /* not including \n */ - iovcnt++; - iov[iovcnt].iov_base = "\r\n"; - iov[iovcnt].iov_len = 2; - iovcnt++; - line = cp = nl + 1; - remainder -= linelen; - } - if ((size_t)(line - buf) != nread) { - /* - * Partial line without a linefeed or multiple lines - * that already had \r\n pairs. - */ - iov[iovcnt].iov_base = line; - iov[iovcnt].iov_len = nread - (line - buf); - iovcnt++; - } - last_char = buf[nread - 1]; /* stash last char of old buffer */ - } else { - /* No conversion needed. */ - iov[0].iov_base = buf; - iov[0].iov_len = nread; - iovcnt = 1; - } + closure->sigtstp_ev = sudo_ev_alloc(SIGTSTP, SUDO_EV_SIGNAL, signal_cb, + closure); + if (closure->sigtstp_ev == NULL) + goto bad; + if (sudo_ev_add(closure->evbase, closure->sigtstp_ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); - /* Setup closure for write_output. */ - wc.wevent = output_ev; - wc.iov = iov; - wc.iovcnt = iovcnt; - wc.nbytes = 0; - for (i = 0; i < iovcnt; i++) - wc.nbytes += iov[i].iov_len; - - /* Run event event loop to write output. */ - /* XXX - should use a single event loop with a circular buffer. */ - if (sudo_ev_add(evbase, output_ev, NULL, false) == -1) - sudo_fatal(U_("unable to add event to queue")); - sudo_ev_loop(evbase, 0); - } + debug_return_ptr(closure); +bad: + replay_closure_free(closure); + debug_return_ptr(NULL); +} + +static int +replay_session(double max_delay, const char *decimal, bool interactive) +{ + struct replay_closure *closure; + int ret = 0; + debug_decl(replay_session, SUDO_DEBUG_UTIL) + + /* Allocate the delay closure and read the first timing record. */ + closure = replay_closure_alloc(max_delay, decimal, interactive); + if (read_timing_record(closure) != 0) { + ret = 1; + goto done; } - if (iov != &iovb) - free(iov); - sudo_ev_base_free(evbase); - sudo_ev_free(input_ev); - sudo_ev_free(output_ev); - debug_return; + + /* Run event loop. */ + sudo_ev_loop(closure->evbase, 0); + if (sudo_ev_got_break(closure->evbase)) + ret = 1; + +done: + /* Clean up and return. */ + replay_closure_free(closure); + debug_return_int(ret); } static int @@ -943,48 +1046,82 @@ open_io_fd(char *path, int len, struct io_log_file *iol) debug_return_int(iol->fd.v ? 0 : -1); } +/* + * Write the I/O buffer. + */ static void write_output(int fd, int what, void *v) { - struct write_closure *wc = v; - size_t nwritten; - unsigned int i; + struct replay_closure *closure = v; + struct io_buffer *iobuf = &closure->iobuf; + unsigned iovcnt = 1; + struct iovec iov[2]; + bool need_nlcr = false; + size_t nbytes, nwritten; debug_decl(write_output, SUDO_DEBUG_UTIL) - nwritten = writev(fd, wc->iov, wc->iovcnt); + /* Refill iobuf if there is more to read and buf is empty. */ + if (!fill_iobuf(closure)) { + sudo_ev_loopbreak(closure->evbase); + debug_return; + } + + if (closure->interactive) + need_nlcr = (fd == IOFD_STDOUT || fd == IOFD_STDERR); + + nbytes = iobuf->len - iobuf->off; + iov[0].iov_base = iobuf->buf + iobuf->off; + iov[0].iov_len = nbytes; + + if (need_nlcr) { + char *nl; + + /* We may need to add a carriage return after the newline. */ + nl = memchr(iov[0].iov_base, '\n', iov[0].iov_len); + if (nl != NULL) { + iov[0].iov_len--; /* skip the existing newline */ + iov[1].iov_base = "\r\n"; + iov[1].iov_len = 2; + iovcnt = 2; + nbytes++; /* account for the added carriage return */ + } + } + + nwritten = writev(fd, iov, iovcnt); switch ((ssize_t)nwritten) { case -1: if (errno != EINTR && errno != EAGAIN) sudo_fatal(U_("unable to write to %s"), "stdout"); break; - case 0: - break; default: - if (wc->nbytes == nwritten) { - /* writev completed */ - debug_return; - } - - /* short writev, adjust iov so we can write the remainder. */ - wc->nbytes -= nwritten; - i = wc->iovcnt; - while (i--) { - if (wc->iov[0].iov_len > nwritten) { - /* Partial write, adjust base and len and reschedule. */ - wc->iov[0].iov_base = (char *)wc->iov[0].iov_base + nwritten; - wc->iov[0].iov_len -= nwritten; - break; - } - nwritten -= wc->iov[0].iov_len; - wc->iov++; - wc->iovcnt--; + if (iovcnt == 2 && nwritten == nbytes) { + /* subtract one for the carriage return we added above. */ + nwritten--; } + iobuf->off += nwritten; break; } - /* Reschedule event to write remainder. */ - if (sudo_ev_add(NULL, wc->wevent, NULL, false) == -1) - sudo_fatal(U_("unable to add event to queue")); + if (iobuf->off == iobuf->len) { + /* Write complete, go to next timing entry if possible. */ + switch (read_timing_record(closure)) { + case 0: + /* success */ + break; + case 1: + /* EOF */ + sudo_ev_loopexit(closure->evbase); + break; + default: + /* error */ + sudo_ev_loopbreak(closure->evbase); + break; + } + } else { + /* Reschedule event to write remainder. */ + if (sudo_ev_add(NULL, closure->output_ev, NULL, false) == -1) + sudo_fatal(U_("unable to add event to queue")); + } debug_return; } @@ -1473,62 +1610,82 @@ list_sessions(int argc, char **argv, const char *pattern, const char *user, } /* - * Check input for ' ', '<', '>', return + * Check keyboard for ' ', '<', '>', return * pause, slow, fast, next */ static void -check_input(int fd, int what, void *v) +read_keyboard(int fd, int what, void *v) { - struct sudo_event *ev = v; - struct timeval tv, *timeout = NULL; - static bool paused = 0; + struct replay_closure *closure = v; + static bool paused = false; + struct timeval tv; char ch; - debug_decl(check_input, SUDO_DEBUG_UTIL) + debug_decl(read_keyboard, SUDO_DEBUG_UTIL) - if (ISSET(what, SUDO_EV_READ)) { - switch (read(fd, &ch, 1)) { - case -1: - if (errno != EINTR && errno != EAGAIN) - sudo_fatal(U_("unable to read %s"), "stdin"); + switch (read(fd, &ch, 1)) { + case -1: + if (errno != EINTR && errno != EAGAIN) + sudo_fatal(U_("unable to read %s"), "stdin"); + break; + case 0: + /* Ignore EOF. */ + break; + default: + if (paused) { + /* Any key will unpause, run the delay callback directly. */ + paused = false; + delay_cb(-1, SUDO_EV_TIMEOUT, closure); + debug_return; + } + switch (ch) { + case ' ': + paused = true; + /* Disable the delay event until we unpause. */ + sudo_ev_del(closure->evbase, closure->delay_ev); break; - case 0: - /* Ignore EOF. */ + case '<': + speed_factor /= 2; + sudo_ev_get_timeleft(closure->delay_ev, &tv); + if (sudo_timevalisset(&tv)) { + /* Double remaining timeout. */ + tv.tv_sec *= 2; + tv.tv_usec *= 2; + if (tv.tv_usec >= 1000000) { + tv.tv_sec++; + tv.tv_usec -= 1000000; + } + if (sudo_ev_add(NULL, closure->delay_ev, &tv, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "failed to double remaining delay timeout"); + } + } + break; + case '>': + speed_factor *= 2; + sudo_ev_get_timeleft(closure->delay_ev, &tv); + if (sudo_timevalisset(&tv)) { + /* Halve remaining timeout. */ + if (tv.tv_sec & 1) + tv.tv_usec += 500000; + tv.tv_sec /= 2; + tv.tv_usec /= 2; + if (sudo_ev_add(NULL, closure->delay_ev, &tv, false) == -1) { + sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO, + "failed to halve remaining delay timeout"); + } + } + break; + case '\r': + case '\n': + /* Cancel existing delay, run callback directly. */ + sudo_ev_del(closure->evbase, closure->delay_ev); + delay_cb(-1, SUDO_EV_TIMEOUT, closure); break; default: - if (paused) { - /* Any key will unpause, event is finished. */ - /* XXX - pause time could be less than timeout */ - paused = false; - debug_return; /* XXX */ - } - switch (ch) { - case ' ': - paused = true; - break; - case '<': - speed_factor /= 2; - break; - case '>': - speed_factor *= 2; - break; - case '\r': - case '\n': - debug_return; /* XXX */ - } + /* Unknown key, nothing to do. */ break; } - if (!paused) { - /* Determine remaining timeout, if any. */ - sudo_ev_get_timeleft(ev, &tv); - if (!sudo_timevalisset(&tv)) { - /* No time left, event is done. */ - debug_return; - } - timeout = &tv; - } - /* Re-enable event. */ - if (sudo_ev_add(NULL, ev, timeout, false) == -1) - sudo_fatal(U_("unable to add event to queue")); + break; } debug_return; } @@ -1538,10 +1695,10 @@ check_input(int fd, int what, void *v) * index sleep_time num_bytes * Where index is IOFD_*, sleep_time is the number of seconds to sleep * before writing the data and num_bytes is the number of bytes to output. - * Returns 1 on success and 0 on failure. + * Returns true on success and false on failure. */ -static int -parse_timing(const char *buf, const char *decimal, struct iolog_timing *timing) +static bool +parse_timing(const char *buf, double *seconds, struct timing_closure *timing) { unsigned long ul; long l; @@ -1571,19 +1728,19 @@ parse_timing(const char *buf, const char *decimal, struct iolog_timing *timing) */ errno = 0; l = strtol(cp, &ep, 10); - if (ep == cp || (*ep != '.' && strncmp(ep, decimal, strlen(decimal)) != 0)) + if (ep == cp || (*ep != '.' && strncmp(ep, timing->decimal, strlen(timing->decimal)) != 0)) goto bad; if (l < 0 || l > INT_MAX || (errno == ERANGE && l == LONG_MAX)) goto bad; - timing->seconds = (double)l; - cp = ep + (*ep == '.' ? 1 : strlen(decimal)); + *seconds = (double)l; + cp = ep + (*ep == '.' ? 1 : strlen(timing->decimal)); d = 10.0; while (isdigit((unsigned char) *cp)) { fract += (*cp - '0') / d; d *= 10; cp++; } - timing->seconds += fract; + *seconds += fract; while (isspace((unsigned char) *cp)) cp++; @@ -1615,9 +1772,9 @@ parse_timing(const char *buf, const char *decimal, struct iolog_timing *timing) timing->u.nbytes = (size_t)ul; } - debug_return_int(1); + debug_return_bool(true); bad: - debug_return_int(0); + debug_return_bool(false); } static void @@ -1658,14 +1815,3 @@ sudoreplay_cleanup(void) restore_terminal_size(); sudo_term_restore(ttyfd, false); } - -/* - * Signal handler for SIGINT, SIGTERM, SIGHUP, SIGQUIT - * Must be installed with SA_RESETHAND enabled. - */ -static void -sudoreplay_handler(int signo) -{ - sudoreplay_cleanup(); - kill(getpid(), signo); -}