include-snprintf-in-util-20010211
[openafs.git] / src / util / snprintf.c
1 /* snprintf.c - Formatted, length-limited print to a string */
2
3 #include <sys/types.h>
4 #include <stdarg.h>
5 #include <stdio.h>
6 #include <ctype.h>
7 #include <netinet/in.h>
8 #include <netdb.h>
9
10 #define MAXPREC 100
11
12 /* Generate an ASCII representation of an integer <val>, as follows:
13  * <base> indicates the base to be used (2-36)
14  * <uc> is nonzero if letter digits should be uppercase
15  * <prec> is the minimum number of digits
16  * The resulting number is stored in <buf>, which must be long enough
17  * to receive it.  The minimum length is <prec> or ceil(log{base}(val)),
18  * whichever is larger, plus room for a trailing NUL.
19  */
20 static void mkint(char *buf, unsigned long val, int base, int uc, int prec)
21 {
22   int len = 0, dig, i;
23
24   while (val) {
25     dig = val % base;
26     val = (val - dig) / base;
27     if (dig < 10)  dig = dig + '0';
28     else if (uc) dig = dig + 'A' - 10;
29     else         dig = dig + 'a' - 10;
30     buf[len++] = dig;
31   }
32   while (len < prec) buf[len++] = '0';
33   for (i = 0; i < (len + 1) / 2; i++) {
34     dig = buf[i];
35     buf[i] = buf[len - i - 1];
36     buf[len - i - 1] = dig;
37   }
38   buf[len] = 0;
39 }
40
41
42 /* This function is a mostly-complete implementation of snprintf,
43  * with the following features:
44  *
45  *   - Actually obeys the length limit, which (unfortunately) many
46  *     implementations of snprintf do not.
47  *  
48  *   - Supports all the standard format specifiers for integers
49  *     (d, i, o, u, x, X), floating-point values (f, e, E, g, G),
50  *     and strings and characters (c, s, %), plus a few unusual
51  *     but useful ones described below.
52  *  
53  *   - Supports all the standard flags (-, 0, +, space, #).  These
54  *     flags are ignored if used when they are not appropriate.
55  *  
56  *   - Supports the standard size modifiers for short (h), long (h),
57  *     and double (L) arguments.  These modifiers are ignored if used
58  *     when they are not appropriate.
59  *  
60  *   - Supports minimum field width and precision, where appropriate,
61  *     including the use of '*' to specify a value given as an argument
62  *     instead of in the format string.  There is a maximum precision
63  *     of 100 digits.
64  *  
65  *   - At present, the 'p' specifier for printing pointers is not
66  *     implemented, because it is inherently non-portable and thus
67  *     can be implemented correctly only by the compiler's run-time
68  *     library.
69  *
70  *   - Floating-point specifier (%e, %f, %g) are implemented by
71  *     calling the standard sprintf, and thus may be unsafe.
72  *  
73  *   - The '%...$' notation is used primarily when the format string
74  *     is specified by the user, who knows but cannot change the order
75  *     of the arguments.  Such usage is inherently dangerous and
76  *     insecure; thus, it is not supported.
77  *  
78  * The custom format specifier '%I' is supported.  This specifier
79  * takes as its argument an unsigned long integer containing an
80  * IPv4 address in network byte order.  The address is rendered
81  * either as a hostname or as a dotted quad, as follows:
82  *  
83  *   - If precision is nonzero or unspecified, a hostname lookup
84  *     is attempted; if it is successful, the hostname is printed.
85  *     If the hostname lookup fails, the address is printed in
86  *     dotted-quad notation.
87  *  
88  *   - If precision is explicitly specified as 0, then the hostname
89  *     lookup is skipped, and dotted-quad notation is always used.
90  *  
91  *   - If a hostname is to be printed:
92  *     + The precision controls the maximum number of characters
93  *       printed, as with %s.
94  *     + If the '#' flag is specified, any letters in the hostname
95  *       will be forced to lower case before printing.
96  *     + If the '+' flag is specified, any letters in the hostname
97  *       will be forced to upper case before printing.  If both
98  *       '#' and '+' are given, the '+' flag will be ignored.
99  *     + The '0' and ' ' flags have no effect.
100  *  
101  *   - If a dotted quad is to be printed:
102  *     + The precision has no effect; dotted quads are always
103  *       7 to 12 characters in length, depending on the value
104  *       to be printed and the format flags used.
105  *     + If the '0' flag is given, each field (byte) of the address
106  *       will be padded with '0' on the left to three digits.
107  *     + If the ' ' flag is given, each field (byte) of the address
108  *       will be padded with spaces on the left to three digits.  If
109  *       both '0' and ' ' are given, the ' ' flag will be ignored.
110  *     + The '#' and '+' flags have no effect.
111  */
112 static void vsnprintf(char *p, unsigned int avail, char *fmt, va_list ap)
113 {
114   unsigned int width, precision, haveprec, len;
115   int ljust, plsign, spsign, altform, zfill;
116   int hflag, lflag, count, *countp, j;
117   char *x, *y, xbuf[MAXPREC + 21], fbuf[20];
118   struct hostent *he;
119   struct in_addr ia;
120   unsigned long UVAL;
121   long SVAL, *lcountp;
122   double FVAL;
123   short *hcountp;
124
125   count = 0;
126   avail--;
127   while (*fmt && avail) {
128     if (*fmt != '%') {
129       *p++ = *fmt++;
130       avail--;
131       count++;
132       continue;
133     }
134
135     /** Found a format specifier **/
136     ljust = plsign = spsign = altform = zfill = 0;
137     width = precision = haveprec = 0;
138     hflag = lflag = 0;
139     fmt++;
140
141     /* parse format flags */
142     while (*fmt) {
143       switch (*fmt) {
144         case '-': ljust   = 1; fmt++; continue;      /* left justify */
145         case '+': plsign  = 1; fmt++; continue;      /* use + or - */
146         case ' ': spsign  = 1; fmt++; continue;      /* use space or - */
147         case '#': altform = 1; fmt++; continue;      /* alternate form */
148         case '0': zfill   = 1; fmt++; continue;      /* pad with 0 */
149         default: break;
150       }
151       break;
152     }
153
154     /* parse minimum width */
155     if (*fmt == '*') {
156       width = va_arg(ap, int);
157       fmt++;
158     } else while (isdigit(*fmt)) {
159       width = (width * 10) + (*fmt - '0');
160       fmt++;
161     }
162
163     /* parse precision */
164     if (*fmt == '.') {
165       fmt++;
166       haveprec = 1;
167       if (*fmt == '*') {
168         precision = va_arg(ap, int);
169         fmt++;
170       } else while (isdigit(*fmt)) {
171         precision = (precision * 10) + (*fmt - '0');
172         fmt++;
173       }
174     }
175
176     /* parse size flags */
177     while (*fmt) {
178       switch (*fmt) {
179         case 'h': hflag   = 1; fmt++; continue;      /* short argument */
180         case 'l': lflag   = 1; fmt++; continue;      /* long argument */
181         default: break;
182       }
183       break;
184     }
185
186     /* parse format specifier */
187     if (!*fmt) break;
188     switch (*fmt++) {
189       case 'e':
190       case 'E':
191       case 'f':
192       case 'g':
193       case 'G':
194         FVAL = va_arg(ap, double);
195         sprintf(fbuf, "%%%s%s.*L%c", plsign ? "+" : (spsign ? " " : ""),
196                 altform ? "#" : "", fmt[-1]);
197         if (!haveprec) precision = 6;
198         if (precision > MAXPREC) precision = MAXPREC;
199         sprintf(xbuf, fbuf, precision, FVAL);
200         x = xbuf;
201         len = strlen(x);
202         break;
203
204       case 'i': 
205       case 'd': /* signed decimal integer */
206         if      (lflag) SVAL = va_arg(ap, long);
207         else if (hflag) SVAL = va_arg(ap, short);
208         else            SVAL = va_arg(ap, int);
209         UVAL = (SVAL < 0) ? -SVAL : SVAL;
210
211         if (SVAL < 0)    xbuf[0] = '-';
212         else if (plsign) xbuf[0] = '+';
213         else if (spsign) xbuf[0] = ' ';
214         else             xbuf[0] = 0;
215
216         if (!haveprec) {
217           if (zfill && !ljust) precision = width - !!xbuf[0];
218           else precision = 1;
219           if (precision < 1 + !!xbuf[0]) precision = 1 + !!xbuf[0];
220         }
221         if (precision > MAXPREC) precision = MAXPREC;
222
223         mkint(xbuf + 1, UVAL, 10, 0, precision);
224         x = xbuf + !xbuf[0];
225         len = strlen(x);
226         break;
227
228
229       case 'o': /* unsigned octal integer */
230         if      (lflag) UVAL = va_arg(ap, unsigned long);
231         else if (hflag) UVAL = va_arg(ap, unsigned short);
232         else            UVAL = va_arg(ap, unsigned int);
233
234         xbuf[0] = '0';
235
236         if (!haveprec) {
237           if (zfill && !ljust) precision = width;
238           else precision = 1;
239         }
240         if (precision > MAXPREC) precision = MAXPREC;
241
242         mkint(xbuf + 1, UVAL, 8, 0, precision);
243         x = xbuf + (xbuf[1] == '0' || !altform);
244         len = strlen(x);
245         break;
246
247       case 'u': /* unsigned decimal integer */
248         if      (lflag) UVAL = va_arg(ap, unsigned long);
249         else if (hflag) UVAL = va_arg(ap, unsigned short);
250         else            UVAL = va_arg(ap, unsigned int);
251
252         if (!haveprec) {
253           if (zfill && !ljust) precision = width;
254           else precision = 1;
255         }
256         if (precision > MAXPREC) precision = MAXPREC;
257
258         mkint(xbuf, UVAL, 10, 0, precision);
259         x = xbuf;
260         len = strlen(x);
261         break;
262
263       case 'x': 
264       case 'X': /* unsigned hexadecimal integer */
265         if      (lflag) UVAL = va_arg(ap, unsigned long);
266         else if (hflag) UVAL = va_arg(ap, unsigned short);
267         else            UVAL = va_arg(ap, unsigned int);
268
269         xbuf[0] = '0';
270         xbuf[1] = 'x';
271
272         if (!haveprec) {
273           if (zfill && !ljust) precision = width;
274           else precision = 1;
275         }
276         if (precision > MAXPREC) precision = MAXPREC;
277
278         mkint(xbuf + 2, UVAL, 16, 0, precision);
279         x = xbuf + ((altform && UVAL) ? 0 : 2);
280         len = strlen(x);
281         break;
282
283       case '%': /* literal % */
284         xbuf[0] = '%';
285         xbuf[1] = 0;
286         x = xbuf;
287         len = 1;
288         break;
289
290       case 'c': /* character */
291         xbuf[0] = va_arg(ap, int);
292         xbuf[1] = 0;
293         x = xbuf;
294         len = 1;
295         break;
296
297       case 's': /* string */
298         x = va_arg(ap, char *);
299         if (!x) x = "<null>";
300         len = strlen(x);
301         if (haveprec && precision < len) len = precision;
302         break;
303
304       case 'I': /* IP address:
305          * value is provided as a network-order unsigned long integer
306          * precision specifies max hostname length, as for %s
307          * if precision is explicitly 0, no hostname lookup is done
308          * if 0fill specified, IPaddr fields are 0-filled to 3 digits
309          * if spsign specified, IPaddr fields are space-filled to 3 digits
310          */
311         UVAL = va_arg(ap, unsigned long);
312         ia.s_addr = UVAL;
313         if (haveprec && !precision) he = 0;
314         else he = gethostbyaddr((char *)&ia, 4, AF_INET);
315         if (he) {
316           x = he->h_name;
317           len = strlen(x);
318           if (haveprec && precision < len) len = precision;
319           if (altform)
320             for (y = x; *y; y++) if (isupper(*y)) *y = tolower(*y);
321           else if (plsign)
322             for (y = x; *y; y++) if (islower(*y)) *y = toupper(*y);
323         } else {
324           UVAL = ntohl(UVAL);
325           if      (zfill)  x = "%03u.%03u.%03u.%03u";
326           else if (spsign) x = "%3u.%3u.%3u.%3u";
327           else             x = "%u.%u.%u.%u";
328           sprintf(xbuf, x,
329                   (UVAL & 0xff000000) >> 24, (UVAL & 0x00ff0000) >> 16,
330                   (UVAL & 0x0000ff00) >> 8,  (UVAL & 0x000000ff));
331           x = xbuf;
332           len = strlen(xbuf);
333         }
334         break;
335
336       case 'n': /* report count so far */
337         if (lflag) {
338           lcountp = va_arg(ap, long *);
339           *lcountp = count;
340         } else if (hflag) {
341           hcountp = va_arg(ap, short *);
342           *hcountp = count;
343         } else {
344           countp = va_arg(ap, int *);
345           *countp = count;
346         }
347         continue;
348
349       default: /* unknown specifier */
350         continue;
351     }
352
353     /* render the results */
354     if (len > avail)   len = avail;
355     if (!width)        width = len;
356     if (width > avail) width = avail;
357     j = width - len;
358     if (j > 0) {
359       avail -= j;
360       count += j;
361     }
362
363     if (!ljust) while (j-- > 0) *p++ = ' ';
364
365     strncpy(p, x, len);
366     avail -= len;
367     count += len;
368     p += len;
369
370     if (ljust) while (j-- > 0) *p++ = ' ';
371   }
372   *p = 0;
373 }
374
375
376 void snprintf(char *p, unsigned int avail, char *fmt, ...)
377 {
378   va_list ap;
379
380   va_start(ap, fmt);
381   vsnprintf(p, avail, fmt, ap);
382   va_end(ap);
383 }