/*- * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 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(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(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(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(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; } struct helpers { // Flush out all the vectors defined by the given uio, // then reset it so that it can be reused. static int sprint(FILE* fp, struct __suio* uio) { if (uio->uio_resid == 0) { uio->uio_iovcnt = 0; return 0; } int result = __sfvwrite(fp, uio); uio->uio_resid = 0; uio->uio_iovcnt = 0; return result; } // Convert a wide character string argument for the %ls format to a multibyte // string representation. If not -1, prec specifies the maximum number of // bytes to output, and also means that we can't assume that the wide char // string is null-terminated. static char* wcsconv(wchar_t* wcsarg, int prec) { mbstate_t mbs; char buf[MB_LEN_MAX]; wchar_t* p; char* convbuf; size_t clen, nbytes; // Allocate space for the maximum number of bytes we could output. if (prec < 0) { memset(&mbs, 0, sizeof(mbs)); p = wcsarg; nbytes = wcsrtombs(NULL, (const wchar_t**)&p, 0, &mbs); if (nbytes == (size_t)-1) return NULL; } else { // Optimisation: if the output precision is small enough, // just allocate enough memory for the maximum instead of // scanning the string. if (prec < 128) { nbytes = prec; } else { nbytes = 0; p = wcsarg; memset(&mbs, 0, sizeof(mbs)); for (;;) { clen = wcrtomb(buf, *p++, &mbs); if (clen == 0 || clen == (size_t)-1 || nbytes + clen > (size_t)prec) break; nbytes += clen; } if (clen == (size_t)-1) return NULL; } } if ((convbuf = static_cast(malloc(nbytes + 1))) == NULL) return NULL; // Fill the output buffer. p = wcsarg; memset(&mbs, 0, sizeof(mbs)); if ((nbytes = wcsrtombs(convbuf, (const wchar_t**)&p, nbytes, &mbs)) == (size_t)-1) { free(convbuf); return NULL; } convbuf[nbytes] = '\0'; return convbuf; } // Like __fputwc_unlock, but handles fake string (__SSTR) files properly. // File must already be locked. static wint_t xfputwc(wchar_t wc, FILE* fp) { if ((fp->_flags & __SSTR) == 0) return __fputwc_unlock(wc, fp); char buf[MB_LEN_MAX]; mbstate_t mbs = {}; size_t len = wcrtomb(buf, wc, &mbs); if (len == (size_t)-1) { fp->_flags |= __SERR; errno = EILSEQ; return WEOF; } struct __siov iov; iov.iov_base = buf; iov.iov_len = len; struct __suio uio; uio.uio_iov = &iov; uio.uio_resid = len; uio.uio_iovcnt = 1; return (__sfvwrite(fp, &uio) != EOF ? (wint_t)wc : WEOF); } // Convert a multibyte character string argument for the %s format to a wide // string representation. ``prec'' specifies the maximum number of bytes // to output. If ``prec'' is greater than or equal to zero, we can't assume // that the multibyte character string ends in a null character. // // Returns NULL on failure. // To find out what happened check errno for ENOMEM, EILSEQ and EINVAL. static wchar_t* mbsconv(char* mbsarg, int prec) { mbstate_t mbs; const char* p; size_t insize, nchars, nconv; if (mbsarg == NULL) return NULL; // Supplied argument is a multibyte string; convert it to wide characters first. if (prec >= 0) { // String is not guaranteed to be NUL-terminated. Find the number of characters to print. p = mbsarg; insize = nchars = nconv = 0; bzero(&mbs, sizeof(mbs)); while (nchars != (size_t)prec) { nconv = mbrlen(p, MB_CUR_MAX, &mbs); if (nconv == (size_t)0 || nconv == (size_t)-1 || nconv == (size_t)-2) break; p += nconv; nchars++; insize += nconv; } if (nconv == (size_t)-1 || nconv == (size_t)-2) return (NULL); } else { insize = strlen(mbsarg); } // Allocate buffer for the result and perform the conversion, // converting at most `size' bytes of the input multibyte string to // wide characters for printing. wchar_t* convbuf = static_cast(calloc(insize + 1, sizeof(*convbuf))); if (convbuf == NULL) return NULL; wchar_t* wcp = convbuf; p = mbsarg; bzero(&mbs, sizeof(mbs)); nconv = 0; while (insize != 0) { nconv = mbrtowc(wcp, p, insize, &mbs); if (nconv == 0 || nconv == (size_t)-1 || nconv == (size_t)-2) break; wcp++; p += nconv; insize -= nconv; } if (nconv == (size_t)-1 || nconv == (size_t)-2) { free(convbuf); return NULL; } *wcp = '\0'; return convbuf; } };