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