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