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