util: New function ovs_scan().
authorBen Pfaff <blp@nicira.com>
Fri, 15 Nov 2013 16:54:56 +0000 (08:54 -0800)
committerBen Pfaff <blp@nicira.com>
Fri, 15 Nov 2013 16:54:56 +0000 (08:54 -0800)
This new function is essentially an implementation of sscanf() with
slightly different behavior (see the comment) that is more convenient for
Open vSwitch internal use.  Also, this implementation ought to work out of
the box on Windows, which has a defective sscanf() that lacks the 'hh'
modifier required to scan into a char variable.

Signed-off-by: Ben Pfaff <blp@nicira.com>
lib/compiler.h
lib/util.c
lib/util.h
tests/library.at
tests/test-util.c

index ccc949b..0dbacbf 100644 (file)
@@ -28,6 +28,7 @@
 #define NO_RETURN __attribute__((__noreturn__))
 #define OVS_UNUSED __attribute__((__unused__))
 #define PRINTF_FORMAT(FMT, ARG1) __attribute__((__format__(printf, FMT, ARG1)))
+#define SCANF_FORMAT(FMT, ARG1) __attribute__((__format__(scanf, FMT, ARG1)))
 #define STRFTIME_FORMAT(FMT) __attribute__((__format__(__strftime__, FMT, 0)))
 #define MALLOC_LIKE __attribute__((__malloc__))
 #define ALWAYS_INLINE __attribute__((always_inline))
@@ -39,6 +40,7 @@
 #define NO_RETURN
 #define OVS_UNUSED
 #define PRINTF_FORMAT(FMT, ARG1)
+#define SCANF_FORMAT(FMT, ARG1)
 #define STRFTIME_FORMAT(FMT)
 #define MALLOC_LIKE
 #define ALWAYS_INLINE
index a32c80a..b633166 100644 (file)
@@ -16,6 +16,7 @@
 
 #include <config.h>
 #include "util.h"
+#include <ctype.h>
 #include <errno.h>
 #include <limits.h>
 #include <pthread.h>
@@ -26,6 +27,7 @@
 #include <string.h>
 #include <sys/stat.h>
 #include <unistd.h>
+#include "bitmap.h"
 #include "byte-order.h"
 #include "coverage.h"
 #include "ovs-thread.h"
@@ -1245,3 +1247,444 @@ bitwise_get(const void *src, unsigned int src_len,
                  n_bits);
     return ntohll(value);
 }
+\f
+/* ovs_scan */
+
+struct scan_spec {
+    unsigned int width;
+    enum {
+        SCAN_DISCARD,
+        SCAN_CHAR,
+        SCAN_SHORT,
+        SCAN_INT,
+        SCAN_LONG,
+        SCAN_LLONG,
+        SCAN_INTMAX_T,
+        SCAN_PTRDIFF_T,
+        SCAN_SIZE_T
+    } type;
+};
+
+static const char *
+skip_spaces(const char *s)
+{
+    while (isspace((unsigned char) *s)) {
+        s++;
+    }
+    return s;
+}
+
+static const char *
+scan_int(const char *s, const struct scan_spec *spec, int base, va_list *args)
+{
+    const char *start = s;
+    uintmax_t value;
+    bool negative;
+    int n_digits;
+
+    negative = *s == '-';
+    s += *s == '-' || *s == '+';
+
+    if ((!base || base == 16) && *s == '0' && (s[1] == 'x' || s[1] == 'X')) {
+        base = 16;
+        s += 2;
+    } else if (!base) {
+        base = *s == '0' ? 8 : 10;
+    }
+
+    if (s - start >= spec->width) {
+        return NULL;
+    }
+
+    value = 0;
+    n_digits = 0;
+    while (s - start < spec->width) {
+        int digit = hexit_value(*s);
+
+        if (digit < 0 || digit >= base) {
+            break;
+        }
+        value = value * base + digit;
+        n_digits++;
+        s++;
+    }
+    if (!n_digits) {
+        return NULL;
+    }
+
+    if (negative) {
+        value = -value;
+    }
+
+    switch (spec->type) {
+    case SCAN_DISCARD:
+        break;
+    case SCAN_CHAR:
+        *va_arg(*args, char *) = value;
+        break;
+    case SCAN_SHORT:
+        *va_arg(*args, short int *) = value;
+        break;
+    case SCAN_INT:
+        *va_arg(*args, int *) = value;
+        break;
+    case SCAN_LONG:
+        *va_arg(*args, long int *) = value;
+        break;
+    case SCAN_LLONG:
+        *va_arg(*args, long long int *) = value;
+        break;
+    case SCAN_INTMAX_T:
+        *va_arg(*args, intmax_t *) = value;
+        break;
+    case SCAN_PTRDIFF_T:
+        *va_arg(*args, ptrdiff_t *) = value;
+        break;
+    case SCAN_SIZE_T:
+        *va_arg(*args, size_t *) = value;
+        break;
+    }
+    return s;
+}
+
+static const char *
+skip_digits(const char *s)
+{
+    while (*s >= '0' && *s <= '9') {
+        s++;
+    }
+    return s;
+}
+
+static const char *
+scan_float(const char *s, const struct scan_spec *spec, va_list *args)
+{
+    const char *start = s;
+    long double value;
+    char *tail;
+    char *copy;
+    bool ok;
+
+    s += *s == '+' || *s == '-';
+    s = skip_digits(s);
+    if (*s == '.') {
+        s = skip_digits(s + 1);
+    }
+    if (*s == 'e' || *s == 'E') {
+        s++;
+        s += *s == '+' || *s == '-';
+        s = skip_digits(s);
+    }
+
+    if (s - start > spec->width) {
+        s = start + spec->width;
+    }
+
+    copy = xmemdup0(start, s - start);
+    value = strtold(copy, &tail);
+    ok = *tail == '\0';
+    free(copy);
+    if (!ok) {
+        return NULL;
+    }
+
+    switch (spec->type) {
+    case SCAN_DISCARD:
+        break;
+    case SCAN_INT:
+        *va_arg(*args, float *) = value;
+        break;
+    case SCAN_LONG:
+        *va_arg(*args, double *) = value;
+        break;
+    case SCAN_LLONG:
+        *va_arg(*args, long double *) = value;
+        break;
+
+    case SCAN_CHAR:
+    case SCAN_SHORT:
+    case SCAN_INTMAX_T:
+    case SCAN_PTRDIFF_T:
+    case SCAN_SIZE_T:
+        NOT_REACHED();
+    }
+    return s;
+}
+
+static void
+scan_output_string(const struct scan_spec *spec,
+                   const char *s, size_t n,
+                   va_list *args)
+{
+    if (spec->type != SCAN_DISCARD) {
+        char *out = va_arg(*args, char *);
+        memcpy(out, s, n);
+        out[n] = '\0';
+    }
+}
+
+static const char *
+scan_string(const char *s, const struct scan_spec *spec, va_list *args)
+{
+    size_t n;
+
+    for (n = 0; n < spec->width; n++) {
+        if (!s[n] || isspace((unsigned char) s[n])) {
+            break;
+        }
+    }
+    if (!n) {
+        return NULL;
+    }
+
+    scan_output_string(spec, s, n, args);
+    return s + n;
+}
+
+static const char *
+parse_scanset(const char *p_, unsigned long *set, bool *complemented)
+{
+    const uint8_t *p = (const uint8_t *) p_;
+
+    *complemented = *p == '^';
+    p += *complemented;
+
+    if (*p == ']') {
+        bitmap_set1(set, ']');
+        p++;
+    }
+
+    while (*p && *p != ']') {
+        if (p[1] == '-' && p[2] != ']' && p[2] > *p) {
+            bitmap_set_multiple(set, *p, p[2] - *p + 1, true);
+            p += 3;
+        } else {
+            bitmap_set1(set, *p++);
+        }
+    }
+    if (*p == ']') {
+        p++;
+    }
+    return (const char *) p;
+}
+
+static const char *
+scan_set(const char *s, const struct scan_spec *spec, const char **pp,
+         va_list *args)
+{
+    unsigned long set[BITMAP_N_LONGS(UCHAR_MAX + 1)];
+    bool complemented;
+    unsigned int n;
+
+    /* Parse the scan set. */
+    memset(set, 0, sizeof set);
+    *pp = parse_scanset(*pp, set, &complemented);
+
+    /* Parse the data. */
+    n = 0;
+    while (s[n]
+           && bitmap_is_set(set, (unsigned char) s[n]) == !complemented
+           && n < spec->width) {
+        n++;
+    }
+    if (!n) {
+        return NULL;
+    }
+    scan_output_string(spec, s, n, args);
+    return s + n;
+}
+
+static const char *
+scan_chars(const char *s, const struct scan_spec *spec, va_list *args)
+{
+    unsigned int n = spec->width == SIZE_MAX ? 1 : spec->width;
+
+    if (strlen(s) < n) {
+        return NULL;
+    }
+    if (spec->type != SCAN_DISCARD) {
+        memcpy(va_arg(*args, char *), s, n);
+    }
+    return s + n;
+}
+
+/* This is an implementation of the standard sscanf() function, with the
+ * following exceptions:
+ *
+ *   - It returns true if the entire template was successfully scanned and
+ *     converted, false if any conversion failed.
+ *
+ *   - The standard doesn't define sscanf() behavior when an out-of-range value
+ *     is scanned, e.g. if a "%"PRIi8 conversion scans "-1" or "0x1ff".  Some
+ *     implementations consider this an error and stop scanning.  This
+ *     implementation never considers an out-of-range value an error; instead,
+ *     it stores the least-significant bits of the converted value in the
+ *     destination, e.g. the value 255 for both examples earlier.
+ *
+ *   - Only single-byte characters are supported, that is, the 'l' modifier
+ *     on %s, %[, and %c is not supported.  The GNU extension 'a' modifier is
+ *     also not supported.
+ *
+ *   - %p is not supported.
+ */
+bool
+ovs_scan(const char *s, const char *template, ...)
+{
+    const char *const start = s;
+    bool ok = false;
+    const char *p;
+    va_list args;
+
+    va_start(args, template);
+    p = template;
+    while (*p != '\0') {
+        struct scan_spec spec;
+        unsigned char c = *p++;
+        bool discard;
+
+        if (isspace(c)) {
+            s = skip_spaces(s);
+            continue;
+        } else if (c != '%') {
+            if (*s != c) {
+                goto exit;
+            }
+            s++;
+            continue;
+        } else if (*p == '%') {
+            if (*s++ != '%') {
+                goto exit;
+            }
+            p++;
+            continue;
+        }
+
+        /* Parse '*' flag. */
+        discard = *p == '*';
+        p += discard;
+
+        /* Parse field width. */
+        spec.width = 0;
+        while (*p >= '0' && *p <= '9') {
+            spec.width = spec.width * 10 + (*p++ - '0');
+        }
+        if (spec.width == 0) {
+            spec.width = UINT_MAX;
+        }
+
+        /* Parse type modifier. */
+        switch (*p) {
+        case 'h':
+            if (p[1] == 'h') {
+                spec.type = SCAN_CHAR;
+                p += 2;
+            } else {
+                spec.type = SCAN_SHORT;
+                p++;
+            }
+            break;
+
+        case 'j':
+            spec.type = SCAN_INTMAX_T;
+            p++;
+            break;
+
+        case 'l':
+            if (p[1] == 'l') {
+                spec.type = SCAN_LLONG;
+                p += 2;
+            } else {
+                spec.type = SCAN_LONG;
+                p++;
+            }
+            break;
+
+        case 'L':
+        case 'q':
+            spec.type = SCAN_LLONG;
+            p++;
+            break;
+
+        case 't':
+            spec.type = SCAN_PTRDIFF_T;
+            p++;
+            break;
+
+        case 'z':
+            spec.type = SCAN_SIZE_T;
+            p++;
+            break;
+
+        default:
+            spec.type = SCAN_INT;
+            break;
+        }
+
+        if (discard) {
+            spec.type = SCAN_DISCARD;
+        }
+
+        c = *p++;
+        if (c != 'c' && c != 'n' && c != '[') {
+            s = skip_spaces(s);
+        }
+        switch (c) {
+        case 'd':
+            s = scan_int(s, &spec, 10, &args);
+            break;
+
+        case 'i':
+            s = scan_int(s, &spec, 0, &args);
+            break;
+
+        case 'o':
+            s = scan_int(s, &spec, 8, &args);
+            break;
+
+        case 'u':
+            s = scan_int(s, &spec, 10, &args);
+            break;
+
+        case 'x':
+        case 'X':
+            s = scan_int(s, &spec, 16, &args);
+            break;
+
+        case 'e':
+        case 'f':
+        case 'g':
+        case 'E':
+        case 'G':
+            s = scan_float(s, &spec, &args);
+            break;
+
+        case 's':
+            s = scan_string(s, &spec, &args);
+            break;
+
+        case '[':
+            s = scan_set(s, &spec, &p, &args);
+            break;
+
+        case 'c':
+            s = scan_chars(s, &spec, &args);
+            break;
+
+        case 'n':
+            if (spec.type != SCAN_DISCARD) {
+                *va_arg(args, int *) = s - start;
+            }
+            break;
+        }
+
+        if (!s) {
+            goto exit;
+        }
+    }
+    ok = true;
+
+exit:
+    va_end(args);
+    return ok;
+}
+
index 32a7c8c..fd686d6 100644 (file)
@@ -264,6 +264,8 @@ bool str_to_uint(const char *, int base, unsigned int *);
 bool str_to_ulong(const char *, int base, unsigned long *);
 bool str_to_ullong(const char *, int base, unsigned long long *);
 
+bool ovs_scan(const char *s, const char *template, ...) SCANF_FORMAT(2, 3);
+
 bool str_to_double(const char *, double *);
 
 int hexit_value(int c);
index 6d1c6da..5cd6c4e 100644 (file)
@@ -119,7 +119,8 @@ m4_foreach(
    [bitwise_copy],
    [bitwise_zero],
    [bitwise_one],
-   [bitwise_is_all_zeros]],
+   [bitwise_is_all_zeros],
+   [ovs_scan]],
   [AT_SETUP([testname[()] function])
    AT_KEYWORDS([testname])
    AT_CHECK([test-util testname], [0], [], [])
index 6ed2f77..4256509 100644 (file)
@@ -406,6 +406,538 @@ test_assert(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
 {
     ovs_assert(false);
 }
+
+static void
+test_ovs_scan(int argc OVS_UNUSED, char *argv[] OVS_UNUSED)
+{
+    char str[16], str2[16], str3[16];
+    long double ld, ld2;
+    long long ll, ll2;
+    signed char c, c2;
+    ptrdiff_t pd, pd2;
+    intmax_t im, im2;
+    size_t sz, sz2;
+    int n, n2, n3;
+    double d, d2;
+    short s, s2;
+    float f, f2;
+    long l, l2;
+    int i, i2;
+
+    ovs_assert(ovs_scan("", ""));
+    ovs_assert(ovs_scan("", " "));
+    ovs_assert(ovs_scan(" ", " "));
+    ovs_assert(ovs_scan("  ", " "));
+    ovs_assert(ovs_scan(" \t ", " "));
+
+    ovs_assert(ovs_scan("xyzzy", "xyzzy"));
+    ovs_assert(ovs_scan("xy%zzy", "xy%%zzy"));
+    ovs_assert(!ovs_scan(" xy%zzy", "xy%%zzy"));
+    ovs_assert(ovs_scan("    xy%\tzzy", " xy%% zzy"));
+
+    ovs_assert(ovs_scan("123", "%d", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("0", "%d", &i));
+    ovs_assert(i == 0);
+    ovs_assert(!ovs_scan("123", "%d%d", &i, &i2));
+    ovs_assert(ovs_scan("+123", "%d", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("-123", "%d", &i));
+    ovs_assert(i == -123);
+    ovs_assert(ovs_scan("0123", "%d", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan(" 123", "%d", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("0x123", "%d", &i));
+    ovs_assert(i == 0);
+    ovs_assert(ovs_scan("123", "%2d %d", &i, &i2));
+    ovs_assert(i == 12);
+    ovs_assert(i2 == 3);
+    ovs_assert(ovs_scan("+123", "%2d %d", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("-123", "%2d %d", &i, &i2));
+    ovs_assert(i == -1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("0123", "%2d %d", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("123", "%*2d %d", &i));
+    ovs_assert(i == 3);
+    ovs_assert(ovs_scan("+123", "%2d %*d", &i));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("-123", "%*2d %*d"));
+
+    ovs_assert(ovs_scan("123", "%u", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("0", "%u", &i));
+    ovs_assert(i == 0);
+    ovs_assert(!ovs_scan("123", "%u%u", &i, &i2));
+    ovs_assert(ovs_scan("+123", "%u", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("-123", "%u", &i));
+    ovs_assert(i == -123);
+    ovs_assert(ovs_scan("0123", "%u", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan(" 123", "%u", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("0x123", "%u", &i));
+    ovs_assert(i == 0);
+    ovs_assert(ovs_scan("123", "%2u %u", &i, &i2));
+    ovs_assert(i == 12);
+    ovs_assert(i2 == 3);
+    ovs_assert(ovs_scan("+123", "%2u %u", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("-123", "%2u %u", &i, &i2));
+    ovs_assert(i == -1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("0123", "%2u %u", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("123", "%*2u %u", &i));
+    ovs_assert(i == 3);
+    ovs_assert(ovs_scan("+123", "%2u %*u", &i));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("-123", "%*2u %*u"));
+
+    ovs_assert(ovs_scan("123", "%i", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("0", "%i", &i));
+    ovs_assert(i == 0);
+    ovs_assert(!ovs_scan("123", "%i%i", &i, &i2));
+    ovs_assert(ovs_scan("+123", "%i", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("-123", "%i", &i));
+    ovs_assert(i == -123);
+    ovs_assert(ovs_scan("0123", "%i", &i));
+    ovs_assert(i == 0123);
+    ovs_assert(ovs_scan(" 123", "%i", &i));
+    ovs_assert(i == 123);
+    ovs_assert(ovs_scan("0x123", "%i", &i));
+    ovs_assert(i == 0x123);
+    ovs_assert(ovs_scan("123", "%2i %i", &i, &i2));
+    ovs_assert(i == 12);
+    ovs_assert(i2 == 3);
+    ovs_assert(ovs_scan("+123", "%2i %i", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("-123", "%2i %i", &i, &i2));
+    ovs_assert(i == -1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("0123", "%2i %i", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("123", "%*2i %i", &i));
+    ovs_assert(i == 3);
+    ovs_assert(ovs_scan("+123", "%2i %*i", &i));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 23);
+    ovs_assert(ovs_scan("-123", "%*2i %*i"));
+
+    ovs_assert(ovs_scan("123", "%o", &i));
+    ovs_assert(i == 0123);
+    ovs_assert(ovs_scan("0", "%o", &i));
+    ovs_assert(i == 0);
+    ovs_assert(!ovs_scan("123", "%o%o", &i, &i2));
+    ovs_assert(ovs_scan("+123", "%o", &i));
+    ovs_assert(i == 0123);
+    ovs_assert(ovs_scan("-123", "%o", &i));
+    ovs_assert(i == -0123);
+    ovs_assert(ovs_scan("0123", "%o", &i));
+    ovs_assert(i == 0123);
+    ovs_assert(ovs_scan(" 123", "%o", &i));
+    ovs_assert(i == 0123);
+    ovs_assert(ovs_scan("0x123", "%o", &i));
+    ovs_assert(i == 0);
+    ovs_assert(ovs_scan("123", "%2o %o", &i, &i2));
+    ovs_assert(i == 012);
+    ovs_assert(i2 == 3);
+    ovs_assert(ovs_scan("+123", "%2o %o", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 023);
+    ovs_assert(ovs_scan("-123", "%2o %o", &i, &i2));
+    ovs_assert(i == -1);
+    ovs_assert(i2 == 023);
+    ovs_assert(ovs_scan("0123", "%2o %o", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 023);
+    ovs_assert(ovs_scan("123", "%*2o %o", &i));
+    ovs_assert(i == 3);
+    ovs_assert(ovs_scan("+123", "%2o %*o", &i));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 023);
+    ovs_assert(ovs_scan("-123", "%*2o %*o"));
+
+    ovs_assert(ovs_scan("123", "%x", &i));
+    ovs_assert(i == 0x123);
+    ovs_assert(ovs_scan("0", "%x", &i));
+    ovs_assert(i == 0);
+    ovs_assert(!ovs_scan("123", "%x%x", &i, &i2));
+    ovs_assert(ovs_scan("+123", "%x", &i));
+    ovs_assert(i == 0x123);
+    ovs_assert(ovs_scan("-123", "%x", &i));
+    ovs_assert(i == -0x123);
+    ovs_assert(ovs_scan("0123", "%x", &i));
+    ovs_assert(i == 0x123);
+    ovs_assert(ovs_scan(" 123", "%x", &i));
+    ovs_assert(i == 0x123);
+    ovs_assert(ovs_scan("0x123", "%x", &i));
+    ovs_assert(i == 0x123);
+    ovs_assert(ovs_scan("123", "%2x %x", &i, &i2));
+    ovs_assert(i == 0x12);
+    ovs_assert(i2 == 3);
+    ovs_assert(ovs_scan("+123", "%2x %x", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 0x23);
+    ovs_assert(ovs_scan("-123", "%2x %x", &i, &i2));
+    ovs_assert(i == -1);
+    ovs_assert(i2 == 0x23);
+    ovs_assert(ovs_scan("0123", "%2x %x", &i, &i2));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 0x23);
+    ovs_assert(ovs_scan("123", "%*2x %x", &i));
+    ovs_assert(i == 3);
+    ovs_assert(ovs_scan("+123", "%2x %*x", &i));
+    ovs_assert(i == 1);
+    ovs_assert(i2 == 0x23);
+    ovs_assert(ovs_scan("-123", "%*2x %*x"));
+
+    ovs_assert(ovs_scan("123", "%hd", &s));
+    ovs_assert(s == 123);
+    ovs_assert(!ovs_scan("123", "%hd%hd", &s, &s2));
+    ovs_assert(ovs_scan("+123", "%hd", &s));
+    ovs_assert(s == 123);
+    ovs_assert(ovs_scan("-123", "%hd", &s));
+    ovs_assert(s == -123);
+    ovs_assert(ovs_scan("0123", "%hd", &s));
+    ovs_assert(s == 123);
+    ovs_assert(ovs_scan(" 123", "%hd", &s));
+    ovs_assert(s == 123);
+    ovs_assert(ovs_scan("0x123", "%hd", &s));
+    ovs_assert(s == 0);
+    ovs_assert(ovs_scan("123", "%2hd %hd", &s, &s2));
+    ovs_assert(s == 12);
+    ovs_assert(s2 == 3);
+    ovs_assert(ovs_scan("+123", "%2hd %hd", &s, &s2));
+    ovs_assert(s == 1);
+    ovs_assert(s2 == 23);
+    ovs_assert(ovs_scan("-123", "%2hd %hd", &s, &s2));
+    ovs_assert(s == -1);
+    ovs_assert(s2 == 23);
+    ovs_assert(ovs_scan("0123", "%2hd %hd", &s, &s2));
+    ovs_assert(s == 1);
+    ovs_assert(s2 == 23);
+
+    ovs_assert(ovs_scan("123", "%hhd", &c));
+    ovs_assert(c == 123);
+    ovs_assert(ovs_scan("0", "%hhd", &c));
+    ovs_assert(c == 0);
+    ovs_assert(!ovs_scan("123", "%hhd%hhd", &c, &c2));
+    ovs_assert(ovs_scan("+123", "%hhd", &c));
+    ovs_assert(c == 123);
+    ovs_assert(ovs_scan("-123", "%hhd", &c));
+    ovs_assert(c == -123);
+    ovs_assert(ovs_scan("0123", "%hhd", &c));
+    ovs_assert(c == 123);
+    ovs_assert(ovs_scan(" 123", "%hhd", &c));
+    ovs_assert(c == 123);
+    ovs_assert(ovs_scan("0x123", "%hhd", &c));
+    ovs_assert(c == 0);
+    ovs_assert(ovs_scan("123", "%2hhd %hhd", &c, &c2));
+    ovs_assert(c == 12);
+    ovs_assert(c2 == 3);
+    ovs_assert(ovs_scan("+123", "%2hhd %hhd", &c, &c2));
+    ovs_assert(c == 1);
+    ovs_assert(c2 == 23);
+    ovs_assert(ovs_scan("-123", "%2hhd %hhd", &c, &c2));
+    ovs_assert(c == -1);
+    ovs_assert(c2 == 23);
+    ovs_assert(ovs_scan("0123", "%2hhd %hhd", &c, &c2));
+    ovs_assert(c == 1);
+    ovs_assert(c2 == 23);
+
+    ovs_assert(ovs_scan("123", "%ld", &l));
+    ovs_assert(l == 123);
+    ovs_assert(ovs_scan("0", "%ld", &l));
+    ovs_assert(l == 0);
+    ovs_assert(!ovs_scan("123", "%ld%ld", &l, &l2));
+    ovs_assert(ovs_scan("+123", "%ld", &l));
+    ovs_assert(l == 123);
+    ovs_assert(ovs_scan("-123", "%ld", &l));
+    ovs_assert(l == -123);
+    ovs_assert(ovs_scan("0123", "%ld", &l));
+    ovs_assert(l == 123);
+    ovs_assert(ovs_scan(" 123", "%ld", &l));
+    ovs_assert(l == 123);
+    ovs_assert(ovs_scan("0x123", "%ld", &l));
+    ovs_assert(l == 0);
+    ovs_assert(ovs_scan("123", "%2ld %ld", &l, &l2));
+    ovs_assert(l == 12);
+    ovs_assert(l2 == 3);
+    ovs_assert(ovs_scan("+123", "%2ld %ld", &l, &l2));
+    ovs_assert(l == 1);
+    ovs_assert(l2 == 23);
+    ovs_assert(ovs_scan("-123", "%2ld %ld", &l, &l2));
+    ovs_assert(l == -1);
+    ovs_assert(l2 == 23);
+    ovs_assert(ovs_scan("0123", "%2ld %ld", &l, &l2));
+    ovs_assert(l == 1);
+    ovs_assert(l2 == 23);
+
+    ovs_assert(ovs_scan("123", "%lld", &ll));
+    ovs_assert(ll == 123);
+    ovs_assert(ovs_scan("0", "%lld", &ll));
+    ovs_assert(ll == 0);
+    ovs_assert(!ovs_scan("123", "%lld%lld", &ll, &ll2));
+    ovs_assert(ovs_scan("+123", "%lld", &ll));
+    ovs_assert(ll == 123);
+    ovs_assert(ovs_scan("-123", "%lld", &ll));
+    ovs_assert(ll == -123);
+    ovs_assert(ovs_scan("0123", "%lld", &ll));
+    ovs_assert(ll == 123);
+    ovs_assert(ovs_scan(" 123", "%lld", &ll));
+    ovs_assert(ll == 123);
+    ovs_assert(ovs_scan("0x123", "%lld", &ll));
+    ovs_assert(ll == 0);
+    ovs_assert(ovs_scan("123", "%2lld %lld", &ll, &ll2));
+    ovs_assert(ll == 12);
+    ovs_assert(ll2 == 3);
+    ovs_assert(ovs_scan("+123", "%2lld %lld", &ll, &ll2));
+    ovs_assert(ll == 1);
+    ovs_assert(ll2 == 23);
+    ovs_assert(ovs_scan("-123", "%2lld %lld", &ll, &ll2));
+    ovs_assert(ll == -1);
+    ovs_assert(ll2 == 23);
+    ovs_assert(ovs_scan("0123", "%2lld %lld", &ll, &ll2));
+    ovs_assert(ll == 1);
+    ovs_assert(ll2 == 23);
+
+    ovs_assert(ovs_scan("123", "%jd", &im));
+    ovs_assert(im == 123);
+    ovs_assert(ovs_scan("0", "%jd", &im));
+    ovs_assert(im == 0);
+    ovs_assert(!ovs_scan("123", "%jd%jd", &im, &im2));
+    ovs_assert(ovs_scan("+123", "%jd", &im));
+    ovs_assert(im == 123);
+    ovs_assert(ovs_scan("-123", "%jd", &im));
+    ovs_assert(im == -123);
+    ovs_assert(ovs_scan("0123", "%jd", &im));
+    ovs_assert(im == 123);
+    ovs_assert(ovs_scan(" 123", "%jd", &im));
+    ovs_assert(im == 123);
+    ovs_assert(ovs_scan("0x123", "%jd", &im));
+    ovs_assert(im == 0);
+    ovs_assert(ovs_scan("123", "%2jd %jd", &im, &im2));
+    ovs_assert(im == 12);
+    ovs_assert(im2 == 3);
+    ovs_assert(ovs_scan("+123", "%2jd %jd", &im, &im2));
+    ovs_assert(im == 1);
+    ovs_assert(im2 == 23);
+    ovs_assert(ovs_scan("-123", "%2jd %jd", &im, &im2));
+    ovs_assert(im == -1);
+    ovs_assert(im2 == 23);
+    ovs_assert(ovs_scan("0123", "%2jd %jd", &im, &im2));
+    ovs_assert(im == 1);
+    ovs_assert(im2 == 23);
+
+    ovs_assert(ovs_scan("123", "%td", &pd));
+    ovs_assert(pd == 123);
+    ovs_assert(ovs_scan("0", "%td", &pd));
+    ovs_assert(pd == 0);
+    ovs_assert(!ovs_scan("123", "%td%td", &pd, &pd2));
+    ovs_assert(ovs_scan("+123", "%td", &pd));
+    ovs_assert(pd == 123);
+    ovs_assert(ovs_scan("-123", "%td", &pd));
+    ovs_assert(pd == -123);
+    ovs_assert(ovs_scan("0123", "%td", &pd));
+    ovs_assert(pd == 123);
+    ovs_assert(ovs_scan(" 123", "%td", &pd));
+    ovs_assert(pd == 123);
+    ovs_assert(ovs_scan("0x123", "%td", &pd));
+    ovs_assert(pd == 0);
+    ovs_assert(ovs_scan("123", "%2td %td", &pd, &pd2));
+    ovs_assert(pd == 12);
+    ovs_assert(pd2 == 3);
+    ovs_assert(ovs_scan("+123", "%2td %td", &pd, &pd2));
+    ovs_assert(pd == 1);
+    ovs_assert(pd2 == 23);
+    ovs_assert(ovs_scan("-123", "%2td %td", &pd, &pd2));
+    ovs_assert(pd == -1);
+    ovs_assert(pd2 == 23);
+    ovs_assert(ovs_scan("0123", "%2td %td", &pd, &pd2));
+    ovs_assert(pd == 1);
+    ovs_assert(pd2 == 23);
+
+    ovs_assert(ovs_scan("123", "%zd", &sz));
+    ovs_assert(sz == 123);
+    ovs_assert(ovs_scan("0", "%zd", &sz));
+    ovs_assert(sz == 0);
+    ovs_assert(!ovs_scan("123", "%zd%zd", &sz, &sz2));
+    ovs_assert(ovs_scan("+123", "%zd", &sz));
+    ovs_assert(sz == 123);
+    ovs_assert(ovs_scan("-123", "%zd", &sz));
+    ovs_assert(sz == -123);
+    ovs_assert(ovs_scan("0123", "%zd", &sz));
+    ovs_assert(sz == 123);
+    ovs_assert(ovs_scan(" 123", "%zd", &sz));
+    ovs_assert(sz == 123);
+    ovs_assert(ovs_scan("0x123", "%zd", &sz));
+    ovs_assert(sz == 0);
+    ovs_assert(ovs_scan("123", "%2zd %zd", &sz, &sz2));
+    ovs_assert(sz == 12);
+    ovs_assert(sz2 == 3);
+    ovs_assert(ovs_scan("+123", "%2zd %zd", &sz, &sz2));
+    ovs_assert(sz == 1);
+    ovs_assert(sz2 == 23);
+    ovs_assert(ovs_scan("-123", "%2zd %zd", &sz, &sz2));
+    ovs_assert(sz == -1);
+    ovs_assert(sz2 == 23);
+    ovs_assert(ovs_scan("0123", "%2zd %zd", &sz, &sz2));
+    ovs_assert(sz == 1);
+    ovs_assert(sz2 == 23);
+
+    ovs_assert(ovs_scan("0.25", "%f", &f));
+    ovs_assert(f == 0.25);
+    ovs_assert(ovs_scan("1.0", "%f", &f));
+    ovs_assert(f == 1.0);
+    ovs_assert(ovs_scan("-5", "%f", &f));
+    ovs_assert(f == -5.0);
+    ovs_assert(ovs_scan("+6", "%f", &f));
+    ovs_assert(f == 6.0);
+    ovs_assert(ovs_scan("-1e5", "%f", &f));
+    ovs_assert(f == -1e5);
+    ovs_assert(ovs_scan("-.25", "%f", &f));
+    ovs_assert(f == -.25);
+    ovs_assert(ovs_scan("+123.e1", "%f", &f));
+    ovs_assert(f == 1230.0);
+    ovs_assert(ovs_scan("25e-2", "%f", &f));
+    ovs_assert(f == 0.25);
+    ovs_assert(ovs_scan("0.25", "%1f %f", &f, &f2));
+    ovs_assert(f == 0);
+    ovs_assert(f2 == 0.25);
+    ovs_assert(ovs_scan("1.0", "%2f %f", &f, &f2));
+    ovs_assert(f == 1.0);
+    ovs_assert(f2 == 0.0);
+    ovs_assert(!ovs_scan("-5", "%1f", &f));
+    ovs_assert(!ovs_scan("+6", "%1f", &f));
+    ovs_assert(!ovs_scan("-1e5", "%2f %*f", &f));
+    ovs_assert(f == -1);
+    ovs_assert(!ovs_scan("-.25", "%2f", &f));
+    ovs_assert(!ovs_scan("+123.e1", "%6f", &f));
+    ovs_assert(!ovs_scan("25e-2", "%4f", &f));
+
+    ovs_assert(ovs_scan("0.25", "%lf", &d));
+    ovs_assert(d == 0.25);
+    ovs_assert(ovs_scan("1.0", "%lf", &d));
+    ovs_assert(d == 1.0);
+    ovs_assert(ovs_scan("-5", "%lf", &d));
+    ovs_assert(d == -5.0);
+    ovs_assert(ovs_scan("+6", "%lf", &d));
+    ovs_assert(d == 6.0);
+    ovs_assert(ovs_scan("-1e5", "%lf", &d));
+    ovs_assert(d == -1e5);
+    ovs_assert(ovs_scan("-.25", "%lf", &d));
+    ovs_assert(d == -.25);
+    ovs_assert(ovs_scan("+123.e1", "%lf", &d));
+    ovs_assert(d == 1230.0);
+    ovs_assert(ovs_scan("25e-2", "%lf", &d));
+    ovs_assert(d == 0.25);
+    ovs_assert(ovs_scan("0.25", "%1lf %lf", &d, &d2));
+    ovs_assert(d == 0);
+    ovs_assert(d2 == 0.25);
+    ovs_assert(ovs_scan("1.0", "%2lf %lf", &d, &d2));
+    ovs_assert(d == 1.0);
+    ovs_assert(d2 == 0.0);
+    ovs_assert(!ovs_scan("-5", "%1lf", &d));
+    ovs_assert(!ovs_scan("+6", "%1lf", &d));
+    ovs_assert(!ovs_scan("-1e5", "%2lf %*f", &d));
+    ovs_assert(d == -1);
+    ovs_assert(!ovs_scan("-.25", "%2lf", &d));
+    ovs_assert(!ovs_scan("+123.e1", "%6lf", &d));
+    ovs_assert(!ovs_scan("25e-2", "%4lf", &d));
+
+    ovs_assert(ovs_scan("0.25", "%Lf", &ld));
+    ovs_assert(ld == 0.25);
+    ovs_assert(ovs_scan("1.0", "%Lf", &ld));
+    ovs_assert(ld == 1.0);
+    ovs_assert(ovs_scan("-5", "%Lf", &ld));
+    ovs_assert(ld == -5.0);
+    ovs_assert(ovs_scan("+6", "%Lf", &ld));
+    ovs_assert(ld == 6.0);
+    ovs_assert(ovs_scan("-1e5", "%Lf", &ld));
+    ovs_assert(ld == -1e5);
+    ovs_assert(ovs_scan("-.25", "%Lf", &ld));
+    ovs_assert(ld == -.25);
+    ovs_assert(ovs_scan("+123.e1", "%Lf", &ld));
+    ovs_assert(ld == 1230.0);
+    ovs_assert(ovs_scan("25e-2", "%Lf", &ld));
+    ovs_assert(ld == 0.25);
+    ovs_assert(ovs_scan("0.25", "%1Lf %Lf", &ld, &ld2));
+    ovs_assert(ld == 0);
+    ovs_assert(ld2 == 0.25);
+    ovs_assert(ovs_scan("1.0", "%2Lf %Lf", &ld, &ld2));
+    ovs_assert(ld == 1.0);
+    ovs_assert(ld2 == 0.0);
+    ovs_assert(!ovs_scan("-5", "%1Lf", &ld));
+    ovs_assert(!ovs_scan("+6", "%1Lf", &ld));
+    ovs_assert(!ovs_scan("-1e5", "%2Lf %*f", &ld));
+    ovs_assert(ld == -1);
+    ovs_assert(!ovs_scan("-.25", "%2Lf", &ld));
+    ovs_assert(!ovs_scan("+123.e1", "%6Lf", &ld));
+    ovs_assert(!ovs_scan("25e-2", "%4Lf", &ld));
+
+    ovs_assert(ovs_scan(" Hello,\tworld ", "%*s%n%*s%n", &n, &n2));
+    ovs_assert(n == 7);
+    ovs_assert(n2 == 13);
+    ovs_assert(!ovs_scan(" Hello,\tworld ", "%*s%*s%*s"));
+    ovs_assert(ovs_scan(" Hello,\tworld ", "%6s%n%5s%n", str, &n, str2, &n2));
+    ovs_assert(!strcmp(str, "Hello,"));
+    ovs_assert(n == 7);
+    ovs_assert(!strcmp(str2, "world"));
+    ovs_assert(n2 == 13);
+    ovs_assert(ovs_scan(" Hello,\tworld ", "%5s%5s%5s", str, str2, str3));
+    ovs_assert(!strcmp(str, "Hello"));
+    ovs_assert(!strcmp(str2, ","));
+    ovs_assert(!strcmp(str3, "world"));
+    ovs_assert(!ovs_scan(" ", "%*s"));
+
+    ovs_assert(ovs_scan(" Hello,\tworld ", "%*c%n%*c%n%c%n",
+                        &n, &n2, &c, &n3));
+    ovs_assert(n == 1);
+    ovs_assert(n2 == 2);
+    ovs_assert(c == 'e');
+    ovs_assert(n3 == 3);
+    ovs_assert(ovs_scan(" Hello,\tworld ", "%*5c%5c", str));
+    ovs_assert(!memcmp(str, "o,\two", 5));
+    ovs_assert(!ovs_scan(" Hello,\tworld ", "%*15c"));
+
+    ovs_assert(ovs_scan("0x1234xyzzy", "%9[x0-9a-fA-F]%n", str, &n));
+    ovs_assert(!strcmp(str, "0x1234x"));
+    ovs_assert(n == 7);
+    ovs_assert(ovs_scan("foo:bar=baz", "%5[^:=]%n:%5[^:=]%n=%5[^:=]%n",
+                        str, &n, str2, &n2, str3, &n3));
+    ovs_assert(!strcmp(str, "foo"));
+    ovs_assert(n == 3);
+    ovs_assert(!strcmp(str2, "bar"));
+    ovs_assert(n2 == 7);
+    ovs_assert(!strcmp(str3, "baz"));
+    ovs_assert(n3 == 11);
+    ovs_assert(!ovs_scan(" ", "%*[0-9]"));
+    ovs_assert(ovs_scan("0x123a]4xyzzy-", "%[]x0-9a-fA-F]", str));
+    ovs_assert(!strcmp(str, "0x123a]4x"));
+    ovs_assert(ovs_scan("abc]xyz","%[^]xyz]", str));
+    ovs_assert(!strcmp(str, "abc"));
+    ovs_assert(!ovs_scan("0x123a]4xyzzy-", "%[x0-9]a-fA-F]", str));
+    ovs_assert(ovs_scan("0x12-3]xyz", "%[x0-9a-f-]", str));
+    ovs_assert(!strcmp(str, "0x12-3"));
+    ovs_assert(ovs_scan("0x12-3]xyz", "%[^a-f-]", str));
+    ovs_assert(!strcmp(str, "0x12"));
+    ovs_assert(sscanf("0x12-3]xyz", "%[^-a-f]", str));
+    ovs_assert(!strcmp(str, "0x12"));
+}
 \f
 static const struct command commands[] = {
     {"ctz", 0, 0, test_ctz},
@@ -419,6 +951,7 @@ static const struct command commands[] = {
     {"bitwise_is_all_zeros", 0, 0, test_bitwise_is_all_zeros},
     {"follow-symlinks", 1, INT_MAX, test_follow_symlinks},
     {"assert", 0, 0, test_assert},
+    {"ovs_scan", 0, 0, test_ovs_scan},
     {NULL, 0, 0, NULL},
 };