]>
Commit | Line | Data |
---|---|---|
203ffbfa CPE |
1 | /* gettext.c - gettext module */ |
2 | /* | |
3 | * GRUB -- GRand Unified Bootloader | |
4 | * Copyright (C) 2009 Free Software Foundation, Inc. | |
5 | * | |
6 | * GRUB is free software: you can redistribute it and/or modify | |
7 | * it under the terms of the GNU General Public License as published by | |
8 | * the Free Software Foundation, either version 3 of the License, or | |
9 | * (at your option) any later version. | |
10 | * | |
11 | * GRUB is distributed in the hope that it will be useful, | |
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
14 | * GNU General Public License for more details. | |
15 | * | |
16 | * You should have received a copy of the GNU General Public License | |
17 | * along with GRUB. If not, see <http://www.gnu.org/licenses/>. | |
18 | */ | |
19 | ||
98d3dc02 | 20 | #include <grub/list.h> |
203ffbfa CPE |
21 | #include <grub/types.h> |
22 | #include <grub/misc.h> | |
23 | #include <grub/mm.h> | |
24 | #include <grub/err.h> | |
25 | #include <grub/dl.h> | |
26 | #include <grub/normal.h> | |
27 | #include <grub/file.h> | |
28 | #include <grub/kernel.h> | |
0648f857 | 29 | #include <grub/gzio.h> |
4a8572e9 | 30 | #include <grub/i18n.h> |
203ffbfa | 31 | |
a2c1332b FZ |
32 | /* |
33 | .mo file information from: | |
203ffbfa CPE |
34 | http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html . |
35 | */ | |
36 | ||
37 | ||
203ffbfa CPE |
38 | static grub_file_t fd_mo; |
39 | ||
40 | static int grub_gettext_offsetoriginal; | |
41 | static int grub_gettext_max; | |
42 | ||
0648f857 | 43 | static const char *(*grub_gettext_original) (const char *s); |
203ffbfa | 44 | |
98d3dc02 CPE |
45 | struct grub_gettext_msg |
46 | { | |
47 | struct grub_gettext_msg *next; | |
48 | const char *name; | |
49 | ||
50 | const char *translated; | |
51 | }; | |
52 | ||
53 | struct grub_gettext_msg *grub_gettext_msg_list = NULL; | |
54 | ||
0648f857 CPE |
55 | #define GETTEXT_MAGIC_NUMBER 0 |
56 | #define GETTEXT_FILE_FORMAT 4 | |
57 | #define GETTEXT_NUMBER_OF_STRINGS 8 | |
58 | #define GETTEXT_OFFSET_ORIGINAL 12 | |
59 | #define GETTEXT_OFFSET_TRANSLATION 16 | |
203ffbfa | 60 | |
0648f857 | 61 | #define MO_MAGIC_NUMBER 0x950412de |
203ffbfa | 62 | |
e5fb78c6 CPE |
63 | static grub_ssize_t |
64 | grub_gettext_pread (grub_file_t file, void *buf, grub_size_t len, | |
65 | grub_off_t offset) | |
66 | { | |
67 | if (grub_file_seek (file, offset) == (grub_off_t) - 1) | |
68 | { | |
69 | return -1; | |
70 | } | |
71 | return grub_file_read (file, buf, len); | |
72 | } | |
73 | ||
203ffbfa CPE |
74 | static grub_uint32_t |
75 | grub_gettext_get_info (int offset) | |
76 | { | |
77 | grub_uint32_t value; | |
78 | ||
e5fb78c6 | 79 | grub_gettext_pread (fd_mo, (char *) &value, 4, offset); |
0648f857 | 80 | |
203ffbfa CPE |
81 | value = grub_cpu_to_le32 (value); |
82 | return value; | |
83 | } | |
84 | ||
85 | static void | |
0648f857 CPE |
86 | grub_gettext_getstring_from_offset (grub_uint32_t offset, |
87 | grub_uint32_t length, char *translation) | |
203ffbfa | 88 | { |
e5fb78c6 | 89 | grub_gettext_pread (fd_mo, translation, length, offset); |
203ffbfa CPE |
90 | translation[length] = '\0'; |
91 | } | |
92 | ||
98d3dc02 | 93 | static const char * |
203ffbfa CPE |
94 | grub_gettext_gettranslation_from_position (int position) |
95 | { | |
96 | int offsettranslation; | |
97 | int internal_position; | |
98 | grub_uint32_t length, offset; | |
99 | char *translation; | |
100 | ||
101 | offsettranslation = grub_gettext_get_info (GETTEXT_OFFSET_TRANSLATION); | |
102 | ||
103 | internal_position = offsettranslation + position * 8; | |
104 | ||
e5fb78c6 | 105 | grub_gettext_pread (fd_mo, (char *) &length, 4, internal_position); |
203ffbfa | 106 | length = grub_cpu_to_le32 (length); |
0648f857 | 107 | |
e5fb78c6 | 108 | grub_gettext_pread (fd_mo, (char *) &offset, 4, internal_position + 4); |
203ffbfa CPE |
109 | offset = grub_cpu_to_le32 (offset); |
110 | ||
0648f857 | 111 | translation = grub_malloc (length + 1); |
203ffbfa CPE |
112 | grub_gettext_getstring_from_offset (offset, length, translation); |
113 | ||
114 | return translation; | |
115 | } | |
116 | ||
0648f857 | 117 | static char * |
203ffbfa CPE |
118 | grub_gettext_getstring_from_position (int position) |
119 | { | |
120 | int internal_position; | |
121 | int length, offset; | |
122 | char *original; | |
123 | ||
124 | /* Get position for string i. */ | |
125 | internal_position = grub_gettext_offsetoriginal + (position * 8); | |
126 | ||
127 | /* Get the length of the string i. */ | |
e5fb78c6 | 128 | grub_gettext_pread (fd_mo, (char *) &length, 4, internal_position); |
203ffbfa CPE |
129 | |
130 | /* Get the offset of the string i. */ | |
e5fb78c6 | 131 | grub_gettext_pread (fd_mo, (char *) &offset, 4, internal_position + 4); |
203ffbfa CPE |
132 | |
133 | /* Get the string i. */ | |
134 | original = grub_malloc (length + 1); | |
135 | grub_gettext_getstring_from_offset (offset, length, original); | |
136 | ||
137 | return original; | |
138 | } | |
139 | ||
0648f857 | 140 | static const char * |
203ffbfa CPE |
141 | grub_gettext_translate (const char *orig) |
142 | { | |
143 | char *current_string; | |
98d3dc02 | 144 | const char *ret; |
203ffbfa | 145 | |
0648f857 | 146 | int min, max, current; |
98d3dc02 CPE |
147 | int found = 0; |
148 | ||
149 | struct grub_gettext_msg *cur; | |
150 | ||
ba2f6848 | 151 | /* Make sure we can use grub_gettext_translate for error messages. Push |
152 | active error message to error stack and reset error message. */ | |
153 | grub_error_push (); | |
154 | ||
98d3dc02 CPE |
155 | cur = grub_named_list_find (GRUB_AS_NAMED_LIST (grub_gettext_msg_list), |
156 | orig); | |
157 | ||
158 | if (cur) | |
ba2f6848 | 159 | { |
160 | grub_error_pop (); | |
161 | return cur->translated; | |
162 | } | |
203ffbfa CPE |
163 | |
164 | if (fd_mo == 0) | |
ba2f6848 | 165 | { |
166 | grub_error_pop (); | |
167 | return orig; | |
168 | } | |
203ffbfa CPE |
169 | |
170 | min = 0; | |
171 | max = grub_gettext_max; | |
172 | ||
173 | current = (max + min) / 2; | |
174 | ||
98d3dc02 | 175 | while (current != min && current != max && found == 0) |
203ffbfa CPE |
176 | { |
177 | current_string = grub_gettext_getstring_from_position (current); | |
178 | ||
179 | /* Search by bisection. */ | |
180 | if (grub_strcmp (current_string, orig) < 0) | |
0648f857 CPE |
181 | { |
182 | grub_free (current_string); | |
183 | min = current; | |
184 | } | |
203ffbfa | 185 | else if (grub_strcmp (current_string, orig) > 0) |
0648f857 CPE |
186 | { |
187 | grub_free (current_string); | |
188 | max = current; | |
189 | } | |
203ffbfa | 190 | else if (grub_strcmp (current_string, orig) == 0) |
0648f857 CPE |
191 | { |
192 | grub_free (current_string); | |
98d3dc02 | 193 | found = 1; |
0648f857 CPE |
194 | } |
195 | current = (max + min) / 2; | |
203ffbfa CPE |
196 | } |
197 | ||
98d3dc02 CPE |
198 | ret = found ? grub_gettext_gettranslation_from_position (current) : orig; |
199 | ||
200 | if (found) | |
201 | { | |
202 | cur = grub_zalloc (sizeof (*cur)); | |
203 | ||
204 | if (cur) | |
205 | { | |
206 | cur->name = grub_strdup (orig); | |
207 | if (cur->name) | |
208 | { | |
209 | cur->translated = ret; | |
210 | grub_list_push (GRUB_AS_LIST_P (&grub_gettext_msg_list), | |
211 | GRUB_AS_LIST (cur)); | |
212 | } | |
213 | } | |
214 | else | |
215 | grub_errno = GRUB_ERR_NONE; | |
216 | } | |
217 | ||
ba2f6848 | 218 | grub_error_pop (); |
203ffbfa CPE |
219 | return ret; |
220 | } | |
221 | ||
222 | /* This is similar to grub_gzfile_open. */ | |
223 | static grub_file_t | |
224 | grub_mofile_open (const char *filename) | |
225 | { | |
226 | int unsigned magic; | |
227 | int version; | |
228 | ||
229 | /* Using fd_mo and not another variable because | |
230 | it's needed for grub_gettext_get_info. */ | |
231 | ||
c505aa62 | 232 | fd_mo = grub_gzfile_open (filename, 1); |
0648f857 CPE |
233 | grub_errno = GRUB_ERR_NONE; |
234 | ||
c505aa62 | 235 | if (!fd_mo) |
203ffbfa | 236 | { |
b6c871b0 | 237 | grub_dprintf ("gettext", "Cannot read %s\n", filename); |
203ffbfa CPE |
238 | return 0; |
239 | } | |
240 | ||
241 | magic = grub_gettext_get_info (GETTEXT_MAGIC_NUMBER); | |
242 | ||
243 | if (magic != MO_MAGIC_NUMBER) | |
244 | { | |
0648f857 CPE |
245 | grub_error (GRUB_ERR_BAD_FILE_TYPE, "mo: invalid mo file: %s", |
246 | filename); | |
203ffbfa CPE |
247 | grub_file_close (fd_mo); |
248 | fd_mo = 0; | |
249 | return 0; | |
250 | } | |
0648f857 | 251 | |
203ffbfa CPE |
252 | version = grub_gettext_get_info (GETTEXT_FILE_FORMAT); |
253 | ||
254 | if (version != 0) | |
255 | { | |
0648f857 | 256 | grub_error (GRUB_ERR_BAD_FILE_TYPE, |
7fd0baee | 257 | "mo: invalid mo version in file: %s", filename); |
203ffbfa CPE |
258 | fd_mo = 0; |
259 | return 0; | |
260 | } | |
203ffbfa CPE |
261 | |
262 | return fd_mo; | |
263 | } | |
264 | ||
265 | static void | |
266 | grub_gettext_init_ext (const char *lang) | |
267 | { | |
268 | char *mo_file; | |
a239a5e9 | 269 | char *locale_dir; |
203ffbfa | 270 | |
a239a5e9 | 271 | locale_dir = grub_env_get ("locale_dir"); |
0648f857 CPE |
272 | if (locale_dir == NULL) |
273 | { | |
98d3dc02 | 274 | grub_dprintf ("gettext", "locale_dir variable is not set up.\n"); |
0648f857 CPE |
275 | return; |
276 | } | |
277 | ||
278 | fd_mo = NULL; | |
279 | ||
ee99edc8 | 280 | /* mo_file e.g.: /boot/grub/locale/ca.mo */ |
203ffbfa | 281 | |
0648f857 CPE |
282 | mo_file = |
283 | grub_malloc (grub_strlen (locale_dir) + grub_strlen ("/") + | |
284 | grub_strlen (lang) + grub_strlen (".mo") + 1); | |
285 | ||
ee99edc8 CPE |
286 | /* Warning: if changing some paths in the below line, change the grub_malloc |
287 | contents below. */ | |
0648f857 | 288 | |
8b442f3f VS |
289 | mo_file = grub_asprintf ("%s/%s.mo", locale_dir, lang); |
290 | if (!mo_file) | |
291 | return; | |
a239a5e9 | 292 | |
0648f857 | 293 | fd_mo = grub_mofile_open (mo_file); |
203ffbfa | 294 | |
0648f857 CPE |
295 | /* Will try adding .gz as well. */ |
296 | if (fd_mo == NULL) | |
297 | { | |
8b442f3f VS |
298 | grub_free (mo_file); |
299 | mo_file = grub_asprintf ("%s.gz", mo_file); | |
300 | if (!mo_file) | |
301 | return; | |
0648f857 CPE |
302 | fd_mo = grub_mofile_open (mo_file); |
303 | } | |
203ffbfa CPE |
304 | |
305 | if (fd_mo) | |
306 | { | |
0648f857 CPE |
307 | grub_gettext_offsetoriginal = |
308 | grub_gettext_get_info (GETTEXT_OFFSET_ORIGINAL); | |
309 | grub_gettext_max = grub_gettext_get_info (GETTEXT_NUMBER_OF_STRINGS); | |
203ffbfa CPE |
310 | |
311 | grub_gettext_original = grub_gettext; | |
312 | grub_gettext = grub_gettext_translate; | |
313 | } | |
314 | } | |
315 | ||
98d3dc02 | 316 | static void |
90d1e879 | 317 | grub_gettext_delete_list (void) |
98d3dc02 CPE |
318 | { |
319 | struct grub_gettext_msg *item; | |
320 | ||
321 | while ((item = | |
322 | grub_list_pop (GRUB_AS_LIST_P (&grub_gettext_msg_list))) != 0) | |
323 | { | |
324 | char *original = (char *) ((struct grub_gettext_msg *) item)->name; | |
325 | grub_free (original); | |
326 | ||
ba2f6848 | 327 | /* Don't delete the translated message because could be in use. */ |
98d3dc02 CPE |
328 | } |
329 | } | |
330 | ||
0648f857 CPE |
331 | static char * |
332 | grub_gettext_env_write_lang (struct grub_env_var *var | |
333 | __attribute__ ((unused)), const char *val) | |
203ffbfa CPE |
334 | { |
335 | grub_gettext_init_ext (val); | |
336 | ||
98d3dc02 CPE |
337 | grub_gettext_delete_list (); |
338 | ||
203ffbfa CPE |
339 | return grub_strdup (val); |
340 | } | |
341 | ||
b6c871b0 CPE |
342 | static grub_err_t |
343 | grub_cmd_translate (grub_command_t cmd __attribute__ ((unused)), | |
344 | int argc, char **args) | |
345 | { | |
346 | if (argc != 1) | |
347 | return grub_error (GRUB_ERR_BAD_ARGUMENT, "text to translate required"); | |
348 | ||
349 | const char *translation; | |
350 | translation = grub_gettext_translate (args[0]); | |
351 | grub_printf ("%s\n", translation); | |
352 | return 0; | |
353 | } | |
354 | ||
0648f857 | 355 | GRUB_MOD_INIT (gettext) |
203ffbfa | 356 | { |
0648f857 CPE |
357 | (void) mod; /* To stop warning. */ |
358 | ||
203ffbfa CPE |
359 | const char *lang; |
360 | ||
0648f857 | 361 | lang = grub_env_get ("lang"); |
203ffbfa CPE |
362 | |
363 | grub_gettext_init_ext (lang); | |
364 | ||
b6c871b0 | 365 | grub_register_command_p1 ("gettext", grub_cmd_translate, |
5ce0a83a | 366 | N_("STRING"), |
367 | N_("Translates the string with the current settings.")); | |
b6c871b0 | 368 | |
203ffbfa CPE |
369 | /* Reload .mo file information if lang changes. */ |
370 | grub_register_variable_hook ("lang", NULL, grub_gettext_env_write_lang); | |
371 | ||
372 | /* Preserve hooks after context changes. */ | |
373 | grub_env_export ("lang"); | |
374 | } | |
375 | ||
0648f857 | 376 | GRUB_MOD_FINI (gettext) |
203ffbfa CPE |
377 | { |
378 | if (fd_mo != 0) | |
0648f857 | 379 | grub_file_close (fd_mo); |
203ffbfa | 380 | |
98d3dc02 CPE |
381 | grub_gettext_delete_list (); |
382 | ||
203ffbfa CPE |
383 | grub_gettext = grub_gettext_original; |
384 | } |