source: mainline/uspace/lib/c/generic/capa.c@ cfd04c4

Last change on this file since cfd04c4 was cfd04c4, checked in by Jiri Svoboda <jiri@…>, 2 months ago

Add docblocks to capa functions.

  • Property mode set to 100644
File size: 9.4 KB
Line 
1/*
2 * Copyright (c) 2025 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libc
30 * @{
31 */
32/**
33 * @file Storage capacity specification.
34 */
35
36#include <assert.h>
37#include <capa.h>
38#include <errno.h>
39#include <imath.h>
40#include <stddef.h>
41#include <stdio.h>
42#include <str.h>
43
44/** Simplified capacity parameters */
45enum {
46 /** Simplified capacity maximum integer digits */
47 scapa_max_idig = 3,
48 /** Simplified capacity maximum significant digits */
49 scapa_max_sdig = 4
50};
51
52static const char *cu_str[] = {
53 [cu_byte] = "B",
54 [cu_kbyte] = "kB",
55 [cu_mbyte] = "MB",
56 [cu_gbyte] = "GB",
57 [cu_tbyte] = "TB",
58 [cu_pbyte] = "PB",
59 [cu_ebyte] = "EB",
60 [cu_zbyte] = "ZB",
61 [cu_ybyte] = "YB"
62};
63
64void capa_from_blocks(uint64_t nblocks, size_t block_size, capa_spec_t *capa)
65{
66 uint64_t tsize;
67
68 tsize = nblocks * block_size;
69 capa->m = tsize;
70 capa->dp = 0;
71 capa->cunit = cu_byte;
72}
73
74/** Convert capacity to blocks.
75 *
76 * If the value of bytes is not integer, it is properly rounded. If the number
77 * of bytes is not divisible by the number of blocks, it is rounded
78 * up to an integer number of blocks.
79 *
80 * A capacity value entails precision, i.e. it corresponds to a range
81 * of values. @a cvsel selects the value to return. @c cv_nom gives
82 * the nominal (middle) value, @c cv_min gives the minimum value
83 * and @c cv_max gives the maximum value.
84 */
85errno_t capa_to_blocks(capa_spec_t *capa, capa_vsel_t cvsel, size_t block_size,
86 uint64_t *rblocks)
87{
88 int exp;
89 uint64_t bytes;
90 uint64_t f;
91 uint64_t adj;
92 uint64_t blocks;
93 uint64_t rem;
94 errno_t rc;
95
96 exp = capa->cunit * 3 - capa->dp;
97 if (exp < 0) {
98 rc = ipow10_u64(-exp, &f);
99 if (rc != EOK)
100 return ERANGE;
101 bytes = (capa->m + (f / 2)) / f;
102 if (bytes * f - (f / 2) != capa->m)
103 return ERANGE;
104 } else {
105 rc = ipow10_u64(exp, &f);
106 if (rc != EOK)
107 return ERANGE;
108
109 adj = 0;
110 switch (cvsel) {
111 case cv_nom:
112 adj = 0;
113 break;
114 case cv_min:
115 adj = -(f / 2);
116 break;
117 case cv_max:
118 adj = f / 2 - 1;
119 break;
120 }
121
122 bytes = capa->m * f + adj;
123 if ((bytes - adj) / f != capa->m)
124 return ERANGE;
125 }
126
127 rem = bytes % block_size;
128 if ((bytes + rem) < bytes)
129 return ERANGE;
130
131 blocks = (bytes + rem) / block_size;
132
133 *rblocks = blocks;
134 return EOK;
135}
136
137/** Simplify and round capacity to a human-friendly form.
138 *
139 * Change unit and round the number so that we have at most three integer
140 * digits and at most two fractional digits, e.g abc.xy <unit>.
141 */
142void capa_simplify(capa_spec_t *capa)
143{
144 uint64_t div;
145 uint64_t maxv;
146 unsigned sdig;
147 unsigned rdig;
148 errno_t rc;
149
150 /* Change units so that we have at most @c scapa_max_idig integer digits */
151 rc = ipow10_u64(scapa_max_idig, &maxv);
152 assert(rc == EOK);
153
154 rc = ipow10_u64(capa->dp, &div);
155 assert(rc == EOK);
156
157 /* Change units until we have no more than scapa_max_idig integer digits */
158 while (capa->m / div >= maxv) {
159 ++capa->cunit;
160 capa->dp += 3;
161 div = div * 1000;
162 }
163
164 /* Round the number so that we have at most @c scapa_max_sdig significant digits */
165 sdig = 1 + ilog10_u64(capa->m); /* number of significant digits */
166 if (sdig > scapa_max_sdig) {
167 /* Number of digits to remove */
168 rdig = sdig - scapa_max_sdig;
169 if (rdig > capa->dp)
170 rdig = capa->dp;
171
172 rc = ipow10_u64(rdig, &div);
173 assert(rc == EOK);
174
175 /* Division with rounding */
176 capa->m = (capa->m + (div / 2)) / div;
177 capa->dp -= rdig;
178 }
179
180 /*
181 * If we rounded up from something like 999.95 to 1000.0,, we still
182 * have more than scapa_max_idig integer digits and need to change
183 * units once more.
184 */
185 rc = ipow10_u64(capa->dp, &div);
186 assert(rc == EOK);
187
188 if (capa->m / div >= 1000) {
189 ++capa->cunit;
190 capa->dp += 3;
191
192 /*
193 * We now have one more significant digit than we want
194 * so round to one less digits
195 */
196 capa->m = (capa->m + 5) / 10;
197 --capa->dp;
198 }
199}
200
201/** Format capacity as string into a newly allocated buffer.
202 *
203 * @param capa Capacity
204 * @param rstr Place to store pointer to newly allocated string
205 * @return EOK on success or an error code
206 */
207errno_t capa_format(capa_spec_t *capa, char **rstr)
208{
209 errno_t rc;
210 int ret;
211 const char *sunit;
212 uint64_t ipart;
213 uint64_t fpart;
214 uint64_t div;
215
216 sunit = NULL;
217
218 assert(capa->cunit < CU_LIMIT);
219
220 rc = ipow10_u64(capa->dp, &div);
221 if (rc != EOK)
222 return rc;
223
224 ipart = capa->m / div;
225 fpart = capa->m % div;
226
227 sunit = cu_str[capa->cunit];
228 if (capa->dp > 0) {
229 ret = asprintf(rstr, "%" PRIu64 ".%0*" PRIu64 " %s", ipart,
230 (int)capa->dp, fpart, sunit);
231 } else {
232 ret = asprintf(rstr, "%" PRIu64 " %s", ipart, sunit);
233 }
234
235 if (ret < 0)
236 return ENOMEM;
237
238 return EOK;
239}
240
241/** Format capacity as string into an existing buffer.
242 *
243 * @param capa Capacity
244 * @param buf Buffer for storing string
245 * @param bufsize Size of buffer in bytes
246 * @return EOK on success or an error code
247 */
248errno_t capa_format_buf(capa_spec_t *capa, char *buf, size_t bufsize)
249{
250 errno_t rc;
251 const char *sunit;
252 uint64_t ipart;
253 uint64_t fpart;
254 uint64_t div;
255
256 sunit = NULL;
257
258 assert(capa->cunit < CU_LIMIT);
259
260 rc = ipow10_u64(capa->dp, &div);
261 if (rc != EOK)
262 return rc;
263
264 ipart = capa->m / div;
265 fpart = capa->m % div;
266
267 sunit = cu_str[capa->cunit];
268 if (capa->dp > 0) {
269 snprintf(buf, bufsize, "%" PRIu64 ".%0*" PRIu64 " %s", ipart,
270 (int)capa->dp, fpart, sunit);
271 } else {
272 snprintf(buf, bufsize, "%" PRIu64 " %s", ipart, sunit);
273 }
274
275 return EOK;
276}
277
278/** Format capacity of n blocks as string into a newly allocated buffer.
279 *
280 * This computes the total capacity of the blocks, simplifies it
281 * and formats it as string.
282 *
283 * @param nblocks Number of blocks
284 * @param block_size Size of each block in bytes
285 * @param rstr Place to store pointer to newly allocated string
286 * @return EOK on success or an error code
287 */
288errno_t capa_blocks_format(uint64_t nblocks, size_t block_size,
289 char **rptr)
290{
291 capa_spec_t capa;
292
293 capa_from_blocks(nblocks, block_size, &capa);
294 capa_simplify(&capa);
295 return capa_format(&capa, rptr);
296}
297
298/** Format capacity of n blocks as string into an existing buffer.
299 *
300 * This computes the total capacity of the blocks, simplifies it
301 * and formats it as string.
302 *
303 * This function does not return error. If the buffer is too small,
304 * the string will be truncated. To make sure it is not truncated,
305 * bufsize should be at least CAPA_BLOCKS_BUFSIZE.
306 *
307 * @param nblocks Number of blocks
308 * @param block_size Size of each block in bytes
309 * @param buf Buffer for storing string
310 * @param bufsize Size of buffer in bytes
311 */
312void capa_blocks_format_buf(uint64_t nblocks, size_t block_size,
313 char *buf, size_t bufsize)
314{
315 capa_spec_t capa;
316 errno_t rc;
317
318 capa_from_blocks(nblocks, block_size, &capa);
319 capa_simplify(&capa);
320
321 /* Should not get range error because of nblocks * block_size limits */
322 rc = capa_format_buf(&capa, buf, bufsize);
323 assert(rc == EOK);
324 (void)rc;
325}
326
327static errno_t capa_digit_val(char c, int *val)
328{
329 switch (c) {
330 case '0':
331 *val = 0;
332 break;
333 case '1':
334 *val = 1;
335 break;
336 case '2':
337 *val = 2;
338 break;
339 case '3':
340 *val = 3;
341 break;
342 case '4':
343 *val = 4;
344 break;
345 case '5':
346 *val = 5;
347 break;
348 case '6':
349 *val = 6;
350 break;
351 case '7':
352 *val = 7;
353 break;
354 case '8':
355 *val = 8;
356 break;
357 case '9':
358 *val = 9;
359 break;
360 default:
361 return EINVAL;
362 }
363
364 return EOK;
365}
366
367/** Parse string as capacity specification.
368 *
369 * @param str String (e.g. "100 kB")
370 * @param capa Place to store capacity
371 * @return EOK on success or an error code
372 */
373errno_t capa_parse(const char *str, capa_spec_t *capa)
374{
375 const char *eptr;
376 const char *p;
377 int d;
378 int dp;
379 unsigned long m;
380 int i;
381
382 m = 0;
383
384 eptr = str;
385 while (capa_digit_val(*eptr, &d) == EOK) {
386 m = m * 10 + d;
387 ++eptr;
388 }
389
390 if (*eptr == '.') {
391 ++eptr;
392 dp = 0;
393 while (capa_digit_val(*eptr, &d) == EOK) {
394 m = m * 10 + d;
395 ++dp;
396 ++eptr;
397 }
398 } else {
399 dp = 0;
400 }
401
402 while (*eptr == ' ')
403 ++eptr;
404
405 if (*eptr == '\0') {
406 capa->cunit = cu_byte;
407 } else {
408 for (i = 0; i < CU_LIMIT; i++) {
409 if (str_lcasecmp(eptr, cu_str[i],
410 str_length(cu_str[i])) == 0) {
411 p = eptr + str_size(cu_str[i]);
412 while (*p == ' ')
413 ++p;
414 if (*p == '\0')
415 goto found;
416 }
417 }
418
419 return EINVAL;
420 found:
421 capa->cunit = i;
422 }
423
424 capa->m = m;
425 capa->dp = dp;
426 return EOK;
427}
428
429/** @}
430 */
Note: See TracBrowser for help on using the repository browser.