I want to teach the data plugin how to parse @4k out of a larger input
string, but that entails the need for a way to opt in to not erroring
out when an unrecognized suffix is present.

Some of the existing unit tests expose cases where parsing a value
substring out of a larger string can have interesting results; but in
the case of the data plugin, the caller will validate that any
remaining suffix starts with whitespace (and not '.' or a letter),
which should avoid the oddest cases due to the additional context that
the unit test lacks.

Signed-off-by: Eric Blake <ebl...@redhat.com>
---
 common/include/human-size-test-cases.h | 83 ++++++++++++++------------
 common/include/human-size.h            | 57 ++++++++++++------
 common/include/test-human-size.c       | 52 ++++++++++++++--
 server/test-public.c                   | 13 ++--
 4 files changed, 137 insertions(+), 68 deletions(-)

diff --git a/common/include/human-size-test-cases.h 
b/common/include/human-size-test-cases.h
index f351f7c5..ef845f4c 100644
--- a/common/include/human-size-test-cases.h
+++ b/common/include/human-size-test-cases.h
@@ -38,17 +38,22 @@
 /* Just some common test cases shared (in nbdkit) between
  * common/include/test-human-size.c and server/test-public.c
  */
-static struct pair {
+static struct tuple {
   const char *str;
   int64_t res;
-} pairs[] = {
+  const char *tail;
+} tuples[] = {
   /* Bogus strings */
   { "", -1 },
-  { "0x0", -1 },
+  { " ", -1 },
+  { "+ 1", -1 },
   { "garbage", -1 },
-  { "0garbage", -1 },
-  { "8E", -1 },
-  { "8192P", -1 },
+
+  /* Extracting substring value from larger string */
+  { "0junk", 0, "junk" }, /* no suffix j */
+  { "0garbage", 0, "arbage" }, /* 0g recognized */
+  { "1 1", 1, " 1" },
+  { "1M 1", 1024 * 1024, " 1" },

   /* Strings leading to overflow */
   { "9223372036854775808", -1 }, /* INT_MAX + 1 */
@@ -56,6 +61,8 @@ static struct pair {
   { "18446744073709551615", -1 }, /* UINT64_MAX */
   { "18446744073709551616", -1 }, /* UINT64_MAX + 1 */
   { "999999999999999999999999", -1 },
+  { "8E", -1 },
+  { "8192P", -1 },

   /* Strings representing negative values */
   { "-1", -1 },
@@ -68,39 +75,41 @@ static struct pair {
   { "-18446744073709551614", -1 }, /* -UINT64_MAX + 1 */

   /* Strings we may want to support in the future */
-  { "M", -1 },
-  { "1MB", -1 },
-  { "1MiB", -1 },
-  { "1.5M", -1 },
+  { "0x0", 0, "x0" }, /* Nicer would be 0 with tail "" */
+  { "M", -1 }, /* Nicer would be 1024 * 1024 with tail "" */
+  { "1MB", 1024 * 1024, "B" }, /* Nicer would be 1000 * 1000 with tail "" */
+  { "1MiB", 1024 * 1024, "iB" }, /* Nicer would be 1024 * 1024 with tail "" */
+  { "1.5M", 1, ".5M" }, /* Nicer would be 512 * 3 * 1024 with tail "" */
+  { "1.5MB", 1, ".5MB" }, /* Nicer would be 1500 * 1000 with tail "" */

   /* Valid strings */
-  { "-0", 0 },
-  { "0", 0 },
-  { "+0", 0 },
-  { " 08", 8 },
-  { "1", 1 },
-  { "+1", 1 },
-  { "1234567890", 1234567890 },
-  { "+1234567890", 1234567890 },
-  { "9223372036854775807", INT64_MAX },
-  { "1s", 512 },
-  { "2S", 1024 },
-  { "1b", 1 },
-  { "1B", 1 },
-  { "1k", 1024 },
-  { "1K", 1024 },
-  { "1m", 1024 * 1024 },
-  { "1M", 1024 * 1024 },
-  { "+1M", 1024 * 1024 },
-  { "1g", 1024 * 1024 * 1024 },
-  { "1G", 1024 * 1024 * 1024 },
-  { "1t", 1024LL * 1024 * 1024 * 1024 },
-  { "1T", 1024LL * 1024 * 1024 * 1024 },
-  { "1p", 1024LL * 1024 * 1024 * 1024 * 1024 },
-  { "1P", 1024LL * 1024 * 1024 * 1024 * 1024 },
-  { "8191p", 1024LL * 1024 * 1024 * 1024 * 1024 * 8191 },
-  { "1e", 1024LL * 1024 * 1024 * 1024 * 1024 * 1024 },
-  { "1E", 1024LL * 1024 * 1024 * 1024 * 1024 * 1024 },
+  { " -0", 0, "" },
+  { "0", 0, "" },
+  { "+0", 0, "" },
+  { " 08", 8, "" },
+  { "1", 1, "" },
+  { "+1", 1, "" },
+  { "1234567890", 1234567890, "" },
+  { "+1234567890", 1234567890, "" },
+  { "9223372036854775807", INT64_MAX, "" },
+  { "1s", 512, "" },
+  { "2S", 1024, "" },
+  { "1b", 1, "" },
+  { "1B", 1, "" },
+  { "1k", 1024, "" },
+  { "1K", 1024, "" },
+  { "1m", 1024 * 1024, "" },
+  { "1M", 1024 * 1024, "" },
+  { "+1M", 1024 * 1024, "" },
+  { "1g", 1024 * 1024 * 1024, "" },
+  { "1G", 1024 * 1024 * 1024, "" },
+  { "1t", 1024LL * 1024 * 1024 * 1024, "" },
+  { "1T", 1024LL * 1024 * 1024 * 1024, "" },
+  { "1p", 1024LL * 1024 * 1024 * 1024 * 1024, "" },
+  { "1P", 1024LL * 1024 * 1024 * 1024 * 1024, "" },
+  { "8191p", 1024LL * 1024 * 1024 * 1024 * 1024 * 8191, "" },
+  { "1e", 1024LL * 1024 * 1024 * 1024 * 1024 * 1024, "" },
+  { "1E", 1024LL * 1024 * 1024 * 1024 * 1024 * 1024, "" },
 };

 #endif /* NBDKIT_HUMAN_SIZE_TEST_CASES_H */
diff --git a/common/include/human-size.h b/common/include/human-size.h
index 788dbd7b..aa551888 100644
--- a/common/include/human-size.h
+++ b/common/include/human-size.h
@@ -39,14 +39,17 @@

 /* Attempt to parse a string with a possible scaling suffix, such as
  * "2M".  Disk sizes cannot usefully exceed off_t (which is signed)
- * and cannot be negative.
+ * and cannot be negative.  If rest is not NULL, the number being
+ * parsed is treated as a substring within a larger input; if a value
+ * was parsed, *rest is set to the first unparsed byte of str.  If
+ * rest is NULL, any trailing garbage is treated as an error.
  *
  * On error, returns -1 and sets *error and *pstr.  You can form a
  * final error message by appending "<error>: <pstr>".
  */
 static inline int64_t
-human_size_parse (const char *str,
-                  const char **error, const char **pstr)
+human_size_parse_substr (const char *str, char **rest,
+                         const char **error, const char **pstr)
 {
   int64_t size;
   char *end;
@@ -56,6 +59,8 @@ human_size_parse (const char *str,
   /* XXX Should we allow hex? If so, hex cannot use scaling suffixes,
    * because some of them are valid hex digits.
    */
+  if (rest)
+    *rest = NULL;
   errno = 0;
   size = strtoimax (str, &end, 10);
   if (str == end) {
@@ -77,7 +82,7 @@ human_size_parse (const char *str,
   switch (*end) {
     /* No suffix */
   case '\0':
-    end--; /* Safe, since we already filtered out empty string */
+    /* Safe, since we already filtered out empty string */
     break;

     /* Powers of 1024 */
@@ -100,6 +105,7 @@ human_size_parse (const char *str,
     scale *= 1024;
     /* fallthru */
   case 'b': case 'B':
+    end++;
     break;

     /* "sectors", ie. units of 512 bytes, even if that's not the real
@@ -107,22 +113,8 @@ human_size_parse (const char *str,
      */
   case 's': case 'S':
     scale = 512;
+    end++;
     break;
-
-  default:
-    *error = "could not parse size: unknown suffix";
-    *pstr = end;
-    return -1;
-  }
-
-  /* XXX Maybe we should support 'MiB' as a synonym for 'M'; and 'MB'
-   * for powers of 1000, for similarity to GNU tools. But for now,
-   * anything beyond 'M' is dropped.
-   */
-  if (end[1]) {
-    *error = "could not parse size: unknown suffix";
-    *pstr = end;
-    return -1;
   }

   if (INT64_MAX / scale < size) {
@@ -131,7 +123,34 @@ human_size_parse (const char *str,
     return -1;
   }

+  /* XXX Maybe we should support 'MiB' as a synonym for 'M'; and 'MB'
+   * for powers of 1000, for similarity to GNU tools. But for now,
+   * anything beyond 'M' is dropped.
+   */
+  if (rest)
+    *rest = end;
+  else if (*end) {
+    *error = "could not parse size: unknown suffix";
+    *pstr = end;
+    return -1;
+  }
+
   return size * scale;
 }

+/* Attempt to parse a string with a possible scaling suffix, such as
+ * "2M".  Disk sizes cannot usefully exceed off_t (which is signed)
+ * and cannot be negative.  str must not have any trailing garbage.
+ *
+ * On error, returns -1 and sets *error and *pstr.  You can form a
+ * final error message by appending "<error>: <pstr>".
+ */
+static inline int64_t
+human_size_parse (const char *str,
+                  const char **error, const char **pstr)
+{
+  return human_size_parse_substr (str, NULL, error, pstr);
+}
+
+
 #endif /* NBDKIT_HUMAN_SIZE_H */
diff --git a/common/include/test-human-size.c b/common/include/test-human-size.c
index 7f75bb4b..3e388e39 100644
--- a/common/include/test-human-size.c
+++ b/common/include/test-human-size.c
@@ -36,10 +36,11 @@
 #include <stdlib.h>
 #include <stdbool.h>
 #include <stdint.h>
+#include <string.h>

 #include "array-size.h"
 #include "human-size.h"
-#include "human-size-test-cases.h" /* defines 'pairs' below */
+#include "human-size-test-cases.h" /* defines 'tuples' below */

 int
 main (void)
@@ -47,20 +48,59 @@ main (void)
   size_t i;
   bool pass = true;

-  for (i = 0; i < ARRAY_SIZE (pairs); i++) {
+  for (i = 0; i < ARRAY_SIZE (tuples); i++) {
     const char *error = NULL, *pstr = NULL;
+    char *rest = NULL;
     int64_t r;
+    int64_t expect;

-    r = human_size_parse (pairs[i].str, &error, &pstr);
-    if (r != pairs[i].res) {
+    r = human_size_parse (tuples[i].str, &error, &pstr);
+    expect = tuples[i].tail && *tuples[i].tail ? -1 : tuples[i].res;
+    if (r != expect) {
       fprintf (stderr,
                "Wrong parse for %s, got %" PRId64 ", expected %" PRId64 "\n",
-               pairs[i].str, r, pairs[i].res);
+               tuples[i].str, r, expect);
       pass = false;
     }
     if (r == -1) {
       if (error == NULL || pstr == NULL) {
-        fprintf (stderr, "Wrong error message handling for %s\n", 
pairs[i].str);
+        fprintf (stderr, "Wrong error message handling for %s\n",
+                 tuples[i].str);
+        pass = false;
+      }
+    }
+
+    r = human_size_parse_substr (tuples[i].str, &rest, &error, &pstr);
+    if (r != tuples[i].res) {
+      fprintf (stderr,
+               "Wrong parse for %s, got %" PRId64 ", expected %" PRId64 "\n",
+               tuples[i].str, r, tuples[i].res);
+      pass = false;
+    }
+    if (r == -1) {
+      if (error == NULL || pstr == NULL) {
+        fprintf (stderr, "Wrong error message handling for %s\n",
+                 tuples[i].str);
+        pass = false;
+      }
+      if (rest != NULL) {
+        fprintf (stderr,
+                 "Wrong suffix handling for %s, expected NULL, got '%s'\n",
+                 tuples[i].str, rest);
+        pass = false;
+      }
+    }
+    else {
+      if (rest == NULL) {
+        fprintf (stderr,
+                 "Wrong suffix handling for %s, expected '%s', got NULL\n",
+                 tuples[i].str, tuples[i].tail);
+        pass = false;
+      }
+      else if (strcmp (rest, tuples[i].tail) != 0) {
+        fprintf (stderr,
+                 "Wrong suffix handling for %s, expected '%s', got '%s'\n",
+                 tuples[i].str, tuples[i].tail, rest);
         pass = false;
       }
     }
diff --git a/server/test-public.c b/server/test-public.c
index 0eddd6ca..bb30134e 100644
--- a/server/test-public.c
+++ b/server/test-public.c
@@ -110,7 +110,7 @@ backend_default_export (struct backend *b, int readonly)

 /* Unit tests. */

-#include "human-size-test-cases.h" /* defines 'pairs' below */
+#include "human-size-test-cases.h" /* defines 'tuples' below */

 static bool
 test_nbdkit_parse_size (void)
@@ -118,19 +118,20 @@ test_nbdkit_parse_size (void)
   size_t i;
   bool pass = true;

-  for (i = 0; i < ARRAY_SIZE (pairs); i++) {
+  for (i = 0; i < ARRAY_SIZE (tuples); i++) {
     int64_t r;
+    int64_t expect = tuples[i].tail && *tuples[i].tail ? -1 : tuples[i].res;

     error_flagged = false;
-    r = nbdkit_parse_size (pairs[i].str);
-    if (r != pairs[i].res) {
+    r = nbdkit_parse_size (tuples[i].str);
+    if (r != expect) {
       fprintf (stderr,
                "Wrong parse for %s, got %" PRId64 ", expected %" PRId64 "\n",
-               pairs[i].str, r, pairs[i].res);
+               tuples[i].str, r, expect);
       pass = false;
     }
     if ((r == -1) != error_flagged) {
-      fprintf (stderr, "Wrong error message handling for %s\n", pairs[i].str);
+      fprintf (stderr, "Wrong error message handling for %s\n", tuples[i].str);
       pass = false;
     }
   }
-- 
2.49.0
_______________________________________________
Libguestfs mailing list -- guestfs@lists.libguestfs.org
To unsubscribe send an email to guestfs-le...@lists.libguestfs.org

Reply via email to