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
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@
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
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)
./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 + $$?`; \
$(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 \
#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 *
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
* 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);
# 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 */
{
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");
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;
};
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. */
/* 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);
}
--- /dev/null
+/*
+ * 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);
+}
{
struct timespec timeout;
char buf[LINE_MAX];
- double delay;
debug_decl(read_timing_record, SUDO_DEBUG_UTIL)
/* Read next record from timing file. */
/* 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. */
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)