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