freebsd-skq/lib/libc/stdio/xprintf.c
Poul-Henning Kamp 6dbacee284 Make the %V{is} extension handle a NULL pointer like %s does: output "(null)"
Add %M{essage} extension which prints an errno value as the
corresponding string if possible or numerically otherwise.

It is not currently possible to do the syslog(3) like %m extension
because errno would need to get capatured on entry to the first
function in the printf family, so %M requires you to supply errno
as an argument.

Add %Q{uote} extension which will print a string in double quotes with
appropriate back-slash escapes (only) if necessary.
2006-01-25 12:45:24 +00:00

685 lines
16 KiB
C

/*-
* Copyright (c) 2005 Poul-Henning Kamp
* Copyright (c) 1990, 1993
* The Regents of the University of California. All rights reserved.
*
* This code is derived from software contributed to Berkeley by
* Chris Torek.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <namespace.h>
#include <err.h>
#include <sys/types.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <locale.h>
#include <stdint.h>
#include <assert.h>
#include <stdarg.h>
#include <namespace.h>
#include <string.h>
#include <wchar.h>
#include <un-namespace.h>
#include "printf.h"
#include "fvwrite.h"
int __use_xprintf = -1;
/* private stuff -----------------------------------------------------*/
union arg {
int intarg;
long longarg;
intmax_t intmaxarg;
#ifndef NO_FLOATING_POINT
double doublearg;
long double longdoublearg;
#endif
wint_t wintarg;
char *pchararg;
wchar_t *pwchararg;
void *pvoidarg;
};
/*
* Macros for converting digits to letters and vice versa
*/
#define to_digit(c) ((c) - '0')
#define is_digit(c) (((unsigned)to_digit(c)) <= 9)
/* various globals ---------------------------------------------------*/
const char __lowercase_hex[17] = "0123456789abcdef?"; /*lint !e784 */
const char __uppercase_hex[17] = "0123456789ABCDEF?"; /*lint !e784 */
#define PADSIZE 16
static char blanks[PADSIZE] =
{' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
static char zeroes[PADSIZE] =
{'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
/* printing and padding functions ------------------------------------*/
#define NIOV 8
struct __printf_io {
FILE *fp;
struct __suio uio;
struct __siov iov[NIOV];
struct __siov *iovp;
};
static void
__printf_init(struct __printf_io *io)
{
io->uio.uio_iov = io->iovp = &io->iov[0];
io->uio.uio_resid = 0;
io->uio.uio_iovcnt = 0;
}
void
__printf_flush(struct __printf_io *io)
{
__sfvwrite(io->fp, &io->uio);
__printf_init(io);
}
int
__printf_puts(struct __printf_io *io, const void *ptr, int len)
{
if (io->fp->_flags & __SERR)
return (0);
if (len == 0)
return (0);
io->iovp->iov_base = __DECONST(void *, ptr);
io->iovp->iov_len = len;
io->uio.uio_resid += len;
io->iovp++;
io->uio.uio_iovcnt++;
if (io->uio.uio_iovcnt >= NIOV)
__printf_flush(io);
return (len);
}
int
__printf_pad(struct __printf_io *io, int howmany, int zero)
{
int n;
const char *with;
int ret = 0;
if (zero)
with = zeroes;
else
with = blanks;
if ((n = (howmany)) > 0) {
while (n > PADSIZE) {
ret += __printf_puts(io, with, PADSIZE);
n -= PADSIZE;
}
ret += __printf_puts(io, with, n);
}
return (ret);
}
int
__printf_out(struct __printf_io *io, const struct printf_info *pi, const void *ptr, int len)
{
int ret = 0;
if ((!pi->left) && pi->width > len)
ret += __printf_pad(io, pi->width - len, pi->pad == '0');
ret += __printf_puts(io, ptr, len);
if (pi->left && pi->width > len)
ret += __printf_pad(io, pi->width - len, pi->pad == '0');
return (ret);
}
/* percent handling -------------------------------------------------*/
static int
__printf_arginfo_pct(const struct printf_info *pi __unused, size_t n __unused, int *argt __unused)
{
return (0);
}
static int
__printf_render_pct(struct __printf_io *io, const struct printf_info *pi __unused, const void *const *arg __unused)
{
return (__printf_puts(io, "%", 1));
}
/* 'n' ---------------------------------------------------------------*/
static int
__printf_arginfo_n(const struct printf_info *pi, size_t n, int *argt)
{
assert(n >= 1);
argt[0] = PA_POINTER;
return (1);
}
/*
* This is a printf_render so that all output has been flushed before it
* gets called.
*/
static int
__printf_render_n(FILE *io __unused, const struct printf_info *pi, const void *const *arg)
{
if (pi->is_char)
**((signed char **)arg[0]) = (signed char)pi->sofar;
else if (pi->is_short)
**((short **)arg[0]) = (short)pi->sofar;
else if (pi->is_long)
**((long **)arg[0]) = pi->sofar;
else if (pi->is_long_double)
**((long long **)arg[0]) = pi->sofar;
else if (pi->is_intmax)
**((intmax_t **)arg[0]) = pi->sofar;
else if (pi->is_ptrdiff)
**((ptrdiff_t **)arg[0]) = pi->sofar;
else if (pi->is_quad)
**((quad_t **)arg[0]) = pi->sofar;
else if (pi->is_size)
**((size_t **)arg[0]) = pi->sofar;
else
**((int **)arg[0]) = pi->sofar;
return (0);
}
/* table -------------------------------------------------------------*/
/*lint -esym(785, printf_tbl) */
static struct {
printf_arginfo_function *arginfo;
printf_function *gnurender;
printf_render *render;
} printf_tbl[256] = {
['%'] = { __printf_arginfo_pct, NULL, __printf_render_pct },
['A'] = { __printf_arginfo_float, NULL, __printf_render_float },
['C'] = { __printf_arginfo_chr, NULL, __printf_render_chr },
['E'] = { __printf_arginfo_float, NULL, __printf_render_float },
['F'] = { __printf_arginfo_float, NULL, __printf_render_float },
['G'] = { __printf_arginfo_float, NULL, __printf_render_float },
['S'] = { __printf_arginfo_str, NULL, __printf_render_str },
['X'] = { __printf_arginfo_int, NULL, __printf_render_int },
['a'] = { __printf_arginfo_float, NULL, __printf_render_float },
['c'] = { __printf_arginfo_chr, NULL, __printf_render_chr },
['d'] = { __printf_arginfo_int, NULL, __printf_render_int },
['e'] = { __printf_arginfo_float, NULL, __printf_render_float },
['f'] = { __printf_arginfo_float, NULL, __printf_render_float },
['g'] = { __printf_arginfo_float, NULL, __printf_render_float },
['i'] = { __printf_arginfo_int, NULL, __printf_render_int },
['n'] = { __printf_arginfo_n, __printf_render_n, NULL },
['o'] = { __printf_arginfo_int, NULL, __printf_render_int },
['p'] = { __printf_arginfo_ptr, NULL, __printf_render_ptr },
['q'] = { __printf_arginfo_int, NULL, __printf_render_int },
['s'] = { __printf_arginfo_str, NULL, __printf_render_str },
['u'] = { __printf_arginfo_int, NULL, __printf_render_int },
['x'] = { __printf_arginfo_int, NULL, __printf_render_int },
};
static int
__v2printf(FILE *fp, const char *fmt0, unsigned pct, const va_list ap)
{
struct printf_info *pi, *pil;
const char *fmt;
int ch;
struct printf_info pia[pct + 10];
int argt[pct + 10];
union arg args[pct + 10];
int nextarg;
int maxarg;
int ret = 0;
int n;
struct __printf_io io;
__printf_init(&io);
io.fp = fp;
fmt = fmt0;
maxarg = 0;
nextarg = 1;
memset(argt, 0, sizeof argt);
for (pi = pia; ; pi++) {
memset(pi, 0, sizeof *pi);
pil = pi;
if (*fmt == '\0')
break;
pil = pi + 1;
pi->prec = -1;
pi->pad = ' ';
pi->begin = pi->end = fmt;
while (*fmt != '\0' && *fmt != '%')
pi->end = ++fmt;
if (*fmt == '\0')
break;
fmt++;
for (;;) {
pi->spec = *fmt;
switch (pi->spec) {
case ' ':
/*-
* ``If the space and + flags both appear, the space
* flag will be ignored.''
* -- ANSI X3J11
*/
if (pi->showsign == 0)
pi->showsign = ' ';
fmt++;
continue;
case '#':
pi->alt = 1;
fmt++;
continue;
case '.':
pi->prec = 0;
fmt++;
if (*fmt == '*') {
fmt++;
pi->get_prec = nextarg;
argt[nextarg++] = PA_INT;
continue;
}
while (*fmt != '\0' && is_digit(*fmt)) {
pi->prec *= 10;
pi->prec += to_digit(*fmt);
fmt++;
}
continue;
case '-':
pi->left = 1;
fmt++;
continue;
case '+':
pi->showsign = '+';
fmt++;
continue;
case '*':
fmt++;
pi->get_width = nextarg;
argt[nextarg++] = PA_INT;
continue;
case '%':
fmt++;
break;
case '\'':
pi->group = 1;
fmt++;
continue;
case '0':
/*-
* ``Note that 0 is taken as a flag, not as the
* beginning of a field width.''
* -- ANSI X3J11
*/
pi->pad = '0';
fmt++;
continue;
case '1': case '2': case '3':
case '4': case '5': case '6':
case '7': case '8': case '9':
n = 0;
while (*fmt != '\0' && is_digit(*fmt)) {
n *= 10;
n += to_digit(*fmt);
fmt++;
}
if (*fmt == '$') {
if (nextarg > maxarg)
maxarg = nextarg;
nextarg = n;
fmt++;
} else
pi->width = n;
continue;
case 'D':
case 'O':
case 'U':
pi->spec += ('a' - 'A');
pi->is_intmax = 0;
if (pi->is_long_double || pi->is_quad) {
pi->is_long = 0;
pi->is_long_double = 1;
} else {
pi->is_long = 1;
pi->is_long_double = 0;
}
fmt++;
break;
case 'j':
pi->is_intmax = 1;
fmt++;
continue;
case 'q':
pi->is_long = 0;
pi->is_quad = 1;
fmt++;
continue;
case 'L':
pi->is_long_double = 1;
fmt++;
continue;
case 'h':
fmt++;
if (*fmt == 'h') {
fmt++;
pi->is_char = 1;
} else {
pi->is_short = 1;
}
continue;
case 'l':
fmt++;
if (*fmt == 'l') {
fmt++;
pi->is_long_double = 1;
pi->is_quad = 0;
} else {
pi->is_quad = 0;
pi->is_long = 1;
}
continue;
case 't':
pi->is_ptrdiff = 1;
fmt++;
continue;
case 'z':
pi->is_size = 1;
fmt++;
continue;
default:
fmt++;
break;
}
if (printf_tbl[pi->spec].arginfo == NULL)
errx(1, "arginfo[%c] = NULL", pi->spec);
ch = printf_tbl[pi->spec].arginfo(
pi, __PRINTFMAXARG, &argt[nextarg]);
if (ch > 0)
pi->arg[0] = &args[nextarg];
if (ch > 1)
pi->arg[1] = &args[nextarg + 1];
nextarg += ch;
break;
}
}
if (nextarg > maxarg)
maxarg = nextarg;
#if 0
fprintf(stderr, "fmt0 <%s>\n", fmt0);
fprintf(stderr, "pil %p\n", pil);
#endif
for (ch = 1; ch < maxarg; ch++) {
#if 0
fprintf(stderr, "arg %d %x\n", ch, argt[ch]);
#endif
switch(argt[ch]) {
case PA_CHAR:
args[ch].intarg = (char)va_arg (ap, int);
break;
case PA_INT:
args[ch].intarg = va_arg (ap, int);
break;
case PA_INT | PA_FLAG_SHORT:
args[ch].intarg = (short)va_arg (ap, int);
break;
case PA_INT | PA_FLAG_LONG:
args[ch].longarg = va_arg (ap, long);
break;
case PA_INT | PA_FLAG_INTMAX:
args[ch].intmaxarg = va_arg (ap, intmax_t);
break;
case PA_INT | PA_FLAG_QUAD:
args[ch].intmaxarg = va_arg (ap, quad_t);
break;
case PA_INT | PA_FLAG_LONG_LONG:
args[ch].intmaxarg = va_arg (ap, long long);
break;
case PA_INT | PA_FLAG_SIZE:
args[ch].intmaxarg = va_arg (ap, size_t);
break;
case PA_INT | PA_FLAG_PTRDIFF:
args[ch].intmaxarg = va_arg (ap, ptrdiff_t);
break;
case PA_WCHAR:
args[ch].wintarg = va_arg (ap, wint_t);
break;
case PA_POINTER:
args[ch].pvoidarg = va_arg (ap, void *);
break;
case PA_STRING:
args[ch].pchararg = va_arg (ap, char *);
break;
case PA_WSTRING:
args[ch].pwchararg = va_arg (ap, wchar_t *);
break;
case PA_DOUBLE:
args[ch].doublearg = va_arg (ap, double);
break;
case PA_DOUBLE | PA_FLAG_LONG_DOUBLE:
args[ch].longdoublearg = va_arg (ap, long double);
break;
default:
errx(1, "argtype = %x (fmt = \"%s\")\n",
argt[ch], fmt0);
}
}
for (pi = pia; pi < pil; pi++) {
#if 0
fprintf(stderr, "pi %p", pi);
fprintf(stderr, " spec '%c'", pi->spec);
fprintf(stderr, " args %d",
((uintptr_t)pi->arg[0] - (uintptr_t)args) / sizeof args[0]);
if (pi->width) fprintf(stderr, " width %d", pi->width);
if (pi->pad) fprintf(stderr, " pad 0x%x", pi->pad);
if (pi->left) fprintf(stderr, " left");
if (pi->showsign) fprintf(stderr, " showsign");
if (pi->prec != -1) fprintf(stderr, " prec %d", pi->prec);
if (pi->is_char) fprintf(stderr, " char");
if (pi->is_short) fprintf(stderr, " short");
if (pi->is_long) fprintf(stderr, " long");
if (pi->is_long_double) fprintf(stderr, " long_double");
fprintf(stderr, "\n");
fprintf(stderr, "\t\"%.*s\"\n", pi->end - pi->begin, pi->begin);
#endif
if (pi->get_width) {
pi->width = args[pi->get_width].intarg;
/*-
* ``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.
*/
if (pi->width < 0) {
pi->left = 1;
pi->width = -pi->width;
}
}
if (pi->get_prec)
pi->prec = args[pi->get_prec].intarg;
ret += __printf_puts(&io, pi->begin, pi->end - pi->begin);
if (printf_tbl[pi->spec].gnurender != NULL) {
__printf_flush(&io);
pi->sofar = ret;
ret += printf_tbl[pi->spec].gnurender(
fp, pi, (const void *)pi->arg);
} else if (printf_tbl[pi->spec].render != NULL) {
pi->sofar = ret;
n = printf_tbl[pi->spec].render(
&io, pi, (const void *)pi->arg);
if (n < 0)
io.fp->_flags |= __SERR;
else
ret += n;
} else if (pi->begin == pi->end)
errx(1, "render[%c] = NULL", *fmt);
}
__printf_flush(&io);
return (ret);
}
extern int __fflush(FILE *fp);
/*
* Helper function for `fprintf to unbuffered unix file': creates a
* temporary buffer. We only work on write-only files; this avoids
* worries about ungetc buffers and so forth.
*/
static int
__v3printf(FILE *fp, const char *fmt, int pct, va_list ap)
{
int ret;
FILE fake;
unsigned char buf[BUFSIZ];
/* copy the important variables */
fake._flags = fp->_flags & ~__SNBF;
fake._file = fp->_file;
fake._cookie = fp->_cookie;
fake._write = fp->_write;
fake._extra = fp->_extra;
/* set up the buffer */
fake._bf._base = fake._p = buf;
fake._bf._size = fake._w = sizeof(buf);
fake._lbfsize = 0; /* not actually used, but Just In Case */
/* do the work, then copy any error status */
ret = __v2printf(&fake, fmt, pct, ap);
if (ret >= 0 && __fflush(&fake))
ret = EOF;
if (fake._flags & __SERR)
fp->_flags |= __SERR;
return (ret);
}
int
__xvprintf(FILE *fp, const char *fmt0, va_list ap)
{
unsigned u;
const char *p;
/* Count number of '%' signs handling double '%' signs */
for (p = fmt0, u = 0; *p; p++) {
if (*p != '%')
continue;
u++;
if (p[1] == '%')
p++;
}
/* optimise fprintf(stderr) (and other unbuffered Unix files) */
if ((fp->_flags & (__SNBF|__SWR|__SRW)) == (__SNBF|__SWR) &&
fp->_file >= 0)
return (__v3printf(fp, fmt0, u, ap));
else
return (__v2printf(fp, fmt0, u, ap));
}
/* extending ---------------------------------------------------------*/
int
register_printf_function(int spec, printf_function *render, printf_arginfo_function *arginfo)
{
if (spec > 255 || spec < 0)
return (-1);
printf_tbl[spec].gnurender = render;
printf_tbl[spec].arginfo = arginfo;
__use_xprintf = 1;
return (0);
}
int
register_printf_render(int spec, printf_render *render, printf_arginfo_function *arginfo)
{
if (spec > 255 || spec < 0)
return (-1);
printf_tbl[spec].render = render;
printf_tbl[spec].arginfo = arginfo;
__use_xprintf = 1;
return (0);
}
int
register_printf_render_std(const unsigned char *specs)
{
for (; *specs != '\0'; specs++) {
switch (*specs) {
case 'H':
register_printf_render(*specs,
__printf_render_hexdump,
__printf_arginfo_hexdump);
break;
case 'M':
register_printf_render(*specs,
__printf_render_errno,
__printf_arginfo_errno);
break;
case 'Q':
register_printf_render(*specs,
__printf_render_quote,
__printf_arginfo_quote);
break;
case 'T':
register_printf_render(*specs,
__printf_render_time,
__printf_arginfo_time);
break;
case 'V':
register_printf_render(*specs,
__printf_render_vis,
__printf_arginfo_vis);
break;
default:
return (-1);
}
}
return (0);
}