]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/locale-util.c
tree-wide: use TAKE_PTR() and TAKE_FD() macros
[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 = TAKE_PTR(l);
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 = TAKE_PTR(l);
344
345 return 0;
346 }
347
348 bool keymap_is_valid(const char *name) {
349
350 if (isempty(name))
351 return false;
352
353 if (strlen(name) >= 128)
354 return false;
355
356 if (!utf8_is_valid(name))
357 return false;
358
359 if (!filename_is_valid(name))
360 return false;
361
362 if (!string_is_safe(name))
363 return false;
364
365 return true;
366 }
367
368 const char *special_glyph(SpecialGlyph code) {
369
370 static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = {
371 /* ASCII fallback */
372 [false] = {
373 [TREE_VERTICAL] = "| ",
374 [TREE_BRANCH] = "|-",
375 [TREE_RIGHT] = "`-",
376 [TREE_SPACE] = " ",
377 [TRIANGULAR_BULLET] = ">",
378 [BLACK_CIRCLE] = "*",
379 [ARROW] = "->",
380 [MDASH] = "-",
381 },
382
383 /* UTF-8 */
384 [ true ] = {
385 [TREE_VERTICAL] = "\342\224\202 ", /* │ */
386 [TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
387 [TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
388 [TREE_SPACE] = " ", /* */
389 [TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
390 [BLACK_CIRCLE] = "\342\227\217", /* ● */
391 [ARROW] = "\342\206\222", /* → */
392 [MDASH] = "\342\200\223", /* – */
393 },
394 };
395
396 return draw_table[is_locale_utf8()][code];
397 }
398
399 static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
400 [VARIABLE_LANG] = "LANG",
401 [VARIABLE_LANGUAGE] = "LANGUAGE",
402 [VARIABLE_LC_CTYPE] = "LC_CTYPE",
403 [VARIABLE_LC_NUMERIC] = "LC_NUMERIC",
404 [VARIABLE_LC_TIME] = "LC_TIME",
405 [VARIABLE_LC_COLLATE] = "LC_COLLATE",
406 [VARIABLE_LC_MONETARY] = "LC_MONETARY",
407 [VARIABLE_LC_MESSAGES] = "LC_MESSAGES",
408 [VARIABLE_LC_PAPER] = "LC_PAPER",
409 [VARIABLE_LC_NAME] = "LC_NAME",
410 [VARIABLE_LC_ADDRESS] = "LC_ADDRESS",
411 [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE",
412 [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT",
413 [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
414 };
415
416 DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);