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