/* * Copyright (c) 2000-2005 Sendmail, Inc. and its suppliers. * All rights reserved. * 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. * * By using this file, you agree to the terms and conditions set * forth in the LICENSE file which can be found at the top level of * the sendmail distribution. */ #include "sm/generic.h" SM_RCSID("@(#)$Id: vfprintf.c,v 1.58 2007/11/18 05:37:01 ca Exp $") #include "sm/varargs.h" #include "sm/assert.h" #include "sm/io.h" #include "sm/memops.h" #include "sm/types.h" #include "sm/ctype.h" #include "sm/string.h" #include "sm/base64.h" #include "sm/str.h" #include "sm/str-int.h" #if !SM_NO_CSTR #include "sm/cstr.h" #endif #include "sm/error.h" #include "sm/heap.h" #include "io-int.h" #include "fvwrite.h" #if MTA_USE_FP_PRINT # include /* for sprintf() */ #endif /* ** Format specifiers ** ** + A IPv4 address ** a - ** B - ** b - [binary?] ** + C sm_cstr ** c char ** D long int (decimal) ** d int (decimal) ** E double float (NOT IMPL) ** e double float (NOT IMPL) ** F - ** f float (NOT IMPL) ** G double float (NOT IMPL) ** g double float (NOT IMPL) ** H - ** h - ** I - ** i int (decimal) ** J - ** j - ** K - ** k - ** L - ** l - ** M error description (do not use width/prec/...) ** m error text ** + N sm_str (does not print trailing '\0') ** n number of characters written so far stored in int * ** O - ** o - ** P - ** p pointer ** Q - ** q - ** R - ** + r if <0: print in hex, otherwise decimal ** this is very useful for sm_ret_T ** + S sm_str ** s char * ** + T sm_str without trailing \r, \n, or \0 ** t - ** U unsigned long int ** u unsigned int ** V - ** v - ** W - ** w - ** X hex (upper case) ** x hex (lower case) ** y - ** + Y base 64 ** y - ** Z base 32 ** z (size_t?) ** ** Note: specifiers marked as '+' are local additions which cause gcc ** to create bogus error messages because it can't be told about them... ** ** C99 modifiers: ** t ptrdiff_t ** z size_t ** Local additions: ** # for string types: "sanitize" string (replace unprintable ** characters by %HEX). ** @ for string types: same as above (#) and replace ',' with %2C ** (useful for logging: ',' is then a unique delimiter). ** If this is changed: grep '%@' in all files. */ /* ** Overall: ** Actual printing innards. ** This code is large and complicated... */ static void sm_find_arguments(const char *, va_list, va_list **); static void sm_grow_type_table(int, uchar **, int *); static int sm_print(sm_file_T *, struct sm_uio *); /* ** SM_PRINT -- print/flush to the file ** ** Flush out all the vectors defined by the given uio, ** then reset it so that it can be reused. ** ** Parameters: ** fp -- file pointer ** uio -- vector list of memory locations of data for printing ** ** Results: ** Success: 0 (zero) ** Failure: */ static int sm_print(sm_file_T *fp, struct sm_uio *uio) { int err; if (uio->uio_resid == 0) { uio->uio_iovcnt = 0; return 0; } err = sm_fvwrite(fp, uio); uio->uio_resid = 0; uio->uio_iovcnt = 0; return err; } /* ** SM_BPRINTF -- allow formating to an unbuffered file. ** ** Helper function for `fprintf to unbuffered unix file': creates a ** temporary buffer (via a "fake" file pointer). ** We only work on write-only files; this avoids ** worries about ungetc buffers and so forth. ** ** Parameters: ** fp -- the file to send the o/p to ** fmt -- format instructions for the o/p ** ap -- vectors of data units used for formating ** ** Results: ** Failure: SM_IO_EOF and errno set ** Success: number of data units used in the formating ** ** Side effects: ** formatted o/p can be SM_IO_BUFSIZ length maximum */ static int sm_bprintf(sm_file_T *fp, const char *fmt, va_list ap) { int ret; sm_file_T fake; uchar buf[SM_IO_BUFSIZ]; /* copy the important variables */ fake.sm_magic = SM_FILE_MAGIC; fake.f_timeout = SM_TIME_FOREVER; f_flags(fake) = f_flags(*fp) & ~SMNBF; f_fd(fake) = f_fd(*fp); fake.f_cookie = fp->f_cookie; f_read(fake) = NULL; f_write(fake) = f_write(*fp); f_close(fake) = NULL; f_open(fake) = NULL; f_seek(fake) = NULL; f_setinfo(fake) = f_getinfo(fake) = NULL; /* set up the buffer */ f_bfbase(fake) = f_p(fake) = buf; f_bfsize(fake) = f_w(fake) = sizeof(buf); /* do the work, then copy any error status */ ret = sm_io_vfprintf(&fake, fmt, ap); if (ret >= 0 && sm_io_flush(&fake)) ret = SM_IO_EOF; /* errno set by sm_io_flush */ if (f_flags(fake) & SMERR) { f_flags(*fp) |= SMERR; #if SM_IO_ERR_VAL f_error(*fp) = f_error(fake); #endif } return ret; } #define BUF 40 #define STATIC_ARG_TBL_SIZE 8 /* Size of static argument table. */ /* Macros for converting digits to letters and vice versa */ #define to_digit(c) ((c) - '0') #define is_digit(c) ((uint) to_digit(c) <= 9) #define to_char(n) ((char) (n) + '0') /* Flags used during conversion. */ #define ALT 0x0001 /* alternate form */ #define HEXPREFIX 0x0002 /* add 0x or 0X prefix */ #define LADJUST 0x0004 /* left adjustment */ #define LONGINT 0x0010 /* long integer */ #define QUADINT 0x0020 /* quad integer */ #define SHORTINT 0x0040 /* short integer */ #define ZEROPAD 0x0080 /* zero (as opposed to blank) pad */ #define SANITIZE 0x0100 /* replace non-printable chars */ #define PTRINT 0x0200 /* (unsigned) ptrdiff_t */ #define SIZEINT 0x0400 /* (signed) size_t */ /* ** SM_IO_VPRINTF -- performs actual formating for o/p ** ** Parameters: ** fp -- file pointer for o/p ** timeout -- time to complete the print ** fmt0 -- formating directives ** ap -- vectors with data units for formating ** ** Results: ** Success: number of data units used for formatting ** Failure: SM_IO_EOF and sets errno */ int sm_io_vfprintf(sm_file_T *fp, const char *fmt0, va_list ap) { const char *fmt; /* format string */ int ch; /* character from fmt */ int n, m, n2; /* handy integers (short term usage) */ const char *cp; /* handy const char pointer (short term usage) */ char *wcp; /* handy char pointer (short term usage) */ struct sm_iov *iovp; /* for PRINT macro */ 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 */ int delim; /* delimiter to be encoded */ char sign; /* sign prefix (' ', '+', '-', or \0) */ wchar_t wc; ulonglong_T _uquad; /* integer arguments %[diouxX] */ enum { OCT, DEC, HEX, BASE32, BASE64 } 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 */ int osize; /* original size before sanitizing */ char *xdigs="0123456789abcdef"; /* digits for [xX] conversion */ #define SM_B32DIGS "0123456789ABCDEFGHIJKLMNOPQRSTUV" const char *b64digs = SM_B64DIGS; const char *b32digs = SM_B32DIGS; #define NIOV 8 struct sm_uio uio; /* output information: summary */ struct sm_iov iov[NIOV];/* ... and individual io vectors */ char buf[BUF]; /* space for %c, %[diouxX], %[eEfgG] */ char ox[2]; /* space for 0x hex-prefix */ va_list *argtable; /* args, built due to positional arg */ va_list statargtable[STATIC_ARG_TBL_SIZE]; int nextarg; /* 1-based argument index */ sm_str_P str; #if !SM_NO_CSTR sm_cstr_P cstr; #endif va_list orgap; /* original argument pointer */ #define OKTOPRINT(ch, delim) (ISPRINT(ch) && (ch) != '%' && (ch) != (delim)) #define SANITIZE_SIZE(delim) do { \ if (flags & SANITIZE) { \ osize = size; \ for (m = 0; m < osize; m++) { \ n2 = cp[m]; \ if (!OKTOPRINT(n2, delim)) \ size += 2; \ } \ if (prec >= 0 && size > prec) \ size = prec; \ } \ } while (0) /* ** Choose PADSIZE to trade efficiency vs. size. If larger printf ** fields occur frequently, increase PADSIZE and make the initialisers ** below longer. */ #define PADSIZE 16 /* pad chunk size */ static char blanks[PADSIZE] = {' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' ',' '}; static char zeroes[PADSIZE] = {'0','0','0','0','0','0','0','0','0','0','0','0','0','0','0','0'}; /* ** BEWARE, these `goto error' on error, and PAD uses `n'. */ #define PRINT(ptr, len) do { \ iovp->iov_base = (void *)(ptr); \ iovp->iov_len = (len); \ uio.uio_resid += (len); \ iovp++; \ if (++uio.uio_iovcnt >= NIOV) \ { \ if (sm_print(fp, &uio)) \ goto error; \ iovp = iov; \ } \ } while (0) #define PAD(howmany, with) do \ { \ if ((n = (howmany)) > 0) \ { \ while (n > PADSIZE) { \ PRINT(with, PADSIZE); \ n -= PADSIZE; \ } \ PRINT(with, n); \ } \ } while (0) #define FLUSH() do \ { \ if (uio.uio_resid && sm_print(fp, &uio)) \ goto error; \ uio.uio_iovcnt = 0; \ iovp = iov; \ } while (0) /* ** To extend shorts properly, we need both signed and unsigned ** argument extraction methods. */ #define SARG() \ (flags&QUADINT ? va_arg(ap, longlong_T) : \ flags&LONGINT ? GETARG(long) : \ flags&PTRINT ? GETARG(ptrdiff_t) : \ flags&SIZEINT ? GETARG(ssize_t) : \ flags&SHORTINT ? (long) (short) GETARG(int) : \ (long) GETARG(int)) /* ptrdiff_t should be cast to an unsigned type here, but which? ulong? */ #define UARG() \ (flags&QUADINT ? va_arg(ap, ulonglong_T) : \ flags&LONGINT ? GETARG(ulong) : \ flags&PTRINT ? GETARG(ptrdiff_t) : /* XXX */ \ flags&SIZEINT ? GETARG(size_t) : \ flags&SHORTINT ? (ulong) (ushort) GETARG(int) : \ (ulong) GETARG(uint)) /* ** 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)) \ { \ n2 = 10 * n2 + to_digit(*cp); \ cp++; \ } \ if (*cp == '$') \ { \ int hold = nextarg; \ if (argtable == NULL) \ { \ argtable = statargtable; \ sm_find_arguments(fmt0, orgap, &argtable); \ } \ 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) ? (void) (ap = argtable[nextarg]) : (void) 0), \ nextarg++, va_arg(ap, type)) /* sorry, fprintf(read_only_file, "") returns SM_IO_EOF, not 0 */ if (cantwrite(fp)) { errno = EBADF; return SM_IO_EOF; } /* optimise fprintf(stderr) (and other unbuffered Unix files) */ if ((f_flags(*fp) & (SMNBF|SMWR|SMRW)) == (SMNBF|SMWR) && f_fd(*fp) >= 0) return sm_bprintf(fp, fmt0, ap); fmt = fmt0; argtable = NULL; nextarg = 1; SM_VA_COPY(orgap, ap); uio.uio_iov = iovp = iov; uio.uio_resid = 0; uio.uio_iovcnt = 0; ret = 0; /* Scan the format for conversions (`%' character). */ for (;;) { cp = fmt; n = 0; delim = 0; while ((wc = *fmt) != '\0') { if (wc == '%') { n = 1; break; } fmt++; } if ((m = fmt - cp) != 0) { PRINT(cp, m); ret += m; } if (n <= 0) goto done; fmt++; /* skip over '%' */ flags = 0; dprec = 0; width = 0; prec = -1; sign = '\0'; osize = 0; /* bogus "might be used uninitialized" */ 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 '@': flags |= ALT; delim = ','; 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. */ GETASTER(width); if (width >= 0) goto rflag; width = -width; /* FALLTHROUGH */ case '-': flags |= LADJUST; goto rflag; case '+': sign = '+'; goto rflag; case '.': if ((ch = *fmt++) == '*') { GETASTER(n); prec = n < 0 ? -1 : n; goto rflag; } n = 0; while (is_digit(ch)) { n = 10 * n + to_digit(ch); ch = *fmt++; } if (ch == '$') { nextarg = n; if (argtable == NULL) { argtable = statargtable; sm_find_arguments(fmt0, orgap, &argtable); } goto rflag; } 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)); if (ch == '$') { nextarg = n; if (argtable == NULL) { argtable = statargtable; sm_find_arguments(fmt0, orgap, &argtable); } goto rflag; } width = n; goto reswitch; case 'h': flags |= SHORTINT; 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': *buf = GETARG(int); cp = buf; size = 1; sign = '\0'; if (flags & ALT) { flags |= SANITIZE; SANITIZE_SIZE(delim); } break; case 'D': flags |= LONGINT; /*FALLTHROUGH*/ case 'd': case 'i': _uquad = SARG(); if ((longlong_T) _uquad < 0) { _uquad = -(longlong_T) _uquad; sign = '-'; } base = DEC; goto number; case 'e': case 'E': case 'f': case 'g': case 'G': #if MTA_USE_FP_PRINT { double val; char *p; char fmt[16]; char out[150]; size_t len; /* ** This code implements floating point output ** in the most portable manner possible, ** relying only on 'sprintf' as defined by ** the 1989 ANSI C standard. ** We silently cap width and precision ** at 120, to avoid buffer overflow. */ val = GETARG(double); p = fmt; *p++ = '%'; if (sign) *p++ = sign; if (flags & ALT) *p++ = '#'; if (flags & LADJUST) *p++ = '-'; if (flags & ZEROPAD) *p++ = '0'; *p++ = '*'; if (prec >= 0) { *p++ = '.'; *p++ = '*'; } *p++ = ch; *p = '\0'; if (width > 120) width = 120; if (prec > 120) prec = 120; if (prec >= 0) sprintf(out, fmt, width, prec, val); else sprintf(out, fmt, width, val); len = strlen(out); PRINT(out, len); FLUSH(); continue; } #else /* MTA_USE_FP_PRINT */ /* fixme: Complain about unsupported format?? */ #define FPNOTSUPP "FPnotsupp" PRINT(FPNOTSUPP, strlen(FPNOTSUPP)); FLUSH(); continue; #endif /* MTA_USE_FP_PRINT */ case 'n': if (flags & QUADINT) *GETARG(longlong_T *) = ret; else if (flags & LONGINT) *GETARG(long *) = ret; else if (flags & SHORTINT) *GETARG(short *) = ret; else if (flags & PTRINT) *GETARG(ptrdiff_t *) = ret; else if (flags & SIZEINT) *GETARG(ssize_t *) = ret; else *GETARG(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 */ { union { void *p; ulonglong_T ll; ulong l; uint i; } u; u.p = GETARG(void *); if (sizeof(void *) == sizeof(ulonglong_T)) _uquad = u.ll; else if (sizeof(void *) == sizeof(long)) _uquad = u.l; else _uquad = u.i; } base = HEX; xdigs = "0123456789abcdef"; flags |= HEXPREFIX; ch = 'x'; goto nosign; case 'M': _uquad = SARG(); if ((longlong_T) _uquad >= 0) { base = DEC; goto number; } else if (prec < 0) { sm_error_T err; err = (sm_error_T) _uquad; cp = smerr2txt(err); if (cp == NULL) cp = "unknown_error"; size = strlen(cp); PRINT(cp, size); cp = ", module="; size = strlen(cp); PRINT(cp, size); cp = smmod2txt(err); if (cp == NULL) cp = "unknown_module"; size = strlen(cp); /* module/type?? */ goto prtstring; } else /* if (prec >= 0) */ { sm_error_T err; char *p; /* ** this is a hack... ** it doesn't deal with prec/width/... properly ** just don't use those with %M */ err = (sm_error_T) _uquad; cp = smerr2txt(err); if (cp == NULL) cp = "unknown_error"; realsz = strlen(cp); if (realsz <= prec) { PRINT(cp, realsz); prec -= realsz; cp = ", module="; size = strlen(cp); if (size >= prec) { prec = 0; ret += realsz; goto flushit; } PRINT(cp, size); prec -= size; cp = smmod2txt(err); if (cp == NULL) cp = "unknown_module"; size = strlen(cp); /* module/type?? */ goto prtstring; } else { /* not enough space */ p = memchr(cp, 0, prec); if (p != NULL) { size = p - cp; if (size > prec) size = prec; } else size = prec; } } sign = '\0'; break; case 'm': _uquad = SARG(); if ((longlong_T) _uquad >= 0) { base = DEC; goto number; } cp = smerr2txt((sm_ret_T) _uquad); if (cp == NULL || strcmp(cp, SM_ETXT_UNKNOWN) == 0) { _uquad &= (ulonglong_T) UINT32_MAX; base = HEX; xdigs = "0123456789abcdef"; ch = 'x'; flags |= HEXPREFIX; goto nosign; } goto prtstring; case 's': if ((cp = GETARG(char *)) == NULL) cp = "(null)"; else if (flags & ALT) flags |= SANITIZE; prtstring: 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, prec); if (p != NULL) { size = p - cp; if (size > prec) size = prec; } else size = prec; } else size = strlen(cp); SANITIZE_SIZE(delim); sign = '\0'; break; case 'A': { uchar *src; uint32_t u32; /* XXX right size? */ #if SIZEOF_INT == 4 #elif SIZEOF_LONG == 4 flags |= LONGINT; #else ERROR: cannot find size for uint32 #endif _uquad = UARG(); u32 = _uquad; src = (uchar *) &u32; wcp = buf + BUF; /* sizeof(buf)? */ for (m = 3; m >= 0; m--) { _uquad = src[m]; while (_uquad >= 10) { *--wcp = to_char(_uquad % 10); _uquad /= 10; } *--wcp = to_char(_uquad); if (m > 0) *--wcp = '.'; } SM_ASSERT(wcp >= buf); size = buf + BUF - wcp; /* sizeof(buf)? */ cp = wcp; } break; #if !SM_NO_CSTR case 'C': size = 6; /* strlen("NULL") */ if ((cstr = GETARG(sm_cstr_P)) == NULL) cp = "(NULL)"; else { SM_IS_CSTR(cstr); if ((cp = (char *) sm_cstr_data(cstr)) == NULL) cp = "(Null)"; else { size = sm_cstr_getlen(cstr); while (size > 0 && sm_cstr_rd_elem(cstr, size - 1) == '\0') { --size; } if (flags & ALT) { flags |= SANITIZE; SANITIZE_SIZE(delim); } } } if (prec >= 0 && size > prec) size = prec; sign = '\0'; break; #endif /* !SM_NO_CSTR */ case 'S': size = 6; /* strlen("NULL") */ if ((str = GETARG(sm_str_P)) == NULL) cp = "(NULL)"; else { SM_IS_BUF(str); if ((cp = (char *) sm_str_data(str)) == NULL) cp = "(Null)"; else { size = sm_str_getlen(str); if (flags & ALT) { flags |= SANITIZE; SANITIZE_SIZE(delim); } } } if (prec >= 0 && size > prec) size = prec; sign = '\0'; break; case 'T': size = 6; /* strlen("NULL") */ if ((str = GETARG(sm_str_P)) == NULL) cp = "(NULL)"; else { SM_IS_BUF(str); if ((cp = (char *) sm_str_data(str)) == NULL) cp = "(Null)"; else { uchar c; size = sm_str_getlen(str); while (size > 0 && ( (c = sm_str_rd_elem(str, size - 1)) == '\0' || c == '\n' || c == '\r')) { --size; } } if (flags & ALT) { flags |= SANITIZE; SANITIZE_SIZE(delim); } } if (prec >= 0 && size > prec) size = prec; sign = '\0'; break; case 'N': size = 6; /* strlen("NULL") */ if ((str = GETARG(sm_str_P)) == NULL) cp = "(NULL)"; else { SM_IS_BUF(str); if ((cp = (char *) sm_str_data(str)) == NULL) cp = "(Null)"; else { size = sm_str_getlen(str); while (size > 0 && sm_str_rd_elem(str, size - 1) == '\0') { --size; } } if (flags & ALT) { flags |= SANITIZE; SANITIZE_SIZE(delim); } } if (prec >= 0 && size > prec) size = prec; sign = '\0'; break; case 'U': flags |= LONGINT; /*FALLTHROUGH*/ case 'u': _uquad = UARG(); base = DEC; goto nosign; case 'r': _uquad = SARG(); if ((longlong_T) _uquad < 0) { _uquad &= (ulonglong_T) UINT32_MAX; base = HEX; xdigs = "0123456789abcdef"; ch = 'x'; flags |= HEXPREFIX; goto nosign; } base = DEC; goto number; case 'Y': _uquad = UARG(); base = BASE64; sign = '\0'; goto number; case 'Z': _uquad = UARG(); base = BASE32; sign = '\0'; goto number; case 'X': xdigs = "0123456789ABCDEF"; goto hex; case 'x': xdigs = "0123456789abcdef"; 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 */ wcp = buf + BUF; /* sizeof(buf)? */ 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 { *--wcp = to_char(_uquad & 7); _uquad >>= 3; } while (_uquad); /* handle octal leading 0 */ if (flags & ALT && *wcp != '0') *--wcp = '0'; break; case DEC: /* many numbers are 1 digit */ while (_uquad >= 10) { *--wcp = to_char(_uquad % 10); _uquad /= 10; } *--wcp = to_char(_uquad); break; case HEX: do { *--wcp = xdigs[_uquad & 15]; _uquad >>= 4; } while (_uquad); break; case BASE32: while (_uquad >= 32) { *--wcp = b32digs[_uquad & 31]; _uquad >>= 5; } *--wcp = b32digs[_uquad & 31]; break; case BASE64: while (_uquad > 63) { *--wcp = b64digs[_uquad & 63]; _uquad >>= 6; } *--wcp = b64digs[_uquad & 63]; break; default: cp = "bug in sm_io_vfprintf: bad base"; size = strlen(cp); goto skipsize; } } size = buf + BUF - wcp; /* sizeof(buf)? */ cp = wcp; skipsize: break; default: /* "%?" prints ?, unless ? is NUL */ if (ch == '\0') goto done; /* pretend it was %c with argument ch */ *buf = ch; cp = buf; 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; /* right-adjusting blank padding */ if ((flags & (LADJUST|ZEROPAD)) == 0) PAD(width - realsz, blanks); /* prefix */ if (sign) { PRINT(&sign, 1); } else if (flags & HEXPREFIX) { ox[0] = '0'; ox[1] = ch; PRINT(ox, 2); } /* right-adjusting zero padding */ if ((flags & (LADJUST|ZEROPAD)) == ZEROPAD) PAD(width - realsz, zeroes); /* leading zeroes from decimal precision */ PAD(dprec - size, zeroes); if ((flags & SANITIZE) != 0 && (osize != size || prec >= 0)) { int limit; /* limit for fixed size files */ if (f_flags(*fp) & (SMSTR|SMSTRSTR)) limit = SM_MIN(f_w(*fp), size); else limit = size; FLUSH(); xdigs = "0123456789ABCDEF"; for (m = 0, n = 0; m < osize && n < limit; m++, n++) { n2 = cp[m]; if (OKTOPRINT(n2, delim)) { if (sm_is_err(sm_io_putc(fp, n2))) goto error; } else { if (n + 3 > limit) { while (n < limit) { if (sm_is_err(sm_io_putc(fp, (int) '_'))) goto error; ++n; } break; } if (sm_is_err(sm_io_putc(fp, (int) '%'))) goto error; if (sm_is_err(sm_io_putc(fp, xdigs[(n2 >> 4) & 15]))) goto error; if (sm_is_err(sm_io_putc(fp, xdigs[n2 & 15]))) goto error; n += 2; } } } else { /* the string or number proper */ PRINT(cp, size); } /* left-adjusting padding (always blank) */ if (flags & LADJUST) PAD(width - realsz, blanks); /* finally, adjust ret */ ret += width > realsz ? width : realsz; flushit: FLUSH(); /* copy out the I/O vectors */ } done: FLUSH(); error: if ((argtable != NULL) && (argtable != statargtable)) sm_free(argtable); return sm_error(fp) ? SM_IO_EOF : ret; /* NOTREACHED */ } /* 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_QUAD 10 #define T_U_QUAD 11 #define TP_QUAD 12 #define T_DOUBLE 13 #define TP_CHAR 15 #define TP_VOID 16 #define TP_SM_STR 17 #define TP_SM_CSTR 18 /* XXX need to extend this for new formats? */ #define T_PTRINT 19 #define TP_PTRINT 20 #define T_SIZEINT 21 #define T_SSIZEINT 22 #define TP_SSIZEINT 23 /* ** SM_FIND_ARGUMENTS -- find all args when a positional parameter is found. ** ** 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 malloc-ed one if it overflows. ** ** Parameters: ** fmt0 -- formating directives ** ap -- vector list of data unit for formating consumption ** argtable -- an indexable table (returned) of 'ap' ** ** Results: ** none. */ static void sm_find_arguments(const char *fmt0, va_list ap, va_list **pargtable) { const char *fmt; /* format string */ int ch; /* character from fmt */ int n, n2; /* handy integer (short term usage) */ const char *cp; /* handy char pointer (short term usage) */ int flags; /* flags as above */ uchar *typetable; /* table of types */ uchar 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 */ /* Add an argument type to the table, expanding if necessary. */ #define ADDTYPE(type) \ ((nextarg >= tablesize) ? \ (sm_grow_type_table(nextarg, &typetable, &tablesize), 0) : 0, \ (nextarg > tablemax) ? tablemax = nextarg : 0, \ typetable[nextarg++] = type) #define ADDSARG() \ ((flags & LONGINT) ? ADDTYPE(T_LONG) : \ ((flags & SHORTINT) ? ADDTYPE(T_SHORT) : ADDTYPE(T_INT))) #define ADDUARG() \ ((flags & LONGINT) ? ADDTYPE(T_U_LONG) : \ ((flags & SHORTINT) ? ADDTYPE(T_U_SHORT) : ADDTYPE(T_U_INT))) /* Add * arguments to the type array. */ #define ADDASTER() \ n2 = 0; \ cp = fmt; \ while (is_digit(*cp)) \ { \ n2 = 10 * n2 + to_digit(*cp); \ cp++; \ } \ if (*cp == '$') \ { \ int hold = nextarg; \ nextarg = n2; \ ADDTYPE (T_INT); \ nextarg = hold; \ fmt = ++cp; \ } \ else \ { \ ADDTYPE (T_INT); \ } fmt = fmt0; typetable = stattypetable; tablesize = STATIC_ARG_TBL_SIZE; tablemax = 0; nextarg = 1; (void) sm_memset(typetable, T_UNUSED, STATIC_ARG_TBL_SIZE); /* Scan the format for conversions (`%' character). */ for (;;) { for (cp = fmt; (ch = *fmt) != '\0' && ch != '%'; fmt++) /* void */; if (ch == '\0') goto done; fmt++; /* skip over '%' */ flags = 0; rflag: ch = *fmt++; reswitch: switch (ch) { 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 { n = 10 * n + to_digit(ch); ch = *fmt++; } while (is_digit(ch)); if (ch == '$') { nextarg = n; goto rflag; } goto reswitch; case 'h': flags |= SHORTINT; 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': ADDTYPE(T_INT); break; case 'D': flags |= LONGINT; /*FALLTHROUGH*/ case 'd': case 'i': case 'r': if (flags & QUADINT) ADDTYPE(T_QUAD); else if (flags & PTRINT) ADDTYPE(T_PTRINT); else if (flags & SIZEINT) ADDTYPE(T_SSIZEINT); else ADDSARG(); break; case 'e': case 'E': case 'f': case 'g': case 'G': ADDTYPE(T_DOUBLE); break; case 'n': if (flags & QUADINT) ADDTYPE(TP_QUAD); 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 ADDTYPE(TP_INT); continue; /* no output */ case 'O': flags |= LONGINT; /*FALLTHROUGH*/ case 'o': if (flags & QUADINT) ADDTYPE(T_U_QUAD); else ADDUARG(); break; case 'p': ADDTYPE(TP_VOID); break; case 's': ADDTYPE(TP_CHAR); break; case 'U': flags |= LONGINT; /*FALLTHROUGH*/ case 'u': if (flags & QUADINT) ADDTYPE(T_U_QUAD); else ADDUARG(); break; case 'X': case 'x': case 'Y': case 'Z': if (flags & QUADINT) ADDTYPE(T_U_QUAD); else if (flags & PTRINT) ADDTYPE(T_PTRINT); else if (flags & SIZEINT) ADDTYPE(T_SIZEINT); else ADDUARG(); break; case 'A': /* deal with 'l' modifier? */ #if SIZEOF_INT == 4 ADDTYPE(T_U_INT); #elif SIZEOF_LONG == 4 ADDTYPE(T_U_LONG); #else #endif break; #if !SM_NO_CSTR case 'C': ADDTYPE(TP_SM_CSTR); break; #endif /* !SM_NO_CSTR */ case 'N': case 'S': case 'T': ADDTYPE(TP_SM_STR); break; default: /* "%?" prints ?, unless ? is NUL */ if (ch == '\0') goto done; break; } } done: /* Build the argument table. */ if (tablemax >= STATIC_ARG_TBL_SIZE) { SM_ASSERT(pargtable != NULL); *pargtable = (va_list *) sm_malloc(sizeof(va_list) * (tablemax + 1)); /* XXX check result? */ SM_ASSERT(*pargtable != NULL); } #if 0 (*pargtable)[0] = NULL; #endif /* 0 */ for (n = 1; n <= tablemax; n++) { SM_VA_COPY((*pargtable)[n], ap); switch (typetable[n]) { case T_UNUSED: (void) va_arg(ap, int); break; case T_SHORT: (void) va_arg(ap, int); break; case T_U_SHORT: (void) va_arg(ap, int); break; case TP_SHORT: (void) va_arg(ap, short *); break; case T_INT: (void) va_arg(ap, int); break; case T_U_INT: (void) va_arg(ap, uint); break; case TP_INT: (void) va_arg(ap, int *); break; case T_LONG: (void) va_arg(ap, long); break; case T_U_LONG: (void) va_arg(ap, ulong); break; case TP_LONG: (void) va_arg(ap, long *); break; case T_QUAD: (void) va_arg(ap, longlong_T); break; case T_U_QUAD: (void) va_arg(ap, ulonglong_T); break; case TP_QUAD: (void) va_arg(ap, longlong_T *); break; case T_DOUBLE: (void) va_arg(ap, double); break; case TP_CHAR: (void) va_arg(ap, char *); break; case TP_VOID: (void) va_arg(ap, void *); break; case T_PTRINT: (void) va_arg(ap, ptrdiff_t); break; case TP_PTRINT: (void) va_arg(ap, ptrdiff_t *); break; case T_SIZEINT: (void) va_arg(ap, size_t); break; case T_SSIZEINT: (void) va_arg(ap, ssize_t); break; case TP_SSIZEINT: (void) va_arg(ap, ssize_t *); break; } } if ((typetable != NULL) && (typetable != stattypetable)) sm_free(typetable); } /* ** SM_GROW_TYPE_TABLE -- Increase the size of the type table. ** ** Parameters: ** tabletype -- type of table to grow ** tablesize -- requested new table size */ static void sm_grow_type_table(int nextarg, uchar **typetable, int *tablesize) { uchar *oldtable = *typetable; int newsize = *tablesize * 2; if (newsize < nextarg + 1) newsize = nextarg + 1; if (*tablesize == STATIC_ARG_TBL_SIZE) { *typetable = (uchar *) sm_malloc(sizeof(uchar) * newsize); /* XXX check result? */ SM_ASSERT(*typetable != NULL); (void) sm_memmove(*typetable, oldtable, *tablesize); } else { *typetable = (uchar *) sm_realloc(*typetable, sizeof(uchar) * newsize); /* XXX check result? */ SM_ASSERT(*typetable != NULL); } (void) sm_memset(*typetable + *tablesize, T_UNUSED, (newsize - *tablesize)); *tablesize = newsize; }