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
