]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/basic/locale-util.c
basic: include only what we use
[thirdparty/systemd.git] / src / basic / locale-util.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4 This file is part of systemd.
5
6 Copyright 2014 Lennart Poettering
7
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
12
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
17
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <dirent.h>
23 #include <errno.h>
24 #include <fcntl.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 "dirent-util.h"
36 #include "fd-util.h"
37 #include "locale-util.h"
38 #include "path-util.h"
39 #include "set.h"
40 #include "hashmap.h"
41 #include "string-table.h"
42 #include "string-util.h"
43 #include "strv.h"
44 #include "utf8.h"
45
46 static int add_locales_from_archive(Set *locales) {
47 /* Stolen from glibc... */
48
49 struct locarhead {
50 uint32_t magic;
51 /* Serial number. */
52 uint32_t serial;
53 /* Name hash table. */
54 uint32_t namehash_offset;
55 uint32_t namehash_used;
56 uint32_t namehash_size;
57 /* String table. */
58 uint32_t string_offset;
59 uint32_t string_used;
60 uint32_t string_size;
61 /* Table with locale records. */
62 uint32_t locrectab_offset;
63 uint32_t locrectab_used;
64 uint32_t locrectab_size;
65 /* MD5 sum hash table. */
66 uint32_t sumhash_offset;
67 uint32_t sumhash_used;
68 uint32_t sumhash_size;
69 };
70
71 struct namehashent {
72 /* Hash value of the name. */
73 uint32_t hashval;
74 /* Offset of the name in the string table. */
75 uint32_t name_offset;
76 /* Offset of the locale record. */
77 uint32_t locrec_offset;
78 };
79
80 const struct locarhead *h;
81 const struct namehashent *e;
82 const void *p = MAP_FAILED;
83 _cleanup_close_ int fd = -1;
84 size_t sz = 0;
85 struct stat st;
86 unsigned i;
87 int r;
88
89 fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
90 if (fd < 0)
91 return errno == ENOENT ? 0 : -errno;
92
93 if (fstat(fd, &st) < 0)
94 return -errno;
95
96 if (!S_ISREG(st.st_mode))
97 return -EBADMSG;
98
99 if (st.st_size < (off_t) sizeof(struct locarhead))
100 return -EBADMSG;
101
102 p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
103 if (p == MAP_FAILED)
104 return -errno;
105
106 h = (const struct locarhead *) p;
107 if (h->magic != 0xde020109 ||
108 h->namehash_offset + h->namehash_size > st.st_size ||
109 h->string_offset + h->string_size > st.st_size ||
110 h->locrectab_offset + h->locrectab_size > st.st_size ||
111 h->sumhash_offset + h->sumhash_size > st.st_size) {
112 r = -EBADMSG;
113 goto finish;
114 }
115
116 e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
117 for (i = 0; i < h->namehash_size; i++) {
118 char *z;
119
120 if (e[i].locrec_offset == 0)
121 continue;
122
123 if (!utf8_is_valid((char*) p + e[i].name_offset))
124 continue;
125
126 z = strdup((char*) p + e[i].name_offset);
127 if (!z) {
128 r = -ENOMEM;
129 goto finish;
130 }
131
132 r = set_consume(locales, z);
133 if (r < 0)
134 goto finish;
135 }
136
137 r = 0;
138
139 finish:
140 if (p != MAP_FAILED)
141 munmap((void*) p, sz);
142
143 return r;
144 }
145
146 static int add_locales_from_libdir (Set *locales) {
147 _cleanup_closedir_ DIR *dir = NULL;
148 struct dirent *entry;
149 int r;
150
151 dir = opendir("/usr/lib/locale");
152 if (!dir)
153 return errno == ENOENT ? 0 : -errno;
154
155 FOREACH_DIRENT(entry, dir, return -errno) {
156 char *z;
157
158 if (entry->d_type != DT_DIR)
159 continue;
160
161 z = strdup(entry->d_name);
162 if (!z)
163 return -ENOMEM;
164
165 r = set_consume(locales, z);
166 if (r < 0 && r != -EEXIST)
167 return r;
168 }
169
170 return 0;
171 }
172
173 int get_locales(char ***ret) {
174 _cleanup_set_free_ Set *locales = NULL;
175 _cleanup_strv_free_ char **l = NULL;
176 int r;
177
178 locales = set_new(&string_hash_ops);
179 if (!locales)
180 return -ENOMEM;
181
182 r = add_locales_from_archive(locales);
183 if (r < 0 && r != -ENOENT)
184 return r;
185
186 r = add_locales_from_libdir(locales);
187 if (r < 0)
188 return r;
189
190 l = set_get_strv(locales);
191 if (!l)
192 return -ENOMEM;
193
194 strv_sort(l);
195
196 *ret = l;
197 l = NULL;
198
199 return 0;
200 }
201
202 bool locale_is_valid(const char *name) {
203
204 if (isempty(name))
205 return false;
206
207 if (strlen(name) >= 128)
208 return false;
209
210 if (!utf8_is_valid(name))
211 return false;
212
213 if (!filename_is_valid(name))
214 return false;
215
216 if (!string_is_safe(name))
217 return false;
218
219 return true;
220 }
221
222 void init_gettext(void) {
223 setlocale(LC_ALL, "");
224 textdomain(GETTEXT_PACKAGE);
225 }
226
227 bool is_locale_utf8(void) {
228 const char *set;
229 static int cached_answer = -1;
230
231 /* Note that we default to 'true' here, since today UTF8 is
232 * pretty much supported everywhere. */
233
234 if (cached_answer >= 0)
235 goto out;
236
237 if (!setlocale(LC_ALL, "")) {
238 cached_answer = true;
239 goto out;
240 }
241
242 set = nl_langinfo(CODESET);
243 if (!set) {
244 cached_answer = true;
245 goto out;
246 }
247
248 if (streq(set, "UTF-8")) {
249 cached_answer = true;
250 goto out;
251 }
252
253 /* For LC_CTYPE=="C" return true, because CTYPE is effectly
254 * unset and everything can do to UTF-8 nowadays. */
255 set = setlocale(LC_CTYPE, NULL);
256 if (!set) {
257 cached_answer = true;
258 goto out;
259 }
260
261 /* Check result, but ignore the result if C was set
262 * explicitly. */
263 cached_answer =
264 STR_IN_SET(set, "C", "POSIX") &&
265 !getenv("LC_ALL") &&
266 !getenv("LC_CTYPE") &&
267 !getenv("LANG");
268
269 out:
270 return (bool) cached_answer;
271 }
272
273
274 const char *draw_special_char(DrawSpecialChar ch) {
275
276 static const char *draw_table[2][_DRAW_SPECIAL_CHAR_MAX] = {
277
278 /* UTF-8 */ {
279 [DRAW_TREE_VERTICAL] = "\342\224\202 ", /* │ */
280 [DRAW_TREE_BRANCH] = "\342\224\234\342\224\200", /* ├─ */
281 [DRAW_TREE_RIGHT] = "\342\224\224\342\224\200", /* └─ */
282 [DRAW_TREE_SPACE] = " ", /* */
283 [DRAW_TRIANGULAR_BULLET] = "\342\200\243", /* ‣ */
284 [DRAW_BLACK_CIRCLE] = "\342\227\217", /* ● */
285 [DRAW_ARROW] = "\342\206\222", /* → */
286 [DRAW_DASH] = "\342\200\223", /* – */
287 },
288
289 /* ASCII fallback */ {
290 [DRAW_TREE_VERTICAL] = "| ",
291 [DRAW_TREE_BRANCH] = "|-",
292 [DRAW_TREE_RIGHT] = "`-",
293 [DRAW_TREE_SPACE] = " ",
294 [DRAW_TRIANGULAR_BULLET] = ">",
295 [DRAW_BLACK_CIRCLE] = "*",
296 [DRAW_ARROW] = "->",
297 [DRAW_DASH] = "-",
298 }
299 };
300
301 return draw_table[!is_locale_utf8()][ch];
302 }
303
304 static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
305 [VARIABLE_LANG] = "LANG",
306 [VARIABLE_LANGUAGE] = "LANGUAGE",
307 [VARIABLE_LC_CTYPE] = "LC_CTYPE",
308 [VARIABLE_LC_NUMERIC] = "LC_NUMERIC",
309 [VARIABLE_LC_TIME] = "LC_TIME",
310 [VARIABLE_LC_COLLATE] = "LC_COLLATE",
311 [VARIABLE_LC_MONETARY] = "LC_MONETARY",
312 [VARIABLE_LC_MESSAGES] = "LC_MESSAGES",
313 [VARIABLE_LC_PAPER] = "LC_PAPER",
314 [VARIABLE_LC_NAME] = "LC_NAME",
315 [VARIABLE_LC_ADDRESS] = "LC_ADDRESS",
316 [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE",
317 [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT",
318 [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
319 };
320
321 DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);