Added silc_snprintf, silc_vsnprintf, silc_asprintf, silc_vasprintf
[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
95 /* Conversion Flags */
96 #define DP_C_SHORT   1
97 #define DP_C_LONG    2
98 #define DP_C_LDOUBLE 3
99 #define DP_C_LLONG   4
100
101 #define char_to_int(p) ((p)- '0')
102 #ifndef MAX
103 #define MAX(p,q) (((p) >= (q)) ? (p) : (q))
104 #endif
105
106 static size_t dopr(char *buffer, size_t maxlen, const char *format,
107                    va_list args_in);
108 static void fmtstr(char *buffer, size_t *currlen, size_t maxlen,
109                     char *value, int flags, int min, int max);
110 static void fmtint(char *buffer, size_t *currlen, size_t maxlen,
111                     long value, int base, int min, int max, int flags);
112 static void fmtfp(char *buffer, size_t *currlen, size_t maxlen,
113                    LDOUBLE fvalue, int min, int max, int flags);
114 static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c);
115
116 static size_t dopr(char *buffer, size_t maxlen, const char *format,
117                    va_list args_in)
118 {
119   char ch;
120   LLONG value;
121   LDOUBLE fvalue;
122   char *strvalue;
123   int min;
124   int max;
125   int state;
126   int flags;
127   int cflags;
128   size_t currlen;
129   va_list args;
130
131   silc_va_copy(args, args_in);
132
133   state = DP_S_DEFAULT;
134   currlen = flags = cflags = min = 0;
135   max = -1;
136   ch = *format++;
137
138   while (state != DP_S_DONE) {
139     if (ch == '\0')
140       state = DP_S_DONE;
141
142     switch(state) {
143     case DP_S_DEFAULT:
144       if (ch == '%')
145         state = DP_S_FLAGS;
146       else
147         dopr_outch (buffer, &currlen, maxlen, ch);
148       ch = *format++;
149       break;
150     case DP_S_FLAGS:
151       switch (ch) {
152       case '-':
153         flags |= DP_F_MINUS;
154         ch = *format++;
155         break;
156       case '+':
157         flags |= DP_F_PLUS;
158         ch = *format++;
159         break;
160       case ' ':
161         flags |= DP_F_SPACE;
162         ch = *format++;
163         break;
164       case '#':
165         flags |= DP_F_NUM;
166         ch = *format++;
167         break;
168       case '0':
169         flags |= DP_F_ZERO;
170         ch = *format++;
171         break;
172       default:
173         state = DP_S_MIN;
174         break;
175       }
176       break;
177     case DP_S_MIN:
178       if (isdigit((unsigned char)ch)) {
179         min = 10*min + char_to_int (ch);
180         ch = *format++;
181       } else if (ch == '*') {
182         min = va_arg (args, int);
183         ch = *format++;
184         state = DP_S_DOT;
185       } else {
186         state = DP_S_DOT;
187       }
188       break;
189     case DP_S_DOT:
190       if (ch == '.') {
191         state = DP_S_MAX;
192         ch = *format++;
193       } else {
194         state = DP_S_MOD;
195       }
196       break;
197     case DP_S_MAX:
198       if (isdigit((unsigned char)ch)) {
199         if (max < 0)
200           max = 0;
201         max = 10*max + char_to_int (ch);
202         ch = *format++;
203       } else if (ch == '*') {
204         max = va_arg (args, int);
205         ch = *format++;
206         state = DP_S_MOD;
207       } else {
208         state = DP_S_MOD;
209       }
210       break;
211     case DP_S_MOD:
212       switch (ch) {
213       case 'h':
214         cflags = DP_C_SHORT;
215         ch = *format++;
216         break;
217       case 'l':
218         cflags = DP_C_LONG;
219         ch = *format++;
220         if (ch == 'l') {        /* It's a long long */
221           cflags = DP_C_LLONG;
222           ch = *format++;
223         }
224         break;
225       case 'L':
226         cflags = DP_C_LDOUBLE;
227         ch = *format++;
228         break;
229       default:
230         break;
231       }
232       state = DP_S_CONV;
233       break;
234     case DP_S_CONV:
235       switch (ch) {
236       case 'd':
237       case 'i':
238         if (cflags == DP_C_SHORT)
239           value = va_arg (args, int);
240         else if (cflags == DP_C_LONG)
241           value = va_arg (args, long int);
242         else if (cflags == DP_C_LLONG)
243           value = va_arg (args, LLONG);
244         else
245           value = va_arg (args, int);
246         fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
247         break;
248       case 'o':
249         flags |= DP_F_UNSIGNED;
250         if (cflags == DP_C_SHORT)
251           value = va_arg (args, unsigned int);
252         else if (cflags == DP_C_LONG)
253           value = (long)va_arg (args, unsigned long int);
254         else if (cflags == DP_C_LLONG)
255           value = (long)va_arg (args, unsigned LLONG);
256         else
257           value = (long)va_arg (args, unsigned int);
258         fmtint (buffer, &currlen, maxlen, value, 8, min, max, flags);
259         break;
260       case 'u':
261         flags |= DP_F_UNSIGNED;
262         if (cflags == DP_C_SHORT)
263           value = va_arg (args, unsigned int);
264         else if (cflags == DP_C_LONG)
265           value = (long)va_arg (args, unsigned long int);
266         else if (cflags == DP_C_LLONG)
267           value = (LLONG)va_arg (args, unsigned LLONG);
268         else
269           value = (long)va_arg (args, unsigned int);
270         fmtint (buffer, &currlen, maxlen, value, 10, min, max, flags);
271         break;
272       case 'X':
273         flags |= DP_F_UP;
274       case 'x':
275         flags |= DP_F_UNSIGNED;
276         if (cflags == DP_C_SHORT)
277           value = va_arg (args, unsigned int);
278         else if (cflags == DP_C_LONG)
279           value = (long)va_arg (args, unsigned long int);
280         else if (cflags == DP_C_LLONG)
281           value = (LLONG)va_arg (args, unsigned LLONG);
282         else
283           value = (long)va_arg (args, unsigned int);
284         fmtint (buffer, &currlen, maxlen, value, 16, min, max, flags);
285         break;
286       case 'f':
287         if (cflags == DP_C_LDOUBLE)
288           fvalue = va_arg (args, LDOUBLE);
289         else
290           fvalue = va_arg (args, double);
291         /* um, floating point? */
292         fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
293         break;
294       case 'E':
295         flags |= DP_F_UP;
296       case 'e':
297         if (cflags == DP_C_LDOUBLE)
298           fvalue = va_arg (args, LDOUBLE);
299         else
300           fvalue = va_arg (args, double);
301         fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
302         break;
303       case 'G':
304         flags |= DP_F_UP;
305       case 'g':
306         if (cflags == DP_C_LDOUBLE)
307           fvalue = va_arg (args, LDOUBLE);
308         else
309           fvalue = va_arg (args, double);
310         fmtfp (buffer, &currlen, maxlen, fvalue, min, max, flags);
311         break;
312       case 'c':
313         dopr_outch (buffer, &currlen, maxlen, va_arg (args, int));
314         break;
315       case 's':
316         strvalue = va_arg (args, char *);
317         if (!strvalue) strvalue = "(NULL)";
318         if (max == -1) {
319           max = strlen(strvalue);
320         }
321         if (min > 0 && max >= 0 && min > max) max = min;
322         fmtstr (buffer, &currlen, maxlen, strvalue, flags, min, max);
323         break;
324       case 'p':
325         strvalue = va_arg (args, void *);
326         fmtint (buffer, &currlen, maxlen, (long) strvalue, 16, min, max, flags);
327         break;
328       case 'n':
329         if (cflags == DP_C_SHORT) {
330           short int *num;
331           num = va_arg (args, short int *);
332           *num = currlen;
333         } else if (cflags == DP_C_LONG) {
334           long int *num;
335           num = va_arg (args, long int *);
336           *num = (long int)currlen;
337         } else if (cflags == DP_C_LLONG) {
338           LLONG *num;
339           num = va_arg (args, LLONG *);
340           *num = (LLONG)currlen;
341         } else {
342           int *num;
343           num = va_arg (args, int *);
344           *num = currlen;
345         }
346         break;
347       case '%':
348         dopr_outch (buffer, &currlen, maxlen, ch);
349         break;
350       case 'w':
351         /* not supported yet, treat as next char */
352         ch = *format++;
353         break;
354       default:
355         /* Unknown, skip */
356         break;
357       }
358       ch = *format++;
359       state = DP_S_DEFAULT;
360       flags = cflags = min = 0;
361       max = -1;
362       break;
363     case DP_S_DONE:
364       break;
365     default:
366       /* hmm? */
367       break; /* some picky compilers need this */
368     }
369   }
370   if (maxlen != 0) {
371     if (currlen < maxlen - 1)
372       buffer[currlen] = '\0';
373     else if (maxlen > 0)
374       buffer[maxlen - 1] = '\0';
375   }
376
377   return currlen;
378 }
379
380 static void fmtstr(char *buffer, size_t *currlen, size_t maxlen,
381                     char *value, int flags, int min, int max)
382 {
383   int padlen, strln;     /* amount to pad */
384   int cnt = 0;
385
386   if (value == 0) {
387     value = "<NULL>";
388   }
389
390   for (strln = 0; value[strln]; ++strln); /* strlen */
391   padlen = min - strln;
392   if (padlen < 0)
393     padlen = 0;
394   if (flags & DP_F_MINUS)
395     padlen = -padlen; /* Left Justify */
396
397   while ((padlen > 0) && (cnt < max)) {
398     dopr_outch (buffer, currlen, maxlen, ' ');
399     --padlen;
400     ++cnt;
401   }
402   while (*value && (cnt < max)) {
403     dopr_outch (buffer, currlen, maxlen, *value++);
404     ++cnt;
405   }
406   while ((padlen < 0) && (cnt < max)) {
407     dopr_outch (buffer, currlen, maxlen, ' ');
408     ++padlen;
409     ++cnt;
410   }
411 }
412
413 /* Have to handle DP_F_NUM (ie 0x and 0 alternates) */
414
415 static void fmtint(char *buffer, size_t *currlen, size_t maxlen,
416                     long value, int base, int min, int max, int flags)
417 {
418   int signvalue = 0;
419   unsigned long uvalue;
420   char convert[20];
421   int place = 0;
422   int spadlen = 0; /* amount to space pad */
423   int zpadlen = 0; /* amount to zero pad */
424   int caps = 0;
425
426   if (max < 0)
427     max = 0;
428
429   uvalue = value;
430
431   if(!(flags & DP_F_UNSIGNED)) {
432     if( value < 0 ) {
433       signvalue = '-';
434       uvalue = -value;
435     } else {
436       if (flags & DP_F_PLUS)  /* Do a sign (+/i) */
437         signvalue = '+';
438       else if (flags & DP_F_SPACE)
439         signvalue = ' ';
440     }
441   }
442
443   if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
444
445   do {
446     convert[place++] =
447       (caps? "0123456789ABCDEF":"0123456789abcdef")
448       [uvalue % (unsigned)base  ];
449     uvalue = (uvalue / (unsigned)base );
450   } while(uvalue && (place < 20));
451   if (place == 20) place--;
452   convert[place] = 0;
453
454   zpadlen = max - place;
455   spadlen = min - MAX (max, place) - (signvalue ? 1 : 0);
456   if (zpadlen < 0) zpadlen = 0;
457   if (spadlen < 0) spadlen = 0;
458   if (flags & DP_F_ZERO) {
459     zpadlen = MAX(zpadlen, spadlen);
460     spadlen = 0;
461   }
462   if (flags & DP_F_MINUS)
463     spadlen = -spadlen; /* Left Justifty */
464
465   /* Spaces */
466   while (spadlen > 0) {
467     dopr_outch (buffer, currlen, maxlen, ' ');
468     --spadlen;
469   }
470
471   /* Sign */
472   if (signvalue)
473     dopr_outch (buffer, currlen, maxlen, signvalue);
474
475   /* Zeros */
476   if (zpadlen > 0) {
477     while (zpadlen > 0) {
478       dopr_outch (buffer, currlen, maxlen, '0');
479       --zpadlen;
480     }
481   }
482
483   /* Digits */
484   while (place > 0)
485     dopr_outch (buffer, currlen, maxlen, convert[--place]);
486
487   /* Left Justified spaces */
488   while (spadlen < 0) {
489     dopr_outch (buffer, currlen, maxlen, ' ');
490     ++spadlen;
491   }
492 }
493
494 static LDOUBLE abs_val(LDOUBLE value)
495 {
496   LDOUBLE result = value;
497
498   if (value < 0)
499     result = -value;
500
501   return result;
502 }
503
504 static LDOUBLE POW10(int exp)
505 {
506   LDOUBLE result = 1;
507
508   while (exp) {
509     result *= 10;
510     exp--;
511   }
512
513   return result;
514 }
515
516 static LLONG ROUND(LDOUBLE value)
517 {
518   LLONG intpart;
519
520   intpart = (LLONG)value;
521   value = value - intpart;
522   if (value >= 0.5) intpart++;
523
524   return intpart;
525 }
526
527 /* a replacement for modf that doesn't need the math library. Should
528    be portable, but slow */
529 static double my_modf(double x0, double *iptr)
530 {
531   int i;
532   long l;
533   double x = x0;
534   double f = 1.0;
535
536   for (i=0;i<100;i++) {
537     l = (long)x;
538     if (l <= (x+1) && l >= (x-1)) break;
539     x *= 0.1;
540     f *= 10.0;
541   }
542
543   if (i == 100) {
544     /* yikes! the number is beyond what we can handle.
545        What do we do? */
546     (*iptr) = 0;
547     return 0;
548   }
549
550   if (i != 0) {
551     double i2;
552     double ret;
553
554     ret = my_modf(x0-l*f, &i2);
555     (*iptr) = l*f + i2;
556     return ret;
557   }
558
559   (*iptr) = l;
560   return x - (*iptr);
561 }
562
563
564 static void fmtfp (char *buffer, size_t *currlen, size_t maxlen,
565                    LDOUBLE fvalue, int min, int max, int flags)
566 {
567   int signvalue = 0;
568   double ufvalue;
569   char iconvert[311];
570   char fconvert[311];
571   int iplace = 0;
572   int fplace = 0;
573   int padlen = 0; /* amount to pad */
574   int zpadlen = 0;
575   int caps = 0;
576   int idx;
577   double intpart;
578   double fracpart;
579   double temp;
580
581   /*
582    * AIX manpage says the default is 0, but Solaris says the default
583    * is 6, and sprintf on AIX defaults to 6
584    */
585   if (max < 0)
586     max = 6;
587
588   ufvalue = abs_val (fvalue);
589
590   if (fvalue < 0) {
591     signvalue = '-';
592   } else {
593     if (flags & DP_F_PLUS) { /* Do a sign (+/i) */
594       signvalue = '+';
595     } else {
596       if (flags & DP_F_SPACE)
597         signvalue = ' ';
598     }
599   }
600
601 #if 0
602   if (flags & DP_F_UP) caps = 1; /* Should characters be upper case? */
603 #endif
604
605 #if 0
606   if (max == 0) ufvalue += 0.5; /* if max = 0 we must round */
607 #endif
608
609   /*
610    * Sorry, we only support 16 digits past the decimal because of our
611    * conversion method
612    */
613   if (max > 16)
614     max = 16;
615
616   /* We "cheat" by converting the fractional part to integer by
617    * multiplying by a factor of 10
618    */
619
620   temp = ufvalue;
621   my_modf(temp, &intpart);
622
623   fracpart = ROUND((POW10(max)) * (ufvalue - intpart));
624
625   if (fracpart >= POW10(max)) {
626     intpart++;
627     fracpart -= POW10(max);
628   }
629
630
631   /* Convert integer part */
632   do {
633     temp = intpart*0.1;
634     my_modf(temp, &intpart);
635     idx = (int) ((temp -intpart +0.05)* 10.0);
636     /* idx = (int) (((double)(temp*0.1) -intpart +0.05) *10.0); */
637     /* printf ("%llf, %f, %x\n", temp, intpart, idx); */
638     iconvert[iplace++] =
639       (caps? "0123456789ABCDEF":"0123456789abcdef")[idx];
640   } while (intpart && (iplace < 311));
641   if (iplace == 311) iplace--;
642   iconvert[iplace] = 0;
643
644   /* Convert fractional part */
645   if (fracpart)
646     {
647       do {
648         temp = fracpart*0.1;
649         my_modf(temp, &fracpart);
650         idx = (int) ((temp -fracpart +0.05)* 10.0);
651         /* idx = (int) ((((temp/10) -fracpart) +0.05) *10); */
652         /* printf ("%lf, %lf, %ld\n", temp, fracpart, idx ); */
653         fconvert[fplace++] =
654           (caps? "0123456789ABCDEF":"0123456789abcdef")[idx];
655       } while(fracpart && (fplace < 311));
656       if (fplace == 311) fplace--;
657     }
658   fconvert[fplace] = 0;
659
660   /* -1 for decimal point, another -1 if we are printing a sign */
661   padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0);
662   zpadlen = max - fplace;
663   if (zpadlen < 0) zpadlen = 0;
664   if (padlen < 0)
665     padlen = 0;
666   if (flags & DP_F_MINUS)
667     padlen = -padlen; /* Left Justifty */
668
669   if ((flags & DP_F_ZERO) && (padlen > 0)) {
670     if (signvalue) {
671       dopr_outch (buffer, currlen, maxlen, signvalue);
672       --padlen;
673       signvalue = 0;
674     }
675     while (padlen > 0) {
676       dopr_outch (buffer, currlen, maxlen, '0');
677       --padlen;
678     }
679   }
680   while (padlen > 0) {
681     dopr_outch (buffer, currlen, maxlen, ' ');
682     --padlen;
683   }
684   if (signvalue)
685     dopr_outch (buffer, currlen, maxlen, signvalue);
686
687   while (iplace > 0)
688     dopr_outch (buffer, currlen, maxlen, iconvert[--iplace]);
689
690   /*
691    * Decimal point.  This should probably use locale to find the correct
692    * char to print out.
693    */
694   if (max > 0) {
695     dopr_outch (buffer, currlen, maxlen, '.');
696
697     while (zpadlen > 0) {
698       dopr_outch (buffer, currlen, maxlen, '0');
699       --zpadlen;
700     }
701
702     while (fplace > 0)
703       dopr_outch (buffer, currlen, maxlen, fconvert[--fplace]);
704   }
705
706   while (padlen < 0) {
707     dopr_outch (buffer, currlen, maxlen, ' ');
708     ++padlen;
709   }
710 }
711
712 static void dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c)
713 {
714   if (*currlen < maxlen) {
715     buffer[(*currlen)] = c;
716   }
717   (*currlen)++;
718 }
719
720 int silc_vsnprintf(char *str, size_t count, const char *fmt, va_list args)
721 {
722   if (str != NULL)
723     str[0] = 0;
724   return dopr(str, count, fmt, args);
725 }
726
727 int silc_snprintf(char *str, size_t count, const char *fmt, ...)
728 {
729   size_t ret;
730   va_list ap;
731
732   va_start(ap, fmt);
733   ret = silc_vsnprintf(str, count, fmt, ap);
734   va_end(ap);
735   return ret;
736 }
737
738 int silc_vasprintf(char **ptr, const char *format, va_list ap)
739 {
740   int ret;
741   va_list ap2;
742
743   silc_va_copy(ap2, ap);
744
745   ret = silc_vsnprintf(NULL, 0, format, ap2);
746   if (ret <= 0) return ret;
747
748   (*ptr) = (char *)silc_malloc(ret+1);
749   if (!*ptr) return -1;
750
751   silc_va_copy(ap2, ap);
752
753   ret = silc_vsnprintf(*ptr, ret+1, format, ap2);
754
755   return ret;
756 }
757
758 int silc_asprintf(char **ptr, const char *format, ...)
759 {
760   va_list ap;
761   int ret;
762
763   *ptr = NULL;
764   va_start(ap, format);
765   ret = silc_vasprintf(ptr, format, ap);
766   va_end(ap);
767
768   return ret;
769 }