1 /* $NetBSD: vbuf_print.c,v 1.4 2022/10/08 16:12:50 christos 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 /* Wietse Venema 48 /* Google, Inc. 49 /* 111 8th Avenue 50 /* New York, NY 10011, USA 51 /*--*/ 52 53 /* System library. */ 54 55 #include "sys_defs.h" 56 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */ 57 #include <stdarg.h> 58 #include <string.h> 59 #include <ctype.h> 60 #include <stdlib.h> /* 44bsd stdarg.h uses abort() */ 61 #include <stdio.h> /* sprintf() prototype */ 62 #include <float.h> /* range of doubles */ 63 #include <errno.h> 64 #include <limits.h> /* CHAR_BIT, INT_MAX */ 65 66 /* Application-specific. */ 67 68 #include "msg.h" 69 #include "mymalloc.h" 70 #include "vbuf.h" 71 #include "vstring.h" 72 #include "vbuf_print.h" 73 74 /* 75 * What we need here is a *sprintf() routine that can ask for more room (as 76 * in 4.4 BSD). However, that functionality is not widely available, and I 77 * have no plans to maintain a complete 4.4 BSD *sprintf() alternative. 78 * 79 * Postfix vbuf_print() was implemented when many mainstream systems had no 80 * usable snprintf() implementation (usable means: return the length, 81 * excluding terminator, that the output would have if the buffer were large 82 * enough). For example, GLIBC before 2.1 (1999) snprintf() did not 83 * distinguish between formatting error and buffer size error, while SUN had 84 * no snprintf() implementation before Solaris 2.6 (1997). 85 * 86 * For the above reasons, vbuf_print() was implemented with sprintf() and a 87 * generously-sized output buffer. Current vbuf_print() implementations use 88 * snprintf(), and report an error if the output does not fit (in that case, 89 * the old sprintf()-based implementation would have had a buffer overflow 90 * vulnerability). The old implementation is still available for building 91 * Postfix on ancient systems. 92 * 93 * Guessing the output size of a string (%s) conversion is not hard. The 94 * problem is with numerical results. Instead of making an accurate guess we 95 * take a wide margin when reserving space. The INT_SPACE margin should be 96 * large enough to hold the result from any (octal, hex, decimal) integer 97 * conversion that has no explicit width or precision specifiers. With 98 * floating-point numbers, use a similar estimate, and add DBL_MAX_10_EXP 99 * just to be sure. 100 */ 101 #define INT_SPACE ((CHAR_BIT * sizeof(long)) / 2) 102 #define DBL_SPACE ((CHAR_BIT * sizeof(double)) / 2 + DBL_MAX_10_EXP) 103 #define PTR_SPACE ((CHAR_BIT * sizeof(char *)) / 2) 104 105 /* 106 * Helper macros... Note that there is no need to check the result from 107 * VSTRING_SPACE() because that always succeeds or never returns. 108 */ 109 #ifndef NO_SNPRINTF 110 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \ 111 ssize_t _ret; \ 112 if (VBUF_SPACE((bp), (sz)) != 0) \ 113 return (bp); \ 114 _ret = snprintf((char *) (bp)->ptr, (bp)->cnt, (fmt), (arg)); \ 115 if (_ret < 0) \ 116 msg_panic("%s: output error for '%s'", myname, mystrdup(fmt)); \ 117 if (_ret >= (bp)->cnt) \ 118 msg_panic("%s: output for '%s' exceeds space %ld", \ 119 myname, mystrdup(fmt), (long) (bp)->cnt); \ 120 VBUF_SKIP(bp); \ 121 } while (0) 122 #else 123 #define VBUF_SNPRINTF(bp, sz, fmt, arg) do { \ 124 if (VBUF_SPACE((bp), (sz)) != 0) \ 125 return (bp); \ 126 sprintf((char *) (bp)->ptr, (fmt), (arg)); \ 127 VBUF_SKIP(bp); \ 128 } while (0) 129 #endif 130 131 #define VBUF_SKIP(bp) do { \ 132 while ((bp)->cnt > 0 && *(bp)->ptr) \ 133 (bp)->ptr++, (bp)->cnt--; \ 134 } while (0) 135 136 #define VSTRING_ADDNUM(vp, n) do { \ 137 VBUF_SNPRINTF(&(vp)->vbuf, INT_SPACE, "%d", n); \ 138 } while (0) 139 140 #define VBUF_STRCAT(bp, s) do { \ 141 unsigned char *_cp = (unsigned char *) (s); \ 142 int _ch; \ 143 while ((_ch = *_cp++) != 0) \ 144 VBUF_PUT((bp), _ch); \ 145 } while (0) 146 147 /* vbuf_print - format string, vsprintf-like interface */ 148 149 VBUF *vbuf_print(VBUF *bp, const char *format, va_list ap) 150 { 151 const char *myname = "vbuf_print"; 152 static VSTRING *fmt; /* format specifier */ 153 unsigned char *cp; 154 int width; /* width and numerical precision */ 155 int prec; /* are signed for overflow defense */ 156 unsigned long_flag; /* long or plain integer */ 157 int ch; 158 char *s; 159 int saved_errno = errno; /* VBUF_SPACE() may clobber it */ 160 161 /* 162 * Assume that format strings are short. 163 */ 164 if (fmt == 0) 165 fmt = vstring_alloc(INT_SPACE); 166 167 /* 168 * Iterate over characters in the format string, picking up arguments 169 * when format specifiers are found. 170 */ 171 for (cp = (unsigned char *) format; *cp; cp++) { 172 if (*cp != '%') { 173 VBUF_PUT(bp, *cp); /* ordinary character */ 174 } else if (cp[1] == '%') { 175 VBUF_PUT(bp, *cp++); /* %% becomes % */ 176 } else { 177 178 /* 179 * Handle format specifiers one at a time, since we can only deal 180 * with arguments one at a time. Try to determine the end of the 181 * format specifier. We do not attempt to fully parse format 182 * strings, since we are ging to let sprintf() do the hard work. 183 * In regular expression notation, we recognize: 184 * 185 * %-?+?0?([0-9]+|\*)?(\.([0-9]+|\*))?l?[a-zA-Z] 186 * 187 * which includes some combinations that do not make sense. Garbage 188 * in, garbage out. 189 */ 190 VSTRING_RESET(fmt); /* clear format string */ 191 VSTRING_ADDCH(fmt, *cp++); 192 if (*cp == '-') /* left-adjusted field? */ 193 VSTRING_ADDCH(fmt, *cp++); 194 if (*cp == '+') /* signed field? */ 195 VSTRING_ADDCH(fmt, *cp++); 196 if (*cp == '0') /* zero-padded field? */ 197 VSTRING_ADDCH(fmt, *cp++); 198 if (*cp == '*') { /* dynamic field width */ 199 width = va_arg(ap, int); 200 if (width < 0) { 201 msg_warn("%s: bad width %d in %.50s", 202 myname, width, format); 203 width = 0; 204 } else 205 VSTRING_ADDNUM(fmt, width); 206 cp++; 207 } else { /* hard-coded field width */ 208 for (width = 0; ch = *cp, ISDIGIT(ch); cp++) { 209 int digit = ch - '0'; 210 211 if (width > INT_MAX / 10 212 || (width *= 10) > INT_MAX - digit) 213 msg_panic("%s: bad width %d... in %.50s", 214 myname, width, format); 215 width += digit; 216 VSTRING_ADDCH(fmt, ch); 217 } 218 } 219 if (*cp == '.') { /* width/precision separator */ 220 VSTRING_ADDCH(fmt, *cp++); 221 if (*cp == '*') { /* dynamic precision */ 222 prec = va_arg(ap, int); 223 if (prec < 0) { 224 msg_warn("%s: bad precision %d in %.50s", 225 myname, prec, format); 226 prec = -1; 227 } else 228 VSTRING_ADDNUM(fmt, prec); 229 cp++; 230 } else { /* hard-coded precision */ 231 for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) { 232 int digit = ch - '0'; 233 234 if (prec > INT_MAX / 10 235 || (prec *= 10) > INT_MAX - digit) 236 msg_panic("%s: bad precision %d... in %.50s", 237 myname, prec, format); 238 prec += digit; 239 VSTRING_ADDCH(fmt, ch); 240 } 241 } 242 } else { 243 prec = -1; 244 } 245 if ((long_flag = (*cp == 'l')) != 0)/* long whatever */ 246 VSTRING_ADDCH(fmt, *cp++); 247 if (*cp == 0) /* premature end, punt */ 248 break; 249 VSTRING_ADDCH(fmt, *cp); /* type (checked below) */ 250 VSTRING_TERMINATE(fmt); /* null terminate */ 251 252 /* 253 * Execute the format string - let sprintf() do the hard work for 254 * non-trivial cases only. For simple string conversions and for 255 * long string conversions, do a direct copy to the output 256 * buffer. 257 */ 258 switch (*cp) { 259 case 's': /* string-valued argument */ 260 if (long_flag) 261 msg_panic("%s: %%l%c is not supported", myname, *cp); 262 s = va_arg(ap, char *); 263 if (prec >= 0 || (width > 0 && width > strlen(s))) { 264 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, 265 vstring_str(fmt), s); 266 } else { 267 VBUF_STRCAT(bp, s); 268 } 269 break; 270 case 'c': /* integral-valued argument */ 271 if (long_flag) 272 msg_panic("%s: %%l%c is not supported", myname, *cp); 273 /* FALLTHROUGH */ 274 case 'd': 275 case 'u': 276 case 'o': 277 case 'x': 278 case 'X': 279 if (long_flag) 280 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, 281 vstring_str(fmt), va_arg(ap, long)); 282 else 283 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + INT_SPACE, 284 vstring_str(fmt), va_arg(ap, int)); 285 break; 286 case 'e': /* float-valued argument */ 287 case 'f': 288 case 'g': 289 /* C99 *printf ignore the 'l' modifier. */ 290 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + DBL_SPACE, 291 vstring_str(fmt), va_arg(ap, double)); 292 break; 293 case 'm': 294 /* Ignore the 'l' modifier, width and precision. */ 295 VBUF_STRCAT(bp, saved_errno ? 296 strerror(saved_errno) : "Application error"); 297 break; 298 case 'p': 299 if (long_flag) 300 msg_panic("%s: %%l%c is not supported", myname, *cp); 301 VBUF_SNPRINTF(bp, (width > prec ? width : prec) + PTR_SPACE, 302 vstring_str(fmt), va_arg(ap, char *)); 303 break; 304 default: /* anything else is bad */ 305 msg_panic("vbuf_print: unknown format type: %c", *cp); 306 /* NOTREACHED */ 307 break; 308 } 309 } 310 } 311 return (bp); 312 } 313 314 #ifdef TEST 315 #include <argv.h> 316 #include <msg_vstream.h> 317 #include <vstring.h> 318 #include <vstring_vstream.h> 319 320 int main(int argc, char **argv) 321 { 322 VSTRING *ibuf = vstring_alloc(100); 323 324 msg_vstream_init(argv[0], VSTREAM_ERR); 325 326 while (vstring_fgets_nonl(ibuf, VSTREAM_IN)) { 327 ARGV *args = argv_split(vstring_str(ibuf), CHARS_SPACE); 328 char *cp; 329 330 if (args->argc == 0 || *(cp = args->argv[0]) == '#') { 331 /* void */ ; 332 } else if (args->argc != 2 || *cp != '%') { 333 msg_warn("usage: format number"); 334 } else { 335 char *fmt = cp++; 336 int lflag; 337 338 /* Determine the vstring_sprintf() argument type. */ 339 cp += strspn(cp, "+-*0123456789."); 340 if ((lflag = (*cp == 'l')) != 0) 341 cp++; 342 if (cp[1] != 0) { 343 msg_warn("bad format: \"%s\"", fmt); 344 } else { 345 VSTRING *obuf = vstring_alloc(1); 346 char *val = args->argv[1]; 347 348 /* Test the worst-case memory allocation. */ 349 #ifdef CA_VSTRING_CTL_EXACT 350 vstring_ctl(obuf, CA_VSTRING_CTL_EXACT, CA_VSTRING_CTL_END); 351 #endif 352 switch (*cp) { 353 case 'c': 354 case 'd': 355 case 'o': 356 case 'u': 357 case 'x': 358 case 'X': 359 if (lflag) 360 vstring_sprintf(obuf, fmt, atol(val)); 361 else 362 vstring_sprintf(obuf, fmt, atoi(val)); 363 msg_info("\"%s\"", vstring_str(obuf)); 364 break; 365 case 's': 366 vstring_sprintf(obuf, fmt, val); 367 msg_info("\"%s\"", vstring_str(obuf)); 368 break; 369 case 'f': 370 case 'g': 371 vstring_sprintf(obuf, fmt, atof(val)); 372 msg_info("\"%s\"", vstring_str(obuf)); 373 break; 374 default: 375 msg_warn("bad format: \"%s\"", fmt); 376 break; 377 } 378 vstring_free(obuf); 379 } 380 } 381 argv_free(args); 382 } 383 vstring_free(ibuf); 384 return (0); 385 } 386 387 #endif 388