/src/freeradius-server/src/lib/util/size.c
Line | Count | Source |
1 | | /* |
2 | | * This program is is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or (at |
5 | | * your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** Size printing and parsing functions |
18 | | * |
19 | | * @file src/lib/util/size.c |
20 | | * |
21 | | * @copyright 2022 Arran Cudbard-Bell (a.cudbardb@freeradius.org) |
22 | | */ |
23 | | RCSID("$Id: bf93de635b48c588c48a809721c2a68f1e3bb121 $") |
24 | | |
25 | | #include <freeradius-devel/util/math.h> |
26 | | #include <freeradius-devel/util/sbuff.h> |
27 | | |
28 | | #include "size.h" |
29 | | |
30 | | /** Parse a size string with optional unit |
31 | | * |
32 | | * Default scale with no suffix is bytes. |
33 | | * |
34 | | * @param[out] out Parsed and scaled size |
35 | | * @param[in] in sbuff to parse. |
36 | | * @return |
37 | | * - >0 on success. |
38 | | * - <0 on error. |
39 | | */ |
40 | | fr_slen_t fr_size_from_str(size_t *out, fr_sbuff_t *in) |
41 | 48 | { |
42 | 48 | static uint64_t base2_units[]= { |
43 | 48 | ['k'] = (uint64_t)1024, |
44 | 48 | ['m'] = (uint64_t)1024 * 1024, |
45 | 48 | ['g'] = (uint64_t)1024 * 1024 * 1024, |
46 | 48 | ['t'] = (uint64_t)1024 * 1024 * 1024 * 1024, |
47 | 48 | ['p'] = (uint64_t)1024 * 1024 * 1024 * 1024 * 1024, |
48 | 48 | ['e'] = (uint64_t)1024 * 1024 * 1024 * 1024 * 1024 * 1024, |
49 | 48 | }; |
50 | 48 | static size_t base2_units_len = NUM_ELEMENTS(base2_units); |
51 | | |
52 | 48 | static uint64_t base10_units[] = { |
53 | 48 | ['k'] = (uint64_t)1000, |
54 | 48 | ['m'] = (uint64_t)1000 * 1000, |
55 | 48 | ['g'] = (uint64_t)1000 * 1000 * 1000, |
56 | 48 | ['t'] = (uint64_t)1000 * 1000 * 1000 * 1000, |
57 | 48 | ['p'] = (uint64_t)1000 * 1000 * 1000 * 1000 * 1000, |
58 | 48 | ['e'] = (uint64_t)1000 * 1000 * 1000 * 1000 * 1000 * 1000, |
59 | 48 | }; |
60 | 48 | static size_t base10_units_len = NUM_ELEMENTS(base10_units); |
61 | | |
62 | 48 | fr_sbuff_t our_in = FR_SBUFF(in); |
63 | 48 | char c = '\0'; |
64 | 48 | uint64_t size; |
65 | | |
66 | 48 | *out = 0; |
67 | | |
68 | 48 | if (fr_sbuff_out(NULL, &size, &our_in) < 0) FR_SBUFF_ERROR_RETURN(&our_in); |
69 | 40 | if (!fr_sbuff_extend(&our_in)) goto done; |
70 | | |
71 | 36 | c = tolower(fr_sbuff_char(&our_in, '\0')); |
72 | | |
73 | | /* |
74 | | * Special cases first... |
75 | | */ |
76 | 36 | switch (c) { |
77 | 2 | case 'n': /* nibble */ |
78 | 2 | fr_sbuff_next(&our_in); |
79 | 2 | if (size & 0x01) { |
80 | 1 | fr_strerror_const("Sizes specified in nibbles must be an even number"); |
81 | 1 | fr_sbuff_set_to_start(&our_in); |
82 | 1 | FR_SBUFF_ERROR_RETURN(&our_in); |
83 | 1 | } |
84 | 1 | size /= 2; |
85 | 1 | break; |
86 | | |
87 | 2 | case '\0': |
88 | 2 | break; |
89 | | |
90 | 2 | case 'b': /* byte */ |
91 | 2 | fr_sbuff_next(&our_in); |
92 | 2 | break; |
93 | | |
94 | 30 | default: |
95 | 30 | { |
96 | 30 | uint64_t *units; |
97 | 30 | size_t units_len; |
98 | 30 | bool is_base2; |
99 | | |
100 | 30 | fr_sbuff_next(&our_in); |
101 | 30 | is_base2 = fr_sbuff_next_if_char(&our_in, 'i') || fr_sbuff_next_if_char(&our_in, 'I'); |
102 | | |
103 | 30 | if (!fr_sbuff_next_if_char(&our_in, 'b')) (void)fr_sbuff_next_if_char(&our_in, 'B'); /* Optional */ |
104 | | |
105 | 30 | if (is_base2) { |
106 | 6 | units = base2_units; |
107 | 6 | units_len = base2_units_len; |
108 | 24 | } else { |
109 | 24 | units = base10_units; |
110 | 24 | units_len = base10_units_len; |
111 | 24 | } |
112 | | |
113 | 30 | if (((size_t)c >= units_len) || units[(uint8_t)c] == 0) { |
114 | 21 | fr_strerror_printf("Unknown unit '%c'", c); |
115 | 21 | FR_SBUFF_ERROR_RETURN(&our_in); |
116 | 21 | } |
117 | | |
118 | 9 | if (!fr_multiply(&size, size, units[(uint8_t)c])) { |
119 | 1 | overflow: |
120 | 1 | fr_strerror_printf("Value must be less than %zu", (size_t)SIZE_MAX); |
121 | 1 | fr_sbuff_set_to_start(&our_in); |
122 | 1 | FR_SBUFF_ERROR_RETURN(&our_in); |
123 | 1 | } |
124 | 9 | } |
125 | 36 | } |
126 | | |
127 | 13 | if (size > SIZE_MAX) { |
128 | 0 | fr_strerror_printf("Value %" PRIu64 " is greater than the maximum " |
129 | 0 | "file/memory size of this system (%zu)", size, (size_t)SIZE_MAX); |
130 | |
|
131 | 0 | goto overflow; |
132 | 0 | } |
133 | | |
134 | 17 | done: |
135 | 17 | *out = (size_t)size; |
136 | | |
137 | 17 | FR_SBUFF_SET_RETURN(in, &our_in); |
138 | 13 | } |
139 | | |
140 | | typedef struct { |
141 | | char const *suffix; |
142 | | uint64_t mul; |
143 | | } fr_size_unit_t; |
144 | | |
145 | | /** Print a size string with unit |
146 | | * |
147 | | * Suffix is the largest unit possible without losing precision. |
148 | | * |
149 | | * @param[out] out To write size to. |
150 | | * @param[in] in size to print. |
151 | | * @return |
152 | | * - >0 on success. |
153 | | * - <0 on error. |
154 | | */ |
155 | | fr_slen_t fr_size_to_str(fr_sbuff_t *out, size_t in) |
156 | 0 | { |
157 | 0 | fr_sbuff_t our_out = FR_SBUFF(out); |
158 | |
|
159 | 0 | static fr_size_unit_t const base2_units[] = { |
160 | 0 | { "B", (uint64_t)1 }, |
161 | 0 | { "KiB", (uint64_t)1024 }, |
162 | 0 | { "MiB", (uint64_t)1024 * 1024 }, |
163 | 0 | { "GiB", (uint64_t)1024 * 1024 * 1024}, |
164 | 0 | { "TiB", (uint64_t)1024 * 1024 * 1024 * 1024}, |
165 | 0 | { "PiB", (uint64_t)1024 * 1024 * 1024 * 1024 * 1024}, |
166 | 0 | { "EiB", (uint64_t)1024 * 1024 * 1024 * 1024 * 1024 * 1024}, |
167 | 0 | }; |
168 | 0 | static fr_size_unit_t const base10_units[] = { |
169 | 0 | { "B", (uint64_t)1 }, |
170 | 0 | { "KB", (uint64_t)1000 }, |
171 | 0 | { "MB", (uint64_t)1000 * 1000 }, |
172 | 0 | { "GB", (uint64_t)1000 * 1000 * 1000}, |
173 | 0 | { "TB", (uint64_t)1000 * 1000 * 1000 * 1000}, |
174 | 0 | { "PB", (uint64_t)1000 * 1000 * 1000 * 1000 * 1000}, |
175 | 0 | { "EB", (uint64_t)1000 * 1000 * 1000 * 1000 * 1000 * 1000}, |
176 | 0 | }; |
177 | 0 | fr_slen_t slen; |
178 | 0 | fr_size_unit_t const *unit = &base10_units[0]; |
179 | 0 | uint8_t b2_idx = 0, b10_idx = 0; |
180 | |
|
181 | 0 | uint8_t pos2 = fr_low_bit_pos(in); |
182 | 0 | uint8_t pos10; |
183 | 0 | size_t tmp; |
184 | | |
185 | | /* |
186 | | * Fast path - Won't be divisible by a power of 1000 or a power of 1024 |
187 | | */ |
188 | 0 | if (pos2 < 3) goto done; |
189 | 0 | pos2--; |
190 | | |
191 | | /* |
192 | | * Get a count of trailing decimal zeroes. |
193 | | */ |
194 | 0 | for (tmp = in, pos10 = 0; tmp && ((tmp % 1000) == 0); pos10++) tmp /= 1000; |
195 | |
|
196 | 0 | if (pos10 > 0) b10_idx = (uint8_t)pos10; |
197 | 0 | if (pos2 >= 10) b2_idx = (uint8_t)(pos2 / 10); |
198 | | |
199 | | /* |
200 | | * Pick the most significant base2 or base10 unit, preferring base10. |
201 | | */ |
202 | 0 | if (b2_idx > b10_idx) { |
203 | 0 | unit = &base2_units[b2_idx]; |
204 | 0 | } else { |
205 | 0 | unit = &base10_units[b10_idx]; |
206 | 0 | } |
207 | |
|
208 | 0 | done: |
209 | 0 | slen = fr_sbuff_in_sprintf(&our_out, "%zu%s", in / unit->mul, unit->suffix); |
210 | 0 | if (slen < 0) return slen; |
211 | 0 | return fr_sbuff_set(out, &our_out); |
212 | 0 | } |