xprintf.c   [plain text]


/*-
 * 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: src/lib/libc/stdio/xprintf.c,v 1.9 2010/03/11 17:03:32 jhb Exp $
 */

#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 <errno.h>
#include "un-namespace.h"

//#define MACHTIME
#ifdef MACHTIME
#include <mach/mach_time.h>
#endif // MACHTIME

#ifdef XPRINTF_PERF
#include <libkern/OSAtomic.h>
#endif /* XPRINTF_PERF */

#include "local.h"
#include "xprintf_private.h"
#include "xprintf_domain.h"
#include "fvwrite.h"

/*
 * Defining XPRINTF_DEBUG allows the __private_extern__ variable __use_xprintf
 * to be set so that regular printf variants will use the extensible printf
 * code path.  This is normally off, and is only used to test the extensible
 * printf code in the conformance tests.
 */
#ifdef XPRINTF_DEBUG
#include <unistd.h>
int __use_xprintf = 0;
#endif

/* 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;
#ifdef VECTORS
	VECTORTYPE		vectorarg;
	unsigned char		vuchararg[16];
	signed char		vchararg[16];
	unsigned short		vushortarg[8];
	signed short		vshortarg[8];
	unsigned int		vuintarg[4];
	signed int		vintarg[4];
	float			vfloatarg[4];
#ifdef V64TYPE
	double			vdoublearg[2];
	unsigned long long	vulonglongarg[2];
	long long		vlonglongarg[2];
#endif /* V64TYPE */
#endif /* VECTORS */
};

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

__private_extern__ const char __lowercase_hex[17] = "0123456789abcdef?";	/*lint !e784 */
__private_extern__ 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;
}

__private_extern__ void
__printf_flush(struct __printf_io *io)
{

	__sfvwrite(io->fp, &io->uio);
	__printf_init(io);
}

__private_extern__ int
__printf_puts(struct __printf_io *io, const void *ptr, int len)
{


#if 0
	if (io->fp->_flags & __SERR)
		return (0);
#endif
	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);
}

__private_extern__ 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);
}

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

__private_extern__ int
__printf_arginfo_pct(const struct printf_info *pi __unused, size_t n __unused, int *argt __unused)
{

	return (0);
}

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

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

__private_extern__ 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);
}

/* dynamic array handling  -------------------------------------------------*/
#define ARRAYDELTA 8

struct array {
#ifdef XPRINTF_PERF
	struct array *next;
#endif /* XPRINTF_PERF */
	void *data;
	int itemsize;
	int max;
};

#ifdef XPRINTF_PERF
__private_extern__
#else /* !XPRINTF_PERF */
static
#endif /* !XPRINTF_PERF */
void
arrayfree(struct array *a)
{
	if(a) free(a->data);
}

static void *
arrayget(struct array *a, int i)
{
	if (i >= a->max) {
		int oldsize = a->max * a->itemsize;
		int newmax = i + ARRAYDELTA;
		int newsize = newmax * a->itemsize;
		void *newdata = realloc(a->data, newsize);
		if(!newdata) return NULL;
		bzero(newdata + oldsize, newsize - oldsize);
		a->data = newdata;
		a->max = newmax;
	}
	return a->data + i * a->itemsize;
}

static struct array *
arrayinit(struct array *a, int itemsize)
{
	a->data = CALLOC(ARRAYDELTA, itemsize);
	if(!a->data) return NULL;
	a->itemsize = itemsize;
	a->max = ARRAYDELTA;
	return a;
}

/* dynamic array caching  -------------------------------------------------*/
/*
 * Normally, dynamic array structures are created on the stack, and array
 * itself is freshly allocated, and then freed when no longer needed.  When
 * the XPRINTF_PERF macro is defined, the dynamic array structures associated
 * with all-in-one printf variants are not freed, but store in a cache for
 * later use (dynamic array structures used for compile/execute continue to
 * be freed after they are no longer needed).  This means there should be
 * at most one structure in the cached per thread that actually used the
 * all-in-one printf variant.
 *
 * The amount of memory that is cached is fairly small, totally about 1K
 * for three structures used by a format string using ten conversion
 * specifiers.  This is too small for purgeable memory.
 *
 * However, we do flush these caches in case we every are unable to allocate
 * memory, and retry the allocation, just in case.
 */
#ifdef XPRINTF_PERF
static OSQueueHead arg_type_queue = OS_ATOMIC_QUEUE_INIT;
static OSQueueHead printf_info_queue = OS_ATOMIC_QUEUE_INIT;
static OSQueueHead union_arg_queue = OS_ATOMIC_QUEUE_INIT;

#define DEFINE_DEQUEUE(which, type) \
static struct array * \
which ## _dequeue(void) \
{ \
	struct array *a = (struct array *)OSAtomicDequeue(&which ## _queue, offsetof(struct array, next)); \
 \
	if (a) { \
		bzero(a->data, a->max * a->itemsize); \
		return a; \
	} \
	a = (struct array *)MALLOC(sizeof(*a)); \
	if (!a) return NULL; \
	if (!arrayinit(a, sizeof(type))) { \
		free(a); \
		return NULL; \
	} \
	return a; \
}

#define DEFINE_ENQUEUE(which) \
__private_extern__ void \
which ## _enqueue(struct array *a) \
{ \
	if (!a) return; \
	OSAtomicEnqueue(&which ## _queue, a, offsetof(struct array, next)); \
}

#define DEFINE_FLUSH(which) \
static void \
which ## _flush(void) \
{ \
	struct array *a; \
	while((a = (struct array *)OSAtomicDequeue(&which ## _queue, offsetof(struct array, next))) != NULL) { \
		arrayfree(a); \
		free(a); \
	} \
}

DEFINE_DEQUEUE(arg_type, int)
DEFINE_ENQUEUE(arg_type)
DEFINE_FLUSH(arg_type)
DEFINE_DEQUEUE(printf_info, struct printf_info)
DEFINE_ENQUEUE(printf_info)
DEFINE_FLUSH(printf_info)
DEFINE_DEQUEUE(union_arg, union arg)
DEFINE_ENQUEUE(union_arg)
DEFINE_FLUSH(union_arg)

static void
flush_queues(void)
{
    arg_type_flush();
    printf_info_flush();
    union_arg_flush();
}

__private_extern__ void *
xprintf_calloc(size_t count, size_t size)
{
    void *x = calloc(count, size);
    if(!x) {
	flush_queues();
	x = calloc(count, size);
    }
    return x;
}

__private_extern__ void *
xprintf_malloc(size_t size)
{
    void *x = malloc(size);
    if(!x) {
	flush_queues();
	x = malloc(size);
    }
    return x;
}

#if 0
void
show_queues(void)
{
    struct array *a;
    printf("arg_type:");
    while((a = (struct array *)OSAtomicDequeue(&arg_type_queue, offsetof(struct array, next))) != NULL) printf("\n%p", a);
    printf("\nprintf_info:");
    while((a = (struct array *)OSAtomicDequeue(&printf_info_queue, offsetof(struct array, next))) != NULL) printf("\n%p", a);
    printf("\nunion_arg:");
    while((a = (struct array *)OSAtomicDequeue(&union_arg_queue, offsetof(struct array, next))) != NULL) printf("\n%p", a);
    printf("\n");
}
#endif
#endif /* XPRINTF_PERF */

/* -------------------------------------------------------------------------*/

__private_extern__ int
__printf_comp(printf_comp_t restrict pc, printf_domain_t restrict domain)
{
	struct printf_info	*pi, *pil;
	const char		*fmt;
	int			ch, pii;
	int			*argt;
	int			nextarg;
	int			maxarg;
	int			ret = 0;
	int			n;
#ifndef XPRINTF_PERF
	struct array		piarr, argtarr;
#endif /* XPRINTF_PERF */
	struct array		*pa, *aa;

	fmt = pc->fmt;
	maxarg = 0;
	nextarg = 1;
#ifdef XPRINTF_PERF
	pa = printf_info_dequeue();
#else /* !XPRINTF_PERF */
	pa = arrayinit(&piarr, sizeof(*pi));
#endif /* !XPRINTF_PERF */
	if (!pa) {
#ifdef XPRINTF_PERF
	    flush_queues();
#endif /* XPRINTF_PERF */
	    return EOF;
	}
#ifdef XPRINTF_PERF
	pc->pa = pa;
	aa = arg_type_dequeue();
#else /* !XPRINTF_PERF */
	aa = arrayinit(&argtarr, sizeof(*argt));
#endif /* !XPRINTF_PERF */
	if (!aa) {
		arrayfree(pa);
#ifdef XPRINTF_PERF
		free(pa);
		flush_queues();
#endif /* XPRINTF_PERF */
		return EOF;
	}
#ifdef XPRINTF_PERF
	pc->aa = aa;
#endif /* XPRINTF_PERF */
	for (pii = 0; ; pii++) {
		pi = arrayget(pa, pii);
		if (!pi) {
			ret = EOF;
			goto error;
		}
		pil = pi;
		if (*fmt == '\0')
			break;
		pil = pi + 1;
		pi->prec = -1;
		pi->pad = ' ';
#ifdef VECTORS
		pi->vsep = 'X'; /* Illegal value, changed to defaults later. */
#endif /* VECTORS */
		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->space = 1;
					pi->signchar = ' ';
				}
				fmt++;
				continue;
			case '#':
				pi->alt = 1;
				fmt++;
				continue;
#ifdef VECTORS
			case ',': case ';': case ':': case '_':
				pi->vsep = pi->spec;
				fmt++;
				continue;
#endif /* VECTORS */
			case '.':
				pi->prec = 0;
				fmt++;
				if (*fmt == '*') {
					fmt++;
					/* Look for *nn$ and deal with it */
					n = 0;
					while (*fmt != '\0' && is_digit(*fmt)) {
						n *= 10;
						n += to_digit(*fmt);
						fmt++;
					}
					if (*fmt == '$') {
						if ((n + 1) > maxarg)
							maxarg = (n + 1);
						fmt++;
					} else n = nextarg++;
					pi->get_prec = n;
					argt = (int *)arrayget(aa, n);
					if (!argt) {
						ret = EOF;
						goto error;
					}
					*argt = 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 = 1;
				pi->signchar = '+';
				fmt++;
				continue;
			case '*':
				fmt++;
				/* Look for *nn$ and deal with it */
				n = 0;
				while (*fmt != '\0' && is_digit(*fmt)) {
					n *= 10;
					n += to_digit(*fmt);
					fmt++;
				}
				if (*fmt == '$') {
					if ((n + 1) > maxarg)
						maxarg = (n + 1);
					fmt++;
				} else n = nextarg++;
				pi->get_width = n;
				argt = (int *)arrayget(aa, n);
				if (!argt) {
					ret = EOF;
					goto error;
				}
				*argt = 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;
#if 0
			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;
#endif
			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 'v':
#ifdef VECTORS
				pi->is_vec = 1;
#endif /* VECTORS */
				fmt++;
				continue;
			case 'z':
				pi->is_size = 1;
				fmt++;
				continue;
			default:
				fmt++;
				break;
			}
			if (printf_tbl_in_range(pi->spec)) {
				switch(domain->type[printf_tbl_index(pi->spec)]) {
				/* ignore PRINTF_DOMAIN_UNUSED until later */
				case PRINTF_DOMAIN_FLAG:
					errx(1, "Unexpected flag: %c", pi->spec);
				case PRINTF_DOMAIN_GLIBC_API:
				case PRINTF_DOMAIN_FBSD_API:
					/*
					 * Insure that there are always
					 * __PRINTFMAXARG available.
					 */
					if (!arrayget(aa, nextarg + __PRINTFMAXARG - 1)) {
						ret = EOF;
						goto error;
					}
					pi->context = domain->tbl[printf_tbl_index(pi->spec)].context;
					pi->loc = pc->loc;
					ch = domain->tbl[printf_tbl_index(pi->spec)].arginfo(
					    pi, __PRINTFMAXARG, arrayget(aa, nextarg));
					if (ch > 0)
						pi->arg[0] = (void *)(long)nextarg;
					if (ch > 1)
						pi->arg[1] = (void *)(long)(nextarg + 1);
					nextarg += ch;
					break;
				}
			}
			break;
		}
	}
	if (nextarg > maxarg)
		maxarg = nextarg;
	pc->argt = aa->data;
	pc->pi = pa->data;
	pc->pil = pil;
	pc->maxarg = ch = maxarg;
	if (ch < 1) ch = 1;
#ifdef XPRINTF_PERF
	pc->ua = union_arg_dequeue();
	if (!pc->ua) {
	    ret = EOF;
	    goto error;
	}
	if (!arrayget(pc->ua, ch)) {
	    union_arg_enqueue(pc->ua);
	    ret = EOF;
	    goto error;
	}
	pc->args = pc->ua->data;
#else /* !XPRINTF_PERF */
	pc->args = (union arg *)malloc(ch * sizeof(*pc->args));
	if (!pc->args) {
		ret = EOF;
		goto error;
	}
#endif /* !XPRINTF_PERF */
	for (pi = pc->pi; pi < pil; pi++) {
		if (pi->arg[0]) pi->arg[0] = &pc->args[(long)pi->arg[0]];
		if (pi->arg[1]) pi->arg[1] = &pc->args[(long)pi->arg[1]];
	}
#if 0
	fprintf(stderr, "fmt0 <%s>\n", fmt0);
	fprintf(stderr, "pil %p\n", pil);
#endif
	pc->domain = domain;

	return (ret);
error:
	arrayfree(pa);
	arrayfree(aa);
#ifdef XPRINTF_PERF
	free(pa);
	free(aa);
	flush_queues();
#endif /* XPRINTF_PERF */
	return (ret);
}

__private_extern__ int
__printf_exec(printf_comp_t restrict pc, FILE * restrict fp, va_list ap)
{
	struct printf_info	*pi;
	int			ch;
	int			ret = 0;
	int			n;
	struct __printf_io	io;

	__printf_init(&io);
	io.fp = fp;

	for (ch = 1; ch < pc->maxarg; ch++) {
#if 0
		fprintf(stderr, "arg %d %x\n", ch, pc->argt[ch]);
#endif
		switch(pc->argt[ch]) {
		case PA_CHAR:
			pc->args[ch].intarg = (char)va_arg (ap, int);
			break;
		case PA_INT:
			pc->args[ch].intarg = va_arg (ap, int);
			break;
		case PA_INT | PA_FLAG_SHORT:
			pc->args[ch].intarg = (short)va_arg (ap, int);
			break;
		case PA_INT | PA_FLAG_LONG:
			pc->args[ch].longarg = va_arg (ap, long);
			break;
		case PA_INT | PA_FLAG_INTMAX:
			pc->args[ch].intmaxarg = va_arg (ap, intmax_t);
			break;
		case PA_INT | PA_FLAG_QUAD:
			pc->args[ch].intmaxarg = va_arg (ap, quad_t);
			break;
		case PA_INT | PA_FLAG_LONG_LONG:
			pc->args[ch].intmaxarg = va_arg (ap, long long);
			break;
		case PA_INT | PA_FLAG_SIZE:
			pc->args[ch].intmaxarg = va_arg (ap, size_t);
			break;
		case PA_INT | PA_FLAG_PTRDIFF:
			pc->args[ch].intmaxarg = (unsigned long)va_arg (ap, ptrdiff_t);
			break;
		case PA_WCHAR:
			pc->args[ch].wintarg = va_arg (ap, wint_t);
			break;
		case PA_POINTER:
			pc->args[ch].pvoidarg = va_arg (ap, void *);
			break;
		case PA_STRING:
			pc->args[ch].pchararg = va_arg (ap, char *);
			break;
		case PA_WSTRING:
			pc->args[ch].pwchararg = va_arg (ap, wchar_t *);
			break;
		case PA_DOUBLE:
#ifndef NO_FLOATING_POINT
			pc->args[ch].doublearg = va_arg (ap, double);
#endif
			break;
		case PA_DOUBLE | PA_FLAG_LONG_DOUBLE:
#ifndef NO_FLOATING_POINT
			pc->args[ch].longdoublearg = va_arg (ap, long double);
#endif
			break;
#ifdef VECTORS
		case PA_VECTOR:
			pc->args[ch].vectorarg = va_arg (ap, VECTORTYPE);
			break;
#endif /* VECTORS */
		default:
			errx(1, "argtype = %x (fmt = \"%s\")\n",
			    pc->argt[ch], pc->fmt);
		}
	}
	for (pi = pc->pi; pi < pc->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)pc->args) / sizeof pc->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->signchar) fprintf(stderr, " signchar 0x%x", pi->signchar);
		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 = pc->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 = pc->args[pi->get_prec].intarg;
		ret += __printf_puts(&io, pi->begin, pi->end - pi->begin);
		if (pi->spec) {
			if (!printf_tbl_in_range(pi->spec)) goto unused;
			switch(pc->domain->type[printf_tbl_index(pi->spec)]) {
			case PRINTF_DOMAIN_UNUSED:
		unused:
			{
				char unknown = pi->spec;
				ret += __printf_out(&io, pi, &unknown, 1);
				break;
			}
			case PRINTF_DOMAIN_GLIBC_API:
				__printf_flush(&io);
				pi->sofar = ret;
				ret += ((printf_function *)pc->domain->tbl[printf_tbl_index(pi->spec)].render)(
				    fp, pi, (const void *)pi->arg);
				break;
			case PRINTF_DOMAIN_FBSD_API:
				pi->sofar = ret;
				n = ((printf_render *)pc->domain->tbl[printf_tbl_index(pi->spec)].render)(
				    &io, pi, (const void *)pi->arg);
				if (n < 0)
					io.fp->_flags |= __SERR;
				else
					ret += n;
				break;
			}
		}
	}
	__printf_flush(&io);
	return (ret);
}

__private_extern__ int
__v2printf(printf_comp_t restrict pc, printf_domain_t restrict domain, FILE * restrict fp, locale_t restrict loc, const char * restrict fmt, va_list ap)
{
	struct _printf_compiled spc;
	int ret, saverrno;

	/*
	 * All the printf family (including extensible printf variants) funnel
	 * down to this point.  So we can do common work here, and then fork
	 * out to the appropriate handler.
	 */
	/* sorry, fprintf(read_only_file, "") returns EOF, not 0 */
	if (prepwrite(fp) != 0) {
		errno = EBADF;
		return (EOF);
	}
	ORIENT(fp, -1);

	if (pc == XPRINTF_PLAIN) {
		NORMALIZE_LOCALE(loc);
#ifdef XPRINTF_DEBUG
		if (!__use_xprintf)
#endif
				    return __vfprintf(fp, loc, fmt, ap);
#ifdef XPRINTF_DEBUG
		xprintf_domain_init();
		domain = xprintf_domain_global;
#endif
	} else if (pc) {
	    pthread_mutex_lock(&pc->mutex);
	    pthread_rwlock_rdlock(&pc->domain->rwlock);
	    ret = __printf_exec(pc, fp, ap);
	    saverrno = errno;
	    pthread_rwlock_unlock(&pc->domain->rwlock);
	    pthread_mutex_unlock(&pc->mutex);
	    errno = saverrno;
	    return ret;
	}
	if (!domain) {
		errno = EINVAL;
		return EOF;
	}
	xprintf_domain_init();
	bzero(&spc, sizeof(spc));
	spc.fmt = fmt;
	DEFAULT_CURRENT_LOCALE(loc);
	XL_RETAIN(loc);
	spc.loc = loc;
	/*
	 * We don't need to lock the printf_comp_t mutex, since the
	 * printf_comp_t was just created on the stack, and is private.
	 */
	pthread_rwlock_rdlock(&domain->rwlock);
	if (__printf_comp(&spc, domain) < 0) {
	    saverrno = errno;
	    pthread_rwlock_unlock(&domain->rwlock);
	    XL_RELEASE(loc);
	    errno = saverrno;
	    return EOF;
	}
	ret = __printf_exec(&spc, fp, ap);
	saverrno = errno;
	pthread_rwlock_unlock(&domain->rwlock);
	XL_RELEASE(loc);

#ifdef XPRINTF_PERF
	printf_info_enqueue(spc.pa);
	arg_type_enqueue(spc.aa);
	union_arg_enqueue(spc.ua);
#else /* !XPRINTF_PERF */
	free(spc.pi);
	free(spc.argt);
	free(spc.args);
#endif /* !XPRINTF_PERF */
	errno = saverrno;
	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(printf_comp_t restrict pc, printf_domain_t restrict domain, FILE * restrict fp, locale_t restrict loc, const char * restrict fmt, va_list ap)
{
	int ret;
	FILE fake;
	struct __sFILEX extra;
	unsigned char buf[BUFSIZ];

	fake._extra = &extra;
	INITEXTRA(&fake);

	/* copy the important variables */
	fake._flags = fp->_flags & ~__SNBF;
	fake._file = fp->_file;
	fake._cookie = fp->_cookie;
	fake._write = fp->_write;
	fake._orientation = fp->_orientation;
	fake._mbstate = fp->_mbstate;

	/* 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(pc, domain, &fake, loc, fmt, ap);
	if (ret >= 0 && __fflush(&fake))
		ret = EOF;
	if (fake._flags & __SERR)
		fp->_flags |= __SERR;
	return (ret);
}

__private_extern__ int
__xvprintf(printf_comp_t restrict pc, printf_domain_t restrict domain, FILE * restrict fp, locale_t restrict loc, const char * restrict fmt0, va_list ap)
{
	int ret;

	/* optimise fprintf(stderr) (and other unbuffered Unix files) */
	if ((fp->_flags & (__SNBF|__SWR|__SRW)) == (__SNBF|__SWR) &&
	    fp->_file >= 0)
		ret = __v3printf(pc, domain, fp, loc, fmt0, ap);
	else
		ret = __v2printf(pc, domain, fp, loc, fmt0, ap);
	return ret;
}

/* extending ---------------------------------------------------------*/

// No global domain support
#if 0
int
register_printf_function(int spec, printf_function *render, printf_arginfo_function *arginfo)
{
	return register_printf_domain_function(NULL, spec, render, arginfo);
}

__private_extern__ int
register_printf_render(int spec, printf_render *render, printf_arginfo_function *arginfo)
{
	return register_printf_domain_render(NULL, spec, render, arginfo);
}

int
register_printf_render_std(const char *specs)
{
	return register_printf_domain_render_std(NULL, specs);
}
#endif

#ifdef VECTORS
/* vector support ----------------------------------------------------*/

#define PRINTVECTOR(_io, _pi, _arg, _cnt, _type, _elem, _render, _ret) { \
	int i; \
	_type a, *ap; \
	a = (_type)(_arg)->_elem[0]; \
	ap = &a; \
	(_ret) += _render((_io), (_pi), (const void *)&ap); \
	for(i = 1; i < (_cnt); i++) { \
		(_ret) += __printf_puts((_io), (_pi)->begin, (_pi)->end - (_pi)->begin); \
		a = (_type)(_arg)->_elem[i]; \
		(_ret) += _render((_io), (_pi), (const void *)&ap); \
	} \
}

#define PRINTVECTOR_P(_io, _pi, _arg, _cnt, _elem, _render, _ret) { \
	int i; \
	void * a, *ap; \
	a = (void *)(uintptr_t)(_arg)->_elem[0]; \
	ap = &a; \
	(_ret) += _render((_io), (_pi), (const void *)&ap); \
	for(i = 1; i < (_cnt); i++) { \
		(_ret) += __printf_puts((_io), (_pi)->begin, (_pi)->end - (_pi)->begin); \
		a = (void *)(uintptr_t)(_arg)->_elem[i]; \
		(_ret) += _render((_io), (_pi), (const void *)&ap); \
	} \
}

__private_extern__ int
__xprintf_vector(struct __printf_io *io, const struct printf_info *pi, const void *const *arg)
{
	char vsep;	/* Vector separator character. */
	const union arg *argp;
	int ret = 0;
	struct printf_info info = *pi;

	argp = arg[0];
	vsep = pi->vsep;
	if (vsep == 'X') {
		if (pi->spec == 'c')
			vsep = '\0';
		else
			vsep = ' ';
	}
	info.begin = info.end = &vsep;
	if (vsep) info.end++;
	info.is_vec = 0;

	if (pi->is_short) {
		if (pi->spec == 'p') {
			PRINTVECTOR_P(io, &info, argp, 8, vushortarg, __printf_render_ptr, ret);
		} else {
			PRINTVECTOR(io, &info, argp, 8, unsigned int, vushortarg, __printf_render_int, ret);
		}
	} else if (pi->is_long) {
		info.is_long = 0;
		if (pi->spec == 'p') {
			PRINTVECTOR_P(io, &info, argp, 4, vuintarg, __printf_render_ptr, ret);
		} else {
			PRINTVECTOR(io, &info, argp, 4, unsigned int, vuintarg, __printf_render_int, ret);
		}
#ifdef V64TYPE
	} else if (pi->is_long_double) {
		switch (pi->spec) {
		case 'a':
		case 'A':
		case 'e':
		case 'E':
		case 'f':
		case 'g':
		case 'G':
			info.is_long_double = 0;
			PRINTVECTOR(io, &info, argp, 2, double, vdoublearg, __printf_render_float, ret);
			break;
		case 'p':
			info.is_long_double = 0;
			PRINTVECTOR_P(io, &info, argp, 2, vulonglongarg, __printf_render_ptr, ret);
			break;
		case 'd':
		case 'i':
		case 'u':
		case 'o':
		case 'x':
		case 'X':
			PRINTVECTOR(io, &info, argp, 2, unsigned long long, vulonglongarg, __printf_render_int, ret);
			break;
		default:
			/*
			 * The default case should never
			 * happen.
			 */
		case 'c':
			info.is_long_double = 0;
			PRINTVECTOR(io, &info, argp, 16, unsigned int, vuchararg, __printf_render_chr, ret);
		}
#endif /* V64TYPE */
	} else {
		switch (pi->spec) {
		case 'a':
		case 'A':
		case 'e':
		case 'E':
		case 'f':
		case 'g':
		case 'G':
			PRINTVECTOR(io, &info, argp, 4, double, vfloatarg, __printf_render_float, ret);
			break;
		default:
			/*
			 * The default case should never
			 * happen.
			 */
		case 'p':
			PRINTVECTOR_P(io, &info, argp, 16, vuchararg, __printf_render_ptr, ret);
			break;
		case 'd':
		case 'i':
		case 'u':
		case 'o':
		case 'x':
		case 'X':
			info.is_char = 1;
			PRINTVECTOR(io, &info, argp, 16, unsigned int, vuchararg, __printf_render_int, ret);
			break;
		case 'c':
			PRINTVECTOR(io, &info, argp, 16, unsigned int, vuchararg, __printf_render_chr, ret);
		}
	}
	return ret;
}
#endif /* VECTORS */