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