chiark / gitweb /
tree-wide: get rid of more strerror() calls
[elogind.git] / src / shared / 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 <sys/mman.h>
23
24 #include "set.h"
25 #include "util.h"
26 #include "utf8.h"
27 #include "strv.h"
28
29 #include "locale-util.h"
30
31 static int add_locales_from_archive(Set *locales) {
32         /* Stolen from glibc... */
33
34         struct locarhead {
35                 uint32_t magic;
36                 /* Serial number.  */
37                 uint32_t serial;
38                 /* Name hash table.  */
39                 uint32_t namehash_offset;
40                 uint32_t namehash_used;
41                 uint32_t namehash_size;
42                 /* String table.  */
43                 uint32_t string_offset;
44                 uint32_t string_used;
45                 uint32_t string_size;
46                 /* Table with locale records.  */
47                 uint32_t locrectab_offset;
48                 uint32_t locrectab_used;
49                 uint32_t locrectab_size;
50                 /* MD5 sum hash table.  */
51                 uint32_t sumhash_offset;
52                 uint32_t sumhash_used;
53                 uint32_t sumhash_size;
54         };
55
56         struct namehashent {
57                 /* Hash value of the name.  */
58                 uint32_t hashval;
59                 /* Offset of the name in the string table.  */
60                 uint32_t name_offset;
61                 /* Offset of the locale record.  */
62                 uint32_t locrec_offset;
63         };
64
65         const struct locarhead *h;
66         const struct namehashent *e;
67         const void *p = MAP_FAILED;
68         _cleanup_close_ int fd = -1;
69         size_t sz = 0;
70         struct stat st;
71         unsigned i;
72         int r;
73
74         fd = open("/usr/lib/locale/locale-archive", O_RDONLY|O_NOCTTY|O_CLOEXEC);
75         if (fd < 0)
76                 return errno == ENOENT ? 0 : -errno;
77
78         if (fstat(fd, &st) < 0)
79                 return -errno;
80
81         if (!S_ISREG(st.st_mode))
82                 return -EBADMSG;
83
84         if (st.st_size < (off_t) sizeof(struct locarhead))
85                 return -EBADMSG;
86
87         p = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd, 0);
88         if (p == MAP_FAILED)
89                 return -errno;
90
91         h = (const struct locarhead *) p;
92         if (h->magic != 0xde020109 ||
93             h->namehash_offset + h->namehash_size > st.st_size ||
94             h->string_offset + h->string_size > st.st_size ||
95             h->locrectab_offset + h->locrectab_size > st.st_size ||
96             h->sumhash_offset + h->sumhash_size > st.st_size) {
97                 r = -EBADMSG;
98                 goto finish;
99         }
100
101         e = (const struct namehashent*) ((const uint8_t*) p + h->namehash_offset);
102         for (i = 0; i < h->namehash_size; i++) {
103                 char *z;
104
105                 if (e[i].locrec_offset == 0)
106                         continue;
107
108                 if (!utf8_is_valid((char*) p + e[i].name_offset))
109                         continue;
110
111                 z = strdup((char*) p + e[i].name_offset);
112                 if (!z) {
113                         r = -ENOMEM;
114                         goto finish;
115                 }
116
117                 r = set_consume(locales, z);
118                 if (r < 0)
119                         goto finish;
120         }
121
122         r = 0;
123
124  finish:
125         if (p != MAP_FAILED)
126                 munmap((void*) p, sz);
127
128         return r;
129 }
130
131 static int add_locales_from_libdir (Set *locales) {
132         _cleanup_closedir_ DIR *dir = NULL;
133         struct dirent *entry;
134         int r;
135
136         dir = opendir("/usr/lib/locale");
137         if (!dir)
138                 return errno == ENOENT ? 0 : -errno;
139
140         FOREACH_DIRENT(entry, dir, return -errno) {
141                 char *z;
142
143                 if (entry->d_type != DT_DIR)
144                         continue;
145
146                 z = strdup(entry->d_name);
147                 if (!z)
148                         return -ENOMEM;
149
150                 r = set_consume(locales, z);
151                 if (r < 0 && r != -EEXIST)
152                         return r;
153         }
154
155         return 0;
156 }
157
158 int get_locales(char ***ret) {
159         _cleanup_set_free_ Set *locales = NULL;
160         _cleanup_strv_free_ char **l = NULL;
161         int r;
162
163         locales = set_new(&string_hash_ops);
164         if (!locales)
165                 return -ENOMEM;
166
167         r = add_locales_from_archive(locales);
168         if (r < 0 && r != -ENOENT)
169                 return r;
170
171         r = add_locales_from_libdir(locales);
172         if (r < 0)
173                 return r;
174
175         l = set_get_strv(locales);
176         if (!l)
177                 return -ENOMEM;
178
179         strv_sort(l);
180
181         *ret = l;
182         l = NULL;
183
184         return 0;
185 }
186
187 bool locale_is_valid(const char *name) {
188
189         if (isempty(name))
190                 return false;
191
192         if (strlen(name) >= 128)
193                 return false;
194
195         if (!utf8_is_valid(name))
196                 return false;
197
198         if (!filename_is_valid(name))
199                 return false;
200
201         if (!string_is_safe(name))
202                 return false;
203
204         return true;
205 }
206
207 static const char * const locale_variable_table[_VARIABLE_LC_MAX] = {
208         [VARIABLE_LANG] = "LANG",
209         [VARIABLE_LANGUAGE] = "LANGUAGE",
210         [VARIABLE_LC_CTYPE] = "LC_CTYPE",
211         [VARIABLE_LC_NUMERIC] = "LC_NUMERIC",
212         [VARIABLE_LC_TIME] = "LC_TIME",
213         [VARIABLE_LC_COLLATE] = "LC_COLLATE",
214         [VARIABLE_LC_MONETARY] = "LC_MONETARY",
215         [VARIABLE_LC_MESSAGES] = "LC_MESSAGES",
216         [VARIABLE_LC_PAPER] = "LC_PAPER",
217         [VARIABLE_LC_NAME] = "LC_NAME",
218         [VARIABLE_LC_ADDRESS] = "LC_ADDRESS",
219         [VARIABLE_LC_TELEPHONE] = "LC_TELEPHONE",
220         [VARIABLE_LC_MEASUREMENT] = "LC_MEASUREMENT",
221         [VARIABLE_LC_IDENTIFICATION] = "LC_IDENTIFICATION"
222 };
223
224 DEFINE_STRING_TABLE_LOOKUP(locale_variable, LocaleVariable);