Questionable behavior of strtoi(3bsd) / strtou(3bsd)

Alejandro Colomar alx at kernel.org
Sun Jan 7 02:44:23 UTC 2024


Hi Guillem,

While implementing my own strtoi/u() for shadow, and testing it against
strtoi/u(3bsd), I found that the behavior differed slightly for one
specific case: when both ERANGE and ENOTSUP happen at the same time, my
implementation reports ERANGE, while strtoi/u(3bsd) reports ENOTSUP.

	strtou("5z", NULL, 0, 0, 4, &status);

I don't know what's the behavior of the original NetBSD implementation,
as I couldn't find the file where it's implemented.  If you can give a
pointer to where it's implemented in NetBSD, it would help.  Maybe you
could also CC some NetBSD maintainers or list to get their opinion.

I guess you just copied the implementation from NetBSD code, so it
probably behaves like NetBSD.  I think that design is wrong, and would
like to justify:

If a value is out-of-range, that's a more problematic error than just
having trailing text.  Trailing text is often expected, and is not
treated as an error, but out-of-range is often a hard error.  Silencing
that error is problematic, and there's no way to work around it.  If
instead strtoi(3bsd) reported ERANGE when both ERANGE + ENOTSUP happen,
it would be trivial for the caller to check it there's trailing text via
'endptr'.

Here's my implementation of strtoi(), which prefers ERANGE to ENOTSUP:


#define strtoN(str, endptr, base, min, max, status, TYPE)                     \
({                                                                            \
	const char  *str_ = str;                                              \
	char        **endptr_ = endptr;                                       \
	int         base_ = base;                                             \
	TYPE        min_ = min;                                               \
	TYPE        max_ = max;                                               \
	int         *status_ = status;                                        \
                                                                              \
	int         errno_saved_, s_;                                         \
	char        *ep_;                                                     \
	TYPE        n_;                                                       \
                                                                              \
	errno_saved_ = errno;                                                 \
                                                                              \
	if (endptr_ == NULL)                                                  \
		endptr_ = &ep_;                                               \
	if (status_ == NULL)                                                  \
		status_ = &s_;                                                \
                                                                              \
	errno = 0;                                                            \
	strtol("0", NULL, base_);                                             \
	if (errno == EINVAL) {                                                \
		*status_ = EINVAL;                                            \
		n_ = 0;                                                       \
                                                                              \
	} else {                                                              \
		n_ = _Generic((TYPE) 0,                                       \
		              intmax_t:  strtoimax(str_, endptr_, base_),     \
		              uintmax_t: strtoumax(str_, endptr_, base_));    \
                                                                              \
		if (*endptr_ == str_)                                         \
			*status_ = ECANCELED;                                 \
		else if (errno == ERANGE)                                     \
			*status_ = ERANGE;                                    \
		else if (n_ < min_ || n_ > max_)                              \
			*status_ = ERANGE;                                    \
		else if (**endptr_ != '\0')                                   \
			*status_ = ENOTSUP;                                   \
		else                                                          \
			*status_ = 0;                                         \
	}                                                                     \
                                                                              \
	errno = errno_saved_;                                                 \
	SATURATE(min_, max_, n_);                                             \
})

#define SATURATE(min, max, n)  MAX(min, MIN(max, n))

intmax_t
shadow_strtoi(const char *str, char **restrict endptr, int base,
    intmax_t min, intmax_t max, int *restrict status)
{
	return strtoN(str, endptr, base, min, max, status, intmax_t);
}

uintmax_t
shadow_strtou(const char *str, char **restrict endptr, int base,
    uintmax_t min, uintmax_t max, int *restrict status)
{
	return strtoN(str, endptr, base, min, max, status, uintmax_t);
}


Have a lovely day,
Alex

-- 
<https://www.alejandro-colomar.es/>
Looking for a remote C programming job at the moment.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 833 bytes
Desc: not available
URL: <https://lists.freedesktop.org/archives/libbsd/attachments/20240107/0422d9cd/attachment.sig>


More information about the libbsd mailing list