chiark / gitweb /
Horrible bodge to wait for root user to be created before attempting to
[disorder] / lib / printf.c
CommitLineData
460b9539 1/*
2 * This file is part of DisOrder
681cb9fb 3 * Copyright (C) 2004, 2007, 2008 Richard Kettlewell
460b9539 4 *
e7eb3a27 5 * This program is free software: you can redistribute it and/or modify
460b9539 6 * it under the terms of the GNU General Public License as published by
e7eb3a27 7 * the Free Software Foundation, either version 3 of the License, or
460b9539 8 * (at your option) any later version.
e7eb3a27
RK
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
460b9539 15 * You should have received a copy of the GNU General Public License
e7eb3a27 16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
460b9539 17 */
132a5a4a
RK
18/** @file lib/printf.c
19 * @brief UTF-8 *printf workalike (core)
20 */
460b9539 21
22#define NO_MEMORY_ALLOCATION
23/* because byte_snprintf used from log.c */
24
05b75f8d 25#include "common.h"
460b9539 26
460b9539 27#include <stdarg.h>
460b9539 28#include <errno.h>
460b9539 29#include <stddef.h>
30
31#include "printf.h"
32#include "sink.h"
e3426f7b 33#include "vacopy.h"
460b9539 34
35enum flags {
36 f_thousands = 1,
37 f_left = 2,
38 f_sign = 4,
39 f_space = 8,
40 f_hash = 16,
41 f_zero = 32,
42 f_width = 256,
43 f_precision = 512
44};
45
46enum lengths {
47 l_char = 1,
48 l_short,
49 l_long,
50 l_longlong,
51 l_size_t,
52 l_intmax_t,
53 l_ptrdiff_t,
54 l_longdouble
55};
56
57struct conversion;
58
59struct state {
60 struct sink *output;
61 int bytes;
6a6b327d 62 va_list ap;
460b9539 63};
64
65struct specifier {
66 int ch;
67 int (*check)(const struct conversion *c);
68 int (*output)(struct state *s, struct conversion *c);
69 int base;
70 const char *digits;
71 const char *xform;
72};
73
74struct conversion {
75 unsigned flags;
76 int width;
77 int precision;
78 int length;
79 const struct specifier *specifier;
80};
81
82static const char flags[] = "'-+ #0";
83
84/* write @nbytes@ to the output. Return -1 on error, 0 on success.
85 * Keeps track of the number of bytes written. */
86static int do_write(struct state *s,
87 const void *buffer,
88 int nbytes) {
89 if(s->bytes > INT_MAX - nbytes) {
90#ifdef EOVERFLOW
91 errno = EOVERFLOW;
92#endif
93 return -1;
94 }
681cb9fb
RK
95 if(s->output->write(s->output, buffer, nbytes) < 0)
96 return -1;
460b9539 97 s->bytes += nbytes;
98 return 0;
99}
100
101/* write character @ch@ @n@ times, reasonably efficiently */
102static int do_pad(struct state *s, int ch, unsigned n) {
103 unsigned t;
104 const char *padding;
105
106 switch(ch) {
107 case ' ': padding = " "; break;
108 case '0': padding = "00000000000000000000000000000000"; break;
109 default: abort();
110 }
111 t = n / 32;
112 n %= 32;
113 while(t-- > 0)
681cb9fb
RK
114 if(do_write(s, padding, 32) < 0)
115 return -1;
460b9539 116 if(n > 0)
681cb9fb
RK
117 if(do_write(s, padding, n) < 0)
118 return -1;
460b9539 119 return 0;
120}
121
122/* pick up the integer at @ptr@, returning it via @intp@. Return the
123 * number of characters consumed. Return 0 if there is no integer
124 * there and -1 if an error occurred (e.g. too big) */
125static int get_integer(int *intp, const char *ptr) {
126 long n;
127 char *e;
128
129 errno = 0;
130 n = strtol(ptr, &e, 10);
681cb9fb
RK
131 if(errno || n > INT_MAX || n < INT_MIN || e == ptr)
132 return -1;
460b9539 133 *intp = n;
134 return e - ptr;
135}
136
137/* consistency checks for various conversion specifications */
138
139static int check_integer(const struct conversion *c) {
140 switch(c->length) {
141 case 0:
142 case l_char:
143 case l_short:
144 case l_long:
145 case l_longlong:
146 case l_intmax_t:
147 case l_size_t:
148 case l_longdouble:
cb9a695c 149 case l_ptrdiff_t:
460b9539 150 return 0;
151 default:
152 return -1;
153 }
154}
155
156static int check_string(const struct conversion *c) {
157 switch(c->length) {
158 case 0:
159 /* XXX don't support %ls, %lc */
160 return 0;
161 default:
162 return -1;
163 }
164}
165
166static int check_pointer(const struct conversion *c) {
681cb9fb
RK
167 if(c->length)
168 return -1;
460b9539 169 return 0;
170}
171
172static int check_percent(const struct conversion *c) {
681cb9fb
RK
173 if(c->flags || c->width || c->precision || c->length)
174 return -1;
460b9539 175 return 0;
176}
177
178/* output functions for various conversion specifications */
179
180static int output_percent(struct state *s,
181 struct conversion attribute((unused)) *c) {
182 return do_write(s, "%", 1);
183}
184
185static int output_integer(struct state *s, struct conversion *c) {
186 uintmax_t u;
187 intmax_t l;
188 char sign;
189 int base, dp, iszero, ndigits, prec, xform, sign_bytes, pad;
190 char digits[CHAR_BIT * sizeof (uintmax_t)]; /* overestimate */
191
192 switch(c->specifier->ch) {
193 default:
194 if(c->specifier->base < 0) {
195 switch(c->length) {
6a6b327d
RK
196 case 0: l = va_arg(s->ap, int); break;
197 case l_char: l = (signed char)va_arg(s->ap, int); break;
198 case l_short: l = (short)va_arg(s->ap, int); break;
199 case l_long: l = va_arg(s->ap, long); break;
200 case l_longlong: l = va_arg(s->ap, long_long); break;
201 case l_intmax_t: l = va_arg(s->ap, intmax_t); break;
202 case l_size_t: l = va_arg(s->ap, ssize_t); break;
203 case l_ptrdiff_t: l = va_arg(s->ap, ptrdiff_t); break;
460b9539 204 default: abort();
205 }
206 base = -c->specifier->base;
207 if(l < 0) {
208 u = -l;
209 sign = '-';
210 } else {
211 u = l;
212 sign = 0;
213 }
214 } else {
215 switch(c->length) {
6a6b327d
RK
216 case 0: u = va_arg(s->ap, unsigned int); break;
217 case l_char: u = (unsigned char)va_arg(s->ap, unsigned int); break;
218 case l_short: u = (unsigned short)va_arg(s->ap, unsigned int); break;
219 case l_long: u = va_arg(s->ap, unsigned long); break;
220 case l_longlong: u = va_arg(s->ap, u_long_long); break;
221 case l_intmax_t: u = va_arg(s->ap, uintmax_t); break;
222 case l_size_t: u = va_arg(s->ap, size_t); break;
223 case l_ptrdiff_t: u = va_arg(s->ap, ptrdiff_t); break;
460b9539 224 default: abort();
225 }
226 base = c->specifier->base;
227 sign = 0;
228 }
229 break;
230 case 'p':
6a6b327d 231 u = (uintptr_t)va_arg(s->ap, void *);
460b9539 232 c->flags |= f_hash;
233 base = c->specifier->base;
234 sign = 0;
235 break;
236 }
237 /* default precision */
238 if(!(c->flags & f_precision))
239 c->precision = 1;
240 /* enforce sign */
681cb9fb
RK
241 if((c->flags & f_sign) && !sign)
242 sign = '+';
460b9539 243 /* compute the digits */
244 iszero = (u == 0);
245 dp = sizeof digits;
246 while(u) {
247 digits[--dp] = c->specifier->digits[u % base];
248 u /= base;
249 }
250 ndigits = sizeof digits - dp;
251 /* alternative form */
252 if(c->flags & f_hash) {
253 switch(base) {
254 case 8:
255 if((dp == sizeof digits || digits[dp] != '0')
256 && c->precision <= ndigits)
257 c->precision = ndigits + 1;
258 break;
259 }
260 if(!iszero && c->specifier->xform)
261 xform = strlen(c->specifier->xform);
262 else
263 xform = 0;
264 } else
265 xform = 0;
266 /* calculate number of 0s to add for precision */
267 if(ndigits < c->precision)
268 prec = c->precision - ndigits;
269 else
270 prec = 0;
271 /* bytes occupied by the sign */
272 if(sign)
273 sign_bytes = 1;
274 else
275 sign_bytes = 0;
276 /* XXX implement the ' ' flag */
277 /* calculate number of bytes of padding */
278 if(c->flags & f_width) {
279 if((pad = c->width - (ndigits + prec + xform + sign_bytes)) < 0)
280 pad = 0;
281 } else
282 pad = 0;
283 /* now we are ready to output. Possibilities are:
284 * [space pad][sign][xform][0 prec]digits
285 * [sign][xform][0 pad][0 prec]digits
286 * [sign][xform][0 prec]digits[space pad]
287 *
288 * '-' beats '0'.
289 */
290 if(c->flags & f_left) {
681cb9fb
RK
291 if(sign && do_write(s, &sign, 1))
292 return -1;
293 if(xform && do_write(s, c->specifier->xform, xform))
294 return -1;
295 if(prec && do_pad(s, '0', prec) < 0)
296 return -1;
297 if(ndigits && do_write(s, digits + dp, ndigits))
298 return -1;
299 if(pad && do_pad(s, ' ', pad) < 0)
300 return -1;
460b9539 301 } else if(c->flags & f_zero) {
681cb9fb
RK
302 if(sign && do_write(s, &sign, 1))
303 return -1;
304 if(xform && do_write(s, c->specifier->xform, xform))
305 return -1;
306 if(pad && do_pad(s, '0', pad) < 0)
307 return -1;
308 if(prec && do_pad(s, '0', prec) < 0)
309 return -1;
310 if(ndigits && do_write(s, digits + dp, ndigits))
311 return -1;
460b9539 312 } else {
681cb9fb
RK
313 if(pad && do_pad(s, ' ', pad) < 0)
314 return -1;
315 if(sign && do_write(s, &sign, 1))
316 return -1;
317 if(xform && do_write(s, c->specifier->xform, xform))
318 return -1;
319 if(prec && do_pad(s, '0', prec) < 0)
320 return -1;
321 if(ndigits && do_write(s, digits + dp, ndigits))
322 return -1;
460b9539 323 }
324 return 0;
325}
326
327static int output_string(struct state *s, struct conversion *c) {
328 const char *str, *n;
329 int pad, len;
330
6a6b327d 331 str = va_arg(s->ap, const char *);
460b9539 332 if(c->flags & f_precision) {
333 if((n = memchr(str, 0, c->precision)))
334 len = n - str;
335 else
336 len = c->precision;
337 } else
338 len = strlen(str);
339 if(c->flags & f_width) {
340 if((pad = c->width - len) < 0)
341 pad = 0;
342 } else
343 pad = 0;
344 if(c->flags & f_left) {
681cb9fb
RK
345 if(do_write(s, str, len) < 0)
346 return -1;
347 if(pad && do_pad(s, ' ', pad) < 0)
348 return -1;
460b9539 349 } else {
681cb9fb
RK
350 if(pad && do_pad(s, ' ', pad) < 0)
351 return -1;
352 if(do_write(s, str, len) < 0)
353 return -1;
460b9539 354 }
355 return 0;
356
357}
358
359static int output_char(struct state *s, struct conversion *c) {
360 int pad;
361 char ch;
362
6a6b327d 363 ch = va_arg(s->ap, int);
460b9539 364 if(c->flags & f_width) {
365 if((pad = c->width - 1) < 0)
366 pad = 0;
367 } else
368 pad = 0;
369 if(c->flags & f_left) {
681cb9fb
RK
370 if(do_write(s, &ch, 1) < 0)
371 return -1;
372 if(pad && do_pad(s, ' ', pad) < 0)
373 return -1;
460b9539 374 } else {
681cb9fb
RK
375 if(pad && do_pad(s, ' ', pad) < 0)
376 return -1;
377 if(do_write(s, &ch, 1) < 0)
378 return -1;
460b9539 379 }
380 return 0;
381}
382
383static int output_count(struct state *s, struct conversion *c) {
384 switch(c->length) {
6a6b327d
RK
385 case 0: *va_arg(s->ap, int *) = s->bytes; break;
386 case l_char: *va_arg(s->ap, signed char *) = s->bytes; break;
387 case l_short: *va_arg(s->ap, short *) = s->bytes; break;
388 case l_long: *va_arg(s->ap, long *) = s->bytes; break;
389 case l_longlong: *va_arg(s->ap, long_long *) = s->bytes; break;
390 case l_intmax_t: *va_arg(s->ap, intmax_t *) = s->bytes; break;
391 case l_size_t: *va_arg(s->ap, ssize_t *) = s->bytes; break;
392 case l_ptrdiff_t: *va_arg(s->ap, ptrdiff_t *) = s->bytes; break;
460b9539 393 default: abort();
394 }
395 return 0;
396}
397
398/* table of conversion specifiers */
399static const struct specifier specifiers[] = {
400 /* XXX don't support floating point conversions */
401 { '%', check_percent, output_percent, 0, 0, 0 },
402 { 'X', check_integer, output_integer, 16, "0123456789ABCDEF", "0X" },
403 { 'c', check_string, output_char, 0, 0, 0 },
404 { 'd', check_integer, output_integer, -10, "0123456789", 0 },
405 { 'i', check_integer, output_integer, -10, "0123456789", 0 },
406 { 'n', check_integer, output_count, 0, 0, 0 },
407 { 'o', check_integer, output_integer, 8, "01234567", 0 },
408 { 'p', check_pointer, output_integer, 16, "0123456789abcdef", "0x" },
409 { 's', check_string, output_string, 0, 0, 0 },
410 { 'u', check_integer, output_integer, 10, "0123456789", 0 },
411 { 'x', check_integer, output_integer, 16, "0123456789abcdef", "0x" },
412};
413
414/* collect and check information about a conversion specification */
415static int parse_conversion(struct conversion *c, const char *ptr) {
416 int n, ch, l, r, m;
417 const char *q, *start = ptr;
418
419 memset(c, 0, sizeof *c);
420 /* flags */
421 while(*ptr && (q = strchr(flags, *ptr))) {
422 c->flags |= (1 << (q - flags));
423 ++ptr;
424 }
425 /* minimum field width */
426 if(*ptr >= '0' && *ptr <= '9') {
681cb9fb
RK
427 if((n = get_integer(&c->width, ptr)) < 0)
428 return -1;
460b9539 429 ptr += n;
430 c->flags |= f_width;
431 } else if(*ptr == '*') {
432 ++ptr;
433 c->width = -1;
434 c->flags |= f_width;
435 }
436 /* precision */
437 if(*ptr == '.') {
438 ++ptr;
439 if(*ptr >= '0' && *ptr <= '9') {
681cb9fb
RK
440 if((n = get_integer(&c->precision, ptr)) < 0)
441 return -1;
460b9539 442 ptr += n;
443 } else if(*ptr == '*') {
444 ++ptr;
445 c->precision = -1;
446 } else
0c740e59 447 c->precision = 0;
460b9539 448 c->flags |= f_precision;
449 }
450 /* length modifier */
451 switch(ch = *ptr++) {
452 case 'h':
681cb9fb
RK
453 if((ch = *ptr++) == 'h') {
454 c->length = l_char;
455 ch = *ptr++;
456 }
457 else
458 c->length = l_short;
460b9539 459 break;
460 case 'l':
681cb9fb
RK
461 if((ch = *ptr++) == 'l') {
462 c->length = l_longlong;
463 ch = *ptr++;
464 }
465 else
466 c->length = l_long;
460b9539 467 break;
468 case 'q': c->length = l_longlong; ch = *ptr++; break;
469 case 'j': c->length = l_intmax_t; ch = *ptr++; break;
470 case 'z': c->length = l_size_t; ch = *ptr++; break;
471 case 't': c->length = l_ptrdiff_t; ch = *ptr++; break;
472 case 'L': c->length = l_longdouble; ch = *ptr++; break;
473 }
474 /* conversion specifier */
475 l = 0;
476 r = sizeof specifiers / sizeof *specifiers;
477 while(l <= r && (specifiers[m = (l + r) / 2].ch != ch))
681cb9fb
RK
478 if(ch < specifiers[m].ch)
479 r = m - 1;
480 else
481 l = m + 1;
482 if(specifiers[m].ch != ch)
483 return -1;
484 if(specifiers[m].check(c))
485 return -1;
460b9539 486 c->specifier = &specifiers[m];
487 return ptr - start;
488}
489
490/* ISO/IEC 9899:1999 7.19.6.1 */
491/* http://www.opengroup.org/onlinepubs/009695399/functions/fprintf.html */
492
493int byte_vsinkprintf(struct sink *output,
494 const char *fmt,
495 va_list ap) {
496 int n;
497 const char *ptr;
498 struct state s;
499 struct conversion c;
500
501 memset(&s, 0, sizeof s);
502 s.output = output;
6a6b327d 503 va_copy(s.ap,ap);
460b9539 504 while(*fmt) {
505 /* output text up to next conversion specification */
506 for(ptr = fmt; *fmt && *fmt != '%'; ++fmt)
507 ;
508 if((n = fmt - ptr))
681cb9fb
RK
509 if(do_write(&s, ptr, n) < 0)
510 goto error;
460b9539 511 if(!*fmt)
512 break;
513 ++fmt;
514 /* parse conversion */
681cb9fb
RK
515 if((n = parse_conversion(&c, fmt)) < 0)
516 goto error;
460b9539 517 fmt += n;
518 /* fill in width and precision */
519 if((c.flags & f_width) && c.width == -1)
6a6b327d 520 if((c.width = va_arg(s.ap, int)) < 0) {
460b9539 521 c.width = -c.width;
522 c.flags |= f_left;
523 }
524 if((c.flags & f_precision) && c.precision == -1)
6a6b327d 525 if((c.precision = va_arg(s.ap, int)) < 0)
460b9539 526 c.flags ^= f_precision;
527 /* generate the output */
681cb9fb
RK
528 if(c.specifier->output(&s, &c) < 0)
529 goto error;
460b9539 530 }
6a6b327d 531 va_end(s.ap);
460b9539 532 return s.bytes;
6a6b327d
RK
533error:
534 va_end(s.ap);
535 return -1;
460b9539 536}
537
681cb9fb
RK
538int byte_sinkprintf(struct sink *output, const char *fmt, ...) {
539 int n;
540 va_list ap;
541
542 va_start(ap, fmt);
543 n = byte_vsinkprintf(output, fmt, ap);
544 va_end(ap);
545 return n;
546}
547
460b9539 548/*
549Local Variables:
550c-basic-offset:2
551comment-column:40
552End:
553*/