/*++ /* NAME /* vbuf_print 3 /* SUMMARY /* formatted print to generic buffer /* SYNOPSIS /* #include <stdarg.h> /* #include <vbuf_print.h> /* /* VBUF *vbuf_print(bp, format, ap) /* VBUF *bp; /* const char *format; /* va_list ap; /* DESCRIPTION /* vbuf_print() appends data to the named buffer according to its /* \fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e, /* f and g format types, the l modifier, field width and precision, /* sign, and padding with zeros or spaces. /* /* In addition, vbuf_print() recognizes the %m format specifier /* and expands it to the error message corresponding to the current /* value of the global \fIerrno\fR variable. /* LICENSE /* .ad /* .fi /* The Secure Mailer license must be distributed with this software. /* AUTHOR(S) /* Wietse Venema /* IBM T.J. Watson Research /* P.O. Box 704 /* Yorktown Heights, NY 10598, USA /*--*/ /* System library. */ #include "sys_defs.h" #include <stdlib.h> /* 44BSD stdarg.h uses abort() */ #include <stdarg.h> #include <string.h> #include <ctype.h> #include <stdlib.h> /* 44bsd stdarg.h uses abort() */ #include <stdio.h> /* sprintf() prototype */ #include <float.h> /* range of doubles */ #include <errno.h> /* Application-specific. */ #include "msg.h" #include "vbuf.h" #include "vstring.h" #include "vbuf_print.h" /* * What we need here is a *sprintf() routine that can ask for more room (as * in 4.4 BSD). However, that functionality is not widely available, and I * have no plans to maintain a complete 4.4 BSD *sprintf() alternative. * * This means we're stuck with plain old ugly sprintf() for all non-trivial * conversions. We cannot use snprintf() even if it is available, because * that routine truncates output, and we want everything. Therefore, it is * up to us to ensure that sprintf() output always stays within bounds. * * Due to the complexity of *printf() format strings we cannot easily predict * how long results will be without actually doing the conversions. A trick * used by some people is to print to a temporary file and to read the * result back. In programs that do a lot of formatting, that might be too * expensive. * * Guessing the output size of a string (%s) conversion is not hard. The * problem is with numerical results. Instead of making an accurate guess we * take a wide margin when reserving space. The INT_SPACE margin should be * large enough to hold the result from any (octal, hex, decimal) integer * conversion that has no explicit width or precision specifiers. With * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP * just to be sure. */ #define INT_SPACE (4 * sizeof(long)) #define DBL_SPACE (4 * sizeof(double) + DBL_MAX_10_EXP) #define PTR_SPACE (4 * sizeof(char *)) /* * Helper macros... Note that there is no need to check the result from * VSTRING_SPACE() because that always succeeds or never returns. */ #define VBUF_SKIP(bp) { \ while ((bp)->cnt > 0 && *(bp)->ptr) \ (bp)->ptr++, (bp)->cnt--; \ } #define VSTRING_ADDNUM(vp, n) { \ VSTRING_SPACE(vp, INT_SPACE); \ sprintf(vstring_end(vp), "%d", n); \ VBUF_SKIP(&vp->vbuf); \ } #define VBUF_STRCAT(bp, s) { \ unsigned char *_cp = (unsigned char *) (s); \ int ch; \ while ((ch = *_cp++) != 0) \ VBUF_PUT((bp), ch); \ } /* vbuf_print - format string, vsprintf-like interface */ VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap) { static VSTRING *fmt; /* format specifier */ unsigned char *cp; unsigned width; /* field width */ unsigned prec; /* numerical precision */ unsigned long_flag; /* long or plain integer */ int ch; char *s; /* * Assume that format strings are short. */ if (fmt == 0) fmt = vstring_alloc(INT_SPACE); /* * Iterate over characters in the format string, picking up arguments * when format specifiers are found. */ for (cp = (unsigned char *) format; *cp; cp++) { if (*cp != '%') { VBUF_PUT(bp, *cp); /* ordinary character */ } else if (cp[1] == '%') { VBUF_PUT(bp, *cp++); /* %% becomes % */ } else { /* * Handle format specifiers one at a time, since we can only deal * with arguments one at a time. Try to determine the end of the * format specifier. We do not attempt to fully parse format * strings, since we are ging to let sprintf() do the hard work. * In regular expression notation, we recognize: * * %-?0?([0-9]+|\*)?\.?([0-9]+|\*)?l?[a-zA-Z] * * which includes some combinations that do not make sense. Garbage * in, garbage out. */ VSTRING_RESET(fmt); /* clear format string */ VSTRING_ADDCH(fmt, *cp++); if (*cp == '-') /* left-adjusted field? */ VSTRING_ADDCH(fmt, *cp++); if (*cp == '+') /* signed field? */ VSTRING_ADDCH(fmt, *cp++); if (*cp == '0') /* zero-padded field? */ VSTRING_ADDCH(fmt, *cp++); if (*cp == '*') { /* dynamic field width */ width = va_arg(ap, int); VSTRING_ADDNUM(fmt, width); cp++; } else { /* hard-coded field width */ for (width = 0; ISDIGIT(ch = *cp); cp++) { width = width * 10 + ch - '0'; VSTRING_ADDCH(fmt, ch); } } if (*cp == '.') /* width/precision separator */ VSTRING_ADDCH(fmt, *cp++); if (*cp == '*') { /* dynamic precision */ prec = va_arg(ap, int); VSTRING_ADDNUM(fmt, prec); cp++; } else { /* hard-coded precision */ for (prec = 0; ISDIGIT(ch = *cp); cp++) { prec = prec * 10 + ch - '0'; VSTRING_ADDCH(fmt, ch); } } if ((long_flag = (*cp == 'l')) != 0)/* long whatever */ VSTRING_ADDCH(fmt, *cp++); if (*cp == 0) /* premature end, punt */ break; VSTRING_ADDCH(fmt, *cp); /* type (checked below) */ VSTRING_TERMINATE(fmt); /* null terminate */ /* * Execute the format string - let sprintf() do the hard work for * non-trivial cases only. For simple string conversions and for * long string conversions, do a direct copy to the output * buffer. */ switch (*cp) { case 's': /* string-valued argument */ s = va_arg(ap, char *); if (prec > 0 || (width > 0 && width > strlen(s))) { if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE)) return (bp); sprintf((char *) bp->ptr, vstring_str(fmt), s); VBUF_SKIP(bp); } else { VBUF_STRCAT(bp, s); } break; case 'c': /* integral-valued argument */ case 'd': case 'u': case 'o': case 'x': case 'X': if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE)) return (bp); if (long_flag) sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, long)); else sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, int)); VBUF_SKIP(bp); break; case 'e': /* float-valued argument */ case 'f': case 'g': if (VBUF_SPACE(bp, (width > prec ? width : prec) + DBL_SPACE)) return (bp); sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, double)); VBUF_SKIP(bp); break; case 'm': VBUF_STRCAT(bp, strerror(errno)); break; case 'p': if (VBUF_SPACE(bp, (width > prec ? width : prec) + PTR_SPACE)) return (bp); sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, char *)); VBUF_SKIP(bp); break; default: /* anything else is bad */ msg_panic("vbuf_print: unknown format type: %c", *cp); /* NOTREACHED */ break; } } } return (bp); }