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