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