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