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 <[email protected]>
---
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