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