]>
Commit | Line | Data |
---|---|---|
0c1f3509 MG |
1 | #include <stdio.h> |
2 | ||
3 | #include "asprintf.h" | |
4 | #include "warnp.h" | |
5 | ||
6 | #include "humansize.h" | |
7 | ||
8 | /** | |
9 | * humansize(size): | |
10 | * Given a size in bytes, allocate and return a string of the form "<N> B" | |
11 | * for 0 <= N <= 999 or "<X> <prefix>B" where either 10 <= X <= 999 or | |
12 | * 1.0 <= X <= 9.9 and <prefix> is "k", "M", "G", "T", "P", or "E"; and where | |
13 | * the value returned is the largest valid value <= the provided size. | |
14 | */ | |
15 | char * | |
16 | humansize(uint64_t size) | |
17 | { | |
18 | char * s; | |
19 | char prefix; | |
20 | int shiftcnt; | |
21 | int rc; | |
22 | ||
23 | /* Special-case for size < 1000. */ | |
24 | if (size < 1000) { | |
25 | rc = asprintf(&s, "%d B", (int)size); | |
26 | } else { | |
27 | /* Keep 10 * size / 1000^(3n) in size. */ | |
28 | for (size /= 100, shiftcnt = 1; size >= 10000; shiftcnt++) | |
29 | size /= 1000; | |
30 | ||
31 | /* | |
32 | * Figure out what prefix to use. Since 1 EB = 10^18 B and | |
33 | * the maximum value of a uint64_t is 2^64 which is roughly | |
34 | * 18.4 * 10^18, this cannot reference beyond the end of the | |
35 | * string. | |
36 | */ | |
37 | prefix = " kMGTPE"[shiftcnt]; | |
38 | ||
39 | /* Construct the string. */ | |
40 | if (size < 100) | |
41 | rc = asprintf(&s, "%d.%d %cB", (int)size / 10, | |
42 | (int)size % 10, prefix); | |
43 | else | |
44 | rc = asprintf(&s, "%d %cB", (int)size / 10, prefix); | |
45 | } | |
46 | ||
47 | if (rc == -1) { | |
48 | warnp("asprintf"); | |
49 | goto err0; | |
50 | } | |
51 | ||
52 | /* Success! */ | |
53 | return (s); | |
54 | ||
55 | err0: | |
56 | /* Failure! */ | |
57 | return (NULL); | |
58 | } | |
59 | ||
60 | /** | |
61 | * humansize_parse(s, size): | |
62 | * Parse a string matching /[0-9]+ ?[kMGTPE]?B?/ as a size in bytes. | |
63 | */ | |
64 | int | |
65 | humansize_parse(const char * s, uint64_t * size) | |
66 | { | |
67 | int state = 0; | |
68 | uint64_t multiplier = 1; | |
69 | ||
70 | do { | |
71 | switch (state) { | |
72 | case -1: | |
73 | /* Error state. */ | |
74 | break; | |
75 | case 0: | |
76 | /* Initial state. */ | |
77 | *size = 0; | |
78 | ||
79 | /* We must start with at least one digit. */ | |
80 | if ((*s < '0') || (*s > '9')) { | |
81 | state = -1; | |
82 | break; | |
83 | } | |
84 | ||
85 | /* FALLTHROUGH */ | |
86 | case 1: | |
87 | /* We're now processing digits. */ | |
88 | state = 1; | |
89 | ||
90 | /* Digit-parsing state. */ | |
91 | if (('0' <= *s) && (*s <= '9')) { | |
92 | if (*size > UINT64_MAX / 10) | |
93 | state = -1; | |
94 | else | |
95 | *size *= 10; | |
96 | if (*size > UINT64_MAX - (uint64_t)(*s - '0')) | |
97 | state = -1; | |
98 | else | |
99 | *size += (uint64_t)(*s - '0'); | |
100 | break; | |
101 | } | |
102 | ||
103 | /* FALLTHROUGH */ | |
104 | case 2: | |
105 | /* We move into state 3 after an optional ' '. */ | |
106 | state = 3; | |
107 | if (*s == ' ') | |
108 | break; | |
109 | ||
110 | /* FALLTHROUGH */ | |
111 | case 3: | |
112 | /* We may have one SI prefix. */ | |
113 | switch (*s) { | |
114 | case 'E': | |
115 | multiplier *= 1000; | |
116 | /* FALLTHROUGH */ | |
117 | case 'P': | |
118 | multiplier *= 1000; | |
119 | /* FALLTHROUGH */ | |
120 | case 'T': | |
121 | multiplier *= 1000; | |
122 | /* FALLTHROUGH */ | |
123 | case 'G': | |
124 | multiplier *= 1000; | |
125 | /* FALLTHROUGH */ | |
126 | case 'M': | |
127 | multiplier *= 1000; | |
128 | /* FALLTHROUGH */ | |
129 | case 'k': | |
130 | multiplier *= 1000; | |
131 | break; | |
132 | } | |
133 | ||
134 | /* We move into state 4 after the optional prefix. */ | |
135 | state = 4; | |
136 | if (multiplier != 1) | |
137 | break; | |
138 | ||
139 | /* FALLTHROUGH */ | |
140 | case 4: | |
141 | /* We move into state 5 after an optional 'B'. */ | |
142 | state = 5; | |
143 | if (*s == 'B') | |
144 | break; | |
145 | ||
146 | /* FALLTHROUGH */ | |
147 | case 5: | |
148 | /* We have trailing garbage. */ | |
149 | state = -1; | |
150 | break; | |
151 | } | |
152 | ||
153 | /* Move on to the next character. */ | |
154 | s++; | |
155 | } while (*s != '\0'); | |
156 | ||
157 | /* Multiply by multiplier. */ | |
158 | if (*size > UINT64_MAX / multiplier) | |
159 | state = -1; | |
160 | else | |
161 | *size *= multiplier; | |
162 | ||
163 | /* Anything other than state -1 is success. */ | |
164 | return ((state == -1) ? -1 : 0); | |
165 | } |