1 /* strfmon_l override.
2 Copyright (C) 2017-2021 Free Software Foundation, Inc.
3
4 This file is free software: you can redistribute it and/or modify
5 it under the terms of the GNU Lesser General Public License as
6 published by the Free Software Foundation; either version 2.1 of the
7 License, or (at your option) any later version.
8
9 This file is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU Lesser General Public License for more details.
13
14 You should have received a copy of the GNU Lesser General Public License
15 along with this program. If not, see <https://www.gnu.org/licenses/>. */
16
17 #include <config.h>
18
19 /* Specification. */
20 #include <monetary.h>
21
22 #include <errno.h>
23 #include <locale.h>
24 #include <stdarg.h>
25 #include <stdbool.h>
26 #include <stdlib.h>
27 #include <string.h>
28
29 #undef strfmon_l
30
31 /* This override can only support a limited number of arguments. */
32 #define MAX_ARGS 16
33
34 /* A parsed directive. */
35 typedef struct
36 {
37 bool needs_long_double;
38 const char *conversion_ptr;
39 }
40 directive_t;
41
42 /* A parsed format string. */
43 typedef struct
44 {
45 size_t count;
46 directive_t dir[MAX_ARGS];
47 }
48 directives_t;
49
50 /* Parses a monetary format string.
51 Returns 0 and fills *DIRECTIVESP if valid.
52 Returns -1 if invalid. */
53 static int
54 fmon_parse (const char *format, directives_t *directivesp)
/* ![[previous]](../icons/n_left.png)
![[next]](../icons/right.png)
![[first]](../icons/n_first.png)
![[last]](../icons/last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
55 {
56 size_t count = 0;
57 const char *cp = format;
58
59 while (*cp != '\0')
60 {
61 if (*cp++ == '%')
62 {
63 /* Parse flags. */
64 while (*cp == '=' || *cp == '^' || *cp == '+' || *cp == '('
65 || *cp == '!' || *cp == '-')
66 {
67 if (*cp == '=')
68 {
69 cp++;
70 if (*cp == '\0')
71 return -1;
72 }
73 cp++;
74 }
75 /* Parse field width. */
76 while (*cp >= '0' && *cp <= '9')
77 cp++;
78 /* Parse left precision. */
79 if (*cp == '#')
80 {
81 cp++;
82 while (*cp >= '0' && *cp <= '9')
83 cp++;
84 }
85 /* Parse right precision. */
86 if (*cp == '.')
87 {
88 cp++;
89 while (*cp >= '0' && *cp <= '9')
90 cp++;
91 }
92 /* Now comes the conversion specifier. */
93 if (*cp != '%')
94 {
95 if (count == MAX_ARGS)
96 /* Too many arguments. */
97 return -1;
98
99 /* glibc supports an 'L' modifier before the conversion specifier. */
100 if (*cp == 'L')
101 {
102 cp++;
103 directivesp->dir[count].needs_long_double = true;
104 }
105 else
106 directivesp->dir[count].needs_long_double = false;
107 if (!(*cp == 'i' || *cp == 'n'))
108 return -1;
109 directivesp->dir[count].conversion_ptr = cp;
110 count++;
111 }
112 cp++;
113 }
114 }
115
116 directivesp->count = count;
117 return 0;
118 }
119
120 ssize_t
121 rpl_strfmon_l (char *s, size_t maxsize, locale_t locale, const char *format, ...)
/* ![[previous]](../icons/left.png)
![[next]](../icons/n_right.png)
![[first]](../icons/first.png)
![[last]](../icons/n_last.png)
![[top]](../icons/top.png)
![[bottom]](../icons/bottom.png)
![[index]](../icons/index.png)
*/
122 {
123 /* Work around glibc 2.23 bug
124 <https://sourceware.org/bugzilla/show_bug.cgi?id=19633>. */
125 va_list argptr;
126 locale_t orig_locale;
127 directives_t directives;
128 ssize_t result;
129
130 orig_locale = uselocale ((locale_t)0);
131
132 if (uselocale (locale) == (locale_t)0)
133 /* errno is set. */
134 return -1;
135
136 /* The format string may consume 'double' or 'long double' arguments.
137 In order not to have to link with libffcall or libffi, convert all
138 arguments to 'long double', and use a modified format string that
139 requests 'long double' arguments. But since 'long double' arguments
140 are only supported on glibc, do so only if the original format string
141 consumes at least one 'long double' argument. */
142 if (fmon_parse (format, &directives) < 0)
143 {
144 errno = EINVAL;
145 result = -1;
146 }
147 else
148 {
149 bool use_long_double;
150 unsigned int i;
151
152 use_long_double = false;
153 for (i = 0; i < directives.count; i++)
154 if (directives.dir[i].needs_long_double)
155 {
156 use_long_double = true;
157 break;
158 }
159
160 va_start (argptr, format);
161
162 if (use_long_double)
163 {
164 char *ld_format;
165
166 /* Allocate room for the modified format string. */
167 ld_format = (char *) malloc (strlen (format) + directives.count + 1);
168 if (ld_format == NULL)
169 {
170 errno = ENOMEM;
171 result = -1;
172 }
173 else
174 {
175 long double args[MAX_ARGS];
176
177 /* Create the modified format string. */
178 {
179 const char *p = format;
180 char *dest = ld_format;
181 for (i = 0; i < directives.count; i++)
182 {
183 const char *q = directives.dir[i].conversion_ptr;
184 memcpy (dest, p, q - p);
185 dest += q - p;
186 if (!directives.dir[i].needs_long_double)
187 *dest++ = 'L';
188 p = q;
189 }
190 strcpy (dest, p);
191 }
192
193 /* Set up arguments array. */
194 for (i = 0; i < directives.count; i++)
195 args[i] = (directives.dir[i].needs_long_double
196 ? va_arg (argptr, long double)
197 : (long double) va_arg (argptr, double));
198 /* Avoid uninitialized memory references. */
199 for (; i < MAX_ARGS; i++)
200 args[i] = 0.0L;
201
202 result = strfmon_l (s, maxsize, locale, ld_format,
203 args[0], args[1], args[2], args[3], args[4],
204 args[5], args[6], args[7], args[8], args[9],
205 args[10], args[11], args[12], args[13],
206 args[14], args[15]);
207
208 free (ld_format);
209 }
210 }
211 else
212 {
213 double args[MAX_ARGS];
214
215 /* Set up arguments array. */
216 for (i = 0; i < directives.count; i++)
217 args[i] = va_arg (argptr, double);
218 /* Avoid uninitialized memory references. */
219 for (; i < MAX_ARGS; i++)
220 args[i] = 0.0;
221
222 result = strfmon_l (s, maxsize, locale, format,
223 args[0], args[1], args[2], args[3], args[4],
224 args[5], args[6], args[7], args[8], args[9],
225 args[10], args[11], args[12], args[13], args[14],
226 args[15]);
227 }
228
229 va_end (argptr);
230 }
231
232 if (uselocale (orig_locale) == (locale_t)0)
233 /* errno is set. */
234 return -1;
235
236 return result;
237 }