1 /*
2 * linux/lib/vsprintf.c
3 *
4 * Copyright (C) 1991, 1992 Linus Torvalds
5 */
6
7 /* vsprintf.c -- Lars Wirzenius & Linus Torvalds. */
8 /*
9 * Wirzenius wrote this portably, Torvalds fucked it up :-)
10 */
11
12 /*
13 * Fri Jul 13 2001 Crutcher Dunnavant <crutcher+kernel@datastacks.com>
14 * - changed to provide snprintf and vsnprintf functions
15 * So Feb 1 16:51:32 CET 2004 Juergen Quade <quade@hsnr.de>
16 * - scnprintf and vscnprintf
17 */
18
19 #include <xen/ctype.h>
20 #include <xen/symbols.h>
21 #include <xen/lib.h>
22 #include <xen/sched.h>
23 #include <xen/livepatch.h>
24 #include <asm/div64.h>
25 #include <asm/page.h>
26
skip_atoi(const char ** s)27 static int skip_atoi(const char **s)
28 {
29 int i=0;
30
31 while (isdigit(**s))
32 i = i*10 + *((*s)++) - '0';
33 return i;
34 }
35
36 #define ZEROPAD 1 /* pad with zero */
37 #define SIGN 2 /* unsigned/signed long */
38 #define PLUS 4 /* show plus */
39 #define SPACE 8 /* space if plus */
40 #define LEFT 16 /* left justified */
41 #define SPECIAL 32 /* 0x */
42 #define LARGE 64 /* use 'ABCDEF' instead of 'abcdef' */
43
number(char * buf,const char * end,unsigned long long num,int base,int size,int precision,int type)44 static char *number(
45 char *buf, const char *end, unsigned long long num,
46 int base, int size, int precision, int type)
47 {
48 char c,sign,tmp[66];
49 const char *digits;
50 static const char small_digits[] = "0123456789abcdefghijklmnopqrstuvwxyz";
51 static const char large_digits[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ";
52 int i;
53
54 ASSERT(base >= 2 && base <= 36);
55
56 digits = (type & LARGE) ? large_digits : small_digits;
57 if (type & LEFT)
58 type &= ~ZEROPAD;
59 c = (type & ZEROPAD) ? '0' : ' ';
60 sign = 0;
61 if (type & SIGN) {
62 if ((signed long long) num < 0) {
63 sign = '-';
64 num = - (signed long long) num;
65 size--;
66 } else if (type & PLUS) {
67 sign = '+';
68 size--;
69 } else if (type & SPACE) {
70 sign = ' ';
71 size--;
72 }
73 }
74 if (type & SPECIAL) {
75 if (num == 0)
76 type &= ~SPECIAL;
77 else if (base == 16)
78 size -= 2;
79 else if (base == 8)
80 size--;
81 else
82 type &= ~SPECIAL;
83 }
84 i = 0;
85 if (num == 0)
86 tmp[i++]='0';
87 else while (num != 0)
88 tmp[i++] = digits[do_div(num,base)];
89 if (i > precision)
90 precision = i;
91 size -= precision;
92 if (!(type&(ZEROPAD+LEFT))) {
93 while(size-->0) {
94 if (buf < end)
95 *buf = ' ';
96 ++buf;
97 }
98 }
99 if (sign) {
100 if (buf < end)
101 *buf = sign;
102 ++buf;
103 }
104 if (type & SPECIAL) {
105 if (buf < end)
106 *buf = '0';
107 ++buf;
108 if (base == 16) {
109 if (buf < end)
110 *buf = digits[33];
111 ++buf;
112 }
113 }
114 if (!(type & LEFT)) {
115 while (size-- > 0) {
116 if (buf < end)
117 *buf = c;
118 ++buf;
119 }
120 }
121 while (i < precision--) {
122 if (buf < end)
123 *buf = '0';
124 ++buf;
125 }
126 while (i-- > 0) {
127 if (buf < end)
128 *buf = tmp[i];
129 ++buf;
130 }
131 while (size-- > 0) {
132 if (buf < end)
133 *buf = ' ';
134 ++buf;
135 }
136 return buf;
137 }
138
string(char * str,const char * end,const char * s,int field_width,int precision,int flags)139 static char *string(char *str, const char *end, const char *s,
140 int field_width, int precision, int flags)
141 {
142 int i, len = (precision < 0) ? strlen(s) : strnlen(s, precision);
143
144 if (!(flags & LEFT)) {
145 while (len < field_width--) {
146 if (str < end)
147 *str = ' ';
148 ++str;
149 }
150 }
151 for (i = 0; i < len; ++i) {
152 if (str < end)
153 *str = *s;
154 ++str; ++s;
155 }
156 while (len < field_width--) {
157 if (str < end)
158 *str = ' ';
159 ++str;
160 }
161
162 return str;
163 }
164
165 /* Print a bitmap as '0-3,6-15' */
print_bitmap_list(char * str,const char * end,const unsigned long * bitmap,unsigned int nr_bits)166 static char *print_bitmap_list(char *str, const char *end,
167 const unsigned long *bitmap,
168 unsigned int nr_bits)
169 {
170 /* current bit is 'cur', most recently seen range is [rbot, rtop] */
171 unsigned int cur, rbot, rtop;
172 bool first = true;
173
174 rbot = cur = find_first_bit(bitmap, nr_bits);
175 while ( cur < nr_bits )
176 {
177 rtop = cur;
178 cur = find_next_bit(bitmap, nr_bits, cur + 1);
179
180 if ( cur < nr_bits && cur <= rtop + 1 )
181 continue;
182
183 if ( !first )
184 {
185 if ( str < end )
186 *str = ',';
187 str++;
188 }
189 first = false;
190
191 str = number(str, end, rbot, 10, -1, -1, 0);
192 if ( rbot < rtop )
193 {
194 if ( str < end )
195 *str = '-';
196 str++;
197
198 str = number(str, end, rtop, 10, -1, -1, 0);
199 }
200
201 rbot = cur;
202 }
203
204 return str;
205 }
206
207 /* Print a bitmap as a comma separated hex string. */
print_bitmap_string(char * str,const char * end,const unsigned long * bitmap,unsigned int nr_bits)208 static char *print_bitmap_string(char *str, const char *end,
209 const unsigned long *bitmap,
210 unsigned int nr_bits)
211 {
212 const unsigned int CHUNKSZ = 32;
213 unsigned int chunksz;
214 int i;
215 bool first = true;
216
217 chunksz = nr_bits & (CHUNKSZ - 1);
218 if ( chunksz == 0 )
219 chunksz = CHUNKSZ;
220
221 /*
222 * First iteration copes with the trailing partial word if nr_bits isn't a
223 * round multiple of CHUNKSZ. All subsequent iterations work on a
224 * complete CHUNKSZ block.
225 */
226 for ( i = ROUNDUP(nr_bits, CHUNKSZ) - CHUNKSZ; i >= 0; i -= CHUNKSZ )
227 {
228 unsigned int chunkmask = (1ULL << chunksz) - 1;
229 unsigned int word = i / BITS_PER_LONG;
230 unsigned int offset = i % BITS_PER_LONG;
231 unsigned long val = (bitmap[word] >> offset) & chunkmask;
232
233 if ( !first )
234 {
235 if ( str < end )
236 *str = ',';
237 str++;
238 }
239 first = false;
240
241 str = number(str, end, val, 16, DIV_ROUND_UP(chunksz, 4), -1, ZEROPAD);
242
243 chunksz = CHUNKSZ;
244 }
245
246 return str;
247 }
248
249 /* Print a domain id, using names for system domains. (e.g. d0 or d[IDLE]) */
print_domain(char * str,const char * end,const struct domain * d)250 static char *print_domain(char *str, const char *end, const struct domain *d)
251 {
252 const char *name = NULL;
253
254 /* Some debugging may have an optionally-NULL pointer. */
255 if ( unlikely(!d) )
256 return string(str, end, "NULL", -1, -1, 0);
257
258 switch ( d->domain_id )
259 {
260 case DOMID_IO: name = "[IO]"; break;
261 case DOMID_XEN: name = "[XEN]"; break;
262 case DOMID_COW: name = "[COW]"; break;
263 case DOMID_IDLE: name = "[IDLE]"; break;
264 /*
265 * In principle, we could ASSERT_UNREACHABLE() in the default case.
266 * However, this path is used to print out crash information, which
267 * risks recursing infinitely and not printing any useful information.
268 */
269 }
270
271 if ( str < end )
272 *str = 'd';
273
274 if ( name )
275 return string(str + 1, end, name, -1, -1, 0);
276 else
277 return number(str + 1, end, d->domain_id, 10, -1, -1, 0);
278 }
279
280 /* Print a vcpu id. (e.g. d0v1 or d[IDLE]v0) */
print_vcpu(char * str,const char * end,const struct vcpu * v)281 static char *print_vcpu(char *str, const char *end, const struct vcpu *v)
282 {
283 /* Some debugging may have an optionally-NULL pointer. */
284 if ( unlikely(!v) )
285 return string(str, end, "NULL", -1, -1, 0);
286
287 str = print_domain(str, end, v->domain);
288
289 if ( str < end )
290 *str = 'v';
291
292 return number(str + 1, end, v->vcpu_id, 10, -1, -1, 0);
293 }
294
print_pci_addr(char * str,const char * end,const pci_sbdf_t * sbdf)295 static char *print_pci_addr(char *str, const char *end, const pci_sbdf_t *sbdf)
296 {
297 str = number(str, end, sbdf->seg, 16, 4, -1, ZEROPAD);
298 if ( str < end )
299 *str = ':';
300 str = number(str + 1, end, sbdf->bus, 16, 2, -1, ZEROPAD);
301 if ( str < end )
302 *str = ':';
303 str = number(str + 1, end, sbdf->dev, 16, 2, -1, ZEROPAD);
304 if ( str < end )
305 *str = '.';
306 return number(str + 1, end, sbdf->fn, 8, -1, -1, 0);
307 }
308
pointer(char * str,const char * end,const char ** fmt_ptr,const void * arg,int field_width,int precision,int flags)309 static char *pointer(char *str, const char *end, const char **fmt_ptr,
310 const void *arg, int field_width, int precision,
311 int flags)
312 {
313 const char *fmt = *fmt_ptr, *s;
314
315 /* Custom %p suffixes. See XEN_ROOT/docs/misc/printk-formats.txt */
316 switch ( fmt[1] )
317 {
318 case 'b': /* Bitmap as hex, or list */
319 ++*fmt_ptr;
320
321 if ( field_width < 0 )
322 return str;
323
324 if ( fmt[2] == 'l' )
325 {
326 ++*fmt_ptr;
327
328 return print_bitmap_list(str, end, arg, field_width);
329 }
330
331 return print_bitmap_string(str, end, arg, field_width);
332
333 case 'd': /* Domain ID from a struct domain *. */
334 ++*fmt_ptr;
335 return print_domain(str, end, arg);
336
337 case 'h': /* Raw buffer as hex string. */
338 {
339 const uint8_t *hex_buffer = arg;
340 char sep = ' '; /* Separator character. */
341 unsigned int i;
342
343 /* Consumed 'h' from the format string. */
344 ++*fmt_ptr;
345
346 /* Bound user count from %* to between 0 and 64 bytes. */
347 if ( field_width <= 0 )
348 return str;
349 if ( field_width > 64 )
350 field_width = 64;
351
352 /*
353 * Peek ahead in the format string to see if a recognised separator
354 * modifier is present.
355 */
356 switch ( fmt[2] )
357 {
358 case 'C': /* Colons. */
359 ++*fmt_ptr;
360 sep = ':';
361 break;
362
363 case 'D': /* Dashes. */
364 ++*fmt_ptr;
365 sep = '-';
366 break;
367
368 case 'N': /* No separator. */
369 ++*fmt_ptr;
370 sep = 0;
371 break;
372 }
373
374 for ( i = 0; ; )
375 {
376 /* Each byte: 2 chars, 0-padded, base 16, no hex prefix. */
377 str = number(str, end, hex_buffer[i], 16, 2, -1, ZEROPAD);
378
379 if ( ++i == field_width )
380 break;
381
382 if ( sep )
383 {
384 if ( str < end )
385 *str = sep;
386 ++str;
387 }
388 }
389
390 return str;
391 }
392
393 case 'p': /* PCI SBDF. */
394 ++*fmt_ptr;
395 return print_pci_addr(str, end, arg);
396
397 case 's': /* Symbol name with offset and size (iff offset != 0) */
398 case 'S': /* Symbol name unconditionally with offset and size */
399 {
400 unsigned long sym_size, sym_offset;
401 char namebuf[KSYM_NAME_LEN+1];
402
403 /* Advance parents fmt string, as we have consumed 's' or 'S' */
404 ++*fmt_ptr;
405
406 s = symbols_lookup((unsigned long)arg, &sym_size, &sym_offset, namebuf);
407
408 /* If the symbol is not found, fall back to printing the address */
409 if ( !s )
410 break;
411
412 /* Print symbol name */
413 str = string(str, end, s, -1, -1, 0);
414
415 if ( fmt[1] == 'S' || sym_offset != 0 )
416 {
417 /* Print '+<offset>/<len>' */
418 str = number(str, end, sym_offset, 16, -1, -1, SPECIAL|SIGN|PLUS);
419 if ( str < end )
420 *str = '/';
421 ++str;
422 str = number(str, end, sym_size, 16, -1, -1, SPECIAL);
423 }
424
425 /*
426 * namebuf contents and s for core hypervisor are same but for Live Patch
427 * payloads they differ (namebuf contains the name of the payload).
428 */
429 if ( namebuf != s )
430 {
431 str = string(str, end, " [", -1, -1, 0);
432 str = string(str, end, namebuf, -1, -1, 0);
433 str = string(str, end, "]", -1, -1, 0);
434 }
435
436 return str;
437 }
438
439 case 'v': /* d<domain-id>v<vcpu-id> from a struct vcpu */
440 ++*fmt_ptr;
441 return print_vcpu(str, end, arg);
442 }
443
444 if ( field_width == -1 )
445 {
446 field_width = 2 * sizeof(void *);
447 flags |= ZEROPAD;
448 }
449
450 return number(str, end, (unsigned long)arg,
451 16, field_width, precision, flags);
452 }
453
454 /**
455 * vsnprintf - Format a string and place it in a buffer
456 * @buf: The buffer to place the result into
457 * @size: The size of the buffer, including the trailing null space
458 * @fmt: The format string to use
459 * @args: Arguments for the format string
460 *
461 * The return value is the number of characters which would
462 * be generated for the given input, excluding the trailing
463 * '\0', as per ISO C99. If you want to have the exact
464 * number of characters written into @buf as return value
465 * (not including the trailing '\0'), use vscnprintf. If the
466 * return is greater than or equal to @size, the resulting
467 * string is truncated.
468 *
469 * Call this function if you are already dealing with a va_list.
470 * You probably want snprintf instead.
471 */
vsnprintf(char * buf,size_t size,const char * fmt,va_list args)472 int vsnprintf(char *buf, size_t size, const char *fmt, va_list args)
473 {
474 unsigned long long num;
475 int base;
476 char *str, *end, c;
477 const char *s;
478
479 int flags; /* flags to number() */
480
481 int field_width; /* width of output field */
482 int precision; /* min. # of digits for integers; max
483 number of chars for from string */
484 int qualifier; /* 'h', 'l', or 'L' for integer fields */
485 /* 'z' support added 23/7/1999 S.H. */
486 /* 'z' changed to 'Z' --davidm 1/25/99 */
487
488 /* Reject out-of-range values early */
489 BUG_ON(((int)size < 0) || ((unsigned int)size != size));
490
491 str = buf;
492 end = buf + size;
493
494 if (end < buf) {
495 end = ((void *) -1);
496 size = end - buf;
497 }
498
499 for (; *fmt ; ++fmt) {
500 if (*fmt != '%') {
501 if (str < end)
502 *str = *fmt;
503 ++str;
504 continue;
505 }
506
507 /* process flags */
508 flags = 0;
509 repeat:
510 ++fmt; /* this also skips first '%' */
511 switch (*fmt) {
512 case '-': flags |= LEFT; goto repeat;
513 case '+': flags |= PLUS; goto repeat;
514 case ' ': flags |= SPACE; goto repeat;
515 case '#': flags |= SPECIAL; goto repeat;
516 case '0': flags |= ZEROPAD; goto repeat;
517 }
518
519 /* get field width */
520 field_width = -1;
521 if (isdigit(*fmt))
522 field_width = skip_atoi(&fmt);
523 else if (*fmt == '*') {
524 ++fmt;
525 /* it's the next argument */
526 field_width = va_arg(args, int);
527 if (field_width < 0) {
528 field_width = -field_width;
529 flags |= LEFT;
530 }
531 }
532
533 /* get the precision */
534 precision = -1;
535 if (*fmt == '.') {
536 ++fmt;
537 if (isdigit(*fmt))
538 precision = skip_atoi(&fmt);
539 else if (*fmt == '*') {
540 ++fmt;
541 /* it's the next argument */
542 precision = va_arg(args, int);
543 }
544 if (precision < 0)
545 precision = 0;
546 }
547
548 /* get the conversion qualifier */
549 qualifier = -1;
550 if (*fmt == 'h' || *fmt == 'l' || *fmt == 'L' ||
551 *fmt =='Z' || *fmt == 'z') {
552 qualifier = *fmt;
553 ++fmt;
554 if (qualifier == 'l' && *fmt == 'l') {
555 qualifier = 'L';
556 ++fmt;
557 }
558 }
559
560 /* default base */
561 base = 10;
562
563 switch (*fmt) {
564 case 'c':
565 if (!(flags & LEFT)) {
566 while (--field_width > 0) {
567 if (str < end)
568 *str = ' ';
569 ++str;
570 }
571 }
572 c = (unsigned char) va_arg(args, int);
573 if (str < end)
574 *str = c;
575 ++str;
576 while (--field_width > 0) {
577 if (str < end)
578 *str = ' ';
579 ++str;
580 }
581 continue;
582
583 case 's':
584 s = va_arg(args, char *);
585 if ((unsigned long)s < PAGE_SIZE)
586 s = "<NULL>";
587
588 str = string(str, end, s, field_width, precision, flags);
589 continue;
590
591 case 'p':
592 /* pointer() might advance fmt (%pS for example) */
593 str = pointer(str, end, &fmt, va_arg(args, const void *),
594 field_width, precision, flags);
595 continue;
596
597
598 case 'n':
599 if (qualifier == 'l') {
600 long * ip = va_arg(args, long *);
601 *ip = (str - buf);
602 } else if (qualifier == 'Z' || qualifier == 'z') {
603 size_t * ip = va_arg(args, size_t *);
604 *ip = (str - buf);
605 } else {
606 int * ip = va_arg(args, int *);
607 *ip = (str - buf);
608 }
609 continue;
610
611 case '%':
612 if (str < end)
613 *str = '%';
614 ++str;
615 continue;
616
617 /* integer number formats - set up the flags and "break" */
618 case 'o':
619 base = 8;
620 break;
621
622 case 'X':
623 flags |= LARGE;
624 fallthrough;
625 case 'x':
626 base = 16;
627 break;
628
629 case 'd':
630 case 'i':
631 flags |= SIGN;
632 fallthrough;
633 case 'u':
634 break;
635
636 default:
637 if (str < end)
638 *str = '%';
639 ++str;
640 if (*fmt) {
641 if (str < end)
642 *str = *fmt;
643 ++str;
644 } else {
645 --fmt;
646 }
647 continue;
648 }
649 if (qualifier == 'L')
650 num = va_arg(args, long long);
651 else if (qualifier == 'l') {
652 num = va_arg(args, unsigned long);
653 if (flags & SIGN)
654 num = (signed long) num;
655 } else if (qualifier == 'Z' || qualifier == 'z') {
656 num = va_arg(args, size_t);
657 } else if (qualifier == 'h') {
658 num = (unsigned short) va_arg(args, int);
659 if (flags & SIGN)
660 num = (signed short) num;
661 } else {
662 num = va_arg(args, unsigned int);
663 if (flags & SIGN)
664 num = (signed int) num;
665 }
666
667 str = number(str, end, num, base,
668 field_width, precision, flags);
669 }
670
671 /* don't write out a null byte if the buf size is zero */
672 if (size > 0) {
673 if (str < end)
674 *str = '\0';
675 else
676 end[-1] = '\0';
677 }
678 /* the trailing null byte doesn't count towards the total
679 * ++str;
680 */
681 return str-buf;
682 }
683
684 EXPORT_SYMBOL(vsnprintf);
685
686 /**
687 * vscnprintf - Format a string and place it in a buffer
688 * @buf: The buffer to place the result into
689 * @size: The size of the buffer, including the trailing null space
690 * @fmt: The format string to use
691 * @args: Arguments for the format string
692 *
693 * The return value is the number of characters which have been written into
694 * the @buf not including the trailing '\0'. If @size is <= 0 the function
695 * returns 0.
696 *
697 * Call this function if you are already dealing with a va_list.
698 * You probably want scnprintf instead.
699 */
vscnprintf(char * buf,size_t size,const char * fmt,va_list args)700 int vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
701 {
702 int i;
703
704 i = vsnprintf(buf,size,fmt,args);
705 if (i >= size)
706 i = size - 1;
707 return (i > 0) ? i : 0;
708 }
709
710 EXPORT_SYMBOL(vscnprintf);
711
712 /**
713 * snprintf - Format a string and place it in a buffer
714 * @buf: The buffer to place the result into
715 * @size: The size of the buffer, including the trailing null space
716 * @fmt: The format string to use
717 * @...: Arguments for the format string
718 *
719 * The return value is the number of characters which would be
720 * generated for the given input, excluding the trailing null,
721 * as per ISO C99. If the return is greater than or equal to
722 * @size, the resulting string is truncated.
723 */
snprintf(char * buf,size_t size,const char * fmt,...)724 int snprintf(char * buf, size_t size, const char *fmt, ...)
725 {
726 va_list args;
727 int i;
728
729 va_start(args, fmt);
730 i=vsnprintf(buf,size,fmt,args);
731 va_end(args);
732 return i;
733 }
734
735 EXPORT_SYMBOL(snprintf);
736
737 /**
738 * scnprintf - Format a string and place it in a buffer
739 * @buf: The buffer to place the result into
740 * @size: The size of the buffer, including the trailing null space
741 * @fmt: The format string to use
742 * @...: Arguments for the format string
743 *
744 * The return value is the number of characters written into @buf not including
745 * the trailing '\0'. If @size is <= 0 the function returns 0. If the return is
746 * greater than or equal to @size, the resulting string is truncated.
747 */
748
scnprintf(char * buf,size_t size,const char * fmt,...)749 int scnprintf(char * buf, size_t size, const char *fmt, ...)
750 {
751 va_list args;
752 int i;
753
754 va_start(args, fmt);
755 i = vsnprintf(buf, size, fmt, args);
756 va_end(args);
757 if (i >= size)
758 i = size - 1;
759 return (i > 0) ? i : 0;
760 }
761 EXPORT_SYMBOL(scnprintf);
762
763 /**
764 * xvasprintf - Format a string and allocate a buffer to place it in
765 *
766 * @bufp: Pointer to a pointer to receive the allocated buffer
767 * @fmt: The format string to use
768 * @args: Arguments for the format string
769 *
770 * -ENOMEM is returned on failure and @bufp is not touched.
771 * On success, 0 is returned. The buffer passed back is
772 * guaranteed to be null terminated. The memory is allocated
773 * from xenheap, so the buffer should be freed with xfree().
774 */
xvasprintf(char ** bufp,const char * fmt,va_list args)775 int xvasprintf(char **bufp, const char *fmt, va_list args)
776 {
777 va_list args_copy;
778 size_t size;
779 char *buf;
780
781 va_copy(args_copy, args);
782 size = vsnprintf(NULL, 0, fmt, args_copy);
783 va_end(args_copy);
784
785 buf = xmalloc_array(char, ++size);
786 if ( !buf )
787 return -ENOMEM;
788
789 (void) vsnprintf(buf, size, fmt, args);
790
791 *bufp = buf;
792 return 0;
793 }
794
795 /**
796 * xasprintf - Format a string and place it in a buffer
797 * @bufp: Pointer to a pointer to receive the allocated buffer
798 * @fmt: The format string to use
799 * @...: Arguments for the format string
800 *
801 * -ENOMEM is returned on failure and @bufp is not touched.
802 * On success, 0 is returned. The buffer passed back is
803 * guaranteed to be null terminated. The memory is allocated
804 * from xenheap, so the buffer should be freed with xfree().
805 */
xasprintf(char ** bufp,const char * fmt,...)806 int xasprintf(char **bufp, const char *fmt, ...)
807 {
808 va_list args;
809 int i;
810
811 va_start(args, fmt);
812 i = xvasprintf(bufp, fmt, args);
813 va_end(args);
814
815 return i;
816 }
817
818 /*
819 * Local variables:
820 * mode: C
821 * c-file-style: "BSD"
822 * c-basic-offset: 4
823 * tab-width: 4
824 * indent-tabs-mode: nil
825 * End:
826 */
827