The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/* A wrapper around strtol() and strtoul() to correct some
 * "out of bounds" cases that don't work well on at least UTS.
 * If a value is Larger than the max, strto[u]l should return
 * the max value, and set errno to ERANGE
 *  The same if a value is smaller than the min value (only
 * relevant for strtol(); not strtoul()), except the minimum
 * value is returned (and errno == ERANGE).
 */

#include	<ctype.h>
#include	<string.h>
#include	<sys/errno.h>
#include	<stdlib.h>

extern int	errno;

#undef	I32
#undef	U32

#define	I32	int
#define	U32	unsigned int

struct	base_info {
	char	*ValidChars;

	char	*Ulong_max_str;
	char	*Long_max_str;
	char	*Long_min_str;	/* Absolute value */

	int	Ulong_max_str_len;
	int	Long_max_str_len;
	int	Long_min_str_len;	/* Absolute value */

	U32	Ulong_max;
	I32	Long_max;
	I32	Long_min;	/* NOT Absolute value */
};
static struct	base_info Base_info[37];

static struct base_info Base_info_16 = {
	"0123456789abcdefABCDEF",
	"4294967295", "2147483648" /* <== ABS VAL */ , "2147483647",
	10, 10, 10,
	4294967295, 2147483647, - 2147483648,
};

static struct base_info Base_info_10 = {
	"0123456789",
	"4294967295", "2147483648" /* <== ABS VAL */ , "2147483647",
	10, 10, 10,
	4294967295, 2147483647, - 2147483648,
};

 /* Used eventually (if this is fully developed) to hold info
  * for processing bases 2-36.  So that we can just plug the
  * base in as a selector for its info, we sacrifice
  * Base_info[0] and Base_info[1] (unless they are used
  * at some point for special information).
  */

/* This may be replaced later by something more universal */
static void
init_Base_info()
{
	if(Base_info[10].ValidChars) return;
	Base_info[10] = Base_info_10;
	Base_info[16] = Base_info_16;
}

unsigned int
strtoul_wrap32(char *s, char **pEnd, int base)
{
	int	Len;
	int	isNegated = 0;
	char	*sOrig = s;

	init_Base_info();

	while(*s && isspace(*s)) ++s;

	if(*s == '-') {
		++isNegated;
		++s;
		while(*s && isspace(*s)) ++s;
	}
	if(base == 0) {
		if(*s == '0') {
			if(s[1] == 'x' || s[1] == 'X') {
				s += 2;
				base = 16;
			} else {
				++s;
				base = 8;
			}
		} else if(isdigit(*s)) {
			base = 10;
		}
	}
	if(base != 10) {
		return strtoul(sOrig, pEnd, base);
	}
	
	Len = strspn(s, Base_info[base].ValidChars);

	if(Len > Base_info[base].Ulong_max_str_len
		||
	   (Len == Base_info[base].Ulong_max_str_len
	   		&&
	    strncmp(Base_info[base].Ulong_max_str, s, Len) < 0)
	  ) {
		/* In case isNegated is set - what to do?? */
		/* Mightn't we say a negative number is ERANGE for strtoul? */
		errno = ERANGE;
		return Base_info[base].Ulong_max;
	}

	return strtoul(sOrig, pEnd, base);
}

int
strtol_wrap32(char *s, char **pEnd, int base)
{
	int	Len;
	int	isNegated = 0;
	char	*sOrig = s;

	init_Base_info();

	while(*s && isspace(*s)) ++s;

	if(*s == '-') {
		++isNegated;
		++s;
		while(*s && isspace(*s)) ++s;
	}
	if(base == 0) {
		if(*s == '0') {
			if(s[1] == 'x' || s[1] == 'X') {
				s += 2;
				base = 16;
			} else {
				++s;
				base = 8;
			}
		} else if(isdigit(*s)) {
			base = 10;
		}
	}
	if(base != 10) {
		return strtol(sOrig, pEnd, base);
	}
	
	Len = strspn(s, Base_info[base].ValidChars);

	if(Len > Base_info[base].Long_max_str_len
				||
	   (!isNegated && Len == Base_info[base].Long_max_str_len
	   	&&
	    strncmp(Base_info[base].Long_max_str, s, Len) < 0)
	    			||
	   (isNegated && Len == Base_info[base].Long_min_str_len
	   	&&
	    strncmp(Base_info[base].Long_min_str, s, Len) < 0)
	  ) {
		/* In case isNegated is set - what to do?? */
		/* Mightn't we say a negative number is ERANGE for strtol? */
		errno = ERANGE;
		return(isNegated ? Base_info[base].Long_min
					:
				   Base_info[base].Long_min);
	}

	return strtol(sOrig, pEnd, base);
}