root/usr/src/cmd/sendmail/libsm/vfprintf.c
/*
 * Copyright (c) 2000-2001, 2004 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/gen.h>
SM_IDSTR(id, "@(#)$Id: vfprintf.c,v 1.54 2005/05/16 03:52:00 ca Exp $")

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

#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sm/config.h>
#include <sm/varargs.h>
#include <sm/io.h>
#include <sm/heap.h>
#include <sm/conf.h>
#include "local.h"
#include "fvwrite.h"

static int      sm_bprintf __P((SM_FILE_T *, const char *, va_list));
static void     sm_find_arguments __P((const char *, va_list , va_list **));
static void     sm_grow_type_table_x __P((unsigned char **, int *));
static int      sm_print __P((SM_FILE_T *, int, 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
**              timeout -- time to complete operation (milliseconds)
**              uio -- vector list of memory locations of data for printing
**
**      Results:
**              Success: 0 (zero)
**              Failure:
*/

static int
sm_print(fp, timeout, uio)
        SM_FILE_T *fp;
        int timeout;
        register struct sm_uio *uio;
{
        register int err;

        if (uio->uio_resid == 0)
        {
                uio->uio_iovcnt = 0;
                return 0;
        }
        err = sm_fvwrite(fp, timeout, 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(fp, fmt, ap)
        SM_FILE_T *fp;
        const char *fmt;
        SM_VA_LOCAL_DECL
{
        int ret;
        SM_FILE_T fake;
        unsigned char buf[SM_IO_BUFSIZ];
        extern const char SmFileMagic[];

        /* copy the important variables */
        fake.sm_magic = SmFileMagic;
        fake.f_timeout = SM_TIME_FOREVER;
        fake.f_timeoutstate = SM_TIME_BLOCK;
        fake.f_flags = fp->f_flags & ~SMNBF;
        fake.f_file = fp->f_file;
        fake.f_cookie = fp->f_cookie;
        fake.f_write = fp->f_write;
        fake.f_close = NULL;
        fake.f_open = NULL;
        fake.f_read = NULL;
        fake.f_seek = NULL;
        fake.f_setinfo = fake.f_getinfo = NULL;
        fake.f_type = "sm_bprintf:fake";

        /* set up the buffer */
        fake.f_bf.smb_base = fake.f_p = buf;
        fake.f_bf.smb_size = fake.f_w = sizeof(buf);
        fake.f_lbfsize = 0;     /* not actually used, but Just In Case */

        /* do the work, then copy any error status */
        ret = sm_io_vfprintf(&fake, SM_TIME_FOREVER, fmt, ap);
        if (ret >= 0 && sm_io_flush(&fake, SM_TIME_FOREVER))
                ret = SM_IO_EOF;        /* errno set by sm_io_flush */
        if (fake.f_flags & SMERR)
                fp->f_flags |= SMERR;
        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)     ((unsigned) to_digit(c) <= 9)
#define to_char(n)      ((char) (n) + '0')

/* Flags used during conversion. */
#define ALT             0x001           /* alternate form */
#define HEXPREFIX       0x002           /* add 0x or 0X prefix */
#define LADJUST         0x004           /* left adjustment */
#define LONGINT         0x010           /* long integer */
#define QUADINT         0x020           /* quad integer */
#define SHORTINT        0x040           /* short integer */
#define ZEROPAD         0x080           /* zero (as opposed to blank) pad */
#define FPT             0x100           /* Floating point number */

/*
**  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(fp, timeout, fmt0, ap)
        SM_FILE_T *fp;
        int timeout;
        const char *fmt0;
        SM_VA_LOCAL_DECL
{
        register char *fmt;     /* format string */
        register int ch;        /* character from fmt */
        register int n, m, n2;  /* handy integers (short term usage) */
        register char *cp;      /* handy char pointer (short term usage) */
        register struct sm_iov *iovp;/* for PRINT macro */
        register 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 */
        char sign;              /* sign prefix (' ', '+', '-', or \0) */
        wchar_t wc;
        ULONGLONG_T _uquad;     /* integer arguments %[diouxX] */
        enum { OCT, DEC, HEX } 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 */
        char *xdigs="0123456789abcdef"; /* digits for [xX] conversion */
#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 */
        va_list orgap;          /* original argument pointer */

        /*
        **  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 = (ptr); \
        iovp->iov_len = (len); \
        uio.uio_resid += (len); \
        iovp++; \
        if (++uio.uio_iovcnt >= NIOV) \
        { \
                if (sm_print(fp, timeout, &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, timeout, &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 ? SM_VA_ARG(ap, LONGLONG_T) : \
            flags&LONGINT ? GETARG(long) : \
            flags&SHORTINT ? (long) (short) GETARG(int) : \
            (long) GETARG(int))
#define UARG() \
        (flags&QUADINT ? SM_VA_ARG(ap, ULONGLONG_T) : \
            flags&LONGINT ? GETARG(unsigned long) : \
            flags&SHORTINT ? (unsigned long) (unsigned short) GETARG(int) : \
            (unsigned long) GETARG(unsigned int))

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

#if SM_VA_STD
# define GETARG(type) \
        (((argtable != NULL) ? (void) (ap = argtable[nextarg]) : (void) 0), \
         nextarg++, SM_VA_ARG(ap, type))
#else /* SM_VA_STD */
# define GETARG(type) \
        ((argtable != NULL) ? (*((type*)(argtable[nextarg++]))) : \
                              (nextarg++, SM_VA_ARG(ap, type)))
#endif /* SM_VA_STD */

        /* 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 ((fp->f_flags & (SMNBF|SMWR|SMRW)) == (SMNBF|SMWR) &&
            fp->f_file >= 0)
                return sm_bprintf(fp, fmt0, ap);

        fmt = (char *) 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;
                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';

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 '*':

                        /*
                        **  ``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 'c':
                        *(cp = buf) = GETARG(int);
                        size = 1;
                        sign = '\0';
                        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':
                        {
                                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)
#if HASSNPRINTF
                                        snprintf(out, sizeof(out), fmt, width,
                                                prec, val);
#else /* HASSNPRINTF */
                                        sprintf(out, fmt, width, prec, val);
#endif /* HASSNPRINTF */
                                else
#if HASSNPRINTF
                                        snprintf(out, sizeof(out), fmt, width,
                                                val);
#else /* HASSNPRINTF */
                                        sprintf(out, fmt, width, val);
#endif /* HASSNPRINTF */
                                len = strlen(out);
                                PRINT(out, len);
                                FLUSH();
                                continue;
                        }
                case 'n':
                        if (flags & QUADINT)
                                *GETARG(LONGLONG_T *) = ret;
                        else if (flags & LONGINT)
                                *GETARG(long *) = ret;
                        else if (flags & SHORTINT)
                                *GETARG(short *) = 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;
                                        unsigned long l;
                                        unsigned 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 's':
                        if ((cp = GETARG(char *)) == NULL)
                                cp = "(null)";
                        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);
                        sign = '\0';
                        break;
                  case 'U':
                        flags |= LONGINT;
                        /*FALLTHROUGH*/
                  case 'u':
                        _uquad = UARG();
                        base = DEC;
                        goto nosign;
                  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
                        */

                        cp = buf + 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
                                        {
                                                *--cp = to_char(_uquad & 7);
                                                _uquad >>= 3;
                                        } while (_uquad);
                                        /* handle octal leading 0 */
                                        if (flags & ALT && *cp != '0')
                                                *--cp = '0';
                                        break;

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

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

                                  default:
                                        cp = "bug in sm_io_vfprintf: bad base";
                                        size = strlen(cp);
                                        goto skipsize;
                                }
                        }
                        size = buf + BUF - cp;
                  skipsize:
                        break;
                  default:      /* "%?" prints ?, unless ? is NUL */
                        if (ch == '\0')
                                goto done;
                        /* pretend it was %c with argument ch */
                        cp = buf;
                        *cp = ch;
                        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);

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

                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

/*
**  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(fmt0, ap, argtable)
        const char *fmt0;
        SM_VA_LOCAL_DECL
        va_list **argtable;
{
        register char *fmt;     /* format string */
        register int ch;        /* character from fmt */
        register int n, n2;     /* handy integer (short term usage) */
        register char *cp;      /* handy char pointer (short term usage) */
        register int flags;     /* flags as above */
        unsigned char *typetable; /* table of types */
        unsigned char 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_x(&typetable, &tablesize), 0) : 0, \
        typetable[nextarg++] = type, \
        (nextarg > tablemax) ? tablemax = nextarg : 0)

#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 = (char *) fmt0;
        typetable = stattypetable;
        tablesize = STATIC_ARG_TBL_SIZE;
        tablemax = 0;
        nextarg = 1;
        (void) 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':
                        flags |= LONGINT;
                        goto rflag;
                  case 'q':
                        flags |= QUADINT;
                        goto rflag;
                  case 'c':
                        ADDTYPE(T_INT);
                        break;
                  case 'D':
                        flags |= LONGINT;
                        /*FALLTHROUGH*/
                  case 'd':
                  case 'i':
                        if (flags & QUADINT)
                        {
                                ADDTYPE(T_QUAD);
                        }
                        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
                                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':
                        if (flags & QUADINT)
                                ADDTYPE(T_U_QUAD);
                        else
                                ADDUARG();
                        break;
                  default:      /* "%?" prints ?, unless ? is NUL */
                        if (ch == '\0')
                                goto done;
                        break;
                }
        }
done:
        /* Build the argument table. */
        if (tablemax >= STATIC_ARG_TBL_SIZE)
        {
                *argtable = (va_list *)
                    sm_malloc(sizeof(va_list) * (tablemax + 1));
        }

        for (n = 1; n <= tablemax; n++)
        {
                SM_VA_COPY((*argtable)[n], ap);
                switch (typetable [n])
                {
                  case T_UNUSED:
                        (void) SM_VA_ARG(ap, int);
                        break;
                  case T_SHORT:
                        (void) SM_VA_ARG(ap, int);
                        break;
                  case T_U_SHORT:
                        (void) SM_VA_ARG(ap, int);
                        break;
                  case TP_SHORT:
                        (void) SM_VA_ARG(ap, short *);
                        break;
                  case T_INT:
                        (void) SM_VA_ARG(ap, int);
                        break;
                  case T_U_INT:
                        (void) SM_VA_ARG(ap, unsigned int);
                        break;
                  case TP_INT:
                        (void) SM_VA_ARG(ap, int *);
                        break;
                  case T_LONG:
                        (void) SM_VA_ARG(ap, long);
                        break;
                  case T_U_LONG:
                        (void) SM_VA_ARG(ap, unsigned long);
                        break;
                  case TP_LONG:
                        (void) SM_VA_ARG(ap, long *);
                        break;
                  case T_QUAD:
                        (void) SM_VA_ARG(ap, LONGLONG_T);
                        break;
                  case T_U_QUAD:
                        (void) SM_VA_ARG(ap, ULONGLONG_T);
                        break;
                  case TP_QUAD:
                        (void) SM_VA_ARG(ap, LONGLONG_T *);
                        break;
                  case T_DOUBLE:
                        (void) SM_VA_ARG(ap, double);
                        break;
                  case TP_CHAR:
                        (void) SM_VA_ARG(ap, char *);
                        break;
                  case TP_VOID:
                        (void) SM_VA_ARG(ap, void *);
                        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
**
**      Results:
**              Raises an exception if can't allocate memory.
*/

static void
sm_grow_type_table_x(typetable, tablesize)
        unsigned char **typetable;
        int *tablesize;
{
        unsigned char *oldtable = *typetable;
        int newsize = *tablesize * 2;

        if (*tablesize == STATIC_ARG_TBL_SIZE)
        {
                *typetable = (unsigned char *) sm_malloc_x(sizeof(unsigned char)
                                                           * newsize);
                (void) memmove(*typetable, oldtable, *tablesize);
        }
        else
        {
                *typetable = (unsigned char *) sm_realloc_x(typetable,
                                        sizeof(unsigned char) * newsize);
        }
        (void) memset(&typetable [*tablesize], T_UNUSED,
                       (newsize - *tablesize));

        *tablesize = newsize;
}