]> git.ipfire.org Git - thirdparty/glibc.git/blame - stdlib/strfmon_l.c
Linux: readdir64_r should not skip d_ino == 0 entries (bug 32126)
[thirdparty/glibc.git] / stdlib / strfmon_l.c
CommitLineData
c84142e8 1/* Formatting a monetary value according to the given locale.
dff8da6b 2 Copyright (C) 1996-2024 Free Software Foundation, Inc.
c84142e8 3 This file is part of the GNU C Library.
c84142e8
UD
4
5 The GNU C Library is free software; you can redistribute it and/or
41bdb6e2
AJ
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.
c84142e8
UD
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
41bdb6e2 13 Lesser General Public License for more details.
c84142e8 14
41bdb6e2 15 You should have received a copy of the GNU Lesser General Public
59ba27a6 16 License along with the GNU C Library; if not, see
5a82c748 17 <https://www.gnu.org/licenses/>. */
c84142e8 18
ccadf7b5
UD
19#include <ctype.h>
20#include <errno.h>
21#include <langinfo.h>
22#include <locale.h>
23#include <monetary.h>
c6251f03
RM
24#include "../libio/libioP.h"
25#include "../libio/strfile.h"
ccadf7b5
UD
26#include <printf.h>
27#include <stdarg.h>
28#include <stdio.h>
29#include <string.h>
30#include "../locale/localeinfo.h"
66fa3082 31#include <bits/floatn.h>
e88b9f0e
FW
32#include <stdio-common/grouping_iterator.h>
33#include <printf_buffer.h>
ccadf7b5
UD
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
ccadf7b5
UD
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. */
e88b9f0e
FW
53static void
54__vstrfmon_l_buffer (struct __printf_buffer *buf, locale_t loc,
55 const char *fmt, va_list ap, unsigned int flags)
ccadf7b5 56{
f095bb72 57 struct __locale_data *current = loc->__locales[LC_MONETARY];
ccadf7b5 58 struct printf_info info;
ccadf7b5
UD
59
60 /* Loop through the format-string. */
e88b9f0e 61 while (*fmt != '\0' && !__printf_buffer_has_failed (buf))
ccadf7b5
UD
62 {
63 /* The floating-point value to output. */
64 union
65 {
66 double dbl;
8b164787 67 long double ldbl;
66fa3082
RS
68#if __HAVE_DISTINCT_FLOAT128
69 _Float128 f128;
70#endif
ccadf7b5
UD
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;
66fa3082 81 int is_binary128;
ccadf7b5
UD
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;
ccadf7b5
UD
94 const char *currency_symbol;
95 size_t currency_symbol_len;
153aa31b 96 long int width;
ccadf7b5
UD
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 {
e88b9f0e 104 __printf_buffer_putc (buf, *fmt++);
ccadf7b5
UD
105 continue;
106 }
107
108 /* "%%" means a single '%' character. */
109 if (fmt[1] == '%')
110 {
e88b9f0e 111 __printf_buffer_putc (buf, *++fmt);
ccadf7b5
UD
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. */
66fa3082 124 is_binary128 = 0; /* Long double argument by default. */
a7931fcf
AS
125 p_sign_posn = -2; /* This indicates whether the */
126 n_sign_posn = -2; /* '(' flag is given. */
ccadf7b5
UD
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);
e88b9f0e
FW
141 __printf_buffer_mark_failed (buf);
142 return;
ccadf7b5
UD
143 }
144 continue;
145 case '^': /* Don't group digits. */
146 group = 0;
147 continue;
148 case '+': /* Use +/- for sign of number. */
a7931fcf 149 if (n_sign_posn != -2)
ccadf7b5
UD
150 {
151 __set_errno (EINVAL);
e88b9f0e
FW
152 __printf_buffer_mark_failed (buf);
153 return;
ccadf7b5
UD
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. */
a7931fcf 159 if (n_sign_posn != -2)
ccadf7b5
UD
160 {
161 __set_errno (EINVAL);
e88b9f0e
FW
162 __printf_buffer_mark_failed (buf);
163 return;
ccadf7b5
UD
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 {
153aa31b
UD
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);
e88b9f0e
FW
193 __printf_buffer_mark_failed (buf);
194 return;
153aa31b
UD
195 }
196
197 width = width * 10 + val;
ccadf7b5 198 }
ccadf7b5
UD
199 }
200
201 /* Recognize left precision. */
202 if (*fmt == '#')
203 {
204 if (!isdigit (*++fmt))
205 {
206 __set_errno (EINVAL);
e88b9f0e
FW
207 __printf_buffer_mark_failed (buf);
208 return;
ccadf7b5
UD
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);
e88b9f0e
FW
225 __printf_buffer_mark_failed (buf);
226 return;
ccadf7b5
UD
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;
c75772e3 241 if (__glibc_likely ((flags & STRFMON_LDBL_IS_DBL) == 0))
c6251f03 242 is_long_double = 1;
66fa3082
RS
243#if __HAVE_DISTINCT_FLOAT128
244 if (__glibc_likely ((flags & STRFMON_LDBL_USES_FLOAT128) != 0))
245 is_binary128 = is_long_double;
246#endif
ccadf7b5
UD
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);
e88b9f0e
FW
274 __printf_buffer_mark_failed (buf);
275 return;
ccadf7b5
UD
276 }
277
278 /* If not specified by the format string now find the values for
279 the format specification. */
a7931fcf 280 if (p_sign_posn == -2)
ccadf7b5 281 p_sign_posn = *_NL_CURRENT (LC_MONETARY, int_format ? INT_P_SIGN_POSN : P_SIGN_POSN);
a7931fcf 282 if (n_sign_posn == -2)
ccadf7b5
UD
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
a7931fcf 289 if (right_prec == '\377')
ccadf7b5
UD
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)
e88b9f0e
FW
296 {
297 struct grouping_iterator it;
298 __grouping_iterator_init (&it, LC_MONETARY, loc, left_prec);
299 left_prec += it.separators;
300 }
ccadf7b5
UD
301
302 /* Now it's time to get the value. */
303 if (is_long_double == 1)
304 {
66fa3082
RS
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 }
ccadf7b5
UD
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;
a7931fcf 367 if (sep_by_space == '\377')
ccadf7b5 368 sep_by_space = 0;
a7931fcf 369 if (other_sep_by_space == '\377')
ccadf7b5 370 other_sep_by_space = 0;
a7931fcf 371 if (sign_posn == '\377')
ccadf7b5 372 sign_posn = 1;
a7931fcf 373 if (other_sign_posn == '\377')
ccadf7b5
UD
374 other_sign_posn = 1;
375
376 /* Check for degenerate cases */
377 if (sep_by_space == 2)
378 {
34a5a146
JM
379 if (sign_posn == 0
380 || (sign_posn == 1 && !cs_precedes)
381 || (sign_posn == 2 && cs_precedes))
ccadf7b5
UD
382 /* sign and symbol are not adjacent, so no separator */
383 sep_by_space = 0;
384 }
385 if (other_sep_by_space == 2)
386 {
34a5a146
JM
387 if (other_sign_posn == 0
388 || (other_sign_posn == 1 && !other_cs_precedes)
389 || (other_sign_posn == 2 && other_cs_precedes))
ccadf7b5
UD
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);
34a5a146
JM
437 else if (other_cs_precedes
438 && (other_sign_posn == 3 || other_sign_posn == 4))
ccadf7b5
UD
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
e88b9f0e 454 char *startp = buf->write_ptr;
ccadf7b5 455
e88b9f0e 456 __printf_buffer_pad (buf, ' ', left_pad);
ccadf7b5
UD
457
458 if (sign_posn == 0 && is_negative)
e88b9f0e 459 __printf_buffer_putc (buf, left_paren);
ccadf7b5
UD
460
461 if (cs_precedes)
462 {
463 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 4
464 && sign_posn != 5)
465 {
e88b9f0e 466 __printf_buffer_puts (buf, sign_string);
ccadf7b5 467 if (sep_by_space == 2)
e88b9f0e 468 __printf_buffer_putc (buf, ' ');
ccadf7b5
UD
469 }
470
471 if (print_curr_symbol)
e88b9f0e 472 __printf_buffer_puts (buf, currency_symbol);
a334319f 473
c61a9cfb
UD
474 if (sign_posn == 4)
475 {
476 if (print_curr_symbol && sep_by_space == 2)
e88b9f0e
FW
477 __printf_buffer_putc (buf, space_char);
478 __printf_buffer_puts (buf, sign_string);
c61a9cfb
UD
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 */
e88b9f0e 482 __printf_buffer_putc (buf, ' ');
ccadf7b5 483 }
c61a9cfb
UD
484 else
485 if (print_curr_symbol && sep_by_space == 1)
e88b9f0e 486 __printf_buffer_putc (buf, space_char);
ccadf7b5
UD
487 }
488 else
489 if (sign_posn != 0 && sign_posn != 2 && sign_posn != 3
490 && sign_posn != 4 && sign_posn != 5)
e88b9f0e 491 __printf_buffer_puts (buf, sign_string);
ccadf7b5
UD
492
493 /* Print the number. */
01f7e928 494 memset (&info, '\0', sizeof (info));
ccadf7b5
UD
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;
66fa3082 499 info.is_binary128 = is_binary128;
ccadf7b5
UD
500 info.group = group;
501 info.pad = pad;
502 info.extra = 1; /* This means use values from LC_MONETARY. */
ccadf7b5
UD
503
504 ptr = &fpnum;
e88b9f0e
FW
505 __printf_fp_l_buffer (buf, loc, &info, &ptr);
506 if (__printf_buffer_has_failed (buf))
507 return;
ccadf7b5
UD
508
509 if (!cs_precedes)
510 {
511 if (sign_posn == 3)
512 {
513 if (sep_by_space == 1)
e88b9f0e
FW
514 __printf_buffer_putc (buf, ' ');
515 __printf_buffer_puts (buf, sign_string);
ccadf7b5
UD
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))
e88b9f0e
FW
525 __printf_buffer_putc (buf, space_char);
526 __printf_buffer_write (buf, currency_symbol,
527 __strnlen (currency_symbol,
528 currency_symbol_len));
c61a9cfb 529 }
153aa31b 530
c61a9cfb
UD
531 if (sign_posn == 4)
532 {
533 if (sep_by_space == 2)
e88b9f0e
FW
534 __printf_buffer_putc (buf, ' ');
535 __printf_buffer_puts (buf, sign_string);
ccadf7b5
UD
536 }
537 }
538
539 if (sign_posn == 2)
540 {
541 if (sep_by_space == 2)
e88b9f0e
FW
542 __printf_buffer_putc (buf, ' ');
543 __printf_buffer_puts (buf, sign_string);
ccadf7b5
UD
544 }
545
546 if (sign_posn == 0 && is_negative)
e88b9f0e 547 __printf_buffer_putc (buf, right_paren);
ccadf7b5
UD
548
549 /* Now test whether the output width is filled. */
e88b9f0e 550 if (buf->write_ptr - startp < width)
ccadf7b5 551 {
e88b9f0e
FW
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)
ccadf7b5 560 {
e88b9f0e
FW
561 memmove (startp + pad_width, startp, buf->write_ptr - startp);
562 memset (startp, ' ', pad_width);
ccadf7b5
UD
563 }
564 }
565 }
e88b9f0e 566}
ccadf7b5 567
e88b9f0e
FW
568ssize_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. */
ccadf7b5
UD
580}
581
582ssize_t
af85385f 583___strfmon_l (char *s, size_t maxsize, locale_t loc, const char *format, ...)
ccadf7b5
UD
584{
585 va_list ap;
586
587 va_start (ap, format);
588
c75772e3 589 ssize_t res = __vstrfmon_l_internal (s, maxsize, loc, format, ap, 0);
ccadf7b5
UD
590
591 va_end (ap);
592
593 return res;
594}
c6251f03
RM
595ldbl_strong_alias (___strfmon_l, __strfmon_l)
596ldbl_weak_alias (___strfmon_l, strfmon_l)