tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

Re: Reuse strtonum(3) and reallocarray(3) from OpenBSD



In article <20141129135653.GA1626%britannica.bec.de@localhost>,
Joerg Sonnenberger  <joerg%britannica.bec.de@localhost> wrote:
>On Sat, Nov 29, 2014 at 04:18:24AM +0100, Kamil Rytarowski wrote:
>> I've prepared a small patch adding strtonum(3)
>
>Have you bothered to search the list archive why strtonum(3) was
>rejected the first time? I don't think any of the problems has been
>addressed by OpenBSD. Just because people want to reinvent strfry(3)
>doesn't mean it should be used.

With the helpful feedback from rmind, joerg, etc. I've written a function
which I call strtoi() (and strtou()) which tries to fix the API issues with
strtonum() and provides strtonum() as a wrapper:

#include <stdio.h>
#include <errno.h>
#include <inttypes.h>

/* 
 * in inttypes.h:
 */
intmax_t strtoi(const char * __restrict, char ** __restrict, int,
    intmax_t, intmax_t, int *);
uintmax_t strtou(const char * __restrict, char ** __restrict, int,
    uintmax_t, uintmax_t, int *);

#ifdef _OPENBSD_SOURCE
long long strtonum(const char *, long long, long long, const char **)
#endif

/*
 * in libc.
 */

/*
 * Problems with the strtonum(3) API:
 *   - will return 0 on failure; 0 might not be in range, so
 *     that necessitates an error check even if you want to avoid it.
 *   - does not differentiate 'illegal' returns, so we can't tell
 *     the difference between partial and no conversions.
 *   - returns english strings
 *   - can't set the base, or find where the conversion ended
 */
long long
strtonum(const char *ptr, long long lo, long long hi, const char **res)
{
	int e;
	intmax_t rv;
	const char *resp;

	if (res == NULL)
		res = &resp;

	rv = strtoi(ptr, NULL, 0, lo, hi, &e);
	if (e == 0) {
		*res = NULL;
		return rv;
	}
	*res = e != ERANGE ? "invalid" : (rv == hi ? "too large" : "too small");
	return 0;
}

/*
 * - Always returns numbers in range. Errors are indicated in *rerror:
 *	- 0       -> OK
 *	- ENOTSUP -> the string conversion was incomplere
 *	- EINVAL  -> the base was not supported
 *	- ERANGE  -> the value returned from the conversion was out of range
 *		     and was corrected to be in range.
 *
 * - Does not need to reset or deal with errno. Simple error checking:
 *	int e;
 *	type num = (type)strtol(buf, NULL, 0, lo, hi, &e)
 *	if (e)
 *		warnx("Bad number `%s' (%s)", buf, strerror(e));
 *   Or more precise:
 *	switch (e) {
 *	case 0:
 *		break;
 *	case ENOTSUP:
 *		warnx("Bad number `%s'", buf);
 *		break;
 *	case ERANGE:
 *		warnx("Too %s number `%s'", buf, num == hi ? "big" : "small");
 *		break;
 *	default:
 *		abort();	// EINVAL can't happen here 
 *	}
 *
 * - Ignore all information: strtoi("foo", NULL, 0, 10, 20, NULL), but return
 *	"reasonable" result even on error: This returns 10, since "foo" could
 *	not be converted, and the default unconverted result from strtoimax(3)
 *	is 0.
 *
 * - Doesn't suffer from i17n
 *
 * - Same basic functionality as other strto*() functions (includes eptr, base)
 */
intmax_t
strtoi(const char * __restrict ptr, char ** __restrict eptr, int base,
    intmax_t lo, intmax_t hi, int *rerror)
{
	int serrno;
	intmax_t im;
	char *ep;
	int rep;

	if (*eptr == NULL)
		eptr = &ep;

	if (rerror == NULL)
		rerror = &rep;

	serrno = errno;
	errno = 0;
	im = strtoimax(ptr, eptr, base);
	*rerror = errno;
	errno = serrno;

	if (*rerror == 0 && (ptr == *eptr || *eptr))
		*rerror = ENOTSUP;

	if (im < lo) {
		if (*rerror == 0)
			*rerror = ERANGE;
		return lo;
	}
	if (im > hi) {
		if (*rerror == 0)
			*rerror = ERANGE;
		return hi;
	}

	return im;
}




Home | Main Index | Thread Index | Old Index