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