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