2 * Replacement for a missing snprintf or vsnprintf.
4 * The following implementation of snprintf was taken mostly verbatim from
5 * <http://www.fiction.net/blong/programs/>; it is the version of snprintf
6 * used in Mutt. A possibly newer version is used in wget, found at
7 * <https://github.com/wertarbyte/wget/blob/master/src/snprintf.c>.
9 * Please do not reformat or otherwise change this file more than necessary so
10 * that later merges with the original source are easy. Bug fixes and
11 * improvements should be sent back to the original author.
13 * The canonical version of this file is maintained in the rra-c-util package,
14 * which can be found at <https://www.eyrie.org/~eagle/software/rra-c-util/>.
18 * If we're running the test suite, rename snprintf and vsnprintf to avoid
19 * conflicts with the system version.
24 # define snprintf test_snprintf
25 # define vsnprintf test_vsnprintf
29 * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
30 * could you use the __format__ form of the attributes, which is what we use
31 * (to avoid confusion with other macros).
34 # if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 7)
35 # define __attribute__(spec) /* empty */
40 * Older Clang doesn't support __attribute__((fallthrough)) properly and
41 * complains about the empty statement that it is decorating. Suppress that
42 * warning. Also suppress warnings about unknown attributes to handle older
45 #if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
46 # pragma GCC diagnostic ignored "-Wattributes"
47 # pragma GCC diagnostic ignored "-Wmissing-declarations"
50 /* Specific to rra-c-util, but only when debugging is enabled. */
52 # include <util/messages.h>
56 * Copyright Patrick Powell 1995
57 * This code is based on code written by Patrick Powell (papowell@astart.com)
58 * It may be used for any purpose as long as this notice remains intact
59 * on all source code distributions
61 * There is no SPDX-License-Identifier registered for this license.
64 /**************************************************************
66 * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
67 * A bombproof version of doprnt (dopr) included.
68 * Sigh. This sort of thing is always nasty do deal with. Note that
69 * the version here does not include floating point...
71 * snprintf() is used instead of sprintf() as it does limit checks
72 * for string length. This covers a nasty loophole.
74 * The other functions are there to prevent NULL pointers from
75 * causing nast effects.
78 * Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
79 * This was ugly. It is still ugly. I opted out of floating point
80 * numbers, but the formatter understands just about everything
81 * from the normal C string format, at least as far as I can tell from
82 * the Solaris 2.5 printf(3S) man page.
84 * Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
85 * Ok, added some minimal floating point support, which means this
86 * probably requires libm on most operating systems. Don't yet
87 * support the exponent (e,E) and sigfig (g,G). Also, fmtint()
88 * was pretty badly broken, it just wasn't being exercised in ways
89 * which showed it, so that's been fixed. Also, formatted the code
90 * to mutt conventions, and removed dead code left over from the
91 * original. Also, there is now a builtin-test, just compile with:
92 * gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
93 * and run snprintf for results.
95 * Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
96 * The PGP code was using unsigned hexadecimal formats.
97 * Unfortunately, unsigned formats simply didn't work.
99 * Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
100 * The original code assumed that both snprintf() and vsnprintf() were
101 * missing. Some systems only have snprintf() but not vsnprintf(), so
102 * the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
104 * Andrew Tridgell (tridge@samba.org) Oct 1998
105 * fixed handling of %.0f
106 * added test for HAVE_LONG_DOUBLE
108 * Russ Allbery <eagle@eyrie.org> 2000-08-26
109 * fixed return value to comply with C99
110 * fixed handling of snprintf(NULL, ...)
111 * added explicit casts for double to long long int conversion
112 * fixed various warnings with GCC 7
113 * fixed various warnings with Clang
115 * Hrvoje Niksic <hniksic@xemacs.org> 2000-11-04
116 * include <config.h> instead of "config.h".
117 * moved TEST_SNPRINTF stuff out of HAVE_SNPRINTF ifdef.
118 * include <stdio.h> for NULL.
119 * added support and test cases for long long.
120 * don't declare argument types to (v)snprintf if stdarg is not used.
121 * use int instead of short int as 2nd arg to va_arg.
123 * alexk (INN) 2002-08-21
124 * use LLONG in fmtfp to handle more characters during floating
127 * herb (Samba) 2002-12-19
128 * actually print args for %g and %e
130 * Hrvoje Niksic <hniksic@xemacs.org> 2005-04-15
131 * use the PARAMS macro to handle prototypes.
132 * write function definitions in the ansi2knr-friendly way.
133 * if string precision is specified, don't read VALUE past it.
134 * fix bug in fmtfp that caused 0.01 to be printed as 0.1.
135 * don't include <ctype.h> because none of it is used.
136 * interpret precision as number of significant digits with %g
137 * omit trailing decimal zeros with %g
139 **************************************************************/
145 #include <sys/types.h>
151 /* varargs declarations: */
155 /* Assume all compilers support long double, per Autoconf documentation. */
156 #define LDOUBLE long double
158 #ifdef HAVE_LONG_LONG_INT
159 # define LLONG long long
164 int snprintf (char *str, size_t count, const char *fmt, ...);
165 int vsnprintf (char *str, size_t count, const char *fmt, va_list arg);
167 static int dopr (char *buffer, size_t maxlen, const char *format,
169 static int fmtstr (char *buffer, size_t *currlen, size_t maxlen,
170 const char *value, int flags, int min, int max);
171 static int fmtint (char *buffer, size_t *currlen, size_t maxlen,
172 LLONG value, int base, int min, int max, int flags);
173 static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
174 LDOUBLE fvalue, int min, int max, int flags);
175 static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c );
178 * dopr(): poor man's version of doprintf
181 /* format read states */
182 #define DP_S_DEFAULT 0
192 /* format flags - Bits */
193 #define DP_F_MINUS (1 << 0)
194 #define DP_F_PLUS (1 << 1)
195 #define DP_F_SPACE (1 << 2)
196 #define DP_F_NUM (1 << 3)
197 #define DP_F_ZERO (1 << 4)
198 #define DP_F_UP (1 << 5)
199 #define DP_F_UNSIGNED (1 << 6)
200 #define DP_F_FP_G (1 << 7)
202 /* Conversion Flags */
206 #define DP_C_LDOUBLE 4
208 #define char_to_int(p) (p - '0')
209 #define MAX(p,q) ((p >= q) ? p : q)
210 #define MIN(p,q) ((p <= q) ? p : q)
212 static int dopr (char *buffer, size_t maxlen, const char *format, va_list args)
226 state = DP_S_DEFAULT;
227 currlen = flags = cflags = min = 0;
232 while (state != DP_S_DONE)
243 total += dopr_outch (buffer, &currlen, maxlen, ch);
275 if ('0' <= ch && ch <= '9')
277 min = 10*min + char_to_int (ch);
282 min = va_arg (args, int);
299 if ('0' <= ch && ch <= '9')
303 max = 10*max + char_to_int (ch);
308 max = va_arg (args, int);
327 cflags = DP_C_LDOUBLE;
333 if (cflags != DP_C_LONG)
355 if (cflags == DP_C_SHORT)
356 value = (short int) va_arg (args, int);
357 else if (cflags == DP_C_LONG)
358 value = va_arg (args, long int);
359 else if (cflags == DP_C_LLONG)
360 value = va_arg (args, LLONG);
362 value = va_arg (args, int);
363 total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
366 flags |= DP_F_UNSIGNED;
367 if (cflags == DP_C_SHORT)
368 value = (unsigned short int) va_arg (args, unsigned int);
369 else if (cflags == DP_C_LONG)
370 value = va_arg (args, unsigned long int);
371 else if (cflags == DP_C_LLONG)
372 value = va_arg (args, unsigned LLONG);
374 value = va_arg (args, unsigned int);
375 total += fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags);
378 flags |= DP_F_UNSIGNED;
379 if (cflags == DP_C_SHORT)
380 value = (unsigned short int) va_arg (args, unsigned int);
381 else if (cflags == DP_C_LONG)
382 value = va_arg (args, unsigned long int);
383 else if (cflags == DP_C_LLONG)
384 value = va_arg (args, unsigned LLONG);
386 value = va_arg (args, unsigned int);
387 total += fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
391 __attribute__((fallthrough));
394 flags |= DP_F_UNSIGNED;
395 if (cflags == DP_C_SHORT)
396 value = (unsigned short int) va_arg (args, unsigned int);
397 else if (cflags == DP_C_LONG)
398 value = va_arg (args, unsigned long int);
399 else if (cflags == DP_C_LLONG)
400 value = va_arg (args, unsigned LLONG);
402 value = va_arg (args, unsigned int);
403 total += fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags);
406 if (cflags == DP_C_LDOUBLE)
407 fvalue = va_arg (args, LDOUBLE);
409 fvalue = (LDOUBLE) va_arg (args, double);
410 total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
414 __attribute__((fallthrough));
417 if (cflags == DP_C_LDOUBLE)
418 fvalue = va_arg (args, LDOUBLE);
420 fvalue = (LDOUBLE) va_arg (args, double);
421 total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
425 __attribute__((fallthrough));
429 if (cflags == DP_C_LDOUBLE)
430 fvalue = va_arg (args, LDOUBLE);
432 fvalue = (LDOUBLE) va_arg (args, double);
434 /* C99 says: if precision [for %g] is zero, it is taken as one */
436 total += fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
439 total += dopr_outch (buffer, &currlen, maxlen,
440 (char) va_arg (args, int));
443 strvalue = va_arg (args, char *);
444 total += fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max);
447 strvalue = va_arg (args, void *);
448 total += fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min,
452 if (cflags == DP_C_SHORT)
455 num = va_arg (args, short int *);
456 *num = (short) currlen;
458 else if (cflags == DP_C_LONG)
461 num = va_arg (args, long int *);
464 else if (cflags == DP_C_LLONG)
467 num = va_arg (args, LLONG *);
473 num = va_arg (args, int *);
474 *num = (int) currlen;
478 total += dopr_outch (buffer, &currlen, maxlen, ch);
481 /* not supported yet, treat as next char */
489 state = DP_S_DEFAULT;
490 flags = cflags = min = 0;
497 break; /* some picky compilers need this */
502 if (currlen < maxlen - 1)
503 buffer[currlen] = '\0';
505 buffer[maxlen - 1] = '\0';
510 static int fmtstr (char *buffer, size_t *currlen, size_t maxlen,
511 const char *value, int flags, int min, int max)
513 int padlen, strln; /* amount to pad */
523 strln = (int) strlen (value);
525 /* When precision is specified, don't read VALUE past precision. */
526 /*strln = strnlen (value, max);*/
527 for (strln = 0; strln < max && value[strln]; ++strln);
528 padlen = min - strln;
531 if (flags & DP_F_MINUS)
532 padlen = -padlen; /* Left Justify */
536 total += dopr_outch (buffer, currlen, maxlen, ' ');
539 while (*value && ((max < 0) || (cnt < max)))
541 total += dopr_outch (buffer, currlen, maxlen, *value++);
546 total += dopr_outch (buffer, currlen, maxlen, ' ');
552 /* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
554 static int fmtint (char *buffer, size_t *currlen, size_t maxlen,
555 LLONG value, int base, int min, int max, int flags)
558 unsigned LLONG uvalue;
560 unsigned int place = 0;
561 int spadlen = 0; /* amount to space pad */
562 int zpadlen = 0; /* amount to zero pad */
571 if(!(flags & DP_F_UNSIGNED))
578 if (flags & DP_F_PLUS) /* Do a sign (+/i) */
581 if (flags & DP_F_SPACE)
586 /* Should characters be upper case? */
587 digits = "0123456789ABCDEF";
589 digits = "0123456789abcdef";
592 convert[place++] = digits[uvalue % (unsigned)base];
593 uvalue = (uvalue / (unsigned)base );
594 } while(uvalue && (place < sizeof (convert)));
595 if (place == sizeof (convert)) place--;
598 zpadlen = max - place;
599 spadlen = min - MAX ((unsigned int)max, place) - (signvalue ? 1 : 0);
600 if (zpadlen < 0) zpadlen = 0;
601 if (spadlen < 0) spadlen = 0;
602 if (flags & DP_F_ZERO)
604 zpadlen = MAX(zpadlen, spadlen);
607 if (flags & DP_F_MINUS)
608 spadlen = -spadlen; /* Left Justifty */
610 #ifdef DEBUG_SNPRINTF
611 debug ("zpad: %d, spad: %d, min: %d, max: %d, place: %u\n",
612 zpadlen, spadlen, min, max, place);
618 total += dopr_outch (buffer, currlen, maxlen, ' ');
624 total += dopr_outch (buffer, currlen, maxlen, signvalue);
631 total += dopr_outch (buffer, currlen, maxlen, '0');
638 total += dopr_outch (buffer, currlen, maxlen, convert[--place]);
640 /* Left Justified spaces */
641 while (spadlen < 0) {
642 total += dopr_outch (buffer, currlen, maxlen, ' ');
649 static LDOUBLE abs_val (LDOUBLE value)
651 LDOUBLE result = value;
659 static LLONG pow10_int (unsigned int exp)
669 return (LLONG) result;
672 static LLONG round_int (LDOUBLE value)
676 intpart = (LLONG) value;
677 value = value - intpart;
678 if (value >= (LDOUBLE) 0.5)
685 * GCC 7.1 issues this warning at the point of the function definition header
686 * (not in any actual code), and I can't figure out what's triggering it since
687 * the comparison form doesn't appear anywhere in this code. Since this is
688 * rarely-used portability code, suppress the warning.
690 #pragma GCC diagnostic ignored "-Wstrict-overflow"
692 static int fmtfp (char *buffer, size_t *currlen, size_t maxlen,
693 LDOUBLE fvalue, int min, int max, int flags)
701 long padlen = 0; /* amount to pad */
707 int leadingfrac0s = 0; /* zeroes at the start of fractional part */
709 size_t omitcount = 0;
712 * AIX manpage says the default is 0, but Solaris says the default
713 * is 6, and sprintf on AIX defaults to 6
718 ufvalue = abs_val (fvalue);
723 if (flags & DP_F_PLUS) /* Do a sign (+/i) */
726 if (flags & DP_F_SPACE)
730 if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
733 intpart = (LLONG) ufvalue;
735 /* With %g precision is the number of significant digits, which
736 includes the digits in intpart. */
737 if (flags & DP_F_FP_G)
741 /* For each digit of INTPART, print one less fractional digit. */
743 for (temp = intpart; temp != 0; temp /= 10)
750 /* For each leading 0 in fractional part, print one more
754 for (temp = ufvalue; temp < (LDOUBLE) 0.1; temp *= 10)
759 /* C99: trailing zeros are removed from the fractional portion of the
760 result unless the # flag is specified */
761 if ((flags & DP_F_FP_G) && !(flags & DP_F_NUM))
764 #if SIZEOF_LONG_LONG > 0
765 # define MAX_DIGITS 18 /* grok more digits with long long */
767 # define MAX_DIGITS 9 /* just long */
771 * Sorry, we only support several digits past the decimal because of
772 * our conversion method
774 if (max > MAX_DIGITS)
777 /* Factor of 10 with the needed number of digits, e.g. 1000 for max==3 */
778 mask10 = pow10_int (max);
780 /* We "cheat" by converting the fractional part to integer by
781 * multiplying by a factor of 10
783 fracpart = round_int (mask10 * (ufvalue - intpart));
785 if (fracpart >= mask10)
790 else if (fracpart != 0)
791 /* If fracpart has less digits than the 10* mask, we need to
792 manually insert leading 0s. For example 2.01's fractional part
793 requires one leading zero to distinguish it from 2.1. */
794 while (fracpart < mask10 / 10)
800 #ifdef DEBUG_SNPRINTF
801 # ifdef HAVE_LONG_LONG_INT
802 debug ("fmtfp: %Lf =? %lld.%lld\n", fvalue, intpart, fracpart);
804 debug ("fmtfp: %Lf =? %ld.%ld\n", fvalue, intpart, fracpart);
808 /* Convert integer part */
810 iconvert[iplace++] = (char) ('0' + (intpart % 10));
811 intpart = (intpart / 10);
812 } while(intpart && (iplace < sizeof(iconvert)));
813 if (iplace == sizeof(iconvert)) iplace--;
814 iconvert[iplace] = 0;
816 /* Convert fractional part */
818 fconvert[fplace++] = (char) ('0' + (fracpart % 10));
819 fracpart = (fracpart / 10);
820 } while(fracpart && (fplace < sizeof(fconvert)));
821 while (leadingfrac0s-- > 0 && fplace < sizeof(fconvert))
822 fconvert[fplace++] = '0';
823 if (fplace == sizeof(fconvert)) fplace--;
824 fconvert[fplace] = 0;
826 while (omitcount < fplace && fconvert[omitcount] == '0')
829 /* -1 for decimal point, another -1 if we are printing a sign */
830 padlen = min - iplace - (max - omitcount) - 1 - ((signvalue) ? 1 : 0);
832 zpadlen = max - fplace;
837 if (flags & DP_F_MINUS)
838 padlen = -padlen; /* Left Justifty */
840 if ((flags & DP_F_ZERO) && (padlen > 0))
844 total += dopr_outch (buffer, currlen, maxlen, signvalue);
850 total += dopr_outch (buffer, currlen, maxlen, '0');
856 total += dopr_outch (buffer, currlen, maxlen, ' ');
860 total += dopr_outch (buffer, currlen, maxlen, signvalue);
863 total += dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]);
866 * Decimal point. This should probably use locale to find the correct
869 if (max > 0 && (fplace > omitcount || zpadlen > 0))
871 total += dopr_outch (buffer, currlen, maxlen, '.');
873 while (fplace > omitcount)
874 total += dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]);
879 total += dopr_outch (buffer, currlen, maxlen, '0');
885 total += dopr_outch (buffer, currlen, maxlen, ' ');
892 static int dopr_outch (char *buffer, size_t *currlen, size_t maxlen, char c)
894 if (*currlen + 1 < maxlen)
895 buffer[(*currlen)++] = c;
899 int vsnprintf (char *str, size_t count, const char *fmt, va_list args)
903 return dopr(str, count, fmt, args);
906 int snprintf (char *str, size_t count, const char *fmt,...)
912 total = vsnprintf(str, count, fmt, ap);
919 #define LONG_STRING 1024
923 char buf1[LONG_STRING];
924 char buf2[LONG_STRING];
941 double fp_nums[] = { -1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996,
942 0.9996, 1.996, 4.136, 0};
955 long int_nums[] = { -1, 134, 91340, 341, 0203, 0};
960 printf ("Testing snprintf format codes against system sprintf...\n");
962 for (x = 0; fp_fmt[x] != NULL ; x++)
963 for (y = 0; fp_nums[y] != 0 ; y++)
965 snprintf (buf1, sizeof (buf1), fp_fmt[x], fp_nums[y]);
966 sprintf (buf2, fp_fmt[x], fp_nums[y]);
967 if (strcmp (buf1, buf2))
969 printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n",
970 fp_fmt[x], buf1, buf2);
976 for (x = 0; int_fmt[x] != NULL ; x++)
977 for (y = 0; int_nums[y] != 0 ; y++)
979 snprintf (buf1, sizeof (buf1), int_fmt[x], int_nums[y]);
980 sprintf (buf2, int_fmt[x], int_nums[y]);
981 if (strcmp (buf1, buf2))
983 printf("snprintf doesn't match Format: %s\n\tsnprintf = %s\n\tsprintf = %s\n",
984 int_fmt[x], buf1, buf2);
989 printf ("%d tests failed out of %d.\n", fail, num);
992 #endif /* TEST_SNPRINTF */