1/*++ 2/* NAME 3/* vbuf_print 3 4/* SUMMARY 5/* formatted print to generic buffer 6/* SYNOPSIS 7/* #include <stdarg.h> 8/* #include <vbuf_print.h> 9/* 10/* VBUF *vbuf_print(bp, format, ap) 11/* VBUF *bp; 12/* const char *format; 13/* va_list ap; 14/* DESCRIPTION 15/* vbuf_print() appends data to the named buffer according to its 16/* \fIformat\fR argument. It understands the s, c, d, u, o, x, X, p, e, 17/* f and g format types, the l modifier, field width and precision, 18/* sign, and padding with zeros or spaces. 19/* 20/* In addition, vbuf_print() recognizes the %m format specifier 21/* and expands it to the error message corresponding to the current 22/* value of the global \fIerrno\fR variable. 23/* REENTRANCY 24/* .ad 25/* .fi 26/* vbuf_print() allocates a static buffer. After completion 27/* of the first vbuf_print() call, this buffer is safe for 28/* reentrant vbuf_print() calls by (asynchronous) terminating 29/* signal handlers or by (synchronous) terminating error 30/* handlers. vbuf_print() initialization typically happens 31/* upon the first formatted output to a VSTRING or VSTREAM. 32/* 33/* However, it is up to the caller to ensure that the destination 34/* VSTREAM or VSTRING buffer is protected against reentrant usage. 35/* LICENSE 36/* .ad 37/* .fi 38/* The Secure Mailer license must be distributed with this software. 39/* AUTHOR(S) 40/* Wietse Venema 41/* IBM T.J. Watson Research 42/* P.O. Box 704 43/* Yorktown Heights, NY 10598, USA 44/*--*/ 45 46/* System library. */ 47 48#include "sys_defs.h" 49#include <stdlib.h> /* 44BSD stdarg.h uses abort() */ 50#include <stdarg.h> 51#include <string.h> 52#include <ctype.h> 53#include <stdlib.h> /* 44bsd stdarg.h uses abort() */ 54#include <stdio.h> /* sprintf() prototype */ 55#include <float.h> /* range of doubles */ 56#include <errno.h> 57#include <limits.h> /* CHAR_BIT */ 58 59/* Application-specific. */ 60 61#include "msg.h" 62#include "vbuf.h" 63#include "vstring.h" 64#include "vbuf_print.h" 65 66 /* 67 * What we need here is a *sprintf() routine that can ask for more room (as 68 * in 4.4 BSD). However, that functionality is not widely available, and I 69 * have no plans to maintain a complete 4.4 BSD *sprintf() alternative. 70 * 71 * This means we're stuck with plain old ugly sprintf() for all non-trivial 72 * conversions. We cannot use snprintf() even if it is available, because 73 * that routine truncates output, and we want everything. Therefore, it is 74 * up to us to ensure that sprintf() output always stays within bounds. 75 * 76 * Due to the complexity of *printf() format strings we cannot easily predict 77 * how long results will be without actually doing the conversions. A trick 78 * used by some people is to print to a temporary file and to read the 79 * result back. In programs that do a lot of formatting, that might be too 80 * expensive. 81 * 82 * Guessing the output size of a string (%s) conversion is not hard. The 83 * problem is with numerical results. Instead of making an accurate guess we 84 * take a wide margin when reserving space. The INT_SPACE margin should be 85 * large enough to hold the result from any (octal, hex, decimal) integer 86 * conversion that has no explicit width or precision specifiers. With 87 * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP 88 * just to be sure. 89 */ 90#define INT_SPACE ((CHAR_BIT * sizeof(long)) / 2) 91#define DBL_SPACE ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP) 92#define PTR_SPACE ((CHAR_BIT * sizeof(char *)) / 2) 93 94 /* 95 * Helper macros... Note that there is no need to check the result from 96 * VSTRING_SPACE() because that always succeeds or never returns. 97 */ 98#define VBUF_SKIP(bp) { \ 99 while ((bp)->cnt > 0 && *(bp)->ptr) \ 100 (bp)->ptr++, (bp)->cnt--; \ 101 } 102 103#define VSTRING_ADDNUM(vp, n) { \ 104 VSTRING_SPACE(vp, INT_SPACE); \ 105 sprintf(vstring_end(vp), "%d", n); \ 106 VBUF_SKIP(&vp->vbuf); \ 107 } 108 109#define VBUF_STRCAT(bp, s) { \ 110 unsigned char *_cp = (unsigned char *) (s); \ 111 int _ch; \ 112 while ((_ch = *_cp++) != 0) \ 113 VBUF_PUT((bp), _ch); \ 114 } 115 116/* vbuf_print - format string, vsprintf-like interface */ 117 118VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap) 119{ 120 const char *myname = "vbuf_print"; 121 static VSTRING *fmt; /* format specifier */ 122 unsigned char *cp; 123 int width; /* width and numerical precision */ 124 int prec; /* are signed for overflow defense */ 125 unsigned long_flag; /* long or plain integer */ 126 int ch; 127 char *s; 128 int saved_errno = errno; /* VBUF_SPACE() may clobber it */ 129 130 /* 131 * Assume that format strings are short. 132 */ 133 if (fmt == 0) 134 fmt = vstring_alloc(INT_SPACE); 135 136 /* 137 * Iterate over characters in the format string, picking up arguments 138 * when format specifiers are found. 139 */ 140 for (cp = (unsigned char *) format; *cp; cp++) { 141 if (*cp != '%') { 142 VBUF_PUT(bp, *cp); /* ordinary character */ 143 } else if (cp[1] == '%') { 144 VBUF_PUT(bp, *cp++); /* %% becomes % */ 145 } else { 146 147 /* 148 * Handle format specifiers one at a time, since we can only deal 149 * with arguments one at a time. Try to determine the end of the 150 * format specifier. We do not attempt to fully parse format 151 * strings, since we are ging to let sprintf() do the hard work. 152 * In regular expression notation, we recognize: 153 * 154 * %-?0?([0-9]+|\*)?\.?([0-9]+|\*)?l?[a-zA-Z] 155 * 156 * which includes some combinations that do not make sense. Garbage 157 * in, garbage out. 158 */ 159 VSTRING_RESET(fmt); /* clear format string */ 160 VSTRING_ADDCH(fmt, *cp++); 161 if (*cp == '-') /* left-adjusted field? */ 162 VSTRING_ADDCH(fmt, *cp++); 163 if (*cp == '+') /* signed field? */ 164 VSTRING_ADDCH(fmt, *cp++); 165 if (*cp == '0') /* zero-padded field? */ 166 VSTRING_ADDCH(fmt, *cp++); 167 if (*cp == '*') { /* dynamic field width */ 168 width = va_arg(ap, int); 169 VSTRING_ADDNUM(fmt, width); 170 cp++; 171 } else { /* hard-coded field width */ 172 for (width = 0; ch = *cp, ISDIGIT(ch); cp++) { 173 width = width * 10 + ch - '0'; 174 VSTRING_ADDCH(fmt, ch); 175 } 176 } 177 if (width < 0) { 178 msg_warn("%s: bad width %d in %.50s", myname, width, format); 179 width = 0; 180 } 181 if (*cp == '.') /* width/precision separator */ 182 VSTRING_ADDCH(fmt, *cp++); 183 if (*cp == '*') { /* dynamic precision */ 184 prec = va_arg(ap, int); 185 VSTRING_ADDNUM(fmt, prec); 186 cp++; 187 } else { /* hard-coded precision */ 188 for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) { 189 prec = prec * 10 + ch - '0'; 190 VSTRING_ADDCH(fmt, ch); 191 } 192 } 193 if (prec < 0) { 194 msg_warn("%s: bad precision %d in %.50s", myname, prec, format); 195 prec = 0; 196 } 197 if ((long_flag = (*cp == 'l')) != 0)/* long whatever */ 198 VSTRING_ADDCH(fmt, *cp++); 199 if (*cp == 0) /* premature end, punt */ 200 break; 201 VSTRING_ADDCH(fmt, *cp); /* type (checked below) */ 202 VSTRING_TERMINATE(fmt); /* null terminate */ 203 204 /* 205 * Execute the format string - let sprintf() do the hard work for 206 * non-trivial cases only. For simple string conversions and for 207 * long string conversions, do a direct copy to the output 208 * buffer. 209 */ 210 switch (*cp) { 211 case 's': /* string-valued argument */ 212 s = va_arg(ap, char *); 213 if (prec > 0 || (width > 0 && width > strlen(s))) { 214 if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE)) 215 return (bp); 216 sprintf((char *) bp->ptr, vstring_str(fmt), s); 217 VBUF_SKIP(bp); 218 } else { 219 VBUF_STRCAT(bp, s); 220 } 221 break; 222 case 'c': /* integral-valued argument */ 223 case 'd': 224 case 'u': 225 case 'o': 226 case 'x': 227 case 'X': 228 if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE)) 229 return (bp); 230 if (long_flag) 231 sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, long)); 232 else 233 sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, int)); 234 VBUF_SKIP(bp); 235 break; 236 case 'e': /* float-valued argument */ 237 case 'f': 238 case 'g': 239 if (VBUF_SPACE(bp, (width > prec ? width : prec) + DBL_SPACE)) 240 return (bp); 241 sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, double)); 242 VBUF_SKIP(bp); 243 break; 244 case 'm': 245 VBUF_STRCAT(bp, strerror(saved_errno)); 246 break; 247 case 'p': 248 if (VBUF_SPACE(bp, (width > prec ? width : prec) + PTR_SPACE)) 249 return (bp); 250 sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, char *)); 251 VBUF_SKIP(bp); 252 break; 253 default: /* anything else is bad */ 254 msg_panic("vbuf_print: unknown format type: %c", *cp); 255 /* NOTREACHED */ 256 break; 257 } 258 } 259 } 260 return (bp); 261} 262