xref: /freebsd/lib/libutil/expand_number.c (revision b3127a2dc25ac63cae8e33e6f3dbd3580644fe52)
1 /*-
2  * SPDX-License-Identifier: BSD-2-Clause
3  *
4  * Copyright (c) 2007 Eric Anderson <anderson@FreeBSD.org>
5  * Copyright (c) 2007 Pawel Jakub Dawidek <pjd@FreeBSD.org>
6  * Copyright (c) 2025 Dag-Erling Smørgrav <des@FreeBSD.org>
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
19  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
22  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
23  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
24  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
25  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
27  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
28  * SUCH DAMAGE.
29  */
30 
31 #include <sys/types.h>
32 
33 #include <ctype.h>
34 #include <errno.h>
35 #include <inttypes.h>
36 #include <libutil.h>
37 #include <stdbool.h>
38 #include <stdint.h>
39 
40 int
41 expand_number(const char *buf, int64_t *num)
42 {
43 	char *endptr;
44 	uintmax_t number;
45 	unsigned int shift;
46 	bool neg;
47 	int serrno;
48 
49 	/*
50 	 * Skip whitespace and optional sign.
51 	 */
52 	while (isspace((unsigned char)*buf))
53 		buf++;
54 	if (*buf == '-') {
55 		neg = true;
56 		buf++;
57 	} else {
58 		neg = false;
59 		if (*buf == '+')
60 			buf++;
61 	}
62 
63 	/*
64 	 * The next character should be the first digit of the number.  If
65 	 * we don't enforce this ourselves, strtoumax() will allow further
66 	 * whitespace and a (second?) sign.
67 	 */
68 	if (!isdigit((unsigned char)*buf)) {
69 		errno = EINVAL;
70 		return (-1);
71 	}
72 
73 	serrno = errno;
74 	errno = 0;
75 	number = strtoumax(buf, &endptr, 0);
76 	if (errno != 0)
77 		return (-1);
78 	errno = serrno;
79 
80 	switch (tolower((unsigned char)*endptr)) {
81 	case 'e':
82 		shift = 60;
83 		endptr++;
84 		break;
85 	case 'p':
86 		shift = 50;
87 		endptr++;
88 		break;
89 	case 't':
90 		shift = 40;
91 		endptr++;
92 		break;
93 	case 'g':
94 		shift = 30;
95 		endptr++;
96 		break;
97 	case 'm':
98 		shift = 20;
99 		endptr++;
100 		break;
101 	case 'k':
102 		shift = 10;
103 		endptr++;
104 		break;
105 	default:
106 		shift = 0;
107 	}
108 
109 	/*
110 	 * Treat 'b' as an ignored suffix for all unit except 'b',
111 	 * otherwise there should be no remaining character(s).
112 	 */
113 	if (tolower((unsigned char)*endptr) == 'b')
114 		endptr++;
115 	if (*endptr != '\0') {
116 		errno = EINVAL;
117 		return (-1);
118 	}
119 
120 	/*
121 	 * Apply the shift and check for overflow.
122 	 */
123 	if ((number << shift) >> shift != number) {
124 		/* Overflow */
125 		errno = ERANGE;
126 		return (-1);
127 	}
128 	number <<= shift;
129 
130 	/*
131 	 * Apply the sign and check for overflow.
132 	 */
133 	if (neg) {
134 		if (number > 0x8000000000000000LLU /* -INT64_MIN */) {
135 			errno = ERANGE;
136 			return (-1);
137 		}
138 		*num = -number;
139 	} else {
140 		if (number > INT64_MAX) {
141 			errno = ERANGE;
142 			return (-1);
143 		}
144 		*num = number;
145 	}
146 
147 	return (0);
148 }
149