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 * Ben Lindstrom <mouring@eviladmin.org> 09/27/00 for OpenSSH 49 * Welcome to the world of %lld and %qd support. With other 50 * long long support. This is needed for sftp-server to work 51 * right. 52 * 53 * Ben Lindstrom <mouring@eviladmin.org> 02/12/01 for OpenSSH 54 * Removed all hint of VARARGS stuff and banished it to the void, 55 * and did a bit of KNF style work to make things a bit more 56 * acceptable. Consider stealing from mutt or enlightenment. 57 **************************************************************/ 58 59 #include "includes.h" 60 61 RCSID("$Id: bsd-snprintf.c,v 1.6 2003/04/01 11:31:56 djm Exp $"); 62 63 #if defined(BROKEN_SNPRINTF) /* For those with broken snprintf() */ 64 # undef HAVE_SNPRINTF 65 # undef HAVE_VSNPRINTF 66 #endif 67 68 #if !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) 69 70 static void 71 dopr(char *buffer, size_t maxlen, const char *format, va_list args); 72 73 static void 74 fmtstr(char *buffer, size_t *currlen, size_t maxlen, char *value, int flags, 75 int min, int max); 76 77 static void 78 fmtint(char *buffer, size_t *currlen, size_t maxlen, long value, int base, 79 int min, int max, int flags); 80 81 static void 82 fmtfp(char *buffer, size_t *currlen, size_t maxlen, long double fvalue, 83 int min, int max, int flags); 84 85 static void 86 dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c); 87 88 /* 89 * dopr(): poor man's version of doprintf 90 */ 91 92 /* format read states */ 93 #define DP_S_DEFAULT 0 94 #define DP_S_FLAGS 1 95 #define DP_S_MIN 2 96 #define DP_S_DOT 3 97 #define DP_S_MAX 4 98 #define DP_S_MOD 5 99 #define DP_S_CONV 6 100 #define DP_S_DONE 7 101 102 /* format flags - Bits */ 103 #define DP_F_MINUS (1 << 0) 104 #define DP_F_PLUS (1 << 1) 105 #define DP_F_SPACE (1 << 2) 106 #define DP_F_NUM (1 << 3) 107 #define DP_F_ZERO (1 << 4) 108 #define DP_F_UP (1 << 5) 109 #define DP_F_UNSIGNED (1 << 6) 110 111 /* Conversion Flags */ 112 #define DP_C_SHORT 1 113 #define DP_C_LONG 2 114 #define DP_C_LDOUBLE 3 115 #define DP_C_LONG_LONG 4 116 117 #define char_to_int(p) (p - '0') 118 #define abs_val(p) (p < 0 ? -p : p) 119 120 121 static void 122 dopr(char *buffer, size_t maxlen, const char *format, va_list args) 123 { 124 char *strvalue; 125 char ch; 126 long value; 127 long double fvalue; 128 int min = 0; 129 int max = -1; 130 int state = DP_S_DEFAULT; 131 int flags = 0; 132 int cflags = 0; 133 size_t currlen = 0; 134 135 ch = *format++; 136 137 while (state != DP_S_DONE) { 138 if ((ch == '\0') || (currlen >= maxlen)) 139 state = DP_S_DONE; 140 141 switch(state) { 142 case DP_S_DEFAULT: 143 if (ch == '%') 144 state = DP_S_FLAGS; 145 else 146 dopr_outch(buffer, &currlen, maxlen, ch); 147 ch = *format++; 148 break; 149 case DP_S_FLAGS: 150 switch (ch) { 151 case '-': 152 flags |= DP_F_MINUS; 153 ch = *format++; 154 break; 155 case '+': 156 flags |= DP_F_PLUS; 157 ch = *format++; 158 break; 159 case ' ': 160 flags |= DP_F_SPACE; 161 ch = *format++; 162 break; 163 case '#': 164 flags |= DP_F_NUM; 165 ch = *format++; 166 break; 167 case '0': 168 flags |= DP_F_ZERO; 169 ch = *format++; 170 break; 171 default: 172 state = DP_S_MIN; 173 break; 174 } 175 break; 176 case DP_S_MIN: 177 if (isdigit((unsigned char)ch)) { 178 min = 10*min + char_to_int (ch); 179 ch = *format++; 180 } else if (ch == '*') { 181 min = va_arg (args, int); 182 ch = *format++; 183 state = DP_S_DOT; 184 } else 185 state = DP_S_DOT; 186 break; 187 case DP_S_DOT: 188 if (ch == '.') { 189 state = DP_S_MAX; 190 ch = *format++; 191 } else 192 state = DP_S_MOD; 193 break; 194 case DP_S_MAX: 195 if (isdigit((unsigned char)ch)) { 196 if (max < 0) 197 max = 0; 198 max = 10*max + char_to_int(ch); 199 ch = *format++; 200 } else if (ch == '*') { 201 max = va_arg (args, int); 202 ch = *format++; 203 state = DP_S_MOD; 204 } else 205 state = DP_S_MOD; 206 break; 207 case DP_S_MOD: 208 switch (ch) { 209 case 'h': 210 cflags = DP_C_SHORT; 211 ch = *format++; 212 break; 213 case 'l': 214 cflags = DP_C_LONG; 215 ch = *format++; 216 if (ch == 'l') { 217 cflags = DP_C_LONG_LONG; 218 ch = *format++; 219 } 220 break; 221 case 'q': 222 cflags = DP_C_LONG_LONG; 223 ch = *format++; 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_LONG_LONG) 243 value = va_arg (args, long long); 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 = va_arg(args, unsigned long int); 254 else if (cflags == DP_C_LONG_LONG) 255 value = va_arg(args, unsigned long long); 256 else 257 value = 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 = va_arg(args, unsigned long int); 266 else if (cflags == DP_C_LONG_LONG) 267 value = va_arg(args, unsigned long long); 268 else 269 value = 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 = va_arg(args, unsigned long int); 280 else if (cflags == DP_C_LONG_LONG) 281 value = va_arg(args, unsigned long long); 282 else 283 value = 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, long double); 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, long double); 299 else 300 fvalue = va_arg(args, double); 301 break; 302 case 'G': 303 flags |= DP_F_UP; 304 case 'g': 305 if (cflags == DP_C_LDOUBLE) 306 fvalue = va_arg(args, long double); 307 else 308 fvalue = va_arg(args, double); 309 break; 310 case 'c': 311 dopr_outch(buffer, &currlen, maxlen, va_arg(args, int)); 312 break; 313 case 's': 314 strvalue = va_arg(args, char *); 315 if (max < 0) 316 max = maxlen; /* ie, no max */ 317 fmtstr(buffer, &currlen, maxlen, strvalue, flags, min, max); 318 break; 319 case 'p': 320 strvalue = va_arg(args, void *); 321 fmtint(buffer, &currlen, maxlen, (long) strvalue, 16, min, max, flags); 322 break; 323 case 'n': 324 if (cflags == DP_C_SHORT) { 325 short int *num; 326 num = va_arg(args, short int *); 327 *num = currlen; 328 } else if (cflags == DP_C_LONG) { 329 long int *num; 330 num = va_arg(args, long int *); 331 *num = currlen; 332 } else if (cflags == DP_C_LONG_LONG) { 333 long long *num; 334 num = va_arg(args, long long *); 335 *num = currlen; 336 } else { 337 int *num; 338 num = va_arg(args, int *); 339 *num = currlen; 340 } 341 break; 342 case '%': 343 dopr_outch(buffer, &currlen, maxlen, ch); 344 break; 345 case 'w': /* not supported yet, treat as next char */ 346 ch = *format++; 347 break; 348 default: /* Unknown, skip */ 349 break; 350 } 351 ch = *format++; 352 state = DP_S_DEFAULT; 353 flags = cflags = min = 0; 354 max = -1; 355 break; 356 case DP_S_DONE: 357 break; 358 default: /* hmm? */ 359 break; /* some picky compilers need this */ 360 } 361 } 362 if (currlen < maxlen - 1) 363 buffer[currlen] = '\0'; 364 else 365 buffer[maxlen - 1] = '\0'; 366 } 367 368 static void 369 fmtstr(char *buffer, size_t *currlen, size_t maxlen, 370 char *value, int flags, int min, int max) 371 { 372 int padlen, strln; /* amount to pad */ 373 int cnt = 0; 374 375 if (value == 0) 376 value = "<NULL>"; 377 378 for (strln = 0; value[strln]; ++strln); /* strlen */ 379 padlen = min - strln; 380 if (padlen < 0) 381 padlen = 0; 382 if (flags & DP_F_MINUS) 383 padlen = -padlen; /* Left Justify */ 384 385 while ((padlen > 0) && (cnt < max)) { 386 dopr_outch(buffer, currlen, maxlen, ' '); 387 --padlen; 388 ++cnt; 389 } 390 while (*value && (cnt < max)) { 391 dopr_outch(buffer, currlen, maxlen, *value++); 392 ++cnt; 393 } 394 while ((padlen < 0) && (cnt < max)) { 395 dopr_outch(buffer, currlen, maxlen, ' '); 396 ++padlen; 397 ++cnt; 398 } 399 } 400 401 /* Have to handle DP_F_NUM (ie 0x and 0 alternates) */ 402 403 static void 404 fmtint(char *buffer, size_t *currlen, size_t maxlen, 405 long value, int base, int min, int max, int flags) 406 { 407 unsigned long uvalue; 408 char convert[20]; 409 int signvalue = 0; 410 int place = 0; 411 int spadlen = 0; /* amount to space pad */ 412 int zpadlen = 0; /* amount to zero pad */ 413 int caps = 0; 414 415 if (max < 0) 416 max = 0; 417 418 uvalue = value; 419 420 if (!(flags & DP_F_UNSIGNED)) { 421 if (value < 0) { 422 signvalue = '-'; 423 uvalue = -value; 424 } else if (flags & DP_F_PLUS) /* Do a sign (+/i) */ 425 signvalue = '+'; 426 else if (flags & DP_F_SPACE) 427 signvalue = ' '; 428 } 429 430 if (flags & DP_F_UP) 431 caps = 1; /* Should characters be upper case? */ 432 433 do { 434 convert[place++] = 435 (caps? "0123456789ABCDEF":"0123456789abcdef") 436 [uvalue % (unsigned)base]; 437 uvalue = (uvalue / (unsigned)base ); 438 } while (uvalue && (place < 20)); 439 if (place == 20) 440 place--; 441 convert[place] = 0; 442 443 zpadlen = max - place; 444 spadlen = min - MAX (max, place) - (signvalue ? 1 : 0); 445 if (zpadlen < 0) 446 zpadlen = 0; 447 if (spadlen < 0) 448 spadlen = 0; 449 if (flags & DP_F_ZERO) { 450 zpadlen = MAX(zpadlen, spadlen); 451 spadlen = 0; 452 } 453 if (flags & DP_F_MINUS) 454 spadlen = -spadlen; /* Left Justifty */ 455 456 457 /* Spaces */ 458 while (spadlen > 0) { 459 dopr_outch(buffer, currlen, maxlen, ' '); 460 --spadlen; 461 } 462 463 /* Sign */ 464 if (signvalue) 465 dopr_outch(buffer, currlen, maxlen, signvalue); 466 467 /* Zeros */ 468 if (zpadlen > 0) { 469 while (zpadlen > 0) { 470 dopr_outch(buffer, currlen, maxlen, '0'); 471 --zpadlen; 472 } 473 } 474 475 /* Digits */ 476 while (place > 0) 477 dopr_outch(buffer, currlen, maxlen, convert[--place]); 478 479 /* Left Justified spaces */ 480 while (spadlen < 0) { 481 dopr_outch (buffer, currlen, maxlen, ' '); 482 ++spadlen; 483 } 484 } 485 486 static long double 487 pow10(int exp) 488 { 489 long double result = 1; 490 491 while (exp) { 492 result *= 10; 493 exp--; 494 } 495 496 return result; 497 } 498 499 static long 500 round(long double value) 501 { 502 long intpart = value; 503 504 value -= intpart; 505 if (value >= 0.5) 506 intpart++; 507 508 return intpart; 509 } 510 511 static void 512 fmtfp(char *buffer, size_t *currlen, size_t maxlen, long double fvalue, 513 int min, int max, int flags) 514 { 515 char iconvert[20]; 516 char fconvert[20]; 517 int signvalue = 0; 518 int iplace = 0; 519 int fplace = 0; 520 int padlen = 0; /* amount to pad */ 521 int zpadlen = 0; 522 int caps = 0; 523 long intpart; 524 long fracpart; 525 long double ufvalue; 526 527 /* 528 * AIX manpage says the default is 0, but Solaris says the default 529 * is 6, and sprintf on AIX defaults to 6 530 */ 531 if (max < 0) 532 max = 6; 533 534 ufvalue = abs_val(fvalue); 535 536 if (fvalue < 0) 537 signvalue = '-'; 538 else if (flags & DP_F_PLUS) /* Do a sign (+/i) */ 539 signvalue = '+'; 540 else if (flags & DP_F_SPACE) 541 signvalue = ' '; 542 543 intpart = ufvalue; 544 545 /* 546 * Sorry, we only support 9 digits past the decimal because of our 547 * conversion method 548 */ 549 if (max > 9) 550 max = 9; 551 552 /* We "cheat" by converting the fractional part to integer by 553 * multiplying by a factor of 10 554 */ 555 fracpart = round((pow10 (max)) * (ufvalue - intpart)); 556 557 if (fracpart >= pow10 (max)) { 558 intpart++; 559 fracpart -= pow10 (max); 560 } 561 562 /* Convert integer part */ 563 do { 564 iconvert[iplace++] = 565 (caps? "0123456789ABCDEF":"0123456789abcdef")[intpart % 10]; 566 intpart = (intpart / 10); 567 } while(intpart && (iplace < 20)); 568 if (iplace == 20) 569 iplace--; 570 iconvert[iplace] = 0; 571 572 /* Convert fractional part */ 573 do { 574 fconvert[fplace++] = 575 (caps? "0123456789ABCDEF":"0123456789abcdef")[fracpart % 10]; 576 fracpart = (fracpart / 10); 577 } while(fracpart && (fplace < 20)); 578 if (fplace == 20) 579 fplace--; 580 fconvert[fplace] = 0; 581 582 /* -1 for decimal point, another -1 if we are printing a sign */ 583 padlen = min - iplace - max - 1 - ((signvalue) ? 1 : 0); 584 zpadlen = max - fplace; 585 if (zpadlen < 0) 586 zpadlen = 0; 587 if (padlen < 0) 588 padlen = 0; 589 if (flags & DP_F_MINUS) 590 padlen = -padlen; /* Left Justifty */ 591 592 if ((flags & DP_F_ZERO) && (padlen > 0)) { 593 if (signvalue) { 594 dopr_outch(buffer, currlen, maxlen, signvalue); 595 --padlen; 596 signvalue = 0; 597 } 598 while (padlen > 0) { 599 dopr_outch(buffer, currlen, maxlen, '0'); 600 --padlen; 601 } 602 } 603 while (padlen > 0) { 604 dopr_outch(buffer, currlen, maxlen, ' '); 605 --padlen; 606 } 607 if (signvalue) 608 dopr_outch(buffer, currlen, maxlen, signvalue); 609 610 while (iplace > 0) 611 dopr_outch(buffer, currlen, maxlen, iconvert[--iplace]); 612 613 /* 614 * Decimal point. This should probably use locale to find the correct 615 * char to print out. 616 */ 617 dopr_outch(buffer, currlen, maxlen, '.'); 618 619 while (fplace > 0) 620 dopr_outch(buffer, currlen, maxlen, fconvert[--fplace]); 621 622 while (zpadlen > 0) { 623 dopr_outch(buffer, currlen, maxlen, '0'); 624 --zpadlen; 625 } 626 627 while (padlen < 0) { 628 dopr_outch(buffer, currlen, maxlen, ' '); 629 ++padlen; 630 } 631 } 632 633 static void 634 dopr_outch(char *buffer, size_t *currlen, size_t maxlen, char c) 635 { 636 if (*currlen < maxlen) 637 buffer[(*currlen)++] = c; 638 } 639 #endif /* !defined(HAVE_SNPRINTF) || !defined(HAVE_VSNPRINTF) */ 640 641 #ifndef HAVE_VSNPRINTF 642 int 643 vsnprintf(char *str, size_t count, const char *fmt, va_list args) 644 { 645 str[0] = 0; 646 dopr(str, count, fmt, args); 647 648 return(strlen(str)); 649 } 650 #endif /* !HAVE_VSNPRINTF */ 651 652 #ifndef HAVE_SNPRINTF 653 int 654 snprintf(char *str,size_t count,const char *fmt,...) 655 { 656 va_list ap; 657 658 va_start(ap, fmt); 659 (void) vsnprintf(str, count, fmt, ap); 660 va_end(ap); 661 662 return(strlen(str)); 663 } 664 665 #ifdef TEST_SNPRINTF 666 int 667 main(void) 668 { 669 #define LONG_STRING 1024 670 char buf1[LONG_STRING]; 671 char buf2[LONG_STRING]; 672 char *fp_fmt[] = { 673 "%-1.5f", 674 "%1.5f", 675 "%123.9f", 676 "%10.5f", 677 "% 10.5f", 678 "%+22.9f", 679 "%+4.9f", 680 "%01.3f", 681 "%4f", 682 "%3.1f", 683 "%3.2f", 684 NULL 685 }; 686 double fp_nums[] = { 687 -1.5, 688 134.21, 689 91340.2, 690 341.1234, 691 0203.9, 692 0.96, 693 0.996, 694 0.9996, 695 1.996, 696 4.136, 697 0 698 }; 699 char *int_fmt[] = { 700 "%-1.5d", 701 "%1.5d", 702 "%123.9d", 703 "%5.5d", 704 "%10.5d", 705 "% 10.5d", 706 "%+22.33d", 707 "%01.3d", 708 "%4d", 709 "%lld", 710 "%qd", 711 NULL 712 }; 713 long long int_nums[] = { -1, 134, 91340, 341, 0203, 0, 9999999 }; 714 int x, y; 715 int fail = 0; 716 int num = 0; 717 718 printf("Testing snprintf format codes against system sprintf...\n"); 719 720 for (x = 0; fp_fmt[x] != NULL ; x++) { 721 for (y = 0; fp_nums[y] != 0 ; y++) { 722 snprintf(buf1, sizeof (buf1), fp_fmt[x], fp_nums[y]); 723 sprintf (buf2, fp_fmt[x], fp_nums[y]); 724 if (strcmp (buf1, buf2)) { 725 printf("snprintf doesn't match Format: %s\n\t" 726 "snprintf = %s\n\tsprintf = %s\n", 727 fp_fmt[x], buf1, buf2); 728 fail++; 729 } 730 num++; 731 } 732 } 733 for (x = 0; int_fmt[x] != NULL ; x++) { 734 for (y = 0; int_nums[y] != 0 ; y++) { 735 snprintf(buf1, sizeof (buf1), int_fmt[x], int_nums[y]); 736 sprintf(buf2, int_fmt[x], int_nums[y]); 737 if (strcmp (buf1, buf2)) { 738 printf("snprintf doesn't match Format: %s\n\t" 739 "snprintf = %s\n\tsprintf = %s\n", 740 int_fmt[x], buf1, buf2); 741 fail++; 742 } 743 num++; 744 } 745 } 746 printf("%d tests failed out of %d.\n", fail, num); 747 return(0); 748 } 749 #endif /* SNPRINTF_TEST */ 750 751 #endif /* !HAVE_SNPRINTF */ 752