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/types.h> 36 37 #include <ctype.h> 38 #include <errno.h> 39 #include <limits.h> 40 #include <locale.h> 41 #include <monetary.h> 42 #include <stdarg.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 47 #include "xlocale_private.h" 48 49 /* internal flags */ 50 #define NEED_GROUPING 0x01 /* print digits grouped (default) */ 51 #define SIGN_POSN_USED 0x02 /* '+' or '(' usage flag */ 52 #define LOCALE_POSN 0x04 /* use locale defined +/- (default) */ 53 #define PARENTH_POSN 0x08 /* enclose negative amount in () */ 54 #define SUPPRESS_CURR_SYMBOL 0x10 /* suppress the currency from output */ 55 #define LEFT_JUSTIFY 0x20 /* left justify */ 56 #define USE_INTL_CURRENCY 0x40 /* use international currency symbol */ 57 #define IS_NEGATIVE 0x80 /* is argument value negative ? */ 58 59 /* internal macros */ 60 #define PRINT(CH) do { \ 61 if (dst >= s + maxsize) \ 62 goto e2big_error; \ 63 *dst++ = CH; \ 64 } while (0) 65 66 #define PRINTS(STR) do { \ 67 char *tmps = STR; \ 68 while (*tmps != '\0') \ 69 PRINT(*tmps++); \ 70 } while (0) 71 72 #define GET_NUMBER(VAR, LOC) do { \ 73 VAR = 0; \ 74 while (isdigit_l((unsigned char)*fmt, LOC)) { \ 75 if (VAR > INT_MAX / 10) \ 76 goto e2big_error; \ 77 VAR *= 10; \ 78 VAR += *fmt - '0'; \ 79 if (VAR < 0) \ 80 goto e2big_error; \ 81 fmt++; \ 82 } \ 83 } while (0) 84 85 #define GRPCPY(howmany) do { \ 86 int i = howmany; \ 87 while (i-- > 0) { \ 88 avalue_size--; \ 89 *--bufend = *(avalue + avalue_size + padded); \ 90 } \ 91 } while (0) 92 93 #define GRPSEP do { \ 94 bufend -= thousands_sep_size; \ 95 memcpy(bufend, thousands_sep, thousands_sep_size); \ 96 groups++; \ 97 } while (0) 98 99 static void __setup_vars(int, char *, char *, char *, char **, struct lconv *); 100 static int __calc_left_pad(int, char *, struct lconv *); 101 static char *__format_grouped_double(double, int *, int, int, int, 102 struct lconv *, locale_t); 103 104 static ssize_t 105 vstrfmon_l(char *__restrict s, size_t maxsize, locale_t loc, 106 const char *__restrict format, va_list ap) 107 { 108 char *dst; /* output destination pointer */ 109 const char *fmt; /* current format position pointer */ 110 struct lconv *lc; /* pointer to lconv structure */ 111 char *asciivalue; /* formatted double pointer */ 112 113 int flags; /* formatting options */ 114 int pad_char; /* padding character */ 115 int pad_size; /* pad size */ 116 int width; /* field width */ 117 int left_prec; /* left precision */ 118 int right_prec; /* right precision */ 119 double value; /* just value */ 120 char space_char = ' '; /* space after currency */ 121 122 char cs_precedes, /* values gathered from struct lconv */ 123 sep_by_space, 124 sign_posn, 125 *signstr, 126 *currency_symbol; 127 128 char *tmpptr; /* temporary vars */ 129 int sverrno; 130 131 FIX_LOCALE(loc); 132 133 lc = localeconv_l(loc); 134 dst = s; 135 fmt = format; 136 asciivalue = NULL; 137 currency_symbol = NULL; 138 139 while (*fmt != 0) { 140 /* pass nonformating characters AS IS */ 141 if (*fmt != '%') 142 goto literal; 143 144 /* '%' found ! */ 145 146 /* "%%" mean just '%' */ 147 if (*(fmt + 1) == '%') { 148 fmt++; 149 literal: 150 PRINT(*fmt++); 151 continue; 152 } 153 154 /* set up initial values */ 155 flags = NEED_GROUPING | LOCALE_POSN; 156 pad_char = ' '; /* padding character is "space" */ 157 pad_size = 0; /* no padding initially */ 158 left_prec = -1; /* no left precision specified */ 159 right_prec = -1; /* no right precision specified */ 160 width = -1; /* no width specified */ 161 162 /* Flags */ 163 while (1) { 164 switch (*++fmt) { 165 case '=': /* fill character */ 166 pad_char = *++fmt; 167 if (pad_char == '\0') 168 goto format_error; 169 continue; 170 case '^': /* not group currency */ 171 flags &= ~(NEED_GROUPING); 172 continue; 173 case '+': /* use locale defined signs */ 174 if ((flags & SIGN_POSN_USED) || 175 ((lc->positive_sign[0] == '\0') && 176 (lc->negative_sign[0] == '\0'))) 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 /* 201 * Do we have enough space to put number with 202 * required width ? 203 */ 204 if ((unsigned int)width >= maxsize - (dst - s)) 205 goto e2big_error; 206 } 207 208 /* Left precision */ 209 if (*fmt == '#') { 210 if (!isdigit_l((unsigned char)*++fmt, loc)) 211 goto format_error; 212 GET_NUMBER(left_prec, loc); 213 if ((unsigned int)left_prec >= maxsize - (dst - s)) 214 goto e2big_error; 215 } 216 217 /* Right precision */ 218 if (*fmt == '.') { 219 if (!isdigit_l((unsigned char)*++fmt, loc)) 220 goto format_error; 221 GET_NUMBER(right_prec, loc); 222 if ((unsigned int)right_prec >= 223 maxsize - (dst - s) - left_prec) 224 goto e2big_error; 225 } 226 227 /* Conversion Characters */ 228 switch (*fmt++) { 229 case 'i': /* use international currency format */ 230 flags |= USE_INTL_CURRENCY; 231 break; 232 case 'n': /* use national currency format */ 233 flags &= ~(USE_INTL_CURRENCY); 234 break; 235 default: /* 236 * required character is missing or 237 * premature EOS 238 */ 239 goto format_error; 240 } 241 242 if (currency_symbol != NULL) 243 free(currency_symbol); 244 if (flags & USE_INTL_CURRENCY) { 245 currency_symbol = strdup(lc->int_curr_symbol); 246 if (currency_symbol != NULL && 247 strlen(currency_symbol) > 3) { 248 space_char = currency_symbol[3]; 249 currency_symbol[3] = '\0'; 250 } 251 } else 252 currency_symbol = strdup(lc->currency_symbol); 253 254 if (currency_symbol == NULL) 255 goto end_error; /* ENOMEM. */ 256 257 /* value itself */ 258 value = va_arg(ap, double); 259 260 /* detect sign */ 261 if (value < 0) { 262 flags |= IS_NEGATIVE; 263 value = -value; 264 } 265 266 /* fill left_prec with amount of padding chars */ 267 if (left_prec >= 0) { 268 pad_size = __calc_left_pad((flags ^ IS_NEGATIVE), 269 currency_symbol, lc) - 270 __calc_left_pad(flags, currency_symbol, lc); 271 if (pad_size < 0) 272 pad_size = 0; 273 } 274 275 if (asciivalue != NULL) 276 free(asciivalue); 277 asciivalue = __format_grouped_double(value, &flags, left_prec, 278 right_prec, pad_char, lc, loc); 279 if (asciivalue == NULL) 280 goto end_error; /* 281 * errno already set to ENOMEM by 282 * malloc() 283 */ 284 285 /* set some variables for later use */ 286 __setup_vars(flags, &cs_precedes, &sep_by_space, &sign_posn, 287 &signstr, lc); 288 289 /* 290 * Description of some LC_MONETARY's values: 291 * 292 * p_cs_precedes & n_cs_precedes 293 * 294 * = 1 - $currency_symbol precedes the value 295 * for a monetary quantity with a non-negative value 296 * = 0 - symbol succeeds the value 297 * 298 * p_sep_by_space & n_sep_by_space 299 * 300 * = 0 - no space separates $currency_symbol 301 * from the value for a monetary quantity with a 302 * non-negative value 303 * = 1 - space separates the symbol from the value 304 * = 2 - space separates the symbol and the sign string, 305 * if adjacent; otherwise, a space separates 306 * the sign string from the value 307 * 308 * p_sign_posn & n_sign_posn 309 * 310 * = 0 - parentheses enclose the quantity and the 311 * $currency_symbol 312 * = 1 - the sign string precedes the quantity and the 313 * $currency_symbol 314 * = 2 - the sign string succeeds the quantity and the 315 * $currency_symbol 316 * = 3 - the sign string precedes the $currency_symbol 317 * = 4 - the sign string succeeds the $currency_symbol 318 */ 319 320 tmpptr = dst; 321 322 while (pad_size-- > 0) 323 PRINT(' '); 324 325 if (sign_posn == 0 && (flags & IS_NEGATIVE)) 326 PRINT('('); 327 328 if (cs_precedes == 1) { 329 if (sign_posn == 1 || sign_posn == 3) { 330 PRINTS(signstr); 331 if (sep_by_space == 2) 332 PRINT(' '); 333 } 334 335 if (!(flags & SUPPRESS_CURR_SYMBOL)) { 336 PRINTS(currency_symbol); 337 338 if (sign_posn == 4) { 339 if (sep_by_space == 2) 340 PRINT(space_char); 341 PRINTS(signstr); 342 if (sep_by_space == 1) 343 PRINT(' '); 344 } else if (sep_by_space == 1) 345 PRINT(space_char); 346 } 347 } else if (sign_posn == 1) { 348 PRINTS(signstr); 349 if (sep_by_space == 2) 350 PRINT(' '); 351 } 352 353 PRINTS(asciivalue); 354 355 if (cs_precedes == 0) { 356 if (sign_posn == 3) { 357 if (sep_by_space == 1) 358 PRINT(' '); 359 PRINTS(signstr); 360 } 361 362 if (!(flags & SUPPRESS_CURR_SYMBOL)) { 363 if ((sign_posn == 3 && sep_by_space == 2) || 364 (sep_by_space == 1 && 365 (sign_posn == 0 || sign_posn == 1 || 366 sign_posn == 2 || sign_posn == 4))) 367 PRINT(space_char); 368 PRINTS(currency_symbol); 369 if (sign_posn == 4) { 370 if (sep_by_space == 2) 371 PRINT(' '); 372 PRINTS(signstr); 373 } 374 } 375 } 376 377 if (sign_posn == 2) { 378 if (sep_by_space == 2) 379 PRINT(' '); 380 PRINTS(signstr); 381 } 382 383 if (sign_posn == 0) { 384 if (flags & IS_NEGATIVE) 385 PRINT(')'); 386 else if (left_prec >= 0) 387 PRINT(' '); 388 } 389 390 if (dst - tmpptr < width) { 391 if (flags & LEFT_JUSTIFY) { 392 while (dst - tmpptr < width) 393 PRINT(' '); 394 } else { 395 pad_size = dst - tmpptr; 396 memmove(tmpptr + width - pad_size, tmpptr, 397 pad_size); 398 memset(tmpptr, ' ', width - pad_size); 399 dst += width - pad_size; 400 } 401 } 402 } 403 404 PRINT('\0'); 405 free(asciivalue); 406 free(currency_symbol); 407 return (dst - s - 1); /* size of put data except trailing '\0' */ 408 409 e2big_error: 410 errno = E2BIG; 411 goto end_error; 412 413 format_error: 414 errno = EINVAL; 415 416 end_error: 417 sverrno = errno; 418 if (asciivalue != NULL) 419 free(asciivalue); 420 if (currency_symbol != NULL) 421 free(currency_symbol); 422 errno = sverrno; 423 return (-1); 424 } 425 426 static void 427 __setup_vars(int flags, char *cs_precedes, char *sep_by_space, char *sign_posn, 428 char **signstr, struct lconv *lc) 429 { 430 if ((flags & IS_NEGATIVE) && (flags & USE_INTL_CURRENCY)) { 431 *cs_precedes = lc->int_n_cs_precedes; 432 *sep_by_space = lc->int_n_sep_by_space; 433 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_n_sign_posn; 434 *signstr = (lc->negative_sign[0] == '\0') ? "-" : 435 lc->negative_sign; 436 } else if (flags & USE_INTL_CURRENCY) { 437 *cs_precedes = lc->int_p_cs_precedes; 438 *sep_by_space = lc->int_p_sep_by_space; 439 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_p_sign_posn; 440 *signstr = lc->positive_sign; 441 } else if (flags & IS_NEGATIVE) { 442 *cs_precedes = lc->n_cs_precedes; 443 *sep_by_space = lc->n_sep_by_space; 444 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->n_sign_posn; 445 *signstr = (lc->negative_sign[0] == '\0') ? "-" : 446 lc->negative_sign; 447 } else { 448 *cs_precedes = lc->p_cs_precedes; 449 *sep_by_space = lc->p_sep_by_space; 450 *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->p_sign_posn; 451 *signstr = lc->positive_sign; 452 } 453 454 /* Set default values for unspecified information. */ 455 if (*cs_precedes != 0) 456 *cs_precedes = 1; 457 if (*sep_by_space == CHAR_MAX) 458 *sep_by_space = 0; 459 if (*sign_posn == CHAR_MAX) 460 *sign_posn = 1; 461 } 462 463 static int 464 __calc_left_pad(int flags, char *cur_symb, struct lconv *lc) 465 { 466 char cs_precedes, sep_by_space, sign_posn, *signstr; 467 int left_chars = 0; 468 469 __setup_vars(flags, &cs_precedes, &sep_by_space, &sign_posn, &signstr, 470 lc); 471 472 if (cs_precedes != 0) { 473 left_chars += strlen(cur_symb); 474 if (sep_by_space != 0) 475 left_chars++; 476 } 477 478 switch (sign_posn) { 479 case 0: 480 if (flags & IS_NEGATIVE) 481 left_chars++; 482 break; 483 case 1: 484 left_chars += strlen(signstr); 485 break; 486 case 3: 487 case 4: 488 if (cs_precedes != 0) 489 left_chars += strlen(signstr); 490 } 491 return (left_chars); 492 } 493 494 static int 495 get_groups(int size, char *grouping) 496 { 497 int chars = 0; 498 499 if (*grouping == CHAR_MAX || *grouping <= 0) /* no grouping ? */ 500 return (0); 501 502 while (size > (int)*grouping) { 503 chars++; 504 size -= (int)*grouping++; 505 /* no more grouping ? */ 506 if (*grouping == CHAR_MAX) 507 break; 508 /* rest grouping with same value ? */ 509 if (*grouping == 0) { 510 chars += (size - 1) / *(grouping - 1); 511 break; 512 } 513 } 514 return (chars); 515 } 516 517 /* convert double to locale-encoded string */ 518 static char * 519 __format_grouped_double(double value, int *flags, int left_prec, int right_prec, 520 int pad_char, struct lconv *lc, locale_t loc) 521 { 522 523 char *rslt; 524 char *avalue; 525 int avalue_size; 526 527 size_t bufsize; 528 char *bufend; 529 530 int padded; 531 532 char *grouping; 533 const char *decimal_point; 534 const char *thousands_sep; 535 size_t decimal_point_size; 536 size_t thousands_sep_size; 537 538 int groups = 0; 539 540 grouping = lc->mon_grouping; 541 decimal_point = lc->mon_decimal_point; 542 if (*decimal_point == '\0') 543 decimal_point = lc->decimal_point; 544 thousands_sep = lc->mon_thousands_sep; 545 if (*thousands_sep == '\0') 546 thousands_sep = lc->thousands_sep; 547 548 decimal_point_size = strlen(decimal_point); 549 thousands_sep_size = strlen(thousands_sep); 550 551 /* fill left_prec with default value */ 552 if (left_prec == -1) 553 left_prec = 0; 554 555 /* fill right_prec with default value */ 556 if (right_prec == -1) { 557 if (*flags & USE_INTL_CURRENCY) 558 right_prec = lc->int_frac_digits; 559 else 560 right_prec = lc->frac_digits; 561 562 if (right_prec == CHAR_MAX) /* POSIX locale ? */ 563 right_prec = 2; 564 } 565 566 if (*flags & NEED_GROUPING) 567 left_prec += get_groups(left_prec, grouping); 568 569 /* convert to string */ 570 avalue_size = asprintf_l(&avalue, loc, "%*.*f", 571 left_prec + right_prec + 1, right_prec, value); 572 if (avalue_size < 0) 573 return (NULL); 574 575 /* make sure that we've enough space for result string */ 576 bufsize = avalue_size * (1 + thousands_sep_size) + decimal_point_size + 577 1; 578 rslt = calloc(1, bufsize); 579 if (rslt == NULL) { 580 free(avalue); 581 return (NULL); 582 } 583 bufend = rslt + bufsize - 1; /* reserve space for trailing '\0' */ 584 585 /* skip spaces at beginning */ 586 padded = 0; 587 while (avalue[padded] == ' ') { 588 padded++; 589 avalue_size--; 590 } 591 592 if (right_prec > 0) { 593 bufend -= right_prec; 594 memcpy(bufend, avalue + avalue_size + padded - right_prec, 595 right_prec); 596 bufend -= decimal_point_size; 597 memcpy(bufend, decimal_point, decimal_point_size); 598 avalue_size -= (right_prec + 1); 599 } 600 601 if ((*flags & NEED_GROUPING) && thousands_sep_size > 0 && 602 *grouping != CHAR_MAX && *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 ssize_t ret; 648 va_list ap; 649 650 va_start(ap, format); 651 ret = vstrfmon_l(s, maxsize, __get_locale(), format, ap); 652 va_end(ap); 653 654 return (ret); 655 } 656 657 ssize_t 658 strfmon_l(char *restrict s, size_t maxsize, locale_t loc, 659 const char *restrict format, ...) 660 { 661 ssize_t ret; 662 va_list ap; 663 664 va_start(ap, format); 665 ret = vstrfmon_l(s, maxsize, loc, format, ap); 666 va_end(ap); 667 668 return (ret); 669 } 670