virScaleInteger() already turns a number plus a unit suffix into
bytes, but every caller has to split a whole string like "10GiB" by
hand via virStrToLong_ullp() first, the way virFileReadValueScaledInt()
does for sysfs files. Add virStrToBytes(), which does that split once,
and virConfGetValueBytes() on top of it, so a qemu.conf-style setting
can accept a scaled size in one call. Named "Bytes" rather than "Size"
to avoid reading as a variant of virConfGetValueSizeT()/SSizeT(),
whose "T" is the C type they fill, not a unit.

Add unit tests for virStrToBytes() in virstringtest.c.

Signed-off-by: Denis V. Lunev <[email protected]>
---
 src/libvirt_private.syms |  2 ++
 src/util/virconf.c       | 47 +++++++++++++++++++++++++
 src/util/virconf.h       |  3 ++
 src/util/virutil.c       | 23 ++++++++++++
 src/util/virutil.h       |  5 +++
 tests/virstringtest.c    | 76 ++++++++++++++++++++++++++++++++++++++++
 6 files changed, 156 insertions(+)

diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms
index c76e5cb08a..758861b266 100644
--- a/src/libvirt_private.syms
+++ b/src/libvirt_private.syms
@@ -2278,6 +2278,7 @@ virConfGetValue;
 virConfGetValueBool;
 virConfGetValueInt;
 virConfGetValueLLong;
+virConfGetValueBytes;
 virConfGetValueSizeT;
 virConfGetValueSSizeT;
 virConfGetValueString;
@@ -3566,6 +3567,7 @@ virStrToLong_ul;
 virStrToLong_ull;
 virStrToLong_ullp;
 virStrToLong_ulp;
+virStrToBytes;
 virTrimSpaces;
 
 
diff --git a/src/util/virconf.c b/src/util/virconf.c
index c820c94037..20a54905b0 100644
--- a/src/util/virconf.c
+++ b/src/util/virconf.c
@@ -1116,6 +1116,53 @@ int virConfGetValueUInt(virConf *conf,
 }
 
 
+/**
+ * virConfGetValueBytes:
+ * @conf: the config object
+ * @setting: the config entry name
+ * @value: pointer to hold the byte count
+ *
+ * Get the value of the config entry @setting, storing it in @value.
+ * The entry may be a plain integer, taken as a byte count, or a
+ * string holding a byte count followed by a unit suffix understood
+ * by virStrToBytes(). If the config entry is not present, then
+ * @value will be unmodified.
+ *
+ * Reports an error if the config entry is set but has an unexpected
+ * type, or if a string entry cannot be parsed as a size.
+ *
+ * Returns: 1 if the value was present, 0 if missing, -1 on error
+ */
+int virConfGetValueBytes(virConf *conf,
+                        const char *setting,
+                        unsigned long long *value)
+{
+    virConfValue *cval = virConfGetValue(conf, setting);
+
+    VIR_DEBUG("Get value size %p %d",
+              cval, cval ? cval->type : VIR_CONF_NONE);
+
+    if (!cval)
+        return 0;
+
+    if (cval->type == VIR_CONF_ULLONG) {
+        *value = cval->l;
+        return 1;
+    }
+
+    if (cval->type == VIR_CONF_STRING) {
+        if (virStrToBytes(cval->str, ULLONG_MAX, value) < 0)
+            return -1;
+        return 1;
+    }
+
+    virReportError(VIR_ERR_INTERNAL_ERROR,
+                   _("%1$s: expected an unsigned integer or a size string for 
'%2$s' parameter"),
+                   conf->filename, setting);
+    return -1;
+}
+
+
 /**
  * virConfGetValueSizeT:
  * @conf: the config object
diff --git a/src/util/virconf.h b/src/util/virconf.h
index e656a6a815..e98e3c6c59 100644
--- a/src/util/virconf.h
+++ b/src/util/virconf.h
@@ -100,6 +100,9 @@ int virConfGetValueInt(virConf *conf,
 int virConfGetValueUInt(virConf *conf,
                         const char *setting,
                         unsigned int *value);
+int virConfGetValueBytes(virConf *conf,
+                        const char *setting,
+                        unsigned long long *value);
 int virConfGetValueSizeT(virConf *conf,
                          const char *setting,
                          size_t *value);
diff --git a/src/util/virutil.c b/src/util/virutil.c
index 3e107cdae6..0f34ae3047 100644
--- a/src/util/virutil.c
+++ b/src/util/virutil.c
@@ -235,6 +235,29 @@ virScaleInteger(unsigned long long *value, const char 
*suffix,
 }
 
 
+/* Parse the whole of STR as a byte count into RESULT, rejecting the
+ * result if it exceeds LIMIT. STR is a plain decimal integer, or a
+ * decimal integer immediately followed by one of the unit suffixes
+ * recognized by virScaleInteger(); unlike virStrToLong_ullp(), no
+ * characters may be left over after that optional suffix. Return 0 on
+ * success, -1 with error message raised on failure. */
+int
+virStrToBytes(const char *str,
+            unsigned long long limit,
+            unsigned long long *result)
+{
+    char *end;
+
+    if (virStrToLong_ullp(str, &end, 10, result) < 0) {
+        virReportError(VIR_ERR_INVALID_ARG,
+                       _("Unable to parse integer from size '%1$s'"), str);
+        return -1;
+    }
+
+    return virScaleInteger(result, end, 1, limit);
+}
+
+
 /**
  * Format @val as a base-10 decimal number, in the
  * buffer @buf of size @buflen. To allocate a suitable
diff --git a/src/util/virutil.h b/src/util/virutil.h
index 2accb5777d..1b943d75e6 100644
--- a/src/util/virutil.h
+++ b/src/util/virutil.h
@@ -44,6 +44,11 @@ int virScaleInteger(unsigned long long *value, const char 
*suffix,
                     unsigned long long scale, unsigned long long limit)
     ATTRIBUTE_NONNULL(1) G_GNUC_WARN_UNUSED_RESULT;
 
+int virStrToBytes(const char *str,
+                 unsigned long long limit,
+                 unsigned long long *result)
+    ATTRIBUTE_NONNULL(1) ATTRIBUTE_NONNULL(3) G_GNUC_WARN_UNUSED_RESULT;
+
 char *virFormatIntDecimal(char *buf, size_t buflen, int val)
     ATTRIBUTE_NONNULL(1) G_GNUC_WARN_UNUSED_RESULT;
 
diff --git a/tests/virstringtest.c b/tests/virstringtest.c
index 0792155cc3..64a594eb6f 100644
--- a/tests/virstringtest.c
+++ b/tests/virstringtest.c
@@ -18,10 +18,13 @@
 
 #include <config.h>
 
+#include <limits.h>
+
 
 #include "testutils.h"
 #include "virlog.h"
 #include "virstring.h"
+#include "virutil.h"
 
 #define VIR_FROM_THIS VIR_FROM_NONE
 
@@ -378,6 +381,38 @@ testStringToLong(const void *opaque)
 }
 
 
+struct stringToScaledIntegerData {
+    const char *str;
+    unsigned long long limit;
+    unsigned long long expect;
+    int expect_ret;
+};
+
+static int
+testStringToScaledInteger(const void *opaque)
+{
+    const struct stringToScaledIntegerData *data = opaque;
+    unsigned long long value;
+    int ret;
+
+    ret = virStrToBytes(data->str, data->limit, &value);
+
+    if (ret != data->expect_ret) {
+        fprintf(stderr, "Expected return '%d', got '%d' for '%s'\n",
+                data->expect_ret, ret, data->str);
+        return -1;
+    }
+
+    if (ret == 0 && value != data->expect) {
+        fprintf(stderr, "Expected value '%llu', got '%llu' for '%s'\n",
+                data->expect, value, data->str);
+        return -1;
+    }
+
+    return 0;
+}
+
+
 struct stringToDoubleData {
     const char *str;
     const char *end_ptr;
@@ -678,6 +713,47 @@ mymain(void)
     TEST_STRTOL("-18446744073709551616", NULL, 0, -1, 0U, -1,
                 0LL, -1, 0ULL, -1);
 
+#define TEST_SCALED_SIZE(str, limit, expect, expect_ret) \
+    do { \
+        struct stringToScaledIntegerData data = { \
+            str, limit, expect, expect_ret, \
+        }; \
+        if (virTestRun("virStrToBytes '" str "'", \
+                       testStringToScaledInteger, &data) < 0) \
+            ret = -1; \
+    } while (0)
+
+    /* Plain byte counts, no suffix */
+    TEST_SCALED_SIZE("0", ULLONG_MAX, 0, 0);
+    TEST_SCALED_SIZE("1073741824", ULLONG_MAX, 1073741824, 0);
+
+    /* Binary suffixes, and their bare single-letter equivalents */
+    TEST_SCALED_SIZE("10K", ULLONG_MAX, 10240, 0);
+    TEST_SCALED_SIZE("10KiB", ULLONG_MAX, 10240, 0);
+    TEST_SCALED_SIZE("1M", ULLONG_MAX, 1048576, 0);
+    TEST_SCALED_SIZE("1G", ULLONG_MAX, 1073741824, 0);
+    TEST_SCALED_SIZE("1GiB", ULLONG_MAX, 1073741824, 0);
+    TEST_SCALED_SIZE("1T", ULLONG_MAX, 1099511627776ULL, 0);
+
+    /* Decimal (SI) suffixes */
+    TEST_SCALED_SIZE("10KB", ULLONG_MAX, 10000, 0);
+    TEST_SCALED_SIZE("1GB", ULLONG_MAX, 1000000000, 0);
+
+    /* Bytes, spelled out */
+    TEST_SCALED_SIZE("42b", ULLONG_MAX, 42, 0);
+    TEST_SCALED_SIZE("42byte", ULLONG_MAX, 42, 0);
+    TEST_SCALED_SIZE("42bytes", ULLONG_MAX, 42, 0);
+
+    /* Unknown suffix */
+    TEST_SCALED_SIZE("10Q", ULLONG_MAX, 0, -1);
+
+    /* Trailing garbage after a valid suffix */
+    TEST_SCALED_SIZE("10Gextra", ULLONG_MAX, 0, -1);
+
+    /* Overflow */
+    TEST_SCALED_SIZE("18446744073709551615", 1000, 0, -1);
+    TEST_SCALED_SIZE("100E", ULLONG_MAX, 0, -1);
+
 #define TEST_STRTOD(str, end_ptr, res) \
     do { \
         struct stringToDoubleData data = { \
-- 
2.53.0

Reply via email to