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