Commit | Line | Data |
---|---|---|
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" | |
902b9f3f | 32 | #include "log.h" |
460b9539 | 33 | #include "sink.h" |
e3426f7b | 34 | #include "vacopy.h" |
460b9539 | 35 | |
00682707 RK |
36 | /** @brief Flags from a converstion specification |
37 | * | |
38 | * Order significant! | |
39 | */ | |
460b9539 | 40 | enum flags { |
41 | f_thousands = 1, | |
42 | f_left = 2, | |
43 | f_sign = 4, | |
44 | f_space = 8, | |
45 | f_hash = 16, | |
46 | f_zero = 32, | |
47 | f_width = 256, | |
48 | f_precision = 512 | |
49 | }; | |
50 | ||
00682707 | 51 | /** @brief Possible lengths of a conversion specification */ |
460b9539 | 52 | enum lengths { |
53 | l_char = 1, | |
54 | l_short, | |
55 | l_long, | |
56 | l_longlong, | |
57 | l_size_t, | |
58 | l_intmax_t, | |
59 | l_ptrdiff_t, | |
60 | l_longdouble | |
61 | }; | |
62 | ||
63 | struct conversion; | |
64 | ||
00682707 | 65 | /** @brief Formatter state */ |
460b9539 | 66 | struct state { |
00682707 | 67 | /** @brief Output stream */ |
460b9539 | 68 | struct sink *output; |
00682707 RK |
69 | |
70 | /** @brief Number of bytes written */ | |
460b9539 | 71 | int bytes; |
00682707 RK |
72 | |
73 | /** @brief Argument list */ | |
6a6b327d | 74 | va_list ap; |
460b9539 | 75 | }; |
76 | ||
00682707 | 77 | /** @brief Definition of a conversion specifier */ |
460b9539 | 78 | struct specifier { |
00682707 | 79 | /** @brief Defining character ('d', 's' etc) */ |
460b9539 | 80 | int ch; |
00682707 RK |
81 | |
82 | /** @brief Consistency check | |
83 | * @param c Conversion being processed | |
84 | * @return 0 if OK, -1 on error | |
85 | */ | |
460b9539 | 86 | int (*check)(const struct conversion *c); |
00682707 RK |
87 | |
88 | /** @brief Generate output | |
89 | * @param s Formatter state | |
90 | * @param c Conversion being processed | |
91 | * @return 0 on success, -1 on error | |
92 | */ | |
460b9539 | 93 | int (*output)(struct state *s, struct conversion *c); |
00682707 RK |
94 | |
95 | /** @brief Number base */ | |
460b9539 | 96 | int base; |
00682707 RK |
97 | |
98 | /** @brief Digit set */ | |
460b9539 | 99 | const char *digits; |
00682707 RK |
100 | |
101 | /** @brief Alternative-form prefix */ | |
460b9539 | 102 | const char *xform; |
103 | }; | |
104 | ||
00682707 | 105 | /** @brief One conversion specified as it's handled */ |
460b9539 | 106 | struct conversion { |
00682707 | 107 | /** @brief Flags in this conversion */ |
460b9539 | 108 | unsigned flags; |
00682707 RK |
109 | |
110 | /** @brief Field width (if @ref f_width) */ | |
460b9539 | 111 | int width; |
00682707 RK |
112 | |
113 | /** @brief Precision (if @ref f_precision) */ | |
460b9539 | 114 | int precision; |
00682707 RK |
115 | |
116 | /** @brief Length modifier or 0 */ | |
460b9539 | 117 | int length; |
00682707 RK |
118 | |
119 | /** @brief Specifier used */ | |
460b9539 | 120 | const struct specifier *specifier; |
121 | }; | |
122 | ||
00682707 | 123 | /** @brief Flag characters (order significant!) */ |
460b9539 | 124 | static const char flags[] = "'-+ #0"; |
125 | ||
126 | /* write @nbytes@ to the output. Return -1 on error, 0 on success. | |
127 | * Keeps track of the number of bytes written. */ | |
128 | static int do_write(struct state *s, | |
129 | const void *buffer, | |
130 | int nbytes) { | |
131 | if(s->bytes > INT_MAX - nbytes) { | |
132 | #ifdef EOVERFLOW | |
133 | errno = EOVERFLOW; | |
134 | #endif | |
135 | return -1; | |
136 | } | |
681cb9fb RK |
137 | if(s->output->write(s->output, buffer, nbytes) < 0) |
138 | return -1; | |
460b9539 | 139 | s->bytes += nbytes; |
140 | return 0; | |
141 | } | |
142 | ||
143 | /* write character @ch@ @n@ times, reasonably efficiently */ | |
144 | static int do_pad(struct state *s, int ch, unsigned n) { | |
145 | unsigned t; | |
146 | const char *padding; | |
147 | ||
148 | switch(ch) { | |
149 | case ' ': padding = " "; break; | |
150 | case '0': padding = "00000000000000000000000000000000"; break; | |
151 | default: abort(); | |
152 | } | |
153 | t = n / 32; | |
154 | n %= 32; | |
155 | while(t-- > 0) | |
681cb9fb RK |
156 | if(do_write(s, padding, 32) < 0) |
157 | return -1; | |
460b9539 | 158 | if(n > 0) |
681cb9fb RK |
159 | if(do_write(s, padding, n) < 0) |
160 | return -1; | |
460b9539 | 161 | return 0; |
162 | } | |
163 | ||
164 | /* pick up the integer at @ptr@, returning it via @intp@. Return the | |
165 | * number of characters consumed. Return 0 if there is no integer | |
166 | * there and -1 if an error occurred (e.g. too big) */ | |
167 | static int get_integer(int *intp, const char *ptr) { | |
168 | long n; | |
169 | char *e; | |
170 | ||
171 | errno = 0; | |
172 | n = strtol(ptr, &e, 10); | |
681cb9fb RK |
173 | if(errno || n > INT_MAX || n < INT_MIN || e == ptr) |
174 | return -1; | |
460b9539 | 175 | *intp = n; |
176 | return e - ptr; | |
177 | } | |
178 | ||
179 | /* consistency checks for various conversion specifications */ | |
180 | ||
181 | static int check_integer(const struct conversion *c) { | |
182 | switch(c->length) { | |
183 | case 0: | |
184 | case l_char: | |
185 | case l_short: | |
186 | case l_long: | |
187 | case l_longlong: | |
188 | case l_intmax_t: | |
189 | case l_size_t: | |
190 | case l_longdouble: | |
cb9a695c | 191 | case l_ptrdiff_t: |
460b9539 | 192 | return 0; |
193 | default: | |
194 | return -1; | |
195 | } | |
196 | } | |
197 | ||
198 | static int check_string(const struct conversion *c) { | |
199 | switch(c->length) { | |
200 | case 0: | |
201 | /* XXX don't support %ls, %lc */ | |
202 | return 0; | |
203 | default: | |
204 | return -1; | |
205 | } | |
206 | } | |
207 | ||
208 | static int check_pointer(const struct conversion *c) { | |
681cb9fb RK |
209 | if(c->length) |
210 | return -1; | |
460b9539 | 211 | return 0; |
212 | } | |
213 | ||
214 | static int check_percent(const struct conversion *c) { | |
681cb9fb RK |
215 | if(c->flags || c->width || c->precision || c->length) |
216 | return -1; | |
460b9539 | 217 | return 0; |
218 | } | |
219 | ||
220 | /* output functions for various conversion specifications */ | |
221 | ||
222 | static int output_percent(struct state *s, | |
223 | struct conversion attribute((unused)) *c) { | |
224 | return do_write(s, "%", 1); | |
225 | } | |
226 | ||
227 | static int output_integer(struct state *s, struct conversion *c) { | |
228 | uintmax_t u; | |
229 | intmax_t l; | |
230 | char sign; | |
231 | int base, dp, iszero, ndigits, prec, xform, sign_bytes, pad; | |
232 | char digits[CHAR_BIT * sizeof (uintmax_t)]; /* overestimate */ | |
233 | ||
234 | switch(c->specifier->ch) { | |
235 | default: | |
236 | if(c->specifier->base < 0) { | |
237 | switch(c->length) { | |
6a6b327d RK |
238 | case 0: l = va_arg(s->ap, int); break; |
239 | case l_char: l = (signed char)va_arg(s->ap, int); break; | |
240 | case l_short: l = (short)va_arg(s->ap, int); break; | |
241 | case l_long: l = va_arg(s->ap, long); break; | |
242 | case l_longlong: l = va_arg(s->ap, long_long); break; | |
243 | case l_intmax_t: l = va_arg(s->ap, intmax_t); break; | |
244 | case l_size_t: l = va_arg(s->ap, ssize_t); break; | |
245 | case l_ptrdiff_t: l = va_arg(s->ap, ptrdiff_t); break; | |
460b9539 | 246 | default: abort(); |
247 | } | |
248 | base = -c->specifier->base; | |
249 | if(l < 0) { | |
250 | u = -l; | |
251 | sign = '-'; | |
252 | } else { | |
253 | u = l; | |
254 | sign = 0; | |
255 | } | |
256 | } else { | |
257 | switch(c->length) { | |
6a6b327d RK |
258 | case 0: u = va_arg(s->ap, unsigned int); break; |
259 | case l_char: u = (unsigned char)va_arg(s->ap, unsigned int); break; | |
260 | case l_short: u = (unsigned short)va_arg(s->ap, unsigned int); break; | |
261 | case l_long: u = va_arg(s->ap, unsigned long); break; | |
262 | case l_longlong: u = va_arg(s->ap, u_long_long); break; | |
263 | case l_intmax_t: u = va_arg(s->ap, uintmax_t); break; | |
264 | case l_size_t: u = va_arg(s->ap, size_t); break; | |
265 | case l_ptrdiff_t: u = va_arg(s->ap, ptrdiff_t); break; | |
460b9539 | 266 | default: abort(); |
267 | } | |
268 | base = c->specifier->base; | |
269 | sign = 0; | |
270 | } | |
271 | break; | |
272 | case 'p': | |
6a6b327d | 273 | u = (uintptr_t)va_arg(s->ap, void *); |
460b9539 | 274 | c->flags |= f_hash; |
275 | base = c->specifier->base; | |
276 | sign = 0; | |
277 | break; | |
278 | } | |
279 | /* default precision */ | |
280 | if(!(c->flags & f_precision)) | |
281 | c->precision = 1; | |
282 | /* enforce sign */ | |
681cb9fb RK |
283 | if((c->flags & f_sign) && !sign) |
284 | sign = '+'; | |
460b9539 | 285 | /* compute the digits */ |
286 | iszero = (u == 0); | |
287 | dp = sizeof digits; | |
288 | while(u) { | |
289 | digits[--dp] = c->specifier->digits[u % base]; | |
290 | u /= base; | |
291 | } | |
292 | ndigits = sizeof digits - dp; | |
293 | /* alternative form */ | |
294 | if(c->flags & f_hash) { | |
295 | switch(base) { | |
296 | case 8: | |
297 | if((dp == sizeof digits || digits[dp] != '0') | |
298 | && c->precision <= ndigits) | |
299 | c->precision = ndigits + 1; | |
300 | break; | |
301 | } | |
302 | if(!iszero && c->specifier->xform) | |
303 | xform = strlen(c->specifier->xform); | |
304 | else | |
305 | xform = 0; | |
306 | } else | |
307 | xform = 0; | |
308 | /* calculate number of 0s to add for precision */ | |
309 | if(ndigits < c->precision) | |
310 | prec = c->precision - ndigits; | |
311 | else | |
312 | prec = 0; | |
313 | /* bytes occupied by the sign */ | |
314 | if(sign) | |
315 | sign_bytes = 1; | |
316 | else | |
317 | sign_bytes = 0; | |
318 | /* XXX implement the ' ' flag */ | |
319 | /* calculate number of bytes of padding */ | |
320 | if(c->flags & f_width) { | |
321 | if((pad = c->width - (ndigits + prec + xform + sign_bytes)) < 0) | |
322 | pad = 0; | |
323 | } else | |
324 | pad = 0; | |
325 | /* now we are ready to output. Possibilities are: | |
326 | * [space pad][sign][xform][0 prec]digits | |
327 | * [sign][xform][0 pad][0 prec]digits | |
328 | * [sign][xform][0 prec]digits[space pad] | |
329 | * | |
330 | * '-' beats '0'. | |
331 | */ | |
332 | if(c->flags & f_left) { | |
681cb9fb RK |
333 | if(sign && do_write(s, &sign, 1)) |
334 | return -1; | |
335 | if(xform && do_write(s, c->specifier->xform, xform)) | |
336 | return -1; | |
337 | if(prec && do_pad(s, '0', prec) < 0) | |
338 | return -1; | |
339 | if(ndigits && do_write(s, digits + dp, ndigits)) | |
340 | return -1; | |
341 | if(pad && do_pad(s, ' ', pad) < 0) | |
342 | return -1; | |
460b9539 | 343 | } else if(c->flags & f_zero) { |
681cb9fb RK |
344 | if(sign && do_write(s, &sign, 1)) |
345 | return -1; | |
346 | if(xform && do_write(s, c->specifier->xform, xform)) | |
347 | return -1; | |
348 | if(pad && do_pad(s, '0', pad) < 0) | |
349 | return -1; | |
350 | if(prec && do_pad(s, '0', prec) < 0) | |
351 | return -1; | |
352 | if(ndigits && do_write(s, digits + dp, ndigits)) | |
353 | return -1; | |
460b9539 | 354 | } else { |
681cb9fb RK |
355 | if(pad && do_pad(s, ' ', pad) < 0) |
356 | return -1; | |
357 | if(sign && do_write(s, &sign, 1)) | |
358 | return -1; | |
359 | if(xform && do_write(s, c->specifier->xform, xform)) | |
360 | return -1; | |
361 | if(prec && do_pad(s, '0', prec) < 0) | |
362 | return -1; | |
363 | if(ndigits && do_write(s, digits + dp, ndigits)) | |
364 | return -1; | |
460b9539 | 365 | } |
366 | return 0; | |
367 | } | |
368 | ||
369 | static int output_string(struct state *s, struct conversion *c) { | |
370 | const char *str, *n; | |
371 | int pad, len; | |
372 | ||
6a6b327d | 373 | str = va_arg(s->ap, const char *); |
460b9539 | 374 | if(c->flags & f_precision) { |
375 | if((n = memchr(str, 0, c->precision))) | |
376 | len = n - str; | |
377 | else | |
378 | len = c->precision; | |
379 | } else | |
380 | len = strlen(str); | |
381 | if(c->flags & f_width) { | |
382 | if((pad = c->width - len) < 0) | |
383 | pad = 0; | |
384 | } else | |
385 | pad = 0; | |
386 | if(c->flags & f_left) { | |
681cb9fb RK |
387 | if(do_write(s, str, len) < 0) |
388 | return -1; | |
389 | if(pad && do_pad(s, ' ', pad) < 0) | |
390 | return -1; | |
460b9539 | 391 | } else { |
681cb9fb RK |
392 | if(pad && do_pad(s, ' ', pad) < 0) |
393 | return -1; | |
394 | if(do_write(s, str, len) < 0) | |
395 | return -1; | |
460b9539 | 396 | } |
397 | return 0; | |
398 | ||
399 | } | |
400 | ||
401 | static int output_char(struct state *s, struct conversion *c) { | |
402 | int pad; | |
403 | char ch; | |
404 | ||
6a6b327d | 405 | ch = va_arg(s->ap, int); |
460b9539 | 406 | if(c->flags & f_width) { |
407 | if((pad = c->width - 1) < 0) | |
408 | pad = 0; | |
409 | } else | |
410 | pad = 0; | |
411 | if(c->flags & f_left) { | |
681cb9fb RK |
412 | if(do_write(s, &ch, 1) < 0) |
413 | return -1; | |
414 | if(pad && do_pad(s, ' ', pad) < 0) | |
415 | return -1; | |
460b9539 | 416 | } else { |
681cb9fb RK |
417 | if(pad && do_pad(s, ' ', pad) < 0) |
418 | return -1; | |
419 | if(do_write(s, &ch, 1) < 0) | |
420 | return -1; | |
460b9539 | 421 | } |
422 | return 0; | |
423 | } | |
424 | ||
425 | static int output_count(struct state *s, struct conversion *c) { | |
426 | switch(c->length) { | |
6a6b327d RK |
427 | case 0: *va_arg(s->ap, int *) = s->bytes; break; |
428 | case l_char: *va_arg(s->ap, signed char *) = s->bytes; break; | |
429 | case l_short: *va_arg(s->ap, short *) = s->bytes; break; | |
430 | case l_long: *va_arg(s->ap, long *) = s->bytes; break; | |
431 | case l_longlong: *va_arg(s->ap, long_long *) = s->bytes; break; | |
432 | case l_intmax_t: *va_arg(s->ap, intmax_t *) = s->bytes; break; | |
433 | case l_size_t: *va_arg(s->ap, ssize_t *) = s->bytes; break; | |
434 | case l_ptrdiff_t: *va_arg(s->ap, ptrdiff_t *) = s->bytes; break; | |
460b9539 | 435 | default: abort(); |
436 | } | |
437 | return 0; | |
438 | } | |
439 | ||
440 | /* table of conversion specifiers */ | |
441 | static const struct specifier specifiers[] = { | |
442 | /* XXX don't support floating point conversions */ | |
443 | { '%', check_percent, output_percent, 0, 0, 0 }, | |
444 | { 'X', check_integer, output_integer, 16, "0123456789ABCDEF", "0X" }, | |
445 | { 'c', check_string, output_char, 0, 0, 0 }, | |
446 | { 'd', check_integer, output_integer, -10, "0123456789", 0 }, | |
447 | { 'i', check_integer, output_integer, -10, "0123456789", 0 }, | |
448 | { 'n', check_integer, output_count, 0, 0, 0 }, | |
449 | { 'o', check_integer, output_integer, 8, "01234567", 0 }, | |
450 | { 'p', check_pointer, output_integer, 16, "0123456789abcdef", "0x" }, | |
451 | { 's', check_string, output_string, 0, 0, 0 }, | |
452 | { 'u', check_integer, output_integer, 10, "0123456789", 0 }, | |
453 | { 'x', check_integer, output_integer, 16, "0123456789abcdef", "0x" }, | |
454 | }; | |
455 | ||
456 | /* collect and check information about a conversion specification */ | |
457 | static int parse_conversion(struct conversion *c, const char *ptr) { | |
458 | int n, ch, l, r, m; | |
459 | const char *q, *start = ptr; | |
460 | ||
461 | memset(c, 0, sizeof *c); | |
462 | /* flags */ | |
463 | while(*ptr && (q = strchr(flags, *ptr))) { | |
464 | c->flags |= (1 << (q - flags)); | |
465 | ++ptr; | |
466 | } | |
467 | /* minimum field width */ | |
468 | if(*ptr >= '0' && *ptr <= '9') { | |
681cb9fb RK |
469 | if((n = get_integer(&c->width, ptr)) < 0) |
470 | return -1; | |
460b9539 | 471 | ptr += n; |
472 | c->flags |= f_width; | |
473 | } else if(*ptr == '*') { | |
474 | ++ptr; | |
475 | c->width = -1; | |
476 | c->flags |= f_width; | |
477 | } | |
478 | /* precision */ | |
479 | if(*ptr == '.') { | |
480 | ++ptr; | |
481 | if(*ptr >= '0' && *ptr <= '9') { | |
681cb9fb RK |
482 | if((n = get_integer(&c->precision, ptr)) < 0) |
483 | return -1; | |
460b9539 | 484 | ptr += n; |
485 | } else if(*ptr == '*') { | |
486 | ++ptr; | |
487 | c->precision = -1; | |
488 | } else | |
0c740e59 | 489 | c->precision = 0; |
460b9539 | 490 | c->flags |= f_precision; |
491 | } | |
492 | /* length modifier */ | |
493 | switch(ch = *ptr++) { | |
494 | case 'h': | |
681cb9fb RK |
495 | if((ch = *ptr++) == 'h') { |
496 | c->length = l_char; | |
497 | ch = *ptr++; | |
498 | } | |
499 | else | |
500 | c->length = l_short; | |
460b9539 | 501 | break; |
502 | case 'l': | |
681cb9fb RK |
503 | if((ch = *ptr++) == 'l') { |
504 | c->length = l_longlong; | |
505 | ch = *ptr++; | |
506 | } | |
507 | else | |
508 | c->length = l_long; | |
460b9539 | 509 | break; |
510 | case 'q': c->length = l_longlong; ch = *ptr++; break; | |
511 | case 'j': c->length = l_intmax_t; ch = *ptr++; break; | |
512 | case 'z': c->length = l_size_t; ch = *ptr++; break; | |
513 | case 't': c->length = l_ptrdiff_t; ch = *ptr++; break; | |
514 | case 'L': c->length = l_longdouble; ch = *ptr++; break; | |
515 | } | |
516 | /* conversion specifier */ | |
517 | l = 0; | |
518 | r = sizeof specifiers / sizeof *specifiers; | |
519 | while(l <= r && (specifiers[m = (l + r) / 2].ch != ch)) | |
681cb9fb RK |
520 | if(ch < specifiers[m].ch) |
521 | r = m - 1; | |
522 | else | |
523 | l = m + 1; | |
524 | if(specifiers[m].ch != ch) | |
525 | return -1; | |
526 | if(specifiers[m].check(c)) | |
527 | return -1; | |
460b9539 | 528 | c->specifier = &specifiers[m]; |
529 | return ptr - start; | |
530 | } | |
531 | ||
532 | /* ISO/IEC 9899:1999 7.19.6.1 */ | |
533 | /* http://www.opengroup.org/onlinepubs/009695399/functions/fprintf.html */ | |
534 | ||
535 | int byte_vsinkprintf(struct sink *output, | |
536 | const char *fmt, | |
537 | va_list ap) { | |
538 | int n; | |
539 | const char *ptr; | |
540 | struct state s; | |
541 | struct conversion c; | |
542 | ||
543 | memset(&s, 0, sizeof s); | |
544 | s.output = output; | |
6a6b327d | 545 | va_copy(s.ap,ap); |
460b9539 | 546 | while(*fmt) { |
547 | /* output text up to next conversion specification */ | |
548 | for(ptr = fmt; *fmt && *fmt != '%'; ++fmt) | |
549 | ; | |
550 | if((n = fmt - ptr)) | |
681cb9fb RK |
551 | if(do_write(&s, ptr, n) < 0) |
552 | goto error; | |
460b9539 | 553 | if(!*fmt) |
554 | break; | |
555 | ++fmt; | |
556 | /* parse conversion */ | |
681cb9fb RK |
557 | if((n = parse_conversion(&c, fmt)) < 0) |
558 | goto error; | |
460b9539 | 559 | fmt += n; |
560 | /* fill in width and precision */ | |
561 | if((c.flags & f_width) && c.width == -1) | |
6a6b327d | 562 | if((c.width = va_arg(s.ap, int)) < 0) { |
460b9539 | 563 | c.width = -c.width; |
564 | c.flags |= f_left; | |
565 | } | |
566 | if((c.flags & f_precision) && c.precision == -1) | |
6a6b327d | 567 | if((c.precision = va_arg(s.ap, int)) < 0) |
460b9539 | 568 | c.flags ^= f_precision; |
569 | /* generate the output */ | |
681cb9fb RK |
570 | if(c.specifier->output(&s, &c) < 0) |
571 | goto error; | |
460b9539 | 572 | } |
6a6b327d | 573 | va_end(s.ap); |
460b9539 | 574 | return s.bytes; |
6a6b327d RK |
575 | error: |
576 | va_end(s.ap); | |
577 | return -1; | |
460b9539 | 578 | } |
579 | ||
681cb9fb RK |
580 | int byte_sinkprintf(struct sink *output, const char *fmt, ...) { |
581 | int n; | |
582 | va_list ap; | |
583 | ||
584 | va_start(ap, fmt); | |
585 | n = byte_vsinkprintf(output, fmt, ap); | |
586 | va_end(ap); | |
587 | return n; | |
588 | } | |
589 | ||
460b9539 | 590 | /* |
591 | Local Variables: | |
592 | c-basic-offset:2 | |
593 | comment-column:40 | |
594 | End: | |
595 | */ |