xref: /netbsd-src/lib/libc/stdio/vsnprintf_ss.c (revision 388550b026d49b7f7b7480b1113bf82bb8d6a480)
1*388550b0Srillig /*	$NetBSD: vsnprintf_ss.c,v 1.14 2022/04/19 20:32:16 rillig Exp $	*/
2abca035cSchristos 
3abca035cSchristos /*-
4abca035cSchristos  * Copyright (c) 1990, 1993
5abca035cSchristos  *	The Regents of the University of California.  All rights reserved.
6abca035cSchristos  *
7abca035cSchristos  * This code is derived from software contributed to Berkeley by
8abca035cSchristos  * Chris Torek.
9abca035cSchristos  *
10abca035cSchristos  * Redistribution and use in source and binary forms, with or without
11abca035cSchristos  * modification, are permitted provided that the following conditions
12abca035cSchristos  * are met:
13abca035cSchristos  * 1. Redistributions of source code must retain the above copyright
14abca035cSchristos  *    notice, this list of conditions and the following disclaimer.
15abca035cSchristos  * 2. Redistributions in binary form must reproduce the above copyright
16abca035cSchristos  *    notice, this list of conditions and the following disclaimer in the
17abca035cSchristos  *    documentation and/or other materials provided with the distribution.
18abca035cSchristos  * 3. Neither the name of the University nor the names of its contributors
19abca035cSchristos  *    may be used to endorse or promote products derived from this software
20abca035cSchristos  *    without specific prior written permission.
21abca035cSchristos  *
22abca035cSchristos  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
23abca035cSchristos  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24abca035cSchristos  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25abca035cSchristos  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
26abca035cSchristos  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27abca035cSchristos  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
28abca035cSchristos  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
29abca035cSchristos  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30abca035cSchristos  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
31abca035cSchristos  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32abca035cSchristos  * SUCH DAMAGE.
33abca035cSchristos  */
34abca035cSchristos 
35abca035cSchristos #include <sys/cdefs.h>
36abca035cSchristos #if defined(LIBC_SCCS) && !defined(lint)
37abca035cSchristos #if 0
38abca035cSchristos static char sccsid[] = "@(#)vsnprintf.c	8.1 (Berkeley) 6/4/93";
39abca035cSchristos #else
40*388550b0Srillig __RCSID("$NetBSD: vsnprintf_ss.c,v 1.14 2022/04/19 20:32:16 rillig Exp $");
41abca035cSchristos #endif
42abca035cSchristos #endif /* LIBC_SCCS and not lint */
43abca035cSchristos 
44abca035cSchristos #include "namespace.h"
45abca035cSchristos 
46aa781c37Schristos #include <sys/types.h>
47aa781c37Schristos #include <inttypes.h>
48abca035cSchristos #include <assert.h>
49abca035cSchristos #include <stdio.h>
50aa781c37Schristos #include <errno.h>
51aa781c37Schristos #include <stdarg.h>
52aa781c37Schristos #include <string.h>
53abca035cSchristos #include "reentrant.h"
544ca73ce8Schristos #include "extern.h"
55abca035cSchristos #include "local.h"
56abca035cSchristos 
57abca035cSchristos #ifdef __weak_alias
__weak_alias(vsnprintf_ss,_vsnprintf_ss)58abca035cSchristos __weak_alias(vsnprintf_ss,_vsnprintf_ss)
59abca035cSchristos #endif
60abca035cSchristos 
61aa781c37Schristos /*
62aa781c37Schristos  * vsnprintf_ss: scaled down version of printf(3).
63aa781c37Schristos  *
64aa781c37Schristos  * this version based on vfprintf() from libc which was derived from
65aa781c37Schristos  * software contributed to Berkeley by Chris Torek.
66aa781c37Schristos  *
67aa781c37Schristos  */
68abca035cSchristos 
69aa781c37Schristos /*
70aa781c37Schristos  * macros for converting digits to letters and vice versa
71aa781c37Schristos  */
72aa781c37Schristos #define	to_digit(c)	((c) - '0')
73aa781c37Schristos #define is_digit(c)	((unsigned)to_digit(c) <= 9)
74aa781c37Schristos #define	to_char(n)	(char)((n) + '0')
75aa781c37Schristos 
76aa781c37Schristos /*
77aa781c37Schristos  * flags used during conversion.
78aa781c37Schristos  */
79aa781c37Schristos #define	ALT		0x001		/* alternate form */
80aa781c37Schristos #define	HEXPREFIX	0x002		/* add 0x or 0X prefix */
81aa781c37Schristos #define	LADJUST		0x004		/* left adjustment */
82aa781c37Schristos #define	LONGDBL		0x008		/* long double; unimplemented */
83aa781c37Schristos #define	LONGINT		0x010		/* long integer */
84aa781c37Schristos #define	QUADINT		0x020		/* quad integer */
85aa781c37Schristos #define	SHORTINT	0x040		/* short integer */
86aa781c37Schristos #define	MAXINT		0x080		/* intmax_t */
87aa781c37Schristos #define	PTRINT		0x100		/* intptr_t */
88aa781c37Schristos #define	SIZEINT		0x200		/* size_t */
89aa781c37Schristos #define	ZEROPAD		0x400		/* zero (as opposed to blank) pad */
90aa781c37Schristos #define FPT		0x800		/* Floating point number */
91aa781c37Schristos 
92aa781c37Schristos 	/*
93aa781c37Schristos 	 * To extend shorts properly, we need both signed and unsigned
94aa781c37Schristos 	 * argument extraction methods.
95aa781c37Schristos 	 */
96aa781c37Schristos #define	SARG() \
97aa781c37Schristos 	(flags&MAXINT ? va_arg(ap, intmax_t) : \
98aa781c37Schristos 	    flags&PTRINT ? va_arg(ap, intptr_t) : \
99aa781c37Schristos 	    flags&SIZEINT ? va_arg(ap, ssize_t) : /* XXX */ \
100aa781c37Schristos 	    flags&QUADINT ? va_arg(ap, quad_t) : \
101aa781c37Schristos 	    flags&LONGINT ? va_arg(ap, long) : \
102aa781c37Schristos 	    flags&SHORTINT ? (long)(short)va_arg(ap, int) : \
103aa781c37Schristos 	    (long)va_arg(ap, int))
104aa781c37Schristos #define	UARG() \
105aa781c37Schristos 	(flags&MAXINT ? va_arg(ap, uintmax_t) : \
106aa781c37Schristos 	    flags&PTRINT ? va_arg(ap, uintptr_t) : \
107aa781c37Schristos 	    flags&SIZEINT ? va_arg(ap, size_t) : \
108aa781c37Schristos 	    flags&QUADINT ? va_arg(ap, u_quad_t) : \
109aa781c37Schristos 	    flags&LONGINT ? va_arg(ap, u_long) : \
110aa781c37Schristos 	    flags&SHORTINT ? (u_long)(u_short)va_arg(ap, int) : \
111aa781c37Schristos 	    (u_long)va_arg(ap, u_int))
112aa781c37Schristos 
113aa781c37Schristos #define PUTCHAR(C) do {					\
114aa781c37Schristos 	if (sbuf < tailp)				\
115aa781c37Schristos 		*sbuf++ = (C);				\
116*388550b0Srillig } while (0)
117aa781c37Schristos 
118aa781c37Schristos int
1193eb244d8Sjoerg vsnprintf_ss(char *sbuf, size_t slen, const char *fmt0, va_list ap)
120aa781c37Schristos {
121aa781c37Schristos 	const char *fmt;	/* format string */
122aa781c37Schristos 	int ch;			/* character from fmt */
123aa781c37Schristos 	int n;			/* handy integer (short term usage) */
124aa781c37Schristos 	char *cp;		/* handy char pointer (short term usage) */
125aa781c37Schristos 	int flags;		/* flags as above */
126aa781c37Schristos 	int ret;		/* return value accumulator */
127aa781c37Schristos 	int width;		/* width from format (%8d), or 0 */
128aa781c37Schristos 	int prec;		/* precision from format (%.3d), or -1 */
129aa781c37Schristos 	char sign;		/* sign prefix (' ', '+', '-', or \0) */
130aa781c37Schristos 
131aa781c37Schristos 	u_quad_t _uquad;	/* integer arguments %[diouxX] */
132aa781c37Schristos 	enum { OCT, DEC, HEX } base;/* base for [diouxX] conversion */
133aa781c37Schristos 	int dprec;		/* a copy of prec if [diouxX], 0 otherwise */
134aa781c37Schristos 	int realsz;		/* field size expanded by dprec */
135aa781c37Schristos 	int size;		/* size of converted field or string */
136aa781c37Schristos 	const char *xdigs;	/* digits for [xX] conversion */
137aa781c37Schristos 	char bf[128]; 		/* space for %c, %[diouxX] */
138aa781c37Schristos 	char *tailp;		/* tail pointer for snprintf */
139c5e820caSchristos 	size_t len;
140aa781c37Schristos 
141aa781c37Schristos 	static const char xdigs_lower[16] = "0123456789abcdef";
142aa781c37Schristos 	static const char xdigs_upper[16] = "0123456789ABCDEF";
143aa781c37Schristos 
1445d7be274Schristos 	_DIAGASSERT(slen == 0 || sbuf != NULL);
14572e43063Schristos 	_DIAGASSERT(fmt0 != NULL);
146abca035cSchristos 
1473a55b338Schristos 	if (slen > INT_MAX) {
1483a55b338Schristos 		errno = EOVERFLOW;
149526d9427Schristos 		return -1;
150cca9be70Schristos 	}
151cca9be70Schristos 
152aa781c37Schristos 	tailp = sbuf + slen;
153aa781c37Schristos 
154aa781c37Schristos 	cp = NULL;	/* XXX: shutup gcc */
155aa781c37Schristos 	size = 0;	/* XXX: shutup gcc */
156aa781c37Schristos 
157aa781c37Schristos 	fmt = fmt0;
158aa781c37Schristos 	ret = 0;
159aa781c37Schristos 
160aa781c37Schristos 	xdigs = NULL;		/* XXX: shut up gcc warning */
161aa781c37Schristos 
162aa781c37Schristos 	/*
163aa781c37Schristos 	 * Scan the format for conversions (`%' character).
164aa781c37Schristos 	 */
165aa781c37Schristos 	for (;;) {
166aa781c37Schristos 		while (*fmt != '%' && *fmt) {
167aa781c37Schristos 			ret++;
168b56987f3Schristos 			PUTCHAR(*fmt);
169b56987f3Schristos 			fmt++;
170aa781c37Schristos 		}
171aa781c37Schristos 		if (*fmt == 0)
172aa781c37Schristos 			goto done;
173aa781c37Schristos 
174aa781c37Schristos 		fmt++;		/* skip over '%' */
175aa781c37Schristos 
176aa781c37Schristos 		flags = 0;
177aa781c37Schristos 		dprec = 0;
178aa781c37Schristos 		width = 0;
179aa781c37Schristos 		prec = -1;
180aa781c37Schristos 		sign = '\0';
181aa781c37Schristos 
182aa781c37Schristos rflag:		ch = *fmt++;
183aa781c37Schristos reswitch:	switch (ch) {
184aa781c37Schristos 		case ' ':
185aa781c37Schristos 			/*
186aa781c37Schristos 			 * ``If the space and + flags both appear, the space
187aa781c37Schristos 			 * flag will be ignored.''
188aa781c37Schristos 			 *	-- ANSI X3J11
189aa781c37Schristos 			 */
190aa781c37Schristos 			if (!sign)
191aa781c37Schristos 				sign = ' ';
192aa781c37Schristos 			goto rflag;
193aa781c37Schristos 		case '#':
194aa781c37Schristos 			flags |= ALT;
195aa781c37Schristos 			goto rflag;
196aa781c37Schristos 		case '*':
197aa781c37Schristos 			/*
198aa781c37Schristos 			 * ``A negative field width argument is taken as a
199aa781c37Schristos 			 * - flag followed by a positive field width.''
200aa781c37Schristos 			 *	-- ANSI X3J11
201aa781c37Schristos 			 * They don't exclude field widths read from args.
202aa781c37Schristos 			 */
203aa781c37Schristos 			if ((width = va_arg(ap, int)) >= 0)
204aa781c37Schristos 				goto rflag;
205aa781c37Schristos 			width = -width;
206aa781c37Schristos 			/* FALLTHROUGH */
207aa781c37Schristos 		case '-':
208aa781c37Schristos 			flags |= LADJUST;
209aa781c37Schristos 			goto rflag;
210aa781c37Schristos 		case '+':
211aa781c37Schristos 			sign = '+';
212aa781c37Schristos 			goto rflag;
213aa781c37Schristos 		case '.':
214aa781c37Schristos 			if ((ch = *fmt++) == '*') {
215aa781c37Schristos 				n = va_arg(ap, int);
216aa781c37Schristos 				prec = n < 0 ? -1 : n;
217aa781c37Schristos 				goto rflag;
218aa781c37Schristos 			}
219aa781c37Schristos 			n = 0;
220aa781c37Schristos 			while (is_digit(ch)) {
221aa781c37Schristos 				n = 10 * n + to_digit(ch);
222aa781c37Schristos 				ch = *fmt++;
223aa781c37Schristos 			}
224aa781c37Schristos 			prec = n < 0 ? -1 : n;
225aa781c37Schristos 			goto reswitch;
226aa781c37Schristos 		case '0':
227aa781c37Schristos 			/*
228aa781c37Schristos 			 * ``Note that 0 is taken as a flag, not as the
229aa781c37Schristos 			 * beginning of a field width.''
230aa781c37Schristos 			 *	-- ANSI X3J11
231aa781c37Schristos 			 */
232aa781c37Schristos 			flags |= ZEROPAD;
233aa781c37Schristos 			goto rflag;
234aa781c37Schristos 		case '1': case '2': case '3': case '4':
235aa781c37Schristos 		case '5': case '6': case '7': case '8': case '9':
236aa781c37Schristos 			n = 0;
237aa781c37Schristos 			do {
238aa781c37Schristos 				n = 10 * n + to_digit(ch);
239aa781c37Schristos 				ch = *fmt++;
240aa781c37Schristos 			} while (is_digit(ch));
241aa781c37Schristos 			width = n;
242aa781c37Schristos 			goto reswitch;
243aa781c37Schristos 		case 'h':
244aa781c37Schristos 			flags |= SHORTINT;
245aa781c37Schristos 			goto rflag;
246aa781c37Schristos 		case 'j':
247aa781c37Schristos 			flags |= MAXINT;
248aa781c37Schristos 			goto rflag;
249aa781c37Schristos 		case 'l':
250aa781c37Schristos 			if (*fmt == 'l') {
251aa781c37Schristos 				fmt++;
252aa781c37Schristos 				flags |= QUADINT;
253aa781c37Schristos 			} else {
254aa781c37Schristos 				flags |= LONGINT;
255aa781c37Schristos 			}
256aa781c37Schristos 			goto rflag;
257aa781c37Schristos 		case 'q':
258aa781c37Schristos 			flags |= QUADINT;
259aa781c37Schristos 			goto rflag;
260aa781c37Schristos 		case 't':
261aa781c37Schristos 			flags |= PTRINT;
262aa781c37Schristos 			goto rflag;
263aa781c37Schristos 		case 'z':
264aa781c37Schristos 			flags |= SIZEINT;
265aa781c37Schristos 			goto rflag;
266aa781c37Schristos 		case 'c':
267aa781c37Schristos 			*(cp = bf) = va_arg(ap, int);
268aa781c37Schristos 			size = 1;
269aa781c37Schristos 			sign = '\0';
270aa781c37Schristos 			break;
271aa781c37Schristos 		case 'D':
272aa781c37Schristos 			flags |= LONGINT;
273aa781c37Schristos 			/*FALLTHROUGH*/
274aa781c37Schristos 		case 'd':
275aa781c37Schristos 		case 'i':
276aa781c37Schristos 			_uquad = SARG();
277aa781c37Schristos 			if ((quad_t)_uquad < 0) {
278aa781c37Schristos 				_uquad = -_uquad;
279aa781c37Schristos 				sign = '-';
280aa781c37Schristos 			}
281aa781c37Schristos 			base = DEC;
282aa781c37Schristos 			goto number;
283aa781c37Schristos 		case 'n':
284aa781c37Schristos 			if (flags & MAXINT)
285aa781c37Schristos 				*va_arg(ap, intmax_t *) = ret;
286aa781c37Schristos 			else if (flags & PTRINT)
287aa781c37Schristos 				*va_arg(ap, intptr_t *) = ret;
288aa781c37Schristos 			else if (flags & SIZEINT)
289aa781c37Schristos 				*va_arg(ap, ssize_t *) = ret;
290aa781c37Schristos 			else if (flags & QUADINT)
291aa781c37Schristos 				*va_arg(ap, quad_t *) = ret;
292aa781c37Schristos 			else if (flags & LONGINT)
293aa781c37Schristos 				*va_arg(ap, long *) = ret;
294aa781c37Schristos 			else if (flags & SHORTINT)
295aa781c37Schristos 				*va_arg(ap, short *) = ret;
296aa781c37Schristos 			else
297aa781c37Schristos 				*va_arg(ap, int *) = ret;
298aa781c37Schristos 			continue;	/* no output */
299aa781c37Schristos 		case 'O':
300aa781c37Schristos 			flags |= LONGINT;
301aa781c37Schristos 			/*FALLTHROUGH*/
302aa781c37Schristos 		case 'o':
303aa781c37Schristos 			_uquad = UARG();
304aa781c37Schristos 			base = OCT;
305aa781c37Schristos 			goto nosign;
306aa781c37Schristos 		case 'p':
307aa781c37Schristos 			/*
308aa781c37Schristos 			 * ``The argument shall be a pointer to void.  The
309aa781c37Schristos 			 * value of the pointer is converted to a sequence
310aa781c37Schristos 			 * of printable characters, in an implementation-
311aa781c37Schristos 			 * defined manner.''
312aa781c37Schristos 			 *	-- ANSI X3J11
313aa781c37Schristos 			 */
314aa781c37Schristos 			/* NOSTRICT */
315aa781c37Schristos 			_uquad = (u_long)va_arg(ap, void *);
316aa781c37Schristos 			base = HEX;
317aa781c37Schristos 			xdigs = xdigs_lower;
318aa781c37Schristos 			flags |= HEXPREFIX;
319aa781c37Schristos 			ch = 'x';
320aa781c37Schristos 			goto nosign;
321aa781c37Schristos 		case 's':
322aa781c37Schristos 			if ((cp = va_arg(ap, char *)) == NULL)
323aa781c37Schristos 				/*XXXUNCONST*/
324aa781c37Schristos 				cp = __UNCONST("(null)");
325aa781c37Schristos 			if (prec >= 0) {
326aa781c37Schristos 				/*
327aa781c37Schristos 				 * can't use strlen; can only look for the
328aa781c37Schristos 				 * NUL in the first `prec' characters, and
329aa781c37Schristos 				 * strlen() will go further.
330aa781c37Schristos 				 */
331cfbb35edSchristos 				char *p = memchr(cp, 0, (size_t)prec);
332aa781c37Schristos 
333aa781c37Schristos 				if (p != NULL) {
334c5e820caSchristos 					_DIAGASSERT(__type_fit(int, p - cp));
335c5e820caSchristos 					size = (int)(p - cp);
336aa781c37Schristos 					if (size > prec)
337aa781c37Schristos 						size = prec;
338aa781c37Schristos 				} else
339aa781c37Schristos 					size = prec;
340c5e820caSchristos 			} else {
341c5e820caSchristos 				len = strlen(cp);
342c5e820caSchristos 				_DIAGASSERT(__type_fit(int, len));
343c5e820caSchristos 				size = (int)len;
344c5e820caSchristos 			}
345aa781c37Schristos 			sign = '\0';
346aa781c37Schristos 			break;
347aa781c37Schristos 		case 'U':
348aa781c37Schristos 			flags |= LONGINT;
349aa781c37Schristos 			/*FALLTHROUGH*/
350aa781c37Schristos 		case 'u':
351aa781c37Schristos 			_uquad = UARG();
352aa781c37Schristos 			base = DEC;
353aa781c37Schristos 			goto nosign;
354aa781c37Schristos 		case 'X':
355aa781c37Schristos 			xdigs = xdigs_upper;
356aa781c37Schristos 			goto hex;
357aa781c37Schristos 		case 'x':
358aa781c37Schristos 			xdigs = xdigs_lower;
359aa781c37Schristos hex:			_uquad = UARG();
360aa781c37Schristos 			base = HEX;
361aa781c37Schristos 			/* leading 0x/X only if non-zero */
362aa781c37Schristos 			if (flags & ALT && _uquad != 0)
363aa781c37Schristos 				flags |= HEXPREFIX;
364aa781c37Schristos 
365aa781c37Schristos 			/* unsigned conversions */
366aa781c37Schristos nosign:			sign = '\0';
367aa781c37Schristos 			/*
368aa781c37Schristos 			 * ``... diouXx conversions ... if a precision is
369aa781c37Schristos 			 * specified, the 0 flag will be ignored.''
370aa781c37Schristos 			 *	-- ANSI X3J11
371aa781c37Schristos 			 */
372aa781c37Schristos number:			if ((dprec = prec) >= 0)
373aa781c37Schristos 				flags &= ~ZEROPAD;
374aa781c37Schristos 
375aa781c37Schristos 			/*
376aa781c37Schristos 			 * ``The result of converting a zero value with an
377aa781c37Schristos 			 * explicit precision of zero is no characters.''
378aa781c37Schristos 			 *	-- ANSI X3J11
379aa781c37Schristos 			 */
380aa781c37Schristos 			cp = bf + sizeof(bf);
381aa781c37Schristos 			if (_uquad != 0 || prec != 0) {
382aa781c37Schristos 				/*
383aa781c37Schristos 				 * Unsigned mod is hard, and unsigned mod
384aa781c37Schristos 				 * by a constant is easier than that by
385aa781c37Schristos 				 * a variable; hence this switch.
386aa781c37Schristos 				 */
387aa781c37Schristos 				switch (base) {
388aa781c37Schristos 				case OCT:
389aa781c37Schristos 					do {
390aa781c37Schristos 						*--cp = to_char(_uquad & 7);
391aa781c37Schristos 						_uquad >>= 3;
392aa781c37Schristos 					} while (_uquad);
393aa781c37Schristos 					/* handle octal leading 0 */
394aa781c37Schristos 					if (flags & ALT && *cp != '0')
395aa781c37Schristos 						*--cp = '0';
396aa781c37Schristos 					break;
397aa781c37Schristos 
398aa781c37Schristos 				case DEC:
399aa781c37Schristos 					/* many numbers are 1 digit */
400aa781c37Schristos 					while (_uquad >= 10) {
401aa781c37Schristos 						*--cp = to_char(_uquad % 10);
402aa781c37Schristos 						_uquad /= 10;
403aa781c37Schristos 					}
404aa781c37Schristos 					*--cp = to_char(_uquad);
405aa781c37Schristos 					break;
406aa781c37Schristos 
407aa781c37Schristos 				case HEX:
408aa781c37Schristos 					do {
409cfbb35edSchristos 						*--cp = xdigs[(size_t)_uquad & 15];
410aa781c37Schristos 						_uquad >>= 4;
411aa781c37Schristos 					} while (_uquad);
412aa781c37Schristos 					break;
413aa781c37Schristos 
414aa781c37Schristos 				default:
415aa781c37Schristos 					/*XXXUNCONST*/
416aa781c37Schristos 					cp = __UNCONST("bug bad base");
417c5e820caSchristos 					len = strlen(cp);
418c5e820caSchristos 					_DIAGASSERT(__type_fit(int, len));
419c5e820caSchristos 					size = (int)len;
420aa781c37Schristos 					goto skipsize;
421aa781c37Schristos 				}
422aa781c37Schristos 			}
423c5e820caSchristos 			_DIAGASSERT(__type_fit(int, bf + sizeof(bf) - cp));
424c5e820caSchristos 			size = (int)(bf + sizeof(bf) - cp);
425aa781c37Schristos 		skipsize:
426aa781c37Schristos 			break;
427aa781c37Schristos 		default:	/* "%?" prints ?, unless ? is NUL */
428aa781c37Schristos 			if (ch == '\0')
429aa781c37Schristos 				goto done;
430aa781c37Schristos 			/* pretend it was %c with argument ch */
431aa781c37Schristos 			cp = bf;
432aa781c37Schristos 			*cp = ch;
433aa781c37Schristos 			size = 1;
434aa781c37Schristos 			sign = '\0';
435aa781c37Schristos 			break;
436abca035cSchristos 		}
437abca035cSchristos 
438aa781c37Schristos 		/*
439aa781c37Schristos 		 * All reasonable formats wind up here.  At this point, `cp'
440aa781c37Schristos 		 * points to a string which (if not flags&LADJUST) should be
441aa781c37Schristos 		 * padded out to `width' places.  If flags&ZEROPAD, it should
442aa781c37Schristos 		 * first be prefixed by any sign or other prefix; otherwise,
443aa781c37Schristos 		 * it should be blank padded before the prefix is emitted.
444aa781c37Schristos 		 * After any left-hand padding and prefixing, emit zeroes
445aa781c37Schristos 		 * required by a decimal [diouxX] precision, then print the
446aa781c37Schristos 		 * string proper, then emit zeroes required by any leftover
447aa781c37Schristos 		 * floating precision; finally, if LADJUST, pad with blanks.
448aa781c37Schristos 		 *
449aa781c37Schristos 		 * Compute actual size, so we know how much to pad.
450aa781c37Schristos 		 * size excludes decimal prec; realsz includes it.
451aa781c37Schristos 		 */
452aa781c37Schristos 		realsz = dprec > size ? dprec : size;
453aa781c37Schristos 		if (sign)
454aa781c37Schristos 			realsz++;
455aa781c37Schristos 		else if (flags & HEXPREFIX)
456aa781c37Schristos 			realsz+= 2;
457aa781c37Schristos 
458aa781c37Schristos 		/* adjust ret */
459aa781c37Schristos 		ret += width > realsz ? width : realsz;
460aa781c37Schristos 
461aa781c37Schristos 		/* right-adjusting blank padding */
462aa781c37Schristos 		if ((flags & (LADJUST|ZEROPAD)) == 0) {
463aa781c37Schristos 			n = width - realsz;
464aa781c37Schristos 			while (n-- > 0)
465aa781c37Schristos 				PUTCHAR(' ');
466abca035cSchristos 		}
467aa781c37Schristos 
468aa781c37Schristos 		/* prefix */
469aa781c37Schristos 		if (sign) {
470aa781c37Schristos 			PUTCHAR(sign);
471aa781c37Schristos 		} else if (flags & HEXPREFIX) {
472aa781c37Schristos 			PUTCHAR('0');
473aa781c37Schristos 			PUTCHAR(ch);
474aa781c37Schristos 		}
475aa781c37Schristos 
476aa781c37Schristos 		/* right-adjusting zero padding */
477aa781c37Schristos 		if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD) {
478aa781c37Schristos 			n = width - realsz;
479aa781c37Schristos 			while (n-- > 0)
480aa781c37Schristos 				PUTCHAR('0');
481aa781c37Schristos 		}
482aa781c37Schristos 
483aa781c37Schristos 		/* leading zeroes from decimal precision */
484aa781c37Schristos 		n = dprec - size;
485aa781c37Schristos 		while (n-- > 0)
486aa781c37Schristos 			PUTCHAR('0');
487aa781c37Schristos 
488aa781c37Schristos 		/* the string or number proper */
489aa781c37Schristos 		while (size--)
490aa781c37Schristos 			PUTCHAR(*cp++);
491aa781c37Schristos 		/* left-adjusting padding (always blank) */
492aa781c37Schristos 		if (flags & LADJUST) {
493aa781c37Schristos 			n = width - realsz;
494aa781c37Schristos 			while (n-- > 0)
495aa781c37Schristos 				PUTCHAR(' ');
496aa781c37Schristos 		}
497aa781c37Schristos 	}
498aa781c37Schristos 
499aa781c37Schristos done:
500aa781c37Schristos 	if (sbuf == tailp)
501aa781c37Schristos 		sbuf[-1] = '\0';
502aa781c37Schristos 	else
503aa781c37Schristos 		*sbuf = '\0';
504526d9427Schristos 	return ret;
505aa781c37Schristos 	/* NOTREACHED */
506abca035cSchristos }
507