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