xref: /netbsd-src/lib/libc/stdlib/strfmon.c (revision 82eb5ce2b32f8740d41ebe6b63422180afc8498b)
1 /*	$NetBSD: strfmon.c,v 1.21 2023/11/27 19:46:14 christos Exp $	*/
2 
3 /*-
4  * Copyright (c) 2001 Alexey Zelkin <phantom@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.21 2023/11/27 19:46:14 christos 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
vstrfmon_l(char * __restrict s,size_t maxsize,locale_t loc,const char * __restrict format,va_list ap)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 internaltion 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
__setup_vars(int flags,char * cs_precedes,char * sep_by_space,char * sign_posn,const char ** signstr,struct lconv * lc)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
__calc_left_pad(int flags,char * cur_symb,struct lconv * lc)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
get_groups(int size,char * grouping)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 *
__format_grouped_double(double value,int * flags,int left_prec,int right_prec,int pad_char,struct lconv * lc,locale_t loc)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
strfmon(char * __restrict s,size_t maxsize,const char * __restrict format,...)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
strfmon_l(char * __restrict s,size_t maxsize,locale_t loc,const char * __restrict format,...)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