| /* $NetBSD: vsnprintf_ss.c,v 1.2.2.1 2007/05/07 19:49:09 pavel Exp $ */ | |
| /*- | |
| * 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. | |
| */ | |
| #include <LibConfig.h> | |
| #include <sys/EfiCdefs.h> | |
| #if defined(LIBC_SCCS) && !defined(lint) | |
| #if 0 | |
| static char sccsid[] = "@(#)vsnprintf.c 8.1 (Berkeley) 6/4/93"; | |
| #else | |
| __RCSID("$NetBSD: vsnprintf_ss.c,v 1.2.2.1 2007/05/07 19:49:09 pavel Exp $"); | |
| #endif | |
| #endif /* LIBC_SCCS and not lint */ | |
| #include "namespace.h" | |
| #include <sys/types.h> | |
| #include <inttypes.h> | |
| #include <assert.h> | |
| #include <stdio.h> | |
| #include <errno.h> | |
| #include <stdarg.h> | |
| #include <string.h> | |
| #include "reentrant.h" | |
| #include "extern.h" | |
| #include "local.h" | |
| #ifdef __weak_alias | |
| __weak_alias(vsnprintf_ss,_vsnprintf_ss) | |
| #endif | |
| /* | |
| * vsnprintf_ss: scaled down version of printf(3). | |
| * | |
| * this version based on vfprintf() from libc which was derived from | |
| * software contributed to Berkeley by Chris Torek. | |
| * | |
| */ | |
| /* | |
| * macros for converting digits to letters and vice versa | |
| */ | |
| #define to_digit(c) ((c) - '0') | |
| #define is_digit(c) ((unsigned)to_digit(c) <= 9) | |
| #define to_char(n) (char)((n) + '0') | |
| /* | |
| * flags used during conversion. | |
| */ | |
| #define ALT 0x001 /* alternate form */ | |
| #define HEXPREFIX 0x002 /* add 0x or 0X prefix */ | |
| #define LADJUST 0x004 /* left adjustment */ | |
| #define LONGDBL 0x008 /* long double; unimplemented */ | |
| #define LONGINT 0x010 /* long integer */ | |
| #define QUADINT 0x020 /* quad integer */ | |
| #define SHORTINT 0x040 /* short integer */ | |
| #define MAXINT 0x080 /* intmax_t */ | |
| #define PTRINT 0x100 /* intptr_t */ | |
| #define SIZEINT 0x200 /* size_t */ | |
| #define ZEROPAD 0x400 /* zero (as opposed to blank) pad */ | |
| #define FPT 0x800 /* Floating point number */ | |
| /* | |
| * To extend shorts properly, we need both signed and unsigned | |
| * argument extraction methods. | |
| */ | |
| #define SARG() \ | |
| ((INT64)(flags&MAXINT ? va_arg(ap, intmax_t) : \ | |
| flags&PTRINT ? va_arg(ap, intptr_t) : \ | |
| flags&SIZEINT ? va_arg(ap, ssize_t) : /* XXX */ \ | |
| flags&QUADINT ? va_arg(ap, quad_t) : \ | |
| flags&LONGINT ? va_arg(ap, long) : \ | |
| flags&SHORTINT ? (short)va_arg(ap, int) : \ | |
| va_arg(ap, int))) | |
| #define UARG() \ | |
| ((UINT64)(flags&MAXINT ? va_arg(ap, uintmax_t) : \ | |
| flags&PTRINT ? va_arg(ap, uintptr_t) : \ | |
| flags&SIZEINT ? va_arg(ap, size_t) : \ | |
| flags&QUADINT ? va_arg(ap, u_quad_t) : \ | |
| flags&LONGINT ? va_arg(ap, unsigned long) : \ | |
| flags&SHORTINT ? (u_short)va_arg(ap, int) : \ | |
| va_arg(ap, u_int))) | |
| #define PUTCHAR(C) do { \ | |
| if (sbuf < tailp) \ | |
| *sbuf++ = (C); \ | |
| } while (/*CONSTCOND*/0) | |
| int | |
| vsnprintf_ss(char *sbuf, size_t slen, const char *fmt0, va_list ap) | |
| { | |
| const char *fmt; /* format string */ | |
| int ch; /* character from fmt */ | |
| int n; /* handy integer (short term usage) */ | |
| char *cp; /* handy char pointer (short term usage) */ | |
| int flags; /* flags as above */ | |
| int ret; /* return value accumulator */ | |
| int width; /* width from format (%8d), or 0 */ | |
| int prec; /* precision from format (%.3d), or -1 */ | |
| char sign; /* sign prefix (' ', '+', '-', or \0) */ | |
| u_quad_t _uquad; /* integer arguments %[diouxX] */ | |
| enum { OCT, DEC, HEX } base;/* base for [diouxX] conversion */ | |
| int dprec; /* a copy of prec if [diouxX], 0 otherwise */ | |
| int realsz; /* field size expanded by dprec */ | |
| int size; /* size of converted field or string */ | |
| const char *xdigs; /* digits for [xX] conversion */ | |
| char bf[128]; /* space for %c, %[diouxX] */ | |
| char *tailp; /* tail pointer for snprintf */ | |
| static const char xdigs_lower[16] = "0123456789abcdef"; | |
| static const char xdigs_upper[16] = "0123456789ABCDEF"; | |
| _DIAGASSERT(n == 0 || sbuf != NULL); | |
| _DIAGASSERT(fmt != NULL); | |
| tailp = sbuf + slen; | |
| cp = NULL; /* XXX: shutup gcc */ | |
| size = 0; /* XXX: shutup gcc */ | |
| fmt = fmt0; | |
| ret = 0; | |
| xdigs = NULL; /* XXX: shut up gcc warning */ | |
| /* | |
| * Scan the format for conversions (`%' character). | |
| */ | |
| for (;;) { | |
| while (*fmt != '%' && *fmt) { | |
| ret++; | |
| PUTCHAR(*fmt++); | |
| } | |
| if (*fmt == 0) | |
| goto done; | |
| fmt++; /* skip over '%' */ | |
| flags = 0; | |
| dprec = 0; | |
| width = 0; | |
| prec = -1; | |
| sign = '\0'; | |
| rflag: ch = *fmt++; | |
| reswitch: switch (ch) { | |
| case ' ': | |
| /* | |
| * ``If the space and + flags both appear, the space | |
| * flag will be ignored.'' | |
| * -- ANSI X3J11 | |
| */ | |
| if (!sign) | |
| sign = ' '; | |
| goto rflag; | |
| case '#': | |
| flags |= ALT; | |
| goto rflag; | |
| case '*': | |
| /* | |
| * ``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 ((width = va_arg(ap, int)) >= 0) | |
| goto rflag; | |
| width = -width; | |
| /* FALLTHROUGH */ | |
| case '-': | |
| flags |= LADJUST; | |
| goto rflag; | |
| case '+': | |
| sign = '+'; | |
| goto rflag; | |
| case '.': | |
| if ((ch = *fmt++) == '*') { | |
| n = va_arg(ap, int); | |
| prec = n < 0 ? -1 : n; | |
| goto rflag; | |
| } | |
| n = 0; | |
| while (is_digit(ch)) { | |
| n = 10 * n + to_digit(ch); | |
| ch = *fmt++; | |
| } | |
| prec = n < 0 ? -1 : n; | |
| goto reswitch; | |
| case '0': | |
| /* | |
| * ``Note that 0 is taken as a flag, not as the | |
| * beginning of a field width.'' | |
| * -- ANSI X3J11 | |
| */ | |
| flags |= ZEROPAD; | |
| goto rflag; | |
| case '1': case '2': case '3': case '4': | |
| case '5': case '6': case '7': case '8': case '9': | |
| n = 0; | |
| do { | |
| n = 10 * n + to_digit(ch); | |
| ch = *fmt++; | |
| } while (is_digit(ch)); | |
| width = n; | |
| goto reswitch; | |
| case 'h': | |
| flags |= SHORTINT; | |
| goto rflag; | |
| case 'j': | |
| flags |= MAXINT; | |
| goto rflag; | |
| case 'l': | |
| if (*fmt == 'l') { | |
| fmt++; | |
| flags |= QUADINT; | |
| } else { | |
| flags |= LONGINT; | |
| } | |
| goto rflag; | |
| case 'q': | |
| flags |= QUADINT; | |
| goto rflag; | |
| case 't': | |
| flags |= PTRINT; | |
| goto rflag; | |
| case 'z': | |
| flags |= SIZEINT; | |
| goto rflag; | |
| case 'c': | |
| *(cp = bf) = va_arg(ap, int); | |
| size = 1; | |
| sign = '\0'; | |
| break; | |
| case 'D': | |
| flags |= LONGINT; | |
| /*FALLTHROUGH*/ | |
| case 'd': | |
| case 'i': | |
| _uquad = SARG(); | |
| if ((quad_t)_uquad < 0) { | |
| _uquad = -_uquad; | |
| sign = '-'; | |
| } | |
| base = DEC; | |
| goto number; | |
| case 'n': | |
| if (flags & MAXINT) | |
| *va_arg(ap, intmax_t *) = ret; | |
| else if (flags & PTRINT) | |
| *va_arg(ap, intptr_t *) = ret; | |
| else if (flags & SIZEINT) | |
| *va_arg(ap, ssize_t *) = ret; | |
| else if (flags & QUADINT) | |
| *va_arg(ap, quad_t *) = ret; | |
| else if (flags & LONGINT) | |
| *va_arg(ap, long *) = (long)ret; | |
| else if (flags & SHORTINT) | |
| *va_arg(ap, short *) = (short)ret; | |
| else | |
| *va_arg(ap, int *) = ret; | |
| continue; /* no output */ | |
| case 'O': | |
| flags |= LONGINT; | |
| /*FALLTHROUGH*/ | |
| case 'o': | |
| _uquad = UARG(); | |
| base = OCT; | |
| goto nosign; | |
| case 'p': | |
| /* | |
| * ``The argument shall be a pointer to void. The | |
| * value of the pointer is converted to a sequence | |
| * of printable characters, in an implementation- | |
| * defined manner.'' | |
| * -- ANSI X3J11 | |
| */ | |
| /* NOSTRICT */ | |
| _uquad = (u_long)va_arg(ap, void *); | |
| base = HEX; | |
| xdigs = xdigs_lower; | |
| flags |= HEXPREFIX; | |
| ch = 'x'; | |
| goto nosign; | |
| case 's': | |
| if ((cp = va_arg(ap, char *)) == NULL) | |
| /*XXXUNCONST*/ | |
| cp = __UNCONST("(null)"); | |
| if (prec >= 0) { | |
| /* | |
| * can't use strlen; can only look for the | |
| * NUL in the first `prec' characters, and | |
| * strlen() will go further. | |
| */ | |
| char *p = memchr(cp, 0, (size_t)prec); | |
| if (p != NULL) { | |
| size = p - cp; | |
| if (size > prec) | |
| size = prec; | |
| } else | |
| size = prec; | |
| } else | |
| size = strlen(cp); | |
| sign = '\0'; | |
| break; | |
| case 'U': | |
| flags |= LONGINT; | |
| /*FALLTHROUGH*/ | |
| case 'u': | |
| _uquad = UARG(); | |
| base = DEC; | |
| goto nosign; | |
| case 'X': | |
| xdigs = xdigs_upper; | |
| goto hex; | |
| case 'x': | |
| xdigs = xdigs_lower; | |
| hex: _uquad = UARG(); | |
| base = HEX; | |
| /* leading 0x/X only if non-zero */ | |
| if (flags & ALT && _uquad != 0) | |
| flags |= HEXPREFIX; | |
| /* unsigned conversions */ | |
| nosign: sign = '\0'; | |
| /* | |
| * ``... diouXx conversions ... if a precision is | |
| * specified, the 0 flag will be ignored.'' | |
| * -- ANSI X3J11 | |
| */ | |
| number: if ((dprec = prec) >= 0) | |
| flags &= ~ZEROPAD; | |
| /* | |
| * ``The result of converting a zero value with an | |
| * explicit precision of zero is no characters.'' | |
| * -- ANSI X3J11 | |
| */ | |
| cp = bf + sizeof(bf); | |
| if (_uquad != 0 || prec != 0) { | |
| /* | |
| * Unsigned mod is hard, and unsigned mod | |
| * by a constant is easier than that by | |
| * a variable; hence this switch. | |
| */ | |
| switch (base) { | |
| case OCT: | |
| do { | |
| *--cp = to_char(_uquad & 7); | |
| _uquad >>= 3; | |
| } while (_uquad); | |
| /* handle octal leading 0 */ | |
| if (flags & ALT && *cp != '0') | |
| *--cp = '0'; | |
| break; | |
| case DEC: | |
| /* many numbers are 1 digit */ | |
| while (_uquad >= 10) { | |
| *--cp = to_char(_uquad % 10); | |
| _uquad /= 10; | |
| } | |
| *--cp = to_char(_uquad); | |
| break; | |
| case HEX: | |
| do { | |
| *--cp = xdigs[(size_t)_uquad & 15]; | |
| _uquad >>= 4; | |
| } while (_uquad); | |
| break; | |
| default: | |
| /*XXXUNCONST*/ | |
| cp = __UNCONST("bug bad base"); | |
| size = strlen(cp); | |
| goto skipsize; | |
| } | |
| } | |
| size = bf + sizeof(bf) - cp; | |
| skipsize: | |
| break; | |
| default: /* "%?" prints ?, unless ? is NUL */ | |
| if (ch == '\0') | |
| goto done; | |
| /* pretend it was %c with argument ch */ | |
| cp = bf; | |
| *cp = ch; | |
| size = 1; | |
| sign = '\0'; | |
| break; | |
| } | |
| /* | |
| * All reasonable formats wind up here. At this point, `cp' | |
| * points to a string which (if not flags&LADJUST) should be | |
| * padded out to `width' places. If flags&ZEROPAD, it should | |
| * first be prefixed by any sign or other prefix; otherwise, | |
| * it should be blank padded before the prefix is emitted. | |
| * After any left-hand padding and prefixing, emit zeroes | |
| * required by a decimal [diouxX] precision, then print the | |
| * string proper, then emit zeroes required by any leftover | |
| * floating precision; finally, if LADJUST, pad with blanks. | |
| * | |
| * Compute actual size, so we know how much to pad. | |
| * size excludes decimal prec; realsz includes it. | |
| */ | |
| realsz = dprec > size ? dprec : size; | |
| if (sign) | |
| realsz++; | |
| else if (flags & HEXPREFIX) | |
| realsz+= 2; | |
| /* adjust ret */ | |
| ret += width > realsz ? width : realsz; | |
| /* right-adjusting blank padding */ | |
| if ((flags & (LADJUST|ZEROPAD)) == 0) { | |
| n = width - realsz; | |
| while (n-- > 0) | |
| PUTCHAR(' '); | |
| } | |
| /* prefix */ | |
| if (sign) { | |
| PUTCHAR(sign); | |
| } else if (flags & HEXPREFIX) { | |
| PUTCHAR('0'); | |
| PUTCHAR(ch); | |
| } | |
| /* right-adjusting zero padding */ | |
| if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD) { | |
| n = width - realsz; | |
| while (n-- > 0) | |
| PUTCHAR('0'); | |
| } | |
| /* leading zeroes from decimal precision */ | |
| n = dprec - size; | |
| while (n-- > 0) | |
| PUTCHAR('0'); | |
| /* the string or number proper */ | |
| while (size--) | |
| PUTCHAR(*cp++); | |
| /* left-adjusting padding (always blank) */ | |
| if (flags & LADJUST) { | |
| n = width - realsz; | |
| while (n-- > 0) | |
| PUTCHAR(' '); | |
| } | |
| } | |
| done: | |
| if (sbuf == tailp) | |
| sbuf[-1] = '\0'; | |
| else | |
| *sbuf = '\0'; | |
| return (ret); | |
| /* NOTREACHED */ | |
| } |