]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/basic/locale-util.c
Add SPDX license identifiers to source files under the LGPL
[thirdparty/systemd.git] / src / basic / locale-util.c
CommitLineData
53e1b683 1/* SPDX-License-Identifier: LGPL-2.1+ */
75683450
LP
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
11c3a366
TA
21#include <dirent.h>
22#include <errno.h>
23#include <fcntl.h>
ed457f13 24#include <ftw.h>
8752c575 25#include <langinfo.h>
11c3a366 26#include <libintl.h>
8752c575 27#include <locale.h>
11c3a366
TA
28#include <stddef.h>
29#include <stdint.h>
30#include <stdlib.h>
31#include <string.h>
75683450 32#include <sys/mman.h>
11c3a366 33#include <sys/stat.h>
75683450 34
ed457f13 35#include "def.h"
a0956174 36#include "dirent-util.h"
3ffd4af2 37#include "fd-util.h"
93cc7779 38#include "hashmap.h"
3ffd4af2 39#include "locale-util.h"
bb15fafe 40#include "path-util.h"
75683450 41#include "set.h"
8b43440b 42#include "string-table.h"
07630cea 43#include "string-util.h"
75683450 44#include "strv.h"
07630cea 45#include "utf8.h"
75683450
LP
46
47static 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
147static 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
331fb4ca
EV
159 dirent_ensure_type(dir, entry);
160
75683450
LP
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
176int get_locales(char ***ret) {
177 _cleanup_set_free_ Set *locales = NULL;
178 _cleanup_strv_free_ char **l = NULL;
179 int r;
180
d5099efc 181 locales = set_new(&string_hash_ops);
75683450
LP
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
205bool 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
ae6c3cc0 216 if (!filename_is_valid(name))
75683450
LP
217 return false;
218
219 if (!string_is_safe(name))
220 return false;
221
222 return true;
223}
a3428668 224
8752c575
LP
225void init_gettext(void) {
226 setlocale(LC_ALL, "");
227 textdomain(GETTEXT_PACKAGE);
228}
229
230bool 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
272out:
273 return (bool) cached_answer;
274}
275
ed457f13
TB
276static thread_local Set *keymaps = NULL;
277
278static 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
313int 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 = l;
345 l = NULL;
346
347 return 0;
348}
349
350bool keymap_is_valid(const char *name) {
351
352 if (isempty(name))
353 return false;
354
355 if (strlen(name) >= 128)
356 return false;
357
358 if (!utf8_is_valid(name))
359 return false;
360
361 if (!filename_is_valid(name))
362 return false;
363
364 if (!string_is_safe(name))
365 return false;
366
367 return true;
368}
8752c575 369
323b7dc9
ZJS
370const char *special_glyph(SpecialGlyph code) {
371
dff4bf93 372 static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = {
323b7dc9
ZJS
373 /* ASCII fallback */
374 [false] = {
375 [TREE_VERTICAL] = "| ",
376 [TREE_BRANCH] = "|-",
377 [TREE_RIGHT] = "`-",
378 [TREE_SPACE] = " ",
379 [TRIANGULAR_BULLET] = ">",
380 [BLACK_CIRCLE] = "*",
381 [ARROW] = "->",
382 [MDASH] = "-",
8752c575
LP
383 },
384
323b7dc9
ZJS
385 /* UTF-8 */
386 [ true ] = {
387 [TREE_VERTICAL] = "\342\224\202 ", /* │ */
388 [TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
389 [TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
390 [TREE_SPACE] = " ", /* */
391 [TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
392 [BLACK_CIRCLE] = "\342\227\217", /* ● */
393 [ARROW] = "\342\206\222", /* → */
394 [MDASH] = "\342\200\223", /* – */
395 },
8752c575
LP
396 };
397
323b7dc9 398 return draw_table[is_locale_utf8()][code];
8752c575
LP
399}
400
a3428668
MS
401static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
402 [VARIABLE_LANG] = "LANG",
403 [VARIABLE_LANGUAGE] = "LANGUAGE",
404 [VARIABLE_LC_CTYPE] = "LC_CTYPE",
405 [VARIABLE_LC_NUMERIC] = "LC_NUMERIC",
406 [VARIABLE_LC_TIME] = "LC_TIME",
407 [VARIABLE_LC_COLLATE] = "LC_COLLATE",
408 [VARIABLE_LC_MONETARY] = "LC_MONETARY",
409 [VARIABLE_LC_MESSAGES] = "LC_MESSAGES",
410 [VARIABLE_LC_PAPER] = "LC_PAPER",
411 [VARIABLE_LC_NAME] = "LC_NAME",
412 [VARIABLE_LC_ADDRESS] = "LC_ADDRESS",
413 [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE",
414 [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT",
415 [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
416};
417
418DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);