gettext.c 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552
  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. #include <grub/types.h>
  20. #include <grub/misc.h>
  21. #include <grub/mm.h>
  22. #include <grub/err.h>
  23. #include <grub/dl.h>
  24. #include <grub/normal.h>
  25. #include <grub/file.h>
  26. #include <grub/kernel.h>
  27. #include <grub/i18n.h>
  28. #include <grub/safemath.h>
  29. GRUB_MOD_LICENSE ("GPLv3+");
  30. /*
  31. .mo file information from:
  32. http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html .
  33. */
  34. static const char *(*grub_gettext_original) (const char *s);
  35. struct grub_gettext_msg
  36. {
  37. char *name;
  38. char *translated;
  39. };
  40. struct header
  41. {
  42. grub_uint32_t magic;
  43. grub_uint32_t version;
  44. grub_uint32_t number_of_strings;
  45. grub_uint32_t offset_original;
  46. grub_uint32_t offset_translation;
  47. };
  48. struct string_descriptor
  49. {
  50. grub_uint32_t length;
  51. grub_uint32_t offset;
  52. };
  53. struct grub_gettext_context
  54. {
  55. grub_file_t fd_mo;
  56. grub_off_t grub_gettext_offset_original;
  57. grub_off_t grub_gettext_offset_translation;
  58. grub_size_t grub_gettext_max;
  59. int grub_gettext_max_log;
  60. struct grub_gettext_msg *grub_gettext_msg_list;
  61. };
  62. static struct grub_gettext_context main_context, secondary_context;
  63. #define MO_MAGIC_NUMBER 0x950412de
  64. static grub_err_t
  65. grub_gettext_pread (grub_file_t file, void *buf, grub_size_t len,
  66. grub_off_t offset)
  67. {
  68. if (len == 0)
  69. return GRUB_ERR_NONE;
  70. if (grub_file_seek (file, offset) == (grub_off_t) - 1)
  71. return grub_errno;
  72. if (grub_file_read (file, buf, len) != (grub_ssize_t) len)
  73. {
  74. if (!grub_errno)
  75. grub_error (GRUB_ERR_READ_ERROR, N_("premature end of file"));
  76. return grub_errno;
  77. }
  78. return GRUB_ERR_NONE;
  79. }
  80. static char *
  81. grub_gettext_getstr_from_position (struct grub_gettext_context *ctx,
  82. grub_off_t off,
  83. grub_size_t position)
  84. {
  85. grub_off_t internal_position;
  86. grub_size_t length;
  87. grub_off_t offset;
  88. char *translation;
  89. struct string_descriptor desc;
  90. grub_err_t err;
  91. grub_size_t alloc_sz;
  92. internal_position = (off + position * sizeof (desc));
  93. err = grub_gettext_pread (ctx->fd_mo, (char *) &desc,
  94. sizeof (desc), internal_position);
  95. if (err)
  96. return NULL;
  97. length = grub_cpu_to_le32 (desc.length);
  98. offset = grub_cpu_to_le32 (desc.offset);
  99. if (grub_add (length, 1, &alloc_sz))
  100. return NULL;
  101. translation = grub_malloc (alloc_sz);
  102. if (!translation)
  103. return NULL;
  104. err = grub_gettext_pread (ctx->fd_mo, translation, length, offset);
  105. if (err)
  106. {
  107. grub_free (translation);
  108. return NULL;
  109. }
  110. translation[length] = '\0';
  111. return translation;
  112. }
  113. static const char *
  114. grub_gettext_gettranslation_from_position (struct grub_gettext_context *ctx,
  115. grub_size_t position)
  116. {
  117. if (!ctx->grub_gettext_msg_list[position].translated)
  118. ctx->grub_gettext_msg_list[position].translated
  119. = grub_gettext_getstr_from_position (ctx,
  120. ctx->grub_gettext_offset_translation,
  121. position);
  122. return ctx->grub_gettext_msg_list[position].translated;
  123. }
  124. static const char *
  125. grub_gettext_getstring_from_position (struct grub_gettext_context *ctx,
  126. grub_size_t position)
  127. {
  128. if (!ctx->grub_gettext_msg_list[position].name)
  129. ctx->grub_gettext_msg_list[position].name
  130. = grub_gettext_getstr_from_position (ctx,
  131. ctx->grub_gettext_offset_original,
  132. position);
  133. return ctx->grub_gettext_msg_list[position].name;
  134. }
  135. static const char *
  136. grub_gettext_translate_real (struct grub_gettext_context *ctx,
  137. const char *orig)
  138. {
  139. grub_size_t current = 0;
  140. int i;
  141. const char *current_string;
  142. static int depth = 0;
  143. if (!ctx->grub_gettext_msg_list || !ctx->fd_mo)
  144. return NULL;
  145. /* Shouldn't happen. Just a precaution if our own code
  146. calls gettext somehow. */
  147. if (depth > 2)
  148. return NULL;
  149. depth++;
  150. /* Make sure we can use grub_gettext_translate for error messages. Push
  151. active error message to error stack and reset error message. */
  152. grub_error_push ();
  153. for (i = ctx->grub_gettext_max_log; i >= 0; i--)
  154. {
  155. grub_size_t test;
  156. int cmp;
  157. test = current | (1 << i);
  158. if (test >= ctx->grub_gettext_max)
  159. continue;
  160. current_string = grub_gettext_getstring_from_position (ctx, test);
  161. if (!current_string)
  162. {
  163. grub_errno = GRUB_ERR_NONE;
  164. grub_error_pop ();
  165. depth--;
  166. return NULL;
  167. }
  168. /* Search by bisection. */
  169. cmp = grub_strcmp (current_string, orig);
  170. if (cmp <= 0)
  171. current = test;
  172. if (cmp == 0)
  173. {
  174. const char *ret = 0;
  175. ret = grub_gettext_gettranslation_from_position (ctx, current);
  176. if (!ret)
  177. {
  178. grub_errno = GRUB_ERR_NONE;
  179. grub_error_pop ();
  180. depth--;
  181. return NULL;
  182. }
  183. grub_error_pop ();
  184. depth--;
  185. return ret;
  186. }
  187. }
  188. if (current == 0 && ctx->grub_gettext_max != 0)
  189. {
  190. current_string = grub_gettext_getstring_from_position (ctx, 0);
  191. if (!current_string)
  192. {
  193. grub_errno = GRUB_ERR_NONE;
  194. grub_error_pop ();
  195. depth--;
  196. return NULL;
  197. }
  198. if (grub_strcmp (current_string, orig) == 0)
  199. {
  200. const char *ret = 0;
  201. ret = grub_gettext_gettranslation_from_position (ctx, current);
  202. if (!ret)
  203. {
  204. grub_errno = GRUB_ERR_NONE;
  205. grub_error_pop ();
  206. depth--;
  207. return NULL;
  208. }
  209. grub_error_pop ();
  210. depth--;
  211. return ret;
  212. }
  213. }
  214. grub_error_pop ();
  215. depth--;
  216. return NULL;
  217. }
  218. static const char *
  219. grub_gettext_translate (const char *orig)
  220. {
  221. const char *ret;
  222. if (orig[0] == 0)
  223. return orig;
  224. ret = grub_gettext_translate_real (&main_context, orig);
  225. if (ret)
  226. return ret;
  227. ret = grub_gettext_translate_real (&secondary_context, orig);
  228. if (ret)
  229. return ret;
  230. return orig;
  231. }
  232. static void
  233. grub_gettext_delete_list (struct grub_gettext_context *ctx)
  234. {
  235. struct grub_gettext_msg *l = ctx->grub_gettext_msg_list;
  236. grub_size_t i;
  237. if (!l)
  238. return;
  239. ctx->grub_gettext_msg_list = 0;
  240. for (i = 0; i < ctx->grub_gettext_max; i++)
  241. grub_free (l[i].name);
  242. /* Don't delete the translated message because could be in use. */
  243. grub_free (l);
  244. if (ctx->fd_mo)
  245. grub_file_close (ctx->fd_mo);
  246. ctx->fd_mo = 0;
  247. grub_memset (ctx, 0, sizeof (*ctx));
  248. }
  249. /* This is similar to grub_file_open. */
  250. static grub_err_t
  251. grub_mofile_open (struct grub_gettext_context *ctx,
  252. const char *filename)
  253. {
  254. struct header head;
  255. grub_err_t err;
  256. grub_file_t fd;
  257. /* Using fd_mo and not another variable because
  258. it's needed for grub_gettext_get_info. */
  259. fd = grub_file_open (filename, GRUB_FILE_TYPE_GETTEXT_CATALOG);
  260. if (!fd)
  261. return grub_errno;
  262. err = grub_gettext_pread (fd, &head, sizeof (head), 0);
  263. if (err)
  264. {
  265. grub_file_close (fd);
  266. return err;
  267. }
  268. if (head.magic != grub_cpu_to_le32_compile_time (MO_MAGIC_NUMBER))
  269. {
  270. grub_file_close (fd);
  271. return grub_error (GRUB_ERR_BAD_FILE_TYPE,
  272. "mo: invalid mo magic in file: %s", filename);
  273. }
  274. if (head.version != 0)
  275. {
  276. grub_file_close (fd);
  277. return grub_error (GRUB_ERR_BAD_FILE_TYPE,
  278. "mo: invalid mo version in file: %s", filename);
  279. }
  280. ctx->grub_gettext_offset_original = grub_le_to_cpu32 (head.offset_original);
  281. ctx->grub_gettext_offset_translation = grub_le_to_cpu32 (head.offset_translation);
  282. ctx->grub_gettext_max = grub_le_to_cpu32 (head.number_of_strings);
  283. for (ctx->grub_gettext_max_log = 0; ctx->grub_gettext_max >> ctx->grub_gettext_max_log;
  284. ctx->grub_gettext_max_log++);
  285. ctx->grub_gettext_msg_list = grub_calloc (ctx->grub_gettext_max,
  286. sizeof (ctx->grub_gettext_msg_list[0]));
  287. if (!ctx->grub_gettext_msg_list)
  288. {
  289. grub_file_close (fd);
  290. return grub_errno;
  291. }
  292. ctx->fd_mo = fd;
  293. if (grub_gettext != grub_gettext_translate)
  294. {
  295. grub_gettext_original = grub_gettext;
  296. grub_gettext = grub_gettext_translate;
  297. }
  298. return 0;
  299. }
  300. /* Returning grub_file_t would be more natural, but grub_mofile_open assigns
  301. to fd_mo anyway ... */
  302. static grub_err_t
  303. grub_mofile_open_lang (struct grub_gettext_context *ctx,
  304. const char *part1, const char *part2, const char *locale)
  305. {
  306. char *mo_file;
  307. grub_err_t err;
  308. /* mo_file e.g.: /boot/grub/locale/ca.mo */
  309. mo_file = grub_xasprintf ("%s%s/%s.mo", part1, part2, locale);
  310. if (!mo_file)
  311. return grub_errno;
  312. err = grub_mofile_open (ctx, mo_file);
  313. grub_free (mo_file);
  314. /* Will try adding .gz as well. */
  315. if (err)
  316. {
  317. grub_errno = GRUB_ERR_NONE;
  318. mo_file = grub_xasprintf ("%s%s/%s.mo.gz", part1, part2, locale);
  319. if (!mo_file)
  320. return grub_errno;
  321. err = grub_mofile_open (ctx, mo_file);
  322. grub_free (mo_file);
  323. }
  324. /* Will try adding .gmo as well. */
  325. if (err)
  326. {
  327. grub_errno = GRUB_ERR_NONE;
  328. mo_file = grub_xasprintf ("%s%s/%s.gmo", part1, part2, locale);
  329. if (!mo_file)
  330. return grub_errno;
  331. err = grub_mofile_open (ctx, mo_file);
  332. grub_free (mo_file);
  333. }
  334. return err;
  335. }
  336. static grub_err_t
  337. grub_gettext_init_ext (struct grub_gettext_context *ctx,
  338. const char *locale,
  339. const char *locale_dir, const char *prefix)
  340. {
  341. const char *part1, *part2;
  342. grub_err_t err;
  343. grub_gettext_delete_list (ctx);
  344. if (!locale || locale[0] == 0)
  345. return 0;
  346. part1 = locale_dir;
  347. part2 = "";
  348. if (!part1 || part1[0] == 0)
  349. {
  350. part1 = prefix;
  351. part2 = "/locale";
  352. }
  353. if (!part1 || part1[0] == 0)
  354. return 0;
  355. err = grub_mofile_open_lang (ctx, part1, part2, locale);
  356. /* ll_CC didn't work, so try ll. */
  357. if (err)
  358. {
  359. char *lang = grub_strdup (locale);
  360. char *underscore = lang ? grub_strchr (lang, '_') : 0;
  361. if (underscore)
  362. {
  363. *underscore = '\0';
  364. grub_errno = GRUB_ERR_NONE;
  365. err = grub_mofile_open_lang (ctx, part1, part2, lang);
  366. }
  367. grub_free (lang);
  368. }
  369. if (locale[0] == 'e' && locale[1] == 'n'
  370. && (locale[2] == '\0' || locale[2] == '_'))
  371. grub_errno = err = GRUB_ERR_NONE;
  372. return err;
  373. }
  374. static char *
  375. grub_gettext_env_write_lang (struct grub_env_var *var
  376. __attribute__ ((unused)), const char *val)
  377. {
  378. grub_err_t err;
  379. err = grub_gettext_init_ext (&main_context, val, grub_env_get ("locale_dir"),
  380. grub_env_get ("prefix"));
  381. if (err)
  382. grub_print_error ();
  383. err = grub_gettext_init_ext (&secondary_context, val,
  384. grub_env_get ("secondary_locale_dir"), 0);
  385. if (err)
  386. grub_print_error ();
  387. return grub_strdup (val);
  388. }
  389. void
  390. grub_gettext_reread_prefix (const char *val)
  391. {
  392. grub_err_t err;
  393. err = grub_gettext_init_ext (&main_context, grub_env_get ("lang"),
  394. grub_env_get ("locale_dir"),
  395. val);
  396. if (err)
  397. grub_print_error ();
  398. }
  399. static char *
  400. read_main (struct grub_env_var *var
  401. __attribute__ ((unused)), const char *val)
  402. {
  403. grub_err_t err;
  404. err = grub_gettext_init_ext (&main_context, grub_env_get ("lang"), val,
  405. grub_env_get ("prefix"));
  406. if (err)
  407. grub_print_error ();
  408. return grub_strdup (val);
  409. }
  410. static char *
  411. read_secondary (struct grub_env_var *var
  412. __attribute__ ((unused)), const char *val)
  413. {
  414. grub_err_t err;
  415. err = grub_gettext_init_ext (&secondary_context, grub_env_get ("lang"), val,
  416. 0);
  417. if (err)
  418. grub_print_error ();
  419. return grub_strdup (val);
  420. }
  421. static grub_err_t
  422. grub_cmd_translate (grub_command_t cmd __attribute__ ((unused)),
  423. int argc, char **args)
  424. {
  425. if (argc != 1)
  426. return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
  427. const char *translation;
  428. translation = grub_gettext_translate (args[0]);
  429. grub_printf ("%s\n", translation);
  430. return 0;
  431. }
  432. GRUB_MOD_INIT (gettext)
  433. {
  434. const char *lang;
  435. grub_err_t err;
  436. lang = grub_env_get ("lang");
  437. err = grub_gettext_init_ext (&main_context, lang, grub_env_get ("locale_dir"),
  438. grub_env_get ("prefix"));
  439. if (err)
  440. grub_print_error ();
  441. err = grub_gettext_init_ext (&secondary_context, lang,
  442. grub_env_get ("secondary_locale_dir"), 0);
  443. if (err)
  444. grub_print_error ();
  445. grub_register_variable_hook ("locale_dir", NULL, read_main);
  446. grub_register_variable_hook ("secondary_locale_dir", NULL, read_secondary);
  447. grub_register_command_p1 ("gettext", grub_cmd_translate,
  448. N_("STRING"),
  449. /* TRANSLATORS: It refers to passing the string through gettext.
  450. So it's "translate" in the same meaning as in what you're
  451. doing now.
  452. */
  453. N_("Translates the string with the current settings."));
  454. /* Reload .mo file information if lang changes. */
  455. grub_register_variable_hook ("lang", NULL, grub_gettext_env_write_lang);
  456. /* Preserve hooks after context changes. */
  457. grub_env_export ("lang");
  458. grub_env_export ("locale_dir");
  459. grub_env_export ("secondary_locale_dir");
  460. }
  461. GRUB_MOD_FINI (gettext)
  462. {
  463. grub_register_variable_hook ("locale_dir", NULL, NULL);
  464. grub_register_variable_hook ("secondary_locale_dir", NULL, NULL);
  465. grub_register_variable_hook ("lang", NULL, NULL);
  466. grub_gettext_delete_list (&main_context);
  467. grub_gettext_delete_list (&secondary_context);
  468. grub_gettext = grub_gettext_original;
  469. }