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