The branch main has been updated by des:

URL: 
https://cgit.FreeBSD.org/src/commit/?id=ab1c6874e5004a8ac4e6b077a4aee307e22628b1

commit ab1c6874e5004a8ac4e6b077a4aee307e22628b1
Author:     Dag-Erling Smørgrav <d...@freebsd.org>
AuthorDate: 2025-08-06 20:34:13 +0000
Commit:     Dag-Erling Smørgrav <d...@freebsd.org>
CommitDate: 2025-08-06 20:43:13 +0000

    libutil: Backward compatibility for expand_number()
    
    Reimplement expand_number() in terms of expand_unsigned(), which takes
    a pointer to uint64_t like expand_number() did before.  Provide a macro
    that picks the correct version based on the type of the argument.
    
    Fixes:          2e0caa7c7e14
    Reviewed by:    jhb
    Differential Revision:  https://reviews.freebsd.org/D51723
---
 lib/libutil/Symbol.map                 |  1 +
 lib/libutil/expand_number.3            | 58 +++++++++++++++++++++++---
 lib/libutil/expand_number.c            | 49 +++++++++++++++++++---
 lib/libutil/libutil.h                  |  8 ++++
 lib/libutil/tests/expand_number_test.c | 75 ++++++++++++++++++++++++++++++++++
 5 files changed, 181 insertions(+), 10 deletions(-)

diff --git a/lib/libutil/Symbol.map b/lib/libutil/Symbol.map
index 8c8fff451cd1..6b8a1ec099bf 100644
--- a/lib/libutil/Symbol.map
+++ b/lib/libutil/Symbol.map
@@ -13,6 +13,7 @@ FBSD_1.8 {
        cpuset_parselist;
        domainset_parselist;
        expand_number;
+       expand_unsigned;
        flopen;
        flopenat;
        forkpty;
diff --git a/lib/libutil/expand_number.3 b/lib/libutil/expand_number.3
index 1b932400de69..b1833cedf406 100644
--- a/lib/libutil/expand_number.3
+++ b/lib/libutil/expand_number.3
@@ -24,11 +24,12 @@
 .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 .\" SUCH DAMAGE.
 .\"
-.Dd July 25, 2025
+.Dd August 6, 2025
 .Dt EXPAND_NUMBER 3
 .Os
 .Sh NAME
-.Nm expand_number
+.Nm expand_number ,
+.Nm expand_unsigned
 .Nd parse a number from human readable form
 .Sh LIBRARY
 .Lb libutil
@@ -38,6 +39,10 @@
 .Fo expand_number
 .Fa "const char *buf" "int64_t *num"
 .Fc
+.Ft int
+.Fo expand_unsigned
+.Fa "const char *buf" "uint64_t *num"
+.Fc
 .Sh DESCRIPTION
 The
 .Fn expand_number
@@ -48,6 +53,17 @@ quantity in the location pointed to by its
 .Fa *num
 argument.
 .Pp
+The
+.Fn expand_unsigned
+function is similar to
+.Fn expand_number ,
+but accepts only positive numbers in the range
+.Bq 0, Ns Dv UINT64_MAX .
+.Pp
+Both functions interpret the input
+.Dq -0
+as 0.
+.Pp
 The input string must consist of a decimal number, optionally preceded
 by a
 .Sq +
@@ -81,20 +97,38 @@ is interpreted as 5, and
 .Dq 5kb
 is interpreted as 5,120).
 However, the usage of this suffix is discouraged.
+.Pp
+For backward compatibility reasons, if the compiler supports generic
+selection, a macro is provided which automatically replaces calls to
+.Fn expand_number
+with calls to
+.Fn expand_unsigned
+if the type of the actual
+.Va num
+argument is compatible with
+.Vt uint64_t * .
 .Sh RETURN VALUES
 .Rv -std
 .Sh ERRORS
 The
 .Fn expand_number
-function will fail if:
+and
+.Fn expand_unsigned
+functions will fail if:
 .Bl -tag -width Er
 .It Bq Er EINVAL
 The given string does not contain a valid number.
 .It Bq Er EINVAL
 An unrecognized suffix was encountered.
 .It Bq Er ERANGE
-The given string represents a number which does not fit into a
-.Vt int64_t .
+The given string represents a number which does not fit into an
+.Vt int64_t
+(for
+.Fn expand_number )
+or
+.Vt uint64_t
+(for
+.Fn expand_unsigned ) .
 .El
 .Sh SEE ALSO
 .Xr humanize_number 3
@@ -103,3 +137,17 @@ The
 .Fn expand_number
 function first appeared in
 .Fx 6.3 .
+The original implementation did not handle negative numbers correctly,
+and it was switched to taking a
+.Vt uint64_t *
+and accepting only positive numbers in
+.Fx 9.0 .
+The
+.Fn expand_unsigned
+function was added,
+and
+.Fn expand_number
+switched back to
+.Vt int64_t * ,
+in
+.Fx 15.0 .
diff --git a/lib/libutil/expand_number.c b/lib/libutil/expand_number.c
index f4c19d7867a3..a3313ba39d98 100644
--- a/lib/libutil/expand_number.c
+++ b/lib/libutil/expand_number.c
@@ -37,13 +37,12 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-int
-expand_number(const char *buf, int64_t *num)
+static int
+expand_impl(const char *buf, uint64_t *num, bool *neg)
 {
        char *endptr;
        uintmax_t number;
        unsigned int shift;
-       bool neg;
        int serrno;
 
        /*
@@ -52,10 +51,10 @@ expand_number(const char *buf, int64_t *num)
        while (isspace((unsigned char)*buf))
                buf++;
        if (*buf == '-') {
-               neg = true;
+               *neg = true;
                buf++;
        } else {
-               neg = false;
+               *neg = false;
                if (*buf == '+')
                        buf++;
        }
@@ -127,6 +126,22 @@ expand_number(const char *buf, int64_t *num)
        }
        number <<= shift;
 
+       *num = number;
+       return (0);
+}
+
+int
+(expand_number)(const char *buf, int64_t *num)
+{
+       uint64_t number;
+       bool neg;
+
+       /*
+        * Parse the number.
+        */
+       if (expand_impl(buf, &number, &neg) != 0)
+               return (-1);
+
        /*
         * Apply the sign and check for overflow.
         */
@@ -146,3 +161,27 @@ expand_number(const char *buf, int64_t *num)
 
        return (0);
 }
+
+int
+expand_unsigned(const char *buf, uint64_t *num)
+{
+       uint64_t number;
+       bool neg;
+
+       /*
+        * Parse the number.
+        */
+       if (expand_impl(buf, &number, &neg) != 0)
+               return (-1);
+
+       /*
+        * Negative numbers are out of range.
+        */
+       if (neg && number > 0) {
+               errno = ERANGE;
+               return (-1);
+       }
+
+       *num = number;
+       return (0);
+}
diff --git a/lib/libutil/libutil.h b/lib/libutil/libutil.h
index d27262e44daf..9b5b2abe7f09 100644
--- a/lib/libutil/libutil.h
+++ b/lib/libutil/libutil.h
@@ -89,6 +89,14 @@ __BEGIN_DECLS
 void   clean_environment(const char * const *_white,
            const char * const *_more_white);
 int    expand_number(const char *_buf, int64_t *_num);
+int    expand_unsigned(const char *_buf, uint64_t *_num);
+#if (defined(__STDC_VERSION__) && __STDC_VERSION__ >= 201112L) ||      \
+       __has_extension(c_generic_selections)
+#define expand_number(_buf, _num)                                      \
+       _Generic((_num),                                                \
+                uint64_t *: expand_unsigned,                           \
+                default: expand_number)((_buf), (_num))
+#endif
 int    extattr_namespace_to_string(int _attrnamespace, char **_string);
 int    extattr_string_to_namespace(const char *_string, int *_attrnamespace);
 int    flopen(const char *_path, int _flags, ...);
diff --git a/lib/libutil/tests/expand_number_test.c 
b/lib/libutil/tests/expand_number_test.c
index 8e7458994de4..8ff56e1ed01f 100644
--- a/lib/libutil/tests/expand_number_test.c
+++ b/lib/libutil/tests/expand_number_test.c
@@ -206,10 +206,85 @@ ATF_TC_BODY(expand_number__bad, tp)
        require_error(" + 1", EINVAL);
 }
 
+ATF_TC_WITHOUT_HEAD(expand_unsigned);
+ATF_TC_BODY(expand_unsigned, tp)
+{
+       static struct tc {
+               const char *str;
+               uint64_t num;
+               int error;
+       } tcs[] = {
+               { "0", 0, 0 },
+               { "+0", 0, 0 },
+               { "-0", 0, 0 },
+               { "1", 1, 0 },
+               { "+1", 1, 0 },
+               { "-1", 0, ERANGE },
+               { "18446744073709551615", UINT64_MAX, 0 },
+               { "+18446744073709551615", UINT64_MAX, 0 },
+               { "-18446744073709551615", 0, ERANGE },
+               { 0 },
+       };
+       struct tc *tc;
+       uint64_t num;
+       int error, ret;
+
+       for (tc = tcs; tc->str != NULL; tc++) {
+               ret = expand_number(tc->str, &num);
+               error = errno;
+               if (tc->error == 0) {
+                       ATF_REQUIRE_EQ_MSG(0, ret,
+                           "%s ret = %d", tc->str, ret);
+                       ATF_REQUIRE_EQ_MSG(tc->num, num,
+                           "%s num = %ju", tc->str, (uintmax_t)num);
+               } else {
+                       ATF_REQUIRE_EQ_MSG(-1, ret,
+                           "%s ret = %d", tc->str, ret);
+                       ATF_REQUIRE_EQ_MSG(tc->error, error,
+                           "%s errno = %d", tc->str, error);
+               }
+       }
+}
+
+ATF_TC_WITHOUT_HEAD(expand_generic);
+ATF_TC_BODY(expand_generic, tp)
+{
+       uint64_t uint64;
+       int64_t int64;
+       size_t size;
+       off_t off;
+
+       ATF_REQUIRE_EQ(0, expand_number("18446744073709551615", &uint64));
+       ATF_REQUIRE_EQ(UINT64_MAX, uint64);
+       ATF_REQUIRE_EQ(-1, expand_number("-1", &uint64));
+       ATF_REQUIRE_EQ(ERANGE, errno);
+
+       ATF_REQUIRE_EQ(0, expand_number("9223372036854775807", &int64));
+       ATF_REQUIRE_EQ(INT64_MAX, int64);
+       ATF_REQUIRE_EQ(-1, expand_number("9223372036854775808", &int64));
+       ATF_REQUIRE_EQ(ERANGE, errno);
+       ATF_REQUIRE_EQ(0, expand_number("-9223372036854775808", &int64));
+       ATF_REQUIRE_EQ(INT64_MIN, int64);
+
+       ATF_REQUIRE_EQ(0, expand_number("18446744073709551615", &size));
+       ATF_REQUIRE_EQ(UINT64_MAX, size);
+       ATF_REQUIRE_EQ(-1, expand_number("-1", &size));
+       ATF_REQUIRE_EQ(ERANGE, errno);
+
+       ATF_REQUIRE_EQ(0, expand_number("9223372036854775807", &off));
+       ATF_REQUIRE_EQ(INT64_MAX, off);
+       ATF_REQUIRE_EQ(-1, expand_number("9223372036854775808", &off));
+       ATF_REQUIRE_EQ(ERANGE, errno);
+       ATF_REQUIRE_EQ(0, expand_number("-9223372036854775808", &off));
+       ATF_REQUIRE_EQ(INT64_MIN, off);
+}
+
 ATF_TP_ADD_TCS(tp)
 {
        ATF_TP_ADD_TC(tp, expand_number__ok);
        ATF_TP_ADD_TC(tp, expand_number__bad);
+       ATF_TP_ADD_TC(tp, expand_unsigned);
+       ATF_TP_ADD_TC(tp, expand_generic);
 
        return (atf_no_error());
 }

Reply via email to