chiark / gitweb /
tree-wide: drop 'This file is part of systemd' blurb
[elogind.git] / src / basic / locale-util.c
1 /* SPDX-License-Identifier: LGPL-2.1+ */
2 /***
3   Copyright 2014 Lennart Poettering
4 ***/
5
6 #include <dirent.h>
7 #include <errno.h>
8 #include <fcntl.h>
9 #include <ftw.h>
10 #include <langinfo.h>
11 #include <libintl.h>
12 #include <locale.h>
13 #include <stddef.h>
14 #include <stdint.h>
15 #include <stdlib.h>
16 #include <string.h>
17 #include <sys/mman.h>
18 #include <sys/stat.h>
19
20 #include "def.h"
21 #include "dirent-util.h"
22 #include "fd-util.h"
23 #include "hashmap.h"
24 #include "locale-util.h"
25 #include "path-util.h"
26 #include "set.h"
27 #include "string-table.h"
28 #include "string-util.h"
29 #include "strv.h"
30 #include "utf8.h"
31
32 static 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;
72         size_t i;
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
132 static 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
144                 dirent_ensure_type(dir, entry);
145
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
161 int get_locales(char ***ret) {
162         _cleanup_set_free_ Set *locales = NULL;
163         _cleanup_strv_free_ char **l = NULL;
164         int r;
165
166         locales = set_new(&string_hash_ops);
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
184         *ret = TAKE_PTR(l);
185
186         return 0;
187 }
188
189 bool 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
200         if (!filename_is_valid(name))
201                 return false;
202
203         if (!string_is_safe(name))
204                 return false;
205
206         return true;
207 }
208
209 void init_gettext(void) {
210         setlocale(LC_ALL, "");
211         textdomain(GETTEXT_PACKAGE);
212 }
213
214 bool 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
256 out:
257         return (bool) cached_answer;
258 }
259
260 static thread_local Set *keymaps = NULL;
261
262 static 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
297 int 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) {
307                 r = nftw(dir, nftw_cb, 20, FTW_PHYS|FTW_ACTIONRETVAL);
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
328         *ret = TAKE_PTR(l);
329
330         return 0;
331 }
332
333 bool 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 }
352
353 const char *special_glyph(SpecialGlyph code) {
354
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
362         static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = {
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]              = "-",
373                         [ELLIPSIS]           = "..."
374                 },
375
376                 /* UTF-8 */
377                 [true] = {
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",             /* – */
386                         [ELLIPSIS]           = "\342\200\246",             /* … */
387                 },
388         };
389
390         return draw_table[is_locale_utf8()][code];
391 }
392
393 static 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
410 DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);