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 <den@openvz.org> Reviewed-by: Daniel P. Berrangé <berrange@redhat.com> --- 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..a1d42e93dd 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2276,6 +2276,7 @@ virConfFree; virConfFreeValue; virConfGetValue; virConfGetValueBool; +virConfGetValueBytes; virConfGetValueInt; virConfGetValueLLong; virConfGetValueSizeT; @@ -3557,6 +3558,7 @@ virStringStripIPv6Brackets; virStringStripSuffix; virStringToUpper; virStringTrimOptionalNewline; +virStrToBytes; virStrToDouble; virStrToLong_i; virStrToLong_ll; diff --git a/src/util/virconf.c b/src/util/virconf.c index c820c94037..c4238e68d9 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 bytes %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..0932823ce3 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..2be9a460ac 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..5678c339ba 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..9c1448672c 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 stringToBytesData { + const char *str; + unsigned long long limit; + unsigned long long expect; + int expect_ret; +}; + +static int +testStringToBytes(const void *opaque) +{ + const struct stringToBytesData *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_STRTOBYTES(str, limit, expect, expect_ret) \ + do { \ + struct stringToBytesData data = { \ + str, limit, expect, expect_ret, \ + }; \ + if (virTestRun("virStrToBytes '" str "'", \ + testStringToBytes, &data) < 0) \ + ret = -1; \ + } while (0) + + /* Plain byte counts, no suffix */ + TEST_STRTOBYTES("0", ULLONG_MAX, 0, 0); + TEST_STRTOBYTES("1073741824", ULLONG_MAX, 1073741824, 0); + + /* Binary suffixes, and their bare single-letter equivalents */ + TEST_STRTOBYTES("10K", ULLONG_MAX, 10240, 0); + TEST_STRTOBYTES("10KiB", ULLONG_MAX, 10240, 0); + TEST_STRTOBYTES("1M", ULLONG_MAX, 1048576, 0); + TEST_STRTOBYTES("1G", ULLONG_MAX, 1073741824, 0); + TEST_STRTOBYTES("1GiB", ULLONG_MAX, 1073741824, 0); + TEST_STRTOBYTES("1T", ULLONG_MAX, 1099511627776ULL, 0); + + /* Decimal (SI) suffixes */ + TEST_STRTOBYTES("10KB", ULLONG_MAX, 10000, 0); + TEST_STRTOBYTES("1GB", ULLONG_MAX, 1000000000, 0); + + /* Bytes, spelled out */ + TEST_STRTOBYTES("42b", ULLONG_MAX, 42, 0); + TEST_STRTOBYTES("42byte", ULLONG_MAX, 42, 0); + TEST_STRTOBYTES("42bytes", ULLONG_MAX, 42, 0); + + /* Unknown suffix */ + TEST_STRTOBYTES("10Q", ULLONG_MAX, 0, -1); + + /* Trailing garbage after a valid suffix */ + TEST_STRTOBYTES("10Gextra", ULLONG_MAX, 0, -1); + + /* Overflow */ + TEST_STRTOBYTES("18446744073709551615", 1000, 0, -1); + TEST_STRTOBYTES("100E", ULLONG_MAX, 0, -1); + #define TEST_STRTOD(str, end_ptr, res) \ do { \ struct stringToDoubleData data = { \ -- 2.53.0