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