Fix "assuming signed overflow does not occur" warning.
[darkstat] / str.c
1 /* darkstat 3
2 * copyright (c) 2001-2011 Emil Mikulic.
3 *
4 * str.c: string buffer with pool-based reallocation
5 *
6 * Permission to use, copy, modify, and distribute this file for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18
19 #include <assert.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <stdint.h> /* for uint32_t on Linux and OS X */
23
24 #include "conv.h"
25 #include "err.h"
26 #include "str.h"
27
28 #define INITIAL_LEN 1024
29
30 struct str {
31 char *buf;
32 size_t len, pool;
33 };
34
35 struct str *
36 str_make(void)
37 {
38 struct str *s = xmalloc(sizeof(*s));
39 s->len = 0;
40 s->pool = INITIAL_LEN;
41 s->buf = xmalloc(s->pool);
42 return (s);
43 }
44
45 void
46 str_free(struct str *s)
47 {
48 free(s->buf);
49 free(s);
50 }
51
52 /*
53 * Extract struct str into buffer and length, freeing the struct in the
54 * process.
55 */
56 void
57 str_extract(struct str *s, size_t *len, char **str)
58 {
59 *len = s->len;
60 *str = s->buf;
61 free(s);
62 }
63
64 void
65 str_appendn(struct str *buf, const char *s, const size_t len)
66 {
67 if (buf->pool < buf->len + len) {
68 /* pool has dried up */
69 while (buf->pool < buf->len + len)
70 buf->pool *= 2;
71 buf->buf = xrealloc(buf->buf, buf->pool);
72 }
73 memcpy(buf->buf + buf->len, s, len);
74 buf->len += len;
75 }
76
77 void
78 str_appendstr(struct str *buf, const struct str *s)
79 {
80 str_appendn(buf, s->buf, s->len);
81 }
82
83 #ifndef str_append
84 void
85 str_append(struct str *buf, const char *s)
86 {
87 str_appendn(buf, s, strlen(s));
88 }
89 #endif
90
91 /*
92 * Apparently, some wacky locales use periods, or another character that isn't
93 * a comma, to separate thousands. If you are afflicted by such a locale,
94 * change this macro:
95 */
96 #define COMMA ','
97
98 /* 2^32 = 4,294,967,296 (10 digits, 13 chars) */
99 #define I32_MAXLEN 13
100
101 /* 2^64 = 18,446,744,073,709,551,616 (20 digits, 26 chars) */
102 #define I64_MAXLEN 26
103
104 static void
105 str_append_u32(struct str *s, const uint32_t i, const int mod_sep)
106 {
107 char out[I32_MAXLEN];
108 int pos;
109 unsigned int len;
110 uint32_t rem, next;
111
112 if (i == 0) {
113 str_append(s, "0");
114 return;
115 }
116
117 pos = sizeof(out)-1;
118 len = 0;
119 rem = i;
120
121 while (rem > 0) {
122 assert(pos >= 0);
123 next = rem / 10;
124 rem = rem - next * 10;
125 assert(rem < 10);
126 out[pos] = '0' + rem;
127 pos--;
128 len++;
129 rem = next;
130 if (mod_sep && (rem > 0) && (len > 0) && (len % 3 == 0)) {
131 out[pos] = COMMA;
132 pos--;
133 }
134 }
135 str_appendn(s, out+pos+1, sizeof(out)-1-pos);
136 }
137
138 static void
139 str_append_i32(struct str *s, int32_t i, const int mod_sep)
140 {
141 if (i < 0) {
142 str_append(s, "-");
143 i = -i;
144 }
145 str_append_u32(s, (uint32_t)i, mod_sep);
146 }
147
148 static void
149 str_append_u64(struct str *s, const uint64_t i, const int mod_sep)
150 {
151 char out[I64_MAXLEN];
152 int pos;
153 unsigned int len;
154 uint64_t rem, next;
155 uint32_t rem32, next32;
156
157 if (i == 0) {
158 str_append(s, "0");
159 return;
160 }
161
162 pos = sizeof(out)-1;
163 len = 0;
164 rem = i;
165
166 while (rem >= 4294967295U) {
167 assert(pos >= 0);
168 next = rem / 10;
169 rem = rem - next * 10;
170 assert(rem < 10);
171 out[pos] = '0' + rem;
172 pos--;
173 len++;
174 rem = next;
175 if (mod_sep && (rem > 0) && (len > 0) && (len % 3 == 0)) {
176 out[pos] = COMMA;
177 pos--;
178 }
179 }
180
181 /*
182 * Stick to 32-bit math when we can as it's faster on 32-bit platforms.
183 * FIXME: a tunable way to switch this off?
184 */
185 rem32 = (uint32_t)rem;
186 while (rem32 > 0) {
187 assert(pos >= 0);
188 next32 = rem32 / 10;
189 rem32 = rem32 - next32 * 10;
190 assert(rem32 < 10);
191 out[pos] = '0' + rem32;
192 pos--;
193 len++;
194 rem32 = next32;
195 if (mod_sep && (rem32 > 0) && (len > 0) && (len % 3 == 0)) {
196 out[pos] = COMMA;
197 pos--;
198 }
199 }
200 str_appendn(s, out+pos+1, sizeof(out)-1-pos);
201 }
202
203 static void
204 str_append_i64(struct str *s, int64_t i, const int mod_sep)
205 {
206 if (i < 0) {
207 str_append(s, "-");
208 i = -i;
209 }
210 str_append_u64(s, (uint64_t)i, mod_sep);
211 }
212
213 static void
214 str_append_hex8(struct str *s, const uint8_t b)
215 {
216 char out[2];
217 static const char hexset[] = "0123456789abcdef";
218
219 out[0] = hexset[ ((b >> 4) & 15) ];
220 out[1] = hexset[ (b & 15) ];
221 str_appendn(s, out, 2);
222 }
223
224 /* accepted formats: %s %d %u %x
225 * accepted modifiers: q and '
226 *
227 * %x is equivalent to %02x and expects a uint8_t
228 */
229 static void
230 str_vappendf(struct str *s, const char *format, va_list va)
231 {
232 size_t pos, len;
233 len = strlen(format);
234
235 for (pos=0; pos<len; pos++) {
236 size_t span_start = pos, span_len = 0;
237
238 while ((format[pos] != '\0') && (format[pos] != '%')) {
239 span_len++;
240 pos++;
241 }
242 if (span_len > 0)
243 str_appendn(s, format+span_start, span_len);
244
245 if (format[pos] == '%') {
246 int mod_quad = 0, mod_sep = 0;
247 char *arg_str;
248 FORMAT:
249 pos++;
250 switch (format[pos]) {
251 case '%':
252 str_append(s, "%");
253 break;
254 case 'q':
255 mod_quad = 1;
256 goto FORMAT;
257 case '\'':
258 mod_sep = 1;
259 goto FORMAT;
260 case 's':
261 arg_str = va_arg(va, char*);
262 str_append(s, arg_str);
263 /* str_append can be a macro! passing it va_arg can result in
264 * va_arg being called twice
265 */
266 break;
267 case 'd':
268 if (mod_quad)
269 str_append_i64(s, va_arg(va, int64_t), mod_sep);
270 else
271 str_append_i32(s, (int32_t)va_arg(va, int), mod_sep);
272 break;
273 case 'u':
274 if (mod_quad)
275 str_append_u64(s, va_arg(va, uint64_t), mod_sep);
276 else
277 str_append_u32(s, (uint32_t)va_arg(va, unsigned int), mod_sep);
278 break;
279 case 'x':
280 str_append_hex8(s, (uint8_t)va_arg(va, int));
281 break;
282 default:
283 errx(1, "format string is \"%s\", unknown format '%c' at %u",
284 format, format[pos], (unsigned int)pos);
285 }
286 }
287 }
288 }
289
290 void
291 str_appendf(struct str *s, const char *format, ...)
292 {
293 va_list va;
294 va_start(va, format);
295 str_vappendf(s, format, va);
296 va_end(va);
297 }
298
299 size_t
300 xvasprintf(char **result, const char *format, va_list va)
301 {
302 size_t len;
303 struct str *s = str_make();
304 str_vappendf(s, format, va);
305 str_appendn(s, "", 1); /* "" still contains \0 */
306 str_extract(s, &len, result);
307 return (len-1);
308 }
309
310 size_t
311 xasprintf(char **result, const char *format, ...)
312 {
313 va_list va;
314 size_t ret;
315 va_start(va, format);
316 ret = xvasprintf(result, format, va);
317 va_end(va);
318 return (ret);
319 }
320
321 /*
322 * Format a length of time in seconds to "n days, n hrs, n mins, n secs".
323 * Returns a newly allocated str.
324 */
325 struct str *
326 length_of_time(const time_t t)
327 {
328 struct str *buf = str_make();
329 int secs = t % 60;
330 int mins = (t / 60) % 60;
331 int hours = (t / 3600) % 24;
332 int days = t / 86400;
333
334 int show_zeroes = 0;
335
336 if (days > 0) {
337 str_appendf(buf, "%d %s", days, (days==1)?"day":"days");
338 show_zeroes = 1;
339 }
340
341 if (show_zeroes || (hours > 0)) {
342 if (show_zeroes) str_append(buf, ", ");
343 str_appendf(buf, "%d %s", hours, (hours==1)?"hr":"hrs");
344 show_zeroes = 1;
345 }
346
347 if (show_zeroes || (mins > 0)) {
348 if (show_zeroes) str_append(buf, ", ");
349 str_appendf(buf, "%d %s", mins, (mins==1)?"min":"mins");
350 show_zeroes = 1;
351 }
352
353 if (show_zeroes) str_append(buf, ", ");
354 str_appendf(buf, "%d %s", secs, (secs==1)?"sec":"secs");
355
356 return buf;
357 }
358
359 /* vim:set ts=3 sw=3 tw=78 expandtab: */