xref: /freebsd/lib/libutil/expand_number.c (revision e7be843b4a162e68651d3911f0357ed464915629)
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 static int
41 expand_impl(const char *buf, uint64_t *num, bool *neg)
42 {
43 	char *endptr;
44 	uintmax_t number;
45 	unsigned int shift;
46 	int serrno;
47 
48 	/*
49 	 * Skip whitespace and optional sign.
50 	 */
51 	while (isspace((unsigned char)*buf))
52 		buf++;
53 	if (*buf == '-') {
54 		*neg = true;
55 		buf++;
56 	} else {
57 		*neg = false;
58 		if (*buf == '+')
59 			buf++;
60 	}
61 
62 	/*
63 	 * The next character should be the first digit of the number.  If
64 	 * we don't enforce this ourselves, strtoumax() will allow further
65 	 * whitespace and a (second?) sign.
66 	 */
67 	if (!isdigit((unsigned char)*buf)) {
68 		errno = EINVAL;
69 		return (-1);
70 	}
71 
72 	serrno = errno;
73 	errno = 0;
74 	number = strtoumax(buf, &endptr, 0);
75 	if (errno != 0)
76 		return (-1);
77 	errno = serrno;
78 
79 	switch (tolower((unsigned char)*endptr)) {
80 	case 'e':
81 		shift = 60;
82 		endptr++;
83 		break;
84 	case 'p':
85 		shift = 50;
86 		endptr++;
87 		break;
88 	case 't':
89 		shift = 40;
90 		endptr++;
91 		break;
92 	case 'g':
93 		shift = 30;
94 		endptr++;
95 		break;
96 	case 'm':
97 		shift = 20;
98 		endptr++;
99 		break;
100 	case 'k':
101 		shift = 10;
102 		endptr++;
103 		break;
104 	default:
105 		shift = 0;
106 	}
107 
108 	/*
109 	 * Treat 'b' as an ignored suffix for all unit except 'b',
110 	 * otherwise there should be no remaining character(s).
111 	 */
112 	if (tolower((unsigned char)*endptr) == 'b')
113 		endptr++;
114 	if (*endptr != '\0') {
115 		errno = EINVAL;
116 		return (-1);
117 	}
118 
119 	/*
120 	 * Apply the shift and check for overflow.
121 	 */
122 	if ((number << shift) >> shift != number) {
123 		/* Overflow */
124 		errno = ERANGE;
125 		return (-1);
126 	}
127 	number <<= shift;
128 
129 	*num = number;
130 	return (0);
131 }
132 
133 int
134 (expand_number)(const char *buf, int64_t *num)
135 {
136 	uint64_t number;
137 	bool neg;
138 
139 	/*
140 	 * Parse the number.
141 	 */
142 	if (expand_impl(buf, &number, &neg) != 0)
143 		return (-1);
144 
145 	/*
146 	 * Apply the sign and check for overflow.
147 	 */
148 	if (neg) {
149 		if (number > 0x8000000000000000LLU /* -INT64_MIN */) {
150 			errno = ERANGE;
151 			return (-1);
152 		}
153 		*num = -number;
154 	} else {
155 		if (number > INT64_MAX) {
156 			errno = ERANGE;
157 			return (-1);
158 		}
159 		*num = number;
160 	}
161 
162 	return (0);
163 }
164 
165 int
166 expand_unsigned(const char *buf, uint64_t *num)
167 {
168 	uint64_t number;
169 	bool neg;
170 
171 	/*
172 	 * Parse the number.
173 	 */
174 	if (expand_impl(buf, &number, &neg) != 0)
175 		return (-1);
176 
177 	/*
178 	 * Negative numbers are out of range.
179 	 */
180 	if (neg && number > 0) {
181 		errno = ERANGE;
182 		return (-1);
183 	}
184 
185 	*num = number;
186 	return (0);
187 }
188