diff options
Diffstat (limited to 'libc/stdio/printf_common.h')
-rw-r--r-- | libc/stdio/printf_common.h | 683 |
1 files changed, 683 insertions, 0 deletions
diff --git a/libc/stdio/printf_common.h b/libc/stdio/printf_common.h new file mode 100644 index 000000000..13b424689 --- /dev/null +++ b/libc/stdio/printf_common.h @@ -0,0 +1,683 @@ +/*- + * Copyright (c) 1990 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 <sys/mman.h> +#include <sys/types.h> + +#include <errno.h> +#include <float.h> +#include <langinfo.h> +#include <limits.h> +#include <locale.h> +#include <math.h> +#include <stdarg.h> +#include <stddef.h> +#include <stdint.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <wchar.h> + +#include "fvwrite.h" +#include "gdtoa.h" +#include "local.h" + +union arg { + int intarg; + unsigned int uintarg; + long longarg; + unsigned long ulongarg; + long long longlongarg; + unsigned long long ulonglongarg; + ptrdiff_t ptrdiffarg; + size_t sizearg; + ssize_t ssizearg; + intmax_t intmaxarg; + uintmax_t uintmaxarg; + void* pvoidarg; + char* pchararg; + signed char* pschararg; + short* pshortarg; + int* pintarg; + long* plongarg; + long long* plonglongarg; + ptrdiff_t* pptrdiffarg; + ssize_t* pssizearg; + intmax_t* pintmaxarg; + double doublearg; + long double longdoublearg; + wint_t wintarg; + wchar_t* pwchararg; +}; + +// 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 __sbprintf(FILE* fp, const CHAR_TYPE* fmt, va_list ap) { + FILE fake; + struct __sfileext fakeext; + unsigned char buf[BUFSIZ]; + + _FILEEXT_SETUP(&fake, &fakeext); + /* copy the important variables */ + fake._flags = fp->_flags & ~__SNBF; + fake._file = fp->_file; + fake._cookie = fp->_cookie; + fake._write = fp->_write; + + /* 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 */ + int ret = FUNCTION_NAME(&fake, fmt, ap); + if (ret >= 0 && __sflush(&fake)) ret = EOF; + if (fake._flags & __SERR) fp->_flags |= __SERR; + return ret; +} + +static int __find_arguments(const CHAR_TYPE* fmt0, va_list ap, union arg** argtable, size_t* argtablesiz); +static int __grow_type_table(unsigned char** typetable, int* tablesize); + +#define DEFPREC 6 + +#define to_digit(c) ((c) - '0') +#define is_digit(c) ((unsigned)to_digit(c) <= 9) +#define to_char(n) ((CHAR_TYPE)((n) + '0')) + +template <typename CharT> +static int exponent(CharT* p0, int exp, int fmtch) { + CharT* p = p0; + *p++ = fmtch; + if (exp < 0) { + exp = -exp; + *p++ = '-'; + } else { + *p++ = '+'; + } + + CharT expbuf[MAXEXPDIG]; + CharT* t = expbuf + MAXEXPDIG; + if (exp > 9) { + do { + *--t = to_char(exp % 10); + } while ((exp /= 10) > 9); + *--t = to_char(exp); + for (; t < expbuf + MAXEXPDIG; *p++ = *t++) /* nothing */; + } else { + /* + * Exponents for decimal floating point conversions + * (%[eEgG]) must be at least two characters long, + * whereas exponents for hexadecimal conversions can + * be only one character long. + */ + if (fmtch == 'e' || fmtch == 'E') *p++ = '0'; + *p++ = to_char(exp); + } + return (p - p0); +} + +#define PAD(howmany, with) \ + do { \ + if ((n = (howmany)) > 0) { \ + while (n > PADSIZE) { \ + PRINT(with, PADSIZE); \ + n -= PADSIZE; \ + } \ + PRINT(with, n); \ + } \ + } while (0) + +#define PRINTANDPAD(p, ep, len, with) \ + do { \ + n2 = (ep) - (p); \ + if (n2 > (len)) n2 = (len); \ + if (n2 > 0) PRINT((p), n2); \ + PAD((len) - (n2 > 0 ? n2 : 0), (with)); \ + } while (0) + +/* + * The size of the buffer we use as scratch space for integer + * conversions, among other things. Technically, we would need the + * most space for base 10 conversions with thousands' grouping + * characters between each pair of digits. 100 bytes is a + * conservative overestimate even for a 128-bit uintmax_t. + */ +#define BUF 100 + +#define STATIC_ARG_TBL_SIZE 8 /* Size of static argument table. */ + +/* + * Flags used during conversion. + */ +#define ALT 0x0001 /* alternate form */ +#define LADJUST 0x0004 /* left adjustment */ +#define LONGDBL 0x0008 /* long double */ +#define LONGINT 0x0010 /* long integer */ +#define LLONGINT 0x0020 /* long long integer */ +#define SHORTINT 0x0040 /* short integer */ +#define ZEROPAD 0x0080 /* zero (as opposed to blank) pad */ +#define FPT 0x0100 /* Floating point number */ +#define PTRINT 0x0200 /* (unsigned) ptrdiff_t */ +#define SIZEINT 0x0400 /* (signed) size_t */ +#define CHARINT 0x0800 /* 8 bit integer */ +#define MAXINT 0x1000 /* largest integer size (intmax_t) */ + +/* + * Type ids for argument type table. + */ +#define T_UNUSED 0 +#define T_SHORT 1 +#define T_U_SHORT 2 +#define TP_SHORT 3 +#define T_INT 4 +#define T_U_INT 5 +#define TP_INT 6 +#define T_LONG 7 +#define T_U_LONG 8 +#define TP_LONG 9 +#define T_LLONG 10 +#define T_U_LLONG 11 +#define TP_LLONG 12 +#define T_DOUBLE 13 +#define T_LONG_DOUBLE 14 +#define TP_CHAR 15 +#define TP_VOID 16 +#define T_PTRINT 17 +#define TP_PTRINT 18 +#define T_SIZEINT 19 +#define T_SSIZEINT 20 +#define TP_SSIZEINT 21 +#define T_MAXINT 22 +#define T_MAXUINT 23 +#define TP_MAXINT 24 +#define T_CHAR 25 +#define T_U_CHAR 26 +#define T_WINT 27 +#define TP_WCHAR 28 + +// To extend shorts properly, we need both signed and unsigned +// argument extraction methods. +#define SARG() \ + ((intmax_t)(flags & MAXINT \ + ? GETARG(intmax_t) \ + : flags & LLONGINT \ + ? GETARG(long long) \ + : flags & LONGINT \ + ? GETARG(long) \ + : flags & PTRINT \ + ? GETARG(ptrdiff_t) \ + : flags & SIZEINT \ + ? GETARG(ssize_t) \ + : flags & SHORTINT \ + ? (short)GETARG(int) \ + : flags & CHARINT ? (signed char)GETARG(int) \ + : GETARG(int))) +#define UARG() \ + ((uintmax_t)(flags & MAXINT \ + ? GETARG(uintmax_t) \ + : flags & LLONGINT \ + ? GETARG(unsigned long long) \ + : flags & LONGINT \ + ? GETARG(unsigned long) \ + : flags & PTRINT ? (uintptr_t)GETARG(ptrdiff_t) : /* XXX */ \ + flags & SIZEINT \ + ? GETARG(size_t) \ + : flags & SHORTINT \ + ? (unsigned short)GETARG(int) \ + : flags & CHARINT ? (unsigned char)GETARG(int) \ + : GETARG(unsigned int))) + +// Append a digit to a value and check for overflow. +#define APPEND_DIGIT(val, dig) \ + do { \ + if ((val) > INT_MAX / 10) goto overflow; \ + (val) *= 10; \ + if ((val) > INT_MAX - to_digit((dig))) goto overflow; \ + (val) += to_digit((dig)); \ + } while (0) + +// Get * arguments, including the form *nn$. Preserve the nextarg +// that the argument can be gotten once the type is determined. +#define GETASTER(val) \ + n2 = 0; \ + cp = fmt; \ + while (is_digit(*cp)) { \ + APPEND_DIGIT(n2, *cp); \ + cp++; \ + } \ + if (*cp == '$') { \ + int hold = nextarg; \ + if (argtable == NULL) { \ + argtable = statargtable; \ + if (__find_arguments(fmt0, orgap, &argtable, &argtablesiz) == -1) { \ + ret = -1; \ + goto error; \ + } \ + } \ + nextarg = n2; \ + val = GETARG(int); \ + nextarg = hold; \ + fmt = ++cp; \ + } else { \ + val = GETARG(int); \ + } + +// Get the argument indexed by nextarg. If the argument table is +// built, use it to get the argument. If its not, get the next +// argument (and arguments must be gotten sequentially). +#define GETARG(type) \ + ((argtable != NULL) ? *((type*)(&argtable[nextarg++])) : (nextarg++, va_arg(ap, type))) + +/* + * Find all arguments when a positional parameter is encountered. Returns a + * table, indexed by argument number, of pointers to each arguments. The + * initial argument table should be an array of STATIC_ARG_TBL_SIZE entries. + * It will be replaced with a mmap-ed one if it overflows (malloc cannot be + * used since we are attempting to make snprintf thread safe, and alloca is + * problematic since we have nested functions..) + */ +static int __find_arguments(const CHAR_TYPE* fmt0, va_list ap, union arg** argtable, + size_t* argtablesiz) { + int ch; /* character from fmt */ + int n, n2; /* handy integer (short term usage) */ + int flags; /* flags as above */ + unsigned char* typetable; /* table of types */ + unsigned char stattypetable[STATIC_ARG_TBL_SIZE]; + int tablesize; /* current size of type table */ + int tablemax; /* largest used index in table */ + int nextarg; /* 1-based argument index */ + int ret = 0; /* return value */ + + /* + * Add an argument type to the table, expanding if necessary. + */ +#define ADDTYPE(type) \ + ((nextarg >= tablesize) ? __grow_type_table(&typetable, &tablesize) : 0, \ + (nextarg > tablemax) ? tablemax = nextarg : 0, typetable[nextarg++] = type) + +#define ADDSARG() \ + ((flags & MAXINT) \ + ? ADDTYPE(T_MAXINT) \ + : ((flags & PTRINT) ? ADDTYPE(T_PTRINT) \ + : ((flags & SIZEINT) \ + ? ADDTYPE(T_SSIZEINT) \ + : ((flags & LLONGINT) \ + ? ADDTYPE(T_LLONG) \ + : ((flags & LONGINT) \ + ? ADDTYPE(T_LONG) \ + : ((flags & SHORTINT) \ + ? ADDTYPE(T_SHORT) \ + : ((flags & CHARINT) ? ADDTYPE(T_CHAR) \ + : ADDTYPE(T_INT)))))))) + +#define ADDUARG() \ + ((flags & MAXINT) \ + ? ADDTYPE(T_MAXUINT) \ + : ((flags & PTRINT) \ + ? ADDTYPE(T_PTRINT) \ + : ((flags & SIZEINT) \ + ? ADDTYPE(T_SIZEINT) \ + : ((flags & LLONGINT) \ + ? ADDTYPE(T_U_LLONG) \ + : ((flags & LONGINT) \ + ? ADDTYPE(T_U_LONG) \ + : ((flags & SHORTINT) \ + ? ADDTYPE(T_U_SHORT) \ + : ((flags & CHARINT) ? ADDTYPE(T_U_CHAR) \ + : ADDTYPE(T_U_INT)))))))) + + /* + * Add * arguments to the type array. + */ +#define ADDASTER() \ + n2 = 0; \ + cp = fmt; \ + while (is_digit(*cp)) { \ + APPEND_DIGIT(n2, *cp); \ + cp++; \ + } \ + if (*cp == '$') { \ + int hold = nextarg; \ + nextarg = n2; \ + ADDTYPE(T_INT); \ + nextarg = hold; \ + fmt = ++cp; \ + } else { \ + ADDTYPE(T_INT); \ + } + CHAR_TYPE* fmt = const_cast<CHAR_TYPE*>(fmt0); + CHAR_TYPE* cp; + typetable = stattypetable; + tablesize = STATIC_ARG_TBL_SIZE; + tablemax = 0; + nextarg = 1; + memset(typetable, T_UNUSED, STATIC_ARG_TBL_SIZE); + + /* + * Scan the format for conversions (`%' character). + */ + for (;;) { + for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++) continue; + if (ch == '\0') goto done; + fmt++; /* skip over '%' */ + + flags = 0; + + rflag: + ch = *fmt++; + reswitch: + switch (ch) { + case ' ': + case '#': + case '\'': + goto rflag; + case '*': + ADDASTER(); + goto rflag; + case '-': + case '+': + goto rflag; + case '.': + if ((ch = *fmt++) == '*') { + ADDASTER(); + goto rflag; + } + while (is_digit(ch)) { + ch = *fmt++; + } + goto reswitch; + case '0': + goto rflag; + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + n = 0; + do { + APPEND_DIGIT(n, ch); + ch = *fmt++; + } while (is_digit(ch)); + if (ch == '$') { + nextarg = n; + goto rflag; + } + goto reswitch; + case 'L': + flags |= LONGDBL; + goto rflag; + case 'h': + if (*fmt == 'h') { + fmt++; + flags |= CHARINT; + } else { + flags |= SHORTINT; + } + goto rflag; + case 'j': + flags |= MAXINT; + goto rflag; + case 'l': + if (*fmt == 'l') { + fmt++; + flags |= LLONGINT; + } else { + flags |= LONGINT; + } + goto rflag; + case 'q': + flags |= LLONGINT; + goto rflag; + case 't': + flags |= PTRINT; + goto rflag; + case 'z': + flags |= SIZEINT; + goto rflag; + case 'C': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 'c': + if (flags & LONGINT) + ADDTYPE(T_WINT); + else + ADDTYPE(T_INT); + break; + case 'D': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 'd': + case 'i': + ADDSARG(); + break; + case 'a': + case 'A': + case 'e': + case 'E': + case 'f': + case 'F': + case 'g': + case 'G': + if (flags & LONGDBL) + ADDTYPE(T_LONG_DOUBLE); + else + ADDTYPE(T_DOUBLE); + break; +#ifndef NO_PRINTF_PERCENT_N + case 'n': + if (flags & LLONGINT) + ADDTYPE(TP_LLONG); + else if (flags & LONGINT) + ADDTYPE(TP_LONG); + else if (flags & SHORTINT) + ADDTYPE(TP_SHORT); + else if (flags & PTRINT) + ADDTYPE(TP_PTRINT); + else if (flags & SIZEINT) + ADDTYPE(TP_SSIZEINT); + else if (flags & MAXINT) + ADDTYPE(TP_MAXINT); + else + ADDTYPE(TP_INT); + continue; /* no output */ +#endif /* NO_PRINTF_PERCENT_N */ + case 'O': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 'o': + ADDUARG(); + break; + case 'p': + ADDTYPE(TP_VOID); + break; + case 'S': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 's': + ADDTYPE((flags & LONGINT) ? TP_WCHAR : TP_CHAR); + break; + case 'U': + flags |= LONGINT; + /*FALLTHROUGH*/ + case 'u': + case 'X': + case 'x': + ADDUARG(); + break; + default: /* "%?" prints ?, unless ? is NUL */ + if (ch == '\0') goto done; + break; + } + } +done: + /* + * Build the argument table. + */ + if (tablemax >= STATIC_ARG_TBL_SIZE) { + *argtablesiz = sizeof(union arg) * (tablemax + 1); + *argtable = static_cast<arg*>(mmap(NULL, *argtablesiz, + PROT_WRITE | PROT_READ, + MAP_ANON | MAP_PRIVATE, -1, 0)); + if (*argtable == MAP_FAILED) return -1; + } + + for (n = 1; n <= tablemax; n++) { + switch (typetable[n]) { + case T_UNUSED: + case T_CHAR: + case T_U_CHAR: + case T_SHORT: + case T_U_SHORT: + case T_INT: + (*argtable)[n].intarg = va_arg(ap, int); + break; + case TP_SHORT: + (*argtable)[n].pshortarg = va_arg(ap, short*); + break; + case T_U_INT: + (*argtable)[n].uintarg = va_arg(ap, unsigned int); + break; + case TP_INT: + (*argtable)[n].pintarg = va_arg(ap, int*); + break; + case T_LONG: + (*argtable)[n].longarg = va_arg(ap, long); + break; + case T_U_LONG: + (*argtable)[n].ulongarg = va_arg(ap, unsigned long); + break; + case TP_LONG: + (*argtable)[n].plongarg = va_arg(ap, long*); + break; + case T_LLONG: + (*argtable)[n].longlongarg = va_arg(ap, long long); + break; + case T_U_LLONG: + (*argtable)[n].ulonglongarg = va_arg(ap, unsigned long long); + break; + case TP_LLONG: + (*argtable)[n].plonglongarg = va_arg(ap, long long*); + break; + case T_DOUBLE: + (*argtable)[n].doublearg = va_arg(ap, double); + break; + case T_LONG_DOUBLE: + (*argtable)[n].longdoublearg = va_arg(ap, long double); + break; + case TP_CHAR: + (*argtable)[n].pchararg = va_arg(ap, char*); + break; + case TP_VOID: + (*argtable)[n].pvoidarg = va_arg(ap, void*); + break; + case T_PTRINT: + (*argtable)[n].ptrdiffarg = va_arg(ap, ptrdiff_t); + break; + case TP_PTRINT: + (*argtable)[n].pptrdiffarg = va_arg(ap, ptrdiff_t*); + break; + case T_SIZEINT: + (*argtable)[n].sizearg = va_arg(ap, size_t); + break; + case T_SSIZEINT: + (*argtable)[n].ssizearg = va_arg(ap, ssize_t); + break; + case TP_SSIZEINT: + (*argtable)[n].pssizearg = va_arg(ap, ssize_t*); + break; + case T_MAXINT: + (*argtable)[n].intmaxarg = va_arg(ap, intmax_t); + break; + case T_MAXUINT: + (*argtable)[n].uintmaxarg = va_arg(ap, uintmax_t); + break; + case TP_MAXINT: + (*argtable)[n].pintmaxarg = va_arg(ap, intmax_t*); + break; + case T_WINT: + (*argtable)[n].wintarg = va_arg(ap, wint_t); + break; + case TP_WCHAR: + (*argtable)[n].pwchararg = va_arg(ap, wchar_t*); + break; + } + } + goto finish; + +overflow: + errno = ENOMEM; + ret = -1; + +finish: + if (typetable != NULL && typetable != stattypetable) { + munmap(typetable, *argtablesiz); + typetable = NULL; + } + return (ret); +} + +/* + * Increase the size of the type table. + */ +static int __grow_type_table(unsigned char** typetable, int* tablesize) { + unsigned char* old_table = *typetable; + int new_size = *tablesize * 2; + + if (new_size < getpagesize()) new_size = getpagesize(); + + if (*tablesize == STATIC_ARG_TBL_SIZE) { + *typetable = static_cast<unsigned char*>(mmap(NULL, new_size, + PROT_WRITE | PROT_READ, + MAP_ANON | MAP_PRIVATE, -1, 0)); + if (*typetable == MAP_FAILED) return -1; + bcopy(old_table, *typetable, *tablesize); + } else { + unsigned char* new_table = static_cast<unsigned char*>(mmap(NULL, new_size, + PROT_WRITE | PROT_READ, + MAP_ANON | MAP_PRIVATE, -1, 0)); + if (new_table == MAP_FAILED) return -1; + memmove(new_table, *typetable, *tablesize); + munmap(*typetable, *tablesize); + *typetable = new_table; + } + memset(*typetable + *tablesize, T_UNUSED, (new_size - *tablesize)); + + *tablesize = new_size; + return 0; +} |