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