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