[PATCH 02/62] kvargs: add numeric conversion helpers
From: Stephen Hemminger <stephen@networkplumber.org>
Date: 2026-09-14 05:49:39
Subsystem:
library code, the rest · Maintainers:
Andrew Morton, Linus Torvalds
Drivers which take numeric values in devargs each open code the
conversion from string to integer, and often get it wrong.
A survey of the tree finds at least fifteen separate
implementations of "parse an unsigned integer devarg", of which two are
exported from lib/ and byte for byte identical to each other.
The recurring bugs are:
- atoi() is used, so overflow is undefined and nothing is validated;
- errno is checked without being reset first, so an unrelated earlier
failure rejects a valid value;
- errno is checked but endptr is not, so "foo" is silently accepted
as zero;
- endptr is checked but errno is not, so an overflowing value is
accepted as ULLONG_MAX;
- the result is stored into a narrower type with no range check, so
nb_desc=65537 silently becomes 1;
- strtoul() is used for an unsigned target, so a leading '-' is
accepted and wrapped around, and dev_caps_mask=-1 enables
everything;
- the value is dereferenced without checking for NULL, so a key given
with no value segfaults;
- base 0 is passed, so a leading zero unexpectedly selects octal.
Add a set of helpers matching arg_handler_t, so they can be passed
straight to rte_kvargs_process(), covering the integer types drivers
actually store into. Each validates the whole string and only writes
the target on success, so a caller supplied default survives a bad
argument.
Add rte_kvargs_handle_bool for on/off style arguments. It accepts the
word forms which only sfc supports today, and treats a key given
without a value as true.
Where a driver needs a range narrower than the target type, expose the
underlying rte_kvargs_to_uint and rte_kvargs_to_int.
Octal is deliberately not supported: no driver documents it, and
reading "010" as eight has been a recurring surprise.
Signed-off-by: Stephen Hemminger <stephen@networkplumber.org>
---
app/test/test_kvargs.c | 221 +++++++++++++
doc/guides/prog_guide/devargs.rst | 18 +
doc/guides/rel_notes/release_26_11.rst | 20 ++
lib/kvargs/rte_kvargs.c | 440 +++++++++++++++++++++++++
lib/kvargs/rte_kvargs.h | 186 +++++++++++
5 files changed, 885 insertions(+)
diff --git a/app/test/test_kvargs.c b/app/test/test_kvargs.c
index a14b75948a..b74dfd6acb 100644
--- a/app/test/test_kvargs.c
+++ b/app/test/test_kvargs.c@@ -2,6 +2,8 @@ * Copyright 2014 6WIND S.A. */ +#include <errno.h> +#include <stdint.h> #include <stdlib.h> #include <stdio.h> #include <string.h>
@@ -328,6 +330,221 @@ static int test_invalid_kvargs(void) return -1; } +/* Check the numeric conversion helpers on a value passed through kvargs. */ +static int +handle_one(arg_handler_t handler, const char *value, void *opaque) +{ + struct rte_kvargs *kvlist; + char args[128]; + int ret; + + if (value != NULL) + snprintf(args, sizeof(args), "k=%s", value); + else + snprintf(args, sizeof(args), "k"); + + kvlist = rte_kvargs_parse(args, NULL); + if (kvlist == NULL) + return -1; + + ret = rte_kvargs_process_opt(kvlist, "k", handler, opaque); + rte_kvargs_free(kvlist); + + /* rte_kvargs_process_opt() flattens the handler error to -1. */ + return ret; +} + +/* A handler must accept a good value, and leave the target alone otherwise. */ +#define CHECK_GOOD(handler, type, str, expected) do { \ + type v = (type)0x5a; \ + TEST_ASSERT_SUCCESS(handle_one(handler, str, &v), \ + "%s rejected \"%s\"", #handler, str); \ + TEST_ASSERT_EQUAL(v, (type)(expected), \ + "%s(\"%s\") gave the wrong value", #handler, str); \ +} while (0) + +#define CHECK_BAD(handler, type, str) do { \ + type v = (type)0x5a; \ + TEST_ASSERT_FAIL(handle_one(handler, str, &v), \ + "%s accepted \"%s\"", #handler, str); \ + TEST_ASSERT_EQUAL(v, (type)0x5a, \ + "%s clobbered the target on \"%s\"", #handler, str); \ +} while (0) + +static int +test_handle_unsigned(void) +{ + CHECK_GOOD(rte_kvargs_handle_u8, uint8_t, "0", 0); + CHECK_GOOD(rte_kvargs_handle_u8, uint8_t, "255", 255); + CHECK_GOOD(rte_kvargs_handle_u8, uint8_t, "0xff", 255); + CHECK_GOOD(rte_kvargs_handle_u8, uint8_t, "0XFF", 255); + CHECK_GOOD(rte_kvargs_handle_u8, uint8_t, "+7", 7); + /* A leading zero must not select octal. */ + CHECK_GOOD(rte_kvargs_handle_u8, uint8_t, "010", 10); + CHECK_BAD(rte_kvargs_handle_u8, uint8_t, "256"); + CHECK_BAD(rte_kvargs_handle_u8, uint8_t, "-1"); + + CHECK_GOOD(rte_kvargs_handle_u16, uint16_t, "65535", 65535); + CHECK_BAD(rte_kvargs_handle_u16, uint16_t, "65536"); + + CHECK_GOOD(rte_kvargs_handle_u32, uint32_t, "4294967295", UINT32_MAX); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "4294967296"); + + CHECK_GOOD(rte_kvargs_handle_u64, uint64_t, "18446744073709551615", + UINT64_MAX); + CHECK_GOOD(rte_kvargs_handle_u64, uint64_t, "0xffffffffffffffff", + UINT64_MAX); + CHECK_BAD(rte_kvargs_handle_u64, uint64_t, "18446744073709551616"); + + CHECK_GOOD(rte_kvargs_handle_uint, unsigned int, "42", 42); + CHECK_GOOD(rte_kvargs_handle_size, size_t, "42", 42); + + /* Malformed values, rejected for every width. */ + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, ""); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "abc"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "12abc"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "12 34"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "0x"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "0x0x10"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "0X0X10"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "--1"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "+-1"); + /* strtoull() would skip the space and negate what follows. */ + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "+ 1"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "- 1"); + CHECK_BAD(rte_kvargs_handle_u32, uint32_t, "+ -1"); + /* Trailing white space is fine, though. */ + CHECK_GOOD(rte_kvargs_handle_u32, uint32_t, " 12 ", 12); + + /* A key with no value at all. */ + { + uint32_t v = 0x5a; + + TEST_ASSERT_FAIL(handle_one(rte_kvargs_handle_u32, NULL, &v), + "u32 accepted a key with no value"); + TEST_ASSERT_EQUAL(v, 0x5aU, "target clobbered"); + } + + return TEST_SUCCESS; +} + +static int +test_handle_signed(void) +{ + CHECK_GOOD(rte_kvargs_handle_i8, int8_t, "-128", -128); + CHECK_GOOD(rte_kvargs_handle_i8, int8_t, "127", 127); + CHECK_BAD(rte_kvargs_handle_i8, int8_t, "-129"); + CHECK_BAD(rte_kvargs_handle_i8, int8_t, "128"); + + CHECK_GOOD(rte_kvargs_handle_i16, int16_t, "-32768", -32768); + CHECK_BAD(rte_kvargs_handle_i16, int16_t, "32768"); + + CHECK_GOOD(rte_kvargs_handle_i32, int32_t, "-2147483648", INT32_MIN); + CHECK_BAD(rte_kvargs_handle_i32, int32_t, "2147483648"); + + /* INT64_MIN has a magnitude one past INT64_MAX. */ + CHECK_GOOD(rte_kvargs_handle_i64, int64_t, "-9223372036854775808", + INT64_MIN); + CHECK_GOOD(rte_kvargs_handle_i64, int64_t, "9223372036854775807", + INT64_MAX); + CHECK_BAD(rte_kvargs_handle_i64, int64_t, "9223372036854775808"); + CHECK_BAD(rte_kvargs_handle_i64, int64_t, "-9223372036854775809"); + + /* A sign in front of a hex value. */ + CHECK_GOOD(rte_kvargs_handle_i32, int32_t, "-0x10", -16); + CHECK_BAD(rte_kvargs_handle_i32, int32_t, "-0x0x10"); + + CHECK_GOOD(rte_kvargs_handle_int, int, "-1", -1); + CHECK_GOOD(rte_kvargs_handle_int, int, "+1", 1); + CHECK_BAD(rte_kvargs_handle_int, int, ""); + CHECK_BAD(rte_kvargs_handle_int, int, "1x"); + CHECK_BAD(rte_kvargs_handle_int, int, "-"); + CHECK_BAD(rte_kvargs_handle_int, int, "- 1"); + CHECK_BAD(rte_kvargs_handle_int, int, "--1"); + + CHECK_GOOD(rte_kvargs_handle_long, long, "-1", -1); + CHECK_GOOD(rte_kvargs_handle_long, long, "+1", 1); + CHECK_BAD(rte_kvargs_handle_long, long, ""); + CHECK_BAD(rte_kvargs_handle_long, long, "1x"); + + CHECK_GOOD(rte_kvargs_handle_ulong, unsigned long, "1", 1); + CHECK_GOOD(rte_kvargs_handle_ulong, unsigned long, "0x10", 16); + CHECK_BAD(rte_kvargs_handle_ulong, unsigned long, "-1"); + CHECK_BAD(rte_kvargs_handle_ulong, unsigned long, "1x"); + + return TEST_SUCCESS; +} + +static int +test_handle_bool(void) +{ + static const char * const yes[] = { + "1", "y", "Y", "yes", "YES", "on", "On", "true", "TRUE", + }; + static const char * const no[] = { + "0", "n", "N", "no", "NO", "off", "Off", "false", "FALSE", + }; + unsigned int i; + bool v; + + for (i = 0; i < RTE_DIM(yes); i++) { + v = false; + TEST_ASSERT_SUCCESS(handle_one(rte_kvargs_handle_bool, yes[i], &v), + "bool rejected \"%s\"", yes[i]); + TEST_ASSERT(v, "\"%s\" should be true", yes[i]); + } + + for (i = 0; i < RTE_DIM(no); i++) { + v = true; + TEST_ASSERT_SUCCESS(handle_one(rte_kvargs_handle_bool, no[i], &v), + "bool rejected \"%s\"", no[i]); + TEST_ASSERT(!v, "\"%s\" should be false", no[i]); + } + + /* A bare key is enough to enable the option. */ + v = false; + TEST_ASSERT_SUCCESS(handle_one(rte_kvargs_handle_bool, NULL, &v), + "bool rejected a key with no value"); + TEST_ASSERT(v, "a key with no value should be true"); + + /* But a blank value is not a missing one. */ + CHECK_BAD(rte_kvargs_handle_bool, bool, ""); + CHECK_BAD(rte_kvargs_handle_bool, bool, "2"); + CHECK_BAD(rte_kvargs_handle_bool, bool, "yep"); + + return TEST_SUCCESS; +} + +static int +test_kvargs_to_range(void) +{ + uint64_t u = 0x5a; + int64_t s = 0x5a; + + TEST_ASSERT_SUCCESS(rte_kvargs_to_uint("10", 0, 10, &u), "10 in [0,10]"); + TEST_ASSERT_EQUAL(u, 10U, "wrong value"); + + TEST_ASSERT_EQUAL(rte_kvargs_to_uint("11", 0, 10, &u), -ERANGE, + "11 should be out of [0,10]"); + TEST_ASSERT_EQUAL(u, 10U, "target clobbered on range error"); + + TEST_ASSERT_EQUAL(rte_kvargs_to_uint("0", 1, 10, &u), -ERANGE, + "0 should be out of [1,10]"); + TEST_ASSERT_EQUAL(rte_kvargs_to_uint(NULL, 0, 10, &u), -EINVAL, + "NULL should be rejected"); + TEST_ASSERT_EQUAL(rte_kvargs_to_uint("x", 0, 10, &u), -EINVAL, + "\"x\" should be rejected"); + TEST_ASSERT_EQUAL(rte_kvargs_to_uint("5", 0, 10, NULL), -EINVAL, + "a NULL result should be rejected"); + + TEST_ASSERT_SUCCESS(rte_kvargs_to_int("-5", -10, 10, &s), "-5 in [-10,10]"); + TEST_ASSERT_EQUAL(s, -5, "wrong value"); + TEST_ASSERT_EQUAL(rte_kvargs_to_int("-11", -10, 10, &s), -ERANGE, + "-11 should be out of [-10,10]"); + + return TEST_SUCCESS; +} + static struct unit_test_suite kvargs_test_suite = { .suite_name = "Kvargs Unit Test Suite", .setup = NULL,
@@ -354,6 +571,10 @@ static struct unit_test_suite kvargs_test_suite = { TEST_CASE(test_parse_empty_elements), TEST_CASE(test_parse_with_only_key), TEST_CASE(test_invalid_kvargs), + TEST_CASE(test_handle_unsigned), + TEST_CASE(test_handle_signed), + TEST_CASE(test_handle_bool), + TEST_CASE(test_kvargs_to_range), TEST_CASES_END() /**< NULL terminate unit test array */ } };
diff --git a/doc/guides/prog_guide/devargs.rst b/doc/guides/prog_guide/devargs.rst
index c8a7224aa0..b73ad20cf6 100644
--- a/doc/guides/prog_guide/devargs.rst
+++ b/doc/guides/prog_guide/devargs.rst@@ -248,6 +248,24 @@ PMD drivers can parse devargs using the kvargs library: return 0; } +Rather than writing a handler for each numeric argument, use the +conversion handlers provided by kvargs, which do the range checking: + +.. code-block:: c + + uint16_t queues = 1; + bool scalar = false; + + rte_kvargs_process(kvlist, "queues", rte_kvargs_handle_u16, &queues); + rte_kvargs_process_opt(kvlist, "scalar", rte_kvargs_handle_bool, &scalar); + +Boolean arguments accept ``1``, ``y``, ``yes``, ``on`` and ``true``, +case insensitively, and their negative counterparts. A bare ``scalar`` +with no value means true, but reaches the handler only through +rte_kvargs_process_opt(); rte_kvargs_process() rejects a missing value +first. An empty ``scalar=`` is rejected, since that is what an unset +shell variable expands to. + For Ethernet devices, use ``rte_eth_devargs_parse()`` to parse standard Ethernet arguments like representors:
diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst
index 87c7e81bde..c175fe089b 100644
--- a/doc/guides/rel_notes/release_26_11.rst
+++ b/doc/guides/rel_notes/release_26_11.rst@@ -55,6 +55,26 @@ New Features Also, make sure to start the actual text at the margin. ======================================================= +* **Added numeric conversion helpers to kvargs.** + + Added a set of ``arg_handler_t`` compatible helpers which convert a device + argument value into a numeric variable, so that drivers no longer need to + open code the conversion and its validation: + + * ``rte_kvargs_handle_u8``, ``rte_kvargs_handle_u16``, + ``rte_kvargs_handle_u32``, ``rte_kvargs_handle_u64``, + ``rte_kvargs_handle_uint``, ``rte_kvargs_handle_ulong`` + and ``rte_kvargs_handle_size`` + * ``rte_kvargs_handle_i8``, ``rte_kvargs_handle_i16``, + ``rte_kvargs_handle_i32``, ``rte_kvargs_handle_i64``, + ``rte_kvargs_handle_int`` and ``rte_kvargs_handle_long`` + * ``rte_kvargs_handle_bool``, accepting ``1``, ``y``, ``yes``, ``on``, + ``true`` and their negative counterparts. A bare ``key`` means true; + an empty ``key=`` is rejected. + + Added ``rte_kvargs_to_uint`` and ``rte_kvargs_to_int`` for the cases where + a driver needs a narrower range than the target type allows. + Removed Items -------------
diff --git a/lib/kvargs/rte_kvargs.c b/lib/kvargs/rte_kvargs.c
index 4e3198b33f..c3bd199f3e 100644
--- a/lib/kvargs/rte_kvargs.c
+++ b/lib/kvargs/rte_kvargs.c@@ -3,15 +3,28 @@ * Copyright(c) 2014 6WIND S.A. */ +#include <ctype.h> +#include <errno.h> +#include <inttypes.h> +#include <limits.h> #include <string.h> #include <stdlib.h> #include <stdbool.h> +#include <stdint.h> #include <eal_export.h> +#include <rte_common.h> +#include <rte_log.h> #include <rte_os_shim.h> #include "rte_kvargs.h" +RTE_LOG_REGISTER_DEFAULT(kvargs_logtype, INFO); +#define RTE_LOGTYPE_KVARGS kvargs_logtype + +#define KVARGS_LOG(level, ...) \ + RTE_LOG_LINE(level, KVARGS, __VA_ARGS__) + /* * Receive a string with a list of arguments following the pattern * key=value,key=value,... and insert them into the list.
@@ -309,3 +322,430 @@ rte_kvargs_parse_delim(const char *args, const char * const valid_keys[], free(copy); return kvlist; } + +/* + * Determine the base of a numeric value and skip over its prefix. + * + * Only decimal and 0x/0X hexadecimal are recognized. Octal is deliberately + * not supported: no driver documents it, and silently reading "010" as eight + * has been a recurring source of surprise. + * + * Returns the base, and advances *str past the "0x" prefix if there is one. + * Returns 0 if what follows the prefix is a second one: strtoull() would + * strip that itself, making "0x0x10" sixteen rather than the garbage it is. + */ +static int +kvargs_get_base(const char **str) +{ + const char *s = *str; + + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && + isxdigit((unsigned char)s[2])) { + s += 2; + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) + return 0; + *str = s; + return 16; + } + + return 10; +} + +/* Skip trailing white space, and tell whether anything else is left. */ +static bool +kvargs_at_end(const char *str) +{ + while (isspace((unsigned char)*str)) + str++; + + return *str == '\0'; +} + +/* + * Consume an optional sign, and report whether it was negative. + * + * strtoull() skips white space and a sign of its own, and negates on '-', + * so the sign has to be taken away from it: it is handled here and anything + * that follows must be a digit or an 0x prefix. That rejects "+-1" and + * "- 1", which strtoull() would otherwise accept. + */ +static bool +kvargs_get_sign(const char **str) +{ + const char *s = *str; + bool negative; + + while (isspace((unsigned char)*s)) + s++; + + negative = (*s == '-'); + if (*s == '-' || *s == '+') + s++; + + *str = s; + return negative; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_to_uint, 26.11) +int +rte_kvargs_to_uint(const char *value, uint64_t min, uint64_t max, + uint64_t *result) +{ + const char *str = value; + unsigned long long val; + char *endptr; + int base; + + if (str == NULL || result == NULL) + return -EINVAL; + + /* "-1" would otherwise be silently wrapped around to UINT64_MAX. */ + if (kvargs_get_sign(&str)) + return -EINVAL; + + base = kvargs_get_base(&str); + if (base == 0) + return -EINVAL; /* doubled 0x prefix */ + + /* Nothing may sit between the sign and the digits. */ + if (!isxdigit((unsigned char)*str)) + return -EINVAL; + + errno = 0; + val = strtoull(str, &endptr, base); + if (endptr == str) + return -EINVAL; /* no digits in this base */ + if (errno == ERANGE) + return -ERANGE; + if (errno != 0) + return -EINVAL; + if (!kvargs_at_end(endptr)) + return -EINVAL; /* trailing garbage */ + + if (val < min || val > max) + return -ERANGE; + + *result = val; + return 0; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_to_int, 26.11) +int +rte_kvargs_to_int(const char *value, int64_t min, int64_t max, int64_t *result) +{ + const char *str = value; + unsigned long long mag; + char *endptr; + bool negative; + int64_t val; + int base; + + if (str == NULL || result == NULL) + return -EINVAL; + + negative = kvargs_get_sign(&str); + base = kvargs_get_base(&str); + if (base == 0) + return -EINVAL; /* doubled 0x prefix */ + + /* Nothing may sit between the sign and the digits. */ + if (!isxdigit((unsigned char)*str)) + return -EINVAL; + + /* + * The sign is consumed above, so that the 0x prefix can be found + * behind it, and the magnitude is parsed unsigned. Letting strtoll() + * do the whole job instead would reject INT64_MIN, whose magnitude is + * one past INT64_MAX. + */ + errno = 0; + mag = strtoull(str, &endptr, base); + if (endptr == str) + return -EINVAL; + if (errno == ERANGE) + return -ERANGE; + if (errno != 0) + return -EINVAL; + if (!kvargs_at_end(endptr)) + return -EINVAL; + + if (negative) { + if (mag > (unsigned long long)INT64_MAX + 1) + return -ERANGE; + /* Negate in unsigned space; -INT64_MIN would overflow. */ + val = (int64_t)(-(uint64_t)mag); + } else { + if (mag > INT64_MAX) + return -ERANGE; + val = (int64_t)mag; + } + + if (val < min || val > max) + return -ERANGE; + + *result = val; + return 0; +} + +/* + * The typed handlers below share this shape: convert with a range matching + * the target type, then store. The target is written only on success, so a + * caller-supplied default survives a bad argument. + */ +static int +kvargs_store_uint(const char *key, const char *value, void *opaque, + uint64_t max, uint64_t *val) +{ + int ret; + + if (opaque == NULL) + return -EINVAL; + + ret = rte_kvargs_to_uint(value, 0, max, val); + if (ret < 0) + KVARGS_LOG(ERR, "invalid value \"%s\" for key \"%s\", expected 0..%" PRIu64, + value != NULL ? value : "", key != NULL ? key : "", max); + + return ret; +} + +static int +kvargs_store_int(const char *key, const char *value, void *opaque, + int64_t min, int64_t max, int64_t *val) +{ + int ret; + + if (opaque == NULL) + return -EINVAL; + + ret = rte_kvargs_to_int(value, min, max, val); + if (ret < 0) + KVARGS_LOG(ERR, "invalid value \"%s\" for key \"%s\", expected %" PRId64 "..%" PRId64, + value != NULL ? value : "", key != NULL ? key : "", + min, max); + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_u8, 26.11) +int +rte_kvargs_handle_u8(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_uint(key, value, opaque, UINT8_MAX, &val); + if (ret == 0) + *(uint8_t *)opaque = (uint8_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_u16, 26.11) +int +rte_kvargs_handle_u16(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_uint(key, value, opaque, UINT16_MAX, &val); + if (ret == 0) + *(uint16_t *)opaque = (uint16_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_u32, 26.11) +int +rte_kvargs_handle_u32(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_uint(key, value, opaque, UINT32_MAX, &val); + if (ret == 0) + *(uint32_t *)opaque = (uint32_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_u64, 26.11) +int +rte_kvargs_handle_u64(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_uint(key, value, opaque, UINT64_MAX, &val); + if (ret == 0) + *(uint64_t *)opaque = (uint64_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_uint, 26.11) +int +rte_kvargs_handle_uint(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_uint(key, value, opaque, UINT_MAX, &val); + if (ret == 0) + *(unsigned int *)opaque = (unsigned int)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_size, 26.11) +int +rte_kvargs_handle_size(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_uint(key, value, opaque, SIZE_MAX, &val); + if (ret == 0) + *(size_t *)opaque = (size_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_i8, 26.11) +int +rte_kvargs_handle_i8(const char *key, const char *value, void *opaque) +{ + int64_t val; + int ret; + + ret = kvargs_store_int(key, value, opaque, INT8_MIN, INT8_MAX, &val); + if (ret == 0) + *(int8_t *)opaque = (int8_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_i16, 26.11) +int +rte_kvargs_handle_i16(const char *key, const char *value, void *opaque) +{ + int64_t val; + int ret; + + ret = kvargs_store_int(key, value, opaque, INT16_MIN, INT16_MAX, &val); + if (ret == 0) + *(int16_t *)opaque = (int16_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_i32, 26.11) +int +rte_kvargs_handle_i32(const char *key, const char *value, void *opaque) +{ + int64_t val; + int ret; + + ret = kvargs_store_int(key, value, opaque, INT32_MIN, INT32_MAX, &val); + if (ret == 0) + *(int32_t *)opaque = (int32_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_i64, 26.11) +int +rte_kvargs_handle_i64(const char *key, const char *value, void *opaque) +{ + int64_t val; + int ret; + + ret = kvargs_store_int(key, value, opaque, INT64_MIN, INT64_MAX, &val); + if (ret == 0) + *(int64_t *)opaque = (int64_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_int, 26.11) +int +rte_kvargs_handle_int(const char *key, const char *value, void *opaque) +{ + int64_t val; + int ret; + + ret = kvargs_store_int(key, value, opaque, INT_MIN, INT_MAX, &val); + if (ret == 0) + *(int *)opaque = (int)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_long, 26.11) +int +rte_kvargs_handle_long(const char *key, const char *value, void *opaque) +{ + int64_t val; + int ret; + + ret = kvargs_store_int(key, value, opaque, LONG_MIN, LONG_MAX, &val); + if (ret == 0) + *(long *)opaque = (long)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_ulong, 26.11) +int +rte_kvargs_handle_ulong(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_uint(key, value, opaque, ULONG_MAX, &val); + if (ret == 0) + *(unsigned long *)opaque = (unsigned long)val; + + return ret; +} + +static const char * const kvargs_true[] = { "1", "y", "yes", "on", "true" }; +static const char * const kvargs_false[] = { "0", "n", "no", "off", "false" }; + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_bool, 26.11) +int +rte_kvargs_handle_bool(const char *key, const char *value, void *opaque) +{ + unsigned int i; + + if (opaque == NULL) + return -EINVAL; + + /* A bare key means true; only rte_kvargs_process_opt() allows it. + * An empty value is a blank value, not a missing one, so it is + * rejected below. + */ + if (value == NULL) { + *(bool *)opaque = true; + return 0; + } + + for (i = 0; i < RTE_DIM(kvargs_true); i++) { + if (strcasecmp(value, kvargs_true[i]) == 0) { + *(bool *)opaque = true; + return 0; + } + } + + for (i = 0; i < RTE_DIM(kvargs_false); i++) { + if (strcasecmp(value, kvargs_false[i]) == 0) { + *(bool *)opaque = false; + return 0; + } + } + + KVARGS_LOG(ERR, "invalid value \"%s\" for key \"%s\", expected a boolean", + value, key != NULL ? key : ""); + + return -EINVAL; +}
diff --git a/lib/kvargs/rte_kvargs.h b/lib/kvargs/rte_kvargs.h
index 73fa1e621b..118cf3c79b 100644
--- a/lib/kvargs/rte_kvargs.h
+++ b/lib/kvargs/rte_kvargs.h@@ -21,6 +21,10 @@ * ethernet devices at initialization for arguments parsing. */ +#include <stdint.h> + +#include <rte_compat.h> + #ifdef __cplusplus extern "C" { #endif
@@ -230,6 +234,188 @@ int rte_kvargs_process_opt(const struct rte_kvargs *kvlist, unsigned rte_kvargs_count(const struct rte_kvargs *kvlist, const char *key_match); +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice. + * + * Handlers to convert a key/value pair into a numeric type. + * + * The functions below all match the ``arg_handler_t`` prototype, so they can + * be passed directly to rte_kvargs_process(): + * + * @code + * uint16_t nb_desc = DEFAULT_NB_DESC; + * + * ret = rte_kvargs_process(kvlist, "nb_desc", + * rte_kvargs_handle_u16, &nb_desc); + * @endcode + * + * The value string is accepted only if it represents the whole number, that + * is: + * + * - it is not NULL and not empty; + * - it is decimal, or hexadecimal with a ``0x`` or ``0X`` prefix; + * - it has no trailing characters other than white space; + * - it does not overflow the target type. + * + * A leading ``+`` or ``-`` sign is accepted. The unsigned handlers reject a + * negative value rather than wrapping it around, which is what strtoul() + * would otherwise do. + * + * Note that a leading zero does @b not select octal, so ``010`` is ten and + * not eight. + * + * @param key + * The key, used for error reporting only. May be NULL. + * @param value + * The value to convert. + * @param opaque + * Pointer to the variable to store the result into. The pointed-to type + * must match the handler: for example rte_kvargs_handle_u16() requires a + * ``uint16_t *``. On error the variable is left unmodified. + * + * @return + * - 0 on success. + * - -EINVAL if the value is missing or malformed, or if @p opaque is NULL. + * - -ERANGE if the value does not fit in the target type. + */ +__rte_experimental +int rte_kvargs_handle_u8(const char *key, const char *value, void *opaque); + +/** Convert a value to uint16_t. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_u16(const char *key, const char *value, void *opaque); + +/** Convert a value to uint32_t. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_u32(const char *key, const char *value, void *opaque); + +/** Convert a value to uint64_t. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_u64(const char *key, const char *value, void *opaque); + +/** Convert a value to int8_t. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_i8(const char *key, const char *value, void *opaque); + +/** Convert a value to int16_t. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_i16(const char *key, const char *value, void *opaque); + +/** Convert a value to int32_t. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_i32(const char *key, const char *value, void *opaque); + +/** Convert a value to int64_t. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_i64(const char *key, const char *value, void *opaque); + +/** Convert a value to unsigned int. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_uint(const char *key, const char *value, void *opaque); + +/** Convert a value to int. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_int(const char *key, const char *value, void *opaque); + +/** Convert a value to long. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_long(const char *key, const char *value, void *opaque); + +/** Convert a value to unsigned long. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_ulong(const char *key, const char *value, void *opaque); + +/** Convert a value to size_t. See rte_kvargs_handle_u8(). */ +__rte_experimental +int rte_kvargs_handle_size(const char *key, const char *value, void *opaque); + +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice. + * + * Convert a key/value pair to a boolean. + * + * Accepts, case insensitively, ``1``, ``y``, ``yes``, ``on`` and ``true`` + * for true; ``0``, ``n``, ``no``, ``off`` and ``false`` for false. + * + * A key given without a value, as in ``key``, is treated as true. Use + * rte_kvargs_process_opt() rather than rte_kvargs_process() to support + * that form, since the latter rejects a missing value before the handler + * is called. An empty value, as in ``key=``, is rejected. + * + * @param key + * The key, used for error reporting only. May be NULL. + * @param value + * The value to convert. NULL means true. + * @param opaque + * Pointer to a ``bool`` to store the result into. On error it is left + * unmodified. + * + * @return + * - 0 on success. + * - -EINVAL if the value is malformed or if @p opaque is NULL. + */ +__rte_experimental +int rte_kvargs_handle_bool(const char *key, const char *value, void *opaque); + +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice. + * + * Convert a string to an unsigned integer, checking it against a range. + * + * This is the underlying conversion used by the rte_kvargs_handle_*() + * unsigned handlers. It is meant for drivers which need a range narrower + * than the target type, or which parse a value obtained from + * rte_kvargs_get() rather than from a handler. + * + * @param value + * The string to convert. Must be non-NULL and non-empty. See + * rte_kvargs_handle_u8() for the accepted syntax. + * @param min + * Smallest acceptable value, inclusive. + * @param max + * Largest acceptable value, inclusive. + * @param result + * Where to store the converted value. Left unmodified on error. + * + * @return + * - 0 on success. + * - -EINVAL if the value is missing or malformed, or if @p result is NULL. + * - -ERANGE if the value is outside [@p min, @p max]. + */ +__rte_experimental +int rte_kvargs_to_uint(const char *value, uint64_t min, uint64_t max, + uint64_t *result); + +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice. + * + * Convert a string to a signed integer, checking it against a range. + * + * This is the signed counterpart of rte_kvargs_to_uint(). + * + * @param value + * The string to convert. Must be non-NULL and non-empty. See + * rte_kvargs_handle_u8() for the accepted syntax. + * @param min + * Smallest acceptable value, inclusive. + * @param max + * Largest acceptable value, inclusive. + * @param result + * Where to store the converted value. Left unmodified on error. + * + * @return + * - 0 on success. + * - -EINVAL if the value is missing or malformed, or if @p result is NULL. + * - -ERANGE if the value is outside [@p min, @p max]. + */ +__rte_experimental +int rte_kvargs_to_int(const char *value, int64_t min, int64_t max, + int64_t *result); + #ifdef __cplusplus } #endif
--
2.53.0