Coverage Report

Created: 2023-03-26 07:33

/src/wget/lib/setlocale_null.c
Line
Count
Source (jump to first uncovered line)
1
/* Query the name of the current global locale.
2
   Copyright (C) 2019-2023 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
/* Written by Bruno Haible <bruno@clisp.org>, 2019.  */
18
19
#include <config.h>
20
21
/* Specification.  */
22
#include "setlocale_null.h"
23
24
#include <errno.h>
25
#include <locale.h>
26
#include <stdlib.h>
27
#include <string.h>
28
#if defined _WIN32 && !defined __CYGWIN__
29
# include <wchar.h>
30
#endif
31
32
#if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE)
33
# if defined _WIN32 && !defined __CYGWIN__
34
35
#  define WIN32_LEAN_AND_MEAN  /* avoid including junk */
36
#  include <windows.h>
37
38
# elif HAVE_PTHREAD_API
39
40
#  include <pthread.h>
41
#  if HAVE_THREADS_H && HAVE_WEAK_SYMBOLS
42
#   include <threads.h>
43
#   pragma weak thrd_exit
44
#   define c11_threads_in_use() (thrd_exit != NULL)
45
#  else
46
#   define c11_threads_in_use() 0
47
#  endif
48
49
# elif HAVE_THREADS_H
50
51
#  include <threads.h>
52
53
# endif
54
#endif
55
56
/* Use the system's setlocale() function, not the gnulib override, here.  */
57
#undef setlocale
58
59
static const char *
60
setlocale_null_androidfix (int category)
61
0
{
62
0
  const char *result = setlocale (category, NULL);
63
64
#ifdef __ANDROID__
65
  if (result == NULL)
66
    switch (category)
67
      {
68
      case LC_CTYPE:
69
      case LC_NUMERIC:
70
      case LC_TIME:
71
      case LC_COLLATE:
72
      case LC_MONETARY:
73
      case LC_MESSAGES:
74
      case LC_ALL:
75
      case LC_PAPER:
76
      case LC_NAME:
77
      case LC_ADDRESS:
78
      case LC_TELEPHONE:
79
      case LC_MEASUREMENT:
80
        result = "C";
81
        break;
82
      default:
83
        break;
84
      }
85
#endif
86
87
0
  return result;
88
0
}
89
90
static int
91
setlocale_null_unlocked (int category, char *buf, size_t bufsize)
92
0
{
93
#if defined _WIN32 && !defined __CYGWIN__ && defined _MSC_VER
94
  /* On native Windows, nowadays, the setlocale() implementation is based
95
     on _wsetlocale() and uses malloc() for the result.  We are better off
96
     using _wsetlocale() directly.  */
97
  const wchar_t *result = _wsetlocale (category, NULL);
98
99
  if (result == NULL)
100
    {
101
      /* CATEGORY is invalid.  */
102
      if (bufsize > 0)
103
        /* Return an empty string in BUF.
104
           This is a convenience for callers that don't want to write explicit
105
           code for handling EINVAL.  */
106
        buf[0] = '\0';
107
      return EINVAL;
108
    }
109
  else
110
    {
111
      size_t length = wcslen (result);
112
      if (length < bufsize)
113
        {
114
          size_t i;
115
116
          /* Convert wchar_t[] -> char[], assuming plain ASCII.  */
117
          for (i = 0; i <= length; i++)
118
            buf[i] = result[i];
119
120
          return 0;
121
        }
122
      else
123
        {
124
          if (bufsize > 0)
125
            {
126
              /* Return a truncated result in BUF.
127
                 This is a convenience for callers that don't want to write
128
                 explicit code for handling ERANGE.  */
129
              size_t i;
130
131
              /* Convert wchar_t[] -> char[], assuming plain ASCII.  */
132
              for (i = 0; i < bufsize; i++)
133
                buf[i] = result[i];
134
              buf[bufsize - 1] = '\0';
135
            }
136
          return ERANGE;
137
        }
138
    }
139
#else
140
0
  const char *result = setlocale_null_androidfix (category);
141
142
0
  if (result == NULL)
143
0
    {
144
      /* CATEGORY is invalid.  */
145
0
      if (bufsize > 0)
146
        /* Return an empty string in BUF.
147
           This is a convenience for callers that don't want to write explicit
148
           code for handling EINVAL.  */
149
0
        buf[0] = '\0';
150
0
      return EINVAL;
151
0
    }
152
0
  else
153
0
    {
154
0
      size_t length = strlen (result);
155
0
      if (length < bufsize)
156
0
        {
157
0
          memcpy (buf, result, length + 1);
158
0
          return 0;
159
0
        }
160
0
      else
161
0
        {
162
0
          if (bufsize > 0)
163
0
            {
164
              /* Return a truncated result in BUF.
165
                 This is a convenience for callers that don't want to write
166
                 explicit code for handling ERANGE.  */
167
0
              memcpy (buf, result, bufsize - 1);
168
0
              buf[bufsize - 1] = '\0';
169
0
            }
170
0
          return ERANGE;
171
0
        }
172
0
    }
173
0
#endif
174
0
}
175
176
#if !(SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE) /* musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin < 3.4.6 */
177
178
/* Use a lock, so that no two threads can invoke setlocale_null_unlocked
179
   at the same time.  */
180
181
/* Prohibit renaming this symbol.  */
182
# undef gl_get_setlocale_null_lock
183
184
# if defined _WIN32 && !defined __CYGWIN__
185
186
extern __declspec(dllimport) CRITICAL_SECTION *gl_get_setlocale_null_lock (void);
187
188
static int
189
setlocale_null_with_lock (int category, char *buf, size_t bufsize)
190
{
191
  CRITICAL_SECTION *lock = gl_get_setlocale_null_lock ();
192
  int ret;
193
194
  EnterCriticalSection (lock);
195
  ret = setlocale_null_unlocked (category, buf, bufsize);
196
  LeaveCriticalSection (lock);
197
198
  return ret;
199
}
200
201
# elif HAVE_PTHREAD_API /* musl libc, macOS, FreeBSD, NetBSD, OpenBSD, AIX, Haiku, Cygwin < 3.4.6 */
202
203
extern
204
#  if defined _WIN32 || defined __CYGWIN__
205
  __declspec(dllimport)
206
#  endif
207
  pthread_mutex_t *gl_get_setlocale_null_lock (void);
208
209
#  if HAVE_WEAK_SYMBOLS /* musl libc, FreeBSD, NetBSD, OpenBSD, Haiku */
210
211
    /* Avoid the need to link with '-lpthread'.  */
212
#   pragma weak pthread_mutex_lock
213
#   pragma weak pthread_mutex_unlock
214
215
    /* Determine whether libpthread is in use.  */
216
#   pragma weak pthread_mutexattr_gettype
217
    /* See the comments in lock.h.  */
218
#   define pthread_in_use() \
219
      (pthread_mutexattr_gettype != NULL || c11_threads_in_use ())
220
221
#  else
222
#   define pthread_in_use() 1
223
#  endif
224
225
static int
226
setlocale_null_with_lock (int category, char *buf, size_t bufsize)
227
{
228
  if (pthread_in_use())
229
    {
230
      pthread_mutex_t *lock = gl_get_setlocale_null_lock ();
231
      int ret;
232
233
      if (pthread_mutex_lock (lock))
234
        abort ();
235
      ret = setlocale_null_unlocked (category, buf, bufsize);
236
      if (pthread_mutex_unlock (lock))
237
        abort ();
238
239
      return ret;
240
    }
241
  else
242
    return setlocale_null_unlocked (category, buf, bufsize);
243
}
244
245
# elif HAVE_THREADS_H
246
247
extern mtx_t *gl_get_setlocale_null_lock (void);
248
249
static int
250
setlocale_null_with_lock (int category, char *buf, size_t bufsize)
251
{
252
  mtx_t *lock = gl_get_setlocale_null_lock ();
253
  int ret;
254
255
  if (mtx_lock (lock) != thrd_success)
256
    abort ();
257
  ret = setlocale_null_unlocked (category, buf, bufsize);
258
  if (mtx_unlock (lock) != thrd_success)
259
    abort ();
260
261
  return ret;
262
}
263
264
# endif
265
266
#endif
267
268
int
269
setlocale_null_r (int category, char *buf, size_t bufsize)
270
0
{
271
0
#if SETLOCALE_NULL_ALL_MTSAFE
272
0
# if SETLOCALE_NULL_ONE_MTSAFE
273
274
0
  return setlocale_null_unlocked (category, buf, bufsize);
275
276
# else
277
278
  if (category == LC_ALL)
279
    return setlocale_null_unlocked (category, buf, bufsize);
280
  else
281
    return setlocale_null_with_lock (category, buf, bufsize);
282
283
# endif
284
#else
285
# if SETLOCALE_NULL_ONE_MTSAFE
286
287
  if (category == LC_ALL)
288
    return setlocale_null_with_lock (category, buf, bufsize);
289
  else
290
    return setlocale_null_unlocked (category, buf, bufsize);
291
292
# else
293
294
  return setlocale_null_with_lock (category, buf, bufsize);
295
296
# endif
297
#endif
298
0
}
299
300
const char *
301
setlocale_null (int category)
302
0
{
303
0
#if SETLOCALE_NULL_ALL_MTSAFE && SETLOCALE_NULL_ONE_MTSAFE
304
0
  return setlocale_null_androidfix (category);
305
#else
306
307
  /* This call must be multithread-safe.  To achieve this without using
308
     thread-local storage:
309
       1. We use a specific static buffer for each possible CATEGORY
310
          argument.  So that different threads can call setlocale_mtsafe
311
          with different CATEGORY arguments, without interfering.
312
       2. We use a simple strcpy or memcpy to fill this static buffer.
313
          Filling it through, for example, strcpy + strcat would not be
314
          guaranteed to leave the buffer's contents intact if another thread
315
          is currently accessing it.  If necessary, the contents is first
316
          assembled in a stack-allocated buffer.  */
317
  if (category == LC_ALL)
318
    {
319
# if SETLOCALE_NULL_ALL_MTSAFE
320
      return setlocale_null_androidfix (LC_ALL);
321
# else
322
      char buf[SETLOCALE_NULL_ALL_MAX];
323
      static char resultbuf[SETLOCALE_NULL_ALL_MAX];
324
325
      if (setlocale_null_r (LC_ALL, buf, sizeof (buf)))
326
        return "C";
327
      strcpy (resultbuf, buf);
328
      return resultbuf;
329
# endif
330
    }
331
  else
332
    {
333
# if SETLOCALE_NULL_ONE_MTSAFE
334
      return setlocale_null_androidfix (category);
335
# else
336
      enum
337
        {
338
          LC_CTYPE_INDEX,
339
          LC_NUMERIC_INDEX,
340
          LC_TIME_INDEX,
341
          LC_COLLATE_INDEX,
342
          LC_MONETARY_INDEX,
343
          LC_MESSAGES_INDEX,
344
#  ifdef LC_PAPER
345
          LC_PAPER_INDEX,
346
#  endif
347
#  ifdef LC_NAME
348
          LC_NAME_INDEX,
349
#  endif
350
#  ifdef LC_ADDRESS
351
          LC_ADDRESS_INDEX,
352
#  endif
353
#  ifdef LC_TELEPHONE
354
          LC_TELEPHONE_INDEX,
355
#  endif
356
#  ifdef LC_MEASUREMENT
357
          LC_MEASUREMENT_INDEX,
358
#  endif
359
#  ifdef LC_IDENTIFICATION
360
          LC_IDENTIFICATION_INDEX,
361
#  endif
362
          LC_INDICES_COUNT
363
        }
364
        i;
365
      char buf[SETLOCALE_NULL_MAX];
366
      static char resultbuf[LC_INDICES_COUNT][SETLOCALE_NULL_MAX];
367
      int err;
368
369
      err = setlocale_null_r (category, buf, sizeof (buf));
370
      if (err == EINVAL)
371
        return NULL;
372
      if (err)
373
        return "C";
374
375
      switch (category)
376
        {
377
        case LC_CTYPE:          i = LC_CTYPE_INDEX;          break;
378
        case LC_NUMERIC:        i = LC_NUMERIC_INDEX;        break;
379
        case LC_TIME:           i = LC_TIME_INDEX;           break;
380
        case LC_COLLATE:        i = LC_COLLATE_INDEX;        break;
381
        case LC_MONETARY:       i = LC_MONETARY_INDEX;       break;
382
        case LC_MESSAGES:       i = LC_MESSAGES_INDEX;       break;
383
#  ifdef LC_PAPER
384
        case LC_PAPER:          i = LC_PAPER_INDEX;          break;
385
#  endif
386
#  ifdef LC_NAME
387
        case LC_NAME:           i = LC_NAME_INDEX;           break;
388
#  endif
389
#  ifdef LC_ADDRESS
390
        case LC_ADDRESS:        i = LC_ADDRESS_INDEX;        break;
391
#  endif
392
#  ifdef LC_TELEPHONE
393
        case LC_TELEPHONE:      i = LC_TELEPHONE_INDEX;      break;
394
#  endif
395
#  ifdef LC_MEASUREMENT
396
        case LC_MEASUREMENT:    i = LC_MEASUREMENT_INDEX;    break;
397
#  endif
398
#  ifdef LC_IDENTIFICATION
399
        case LC_IDENTIFICATION: i = LC_IDENTIFICATION_INDEX; break;
400
#  endif
401
        default:
402
          /* If you get here, a #ifdef LC_xxx is missing.  */
403
          abort ();
404
        }
405
406
      strcpy (resultbuf[i], buf);
407
      return resultbuf[i];
408
# endif
409
    }
410
#endif
411
0
}