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