• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

systemd / systemd / 13688105616

05 Mar 2025 11:59PM UTC coverage: 71.788% (-0.07%) from 71.855%
13688105616

push

github

yuwata
mkosi: update debian commit reference

* dfdab6b205 Install new files
* e00bee5b4a Install new files

294781 of 410628 relevant lines covered (71.79%)

715950.09 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.13
/src/basic/locale-util.c
1
/* SPDX-License-Identifier: LGPL-2.1-or-later */
2

3
#include <errno.h>
4
#include <fcntl.h>
5
#include <langinfo.h>
6
#include <libintl.h>
7
#include <stddef.h>
8
#include <stdint.h>
9
#include <stdlib.h>
10
#include <sys/mman.h>
11
#include <sys/stat.h>
12

13
#include "constants.h"
14
#include "dirent-util.h"
15
#include "env-util.h"
16
#include "fd-util.h"
17
#include "fileio.h"
18
#include "hashmap.h"
19
#include "locale-util.h"
20
#include "path-util.h"
21
#include "process-util.h"
22
#include "set.h"
23
#include "string-table.h"
24
#include "string-util.h"
25
#include "strv.h"
26
#include "utf8.h"
27

28
static char *normalize_locale(const char *name) {
21✔
29
        const char *e;
21✔
30

31
        /* Locale names are weird: glibc has some magic rules when looking for the charset name on disk: it
32
         * lowercases everything, and removes most special chars. This means the official .UTF-8 suffix
33
         * becomes .utf8 when looking things up on disk. When enumerating locales, let's do the reverse
34
         * operation, and go back to ".UTF-8" which appears to be the more commonly accepted name. We only do
35
         * that for UTF-8 however, since it's kinda the only charset that matters. */
36

37
        e = endswith(name, ".utf8");
21✔
38
        if (e) {
21✔
39
                _cleanup_free_ char *prefix = NULL;
16✔
40

41
                prefix = strndup(name, e - name);
16✔
42
                if (!prefix)
16✔
43
                        return NULL;
44

45
                return strjoin(prefix, ".UTF-8");
16✔
46
        }
47

48
        e = strstr(name, ".utf8@");
5✔
49
        if (e) {
5✔
50
                _cleanup_free_ char *prefix = NULL;
×
51

52
                prefix = strndup(name, e - name);
×
53
                if (!prefix)
×
54
                        return NULL;
55

56
                return strjoin(prefix, ".UTF-8@", e + 6);
×
57
        }
58

59
        return strdup(name);
5✔
60
}
61

62
static int add_locales_from_archive(Set *locales) {
9✔
63
        /* Stolen from glibc... */
64

65
        struct locarhead {
9✔
66
                uint32_t magic;
67
                /* Serial number.  */
68
                uint32_t serial;
69
                /* Name hash table.  */
70
                uint32_t namehash_offset;
71
                uint32_t namehash_used;
72
                uint32_t namehash_size;
73
                /* String table.  */
74
                uint32_t string_offset;
75
                uint32_t string_used;
76
                uint32_t string_size;
77
                /* Table with locale records.  */
78
                uint32_t locrectab_offset;
79
                uint32_t locrectab_used;
80
                uint32_t locrectab_size;
81
                /* MD5 sum hash table.  */
82
                uint32_t sumhash_offset;
83
                uint32_t sumhash_used;
84
                uint32_t sumhash_size;
85
        };
86

87
        struct namehashent {
9✔
88
                /* Hash value of the name.  */
89
                uint32_t hashval;
90
                /* Offset of the name in the string table.  */
91
                uint32_t name_offset;
92
                /* Offset of the locale record.  */
93
                uint32_t locrec_offset;
94
        };
95

96
        const struct locarhead *h;
9✔
97
        const struct namehashent *e;
9✔
98
        const void *p = MAP_FAILED;
9✔
99
        _cleanup_close_ int fd = -EBADF;
9✔
100
        size_t sz = 0;
9✔
101
        struct stat st;
9✔
102
        int r;
9✔
103

104
        fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
9✔
105
        if (fd < 0)
9✔
106
                return errno == ENOENT ? 0 : -errno;
3✔
107

108
        if (fstat(fd, &st) < 0)
6✔
109
                return -errno;
×
110

111
        if (!S_ISREG(st.st_mode))
6✔
112
                return -EBADMSG;
113

114
        if (st.st_size < (off_t) sizeof(struct locarhead))
6✔
115
                return -EBADMSG;
116

117
        if (file_offset_beyond_memory_size(st.st_size))
6✔
118
                return -EFBIG;
119

120
        p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
6✔
121
        if (p == MAP_FAILED)
6✔
122
                return -errno;
×
123

124
        h = (const struct locarhead *) p;
6✔
125
        if (h->magic != 0xde020109 ||
6✔
126
            h->namehash_offset + h->namehash_size > st.st_size ||
6✔
127
            h->string_offset + h->string_size > st.st_size ||
6✔
128
            h->locrectab_offset + h->locrectab_size > st.st_size ||
6✔
129
            h->sumhash_offset + h->sumhash_size > st.st_size) {
6✔
130
                r = -EBADMSG;
×
131
                goto finish;
×
132
        }
133

134
        e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
6✔
135
        for (size_t i = 0; i < h->namehash_size; i++) {
5,448✔
136
                char *z;
5,442✔
137

138
                if (e[i].locrec_offset == 0)
5,442✔
139
                        continue;
5,435✔
140

141
                if (!utf8_is_valid((char*) p + e[i].name_offset))
7✔
142
                        continue;
×
143

144
                z = normalize_locale((char*) p + e[i].name_offset);
7✔
145
                if (!z) {
7✔
146
                        r = -ENOMEM;
×
147
                        goto finish;
×
148
                }
149

150
                r = set_consume(locales, z);
7✔
151
                if (r < 0)
7✔
152
                        goto finish;
×
153
        }
154

155
        r = 0;
156

157
 finish:
6✔
158
        if (p != MAP_FAILED)
6✔
159
                munmap((void*) p, sz);
6✔
160

161
        return r;
6✔
162
}
163

164
static int add_locales_from_libdir(Set *locales) {
9✔
165
        _cleanup_closedir_ DIR *dir = NULL;
9✔
166
        int r;
9✔
167

168
        dir = opendir("/usr/lib/locale");
9✔
169
        if (!dir)
9✔
170
                return errno == ENOENT ? 0 : -errno;
×
171

172
        FOREACH_DIRENT(de, dir, return -errno) {
47✔
173
                char *z;
20✔
174

175
                if (de->d_type != DT_DIR)
20✔
176
                        continue;
6✔
177

178
                z = normalize_locale(de->d_name);
14✔
179
                if (!z)
14✔
180
                        return -ENOMEM;
181

182
                r = set_consume(locales, z);
14✔
183
                if (r < 0 && r != -EEXIST)
14✔
184
                        return r;
185
        }
186

187
        return 0;
188
}
189

190
int get_locales(char ***ret) {
9✔
191
        _cleanup_set_free_free_ Set *locales = NULL;
9✔
192
        _cleanup_strv_free_ char **l = NULL;
9✔
193
        int r;
9✔
194

195
        locales = set_new(&string_hash_ops);
9✔
196
        if (!locales)
9✔
197
                return -ENOMEM;
198

199
        r = add_locales_from_archive(locales);
9✔
200
        if (r < 0 && r != -ENOENT)
9✔
201
                return r;
202

203
        r = add_locales_from_libdir(locales);
9✔
204
        if (r < 0)
9✔
205
                return r;
206

207
        char *locale;
9✔
208
        SET_FOREACH(locale, locales) {
39✔
209
                r = locale_is_installed(locale);
21✔
210
                if (r < 0)
21✔
211
                        return r;
×
212
                if (r == 0)
21✔
213
                        free(set_remove(locales, locale));
5✔
214
        }
215

216
        l = set_get_strv(locales);
9✔
217
        if (!l)
9✔
218
                return -ENOMEM;
219

220
        /* Now, all elements are owned by strv 'l'. Hence, do not call set_free_free(). */
221
        locales = set_free(locales);
9✔
222

223
        r = getenv_bool("SYSTEMD_LIST_NON_UTF8_LOCALES");
9✔
224
        if (IN_SET(r, -ENXIO, 0)) {
9✔
225
                char **a, **b;
9✔
226

227
                /* Filter out non-UTF-8 locales, because it's 2019, by default */
228
                for (a = b = l; *a; a++) {
25✔
229

230
                        if (endswith(*a, "UTF-8") ||
16✔
231
                            strstr(*a, ".UTF-8@"))
×
232
                                *(b++) = *a;
16✔
233
                        else
234
                                free(*a);
×
235
                }
236

237
                *b = NULL;
9✔
238

239
        } else if (r < 0)
×
240
                log_debug_errno(r, "Failed to parse $SYSTEMD_LIST_NON_UTF8_LOCALES as boolean");
×
241

242
        strv_sort(l);
9✔
243

244
        *ret = TAKE_PTR(l);
9✔
245

246
        return 0;
9✔
247
}
248

249
bool locale_is_valid(const char *name) {
73✔
250

251
        if (isempty(name))
73✔
252
                return false;
253

254
        if (strlen(name) >= 128)
71✔
255
                return false;
256

257
        if (!utf8_is_valid(name))
71✔
258
                return false;
259

260
        if (!filename_is_valid(name))
71✔
261
                return false;
262

263
        /* Locales look like: ll_CC.ENC@variant, where ll and CC are alphabetic, ENC is alphanumeric with
264
         * dashes, and variant seems to be alphabetic.
265
         * See: https://www.gnu.org/software/gettext/manual/html_node/Locale-Names.html */
266
        if (!in_charset(name, ALPHANUMERICAL "_.-@"))
69✔
267
                return false;
2✔
268

269
        return true;
270
}
271

272
int locale_is_installed(const char *name) {
42✔
273
        if (!locale_is_valid(name))
42✔
274
                return false;
42✔
275

276
        if (STR_IN_SET(name, "C", "POSIX")) /* These ones are always OK */
39✔
277
                return true;
5✔
278

279
        _cleanup_(freelocalep) locale_t loc = newlocale(LC_ALL_MASK, name, (locale_t) 0);
34✔
280
        if (loc == (locale_t) 0)
34✔
281
                return errno == ENOMEM ? -ENOMEM : false;
12✔
282

283
        return true;
284
}
285

286
static bool is_locale_utf8_impl(void) {
13,586✔
287
        const char *set;
13,586✔
288
        int r;
13,586✔
289

290
        /* Note that we default to 'true' here, since today UTF8 is pretty much supported everywhere. */
291

292
        r = secure_getenv_bool("SYSTEMD_UTF8");
13,586✔
293
        if (r >= 0)
13,586✔
294
                return r;
93✔
295
        if (r != -ENXIO)
13,493✔
296
                log_debug_errno(r, "Failed to parse $SYSTEMD_UTF8, ignoring: %m");
×
297

298
        /* This function may be called from libsystemd, and setlocale() is not thread safe. Assuming yes. */
299
        if (!is_main_thread())
13,493✔
300
                return true;
301

302
        if (!setlocale(LC_ALL, ""))
13,470✔
303
                return true;
304

305
        set = nl_langinfo(CODESET);
13,470✔
306
        if (!set || streq(set, "UTF-8"))
13,470✔
307
                return true;
308

309
        set = setlocale(LC_CTYPE, NULL);
6,196✔
310
        if (!set)
6,196✔
311
                return true;
312

313
        /* Unless LC_CTYPE is explicitly overridden, return true. Because here CTYPE is effectively unset
314
         * and everything can do to UTF-8 nowadays. */
315
        return STR_IN_SET(set, "C", "POSIX") &&
12,392✔
316
                !getenv("LC_ALL") &&
12,392✔
317
                !getenv("LC_CTYPE") &&
18,588✔
318
                !getenv("LANG");
6,195✔
319
}
320

321
bool is_locale_utf8(void) {
635,978✔
322
        static int cached = -1;
635,978✔
323

324
        if (cached < 0)
635,978✔
325
                cached = is_locale_utf8_impl();
13,586✔
326

327
        return cached;
635,978✔
328
}
329

330
void locale_variables_free(char *l[_VARIABLE_LC_MAX]) {
8✔
331
        free_many_charp(l, _VARIABLE_LC_MAX);
8✔
332
}
8✔
333

334
void locale_variables_simplify(char *l[_VARIABLE_LC_MAX]) {
280✔
335
        assert(l);
280✔
336

337
        for (LocaleVariable p = 0; p < _VARIABLE_LC_MAX; p++) {
4,200✔
338
                if (p == VARIABLE_LANG)
3,920✔
339
                        continue;
280✔
340
                if (isempty(l[p]) || streq_ptr(l[VARIABLE_LANG], l[p]))
3,655✔
341
                        l[p] = mfree(l[p]);
3,627✔
342
        }
343
}
280✔
344

345
static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
346
        [VARIABLE_LANG]              = "LANG",
347
        [VARIABLE_LANGUAGE]          = "LANGUAGE",
348
        [VARIABLE_LC_CTYPE]          = "LC_CTYPE",
349
        [VARIABLE_LC_NUMERIC]        = "LC_NUMERIC",
350
        [VARIABLE_LC_TIME]           = "LC_TIME",
351
        [VARIABLE_LC_COLLATE]        = "LC_COLLATE",
352
        [VARIABLE_LC_MONETARY]       = "LC_MONETARY",
353
        [VARIABLE_LC_MESSAGES]       = "LC_MESSAGES",
354
        [VARIABLE_LC_PAPER]          = "LC_PAPER",
355
        [VARIABLE_LC_NAME]           = "LC_NAME",
356
        [VARIABLE_LC_ADDRESS]        = "LC_ADDRESS",
357
        [VARIABLE_LC_TELEPHONE]      = "LC_TELEPHONE",
358
        [VARIABLE_LC_MEASUREMENT]    = "LC_MEASUREMENT",
359
        [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
360
};
361

362
DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);
15,934✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc