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