7 /* formatted print to generic buffer
10 /* #include <vbuf_print.h>
12 /* VBUF *vbuf_print(bp, format, ap)
14 /* const char *format;
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.
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.
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.
35 /* However, it is up to the caller to ensure that the destination
36 /* VSTREAM or VSTRING buffer is protected against reentrant usage.
40 /* The Secure Mailer license must be distributed with this software.
43 /* IBM T.J. Watson Research
45 /* Yorktown Heights, NY 10598, USA
51 #include <stdlib.h> /* 44BSD stdarg.h uses abort() */
55 #include <stdlib.h> /* 44bsd stdarg.h uses abort() */
56 #include <stdio.h> /* sprintf() prototype */
57 #include <float.h> /* range of doubles */
59 #include <limits.h> /* CHAR_BIT */
61 /* Application-specific. */
66 #include "vbuf_print.h"
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.
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.
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
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
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)
97 * Helper macros... Note that there is no need to check the result from
98 * VSTRING_SPACE() because that always succeeds or never returns.
100 #define VBUF_SKIP(bp) { \
101 while ((bp)->cnt > 0 && *(bp)->ptr) \
102 (bp)->ptr++, (bp)->cnt--; \
105 #define VSTRING_ADDNUM(vp, n) { \
106 VSTRING_SPACE(vp, INT_SPACE); \
107 sprintf(vstring_end(vp), "%d", n); \
108 VBUF_SKIP(&vp->vbuf); \
111 #define VBUF_STRCAT(bp, s) { \
112 unsigned char *_cp = (unsigned char *) (s); \
114 while ((_ch = *_cp++) != 0) \
115 VBUF_PUT((bp), _ch); \
118 /* vbuf_print - format string, vsprintf-like interface */
120 VBUF
*vbuf_print(VBUF
*bp
, const char *format
, va_list ap
)
122 const char *myname
= "vbuf_print";
123 static VSTRING
*fmt
; /* format specifier */
125 int width
; /* width and numerical precision */
126 int prec
; /* are signed for overflow defense */
127 unsigned long_flag
; /* long or plain integer */
132 * Assume that format strings are short.
135 fmt
= vstring_alloc(INT_SPACE
);
138 * Iterate over characters in the format string, picking up arguments
139 * when format specifiers are found.
141 for (cp
= (unsigned char *) format
; *cp
; cp
++) {
143 VBUF_PUT(bp
, *cp
); /* ordinary character */
144 } else if (cp
[1] == '%') {
145 VBUF_PUT(bp
, *cp
++); /* %% becomes % */
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:
155 * %-?0?([0-9]+|\*)?\.?([0-9]+|\*)?l?[a-zA-Z]
157 * which includes some combinations that do not make sense. Garbage
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
);
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
);
179 msg_warn("%s: bad width %d in %.50s", myname
, width
, format
);
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
);
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
);
195 msg_warn("%s: bad precision %d in %.50s", myname
, prec
, format
);
198 if ((long_flag
= (*cp
== 'l')) != 0)/* long whatever */
199 VSTRING_ADDCH(fmt
, *cp
++);
200 if (*cp
== 0) /* premature end, punt */
202 VSTRING_ADDCH(fmt
, *cp
); /* type (checked below) */
203 VSTRING_TERMINATE(fmt
); /* null terminate */
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
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
))
217 sprintf((char *) bp
->ptr
, vstring_str(fmt
), s
);
223 case 'c': /* integral-valued argument */
229 if (VBUF_SPACE(bp
, (width
> prec
? width
: prec
) + INT_SPACE
))
232 sprintf((char *) bp
->ptr
, vstring_str(fmt
), va_arg(ap
, long));
234 sprintf((char *) bp
->ptr
, vstring_str(fmt
), va_arg(ap
, int));
237 case 'e': /* float-valued argument */
240 if (VBUF_SPACE(bp
, (width
> prec
? width
: prec
) + DBL_SPACE
))
242 sprintf((char *) bp
->ptr
, vstring_str(fmt
), va_arg(ap
, double));
246 VBUF_STRCAT(bp
, strerror(errno
));
249 if (VBUF_SPACE(bp
, (width
> prec
? width
: prec
) + PTR_SPACE
))
251 sprintf((char *) bp
->ptr
, vstring_str(fmt
), va_arg(ap
, char *));
254 default: /* anything else is bad */
255 msg_panic("vbuf_print: unknown format type: %c", *cp
);