123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166 |
- #include <stdio.h>
- #include "asprintf.h"
- #include "warnp.h"
- #include "humansize.h"
- /**
- * humansize(size):
- * Given a size ${size} in bytes, allocate and return a string of the form
- * "<N> B" for 0 <= N <= 999 or "<X> <prefix>B" where either 10 <= X <= 999 or
- * 1.0 <= X <= 9.9 and <prefix> is "k", "M", "G", "T", "P", or "E"; and where
- * the value returned is the largest valid value <= the provided size.
- */
- char *
- humansize(uint64_t size)
- {
- char * s;
- char prefix;
- int shiftcnt;
- int rc;
- /* Special-case for size < 1000. */
- if (size < 1000) {
- rc = asprintf(&s, "%d B", (int)size);
- } else {
- /* Keep 10 * size / 1000^(3n) in size. */
- for (size /= 100, shiftcnt = 1; size >= 10000; shiftcnt++)
- size /= 1000;
- /*
- * Figure out what prefix to use. Since 1 EB = 10^18 B and
- * the maximum value of a uint64_t is 2^64 which is roughly
- * 18.4 * 10^18, this cannot reference beyond the end of the
- * string.
- */
- prefix = " kMGTPE"[shiftcnt];
- /* Construct the string. */
- if (size < 100)
- rc = asprintf(&s, "%d.%d %cB", (int)size / 10,
- (int)size % 10, prefix);
- else
- rc = asprintf(&s, "%d %cB", (int)size / 10, prefix);
- }
- if (rc == -1) {
- warnp("asprintf");
- goto err0;
- }
- /* Success! */
- return (s);
- err0:
- /* Failure! */
- return (NULL);
- }
- /**
- * humansize_parse(s, size):
- * Parse a string matching /[0-9]+ ?[kMGTPE]?B?/ as a size ${size} in bytes.
- */
- int
- humansize_parse(const char * s, uint64_t * size)
- {
- int state = 0;
- uint64_t multiplier = 1;
- do {
- switch (state) {
- case -1:
- /* Error state. */
- break;
- case 0:
- /* Initial state. */
- *size = 0;
- /* We must start with at least one digit. */
- if ((*s < '0') || (*s > '9')) {
- state = -1;
- break;
- }
- /* FALLTHROUGH */
- case 1:
- /* We're now processing digits. */
- state = 1;
- /* Digit-parsing state. */
- if (('0' <= *s) && (*s <= '9')) {
- if (*size > UINT64_MAX / 10)
- state = -1;
- else
- *size *= 10;
- if (*size > UINT64_MAX - (uint64_t)(*s - '0'))
- state = -1;
- else
- *size += (uint64_t)(*s - '0');
- break;
- }
- /* FALLTHROUGH */
- case 2:
- /* We move into state 3 after an optional ' '. */
- state = 3;
- if (*s == ' ')
- break;
- /* FALLTHROUGH */
- case 3:
- /* We may have one SI prefix. */
- switch (*s) {
- case 'E':
- multiplier *= 1000;
- /* FALLTHROUGH */
- case 'P':
- multiplier *= 1000;
- /* FALLTHROUGH */
- case 'T':
- multiplier *= 1000;
- /* FALLTHROUGH */
- case 'G':
- multiplier *= 1000;
- /* FALLTHROUGH */
- case 'M':
- multiplier *= 1000;
- /* FALLTHROUGH */
- case 'k':
- multiplier *= 1000;
- break;
- }
- /* We move into state 4 after the optional prefix. */
- state = 4;
- if (multiplier != 1)
- break;
- /* FALLTHROUGH */
- case 4:
- /* We move into state 5 after an optional 'B'. */
- state = 5;
- if (*s == 'B')
- break;
- /* FALLTHROUGH */
- case 5:
- /* We have trailing garbage. */
- state = -1;
- break;
- }
- /* Move on to the next character. */
- s++;
- } while ((state != -1) && (*s != '\0'));
- /* Multiply by multiplier. */
- if (*size > UINT64_MAX / multiplier)
- state = -1;
- else
- *size *= multiplier;
- /* Anything other than state -1 is success. */
- return ((state == -1) ? -1 : 0);
- }
|