Several device arguments are documented as a bare hexadecimal mask, with no 0x prefix. Each place the driver open codes strtoull() with base 16, and each gets the validation wrong in a one way or another.
Introduce a new helper to handle these type of args. Add rte_kvargs_handle_hex32() and rte_kvargs_handle_hex64(). A sign is rejected, as it is for the unsigned handlers, so a mask cannot be given as -1. Only the 32 and 64 bit widths are added, since those are the only ones any caller needs. Signed-off-by: Stephen Hemminger <[email protected]> Acked-by: Chengwen Feng <[email protected]> --- app/test/test_kvargs.c | 45 +++++++++ doc/guides/rel_notes/release_26_11.rst | 8 +- lib/kvargs/rte_kvargs.c | 131 ++++++++++++++++++++++--- lib/kvargs/rte_kvargs.h | 44 +++++++++ 4 files changed, 213 insertions(+), 15 deletions(-) diff --git a/app/test/test_kvargs.c b/app/test/test_kvargs.c index f05c7918e9..6e84ebf35c 100644 --- a/app/test/test_kvargs.c +++ b/app/test/test_kvargs.c @@ -475,6 +475,50 @@ test_handle_signed(void) return TEST_SUCCESS; } +static int +test_handle_hex(void) +{ + uint64_t u = 0x5a; + + /* The 0x prefix is optional, and a bare value is still hexadecimal. */ + CHECK_GOOD(rte_kvargs_handle_hex32, uint32_t, "0xff", 0xff); + CHECK_GOOD(rte_kvargs_handle_hex32, uint32_t, "0XFF", 0xff); + CHECK_GOOD(rte_kvargs_handle_hex32, uint32_t, "ff", 0xff); + CHECK_GOOD(rte_kvargs_handle_hex32, uint32_t, "FF", 0xff); + /* "10" is sixteen here, not ten. */ + CHECK_GOOD(rte_kvargs_handle_hex32, uint32_t, "10", 0x10); + CHECK_GOOD(rte_kvargs_handle_hex32, uint32_t, "0", 0); + CHECK_GOOD(rte_kvargs_handle_hex32, uint32_t, "00110F10", 0x00110f10); + CHECK_GOOD(rte_kvargs_handle_hex32, uint32_t, "ffffffff", 0xffffffff); + CHECK_BAD(rte_kvargs_handle_hex32, uint32_t, "100000000"); + CHECK_BAD(rte_kvargs_handle_hex32, uint32_t, ""); + CHECK_BAD(rte_kvargs_handle_hex32, uint32_t, "-1"); + CHECK_BAD(rte_kvargs_handle_hex32, uint32_t, "0x"); + CHECK_BAD(rte_kvargs_handle_hex32, uint32_t, "0x0x10"); + CHECK_BAD(rte_kvargs_handle_hex32, uint32_t, "1g"); + CHECK_BAD(rte_kvargs_handle_hex32, uint32_t, "0x1p"); + + CHECK_GOOD(rte_kvargs_handle_hex64, uint64_t, "0xF", 0xf); + CHECK_GOOD(rte_kvargs_handle_hex64, uint64_t, "F", 0xf); + CHECK_GOOD(rte_kvargs_handle_hex64, uint64_t, "ffffffffffffffff", + UINT64_MAX); + CHECK_BAD(rte_kvargs_handle_hex64, uint64_t, "10000000000000000"); + CHECK_BAD(rte_kvargs_handle_hex64, uint64_t, "-1"); + + /* The underlying conversion, with a mask narrower than the type. */ + TEST_ASSERT_SUCCESS(rte_kvargs_to_hex("fff", 0xfff, &u), "fff in 0..fff"); + TEST_ASSERT_EQUAL(u, 0xfffU, "wrong value"); + TEST_ASSERT_EQUAL(rte_kvargs_to_hex("1000", 0xfff, &u), -ERANGE, + "1000 should be out of 0..fff"); + TEST_ASSERT_EQUAL(u, 0xfffU, "target clobbered on range error"); + TEST_ASSERT_EQUAL(rte_kvargs_to_hex(NULL, 0xfff, &u), -EINVAL, + "NULL should be rejected"); + TEST_ASSERT_EQUAL(rte_kvargs_to_hex("f", 0xfff, NULL), -EINVAL, + "a NULL result should be rejected"); + + return TEST_SUCCESS; +} + static int test_handle_bool(void) { @@ -603,6 +647,7 @@ static struct unit_test_suite kvargs_test_suite = { TEST_CASE(test_invalid_kvargs), TEST_CASE(test_handle_unsigned), TEST_CASE(test_handle_signed), + TEST_CASE(test_handle_hex), TEST_CASE(test_handle_bool), TEST_CASE(test_handle_socket_id), TEST_CASE(test_kvargs_to_range), diff --git a/doc/guides/rel_notes/release_26_11.rst b/doc/guides/rel_notes/release_26_11.rst index 7225e9e787..27b3fe4253 100644 --- a/doc/guides/rel_notes/release_26_11.rst +++ b/doc/guides/rel_notes/release_26_11.rst @@ -156,9 +156,13 @@ New Features * ``rte_kvargs_handle_bool``, accepting ``1``, ``y``, ``yes``, ``on``, ``true`` and their negative counterparts. A bare ``key`` means true; an empty ``key=`` is rejected. + * ``rte_kvargs_handle_hex32`` and ``rte_kvargs_handle_hex64``, for the + arguments documented as a bare hexadecimal mask, where the value is + always read as hexadecimal whether or not it carries a ``0x`` prefix - 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. + Added ``rte_kvargs_to_uint``, ``rte_kvargs_to_int`` and + ``rte_kvargs_to_hex`` 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 6e5e3fcd7c..22bde9616c 100644 --- a/lib/kvargs/rte_kvargs.c +++ b/lib/kvargs/rte_kvargs.c @@ -323,6 +323,35 @@ rte_kvargs_parse_delim(const char *args, const char * const valid_keys[], return kvlist; } +/* + * Skip over a "0x" prefix if there is one. + * + * Returns true if a prefix was consumed, and advances *str past it. Only one + * prefix is ever consumed: a second one is left in place so that the caller + * rejects it, since strtoull() would otherwise strip it itself and read + * "0x0x10" as sixteen rather than as the garbage it is. + */ +static bool +kvargs_skip_hex_prefix(const char **str) +{ + const char *s = *str; + + if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X') && + isxdigit((unsigned char)s[2])) { + *str = s + 2; + return true; + } + + return false; +} + +/* Tell whether a "0x" prefix is present, without consuming it. */ +static bool +kvargs_has_hex_prefix(const char *str) +{ + return str[0] == '0' && (str[1] == 'x' || str[1] == 'X'); +} + /* * Determine the base of a numeric value and skip over its prefix. * @@ -331,24 +360,15 @@ rte_kvargs_parse_delim(const char *args, const char * const valid_keys[], * 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. + * Returns 0 if what follows the prefix is a second one, which is not a number. */ 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; - } + if (!kvargs_skip_hex_prefix(str)) + return 10; - return 10; + return kvargs_has_hex_prefix(*str) ? 0 : 16; } /* Skip trailing white space, and tell whether anything else is left. */ @@ -487,6 +507,46 @@ rte_kvargs_to_int(const char *value, int64_t min, int64_t max, int64_t *result) return 0; } +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_to_hex, 26.11) +int +rte_kvargs_to_hex(const char *value, uint64_t max, uint64_t *result) +{ + const char *str = value; + unsigned long long val; + char *endptr; + + if (str == NULL || result == NULL) + return -EINVAL; + + /* A mask has no sign; "-1" must not wrap around to UINT64_MAX. */ + if (kvargs_get_sign(&str)) + return -EINVAL; + + /* The 0x prefix is optional here, but still accepted. */ + if (kvargs_skip_hex_prefix(&str) && kvargs_has_hex_prefix(str)) + return -EINVAL; /* doubled 0x prefix */ + + if (!isxdigit((unsigned char)*str)) + return -EINVAL; + + errno = 0; + val = strtoull(str, &endptr, 16); + if (endptr == str) + return -EINVAL; + if (errno == ERANGE) + return -ERANGE; + if (errno != 0) + return -EINVAL; + if (!kvargs_at_end(endptr)) + return -EINVAL; /* trailing garbage */ + + if (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 @@ -710,6 +770,51 @@ rte_kvargs_handle_ulong(const char *key, const char *value, void *opaque) return ret; } +static int +kvargs_store_hex(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_hex(value, max, val); + if (ret < 0) + KVARGS_LOG(ERR, "invalid value \"%s\" for key \"%s\", expected 0..%" PRIx64 " in hex", + value != NULL ? value : "", key != NULL ? key : "", max); + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_hex32, 26.11) +int +rte_kvargs_handle_hex32(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_hex(key, value, opaque, UINT32_MAX, &val); + if (ret == 0) + *(uint32_t *)opaque = (uint32_t)val; + + return ret; +} + +RTE_EXPORT_EXPERIMENTAL_SYMBOL(rte_kvargs_handle_hex64, 26.11) +int +rte_kvargs_handle_hex64(const char *key, const char *value, void *opaque) +{ + uint64_t val; + int ret; + + ret = kvargs_store_hex(key, value, opaque, UINT64_MAX, &val); + if (ret == 0) + *(uint64_t *)opaque = 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" }; diff --git a/lib/kvargs/rte_kvargs.h b/lib/kvargs/rte_kvargs.h index acc15607bc..fe18841709 100644 --- a/lib/kvargs/rte_kvargs.h +++ b/lib/kvargs/rte_kvargs.h @@ -330,6 +330,24 @@ int rte_kvargs_handle_ulong(const char *key, const char *value, void *opaque); __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 bit mask to uint32_t. + * + * As rte_kvargs_handle_u32(), except that the value is always read as + * hexadecimal, with or without a ``0x`` prefix, so ``10`` is sixteen. This + * is for arguments documented as a bare hexadecimal mask; use + * rte_kvargs_handle_u32() for a count or a size. + */ +__rte_experimental +int rte_kvargs_handle_hex32(const char *key, const char *value, void *opaque); + +/** Convert a hexadecimal value to uint64_t. See rte_kvargs_handle_hex32(). */ +__rte_experimental +int rte_kvargs_handle_hex64(const char *key, const char *value, void *opaque); + /** * @warning * @b EXPERIMENTAL: this API may change without prior notice. @@ -443,6 +461,32 @@ __rte_experimental int rte_kvargs_to_int(const char *value, int64_t min, int64_t max, int64_t *result); +/** + * @warning + * @b EXPERIMENTAL: this API may change without prior notice. + * + * Convert a hexadecimal string to an unsigned integer, checking it against + * a maximum. + * + * This is the conversion underlying rte_kvargs_handle_hex32(), and is the + * hexadecimal counterpart of rte_kvargs_to_uint(). The minimum is always + * zero, since a negative value is rejected rather than wrapped around. + * + * @param value + * The string to convert. Must be non-NULL and non-empty. + * @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 greater than @p max. + */ +__rte_experimental +int rte_kvargs_to_hex(const char *value, uint64_t max, uint64_t *result); + #ifdef __cplusplus } #endif -- 2.53.0

