chiark / gitweb /
Prep v230: Move musl_missing and parse-printf-format to libshared.
[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                 dirent_ensure_type(dir, entry);
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 *special_glyph(SpecialGlyph code) {
275
276         static const char* const draw_table[2][_SPECIAL_GLYPH_MAX] = {
277                 /* ASCII fallback */
278                 [false] = {
279                         [TREE_VERTICAL]      = "| ",
280                         [TREE_BRANCH]        = "|-",
281                         [TREE_RIGHT]         = "`-",
282                         [TREE_SPACE]         = "  ",
283                         [TRIANGULAR_BULLET]  = ">",
284                         [BLACK_CIRCLE]       = "*",
285                         [ARROW]              = "->",
286                         [MDASH]              = "-",
287                 },
288
289                 /* UTF-8 */
290                 [ true ] = {
291                         [TREE_VERTICAL]      = "\342\224\202 ",            /* │  */
292                         [TREE_BRANCH]        = "\342\224\234\342\224\200", /* ├─ */
293                         [TREE_RIGHT]         = "\342\224\224\342\224\200", /* └─ */
294                         [TREE_SPACE]         = "  ",                       /*    */
295                         [TRIANGULAR_BULLET]  = "\342\200\243",             /* ‣ */
296                         [BLACK_CIRCLE]       = "\342\227\217",             /* ● */
297                         [ARROW]              = "\342\206\222",             /* → */
298                         [MDASH]              = "\342\200\223",             /* – */
299                 },
300         };
301
302         return draw_table[is_locale_utf8()][code];
303 }
304
305 static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
306         [VARIABLE_LANG] = "LANG",
307         [VARIABLE_LANGUAGE] = "LANGUAGE",
308         [VARIABLE_LC_CTYPE] = "LC_CTYPE",
309         [VARIABLE_LC_NUMERIC] = "LC_NUMERIC",
310         [VARIABLE_LC_TIME] = "LC_TIME",
311         [VARIABLE_LC_COLLATE] = "LC_COLLATE",
312         [VARIABLE_LC_MONETARY] = "LC_MONETARY",
313         [VARIABLE_LC_MESSAGES] = "LC_MESSAGES",
314         [VARIABLE_LC_PAPER] = "LC_PAPER",
315         [VARIABLE_LC_NAME] = "LC_NAME",
316         [VARIABLE_LC_ADDRESS] = "LC_ADDRESS",
317         [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE",
318         [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT",
319         [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
320 };
321
322 DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);