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