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
expand_impl(const char * buf,uint64_t * num,bool * neg)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
expand_unsigned(const char * buf,uint64_t * num)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