Home | History | Annotate | Line # | Download | only in util
      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