xref: /netbsd-src/external/ibm-public/postfix/dist/src/util/vbuf_print.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /*	$NetBSD: vbuf_print.c,v 1.2 2017/02/14 01:16:49 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 
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     int     saved_errno = errno;	/* VBUF_SPACE() may clobber it */
131 
132     /*
133      * Assume that format strings are short.
134      */
135     if (fmt == 0)
136 	fmt = vstring_alloc(INT_SPACE);
137 
138     /*
139      * Iterate over characters in the format string, picking up arguments
140      * when format specifiers are found.
141      */
142     for (cp = (unsigned char *) format; *cp; cp++) {
143 	if (*cp != '%') {
144 	    VBUF_PUT(bp, *cp);			/* ordinary character */
145 	} else if (cp[1] == '%') {
146 	    VBUF_PUT(bp, *cp++);		/* %% becomes % */
147 	} else {
148 
149 	    /*
150 	     * Handle format specifiers one at a time, since we can only deal
151 	     * with arguments one at a time. Try to determine the end of the
152 	     * format specifier. We do not attempt to fully parse format
153 	     * strings, since we are ging to let sprintf() do the hard work.
154 	     * In regular expression notation, we recognize:
155 	     *
156 	     * %-?0?([0-9]+|\*)?\.(?[0-9]+|\*)?l?[a-zA-Z]
157 	     *
158 	     * which includes some combinations that do not make sense. Garbage
159 	     * in, garbage out.
160 	     */
161 	    VSTRING_RESET(fmt);			/* clear format string */
162 	    VSTRING_ADDCH(fmt, *cp++);
163 	    if (*cp == '-')			/* left-adjusted field? */
164 		VSTRING_ADDCH(fmt, *cp++);
165 	    if (*cp == '+')			/* signed field? */
166 		VSTRING_ADDCH(fmt, *cp++);
167 	    if (*cp == '0')			/* zero-padded field? */
168 		VSTRING_ADDCH(fmt, *cp++);
169 	    if (*cp == '*') {			/* dynamic field width */
170 		width = va_arg(ap, int);
171 		VSTRING_ADDNUM(fmt, width);
172 		cp++;
173 	    } else {				/* hard-coded field width */
174 		for (width = 0; ch = *cp, ISDIGIT(ch); cp++) {
175 		    width = width * 10 + ch - '0';
176 		    VSTRING_ADDCH(fmt, ch);
177 		}
178 	    }
179 	    if (width < 0) {
180 		msg_warn("%s: bad width %d in %.50s", myname, width, format);
181 		width = 0;
182 	    }
183 	    if (*cp == '.') {			/* width/precision separator */
184 		VSTRING_ADDCH(fmt, *cp++);
185 		if (*cp == '*') {		/* dynamic precision */
186 		    prec = va_arg(ap, int);
187 		    VSTRING_ADDNUM(fmt, prec);
188 		    cp++;
189 		} else {			/* hard-coded precision */
190 		    for (prec = 0; ch = *cp, ISDIGIT(ch); cp++) {
191 			prec = prec * 10 + ch - '0';
192 			VSTRING_ADDCH(fmt, ch);
193 		    }
194 		}
195 		if (prec < 0) {
196 		    msg_warn("%s: bad precision %d in %.50s", myname, prec, format);
197 		    prec = -1;
198 		}
199 	    } else {
200 		prec = -1;
201 	    }
202 	    if ((long_flag = (*cp == 'l')) != 0)/* long whatever */
203 		VSTRING_ADDCH(fmt, *cp++);
204 	    if (*cp == 0)			/* premature end, punt */
205 		break;
206 	    VSTRING_ADDCH(fmt, *cp);		/* type (checked below) */
207 	    VSTRING_TERMINATE(fmt);		/* null terminate */
208 
209 	    /*
210 	     * Execute the format string - let sprintf() do the hard work for
211 	     * non-trivial cases only. For simple string conversions and for
212 	     * long string conversions, do a direct copy to the output
213 	     * buffer.
214 	     */
215 	    switch (*cp) {
216 	    case 's':				/* string-valued argument */
217 		s = va_arg(ap, char *);
218 		if (prec >= 0 || (width > 0 && width > strlen(s))) {
219 		    if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE))
220 			return (bp);
221 		    sprintf((char *) bp->ptr, vstring_str(fmt), s);
222 		    VBUF_SKIP(bp);
223 		} else {
224 		    VBUF_STRCAT(bp, s);
225 		}
226 		break;
227 	    case 'c':				/* integral-valued argument */
228 	    case 'd':
229 	    case 'u':
230 	    case 'o':
231 	    case 'x':
232 	    case 'X':
233 		if (VBUF_SPACE(bp, (width > prec ? width : prec) + INT_SPACE))
234 		    return (bp);
235 		if (long_flag)
236 		    sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, long));
237 		else
238 		    sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, int));
239 		VBUF_SKIP(bp);
240 		break;
241 	    case 'e':				/* float-valued argument */
242 	    case 'f':
243 	    case 'g':
244 		if (VBUF_SPACE(bp, (width > prec ? width : prec) + DBL_SPACE))
245 		    return (bp);
246 		sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, double));
247 		VBUF_SKIP(bp);
248 		break;
249 	    case 'm':
250 		VBUF_STRCAT(bp, strerror(saved_errno));
251 		break;
252 	    case 'p':
253 		if (VBUF_SPACE(bp, (width > prec ? width : prec) + PTR_SPACE))
254 		    return (bp);
255 		sprintf((char *) bp->ptr, vstring_str(fmt), va_arg(ap, char *));
256 		VBUF_SKIP(bp);
257 		break;
258 	    default:				/* anything else is bad */
259 		msg_panic("vbuf_print: unknown format type: %c", *cp);
260 		/* NOTREACHED */
261 		break;
262 	    }
263 	}
264     }
265     return (bp);
266 }
267