]> granicus.if.org Git - sudo/commitdiff
When parsing an I/O log timing line, store the result in a timespec,
authorTodd C. Miller <Todd.Miller@sudo.ws>
Wed, 29 Aug 2018 15:57:12 +0000 (09:57 -0600)
committerTodd C. Miller <Todd.Miller@sudo.ws>
Wed, 29 Aug 2018 15:57:12 +0000 (09:57 -0600)
not a double.  The speed factor (for scaling the delay) in sudoreplay
is still a double but we only need to adjust the delay if the factor
is something other than 1.0.

MANIFEST
plugins/sudoers/Makefile.in
plugins/sudoers/iolog_util.c
plugins/sudoers/iolog_util.h
plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c
plugins/sudoers/regress/iolog_util/check_iolog_util.c [new file with mode: 0644]
plugins/sudoers/sudoreplay.c

index 6f1cc104c4993d286a574a7711b0f84ecfe1d6f7..834f91c47d07efe89723a56af8f7fd34bc87c405 100644 (file)
--- a/MANIFEST
+++ b/MANIFEST
@@ -465,6 +465,7 @@ plugins/sudoers/regress/env_match/data
 plugins/sudoers/regress/iolog_path/check_iolog_path.c
 plugins/sudoers/regress/iolog_path/data
 plugins/sudoers/regress/iolog_plugin/check_iolog_plugin.c
+plugins/sudoers/regress/iolog_util/check_iolog_util.c
 plugins/sudoers/regress/logging/check_wrap.c
 plugins/sudoers/regress/logging/check_wrap.in
 plugins/sudoers/regress/logging/check_wrap.out.ok
index 9649cbe72ee8ad4ded9fa875f75dda8f1333304c..8ab5a43509db934031bf365b774497fd35f7b3db 100644 (file)
@@ -143,7 +143,7 @@ PROGS = sudoers.la visudo sudoreplay cvtsudoers testsudoers
 
 TEST_PROGS = check_addr check_base64 check_digest check_env_pattern check_fill \
             check_gentime check_hexchar check_iolog_path check_iolog_plugin \
-            check_wrap check_starttime @SUDOERS_TEST_PROGS@
+            check_iolog_util check_wrap check_starttime @SUDOERS_TEST_PROGS@
 
 AUTH_OBJS = sudo_auth.lo @AUTH_OBJS@
 
@@ -197,6 +197,9 @@ CHECK_IOLOG_PLUGIN_OBJS = check_iolog_plugin.o iolog.lo iolog_path.lo \
                          iolog_util.lo locale.lo mkdir_parents.lo pwutil.lo \
                          pwutil_impl.lo redblack.lo sudoers_debug.lo
 
+CHECK_IOLOG_UTIL_OBJS = check_iolog_util.o iolog_util.lo locale.lo \
+                       sudoers_debug.lo
+
 CHECK_SYMBOLS_OBJS = check_symbols.o
 
 CHECK_STARTTIME_OBJS = check_starttime.o starttime.lo sudoers_debug.lo
@@ -285,6 +288,9 @@ check_iolog_path: $(CHECK_IOLOG_PATH_OBJS) $(LT_LIBS)
 check_iolog_plugin: $(CHECK_IOLOG_PLUGIN_OBJS) $(LT_LIBS)
        $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_PLUGIN_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @ZLIB@
 
+check_iolog_util: $(CHECK_IOLOG_UTIL_OBJS) $(LT_LIBS)
+       $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_IOLOG_UTIL_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS) @ZLIB@
+
 check_starttime: $(CHECK_STARTTIME_OBJS) $(LT_LIBS)
        $(LIBTOOL) $(LTFLAGS) --mode=link $(CC) -o $@ $(CHECK_STARTTIME_OBJS) $(LDFLAGS) $(ASAN_LDFLAGS) $(PIE_LDFLAGS) $(SSP_LDFLAGS) $(LIBS)
 
@@ -419,6 +425,7 @@ check: $(TEST_PROGS) visudo testsudoers cvtsudoers
            ./check_hexchar || rval=`expr $$rval + $$?`; \
            ./check_iolog_path $(srcdir)/regress/iolog_path/data || rval=`expr $$rval + $$?`; \
            ./check_iolog_plugin $(srcdir)/regress/iolog_plugin/iolog || rval=`expr $$rval + $$?`; \
+           ./check_iolog_util || rval=`expr $$rval + $$?`; \
            ./check_starttime || rval=`expr $$rval + $$?`; \
            if test -f check_symbols; then \
                ./check_symbols .libs/sudoers.so $(shlib_exp) || rval=`expr $$rval + $$?`; \
@@ -730,6 +737,11 @@ check_iolog_plugin.o: $(srcdir)/regress/iolog_plugin/check_iolog_plugin.c \
                       $(srcdir)/sudoers.h $(srcdir)/sudoers_debug.h \
                       $(top_builddir)/config.h $(top_builddir)/pathnames.h
        $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_plugin/check_iolog_plugin.c
+check_iolog_util.o: $(srcdir)/regress/iolog_util/check_iolog_util.c \
+                    $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
+                    $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \
+                    $(srcdir)/iolog_util.h $(top_builddir)/config.h
+       $(CC) -c $(CPPFLAGS) $(CFLAGS) $(ASAN_CFLAGS) $(PIE_CFLAGS) $(SSP_CFLAGS) $(srcdir)/regress/iolog_util/check_iolog_util.c
 check_starttime.o: $(srcdir)/regress/starttime/check_starttime.c \
                    $(incdir)/compat/stdbool.h $(incdir)/sudo_compat.h \
                    $(incdir)/sudo_fatal.h $(incdir)/sudo_util.h \
index 630f76b30e372421aef2beda41c1fa9604a1a70e..1704e27dab263401c5fde4b5c5b8023bd4ea19ee 100644 (file)
@@ -35,6 +35,7 @@
 #include <errno.h>
 #include <limits.h>
 #include <fcntl.h>
+#include <time.h>
 #ifdef HAVE_STDBOOL_H
 # include <stdbool.h>
 #else
 #include "sudo_util.h"
 #include "iolog_util.h"
 
+#ifndef TIME_T_MAX
+# if SIZEOF_TIME_T == 8
+#  define TIME_T_MAX   LLONG_MAX
+# else
+#  define TIME_T_MAX   INT_MAX
+# endif
+#endif
+
 static int timing_idx_adj;
 
 struct log_info *
@@ -177,6 +186,115 @@ bad:
     debug_return_ptr(NULL);
 }
 
+void
+adjust_delay(struct timespec *delay, struct timespec *max_delay,
+     double scale_factor)
+{
+    double seconds;
+    debug_decl(adjust_delay, SUDO_DEBUG_UTIL)
+
+    if (scale_factor != 1.0) {
+       /* Order is important: we don't want to double the remainder. */
+        seconds = (double)delay->tv_sec / scale_factor;
+        delay->tv_sec = (time_t)seconds;
+        delay->tv_nsec /= scale_factor;
+        delay->tv_nsec += (seconds - delay->tv_sec) * 1000000000;
+        while (delay->tv_nsec >= 1000000000) {
+            delay->tv_sec++;
+            delay->tv_nsec -= 1000000000;
+        }
+    }
+
+    /* Clamp to max delay. */
+    if (max_delay != NULL) {
+       if (sudo_timespeccmp(delay, max_delay, >)) {
+           delay->tv_sec = max_delay->tv_sec;
+           delay->tv_nsec = max_delay->tv_nsec;
+       }
+    }
+
+    debug_return;
+}
+
+/*
+ * Parse the delay as seconds and nanoseconds: %lld.%09ld
+ * Sudo used to write this as a double, but since timing data is logged
+ * in the C locale this may not match the current locale.
+ */
+char *
+parse_delay(const char *cp, struct timespec *delay, const char *decimal_point)
+{
+    char numbuf[(((sizeof(long long) * 8) + 2) / 3) + 2];
+    const char *errstr, *ep;
+    long long llval;
+    size_t len;
+    debug_decl(parse_delay, SUDO_DEBUG_UTIL)
+
+    /* Parse seconds (whole number portion). */
+    for (ep = cp; isdigit((unsigned char)*ep); ep++)
+       continue;
+    len = (size_t)(ep - cp);
+    if (len >= sizeof(numbuf)) {
+       sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+           "%s: number of seconds is too large", cp);
+       debug_return_ptr(NULL);
+    }
+    memcpy(numbuf, cp, len);
+    numbuf[len] = '\0';
+    delay->tv_sec = strtonum(numbuf, 0, TIME_T_MAX, &errstr);
+    if (errstr != NULL) {
+       sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+           "%s: number of seconds is %s", numbuf, errstr);
+       debug_return_ptr(NULL);
+    }
+
+    /* Radix may be in user's locale for sudo < 1.7.4 so accept that too. */
+    if (*ep != '.' && *ep != *decimal_point) {
+       sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+           "invalid characters after seconds: %s", ep);
+       debug_return_ptr(NULL);
+    }
+    cp = ep + 1;
+
+    /* Parse fractional part, we may read more precision than we can store. */
+    for (ep = cp; isdigit((unsigned char)*ep); ep++)
+       continue;
+    len = (size_t)(ep - cp);
+    if (len >= sizeof(numbuf)) {
+       sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+           "%s: number of nanoseconds is too large", cp);
+       debug_return_ptr(NULL);
+    }
+    memcpy(numbuf, cp, len);
+    numbuf[len] = '\0';
+    llval = strtonum(numbuf, 0, LLONG_MAX, &errstr);
+    if (errstr != NULL) {
+       sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_LINENO,
+           "%s: number of nanoseconds is %s", numbuf, errstr);
+       debug_return_ptr(NULL);
+    }
+
+    /* Adjust fractional part to nanosecond precision. */
+    if (len < 9) {
+       /* Convert to nanosecond precision. */
+       do {
+           llval *= 10;
+       } while (++len < 9);
+    } else if (len > 9) {
+       /* Clamp to nanoseconds. */
+       do {
+           llval /= 10;
+       } while (--len > 9);
+    }
+    delay->tv_nsec = (long)llval;
+
+    /* Advance to the next field. */
+    while (isspace((unsigned char)*ep))
+       ep++;
+
+    debug_return_str((char *)ep);
+}
+
 /*
  * Parse a timing line, which is formatted as:
  *     index sleep_time num_bytes
@@ -185,78 +303,57 @@ bad:
  * Returns true on success and false on failure.
  */
 bool
-parse_timing(const char *buf, double *seconds, struct timing_closure *timing)
+parse_timing(const char *buf, struct timespec *delay,
+    struct timing_closure *timing)
 {
-    unsigned long ul;
-    long l;
-    double d, fract = 0;
+    unsigned long ulval;
     char *cp, *ep;
     debug_decl(parse_timing, SUDO_DEBUG_UTIL)
 
     /* Parse index */
-    ul = strtoul(buf, &ep, 10);
+    ulval = strtoul(buf, &ep, 10);
     if (ep == buf || !isspace((unsigned char) *ep))
        goto bad;
-    if (ul >= IOFD_MAX) {
-       if (ul != 6)
+    if (ulval >= IOFD_MAX) {
+       if (ulval != 6)
            goto bad;
        /* work around a bug in timing files generated by sudo 1.8.7 */
        timing_idx_adj = 2;
     }
-    timing->idx = (int)ul - timing_idx_adj;
+    timing->idx = (int)ulval - timing_idx_adj;
     for (cp = ep + 1; isspace((unsigned char) *cp); cp++)
        continue;
 
-    /*
-     * Parse number of seconds.  Sudo logs timing data in the C locale
-     * but this may not match the current locale so we cannot use strtod().
-     * Furthermore, sudo < 1.7.4 logged with the user's locale so we need
-     * to be able to parse those logs too.
-     */
-    errno = 0;
-    l = strtol(cp, &ep, 10);
-    if (ep == cp || (*ep != '.' && strncmp(ep, timing->decimal, strlen(timing->decimal)) != 0))
+    /* Parse delay, returns the next field or NULL on error. */
+    if ((cp = parse_delay(cp, delay, timing->decimal)) == NULL)
        goto bad;
-    if (l < 0 || l > INT_MAX || (errno == ERANGE && l == LONG_MAX))
-       goto bad;
-    *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++;
-    }
-    *seconds += fract;
-    while (isspace((unsigned char) *cp))
-       cp++;
 
     if (timing->idx == IOFD_TIMING) {
        errno = 0;
-       ul = strtoul(cp, &ep, 10);
+       ulval = strtoul(cp, &ep, 10);
        if (ep == cp || !isspace((unsigned char) *ep))
            goto bad;
-       if (ul > INT_MAX || (errno == ERANGE && ul == ULONG_MAX))
+       if (ulval > INT_MAX || (errno == ERANGE && ulval == ULONG_MAX))
            goto bad;
-       timing->u.winsize.rows = (int)ul;
+       timing->u.winsize.rows = (int)ulval;
        for (cp = ep + 1; isspace((unsigned char) *cp); cp++)
            continue;
 
        errno = 0;
-       ul = strtoul(cp, &ep, 10);
+       ulval = strtoul(cp, &ep, 10);
        if (ep == cp || *ep != '\0')
            goto bad;
-       if (ul > INT_MAX || (errno == ERANGE && ul == ULONG_MAX))
+       if (ulval > INT_MAX || (errno == ERANGE && ulval == ULONG_MAX))
            goto bad;
-       timing->u.winsize.cols = (int)ul;
+       timing->u.winsize.cols = (int)ulval;
     } else {
        errno = 0;
-       ul = strtoul(cp, &ep, 10);
+       ulval = strtoul(cp, &ep, 10);
        if (ep == cp || *ep != '\0')
            goto bad;
-       if (ul > SIZE_MAX || (errno == ERANGE && ul == ULONG_MAX))
+       if (ulval > SIZE_MAX || (errno == ERANGE && ulval == ULONG_MAX))
            goto bad;
-       timing->u.nbytes = (size_t)ul;
+       timing->u.nbytes = (size_t)ulval;
     }
 
     debug_return_bool(true);
index dd2908f17aabbb7b1b56c162bd1c243c995a08ea..5d8cbc4e680bad22649b7b6f61b1f3e1d5b24b42 100644 (file)
@@ -59,8 +59,10 @@ struct timing_closure {
 # define IOFD_MAX      6
 #endif
 
-bool parse_timing(const char *buf, double *seconds, struct timing_closure *timing);
+bool parse_timing(const char *buf, struct timespec *delay, struct timing_closure *timing);
+char *parse_delay(const char *cp, struct timespec *delay, const char *decimal_point);
 struct log_info *parse_logfile(const char *logfile);
 void free_log_info(struct log_info *li);
+void adjust_delay(struct timespec *delay, struct timespec *max_delay, double scale_factor);
 
 #endif /* SUDOERS_IOLOG_UTIL_H */
index 20c145a84d55ae126e4bc4f4da4a3b31d5ae4ec9..c909cbd16d9827156ebabd8989560a56e13c641c 100644 (file)
@@ -147,7 +147,7 @@ validate_timing(FILE *fp, int recno, int type, unsigned int p1, unsigned int p2)
 {
     struct timing_closure timing;
     char buf[LINE_MAX];
-    double delay;
+    struct timespec delay;
 
     if (!fgets(buf, sizeof(buf), fp)) {
        sudo_warn("unable to read timing file");
@@ -181,21 +181,23 @@ validate_timing(FILE *fp, int recno, int type, unsigned int p1, unsigned int p2)
            return false;
        }
     }
-    if (delay > 0.01) {
-       sudo_warnx("record %d: got excessive delay %f", recno, delay);
+    if (delay.tv_sec != 0 || delay.tv_nsec > 10000000) {
+       sudo_warnx("record %d: got excessive delay %lld.%09ld", recno,
+           (long long)delay.tv_sec, delay.tv_nsec);
        return false;
     }
 
     return true;
 }
 
-int
-main(int argc, char *argv[], char *envp[])
+
+/*
+ * Test sudoers I/O log plugin endpoints.
+ */
+void
+test_endpoints(int *ntests, int *nerrors, const char *iolog_dir, char *envp[])
 {
-    struct passwd pw, rpw, *tpw;
-    int rc, tests = 0, errors = 0;
-    int cmnd_argc = 1;
-    const char *iolog_dir;
+    int rc, cmnd_argc = 1;
     char buf[1024], iolog_path[PATH_MAX];
     char runas_gid[64], runas_uid[64];
     FILE *fp;
@@ -230,76 +232,54 @@ main(int argc, char *argv[], char *envp[])
     };
     const char output[] = "uid=0(root) gid=0(wheel)\r\n";
 
-    initprogname(argc > 0 ? argv[0] : "check_iolog_plugin");
-
-    if (argc != 2)
-       usage();
-    iolog_dir = argv[1];
-
-    /* Bare minimum to link. */
-    memset(&pw, 0, sizeof(pw));
-    memset(&rpw, 0, sizeof(rpw));
-    if ((tpw = getpwuid(0)) == NULL) {
-       if ((tpw = getpwnam("root")) == NULL)
-           sudo_fatalx("unable to look up uid 0 or root");
-    }
-    rpw.pw_uid = tpw->pw_uid;
-    rpw.pw_gid = tpw->pw_gid;
-    sudo_user.pw = &pw;
-    sudo_user._runas_pw = &rpw;
-
     /* Set runas uid/gid to root. */
     snprintf(runas_uid, sizeof(runas_uid), "runas_uid=%u",
-       (unsigned int)rpw.pw_uid);
+       (unsigned int)runas_pw->pw_uid);
     snprintf(runas_gid, sizeof(runas_gid), "runas_gid=%u",
-       (unsigned int)rpw.pw_gid);
+       (unsigned int)runas_pw->pw_gid);
 
     /* Set path to the iolog directory the user passed in. */
     snprintf(iolog_path, sizeof(iolog_path), "iolog_path=%s", iolog_dir);
 
-    /* Set iolog uid/gid to invoking user. */
-    iolog_uid = geteuid();
-    iolog_gid = getegid();
-
     /* Test open endpoint. */
     rc = sudoers_io.open(SUDO_API_VERSION, NULL, sudo_printf_int, settings,
        user_info, command_info, cmnd_argc, cmnd_argv, envp, NULL);
-    tests++;
+    (*ntests)++;
     if (rc != 1) {
        sudo_warnx("I/O log open endpoint failed");
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
 
     /* Validate I/O log info file. */
-    tests++;
+    (*ntests)++;
     snprintf(iolog_path, sizeof(iolog_path), "%s/log", iolog_dir);
     if (!validate_iolog_info(iolog_path))
-       errors++;
+       (*nerrors)++;
 
     /* Test log_ttyout endpoint. */
     rc = sudoers_io.log_ttyout(output, strlen(output));
-    tests++;
+    (*ntests)++;
     if (rc != 1) {
        sudo_warnx("I/O log_ttyout endpoint failed");
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
 
     /* Test change_winsize endpoint (twice). */
     rc = sudoers_io.change_winsize(32, 128);
-    tests++;
+    (*ntests)++;
     if (rc != 1) {
        sudo_warnx("I/O change_winsize endpoint failed");
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
     rc = sudoers_io.change_winsize(24, 80);
-    tests++;
+    (*ntests)++;
     if (rc != 1) {
        sudo_warnx("I/O change_winsize endpoint failed");
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
 
     /* Close the plugin. */
@@ -307,54 +287,85 @@ main(int argc, char *argv[], char *envp[])
 
     /* Validate the timing file. */
     snprintf(iolog_path, sizeof(iolog_path), "%s/timing", iolog_dir);
-    tests++;
+    (*ntests)++;
     if ((fp = fopen(iolog_path, "r")) == NULL) {
        sudo_warn("unable to open %s", iolog_path);
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
 
     /* Line 1: output of id command. */
     if (!validate_timing(fp, 1, IOFD_TTYOUT, strlen(output), 0)) {
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
 
     /* Line 2: window size change. */
     if (!validate_timing(fp, 2, IOFD_TIMING, 32, 128)) {
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
 
     /* Line 3: window size change. */
     if (!validate_timing(fp, 3, IOFD_TIMING, 24, 80)) {
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
 
     /* Validate ttyout log file. */
     snprintf(iolog_path, sizeof(iolog_path), "%s/ttyout", iolog_dir);
-    tests++;
+    (*ntests)++;
     fclose(fp);
     if ((fp = fopen(iolog_path, "r")) == NULL) {
        sudo_warn("unable to open %s", iolog_path);
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
     if (!fgets(buf, sizeof(buf), fp)) {
        sudo_warn("unable to read %s", iolog_path);
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
     if (strcmp(buf, output) != 0) {
        sudo_warnx("ttylog mismatch: want \"%s\", got \"%s\"", output, buf);
-       errors++;
-       goto done;
+       (*nerrors)++;
+       return;
     }
+}
+
+int
+main(int argc, char *argv[], char *envp[])
+{
+    struct passwd pw, rpw, *tpw;
+    int tests = 0, errors = 0;
+    const char *iolog_dir;
+
+    initprogname(argc > 0 ? argv[0] : "check_iolog_plugin");
+
+    if (argc != 2)
+       usage();
+    iolog_dir = argv[1];
+
+    /* Bare minimum to link. */
+    memset(&pw, 0, sizeof(pw));
+    memset(&rpw, 0, sizeof(rpw));
+    if ((tpw = getpwuid(0)) == NULL) {
+       if ((tpw = getpwnam("root")) == NULL)
+           sudo_fatalx("unable to look up uid 0 or root");
+    }
+    rpw.pw_uid = tpw->pw_uid;
+    rpw.pw_gid = tpw->pw_gid;
+    sudo_user.pw = &pw;
+    sudo_user._runas_pw = &rpw;
+
+    /* Set iolog uid/gid to invoking user. */
+    iolog_uid = geteuid();
+    iolog_gid = getegid();
+
+    test_endpoints(&tests, &errors, iolog_dir, envp);
 
-done:
     if (tests != 0) {
-       printf("iolog_plugin: %d test%s run, %d errors, %d%% success rate\n",
+       printf("check_iolog_plugin: %d test%s run, %d errors, %d%% success rate\n",
            tests, tests == 1 ? "" : "s", errors,
            (tests - errors) * 100 / tests);
     }
diff --git a/plugins/sudoers/regress/iolog_util/check_iolog_util.c b/plugins/sudoers/regress/iolog_util/check_iolog_util.c
new file mode 100644 (file)
index 0000000..d25aed4
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * Copyright (c) 2018 Todd C. Miller <Todd.Miller@sudo.ws>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef HAVE_STRING_H
+# include <string.h>
+#endif /* HAVE_STRING_H */
+#ifdef HAVE_STRINGS_H
+# include <strings.h>
+#endif /* HAVE_STRINGS_H */
+#include <time.h>
+#include <unistd.h>
+
+#define SUDO_ERROR_WRAP 0
+
+#include "sudo_compat.h"
+#include "sudo_util.h"
+#include "sudo_fatal.h"
+#include "iolog_util.h"
+
+__dso_public int main(int argc, char *argv[]);
+
+static struct parse_delay_test {
+    const char *input;
+    const char *next_field;
+    struct timespec expected_delay;
+} parse_delay_tests[] = {
+    { "10.99999999999 X", "X", { 10, 999999999 } },    /* clamp to nsec */
+    { "10.999999999 X",   "X", { 10, 999999999 } },    /* nsec */
+    { "10.999999 X",      "X", { 10, 999999000 } },    /* usec -> nsec */
+    { "10.000999999 X",   "X", { 10, 999999 } },
+    { "10.9 X",           "X", { 10, 900000000 } },
+    { "10.0 X",           "X", { 10, 0 } }
+};
+
+/*
+ * Test parse_delay()
+ */
+void
+test_parse_delay(int *ntests, int *nerrors)
+{
+    unsigned int i;
+
+    for (i = 0; i < nitems(parse_delay_tests); i++) {
+       struct timespec delay;
+       struct parse_delay_test *test = &parse_delay_tests[i];
+       char *cp = parse_delay(test->input, &delay, ".");
+       if (cp == NULL) {
+           sudo_warnx("%s:%u failed to parse delay: %s", __func__,
+               i, test->input);
+           (*nerrors)++;
+           continue;
+       }
+       if (strcmp(cp, test->next_field) != 0) {
+           sudo_warnx("%s:%u next field (want \"%s\", got \"%s\"", __func__,
+               i, test->next_field, cp);
+           (*nerrors)++;
+           continue;
+       }
+       if (delay.tv_sec != test->expected_delay.tv_sec) {
+           sudo_warnx("%s:%u wrong seconds (want %lld, got %lld)", __func__,
+               i, (long long)test->expected_delay.tv_sec,
+               (long long)delay.tv_sec);
+           (*nerrors)++;
+           continue;
+       }
+       if (delay.tv_nsec != test->expected_delay.tv_nsec) {
+           sudo_warnx("%s:%u wrong nanoseconds (want %ld, got %ld)", __func__,
+               i, test->expected_delay.tv_nsec, delay.tv_nsec);
+           (*nerrors)++;
+           continue;
+       }
+    }
+    (*ntests) += i;
+}
+
+static struct adjust_delay_test {
+    struct timespec in_delay;
+    struct timespec out_delay;
+    struct timespec max_delay;
+    double scale_factor;
+} adjust_delay_tests[] = {
+    { { 10,       300 }, { 10,       300 }, { 0, 0 }, 1.0 },
+    { { 10,       300 }, {  5,       150 }, { 0, 0 }, 2.0 },
+    { {  5,       300 }, {  2, 500000150 }, { 0, 0 }, 2.0 },
+    { {  0,   1000000 }, {  0,    333333 }, { 0, 0 },   3 },
+    { { 10,   1000000 }, {  3, 333666666 }, { 0, 0 },   3 },
+    { {  5,       150 }, { 10,       300 }, { 0, 0 }, 0.5 },
+    { {  5, 500000000 }, { 11,         0 }, { 0, 0 }, 0.5 },
+    { {  5,       150 }, {  5,         0 }, { 5, 0 }, 0.5 }
+};
+
+/*
+ * Test adjust_delay()
+ */
+void
+test_adjust_delay(int *ntests, int *nerrors)
+{
+    unsigned int i;
+
+    for (i = 0; i < nitems(adjust_delay_tests); i++) {
+       struct adjust_delay_test *test = &adjust_delay_tests[i];
+
+       adjust_delay(&test->in_delay, sudo_timespecisset(&test->max_delay) ?
+           &test->max_delay : NULL, test->scale_factor);
+       if (!sudo_timespeccmp(&test->in_delay, &test->out_delay, ==)) {
+           sudo_warnx("%s:%u want {%lld, %ld}, got {%lld, %ld}", __func__, i,
+               (long long)test->out_delay.tv_sec, test->out_delay.tv_nsec,
+               (long long)test->in_delay.tv_sec, test->in_delay.tv_nsec);
+           (*nerrors)++;
+       }
+    }
+    (*ntests) += i;
+}
+
+int
+main(int argc, char *argv[])
+{
+    int tests = 0, errors = 0;
+
+    initprogname(argc > 0 ? argv[0] : "check_iolog_util");
+
+    test_parse_delay(&tests, &errors);
+
+    test_adjust_delay(&tests, &errors);
+
+    if (tests != 0) {
+       printf("check_iolog_util: %d test%s run, %d errors, %d%% success rate\n",
+           tests, tests == 1 ? "" : "s", errors,
+           (tests - errors) * 100 / tests);
+    }
+
+    exit(errors);
+}
index 979b13140023f76455e22227ab63c45793238570..667dbc64b9756a864b0781f1c5f1c9f651a233ca 100644 (file)
@@ -745,7 +745,6 @@ read_timing_record(struct replay_closure *closure)
 {
     struct timespec timeout;
     char buf[LINE_MAX];
-    double delay;
     debug_decl(read_timing_record, SUDO_DEBUG_UTIL)
 
     /* Read next record from timing file. */
@@ -756,7 +755,7 @@ read_timing_record(struct replay_closure *closure)
 
     /* Parse timing file record. */
     buf[strcspn(buf, "\n")] = '\0';
-    if (!parse_timing(buf, &delay, &closure->timing))
+    if (!parse_timing(buf, &timeout, &closure->timing))
        sudo_fatalx(U_("invalid timing file line: %s"), buf);
 
     /* Record number bytes to read. */
@@ -768,18 +767,8 @@ read_timing_record(struct replay_closure *closure)
        closure->iobuf.toread = closure->timing.u.nbytes;
     }
 
-    /* Adjust delay using speed factor. */
-    delay /= speed_factor;
-
-    /* Convert delay to a timespec. */
-    timeout.tv_sec = delay;
-    timeout.tv_nsec = (delay - timeout.tv_sec) * 1000000000.0;
-
-    /* Clamp timeout to max delay. */
-    if (closure->timing.max_delay != NULL) {
-       if (sudo_timespeccmp(&timeout, closure->timing.max_delay, >))
-           timeout = *closure->timing.max_delay;
-    }
+    /* Adjust delay using speed factor and max_delay. */
+    adjust_delay(&timeout, closure->timing.max_delay, speed_factor);
 
     /* Schedule the delay event. */
     if (sudo_ev_add(closure->evbase, closure->delay_ev, &timeout, false) == -1)