1 /* Formatting a monetary value according to the given locale.
2    Copyright (C) 1996-2022 Free Software Foundation, Inc.
3    This file is part of the GNU C Library.
4 
5    The GNU C Library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9 
10    The GNU C Library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14 
15    You should have received a copy of the GNU Lesser General Public
16    License along with the GNU C Library; if not, see
17    <https://www.gnu.org/licenses/>.  */
18 
19 #include <ctype.h>
20 #include <errno.h>
21 #include <langinfo.h>
22 #include <locale.h>
23 #include <monetary.h>
24 #include "../libio/libioP.h"
25 #include "../libio/strfile.h"
26 #include <printf.h>
27 #include <stdarg.h>
28 #include <stdio.h>
29 #include <string.h>
30 #include "../locale/localeinfo.h"
31 #include <bits/floatn.h>
32 
33 
34 #define out_char(Ch)							      \
35   do {									      \
36     if (dest >= s + maxsize - 1)					      \
37       {									      \
38 	__set_errno (E2BIG);						      \
39 	va_end (ap);							      \
40 	return -1;							      \
41       }									      \
42     *dest++ = (Ch);							      \
43   } while (0)
44 
45 #define out_string(String)						      \
46   do {									      \
47     const char *_s = (String);						      \
48     while (*_s)								      \
49       out_char (*_s++);							      \
50   } while (0)
51 
52 #define out_nstring(String, N)						      \
53   do {									      \
54     int _n = (N);							      \
55     const char *_s = (String);						      \
56     while (_n-- > 0)							      \
57       out_char (*_s++);							      \
58   } while (0)
59 
60 #define to_digit(Ch) ((Ch) - '0')
61 
62 
63 /* We use this code also for the extended locale handling where the
64    function gets as an additional argument the locale which has to be
65    used.  To access the values we have to redefine the _NL_CURRENT
66    macro.  */
67 #undef _NL_CURRENT
68 #define _NL_CURRENT(category, item) \
69   (current->values[_NL_ITEM_INDEX (item)].string)
70 
71 
72 /* We have to overcome some problems with this implementation.  On the
73    one hand the strfmon() function is specified in XPG4 and of course
74    it has to follow this.  But on the other hand POSIX.2 specifies
75    some information in the LC_MONETARY category which should be used,
76    too.  Some of the information contradicts the information which can
77    be specified in format string.  */
78 ssize_t
__vstrfmon_l_internal(char * s,size_t maxsize,locale_t loc,const char * format,va_list ap,unsigned int flags)79 __vstrfmon_l_internal (char *s, size_t maxsize, locale_t loc,
80 		       const char *format, va_list ap, unsigned int flags)
81 {
82   struct __locale_data *current = loc->__locales[LC_MONETARY];
83   _IO_strfile f;
84   struct printf_info info;
85   char *dest;			/* Pointer so copy the output.  */
86   const char *fmt;		/* Pointer that walks through format.  */
87 
88   dest = s;
89   fmt = format;
90 
91   /* Loop through the format-string.  */
92   while (*fmt != '\0')
93     {
94       /* The floating-point value to output.  */
95       union
96       {
97 	double dbl;
98 	long double ldbl;
99 #if __HAVE_DISTINCT_FLOAT128
100 	_Float128 f128;
101 #endif
102       }
103       fpnum;
104       int int_format;
105       int print_curr_symbol;
106       int left_prec;
107       int left_pad;
108       int right_prec;
109       int group;
110       char pad;
111       int is_long_double;
112       int is_binary128;
113       int p_sign_posn;
114       int n_sign_posn;
115       int sign_posn;
116       int other_sign_posn;
117       int left;
118       int is_negative;
119       int sep_by_space;
120       int other_sep_by_space;
121       int cs_precedes;
122       int other_cs_precedes;
123       const char *sign_string;
124       const char *other_sign_string;
125       int done;
126       const char *currency_symbol;
127       size_t currency_symbol_len;
128       long int width;
129       char *startp;
130       const void *ptr;
131       char space_char;
132 
133       /* Process all character which do not introduce a format
134 	 specification.  */
135       if (*fmt != '%')
136 	{
137 	  out_char (*fmt++);
138 	  continue;
139 	}
140 
141       /* "%%" means a single '%' character.  */
142       if (fmt[1] == '%')
143 	{
144 	  out_char (*++fmt);
145 	  ++fmt;
146 	  continue;
147 	}
148 
149       /* Defaults for formatting.  */
150       int_format = 0;			/* Use international curr. symbol */
151       print_curr_symbol = 1;		/* Print the currency symbol.  */
152       left_prec = -1;			/* No left precision specified.  */
153       right_prec = -1;			/* No right precision specified.  */
154       group = 1;			/* Print digits grouped.  */
155       pad = ' ';			/* Fill character is <SP>.  */
156       is_long_double = 0;		/* Double argument by default.  */
157       is_binary128 = 0;			/* Long double argument by default.  */
158       p_sign_posn = -2;			/* This indicates whether the */
159       n_sign_posn = -2;			/* '(' flag is given.  */
160       width = -1;			/* No width specified so far.  */
161       left = 0;				/* Right justified by default.  */
162 
163       /* Parse group characters.  */
164       while (1)
165 	{
166 	  switch (*++fmt)
167 	    {
168 	    case '=':			/* Set fill character.  */
169 	      pad = *++fmt;
170 	      if (pad == '\0')
171 		{
172 		  /* Premature EOS.  */
173 		  __set_errno (EINVAL);
174 		  return -1;
175 		}
176 	      continue;
177 	    case '^':			/* Don't group digits.  */
178 	      group = 0;
179 	      continue;
180 	    case '+':			/* Use +/- for sign of number.  */
181 	      if (n_sign_posn != -2)
182 		{
183 		  __set_errno (EINVAL);
184 		  return -1;
185 		}
186 	      p_sign_posn = *_NL_CURRENT (LC_MONETARY, P_SIGN_POSN);
187 	      n_sign_posn = *_NL_CURRENT (LC_MONETARY, N_SIGN_POSN);
188 	      continue;
189 	    case '(':			/* Use ( ) for negative sign.  */
190 	      if (n_sign_posn != -2)
191 		{
192 		  __set_errno (EINVAL);
193 		  return -1;
194 		}
195 	      p_sign_posn = 0;
196 	      n_sign_posn = 0;
197 	      continue;
198 	    case '!':			/* Don't print the currency symbol.  */
199 	      print_curr_symbol = 0;
200 	      continue;
201 	    case '-':			/* Print left justified.  */
202 	      left = 1;
203 	      continue;
204 	    default:
205 	      /* Will stop the loop.  */;
206 	    }
207 	  break;
208 	}
209 
210       if (isdigit (*fmt))
211 	{
212 	  /* Parse field width.  */
213 	  width = to_digit (*fmt);
214 
215 	  while (isdigit (*++fmt))
216 	    {
217 	      int val = to_digit (*fmt);
218 
219 	      if (width > LONG_MAX / 10
220 		  || (width == LONG_MAX && val > LONG_MAX % 10))
221 		{
222 		  __set_errno (E2BIG);
223 		  return -1;
224 		}
225 
226 	      width = width * 10 + val;
227 	    }
228 
229 	  /* If we don't have enough room for the demanded width we
230 	     can stop now and return an error.  */
231 	  if (width >= maxsize - (dest - s))
232 	    {
233 	      __set_errno (E2BIG);
234 	      return -1;
235 	    }
236 	}
237 
238       /* Recognize left precision.  */
239       if (*fmt == '#')
240 	{
241 	  if (!isdigit (*++fmt))
242 	    {
243 	      __set_errno (EINVAL);
244 	      return -1;
245 	    }
246 	  left_prec = to_digit (*fmt);
247 
248 	  while (isdigit (*++fmt))
249 	    {
250 	      left_prec *= 10;
251 	      left_prec += to_digit (*fmt);
252 	    }
253 	}
254 
255       /* Recognize right precision.  */
256       if (*fmt == '.')
257 	{
258 	  if (!isdigit (*++fmt))
259 	    {
260 	      __set_errno (EINVAL);
261 	      return -1;
262 	    }
263 	  right_prec = to_digit (*fmt);
264 
265 	  while (isdigit (*++fmt))
266 	    {
267 	      right_prec *= 10;
268 	      right_prec += to_digit (*fmt);
269 	    }
270 	}
271 
272       /* Handle modifier.  This is an extension.  */
273       if (*fmt == 'L')
274 	{
275 	  ++fmt;
276 	  if (__glibc_likely ((flags & STRFMON_LDBL_IS_DBL) == 0))
277 	    is_long_double = 1;
278 #if __HAVE_DISTINCT_FLOAT128
279 	  if (__glibc_likely ((flags & STRFMON_LDBL_USES_FLOAT128) != 0))
280 	    is_binary128 = is_long_double;
281 #endif
282 	}
283 
284       /* Handle format specifier.  */
285       char int_symbol[4];
286       switch (*fmt++)
287 	{
288 	case 'i': {		/* Use international currency symbol.  */
289 	  const char *int_curr_symbol;
290 
291 	  int_curr_symbol = _NL_CURRENT (LC_MONETARY, INT_CURR_SYMBOL);
292 	  strncpy(int_symbol, int_curr_symbol, 3);
293 	  int_symbol[3] = '\0';
294 
295 	  currency_symbol_len = 3;
296 	  currency_symbol = &int_symbol[0];
297 	  space_char = int_curr_symbol[3];
298 	  int_format = 1;
299 	  break;
300 	}
301 	case 'n':		/* Use national currency symbol.  */
302 	  currency_symbol = _NL_CURRENT (LC_MONETARY, CURRENCY_SYMBOL);
303 	  currency_symbol_len = strlen (currency_symbol);
304 	  space_char = ' ';
305 	  int_format = 0;
306 	  break;
307 	default:		/* Any unrecognized format is an error.  */
308 	  __set_errno (EINVAL);
309 	  return -1;
310 	}
311 
312       /* If not specified by the format string now find the values for
313 	 the format specification.  */
314       if (p_sign_posn == -2)
315 	p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
316       if (n_sign_posn == -2)
317 	n_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SIGN_POSN : N_SIGN_POSN);
318 
319       if (right_prec == -1)
320 	{
321 	  right_prec = *_NL_CURRENT (LC_MONETARY, int_format ? INT_FRAC_DIGITS : FRAC_DIGITS);
322 
323 	  if (right_prec == '\377')
324 	    right_prec = 2;
325 	}
326 
327       /* If we have to print the digits grouped determine how many
328 	 extra characters this means.  */
329       if (group && left_prec != -1)
330 	left_prec += __guess_grouping (left_prec,
331 				       _NL_CURRENT (LC_MONETARY, MON_GROUPING));
332 
333       /* Now it's time to get the value.  */
334       if (is_long_double == 1)
335 	{
336 #if __HAVE_DISTINCT_FLOAT128
337 	  if (is_binary128 == 1)
338 	    {
339 	      fpnum.f128 = va_arg (ap, _Float128);
340 	      is_negative = fpnum.f128 < 0;
341 	      if (is_negative)
342 	        fpnum.f128 = -fpnum.f128;
343 	    }
344 	  else
345 #endif
346 	  {
347 	    fpnum.ldbl = va_arg (ap, long double);
348 	    is_negative = fpnum.ldbl < 0;
349 	    if (is_negative)
350 	      fpnum.ldbl = -fpnum.ldbl;
351 	  }
352 	}
353       else
354 	{
355 	  fpnum.dbl = va_arg (ap, double);
356 	  is_negative = fpnum.dbl < 0;
357 	  if (is_negative)
358 	    fpnum.dbl = -fpnum.dbl;
359 	}
360 
361       /* We now know the sign of the value and can determine the format.  */
362       if (is_negative)
363 	{
364 	  sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
365 	  /* If the locale does not specify a character for the
366 	     negative sign we use a '-'.  */
367 	  if (*sign_string == '\0')
368 	    sign_string = (const char *) "-";
369 	  cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
370 	  sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
371 	  sign_posn = n_sign_posn;
372 
373 	  other_sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
374 	  other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
375 	  other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
376 	  other_sign_posn = p_sign_posn;
377 	}
378       else
379 	{
380 	  sign_string = _NL_CURRENT (LC_MONETARY, POSITIVE_SIGN);
381 	  cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_CS_PRECEDES : P_CS_PRECEDES);
382 	  sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SEP_BY_SPACE : P_SEP_BY_SPACE);
383 	  sign_posn = p_sign_posn;
384 
385 	  other_sign_string = _NL_CURRENT (LC_MONETARY, NEGATIVE_SIGN);
386 	  if (*other_sign_string == '\0')
387 	    other_sign_string = (const char *) "-";
388 	  other_cs_precedes = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_CS_PRECEDES : N_CS_PRECEDES);
389 	  other_sep_by_space = *_NL_CURRENT (LC_MONETARY, int_format ? INT_N_SEP_BY_SPACE : N_SEP_BY_SPACE);
390 	  other_sign_posn = n_sign_posn;
391 	}
392 
393       /* Set default values for unspecified information.  */
394       if (cs_precedes != 0)
395 	cs_precedes = 1;
396       if (other_cs_precedes != 0)
397 	other_cs_precedes = 1;
398       if (sep_by_space == '\377')
399 	sep_by_space = 0;
400       if (other_sep_by_space == '\377')
401 	other_sep_by_space = 0;
402       if (sign_posn == '\377')
403 	sign_posn = 1;
404       if (other_sign_posn == '\377')
405 	other_sign_posn = 1;
406 
407       /* Check for degenerate cases */
408       if (sep_by_space == 2)
409 	{
410 	  if (sign_posn == 0
411 	      || (sign_posn == 1 && !cs_precedes)
412 	      || (sign_posn == 2 && cs_precedes))
413 	    /* sign and symbol are not adjacent, so no separator */
414 	    sep_by_space = 0;
415 	}
416       if (other_sep_by_space == 2)
417 	{
418 	  if (other_sign_posn == 0
419 	      || (other_sign_posn == 1 && !other_cs_precedes)
420 	      || (other_sign_posn == 2 && other_cs_precedes))
421 	    /* sign and symbol are not adjacent, so no separator */
422 	    other_sep_by_space = 0;
423 	}
424 
425       /* Set the left precision and padding needed for alignment */
426       if (left_prec == -1)
427 	{
428 	  left_prec = 0;
429 	  left_pad = 0;
430 	}
431       else
432 	{
433 	  /* Set left_pad to number of spaces needed to align positive
434 	     and negative formats */
435 
436 	  int left_bytes = 0;
437 	  int other_left_bytes = 0;
438 
439 	  /* Work out number of bytes for currency string and separator
440 	     preceding the value */
441 	  if (cs_precedes)
442 	    {
443 	      left_bytes += currency_symbol_len;
444 	      if (sep_by_space != 0)
445 		++left_bytes;
446 	    }
447 
448 	  if (other_cs_precedes)
449 	    {
450 	      other_left_bytes += currency_symbol_len;
451 	      if (other_sep_by_space != 0)
452 		++other_left_bytes;
453 	    }
454 
455 	  /* Work out number of bytes for the sign (or left parenthesis)
456 	     preceding the value */
457 	  if (sign_posn == 0 && is_negative)
458 	    ++left_bytes;
459 	  else if (sign_posn == 1)
460 	    left_bytes += strlen (sign_string);
461 	  else if (cs_precedes && (sign_posn == 3 || sign_posn == 4))
462 	    left_bytes += strlen (sign_string);
463 
464 	  if (other_sign_posn == 0 && !is_negative)
465 	    ++other_left_bytes;
466 	  else if (other_sign_posn == 1)
467 	    other_left_bytes += strlen (other_sign_string);
468 	  else if (other_cs_precedes
469 		   && (other_sign_posn == 3 || other_sign_posn == 4))
470 	    other_left_bytes += strlen (other_sign_string);
471 
472 	  /* Compare the number of bytes preceding the value for
473 	     each format, and set the padding accordingly */
474 	  if (other_left_bytes > left_bytes)
475 	    left_pad = other_left_bytes - left_bytes;
476 	  else
477 	    left_pad = 0;
478 	}
479 
480       /* Perhaps we'll someday make these things configurable so
481 	 better start using symbolic names now.  */
482 #define left_paren '('
483 #define right_paren ')'
484 
485       startp = dest;		/* Remember start so we can compute length.  */
486 
487       while (left_pad-- > 0)
488 	out_char (' ');
489 
490       if (sign_posn == 0 && is_negative)
491 	out_char (left_paren);
492 
493       if (cs_precedes)
494 	{
495 	  if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
496 	      && sign_posn != 5)
497 	    {
498 	      out_string (sign_string);
499 	      if (sep_by_space == 2)
500 		out_char (' ');
501 	    }
502 
503 	  if (print_curr_symbol)
504 	    out_string (currency_symbol);
505 
506 	  if (sign_posn == 4)
507 	    {
508 	      if (print_curr_symbol && sep_by_space == 2)
509 		out_char (space_char);
510 	      out_string (sign_string);
511 	      if (sep_by_space == 1)
512 		/* POSIX.2 and SUS are not clear on this case, but C99
513 		   says a space follows the adjacent-symbol-and-sign */
514 		out_char (' ');
515 	    }
516 	  else
517 	    if (print_curr_symbol && sep_by_space == 1)
518 	      out_char (space_char);
519 	}
520       else
521 	if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
522 	    && sign_posn != 4 && sign_posn != 5)
523 	  out_string (sign_string);
524 
525       /* Print the number.  */
526 #ifdef _IO_MTSAFE_IO
527       f._sbf._f._lock = NULL;
528 #endif
529       _IO_init_internal (&f._sbf._f, 0);
530       _IO_JUMPS (&f._sbf) = &_IO_str_jumps;
531       _IO_str_init_static_internal (&f, dest, (s + maxsize) - dest, dest);
532       /* We clear the last available byte so we can find out whether
533 	 the numeric representation is too long.  */
534       s[maxsize - 1] = '\0';
535 
536       memset (&info, '\0', sizeof (info));
537       info.prec = right_prec;
538       info.width = left_prec + (right_prec ? (right_prec + 1) : 0);
539       info.spec = 'f';
540       info.is_long_double = is_long_double;
541       info.is_binary128 = is_binary128;
542       info.group = group;
543       info.pad = pad;
544       info.extra = 1;		/* This means use values from LC_MONETARY.  */
545 
546       ptr = &fpnum;
547       done = __printf_fp_l (&f._sbf._f, loc, &info, &ptr);
548       if (done < 0)
549 	return -1;
550 
551       if (s[maxsize - 1] != '\0')
552 	{
553 	  __set_errno (E2BIG);
554 	  return -1;
555 	}
556 
557       dest += done;
558 
559       if (!cs_precedes)
560 	{
561 	  if (sign_posn == 3)
562 	    {
563 	      if (sep_by_space == 1)
564 		out_char (' ');
565 	      out_string (sign_string);
566 	    }
567 
568 	  if (print_curr_symbol)
569 	    {
570 	      if ((sign_posn == 3 && sep_by_space == 2)
571 		  || (sign_posn == 4 && sep_by_space == 1)
572 		  || (sign_posn == 2 && sep_by_space == 1)
573 		  || (sign_posn == 1 && sep_by_space == 1)
574 		  || (sign_posn == 0 && sep_by_space == 1))
575 		out_char (space_char);
576 	      out_nstring (currency_symbol, currency_symbol_len);
577 	    }
578 
579 	  if (sign_posn == 4)
580 	    {
581 	      if (sep_by_space == 2)
582 		out_char (' ');
583 	      out_string (sign_string);
584 	    }
585 	}
586 
587       if (sign_posn == 2)
588 	{
589 	  if (sep_by_space == 2)
590 	    out_char (' ');
591 	  out_string (sign_string);
592 	}
593 
594       if (sign_posn == 0 && is_negative)
595 	out_char (right_paren);
596 
597       /* Now test whether the output width is filled.  */
598       if (dest - startp < width)
599 	{
600 	  if (left)
601 	    /* We simply have to fill using spaces.  */
602 	    do
603 	      out_char (' ');
604 	    while (dest - startp < width);
605 	  else
606 	    {
607 	      long int dist = width - (dest - startp);
608 	      for (char *cp = dest - 1; cp >= startp; --cp)
609 		cp[dist] = cp[0];
610 
611 	      dest += dist;
612 
613 	      do
614 		startp[--dist] = ' ';
615 	      while (dist > 0);
616 	    }
617 	}
618     }
619 
620   /* Terminate the string.  */
621   *dest = '\0';
622 
623   return dest - s;
624 }
625 
626 ssize_t
___strfmon_l(char * s,size_t maxsize,locale_t loc,const char * format,...)627 ___strfmon_l (char *s, size_t maxsize, locale_t loc, const char *format, ...)
628 {
629   va_list ap;
630 
631   va_start (ap, format);
632 
633   ssize_t res = __vstrfmon_l_internal (s, maxsize, loc, format, ap, 0);
634 
635   va_end (ap);
636 
637   return res;
638 }
639 ldbl_strong_alias (___strfmon_l, __strfmon_l)
640 ldbl_weak_alias (___strfmon_l, strfmon_l)
641