Change to a single event loop in sudoreplay and use signal events.

This commit is contained in:
Todd C. Miller
2017-07-27 09:45:35 -06:00
parent 9ea9ecb183
commit 2d30c42a03

View File

@@ -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;
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);
}
/* 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)
{
/*
* Free events and event base, then the closure itself.
*/
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;
/*
* 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.
*/
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"));
}
closure->output_ev = sudo_ev_alloc(interactive ? ttyfd : STDOUT_FILENO,
SUDO_EV_WRITE, write_output, closure);
if (closure->output_ev == NULL)
goto bad;
/*
* 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"));
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"));
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"));
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"));
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"));
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)
/* 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);
/*
* 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.
*/
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);
/*
* Read each line of the timing file, displaying the output streams.
*/
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)
sudo_fatal(U_("unable to add event to queue"));
sudo_ev_loop(evbase, 0);
if (timing.idx == IOFD_TIMING) {
resize_terminal(timing.u.winsize.rows, timing.u.winsize.cols);
continue;
}
/* 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;
/* Check whether we need to convert newline to CR LF pairs. */
if (interactive)
need_nlcr = (timing.idx == IOFD_STDOUT || timing.idx == IOFD_STDERR);
/* 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--;
}
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;
}
/* 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);
}
/* 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);
}