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

Last change on this file since bb4d0b5 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
RevLine 
[9854a8f]1/*
[c111da2]2 * Copyright (c) 2025 Jiri Svoboda
[9854a8f]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
[6c4eedf]29/** @addtogroup libc
[9854a8f]30 * @{
31 */
32/**
[6c4eedf]33 * @file Storage capacity specification.
[9854a8f]34 */
35
[5fc8244]36#include <assert.h>
[c24b0dcb]37#include <capa.h>
[9854a8f]38#include <errno.h>
39#include <imath.h>
[5fc8244]40#include <stddef.h>
[9854a8f]41#include <stdio.h>
42#include <str.h>
43
44/** Simplified capacity parameters */
45enum {
46 /** Simplified capacity maximum integer digits */
[c24b0dcb]47 scapa_max_idig = 3,
[9854a8f]48 /** Simplified capacity maximum significant digits */
[c24b0dcb]49 scapa_max_sdig = 4
[9854a8f]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
[c24b0dcb]64void capa_from_blocks(uint64_t nblocks, size_t block_size, capa_spec_t *capa)
[9854a8f]65{
66 uint64_t tsize;
67
68 tsize = nblocks * block_size;
[c24b0dcb]69 capa->m = tsize;
70 capa->dp = 0;
71 capa->cunit = cu_byte;
[9854a8f]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.
[03661d19]79 *
80 * A capacity value entails precision, i.e. it corresponds to a range
[6c4eedf]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.
[9854a8f]84 */
[c24b0dcb]85errno_t capa_to_blocks(capa_spec_t *capa, capa_vsel_t cvsel, size_t block_size,
[6c4eedf]86 uint64_t *rblocks)
[9854a8f]87{
88 int exp;
89 uint64_t bytes;
90 uint64_t f;
[03661d19]91 uint64_t adj;
92 uint64_t blocks;
93 uint64_t rem;
[b7fd2a0]94 errno_t rc;
[9854a8f]95
[c24b0dcb]96 exp = capa->cunit * 3 - capa->dp;
[9854a8f]97 if (exp < 0) {
98 rc = ipow10_u64(-exp, &f);
99 if (rc != EOK)
100 return ERANGE;
[c24b0dcb]101 bytes = (capa->m + (f / 2)) / f;
102 if (bytes * f - (f / 2) != capa->m)
[03661d19]103 return ERANGE;
[9854a8f]104 } else {
105 rc = ipow10_u64(exp, &f);
106 if (rc != EOK)
107 return ERANGE;
[03661d19]108
109 adj = 0;
110 switch (cvsel) {
[6c4eedf]111 case cv_nom:
[03661d19]112 adj = 0;
113 break;
[6c4eedf]114 case cv_min:
[03661d19]115 adj = -(f / 2);
116 break;
[6c4eedf]117 case cv_max:
[03661d19]118 adj = f / 2 - 1;
119 break;
120 }
121
[c24b0dcb]122 bytes = capa->m * f + adj;
123 if ((bytes - adj) / f != capa->m)
[03661d19]124 return ERANGE;
[9854a8f]125 }
126
[03661d19]127 rem = bytes % block_size;
128 if ((bytes + rem) < bytes)
129 return ERANGE;
130
131 blocks = (bytes + rem) / block_size;
132
133 *rblocks = blocks;
[9854a8f]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 */
[c24b0dcb]142void capa_simplify(capa_spec_t *capa)
[9854a8f]143{
144 uint64_t div;
145 uint64_t maxv;
146 unsigned sdig;
147 unsigned rdig;
[b7fd2a0]148 errno_t rc;
[9854a8f]149
[c24b0dcb]150 /* Change units so that we have at most @c scapa_max_idig integer digits */
151 rc = ipow10_u64(scapa_max_idig, &maxv);
[9854a8f]152 assert(rc == EOK);
153
[c24b0dcb]154 rc = ipow10_u64(capa->dp, &div);
[9854a8f]155 assert(rc == EOK);
156
[de65624]157 /* Change units until we have no more than scapa_max_idig integer digits */
[c24b0dcb]158 while (capa->m / div >= maxv) {
159 ++capa->cunit;
160 capa->dp += 3;
[9854a8f]161 div = div * 1000;
162 }
163
[c24b0dcb]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) {
[9854a8f]167 /* Number of digits to remove */
[c24b0dcb]168 rdig = sdig - scapa_max_sdig;
169 if (rdig > capa->dp)
170 rdig = capa->dp;
[9854a8f]171
172 rc = ipow10_u64(rdig, &div);
173 assert(rc == EOK);
174
[de65624]175 /* Division with rounding */
[c24b0dcb]176 capa->m = (capa->m + (div / 2)) / div;
177 capa->dp -= rdig;
[9854a8f]178 }
[de65624]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 }
[9854a8f]199}
200
[cfd04c4]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 */
[c24b0dcb]207errno_t capa_format(capa_spec_t *capa, char **rstr)
[9854a8f]208{
[b7fd2a0]209 errno_t rc;
[d5c1051]210 int ret;
[9854a8f]211 const char *sunit;
212 uint64_t ipart;
213 uint64_t fpart;
214 uint64_t div;
215
216 sunit = NULL;
217
[c24b0dcb]218 assert(capa->cunit < CU_LIMIT);
[9854a8f]219
[c24b0dcb]220 rc = ipow10_u64(capa->dp, &div);
[9854a8f]221 if (rc != EOK)
222 return rc;
223
[c24b0dcb]224 ipart = capa->m / div;
225 fpart = capa->m % div;
[9854a8f]226
[c24b0dcb]227 sunit = cu_str[capa->cunit];
228 if (capa->dp > 0) {
[d5c1051]229 ret = asprintf(rstr, "%" PRIu64 ".%0*" PRIu64 " %s", ipart,
[c24b0dcb]230 (int)capa->dp, fpart, sunit);
[9854a8f]231 } else {
[d5c1051]232 ret = asprintf(rstr, "%" PRIu64 " %s", ipart, sunit);
[9854a8f]233 }
[de65624]234
[d5c1051]235 if (ret < 0)
[9854a8f]236 return ENOMEM;
237
238 return EOK;
239}
240
[cfd04c4]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 */
[c111da2]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
[cfd04c4]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 */
[c111da2]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
[cfd04c4]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 */
[c111da2]312void capa_blocks_format_buf(uint64_t nblocks, size_t block_size,
[cfd04c4]313 char *buf, size_t bufsize)
[c111da2]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 */
[cfd04c4]322 rc = capa_format_buf(&capa, buf, bufsize);
[c111da2]323 assert(rc == EOK);
324 (void)rc;
325}
326
[c24b0dcb]327static errno_t capa_digit_val(char c, int *val)
[03661d19]328{
329 switch (c) {
[1433ecda]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;
[03661d19]360 default:
361 return EINVAL;
362 }
363
364 return EOK;
365}
366
[cfd04c4]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 */
[c24b0dcb]373errno_t capa_parse(const char *str, capa_spec_t *capa)
[9854a8f]374{
[03661d19]375 const char *eptr;
376 const char *p;
377 int d;
378 int dp;
379 unsigned long m;
[9854a8f]380 int i;
381
[03661d19]382 m = 0;
383
384 eptr = str;
[c24b0dcb]385 while (capa_digit_val(*eptr, &d) == EOK) {
[03661d19]386 m = m * 10 + d;
387 ++eptr;
388 }
389
390 if (*eptr == '.') {
391 ++eptr;
392 dp = 0;
[c24b0dcb]393 while (capa_digit_val(*eptr, &d) == EOK) {
[03661d19]394 m = m * 10 + d;
395 ++dp;
396 ++eptr;
397 }
398 } else {
[2dab624]399 dp = 0;
[03661d19]400 }
[9854a8f]401
402 while (*eptr == ' ')
403 ++eptr;
404
405 if (*eptr == '\0') {
[c24b0dcb]406 capa->cunit = cu_byte;
[9854a8f]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;
[1433ecda]420 found:
[c24b0dcb]421 capa->cunit = i;
[9854a8f]422 }
423
[c24b0dcb]424 capa->m = m;
425 capa->dp = dp;
[9854a8f]426 return EOK;
427}
428
429/** @}
430 */
Note: See TracBrowser for help on using the repository browser.