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