1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org> 5 * All rights reserved. 6 * 7 * Copyright (c) 2011 The FreeBSD Foundation 8 * 9 * Portions of this software were developed by David Chisnall 10 * under sponsorship from the FreeBSD Foundation. 11 * 12 * Redistribution and use in source and binary forms, with or without 13 * modification, are permitted provided that the following conditions 14 * are met: 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 22 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 24 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 25 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 26 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 27 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 29 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 30 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 31 * SUCH DAMAGE. 32 * 33 */ 34 35 #include <sys/cdefs.h> 36 __FBSDID("$FreeBSD$"); 37 38 #include <sys/types.h> 39 #include <ctype.h> 40 #include <errno.h> 41 #include <limits.h> 42 #include <locale.h> 43 #include <monetary.h> 44 #include <stdarg.h> 45 #include <stdio.h> 46 #include <stdlib.h> 47 #include <string.h> 48 49 #include "xlocale_private.h" 50 51 /* internal flags */ 52 #define NEED_GROUPING 0x01 /* print digits grouped (default) */ 53 #define SIGN_POSN_USED 0x02 /* '+' or '(' usage flag */ 54 #define LOCALE_POSN 0x04 /* use locale defined +/- (default) */ 55 #define PARENTH_POSN 0x08 /* enclose negative amount in () */ 56 #define SUPPRESS_CURR_SYMBOL 0x10 /* suppress the currency from output */ 57 #define LEFT_JUSTIFY 0x20 /* left justify */ 58 #define USE_INTL_CURRENCY 0x40 /* use international currency symbol */ 59 #define IS_NEGATIVE 0x80 /* is argument value negative ? */ 60 61 /* internal macros */ 62 #define PRINT(CH) do { \ 63 if (dst >= s + maxsize) \ 64 goto e2big_error; \ 65 *dst++ = CH; \ 66 } while (0) 67 68 #define PRINTS(STR) do { \ 69 char *tmps = STR; \ 70 while (*tmps != '\0') \ 71 PRINT(*tmps++); \ 72 } while (0) 73 74 #define GET_NUMBER(VAR, LOC) do { \ 75 VAR = 0; \ 76 while (isdigit_l((unsigned char)*fmt, LOC)) { \ 77 if (VAR > INT_MAX / 10) \ 78 goto e2big_error; \ 79 VAR *= 10; \ 80 VAR += *fmt - '0'; \ 81 if (VAR < 0) \ 82 goto e2big_error; \ 83 fmt++; \ 84 } \ 85 } while (0) 86 87 #define GRPCPY(howmany) do { \ 88 int i = howmany; \ 89 while (i-- > 0) { \ 90 avalue_size--; \ 91 *--bufend = *(avalue + avalue_size + padded); \ 92 } \ 93 } while (0) 94 95 #define GRPSEP do { \ 96 bufend -= thousands_sep_size; \ 97 memcpy(bufend, thousands_sep, thousands_sep_size); \ 98 groups++; \ 99 } while (0) 100 101 static void __setup_vars(int, char *, char *, char *, char **, struct lconv *); 102 static int __calc_left_pad(int, char *, struct lconv *); 103 static char *__format_grouped_double(double, int *, int, int, int, 104 struct lconv *, locale_t); 105 106 static ssize_t 107 vstrfmon_l(char * __restrict s, size_t maxsize, locale_t loc, 108 const char * __restrict format, va_list ap) 109 { 110 char *dst; /* output destination pointer */ 111 const char *fmt; /* current format poistion pointer */ 112 struct lconv *lc; /* pointer to lconv structure */ 113 char *asciivalue; /* formatted double pointer */ 114 115 int flags; /* formatting options */ 116 int pad_char; /* padding character */ 117 int pad_size; /* pad size */ 118 int width; /* field width */ 119 int left_prec; /* left precision */ 120 int right_prec; /* right precision */ 121 double value; /* just value */ 122 char space_char = ' '; /* space after currency */ 123 124 char cs_precedes, /* values gathered from struct lconv */ 125 sep_by_space, 126 sign_posn, 127 *signstr, 128 *currency_symbol; 129 130 char *tmpptr; /* temporary vars */ 131 int sverrno; 132 FIX_LOCALE(loc); 133 134 lc = localeconv_l(loc); 135 dst = s; 136 fmt = format; 137 asciivalue = NULL; 138 currency_symbol = NULL; 139 140 while (*fmt) { 141 /* pass nonformating characters AS IS */ 142 if (*fmt != '%') 143 goto literal; 144 145 /* '%' found ! */ 146 147 /* "%%" mean just '%' */ 148 if (*(fmt + 1) == '%') { 149 fmt++; 150 literal: 151 PRINT(*fmt++); 152 continue; 153 } 154 155 /* set up initial values */ 156 flags = (NEED_GROUPING|LOCALE_POSN); 157 pad_char = ' '; /* padding character is "space" */ 158 pad_size = 0; /* no padding initially */ 159 left_prec = -1; /* no left precision specified */ 160 right_prec = -1; /* no right precision specified */ 161 width = -1; /* no width specified */ 162 value = 0; /* we have no value to print now */ 163 164 /* Flags */ 165 while (1) { 166 switch (*++fmt) { 167 case '=': /* fill character */ 168 pad_char = *++fmt; 169 if (pad_char == '\0') 170 goto format_error; 171 continue; 172 case '^': /* not group currency */ 173 flags &= ~(NEED_GROUPING); 174 continue; 175 case '+': /* use locale defined signs */ 176 if (flags & SIGN_POSN_USED) 177 goto format_error; 178 flags |= (SIGN_POSN_USED|LOCALE_POSN); 179 continue; 180 case '(': /* enclose negatives with () */ 181 if (flags & SIGN_POSN_USED) 182 goto format_error; 183 flags |= (SIGN_POSN_USED|PARENTH_POSN); 184 continue; 185 case '!': /* suppress currency symbol */ 186 flags |= SUPPRESS_CURR_SYMBOL; 187 continue; 188 case '-': /* alignment (left) */ 189 flags |= LEFT_JUSTIFY; 190 continue; 191 default: 192 break; 193 } 194 break; 195 } 196 197 /* field Width */ 198 if (isdigit_l((unsigned char)*fmt, loc)) { 199 GET_NUMBER(width, loc); 200 /* Do we have enough space to put number with 201 * required width ? 202 */ 203 if ((unsigned int)width >= maxsize - (dst - s)) 204 goto e2big_error; 205 } 206 207 /* Left precision */ 208 if (*fmt == '#') { 209 if (!isdigit_l((unsigned char)*++fmt, loc)) 210 goto format_error; 211 GET_NUMBER(left_prec, loc); 212 if ((unsigned int)left_prec >= maxsize - (dst - s)) 213 goto e2big_error; 214 } 215 216 /* Right precision */ 217 if (*fmt == '.') { 218 if (!isdigit_l((unsigned char)*++fmt, loc)) 219 goto format_error; 220 GET_NUMBER(right_prec, loc); 221 if ((unsigned int)right_prec >= maxsize - (dst - s) - 222 left_prec) 223 goto e2big_error; 224 } 225 226 /* Conversion Characters */ 227 switch (*fmt++) { 228 case 'i': /* use international currency format */ 229 flags |= USE_INTL_CURRENCY; 230 break; 231 case 'n': /* use national currency format */ 232 flags &= ~(USE_INTL_CURRENCY); 233 break; 234 default: /* required character is missing or 235 premature EOS */ 236 goto format_error; 237 } 238 239 if (currency_symbol != NULL) 240 free(currency_symbol); 241 if (flags & USE_INTL_CURRENCY) { 242 currency_symbol = strdup(lc->int_curr_symbol); 243 if (currency_symbol != NULL && 244 strlen(currency_symbol) > 3) { 245 space_char = currency_symbol[3]; 246 currency_symbol[3] = '\0'; 247 } 248 } else 249 currency_symbol = strdup(lc->currency_symbol); 250 251 if (currency_symbol == NULL) 252 goto end_error; /* ENOMEM. */ 253 254 /* value itself */ 255 value = va_arg(ap, double); 256 257 /* detect sign */ 258 if (value < 0) { 259 flags |= IS_NEGATIVE; 260 value = -value; 261 } 262 263 /* fill left_prec with amount of padding chars */ 264 if (left_prec >= 0) { 265 pad_size = __calc_left_pad((flags ^ IS_NEGATIVE), 266 currency_symbol, lc) - 267 __calc_left_pad(flags, currency_symbol, lc); 268 if (pad_size < 0) 269 pad_size = 0; 270 } 271 272 if (asciivalue != NULL) 273 free(asciivalue); 274 asciivalue = __format_grouped_double(value, &flags, 275 left_prec, right_prec, pad_char, lc, loc); 276 if (asciivalue == NULL) 277 goto end_error; /* errno already set */ 278 /* to ENOMEM by malloc() */ 279 280 /* set some variables for later use */ 281 __setup_vars(flags, &cs_precedes, &sep_by_space, 282 &sign_posn, &signstr, lc); 283 284 /* 285 * Description of some LC_MONETARY's values: 286 * 287 * p_cs_precedes & n_cs_precedes 288 * 289 * = 1 - $currency_symbol precedes the value 290 * for a monetary quantity with a non-negative value 291 * = 0 - symbol succeeds the value 292 * 293 * p_sep_by_space & n_sep_by_space 294 * 295 * = 0 - no space separates $currency_symbol 296 * from the value for a monetary quantity with a 297 * non-negative value 298 * = 1 - space separates the symbol from the value 299 * = 2 - space separates the symbol and the sign string, 300 * if adjacent; otherwise, a space separates 301 * the sign string from the value 302 * 303 * p_sign_posn & n_sign_posn 304 * 305 * = 0 - parentheses enclose the quantity and the 306 * $currency_symbol 307 * = 1 - the sign string precedes the quantity and the 308 * $currency_symbol 309 * = 2 - the sign string succeeds the quantity and the 310 * $currency_symbol 311 * = 3 - the sign string precedes the $currency_symbol 312 * = 4 - the sign string succeeds the $currency_symbol 313 * 314 */ 315 316 tmpptr = dst; 317 318 while (pad_size-- > 0) 319 PRINT(' '); 320 321 if (sign_posn == 0 && (flags & IS_NEGATIVE)) 322 PRINT('('); 323 324 if (cs_precedes == 1) { 325 if (sign_posn == 1 || sign_posn == 3) { 326 PRINTS(signstr); 327 if (sep_by_space == 2) 328 PRINT(' '); 329 } 330 331 if (!(flags & SUPPRESS_CURR_SYMBOL)) { 332 PRINTS(currency_symbol); 333 334 if (sign_posn == 4) { 335 if (sep_by_space == 2) 336 PRINT(space_char); 337 PRINTS(signstr); 338 if (sep_by_space == 1) 339 PRINT(' '); 340 } else if (sep_by_space == 1) 341 PRINT(space_char); 342 } 343 } else if (sign_posn == 1) { 344 PRINTS(signstr); 345 if (sep_by_space == 2) 346 PRINT(' '); 347 } 348 349 PRINTS(asciivalue); 350 351 if (cs_precedes == 0) { 352 if (sign_posn == 3) { 353 if (sep_by_space == 1) 354 PRINT(' '); 355 PRINTS(signstr); 356 } 357 358 if (!(flags & SUPPRESS_CURR_SYMBOL)) { 359 if ((sign_posn == 3 && sep_by_space == 2) 360 || (sep_by_space == 1 361 && (sign_posn == 0 362 || sign_posn == 1 363 || sign_posn == 2 364 || sign_posn == 4))) 365 PRINT(space_char); 366 PRINTS(currency_symbol); 367 if (sign_posn == 4) { 368 if (sep_by_space == 2) 369 PRINT(' '); 370 PRINTS(signstr); 371 } 372 } 373 } 374 375 if (sign_posn == 2) { 376 if (sep_by_space == 2) 377 PRINT(' '); 378 PRINTS(signstr); 379 } 380 381 if (sign_posn == 0) { 382 if (flags & IS_NEGATIVE) 383 PRINT(')'); 384 else if (left_prec >= 0) 385 PRINT(' '); 386 } 387 388 if (dst - tmpptr < width) { 389 if (flags & LEFT_JUSTIFY) { 390 while (dst - tmpptr < width) 391 PRINT(' '); 392 } else { 393 pad_size = dst - tmpptr; 394 memmove(tmpptr + width - pad_size, tmpptr, 395 pad_size); 396 memset(tmpptr, ' ', width - pad_size); 397 dst += width - pad_size; 398 } 399 } 400 } 401 402 PRINT('\0'); 403 free(asciivalue); 404 free(currency_symbol); 405 return (dst - s - 1); /* return size of put data except trailing '\0' */ 406 407 e2big_error: 408 errno = E2BIG; 409 goto end_error; 410 411 format_error: 412 errno = EINVAL; 413 414 end_error: 415 sverrno = errno; 416 if (asciivalue != NULL) 417 free(asciivalue); 418 if (currency_symbol != NULL) 419 free(currency_symbol); 420 errno = sverrno; 421 return (-1); 422 } 423 424 static void 425 __setup_vars(int flags, char *cs_precedes, char *sep_by_space, 426 char *sign_posn, char **signstr, struct lconv *lc) 427 { 428 if ((flags & IS_NEGATIVE) && (flags & USE_INTL_CURRENCY)) { 429 *cs_precedes = lc->int_n_cs_precedes; 430 *sep_by_space = lc->int_n_sep_by_space; 431 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_n_sign_posn; 432 *signstr = (lc->negative_sign[0] == '\0') ? "-" 433 : lc->negative_sign; 434 } else if (flags & USE_INTL_CURRENCY) { 435 *cs_precedes = lc->int_p_cs_precedes; 436 *sep_by_space = lc->int_p_sep_by_space; 437 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_p_sign_posn; 438 *signstr = lc->positive_sign; 439 } else if (flags & IS_NEGATIVE) { 440 *cs_precedes = lc->n_cs_precedes; 441 *sep_by_space = lc->n_sep_by_space; 442 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->n_sign_posn; 443 *signstr = (lc->negative_sign[0] == '\0') ? "-" 444 : lc->negative_sign; 445 } else { 446 *cs_precedes = lc->p_cs_precedes; 447 *sep_by_space = lc->p_sep_by_space; 448 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->p_sign_posn; 449 *signstr = lc->positive_sign; 450 } 451 452 /* Set default values for unspecified information. */ 453 if (*cs_precedes != 0) 454 *cs_precedes = 1; 455 if (*sep_by_space == CHAR_MAX) 456 *sep_by_space = 0; 457 if (*sign_posn == CHAR_MAX) 458 *sign_posn = 0; 459 } 460 461 static int 462 __calc_left_pad(int flags, char *cur_symb, struct lconv *lc) 463 { 464 char cs_precedes, sep_by_space, sign_posn, *signstr; 465 int left_chars = 0; 466 467 __setup_vars(flags, &cs_precedes, &sep_by_space, &sign_posn, 468 &signstr, lc); 469 470 if (cs_precedes != 0) { 471 left_chars += strlen(cur_symb); 472 if (sep_by_space != 0) 473 left_chars++; 474 } 475 476 switch (sign_posn) { 477 case 0: 478 if (flags & IS_NEGATIVE) 479 left_chars++; 480 break; 481 case 1: 482 left_chars += strlen(signstr); 483 break; 484 case 3: 485 case 4: 486 if (cs_precedes != 0) 487 left_chars += strlen(signstr); 488 } 489 return (left_chars); 490 } 491 492 static int 493 get_groups(int size, char *grouping) 494 { 495 int chars = 0; 496 497 if (*grouping == CHAR_MAX || *grouping <= 0) /* no grouping ? */ 498 return (0); 499 500 while (size > (int)*grouping) { 501 chars++; 502 size -= (int)*grouping++; 503 /* no more grouping ? */ 504 if (*grouping == CHAR_MAX) 505 break; 506 /* rest grouping with same value ? */ 507 if (*grouping == 0) { 508 chars += (size - 1) / *(grouping - 1); 509 break; 510 } 511 } 512 return (chars); 513 } 514 515 /* convert double to locale-encoded string */ 516 static char * 517 __format_grouped_double(double value, int *flags, 518 int left_prec, int right_prec, int pad_char, struct lconv *lc, locale_t loc) 519 { 520 521 char *rslt; 522 char *avalue; 523 int avalue_size; 524 525 size_t bufsize; 526 char *bufend; 527 528 int padded; 529 530 char *grouping; 531 const char *decimal_point; 532 const char *thousands_sep; 533 size_t decimal_point_size; 534 size_t thousands_sep_size; 535 536 int groups = 0; 537 538 grouping = lc->mon_grouping; 539 decimal_point = lc->mon_decimal_point; 540 if (*decimal_point == '\0') 541 decimal_point = lc->decimal_point; 542 thousands_sep = lc->mon_thousands_sep; 543 if (*thousands_sep == '\0') 544 thousands_sep = lc->thousands_sep; 545 546 decimal_point_size = strlen(decimal_point); 547 thousands_sep_size = strlen(thousands_sep); 548 549 /* fill left_prec with default value */ 550 if (left_prec == -1) 551 left_prec = 0; 552 553 /* fill right_prec with default value */ 554 if (right_prec == -1) { 555 if (*flags & USE_INTL_CURRENCY) 556 right_prec = lc->int_frac_digits; 557 else 558 right_prec = lc->frac_digits; 559 560 if (right_prec == CHAR_MAX) /* POSIX locale ? */ 561 right_prec = 2; 562 } 563 564 if (*flags & NEED_GROUPING) 565 left_prec += get_groups(left_prec, grouping); 566 567 /* convert to string */ 568 avalue_size = asprintf_l(&avalue, loc, "%*.*f", 569 left_prec + right_prec + 1, right_prec, value); 570 if (avalue_size < 0) 571 return (NULL); 572 573 /* make sure that we've enough space for result string */ 574 bufsize = avalue_size * (1 + thousands_sep_size) + decimal_point_size + 575 1; 576 rslt = calloc(1, bufsize); 577 if (rslt == NULL) { 578 free(avalue); 579 return (NULL); 580 } 581 bufend = rslt + bufsize - 1; /* reserve space for trailing '\0' */ 582 583 /* skip spaces at beginning */ 584 padded = 0; 585 while (avalue[padded] == ' ') { 586 padded++; 587 avalue_size--; 588 } 589 590 if (right_prec > 0) { 591 bufend -= right_prec; 592 memcpy(bufend, avalue + avalue_size + padded - right_prec, 593 right_prec); 594 bufend -= decimal_point_size; 595 memcpy(bufend, decimal_point, decimal_point_size); 596 avalue_size -= (right_prec + 1); 597 } 598 599 if ((*flags & NEED_GROUPING) && 600 thousands_sep_size > 0 && 601 *grouping != CHAR_MAX && 602 *grouping > 0) { 603 while (avalue_size > (int)*grouping) { 604 GRPCPY(*grouping); 605 GRPSEP; 606 grouping++; 607 608 /* no more grouping ? */ 609 if (*grouping == CHAR_MAX) 610 break; 611 612 /* rest grouping with same value ? */ 613 if (*grouping == 0) { 614 grouping--; 615 while (avalue_size > *grouping) { 616 GRPCPY(*grouping); 617 GRPSEP; 618 } 619 } 620 } 621 if (avalue_size != 0) 622 GRPCPY(avalue_size); 623 padded -= groups; 624 } else { 625 bufend -= avalue_size; 626 memcpy(bufend, avalue + padded, avalue_size); 627 /* decrease assumed $decimal_point */ 628 if (right_prec == 0) 629 padded -= decimal_point_size; 630 } 631 632 /* do padding with pad_char */ 633 if (padded > 0) { 634 bufend -= padded; 635 memset(bufend, pad_char, padded); 636 } 637 638 bufsize = rslt + bufsize - bufend; 639 memmove(rslt, bufend, bufsize); 640 free(avalue); 641 return (rslt); 642 } 643 644 ssize_t 645 strfmon(char * __restrict s, size_t maxsize, const char * __restrict format, 646 ...) 647 { 648 ssize_t ret; 649 va_list ap; 650 651 va_start(ap, format); 652 ret = vstrfmon_l(s, maxsize, __get_locale(), format, ap); 653 va_end(ap); 654 655 return (ret); 656 } 657 658 ssize_t 659 strfmon_l(char * __restrict s, size_t maxsize, locale_t loc, 660 const char * __restrict format, ...) 661 { 662 ssize_t ret; 663 va_list ap; 664 665 va_start(ap, format); 666 ret = vstrfmon_l(s, maxsize, loc, format, ap); 667 va_end(ap); 668 669 return (ret); 670 } 671