blob: b66609ac79beb5768421dcbdbdf301883ab1db0a [file] [log] [blame]
Elliott Hughesbb0c2d52014-01-07 17:34:14 -08001/*
2 * Taken from Linux kernel's linux/lib/vsprintf.c
3 * and somewhat simplified.
4 *
5 * Copyright (C) 1991, 1992 Linus Torvalds
6 */
7/* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
8/*
9 * Wirzenius wrote this portably, Torvalds fucked it up :-)
10 */
11
12#include "defs.h"
13
14#ifdef USE_CUSTOM_PRINTF
15
16#include <stdarg.h>
17#include <limits.h>
18
19#define noinline_for_stack /*nothing*/
20#define likely(expr) (expr)
21#define unlikely(expr) (expr)
22
23#define do_div(n, d) ({ __typeof(num) t = n % d; n /= d; t; })
24
25#undef isdigit
26#define isdigit(a) ((unsigned char)((a) - '0') <= 9)
27
28static inline
29int skip_atoi(const char **s)
30{
31 int i = 0;
32 const char *p = *s;
33
34 while (isdigit(*p))
35 i = i*10 + *p++ - '0';
36
37 *s = p;
38 return i;
39}
40
41/* Decimal conversion is by far the most typical, and is used
42 * for /proc and /sys data. This directly impacts e.g. top performance
43 * with many processes running. We optimize it for speed
44 * using ideas described at <http://www.cs.uiowa.edu/~jones/bcd/divide.html>
45 * (with permission from the author, Douglas W. Jones).
46 */
47
48#if LONG_MAX != 0x7fffffffUL || LLONG_MAX != 0x7fffffffffffffffULL
49/* Formats correctly any integer in [0, 999999999] */
50static noinline_for_stack
51char *put_dec_full9(char *buf, unsigned q)
52{
53 unsigned r;
54
55 /* Possible ways to approx. divide by 10
56 * (x * 0x1999999a) >> 32 x < 1073741829 (multiply must be 64-bit)
57 * (x * 0xcccd) >> 19 x < 81920 (x < 262149 when 64-bit mul)
58 * (x * 0x6667) >> 18 x < 43699
59 * (x * 0x3334) >> 17 x < 16389
60 * (x * 0x199a) >> 16 x < 16389
61 * (x * 0x0ccd) >> 15 x < 16389
62 * (x * 0x0667) >> 14 x < 2739
63 * (x * 0x0334) >> 13 x < 1029
64 * (x * 0x019a) >> 12 x < 1029
65 * (x * 0x00cd) >> 11 x < 1029 shorter code than * 0x67 (on i386)
66 * (x * 0x0067) >> 10 x < 179
67 * (x * 0x0034) >> 9 x < 69 same
68 * (x * 0x001a) >> 8 x < 69 same
69 * (x * 0x000d) >> 7 x < 69 same, shortest code (on i386)
70 * (x * 0x0007) >> 6 x < 19
71 * See <http://www.cs.uiowa.edu/~jones/bcd/divide.html>
72 */
73 r = (q * (uint64_t)0x1999999a) >> 32;
74 *buf++ = (q - 10 * r) + '0'; /* 1 */
75 q = (r * (uint64_t)0x1999999a) >> 32;
76 *buf++ = (r - 10 * q) + '0'; /* 2 */
77 r = (q * (uint64_t)0x1999999a) >> 32;
78 *buf++ = (q - 10 * r) + '0'; /* 3 */
79 q = (r * (uint64_t)0x1999999a) >> 32;
80 *buf++ = (r - 10 * q) + '0'; /* 4 */
81 r = (q * (uint64_t)0x1999999a) >> 32;
82 *buf++ = (q - 10 * r) + '0'; /* 5 */
83 /* Now value is under 10000, can avoid 64-bit multiply */
84 q = (r * 0x199a) >> 16;
85 *buf++ = (r - 10 * q) + '0'; /* 6 */
86 r = (q * 0xcd) >> 11;
87 *buf++ = (q - 10 * r) + '0'; /* 7 */
88 q = (r * 0xcd) >> 11;
89 *buf++ = (r - 10 * q) + '0'; /* 8 */
90 *buf++ = q + '0'; /* 9 */
91 return buf;
92}
93#endif
94
95/* Similar to above but do not pad with zeros.
96 * Code can be easily arranged to print 9 digits too, but our callers
97 * always call put_dec_full9() instead when the number has 9 decimal digits.
98 */
99static noinline_for_stack
100char *put_dec_trunc8(char *buf, unsigned r)
101{
102 unsigned q;
103
104 /* Copy of previous function's body with added early returns */
105 q = (r * (uint64_t)0x1999999a) >> 32;
106 *buf++ = (r - 10 * q) + '0'; /* 2 */
107 if (q == 0) return buf;
108 r = (q * (uint64_t)0x1999999a) >> 32;
109 *buf++ = (q - 10 * r) + '0'; /* 3 */
110 if (r == 0) return buf;
111 q = (r * (uint64_t)0x1999999a) >> 32;
112 *buf++ = (r - 10 * q) + '0'; /* 4 */
113 if (q == 0) return buf;
114 r = (q * (uint64_t)0x1999999a) >> 32;
115 *buf++ = (q - 10 * r) + '0'; /* 5 */
116 if (r == 0) return buf;
117 q = (r * 0x199a) >> 16;
118 *buf++ = (r - 10 * q) + '0'; /* 6 */
119 if (q == 0) return buf;
120 r = (q * 0xcd) >> 11;
121 *buf++ = (q - 10 * r) + '0'; /* 7 */
122 if (r == 0) return buf;
123 q = (r * 0xcd) >> 11;
124 *buf++ = (r - 10 * q) + '0'; /* 8 */
125 if (q == 0) return buf;
126 *buf++ = q + '0'; /* 9 */
127 return buf;
128}
129
130/* There are two algorithms to print larger numbers.
131 * One is generic: divide by 1000000000 and repeatedly print
132 * groups of (up to) 9 digits. It's conceptually simple,
133 * but requires a (unsigned long long) / 1000000000 division.
134 *
135 * Second algorithm splits 64-bit unsigned long long into 16-bit chunks,
136 * manipulates them cleverly and generates groups of 4 decimal digits.
137 * It so happens that it does NOT require long long division.
138 *
139 * If long is > 32 bits, division of 64-bit values is relatively easy,
140 * and we will use the first algorithm.
141 * If long long is > 64 bits (strange architecture with VERY large long long),
142 * second algorithm can't be used, and we again use the first one.
143 *
144 * Else (if long is 32 bits and long long is 64 bits) we use second one.
145 */
146
147#if LONG_MAX != 0x7fffffffUL || LLONG_MAX != 0x7fffffffffffffffULL
148
149/* First algorithm: generic */
150
151static
152char *put_dec(char *buf, unsigned long long n)
153{
154 if (n >= 100*1000*1000) {
155 while (n >= 1000*1000*1000)
156 buf = put_dec_full9(buf, do_div(n, 1000*1000*1000));
157 if (n >= 100*1000*1000)
158 return put_dec_full9(buf, n);
159 }
160 return put_dec_trunc8(buf, n);
161}
162
163#else
164
165/* Second algorithm: valid only for 64-bit long longs */
166
167static noinline_for_stack
168char *put_dec_full4(char *buf, unsigned q)
169{
170 unsigned r;
171 r = (q * 0xcccd) >> 19;
172 *buf++ = (q - 10 * r) + '0';
173 q = (r * 0x199a) >> 16;
174 *buf++ = (r - 10 * q) + '0';
175 r = (q * 0xcd) >> 11;
176 *buf++ = (q - 10 * r) + '0';
177 *buf++ = r + '0';
178 return buf;
179}
180
181/* Based on code by Douglas W. Jones found at
182 * <http://www.cs.uiowa.edu/~jones/bcd/decimal.html#sixtyfour>
183 * (with permission from the author).
184 * Performs no 64-bit division and hence should be fast on 32-bit machines.
185 */
186static
187char *put_dec(char *buf, unsigned long long n)
188{
189 uint32_t d3, d2, d1, q, h;
190
191 if (n < 100*1000*1000)
192 return put_dec_trunc8(buf, n);
193
194 d1 = ((uint32_t)n >> 16); /* implicit "& 0xffff" */
195 h = (n >> 32);
196 d2 = (h ) & 0xffff;
197 d3 = (h >> 16); /* implicit "& 0xffff" */
198
199 q = 656 * d3 + 7296 * d2 + 5536 * d1 + ((uint32_t)n & 0xffff);
200
201 buf = put_dec_full4(buf, q % 10000);
202 q = q / 10000;
203
204 d1 = q + 7671 * d3 + 9496 * d2 + 6 * d1;
205 buf = put_dec_full4(buf, d1 % 10000);
206 q = d1 / 10000;
207
208 d2 = q + 4749 * d3 + 42 * d2;
209 buf = put_dec_full4(buf, d2 % 10000);
210 q = d2 / 10000;
211
212 d3 = q + 281 * d3;
213 if (!d3)
214 goto done;
215 buf = put_dec_full4(buf, d3 % 10000);
216 q = d3 / 10000;
217 if (!q)
218 goto done;
219 buf = put_dec_full4(buf, q);
220 done:
221 while (buf[-1] == '0')
222 --buf;
223
224 return buf;
225}
226
227#endif
228
229/*
230 * For strace, the following formats are not supported:
231 * %h[h]u, %zu, %tu - use [unsigned] int/long/long long fmt instead
232 * %8.4u - no precision field for integers allowed (ok for strings)
233 * %+d, % d - no forced sign or force "space positive" sign
234 * %-07u - use %-7u instead
235 * %X - works as %x
236 */
237
238#define ZEROPAD 1 /* pad with zero */
239#define SIGN 2 /* unsigned/signed long */
240//#define PLUS 4 /* show plus */
241//#define SPACE 8 /* space if plus */
242#define LEFT 16 /* left justified */
243//#deefine SMALL 32 /* use lowercase in hex (must be 32 == 0x20) */
244#define SPECIAL 64 /* prefix hex with "0x", octal with "0" */
245
246enum format_type {
247 FORMAT_TYPE_NONE, /* Just a string part */
248 FORMAT_TYPE_WIDTH,
249 FORMAT_TYPE_PRECISION,
250 FORMAT_TYPE_CHAR,
251 FORMAT_TYPE_STR,
252 FORMAT_TYPE_PTR,
253 FORMAT_TYPE_PERCENT_CHAR,
254 FORMAT_TYPE_INVALID,
255 FORMAT_TYPE_LONG_LONG,
256 FORMAT_TYPE_ULONG,
257 FORMAT_TYPE_LONG,
258 FORMAT_TYPE_UINT,
259 FORMAT_TYPE_INT,
260};
261
262struct printf_spec {
263 uint8_t type; /* format_type enum */
264 uint8_t flags; /* flags to number() */
265 uint8_t base; /* number base, 8, 10 or 16 only */
266 uint8_t qualifier; /* number qualifier, one of 'hHlLtzZ' */
267 int field_width; /* width of output field */
268 int precision; /* # of digits/chars */
269};
270
271static noinline_for_stack
272char *number(char *buf, char *end, unsigned long long num,
273 struct printf_spec spec)
274{
275 /* we are called with base 8, 10 or 16, only, thus don't need "G..." */
276 static const char digits[16] = "0123456789abcdef"; /* "GHIJKLMNOPQRSTUVWXYZ"; */
277
278 char tmp[sizeof(long long)*3 + 4];
279 char sign;
280 int need_pfx = ((spec.flags & SPECIAL) && spec.base != 10);
281 int i;
282
283 /* We may overflow the buf. Crudely check for it */
284 i = sizeof(long long)*3 + 4;
285 if (i < spec.field_width)
286 i = spec.field_width;
287 if ((end - buf) <= i)
288 return buf + i;
289
290//we don't use formats like "%-07u"
291// if (spec.flags & LEFT)
292// spec.flags &= ~ZEROPAD;
293 sign = 0;
294 if (spec.flags & SIGN) {
295 if ((signed long long)num < 0) {
296 sign = '-';
297 num = -(signed long long)num;
298 spec.field_width--;
299// } else if (spec.flags & PLUS) {
300// sign = '+';
301// spec.field_width--;
302// } else if (spec.flags & SPACE) {
303// sign = ' ';
304// spec.field_width--;
305 }
306 }
307 if (need_pfx) {
308 spec.field_width--;
309 if (spec.base == 16)
310 spec.field_width--;
311 }
312
313 /* generate full string in tmp[], in reverse order */
314 i = 0;
315 if (num < spec.base)
316 tmp[i++] = digits[num];
317 /* Generic code, for any base:
318 else do {
319 tmp[i++] = (digits[do_div(num,base)]);
320 } while (num != 0);
321 */
322 else if (spec.base != 10) { /* 8 or 16 */
323 int mask = spec.base - 1;
324 int shift = 3;
325
326 if (spec.base == 16)
327 shift = 4;
328 do {
329 tmp[i++] = digits[((unsigned char)num) & mask];
330 num >>= shift;
331 } while (num);
332 } else { /* base 10 */
333 i = put_dec(tmp, num) - tmp;
334 }
335
336//spec.precision is assumed 0 ("not specified")
337// /* printing 100 using %2d gives "100", not "00" */
338// if (i > spec.precision)
339// spec.precision = i;
340// /* leading space padding */
341// spec.field_width -= spec.precision;
342 spec.field_width -= i;
343 if (!(spec.flags & (ZEROPAD+LEFT))) {
344 while (--spec.field_width >= 0) {
345 ///if (buf < end)
346 *buf = ' ';
347 ++buf;
348 }
349 }
350 /* sign */
351 if (sign) {
352 ///if (buf < end)
353 *buf = sign;
354 ++buf;
355 }
356 /* "0x" / "0" prefix */
357 if (need_pfx) {
358 ///if (buf < end)
359 *buf = '0';
360 ++buf;
361 if (spec.base == 16) {
362 ///if (buf < end)
363 *buf = 'x';
364 ++buf;
365 }
366 }
367 /* zero or space padding */
368 if (!(spec.flags & LEFT)) {
369 char c = (spec.flags & ZEROPAD) ? '0' : ' ';
370 while (--spec.field_width >= 0) {
371 ///if (buf < end)
372 *buf = c;
373 ++buf;
374 }
375 }
376// /* hmm even more zero padding? */
377// while (i <= --spec.precision) {
378// ///if (buf < end)
379// *buf = '0';
380// ++buf;
381// }
382 /* actual digits of result */
383 while (--i >= 0) {
384 ///if (buf < end)
385 *buf = tmp[i];
386 ++buf;
387 }
388 /* trailing space padding */
389 while (--spec.field_width >= 0) {
390 ///if (buf < end)
391 *buf = ' ';
392 ++buf;
393 }
394
395 return buf;
396}
397
398static noinline_for_stack
399char *string(char *buf, char *end, const char *s, struct printf_spec spec)
400{
401 int len, i;
402
403 if (!s)
404 s = "(null)";
405
406 len = strnlen(s, spec.precision);
407
408 /* We may overflow the buf. Crudely check for it */
409 i = len;
410 if (i < spec.field_width)
411 i = spec.field_width;
412 if ((end - buf) <= i)
413 return buf + i;
414
415 if (!(spec.flags & LEFT)) {
416 while (len < spec.field_width--) {
417 ///if (buf < end)
418 *buf = ' ';
419 ++buf;
420 }
421 }
422 for (i = 0; i < len; ++i) {
423 ///if (buf < end)
424 *buf = *s;
425 ++buf; ++s;
426 }
427 while (len < spec.field_width--) {
428 ///if (buf < end)
429 *buf = ' ';
430 ++buf;
431 }
432
433 return buf;
434}
435
436static noinline_for_stack
437char *pointer(const char *fmt, char *buf, char *end, void *ptr,
438 struct printf_spec spec)
439{
440// spec.flags |= SMALL;
441 if (spec.field_width == -1) {
442 spec.field_width = 2 * sizeof(void *);
443 spec.flags |= ZEROPAD;
444 }
445 spec.base = 16;
446
447 return number(buf, end, (unsigned long) ptr, spec);
448}
449
450/*
451 * Helper function to decode printf style format.
452 * Each call decode a token from the format and return the
453 * number of characters read (or likely the delta where it wants
454 * to go on the next call).
455 * The decoded token is returned through the parameters
456 *
457 * 'h', 'l', or 'L' for integer fields
458 * 'z' support added 23/7/1999 S.H.
459 * 'z' changed to 'Z' --davidm 1/25/99
460 * 't' added for ptrdiff_t
461 *
462 * @fmt: the format string
463 * @type of the token returned
464 * @flags: various flags such as +, -, # tokens..
465 * @field_width: overwritten width
466 * @base: base of the number (octal, hex, ...)
467 * @precision: precision of a number
468 * @qualifier: qualifier of a number (long, size_t, ...)
469 */
470static noinline_for_stack
471int format_decode(const char *fmt, struct printf_spec *spec)
472{
473 const char *start = fmt;
474
475 /* we finished early by reading the field width */
476 if (spec->type == FORMAT_TYPE_WIDTH) {
477 if (spec->field_width < 0) {
478 spec->field_width = -spec->field_width;
479 spec->flags |= LEFT;
480 }
481 spec->type = FORMAT_TYPE_NONE;
482 goto precision;
483 }
484
485 /* we finished early by reading the precision */
486 if (spec->type == FORMAT_TYPE_PRECISION) {
487 if (spec->precision < 0)
488 spec->precision = 0;
489
490 spec->type = FORMAT_TYPE_NONE;
491 goto qualifier;
492 }
493
494 /* By default */
495 spec->type = FORMAT_TYPE_NONE;
496
497 for (;;) {
498 if (*fmt == '\0')
499 return fmt - start;
500 if (*fmt == '%')
501 break;
502 ++fmt;
503 }
504
505 /* Return the current non-format string */
506 if (fmt != start)
507 return fmt - start;
508
509 /* Process flags */
510 spec->flags = 0;
511
512 while (1) { /* this also skips first '%' */
513 bool found = true;
514
515 ++fmt;
516
517 switch (*fmt) {
518 case '-': spec->flags |= LEFT; break;
519// case '+': spec->flags |= PLUS; break;
520// case ' ': spec->flags |= SPACE; break;
521 case '#': spec->flags |= SPECIAL; break;
522 case '0': spec->flags |= ZEROPAD; break;
523 default: found = false;
524 }
525
526 if (!found)
527 break;
528 }
529
530 /* get field width */
531 spec->field_width = -1;
532
533 if (isdigit(*fmt))
534 spec->field_width = skip_atoi(&fmt);
535 else if (*fmt == '*') {
536 /* it's the next argument */
537 spec->type = FORMAT_TYPE_WIDTH;
538 return ++fmt - start;
539 }
540
541precision:
542 /* get the precision */
543 spec->precision = -1;
544 if (*fmt == '.') {
545 ++fmt;
546 if (isdigit(*fmt)) {
547 spec->precision = skip_atoi(&fmt);
548// if (spec->precision < 0)
549// spec->precision = 0;
550 } else if (*fmt == '*') {
551 /* it's the next argument */
552 spec->type = FORMAT_TYPE_PRECISION;
553 return ++fmt - start;
554 }
555 }
556
557qualifier:
558 /* get the conversion qualifier */
559 spec->qualifier = -1;
560 if (*fmt == 'l') {
561 spec->qualifier = *fmt++;
562 if (unlikely(spec->qualifier == *fmt)) {
563 spec->qualifier = 'L';
564 ++fmt;
565 }
566 }
567
568 /* default base */
569 spec->base = 10;
570 switch (*fmt) {
571 case 'c':
572 spec->type = FORMAT_TYPE_CHAR;
573 return ++fmt - start;
574
575 case 's':
576 spec->type = FORMAT_TYPE_STR;
577 return ++fmt - start;
578
579 case 'p':
580 spec->type = FORMAT_TYPE_PTR;
581 return ++fmt - start;
582
583 case '%':
584 spec->type = FORMAT_TYPE_PERCENT_CHAR;
585 return ++fmt - start;
586
587 /* integer number formats - set up the flags and "break" */
588 case 'o':
589 spec->base = 8;
590 break;
591
592 case 'x':
593// spec->flags |= SMALL;
594
595 case 'X':
596 spec->base = 16;
597 break;
598
599 case 'd':
600 case 'i':
601 spec->flags |= SIGN;
602 case 'u':
603 break;
604
605 default:
606 spec->type = FORMAT_TYPE_INVALID;
607 return fmt - start;
608 }
609
610 if (spec->qualifier == 'L')
611 spec->type = FORMAT_TYPE_LONG_LONG;
612 else if (spec->qualifier == 'l') {
613 if (spec->flags & SIGN)
614 spec->type = FORMAT_TYPE_LONG;
615 else
616 spec->type = FORMAT_TYPE_ULONG;
617 } else {
618 if (spec->flags & SIGN)
619 spec->type = FORMAT_TYPE_INT;
620 else
621 spec->type = FORMAT_TYPE_UINT;
622 }
623
624 return ++fmt - start;
625}
626
627/**
628 * vsnprintf - Format a string and place it in a buffer
629 * @buf: The buffer to place the result into
630 * @size: The size of the buffer, including the trailing null space
631 * @fmt: The format string to use
632 * @args: Arguments for the format string
633 *
634 * The return value is the number of characters which would
635 * be generated for the given input, excluding the trailing
636 * '\0', as per ISO C99. If you want to have the exact
637 * number of characters written into @buf as return value
638 * (not including the trailing '\0'), use vscnprintf(). If the
639 * return is greater than or equal to @size, the resulting
640 * string is truncated.
641 *
642 * If you're not already dealing with a va_list consider using snprintf().
643 */
644static
645int kernel_vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
646{
647 unsigned long long num;
648 char *str, *end;
649 struct printf_spec spec = {0};
650
651 str = buf;
652 end = buf + size;
653
654 while (*fmt) {
655 const char *old_fmt = fmt;
656 int read = format_decode(fmt, &spec);
657
658 fmt += read;
659
660 switch (spec.type) {
661 case FORMAT_TYPE_NONE: {
662 int copy = read;
663 if (str < end) {
664 if (copy > end - str)
665 copy = end - str;
666 memcpy(str, old_fmt, copy);
667 }
668 str += read;
669 break;
670 }
671
672 case FORMAT_TYPE_WIDTH:
673 spec.field_width = va_arg(args, int);
674 break;
675
676 case FORMAT_TYPE_PRECISION:
677 spec.precision = va_arg(args, int);
678 break;
679
680 case FORMAT_TYPE_CHAR: {
681 char c;
682
683 if (!(spec.flags & LEFT)) {
684 while (--spec.field_width > 0) {
685 if (str < end)
686 *str = ' ';
687 ++str;
688
689 }
690 }
691 c = (unsigned char) va_arg(args, int);
692 if (str < end)
693 *str = c;
694 ++str;
695 while (--spec.field_width > 0) {
696 if (str < end)
697 *str = ' ';
698 ++str;
699 }
700 break;
701 }
702
703 case FORMAT_TYPE_STR:
704 str = string(str, end, va_arg(args, char *), spec);
705 break;
706
707 case FORMAT_TYPE_PTR:
708 str = pointer(fmt+1, str, end, va_arg(args, void *),
709 spec);
710// while (isalnum(*fmt))
711// fmt++;
712 break;
713
714 case FORMAT_TYPE_PERCENT_CHAR:
715 if (str < end)
716 *str = '%';
717 ++str;
718 break;
719
720 case FORMAT_TYPE_INVALID:
721 if (str < end)
722 *str = '%';
723 ++str;
724 break;
725
726 default:
727 switch (spec.type) {
728 case FORMAT_TYPE_LONG_LONG:
729 num = va_arg(args, long long);
730 break;
731 case FORMAT_TYPE_ULONG:
732 num = va_arg(args, unsigned long);
733 break;
734 case FORMAT_TYPE_LONG:
735 num = va_arg(args, long);
736 break;
737 case FORMAT_TYPE_INT:
738 num = (int) va_arg(args, int);
739 break;
740 default:
741 num = va_arg(args, unsigned int);
742 }
743
744 str = number(str, end, num, spec);
745 }
746 }
747
748// if (size > 0) {
749 if (str < end)
750 *str = '\0';
751// else
752// end[-1] = '\0';
753// }
754
755 /* the trailing null byte doesn't count towards the total */
756 return str-buf;
757
758}
759
760int strace_vfprintf(FILE *fp, const char *fmt, va_list args)
761{
762 static char *buf;
763 static unsigned buflen;
764
765 int r;
766 va_list a1;
767
768 va_copy(a1, args);
769 unsigned len = kernel_vsnprintf(buf, buflen, fmt, a1);
770 va_end(a1);
771
772 if (len >= buflen) {
773 buflen = len + 256;
774 free(buf);
775 buf = malloc(buflen);
776 /*len =*/ kernel_vsnprintf(buf, buflen, fmt, args);
777 }
778
779 r = fputs_unlocked(buf, fp);
780 if (r < 0) return r;
781 return len;
782}
783
784#endif /* USE_CUSTOM_PRINTF */