1*32e4e94eSDamien Miller /*	$OpenBSD: fmt_scaled.c,v 1.17 2018/05/14 04:39:04 djm Exp $	*/
2a7058ec7SDamien Miller 
3a7058ec7SDamien Miller /*
4a7058ec7SDamien Miller  * Copyright (c) 2001, 2002, 2003 Ian F. Darwin.  All rights reserved.
5a7058ec7SDamien Miller  *
6a7058ec7SDamien Miller  * Redistribution and use in source and binary forms, with or without
7a7058ec7SDamien Miller  * modification, are permitted provided that the following conditions
8a7058ec7SDamien Miller  * are met:
9a7058ec7SDamien Miller  * 1. Redistributions of source code must retain the above copyright
10a7058ec7SDamien Miller  *    notice, this list of conditions and the following disclaimer.
11a7058ec7SDamien Miller  * 2. Redistributions in binary form must reproduce the above copyright
12a7058ec7SDamien Miller  *    notice, this list of conditions and the following disclaimer in the
13a7058ec7SDamien Miller  *    documentation and/or other materials provided with the distribution.
14a7058ec7SDamien Miller  * 3. The name of the author may not be used to endorse or promote products
15a7058ec7SDamien Miller  *    derived from this software without specific prior written permission.
16a7058ec7SDamien Miller  *
17a7058ec7SDamien Miller  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18a7058ec7SDamien Miller  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19a7058ec7SDamien Miller  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20a7058ec7SDamien Miller  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21a7058ec7SDamien Miller  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22a7058ec7SDamien Miller  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23a7058ec7SDamien Miller  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24a7058ec7SDamien Miller  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25a7058ec7SDamien Miller  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26a7058ec7SDamien Miller  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27a7058ec7SDamien Miller  */
28a7058ec7SDamien Miller 
29a7058ec7SDamien Miller /* OPENBSD ORIGINAL: lib/libutil/fmt_scaled.c */
30a7058ec7SDamien Miller 
31a7058ec7SDamien Miller /*
32a7058ec7SDamien Miller  * fmt_scaled: Format numbers scaled for human comprehension
33a7058ec7SDamien Miller  * scan_scaled: Scan numbers in this format.
34a7058ec7SDamien Miller  *
35a7058ec7SDamien Miller  * "Human-readable" output uses 4 digits max, and puts a unit suffix at
36a7058ec7SDamien Miller  * the end.  Makes output compact and easy-to-read esp. on huge disks.
37a7058ec7SDamien Miller  * Formatting code was originally in OpenBSD "df", converted to library routine.
38a7058ec7SDamien Miller  * Scanning code written for OpenBSD libutil.
39a7058ec7SDamien Miller  */
40a7058ec7SDamien Miller 
41a7058ec7SDamien Miller #include "includes.h"
42a7058ec7SDamien Miller 
43a7058ec7SDamien Miller #ifndef HAVE_FMT_SCALED
44a7058ec7SDamien Miller 
45a7058ec7SDamien Miller #include <stdio.h>
46a7058ec7SDamien Miller #include <stdlib.h>
47a7058ec7SDamien Miller #include <errno.h>
48a7058ec7SDamien Miller #include <string.h>
49a7058ec7SDamien Miller #include <ctype.h>
50a7058ec7SDamien Miller #include <limits.h>
51a7058ec7SDamien Miller 
52a7058ec7SDamien Miller typedef enum {
53a7058ec7SDamien Miller 	NONE = 0, KILO = 1, MEGA = 2, GIGA = 3, TERA = 4, PETA = 5, EXA = 6
54a7058ec7SDamien Miller } unit_type;
55a7058ec7SDamien Miller 
56a7058ec7SDamien Miller /* These three arrays MUST be in sync!  XXX make a struct */
57a7058ec7SDamien Miller static unit_type units[] = { NONE, KILO, MEGA, GIGA, TERA, PETA, EXA };
58a7058ec7SDamien Miller static char scale_chars[] = "BKMGTPE";
59a7058ec7SDamien Miller static long long scale_factors[] = {
60a7058ec7SDamien Miller 	1LL,
61a7058ec7SDamien Miller 	1024LL,
62a7058ec7SDamien Miller 	1024LL*1024,
63a7058ec7SDamien Miller 	1024LL*1024*1024,
64a7058ec7SDamien Miller 	1024LL*1024*1024*1024,
65a7058ec7SDamien Miller 	1024LL*1024*1024*1024*1024,
66a7058ec7SDamien Miller 	1024LL*1024*1024*1024*1024*1024,
67a7058ec7SDamien Miller };
68a7058ec7SDamien Miller #define	SCALE_LENGTH (sizeof(units)/sizeof(units[0]))
69a7058ec7SDamien Miller 
70a7058ec7SDamien Miller #define MAX_DIGITS (SCALE_LENGTH * 3)	/* XXX strlen(sprintf("%lld", -1)? */
71a7058ec7SDamien Miller 
72d94c1dfeSDamien Miller /* Convert the given input string "scaled" into numeric in "result".
73a7058ec7SDamien Miller  * Return 0 on success, -1 and errno set on error.
74a7058ec7SDamien Miller  */
75a7058ec7SDamien Miller int
scan_scaled(char * scaled,long long * result)76a7058ec7SDamien Miller scan_scaled(char *scaled, long long *result)
77a7058ec7SDamien Miller {
78a7058ec7SDamien Miller 	char *p = scaled;
79a7058ec7SDamien Miller 	int sign = 0;
80a7058ec7SDamien Miller 	unsigned int i, ndigits = 0, fract_digits = 0;
81a7058ec7SDamien Miller 	long long scale_fact = 1, whole = 0, fpart = 0;
82a7058ec7SDamien Miller 
83a7058ec7SDamien Miller 	/* Skip leading whitespace */
84d94c1dfeSDamien Miller 	while (isascii((unsigned char)*p) && isspace((unsigned char)*p))
85a7058ec7SDamien Miller 		++p;
86a7058ec7SDamien Miller 
87a7058ec7SDamien Miller 	/* Then at most one leading + or - */
88a7058ec7SDamien Miller 	while (*p == '-' || *p == '+') {
89a7058ec7SDamien Miller 		if (*p == '-') {
90a7058ec7SDamien Miller 			if (sign) {
91a7058ec7SDamien Miller 				errno = EINVAL;
92a7058ec7SDamien Miller 				return -1;
93a7058ec7SDamien Miller 			}
94a7058ec7SDamien Miller 			sign = -1;
95a7058ec7SDamien Miller 			++p;
96a7058ec7SDamien Miller 		} else if (*p == '+') {
97a7058ec7SDamien Miller 			if (sign) {
98a7058ec7SDamien Miller 				errno = EINVAL;
99a7058ec7SDamien Miller 				return -1;
100a7058ec7SDamien Miller 			}
101a7058ec7SDamien Miller 			sign = +1;
102a7058ec7SDamien Miller 			++p;
103a7058ec7SDamien Miller 		}
104a7058ec7SDamien Miller 	}
105a7058ec7SDamien Miller 
106a7058ec7SDamien Miller 	/* Main loop: Scan digits, find decimal point, if present.
107a7058ec7SDamien Miller 	 * We don't allow exponentials, so no scientific notation
108a7058ec7SDamien Miller 	 * (but note that E for Exa might look like e to some!).
109a7058ec7SDamien Miller 	 * Advance 'p' to end, to get scale factor.
110a7058ec7SDamien Miller 	 */
111d94c1dfeSDamien Miller 	for (; isascii((unsigned char)*p) &&
112d94c1dfeSDamien Miller 	    (isdigit((unsigned char)*p) || *p=='.'); ++p) {
113a7058ec7SDamien Miller 		if (*p == '.') {
114a7058ec7SDamien Miller 			if (fract_digits > 0) {	/* oops, more than one '.' */
115a7058ec7SDamien Miller 				errno = EINVAL;
116a7058ec7SDamien Miller 				return -1;
117a7058ec7SDamien Miller 			}
118a7058ec7SDamien Miller 			fract_digits = 1;
119a7058ec7SDamien Miller 			continue;
120a7058ec7SDamien Miller 		}
121a7058ec7SDamien Miller 
122a7058ec7SDamien Miller 		i = (*p) - '0';			/* whew! finally a digit we can use */
123a7058ec7SDamien Miller 		if (fract_digits > 0) {
124a7058ec7SDamien Miller 			if (fract_digits >= MAX_DIGITS-1)
125a7058ec7SDamien Miller 				/* ignore extra fractional digits */
126a7058ec7SDamien Miller 				continue;
127a7058ec7SDamien Miller 			fract_digits++;		/* for later scaling */
128282cad22SDarren Tucker 			if (fpart > LLONG_MAX / 10) {
129d94c1dfeSDamien Miller 				errno = ERANGE;
130d94c1dfeSDamien Miller 				return -1;
131d94c1dfeSDamien Miller 			}
132a7058ec7SDamien Miller 			fpart *= 10;
133282cad22SDarren Tucker 			if (i > LLONG_MAX - fpart) {
134282cad22SDarren Tucker 				errno = ERANGE;
135282cad22SDarren Tucker 				return -1;
136282cad22SDarren Tucker 			}
137a7058ec7SDamien Miller 			fpart += i;
138a7058ec7SDamien Miller 		} else {				/* normal digit */
139a7058ec7SDamien Miller 			if (++ndigits >= MAX_DIGITS) {
140a7058ec7SDamien Miller 				errno = ERANGE;
141a7058ec7SDamien Miller 				return -1;
142a7058ec7SDamien Miller 			}
143282cad22SDarren Tucker 			if (whole > LLONG_MAX / 10) {
144d94c1dfeSDamien Miller 				errno = ERANGE;
145d94c1dfeSDamien Miller 				return -1;
146d94c1dfeSDamien Miller 			}
147a7058ec7SDamien Miller 			whole *= 10;
148282cad22SDarren Tucker 			if (i > LLONG_MAX - whole) {
149282cad22SDarren Tucker 				errno = ERANGE;
150282cad22SDarren Tucker 				return -1;
151282cad22SDarren Tucker 			}
152a7058ec7SDamien Miller 			whole += i;
153a7058ec7SDamien Miller 		}
154a7058ec7SDamien Miller 	}
155a7058ec7SDamien Miller 
156a7058ec7SDamien Miller 	if (sign) {
157a7058ec7SDamien Miller 		whole *= sign;
158a7058ec7SDamien Miller 		fpart *= sign;
159a7058ec7SDamien Miller 	}
160a7058ec7SDamien Miller 
161a7058ec7SDamien Miller 	/* If no scale factor given, we're done. fraction is discarded. */
162a7058ec7SDamien Miller 	if (!*p) {
163a7058ec7SDamien Miller 		*result = whole;
164a7058ec7SDamien Miller 		return 0;
165a7058ec7SDamien Miller 	}
166a7058ec7SDamien Miller 
167a7058ec7SDamien Miller 	/* Validate scale factor, and scale whole and fraction by it. */
168a7058ec7SDamien Miller 	for (i = 0; i < SCALE_LENGTH; i++) {
169a7058ec7SDamien Miller 
170d94c1dfeSDamien Miller 		/* Are we there yet? */
171a7058ec7SDamien Miller 		if (*p == scale_chars[i] ||
172d94c1dfeSDamien Miller 			*p == tolower((unsigned char)scale_chars[i])) {
173a7058ec7SDamien Miller 
174a7058ec7SDamien Miller 			/* If it ends with alphanumerics after the scale char, bad. */
175d94c1dfeSDamien Miller 			if (isalnum((unsigned char)*(p+1))) {
176a7058ec7SDamien Miller 				errno = EINVAL;
177a7058ec7SDamien Miller 				return -1;
178a7058ec7SDamien Miller 			}
179a7058ec7SDamien Miller 			scale_fact = scale_factors[i];
180a7058ec7SDamien Miller 
181c73a229eSDarren Tucker 			/* check for overflow and underflow after scaling */
182c73a229eSDarren Tucker 			if (whole > LLONG_MAX / scale_fact ||
183c73a229eSDarren Tucker 			    whole < LLONG_MIN / scale_fact) {
184d427b73bSDarren Tucker 				errno = ERANGE;
185d427b73bSDarren Tucker 				return -1;
186d427b73bSDarren Tucker 			}
187d427b73bSDarren Tucker 
188a7058ec7SDamien Miller 			/* scale whole part */
189a7058ec7SDamien Miller 			whole *= scale_fact;
190a7058ec7SDamien Miller 
19110479cc2SDamien Miller 			/* truncate fpart so it doesn't overflow.
192a7058ec7SDamien Miller 			 * then scale fractional part.
193a7058ec7SDamien Miller 			 */
194a7058ec7SDamien Miller 			while (fpart >= LLONG_MAX / scale_fact) {
195a7058ec7SDamien Miller 				fpart /= 10;
196a7058ec7SDamien Miller 				fract_digits--;
197a7058ec7SDamien Miller 			}
198a7058ec7SDamien Miller 			fpart *= scale_fact;
199a7058ec7SDamien Miller 			if (fract_digits > 0) {
200a7058ec7SDamien Miller 				for (i = 0; i < fract_digits -1; i++)
201a7058ec7SDamien Miller 					fpart /= 10;
202a7058ec7SDamien Miller 			}
203a7058ec7SDamien Miller 			whole += fpart;
204a7058ec7SDamien Miller 			*result = whole;
205a7058ec7SDamien Miller 			return 0;
206a7058ec7SDamien Miller 		}
207a7058ec7SDamien Miller 	}
208d94c1dfeSDamien Miller 
209d94c1dfeSDamien Miller 	/* Invalid unit or character */
210d94c1dfeSDamien Miller 	errno = EINVAL;
211a7058ec7SDamien Miller 	return -1;
212a7058ec7SDamien Miller }
213a7058ec7SDamien Miller 
214a7058ec7SDamien Miller /* Format the given "number" into human-readable form in "result".
215a7058ec7SDamien Miller  * Result must point to an allocated buffer of length FMT_SCALED_STRSIZE.
216a7058ec7SDamien Miller  * Return 0 on success, -1 and errno set if error.
217a7058ec7SDamien Miller  */
218a7058ec7SDamien Miller int
fmt_scaled(long long number,char * result)219a7058ec7SDamien Miller fmt_scaled(long long number, char *result)
220a7058ec7SDamien Miller {
221a7058ec7SDamien Miller 	long long abval, fract = 0;
222a7058ec7SDamien Miller 	unsigned int i;
223a7058ec7SDamien Miller 	unit_type unit = NONE;
224a7058ec7SDamien Miller 
225d94c1dfeSDamien Miller 	abval = llabs(number);
226a7058ec7SDamien Miller 
227a7058ec7SDamien Miller 	/* Not every negative long long has a positive representation.
228a7058ec7SDamien Miller 	 * Also check for numbers that are just too darned big to format
229a7058ec7SDamien Miller 	 */
230a7058ec7SDamien Miller 	if (abval < 0 || abval / 1024 >= scale_factors[SCALE_LENGTH-1]) {
231a7058ec7SDamien Miller 		errno = ERANGE;
232a7058ec7SDamien Miller 		return -1;
233a7058ec7SDamien Miller 	}
234a7058ec7SDamien Miller 
235a7058ec7SDamien Miller 	/* scale whole part; get unscaled fraction */
236a7058ec7SDamien Miller 	for (i = 0; i < SCALE_LENGTH; i++) {
237a7058ec7SDamien Miller 		if (abval/1024 < scale_factors[i]) {
238a7058ec7SDamien Miller 			unit = units[i];
239a7058ec7SDamien Miller 			fract = (i == 0) ? 0 : abval % scale_factors[i];
240a7058ec7SDamien Miller 			number /= scale_factors[i];
241a7058ec7SDamien Miller 			if (i > 0)
242a7058ec7SDamien Miller 				fract /= scale_factors[i - 1];
243a7058ec7SDamien Miller 			break;
244a7058ec7SDamien Miller 		}
245a7058ec7SDamien Miller 	}
246a7058ec7SDamien Miller 
247a7058ec7SDamien Miller 	fract = (10 * fract + 512) / 1024;
248a7058ec7SDamien Miller 	/* if the result would be >= 10, round main number */
249*32e4e94eSDamien Miller 	if (fract >= 10) {
250a7058ec7SDamien Miller 		if (number >= 0)
251a7058ec7SDamien Miller 			number++;
252a7058ec7SDamien Miller 		else
253a7058ec7SDamien Miller 			number--;
254a7058ec7SDamien Miller 		fract = 0;
255*32e4e94eSDamien Miller 	} else if (fract < 0) {
256*32e4e94eSDamien Miller 		/* shouldn't happen */
257*32e4e94eSDamien Miller 		fract = 0;
258a7058ec7SDamien Miller 	}
259a7058ec7SDamien Miller 
260a7058ec7SDamien Miller 	if (number == 0)
261a7058ec7SDamien Miller 		strlcpy(result, "0B", FMT_SCALED_STRSIZE);
262a7058ec7SDamien Miller 	else if (unit == NONE || number >= 100 || number <= -100) {
263a7058ec7SDamien Miller 		if (fract >= 5) {
264a7058ec7SDamien Miller 			if (number >= 0)
265a7058ec7SDamien Miller 				number++;
266a7058ec7SDamien Miller 			else
267a7058ec7SDamien Miller 				number--;
268a7058ec7SDamien Miller 		}
269a7058ec7SDamien Miller 		(void)snprintf(result, FMT_SCALED_STRSIZE, "%lld%c",
270a7058ec7SDamien Miller 			number, scale_chars[unit]);
271a7058ec7SDamien Miller 	} else
272a7058ec7SDamien Miller 		(void)snprintf(result, FMT_SCALED_STRSIZE, "%lld.%1lld%c",
273a7058ec7SDamien Miller 			number, fract, scale_chars[unit]);
274a7058ec7SDamien Miller 
275a7058ec7SDamien Miller 	return 0;
276a7058ec7SDamien Miller }
277a7058ec7SDamien Miller 
278a7058ec7SDamien Miller #ifdef	MAIN
279a7058ec7SDamien Miller /*
280a7058ec7SDamien Miller  * This is the original version of the program in the man page.
281a7058ec7SDamien Miller  * Copy-and-paste whatever you need from it.
282a7058ec7SDamien Miller  */
283a7058ec7SDamien Miller int
main(int argc,char ** argv)284a7058ec7SDamien Miller main(int argc, char **argv)
285a7058ec7SDamien Miller {
286a7058ec7SDamien Miller 	char *cinput = "1.5K", buf[FMT_SCALED_STRSIZE];
287a7058ec7SDamien Miller 	long long ninput = 10483892, result;
288a7058ec7SDamien Miller 
289a7058ec7SDamien Miller 	if (scan_scaled(cinput, &result) == 0)
290a7058ec7SDamien Miller 		printf("\"%s\" -> %lld\n", cinput, result);
291a7058ec7SDamien Miller 	else
292a7058ec7SDamien Miller 		perror(cinput);
293a7058ec7SDamien Miller 
294a7058ec7SDamien Miller 	if (fmt_scaled(ninput, buf) == 0)
295a7058ec7SDamien Miller 		printf("%lld -> \"%s\"\n", ninput, buf);
296a7058ec7SDamien Miller 	else
297a7058ec7SDamien Miller 		fprintf(stderr, "%lld invalid (%s)\n", ninput, strerror(errno));
298a7058ec7SDamien Miller 
299a7058ec7SDamien Miller 	return 0;
300a7058ec7SDamien Miller }
301a7058ec7SDamien Miller #endif
302a7058ec7SDamien Miller 
303a7058ec7SDamien Miller #endif /* HAVE_FMT_SCALED */
304