Thread (65 messages) flat view 65 messages, 3 authors, 1d ago
WARM1d

[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
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help