Merge branch 'topic/mm-fixes' of git://208.110.73.182/silc into silc.1.1.branch
[silc.git] / lib / silcutil / silcsnprintf.c
1 /*
2  * Copyright Patrick Powell 1995
3  * This code is based on code written by Patrick Powell (papowell@astart.com)
4  * It may be used for any purpose as long as this notice remains intact
5  * on all source code distributions
6  */
7
8 /**************************************************************
9  * Original:
10  * Patrick Powell Tue Apr 11 09:48:21 PDT 1995
11  * A bombproof version of doprnt (dopr) included.
12  * Sigh.  This sort of thing is always nasty do deal with.  Note that
13  * the version here does not include floating point...
14  *
15  * snprintf() is used instead of sprintf() as it does limit checks
16  * for string length.  This covers a nasty loophole.
17  *
18  * The other functions are there to prevent NULL pointers from
19  * causing nast effects.
20  *
21  * More Recently:
22  *  Brandon Long <blong@fiction.net> 9/15/96 for mutt 0.43
23  *  This was ugly.  It is still ugly.  I opted out of floating point
24  *  numbers, but the formatter understands just about everything
25  *  from the normal C string format, at least as far as I can tell from
26  *  the Solaris 2.5 printf(3S) man page.
27  *
28  *  Brandon Long <blong@fiction.net> 10/22/97 for mutt 0.87.1
29  *    Ok, added some minimal floating point support, which means this
30  *    probably requires libm on most operating systems.  Don't yet
31  *    support the exponent (e,E) and sigfig (g,G).  Also, fmtint()
32  *    was pretty badly broken, it just wasn't being exercised in ways
33  *    which showed it, so that's been fixed.  Also, formated the code
34  *    to mutt conventions, and removed dead code left over from the
35  *    original.  Also, there is now a builtin-test, just compile with:
36  *           gcc -DTEST_SNPRINTF -o snprintf snprintf.c -lm
37  *    and run snprintf for results.
38  *
39  *  Thomas Roessler <roessler@guug.de> 01/27/98 for mutt 0.89i
40  *    The PGP code was using unsigned hexadecimal formats.
41  *    Unfortunately, unsigned formats simply didn't work.
42  *
43  *  Michael Elkins <me@cs.hmc.edu> 03/05/98 for mutt 0.90.8
44  *    The original code assumed that both snprintf() and vsnprintf() were
45  *    missing.  Some systems only have snprintf() but not vsnprintf(), so
46  *    the code is now broken down under HAVE_SNPRINTF and HAVE_VSNPRINTF.
47  *
48  *  Andrew Tridgell (tridge@samba.org) Oct 1998
49  *    fixed handling of %.0f
50  *    added test for HAVE_LONG_DOUBLE
51  *
52  * tridge@samba.org, idra@samba.org, April 2001
53  *    got rid of fcvt code (twas buggy and made testing harder)
54  *    added C99 semantics
55  *
56  **************************************************************/
57
58 #include "silc.h"
59
60 #ifdef HAVE_LONG_DOUBLE
61 #define LDOUBLE long double
62 #else
63 #define LDOUBLE double
64 #endif
65
66 #ifdef HAVE_LONG_LONG
67 #define LLONG long long
68 #else
69 #define LLONG long
70 #endif
71
72 /*
73  * dopr(): poor man's version of doprintf
74  */
75
76 /* format read states */
77 #define DP_S_DEFAULT 0
78 #define DP_S_FLAGS   1
79 #define DP_S_MIN     2
80 #define DP_S_DOT     3
81 #define DP_S_MAX     4
82 #define DP_S_MOD     5
83 #define DP_S_CONV    6
84 #define DP_S_DONE    7
85
86 /* format flags - Bits */
87 #define DP_F_MINUS      (1 << 0)
88 #define DP_F_PLUS       (1 << 1)
89 #define DP_F_SPACE      (1 << 2)
90 #define DP_F_NUM        (1 << 3)
91 #define DP_F_ZERO       (1 << 4)
92 #define DP_F_UP         (1 << 5)
93 #define DP_F_UNSIGNED   (1 << 6)
94 #define DP_F_HEXPREFIX  (1 << 7)
95
96 /* Conversion Flags */
97 #define DP_C_SHORT   1
98 #define DP_C_LONG    2
99 #define DP_C_LDOUBLE 3
100 #define DP_C_LLONG   4
101
102 #define char_to_int(p) ((p)- '0')
103 #ifndef MAX
104 #define MAX(p,q) (((p) >= (q)) ? (p) : (q))
105 #endif
106
107 static size_t dopr(char *buffer, size_t maxlen, const char *format,
108                    va_list args_in);
109 static void fmtstr(char *buffer, size_t *currlen, size_t maxlen,
110                     char *value, int flags, int min, int max);
111 static void fmtint(char *buffer, size_t *currlen, size_t maxlen,
112                     long value, int base, int min, int max, int flags);
113 static void fmtfp(char *buffer, size_t *currlen, size_t maxlen,
114                    LDOUBLE fvalue, int min, int max, int flags);
115 static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c);
116
117 static size_t dopr(char *buffer, size_t maxlen, const char *format,
118                    va_list args_in)
119 {
120   char ch;
121   LLONG value;
122   LDOUBLE fvalue;
123   char *strvalue;
124   int min;
125   int max;
126   int state;
127   int flags;
128   int cflags;
129   size_t currlen;
130   va_list args;
131
132   silc_va_copy(args, args_in);
133
134   state = DP_S_DEFAULT;
135   currlen = flags = cflags = min = 0;
136   max = -1;
137   ch = *format++;
138
139   while (state != DP_S_DONE) {
140     if (ch == '\0')
141       state = DP_S_DONE;
142
143     switch(state) {
144     case DP_S_DEFAULT:
145       if (ch == '%')
146         state = DP_S_FLAGS;
147       else
148         dopr_outch (buffer, &currlen, maxlen, ch);
149       ch = *format++;
150       break;
151     case DP_S_FLAGS:
152       switch (ch) {
153       case '-':
154         flags |= DP_F_MINUS;
155         ch = *format++;
156         break;
157       case '+':
158         flags |= DP_F_PLUS;
159         ch = *format++;
160         break;
161       case ' ':
162         flags |= DP_F_SPACE;
163         ch = *format++;
164         break;
165       case '#':
166         flags |= DP_F_NUM;
167         ch = *format++;
168         break;
169       case '0':
170         flags |= DP_F_ZERO;
171         ch = *format++;
172         break;
173       default:
174         state = DP_S_MIN;
175         break;
176       }
177       break;
178     case DP_S_MIN:
179       if (isdigit((unsigned char)ch)) {
180         min = 10*min + char_to_int (ch);
181         ch = *format++;
182       } else if (ch == '*') {
183         min = va_arg (args, int);
184         ch = *format++;
185         state = DP_S_DOT;
186       } else {
187         state = DP_S_DOT;
188       }
189       break;
190     case DP_S_DOT:
191       if (ch == '.') {
192         state = DP_S_MAX;
193         ch = *format++;
194       } else {
195         state = DP_S_MOD;
196       }
197       break;
198     case DP_S_MAX:
199       if (isdigit((unsigned char)ch)) {
200         if (max < 0)
201           max = 0;
202         max = 10*max + char_to_int (ch);
203         ch = *format++;
204       } else if (ch == '*') {
205         max = va_arg (args, int);
206         ch = *format++;
207         state = DP_S_MOD;
208       } else {
209         state = DP_S_MOD;
210       }
211       break;
212     case DP_S_MOD:
213       switch (ch) {
214       case 'h':
215         cflags = DP_C_SHORT;
216         ch = *format++;
217         break;
218       case 'l':
219         cflags = DP_C_LONG;
220         ch = *format++;
221         if (ch == 'l') {        /* It's a long long */
222           cflags = DP_C_LLONG;
223           ch = *format++;
224         }
225         break;
226       case 'L':
227         cflags = DP_C_LDOUBLE;
228         ch = *format++;
229         break;
230       default:
231         break;
232       }
233       state = DP_S_CONV;
234       break;
235     case DP_S_CONV:
236       switch (ch) {
237       case 'd':
238       case 'i':
239         if (cflags == DP_C_SHORT)
240           value = va_arg (args, int);
241         else if (cflags == DP_C_LONG)
242           value = va_arg (args, long int);
243         else if (cflags == DP_C_LLONG)
244           value = va_arg (args, LLONG);
245         else
246           value = va_arg (args, int);
247         fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
248         break;
249       case 'o':
250         flags |= DP_F_UNSIGNED;
251         if (cflags == DP_C_SHORT)
252           value = va_arg (args, unsigned int);
253         else if (cflags == DP_C_LONG)
254           value = (long)va_arg (args, unsigned long int);
255         else if (cflags == DP_C_LLONG)
256           value = (long)va_arg (args, unsigned LLONG);
257         else
258           value = (long)va_arg (args, unsigned int);
259         fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags);
260         break;
261       case 'u':
262         flags |= DP_F_UNSIGNED;
263         if (cflags == DP_C_SHORT)
264           value = va_arg (args, unsigned int);
265         else if (cflags == DP_C_LONG)
266           value = (long)va_arg (args, unsigned long int);
267         else if (cflags == DP_C_LLONG)
268           value = (LLONG)va_arg (args, unsigned LLONG);
269         else
270           value = (long)va_arg (args, unsigned int);
271         fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
272         break;
273       case 'X':
274         flags |= DP_F_UP;
275       case 'x':
276         flags |= DP_F_UNSIGNED;
277         if (cflags == DP_C_SHORT)
278           value = va_arg (args, unsigned int);
279         else if (cflags == DP_C_LONG)
280           value = (long)va_arg (args, unsigned long int);
281         else if (cflags == DP_C_LLONG)
282           value = (LLONG)va_arg (args, unsigned LLONG);
283         else
284           value = (long)va_arg (args, unsigned int);
285         fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags);
286         break;
287       case 'f':
288         if (cflags == DP_C_LDOUBLE)
289           fvalue = va_arg (args, LDOUBLE);
290         else
291           fvalue = va_arg (args, double);
292         /* um, floating point? */
293         fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
294         break;
295       case 'E':
296         flags |= DP_F_UP;
297       case 'e':
298         if (cflags == DP_C_LDOUBLE)
299           fvalue = va_arg (args, LDOUBLE);
300         else
301           fvalue = va_arg (args, double);
302         fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
303         break;
304       case 'G':
305         flags |= DP_F_UP;
306       case 'g':
307         if (cflags == DP_C_LDOUBLE)
308           fvalue = va_arg (args, LDOUBLE);
309         else
310           fvalue = va_arg (args, double);
311         fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
312         break;
313       case 'c':
314         dopr_outch (buffer, &currlen, maxlen, va_arg (args, int));
315         break;
316       case 's':
317         strvalue = va_arg (args, char *);
318         if (!strvalue) strvalue = "(NULL)";
319         if (max == -1) {
320           max = strlen(strvalue);
321         }
322         if (min > 0 && max >= 0 && min > max) max = min;
323         fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max);
324         break;
325       case 'p':
326         flags |= (DP_F_UNSIGNED | DP_F_HEXPREFIX);
327         strvalue = va_arg (args, void *);
328         fmtint (buffer, &currlen, maxlen, (long )strvalue, 16, min, max,
329                 flags);
330         break;
331       case 'n':
332         if (cflags == DP_C_SHORT) {
333           short int *num;
334           num = va_arg (args, short int *);
335           *num = currlen;
336         } else if (cflags == DP_C_LONG) {
337           long int *num;
338           num = va_arg (args, long int *);
339           *num = (long int)currlen;
340         } else if (cflags == DP_C_LLONG) {
341           LLONG *num;
342           num = va_arg (args, LLONG *);
343           *num = (LLONG)currlen;
344         } else {
345           int *num;
346           num = va_arg (args, int *);
347           *num = currlen;
348         }
349         break;
350       case '%':
351         dopr_outch (buffer, &currlen, maxlen, ch);
352         break;
353       case 'w':
354         /* not supported yet, treat as next char */
355         ch = *format++;
356         break;
357       default:
358         /* Unknown, skip */
359         break;
360       }
361       ch = *format++;
362       state = DP_S_DEFAULT;
363       flags = cflags = min = 0;
364       max = -1;
365       break;
366     case DP_S_DONE:
367       break;
368     default:
369       /* hmm? */
370       break; /* some picky compilers need this */
371     }
372   }
373   if (maxlen != 0) {
374     if (currlen < maxlen - 1)
375       buffer[currlen] = '\0';
376     else if (maxlen > 0)
377       buffer[maxlen - 1] = '\0';
378   }
379
380   return currlen;
381 }
382
383 static void fmtstr(char *buffer, size_t *currlen, size_t maxlen,
384                     char *value, int flags, int min, int max)
385 {
386   int padlen, strln;     /* amount to pad */
387   int cnt = 0;
388
389   if (value == 0) {
390     value = "<NULL>";
391   }
392
393   for (strln = 0; value[strln]; ++strln); /* strlen */
394   padlen = min - strln;
395   if (padlen < 0)
396     padlen = 0;
397   if (flags & DP_F_MINUS)
398     padlen = -padlen; /* Left Justify */
399
400   while ((padlen > 0) && (cnt < max)) {
401     dopr_outch (buffer, currlen, maxlen, ' ');
402     --padlen;
403     ++cnt;
404   }
405   while (*value && (cnt < max)) {
406     dopr_outch (buffer, currlen, maxlen, *value++);
407     ++cnt;
408   }
409   while ((padlen < 0) && (cnt < max)) {
410     dopr_outch (buffer, currlen, maxlen, ' ');
411     ++padlen;
412     ++cnt;
413   }
414 }
415
416 /* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
417
418 static void fmtint(char *buffer, size_t *currlen, size_t maxlen,
419                    long value, int base, int min, int max, int flags)
420 {
421   int signvalue = 0;
422   unsigned long uvalue;
423   char convert[20];
424   int place = 0;
425   int spadlen = 0; /* amount to space pad */
426   int zpadlen = 0; /* amount to zero pad */
427   int caps = 0;
428
429   if (max < 0)
430     max = 0;
431
432   uvalue = value;
433
434   if(!(flags & DP_F_UNSIGNED)) {
435     if( value < 0 ) {
436       signvalue = '-';
437       uvalue = -value;
438     } else {
439       if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
440         signvalue = '+';
441       else if (flags & DP_F_SPACE)
442         signvalue = ' ';
443     }
444   }
445
446   if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
447
448   do {
449     convert[place++] =
450       (caps? "0123456789ABCDEF":"0123456789abcdef")
451       [uvalue % (unsigned)base  ];
452     uvalue = (uvalue / (unsigned)base );
453   } while(uvalue && (place < 20));
454   if (place == 20) place--;
455   convert[place] = 0;
456
457   zpadlen = max - place;
458   spadlen = min - MAX (max, place) - (signvalue ? 1 : 0);
459   if (zpadlen < 0) zpadlen = 0;
460   if (spadlen < 0) spadlen = 0;
461   if (flags & DP_F_ZERO) {
462     zpadlen = MAX(zpadlen, spadlen);
463     spadlen = 0;
464   }
465   if (flags & DP_F_MINUS)
466     spadlen = -spadlen; /* Left Justifty */
467
468   /* Spaces */
469   while (spadlen > 0) {
470     dopr_outch (buffer, currlen, maxlen, ' ');
471     --spadlen;
472   }
473
474   /* 0x prefix */
475   if (flags & DP_F_HEXPREFIX) {
476     dopr_outch (buffer, currlen, maxlen, '0');
477     dopr_outch (buffer, currlen, maxlen, 'x');
478   }
479
480   /* Sign */
481   if (signvalue)
482     dopr_outch (buffer, currlen, maxlen, signvalue);
483
484   /* Zeros */
485   if (zpadlen > 0) {
486     while (zpadlen > 0) {
487       dopr_outch (buffer, currlen, maxlen, '0');
488       --zpadlen;
489     }
490   }
491
492   /* Digits */
493   while (place > 0)
494     dopr_outch (buffer, currlen, maxlen, convert[--place]);
495
496   /* Left Justified spaces */
497   while (spadlen < 0) {
498     dopr_outch (buffer, currlen, maxlen, ' ');
499     ++spadlen;
500   }
501 }
502
503 static LDOUBLE abs_val(LDOUBLE value)
504 {
505   LDOUBLE result = value;
506
507   if (value < 0)
508     result = -value;
509
510   return result;
511 }
512
513 static LDOUBLE POW10(int exp)
514 {
515   LDOUBLE result = 1;
516
517   while (exp) {
518     result *= 10;
519     exp--;
520   }
521
522   return result;
523 }
524
525 static LLONG ROUND(LDOUBLE value)
526 {
527   LLONG intpart;
528
529   intpart = (LLONG)value;
530   value = value - intpart;
531   if (value >= 0.5) intpart++;
532
533   return intpart;
534 }
535
536 /* a replacement for modf that doesn't need the math library. Should
537    be portable, but slow */
538 static double my_modf(double x0, double *iptr)
539 {
540   int i;
541   long l;
542   double x = x0;
543   double f = 1.0;
544
545   for (i=0;i<100;i++) {
546     l = (long)x;
547     if (l <= (x+1) && l >= (x-1)) break;
548     x *= 0.1;
549     f *= 10.0;
550   }
551
552   if (i == 100) {
553     /* yikes! the number is beyond what we can handle.
554        What do we do? */
555     (*iptr) = 0;
556     return 0;
557   }
558
559   if (i != 0) {
560     double i2;
561     double ret;
562
563     ret = my_modf(x0-l*f, &i2);
564     (*iptr) = l*f + i2;
565     return ret;
566   }
567
568   (*iptr) = l;
569   return x - (*iptr);
570 }
571
572
573 static void fmtfp (char *buffer, size_t *currlen, size_t maxlen,
574                    LDOUBLE fvalue, int min, int max, int flags)
575 {
576   int signvalue = 0;
577   double ufvalue;
578   char iconvert[311];
579   char fconvert[311];
580   int iplace = 0;
581   int fplace = 0;
582   int padlen = 0; /* amount to pad */
583   int zpadlen = 0;
584   int caps = 0;
585   int idx;
586   double intpart;
587   double fracpart;
588   double temp;
589
590   /*
591    * AIX manpage says the default is 0, but Solaris says the default
592    * is 6, and sprintf on AIX defaults to 6
593    */
594   if (max < 0)
595     max = 6;
596
597   ufvalue = abs_val (fvalue);
598
599   if (fvalue < 0) {
600     signvalue = '-';
601   } else {
602     if (flags & DP_F_PLUS) { /* Do a sign (+/i) */
603       signvalue = '+';
604     } else {
605       if (flags & DP_F_SPACE)
606         signvalue = ' ';
607     }
608   }
609
610 #if 0
611   if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
612 #endif
613
614 #if 0
615   if (max == 0) ufvalue += 0.5; /* if max = 0 we must round */
616 #endif
617
618   /*
619    * Sorry, we only support 16 digits past the decimal because of our
620    * conversion method
621    */
622   if (max > 16)
623     max = 16;
624
625   /* We "cheat" by converting the fractional part to integer by
626    * multiplying by a factor of 10
627    */
628
629   temp = ufvalue;
630   my_modf(temp, &intpart);
631
632   fracpart = ROUND((POW10(max)) * (ufvalue - intpart));
633
634   if (fracpart >= POW10(max)) {
635     intpart++;
636     fracpart -= POW10(max);
637   }
638
639
640   /* Convert integer part */
641   do {
642     temp = intpart*0.1;
643     my_modf(temp, &intpart);
644     idx = (int) ((temp -intpart +0.05)* 10.0);
645     /* idx = (int) (((double)(temp*0.1) -intpart +0.05) *10.0); */
646     /* printf ("%llf, %f, %x\n", temp, intpart, idx); */
647     iconvert[iplace++] =
648       (caps? "0123456789ABCDEF":"0123456789abcdef")[idx];
649   } while (intpart && (iplace < 311));
650   if (iplace == 311) iplace--;
651   iconvert[iplace] = 0;
652
653   /* Convert fractional part */
654   if (fracpart)
655     {
656       do {
657         temp = fracpart*0.1;
658         my_modf(temp, &fracpart);
659         idx = (int) ((temp -fracpart +0.05)* 10.0);
660         /* idx = (int) ((((temp/10) -fracpart) +0.05) *10); */
661         /* printf ("%lf, %lf, %ld\n", temp, fracpart, idx ); */
662         fconvert[fplace++] =
663           (caps? "0123456789ABCDEF":"0123456789abcdef")[idx];
664       } while(fracpart && (fplace < 311));
665       if (fplace == 311) fplace--;
666     }
667   fconvert[fplace] = 0;
668
669   /* -1 for decimal point, another -1 if we are printing a sign */
670   padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
671   zpadlen = max - fplace;
672   if (zpadlen < 0) zpadlen = 0;
673   if (padlen < 0)
674     padlen = 0;
675   if (flags & DP_F_MINUS)
676     padlen = -padlen; /* Left Justifty */
677
678   if ((flags & DP_F_ZERO) && (padlen > 0)) {
679     if (signvalue) {
680       dopr_outch (buffer, currlen, maxlen, signvalue);
681       --padlen;
682       signvalue = 0;
683     }
684     while (padlen > 0) {
685       dopr_outch (buffer, currlen, maxlen, '0');
686       --padlen;
687     }
688   }
689   while (padlen > 0) {
690     dopr_outch (buffer, currlen, maxlen, ' ');
691     --padlen;
692   }
693   if (signvalue)
694     dopr_outch (buffer, currlen, maxlen, signvalue);
695
696   while (iplace > 0)
697     dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]);
698
699   /*
700    * Decimal point.  This should probably use locale to find the correct
701    * char to print out.
702    */
703   if (max > 0) {
704     dopr_outch (buffer, currlen, maxlen, '.');
705
706     while (zpadlen > 0) {
707       dopr_outch (buffer, currlen, maxlen, '0');
708       --zpadlen;
709     }
710
711     while (fplace > 0)
712       dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]);
713   }
714
715   while (padlen < 0) {
716     dopr_outch (buffer, currlen, maxlen, ' ');
717     ++padlen;
718   }
719 }
720
721 static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c)
722 {
723   if (*currlen < maxlen) {
724     buffer[(*currlen)] = c;
725   }
726   (*currlen)++;
727 }
728
729 int silc_vsnprintf(char *str, size_t count, const char *fmt, va_list args)
730 {
731   if (str != NULL)
732     str[0] = 0;
733   return dopr(str, count, fmt, args);
734 }
735
736 int silc_snprintf(char *str, size_t count, const char *fmt, ...)
737 {
738   size_t ret;
739   va_list ap;
740
741   va_start(ap, fmt);
742   ret = silc_vsnprintf(str, count, fmt, ap);
743   va_end(ap);
744   return ret;
745 }
746
747 int silc_vasprintf(char **ptr, const char *format, va_list ap)
748 {
749   int ret;
750   va_list ap2;
751
752   silc_va_copy(ap2, ap);
753
754   ret = silc_vsnprintf(NULL, 0, format, ap2);
755   if (ret <= 0) return ret;
756
757   (*ptr) = (char *)silc_malloc(ret+1);
758   if (!*ptr) return -1;
759
760   silc_va_copy(ap2, ap);
761
762   ret = silc_vsnprintf(*ptr, ret+1, format, ap2);
763
764   return ret;
765 }
766
767 int silc_asprintf(char **ptr, const char *format, ...)
768 {
769   va_list ap;
770   int ret;
771
772   *ptr = NULL;
773   va_start(ap, format);
774   ret = silc_vasprintf(ptr, format, ap);
775   va_end(ap);
776
777   return ret;
778 }