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