1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165
|
#include <stdio.h>
#include "asprintf.h"
#include "warnp.h"
#include "humansize.h"
/**
* humansize(size):
* Given a 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 in bytes.
*/
int
humansize_parse(const char * s, uint64_t * size)
{
int state = 0;
uint64_t multiplier = 1;
while (*s != '\0') {
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++;
}
/* 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);
}
|