]> git.ipfire.org Git - thirdparty/ccache.git/commitdiff
portability: Add Holger Weiß's improved snprintf replacement
authorJoel Rosdahl <joel@rosdahl.net>
Wed, 17 Nov 2010 20:20:24 +0000 (21:20 +0100)
committerJoel Rosdahl <joel@rosdahl.net>
Mon, 22 Nov 2010 20:07:35 +0000 (21:07 +0100)
LICENSE.txt
Makefile.in
ccache.h
configure.ac
m4/snprintf.m4 [new file with mode: 0644]
snprintf.c
system.h

index 3fe2a66e5be9fac6e1e5149ff3ac4821c1652e70..c616009a2417f6f3adc8cbf87373618c43c6e953 100644 (file)
@@ -420,17 +420,28 @@ This fast hash implementation is released to the public domain by Austin
 Appleby. See http://murmurhash.googlepages.com.
 
 
-snprintf.c
-~~~~~~~~~~
+snprintf.c and m4/snprintf.m4
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-This implementation of `snprintf()` and similar functions was copied from the
-Samba project at some point and has the following license:
+This implementation of `snprintf()` and similar functions was downloaded from
+http://www.jhweiss.de/software/snprintf.html and has the following license:
 
 -------------------------------------------------------------------------------
-  Copyright Patrick Powell 1995
-  This code is based on code written by Patrick Powell (papowell@astart.com)
-  It may be used for any purpose as long as this notice remains intact
-  on all source code distributions
+  Copyright (c) 1995 Patrick Powell.
+
+  This code is based on code written by Patrick Powell <papowell@astart.com>.
+  It may be used for any purpose as long as this notice remains intact on all
+  source code distributions.
+
+  Copyright (c) 2008 Holger Weiss.
+
+  This version of the code is maintained by Holger Weiss <holger@jhweiss.de>.
+  My changes to the code may freely be used, modified and/or redistributed for
+  any purpose. It would be nice if additions and fixes to this file (including
+  trivial code cleanups) would be sent back in order to let me include them in
+  the version available at <http://www.jhweiss.de/software/snprintf.html>.
+  However, this is not a requirement for using or redistributing (possibly
+  modified) versions of this file, nor is leaving this notice intact mandatory.
 -------------------------------------------------------------------------------
 
 
index b095e1c839580369c0929373949f6a7dfc5c4434..0ce3cdf3b18ba1709a9ebcb3059ccaa51274a8f5 100644 (file)
@@ -11,7 +11,7 @@ installcmd = @INSTALL@
 AR = @AR@
 CC = @CC@
 CFLAGS = @CFLAGS@
-CPPFLAGS = @CPPFLAGS@ -I. -I$(srcdir)
+CPPFLAGS = @DEFS@ @CPPFLAGS@ -I. -I$(srcdir)
 LDFLAGS = @LDFLAGS@
 EXEEXT = @EXEEXT@
 RANLIB = @RANLIB@
index 68ecdb8524c4b83f23d2f70bb9a1fb607ae1d831..1620b78f2ae92b4ff5ba1db1e12b08611aac18a9 100644 (file)
--- a/ccache.h
+++ b/ccache.h
@@ -175,20 +175,6 @@ void exitfn_add_nullary(void (*function)(void));
 void exitfn_add(void (*function)(void *), void *context);
 void exitfn_call(void);
 
-/* ------------------------------------------------------------------------- */
-/* snprintf.c */
-
-#ifndef HAVE_VASPRINTF
-int vasprintf(char **, const char *, va_list) ATTR_FORMAT(printf, 2, 0);
-#endif
-#ifndef HAVE_ASPRINTF
-int asprintf(char **ptr, const char *, ...) ATTR_FORMAT(printf, 2, 3);
-#endif
-
-#ifndef HAVE_SNPRINTF
-int snprintf(char *, size_t, const char *, ...) ATTR_FORMAT(printf, 3, 4);
-#endif
-
 /* ------------------------------------------------------------------------- */
 /* cleanup.c */
 
index a65c31ee5790c09917ba0a59692c8fab542b920f..ffc49f8e1b1583d37e9d05e6eb5bc15abae4d78d 100644 (file)
@@ -38,18 +38,14 @@ AC_HEADER_SYS_WAIT
 AC_CHECK_HEADERS(ctype.h pwd.h stdlib.h string.h strings.h sys/time.h sys/mman.h)
 AC_CHECK_HEADERS(termios.h)
 
-AC_CHECK_FUNCS(asprintf)
 AC_CHECK_FUNCS(gethostname)
 AC_CHECK_FUNCS(getopt_long)
 AC_CHECK_FUNCS(getpwuid)
 AC_CHECK_FUNCS(gettimeofday)
 AC_CHECK_FUNCS(mkstemp)
 AC_CHECK_FUNCS(realpath)
-AC_CHECK_FUNCS(snprintf)
 AC_CHECK_FUNCS(strndup)
 AC_CHECK_FUNCS(utimes)
-AC_CHECK_FUNCS(vasprintf)
-AC_CHECK_FUNCS(vsnprintf)
 
 AC_CACHE_CHECK([for compar_fn_t in stdlib.h],ccache_cv_COMPAR_FN_T, [
     AC_TRY_COMPILE(
@@ -62,34 +58,12 @@ if test x"$ccache_cv_COMPAR_FN_T" = x"yes"; then
              Define to 1 if you have the `__compar_fn_t' typedef.)
 fi
 
-AC_CACHE_CHECK([for C99 vsnprintf],ccache_cv_HAVE_C99_VSNPRINTF,[
-AC_TRY_RUN([
-#include <sys/types.h>
-#include <stdarg.h>
-#include <stdio.h>
-void foo(const char *format, ...) {
-    va_list ap;
-    int len;
-    char buf[5];
-
-    va_start(ap, format);
-    len = vsnprintf(0, 0, format, ap);
-    va_end(ap);
-    if (len != 5) exit(1);
-
-    if (snprintf(buf, 3, "hello") != 5 || strcmp(buf, "he") != 0) exit(1);
-
-    exit(0);
-}
-main() { foo("hello"); }
-],
-ccache_cv_HAVE_C99_VSNPRINTF=yes,
-ccache_cv_HAVE_C99_VSNPRINTF=no,
-ccache_cv_HAVE_C99_VSNPRINTF=cross)])
-if test x"$ccache_cv_HAVE_C99_VSNPRINTF" = x"yes"; then
-    AC_DEFINE(HAVE_C99_VSNPRINTF, 1,
-              Define to 1 if you have the `vsnprintf' function.)
-fi
+dnl Replacements of snprintf and friends.
+m4_include(m4/snprintf.m4)
+HW_FUNC_VSNPRINTF
+HW_FUNC_SNPRINTF
+HW_FUNC_VASPRINTF
+HW_FUNC_ASPRINTF
 
 dnl Check for zlib.
 AC_CACHE_CHECK(
diff --git a/m4/snprintf.m4 b/m4/snprintf.m4
new file mode 100644 (file)
index 0000000..1a844ba
--- /dev/null
@@ -0,0 +1,230 @@
+# $Id: snprintf.m4,v 1.1.1.1 2008/01/06 03:24:00 holger Exp $
+
+# Copyright (c) 2008 Holger Weiss <holger@jhweiss.de>.
+#
+# This code may freely be used, modified and/or redistributed for any purpose.
+# It would be nice if additions and fixes to this file (including trivial code
+# cleanups) would be sent back in order to let me include them in the version
+# available at <http://www.jhweiss.de/software/snprintf.html>.  However, this is
+# not a requirement for using or redistributing (possibly modified) versions of
+# this file, nor is leaving this notice intact mandatory.
+
+# HW_HEADER_STDARG_H
+# ------------------
+# Define HAVE_STDARG_H to 1 if <stdarg.h> is available.
+AC_DEFUN([HW_HEADER_STDARG_H],
+[
+  AC_PREREQ([2.60])dnl Older releases should work if AC_CHECK_HEADERS is used.
+  AC_CHECK_HEADERS_ONCE([stdarg.h])
+])# HW_HEADER_STDARG_H
+
+# HW_HEADER_VARARGS_H
+# -------------------
+# Define HAVE_VARARGS_H to 1 if <varargs.h> is available.
+AC_DEFUN([HW_HEADER_VARARGS_H],
+[
+  AC_PREREQ([2.60])dnl Older releases should work if AC_CHECK_HEADERS is used.
+  AC_CHECK_HEADERS_ONCE([varargs.h])
+])# HW_HEADER_VARARGS_H
+
+# HW_FUNC_VA_COPY
+# ---------------
+# Set $hw_cv_func_va_copy to "yes" or "no".  Define HAVE_VA_COPY to 1 if
+# $hw_cv_func_va_copy is set to "yes".  Note that it's "unspecified whether
+# va_copy and va_end are macros or identifiers declared with external linkage."
+# (C99: 7.15.1, 1)  Therefore, the presence of va_copy(3) cannot simply "be
+# tested with #ifdef", as suggested by the Autoconf manual (5.5.1).
+AC_DEFUN([HW_FUNC_VA_COPY],
+[
+  AC_REQUIRE([HW_HEADER_STDARG_H])dnl Our check evaluates HAVE_STDARG_H.
+  AC_REQUIRE([HW_HEADER_VARARGS_H])dnl Our check evaluates HAVE_VARARGS_H.
+  AC_CACHE_CHECK([for va_copy],
+    [hw_cv_func_va_copy],
+    [AC_RUN_IFELSE(
+      [AC_LANG_PROGRAM(
+        [[#if HAVE_STDARG_H
+        #include <stdarg.h>
+        #elif HAVE_VARARGS_H
+        #include <varargs.h>
+        #endif]],
+        [[va_list ap, aq; va_copy(aq, ap);]])],
+      [hw_cv_func_va_copy=yes],
+      [hw_cv_func_va_copy=no],
+      [hw_cv_func_va_copy=no])])
+  AS_IF([test "$hw_cv_func_va_copy" = yes],
+    [AC_DEFINE([HAVE_VA_COPY], [1],
+      [Define to 1 if you have the `va_copy' function or macro.])])
+])# HW_FUNC_VA_COPY
+
+# HW_FUNC___VA_COPY
+# -----------------
+# Set $hw_cv_func___va_copy to "yes" or "no".  Define HAVE___VA_COPY to 1 if
+# $hw_cv_func___va_copy is set to "yes".
+AC_DEFUN([HW_FUNC___VA_COPY],
+[
+  AC_REQUIRE([HW_HEADER_STDARG_H])dnl Our check evaluates HAVE_STDARG_H.
+  AC_REQUIRE([HW_HEADER_VARARGS_H])dnl Our check evaluates HAVE_VARARGS_H.
+  AC_CACHE_CHECK([for __va_copy],
+    [hw_cv_func___va_copy],
+    [AC_RUN_IFELSE(
+      [AC_LANG_PROGRAM(
+        [[#if HAVE_STDARG_H
+        #include <stdarg.h>
+        #elif HAVE_VARARGS_H
+        #include <varargs.h>
+        #endif]],
+        [[va_list ap, aq; __va_copy(aq, ap);]])],
+      [hw_cv_func___va_copy=yes],
+      [hw_cv_func___va_copy=no],
+      [hw_cv_func___va_copy=no])])
+  AS_IF([test "$hw_cv_func___va_copy" = yes],
+    [AC_DEFINE([HAVE___VA_COPY], [1],
+      [Define to 1 if you have the `__va_copy' function or macro.])])
+])# HW_FUNC___VA_COPY
+
+# HW_FUNC_VSNPRINTF
+# -----------------
+# Set $hw_cv_func_vsnprintf and $hw_cv_func_vsnprintf_c99 to "yes" or "no",
+# respectively.  Define HAVE_VSNPRINTF to 1 only if $hw_cv_func_vsnprintf_c99
+# is set to "yes".  Otherwise, define vsnprintf to rpl_vsnprintf and make sure
+# the replacement function will be built.
+AC_DEFUN([HW_FUNC_VSNPRINTF],
+[
+  AC_PREREQ([2.60])dnl 2.59 should work if some AC_TYPE_* macros are replaced.
+  AC_REQUIRE([HW_HEADER_STDARG_H])dnl Our check evaluates HAVE_STDARG_H.
+  AC_CHECK_FUNC([vsnprintf],
+    [hw_cv_func_vsnprintf=yes],
+    [hw_cv_func_vsnprintf=no])
+  AS_IF([test "$hw_cv_func_vsnprintf" = yes],
+    [AC_CACHE_CHECK([whether vsnprintf is C99 compliant],
+      [hw_cv_func_vsnprintf_c99],
+      [AC_RUN_IFELSE(
+        [AC_LANG_PROGRAM(
+          [[#if HAVE_STDARG_H
+          #include <stdarg.h>
+          #endif
+          #include <stdio.h>
+          static int testprintf(char *buf, size_t size, const char *format, ...)
+          {
+            int result;
+            va_list ap;
+            va_start(ap, format);
+            result = vsnprintf(buf, size, format, ap);
+            va_end(ap);
+            return result;
+          }]],
+          [[char buf[43];
+          if (testprintf(buf, 4, "The answer is %27.2g.", 42.0) != 42 ||
+              testprintf(buf, 0, "No, it's %32zu.", (size_t)42) != 42 ||
+              buf[0] != 'T' || buf[3] != '\0')
+            return 1;]])],
+        [hw_cv_func_vsnprintf_c99=yes],
+        [hw_cv_func_vsnprintf_c99=no],
+        [hw_cv_func_vsnprintf_c99=no])])],
+    [hw_cv_func_snprintf_c99=no])
+  AS_IF([test "$hw_cv_func_vsnprintf_c99" = yes],
+    [AC_DEFINE([HAVE_VSNPRINTF], [1],
+      [Define to 1 if you have a C99 compliant `vsnprintf' function.])],
+    [AC_DEFINE([vsnprintf], [rpl_vsnprintf],
+      [Define to rpl_vsnprintf if the replacement function should be used.])
+    AC_CHECK_HEADERS([inttypes.h locale.h stddef.h stdint.h])
+    AC_CHECK_MEMBERS([struct lconv.decimal_point, struct lconv.thousands_sep],
+      [], [], [#include <locale.h>])
+    AC_TYPE_LONG_DOUBLE
+    AC_TYPE_LONG_LONG_INT
+    AC_TYPE_UNSIGNED_LONG_LONG_INT
+    AC_TYPE_SIZE_T
+    AC_TYPE_INTMAX_T
+    AC_TYPE_UINTMAX_T
+    AC_TYPE_UINTPTR_T
+    AC_CHECK_TYPES([ptrdiff_t])
+    AC_CHECK_FUNCS([localeconv])
+    _HW_FUNC_XPRINTF_REPLACE])
+])# HW_FUNC_VSNPRINTF
+
+# HW_FUNC_SNPRINTF
+# ----------------
+# Set $hw_cv_func_snprintf and $hw_cv_func_snprintf_c99 to "yes" or "no",
+# respectively.  Define HAVE_SNPRINTF to 1 only if $hw_cv_func_snprintf_c99
+# is set to "yes".  Otherwise, define snprintf to rpl_snprintf and make sure
+# the replacement function will be built.
+AC_DEFUN([HW_FUNC_SNPRINTF],
+[
+  AC_REQUIRE([HW_FUNC_VSNPRINTF])dnl Our snprintf(3) calls vsnprintf(3).
+  AC_CHECK_FUNC([snprintf],
+    [hw_cv_func_snprintf=yes],
+    [hw_cv_func_snprintf=no])
+  AS_IF([test "$hw_cv_func_snprintf" = yes],
+    [AC_CACHE_CHECK([whether snprintf is C99 compliant],
+      [hw_cv_func_snprintf_c99],
+      [AC_RUN_IFELSE(
+        [AC_LANG_PROGRAM([[#include <stdio.h>]],
+          [[char buf[43];
+          if (snprintf(buf, 4, "The answer is %27.2g.", 42.0) != 42 ||
+              snprintf(buf, 0, "No, it's %32zu.", (size_t)42) != 42 ||
+              buf[0] != 'T' || buf[3] != '\0')
+            return 1;]])],
+        [hw_cv_func_snprintf_c99=yes],
+        [hw_cv_func_snprintf_c99=no],
+        [hw_cv_func_snprintf_c99=no])])],
+    [hw_cv_func_snprintf_c99=no])
+  AS_IF([test "$hw_cv_func_snprintf_c99" = yes],
+    [AC_DEFINE([HAVE_SNPRINTF], [1],
+      [Define to 1 if you have a C99 compliant `snprintf' function.])],
+    [AC_DEFINE([snprintf], [rpl_snprintf],
+      [Define to rpl_snprintf if the replacement function should be used.])
+    _HW_FUNC_XPRINTF_REPLACE])
+])# HW_FUNC_SNPRINTF
+
+# HW_FUNC_VASPRINTF
+# -----------------
+# Set $hw_cv_func_vasprintf to "yes" or "no".  Define HAVE_VASPRINTF to 1 if
+# $hw_cv_func_vasprintf is set to "yes".  Otherwise, define vasprintf to
+# rpl_vasprintf and make sure the replacement function will be built.
+AC_DEFUN([HW_FUNC_VASPRINTF],
+[
+  AC_REQUIRE([HW_FUNC_VSNPRINTF])dnl Our vasprintf(3) calls vsnprintf(3).
+  AC_CHECK_FUNCS([vasprintf],
+    [hw_cv_func_vasprintf=yes],
+    [hw_cv_func_vasprintf=no])
+  AS_IF([test "$hw_cv_func_vasprintf" = no],
+    [AC_DEFINE([vasprintf], [rpl_vasprintf],
+      [Define to rpl_vasprintf if the replacement function should be used.])
+    AC_CHECK_HEADERS([stdlib.h])
+    HW_FUNC_VA_COPY
+    AS_IF([test "$hw_cv_func_va_copy" = no],
+      [HW_FUNC___VA_COPY])
+    _HW_FUNC_XPRINTF_REPLACE])
+])# HW_FUNC_VASPRINTF
+
+# HW_FUNC_ASPRINTF
+# ----------------
+# Set $hw_cv_func_asprintf to "yes" or "no".  Define HAVE_ASPRINTF to 1 if
+# $hw_cv_func_asprintf is set to "yes".  Otherwise, define asprintf to
+# rpl_asprintf and make sure the replacement function will be built.
+AC_DEFUN([HW_FUNC_ASPRINTF],
+[
+  AC_REQUIRE([HW_FUNC_VASPRINTF])dnl Our asprintf(3) calls vasprintf(3).
+  AC_CHECK_FUNCS([asprintf],
+    [hw_cv_func_asprintf=yes],
+    [hw_cv_func_asprintf=no])
+  AS_IF([test "$hw_cv_func_asprintf" = no],
+    [AC_DEFINE([asprintf], [rpl_asprintf],
+      [Define to rpl_asprintf if the replacement function should be used.])
+    _HW_FUNC_XPRINTF_REPLACE])
+])# HW_FUNC_ASPRINTF
+
+# _HW_FUNC_XPRINTF_REPLACE
+# ------------------------
+# Arrange for building snprintf.c.  Must be called if one or more of the
+# functions provided by snprintf.c are needed.
+AC_DEFUN([_HW_FUNC_XPRINTF_REPLACE],
+[
+  AS_IF([test "x$_hw_cv_func_xprintf_replace_done" != xyes],
+    [AC_C_CONST
+    HW_HEADER_STDARG_H
+    AC_LIBOBJ([snprintf])
+    _hw_cv_func_xprintf_replace_done=yes])
+])# _HW_FUNC_XPRINTF_REPLACE
+
+dnl vim: set joinspaces textwidth=80:
index 3de189c1d54bc4aff6353389f946ec4c39030268..587c6be3a92a370a7978869f38915a1da1551fb1 100644 (file)
+/* $Id: snprintf.c,v 1.9 2008/01/20 14:02:00 holger Exp $ */
+
+/*
+ * Copyright (c) 1995 Patrick Powell.
+ *
+ * This code is based on code written by Patrick Powell <papowell@astart.com>.
+ * It may be used for any purpose as long as this notice remains intact on all
+ * source code distributions.
+ */
+
 /*
- * Copyright Patrick Powell 1995
- * This code is based on code written by Patrick Powell (papowell@astart.com)
- * It may be used for any purpose as long as this notice remains intact
- * on all source code distributions
+ * Copyright (c) 2008 Holger Weiss.
+ *
+ * This version of the code is maintained by Holger Weiss <holger@jhweiss.de>.
+ * My changes to the code may freely be used, modified and/or redistributed for
+ * any purpose.  It would be nice if additions and fixes to this file (including
+ * trivial code cleanups) would be sent back in order to let me include them in
+ * the version available at <http://www.jhweiss.de/software/snprintf.html>.
+ * However, this is not a requirement for using or redistributing (possibly
+ * modified) versions of this file, nor is leaving this notice intact mandatory.
  */
 
-/**************************************************************
- * Original:
- * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
- * A bombproof version of doprnt (dopr) included.
- * Sigh.  This sort of thing is always nasty do deal with.  Note that
- * the version here does not include floating point...
+/*
+ * History
+ *
+ * 2008-01-20 Holger Weiss <holger@jhweiss.de> for C99-snprintf 1.1:
+ *
+ *     Fixed the detection of infinite floating point values on IRIX (and
+ *     possibly other systems) and applied another few minor cleanups.
+ *
+ * 2008-01-06 Holger Weiss <holger@jhweiss.de> for C99-snprintf 1.0:
+ *
+ *     Added a lot of new features, fixed many bugs, and incorporated various
+ *     improvements done by Andrew Tridgell <tridge@samba.org>, Russ Allbery
+ *     <rra@stanford.edu>, Hrvoje Niksic <hniksic@xemacs.org>, Damien Miller
+ *     <djm@mindrot.org>, and others for the Samba, INN, Wget, and OpenSSH
+ *     projects.  The additions include: support the "e", "E", "g", "G", and
+ *     "F" conversion specifiers (and use conversion style "f" or "F" for the
+ *     still unsupported "a" and "A" specifiers); support the "hh", "ll", "j",
+ *     "t", and "z" length modifiers; support the "#" flag and the (non-C99)
+ *     "'" flag; use localeconv(3) (if available) to get both the current
+ *     locale's decimal point character and the separator between groups of
+ *     digits; fix the handling of various corner cases of field width and
+ *     precision specifications; fix various floating point conversion bugs;
+ *     handle infinite and NaN floating point values; don't attempt to write to
+ *     the output buffer (which may be NULL) if a size of zero was specified;
+ *     check for integer overflow of the field width, precision, and return
+ *     values and during the floating point conversion; use the OUTCHAR() macro
+ *     instead of a function for better performance; provide asprintf(3) and
+ *     vasprintf(3) functions; add new test cases.  The replacement functions
+ *     have been renamed to use an "rpl_" prefix, the function calls in the
+ *     main project (and in this file) must be redefined accordingly for each
+ *     replacement function which is needed (by using Autoconf or other means).
+ *     Various other minor improvements have been applied and the coding style
+ *     was cleaned up for consistency.
+ *
+ * 2007-07-23 Holger Weiss <holger@jhweiss.de> for Mutt 1.5.13:
+ *
+ *     C99 compliant snprintf(3) and vsnprintf(3) functions return the number
+ *     of characters that would have been written to a sufficiently sized
+ *     buffer (excluding the '\0').  The original code simply returned the
+ *     length of the resulting output string, so that's been fixed.
+ *
+ * 1998-03-05 Michael Elkins <me@mutt.org> for Mutt 0.90.8:
+ *
+ *     The original code assumed that both snprintf(3) and vsnprintf(3) were
+ *     missing.  Some systems only have snprintf(3) but not vsnprintf(3), so
+ *     the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
+ *
+ * 1998-01-27 Thomas Roessler <roessler@does-not-exist.org> for Mutt 0.89i:
  *
- * snprintf() is used instead of sprintf() as it does limit checks
- * for string length.  This covers a nasty loophole.
+ *     The PGP code was using unsigned hexadecimal formats.  Unfortunately,
+ *     unsigned formats simply didn't work.
  *
- * The other functions are there to prevent NULL pointers from
- * causing nast effects.
+ * 1997-10-22 Brandon Long <blong@fiction.net> for Mutt 0.87.1:
  *
- * More Recently:
- *  Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
- *  This was ugly.  It is still ugly.  I opted out of floating point
- *  numbers, but the formatter understands just about everything
- *  from the normal C string format, at least as far as I can tell from
- *  the Solaris 2.5 printf(3S) man page.
+ *     Ok, added some minimal floating point support, which means this probably
+ *     requires libm on most operating systems.  Don't yet support the exponent
+ *     (e,E) and sigfig (g,G).  Also, fmtint() was pretty badly broken, it just
+ *     wasn't being exercised in ways which showed it, so that's been fixed.
+ *     Also, formatted the code to Mutt conventions, and removed dead code left
+ *     over from the original.  Also, there is now a builtin-test, run with:
+ *     gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm && ./snprintf
  *
- *  Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
- *    Ok, added some minimal floating point support, which means this
- *    probably requires libm on most operating systems.  Don't yet
- *    support the exponent (e,E) and sigfig (g,G).  Also, fmtint()
- *    was pretty badly broken, it just wasn't being exercised in ways
- *    which showed it, so that's been fixed.  Also, formated the code
- *    to mutt conventions, and removed dead code left over from the
- *    original.  Also, there is now a builtin-test, just compile with:
- *           gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
- *    and run snprintf for results.
+ * 2996-09-15 Brandon Long <blong@fiction.net> for Mutt 0.43:
  *
- *  Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
- *    The PGP code was using unsigned hexadecimal formats.
- *    Unfortunately, unsigned formats simply didn't work.
+ *     This was ugly.  It is still ugly.  I opted out of floating point
+ *     numbers, but the formatter understands just about everything from the
+ *     normal C string format, at least as far as I can tell from the Solaris
+ *     2.5 printf(3S) man page.
+ */
+
+/*
+ * ToDo
+ *
+ * - Add wide character support.
+ * - Add support for "%a" and "%A" conversions.
+ * - Create test routines which predefine the expected results.  Our test cases
+ *   usually expose bugs in system implementations rather than in ours :-)
+ */
+
+/*
+ * Usage
+ *
+ * 1) The following preprocessor macros should be defined to 1 if the feature or
+ *    file in question is available on the target system (by using Autoconf or
+ *    other means), though basic functionality should be available as long as
+ *    HAVE_STDARG_H and HAVE_STDLIB_H are defined correctly:
  *
- *  Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
- *    The original code assumed that both snprintf() and vsnprintf() were
- *    missing.  Some systems only have snprintf() but not vsnprintf(), so
- *    the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
+ *     HAVE_VSNPRINTF
+ *     HAVE_SNPRINTF
+ *     HAVE_VASPRINTF
+ *     HAVE_ASPRINTF
+ *     HAVE_STDARG_H
+ *     HAVE_STDDEF_H
+ *     HAVE_STDINT_H
+ *     HAVE_STDLIB_H
+ *     HAVE_INTTYPES_H
+ *     HAVE_LOCALE_H
+ *     HAVE_LOCALECONV
+ *     HAVE_LCONV_DECIMAL_POINT
+ *     HAVE_LCONV_THOUSANDS_SEP
+ *     HAVE_LONG_DOUBLE
+ *     HAVE_LONG_LONG_INT
+ *     HAVE_UNSIGNED_LONG_LONG_INT
+ *     HAVE_INTMAX_T
+ *     HAVE_UINTMAX_T
+ *     HAVE_UINTPTR_T
+ *     HAVE_PTRDIFF_T
+ *     HAVE_VA_COPY
+ *     HAVE___VA_COPY
  *
- *  Andrew Tridgell (tridge@samba.org) Oct 1998
- *    fixed handling of %.0f
- *    added test for HAVE_LONG_DOUBLE
+ * 2) The calls to the functions which should be replaced must be redefined
+ *    throughout the project files (by using Autoconf or other means):
  *
- * tridge@samba.org, idra@samba.org, April 2001
- *    got rid of fcvt code (twas buggy and made testing harder)
- *    added C99 semantics
+ *     #define vsnprintf rpl_vsnprintf
+ *     #define snprintf rpl_snprintf
+ *     #define vasprintf rpl_vasprintf
+ *     #define asprintf rpl_asprintf
  *
- **************************************************************/
-
-#ifndef NO_CONFIG_H /* for some tests */
-#include "config.h"
-#endif
-
-#ifdef HAVE_STRING_H
-#include <string.h>
-#endif
-
-#ifdef HAVE_STRINGS_H
-#include <strings.h>
-#endif
-#ifdef HAVE_CTYPE_H
-#include <ctype.h>
-#endif
-#include <sys/types.h>
+ * 3) The required replacement functions should be declared in some header file
+ *    included throughout the project files:
+ *
+ *     #if HAVE_CONFIG_H
+ *     #include <config.h>
+ *     #endif
+ *     #if HAVE_STDARG_H
+ *     #include <stdarg.h>
+ *     #if !HAVE_VSNPRINTF
+ *     int rpl_vsnprintf(char *, size_t, const char *, va_list);
+ *     #endif
+ *     #if !HAVE_SNPRINTF
+ *     int rpl_snprintf(char *, size_t, const char *, ...);
+ *     #endif
+ *     #if !HAVE_VASPRINTF
+ *     int rpl_vasprintf(char **, const char *, va_list);
+ *     #endif
+ *     #if !HAVE_ASPRINTF
+ *     int rpl_asprintf(char **, const char *, ...);
+ *     #endif
+ *     #endif
+ *
+ * Autoconf macros for handling step 1 and step 2 are available at
+ * <http://www.jhweiss.de/software/snprintf.html>.
+ */
+
+#if HAVE_CONFIG_H
+#include <config.h>
+#endif /* HAVE_CONFIG_H */
+
+#if TEST_SNPRINTF
+#include <math.h>      /* For pow(3), NAN, and INFINITY. */
+#include <string.h>    /* For strcmp(3). */
+#if defined(__NetBSD__) || \
+    defined(__FreeBSD__) || \
+    defined(__OpenBSD__) || \
+    defined(__NeXT__) || \
+    defined(__bsd__)
+#define OS_BSD 1
+#elif defined(sgi) || defined(__sgi)
+#ifndef __c99
+#define __c99  /* Force C99 mode to get <stdint.h> included on IRIX 6.5.30. */
+#endif /* !defined(__c99) */
+#define OS_IRIX 1
+#define OS_SYSV 1
+#elif defined(__svr4__)
+#define OS_SYSV 1
+#elif defined(__linux__)
+#define OS_LINUX 1
+#endif /* defined(__NetBSD__) || defined(__FreeBSD__) || [...] */
+#if HAVE_CONFIG_H      /* Undefine definitions possibly done in config.h. */
+#ifdef HAVE_SNPRINTF
+#undef HAVE_SNPRINTF
+#endif /* defined(HAVE_SNPRINTF) */
+#ifdef HAVE_VSNPRINTF
+#undef HAVE_VSNPRINTF
+#endif /* defined(HAVE_VSNPRINTF) */
+#ifdef HAVE_ASPRINTF
+#undef HAVE_ASPRINTF
+#endif /* defined(HAVE_ASPRINTF) */
+#ifdef HAVE_VASPRINTF
+#undef HAVE_VASPRINTF
+#endif /* defined(HAVE_VASPRINTF) */
+#ifdef snprintf
+#undef snprintf
+#endif /* defined(snprintf) */
+#ifdef vsnprintf
+#undef vsnprintf
+#endif /* defined(vsnprintf) */
+#ifdef asprintf
+#undef asprintf
+#endif /* defined(asprintf) */
+#ifdef vasprintf
+#undef vasprintf
+#endif /* defined(vasprintf) */
+#else  /* By default, we assume a modern system for testing. */
+#ifndef HAVE_STDARG_H
+#define HAVE_STDARG_H 1
+#endif /* HAVE_STDARG_H */
+#ifndef HAVE_STDDEF_H
+#define HAVE_STDDEF_H 1
+#endif /* HAVE_STDDEF_H */
+#ifndef HAVE_STDINT_H
+#define HAVE_STDINT_H 1
+#endif /* HAVE_STDINT_H */
+#ifndef HAVE_STDLIB_H
+#define HAVE_STDLIB_H 1
+#endif /* HAVE_STDLIB_H */
+#ifndef HAVE_INTTYPES_H
+#define HAVE_INTTYPES_H 1
+#endif /* HAVE_INTTYPES_H */
+#ifndef HAVE_LOCALE_H
+#define HAVE_LOCALE_H 1
+#endif /* HAVE_LOCALE_H */
+#ifndef HAVE_LOCALECONV
+#define HAVE_LOCALECONV 1
+#endif /* !defined(HAVE_LOCALECONV) */
+#ifndef HAVE_LCONV_DECIMAL_POINT
+#define HAVE_LCONV_DECIMAL_POINT 1
+#endif /* HAVE_LCONV_DECIMAL_POINT */
+#ifndef HAVE_LCONV_THOUSANDS_SEP
+#define HAVE_LCONV_THOUSANDS_SEP 1
+#endif /* HAVE_LCONV_THOUSANDS_SEP */
+#ifndef HAVE_LONG_DOUBLE
+#define HAVE_LONG_DOUBLE 1
+#endif /* !defined(HAVE_LONG_DOUBLE) */
+#ifndef HAVE_LONG_LONG_INT
+#define HAVE_LONG_LONG_INT 1
+#endif /* !defined(HAVE_LONG_LONG_INT) */
+#ifndef HAVE_UNSIGNED_LONG_LONG_INT
+#define HAVE_UNSIGNED_LONG_LONG_INT 1
+#endif /* !defined(HAVE_UNSIGNED_LONG_LONG_INT) */
+#ifndef HAVE_INTMAX_T
+#define HAVE_INTMAX_T 1
+#endif /* !defined(HAVE_INTMAX_T) */
+#ifndef HAVE_UINTMAX_T
+#define HAVE_UINTMAX_T 1
+#endif /* !defined(HAVE_UINTMAX_T) */
+#ifndef HAVE_UINTPTR_T
+#define HAVE_UINTPTR_T 1
+#endif /* !defined(HAVE_UINTPTR_T) */
+#ifndef HAVE_PTRDIFF_T
+#define HAVE_PTRDIFF_T 1
+#endif /* !defined(HAVE_PTRDIFF_T) */
+#ifndef HAVE_VA_COPY
+#define HAVE_VA_COPY 1
+#endif /* !defined(HAVE_VA_COPY) */
+#ifndef HAVE___VA_COPY
+#define HAVE___VA_COPY 1
+#endif /* !defined(HAVE___VA_COPY) */
+#endif /* HAVE_CONFIG_H */
+#define snprintf rpl_snprintf
+#define vsnprintf rpl_vsnprintf
+#define asprintf rpl_asprintf
+#define vasprintf rpl_vasprintf
+#endif /* TEST_SNPRINTF */
+
+#if !HAVE_SNPRINTF || !HAVE_VSNPRINTF || !HAVE_ASPRINTF || !HAVE_VASPRINTF
+#include <stdio.h>     /* For NULL, size_t, vsnprintf(3), and vasprintf(3). */
+#ifdef VA_START
+#undef VA_START
+#endif /* defined(VA_START) */
+#ifdef VA_SHIFT
+#undef VA_SHIFT
+#endif /* defined(VA_SHIFT) */
+#if HAVE_STDARG_H
 #include <stdarg.h>
-#ifdef HAVE_STDLIB_H
-#include <stdlib.h>
-#endif
-
-#if defined(HAVE_SNPRINTF) && defined(HAVE_VSNPRINTF) && defined(HAVE_C99_VSNPRINTF)
-/* only include stdio.h if we are not re-defining snprintf or vsnprintf */
-#include <stdio.h>
- /* make the compiler happy with an empty file */
- void dummy_snprintf(void) {}
+#define VA_START(ap, last) va_start(ap, last)
+#define VA_SHIFT(ap, value, type) /* No-op for ANSI C. */
+#else  /* Assume <varargs.h> is available. */
+#include <varargs.h>
+#define VA_START(ap, last) va_start(ap)        /* "last" is ignored. */
+#define VA_SHIFT(ap, value, type) value = va_arg(ap, type)
+#endif /* HAVE_STDARG_H */
+
+#if !HAVE_VASPRINTF
+#if HAVE_STDLIB_H
+#include <stdlib.h>    /* For malloc(3). */
+#endif /* HAVE_STDLIB_H */
+#ifdef VA_COPY
+#undef VA_COPY
+#endif /* defined(VA_COPY) */
+#ifdef VA_END_COPY
+#undef VA_END_COPY
+#endif /* defined(VA_END_COPY) */
+#if HAVE_VA_COPY
+#define VA_COPY(dest, src) va_copy(dest, src)
+#define VA_END_COPY(ap) va_end(ap)
+#elif HAVE___VA_COPY
+#define VA_COPY(dest, src) __va_copy(dest, src)
+#define VA_END_COPY(ap) va_end(ap)
 #else
-
-#ifdef HAVE_LONG_DOUBLE
+#define VA_COPY(dest, src) (void)mymemcpy(&dest, &src, sizeof(va_list))
+#define VA_END_COPY(ap) /* No-op. */
+#define NEED_MYMEMCPY 1
+static void *mymemcpy(void *, void *, size_t);
+#endif /* HAVE_VA_COPY */
+#endif /* !HAVE_VASPRINTF */
+
+#if !HAVE_VSNPRINTF
+#include <errno.h>     /* For ERANGE and errno. */
+#include <limits.h>    /* For *_MAX. */
+#if HAVE_INTTYPES_H
+#include <inttypes.h>  /* For intmax_t (if not defined in <stdint.h>). */
+#endif /* HAVE_INTTYPES_H */
+#if HAVE_LOCALE_H
+#include <locale.h>    /* For localeconv(3). */
+#endif /* HAVE_LOCALE_H */
+#if HAVE_STDDEF_H
+#include <stddef.h>    /* For ptrdiff_t. */
+#endif /* HAVE_STDDEF_H */
+#if HAVE_STDINT_H
+#include <stdint.h>    /* For intmax_t. */
+#endif /* HAVE_STDINT_H */
+
+/* Support for unsigned long long int.  We may also need ULLONG_MAX. */
+#ifndef ULONG_MAX      /* We may need ULONG_MAX as a fallback. */
+#ifdef UINT_MAX
+#define ULONG_MAX UINT_MAX
+#else
+#define ULONG_MAX INT_MAX
+#endif /* defined(UINT_MAX) */
+#endif /* !defined(ULONG_MAX) */
+#ifdef ULLONG
+#undef ULLONG
+#endif /* defined(ULLONG) */
+#if HAVE_UNSIGNED_LONG_LONG_INT
+#define ULLONG unsigned long long int
+#ifndef ULLONG_MAX
+#define ULLONG_MAX ULONG_MAX
+#endif /* !defined(ULLONG_MAX) */
+#else
+#define ULLONG unsigned long int
+#ifdef ULLONG_MAX
+#undef ULLONG_MAX
+#endif /* defined(ULLONG_MAX) */
+#define ULLONG_MAX ULONG_MAX
+#endif /* HAVE_LONG_LONG_INT */
+
+/* Support for uintmax_t.  We also need UINTMAX_MAX. */
+#ifdef UINTMAX_T
+#undef UINTMAX_T
+#endif /* defined(UINTMAX_T) */
+#if HAVE_UINTMAX_T || defined(uintmax_t)
+#define UINTMAX_T uintmax_t
+#ifndef UINTMAX_MAX
+#define UINTMAX_MAX ULLONG_MAX
+#endif /* !defined(UINTMAX_MAX) */
+#else
+#define UINTMAX_T ULLONG
+#ifdef UINTMAX_MAX
+#undef UINTMAX_MAX
+#endif /* defined(UINTMAX_MAX) */
+#define UINTMAX_MAX ULLONG_MAX
+#endif /* HAVE_UINTMAX_T || defined(uintmax_t) */
+
+/* Support for long double. */
+#ifndef LDOUBLE
+#if HAVE_LONG_DOUBLE
 #define LDOUBLE long double
 #else
 #define LDOUBLE double
-#endif
+#endif /* HAVE_LONG_DOUBLE */
+#endif /* !defined(LDOUBLE) */
 
-#ifdef HAVE_LONG_LONG
-#define LLONG long long
+/* Support for long long int. */
+#ifndef LLONG
+#if HAVE_LONG_LONG_INT
+#define LLONG long long int
 #else
-#define LLONG long
-#endif
-
-static size_t dopr(char *buffer, size_t maxlen, const char *format,
-                  va_list args);
-static void fmtstr(char *buffer, size_t *currlen, size_t maxlen,
-                   char *value, int flags, int min, int max);
-static void fmtint(char *buffer, size_t *currlen, size_t maxlen,
-                   long value, int base, int min, int max, int flags);
-static void fmtfp(char *buffer, size_t *currlen, size_t maxlen,
-                  LDOUBLE fvalue, int min, int max, int flags);
-static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c);
+#define LLONG long int
+#endif /* HAVE_LONG_LONG_INT */
+#endif /* !defined(LLONG) */
+
+/* Support for intmax_t. */
+#ifndef INTMAX_T
+#if HAVE_INTMAX_T || defined(intmax_t)
+#define INTMAX_T intmax_t
+#else
+#define INTMAX_T LLONG
+#endif /* HAVE_INTMAX_T || defined(intmax_t) */
+#endif /* !defined(INTMAX_T) */
+
+/* Support for uintptr_t. */
+#ifndef UINTPTR_T
+#if HAVE_UINTPTR_T || defined(uintptr_t)
+#define UINTPTR_T uintptr_t
+#else
+#define UINTPTR_T unsigned long int
+#endif /* HAVE_UINTPTR_T || defined(uintptr_t) */
+#endif /* !defined(UINTPTR_T) */
+
+/* Support for ptrdiff_t. */
+#ifndef PTRDIFF_T
+#if HAVE_PTRDIFF_T || defined(ptrdiff_t)
+#define PTRDIFF_T ptrdiff_t
+#else
+#define PTRDIFF_T long int
+#endif /* HAVE_PTRDIFF_T || defined(ptrdiff_t) */
+#endif /* !defined(PTRDIFF_T) */
 
 /*
- * dopr(): poor man's version of doprintf
+ * We need an unsigned integer type corresponding to ptrdiff_t (cf. C99:
+ * 7.19.6.1, 7).  However, we'll simply use PTRDIFF_T and convert it to an
+ * unsigned type if necessary.  This should work just fine in practice.
  */
+#ifndef UPTRDIFF_T
+#define UPTRDIFF_T PTRDIFF_T
+#endif /* !defined(UPTRDIFF_T) */
 
-/* format read states */
-#define DP_S_DEFAULT 0
-#define DP_S_FLAGS   1
-#define DP_S_MIN     2
-#define DP_S_DOT     3
-#define DP_S_MAX     4
-#define DP_S_MOD     5
-#define DP_S_CONV    6
-#define DP_S_DONE    7
-
-/* format flags - Bits */
-#define DP_F_MINUS     (1 << 0)
-#define DP_F_PLUS      (1 << 1)
-#define DP_F_SPACE     (1 << 2)
-#define DP_F_NUM       (1 << 3)
-#define DP_F_ZERO      (1 << 4)
-#define DP_F_UP        (1 << 5)
-#define DP_F_UNSIGNED  (1 << 6)
-
-/* Conversion Flags */
-#define DP_C_SHORT   1
-#define DP_C_LONG    2
-#define DP_C_LDOUBLE 3
-#define DP_C_LLONG   4
-
-#define char_to_int(p) ((p)- '0')
-#ifndef MAX
-#define MAX(p,q) (((p) >= (q)) ? (p) : (q))
-#endif
+/*
+ * We need a signed integer type corresponding to size_t (cf. C99: 7.19.6.1, 7).
+ * However, we'll simply use size_t and convert it to a signed type if
+ * necessary.  This should work just fine in practice.
+ */
+#ifndef SSIZE_T
+#define SSIZE_T size_t
+#endif /* !defined(SSIZE_T) */
+
+/* Either ERANGE or E2BIG should be available everywhere. */
+#ifndef ERANGE
+#define ERANGE E2BIG
+#endif /* !defined(ERANGE) */
+#ifndef EOVERFLOW
+#define EOVERFLOW ERANGE
+#endif /* !defined(EOVERFLOW) */
+
+/*
+ * Buffer size to hold the octal string representation of UINT128_MAX without
+ * nul-termination ("3777777777777777777777777777777777777777777").
+ */
+#ifdef MAX_CONVERT_LENGTH
+#undef MAX_CONVERT_LENGTH
+#endif /* defined(MAX_CONVERT_LENGTH) */
+#define MAX_CONVERT_LENGTH      43
+
+/* Format read states. */
+#define PRINT_S_DEFAULT         0
+#define PRINT_S_FLAGS           1
+#define PRINT_S_WIDTH           2
+#define PRINT_S_DOT             3
+#define PRINT_S_PRECISION       4
+#define PRINT_S_MOD             5
+#define PRINT_S_CONV            6
+
+/* Format flags. */
+#define PRINT_F_MINUS           (1 << 0)
+#define PRINT_F_PLUS            (1 << 1)
+#define PRINT_F_SPACE           (1 << 2)
+#define PRINT_F_NUM             (1 << 3)
+#define PRINT_F_ZERO            (1 << 4)
+#define PRINT_F_QUOTE           (1 << 5)
+#define PRINT_F_UP              (1 << 6)
+#define PRINT_F_UNSIGNED        (1 << 7)
+#define PRINT_F_TYPE_G          (1 << 8)
+#define PRINT_F_TYPE_E          (1 << 9)
+
+/* Conversion flags. */
+#define PRINT_C_CHAR            1
+#define PRINT_C_SHORT           2
+#define PRINT_C_LONG            3
+#define PRINT_C_LLONG           4
+#define PRINT_C_LDOUBLE         5
+#define PRINT_C_SIZE            6
+#define PRINT_C_PTRDIFF         7
+#define PRINT_C_INTMAX          8
 
-static size_t dopr(char *buffer, size_t maxlen, const char *format, va_list args)
+#ifndef MAX
+#define MAX(x, y) ((x >= y) ? x : y)
+#endif /* !defined(MAX) */
+#ifndef CHARTOINT
+#define CHARTOINT(ch) (ch - '0')
+#endif /* !defined(CHARTOINT) */
+#ifndef ISDIGIT
+#define ISDIGIT(ch) ('0' <= (unsigned char)ch && (unsigned char)ch <= '9')
+#endif /* !defined(ISDIGIT) */
+#ifndef ISNAN
+#define ISNAN(x) (x != x)
+#endif /* !defined(ISNAN) */
+#ifndef ISINF
+#define ISINF(x) (x != 0.0 && x + x == x)
+#endif /* !defined(ISINF) */
+
+#ifdef OUTCHAR
+#undef OUTCHAR
+#endif /* defined(OUTCHAR) */
+#define OUTCHAR(str, len, size, ch)                                          \
+do {                                                                         \
+       if (len + 1 < size)                                                  \
+               str[len] = ch;                                               \
+       (len)++;                                                             \
+} while (/* CONSTCOND */ 0)
+
+static void fmtstr(char *, size_t *, size_t, const char *, int, int, int);
+static void fmtint(char *, size_t *, size_t, INTMAX_T, int, int, int, int);
+static void fmtflt(char *, size_t *, size_t, LDOUBLE, int, int, int, int *);
+static void printsep(char *, size_t *, size_t);
+static int getnumsep(int);
+static int getexponent(LDOUBLE);
+static int convert(UINTMAX_T, char *, size_t, int, int);
+static UINTMAX_T cast(LDOUBLE);
+static UINTMAX_T myround(LDOUBLE);
+static LDOUBLE mypow10(int);
+
+extern int errno;
+
+int
+rpl_vsnprintf(char *str, size_t size, const char *format, va_list args)
 {
-       char ch;
-       LLONG value;
        LDOUBLE fvalue;
-       char *strvalue;
-       int min;
-       int max;
-       int state;
-       int flags;
-       int cflags;
-       size_t currlen;
-
-       state = DP_S_DEFAULT;
-       currlen = flags = cflags = min = 0;
-       max = -1;
-       ch = *format++;
-
-       while (state != DP_S_DONE) {
-               if (ch == '\0')
-                       state = DP_S_DONE;
-
-               switch(state) {
-               case DP_S_DEFAULT:
+       INTMAX_T value;
+       unsigned char cvalue;
+       const char *strvalue;
+       INTMAX_T *intmaxptr;
+       PTRDIFF_T *ptrdiffptr;
+       SSIZE_T *sizeptr;
+       LLONG *llongptr;
+       long int *longptr;
+       int *intptr;
+       short int *shortptr;
+       signed char *charptr;
+       size_t len = 0;
+       int overflow = 0;
+       int base = 0;
+       int cflags = 0;
+       int flags = 0;
+       int width = 0;
+       int precision = -1;
+       int state = PRINT_S_DEFAULT;
+       char ch = *format++;
+
+       /*
+        * C99 says: "If `n' is zero, nothing is written, and `s' may be a null
+        * pointer." (7.19.6.5, 2)  We're forgiving and allow a NULL pointer
+        * even if a size larger than zero was specified.  At least NetBSD's
+        * snprintf(3) does the same, as well as other versions of this file.
+        * (Though some of these versions will write to a non-NULL buffer even
+        * if a size of zero was specified, which violates the standard.)
+        */
+       if (str == NULL && size != 0)
+               size = 0;
+
+       while (ch != '\0')
+               switch (state) {
+               case PRINT_S_DEFAULT:
                        if (ch == '%')
-                               state = DP_S_FLAGS;
+                               state = PRINT_S_FLAGS;
                        else
-                               dopr_outch (buffer, &currlen, maxlen, ch);
+                               OUTCHAR(str, len, size, ch);
                        ch = *format++;
                        break;
-               case DP_S_FLAGS:
+               case PRINT_S_FLAGS:
                        switch (ch) {
                        case '-':
-                               flags |= DP_F_MINUS;
+                               flags |= PRINT_F_MINUS;
                                ch = *format++;
                                break;
                        case '+':
-                               flags |= DP_F_PLUS;
+                               flags |= PRINT_F_PLUS;
                                ch = *format++;
                                break;
                        case ' ':
-                               flags |= DP_F_SPACE;
+                               flags |= PRINT_F_SPACE;
                                ch = *format++;
                                break;
                        case '#':
-                               flags |= DP_F_NUM;
+                               flags |= PRINT_F_NUM;
                                ch = *format++;
                                break;
                        case '0':
-                               flags |= DP_F_ZERO;
+                               flags |= PRINT_F_ZERO;
+                               ch = *format++;
+                               break;
+                       case '\'':      /* SUSv2 flag (not in C99). */
+                               flags |= PRINT_F_QUOTE;
                                ch = *format++;
                                break;
                        default:
-                               state = DP_S_MIN;
+                               state = PRINT_S_WIDTH;
                                break;
                        }
                        break;
-               case DP_S_MIN:
-                       if (isdigit((unsigned char)ch)) {
-                               min = 10*min + char_to_int (ch);
+               case PRINT_S_WIDTH:
+                       if (ISDIGIT(ch)) {
+                               ch = CHARTOINT(ch);
+                               if (width > (INT_MAX - ch) / 10) {
+                                       overflow = 1;
+                                       goto out;
+                               }
+                               width = 10 * width + ch;
                                ch = *format++;
                        } else if (ch == '*') {
-                               min = va_arg (args, int);
+                               /*
+                                * C99 says: "A negative field width argument is
+                                * taken as a `-' flag followed by a positive
+                                * field width." (7.19.6.1, 5)
+                                */
+                               if ((width = va_arg(args, int)) < 0) {
+                                       flags |= PRINT_F_MINUS;
+                                       width = -width;
+                               }
                                ch = *format++;
-                               state = DP_S_DOT;
-                       } else {
-                               state = DP_S_DOT;
-                       }
+                               state = PRINT_S_DOT;
+                       } else
+                               state = PRINT_S_DOT;
                        break;
-               case DP_S_DOT:
+               case PRINT_S_DOT:
                        if (ch == '.') {
-                               state = DP_S_MAX;
+                               state = PRINT_S_PRECISION;
                                ch = *format++;
-                       } else {
-                               state = DP_S_MOD;
-                       }
+                       } else
+                               state = PRINT_S_MOD;
                        break;
-               case DP_S_MAX:
-                       if (isdigit((unsigned char)ch)) {
-                               if (max < 0)
-                                       max = 0;
-                               max = 10*max + char_to_int (ch);
+               case PRINT_S_PRECISION:
+                       if (precision == -1)
+                               precision = 0;
+                       if (ISDIGIT(ch)) {
+                               ch = CHARTOINT(ch);
+                               if (precision > (INT_MAX - ch) / 10) {
+                                       overflow = 1;
+                                       goto out;
+                               }
+                               precision = 10 * precision + ch;
                                ch = *format++;
                        } else if (ch == '*') {
-                               max = va_arg (args, int);
+                               /*
+                                * C99 says: "A negative precision argument is
+                                * taken as if the precision were omitted."
+                                * (7.19.6.1, 5)
+                                */
+                               if ((precision = va_arg(args, int)) < 0)
+                                       precision = -1;
                                ch = *format++;
-                               state = DP_S_MOD;
-                       } else {
-                               state = DP_S_MOD;
-                       }
+                               state = PRINT_S_MOD;
+                       } else
+                               state = PRINT_S_MOD;
                        break;
-               case DP_S_MOD:
+               case PRINT_S_MOD:
                        switch (ch) {
                        case 'h':
-                               cflags = DP_C_SHORT;
                                ch = *format++;
+                               if (ch == 'h') {        /* It's a char. */
+                                       ch = *format++;
+                                       cflags = PRINT_C_CHAR;
+                               } else
+                                       cflags = PRINT_C_SHORT;
                                break;
                        case 'l':
-                               cflags = DP_C_LONG;
                                ch = *format++;
-                               if (ch == 'l') {        /* It's a long long */
-                                       cflags = DP_C_LLONG;
+                               if (ch == 'l') {        /* It's a long long. */
                                        ch = *format++;
-                               }
+                                       cflags = PRINT_C_LLONG;
+                               } else
+                                       cflags = PRINT_C_LONG;
                                break;
                        case 'L':
-                               cflags = DP_C_LDOUBLE;
+                               cflags = PRINT_C_LDOUBLE;
                                ch = *format++;
                                break;
-                       default:
+                       case 'j':
+                               cflags = PRINT_C_INTMAX;
+                               ch = *format++;
+                               break;
+                       case 't':
+                               cflags = PRINT_C_PTRDIFF;
+                               ch = *format++;
+                               break;
+                       case 'z':
+                               cflags = PRINT_C_SIZE;
+                               ch = *format++;
                                break;
                        }
-                       state = DP_S_CONV;
+                       state = PRINT_S_CONV;
                        break;
-               case DP_S_CONV:
+               case PRINT_S_CONV:
                        switch (ch) {
                        case 'd':
+                               /* FALLTHROUGH */
                        case 'i':
-                               if (cflags == DP_C_SHORT)
-                                       value = va_arg (args, int);
-                               else if (cflags == DP_C_LONG)
-                                       value = va_arg (args, long int);
-                               else if (cflags == DP_C_LLONG)
-                                       value = va_arg (args, LLONG);
-                               else
-                                       value = va_arg (args, int);
-                               fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
-                               break;
-                       case 'o':
-                               flags |= DP_F_UNSIGNED;
-                               if (cflags == DP_C_SHORT)
-                                       value = va_arg (args, unsigned int);
-                               else if (cflags == DP_C_LONG)
-                                       value = (long)va_arg (args, unsigned long int);
-                               else if (cflags == DP_C_LLONG)
-                                       value = (long)va_arg (args, unsigned LLONG);
-                               else
-                                       value = (long)va_arg (args, unsigned int);
-                               fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags);
-                               break;
-                       case 'u':
-                               flags |= DP_F_UNSIGNED;
-                               if (cflags == DP_C_SHORT)
-                                       value = va_arg (args, unsigned int);
-                               else if (cflags == DP_C_LONG)
-                                       value = (long)va_arg (args, unsigned long int);
-                               else if (cflags == DP_C_LLONG)
-                                       value = (LLONG)va_arg (args, unsigned LLONG);
-                               else
-                                       value = (long)va_arg (args, unsigned int);
-                               fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
+                               switch (cflags) {
+                               case PRINT_C_CHAR:
+                                       value = (signed char)va_arg(args, int);
+                                       break;
+                               case PRINT_C_SHORT:
+                                       value = (short int)va_arg(args, int);
+                                       break;
+                               case PRINT_C_LONG:
+                                       value = va_arg(args, long int);
+                                       break;
+                               case PRINT_C_LLONG:
+                                       value = va_arg(args, LLONG);
+                                       break;
+                               case PRINT_C_SIZE:
+                                       value = va_arg(args, SSIZE_T);
+                                       break;
+                               case PRINT_C_INTMAX:
+                                       value = va_arg(args, INTMAX_T);
+                                       break;
+                               case PRINT_C_PTRDIFF:
+                                       value = va_arg(args, PTRDIFF_T);
+                                       break;
+                               default:
+                                       value = va_arg(args, int);
+                                       break;
+                               }
+                               fmtint(str, &len, size, value, 10, width,
+                                   precision, flags);
                                break;
                        case 'X':
-                               flags |= DP_F_UP;
+                               flags |= PRINT_F_UP;
+                               /* FALLTHROUGH */
                        case 'x':
-                               flags |= DP_F_UNSIGNED;
-                               if (cflags == DP_C_SHORT)
-                                       value = va_arg (args, unsigned int);
-                               else if (cflags == DP_C_LONG)
-                                       value = (long)va_arg (args, unsigned long int);
-                               else if (cflags == DP_C_LLONG)
-                                       value = (LLONG)va_arg (args, unsigned LLONG);
-                               else
-                                       value = (long)va_arg (args, unsigned int);
-                               fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags);
+                               base = 16;
+                               /* FALLTHROUGH */
+                       case 'o':
+                               if (base == 0)
+                                       base = 8;
+                               /* FALLTHROUGH */
+                       case 'u':
+                               if (base == 0)
+                                       base = 10;
+                               flags |= PRINT_F_UNSIGNED;
+                               switch (cflags) {
+                               case PRINT_C_CHAR:
+                                       value = (unsigned char)va_arg(args,
+                                           unsigned int);
+                                       break;
+                               case PRINT_C_SHORT:
+                                       value = (unsigned short int)va_arg(args,
+                                           unsigned int);
+                                       break;
+                               case PRINT_C_LONG:
+                                       value = va_arg(args, unsigned long int);
+                                       break;
+                               case PRINT_C_LLONG:
+                                       value = va_arg(args, ULLONG);
+                                       break;
+                               case PRINT_C_SIZE:
+                                       value = va_arg(args, size_t);
+                                       break;
+                               case PRINT_C_INTMAX:
+                                       value = va_arg(args, UINTMAX_T);
+                                       break;
+                               case PRINT_C_PTRDIFF:
+                                       value = va_arg(args, UPTRDIFF_T);
+                                       break;
+                               default:
+                                       value = va_arg(args, unsigned int);
+                                       break;
+                               }
+                               fmtint(str, &len, size, value, base, width,
+                                   precision, flags);
                                break;
+                       case 'A':
+                               /* Not yet supported, we'll use "%F". */
+                               /* FALLTHROUGH */
+                       case 'F':
+                               flags |= PRINT_F_UP;
+                       case 'a':
+                               /* Not yet supported, we'll use "%f". */
+                               /* FALLTHROUGH */
                        case 'f':
-                               if (cflags == DP_C_LDOUBLE)
-                                       fvalue = va_arg (args, LDOUBLE);
+                               if (cflags == PRINT_C_LDOUBLE)
+                                       fvalue = va_arg(args, LDOUBLE);
                                else
-                                       fvalue = va_arg (args, double);
-                               /* um, floating point? */
-                               fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
+                                       fvalue = va_arg(args, double);
+                               fmtflt(str, &len, size, fvalue, width,
+                                   precision, flags, &overflow);
+                               if (overflow)
+                                       goto out;
                                break;
                        case 'E':
-                               flags |= DP_F_UP;
+                               flags |= PRINT_F_UP;
+                               /* FALLTHROUGH */
                        case 'e':
-                               if (cflags == DP_C_LDOUBLE)
-                                       fvalue = va_arg (args, LDOUBLE);
+                               flags |= PRINT_F_TYPE_E;
+                               if (cflags == PRINT_C_LDOUBLE)
+                                       fvalue = va_arg(args, LDOUBLE);
                                else
-                                       fvalue = va_arg (args, double);
+                                       fvalue = va_arg(args, double);
+                               fmtflt(str, &len, size, fvalue, width,
+                                   precision, flags, &overflow);
+                               if (overflow)
+                                       goto out;
                                break;
                        case 'G':
-                               flags |= DP_F_UP;
+                               flags |= PRINT_F_UP;
+                               /* FALLTHROUGH */
                        case 'g':
-                               if (cflags == DP_C_LDOUBLE)
-                                       fvalue = va_arg (args, LDOUBLE);
+                               flags |= PRINT_F_TYPE_G;
+                               if (cflags == PRINT_C_LDOUBLE)
+                                       fvalue = va_arg(args, LDOUBLE);
                                else
-                                       fvalue = va_arg (args, double);
+                                       fvalue = va_arg(args, double);
+                               /*
+                                * If the precision is zero, it is treated as
+                                * one (cf. C99: 7.19.6.1, 8).
+                                */
+                               if (precision == 0)
+                                       precision = 1;
+                               fmtflt(str, &len, size, fvalue, width,
+                                   precision, flags, &overflow);
+                               if (overflow)
+                                       goto out;
                                break;
                        case 'c':
-                               dopr_outch (buffer, &currlen, maxlen, va_arg (args, int));
+                               cvalue = va_arg(args, int);
+                               OUTCHAR(str, len, size, cvalue);
                                break;
                        case 's':
-                               strvalue = va_arg (args, char *);
-                               if (!strvalue) strvalue = "(NULL)";
-                               if (max == -1) {
-                                       max = strlen(strvalue);
-                               }
-                               if (min > 0 && max >= 0 && min > max) max = min;
-                               fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max);
+                               strvalue = va_arg(args, char *);
+                               fmtstr(str, &len, size, strvalue, width,
+                                   precision, flags);
                                break;
                        case 'p':
-                               strvalue = (char *)va_arg(args, void *);
-#ifdef _WIN32
-                               fmtint (buffer, &currlen, maxlen, (intptr_t) strvalue, 16, min, max, flags);
-#else
-                               fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, max, flags);
-#endif
+                               /*
+                                * C99 says: "The value of the pointer is
+                                * converted to a sequence of printing
+                                * characters, in an implementation-defined
+                                * manner." (C99: 7.19.6.1, 8)
+                                */
+                               if ((strvalue = va_arg(args, void *)) == NULL)
+                                       /*
+                                        * We use the glibc format.  BSD prints
+                                        * "0x0", SysV "0".
+                                        */
+                                       fmtstr(str, &len, size, "(nil)", width,
+                                           -1, flags);
+                               else {
+                                       /*
+                                        * We use the BSD/glibc format.  SysV
+                                        * omits the "0x" prefix (which we emit
+                                        * using the PRINT_F_NUM flag).
+                                        */
+                                       flags |= PRINT_F_NUM;
+                                       flags |= PRINT_F_UNSIGNED;
+                                       fmtint(str, &len, size,
+                                           (UINTPTR_T)strvalue, 16, width,
+                                           precision, flags);
+                               }
                                break;
                        case 'n':
-                               if (cflags == DP_C_SHORT) {
-                                       short int *num;
-                                       num = va_arg (args, short int *);
-                                       *num = currlen;
-                               } else if (cflags == DP_C_LONG) {
-                                       long int *num;
-                                       num = va_arg (args, long int *);
-                                       *num = (long int)currlen;
-                               } else if (cflags == DP_C_LLONG) {
-                                       LLONG *num;
-                                       num = va_arg (args, LLONG *);
-                                       *num = (LLONG)currlen;
-                               } else {
-                                       int *num;
-                                       num = va_arg (args, int *);
-                                       *num = currlen;
+                               switch (cflags) {
+                               case PRINT_C_CHAR:
+                                       charptr = va_arg(args, signed char *);
+                                       *charptr = len;
+                                       break;
+                               case PRINT_C_SHORT:
+                                       shortptr = va_arg(args, short int *);
+                                       *shortptr = len;
+                                       break;
+                               case PRINT_C_LONG:
+                                       longptr = va_arg(args, long int *);
+                                       *longptr = len;
+                                       break;
+                               case PRINT_C_LLONG:
+                                       llongptr = va_arg(args, LLONG *);
+                                       *llongptr = len;
+                                       break;
+                               case PRINT_C_SIZE:
+                                       /*
+                                        * C99 says that with the "z" length
+                                        * modifier, "a following `n' conversion
+                                        * specifier applies to a pointer to a
+                                        * signed integer type corresponding to
+                                        * size_t argument." (7.19.6.1, 7)
+                                        */
+                                       sizeptr = va_arg(args, SSIZE_T *);
+                                       *sizeptr = len;
+                                       break;
+                               case PRINT_C_INTMAX:
+                                       intmaxptr = va_arg(args, INTMAX_T *);
+                                       *intmaxptr = len;
+                                       break;
+                               case PRINT_C_PTRDIFF:
+                                       ptrdiffptr = va_arg(args, PTRDIFF_T *);
+                                       *ptrdiffptr = len;
+                                       break;
+                               default:
+                                       intptr = va_arg(args, int *);
+                                       *intptr = len;
+                                       break;
                                }
                                break;
-                       case '%':
-                               dopr_outch (buffer, &currlen, maxlen, ch);
-                               break;
-                       case 'w':
-                               /* not supported yet, treat as next char */
-                               ch = *format++;
+                       case '%':       /* Print a "%" character verbatim. */
+                               OUTCHAR(str, len, size, ch);
                                break;
-                       default:
-                               /* Unknown, skip */
+                       default:        /* Skip other characters. */
                                break;
                        }
                        ch = *format++;
-                       state = DP_S_DEFAULT;
-                       flags = cflags = min = 0;
-                       max = -1;
+                       state = PRINT_S_DEFAULT;
+                       base = cflags = flags = width = 0;
+                       precision = -1;
                        break;
-               case DP_S_DONE:
-                       break;
-               default:
-                       /* hmm? */
-                       break; /* some picky compilers need this */
                }
+out:
+       if (len < size)
+               str[len] = '\0';
+       else if (size > 0)
+               str[size - 1] = '\0';
+
+       if (overflow || len >= INT_MAX) {
+               errno = overflow ? EOVERFLOW : ERANGE;
+               return -1;
        }
-       if (maxlen != 0) {
-               if (currlen < maxlen - 1)
-                       buffer[currlen] = '\0';
-               else if (maxlen > 0)
-                       buffer[maxlen - 1] = '\0';
-       }
-
-       return currlen;
+       return (int)len;
 }
 
-static void fmtstr(char *buffer, size_t *currlen, size_t maxlen,
-                   char *value, int flags, int min, int max)
+static void
+fmtstr(char *str, size_t *len, size_t size, const char *value, int width,
+       int precision, int flags)
 {
-       int padlen, strln;     /* amount to pad */
-       int cnt = 0;
+       int padlen, strln;      /* Amount to pad. */
+       int noprecision = (precision == -1);
 
-#ifdef DEBUG_SNPRINTF
-       printf("fmtstr min=%d max=%d s=[%s]\n", min, max, value);
-#endif
-       if (value == 0) {
-               value = "<NULL>";
-       }
+       if (value == NULL)      /* We're forgiving. */
+               value = "(null)";
 
-       for (strln = 0; value[strln]; ++strln); /* strlen */
-       padlen = min - strln;
-       if (padlen < 0)
+       /* If a precision was specified, don't read the string past it. */
+       for (strln = 0; value[strln] != '\0' &&
+           (noprecision || strln < precision); strln++)
+               continue;
+
+       if ((padlen = width - strln) < 0)
                padlen = 0;
-       if (flags & DP_F_MINUS)
-               padlen = -padlen; /* Left Justify */
+       if (flags & PRINT_F_MINUS)      /* Left justify. */
+               padlen = -padlen;
 
-       while ((padlen > 0) && (cnt < max)) {
-               dopr_outch (buffer, currlen, maxlen, ' ');
-               --padlen;
-               ++cnt;
+       while (padlen > 0) {    /* Leading spaces. */
+               OUTCHAR(str, *len, size, ' ');
+               padlen--;
        }
-       while (*value && (cnt < max)) {
-               dopr_outch (buffer, currlen, maxlen, *value++);
-               ++cnt;
+       while (*value != '\0' && (noprecision || precision-- > 0)) {
+               OUTCHAR(str, *len, size, *value);
+               value++;
        }
-       while ((padlen < 0) && (cnt < max)) {
-               dopr_outch (buffer, currlen, maxlen, ' ');
-               ++padlen;
-               ++cnt;
+       while (padlen < 0) {    /* Trailing spaces. */
+               OUTCHAR(str, *len, size, ' ');
+               padlen++;
        }
 }
 
-/* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
-
-static void fmtint(char *buffer, size_t *currlen, size_t maxlen,
-                   long value, int base, int min, int max, int flags)
+static void
+fmtint(char *str, size_t *len, size_t size, INTMAX_T value, int base, int width,
+       int precision, int flags)
 {
-       int signvalue = 0;
-       unsigned long uvalue;
-       char convert[20];
-       int place = 0;
-       int spadlen = 0; /* amount to space pad */
-       int zpadlen = 0; /* amount to zero pad */
-       int caps = 0;
-
-       if (max < 0)
-               max = 0;
-
-       uvalue = value;
-
-       if(!(flags & DP_F_UNSIGNED)) {
-               if( value < 0 ) {
-                       signvalue = '-';
-                       uvalue = -value;
-               } else {
-                       if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
-                               signvalue = '+';
-                       else if (flags & DP_F_SPACE)
-                               signvalue = ' ';
-               }
+       UINTMAX_T uvalue;
+       char iconvert[MAX_CONVERT_LENGTH];
+       char sign = 0;
+       char hexprefix = 0;
+       int spadlen = 0;        /* Amount to space pad. */
+       int zpadlen = 0;        /* Amount to zero pad. */
+       int pos;
+       int separators = (flags & PRINT_F_QUOTE);
+       int noprecision = (precision == -1);
+
+       if (flags & PRINT_F_UNSIGNED)
+               uvalue = value;
+       else {
+               uvalue = (value >= 0) ? value : -value;
+               if (value < 0)
+                       sign = '-';
+               else if (flags & PRINT_F_PLUS)  /* Do a sign. */
+                       sign = '+';
+               else if (flags & PRINT_F_SPACE)
+                       sign = ' ';
        }
 
-       if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
-
-       do {
-               convert[place++] =
-                       (caps? "0123456789ABCDEF":"0123456789abcdef")
-                       [uvalue % (unsigned)base  ];
-               uvalue = (uvalue / (unsigned)base );
-       } while(uvalue && (place < 20));
-       if (place == 20) place--;
-       convert[place] = 0;
-
-       zpadlen = max - place;
-       spadlen = min - MAX (max, place) - (signvalue ? 1 : 0);
-       if (zpadlen < 0) zpadlen = 0;
-       if (spadlen < 0) spadlen = 0;
-       if (flags & DP_F_ZERO) {
-               zpadlen = MAX(zpadlen, spadlen);
-               spadlen = 0;
+       pos = convert(uvalue, iconvert, sizeof(iconvert), base,
+           flags & PRINT_F_UP);
+
+       if (flags & PRINT_F_NUM && uvalue != 0) {
+               /*
+                * C99 says: "The result is converted to an `alternative form'.
+                * For `o' conversion, it increases the precision, if and only
+                * if necessary, to force the first digit of the result to be a
+                * zero (if the value and precision are both 0, a single 0 is
+                * printed).  For `x' (or `X') conversion, a nonzero result has
+                * `0x' (or `0X') prefixed to it." (7.19.6.1, 6)
+                */
+               switch (base) {
+               case 8:
+                       if (precision <= pos)
+                               precision = pos + 1;
+                       break;
+               case 16:
+                       hexprefix = (flags & PRINT_F_UP) ? 'X' : 'x';
+                       break;
+               }
        }
-       if (flags & DP_F_MINUS)
-               spadlen = -spadlen; /* Left Justifty */
 
-#ifdef DEBUG_SNPRINTF
-       printf("zpad: %d, spad: %d, min: %d, max: %d, place: %d\n",
-              zpadlen, spadlen, min, max, place);
-#endif
+       if (separators) /* Get the number of group separators we'll print. */
+               separators = getnumsep(pos);
 
-       /* Spaces */
-       while (spadlen > 0) {
-               dopr_outch (buffer, currlen, maxlen, ' ');
-               --spadlen;
-       }
+       zpadlen = precision - pos - separators;
+       spadlen = width                         /* Minimum field width. */
+           - separators                        /* Number of separators. */
+           - MAX(precision, pos)               /* Number of integer digits. */
+           - ((sign != 0) ? 1 : 0)             /* Will we print a sign? */
+           - ((hexprefix != 0) ? 2 : 0);       /* Will we print a prefix? */
 
-       /* Sign */
-       if (signvalue)
-               dopr_outch (buffer, currlen, maxlen, signvalue);
+       if (zpadlen < 0)
+               zpadlen = 0;
+       if (spadlen < 0)
+               spadlen = 0;
 
-       /* Zeros */
-       if (zpadlen > 0) {
-               while (zpadlen > 0) {
-                       dopr_outch (buffer, currlen, maxlen, '0');
-                       --zpadlen;
-               }
+       /*
+        * C99 says: "If the `0' and `-' flags both appear, the `0' flag is
+        * ignored.  For `d', `i', `o', `u', `x', and `X' conversions, if a
+        * precision is specified, the `0' flag is ignored." (7.19.6.1, 6)
+        */
+       if (flags & PRINT_F_MINUS)      /* Left justify. */
+               spadlen = -spadlen;
+       else if (flags & PRINT_F_ZERO && noprecision) {
+               zpadlen += spadlen;
+               spadlen = 0;
        }
-
-       /* Digits */
-       while (place > 0)
-               dopr_outch (buffer, currlen, maxlen, convert[--place]);
-
-       /* Left Justified spaces */
-       while (spadlen < 0) {
-               dopr_outch (buffer, currlen, maxlen, ' ');
-               ++spadlen;
+       while (spadlen > 0) {   /* Leading spaces. */
+               OUTCHAR(str, *len, size, ' ');
+               spadlen--;
        }
-}
-
-static LDOUBLE abs_val(LDOUBLE value)
-{
-       LDOUBLE result = value;
-
-       if (value < 0)
-               result = -value;
-
-       return result;
-}
-
-static LDOUBLE POW10(int exp)
-{
-       LDOUBLE result = 1;
-
-       while (exp) {
-               result *= 10;
-               exp--;
+       if (sign != 0)  /* Sign. */
+               OUTCHAR(str, *len, size, sign);
+       if (hexprefix != 0) {   /* A "0x" or "0X" prefix. */
+               OUTCHAR(str, *len, size, '0');
+               OUTCHAR(str, *len, size, hexprefix);
+       }
+       while (zpadlen > 0) {   /* Leading zeros. */
+               OUTCHAR(str, *len, size, '0');
+               zpadlen--;
+       }
+       while (pos > 0) {       /* The actual digits. */
+               pos--;
+               OUTCHAR(str, *len, size, iconvert[pos]);
+               if (separators > 0 && pos > 0 && pos % 3 == 0)
+                       printsep(str, len, size);
+       }
+       while (spadlen < 0) {   /* Trailing spaces. */
+               OUTCHAR(str, *len, size, ' ');
+               spadlen++;
        }
-
-       return result;
-}
-
-static LLONG ROUND(LDOUBLE value)
-{
-       LLONG intpart;
-
-       intpart = (LLONG)value;
-       value = value - intpart;
-       if (value >= 0.5) intpart++;
-
-       return intpart;
 }
 
-/* a replacement for modf that doesn't need the math library. Should
-   be portable, but slow */
-static double my_modf(double x0, double *iptr)
+static void
+fmtflt(char *str, size_t *len, size_t size, LDOUBLE fvalue, int width,
+       int precision, int flags, int *overflow)
 {
-       int i;
-       long l;
-       double x = x0;
-       double f = 1.0;
+       LDOUBLE ufvalue;
+       UINTMAX_T intpart;
+       UINTMAX_T fracpart;
+       UINTMAX_T mask;
+       const char *infnan = NULL;
+       char iconvert[MAX_CONVERT_LENGTH];
+       char fconvert[MAX_CONVERT_LENGTH];
+       char econvert[4];       /* "e-12" (without nul-termination). */
+       char esign = 0;
+       char sign = 0;
+       int leadfraczeros = 0;
+       int exponent = 0;
+       int emitpoint = 0;
+       int omitzeros = 0;
+       int omitcount = 0;
+       int padlen = 0;
+       int epos = 0;
+       int fpos = 0;
+       int ipos = 0;
+       int separators = (flags & PRINT_F_QUOTE);
+       int estyle = (flags & PRINT_F_TYPE_E);
+#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT
+       struct lconv *lc = localeconv();
+#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */
 
-       for (i=0;i<100;i++) {
-               l = (long)x;
-               if (l <= (x+1) && l >= (x-1)) break;
-               x *= 0.1;
-               f *= 10.0;
+       /*
+        * AIX' man page says the default is 0, but C99 and at least Solaris'
+        * and NetBSD's man pages say the default is 6, and sprintf(3) on AIX
+        * defaults to 6.
+        */
+       if (precision == -1)
+               precision = 6;
+
+       if (fvalue < 0.0)
+               sign = '-';
+       else if (flags & PRINT_F_PLUS)  /* Do a sign. */
+               sign = '+';
+       else if (flags & PRINT_F_SPACE)
+               sign = ' ';
+
+       if (ISNAN(fvalue))
+               infnan = (flags & PRINT_F_UP) ? "NAN" : "nan";
+       else if (ISINF(fvalue))
+               infnan = (flags & PRINT_F_UP) ? "INF" : "inf";
+
+       if (infnan != NULL) {
+               if (sign != 0)
+                       iconvert[ipos++] = sign;
+               while (*infnan != '\0')
+                       iconvert[ipos++] = *infnan++;
+               fmtstr(str, len, size, iconvert, width, ipos, flags);
+               return;
        }
 
-       if (i == 100) {
-               /* yikes! the number is beyond what we can handle. What do we do? */
-               (*iptr) = 0;
-               return 0;
+       /* "%e" (or "%E") or "%g" (or "%G") conversion. */
+       if (flags & PRINT_F_TYPE_E || flags & PRINT_F_TYPE_G) {
+               if (flags & PRINT_F_TYPE_G) {
+                       /*
+                        * For "%g" (and "%G") conversions, the precision
+                        * specifies the number of significant digits, which
+                        * includes the digits in the integer part.  The
+                        * conversion will or will not be using "e-style" (like
+                        * "%e" or "%E" conversions) depending on the precision
+                        * and on the exponent.  However, the exponent can be
+                        * affected by rounding the converted value, so we'll
+                        * leave this decision for later.  Until then, we'll
+                        * assume that we're going to do an "e-style" conversion
+                        * (in order to get the exponent calculated).  For
+                        * "e-style", the precision must be decremented by one.
+                        */
+                       precision--;
+                       /*
+                        * For "%g" (and "%G") conversions, trailing zeros are
+                        * removed from the fractional portion of the result
+                        * unless the "#" flag was specified.
+                        */
+                       if (!(flags & PRINT_F_NUM))
+                               omitzeros = 1;
+               }
+               exponent = getexponent(fvalue);
+               estyle = 1;
        }
 
-       if (i != 0) {
-               double i2;
-               double ret;
-
-               ret = my_modf(x0-l*f, &i2);
-               (*iptr) = l*f + i2;
-               return ret;
+again:
+       /*
+        * Sorry, we only support 9, 19, or 38 digits (that is, the number of
+        * digits of the 32-bit, the 64-bit, or the 128-bit UINTMAX_MAX value
+        * minus one) past the decimal point due to our conversion method.
+        */
+       switch (sizeof(UINTMAX_T)) {
+       case 16:
+               if (precision > 38)
+                       precision = 38;
+               break;
+       case 8:
+               if (precision > 19)
+                       precision = 19;
+               break;
+       default:
+               if (precision > 9)
+                       precision = 9;
+               break;
        }
 
-       (*iptr) = l;
-       return x - (*iptr);
-}
-
+       ufvalue = (fvalue >= 0.0) ? fvalue : -fvalue;
+       if (estyle)     /* We want exactly one integer digit. */
+               ufvalue /= mypow10(exponent);
 
-static void fmtfp (char *buffer, size_t *currlen, size_t maxlen,
-                  LDOUBLE fvalue, int min, int max, int flags)
-{
-       int signvalue = 0;
-       double ufvalue;
-       char iconvert[311];
-       char fconvert[311];
-       int iplace = 0;
-       int fplace = 0;
-       int padlen = 0; /* amount to pad */
-       int zpadlen = 0;
-       int caps = 0;
-       int index;
-       double intpart;
-       double fracpart;
-       double temp;
+       if ((intpart = cast(ufvalue)) == UINTMAX_MAX) {
+               *overflow = 1;
+               return;
+       }
 
        /*
-        * AIX manpage says the default is 0, but Solaris says the default
-        * is 6, and sprintf on AIX defaults to 6
+        * Factor of ten with the number of digits needed for the fractional
+        * part.  For example, if the precision is 3, the mask will be 1000.
         */
-       if (max < 0)
-               max = 6;
-
-       ufvalue = abs_val (fvalue);
-
-       if (fvalue < 0) {
-               signvalue = '-';
-       } else {
-               if (flags & DP_F_PLUS) { /* Do a sign (+/i) */
-                       signvalue = '+';
-               } else {
-                       if (flags & DP_F_SPACE)
-                               signvalue = ' ';
+       mask = mypow10(precision);
+       /*
+        * We "cheat" by converting the fractional part to integer by
+        * multiplying by a factor of ten.
+        */
+       if ((fracpart = myround(mask * (ufvalue - intpart))) >= mask) {
+               /*
+                * For example, ufvalue = 2.99962, intpart = 2, and mask = 1000
+                * (because precision = 3).  Now, myround(1000 * 0.99962) will
+                * return 1000.  So, the integer part must be incremented by one
+                * and the fractional part must be set to zero.
+                */
+               intpart++;
+               fracpart = 0;
+               if (estyle && intpart == 10) {
+                       /*
+                        * The value was rounded up to ten, but we only want one
+                        * integer digit if using "e-style".  So, the integer
+                        * part must be set to one and the exponent must be
+                        * incremented by one.
+                        */
+                       intpart = 1;
+                       exponent++;
                }
        }
 
-#if 0
-       if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
-#endif
-
-#if 0
-        if (max == 0) ufvalue += 0.5; /* if max = 0 we must round */
-#endif
-
        /*
-        * Sorry, we only support 16 digits past the decimal because of our
-        * conversion method
+        * Now that we know the real exponent, we can check whether or not to
+        * use "e-style" for "%g" (and "%G") conversions.  If we don't need
+        * "e-style", the precision must be adjusted and the integer and
+        * fractional parts must be recalculated from the original value.
+        *
+        * C99 says: "Let P equal the precision if nonzero, 6 if the precision
+        * is omitted, or 1 if the precision is zero.  Then, if a conversion
+        * with style `E' would have an exponent of X:
+        *
+        * - if P > X >= -4, the conversion is with style `f' (or `F') and
+        *   precision P - (X + 1).
+        *
+        * - otherwise, the conversion is with style `e' (or `E') and precision
+        *   P - 1." (7.19.6.1, 8)
+        *
+        * Note that we had decremented the precision by one.
         */
-       if (max > 16)
-               max = 16;
+       if (flags & PRINT_F_TYPE_G && estyle &&
+           precision + 1 > exponent && exponent >= -4) {
+               precision -= exponent;
+               estyle = 0;
+               goto again;
+       }
 
-       /* We "cheat" by converting the fractional part to integer by
-        * multiplying by a factor of 10
-        */
+       if (estyle) {
+               if (exponent < 0) {
+                       exponent = -exponent;
+                       esign = '-';
+               } else
+                       esign = '+';
+
+               /*
+                * Convert the exponent.  The sizeof(econvert) is 4.  So, the
+                * econvert buffer can hold e.g. "e+99" and "e-99".  We don't
+                * support an exponent which contains more than two digits.
+                * Therefore, the following stores are safe.
+                */
+               epos = convert(exponent, econvert, 2, 10, 0);
+               /*
+                * C99 says: "The exponent always contains at least two digits,
+                * and only as many more digits as necessary to represent the
+                * exponent." (7.19.6.1, 8)
+                */
+               if (epos == 1)
+                       econvert[epos++] = '0';
+               econvert[epos++] = esign;
+               econvert[epos++] = (flags & PRINT_F_UP) ? 'E' : 'e';
+       }
 
-       temp = ufvalue;
-       my_modf(temp, &intpart);
+       /* Convert the integer part and the fractional part. */
+       ipos = convert(intpart, iconvert, sizeof(iconvert), 10, 0);
+       if (fracpart != 0)      /* convert() would return 1 if fracpart == 0. */
+               fpos = convert(fracpart, fconvert, sizeof(fconvert), 10, 0);
 
-       fracpart = ROUND((POW10(max)) * (ufvalue - intpart));
+       leadfraczeros = precision - fpos;
 
-       if (fracpart >= POW10(max)) {
-               intpart++;
-               fracpart -= POW10(max);
+       if (omitzeros) {
+               if (fpos > 0)   /* Omit trailing fractional part zeros. */
+                       while (omitcount < fpos && fconvert[omitcount] == '0')
+                               omitcount++;
+               else {  /* The fractional part is zero, omit it completely. */
+                       omitcount = precision;
+                       leadfraczeros = 0;
+               }
+               precision -= omitcount;
        }
 
+       /*
+        * Print a decimal point if either the fractional part is non-zero
+        * and/or the "#" flag was specified.
+        */
+       if (precision > 0 || flags & PRINT_F_NUM)
+               emitpoint = 1;
+       if (separators) /* Get the number of group separators we'll print. */
+               separators = getnumsep(ipos);
+
+       padlen = width                  /* Minimum field width. */
+           - ipos                      /* Number of integer digits. */
+           - epos                      /* Number of exponent characters. */
+           - precision                 /* Number of fractional digits. */
+           - separators                /* Number of group separators. */
+           - (emitpoint ? 1 : 0)       /* Will we print a decimal point? */
+           - ((sign != 0) ? 1 : 0);    /* Will we print a sign character? */
 
-       /* Convert integer part */
-       do {
-               temp = intpart;
-               my_modf(intpart*0.1, &intpart);
-               temp = temp*0.1;
-               index = (int) ((temp -intpart +0.05)* 10.0);
-               /* index = (int) (((double)(temp*0.1) -intpart +0.05) *10.0); */
-               /* printf ("%llf, %f, %x\n", temp, intpart, index); */
-               iconvert[iplace++] =
-                       (caps? "0123456789ABCDEF":"0123456789abcdef")[index];
-       } while (intpart && (iplace < 311));
-       if (iplace == 311) iplace--;
-       iconvert[iplace] = 0;
-
-       /* Convert fractional part */
-       if (fracpart)
-       {
-               do {
-                       temp = fracpart;
-                       my_modf(fracpart*0.1, &fracpart);
-                       temp = temp*0.1;
-                       index = (int) ((temp -fracpart +0.05)* 10.0);
-                       /* index = (int) ((((temp/10) -fracpart) +0.05) *10); */
-                       /* printf ("%lf, %lf, %ld\n", temp, fracpart, index); */
-                       fconvert[fplace++] =
-                       (caps? "0123456789ABCDEF":"0123456789abcdef")[index];
-               } while(fracpart && (fplace < 311));
-               if (fplace == 311) fplace--;
-       }
-       fconvert[fplace] = 0;
-
-       /* -1 for decimal point, another -1 if we are printing a sign */
-       padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
-       zpadlen = max - fplace;
-       if (zpadlen < 0) zpadlen = 0;
        if (padlen < 0)
                padlen = 0;
-       if (flags & DP_F_MINUS)
-               padlen = -padlen; /* Left Justifty */
-
-       if ((flags & DP_F_ZERO) && (padlen > 0)) {
-               if (signvalue) {
-                       dopr_outch (buffer, currlen, maxlen, signvalue);
-                       --padlen;
-                       signvalue = 0;
+
+       /*
+        * C99 says: "If the `0' and `-' flags both appear, the `0' flag is
+        * ignored." (7.19.6.1, 6)
+        */
+       if (flags & PRINT_F_MINUS)      /* Left justifty. */
+               padlen = -padlen;
+       else if (flags & PRINT_F_ZERO && padlen > 0) {
+               if (sign != 0) {        /* Sign. */
+                       OUTCHAR(str, *len, size, sign);
+                       sign = 0;
                }
-               while (padlen > 0) {
-                       dopr_outch (buffer, currlen, maxlen, '0');
-                       --padlen;
+               while (padlen > 0) {    /* Leading zeros. */
+                       OUTCHAR(str, *len, size, '0');
+                       padlen--;
                }
        }
-       while (padlen > 0) {
-               dopr_outch (buffer, currlen, maxlen, ' ');
-               --padlen;
+       while (padlen > 0) {    /* Leading spaces. */
+               OUTCHAR(str, *len, size, ' ');
+               padlen--;
        }
-       if (signvalue)
-               dopr_outch (buffer, currlen, maxlen, signvalue);
-
-       while (iplace > 0)
-               dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]);
+       if (sign != 0)  /* Sign. */
+               OUTCHAR(str, *len, size, sign);
+       while (ipos > 0) {      /* Integer part. */
+               ipos--;
+               OUTCHAR(str, *len, size, iconvert[ipos]);
+               if (separators > 0 && ipos > 0 && ipos % 3 == 0)
+                       printsep(str, len, size);
+       }
+       if (emitpoint) {        /* Decimal point. */
+#if HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT
+               if (lc->decimal_point != NULL && *lc->decimal_point != '\0')
+                       OUTCHAR(str, *len, size, *lc->decimal_point);
+               else    /* We'll always print some decimal point character. */
+#endif /* HAVE_LOCALECONV && HAVE_LCONV_DECIMAL_POINT */
+                       OUTCHAR(str, *len, size, '.');
+       }
+       while (leadfraczeros > 0) {     /* Leading fractional part zeros. */
+               OUTCHAR(str, *len, size, '0');
+               leadfraczeros--;
+       }
+       while (fpos > omitcount) {      /* The remaining fractional part. */
+               fpos--;
+               OUTCHAR(str, *len, size, fconvert[fpos]);
+       }
+       while (epos > 0) {      /* Exponent. */
+               epos--;
+               OUTCHAR(str, *len, size, econvert[epos]);
+       }
+       while (padlen < 0) {    /* Trailing spaces. */
+               OUTCHAR(str, *len, size, ' ');
+               padlen++;
+       }
+}
 
-#ifdef DEBUG_SNPRINTF
-       printf("fmtfp: fplace=%d zpadlen=%d\n", fplace, zpadlen);
-#endif
+static void
+printsep(char *str, size_t *len, size_t size)
+{
+#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP
+       struct lconv *lc = localeconv();
+       int i;
 
-       /*
-        * Decimal point.  This should probably use locale to find the correct
-        * char to print out.
-        */
-       if (max > 0) {
-               dopr_outch (buffer, currlen, maxlen, '.');
+       if (lc->thousands_sep != NULL)
+               for (i = 0; lc->thousands_sep[i] != '\0'; i++)
+                       OUTCHAR(str, *len, size, lc->thousands_sep[i]);
+       else
+#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */
+               OUTCHAR(str, *len, size, ',');
+}
 
-               while (fplace > 0)
-                       dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]);
+static int
+getnumsep(int digits)
+{
+       int separators = (digits - ((digits % 3 == 0) ? 1 : 0)) / 3;
+#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP
+       int strln;
+       struct lconv *lc = localeconv();
+
+       /* We support an arbitrary separator length (including zero). */
+       if (lc->thousands_sep != NULL) {
+               for (strln = 0; lc->thousands_sep[strln] != '\0'; strln++)
+                       continue;
+               separators *= strln;
        }
+#endif /* HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP */
+       return separators;
+}
 
-       while (zpadlen > 0) {
-               dopr_outch (buffer, currlen, maxlen, '0');
-               --zpadlen;
-       }
+static int
+getexponent(LDOUBLE value)
+{
+       LDOUBLE tmp = (value >= 0.0) ? value : -value;
+       int exponent = 0;
 
-       while (padlen < 0) {
-               dopr_outch (buffer, currlen, maxlen, ' ');
-               ++padlen;
-       }
+       /*
+        * We check for 99 > exponent > -99 in order to work around possible
+        * endless loops which could happen (at least) in the second loop (at
+        * least) if we're called with an infinite value.  However, we checked
+        * for infinity before calling this function using our ISINF() macro, so
+        * this might be somewhat paranoid.
+        */
+       while (tmp < 1.0 && tmp > 0.0 && --exponent > -99)
+               tmp *= 10;
+       while (tmp >= 10.0 && ++exponent < 99)
+               tmp /= 10;
+
+       return exponent;
 }
 
-static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c)
+static int
+convert(UINTMAX_T value, char *buf, size_t size, int base, int caps)
 {
-       if (*currlen < maxlen) {
-               buffer[(*currlen)] = c;
-       }
-       (*currlen)++;
+       const char *digits = caps ? "0123456789ABCDEF" : "0123456789abcdef";
+       size_t pos = 0;
+
+       /* We return an unterminated buffer with the digits in reverse order. */
+       do {
+               buf[pos++] = digits[value % base];
+               value /= base;
+       } while (value != 0 && pos < size);
+
+       return (int)pos;
 }
 
-/* yes this really must be a ||. Don't muck with this (tridge) */
-#if !defined(HAVE_VSNPRINTF) || !defined(HAVE_C99_VSNPRINTF)
- int vsnprintf (char *str, size_t count, const char *fmt, va_list args)
+static UINTMAX_T
+cast(LDOUBLE value)
 {
-       return dopr(str, count, fmt, args);
+       UINTMAX_T result;
+
+       /*
+        * We check for ">=" and not for ">" because if UINTMAX_MAX cannot be
+        * represented exactly as an LDOUBLE value (but is less than LDBL_MAX),
+        * it may be increased to the nearest higher representable value for the
+        * comparison (cf. C99: 6.3.1.4, 2).  It might then equal the LDOUBLE
+        * value although converting the latter to UINTMAX_T would overflow.
+        */
+       if (value >= UINTMAX_MAX)
+               return UINTMAX_MAX;
+
+       result = value;
+       /*
+        * At least on NetBSD/sparc64 3.0.2 and 4.99.30, casting long double to
+        * an integer type converts e.g. 1.9 to 2 instead of 1 (which violates
+        * the standard).  Sigh.
+        */
+       return (result <= value) ? result : result - 1;
 }
-#endif
 
-/* yes this really must be a ||. Don't muck wiith this (tridge)
- *
- * The logic for these two is that we need our own definition if the
- * OS *either* has no definition of *sprintf, or if it does have one
- * that doesn't work properly according to the autoconf test.  Perhaps
- * these should really be smb_snprintf to avoid conflicts with buggy
- * linkers? -- mbp
- */
-#if !defined(HAVE_SNPRINTF) || !defined(HAVE_C99_SNPRINTF)
- int snprintf(char *str,size_t count,const char *fmt,...)
+static UINTMAX_T
+myround(LDOUBLE value)
 {
-       size_t ret;
-       va_list ap;
+       UINTMAX_T intpart = cast(value);
 
-       va_start(ap, fmt);
-       ret = vsnprintf(str, count, fmt, ap);
-       va_end(ap);
-       return ret;
+       return ((value -= intpart) < 0.5) ? intpart : intpart + 1;
 }
-#endif
-
-#endif
 
-#ifndef HAVE_VASPRINTF
- int vasprintf(char **ptr, const char *format, va_list ap)
+static LDOUBLE
+mypow10(int exponent)
 {
-       int ret;
+       LDOUBLE result = 1;
 
-       ret = vsnprintf(0, 0, format, ap);
-       if (ret <= 0) return ret;
+       while (exponent > 0) {
+               result *= 10;
+               exponent--;
+       }
+       while (exponent < 0) {
+               result /= 10;
+               exponent++;
+       }
+       return result;
+}
+#endif /* !HAVE_VSNPRINTF */
 
-       (*ptr) = (char *)malloc(ret+1);
-       if (!*ptr) return -1;
-       ret = vsnprintf(*ptr, ret+1, format, ap);
+#if !HAVE_VASPRINTF
+#if NEED_MYMEMCPY
+void *
+mymemcpy(void *dst, void *src, size_t len)
+{
+       const char *from = src;
+       char *to = dst;
 
-       return ret;
+       /* No need for optimization, we use this only to replace va_copy(3). */
+       while (len-- > 0)
+               *to++ = *from++;
+       return dst;
 }
-#endif
+#endif /* NEED_MYMEMCPY */
 
+int
+rpl_vasprintf(char **ret, const char *format, va_list ap)
+{
+       size_t size;
+       int len;
+       va_list aq;
+
+       VA_COPY(aq, ap);
+       len = vsnprintf(NULL, 0, format, aq);
+       VA_END_COPY(aq);
+       if (len < 0 || (*ret = malloc(size = len + 1)) == NULL)
+               return -1;
+       return vsnprintf(*ret, size, format, ap);
+}
+#endif /* !HAVE_VASPRINTF */
 
-#ifndef HAVE_ASPRINTF
- int asprintf(char **ptr, const char *format, ...)
+#if !HAVE_SNPRINTF
+#if HAVE_STDARG_H
+int
+rpl_snprintf(char *str, size_t size, const char *format, ...)
+#else
+int
+rpl_snprintf(va_alist) va_dcl
+#endif /* HAVE_STDARG_H */
 {
+#if !HAVE_STDARG_H
+       char *str;
+       size_t size;
+       char *format;
+#endif /* HAVE_STDARG_H */
        va_list ap;
-       int ret;
+       int len;
 
-       *ptr = 0;
-       va_start(ap, format);
-       ret = vasprintf(ptr, format, ap);
+       VA_START(ap, format);
+       VA_SHIFT(ap, str, char *);
+       VA_SHIFT(ap, size, size_t);
+       VA_SHIFT(ap, format, const char *);
+       len = vsnprintf(str, size, format, ap);
        va_end(ap);
-
-       return ret;
+       return len;
 }
-#endif
+#endif /* !HAVE_SNPRINTF */
 
-#ifndef HAVE_VSYSLOG
-#ifdef HAVE_SYSLOG
- void vsyslog (int facility_priority, char *format, va_list arglist)
+#if !HAVE_ASPRINTF
+#if HAVE_STDARG_H
+int
+rpl_asprintf(char **ret, const char *format, ...)
+#else
+int
+rpl_asprintf(va_alist) va_dcl
+#endif /* HAVE_STDARG_H */
 {
-       char *msg = 0;
-       vasprintf(&msg, format, arglist);
-        if (!msg)
-                return;
-        syslog(facility_priority, "%s", msg);
-        free(msg);
-}
-#endif /* HAVE_SYSLOG */
-#endif /* HAVE_VSYSLOG */
-
-#ifdef TEST_SNPRINTF
-
- int sprintf(char *str,const char *fmt,...);
+#if !HAVE_STDARG_H
+       char **ret;
+       char *format;
+#endif /* HAVE_STDARG_H */
+       va_list ap;
+       int len;
 
- int main (void)
+       VA_START(ap, format);
+       VA_SHIFT(ap, ret, char **);
+       VA_SHIFT(ap, format, const char *);
+       len = vasprintf(ret, format, ap);
+       va_end(ap);
+       return len;
+}
+#endif /* !HAVE_ASPRINTF */
+#else  /* Dummy declaration to avoid empty translation unit warnings. */
+int main(void);
+#endif /* !HAVE_SNPRINTF || !HAVE_VSNPRINTF || !HAVE_ASPRINTF || [...] */
+
+#if TEST_SNPRINTF
+int
+main(void)
 {
-       char buf1[1024];
-       char buf2[1024];
-       char *fp_fmt[] = {
-               "%1.1f",
-               "%-1.5f",
-               "%1.5f",
+       const char *float_fmt[] = {
+               /* "%E" and "%e" formats. */
+#if HAVE_LONG_LONG_INT && !OS_BSD && !OS_IRIX
+               "%.16e",
+               "%22.16e",
+               "%022.16e",
+               "%-22.16e",
+               "%#+'022.16e",
+#endif /* HAVE_LONG_LONG_INT && !OS_BSD && !OS_IRIX */
+               "foo|%#+0123.9E|bar",
+               "%-123.9e",
+               "%123.9e",
+               "%+23.9e",
+               "%+05.8e",
+               "%-05.8e",
+               "%05.8e",
+               "%+5.8e",
+               "%-5.8e",
+               "% 5.8e",
+               "%5.8e",
+               "%+4.9e",
+#if !OS_LINUX  /* glibc sometimes gets these wrong. */
+               "%+#010.0e",
+               "%#10.1e",
+               "%10.5e",
+               "% 10.5e",
+               "%5.0e",
+               "%5.e",
+               "%#5.0e",
+               "%#5.e",
+               "%3.2e",
+               "%3.1e",
+               "%-1.5e",
+               "%1.5e",
+               "%01.3e",
+               "%1.e",
+               "%.1e",
+               "%#.0e",
+               "%+.0e",
+               "% .0e",
+               "%.0e",
+               "%#.e",
+               "%+.e",
+               "% .e",
+               "%.e",
+               "%4e",
+               "%e",
+               "%E",
+#endif /* !OS_LINUX */
+               /* "%F" and "%f" formats. */
+#if !OS_BSD && !OS_IRIX
+               "% '022f",
+               "%+'022f",
+               "%-'22f",
+               "%'22f",
+#if HAVE_LONG_LONG_INT
+               "%.16f",
+               "%22.16f",
+               "%022.16f",
+               "%-22.16f",
+               "%#+'022.16f",
+#endif /* HAVE_LONG_LONG_INT */
+#endif /* !OS_BSD && !OS_IRIX */
+               "foo|%#+0123.9F|bar",
+               "%-123.9f",
                "%123.9f",
+               "%+23.9f",
+               "%+#010.0f",
+               "%#10.1f",
                "%10.5f",
                "% 10.5f",
-               "%+22.9f",
+               "%+05.8f",
+               "%-05.8f",
+               "%05.8f",
+               "%+5.8f",
+               "%-5.8f",
+               "% 5.8f",
+               "%5.8f",
+               "%5.0f",
+               "%5.f",
+               "%#5.0f",
+               "%#5.f",
                "%+4.9f",
-               "%01.3f",
-               "%4f",
-               "%3.1f",
                "%3.2f",
+               "%3.1f",
+               "%-1.5f",
+               "%1.5f",
+               "%01.3f",
+               "%1.f",
+               "%.1f",
+               "%#.0f",
+               "%+.0f",
+               "% .0f",
                "%.0f",
+               "%#.f",
+               "%+.f",
+               "% .f",
+               "%.f",
+               "%4f",
                "%f",
-               "-16.16f",
+               "%F",
+               /* "%G" and "%g" formats. */
+#if !OS_BSD && !OS_IRIX && !OS_LINUX
+               "% '022g",
+               "%+'022g",
+               "%-'22g",
+               "%'22g",
+#if HAVE_LONG_LONG_INT
+               "%.16g",
+               "%22.16g",
+               "%022.16g",
+               "%-22.16g",
+               "%#+'022.16g",
+#endif /* HAVE_LONG_LONG_INT */
+#endif /* !OS_BSD && !OS_IRIX && !OS_LINUX */
+               "foo|%#+0123.9G|bar",
+               "%-123.9g",
+               "%123.9g",
+               "%+23.9g",
+               "%+05.8g",
+               "%-05.8g",
+               "%05.8g",
+               "%+5.8g",
+               "%-5.8g",
+               "% 5.8g",
+               "%5.8g",
+               "%+4.9g",
+#if !OS_LINUX  /* glibc sometimes gets these wrong. */
+               "%+#010.0g",
+               "%#10.1g",
+               "%10.5g",
+               "% 10.5g",
+               "%5.0g",
+               "%5.g",
+               "%#5.0g",
+               "%#5.g",
+               "%3.2g",
+               "%3.1g",
+               "%-1.5g",
+               "%1.5g",
+               "%01.3g",
+               "%1.g",
+               "%.1g",
+               "%#.0g",
+               "%+.0g",
+               "% .0g",
+               "%.0g",
+               "%#.g",
+               "%+.g",
+               "% .g",
+               "%.g",
+               "%4g",
+               "%g",
+               "%G",
+#endif /* !OS_LINUX */
+               NULL
+       };
+       double float_val[] = {
+               -4.136,
+               -134.52,
+               -5.04030201,
+               -3410.01234,
+               -999999.999999,
+               -913450.29876,
+               -913450.2,
+               -91345.2,
+               -9134.2,
+               -913.2,
+               -91.2,
+               -9.2,
+               -9.9,
+               4.136,
+               134.52,
+               5.04030201,
+               3410.01234,
+               999999.999999,
+               913450.29876,
+               913450.2,
+               91345.2,
+               9134.2,
+               913.2,
+               91.2,
+               9.2,
+               9.9,
+               9.96,
+               9.996,
+               9.9996,
+               9.99996,
+               9.999996,
+               9.9999996,
+               9.99999996,
+               0.99999996,
+               0.99999999,
+               0.09999999,
+               0.00999999,
+               0.00099999,
+               0.00009999,
+               0.00000999,
+               0.00000099,
+               0.00000009,
+               0.00000001,
+               0.0000001,
+               0.000001,
+               0.00001,
+               0.0001,
+               0.001,
+               0.01,
+               0.1,
+               1.0,
+               1.5,
+               -1.5,
+               -1.0,
+               -0.1,
+#if !OS_BSD    /* BSD sometimes gets these wrong. */
+#ifdef INFINITY
+               INFINITY,
+               -INFINITY,
+#endif /* defined(INFINITY) */
+#ifdef NAN
+               NAN,
+#endif /* defined(NAN) */
+#endif /* !OS_BSD */
                0
        };
-       double fp_nums[] = { 6442452944.1234, -1.5, 134.21, 91340.2, 341.1234, 0203.9, 0.96, 0.996,
-                            0.9996, 1.996, 4.136,  0};
-       char *int_fmt[] = {
-               "%-1.5d",
-               "%1.5d",
-               "%123.9d",
-               "%5.5d",
-               "%10.5d",
-               "% 10.5d",
-               "%+22.33d",
-               "%01.3d",
-               "%4d",
-               "%d",
+       const char *long_fmt[] = {
+               "foo|%0123ld|bar",
+#if !OS_IRIX
+               "% '0123ld",
+               "%+'0123ld",
+               "%-'123ld",
+               "%'123ld",
+#endif /* !OS_IRiX */
+               "%123.9ld",
+               "% 123.9ld",
+               "%+123.9ld",
+               "%-123.9ld",
+               "%0123ld",
+               "% 0123ld",
+               "%+0123ld",
+               "%-0123ld",
+               "%10.5ld",
+               "% 10.5ld",
+               "%+10.5ld",
+               "%-10.5ld",
+               "%010ld",
+               "% 010ld",
+               "%+010ld",
+               "%-010ld",
+               "%4.2ld",
+               "% 4.2ld",
+               "%+4.2ld",
+               "%-4.2ld",
+               "%04ld",
+               "% 04ld",
+               "%+04ld",
+               "%-04ld",
+               "%5.5ld",
+               "%+22.33ld",
+               "%01.3ld",
+               "%1.5ld",
+               "%-1.5ld",
+               "%44ld",
+               "%4ld",
+               "%4.0ld",
+               "%4.ld",
+               "%.44ld",
+               "%.4ld",
+               "%.0ld",
+               "%.ld",
+               "%ld",
+               NULL
+       };
+       long int long_val[] = {
+#ifdef LONG_MAX
+               LONG_MAX,
+#endif /* LONG_MAX */
+#ifdef LONG_MIN
+               LONG_MIN,
+#endif /* LONG_MIN */
+               -91340,
+               91340,
+               341,
+               134,
+               0203,
+               -1,
+               1,
                0
        };
-       long int_nums[] = { -1, 134, 91340, 341, 0203, 0};
-       char *str_fmt[] = {
-               "10.5s",
-               "5.10s",
-               "10.1s",
-               "0.10s",
-               "10.0s",
-               "1.10s",
-               "%s",
-               "%.1s",
-               "%.10s",
-               "%10s",
+       const char *ulong_fmt[] = {
+               /* "%u" formats. */
+               "foo|%0123lu|bar",
+#if !OS_IRIX
+               "% '0123lu",
+               "%+'0123lu",
+               "%-'123lu",
+               "%'123lu",
+#endif /* !OS_IRiX */
+               "%123.9lu",
+               "% 123.9lu",
+               "%+123.9lu",
+               "%-123.9lu",
+               "%0123lu",
+               "% 0123lu",
+               "%+0123lu",
+               "%-0123lu",
+               "%5.5lu",
+               "%+22.33lu",
+               "%01.3lu",
+               "%1.5lu",
+               "%-1.5lu",
+               "%44lu",
+               "%lu",
+               /* "%o" formats. */
+               "foo|%#0123lo|bar",
+               "%#123.9lo",
+               "%# 123.9lo",
+               "%#+123.9lo",
+               "%#-123.9lo",
+               "%#0123lo",
+               "%# 0123lo",
+               "%#+0123lo",
+               "%#-0123lo",
+               "%#5.5lo",
+               "%#+22.33lo",
+               "%#01.3lo",
+               "%#1.5lo",
+               "%#-1.5lo",
+               "%#44lo",
+               "%#lo",
+               "%123.9lo",
+               "% 123.9lo",
+               "%+123.9lo",
+               "%-123.9lo",
+               "%0123lo",
+               "% 0123lo",
+               "%+0123lo",
+               "%-0123lo",
+               "%5.5lo",
+               "%+22.33lo",
+               "%01.3lo",
+               "%1.5lo",
+               "%-1.5lo",
+               "%44lo",
+               "%lo",
+               /* "%X" and "%x" formats. */
+               "foo|%#0123lX|bar",
+               "%#123.9lx",
+               "%# 123.9lx",
+               "%#+123.9lx",
+               "%#-123.9lx",
+               "%#0123lx",
+               "%# 0123lx",
+               "%#+0123lx",
+               "%#-0123lx",
+               "%#5.5lx",
+               "%#+22.33lx",
+               "%#01.3lx",
+               "%#1.5lx",
+               "%#-1.5lx",
+               "%#44lx",
+               "%#lx",
+               "%#lX",
+               "%123.9lx",
+               "% 123.9lx",
+               "%+123.9lx",
+               "%-123.9lx",
+               "%0123lx",
+               "% 0123lx",
+               "%+0123lx",
+               "%-0123lx",
+               "%5.5lx",
+               "%+22.33lx",
+               "%01.3lx",
+               "%1.5lx",
+               "%-1.5lx",
+               "%44lx",
+               "%lx",
+               "%lX",
+               NULL
+       };
+       unsigned long int ulong_val[] = {
+#ifdef ULONG_MAX
+               ULONG_MAX,
+#endif /* ULONG_MAX */
+               91340,
+               341,
+               134,
+               0203,
+               1,
                0
        };
-       char *str_vals[] = {"hello", "a", "", "a longer string", 0};
-       int x, y;
-       int fail = 0;
-       int num = 0;
-
-       printf ("Testing snprintf format codes against system sprintf...\n");
-
-       for (x = 0; fp_fmt[x] ; x++) {
-               for (y = 0; fp_nums[y] != 0 ; y++) {
-                       int l1 = snprintf(0, 0, fp_fmt[x], fp_nums[y]);
-                       int l2 = snprintf(buf1, sizeof(buf1), fp_fmt[x], fp_nums[y]);
-                       sprintf (buf2, fp_fmt[x], fp_nums[y]);
-                       if (strcmp (buf1, buf2)) {
-                               printf("snprintf doesn't match Format: %s\n\tsnprintf = [%s]\n\t sprintf = [%s]\n",
-                                      fp_fmt[x], buf1, buf2);
-                               fail++;
-                       }
-                       if (l1 != l2) {
-                               printf("snprintf l1 != l2 (%d %d) %s\n", l1, l2, fp_fmt[x]);
-                               fail++;
-                       }
-                       num++;
-               }
-       }
-
-       for (x = 0; int_fmt[x] ; x++) {
-               for (y = 0; int_nums[y] != 0 ; y++) {
-                       int l1 = snprintf(0, 0, int_fmt[x], int_nums[y]);
-                       int l2 = snprintf(buf1, sizeof(buf1), int_fmt[x], int_nums[y]);
-                       sprintf (buf2, int_fmt[x], int_nums[y]);
-                       if (strcmp (buf1, buf2)) {
-                               printf("snprintf doesn't match Format: %s\n\tsnprintf = [%s]\n\t sprintf = [%s]\n",
-                                      int_fmt[x], buf1, buf2);
-                               fail++;
-                       }
-                       if (l1 != l2) {
-                               printf("snprintf l1 != l2 (%d %d) %s\n", l1, l2, int_fmt[x]);
-                               fail++;
-                       }
-                       num++;
-               }
-       }
-
-       for (x = 0; str_fmt[x] ; x++) {
-               for (y = 0; str_vals[y] != 0 ; y++) {
-                       int l1 = snprintf(0, 0, str_fmt[x], str_vals[y]);
-                       int l2 = snprintf(buf1, sizeof(buf1), str_fmt[x], str_vals[y]);
-                       sprintf (buf2, str_fmt[x], str_vals[y]);
-                       if (strcmp (buf1, buf2)) {
-                               printf("snprintf doesn't match Format: %s\n\tsnprintf = [%s]\n\t sprintf = [%s]\n",
-                                      str_fmt[x], buf1, buf2);
-                               fail++;
-                       }
-                       if (l1 != l2) {
-                               printf("snprintf l1 != l2 (%d %d) %s\n", l1, l2, str_fmt[x]);
-                               fail++;
-                       }
-                       num++;
-               }
-       }
-
-       printf ("%d tests failed out of %d.\n", fail, num);
+       const char *llong_fmt[] = {
+               "foo|%0123lld|bar",
+               "%123.9lld",
+               "% 123.9lld",
+               "%+123.9lld",
+               "%-123.9lld",
+               "%0123lld",
+               "% 0123lld",
+               "%+0123lld",
+               "%-0123lld",
+               "%5.5lld",
+               "%+22.33lld",
+               "%01.3lld",
+               "%1.5lld",
+               "%-1.5lld",
+               "%44lld",
+               "%lld",
+               NULL
+       };
+       LLONG llong_val[] = {
+#ifdef LLONG_MAX
+               LLONG_MAX,
+#endif /* LLONG_MAX */
+#ifdef LLONG_MIN
+               LLONG_MIN,
+#endif /* LLONG_MIN */
+               -91340,
+               91340,
+               341,
+               134,
+               0203,
+               -1,
+               1,
+               0
+       };
+       const char *string_fmt[] = {
+               "foo|%10.10s|bar",
+               "%-10.10s",
+               "%10.10s",
+               "%10.5s",
+               "%5.10s",
+               "%10.1s",
+               "%1.10s",
+               "%10.0s",
+               "%0.10s",
+               "%-42.5s",
+               "%2.s",
+               "%.10s",
+               "%.1s",
+               "%.0s",
+               "%.s",
+               "%4s",
+               "%s",
+               NULL
+       };
+       const char *string_val[] = {
+               "Hello",
+               "Hello, world!",
+               "Sound check: One, two, three.",
+               "This string is a little longer than the other strings.",
+               "1",
+               "",
+               NULL
+       };
+#if !OS_SYSV   /* SysV uses a different format than we do. */
+       const char *pointer_fmt[] = {
+               "foo|%p|bar",
+               "%42p",
+               "%p",
+               NULL
+       };
+       const char *pointer_val[] = {
+               *pointer_fmt,
+               *string_fmt,
+               *string_val,
+               NULL
+       };
+#endif /* !OS_SYSV */
+       char buf1[1024], buf2[1024];
+       double value, digits = 9.123456789012345678901234567890123456789;
+       int i, j, r1, r2, failed = 0, num = 0;
 
-       printf("seeing how many digits we support\n");
-       {
-               double v0 = 0.12345678901234567890123456789012345678901;
-               for (x=0; x<100; x++) {
-                       snprintf(buf1, sizeof(buf1), "%1.1f", v0*pow(10, x));
-                       sprintf(buf2,                "%1.1f", v0*pow(10, x));
-                       if (strcmp(buf1, buf2)) {
-                               printf("we seem to support %d digits\n", x-1);
-                               break;
-                       }
+/*
+ * Use -DTEST_NILS in order to also test the conversion of nil values.  Might
+ * segfault on systems which don't support converting a NULL pointer with "%s"
+ * and lets some test cases fail against BSD and glibc due to bugs in their
+ * implementations.
+ */
+#ifndef TEST_NILS
+#define TEST_NILS 0
+#elif TEST_NILS
+#undef TEST_NILS
+#define TEST_NILS 1
+#endif /* !defined(TEST_NILS) */
+#ifdef TEST
+#undef TEST
+#endif /* defined(TEST) */
+#define TEST(fmt, val)                                                         \
+do {                                                                           \
+       for (i = 0; fmt[i] != NULL; i++)                                       \
+               for (j = 0; j == 0 || val[j - TEST_NILS] != 0; j++) {          \
+                       r1 = sprintf(buf1, fmt[i], val[j]);                    \
+                       r2 = snprintf(buf2, sizeof(buf2), fmt[i], val[j]);     \
+                       if (strcmp(buf1, buf2) != 0 || r1 != r2) {             \
+                               (void)printf("Results don't match, "           \
+                                   "format string: %s\n"                      \
+                                   "\t sprintf(3): [%s] (%d)\n"               \
+                                   "\tsnprintf(3): [%s] (%d)\n",              \
+                                   fmt[i], buf1, r1, buf2, r2);               \
+                               failed++;                                      \
+                       }                                                      \
+                       num++;                                                 \
+               }                                                              \
+} while (/* CONSTCOND */ 0)
+
+#if HAVE_LOCALE_H
+       (void)setlocale(LC_ALL, "");
+#endif /* HAVE_LOCALE_H */
+
+       (void)puts("Testing our snprintf(3) against your system's sprintf(3).");
+       TEST(float_fmt, float_val);
+       TEST(long_fmt, long_val);
+       TEST(ulong_fmt, ulong_val);
+       TEST(llong_fmt, llong_val);
+       TEST(string_fmt, string_val);
+#if !OS_SYSV   /* SysV uses a different format than we do. */
+       TEST(pointer_fmt, pointer_val);
+#endif /* !OS_SYSV */
+       (void)printf("Result: %d out of %d tests failed.\n", failed, num);
+
+       (void)fputs("Checking how many digits we support: ", stdout);
+       for (i = 0; i < 100; i++) {
+               value = pow(10, i) * digits;
+               (void)sprintf(buf1, "%.1f", value);
+               (void)snprintf(buf2, sizeof(buf2), "%.1f", value);
+               if (strcmp(buf1, buf2) != 0) {
+                       (void)printf("apparently %d.\n", i);
+                       break;
                }
        }
-
-       return 0;
+       return (failed == 0) ? 0 : 1;
 }
-#endif /* SNPRINTF_TEST */
+#endif /* TEST_SNPRINTF */
+
+/* vim: set joinspaces textwidth=80: */
index e9b0abf2ef92dcf80442080177354d7c3a2923a4..d143cf167dc2e87a30b3a11656ac803da074949b 100644 (file)
--- a/system.h
+++ b/system.h
 #include <time.h>
 #include <utime.h>
 
+#if !HAVE_VSNPRINTF
+  int rpl_vsnprintf(char *, size_t, const char *, va_list);
+#endif
+#if !HAVE_SNPRINTF
+  int rpl_snprintf(char *, size_t, const char *, ...);
+#endif
+#if !HAVE_VASPRINTF
+  int rpl_vasprintf(char **, const char *, va_list);
+#endif
+#if !HAVE_ASPRINTF
+  int rpl_asprintf(char **, const char *, ...);
+#endif
+
 #ifdef HAVE_STDBOOL_H
 #  include <stdbool.h>
 #else