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