util: add virStrToBytes() and virConfGetValueBytes() for scaled sizes

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>
This commit is contained in:
Denis V. Lunev
2026-09-02 18:29:16 +01:00
committed by Daniel P. Berrangé
parent 193398620e
commit 29148e83ac
6 changed files with 156 additions and 0 deletions
+2
View File
@@ -2282,6 +2282,7 @@ virConfFree;
virConfFreeValue;
virConfGetValue;
virConfGetValueBool;
virConfGetValueBytes;
virConfGetValueInt;
virConfGetValueLLong;
virConfGetValueSizeT;
@@ -3563,6 +3564,7 @@ virStringStripIPv6Brackets;
virStringStripSuffix;
virStringToUpper;
virStringTrimOptionalNewline;
virStrToBytes;
virStrToDouble;
virStrToLong_i;
virStrToLong_ll;
+47
View File
@@ -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
+3
View File
@@ -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);
+23
View File
@@ -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
+5
View File
@@ -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;
+76
View File
@@ -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 = { \