1 /* $NetBSD: snprintf.c,v 1.6 2009/03/01 22:52:00 lukem Exp $ */
2
3 /*
4 * Copyright Patrick Powell 1995
5 * This code is based on code written by Patrick Powell (papowell@astart.com)
6 * It may be used for any purpose as long as this notice remains intact
7 * on all source code distributions
8 */
9
10 /**************************************************************
11 * Original:
12 * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
13 * A bombproof version of doprnt (dopr) included.
14 * Sigh. This sort of thing is always nasty do deal with. Note that
15 * the version here does not include floating point...
16 *
17 * snprintf() is used instead of sprintf() as it does limit checks
18 * for string length. This covers a nasty loophole.
19 *
20 * The other functions are there to prevent NULL pointers from
21 * causing nast effects.
22 *
23 * More Recently:
24 * Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
25 * This was ugly. It is still ugly. I opted out of floating point
26 * numbers, but the formatter understands just about everything
27 * from the normal C string format, at least as far as I can tell from
28 * the Solaris 2.5 printf(3S) man page.
29 *
30 * Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
31 * Ok, added some minimal floating point support, which means this
32 * probably requires libm on most operating systems. Don't yet
33 * support the exponent (e,E) and sigfig (g,G). Also, fmtint()
34 * was pretty badly broken, it just wasn't being exercised in ways
35 * which showed it, so that's been fixed. Also, formated the code
36 * to mutt conventions, and removed dead code left over from the
37 * original. Also, there is now a builtin-test, just compile with:
38 * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
39 * and run snprintf for results.
40 *
41 * Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
42 * The PGP code was using unsigned hexadecimal formats.
43 * Unfortunately, unsigned formats simply didn't work.
44 *
45 * Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
46 * The original code assumed that both snprintf() and vsnprintf() were
47 * missing. Some systems only have snprintf() but not vsnprintf(), so
48 * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
49 *
50 * Andrew Tridgell (tridge@samba.org) Oct 1998
51 * fixed handling of %.0f
52 * added test for HAVE_LONG_DOUBLE
53 *
54 * Luke Mewburn <lukem@NetBSD.org>, Thu Sep 30 23:28:21 EST 1999
55 * cleaned up formatting, autoconf tests
56 * added long long support
57 *
58 **************************************************************/
59
60 #include "tnftpd.h"
61
62
63 #if defined(HAVE_LONG_DOUBLE)
64 #define LDOUBLE long double
65 #else
66 #define LDOUBLE double
67 #endif
68
69 #if defined(HAVE_LONG_LONG_INT)
70 #define LLONG long long
71 #else
72 #define LLONG long
73 #endif
74
75 static void dopr(char *buffer, size_t maxlen, size_t *retlen,
76 const char *format, va_list args);
77 static void fmtstr(char *buffer, size_t * currlen, size_t maxlen,
78 char *value, int min, int max, int flags);
79 static void fmtint(char *buffer, size_t * currlen, size_t maxlen,
80 LLONG value, int base, int min, int max, int flags);
81 static void fmtfp(char *buffer, size_t * currlen, size_t maxlen,
82 LDOUBLE fvalue, int min, int max, int flags);
83 static void dopr_outch(char *buffer, size_t * currlen, size_t maxlen, int c);
84
85 /*
86 * dopr(): poor man's version of doprintf
87 */
88
89 /* format read states */
90 #define DP_S_DEFAULT 0
91 #define DP_S_FLAGS 1
92 #define DP_S_MIN 2
93 #define DP_S_DOT 3
94 #define DP_S_MAX 4
95 #define DP_S_MOD 5
96 #define DP_S_CONV 6
97 #define DP_S_DONE 7
98
99 /* format flags - Bits */
100 #define DP_F_MINUS (1 << 0)
101 #define DP_F_PLUS (1 << 1)
102 #define DP_F_SPACE (1 << 2)
103 #define DP_F_NUM (1 << 3)
104 #define DP_F_ZERO (1 << 4)
105 #define DP_F_UP (1 << 5)
106 #define DP_F_UNSIGNED (1 << 6)
107
108 /* Conversion Flags */
109 #define DP_C_SHORT 1
110 #define DP_C_LONG 2
111 #define DP_C_LDOUBLE 3
112 #define DP_C_LLONG 4
113
114 #define char_to_int(p) (p - '0')
115
116 static void
dopr(char * buffer,size_t maxlen,size_t * retlen,const char * format,va_list args)117 dopr(char *buffer, size_t maxlen, size_t *retlen, const char *format,
118 va_list args)
119 {
120 char ch;
121 LLONG value;
122 LDOUBLE fvalue;
123 char *strvalue;
124 int min;
125 int max;
126 int state;
127 int flags;
128 int cflags;
129 size_t currlen;
130
131 state = DP_S_DEFAULT;
132 flags = currlen = cflags = min = 0;
133 max = -1;
134 ch = *format++;
135
136 while (state != DP_S_DONE) {
137 if ((ch == '\0') || (currlen >= maxlen))
138 state = DP_S_DONE;
139
140 switch (state) {
141 case DP_S_DEFAULT:
142 if (ch == '%')
143 state = DP_S_FLAGS;
144 else
145 dopr_outch(buffer, &currlen, maxlen, ch);
146 ch = *format++;
147 break;
148 case DP_S_FLAGS:
149 switch (ch) {
150 case '-':
151 flags |= DP_F_MINUS;
152 ch = *format++;
153 break;
154 case '+':
155 flags |= DP_F_PLUS;
156 ch = *format++;
157 break;
158 case ' ':
159 flags |= DP_F_SPACE;
160 ch = *format++;
161 break;
162 case '#':
163 flags |= DP_F_NUM;
164 ch = *format++;
165 break;
166 case '0':
167 flags |= DP_F_ZERO;
168 ch = *format++;
169 break;
170 default:
171 state = DP_S_MIN;
172 break;
173 }
174 break;
175 case DP_S_MIN:
176 if (isdigit((unsigned char) ch)) {
177 min = 10 * min + char_to_int(ch);
178 ch = *format++;
179 } else if (ch == '*') {
180 min = va_arg(args, int);
181 ch = *format++;
182 state = DP_S_DOT;
183 } else
184 state = DP_S_DOT;
185 break;
186 case DP_S_DOT:
187 if (ch == '.') {
188 state = DP_S_MAX;
189 ch = *format++;
190 } else
191 state = DP_S_MOD;
192 break;
193 case DP_S_MAX:
194 if (isdigit((unsigned char) ch)) {
195 if (max < 0)
196 max = 0;
197 max = 10 * max + char_to_int(ch);
198 ch = *format++;
199 } else if (ch == '*') {
200 max = va_arg(args, int);
201 ch = *format++;
202 state = DP_S_MOD;
203 } else
204 state = DP_S_MOD;
205 break;
206 case DP_S_MOD:
207 switch (ch) {
208 case 'h':
209 cflags = DP_C_SHORT;
210 ch = *format++;
211 break;
212 case 'l':
213 if (*format == 'l') {
214 cflags = DP_C_LLONG;
215 format++;
216 } else
217 cflags = DP_C_LONG;
218 ch = *format++;
219 break;
220 case 'q':
221 cflags = DP_C_LLONG;
222 ch = *format++;
223 break;
224 case 'L':
225 cflags = DP_C_LDOUBLE;
226 ch = *format++;
227 break;
228 default:
229 break;
230 }
231 state = DP_S_CONV;
232 break;
233 case DP_S_CONV:
234 switch (ch) {
235 case 'd':
236 case 'i':
237 switch (cflags) {
238 case DP_C_SHORT:
239 value = va_arg(args, int);
240 break;
241 case DP_C_LONG:
242 value = va_arg(args, long int);
243 break;
244 case DP_C_LLONG:
245 value = va_arg(args, LLONG);
246 break;
247 default:
248 value = va_arg(args, int);
249 break;
250 }
251 fmtint(buffer, &currlen, maxlen, value, 10,
252 min, max, flags);
253 break;
254 case 'X':
255 flags |= DP_F_UP;
256 /* FALLTHROUGH */
257 case 'x':
258 case 'o':
259 case 'u':
260 flags |= DP_F_UNSIGNED;
261 switch (cflags) {
262 case DP_C_SHORT:
263 value = va_arg(args, unsigned int);
264 break;
265 case DP_C_LONG:
266 value = (LLONG) va_arg(args,
267 unsigned long int);
268 break;
269 case DP_C_LLONG:
270 value = va_arg(args, unsigned LLONG);
271 break;
272 default:
273 value = (LLONG) va_arg(args,
274 unsigned int);
275 break;
276 }
277 fmtint(buffer, &currlen, maxlen, value,
278 ch == 'o' ? 8 : (ch == 'u' ? 10 : 16),
279 min, max, flags);
280 break;
281 case 'f':
282 if (cflags == DP_C_LDOUBLE)
283 fvalue = va_arg(args, LDOUBLE);
284 else
285 fvalue = va_arg(args, double);
286 /* um, floating point? */
287 fmtfp(buffer, &currlen, maxlen, fvalue, min,
288 max, flags);
289 break;
290 case 'E':
291 flags |= DP_F_UP;
292 case 'e':
293 if (cflags == DP_C_LDOUBLE)
294 fvalue = va_arg(args, LDOUBLE);
295 else
296 fvalue = va_arg(args, double);
297 break;
298 case 'G':
299 flags |= DP_F_UP;
300 case 'g':
301 if (cflags == DP_C_LDOUBLE)
302 fvalue = va_arg(args, LDOUBLE);
303 else
304 fvalue = va_arg(args, double);
305 break;
306 case 'c':
307 dopr_outch(buffer, &currlen, maxlen,
308 va_arg(args, int));
309 break;
310 case 's':
311 strvalue = va_arg(args, char *);
312 if (max < 0)
313 max = maxlen; /* ie, no max */
314 fmtstr(buffer, &currlen, maxlen, strvalue,
315 min, max, flags);
316 break;
317 case 'p':
318 value = (long)va_arg(args, void *);
319 fmtint(buffer, &currlen, maxlen,
320 value, 16, min, max, flags);
321 break;
322 case 'n':
323 /* XXX */
324 if (cflags == DP_C_SHORT) {
325 short int *num;
326 num = va_arg(args, short int *);
327 *num = currlen;
328 } else if (cflags == DP_C_LONG) { /* XXX */
329 long int *num;
330 num = va_arg(args, long int *);
331 *num = (long int) currlen;
332 } else if (cflags == DP_C_LLONG) { /* XXX */
333 LLONG *num;
334 num = va_arg(args, LLONG *);
335 *num = (LLONG) currlen;
336 } else {
337 int *num;
338 num = va_arg(args, int *);
339 *num = currlen;
340 }
341 break;
342 case '%':
343 dopr_outch(buffer, &currlen, maxlen, ch);
344 break;
345 case 'w':
346 /* not supported yet, treat as next char */
347 ch = *format++;
348 break;
349 default:
350 /* Unknown, skip */
351 break;
352 }
353 ch = *format++;
354 state = DP_S_DEFAULT;
355 flags = cflags = min = 0;
356 max = -1;
357 break;
358 case DP_S_DONE:
359 break;
360 default:
361 /* hmm? */
362 break; /* some picky compilers need this */
363 }
364 }
365 if (currlen >= maxlen - 1)
366 currlen = maxlen - 1;
367 buffer[currlen] = '\0';
368 *retlen = currlen;
369 }
370
371 static void
fmtstr(char * buffer,size_t * currlen,size_t maxlen,char * value,int min,int max,int flags)372 fmtstr(char *buffer, size_t *currlen, size_t maxlen, char *value,
373 int min, int max, int flags)
374 {
375 int padlen, strln; /* amount to pad */
376 int cnt = 0;
377
378 if (value == 0) {
379 value = "<NULL>";
380 }
381 for (strln = 0; value[strln]; ++strln)
382 ; /* strlen */
383 padlen = min - strln;
384 if (padlen < 0)
385 padlen = 0;
386 if (flags & DP_F_MINUS)
387 padlen = -padlen; /* Left Justify */
388
389 while ((padlen > 0) && (cnt < max)) {
390 dopr_outch(buffer, currlen, maxlen, ' ');
391 --padlen;
392 ++cnt;
393 }
394 while (*value && (cnt < max)) {
395 dopr_outch(buffer, currlen, maxlen, *value++);
396 ++cnt;
397 }
398 while ((padlen < 0) && (cnt < max)) {
399 dopr_outch(buffer, currlen, maxlen, ' ');
400 ++padlen;
401 ++cnt;
402 }
403 }
404 /* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
405
406 static void
fmtint(char * buffer,size_t * currlen,size_t maxlen,LLONG value,int base,int min,int max,int flags)407 fmtint(char *buffer, size_t *currlen, size_t maxlen, LLONG value, int base,
408 int min, int max, int flags)
409 {
410 int signvalue = 0;
411 unsigned LLONG uvalue;
412 char convert[20];
413 int place = 0;
414 int spadlen = 0; /* amount to space pad */
415 int zpadlen = 0; /* amount to zero pad */
416 int caps = 0;
417
418 if (max < 0)
419 max = 0;
420
421 uvalue = value;
422
423 if (!(flags & DP_F_UNSIGNED)) {
424 if (value < 0) {
425 signvalue = '-';
426 uvalue = -value;
427 } else if (flags & DP_F_PLUS) /* Do a sign (+/i) */
428 signvalue = '+';
429 else if (flags & DP_F_SPACE)
430 signvalue = ' ';
431 }
432 if (flags & DP_F_UP)
433 caps = 1; /* Should characters be upper case? */
434
435 do {
436 convert[place++] =
437 (caps ? "0123456789ABCDEF" : "0123456789abcdef")
438 [uvalue % (unsigned) base];
439 uvalue = (uvalue / (unsigned) base);
440 } while (uvalue && (place < 20));
441 if (place == 20)
442 place--;
443 convert[place] = 0;
444
445 zpadlen = max - place;
446 spadlen = min - MAX(max, place) - (signvalue ? 1 : 0);
447 if (zpadlen < 0)
448 zpadlen = 0;
449 if (spadlen < 0)
450 spadlen = 0;
451 if (flags & DP_F_ZERO) {
452 zpadlen = MAX(zpadlen, spadlen);
453 spadlen = 0;
454 }
455 if (flags & DP_F_MINUS)
456 spadlen = -spadlen; /* Left Justifty */
457
458 #ifdef DEBUG_SNPRINTF
459 printf("zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
460 zpadlen, spadlen, min, max, place);
461 #endif
462
463 /* Spaces */
464 while (spadlen > 0) {
465 dopr_outch(buffer, currlen, maxlen, ' ');
466 --spadlen;
467 }
468
469 /* Sign */
470 if (signvalue)
471 dopr_outch(buffer, currlen, maxlen, signvalue);
472
473 /* Zeros */
474 if (zpadlen > 0) {
475 while (zpadlen > 0) {
476 dopr_outch(buffer, currlen, maxlen, '0');
477 --zpadlen;
478 }
479 }
480 /* Digits */
481 while (place > 0)
482 dopr_outch(buffer, currlen, maxlen, convert[--place]);
483
484 /* Left Justified spaces */
485 while (spadlen < 0) {
486 dopr_outch(buffer, currlen, maxlen, ' ');
487 ++spadlen;
488 }
489 }
490
491 static LDOUBLE
abs_val(LDOUBLE value)492 abs_val(LDOUBLE value)
493 {
494 LDOUBLE result = value;
495
496 if (value < 0)
497 result = -value;
498
499 return result;
500 }
501
502 static LDOUBLE
sn_pow10(int exp)503 sn_pow10(int exp)
504 {
505 LDOUBLE result = 1;
506
507 while (exp) {
508 result *= 10;
509 exp--;
510 }
511
512 return result;
513 }
514
515 static long
sn_round(LDOUBLE value)516 sn_round(LDOUBLE value)
517 {
518 long intpart;
519
520 intpart = (long) value;
521 value = value - intpart;
522 if (value >= 0.5)
523 intpart++;
524
525 return intpart;
526 }
527
528 static void
fmtfp(char * buffer,size_t * currlen,size_t maxlen,LDOUBLE fvalue,int min,int max,int flags)529 fmtfp(char *buffer, size_t *currlen, size_t maxlen, LDOUBLE fvalue,
530 int min, int max, int flags)
531 {
532 int signvalue = 0;
533 LDOUBLE ufvalue;
534 char iconvert[20];
535 char fconvert[20];
536 int iplace = 0;
537 int fplace = 0;
538 int padlen = 0; /* amount to pad */
539 int zpadlen = 0;
540 int caps = 0;
541 long intpart;
542 long fracpart;
543
544 /* AIX manpage says the default is 0, but Solaris says the default is
545 * 6, and sprintf on AIX defaults to 6 */
546 if (max < 0)
547 max = 6;
548
549 ufvalue = abs_val(fvalue);
550
551 if (fvalue < 0)
552 signvalue = '-';
553 else if (flags & DP_F_PLUS) /* Do a sign (+/i) */
554 signvalue = '+';
555 else if (flags & DP_F_SPACE)
556 signvalue = ' ';
557
558 #if 0
559 if (flags & DP_F_UP)
560 caps = 1; /* Should characters be upper case? */
561 #endif
562
563 intpart = (long) ufvalue;
564
565 /* Sorry, we only support 9 digits past the decimal because of our
566 * conversion method */
567 if (max > 9)
568 max = 9;
569
570 /* We "cheat" by converting the fractional part to integer by
571 * multiplying by a factor of 10 */
572 fracpart = sn_round((sn_pow10(max)) * (ufvalue - intpart));
573
574 if (fracpart >= sn_pow10(max)) {
575 intpart++;
576 fracpart -= sn_pow10(max);
577 }
578 #ifdef DEBUG_SNPRINTF
579 printf("fmtfp: %g %d.%d min=%d max=%d\n",
580 (double) fvalue, intpart, fracpart, min, max);
581 #endif
582
583 /* Convert integer part */
584 do {
585 iconvert[iplace++] =
586 (caps ? "0123456789ABCDEF"
587 : "0123456789abcdef")[intpart % 10];
588 intpart = (intpart / 10);
589 } while (intpart && (iplace < 20));
590 if (iplace == 20)
591 iplace--;
592 iconvert[iplace] = 0;
593
594 /* Convert fractional part */
595 do {
596 fconvert[fplace++] =
597 (caps ? "0123456789ABCDEF"
598 : "0123456789abcdef")[fracpart % 10];
599 fracpart = (fracpart / 10);
600 } while (fracpart && (fplace < 20));
601 if (fplace == 20)
602 fplace--;
603 fconvert[fplace] = 0;
604
605 /* -1 for decimal point, another -1 if we are printing a sign */
606 padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
607 zpadlen = max - fplace;
608 if (zpadlen < 0)
609 zpadlen = 0;
610 if (padlen < 0)
611 padlen = 0;
612 if (flags & DP_F_MINUS)
613 padlen = -padlen; /* Left Justifty */
614
615 if ((flags & DP_F_ZERO) && (padlen > 0)) {
616 if (signvalue) {
617 dopr_outch(buffer, currlen, maxlen, signvalue);
618 --padlen;
619 signvalue = 0;
620 }
621 while (padlen > 0) {
622 dopr_outch(buffer, currlen, maxlen, '0');
623 --padlen;
624 }
625 }
626 while (padlen > 0) {
627 dopr_outch(buffer, currlen, maxlen, ' ');
628 --padlen;
629 }
630 if (signvalue)
631 dopr_outch(buffer, currlen, maxlen, signvalue);
632
633 while (iplace > 0)
634 dopr_outch(buffer, currlen, maxlen, iconvert[--iplace]);
635
636
637 #ifdef DEBUG_SNPRINTF
638 printf("fmtfp: fplace=%d zpadlen=%d\n", fplace, zpadlen);
639 #endif
640
641 /*
642 * Decimal point. This should probably use locale to find the correct
643 * char to print out.
644 */
645 if (max > 0) {
646 dopr_outch(buffer, currlen, maxlen, '.');
647
648 while (fplace > 0)
649 dopr_outch(buffer, currlen, maxlen, fconvert[--fplace]);
650 }
651 while (zpadlen > 0) {
652 dopr_outch(buffer, currlen, maxlen, '0');
653 --zpadlen;
654 }
655
656 while (padlen < 0) {
657 dopr_outch(buffer, currlen, maxlen, ' ');
658 ++padlen;
659 }
660 }
661
662 static void
dopr_outch(char * buffer,size_t * currlen,size_t maxlen,int c)663 dopr_outch(char *buffer, size_t *currlen, size_t maxlen, int c)
664 {
665 if (*currlen < maxlen)
666 buffer[(*currlen)++] = (char)c;
667 }
668
669 int
vsnprintf(char * str,size_t count,const char * fmt,va_list args)670 vsnprintf(char *str, size_t count, const char *fmt, va_list args)
671 {
672 size_t retlen;
673
674 str[0] = 0;
675 dopr(str, count, &retlen, fmt, args);
676 return (retlen);
677 }
678
679 /* VARARGS3 */
680 int
snprintf(char * str,size_t count,const char * fmt,...)681 snprintf(char *str, size_t count, const char *fmt, ...)
682 {
683 va_list ap;
684 int rv;
685
686 va_start(ap, fmt);
687 rv = vsnprintf(str, count, fmt, ap);
688 va_end(ap);
689 return (rv);
690 }
691
692
693 #ifdef TEST_SNPRINTF
694 #ifndef LONG_STRING
695 #define LONG_STRING 1024
696 #endif
697
698 int
main(int argc,char * argv[])699 main(int argc, char *argv[])
700 {
701 char buf1[LONG_STRING];
702 char buf2[LONG_STRING];
703 char *fp_fmt[] = {
704 "%-1.5f",
705 "%1.5f",
706 "%123.9f",
707 "%10.5f",
708 "% 10.5f",
709 "%+22.9f",
710 "%+4.9f",
711 "%01.3f",
712 "%4f",
713 "%3.1f",
714 "%3.2f",
715 "%.0f",
716 "%.1f",
717 NULL
718 };
719 double fp_nums[] = {
720 -1.5, 134.21, 91340.2, 341.1234, 0203.9,
721 0.96, 0.996, 0.9996, 1.996, 4.136,
722 0
723 };
724 char *int_fmt[] = {
725 "%-1.5d",
726 "%1.5d",
727 "%123.9d",
728 "%5.5d",
729 "%10.5d",
730 "% 10.5d",
731 "%+22.33d",
732 "%01.3d",
733 "%4d",
734 #if defined(HAVE_LONG_LONG_INT)
735 "%12lld",
736 #endif
737 NULL
738 };
739 LLONG int_nums[] = {
740 -1, 134, 91340, 341, 0203,
741 4294967290,
742 4294967297,
743 0
744 };
745 int x, y;
746 int fail = 0;
747 int num = 0;
748 int len;
749
750 printf("Testing snprintf format codes against system sprintf...\n");
751
752 for (x = 0; fp_fmt[x] != NULL; x++) {
753 printf("testing %s\n", fp_fmt[x]);
754 for (y = 0; fp_nums[y] != 0; y++) {
755 snprintf(buf1, sizeof(buf1), fp_fmt[x], fp_nums[y]);
756 sprintf(buf2, fp_fmt[x], fp_nums[y]);
757 if (strcmp(buf1, buf2)) {
758 printf("snprintf doesn't match Format: %s\n",
759 fp_fmt[x]);
760 printf("\tsnprintf = %s\n\tsprintf = %s\n",
761 buf1, buf2);
762 fail++;
763 }
764 num++;
765 }
766 }
767
768 for (x = 0; int_fmt[x] != NULL; x++) {
769 printf("testing %s\n", int_fmt[x]);
770 for (y = 0; int_nums[y] != 0; y++) {
771 len = snprintf(buf1, sizeof(buf1), int_fmt[x], int_nums[y]);
772 printf("got %d >%s< (%d)\n", len, buf1, (int)strlen(buf1));
773 sprintf(buf2, int_fmt[x], int_nums[y]);
774 if (strcmp(buf1, buf2)) {
775 printf("snprintf doesn't match Format: %s\n",
776 int_fmt[x]);
777 printf("\tsnprintf = %s\n\tsprintf = %s\n",
778 buf1, buf2);
779 fail++;
780 }
781 num++;
782 }
783 }
784
785 printf("%d tests failed out of %d.\n", fail, num);
786 exit(0);
787 }
788 #endif /* TEST_SNPRINTF */
789