8092666e57491cfb60b4fcb37c08df30215b22cd
[darkstat-debian] / 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, len;
109 uint32_t rem, next;
110
111 if (i == 0) {
112 str_append(s, "0");
113 return;
114 }
115
116 pos = sizeof(out)-1;
117 len = 0;
118 rem = i;
119
120 while (rem > 0) {
121 assert(pos >= 0);
122 next = rem / 10;
123 rem = rem - next * 10;
124 assert(rem < 10);
125 out[pos] = '0' + rem;
126 pos--;
127 len++;
128 rem = next;
129 if (mod_sep && (rem > 0) && (len > 0) && (len % 3 == 0)) {
130 out[pos] = COMMA;
131 pos--;
132 }
133 }
134 str_appendn(s, out+pos+1, sizeof(out)-1-pos);
135 }
136
137 static void
138 str_append_i32(struct str *s, int32_t i, const int mod_sep)
139 {
140 if (i < 0) {
141 str_append(s, "-");
142 i = -i;
143 }
144 str_append_u32(s, (uint32_t)i, mod_sep);
145 }
146
147 static void
148 str_append_u64(struct str *s, const uint64_t i, const int mod_sep)
149 {
150 char out[I64_MAXLEN];
151 int pos, len;
152 uint64_t rem, next;
153 uint32_t rem32, next32;
154
155 if (i == 0) {
156 str_append(s, "0");
157 return;
158 }
159
160 pos = sizeof(out)-1;
161 len = 0;
162 rem = i;
163
164 while (rem >= 4294967295U) {
165 assert(pos >= 0);
166 next = rem / 10;
167 rem = rem - next * 10;
168 assert(rem < 10);
169 out[pos] = '0' + rem;
170 pos--;
171 len++;
172 rem = next;
173 if (mod_sep && (rem > 0) && (len > 0) && (len % 3 == 0)) {
174 out[pos] = COMMA;
175 pos--;
176 }
177 }
178
179 /*
180 * Stick to 32-bit math when we can as it's faster on 32-bit platforms.
181 * FIXME: a tunable way to switch this off?
182 */
183 rem32 = (uint32_t)rem;
184 while (rem32 > 0) {
185 assert(pos >= 0);
186 next32 = rem32 / 10;
187 rem32 = rem32 - next32 * 10;
188 assert(rem32 < 10);
189 out[pos] = '0' + rem32;
190 pos--;
191 len++;
192 rem32 = next32;
193 if (mod_sep && (rem32 > 0) && (len > 0) && (len % 3 == 0)) {
194 out[pos] = COMMA;
195 pos--;
196 }
197 }
198 str_appendn(s, out+pos+1, sizeof(out)-1-pos);
199 }
200
201 static void
202 str_append_i64(struct str *s, int64_t i, const int mod_sep)
203 {
204 if (i < 0) {
205 str_append(s, "-");
206 i = -i;
207 }
208 str_append_u64(s, (uint64_t)i, mod_sep);
209 }
210
211 static void
212 str_append_hex8(struct str *s, const uint8_t b)
213 {
214 char out[2];
215 static const char hexset[] = "0123456789abcdef";
216
217 out[0] = hexset[ ((b >> 4) & 15) ];
218 out[1] = hexset[ (b & 15) ];
219 str_appendn(s, out, 2);
220 }
221
222 /* accepted formats: %s %d %u %x
223 * accepted modifiers: q and '
224 *
225 * %x is equivalent to %02x and expects a uint8_t
226 */
227 static void
228 str_vappendf(struct str *s, const char *format, va_list va)
229 {
230 size_t pos, len;
231 len = strlen(format);
232
233 for (pos=0; pos<len; pos++) {
234 size_t span_start = pos, span_len = 0;
235
236 while ((format[pos] != '\0') && (format[pos] != '%')) {
237 span_len++;
238 pos++;
239 }
240 if (span_len > 0)
241 str_appendn(s, format+span_start, span_len);
242
243 if (format[pos] == '%') {
244 int mod_quad = 0, mod_sep = 0;
245 char *arg_str;
246 FORMAT:
247 pos++;
248 switch (format[pos]) {
249 case '%':
250 str_append(s, "%");
251 break;
252 case 'q':
253 mod_quad = 1;
254 goto FORMAT;
255 case '\'':
256 mod_sep = 1;
257 goto FORMAT;
258 case 's':
259 arg_str = va_arg(va, char*);
260 str_append(s, arg_str);
261 /* str_append can be a macro! passing it va_arg can result in
262 * va_arg being called twice
263 */
264 break;
265 case 'd':
266 if (mod_quad)
267 str_append_i64(s, va_arg(va, int64_t), mod_sep);
268 else
269 str_append_i32(s, (int32_t)va_arg(va, int), mod_sep);
270 break;
271 case 'u':
272 if (mod_quad)
273 str_append_u64(s, va_arg(va, uint64_t), mod_sep);
274 else
275 str_append_u32(s, (uint32_t)va_arg(va, unsigned int), mod_sep);
276 break;
277 case 'x':
278 str_append_hex8(s, (uint8_t)va_arg(va, int));
279 break;
280 default:
281 errx(1, "format string is \"%s\", unknown format '%c' at %u",
282 format, format[pos], (unsigned int)pos);
283 }
284 }
285 }
286 }
287
288 void
289 str_appendf(struct str *s, const char *format, ...)
290 {
291 va_list va;
292 va_start(va, format);
293 str_vappendf(s, format, va);
294 va_end(va);
295 }
296
297 size_t
298 xvasprintf(char **result, const char *format, va_list va)
299 {
300 size_t len;
301 struct str *s = str_make();
302 str_vappendf(s, format, va);
303 str_appendn(s, "", 1); /* "" still contains \0 */
304 str_extract(s, &len, result);
305 return (len-1);
306 }
307
308 size_t
309 xasprintf(char **result, const char *format, ...)
310 {
311 va_list va;
312 size_t ret;
313 va_start(va, format);
314 ret = xvasprintf(result, format, va);
315 va_end(va);
316 return (ret);
317 }
318
319 /*
320 * Format a length of time in seconds to "n days, n hrs, n mins, n secs".
321 * Returns a newly allocated str.
322 */
323 struct str *
324 length_of_time(const time_t t)
325 {
326 struct str *buf = str_make();
327 int secs = t % 60;
328 int mins = (t / 60) % 60;
329 int hours = (t / 3600) % 24;
330 int days = t / 86400;
331
332 int show_zeroes = 0;
333
334 if (days > 0) {
335 str_appendf(buf, "%d %s", days, (days==1)?"day":"days");
336 show_zeroes = 1;
337 }
338
339 if (show_zeroes || (hours > 0)) {
340 if (show_zeroes) str_append(buf, ", ");
341 str_appendf(buf, "%d %s", hours, (hours==1)?"hr":"hrs");
342 show_zeroes = 1;
343 }
344
345 if (show_zeroes || (mins > 0)) {
346 if (show_zeroes) str_append(buf, ", ");
347 str_appendf(buf, "%d %s", mins, (mins==1)?"min":"mins");
348 show_zeroes = 1;
349 }
350
351 if (show_zeroes) str_append(buf, ", ");
352 str_appendf(buf, "%d %s", secs, (secs==1)?"sec":"secs");
353
354 return buf;
355 }
356
357 /* vim:set ts=3 sw=3 tw=78 expandtab: */