1109 lines
24 KiB
C
1109 lines
24 KiB
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.53 2004/08/03 20:54:49 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)
|
|
sprintf(out, fmt, width, prec, val);
|
|
else
|
|
sprintf(out, fmt, width, val);
|
|
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;
|
|
}
|