chiark / gitweb /
b382af192d0ef41fa5704af2c6beb46f8aa59115
[elogind.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);