gettext.c 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379
  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/list.h>
  20. #include <grub/types.h>
  21. #include <grub/misc.h>
  22. #include <grub/mm.h>
  23. #include <grub/err.h>
  24. #include <grub/dl.h>
  25. #include <grub/env.h>
  26. #include <grub/command.h>
  27. #include <grub/file.h>
  28. #include <grub/kernel.h>
  29. #include <grub/gzio.h>
  30. #include <grub/i18n.h>
  31. /*
  32. .mo file information from:
  33. http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html .
  34. */
  35. static grub_file_t fd_mo;
  36. static int grub_gettext_offsetoriginal;
  37. static int grub_gettext_max;
  38. static const char *(*grub_gettext_original) (const char *s);
  39. struct grub_gettext_msg
  40. {
  41. struct grub_gettext_msg *next;
  42. const char *name;
  43. const char *translated;
  44. };
  45. struct grub_gettext_msg *grub_gettext_msg_list = NULL;
  46. #define GETTEXT_MAGIC_NUMBER 0
  47. #define GETTEXT_FILE_FORMAT 4
  48. #define GETTEXT_NUMBER_OF_STRINGS 8
  49. #define GETTEXT_OFFSET_ORIGINAL 12
  50. #define GETTEXT_OFFSET_TRANSLATION 16
  51. #define MO_MAGIC_NUMBER 0x950412de
  52. static grub_ssize_t
  53. grub_gettext_pread (grub_file_t file, void *buf, grub_size_t len,
  54. grub_off_t offset)
  55. {
  56. if (grub_file_seek (file, offset) == (grub_off_t) - 1)
  57. {
  58. return -1;
  59. }
  60. return grub_file_read (file, buf, len);
  61. }
  62. static grub_uint32_t
  63. grub_gettext_get_info (int offset)
  64. {
  65. grub_uint32_t value;
  66. grub_gettext_pread (fd_mo, (char *) &value, 4, offset);
  67. value = grub_cpu_to_le32 (value);
  68. return value;
  69. }
  70. static void
  71. grub_gettext_getstring_from_offset (grub_uint32_t offset,
  72. grub_uint32_t length, char *translation)
  73. {
  74. grub_gettext_pread (fd_mo, translation, length, offset);
  75. translation[length] = '\0';
  76. }
  77. static const char *
  78. grub_gettext_gettranslation_from_position (int position)
  79. {
  80. int offsettranslation;
  81. int internal_position;
  82. grub_uint32_t length, offset;
  83. char *translation;
  84. offsettranslation = grub_gettext_get_info (GETTEXT_OFFSET_TRANSLATION);
  85. internal_position = offsettranslation + position * 8;
  86. grub_gettext_pread (fd_mo, (char *) &length, 4, internal_position);
  87. length = grub_cpu_to_le32 (length);
  88. grub_gettext_pread (fd_mo, (char *) &offset, 4, internal_position + 4);
  89. offset = grub_cpu_to_le32 (offset);
  90. translation = grub_malloc (length + 1);
  91. grub_gettext_getstring_from_offset (offset, length, translation);
  92. return translation;
  93. }
  94. static char *
  95. grub_gettext_getstring_from_position (int position)
  96. {
  97. int internal_position;
  98. int length, offset;
  99. char *original;
  100. /* Get position for string i. */
  101. internal_position = grub_gettext_offsetoriginal + (position * 8);
  102. /* Get the length of the string i. */
  103. grub_gettext_pread (fd_mo, (char *) &length, 4, internal_position);
  104. /* Get the offset of the string i. */
  105. grub_gettext_pread (fd_mo, (char *) &offset, 4, internal_position + 4);
  106. /* Get the string i. */
  107. original = grub_malloc (length + 1);
  108. grub_gettext_getstring_from_offset (offset, length, original);
  109. return original;
  110. }
  111. static const char *
  112. grub_gettext_translate (const char *orig)
  113. {
  114. char *current_string;
  115. const char *ret;
  116. int min, max, current;
  117. int found = 0;
  118. struct grub_gettext_msg *cur;
  119. /* Make sure we can use grub_gettext_translate for error messages. Push
  120. active error message to error stack and reset error message. */
  121. grub_error_push ();
  122. cur = grub_named_list_find (GRUB_AS_NAMED_LIST (grub_gettext_msg_list),
  123. orig);
  124. if (cur)
  125. {
  126. grub_error_pop ();
  127. return cur->translated;
  128. }
  129. if (fd_mo == 0)
  130. {
  131. grub_error_pop ();
  132. return orig;
  133. }
  134. min = 0;
  135. max = grub_gettext_max;
  136. current = (max + min) / 2;
  137. while (current != min && current != max && found == 0)
  138. {
  139. current_string = grub_gettext_getstring_from_position (current);
  140. /* Search by bisection. */
  141. if (grub_strcmp (current_string, orig) < 0)
  142. {
  143. grub_free (current_string);
  144. min = current;
  145. }
  146. else if (grub_strcmp (current_string, orig) > 0)
  147. {
  148. grub_free (current_string);
  149. max = current;
  150. }
  151. else if (grub_strcmp (current_string, orig) == 0)
  152. {
  153. grub_free (current_string);
  154. found = 1;
  155. }
  156. current = (max + min) / 2;
  157. }
  158. ret = found ? grub_gettext_gettranslation_from_position (current) : orig;
  159. if (found)
  160. {
  161. cur = grub_zalloc (sizeof (*cur));
  162. if (cur)
  163. {
  164. cur->name = grub_strdup (orig);
  165. if (cur->name)
  166. {
  167. cur->translated = ret;
  168. grub_list_push (GRUB_AS_LIST_P (&grub_gettext_msg_list),
  169. GRUB_AS_LIST (cur));
  170. }
  171. }
  172. else
  173. grub_errno = GRUB_ERR_NONE;
  174. }
  175. grub_error_pop ();
  176. return ret;
  177. }
  178. /* This is similar to grub_gzfile_open. */
  179. static grub_file_t
  180. grub_mofile_open (const char *filename)
  181. {
  182. int unsigned magic;
  183. int version;
  184. /* Using fd_mo and not another variable because
  185. it's needed for grub_gettext_get_info. */
  186. fd_mo = grub_gzfile_open (filename, 1);
  187. grub_errno = GRUB_ERR_NONE;
  188. if (!fd_mo)
  189. {
  190. grub_dprintf ("gettext", "Cannot read %s\n", filename);
  191. return 0;
  192. }
  193. magic = grub_gettext_get_info (GETTEXT_MAGIC_NUMBER);
  194. if (magic != MO_MAGIC_NUMBER)
  195. {
  196. grub_error (GRUB_ERR_BAD_FILE_TYPE, "mo: invalid mo file: %s",
  197. filename);
  198. grub_file_close (fd_mo);
  199. fd_mo = 0;
  200. return 0;
  201. }
  202. version = grub_gettext_get_info (GETTEXT_FILE_FORMAT);
  203. if (version != 0)
  204. {
  205. grub_error (GRUB_ERR_BAD_FILE_TYPE,
  206. "mo: invalid mo version in file: %s", filename);
  207. fd_mo = 0;
  208. return 0;
  209. }
  210. return fd_mo;
  211. }
  212. static void
  213. grub_gettext_init_ext (const char *lang)
  214. {
  215. char *mo_file;
  216. char *locale_dir;
  217. locale_dir = grub_env_get ("locale_dir");
  218. if (locale_dir == NULL)
  219. {
  220. grub_dprintf ("gettext", "locale_dir variable is not set up.\n");
  221. return;
  222. }
  223. fd_mo = NULL;
  224. /* mo_file e.g.: /boot/grub/locale/ca.mo */
  225. mo_file = grub_xasprintf ("%s/%s.mo", locale_dir, lang);
  226. if (!mo_file)
  227. return;
  228. fd_mo = grub_mofile_open (mo_file);
  229. /* Will try adding .gz as well. */
  230. if (fd_mo == NULL)
  231. {
  232. grub_free (mo_file);
  233. mo_file = grub_xasprintf ("%s.gz", mo_file);
  234. if (!mo_file)
  235. return;
  236. fd_mo = grub_mofile_open (mo_file);
  237. }
  238. if (fd_mo)
  239. {
  240. grub_gettext_offsetoriginal =
  241. grub_gettext_get_info (GETTEXT_OFFSET_ORIGINAL);
  242. grub_gettext_max = grub_gettext_get_info (GETTEXT_NUMBER_OF_STRINGS);
  243. grub_gettext_original = grub_gettext;
  244. grub_gettext = grub_gettext_translate;
  245. }
  246. }
  247. static void
  248. grub_gettext_delete_list (void)
  249. {
  250. struct grub_gettext_msg *item;
  251. while ((item =
  252. grub_list_pop (GRUB_AS_LIST_P (&grub_gettext_msg_list))) != 0)
  253. {
  254. char *original = (char *) ((struct grub_gettext_msg *) item)->name;
  255. grub_free (original);
  256. /* Don't delete the translated message because could be in use. */
  257. }
  258. }
  259. static char *
  260. grub_gettext_env_write_lang (struct grub_env_var *var
  261. __attribute__ ((unused)), const char *val)
  262. {
  263. grub_gettext_init_ext (val);
  264. grub_gettext_delete_list ();
  265. return grub_strdup (val);
  266. }
  267. static grub_err_t
  268. grub_cmd_translate (grub_command_t cmd __attribute__ ((unused)),
  269. int argc, char **args)
  270. {
  271. if (argc != 1)
  272. return grub_error (GRUB_ERR_BAD_ARGUMENT, "text to translate required");
  273. const char *translation;
  274. translation = grub_gettext_translate (args[0]);
  275. grub_printf ("%s\n", translation);
  276. return 0;
  277. }
  278. GRUB_MOD_INIT (gettext)
  279. {
  280. (void) mod; /* To stop warning. */
  281. const char *lang;
  282. lang = grub_env_get ("lang");
  283. grub_gettext_init_ext (lang);
  284. grub_register_command_p1 ("gettext", grub_cmd_translate,
  285. N_("STRING"),
  286. N_("Translates the string with the current settings."));
  287. /* Reload .mo file information if lang changes. */
  288. grub_register_variable_hook ("lang", NULL, grub_gettext_env_write_lang);
  289. /* Preserve hooks after context changes. */
  290. grub_env_export ("lang");
  291. }
  292. GRUB_MOD_FINI (gettext)
  293. {
  294. if (fd_mo != 0)
  295. grub_file_close (fd_mo);
  296. grub_gettext_delete_list ();
  297. grub_gettext = grub_gettext_original;
  298. }