]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/locale-util.c
b382af192d0ef41fa5704af2c6beb46f8aa59115
[thirdparty/systemd.git] / src / basic / locale-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3 This file is part of systemd.
4
5 Copyright 2014 Lennart Poettering
6 ***/
7
8 #include <dirent.h>
9 #include <errno.h>
10 #include <fcntl.h>
11 #include <ftw.h>
12 #include <langinfo.h>
13 #include <libintl.h>
14 #include <locale.h>
15 #include <stddef.h>
16 #include <stdint.h>
17 #include <stdlib.h>
18 #include <string.h>
19 #include <sys/mman.h>
20 #include <sys/stat.h>
21
22 #include "def.h"
23 #include "dirent-util.h"
24 #include "fd-util.h"
25 #include "hashmap.h"
26 #include "locale-util.h"
27 #include "path-util.h"
28 #include "set.h"
29 #include "string-table.h"
30 #include "string-util.h"
31 #include "strv.h"
32 #include "utf8.h"
33
34 static int add_locales_from_archive(Set *locales) {
35 /* Stolen from glibc... */
36
37 struct locarhead {
38 uint32_t magic;
39 /* Serial number. */
40 uint32_t serial;
41 /* Name hash table. */
42 uint32_t namehash_offset;
43 uint32_t namehash_used;
44 uint32_t namehash_size;
45 /* String table. */
46 uint32_t string_offset;
47 uint32_t string_used;
48 uint32_t string_size;
49 /* Table with locale records. */
50 uint32_t locrectab_offset;
51 uint32_t locrectab_used;
52 uint32_t locrectab_size;
53 /* MD5 sum hash table. */
54 uint32_t sumhash_offset;
55 uint32_t sumhash_used;
56 uint32_t sumhash_size;
57 };
58
59 struct namehashent {
60 /* Hash value of the name. */
61 uint32_t hashval;
62 /* Offset of the name in the string table. */
63 uint32_t name_offset;
64 /* Offset of the locale record. */
65 uint32_t locrec_offset;
66 };
67
68 const struct locarhead *h;
69 const struct namehashent *e;
70 const void *p = MAP_FAILED;
71 _cleanup_close_ int fd = -1;
72 size_t sz = 0;
73 struct stat st;
74 size_t i;
75 int r;
76
77 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
78 if (fd < 0)
79 return errno == ENOENT ? 0 : -errno;
80
81 if (fstat(fd, &st) < 0)
82 return -errno;
83
84 if (!S_ISREG(st.st_mode))
85 return -EBADMSG;
86
87 if (st.st_size < (off_t) sizeof(struct locarhead))
88 return -EBADMSG;
89
90 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
91 if (p == MAP_FAILED)
92 return -errno;
93
94 h = (const struct locarhead *) p;
95 if (h->magic != 0xde020109 ||
96 h->namehash_offset + h->namehash_size > st.st_size ||
97 h->string_offset + h->string_size > st.st_size ||
98 h->locrectab_offset + h->locrectab_size > st.st_size ||
99 h->sumhash_offset + h->sumhash_size > st.st_size) {
100 r = -EBADMSG;
101 goto finish;
102 }
103
104 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
105 for (i = 0; i < h->namehash_size; i++) {
106 char *z;
107
108 if (e[i].locrec_offset == 0)
109 continue;
110
111 if (!utf8_is_valid((char*) p + e[i].name_offset))
112 continue;
113
114 z = strdup((char*) p + e[i].name_offset);
115 if (!z) {
116 r = -ENOMEM;
117 goto finish;
118 }
119
120 r = set_consume(locales, z);
121 if (r < 0)
122 goto finish;
123 }
124
125 r = 0;
126
127 finish:
128 if (p != MAP_FAILED)
129 munmap((void*) p, sz);
130
131 return r;
132 }
133
134 static int add_locales_from_libdir (Set *locales) {
135 _cleanup_closedir_ DIR *dir = NULL;
136 struct dirent *entry;
137 int r;
138
139 dir = opendir("/usr/lib/locale");
140 if (!dir)
141 return errno == ENOENT ? 0 : -errno;
142
143 FOREACH_DIRENT(entry, dir, return -errno) {
144 char *z;
145
146 dirent_ensure_type(dir, entry);
147
148 if (entry->d_type != DT_DIR)
149 continue;
150
151 z = strdup(entry->d_name);
152 if (!z)
153 return -ENOMEM;
154
155 r = set_consume(locales, z);
156 if (r < 0 && r != -EEXIST)
157 return r;
158 }
159
160 return 0;
161 }
162
163 int get_locales(char ***ret) {
164 _cleanup_set_free_ Set *locales = NULL;
165 _cleanup_strv_free_ char **l = NULL;
166 int r;
167
168 locales = set_new(&string_hash_ops);
169 if (!locales)
170 return -ENOMEM;
171
172 r = add_locales_from_archive(locales);
173 if (r < 0 && r != -ENOENT)
174 return r;
175
176 r = add_locales_from_libdir(locales);
177 if (r < 0)
178 return r;
179
180 l = set_get_strv(locales);
181 if (!l)
182 return -ENOMEM;
183
184 strv_sort(l);
185
186 *ret = TAKE_PTR(l);
187
188 return 0;
189 }
190
191 bool locale_is_valid(const char *name) {
192
193 if (isempty(name))
194 return false;
195
196 if (strlen(name) >= 128)
197 return false;
198
199 if (!utf8_is_valid(name))
200 return false;
201
202 if (!filename_is_valid(name))
203 return false;
204
205 if (!string_is_safe(name))
206 return false;
207
208 return true;
209 }
210
211 void init_gettext(void) {
212 setlocale(LC_ALL, "");
213 textdomain(GETTEXT_PACKAGE);
214 }
215
216 bool is_locale_utf8(void) {
217 const char *set;
218 static int cached_answer = -1;
219
220 /* Note that we default to 'true' here, since today UTF8 is
221 * pretty much supported everywhere. */
222
223 if (cached_answer >= 0)
224 goto out;
225
226 if (!setlocale(LC_ALL, "")) {
227 cached_answer = true;
228 goto out;
229 }
230
231 set = nl_langinfo(CODESET);
232 if (!set) {
233 cached_answer = true;
234 goto out;
235 }
236
237 if (streq(set, "UTF-8")) {
238 cached_answer = true;
239 goto out;
240 }
241
242 /* For LC_CTYPE=="C" return true, because CTYPE is effectly
243 * unset and everything can do to UTF-8 nowadays. */
244 set = setlocale(LC_CTYPE, NULL);
245 if (!set) {
246 cached_answer = true;
247 goto out;
248 }
249
250 /* Check result, but ignore the result if C was set
251 * explicitly. */
252 cached_answer =
253 STR_IN_SET(set, "C", "POSIX") &&
254 !getenv("LC_ALL") &&
255 !getenv("LC_CTYPE") &&
256 !getenv("LANG");
257
258 out:
259 return (bool) cached_answer;
260 }
261
262 static thread_local Set *keymaps = NULL;
263
264 static int nftw_cb(
265 const char *fpath,
266 const struct stat *sb,
267 int tflag,
268 struct FTW *ftwbuf) {
269
270 char *p, *e;
271 int r;
272
273 if (tflag != FTW_F)
274 return 0;
275
276 if (!endswith(fpath, ".map") &&
277 !endswith(fpath, ".map.gz"))
278 return 0;
279
280 p = strdup(basename(fpath));
281 if (!p)
282 return FTW_STOP;
283
284 e = endswith(p, ".map");
285 if (e)
286 *e = 0;
287
288 e = endswith(p, ".map.gz");
289 if (e)
290 *e = 0;
291
292 r = set_consume(keymaps, p);
293 if (r < 0 && r != -EEXIST)
294 return r;
295
296 return 0;
297 }
298
299 int get_keymaps(char ***ret) {
300 _cleanup_strv_free_ char **l = NULL;
301 const char *dir;
302 int r;
303
304 keymaps = set_new(&string_hash_ops);
305 if (!keymaps)
306 return -ENOMEM;
307
308 NULSTR_FOREACH(dir, KBD_KEYMAP_DIRS) {
309 r = nftw(dir, nftw_cb, 20, FTW_PHYS|FTW_ACTIONRETVAL);
310
311 if (r == FTW_STOP)
312 log_debug("Directory not found %s", dir);
313 else if (r < 0)
314 log_debug_errno(r, "Can't add keymap: %m");
315 }
316
317 l = set_get_strv(keymaps);
318 if (!l) {
319 set_free_free(keymaps);
320 return -ENOMEM;
321 }
322
323 set_free(keymaps);
324
325 if (strv_isempty(l))
326 return -ENOENT;
327
328 strv_sort(l);
329
330 *ret = TAKE_PTR(l);
331
332 return 0;
333 }
334
335 bool keymap_is_valid(const char *name) {
336
337 if (isempty(name))
338 return false;
339
340 if (strlen(name) >= 128)
341 return false;
342
343 if (!utf8_is_valid(name))
344 return false;
345
346 if (!filename_is_valid(name))
347 return false;
348
349 if (!string_is_safe(name))
350 return false;
351
352 return true;
353 }
354
355 const char *special_glyph(SpecialGlyph code) {
356
357 /* A list of a number of interesting unicode glyphs we can use to decorate our output. It's probably wise to be
358 * conservative here, and primarily stick to the glyphs defined in the eurlatgr font, so that display still
359 * works reasonably well on the Linux console. For details see:
360 *
361 * http://git.altlinux.org/people/legion/packages/kbd.git?p=kbd.git;a=blob;f=data/consolefonts/README.eurlatgr
362 */
363
364 static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = {
365 /* ASCII fallback */
366 [false] = {
367 [TREE_VERTICAL] = "| ",
368 [TREE_BRANCH] = "|-",
369 [TREE_RIGHT] = "`-",
370 [TREE_SPACE] = " ",
371 [TRIANGULAR_BULLET] = ">",
372 [BLACK_CIRCLE] = "*",
373 [ARROW] = "->",
374 [MDASH] = "-",
375 [ELLIPSIS] = "..."
376 },
377
378 /* UTF-8 */
379 [true] = {
380 [TREE_VERTICAL] = "\342\224\202 ", /* │ */
381 [TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
382 [TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
383 [TREE_SPACE] = " ", /* */
384 [TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
385 [BLACK_CIRCLE] = "\342\227\217", /* ● */
386 [ARROW] = "\342\206\222", /* → */
387 [MDASH] = "\342\200\223", /* – */
388 [ELLIPSIS] = "\342\200\246", /* … */
389 },
390 };
391
392 return draw_table[is_locale_utf8()][code];
393 }
394
395 static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
396 [VARIABLE_LANG] = "LANG",
397 [VARIABLE_LANGUAGE] = "LANGUAGE",
398 [VARIABLE_LC_CTYPE] = "LC_CTYPE",
399 [VARIABLE_LC_NUMERIC] = "LC_NUMERIC",
400 [VARIABLE_LC_TIME] = "LC_TIME",
401 [VARIABLE_LC_COLLATE] = "LC_COLLATE",
402 [VARIABLE_LC_MONETARY] = "LC_MONETARY",
403 [VARIABLE_LC_MESSAGES] = "LC_MESSAGES",
404 [VARIABLE_LC_PAPER] = "LC_PAPER",
405 [VARIABLE_LC_NAME] = "LC_NAME",
406 [VARIABLE_LC_ADDRESS] = "LC_ADDRESS",
407 [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE",
408 [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT",
409 [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
410 };
411
412 DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);