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