xref: /dragonfly/lib/libc/stdio/xprintf.c (revision f746689a)
1 /*-
2  * Copyright (c) 2005 Poul-Henning Kamp
3  * Copyright (c) 1990, 1993
4  *	The Regents of the University of California.  All rights reserved.
5  *
6  * This code is derived from software contributed to Berkeley by
7  * Chris Torek.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  *
33  * $FreeBSD: src/lib/libc/stdio/xprintf.c,v 1.8 2008/05/05 16:03:52 jhb Exp $
34  */
35 
36 #include "namespace.h"
37 #include <err.h>
38 #include <sys/types.h>
39 #include <stdio.h>
40 #include <stddef.h>
41 #include <stdlib.h>
42 #include <locale.h>
43 #include <stdint.h>
44 #include <assert.h>
45 #include <stdarg.h>
46 #include <namespace.h>
47 #include <string.h>
48 #include <wchar.h>
49 #include "un-namespace.h"
50 
51 #include "printf.h"
52 #include "priv_stdio.h"
53 
54 int __use_xprintf = -1;
55 
56 /* private stuff -----------------------------------------------------*/
57 
58 union arg {
59 	int			intarg;
60 	long			longarg;
61 	intmax_t 		intmaxarg;
62 #ifndef NO_FLOATING_POINT
63 	double			doublearg;
64 	long double 		longdoublearg;
65 #endif
66 	wint_t			wintarg;
67 	char			*pchararg;
68 	wchar_t			*pwchararg;
69 	void			*pvoidarg;
70 };
71 
72 /*
73  * Macros for converting digits to letters and vice versa
74  */
75 #define	to_digit(c)	((c) - '0')
76 #define	is_digit(c)	(((unsigned)to_digit(c)) <= 9)
77 
78 /* various globals ---------------------------------------------------*/
79 
80 const char __lowercase_hex[17] = "0123456789abcdef?";	/*lint !e784 */
81 const char __uppercase_hex[17] = "0123456789ABCDEF?";	/*lint !e784 */
82 
83 #define PADSIZE 16
84 static char blanks[PADSIZE] =
85 	 {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '};
86 static char zeroes[PADSIZE] =
87 	 {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'};
88 
89 /* printing and padding functions ------------------------------------*/
90 
91 #define NIOV 8
92 
93 struct __printf_io {
94 	FILE		*fp;
95 	struct __suio	uio;
96 	struct __siov	iov[NIOV];
97 	struct __siov	*iovp;
98 };
99 
100 static void
101 __printf_init(struct __printf_io *io)
102 {
103 
104 	io->uio.uio_iov = io->iovp = &io->iov[0];
105 	io->uio.uio_resid = 0;
106 	io->uio.uio_iovcnt = 0;
107 }
108 
109 void
110 __printf_flush(struct __printf_io *io)
111 {
112 
113 	__sfvwrite(io->fp, &io->uio);
114 	__printf_init(io);
115 }
116 
117 int
118 __printf_puts(struct __printf_io *io, const void *ptr, int len)
119 {
120 
121 	if (io->fp->pub._flags & __SERR)
122 		return (0);
123 	if (len == 0)
124 		return (0);
125 	io->iovp->iov_base = __DECONST(void *, ptr);
126 	io->iovp->iov_len = len;
127 	io->uio.uio_resid += len;
128 	io->iovp++;
129 	io->uio.uio_iovcnt++;
130 	if (io->uio.uio_iovcnt >= NIOV)
131 		__printf_flush(io);
132 	return (len);
133 }
134 
135 int
136 __printf_pad(struct __printf_io *io, int howmany, int zero)
137 {
138 	int n;
139 	const char *with;
140 	int ret = 0;
141 
142 	if (zero)
143 		with = zeroes;
144 	else
145 		with = blanks;
146 
147 	if ((n = (howmany)) > 0) {
148 		while (n > PADSIZE) {
149 			ret += __printf_puts(io, with, PADSIZE);
150 			n -= PADSIZE;
151 		}
152 		ret += __printf_puts(io, with, n);
153 	}
154 	return (ret);
155 }
156 
157 int
158 __printf_out(struct __printf_io *io, const struct printf_info *pi,
159 	     const void *ptr, int len)
160 {
161 	int ret = 0;
162 
163 	if ((!pi->left) && pi->width > len)
164 		ret += __printf_pad(io, pi->width - len, pi->pad == '0');
165 	ret += __printf_puts(io, ptr, len);
166 	if (pi->left && pi->width > len)
167 		ret += __printf_pad(io, pi->width - len, pi->pad == '0');
168 	return (ret);
169 }
170 
171 
172 /* percent handling  -------------------------------------------------*/
173 
174 static int
175 __printf_arginfo_pct(const struct printf_info *pi __unused, size_t n __unused,
176 		     int *argt __unused)
177 {
178 
179 	return (0);
180 }
181 
182 static int
183 __printf_render_pct(struct __printf_io *io,
184 		    const struct printf_info *pi __unused,
185 		    const void *const *arg __unused)
186 {
187 
188 	return (__printf_puts(io, "%", 1));
189 }
190 
191 /* 'n' ---------------------------------------------------------------*/
192 
193 static int
194 __printf_arginfo_n(const struct printf_info *pi, size_t n, int *argt)
195 {
196 
197 	assert(n >= 1);
198 	argt[0] = PA_POINTER;
199 	return (1);
200 }
201 
202 /*
203  * This is a printf_render so that all output has been flushed before it
204  * gets called.
205  */
206 
207 static int
208 __printf_render_n(FILE *io __unused, const struct printf_info *pi,
209 		  const void *const *arg)
210 {
211 
212 	if (pi->is_char)
213 		**((signed char **)arg[0]) = (signed char)pi->sofar;
214 	else if (pi->is_short)
215 		**((short **)arg[0]) = (short)pi->sofar;
216 	else if (pi->is_long)
217 		**((long **)arg[0]) = pi->sofar;
218 	else if (pi->is_long_double)
219 		**((long long **)arg[0]) = pi->sofar;
220 	else if (pi->is_intmax)
221 		**((intmax_t **)arg[0]) = pi->sofar;
222 	else if (pi->is_ptrdiff)
223 		**((ptrdiff_t **)arg[0]) = pi->sofar;
224 	else if (pi->is_quad)
225 		**((quad_t **)arg[0]) = pi->sofar;
226 	else if (pi->is_size)
227 		**((size_t **)arg[0]) = pi->sofar;
228 	else
229 		**((int **)arg[0]) = pi->sofar;
230 
231 	return (0);
232 }
233 
234 /* table -------------------------------------------------------------*/
235 
236 /*lint -esym(785, printf_tbl) */
237 static struct {
238 	printf_arginfo_function	*arginfo;
239 	printf_function		*gnurender;
240 	printf_render		*render;
241 } printf_tbl[256] = {
242 	['%'] = { __printf_arginfo_pct,		NULL,	__printf_render_pct },
243 	['A'] = { __printf_arginfo_float,	NULL,	__printf_render_float },
244 	['C'] = { __printf_arginfo_chr,		NULL,	__printf_render_chr },
245 	['E'] = { __printf_arginfo_float,	NULL,	__printf_render_float },
246 	['F'] = { __printf_arginfo_float,	NULL,	__printf_render_float },
247 	['G'] = { __printf_arginfo_float,	NULL,	__printf_render_float },
248 	['S'] = { __printf_arginfo_str,		NULL,	__printf_render_str },
249 	['X'] = { __printf_arginfo_int,		NULL,	__printf_render_int },
250 	['a'] = { __printf_arginfo_float,	NULL,	__printf_render_float },
251 	['c'] = { __printf_arginfo_chr,		NULL,	__printf_render_chr },
252 	['d'] = { __printf_arginfo_int,		NULL,	__printf_render_int },
253 	['e'] = { __printf_arginfo_float,	NULL,	__printf_render_float },
254 	['f'] = { __printf_arginfo_float,	NULL,	__printf_render_float },
255 	['g'] = { __printf_arginfo_float,	NULL,	__printf_render_float },
256 	['i'] = { __printf_arginfo_int,		NULL,	__printf_render_int },
257 	['n'] = { __printf_arginfo_n,		__printf_render_n, NULL },
258 	['o'] = { __printf_arginfo_int,		NULL,	__printf_render_int },
259 	['p'] = { __printf_arginfo_ptr,		NULL,	__printf_render_ptr },
260 	['q'] = { __printf_arginfo_int,		NULL,	__printf_render_int },
261 	['s'] = { __printf_arginfo_str,		NULL,	__printf_render_str },
262 	['u'] = { __printf_arginfo_int,		NULL,	__printf_render_int },
263 	['x'] = { __printf_arginfo_int,		NULL,	__printf_render_int },
264 };
265 
266 
267 static int
268 __v2printf(FILE *fp, const char *fmt0, unsigned pct, va_list ap)
269 {
270 	struct printf_info	*pi, *pil;
271 	const char		*fmt;
272 	int			ch;
273 	struct printf_info	pia[pct + 10];
274 	int			argt[pct + 10];
275 	union arg		args[pct + 10];
276 	int			nextarg;
277 	int			maxarg;
278 	int			ret = 0;
279 	int			n;
280 	struct __printf_io	io;
281 
282 	__printf_init(&io);
283 	io.fp = fp;
284 
285 	fmt = fmt0;
286 	maxarg = 0;
287 	nextarg = 1;
288 	memset(argt, 0, sizeof argt);
289 	for (pi = pia; ; pi++) {
290 		memset(pi, 0, sizeof *pi);
291 		pil = pi;
292 		if (*fmt == '\0')
293 			break;
294 		pil = pi + 1;
295 		pi->prec = -1;
296 		pi->pad = ' ';
297 		pi->begin = pi->end = fmt;
298 		while (*fmt != '\0' && *fmt != '%')
299 			pi->end = ++fmt;
300 		if (*fmt == '\0')
301 			break;
302 		fmt++;
303 		for (;;) {
304 			pi->spec = *fmt;
305 			switch (pi->spec) {
306 			case ' ':
307 				/*-
308 				 * ``If the space and + flags both appear, the space
309 				 * flag will be ignored.''
310 				 *      -- ANSI X3J11
311 				 */
312 				if (pi->showsign == 0)
313 					pi->showsign = ' ';
314 				fmt++;
315 				continue;
316 			case '#':
317 				pi->alt = 1;
318 				fmt++;
319 				continue;
320 			case '.':
321 				pi->prec = 0;
322 				fmt++;
323 				if (*fmt == '*') {
324 					fmt++;
325 					pi->get_prec = nextarg;
326 					argt[nextarg++] = PA_INT;
327 					continue;
328 				}
329 				while (*fmt != '\0' && is_digit(*fmt)) {
330 					pi->prec *= 10;
331 					pi->prec += to_digit(*fmt);
332 					fmt++;
333 				}
334 				continue;
335 			case '-':
336 				pi->left = 1;
337 				fmt++;
338 				continue;
339 			case '+':
340 				pi->showsign = '+';
341 				fmt++;
342 				continue;
343 			case '*':
344 				fmt++;
345 				pi->get_width = nextarg;
346 				argt[nextarg++] = PA_INT;
347 				continue;
348 			case '%':
349 				fmt++;
350 				break;
351 			case '\'':
352 				pi->group = 1;
353 				fmt++;
354 				continue;
355 			case '0':
356 				/*-
357 				 * ``Note that 0 is taken as a flag, not as the
358 				 * beginning of a field width.''
359 				 *      -- ANSI X3J11
360 				 */
361 				pi->pad = '0';
362 				fmt++;
363 				continue;
364 			case '1': case '2': case '3':
365 			case '4': case '5': case '6':
366 			case '7': case '8': case '9':
367 				n = 0;
368 				while (*fmt != '\0' && is_digit(*fmt)) {
369 					n *= 10;
370 					n += to_digit(*fmt);
371 					fmt++;
372 				}
373 				if (*fmt == '$') {
374 					if (nextarg > maxarg)
375 						maxarg = nextarg;
376 					nextarg = n;
377 					fmt++;
378 				} else {
379 					pi->width = n;
380 				}
381 				continue;
382 			case 'D':
383 			case 'O':
384 			case 'U':
385 				pi->spec += ('a' - 'A');
386 				pi->is_intmax = 0;
387 				if (pi->is_long_double || pi->is_quad) {
388 					pi->is_long = 0;
389 					pi->is_long_double = 1;
390 				} else {
391 					pi->is_long = 1;
392 					pi->is_long_double = 0;
393 				}
394 				fmt++;
395 				break;
396 			case 'j':
397 				pi->is_intmax = 1;
398 				fmt++;
399 				continue;
400 			case 'q':
401 				pi->is_long = 0;
402 				pi->is_quad = 1;
403 				fmt++;
404 				continue;
405 			case 'L':
406 				pi->is_long_double = 1;
407 				fmt++;
408 				continue;
409 			case 'h':
410 				fmt++;
411 				if (*fmt == 'h') {
412 					fmt++;
413 					pi->is_char = 1;
414 				} else {
415 					pi->is_short = 1;
416 				}
417 				continue;
418 			case 'l':
419 				fmt++;
420 				if (*fmt == 'l') {
421 					fmt++;
422 					pi->is_long_double = 1;
423 					pi->is_quad = 0;
424 				} else {
425 					pi->is_quad = 0;
426 					pi->is_long = 1;
427 				}
428 				continue;
429 			case 't':
430 				pi->is_ptrdiff = 1;
431 				fmt++;
432 				continue;
433 			case 'z':
434 				pi->is_size = 1;
435 				fmt++;
436 				continue;
437 			default:
438 				fmt++;
439 				break;
440 			}
441 			if (printf_tbl[pi->spec].arginfo == NULL)
442 				errx(1, "arginfo[%c] = NULL", pi->spec);
443 			ch = printf_tbl[pi->spec].arginfo(
444 			    pi, __PRINTFMAXARG, &argt[nextarg]);
445 			if (ch > 0)
446 				pi->arg[0] = &args[nextarg];
447 			if (ch > 1)
448 				pi->arg[1] = &args[nextarg + 1];
449 			nextarg += ch;
450 			break;
451 		}
452 	}
453 	if (nextarg > maxarg)
454 		maxarg = nextarg;
455 #if 0
456 	fprintf(stderr, "fmt0 <%s>\n", fmt0);
457 	fprintf(stderr, "pil %p\n", pil);
458 #endif
459 	for (ch = 1; ch < maxarg; ch++) {
460 #if 0
461 		fprintf(stderr, "arg %d %x\n", ch, argt[ch]);
462 #endif
463 		switch(argt[ch]) {
464 		case PA_CHAR:
465 			args[ch].intarg = (char)va_arg(ap, int);
466 			break;
467 		case PA_INT:
468 			args[ch].intarg = va_arg(ap, int);
469 			break;
470 		case PA_INT | PA_FLAG_SHORT:
471 			args[ch].intarg = (short)va_arg(ap, int);
472 			break;
473 		case PA_INT | PA_FLAG_LONG:
474 			args[ch].longarg = va_arg(ap, long);
475 			break;
476 		case PA_INT | PA_FLAG_INTMAX:
477 			args[ch].intmaxarg = va_arg(ap, intmax_t);
478 			break;
479 		case PA_INT | PA_FLAG_QUAD:
480 			args[ch].intmaxarg = va_arg(ap, quad_t);
481 			break;
482 		case PA_INT | PA_FLAG_LONG_LONG:
483 			args[ch].intmaxarg = va_arg(ap, long long);
484 			break;
485 		case PA_INT | PA_FLAG_SIZE:
486 			args[ch].intmaxarg = va_arg(ap, size_t);
487 			break;
488 		case PA_INT | PA_FLAG_PTRDIFF:
489 			args[ch].intmaxarg = va_arg(ap, ptrdiff_t);
490 			break;
491 		case PA_WCHAR:
492 			args[ch].wintarg = va_arg(ap, wint_t);
493 			break;
494 		case PA_POINTER:
495 			args[ch].pvoidarg = va_arg(ap, void *);
496 			break;
497 		case PA_STRING:
498 			args[ch].pchararg = va_arg(ap, char *);
499 			break;
500 		case PA_WSTRING:
501 			args[ch].pwchararg = va_arg(ap, wchar_t *);
502 			break;
503 		case PA_DOUBLE:
504 #ifndef NO_FLOATING_POINT
505 			args[ch].doublearg = va_arg(ap, double);
506 #endif
507 			break;
508 		case PA_DOUBLE | PA_FLAG_LONG_DOUBLE:
509 #ifndef NO_FLOATING_POINT
510 			args[ch].longdoublearg = va_arg(ap, long double);
511 #endif
512 			break;
513 		default:
514 			errx(1, "argtype = %x (fmt = \"%s\")\n",
515 			    argt[ch], fmt0);
516 		}
517 	}
518 	for (pi = pia; pi < pil; pi++) {
519 #if 0
520 		fprintf(stderr, "pi %p", pi);
521 		fprintf(stderr, " spec '%c'", pi->spec);
522 		fprintf(stderr, " args %d",
523 		    ((uintptr_t)pi->arg[0] - (uintptr_t)args) / sizeof args[0]);
524 		if (pi->width) fprintf(stderr, " width %d", pi->width);
525 		if (pi->pad) fprintf(stderr, " pad 0x%x", pi->pad);
526 		if (pi->left) fprintf(stderr, " left");
527 		if (pi->showsign) fprintf(stderr, " showsign");
528 		if (pi->prec != -1) fprintf(stderr, " prec %d", pi->prec);
529 		if (pi->is_char) fprintf(stderr, " char");
530 		if (pi->is_short) fprintf(stderr, " short");
531 		if (pi->is_long) fprintf(stderr, " long");
532 		if (pi->is_long_double) fprintf(stderr, " long_double");
533 		fprintf(stderr, "\n");
534 		fprintf(stderr, "\t\"%.*s\"\n", pi->end - pi->begin, pi->begin);
535 #endif
536 		if (pi->get_width) {
537 			pi->width = args[pi->get_width].intarg;
538 			/*-
539 			 * ``A negative field width argument is taken as a
540 			 * - flag followed by a positive field width.''
541 			 *      -- ANSI X3J11
542 			 * They don't exclude field widths read from args.
543 			 */
544 			if (pi->width < 0) {
545 				pi->left = 1;
546 				pi->width = -pi->width;
547 			}
548 		}
549 		if (pi->get_prec)
550 			pi->prec = args[pi->get_prec].intarg;
551 		ret += __printf_puts(&io, pi->begin, pi->end - pi->begin);
552 		if (printf_tbl[pi->spec].gnurender != NULL) {
553 			__printf_flush(&io);
554 			pi->sofar = ret;
555 			ret += printf_tbl[pi->spec].gnurender(
556 			    fp, pi, (const void *)pi->arg);
557 		} else if (printf_tbl[pi->spec].render != NULL) {
558 			pi->sofar = ret;
559 			n = printf_tbl[pi->spec].render(
560 			    &io, pi, (const void *)pi->arg);
561 			if (n < 0)
562 				io.fp->pub._flags |= __SERR;
563 			else
564 				ret += n;
565 		} else if (pi->begin == pi->end)
566 			errx(1, "render[%c] = NULL", *fmt);
567 	}
568 	__printf_flush(&io);
569 	return (ret);
570 }
571 
572 extern int	__fflush(FILE *fp);
573 
574 /*
575  * Helper function for `fprintf to unbuffered unix file': creates a
576  * temporary buffer.  We only work on write-only files; this avoids
577  * worries about ungetc buffers and so forth.
578  */
579 static int
580 __v3printf(FILE *fp, const char *fmt, int pct, va_list ap)
581 {
582 	int ret;
583 	FILE fake;
584 	unsigned char buf[BUFSIZ];
585 
586 	/* copy the important variables */
587 	fake.pub._flags = fp->pub._flags & ~__SNBF;
588 	fake.pub._fileno = fp->pub._fileno;
589 	fake._cookie = fp->_cookie;
590 	fake._write = fp->_write;
591 	memcpy(WCIO_GET(&fake), WCIO_GET(fp), sizeof(struct wchar_io_data));
592 
593 	/* set up the buffer */
594 	fake._bf._base = fake.pub._p = buf;
595 	fake._bf._size = fake.pub._w = sizeof(buf);
596 	fake.pub._lbfsize = 0;	/* not actually used, but Just In Case */
597 
598 	/* do the work, then copy any error status */
599 	ret = __v2printf(&fake, fmt, pct, ap);
600 	if (ret >= 0 && __fflush(&fake))
601 		ret = EOF;
602 	if (fake.pub._flags & __SERR)
603 		fp->pub._flags |= __SERR;
604 	return (ret);
605 }
606 
607 int
608 __xvprintf(FILE *fp, const char *fmt0, va_list ap)
609 {
610 	unsigned u;
611 	const char *p;
612 
613 	/* Count number of '%' signs handling double '%' signs */
614 	for (p = fmt0, u = 0; *p; p++) {
615 		if (*p != '%')
616 			continue;
617 		u++;
618 		if (p[1] == '%')
619 			p++;
620 	}
621 
622 	/* optimise fprintf(stderr) (and other unbuffered Unix files) */
623 	if ((fp->pub._flags & (__SNBF|__SWR|__SRW)) == (__SNBF|__SWR) &&
624 	    fp->pub._fileno >= 0)
625 		return (__v3printf(fp, fmt0, u, ap));
626 	else
627 		return (__v2printf(fp, fmt0, u, ap));
628 }
629 
630 /* extending ---------------------------------------------------------*/
631 
632 int
633 register_printf_function(int spec, printf_function *render,
634 			 printf_arginfo_function *arginfo)
635 {
636 
637 	if (spec > 255 || spec < 0)
638 		return (-1);
639 	printf_tbl[spec].gnurender = render;
640 	printf_tbl[spec].arginfo = arginfo;
641 	__use_xprintf = 1;
642 	return (0);
643 }
644 
645 int
646 register_printf_render(int spec, printf_render *render,
647 		       printf_arginfo_function *arginfo)
648 {
649 
650 	if (spec > 255 || spec < 0)
651 		return (-1);
652 	printf_tbl[spec].render = render;
653 	printf_tbl[spec].arginfo = arginfo;
654 	__use_xprintf = 1;
655 	return (0);
656 }
657 
658 int
659 register_printf_render_std(const unsigned char *specs)
660 {
661 
662 	for (; *specs != '\0'; specs++) {
663 		switch (*specs) {
664 		case 'H':
665 			register_printf_render(*specs,
666 			    __printf_render_hexdump,
667 			    __printf_arginfo_hexdump);
668 			break;
669 		case 'M':
670 			register_printf_render(*specs,
671 			    __printf_render_errno,
672 			    __printf_arginfo_errno);
673 			break;
674 		case 'Q':
675 			register_printf_render(*specs,
676 			    __printf_render_quote,
677 			    __printf_arginfo_quote);
678 			break;
679 		case 'T':
680 			register_printf_render(*specs,
681 			    __printf_render_time,
682 			    __printf_arginfo_time);
683 			break;
684 		case 'V':
685 			register_printf_render(*specs,
686 			    __printf_render_vis,
687 			    __printf_arginfo_vis);
688 			break;
689 		default:
690 			return (-1);
691 		}
692 	}
693 	return (0);
694 }
695