]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/locale-util.c
core/namespace: rework the return semantics of clone_device_node yet again
[thirdparty/systemd.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);