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