Commit Diff


commit - e445acd17274f019c87c81b8feea8ca1e8b32c8e
commit + 0677399ecf8b2f20d87cf8bb4a29bc9574545917
blob - 1b2d5f59da6911eb007cd5a1fae0d58fc2fce889
blob + af7750a2238f1de720ab98665c30b017ac37c489
--- compat/fmt_scaled.c
+++ compat/fmt_scaled.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: fmt_scaled.c,v 1.19 2020/10/12 22:08:34 deraadt Exp $	*/
+/*	$OpenBSD: fmt_scaled.c,v 1.22 2022/03/11 09:04:59 dtucker Exp $	*/
 
 /*
  * Copyright (c) 2001, 2002, 2003 Ian F. Darwin.  All rights reserved.
@@ -65,6 +65,149 @@ static const long long scale_factors[] = {
 
 #define MAX_DIGITS (SCALE_LENGTH * 3)	/* XXX strlen(sprintf("%lld", -1)? */
 
+/* Convert the given input string "scaled" into numeric in "result".
+ * Return 0 on success, -1 and errno set on error.
+ */
+int
+scan_scaled(char *scaled, long long *result)
+{
+	char *p = scaled;
+	int sign = 0;
+	unsigned int i, ndigits = 0, fract_digits = 0;
+	long long scale_fact = 1, whole = 0, fpart = 0;
+
+	/* Skip leading whitespace */
+	while (isascii((unsigned char)*p) && isspace((unsigned char)*p))
+		++p;
+
+	/* Then at most one leading + or - */
+	while (*p == '-' || *p == '+') {
+		if (*p == '-') {
+			if (sign) {
+				errno = EINVAL;
+				return -1;
+			}
+			sign = -1;
+			++p;
+		} else if (*p == '+') {
+			if (sign) {
+				errno = EINVAL;
+				return -1;
+			}
+			sign = +1;
+			++p;
+		}
+	}
+
+	/* Main loop: Scan digits, find decimal point, if present.
+	 * We don't allow exponentials, so no scientific notation
+	 * (but note that E for Exa might look like e to some!).
+	 * Advance 'p' to end, to get scale factor.
+	 */
+	for (; isascii((unsigned char)*p) &&
+	    (isdigit((unsigned char)*p) || *p=='.'); ++p) {
+		if (*p == '.') {
+			if (fract_digits > 0) {	/* oops, more than one '.' */
+				errno = EINVAL;
+				return -1;
+			}
+			fract_digits = 1;
+			continue;
+		}
+
+		i = (*p) - '0';			/* whew! finally a digit we can use */
+		if (fract_digits > 0) {
+			if (fract_digits >= MAX_DIGITS-1)
+				/* ignore extra fractional digits */
+				continue;
+			fract_digits++;		/* for later scaling */
+			if (fpart > LLONG_MAX / 10) {
+				errno = ERANGE;
+				return -1;
+			}
+			fpart *= 10;
+			if (i > LLONG_MAX - fpart) {
+				errno = ERANGE;
+				return -1;
+			}
+			fpart += i;
+		} else {				/* normal digit */
+			if (++ndigits >= MAX_DIGITS) {
+				errno = ERANGE;
+				return -1;
+			}
+			if (whole > LLONG_MAX / 10) {
+				errno = ERANGE;
+				return -1;
+			}
+			whole *= 10;
+			if (i > LLONG_MAX - whole) {
+				errno = ERANGE;
+				return -1;
+			}
+			whole += i;
+		}
+	}
+
+	if (sign)
+		whole *= sign;
+
+	/* If no scale factor given, we're done. fraction is discarded. */
+	if (!*p) {
+		*result = whole;
+		return 0;
+	}
+
+	/* Validate scale factor, and scale whole and fraction by it. */
+	for (i = 0; i < SCALE_LENGTH; i++) {
+
+		/* Are we there yet? */
+		if (*p == scale_chars[i] ||
+			*p == tolower((unsigned char)scale_chars[i])) {
+
+			/* If it ends with alphanumerics after the scale char, bad. */
+			if (isalnum((unsigned char)*(p+1))) {
+				errno = EINVAL;
+				return -1;
+			}
+			scale_fact = scale_factors[i];
+
+			/* check for overflow and underflow after scaling */
+			if (whole > LLONG_MAX / scale_fact ||
+			    whole < LLONG_MIN / scale_fact) {
+				errno = ERANGE;
+				return -1;
+			}
+
+			/* scale whole part */
+			whole *= scale_fact;
+
+			/* truncate fpart so it does't overflow.
+			 * then scale fractional part.
+			 */
+			while (fpart >= LLONG_MAX / scale_fact) {
+				fpart /= 10;
+				fract_digits--;
+			}
+			fpart *= scale_fact;
+			if (fract_digits > 0) {
+				for (i = 0; i < fract_digits -1; i++)
+					fpart /= 10;
+			}
+			if (sign == -1)
+				whole -= fpart;
+			else
+				whole += fpart;
+			*result = whole;
+			return 0;
+		}
+	}
+
+	/* Invalid unit or character */
+	errno = EINVAL;
+	return -1;
+}
+
 /* Format the given "number" into human-readable form in "result".
  * Result must point to an allocated buffer of length FMT_SCALED_STRSIZE.
  * Return 0 on success, -1 and errno set if error.