19d430a59SAlexey Zelkin /*-
24d846d26SWarner Losh * SPDX-License-Identifier: BSD-2-Clause
3d915a14eSPedro F. Giffuni *
452d6b430SAlexey Zelkin * Copyright (c) 2001 Alexey Zelkin <phantom@FreeBSD.org>
59d430a59SAlexey Zelkin * All rights reserved.
69d430a59SAlexey Zelkin *
73c87aa1dSDavid Chisnall * Copyright (c) 2011 The FreeBSD Foundation
85b5fa75aSEd Maste *
93c87aa1dSDavid Chisnall * Portions of this software were developed by David Chisnall
103c87aa1dSDavid Chisnall * under sponsorship from the FreeBSD Foundation.
113c87aa1dSDavid Chisnall *
129d430a59SAlexey Zelkin * Redistribution and use in source and binary forms, with or without
139d430a59SAlexey Zelkin * modification, are permitted provided that the following conditions
149d430a59SAlexey Zelkin * are met:
159d430a59SAlexey Zelkin * 1. Redistributions of source code must retain the above copyright
169d430a59SAlexey Zelkin * notice, this list of conditions and the following disclaimer.
179d430a59SAlexey Zelkin * 2. Redistributions in binary form must reproduce the above copyright
189d430a59SAlexey Zelkin * notice, this list of conditions and the following disclaimer in the
199d430a59SAlexey Zelkin * documentation and/or other materials provided with the distribution.
209d430a59SAlexey Zelkin *
219d430a59SAlexey Zelkin * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
229d430a59SAlexey Zelkin * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
239d430a59SAlexey Zelkin * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
249d430a59SAlexey Zelkin * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
259d430a59SAlexey Zelkin * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
269d430a59SAlexey Zelkin * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
279d430a59SAlexey Zelkin * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
289d430a59SAlexey Zelkin * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
299d430a59SAlexey Zelkin * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
309d430a59SAlexey Zelkin * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
319d430a59SAlexey Zelkin * SUCH DAMAGE.
329d430a59SAlexey Zelkin *
339d430a59SAlexey Zelkin */
349d430a59SAlexey Zelkin
359d430a59SAlexey Zelkin #include <sys/types.h>
3656a0d544SJose Luis Duran
379d430a59SAlexey Zelkin #include <ctype.h>
389d430a59SAlexey Zelkin #include <errno.h>
399d430a59SAlexey Zelkin #include <limits.h>
409d430a59SAlexey Zelkin #include <locale.h>
4161310091SStefan Farfeleder #include <monetary.h>
429d430a59SAlexey Zelkin #include <stdarg.h>
439d430a59SAlexey Zelkin #include <stdio.h>
449d430a59SAlexey Zelkin #include <stdlib.h>
459d430a59SAlexey Zelkin #include <string.h>
46f81dfea2SJose Luis Duran
473c87aa1dSDavid Chisnall #include "xlocale_private.h"
489d430a59SAlexey Zelkin
499d430a59SAlexey Zelkin /* internal flags */
509d430a59SAlexey Zelkin #define NEED_GROUPING 0x01 /* print digits grouped (default) */
519d430a59SAlexey Zelkin #define SIGN_POSN_USED 0x02 /* '+' or '(' usage flag */
529d430a59SAlexey Zelkin #define LOCALE_POSN 0x04 /* use locale defined +/- (default) */
539d430a59SAlexey Zelkin #define PARENTH_POSN 0x08 /* enclose negative amount in () */
54d5980dffSJose Luis Duran #define SUPPRESS_CURR_SYMBOL 0x10 /* suppress the currency from output */
559d430a59SAlexey Zelkin #define LEFT_JUSTIFY 0x20 /* left justify */
569d430a59SAlexey Zelkin #define USE_INTL_CURRENCY 0x40 /* use international currency symbol */
579d430a59SAlexey Zelkin #define IS_NEGATIVE 0x80 /* is argument value negative ? */
589d430a59SAlexey Zelkin
599d430a59SAlexey Zelkin /* internal macros */
60a5aecc77SMike Barcroft #define PRINT(CH) do { \
619d430a59SAlexey Zelkin if (dst >= s + maxsize) \
629d430a59SAlexey Zelkin goto e2big_error; \
639d430a59SAlexey Zelkin *dst++ = CH; \
64a5aecc77SMike Barcroft } while (0)
659d430a59SAlexey Zelkin
66a5aecc77SMike Barcroft #define PRINTS(STR) do { \
679d430a59SAlexey Zelkin char *tmps = STR; \
689d430a59SAlexey Zelkin while (*tmps != '\0') \
699d430a59SAlexey Zelkin PRINT(*tmps++); \
70a5aecc77SMike Barcroft } while (0)
719d430a59SAlexey Zelkin
72621bf918SJose Luis Duran #define GET_NUMBER(VAR, LOC) do { \
739d430a59SAlexey Zelkin VAR = 0; \
74621bf918SJose Luis Duran while (isdigit_l((unsigned char)*fmt, LOC)) { \
75eff93c80SRuslan Ermilov if (VAR > INT_MAX / 10) \
76eff93c80SRuslan Ermilov goto e2big_error; \
779d430a59SAlexey Zelkin VAR *= 10; \
789d430a59SAlexey Zelkin VAR += *fmt - '0'; \
793890416fSRuslan Ermilov if (VAR < 0) \
803890416fSRuslan Ermilov goto e2big_error; \
819d430a59SAlexey Zelkin fmt++; \
829d430a59SAlexey Zelkin } \
83a5aecc77SMike Barcroft } while (0)
84a5aecc77SMike Barcroft
85a5aecc77SMike Barcroft #define GRPCPY(howmany) do { \
86a5aecc77SMike Barcroft int i = howmany; \
87a5aecc77SMike Barcroft while (i-- > 0) { \
88a5aecc77SMike Barcroft avalue_size--; \
89a5aecc77SMike Barcroft *--bufend = *(avalue + avalue_size + padded); \
90a5aecc77SMike Barcroft } \
91a5aecc77SMike Barcroft } while (0)
92a5aecc77SMike Barcroft
93a5aecc77SMike Barcroft #define GRPSEP do { \
948c1c50ffSConrad Meyer bufend -= thousands_sep_size; \
958c1c50ffSConrad Meyer memcpy(bufend, thousands_sep, thousands_sep_size); \
96a5aecc77SMike Barcroft groups++; \
97a5aecc77SMike Barcroft } while (0)
989d430a59SAlexey Zelkin
99621bf918SJose Luis Duran static void __setup_vars(int, char *, char *, char *, char **, struct lconv *);
100621bf918SJose Luis Duran static int __calc_left_pad(int, char *, struct lconv *);
101621bf918SJose Luis Duran static char *__format_grouped_double(double, int *, int, int, int,
102621bf918SJose Luis Duran struct lconv *, locale_t);
1039d430a59SAlexey Zelkin
1043c87aa1dSDavid Chisnall static ssize_t
vstrfmon_l(char * __restrict s,size_t maxsize,locale_t loc,const char * __restrict format,va_list ap)1053c87aa1dSDavid Chisnall vstrfmon_l(char *__restrict s, size_t maxsize, locale_t loc,
1063c87aa1dSDavid Chisnall const char *__restrict format, va_list ap)
1079d430a59SAlexey Zelkin {
1089d430a59SAlexey Zelkin char *dst; /* output destination pointer */
1099d430a59SAlexey Zelkin const char *fmt; /* current format poistion pointer */
1109d430a59SAlexey Zelkin struct lconv *lc; /* pointer to lconv structure */
1119d430a59SAlexey Zelkin char *asciivalue; /* formatted double pointer */
1129d430a59SAlexey Zelkin
1139d430a59SAlexey Zelkin int flags; /* formatting options */
1149d430a59SAlexey Zelkin int pad_char; /* padding character */
1159d430a59SAlexey Zelkin int pad_size; /* pad size */
1169d430a59SAlexey Zelkin int width; /* field width */
1179d430a59SAlexey Zelkin int left_prec; /* left precision */
1189d430a59SAlexey Zelkin int right_prec; /* right precision */
1199d430a59SAlexey Zelkin double value; /* just value */
1209d430a59SAlexey Zelkin char space_char = ' '; /* space after currency */
1219d430a59SAlexey Zelkin
1229d430a59SAlexey Zelkin char cs_precedes, /* values gathered from struct lconv */
1239d430a59SAlexey Zelkin sep_by_space,
1249d430a59SAlexey Zelkin sign_posn,
1259d430a59SAlexey Zelkin *signstr,
1269d430a59SAlexey Zelkin *currency_symbol;
1279d430a59SAlexey Zelkin
1289d430a59SAlexey Zelkin char *tmpptr; /* temporary vars */
1292e9212d9STim J. Robbins int sverrno;
13056a0d544SJose Luis Duran
1313c87aa1dSDavid Chisnall FIX_LOCALE(loc);
1329d430a59SAlexey Zelkin
1333c87aa1dSDavid Chisnall lc = localeconv_l(loc);
1349d430a59SAlexey Zelkin dst = s;
1359d430a59SAlexey Zelkin fmt = format;
1369d430a59SAlexey Zelkin asciivalue = NULL;
1379d430a59SAlexey Zelkin currency_symbol = NULL;
1389d430a59SAlexey Zelkin
13956a0d544SJose Luis Duran while (*fmt != 0) {
1409d430a59SAlexey Zelkin /* pass nonformating characters AS IS */
141a5aecc77SMike Barcroft if (*fmt != '%')
1429d430a59SAlexey Zelkin goto literal;
1439d430a59SAlexey Zelkin
1449d430a59SAlexey Zelkin /* '%' found ! */
1459d430a59SAlexey Zelkin
1469d430a59SAlexey Zelkin /* "%%" mean just '%' */
1479d430a59SAlexey Zelkin if (*(fmt + 1) == '%') {
1489d430a59SAlexey Zelkin fmt++;
1499d430a59SAlexey Zelkin literal:
1509d430a59SAlexey Zelkin PRINT(*fmt++);
1519d430a59SAlexey Zelkin continue;
1529d430a59SAlexey Zelkin }
1539d430a59SAlexey Zelkin
1549d430a59SAlexey Zelkin /* set up initial values */
15556a0d544SJose Luis Duran flags = NEED_GROUPING | LOCALE_POSN;
1569d430a59SAlexey Zelkin pad_char = ' '; /* padding character is "space" */
15734f88528SJose Luis Duran pad_size = 0; /* no padding initially */
1589d430a59SAlexey Zelkin left_prec = -1; /* no left precision specified */
1599d430a59SAlexey Zelkin right_prec = -1; /* no right precision specified */
1609d430a59SAlexey Zelkin width = -1; /* no width specified */
1619d430a59SAlexey Zelkin
1629d430a59SAlexey Zelkin /* Flags */
1639d430a59SAlexey Zelkin while (1) {
1649d430a59SAlexey Zelkin switch (*++fmt) {
1659d430a59SAlexey Zelkin case '=': /* fill character */
1669d430a59SAlexey Zelkin pad_char = *++fmt;
1679d430a59SAlexey Zelkin if (pad_char == '\0')
1689d430a59SAlexey Zelkin goto format_error;
1699d430a59SAlexey Zelkin continue;
1709d430a59SAlexey Zelkin case '^': /* not group currency */
1719d430a59SAlexey Zelkin flags &= ~(NEED_GROUPING);
1729d430a59SAlexey Zelkin continue;
1739d430a59SAlexey Zelkin case '+': /* use locale defined signs */
1749d430a59SAlexey Zelkin if (flags & SIGN_POSN_USED)
1759d430a59SAlexey Zelkin goto format_error;
1769d430a59SAlexey Zelkin flags |= (SIGN_POSN_USED | LOCALE_POSN);
1779d430a59SAlexey Zelkin continue;
1789d430a59SAlexey Zelkin case '(': /* enclose negatives with () */
1799d430a59SAlexey Zelkin if (flags & SIGN_POSN_USED)
1809d430a59SAlexey Zelkin goto format_error;
1819d430a59SAlexey Zelkin flags |= (SIGN_POSN_USED | PARENTH_POSN);
1829d430a59SAlexey Zelkin continue;
1839d430a59SAlexey Zelkin case '!': /* suppress currency symbol */
184d5980dffSJose Luis Duran flags |= SUPPRESS_CURR_SYMBOL;
1859d430a59SAlexey Zelkin continue;
1869d430a59SAlexey Zelkin case '-': /* alignment (left) */
1879d430a59SAlexey Zelkin flags |= LEFT_JUSTIFY;
1889d430a59SAlexey Zelkin continue;
1899d430a59SAlexey Zelkin default:
1909d430a59SAlexey Zelkin break;
1919d430a59SAlexey Zelkin }
1929d430a59SAlexey Zelkin break;
1939d430a59SAlexey Zelkin }
1949d430a59SAlexey Zelkin
1959d430a59SAlexey Zelkin /* field Width */
196621bf918SJose Luis Duran if (isdigit_l((unsigned char)*fmt, loc)) {
197621bf918SJose Luis Duran GET_NUMBER(width, loc);
19856a0d544SJose Luis Duran /*
19956a0d544SJose Luis Duran * Do we have enough space to put number with
2009d430a59SAlexey Zelkin * required width ?
2019d430a59SAlexey Zelkin */
2023890416fSRuslan Ermilov if ((unsigned int)width >= maxsize - (dst - s))
2039d430a59SAlexey Zelkin goto e2big_error;
2049d430a59SAlexey Zelkin }
2059d430a59SAlexey Zelkin
206284d5622STim J. Robbins /* Left precision */
207284d5622STim J. Robbins if (*fmt == '#') {
208621bf918SJose Luis Duran if (!isdigit_l((unsigned char)*++fmt, loc))
209284d5622STim J. Robbins goto format_error;
210621bf918SJose Luis Duran GET_NUMBER(left_prec, loc);
2113890416fSRuslan Ermilov if ((unsigned int)left_prec >= maxsize - (dst - s))
2123890416fSRuslan Ermilov goto e2big_error;
213284d5622STim J. Robbins }
214284d5622STim J. Robbins
215284d5622STim J. Robbins /* Right precision */
216284d5622STim J. Robbins if (*fmt == '.') {
217621bf918SJose Luis Duran if (!isdigit_l((unsigned char)*++fmt, loc))
218284d5622STim J. Robbins goto format_error;
219621bf918SJose Luis Duran GET_NUMBER(right_prec, loc);
22056a0d544SJose Luis Duran if ((unsigned int)right_prec >=
22156a0d544SJose Luis Duran maxsize - (dst - s) - left_prec)
2223890416fSRuslan Ermilov goto e2big_error;
223284d5622STim J. Robbins }
224284d5622STim J. Robbins
2259d430a59SAlexey Zelkin /* Conversion Characters */
2269d430a59SAlexey Zelkin switch (*fmt++) {
2270afd11d5SJose Luis Duran case 'i': /* use international currency format */
2289d430a59SAlexey Zelkin flags |= USE_INTL_CURRENCY;
2299d430a59SAlexey Zelkin break;
2309d430a59SAlexey Zelkin case 'n': /* use national currency format */
2319d430a59SAlexey Zelkin flags &= ~(USE_INTL_CURRENCY);
2329d430a59SAlexey Zelkin break;
23356a0d544SJose Luis Duran default: /*
23456a0d544SJose Luis Duran * required character is missing or
23556a0d544SJose Luis Duran * premature EOS
23656a0d544SJose Luis Duran */
2379d430a59SAlexey Zelkin goto format_error;
2389d430a59SAlexey Zelkin }
2399d430a59SAlexey Zelkin
2405b30d6caSRuslan Ermilov if (currency_symbol != NULL)
2415b30d6caSRuslan Ermilov free(currency_symbol);
2429d430a59SAlexey Zelkin if (flags & USE_INTL_CURRENCY) {
2439d430a59SAlexey Zelkin currency_symbol = strdup(lc->int_curr_symbol);
2449e03b903SJose Luis Duran if (currency_symbol != NULL &&
2456da51e19SJose Luis Duran strlen(currency_symbol) > 3) {
2469e03b903SJose Luis Duran space_char = currency_symbol[3];
2476da51e19SJose Luis Duran currency_symbol[3] = '\0';
2486da51e19SJose Luis Duran }
249a5aecc77SMike Barcroft } else
2509d430a59SAlexey Zelkin currency_symbol = strdup(lc->currency_symbol);
2519d430a59SAlexey Zelkin
2529d430a59SAlexey Zelkin if (currency_symbol == NULL)
2539d430a59SAlexey Zelkin goto end_error; /* ENOMEM. */
2549d430a59SAlexey Zelkin
2559d430a59SAlexey Zelkin /* value itself */
2569d430a59SAlexey Zelkin value = va_arg(ap, double);
2579d430a59SAlexey Zelkin
2589d430a59SAlexey Zelkin /* detect sign */
2599d430a59SAlexey Zelkin if (value < 0) {
2609d430a59SAlexey Zelkin flags |= IS_NEGATIVE;
2619d430a59SAlexey Zelkin value = -value;
2629d430a59SAlexey Zelkin }
2639d430a59SAlexey Zelkin
2649d430a59SAlexey Zelkin /* fill left_prec with amount of padding chars */
2659d430a59SAlexey Zelkin if (left_prec >= 0) {
2669d430a59SAlexey Zelkin pad_size = __calc_left_pad((flags ^ IS_NEGATIVE),
267621bf918SJose Luis Duran currency_symbol, lc) -
268621bf918SJose Luis Duran __calc_left_pad(flags, currency_symbol, lc);
2699d430a59SAlexey Zelkin if (pad_size < 0)
2709d430a59SAlexey Zelkin pad_size = 0;
2719d430a59SAlexey Zelkin }
2729d430a59SAlexey Zelkin
2735b30d6caSRuslan Ermilov if (asciivalue != NULL)
2745b30d6caSRuslan Ermilov free(asciivalue);
27556a0d544SJose Luis Duran asciivalue = __format_grouped_double(value, &flags, left_prec,
27656a0d544SJose Luis Duran right_prec, pad_char, lc, loc);
2779d430a59SAlexey Zelkin if (asciivalue == NULL)
27856a0d544SJose Luis Duran goto end_error; /*
27956a0d544SJose Luis Duran * errno already set to ENOMEM by
28056a0d544SJose Luis Duran * malloc()
28156a0d544SJose Luis Duran */
2829d430a59SAlexey Zelkin
2839d430a59SAlexey Zelkin /* set some variables for later use */
28456a0d544SJose Luis Duran __setup_vars(flags, &cs_precedes, &sep_by_space, &sign_posn,
28556a0d544SJose Luis Duran &signstr, lc);
2869d430a59SAlexey Zelkin
2879d430a59SAlexey Zelkin /*
2889d430a59SAlexey Zelkin * Description of some LC_MONETARY's values:
2899d430a59SAlexey Zelkin *
2909d430a59SAlexey Zelkin * p_cs_precedes & n_cs_precedes
2919d430a59SAlexey Zelkin *
2929d430a59SAlexey Zelkin * = 1 - $currency_symbol precedes the value
2939d430a59SAlexey Zelkin * for a monetary quantity with a non-negative value
2949d430a59SAlexey Zelkin * = 0 - symbol succeeds the value
2959d430a59SAlexey Zelkin *
2969d430a59SAlexey Zelkin * p_sep_by_space & n_sep_by_space
2979d430a59SAlexey Zelkin *
2989d430a59SAlexey Zelkin * = 0 - no space separates $currency_symbol
2999d430a59SAlexey Zelkin * from the value for a monetary quantity with a
3009d430a59SAlexey Zelkin * non-negative value
3019d430a59SAlexey Zelkin * = 1 - space separates the symbol from the value
3029d430a59SAlexey Zelkin * = 2 - space separates the symbol and the sign string,
303750fe3e6SJose Luis Duran * if adjacent; otherwise, a space separates
304750fe3e6SJose Luis Duran * the sign string from the value
3059d430a59SAlexey Zelkin *
3069d430a59SAlexey Zelkin * p_sign_posn & n_sign_posn
3079d430a59SAlexey Zelkin *
3089d430a59SAlexey Zelkin * = 0 - parentheses enclose the quantity and the
3099d430a59SAlexey Zelkin * $currency_symbol
3109d430a59SAlexey Zelkin * = 1 - the sign string precedes the quantity and the
3119d430a59SAlexey Zelkin * $currency_symbol
3129d430a59SAlexey Zelkin * = 2 - the sign string succeeds the quantity and the
3139d430a59SAlexey Zelkin * $currency_symbol
3149d430a59SAlexey Zelkin * = 3 - the sign string precedes the $currency_symbol
3159d430a59SAlexey Zelkin * = 4 - the sign string succeeds the $currency_symbol
3169d430a59SAlexey Zelkin */
3179d430a59SAlexey Zelkin
3189d430a59SAlexey Zelkin tmpptr = dst;
3199d430a59SAlexey Zelkin
3209d430a59SAlexey Zelkin while (pad_size-- > 0)
3219d430a59SAlexey Zelkin PRINT(' ');
3229d430a59SAlexey Zelkin
32340a48101STim J. Robbins if (sign_posn == 0 && (flags & IS_NEGATIVE))
3249d430a59SAlexey Zelkin PRINT('(');
3259d430a59SAlexey Zelkin
3269d430a59SAlexey Zelkin if (cs_precedes == 1) {
3279d430a59SAlexey Zelkin if (sign_posn == 1 || sign_posn == 3) {
3289d430a59SAlexey Zelkin PRINTS(signstr);
329f0a15aafSJose Luis Duran if (sep_by_space == 2)
3309d430a59SAlexey Zelkin PRINT(' ');
3319d430a59SAlexey Zelkin }
3329d430a59SAlexey Zelkin
333d5980dffSJose Luis Duran if (!(flags & SUPPRESS_CURR_SYMBOL)) {
3349d430a59SAlexey Zelkin PRINTS(currency_symbol);
3359d430a59SAlexey Zelkin
3369d430a59SAlexey Zelkin if (sign_posn == 4) {
3379d430a59SAlexey Zelkin if (sep_by_space == 2)
3389d430a59SAlexey Zelkin PRINT(space_char);
3399d430a59SAlexey Zelkin PRINTS(signstr);
3409d430a59SAlexey Zelkin if (sep_by_space == 1)
3419d430a59SAlexey Zelkin PRINT(' ');
342a5aecc77SMike Barcroft } else if (sep_by_space == 1)
3439d430a59SAlexey Zelkin PRINT(space_char);
3449d430a59SAlexey Zelkin }
345750fe3e6SJose Luis Duran } else if (sign_posn == 1) {
3469d430a59SAlexey Zelkin PRINTS(signstr);
347750fe3e6SJose Luis Duran if (sep_by_space == 2)
348750fe3e6SJose Luis Duran PRINT(' ');
349750fe3e6SJose Luis Duran }
3509d430a59SAlexey Zelkin
3519d430a59SAlexey Zelkin PRINTS(asciivalue);
3529d430a59SAlexey Zelkin
3539d430a59SAlexey Zelkin if (cs_precedes == 0) {
3549d430a59SAlexey Zelkin if (sign_posn == 3) {
3559d430a59SAlexey Zelkin if (sep_by_space == 1)
3569d430a59SAlexey Zelkin PRINT(' ');
3579d430a59SAlexey Zelkin PRINTS(signstr);
3589d430a59SAlexey Zelkin }
3599d430a59SAlexey Zelkin
360d5980dffSJose Luis Duran if (!(flags & SUPPRESS_CURR_SYMBOL)) {
36156a0d544SJose Luis Duran if ((sign_posn == 3 && sep_by_space == 2) ||
36256a0d544SJose Luis Duran (sep_by_space == 1 &&
36356a0d544SJose Luis Duran (sign_posn == 0 || sign_posn == 1 ||
36456a0d544SJose Luis Duran sign_posn == 2 || sign_posn == 4)))
3659d430a59SAlexey Zelkin PRINT(space_char);
366f0a15aafSJose Luis Duran PRINTS(currency_symbol);
3679d430a59SAlexey Zelkin if (sign_posn == 4) {
3689d430a59SAlexey Zelkin if (sep_by_space == 2)
3699d430a59SAlexey Zelkin PRINT(' ');
3709d430a59SAlexey Zelkin PRINTS(signstr);
3719d430a59SAlexey Zelkin }
3729d430a59SAlexey Zelkin }
3739d430a59SAlexey Zelkin }
3749d430a59SAlexey Zelkin
3759d430a59SAlexey Zelkin if (sign_posn == 2) {
3769d430a59SAlexey Zelkin if (sep_by_space == 2)
3779d430a59SAlexey Zelkin PRINT(' ');
3789d430a59SAlexey Zelkin PRINTS(signstr);
3799d430a59SAlexey Zelkin }
3809d430a59SAlexey Zelkin
381947efadcSJose Luis Duran if (sign_posn == 0) {
382947efadcSJose Luis Duran if (flags & IS_NEGATIVE)
3839d430a59SAlexey Zelkin PRINT(')');
384947efadcSJose Luis Duran else if (left_prec >= 0)
385947efadcSJose Luis Duran PRINT(' ');
386947efadcSJose Luis Duran }
3879d430a59SAlexey Zelkin
3889d430a59SAlexey Zelkin if (dst - tmpptr < width) {
3899d430a59SAlexey Zelkin if (flags & LEFT_JUSTIFY) {
390bd26dcd1STim J. Robbins while (dst - tmpptr < width)
3919d430a59SAlexey Zelkin PRINT(' ');
3929d430a59SAlexey Zelkin } else {
3939d430a59SAlexey Zelkin pad_size = dst - tmpptr;
394a5aecc77SMike Barcroft memmove(tmpptr + width - pad_size, tmpptr,
395a5aecc77SMike Barcroft pad_size);
3969d430a59SAlexey Zelkin memset(tmpptr, ' ', width - pad_size);
3979d430a59SAlexey Zelkin dst += width - pad_size;
3989d430a59SAlexey Zelkin }
3999d430a59SAlexey Zelkin }
4009d430a59SAlexey Zelkin }
4019d430a59SAlexey Zelkin
4029d430a59SAlexey Zelkin PRINT('\0');
403bd26dcd1STim J. Robbins free(asciivalue);
404bd26dcd1STim J. Robbins free(currency_symbol);
40556a0d544SJose Luis Duran return (dst - s - 1); /* size of put data except trailing '\0' */
4069d430a59SAlexey Zelkin
4079d430a59SAlexey Zelkin e2big_error:
4089d430a59SAlexey Zelkin errno = E2BIG;
4099d430a59SAlexey Zelkin goto end_error;
4109d430a59SAlexey Zelkin
4119d430a59SAlexey Zelkin format_error:
4129d430a59SAlexey Zelkin errno = EINVAL;
4139d430a59SAlexey Zelkin
4149d430a59SAlexey Zelkin end_error:
4152e9212d9STim J. Robbins sverrno = errno;
4169d430a59SAlexey Zelkin if (asciivalue != NULL)
4179d430a59SAlexey Zelkin free(asciivalue);
4189d430a59SAlexey Zelkin if (currency_symbol != NULL)
4199d430a59SAlexey Zelkin free(currency_symbol);
4202e9212d9STim J. Robbins errno = sverrno;
4219d430a59SAlexey Zelkin return (-1);
4229d430a59SAlexey Zelkin }
4239d430a59SAlexey Zelkin
4249d430a59SAlexey Zelkin static void
__setup_vars(int flags,char * cs_precedes,char * sep_by_space,char * sign_posn,char ** signstr,struct lconv * lc)42556a0d544SJose Luis Duran __setup_vars(int flags, char *cs_precedes, char *sep_by_space, char *sign_posn,
42656a0d544SJose Luis Duran char **signstr, struct lconv *lc)
427f81dfea2SJose Luis Duran {
4282621915fSTim J. Robbins if ((flags & IS_NEGATIVE) && (flags & USE_INTL_CURRENCY)) {
4292621915fSTim J. Robbins *cs_precedes = lc->int_n_cs_precedes;
4302621915fSTim J. Robbins *sep_by_space = lc->int_n_sep_by_space;
4312621915fSTim J. Robbins *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_n_sign_posn;
43256a0d544SJose Luis Duran *signstr = (lc->negative_sign[0] == '\0') ? "-" :
43356a0d544SJose Luis Duran lc->negative_sign;
4342621915fSTim J. Robbins } else if (flags & USE_INTL_CURRENCY) {
4352621915fSTim J. Robbins *cs_precedes = lc->int_p_cs_precedes;
4362621915fSTim J. Robbins *sep_by_space = lc->int_p_sep_by_space;
4372621915fSTim J. Robbins *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->int_p_sign_posn;
4382621915fSTim J. Robbins *signstr = lc->positive_sign;
4392621915fSTim J. Robbins } else if (flags & IS_NEGATIVE) {
4409d430a59SAlexey Zelkin *cs_precedes = lc->n_cs_precedes;
4419d430a59SAlexey Zelkin *sep_by_space = lc->n_sep_by_space;
4429d430a59SAlexey Zelkin *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->n_sign_posn;
44356a0d544SJose Luis Duran *signstr = (lc->negative_sign[0] == '\0') ? "-" :
44456a0d544SJose Luis Duran lc->negative_sign;
4459d430a59SAlexey Zelkin } else {
4469d430a59SAlexey Zelkin *cs_precedes = lc->p_cs_precedes;
4479d430a59SAlexey Zelkin *sep_by_space = lc->p_sep_by_space;
4489d430a59SAlexey Zelkin *sign_posn = (flags & PARENTH_POSN) ? 0 : lc->p_sign_posn;
4499d430a59SAlexey Zelkin *signstr = lc->positive_sign;
4509d430a59SAlexey Zelkin }
4519d430a59SAlexey Zelkin
4520afd11d5SJose Luis Duran /* Set default values for unspecified information. */
4539d430a59SAlexey Zelkin if (*cs_precedes != 0)
4549d430a59SAlexey Zelkin *cs_precedes = 1;
4559d430a59SAlexey Zelkin if (*sep_by_space == CHAR_MAX)
4569d430a59SAlexey Zelkin *sep_by_space = 0;
4579d430a59SAlexey Zelkin if (*sign_posn == CHAR_MAX)
4589d430a59SAlexey Zelkin *sign_posn = 0;
4599d430a59SAlexey Zelkin }
4609d430a59SAlexey Zelkin
4619d430a59SAlexey Zelkin static int
__calc_left_pad(int flags,char * cur_symb,struct lconv * lc)462621bf918SJose Luis Duran __calc_left_pad(int flags, char *cur_symb, struct lconv *lc)
463f81dfea2SJose Luis Duran {
4649d430a59SAlexey Zelkin char cs_precedes, sep_by_space, sign_posn, *signstr;
4659d430a59SAlexey Zelkin int left_chars = 0;
4669d430a59SAlexey Zelkin
46756a0d544SJose Luis Duran __setup_vars(flags, &cs_precedes, &sep_by_space, &sign_posn, &signstr,
46856a0d544SJose Luis Duran lc);
4699d430a59SAlexey Zelkin
4709d430a59SAlexey Zelkin if (cs_precedes != 0) {
4719d430a59SAlexey Zelkin left_chars += strlen(cur_symb);
4729d430a59SAlexey Zelkin if (sep_by_space != 0)
4739d430a59SAlexey Zelkin left_chars++;
4749d430a59SAlexey Zelkin }
4759d430a59SAlexey Zelkin
4769d430a59SAlexey Zelkin switch (sign_posn) {
477947efadcSJose Luis Duran case 0:
478947efadcSJose Luis Duran if (flags & IS_NEGATIVE)
479947efadcSJose Luis Duran left_chars++;
480947efadcSJose Luis Duran break;
4819d430a59SAlexey Zelkin case 1:
4829d430a59SAlexey Zelkin left_chars += strlen(signstr);
4839d430a59SAlexey Zelkin break;
4849d430a59SAlexey Zelkin case 3:
4859d430a59SAlexey Zelkin case 4:
4869d430a59SAlexey Zelkin if (cs_precedes != 0)
4879d430a59SAlexey Zelkin left_chars += strlen(signstr);
4889d430a59SAlexey Zelkin }
489a5aecc77SMike Barcroft return (left_chars);
4909d430a59SAlexey Zelkin }
4919d430a59SAlexey Zelkin
4929d430a59SAlexey Zelkin static int
get_groups(int size,char * grouping)493f81dfea2SJose Luis Duran get_groups(int size, char *grouping)
494f81dfea2SJose Luis Duran {
4959d430a59SAlexey Zelkin int chars = 0;
4969d430a59SAlexey Zelkin
4979d430a59SAlexey Zelkin if (*grouping == CHAR_MAX || *grouping <= 0) /* no grouping ? */
498a5aecc77SMike Barcroft return (0);
4999d430a59SAlexey Zelkin
5009d430a59SAlexey Zelkin while (size > (int)*grouping) {
5019d430a59SAlexey Zelkin chars++;
5029d430a59SAlexey Zelkin size -= (int)*grouping++;
5039d430a59SAlexey Zelkin /* no more grouping ? */
5049d430a59SAlexey Zelkin if (*grouping == CHAR_MAX)
5059d430a59SAlexey Zelkin break;
5069d430a59SAlexey Zelkin /* rest grouping with same value ? */
5079d430a59SAlexey Zelkin if (*grouping == 0) {
5089d430a59SAlexey Zelkin chars += (size - 1) / *(grouping - 1);
5099d430a59SAlexey Zelkin break;
5109d430a59SAlexey Zelkin }
5119d430a59SAlexey Zelkin }
512a5aecc77SMike Barcroft return (chars);
5139d430a59SAlexey Zelkin }
5149d430a59SAlexey Zelkin
5158c1c50ffSConrad Meyer /* convert double to locale-encoded string */
5169d430a59SAlexey Zelkin static char *
__format_grouped_double(double value,int * flags,int left_prec,int right_prec,int pad_char,struct lconv * lc,locale_t loc)51756a0d544SJose Luis Duran __format_grouped_double(double value, int *flags, int left_prec, int right_prec,
51856a0d544SJose Luis Duran int pad_char, struct lconv *lc, locale_t loc)
519f81dfea2SJose Luis Duran {
5209d430a59SAlexey Zelkin
5219d430a59SAlexey Zelkin char *rslt;
5229d430a59SAlexey Zelkin char *avalue;
5239d430a59SAlexey Zelkin int avalue_size;
5249d430a59SAlexey Zelkin
5259d430a59SAlexey Zelkin size_t bufsize;
5269d430a59SAlexey Zelkin char *bufend;
5279d430a59SAlexey Zelkin
5289d430a59SAlexey Zelkin int padded;
5299d430a59SAlexey Zelkin
5309d430a59SAlexey Zelkin char *grouping;
5318c1c50ffSConrad Meyer const char *decimal_point;
5328c1c50ffSConrad Meyer const char *thousands_sep;
5338c1c50ffSConrad Meyer size_t decimal_point_size;
5348c1c50ffSConrad Meyer size_t thousands_sep_size;
5359d430a59SAlexey Zelkin
5369d430a59SAlexey Zelkin int groups = 0;
5379d430a59SAlexey Zelkin
5389d430a59SAlexey Zelkin grouping = lc->mon_grouping;
5398c1c50ffSConrad Meyer decimal_point = lc->mon_decimal_point;
5408c1c50ffSConrad Meyer if (*decimal_point == '\0')
5418c1c50ffSConrad Meyer decimal_point = lc->decimal_point;
5428c1c50ffSConrad Meyer thousands_sep = lc->mon_thousands_sep;
5438c1c50ffSConrad Meyer if (*thousands_sep == '\0')
5448c1c50ffSConrad Meyer thousands_sep = lc->thousands_sep;
5458c1c50ffSConrad Meyer
5468c1c50ffSConrad Meyer decimal_point_size = strlen(decimal_point);
5478c1c50ffSConrad Meyer thousands_sep_size = strlen(thousands_sep);
5489d430a59SAlexey Zelkin
5499d430a59SAlexey Zelkin /* fill left_prec with default value */
5509d430a59SAlexey Zelkin if (left_prec == -1)
5519d430a59SAlexey Zelkin left_prec = 0;
5529d430a59SAlexey Zelkin
5539d430a59SAlexey Zelkin /* fill right_prec with default value */
5549d430a59SAlexey Zelkin if (right_prec == -1) {
5559d430a59SAlexey Zelkin if (*flags & USE_INTL_CURRENCY)
5569d430a59SAlexey Zelkin right_prec = lc->int_frac_digits;
5579d430a59SAlexey Zelkin else
5589d430a59SAlexey Zelkin right_prec = lc->frac_digits;
5599d430a59SAlexey Zelkin
5609d430a59SAlexey Zelkin if (right_prec == CHAR_MAX) /* POSIX locale ? */
5619d430a59SAlexey Zelkin right_prec = 2;
5629d430a59SAlexey Zelkin }
5639d430a59SAlexey Zelkin
5649d430a59SAlexey Zelkin if (*flags & NEED_GROUPING)
5659d430a59SAlexey Zelkin left_prec += get_groups(left_prec, grouping);
5669d430a59SAlexey Zelkin
5679d430a59SAlexey Zelkin /* convert to string */
568621bf918SJose Luis Duran avalue_size = asprintf_l(&avalue, loc, "%*.*f",
569621bf918SJose Luis Duran left_prec + right_prec + 1, right_prec, value);
5709d430a59SAlexey Zelkin if (avalue_size < 0)
571a5aecc77SMike Barcroft return (NULL);
5729d430a59SAlexey Zelkin
5739d430a59SAlexey Zelkin /* make sure that we've enough space for result string */
5748c1c50ffSConrad Meyer bufsize = avalue_size * (1 + thousands_sep_size) + decimal_point_size +
5758c1c50ffSConrad Meyer 1;
57692226c92SXin LI rslt = calloc(1, bufsize);
5779d430a59SAlexey Zelkin if (rslt == NULL) {
5789d430a59SAlexey Zelkin free(avalue);
579a5aecc77SMike Barcroft return (NULL);
5809d430a59SAlexey Zelkin }
5819d430a59SAlexey Zelkin bufend = rslt + bufsize - 1; /* reserve space for trailing '\0' */
5829d430a59SAlexey Zelkin
58332223c1bSPedro F. Giffuni /* skip spaces at beginning */
5849d430a59SAlexey Zelkin padded = 0;
5859d430a59SAlexey Zelkin while (avalue[padded] == ' ') {
5869d430a59SAlexey Zelkin padded++;
5879d430a59SAlexey Zelkin avalue_size--;
5889d430a59SAlexey Zelkin }
5899d430a59SAlexey Zelkin
5909d430a59SAlexey Zelkin if (right_prec > 0) {
5919d430a59SAlexey Zelkin bufend -= right_prec;
592a5aecc77SMike Barcroft memcpy(bufend, avalue + avalue_size + padded - right_prec,
593a5aecc77SMike Barcroft right_prec);
5948c1c50ffSConrad Meyer bufend -= decimal_point_size;
5958c1c50ffSConrad Meyer memcpy(bufend, decimal_point, decimal_point_size);
5969d430a59SAlexey Zelkin avalue_size -= (right_prec + 1);
5979d430a59SAlexey Zelkin }
5989d430a59SAlexey Zelkin
59956a0d544SJose Luis Duran if ((*flags & NEED_GROUPING) && thousands_sep_size > 0 &&
60056a0d544SJose Luis Duran *grouping != CHAR_MAX && *grouping > 0) {
6019d430a59SAlexey Zelkin while (avalue_size > (int)*grouping) {
6029d430a59SAlexey Zelkin GRPCPY(*grouping);
6039d430a59SAlexey Zelkin GRPSEP;
6049d430a59SAlexey Zelkin grouping++;
6059d430a59SAlexey Zelkin
6069d430a59SAlexey Zelkin /* no more grouping ? */
6079d430a59SAlexey Zelkin if (*grouping == CHAR_MAX)
6089d430a59SAlexey Zelkin break;
6099d430a59SAlexey Zelkin
6109d430a59SAlexey Zelkin /* rest grouping with same value ? */
6119d430a59SAlexey Zelkin if (*grouping == 0) {
6129d430a59SAlexey Zelkin grouping--;
6139d430a59SAlexey Zelkin while (avalue_size > *grouping) {
6149d430a59SAlexey Zelkin GRPCPY(*grouping);
6159d430a59SAlexey Zelkin GRPSEP;
6169d430a59SAlexey Zelkin }
6179d430a59SAlexey Zelkin }
6189d430a59SAlexey Zelkin }
6199d430a59SAlexey Zelkin if (avalue_size != 0)
6209d430a59SAlexey Zelkin GRPCPY(avalue_size);
6219d430a59SAlexey Zelkin padded -= groups;
6229d430a59SAlexey Zelkin } else {
6239d430a59SAlexey Zelkin bufend -= avalue_size;
6249d430a59SAlexey Zelkin memcpy(bufend, avalue + padded, avalue_size);
6258c1c50ffSConrad Meyer /* decrease assumed $decimal_point */
6269d430a59SAlexey Zelkin if (right_prec == 0)
6278c1c50ffSConrad Meyer padded -= decimal_point_size;
6289d430a59SAlexey Zelkin }
6299d430a59SAlexey Zelkin
6309d430a59SAlexey Zelkin /* do padding with pad_char */
6319d430a59SAlexey Zelkin if (padded > 0) {
6329d430a59SAlexey Zelkin bufend -= padded;
6339d430a59SAlexey Zelkin memset(bufend, pad_char, padded);
6349d430a59SAlexey Zelkin }
6359d430a59SAlexey Zelkin
6361a4531bcSJohn Baldwin bufsize = rslt + bufsize - bufend;
6379d430a59SAlexey Zelkin memmove(rslt, bufend, bufsize);
6389d430a59SAlexey Zelkin free(avalue);
639a5aecc77SMike Barcroft return (rslt);
6409d430a59SAlexey Zelkin }
641f81dfea2SJose Luis Duran
642f81dfea2SJose Luis Duran ssize_t
strfmon(char * restrict s,size_t maxsize,const char * restrict format,...)643*86e2bcbfSKonstantin Belousov strfmon(char *restrict s, size_t maxsize, const char *restrict format, ...)
644f81dfea2SJose Luis Duran {
645f5924ad8SJose Luis Duran ssize_t ret;
646f81dfea2SJose Luis Duran va_list ap;
647f81dfea2SJose Luis Duran
648f81dfea2SJose Luis Duran va_start(ap, format);
649f81dfea2SJose Luis Duran ret = vstrfmon_l(s, maxsize, __get_locale(), format, ap);
650f81dfea2SJose Luis Duran va_end(ap);
651f81dfea2SJose Luis Duran
652f81dfea2SJose Luis Duran return (ret);
653f81dfea2SJose Luis Duran }
654f81dfea2SJose Luis Duran
655f81dfea2SJose Luis Duran ssize_t
strfmon_l(char * restrict s,size_t maxsize,locale_t loc,const char * restrict format,...)656*86e2bcbfSKonstantin Belousov strfmon_l(char *restrict s, size_t maxsize, locale_t loc,
657*86e2bcbfSKonstantin Belousov const char *restrict format, ...)
658f81dfea2SJose Luis Duran {
659f5924ad8SJose Luis Duran ssize_t ret;
660f81dfea2SJose Luis Duran va_list ap;
661f81dfea2SJose Luis Duran
662f81dfea2SJose Luis Duran va_start(ap, format);
663f81dfea2SJose Luis Duran ret = vstrfmon_l(s, maxsize, loc, format, ap);
664f81dfea2SJose Luis Duran va_end(ap);
665f81dfea2SJose Luis Duran
666f81dfea2SJose Luis Duran return (ret);
667f81dfea2SJose Luis Duran }
668