/*
 * Copyright (c) 2000-2005 Sendmail, Inc. and its suppliers.
 *      All rights reserved.
 * Copyright (c) 1990
 *	The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Chris Torek.
 *
 * By using this file, you agree to the terms and conditions set
 * forth in the LICENSE file which can be found at the top level of
 * the sendmail distribution.
 */

#include "sm/generic.h"
SM_RCSID("@(#)$Id: vfprintf.c,v 1.58 2007/11/18 05:37:01 ca Exp $")
#include "sm/varargs.h"
#include "sm/assert.h"
#include "sm/io.h"
#include "sm/memops.h"
#include "sm/types.h"
#include "sm/ctype.h"
#include "sm/string.h"
#include "sm/base64.h"
#include "sm/str.h"
#include "sm/str-int.h"
#if !SM_NO_CSTR
#include "sm/cstr.h"
#endif
#include "sm/error.h"
#include "sm/heap.h"
#include "io-int.h"
#include "fvwrite.h"
#if MTA_USE_FP_PRINT
# include <stdio.h>	/* for sprintf() */
#endif

/*
**  Format specifiers
**
**  +   A       IPv4 address
**      a       -
**      B       -
**      b       - [binary?]
**  +   C       sm_cstr
**      c       char
**      D       long int (decimal)
**      d       int (decimal)
**      E       double float (NOT IMPL)
**      e       double float (NOT IMPL)
**      F       -
**      f       float (NOT IMPL)
**      G       double float (NOT IMPL)
**      g       double float (NOT IMPL)
**      H       -
**      h       -
**      I       -
**      i       int (decimal)
**      J       -
**      j       -
**      K       -
**      k       -
**      L       -
**      l       -
**      M       error description (do not use width/prec/...)
**      m       error text
**  +   N       sm_str (does not print trailing '\0')
**      n       number of characters written so far stored in int *
**      O       -
**      o       -
**      P       -
**      p       pointer
**      Q       -
**      q       -
**      R       -
**  +   r       if <0: print in hex, otherwise decimal
**              this is very useful for sm_ret_T
**  +   S       sm_str
**      s       char *
**  +   T       sm_str without trailing \r, \n, or \0
**      t       -
**      U       unsigned long int
**      u       unsigned int
**      V       -
**      v       -
**      W       -
**      w       -
**      X       hex (upper case)
**      x       hex (lower case)
**      y       -
**  +   Y       base 64
**      y       -
**      Z       base 32
**      z       (size_t?)
**
**  Note: specifiers marked as '+' are local additions which cause gcc
**      to create bogus error messages because it can't be told about them...
**
**  C99 modifiers:
**      t       ptrdiff_t
**      z       size_t
**  Local additions:
**      #       for string types: "sanitize" string (replace unprintable
**              characters by %HEX).
**      @       for string types: same as above (#) and replace ',' with %2C
**              (useful for logging: ',' is then a unique delimiter).
**              If this is changed: grep '%@' in all files.
*/

/*
**  Overall:
**  Actual printing innards.
**  This code is large and complicated...
*/

static void	sm_find_arguments(const char *, va_list, va_list **);
static void	sm_grow_type_table(int, uchar **, int *);
static int	sm_print(sm_file_T *, struct sm_uio *);

/*
**  SM_PRINT -- print/flush to the file
**
**  Flush out all the vectors defined by the given uio,
**  then reset it so that it can be reused.
**
**	Parameters:
**		fp -- file pointer
**		uio -- vector list of memory locations of data for printing
**
**	Results:
**		Success: 0 (zero)
**		Failure:
*/

static int
sm_print(sm_file_T *fp, struct sm_uio *uio)
{
	int err;

	if (uio->uio_resid == 0)
	{
		uio->uio_iovcnt = 0;
		return 0;
	}
	err = sm_fvwrite(fp, uio);
	uio->uio_resid = 0;
	uio->uio_iovcnt = 0;
	return err;
}

/*
**  SM_BPRINTF -- allow formating to an unbuffered file.
**
**  Helper function for `fprintf to unbuffered unix file': creates a
**  temporary buffer (via a "fake" file pointer).
**  We only work on write-only files; this avoids
**  worries about ungetc buffers and so forth.
**
**	Parameters:
**		fp -- the file to send the o/p to
**		fmt -- format instructions for the o/p
**		ap -- vectors of data units used for formating
**
**	Results:
**		Failure: SM_IO_EOF and errno set
**		Success: number of data units used in the formating
**
**	Side effects:
**		formatted o/p can be SM_IO_BUFSIZ length maximum
*/

static int
sm_bprintf(sm_file_T *fp, const char *fmt, va_list ap)
{
	int ret;
	sm_file_T fake;
	uchar buf[SM_IO_BUFSIZ];

	/* copy the important variables */
	fake.sm_magic = SM_FILE_MAGIC;
	fake.f_timeout = SM_TIME_FOREVER;
	f_flags(fake) = f_flags(*fp) & ~SMNBF;
	f_fd(fake) = f_fd(*fp);
	fake.f_cookie = fp->f_cookie;
	f_read(fake) = NULL;
	f_write(fake) = f_write(*fp);
	f_close(fake) = NULL;
	f_open(fake) = NULL;
	f_seek(fake) = NULL;
	f_setinfo(fake) = f_getinfo(fake) = NULL;

	/* set up the buffer */
	f_bfbase(fake) = f_p(fake) = buf;
	f_bfsize(fake) = f_w(fake) = sizeof(buf);

	/* do the work, then copy any error status */
	ret = sm_io_vfprintf(&fake, fmt, ap);
	if (ret >= 0 && sm_io_flush(&fake))
		ret = SM_IO_EOF;	/* errno set by sm_io_flush */
	if (f_flags(fake) & SMERR)
	{
		f_flags(*fp) |= SMERR;
#if SM_IO_ERR_VAL
		f_error(*fp) = f_error(fake);
#endif
	}
	return ret;
}


#define BUF		40

#define STATIC_ARG_TBL_SIZE 8	/* Size of static argument table. */


/* Macros for converting digits to letters and vice versa */
#define to_digit(c)	((c) - '0')
#define is_digit(c)	((uint) to_digit(c) <= 9)
#define to_char(n)	((char) (n) + '0')

/* Flags used during conversion. */
#define ALT		0x0001		/* alternate form */
#define HEXPREFIX	0x0002		/* add 0x or 0X prefix */
#define LADJUST		0x0004		/* left adjustment */
#define LONGINT		0x0010		/* long integer */
#define QUADINT		0x0020		/* quad integer */
#define SHORTINT	0x0040		/* short integer */
#define ZEROPAD		0x0080		/* zero (as opposed to blank) pad */
#define SANITIZE	0x0100		/* replace non-printable chars */
#define PTRINT		0x0200		/* (unsigned) ptrdiff_t */
#define SIZEINT		0x0400		/* (signed) size_t */

/*
**  SM_IO_VPRINTF -- performs actual formating for o/p
**
**	Parameters:
**		fp -- file pointer for o/p
**		timeout -- time to complete the print
**		fmt0 -- formating directives
**		ap -- vectors with data units for formating
**
**	Results:
**		Success: number of data units used for formatting
**		Failure: SM_IO_EOF and sets errno
*/

int
sm_io_vfprintf(sm_file_T *fp, const char *fmt0, va_list ap)
{
	const char *fmt;       /* format string */
	int ch;                /* character from fmt */
	int n, m, n2;          /* handy integers (short term usage) */
	const char *cp;        /* handy const char pointer (short term usage) */
	char *wcp;             /* handy char pointer (short term usage) */
	struct sm_iov *iovp;   /* for PRINT macro */
	int flags;             /* flags as above */
	int ret;               /* return value accumulator */
	int width;             /* width from format (%8d), or 0 */
	int prec;              /* precision from format (%.3d), or -1 */
	int delim;             /* delimiter to be encoded */
	char sign;             /* sign prefix (' ', '+', '-', or \0) */
	wchar_t wc;
	ulonglong_T _uquad;    /* integer arguments %[diouxX] */
	enum { OCT, DEC, HEX, BASE32, BASE64 } base;/* base for [diouxX] conversion */
	int dprec;             /* a copy of prec if [diouxX], 0 otherwise */
	int realsz;            /* field size expanded by dprec */
	int size;              /* size of converted field or string */
	int osize;             /* original size before sanitizing */
	char *xdigs="0123456789abcdef"; /* digits for [xX] conversion */
#define SM_B32DIGS "0123456789ABCDEFGHIJKLMNOPQRSTUV"
	const char *b64digs = SM_B64DIGS;
	const char *b32digs = SM_B32DIGS;
#define NIOV 8
	struct sm_uio uio;     /* output information: summary */
	struct sm_iov iov[NIOV];/* ... and individual io vectors */
	char buf[BUF];         /* space for %c, %[diouxX], %[eEfgG] */
	char ox[2];            /* space for 0x hex-prefix */
	va_list *argtable;     /* args, built due to positional arg */
	va_list statargtable[STATIC_ARG_TBL_SIZE];
	int nextarg;           /* 1-based argument index */
	sm_str_P str;
#if !SM_NO_CSTR
	sm_cstr_P cstr;
#endif
	va_list orgap;         /* original argument pointer */

#define OKTOPRINT(ch, delim)    (ISPRINT(ch) && (ch) != '%' && (ch) != (delim))
#define SANITIZE_SIZE(delim) do {           \
		if (flags & SANITIZE) {             \
			osize = size;                   \
			for (m = 0; m < osize; m++) {   \
				n2 = cp[m];                 \
				if (!OKTOPRINT(n2, delim))  \
					size += 2;              \
			}                               \
			if (prec >= 0 && size > prec)   \
				size = prec;                \
		}                                   \
	} while (0)

	/*
	**  Choose PADSIZE to trade efficiency vs. size.  If larger printf
	**  fields occur frequently, increase PADSIZE and make the initialisers
	**  below longer.
	*/
#define PADSIZE	16		/* pad chunk size */
	static char blanks[PADSIZE] =
	 {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
	static char zeroes[PADSIZE] =
	 {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};

	/*
	**  BEWARE, these `goto error' on error, and PAD uses `n'.
	*/
#define PRINT(ptr, len) do { \
	iovp->iov_base = (void *)(ptr); \
	iovp->iov_len = (len); \
	uio.uio_resid += (len); \
	iovp++; \
	if (++uio.uio_iovcnt >= NIOV) \
	{ \
		if (sm_print(fp, &uio)) \
			goto error; \
		iovp = iov; \
	} \
} while (0)
#define PAD(howmany, with) do \
{ \
	if ((n = (howmany)) > 0) \
	{ \
		while (n > PADSIZE) { \
			PRINT(with, PADSIZE); \
			n -= PADSIZE; \
		} \
		PRINT(with, n); \
	} \
} while (0)
#define FLUSH() do \
{ \
	if (uio.uio_resid && sm_print(fp, &uio)) \
		goto error; \
	uio.uio_iovcnt = 0; \
	iovp = iov; \
} while (0)

	/*
	**  To extend shorts properly, we need both signed and unsigned
	**  argument extraction methods.
	*/
#define SARG() \
	(flags&QUADINT ? va_arg(ap, longlong_T) : \
	    flags&LONGINT ? GETARG(long) : \
	    flags&PTRINT ? GETARG(ptrdiff_t) : \
	    flags&SIZEINT ? GETARG(ssize_t) : \
	    flags&SHORTINT ? (long) (short) GETARG(int) : \
	    (long) GETARG(int))

/* ptrdiff_t should be cast to an unsigned type here, but which? ulong? */
#define UARG() \
	(flags&QUADINT ? va_arg(ap, ulonglong_T) : \
	    flags&LONGINT ? GETARG(ulong) : \
	    flags&PTRINT ? GETARG(ptrdiff_t) : /* XXX */ \
	    flags&SIZEINT ? GETARG(size_t) : \
	    flags&SHORTINT ? (ulong) (ushort) GETARG(int) : \
	    (ulong) GETARG(uint))

	/*
	**  Get * arguments, including the form *nn$.  Preserve the nextarg
	**  that the argument can be gotten once the type is determined.
	*/
#define GETASTER(val) \
	n2 = 0; \
	cp = fmt; \
	while (is_digit(*cp)) \
	{ \
		n2 = 10 * n2 + to_digit(*cp); \
		cp++; \
	} \
	if (*cp == '$') \
	{ \
		int hold = nextarg; \
		if (argtable == NULL) \
		{ \
			argtable = statargtable; \
			sm_find_arguments(fmt0, orgap, &argtable); \
		} \
		nextarg = n2; \
		val = GETARG(int); \
		nextarg = hold; \
		fmt = ++cp; \
	} \
	else \
	{ \
		val = GETARG(int); \
	}

/*
**  Get the argument indexed by nextarg.   If the argument table is
**  built, use it to get the argument.  If its not, get the next
**  argument (and arguments must be gotten sequentially).
*/

# define GETARG(type) \
	(((argtable != NULL) ? (void) (ap = argtable[nextarg]) : (void) 0), \
	 nextarg++, va_arg(ap, type))

	/* sorry, fprintf(read_only_file, "") returns SM_IO_EOF, not 0 */
	if (cantwrite(fp))
	{
		errno = EBADF;
		return SM_IO_EOF;
	}

	/* optimise fprintf(stderr) (and other unbuffered Unix files) */
	if ((f_flags(*fp) & (SMNBF|SMWR|SMRW)) == (SMNBF|SMWR) &&
	    f_fd(*fp) >= 0)
		return sm_bprintf(fp, fmt0, ap);

	fmt = fmt0;
	argtable = NULL;
	nextarg = 1;
	SM_VA_COPY(orgap, ap);
	uio.uio_iov = iovp = iov;
	uio.uio_resid = 0;
	uio.uio_iovcnt = 0;
	ret = 0;

	/* Scan the format for conversions (`%' character). */
	for (;;)
	{
		cp = fmt;
		n = 0;
		delim = 0;
		while ((wc = *fmt) != '\0')
		{
			if (wc == '%')
			{
				n = 1;
				break;
			}
			fmt++;
		}
		if ((m = fmt - cp) != 0)
		{
			PRINT(cp, m);
			ret += m;
		}
		if (n <= 0)
			goto done;
		fmt++;		/* skip over '%' */

		flags = 0;
		dprec = 0;
		width = 0;
		prec = -1;
		sign = '\0';
		osize = 0;	/* bogus "might be used uninitialized" */

rflag:		ch = *fmt++;
reswitch:	switch (ch)
		{
		  case ' ':

			/*
			**  ``If the space and + flags both appear, the space
			**  flag will be ignored.''
			**	-- ANSI X3J11
			*/

			if (!sign)
				sign = ' ';
			goto rflag;
		  case '#':
			flags |= ALT;
			goto rflag;
		  case '@':
			flags |= ALT;
			delim = ',';
			goto rflag;
		  case '*':

			/*
			**  ``A negative field width argument is taken as a
			**  - flag followed by a positive field width.''
			**	-- ANSI X3J11
			**  They don't exclude field widths read from args.
			*/

			GETASTER(width);
			if (width >= 0)
				goto rflag;
			width = -width;
			/* FALLTHROUGH */
		  case '-':
			flags |= LADJUST;
			goto rflag;
		  case '+':
			sign = '+';
			goto rflag;
		  case '.':
			if ((ch = *fmt++) == '*')
			{
				GETASTER(n);
				prec = n < 0 ? -1 : n;
				goto rflag;
			}
			n = 0;
			while (is_digit(ch))
			{
				n = 10 * n + to_digit(ch);
				ch = *fmt++;
			}
			if (ch == '$')
			{
				nextarg = n;
				if (argtable == NULL)
				{
					argtable = statargtable;
					sm_find_arguments(fmt0, orgap,
					    &argtable);
				}
				goto rflag;
			}
			prec = n < 0 ? -1 : n;
			goto reswitch;
		  case '0':

			/*
			**  ``Note that 0 is taken as a flag, not as the
			**  beginning of a field width.''
			**	-- ANSI X3J11
			*/

			flags |= ZEROPAD;
			goto rflag;
		  case '1': case '2': case '3': case '4':
		  case '5': case '6': case '7': case '8': case '9':
			n = 0;
			do
			{
				n = 10 * n + to_digit(ch);
				ch = *fmt++;
			} while (is_digit(ch));
			if (ch == '$')
			{
				nextarg = n;
				if (argtable == NULL)
				{
					argtable = statargtable;
					sm_find_arguments(fmt0, orgap,
					    &argtable);
				}
				goto rflag;
			}
			width = n;
			goto reswitch;
		  case 'h':
			flags |= SHORTINT;
			goto rflag;
		  case 'l':
			if (*fmt == 'l')
			{
				fmt++;
				flags |= QUADINT;
			}
			else
			{
				flags |= LONGINT;
			}
			goto rflag;
		  case 'q':
			flags |= QUADINT;
			goto rflag;
		  case 't':
			flags |= PTRINT;
			goto rflag;
		  case 'z':
			flags |= SIZEINT;
			goto rflag;
		  case 'c':
			*buf = GETARG(int);
			cp = buf;
			size = 1;
			sign = '\0';
			if (flags & ALT) {
				flags |= SANITIZE;
				SANITIZE_SIZE(delim);
			}
			break;
		  case 'D':
			flags |= LONGINT;
			/*FALLTHROUGH*/
		  case 'd':
		  case 'i':
			_uquad = SARG();
			if ((longlong_T) _uquad < 0)
			{
				_uquad = -(longlong_T) _uquad;
				sign = '-';
			}
			base = DEC;
			goto number;
		  case 'e':
		  case 'E':
		  case 'f':
		  case 'g':
		  case 'G':
#if MTA_USE_FP_PRINT
			{
				double val;
				char *p;
				char fmt[16];
				char out[150];
				size_t len;

				/*
				**  This code implements floating point output
				**  in the most portable manner possible,
				**  relying only on 'sprintf' as defined by
				**  the 1989 ANSI C standard.
				**  We silently cap width and precision
				**  at 120, to avoid buffer overflow.
				*/

				val = GETARG(double);

				p = fmt;
				*p++ = '%';
				if (sign)
					*p++ = sign;
				if (flags & ALT)
					*p++ = '#';
				if (flags & LADJUST)
					*p++ = '-';
				if (flags & ZEROPAD)
					*p++ = '0';
				*p++ = '*';
				if (prec >= 0)
				{
					*p++ = '.';
					*p++ = '*';
				}
				*p++ = ch;
				*p = '\0';

				if (width > 120)
					width = 120;
				if (prec > 120)
					prec = 120;
				if (prec >= 0)
					sprintf(out, fmt, width, prec, val);
				else
					sprintf(out, fmt, width, val);
				len = strlen(out);
				PRINT(out, len);
				FLUSH();
				continue;
			}
#else /* MTA_USE_FP_PRINT */

		/* fixme: Complain about unsupported format?? */
#define FPNOTSUPP "FPnotsupp"
			PRINT(FPNOTSUPP, strlen(FPNOTSUPP));
			FLUSH();
			continue;

#endif /* MTA_USE_FP_PRINT */
		  case 'n':
			if (flags & QUADINT)
				*GETARG(longlong_T *) = ret;
			else if (flags & LONGINT)
				*GETARG(long *) = ret;
			else if (flags & SHORTINT)
				*GETARG(short *) = ret;
			else if (flags & PTRINT)
				*GETARG(ptrdiff_t *) = ret;
			else if (flags & SIZEINT)
				*GETARG(ssize_t *) = ret;
			else
				*GETARG(int *) = ret;
			continue;	/* no output */
		  case 'O':
			flags |= LONGINT;
			/*FALLTHROUGH*/
		  case 'o':
			_uquad = UARG();
			base = OCT;
			goto nosign;
		  case 'p':

			/*
			**  ``The argument shall be a pointer to void.  The
			**  value of the pointer is converted to a sequence
			**  of printable characters, in an implementation-
			**  defined manner.''
			**	-- ANSI X3J11
			*/

			/* NOSTRICT */
			{
				union
				{
					void *p;
					ulonglong_T ll;
					ulong l;
					uint i;
				} u;
				u.p = GETARG(void *);
				if (sizeof(void *) == sizeof(ulonglong_T))
					_uquad = u.ll;
				else if (sizeof(void *) == sizeof(long))
					_uquad = u.l;
				else
					_uquad = u.i;
			}
			base = HEX;
			xdigs = "0123456789abcdef";
			flags |= HEXPREFIX;
			ch = 'x';
			goto nosign;
		  case 'M':
			_uquad = SARG();
			if ((longlong_T) _uquad >= 0)
			{
				base = DEC;
				goto number;
			}
			else if (prec < 0)
			{
				sm_error_T err;

				err = (sm_error_T) _uquad;
				cp = smerr2txt(err);
				if (cp == NULL)
					cp = "unknown_error";
				size = strlen(cp);
				PRINT(cp, size);

				cp = ", module=";
				size = strlen(cp);
				PRINT(cp, size);
				cp = smmod2txt(err);
				if (cp == NULL)
					cp = "unknown_module";
				size = strlen(cp);

				/* module/type?? */
				goto prtstring;
			}
			else /* if (prec >= 0) */
			{
				sm_error_T err;
				char *p;

				/*
				**  this is a hack...
				**  it doesn't deal with prec/width/... properly
				**  just don't use those with %M
				*/

				err = (sm_error_T) _uquad;
				cp = smerr2txt(err);
				if (cp == NULL)
					cp = "unknown_error";
				realsz = strlen(cp);
				if (realsz <= prec)
				{
					PRINT(cp, realsz);
					prec -= realsz;
					cp = ", module=";
					size = strlen(cp);
					if (size >= prec)
					{
						prec = 0;
						ret += realsz;
						goto flushit;
					}
					PRINT(cp, size);
					prec -= size;
					cp = smmod2txt(err);
					if (cp == NULL)
						cp = "unknown_module";
					size = strlen(cp);

					/* module/type?? */
					goto prtstring;
				}
				else
				{
					/* not enough space */
					p = memchr(cp, 0, prec);
					if (p != NULL)
					{
						size = p - cp;
						if (size > prec)
							size = prec;
					}
					else
						size = prec;
				}
			}
			sign = '\0';
			break;
		  case 'm':
			_uquad = SARG();
			if ((longlong_T) _uquad >= 0)
			{
				base = DEC;
				goto number;
			}
			cp = smerr2txt((sm_ret_T) _uquad);
			if (cp == NULL || strcmp(cp, SM_ETXT_UNKNOWN) == 0)
			{
				_uquad &= (ulonglong_T) UINT32_MAX;
				base = HEX;
				xdigs = "0123456789abcdef";
				ch = 'x';
				flags |= HEXPREFIX;
				goto nosign;
			}
			goto prtstring;
		  case 's':
			if ((cp = GETARG(char *)) == NULL)
				cp = "(null)";
			else if (flags & ALT)
				flags |= SANITIZE;
   prtstring:
			if (prec >= 0)
			{
				/*
				**  can't use strlen; can only look for the
				**  NUL in the first `prec' characters, and
				**  strlen() will go further.
				*/

				char *p = memchr(cp, 0, prec);

				if (p != NULL)
				{
					size = p - cp;
					if (size > prec)
						size = prec;
				}
				else
					size = prec;
			}
			else
				size = strlen(cp);
			SANITIZE_SIZE(delim);
			sign = '\0';
			break;

		  case 'A':
			{
			uchar *src;
			uint32_t u32;

			/* XXX right size? */
#if SIZEOF_INT == 4
#elif SIZEOF_LONG == 4
			flags |= LONGINT;
#else
			ERROR: cannot find size for uint32
#endif
			_uquad = UARG();
			u32 = _uquad;
			src = (uchar *) &u32;
			wcp = buf + BUF;	/* sizeof(buf)? */
			for (m = 3; m >= 0; m--)
			{

				_uquad = src[m];
				while (_uquad >= 10)
				{
					*--wcp = to_char(_uquad % 10);
					_uquad /= 10;
				}
				*--wcp = to_char(_uquad);
				if (m > 0)
					*--wcp = '.';
			}
			SM_ASSERT(wcp >= buf);
			size = buf + BUF - wcp;	/* sizeof(buf)? */
			cp = wcp;
			}
			break;

#if !SM_NO_CSTR
		  case 'C':
			size = 6; /* strlen("NULL") */
			if ((cstr = GETARG(sm_cstr_P)) == NULL)
				cp = "(NULL)";
			else
			{
				SM_IS_CSTR(cstr);
				if ((cp = (char *) sm_cstr_data(cstr)) == NULL)
					cp = "(Null)";
				else
				{
					size = sm_cstr_getlen(cstr);
					while (size > 0 &&
					       sm_cstr_rd_elem(cstr,
							      size - 1) == '\0')
					{
						--size;
					}
					if (flags & ALT)
					{
						flags |= SANITIZE;
						SANITIZE_SIZE(delim);
					}
				}
			}
			if (prec >= 0 && size > prec)
				size = prec;
			sign = '\0';
			break;
#endif /* !SM_NO_CSTR */

		  case 'S':
			size = 6; /* strlen("NULL") */
			if ((str = GETARG(sm_str_P)) == NULL)
				cp = "(NULL)";
			else
			{
				SM_IS_BUF(str);
				if ((cp = (char *) sm_str_data(str)) == NULL)
					cp = "(Null)";
				else
				{
					size = sm_str_getlen(str);
					if (flags & ALT)
					{
						flags |= SANITIZE;
						SANITIZE_SIZE(delim);
					}
				}
			}
			if (prec >= 0 && size > prec)
				size = prec;
			sign = '\0';
			break;
		  case 'T':
			size = 6; /* strlen("NULL") */
			if ((str = GETARG(sm_str_P)) == NULL)
				cp = "(NULL)";
			else
			{
				SM_IS_BUF(str);
				if ((cp = (char *) sm_str_data(str)) == NULL)
					cp = "(Null)";
				else
				{
					uchar c;

					size = sm_str_getlen(str);
					while (size > 0 && (
					       (c = sm_str_rd_elem(str,
								size - 1))
							== '\0' ||
					       c == '\n' || c == '\r'))
					{
						--size;
					}
				}
				if (flags & ALT)
				{
					flags |= SANITIZE;
					SANITIZE_SIZE(delim);
				}
			}
			if (prec >= 0 && size > prec)
				size = prec;
			sign = '\0';
			break;
		  case 'N':
			size = 6; /* strlen("NULL") */
			if ((str = GETARG(sm_str_P)) == NULL)
				cp = "(NULL)";
			else
			{
				SM_IS_BUF(str);
				if ((cp = (char *) sm_str_data(str)) == NULL)
					cp = "(Null)";
				else
				{
					size = sm_str_getlen(str);
					while (size > 0 &&
					       sm_str_rd_elem(str,
							      size - 1) == '\0')
					{
						--size;
					}
				}
				if (flags & ALT)
				{
					flags |= SANITIZE;
					SANITIZE_SIZE(delim);
				}
			}
			if (prec >= 0 && size > prec)
				size = prec;
			sign = '\0';
			break;
		  case 'U':
			flags |= LONGINT;
			/*FALLTHROUGH*/
		  case 'u':
			_uquad = UARG();
			base = DEC;
			goto nosign;

		  case 'r':
			_uquad = SARG();
			if ((longlong_T) _uquad < 0)
			{
				_uquad &= (ulonglong_T) UINT32_MAX;
				base = HEX;
				xdigs = "0123456789abcdef";
				ch = 'x';
				flags |= HEXPREFIX;
				goto nosign;
			}
			base = DEC;
			goto number;

		  case 'Y':
			_uquad = UARG();
			base = BASE64;
			sign = '\0';
			goto number;

		  case 'Z':
			_uquad = UARG();
			base = BASE32;
			sign = '\0';
			goto number;

		  case 'X':
			xdigs = "0123456789ABCDEF";
			goto hex;
		  case 'x':
			xdigs = "0123456789abcdef";
hex:			_uquad = UARG();
			base = HEX;
			/* leading 0x/X only if non-zero */
			if (flags & ALT && _uquad != 0)
				flags |= HEXPREFIX;

			/* unsigned conversions */
nosign:			sign = '\0';

			/*
			**  ``... diouXx conversions ... if a precision is
			**  specified, the 0 flag will be ignored.''
			**	-- ANSI X3J11
			*/

number:			if ((dprec = prec) >= 0)
				flags &= ~ZEROPAD;

			/*
			**  ``The result of converting a zero value with an
			**  explicit precision of zero is no characters.''
			**	-- ANSI X3J11
			*/

			wcp = buf + BUF;	/* sizeof(buf)? */
			if (_uquad != 0 || prec != 0)
			{
				/*
				**  Unsigned mod is hard, and unsigned mod
				**  by a constant is easier than that by
				**  a variable; hence this switch.
				*/

				switch (base)
				{
				  case OCT:
					do
					{
						*--wcp = to_char(_uquad & 7);
						_uquad >>= 3;
					} while (_uquad);
					/* handle octal leading 0 */
					if (flags & ALT && *wcp != '0')
						*--wcp = '0';
					break;

				  case DEC:
					/* many numbers are 1 digit */
					while (_uquad >= 10)
					{
						*--wcp = to_char(_uquad % 10);
						_uquad /= 10;
					}
					*--wcp = to_char(_uquad);
					break;

				  case HEX:
					do
					{
						*--wcp = xdigs[_uquad & 15];
						_uquad >>= 4;
					} while (_uquad);
					break;

				  case BASE32:
					while (_uquad >= 32)
					{
						*--wcp = b32digs[_uquad & 31];
						_uquad >>= 5;
					}
					*--wcp = b32digs[_uquad & 31];
					break;

				  case BASE64:
					while (_uquad > 63)
					{
						*--wcp = b64digs[_uquad & 63];
						_uquad >>= 6;
					}
					*--wcp = b64digs[_uquad & 63];
					break;

				  default:
					cp = "bug in sm_io_vfprintf: bad base";
					size = strlen(cp);
					goto skipsize;
				}
			}
			size = buf + BUF - wcp;	/* sizeof(buf)? */
			cp = wcp;

		  skipsize:
			break;
		  default:	/* "%?" prints ?, unless ? is NUL */
			if (ch == '\0')
				goto done;
			/* pretend it was %c with argument ch */
			*buf = ch;
			cp = buf;
			size = 1;
			sign = '\0';
			break;
		}

		/*
		**  All reasonable formats wind up here.  At this point, `cp'
		**  points to a string which (if not flags&LADJUST) should be
		**  padded out to `width' places.  If flags&ZEROPAD, it should
		**  first be prefixed by any sign or other prefix; otherwise,
		**  it should be blank padded before the prefix is emitted.
		**  After any left-hand padding and prefixing, emit zeroes
		**  required by a decimal [diouxX] precision, then print the
		**  string proper, then emit zeroes required by any leftover
		**  floating precision; finally, if LADJUST, pad with blanks.
		**
		**  Compute actual size, so we know how much to pad.
		**  size excludes decimal prec; realsz includes it.
		*/

		realsz = dprec > size ? dprec : size;
		if (sign)
			realsz++;
		else if (flags & HEXPREFIX)
			realsz+= 2;

		/* right-adjusting blank padding */
		if ((flags & (LADJUST|ZEROPAD)) == 0)
			PAD(width - realsz, blanks);

		/* prefix */
		if (sign)
		{
			PRINT(&sign, 1);
		}
		else if (flags & HEXPREFIX)
		{
			ox[0] = '0';
			ox[1] = ch;
			PRINT(ox, 2);
		}

		/* right-adjusting zero padding */
		if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD)
			PAD(width - realsz, zeroes);

		/* leading zeroes from decimal precision */
		PAD(dprec - size, zeroes);

		if ((flags & SANITIZE) != 0 && (osize != size || prec >= 0))
		{
			int limit;	/* limit for fixed size files */

			if (f_flags(*fp) & (SMSTR|SMSTRSTR))
				limit = SM_MIN(f_w(*fp), size);
			else
				limit = size;

			FLUSH();
			xdigs = "0123456789ABCDEF";
			for (m = 0, n = 0; m < osize && n < limit; m++, n++) {
				n2 = cp[m];
				if (OKTOPRINT(n2, delim)) {
					if (sm_is_err(sm_io_putc(fp, n2)))
						goto error;
				}
				else {
					if (n + 3 > limit) {
						while (n < limit) {
							if (sm_is_err(sm_io_putc(fp, (int) '_')))
								goto error;
							++n;
						}
						break;
					}
					if (sm_is_err(sm_io_putc(fp, (int) '%')))
						goto error;
					if (sm_is_err(sm_io_putc(fp, xdigs[(n2 >> 4) & 15])))
						goto error;
					if (sm_is_err(sm_io_putc(fp, xdigs[n2 & 15])))
						goto error;
					n += 2;
				}
			}
		}
		else
		{
			/* the string or number proper */
			PRINT(cp, size);
		}
		/* left-adjusting padding (always blank) */
		if (flags & LADJUST)
			PAD(width - realsz, blanks);

		/* finally, adjust ret */
		ret += width > realsz ? width : realsz;
  flushit:
		FLUSH();	/* copy out the I/O vectors */
	}
done:
	FLUSH();
error:
	if ((argtable != NULL) && (argtable != statargtable))
		sm_free(argtable);
	return sm_error(fp) ? SM_IO_EOF : ret;
	/* NOTREACHED */
}

/* Type ids for argument type table. */
#define T_UNUSED	0
#define T_SHORT		1
#define T_U_SHORT	2
#define TP_SHORT	3
#define T_INT		4
#define T_U_INT		5
#define TP_INT		6
#define T_LONG		7
#define T_U_LONG	8
#define TP_LONG		9
#define T_QUAD		10
#define T_U_QUAD	11
#define TP_QUAD		12
#define T_DOUBLE	13
#define TP_CHAR		15
#define TP_VOID		16
#define TP_SM_STR	17
#define TP_SM_CSTR	18
/* XXX need to extend this for new formats? */
#define T_PTRINT	19
#define TP_PTRINT	20
#define T_SIZEINT	21
#define T_SSIZEINT	22
#define TP_SSIZEINT	23

/*
**  SM_FIND_ARGUMENTS -- find all args when a positional parameter is found.
**
**  Find all arguments when a positional parameter is encountered.  Returns a
**  table, indexed by argument number, of pointers to each arguments.  The
**  initial argument table should be an array of STATIC_ARG_TBL_SIZE entries.
**  It will be replaced with a malloc-ed one if it overflows.
**
**	Parameters:
**		fmt0 -- formating directives
**		ap -- vector list of data unit for formating consumption
**		argtable -- an indexable table (returned) of 'ap'
**
**	Results:
**		none.
*/

static void
sm_find_arguments(const char *fmt0, va_list ap, va_list **pargtable)
{
	const char *fmt; /* format string */
	int ch;		/* character from fmt */
	int n, n2;	/* handy integer (short term usage) */
	const char *cp;	/* handy char pointer (short term usage) */
	int flags;	/* flags as above */
	uchar *typetable; /* table of types */
	uchar stattypetable[STATIC_ARG_TBL_SIZE];
	int tablesize;		/* current size of type table */
	int tablemax;		/* largest used index in table */
	int nextarg;		/* 1-based argument index */

	/* Add an argument type to the table, expanding if necessary. */
#define ADDTYPE(type) \
	((nextarg >= tablesize) ? \
		(sm_grow_type_table(nextarg, &typetable, &tablesize), 0) : 0, \
	(nextarg > tablemax) ? tablemax = nextarg : 0, \
	typetable[nextarg++] = type)

#define ADDSARG() \
	((flags & LONGINT) ? ADDTYPE(T_LONG) : \
		((flags & SHORTINT) ? ADDTYPE(T_SHORT) : ADDTYPE(T_INT)))

#define ADDUARG() \
	((flags & LONGINT) ? ADDTYPE(T_U_LONG) : \
		((flags & SHORTINT) ? ADDTYPE(T_U_SHORT) : ADDTYPE(T_U_INT)))

	/* Add * arguments to the type array. */
#define ADDASTER() \
	n2 = 0; \
	cp = fmt; \
	while (is_digit(*cp)) \
	{ \
		n2 = 10 * n2 + to_digit(*cp); \
		cp++; \
	} \
	if (*cp == '$') \
	{ \
		int hold = nextarg; \
		nextarg = n2; \
		ADDTYPE (T_INT); \
		nextarg = hold; \
		fmt = ++cp; \
	} \
	else \
	{ \
		ADDTYPE (T_INT); \
	}
	fmt = fmt0;
	typetable = stattypetable;
	tablesize = STATIC_ARG_TBL_SIZE;
	tablemax = 0;
	nextarg = 1;
	(void) sm_memset(typetable, T_UNUSED, STATIC_ARG_TBL_SIZE);

	/* Scan the format for conversions (`%' character). */
	for (;;)
	{
		for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++)
			/* void */;
		if (ch == '\0')
			goto done;
		fmt++;		/* skip over '%' */

		flags = 0;

rflag:		ch = *fmt++;
reswitch:	switch (ch)
		{
		  case ' ':
		  case '#':
			goto rflag;
		  case '*':
			ADDASTER();
			goto rflag;
		  case '-':
		  case '+':
			goto rflag;
		  case '.':
			if ((ch = *fmt++) == '*')
			{
				ADDASTER();
				goto rflag;
			}
			while (is_digit(ch))
			{
				ch = *fmt++;
			}
			goto reswitch;
		  case '0':
			goto rflag;
		  case '1': case '2': case '3': case '4':
		  case '5': case '6': case '7': case '8': case '9':
			n = 0;
			do
			{
				n = 10 * n + to_digit(ch);
				ch = *fmt++;
			} while (is_digit(ch));
			if (ch == '$')
			{
				nextarg = n;
				goto rflag;
			}
			goto reswitch;
		  case 'h':
			flags |= SHORTINT;
			goto rflag;
		  case 'l':
			if (*fmt == 'l')
			{
				fmt++;
				flags |= QUADINT;
			}
			else
			{
				flags |= LONGINT;
			}
			goto rflag;
		  case 'q':
			flags |= QUADINT;
			goto rflag;
		  case 't':
			flags |= PTRINT;
			goto rflag;
		  case 'z':
			flags |= SIZEINT;
			goto rflag;
		  case 'c':
			ADDTYPE(T_INT);
			break;
		  case 'D':
			flags |= LONGINT;
			/*FALLTHROUGH*/
		  case 'd':
		  case 'i':
		  case 'r':
			if (flags & QUADINT)
				ADDTYPE(T_QUAD);
			else if (flags & PTRINT)
				ADDTYPE(T_PTRINT);
			else if (flags & SIZEINT)
				ADDTYPE(T_SSIZEINT);
			else
				ADDSARG();
			break;
		  case 'e':
		  case 'E':
		  case 'f':
		  case 'g':
		  case 'G':
			ADDTYPE(T_DOUBLE);
			break;
		  case 'n':
			if (flags & QUADINT)
				ADDTYPE(TP_QUAD);
			else if (flags & LONGINT)
				ADDTYPE(TP_LONG);
			else if (flags & SHORTINT)
				ADDTYPE(TP_SHORT);
			else if (flags & PTRINT)
				ADDTYPE(TP_PTRINT);
			else if (flags & SIZEINT)
				ADDTYPE(TP_SSIZEINT);
			else
				ADDTYPE(TP_INT);
			continue;	/* no output */
		  case 'O':
			flags |= LONGINT;
			/*FALLTHROUGH*/
		  case 'o':
			if (flags & QUADINT)
				ADDTYPE(T_U_QUAD);
			else
				ADDUARG();
			break;
		  case 'p':
			ADDTYPE(TP_VOID);
			break;
		  case 's':
			ADDTYPE(TP_CHAR);
			break;
		  case 'U':
			flags |= LONGINT;
			/*FALLTHROUGH*/
		  case 'u':
			if (flags & QUADINT)
				ADDTYPE(T_U_QUAD);
			else
				ADDUARG();
			break;
		  case 'X':
		  case 'x':
		  case 'Y':
		  case 'Z':
			if (flags & QUADINT)
				ADDTYPE(T_U_QUAD);
			else if (flags & PTRINT)
				ADDTYPE(T_PTRINT);
			else if (flags & SIZEINT)
				ADDTYPE(T_SIZEINT);
			else
				ADDUARG();
			break;
		  case 'A':
			/* deal with 'l' modifier? */
#if SIZEOF_INT == 4
			ADDTYPE(T_U_INT);
#elif SIZEOF_LONG == 4
			ADDTYPE(T_U_LONG);
#else
#endif
			break;
#if !SM_NO_CSTR
		  case 'C':
			ADDTYPE(TP_SM_CSTR);
			break;
#endif /* !SM_NO_CSTR */
		  case 'N':
		  case 'S':
		  case 'T':
			ADDTYPE(TP_SM_STR);
			break;
		  default:	/* "%?" prints ?, unless ? is NUL */
			if (ch == '\0')
				goto done;
			break;
		}
	}
done:
	/* Build the argument table. */
	if (tablemax >= STATIC_ARG_TBL_SIZE)
	{
		SM_ASSERT(pargtable != NULL);
		*pargtable = (va_list *)
		    sm_malloc(sizeof(va_list) * (tablemax + 1));
/* XXX check result? */
		SM_ASSERT(*pargtable != NULL);
	}

#if 0
	(*pargtable)[0] = NULL;
#endif /* 0 */
	for (n = 1; n <= tablemax; n++)
	{
		SM_VA_COPY((*pargtable)[n], ap);
		switch (typetable[n])
		{
		  case T_UNUSED:
			(void) va_arg(ap, int);
			break;
		  case T_SHORT:
			(void) va_arg(ap, int);
			break;
		  case T_U_SHORT:
			(void) va_arg(ap, int);
			break;
		  case TP_SHORT:
			(void) va_arg(ap, short *);
			break;
		  case T_INT:
			(void) va_arg(ap, int);
			break;
		  case T_U_INT:
			(void) va_arg(ap, uint);
			break;
		  case TP_INT:
			(void) va_arg(ap, int *);
			break;
		  case T_LONG:
			(void) va_arg(ap, long);
			break;
		  case T_U_LONG:
			(void) va_arg(ap, ulong);
			break;
		  case TP_LONG:
			(void) va_arg(ap, long *);
			break;
		  case T_QUAD:
			(void) va_arg(ap, longlong_T);
			break;
		  case T_U_QUAD:
			(void) va_arg(ap, ulonglong_T);
			break;
		  case TP_QUAD:
			(void) va_arg(ap, longlong_T *);
			break;
		  case T_DOUBLE:
			(void) va_arg(ap, double);
			break;
		  case TP_CHAR:
			(void) va_arg(ap, char *);
			break;
		  case TP_VOID:
			(void) va_arg(ap, void *);
			break;
		  case T_PTRINT:
			(void) va_arg(ap, ptrdiff_t);
			break;
		  case TP_PTRINT:
			(void) va_arg(ap, ptrdiff_t *);
			break;
		  case T_SIZEINT:
			(void) va_arg(ap, size_t);
			break;
		  case T_SSIZEINT:
			(void) va_arg(ap, ssize_t);
			break;
		  case TP_SSIZEINT:
			(void) va_arg(ap, ssize_t *);
			break;
		}
	}

	if ((typetable != NULL) && (typetable != stattypetable))
		sm_free(typetable);
}

/*
**  SM_GROW_TYPE_TABLE -- Increase the size of the type table.
**
**	Parameters:
**		tabletype -- type of table to grow
**		tablesize -- requested new table size
*/

static void
sm_grow_type_table(int nextarg, uchar **typetable, int *tablesize)
{
	uchar *oldtable = *typetable;
	int newsize = *tablesize * 2;

	if (newsize < nextarg + 1)
		newsize = nextarg + 1;
	if (*tablesize == STATIC_ARG_TBL_SIZE)
	{
		*typetable = (uchar *) sm_malloc(sizeof(uchar)
							   * newsize);
/* XXX check result? */
		SM_ASSERT(*typetable != NULL);
		(void) sm_memmove(*typetable, oldtable, *tablesize);
	}
	else
	{
		*typetable = (uchar *) sm_realloc(*typetable,
					sizeof(uchar) * newsize);
/* XXX check result? */
		SM_ASSERT(*typetable != NULL);
	}
	(void) sm_memset(*typetable + *tablesize, T_UNUSED,
		       (newsize - *tablesize));

	*tablesize = newsize;
}


syntax highlighted by Code2HTML, v. 0.9.1