/*
 * snprintf.c - a portable implementation of snprintf
 *
 * AUTHOR
 *   Mark Martinec <mark.martinec@ijs.si>, April 1999.
 *
 *   Copyright 1999, Mark Martinec. All rights reserved.
 *
 * TERMS AND CONDITIONS
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the "Frontier Artistic License" which comes
 *   with this Kit.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty
 *   of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *   See the Frontier Artistic License for more details.
 *
 *   You should have received a copy of the Frontier Artistic License
 *   with this Kit in the file named LICENSE.txt .
 *   If not, I'll be glad to provide one.
 *
 * FEATURES
 * - careful adherence to specs regarding flags, field width and precision;
 * - good performance for large string handling (large format, large
 *   argument or large paddings). Performance is similar to system's sprintf
 *   and in several cases significantly better (make sure you compile with
 *   optimizations turned on, tell the compiler the code is strict ANSI
 *   if necessary to give it more freedom for optimizations);
 * - return value semantics as per ISO C9X;
 * - written in standard ISO/ANSI C - requires an ANSI C compiler.
 *
 * SUPPORTED FORMATS AND DATA TYPES
 *
 * This snprintf only supports format specifiers:
 * s, c, d, o, u, x, X, p  (and synonyms: i, D, U, O - see below)
 * with flags: '-', '+', ' ', '0' and '#'.
 * An asterisk is supported for field width as well as precision.
 *
 * Data type modifiers 'h' (short int), 'l' (long int)
 * and 'll' (long long int) are supported.
 * NOTE:
 *   If macro SNPRINTF_LONGLONG_SUPPORT is not defined (default) the
 *   data type modifier 'll' is recognized but treated the same as 'l',
 *   which may cause argument value truncation! Defining
 *   SNPRINTF_LONGLONG_SUPPORT requires that your system's sprintf also
 *   handles data type modifier 'll'. long long int is a language
 *   extension which may not be portable.
 *
 * Conversion of numeric data (formats d, o, u, x, X, p) with data type
 * modifiers (none or h, l, ll) is left to the system routine sprintf,
 * but all handling of flags, field width and precision as well as c and
 * s formats is done very carefully by this portable routine. If a string
 * precision (truncation) is specified (e.g. %.8s) it is guaranteed the
 * string beyond the specified precision will not be referenced.
 *
 * Data type modifiers h, l and ll are ignored for c and s formats (data
 * types wint_t and wchar_t are not supported).
 *
 * The following common synonyms for conversion characters are supported:
 *   - i is a synonym for d
 *   - D is a synonym for ld, explicit data type modifiers are ignored
 *   - U is a synonym for lu, explicit data type modifiers are ignored
 *   - O is a synonym for lo, explicit data type modifiers are ignored
 *
 * The following is specifically not supported:
 *   - flag ' (thousands' grouping character) is recognized but ignored
 *   - numeric formats: f, e, E, g, G and synonym F
 *   - data type modifier 'L' (long double) and 'q' (quad - use 'll' instead)
 *   - wide character/string formats: C, lc, S, ls
 *   - writeback of converted string length: conversion character n
 *   - the n$ specification for direct reference to n-th argument
 *   - locales
 *
 * It is permitted for str_m to be zero, and it is permitted to specify NULL
 * pointer for resulting string argument if str_m is zero (as per ISO C9X).
 *
 * The return value is the number of characters which would be generated
 * for the given input, excluding the trailing null. If this value
 * is greater or equal to str_m, not all characters from the result
 * have been stored in str. If str_m is greater than zero it is
 * guaranteed the resulting string will be null-terminated.
 *
 * NOTE that this matches the ISO C9X and GNU C library 2.1,
 * but is different from some older implementations!
 *
 * Routines asprintf and vasprintf return a pointer (in the ptr argument)
 * to a buffer sufficiently large to hold the resulting string. This pointer
 * should be passed to free(3) to release the allocated storage when it is
 * no longer needed. If sufficient space cannot be allocated, these functions
 * will return -1 and set ptr to be a NULL pointer. These two routines are a
 * GNU C library extensions (glibc).
 *
 * Routines asnprintf and vasnprintf are similar to asprintf and vasprintf,
 * yet, like snprintf and vsnprintf counterparts, will write at most str_m-1
 * characters into the allocated output string, the last character in the
 * allocated buffer then gets the terminating null. If the formatted string
 * length (the return value) is greater than or equal to the str_m argument,
 * the resulting string was truncated and some of the formatted characters
 * were discarded. These routines present a handy way to limit the amount
 * of allocated memory to some sane value.
 *
 * AVAILABILITY
 *   http://www.ijs.si/software/snprintf/
 *
 * REVISION HISTORY
 * 1999-04	V0.9  Mark Martinec
 *		- initial version, some modifications after comparing printf
 *		  man pages for Digital Unix 4.0, Solaris 2.6 and HPUX 10,
 *		  and checking how Perl handles sprintf (differently!);
 * 1999-04-09	V1.0  Mark Martinec <mark.martinec@ijs.si>
 *		- added main test program, fixed remaining inconsistencies,
 *		  added optional (long long int) support;
 * 1999-04-12	V1.1  Mark Martinec <mark.martinec@ijs.si>
 *		- support the 'p' format (pointer to void);
 *		- if a string precision is specified
 *		  make sure the string beyond the specified precision
 *		  will not be referenced (e.g. by strlen);
 * 1999-04-13	V1.2  Mark Martinec <mark.martinec@ijs.si>
 *		- support synonyms %D=%ld, %U=%lu, %O=%lo;
 *		- speed up the case of long format string with few conversions;
 * 1999-06-30	V1.3  Mark Martinec <mark.martinec@ijs.si>
 *		- fixed runaway loop (eventually crashing when str_l wraps
 *		  beyond 2*31) while copying format string without
 *		  conversion specifiers to a buffer that is too short
 *		  (thanks to Edwin Young <edwiny@autonomy.com> for
 *		  spotting the problem);
 *		- added macros PORTABLE_SNPRINTF_VERSION_(MAJOR|MINOR)
 *		  to snprintf.h
 * 2000-02-14	V2.0 (never released) Mark Martinec <mark.martinec@ijs.si>
 *		- relaxed license terms: The Artistic License now applies.
 *		  You may still apply the GNU GENERAL PUBLIC LICENSE
 *		  as was distributed with previous versions, if you prefer;
 *		- changed REVISION HISTORY dates to use ISO 8601 date format;
 *		- added vsnprintf (patch also independently proposed by
 *		  Caolan McNamara 2000-05-04, and Keith M Willenson 2000-06-01)
 * 2000-06-27	V2.1  Mark Martinec <mark.martinec@ijs.si>
 *		- removed POSIX check for str_m<1; value 0 for str_m is
 *		  allowed by ISO C9X (and GNU C library 2.1) - (pointed out
 *		  on 2000-05-04 by Caolan McNamara, caolan@ csn dot ul dot ie).
 *		  Besides relaxed license this change in standards adherence
 *		  is the main reason to bump up the major version number;
 *		- added nonstandard routines asnprintf, vasnprintf, asprintf,
 *		  vasprintf that dynamically allocate storage for the
 *		  resulting string; these routines are not compiled by default,
 *		  see comments where NEED_V?ASN?PRINTF macros are defined;
 *		- autoconf contributed by Caolan McNamara
 */


/* Define HAVE_SNPRINTF if your system already has snprintf and vsnprintf.
 *
 * If HAVE_SNPRINTF is defined this module will not produce code for
 * snprintf and vsnprintf, unless PREFER_PORTABLE_SNPRINTF is defined as well,
 * causing this portable version of snprintf to be called fl_portable_snprintf
 * (and fl_portable_vsnprintf).
 */
/* #define HAVE_SNPRINTF */

/* Define PREFER_PORTABLE_SNPRINTF if your system does have snprintf and
 * vsnprintf but you would prefer to use the portable routine(s) instead.
 * In this case the portable routine is declared as fl_portable_snprintf
 * (and fl_portable_vsnprintf) and a macro 'snprintf' (and 'vsnprintf')
 * is defined to expand to 'portable_v?snprintf' - see file snprintf.h .
 * Defining this macro is only useful if HAVE_SNPRINTF is also defined,
 * but does does no harm if defined nevertheless.
 */
/* #define PREFER_PORTABLE_SNPRINTF */

/* Define SNPRINTF_LONGLONG_SUPPORT if you want to support
 * data type (long long int) and data type modifier 'll' (e.g. %lld).
 * If undefined, 'll' is recognized but treated as a single 'l'.
 *
 * If the system's sprintf does not handle 'll'
 * the SNPRINTF_LONGLONG_SUPPORT must not be defined!
 *
 * This is off by default since (long long int) is a language extension.
 */
/* #define SNPRINTF_LONGLONG_SUPPORT */

/* Define NEED_SNPRINTF_ONLY if you only need snprintf, and not vsnprintf.
 * If NEED_SNPRINTF_ONLY is defined, the snprintf will be defined directly,
 * otherwise both snprintf and vsnprintf routines will be defined
 * and snprintf will be a simple wrapper around vsnprintf, at the expense
 * of an extra procedure call.
 */
/* #define NEED_SNPRINTF_ONLY */

/* Define NEED_V?ASN?PRINTF macros if you need library extension
 * routines asprintf, vasprintf, asnprintf, vasnprintf respectively,
 * and your system library does not provide them. They are all small
 * wrapper routines around fl_portable_vsnprintf. Defining any of the four
 * NEED_V?ASN?PRINTF macros automatically turns off NEED_SNPRINTF_ONLY
 * and turns on PREFER_PORTABLE_SNPRINTF.
 *
 * Watch for name conflicts with the system library if these routines
 * are already present there.
 *
 * NOTE: vasprintf and vasnprintf routines need va_copy() from stdarg.h, as
 * specified by C9X, to be able to traverse the same list of arguments twice.
 * I don't know of any other standard and portable way of achieving the same.
 * With some versions of gcc you may use __va_copy(). You might even get away
 * with "ap2 = ap", in this case you must not call va_end(ap2) !
 */
/* #define NEED_ASPRINTF   */
/* #define NEED_ASNPRINTF  */
/* #define NEED_VASPRINTF  */
/* #define NEED_VASNPRINTF */


/* Define the following macros if desired:
 *   SOLARIS_COMPATIBLE, SOLARIS_BUG_COMPATIBLE,
 *   HPUX_COMPATIBLE, HPUX_BUG_COMPATIBLE,
 *   DIGITAL_UNIX_COMPATIBLE, DIGITAL_UNIX_BUG_COMPATIBLE,
 *   PERL_COMPATIBLE, PERL_BUG_COMPATIBLE,
 *
 * - For portable applications it is best not to rely on peculiarities
 *   of a given implementation so it may be best not to define any
 *   of the macros that select compatibility and to avoid features
 *   that vary among the systems.
 *
 * - Selecting compatibility with more than one operating system
 *   is not strictly forbidden but is not recommended.
 *
 * - 'x'_BUG_COMPATIBLE implies 'x'_COMPATIBLE .
 *
 * - 'x'_COMPATIBLE refers to (and enables) a behaviour that is
 *   documented in a sprintf man page on a given operating system
 *   and actually adhered to by the system's sprintf (but not on
 *   most other operating systems). It may also refer to and enable
 *   a behaviour that is declared 'undefined' or 'implementation specific'
 *   in the man page but a given implementation behaves predictably
 *   in a certain way.
 *
 * - 'x'_BUG_COMPATIBLE refers to (and enables) a behaviour of system's sprintf
 *   that contradicts the sprintf man page on the same operating system.
 *
 * - I do not claim that the 'x'_COMPATIBLE and 'x'_BUG_COMPATIBLE
 *   conditionals take into account all idiosyncrasies of a particular
 *   implementation, there may be other incompatibilities.
 */

/* added by Lgb, the LyX Project */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* ============================================= */
/* NO USER SERVICABLE PARTS FOLLOWING THIS POINT */
/* ============================================= */

#define PORTABLE_SNPRINTF_VERSION_MAJOR 2
#define PORTABLE_SNPRINTF_VERSION_MINOR 1

#if defined ( NEED_ASPRINTF ) || defined ( NEED_ASNPRINTF ) || defined ( NEED_VASPRINTF ) || defined ( NEED_VASNPRINTF )
# if defined ( NEED_SNPRINTF_ONLY )
# undef NEED_SNPRINTF_ONLY
# endif
# if ! defined ( PREFER_PORTABLE_SNPRINTF )
# define PREFER_PORTABLE_SNPRINTF
# endif
#endif

#if defined ( SOLARIS_BUG_COMPATIBLE ) && ! defined ( SOLARIS_COMPATIBLE )
#define SOLARIS_COMPATIBLE
#endif

#if defined ( HPUX_BUG_COMPATIBLE ) && ! defined ( HPUX_COMPATIBLE )
#define HPUX_COMPATIBLE
#endif

#if defined ( DIGITAL_UNIX_BUG_COMPATIBLE ) && ! defined( DIGITAL_UNIX_COMPATIBLE )
#define DIGITAL_UNIX_COMPATIBLE
#endif

#if defined ( PERL_BUG_COMPATIBLE ) && ! defined ( PERL_COMPATIBLE )
#define PERL_COMPATIBLE
#endif


#include <sys/types.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>

#ifdef isdigit
#undef isdigit
#endif
#define isdigit( c )  ( ( c ) >= '0' && ( c ) <= '9' )

/* prototypes */

#if defined ( NEED_ASPRINTF )
int asprintf( char **,
			  const char *,
			  ... );
#endif
#if defined ( NEED_VASPRINTF )
int vasprintf( char **,
			   const char *,
			   va_list );
#endif
#if defined ( NEED_ASNPRINTF )
int asnprintf( char **,
			   size_t,
			   const char *,
			   ... );
#endif
#if defined ( NEED_VASNPRINTF )
int vasnprintf( char **,
				size_t,
				const char *,
				va_list );
#endif

#ifndef va_copy
#define va_copy( ap2, ap )  ap2 = ap
#endif

#if 0
#if defined ( HAVE_SNPRINTF )
/* declare our portable snprintf  routine under name fl_portable_snprintf  */
/* declare our portable vsnprintf routine under name fl_portable_vsnprintf */
#else
/* declare our portable routines under names snprintf and vsnprintf */
#define fl_portable_snprintf snprintf
#if !defined ( NEED_SNPRINTF_ONLY )
#define fl_portable_vsnprintf vsnprintf
#endif
#endif
#endif /* 0 */

#if ! defined ( HAVE_SNPRINTF ) || defined ( PREFER_PORTABLE_SNPRINTF )
int fl_portable_snprintf( char *,
						  size_t,
						  const char *,
						  ... );
#if ! defined ( NEED_SNPRINTF_ONLY )
int fl_portable_vsnprintf( char *,
						   size_t,
						   const char *,
						   va_list );
#endif
#endif


/***************************************
 ***************************************/

#if defined ( NEED_ASPRINTF )
int asprintf( char **ptr,
			  const char *fmt,
			  ... )
{
	va_list ap;
	size_t str_m;
	int str_l;

	*ptr = NULL;
	va_start( ap, fmt );                       /* measure the required size */
	str_l = fl_portable_vsnprintf( NULL, ( size_t ) 0, fmt, ap );
	va_end( ap );

	assert( str_l >= 0 );    /* possible integer overflow if str_m > INT_MAX */

	*ptr = fl_malloc( str_m = str_l + 1 );
	if ( *ptr == NULL )
	{
		errno = ENOMEM;
		str_l = -1;
	}
	else
	{
		int str_l2;

		va_start( ap, fmt );
		str_l2 = fl_portable_vsnprintf( *ptr, str_m, fmt, ap );
		va_end( ap );

		assert( str_l2 == str_l );
	}

	return str_l;
}
#endif



/***************************************
 ***************************************/

#if defined ( NEED_VASPRINTF )
int
vasprintf( char       ** ptr,
		   const char  * fmt,
		   va_list       ap )
{
	size_t str_m;
	int str_l;

	*ptr = NULL;
	{
		va_list ap2;
		va_copy( ap2, ap );  /* don't consume the original ap, we'll need
								it again */

		/* measure the required size: */

		str_l = fl_portable_vsnprintf( NULL, ( size_t ) 0, fmt, ap2 );
		va_end( ap2 );
	}

	assert( str_l >= 0 );     /* possible integer overflow if str_m > INT_MAX */

	*ptr = fl_malloc( str_m = str_l + 1 );

	if ( *ptr == NULL )
	{
		errno = ENOMEM;
		str_l = -1;
	}
	else
	{
		int str_l2 = fl_portable_vsnprintf( *ptr, str_m, fmt, ap );

		assert( str_l2 == str_l );
	}

	return str_l;
}
#endif


/***************************************
 ***************************************/

#if defined ( NEED_ASNPRINTF )
int
asnprintf( char       ** ptr,
		   size_t        str_m,
		   const char  * fmt,
		   ... )
{
	va_list ap;
	int str_l;

	*ptr = NULL;
	va_start( ap, fmt );                        /* measure the required size */
	str_l = fl_portable_vsnprintf( NULL, ( size_t ) 0, fmt, ap );
	va_end( ap );

	assert( str_l >= 0 );     /* possible integer overflow if str_m > INT_MAX */

	if ( ( size_t ) str_l + 1 < str_m )
		str_m = ( size_t ) str_l + 1;      /* truncate */

	/* if str_m is 0, no buffer is allocated, just set *ptr to NULL */

	if ( str_m == 0 )
	{
		/* not interested in resulting string, just return size */
	}
	else
	{
		*ptr = fl_malloc( str_m );
		if ( *ptr == NULL )
		{
			errno = ENOMEM;
			str_l = -1;
		}
		else
		{
			int str_l2;

			va_start( ap, fmt );
			str_l2 = fl_portable_vsnprintf( *ptr, str_m, fmt, ap );
			va_end( ap );
			assert( str_l2 == str_l );
		}
	}
	return str_l;
}
#endif


/***************************************
 ***************************************/

#if defined ( NEED_VASNPRINTF )
int
vasnprintf ( char **      ptr,
			 size_t       str_m,
			 const char * fmt,
			 va_list      ap )
{
	int str_l;

	*ptr = NULL;
	{
		va_list ap2;
		va_copy( ap2, ap );  /* don't consume the original ap, we'll need
								it again */

		/* measure the required size: */

		str_l = fl_portable_vsnprintf( NULL, ( size_t ) 0, fmt, ap2 );
		va_end( ap2 );
	}

	assert( str_l >= 0 );     /* possible integer overflow if str_m > INT_MAX */
	if ( ( size_t ) str_l + 1 < str_m )
		str_m = ( size_t )str_l + 1;      /* truncate */

	/* if str_m is 0, no buffer is allocated, just set *ptr to NULL */

	if ( str_m == 0 )
	{
		/* not interested in resulting string, just return size */
	}
	else
	{
		*ptr = fl_malloc( str_m );
		if ( *ptr == NULL )
		{
			errno = ENOMEM;
			str_l = -1;
		}
		else
		{
			int str_l2 = fl_portable_vsnprintf( *ptr, str_m, fmt, ap );

			assert( str_l2 == str_l );
		}
	}

	return str_l;
}
#endif



/***************************************
 ***************************************/

/*
 * If the system does have snprintf and the portable routine is not
 * specifically required, this module produces no code for snprintf/vsnprintf.
 */

#if ! defined ( HAVE_SNPRINTF ) || defined ( PREFER_PORTABLE_SNPRINTF )

#if ! defined ( NEED_SNPRINTF_ONLY )
int
fl_portable_snprintf( char       * str,
					  size_t       str_m,
					  const char * fmt,
					  ... )
{
	va_list ap;
	int str_l;

	va_start( ap, fmt );
	str_l = fl_portable_vsnprintf( str, str_m, fmt, ap );
	va_end( ap );
	return str_l;
}
#endif


/***************************************
 ***************************************/

#if defined ( NEED_SNPRINTF_ONLY )
int
fl_portable_snprintf( char       * str,
					  size_t       str_m,
					  const char * fmt,
					  ... )
{
#else
int
fl_portable_vsnprintf( char       * str,
					   size_t       str_m,
					   const char * fmt,
					   va_list      ap )
{
#endif

#if defined ( NEED_SNPRINTF_ONLY )
	 va_list ap;
#endif
	 size_t str_l = 0;
	 const char *p = fmt;

	/* In contrast with POSIX, the ISO C9X now says
	 * that str can be NULL and str_m can be 0. This is more useful. */

	/*if (str_m < 1) return -1;*/

#if defined ( NEED_SNPRINTF_ONLY )
	 va_start( ap, fmt );
#endif
	 if ( ! p )
		 p = "";
	 while ( *p )
	 {
		 if ( *p != '%' )
		 {
			 /* if (str_l < str_m) str[str_l++] = *p++; -- this would be
				sufficient but the following code achieves better performance
				for cases where format string is long and contains few
				conversions */

			 const char *q = strchr( p + 1, '%' );

			 int n = ! q ? strlen( p ) : ( q - p );
			 int avail = ( int ) ( str_m - str_l );

			 if ( avail > 0 )
			 {
				 int k;
				 char *r;
				 const char* p1;

				 for ( p1 = p, r = str + str_l, k = ( n > avail ? avail : n );
					   k > 0 ; k-- )
					 *r++ = *p1++;
			 }
			 p += n;
			 str_l += n;
		 }
		 else
		 {
			 const char *starting_p;
			 int min_field_width = 0,
				 precision = 0;
			 int zero_padding = 0,
				 precision_specified = 0,
				 justify_left = 0;
			 int alternative_form = 0,
				 force_sign = 0;
			 int space_for_positive = 1; /* If both the ' ' and '+' flags
											appear, the ' ' flag should be
											ignored. */
			 char data_type_modifier = '\0';/* allowed valued: \0, h, l, L, p */
			 char tmp[ 32 ];                /* temporary buffer for simple
											   numeric->string conversion */

			 const char *str_arg = NULL;    /* string address in case of
											   string arguments  */
			 int str_arg_l;                 /* natural field width of arg
											   without padding and sign */

			 long int long_arg;  /* long int argument value - always defined
									in case of numeric arguments, regardless
									of data type modifiers. In case of data
									type modifier 'll' the value is stored in
									long_long_arg and only the sign of long_arg
									is guaranteed to be correct */
			 void *ptr_arg;      /* pointer argument value - only defined for
									p format   */
			 int int_arg;        /* int argument value - only defined if no
									h or l modif.*/
#ifdef SNPRINTF_LONGLONG_SUPPORT
			 long long int long_long_arg = 0; /* long long argument value - only
												 defined if ll modifier is
												 present */
#endif
			 int number_of_zeros_to_pad = 0;
			 int zero_padding_insertion_ind = 0;
			 char fmt_spec = '\0';            /* current format specifier
												 character */

			 starting_p = p;
			 p++;               /* skip '%' */

			 /* parse flags */

			 while (    *p == '0' || *p == '-' || *p == '+'
					 || *p == ' ' || *p == '#' || *p == '\'') {
				 switch ( *p )
				 {
					 case '0':
						 zero_padding = 1;
						 break;

					 case '-':
						 justify_left = 1;
						 break;

					 case '+':
						 force_sign = 1;
						 space_for_positive = 0;
						 break;

					 case ' ': 
						 force_sign = 1;
						 /* If both the ' ' and '+' flags appear, the ' ' flag
							should be ignored */
#ifdef PERL_COMPATIBLE
						 /* ... but in Perl the last of ' ' and '+' applies */

						 space_for_positive = 1;
#endif
						 break;

					 case '#':
						 alternative_form = 1;
						 break;

					 case '\'':
						 break;
				 }

				 p++;
			 }

			 /* If the '0' and '-' flags both appear, the '0' flag should
				be ignored. */

			 /* parse field width */

			 if ( *p == '*' )
			 {
				 p++;
				 min_field_width = va_arg( ap, int );
				 if ( min_field_width < 0 )
				 {
					 min_field_width = -min_field_width;
					 justify_left = 1;
				 }
			 }
			 else if ( isdigit( ( int ) *p ) )
			 {
				 min_field_width = *p++ - '0';
				 while (isdigit( ( int ) *p ) )
					 min_field_width = 10 * min_field_width + ( *p++ - '0' );
			 }

			 /* parse precision */

			 if ( *p == '.' )
			 {
				 p++;
				 precision_specified = 1;
				 if ( *p == '*' )
				 {
					 p++;
					 precision = va_arg( ap, int );
					 if ( precision < 0 )
					 {
						 precision_specified = 0;
						 precision = 0;

						 /* NOTE: Solaris 2.6 man page claims that in this
						  * case the precision should be set to 0. Digital
						  * Unix 4.0 and HPUX 10 man page claim that this
						  * case should be treated as unspecified precision,
						  * which is what we do here.
						  */
					 }
				 } else if ( isdigit( ( int ) *p ) )
				 {
					 precision = *p++ - '0';
					 while ( isdigit( ( int ) *p ) )
						 precision = 10 * precision + ( *p++ - '0' );
				 }
			 }

			 /* parse 'h', 'l' and 'll' data type modifiers */

			 if ( *p == 'h' || *p == 'l' )
			 {
				 data_type_modifier = *p;
				 p++;
				 if ( data_type_modifier == 'l' && *p == 'l' )
				 {
					 /* double l = long long */
#ifdef SNPRINTF_LONGLONG_SUPPORT
					 data_type_modifier = '2';     /* double l encoded as '2' */
#else
					 data_type_modifier = 'l';     /* treat it as single 'l' */
#endif
					 p++;
				 }
			 }
			 fmt_spec = *p;

			 /* common synonyms: */

			 switch ( fmt_spec )
			 {
				 case 'i':
					 fmt_spec = 'd';
					 break;

				 case 'D':
					 fmt_spec = 'd';
					 data_type_modifier = 'l';
					 break;

				 case 'U':
					 fmt_spec = 'u';
					 data_type_modifier = 'l';
					 break;

				 case 'O':
					 fmt_spec = 'o';
					 data_type_modifier = 'l';
					 break;

				 default:
					 break;
			 }

			 /* get parameter value, do initial processing */

			 switch ( fmt_spec )
			 {
				 case '%':
					 /* % behaves similar to 's' regarding flags and field
						widths */

				 case 'c':
					 /* c behaves similar to 's' regarding flags and field
						widths */

				 case 's':
					 data_type_modifier = '\0';  

					 /* wint_t and wchar_t not supported */
					 /* the result of zero padding flag with non-numeric
						format is undefined */
					 /* Solaris and HPUX 10 does zero padding in this case,
						Digital Unix not */
#ifdef DIGITAL_UNIX_COMPATIBLE
					 zero_padding = 0;        /* turn zero padding off for
												 string formats */
#endif
					 str_arg_l = 1;

					 switch ( fmt_spec )
					 {
						 case '%':
							 str_arg = p;
							 break;

						 case 'c':
						 {
							 int j = va_arg( ap, int );

							 str_arg = ( const char * ) &j;
						 }
						 break;

						 case 's':
							 str_arg = va_arg( ap, const char * );
							 if ( ! str_arg )
								 str_arg_l = 0;

							 /* make sure not to address string beyond the
								specified precision !!! */
							 else if ( ! precision_specified )
								 str_arg_l = strlen( str_arg );

							 /* truncate string if necessary as requested by
								precision */

							 else if ( precision <= 0 )
								 str_arg_l = 0;
							 else
							 {
								 const char *q = memchr( str_arg, '\0',
														 ( size_t ) precision );
								 str_arg_l = ! q ? precision : ( q - str_arg );
							 }
							 break;

						 default:
							 break;
					 }
					 break;

				 case 'd':
				 case 'o':
				 case 'u':
				 case 'x':
				 case 'X':
				 case 'p':
					 long_arg = 0;
					 int_arg = 0;
					 ptr_arg = NULL;

					 if ( fmt_spec == 'p' )
					 {
						 /* * HPUX 10:
							  An l, h, ll or L before any other conversion
							  character (other than d, i, o, u, x, or X) is
							  ignored.
							* Digital Unix:
							  not specified, but seems to behave as HPUX does.
							* Solaris:
							  If an h, l, or L appears before any other
							  conversion specifier (other than d, i, o, u, x,
							  or X), the behavior is undefined. (Actually %hp
							  converts only 16-bits of address and %llp treats
							  address as 64-bit data which is incompatible
							  with (void *) argument on a 32-bit system). */

#ifdef SOLARIS_COMPATIBLE
#  ifdef SOLARIS_BUG_COMPATIBLE
						 /* keep data type modifiers even if it
							represents 'll' */
#  else
						 if ( data_type_modifier == '2' )
							 data_type_modifier = '\0';
#  endif
#else
						 data_type_modifier = '\0';
#endif
						 ptr_arg = va_arg( ap, void * );
						 long_arg = ! ptr_arg ? 0 : 1;
					 } else {
						 switch ( data_type_modifier )
						 {
							 case '\0':
							 case 'h':
								 /* It is non-portable to specify a second
									argument of char or short to va_arg,
									because arguments seen by the called
									function are not char or short. C converts
									char and short arguments to int before
									passing them to a function. */

								 int_arg = va_arg( ap, int );
								 long_arg = int_arg;
								 break;

							 case 'l':
								 long_arg = va_arg( ap, long int );
								 break;

#ifdef SNPRINTF_LONGLONG_SUPPORT
							 case '2':
								 long_long_arg = va_arg( ap, long long int );

								 /* only the sign of long_arg is guaranteed */

								 if ( long_long_arg > 0 )
									 long_arg = +1;
								 else if ( long_long_arg < 0 )
									 long_arg = -1;
								 else
									 long_arg = 0;
								 break;
#endif
						 }
					 }

					 str_arg = tmp;
					 str_arg_l = 0;

					 /* NOTE:
					  *   For d, i, o, u, x, and X conversions, if precision
					  *   is specified, the '0' flag should be ignored. This
					  *   is so with Solaris 2.6, Digital UNIX 4.0 and HPUX 10;
					  *   but not with Perl. */

#ifndef PERL_COMPATIBLE
					 if ( precision_specified )
						 zero_padding = 0;
#endif

					 if ( fmt_spec == 'd' )
					 {
						 if ( force_sign && long_arg >= 0 )
							 tmp[ str_arg_l++ ] =
								                space_for_positive ? ' ' : '+';

						 /* leave negative numbers for sprintf to handle,
							to avoid handling tricky cases like
							(short int)(-32768) */

					 }
					 else if ( alternative_form )
					 {
						 if (    long_arg != 0
							  && ( fmt_spec == 'x' || fmt_spec == 'X' ) )
						 {
							 tmp[ str_arg_l++ ] = '0';
							 tmp[ str_arg_l++ ] = fmt_spec; }
#ifdef HPUX_COMPATIBLE
						 else if ( fmt_spec == 'p'

						/* HPUX 10: for an alternative form of p conversion,
						 *          a nonzero result is prefixed by 0x. */
#ifndef HPUX_BUG_COMPATIBLE
					    /* Actually it uses 0x prefix even for a zero value. */
								   && long_arg != 0
#endif
							 )
						 {
							 tmp[ str_arg_l++ ] = '0';
							 tmp[ str_arg_l++ ] = 'x';
						 }
#endif
					 }

					 zero_padding_insertion_ind = str_arg_l;

					 if ( ! precision_specified )
						 precision = 1;   /* default precision is 1 */

					 if ( precision == 0 && long_arg == 0
#ifdef HPUX_BUG_COMPATIBLE
						  && fmt_spec != 'p'

						  /* HPUX 10 man page claims: With conversion character
						   * p the result of converting a zero value with a
						   * precision of zero is a null string.
						   * Actually it returns all zeroes. */
#endif
						 )
					 {
						 /* converted to null string */
					 }
					 else
					 {
						 char f[ 5 ];
						 int f_l = 0;

						 f[ f_l++ ] = '%';

						 if ( ! data_type_modifier )
						 {
						 }
						 else if ( data_type_modifier == '2' )
						 {
							 f[ f_l++ ] = 'l';
							 f[ f_l++ ] = 'l';
						 }
						 else
							 f[ f_l++ ] = data_type_modifier;

						 f[ f_l++ ] = fmt_spec; f[ f_l++ ] = '\0';

						 if ( fmt_spec == 'p' )
							 str_arg_l += sprintf( tmp + str_arg_l,
												   f, ptr_arg );
						 else
						 {
							 switch ( data_type_modifier )
							 {
								 case '\0':
								 case 'h':
									 str_arg_l += sprintf( tmp + str_arg_l,
														   f, int_arg );
									 break;

								 case 'l':
									 str_arg_l += sprintf( tmp + str_arg_l, f,
														   long_arg );
									 break;

#ifdef SNPRINTF_LONGLONG_SUPPORT
								 case '2':
									 str_arg_l += sprintf( tmp + str_arg_l,
														   f, long_long_arg );
									 break;
#endif
							 }
						 }

						 if (    zero_padding_insertion_ind < str_arg_l
							  && tmp[ zero_padding_insertion_ind ] == '-' )
							 zero_padding_insertion_ind++;
					 }

					 {
						 int num_of_digits =
							           str_arg_l - zero_padding_insertion_ind;

						 if ( alternative_form && fmt_spec == 'o'
#ifdef HPUX_COMPATIBLE                                  /* ("%#.o",0) -> ""  */
							  && ( str_arg_l > 0 )
#endif
#ifdef DIGITAL_UNIX_BUG_COMPATIBLE                      /* ("%#o",0) -> "00" */
#else
							  && ! ( zero_padding_insertion_ind < str_arg_l
									 && tmp[zero_padding_insertion_ind] == '0' )
#endif
							 )
						 {
							 /* assure leading zero for alternative-form
								octal numbers */

							 if (    ! precision_specified
								  || precision < num_of_digits + 1 )
							 {
								 precision = num_of_digits + 1;
								 precision_specified = 1;
							 }
						 }

						 /* zero padding to specified precision? */

						 if ( num_of_digits < precision )
							 number_of_zeros_to_pad = precision - num_of_digits;
					 }

					 /* zero padding to specified minimal field width? */

					 if ( ! justify_left && zero_padding )
					 {
						 int n = min_field_width
							     - ( str_arg_l + number_of_zeros_to_pad );

						 if ( n > 0 )
							 number_of_zeros_to_pad += n;
					 }
					 break;

				 default:  /* unrecognized format, keep format string
							  unchanged */

					 zero_padding = 0;   /* turn zero padding off for
											non-numeric formats */

#ifndef DIGITAL_UNIX_COMPATIBLE
					 justify_left = 1;
					 min_field_width = 0;                /* reset flags */
#endif
#ifdef PERL_COMPATIBLE
					 /* keep the entire format string unchanged */

					 str_arg = starting_p;
					 str_arg_l = p - starting_p;
#else

					 /* discard the unrecognized format, just keep the
						unrecognized fmt char */

					 str_arg = p;
					 str_arg_l = 0;
#endif

					 if ( *p )
						 str_arg_l++;  /* include invalid fmt specifier if
										  not at EOS */
					 break;
			 }

			 if ( *p )
				 p++;       /* step over the just processed format specifier */

			 /* insert padding to the left as requested by min_field_width */

			 if ( ! justify_left )
			 {                /* left padding with blank or zero */
				 int n =   min_field_width
					     - ( str_arg_l + number_of_zeros_to_pad );

				 if ( n > 0 )
				 {
					 int avail = ( int ) ( str_m - str_l );

					 if ( avail > 0 )
					 {      /* memset(str+str_l, zp, (n>avail?avail:n)); */
						 const char zp = zero_padding ? '0' : ' ';
						 int k;
						 char *r;

						 for ( r = str + str_l, k= ( n > avail ? avail : n );
							   k > 0; k-- )
							 *r++ = zp;
					 }
					 str_l += n;
				 }
			 }

			 /* zero padding as requested by the precision for numeric
				formats requred?*/

			 if ( number_of_zeros_to_pad <= 0 )
			 {
				 /* will not copy first part of numeric here,   *
				  * force it to be copied later in its entirety */

				 zero_padding_insertion_ind = 0;
			 }
			 else
			 {
				 /* insert first part of numerics (sign or '0x') before
					zero padding */

				 int n = zero_padding_insertion_ind;

				 if ( n > 0 )
				 {
					 int avail = ( int ) ( str_m - str_l );

					 if ( avail > 0 )
						 memcpy( str + str_l, str_arg,
								 ( size_t ) ( n > avail ? avail : n ) );
					 str_l += n;
				 }

				 /* insert zero padding as requested by the precision */

				 n = number_of_zeros_to_pad;
				 if ( n > 0 )
				 {
					 int avail = ( int ) ( str_m - str_l );

					 if ( avail > 0 )
					 {     /* memset(str+str_l, '0', (n>avail?avail:n)); */
						 int k;
						 char *r;

						 for ( r = str + str_l, k = ( n > avail ? avail : n );
							   k > 0; k-- )
							 *r++ = '0';
					 }
					 str_l += n;
				 }
			 }

			 /* insert formatted string (or unmodified format for unknown
				formats) */

			 {
				 int n = str_arg_l - zero_padding_insertion_ind;

				 if ( n > 0 )
				 {
					 int avail = ( int ) ( str_m - str_l );

					 if ( avail > 0 )
						 memcpy( str + str_l,
								 str_arg + zero_padding_insertion_ind,
								 ( size_t ) ( n > avail ? avail : n ) );
					 str_l += n;
				 }
			 }

			 /* insert right padding */

			 if ( justify_left )
			 {          /* right blank padding to the field width */
				 int n = min_field_width
					     - ( str_arg_l + number_of_zeros_to_pad );

				 if ( n > 0 )
				 {
					 int avail = ( int ) ( str_m - str_l );

					 if ( avail > 0 )
					 {     /* memset(str+str_l, ' ', (n>avail?avail:n)); */
						 int k;
						 char *r;

						 for ( r = str + str_l, k= n > avail ? avail : n;
							   k > 0;  k-- )
							 *r++ = ' ';
					 }
					 str_l += n;
				 }
			 }
		 }
	 }
#if defined(NEED_SNPRINTF_ONLY)
	 va_end( ap );
#endif

	 if ( str_m > 0 )
	 {               /* make sure the string is null-terminated
						even at the expense of overwriting the last character */
		 str[ str_l <= str_m - 1 ? str_l : str_m-1 ] = '\0';
	 }

	 return str_l;    /* return the number of characters formatted
						 (excluding trailing null character),
						 that is, the number of characters that would have been
						 written to the buffer if it were large enough */
}
#endif
