Blob Blame History Raw
/* $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 (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.
 */

/*
 * 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:
 *
 * 	The PGP code was using unsigned hexadecimal formats.  Unfortunately,
 * 	unsigned formats simply didn't work.
 *
 * 1997-10-22 Brandon Long <blong@fiction.net> 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, 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
 *
 * 2996-09-15 Brandon Long <blong@fiction.net> 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.
 */

/*
 * 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:
 *
 *    	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
 *
 * 2) The calls to the functions which should be replaced must be redefined
 *    throughout the project files (by using Autoconf or other means):
 *
 *    	#define vsnprintf rpl_vsnprintf
 *    	#define snprintf rpl_snprintf
 *    	#define vasprintf rpl_vasprintf
 *    	#define asprintf rpl_asprintf
 *
 * 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 snprintf
#undef snprintf
#endif	/* defined(snprintf) */
#ifdef vsnprintf
#undef vsnprintf
#endif	/* defined(vsnprintf) */
#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
#endif	/* TEST_SNPRINTF */

#if !HAVE_SNPRINTF || !HAVE_VSNPRINTF
#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>
#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_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 defined(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	/* HAVE_LONG_DOUBLE */
#endif	/* !defined(LDOUBLE) */

/* Support for long long int. */
#ifndef LLONG
#if HAVE_LONG_LONG_INT
#define LLONG long long int
#else
#define LLONG long int
#endif	/* HAVE_LONG_LONG_INT */
#endif	/* !defined(LLONG) */

/* Support for intmax_t. */
#ifndef INTMAX_T
#if defined(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 defined(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 defined(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) */

/*
 * 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) */

/*
 * 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

#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)
{
	LDOUBLE fvalue;
	INTMAX_T value;
	unsigned char cvalue;
	const char *strvalue;
	INTMAX_T *intmaxptr;
	PTRDIFF_T *ptrdiffptr;
	SSIZE_T *sizeptr;
	/* Disabling, as long long is not supported in C90.
	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 = PRINT_S_FLAGS;
			else
				OUTCHAR(str, len, size, ch);
			ch = *format++;
			break;
		case PRINT_S_FLAGS:
			switch (ch) {
			case '-':
				flags |= PRINT_F_MINUS;
				ch = *format++;
				break;
			case '+':
				flags |= PRINT_F_PLUS;
				ch = *format++;
				break;
			case ' ':
				flags |= PRINT_F_SPACE;
				ch = *format++;
				break;
			case '#':
				flags |= PRINT_F_NUM;
				ch = *format++;
				break;
			case '0':
				flags |= PRINT_F_ZERO;
				ch = *format++;
				break;
			case '\'':	/* SUSv2 flag (not in C99). */
				flags |= PRINT_F_QUOTE;
				ch = *format++;
				break;
			default:
				state = PRINT_S_WIDTH;
				break;
			}
			break;
		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 == '*') {
				/*
				 * 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 = PRINT_S_DOT;
			} else
				state = PRINT_S_DOT;
			break;
		case PRINT_S_DOT:
			if (ch == '.') {
				state = PRINT_S_PRECISION;
				ch = *format++;
			} else
				state = PRINT_S_MOD;
			break;
		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 == '*') {
				/*
				 * 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 = PRINT_S_MOD;
			} else
				state = PRINT_S_MOD;
			break;
		case PRINT_S_MOD:
			switch (ch) {
			case 'h':
				ch = *format++;
				if (ch == 'h') {	/* It's a char. */
					ch = *format++;
					cflags = PRINT_C_CHAR;
				} else
					cflags = PRINT_C_SHORT;
				break;
			case 'l':
				ch = *format++;
				/*
				if (ch == 'l') }
					ch = *format++;
					cflags = PRINT_C_LLONG;
				} else */
					cflags = PRINT_C_LONG;
				break;
			case 'L':
				cflags = PRINT_C_LDOUBLE;
				ch = *format++;
				break;
			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;
			default:
				/* Lenght modifier is invalid */
				break;
			}
			state = PRINT_S_CONV;
			break;
		case PRINT_S_CONV:
			switch (ch) {
			case 'd':
				/* FALLTHROUGH */
			case 'i':
				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 |= PRINT_F_UP;
				/* FALLTHROUGH */
			case 'x':
				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 == PRINT_C_LDOUBLE)
					fvalue = va_arg(args, LDOUBLE);
				else
					fvalue = va_arg(args, double);
				fmtflt(str, &len, size, fvalue, width,
				    precision, flags, &overflow);
				if (overflow)
					goto out;
				break;
			case 'E':
				flags |= PRINT_F_UP;
				/* FALLTHROUGH */
			case 'e':
				flags |= PRINT_F_TYPE_E;
				if (cflags == PRINT_C_LDOUBLE)
					fvalue = va_arg(args, LDOUBLE);
				else
					fvalue = va_arg(args, double);
				fmtflt(str, &len, size, fvalue, width,
				    precision, flags, &overflow);
				if (overflow)
					goto out;
				break;
			case 'G':
				flags |= PRINT_F_UP;
				/* FALLTHROUGH */
			case 'g':
				flags |= PRINT_F_TYPE_G;
				if (cflags == PRINT_C_LDOUBLE)
					fvalue = va_arg(args, LDOUBLE);
				else
					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':
				cvalue = va_arg(args, int);
				OUTCHAR(str, len, size, cvalue);
				break;
			case 's':
				strvalue = va_arg(args, char *);
				fmtstr(str, &len, size, strvalue, width,
				    precision, flags);
				break;
			case 'p':
				/*
				 * 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 = (const char *)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':
				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 '%':	/* Print a "%" character verbatim. */
				OUTCHAR(str, len, size, ch);
				break;
			default:	/* Skip other characters. */
				break;
			}
			ch = *format++;
			state = PRINT_S_DEFAULT;
			base = cflags = flags = width = 0;
			precision = -1;
			break;
		default:
			/* This is an invalid state, should not get here */
			break;
		}
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;
	}
	return (int)len;
}

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 noprecision = (precision == -1);

	if (value == NULL)	/* We're forgiving. */
		value = "(null)";

	/* 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 & PRINT_F_MINUS)	/* Left justify. */
		padlen = -padlen;

	while (padlen > 0) {	/* Leading spaces. */
		OUTCHAR(str, *len, size, ' ');
		padlen--;
	}
	while (*value != '\0' && (noprecision || precision-- > 0)) {
		OUTCHAR(str, *len, size, *value);
		value++;
	}
	while (padlen < 0) {	/* Trailing spaces. */
		OUTCHAR(str, *len, size, ' ');
		padlen++;
	}
}

static void
fmtint(char *str, size_t *len, size_t size, INTMAX_T value, int base, int width,
       int precision, int flags)
{
	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 = ' ';
	}

	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;
		default:
			/* Invalid base */
			break;
		}
	}

	if (separators)	/* Get the number of group separators we'll print. */
		separators = getnumsep(pos);

	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? */

	if (zpadlen < 0)
		zpadlen = 0;
	if (spadlen < 0)
		spadlen = 0;

	/*
	 * 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;
	}
	while (spadlen > 0) {	/* Leading spaces. */
		OUTCHAR(str, *len, size, ' ');
		spadlen--;
	}
	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++;
	}
}

static void
fmtflt(char *str, size_t *len, size_t size, LDOUBLE fvalue, int width,
       int precision, int flags, int *overflow)
{
	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 */

	/*
	 * 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;
	}

	/* "%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;
	}

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;
	}

	ufvalue = (fvalue >= 0.0) ? fvalue : -fvalue;
	if (estyle)	/* We want exactly one integer digit. */
		ufvalue /= mypow10(exponent);

	if ((intpart = cast(ufvalue)) == UINTMAX_MAX) {
		*overflow = 1;
		return;
	}

	/*
	 * 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.
	 */
	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++;
		}
	}

	/*
	 * 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 (flags & PRINT_F_TYPE_G && estyle &&
	    precision + 1 > exponent && exponent >= -4) {
		precision -= exponent;
		estyle = 0;
		goto again;
	}

	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';
	}

	/* 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);

	leadfraczeros = precision - fpos;

	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? */

	if (padlen < 0)
		padlen = 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) {	/* Leading zeros. */
			OUTCHAR(str, *len, size, '0');
			padlen--;
		}
	}
	while (padlen > 0) {	/* Leading spaces. */
		OUTCHAR(str, *len, size, ' ');
		padlen--;
	}
	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++;
	}
}

static void
printsep(char *str, size_t *len, size_t size)
{
#if HAVE_LOCALECONV && HAVE_LCONV_THOUSANDS_SEP
	struct lconv *lc = localeconv();
	int i;

	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, ',');
}

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;
}

static int
getexponent(LDOUBLE value)
{
	LDOUBLE tmp = (value >= 0.0) ? value : -value;
	int exponent = 0;

	/*
	 * 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 int
convert(UINTMAX_T value, char *buf, size_t size, int base, int caps)
{
	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;
}

static UINTMAX_T
cast(LDOUBLE value)
{
	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;
}

static UINTMAX_T
myround(LDOUBLE value)
{
	UINTMAX_T intpart = cast(value);

	return ((value -= intpart) < 0.5) ? intpart : intpart + 1;
}

static LDOUBLE
mypow10(int exponent)
{
	LDOUBLE result = 1;

	while (exponent > 0) {
		result *= 10;
		exponent--;
	}
	while (exponent < 0) {
		result /= 10;
		exponent++;
	}
	return result;
}
#endif	/* !HAVE_VSNPRINTF */

#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 len;

	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 len;
}
#endif	/* !HAVE_SNPRINTF */

#else	/* Dummy declaration to avoid empty translation unit warnings. */
int main(void);
#endif	/* !HAVE_SNPRINTF || !HAVE_VSNPRINTF*/

#if TEST_SNPRINTF
int
main(void)
{
	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",
		"%+05.8f",
		"%-05.8f",
		"%05.8f",
		"%+5.8f",
		"%-5.8f",
		"% 5.8f",
		"%5.8f",
		"%5.0f",
		"%5.f",
		"%#5.0f",
		"%#5.f",
		"%+4.9f",
		"%3.2f",
		"%3.1f",
		"%-1.5f",
		"%1.5f",
		"%01.3f",
		"%1.f",
		"%.1f",
		"%#.0f",
		"%+.0f",
		"% .0f",
		"%.0f",
		"%#.f",
		"%+.f",
		"% .f",
		"%.f",
		"%4f",
		"%f",
		"%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
	};
	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
	};
	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
	};
	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;

/*
 * 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 (failed == 0) ? 0 : 1;
}
#endif	/* TEST_SNPRINTF */

/* vim: set joinspaces textwidth=80: */