aboutsummaryrefslogtreecommitdiffstats
path: root/grub-core/gettext/gettext.c
diff options
context:
space:
mode:
Diffstat (limited to 'grub-core/gettext/gettext.c')
-rw-r--r--grub-core/gettext/gettext.c400
1 files changed, 400 insertions, 0 deletions
diff --git a/grub-core/gettext/gettext.c b/grub-core/gettext/gettext.c
new file mode 100644
index 0000000..cca8b90
--- /dev/null
+++ b/grub-core/gettext/gettext.c
@@ -0,0 +1,400 @@
+/* gettext.c - gettext module */
+/*
+ * GRUB -- GRand Unified Bootloader
+ * Copyright (C) 2009 Free Software Foundation, Inc.
+ *
+ * GRUB is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GRUB is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with GRUB. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <grub/list.h>
+#include <grub/types.h>
+#include <grub/misc.h>
+#include <grub/mm.h>
+#include <grub/err.h>
+#include <grub/dl.h>
+#include <grub/normal.h>
+#include <grub/file.h>
+#include <grub/kernel.h>
+#include <grub/i18n.h>
+
+GRUB_MOD_LICENSE ("GPLv3+");
+
+/*
+ .mo file information from:
+ http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html .
+*/
+
+
+static grub_file_t fd_mo;
+
+static int grub_gettext_offsetoriginal;
+static int grub_gettext_max;
+
+static const char *(*grub_gettext_original) (const char *s);
+
+struct grub_gettext_msg
+{
+ struct grub_gettext_msg *next;
+ const char *name;
+
+ const char *translated;
+};
+
+static struct grub_gettext_msg *grub_gettext_msg_list = NULL;
+
+#define GETTEXT_MAGIC_NUMBER 0
+#define GETTEXT_FILE_FORMAT 4
+#define GETTEXT_NUMBER_OF_STRINGS 8
+#define GETTEXT_OFFSET_ORIGINAL 12
+#define GETTEXT_OFFSET_TRANSLATION 16
+
+#define MO_MAGIC_NUMBER 0x950412de
+
+static grub_ssize_t
+grub_gettext_pread (grub_file_t file, void *buf, grub_size_t len,
+ grub_off_t offset)
+{
+ if (grub_file_seek (file, offset) == (grub_off_t) - 1)
+ {
+ return -1;
+ }
+ return grub_file_read (file, buf, len);
+}
+
+static grub_uint32_t
+grub_gettext_get_info (int offset)
+{
+ grub_uint32_t value;
+
+ grub_gettext_pread (fd_mo, (char *) &value, 4, offset);
+
+ value = grub_cpu_to_le32 (value);
+ return value;
+}
+
+static void
+grub_gettext_getstring_from_offset (grub_uint32_t offset,
+ grub_uint32_t length, char *translation)
+{
+ grub_gettext_pread (fd_mo, translation, length, offset);
+ translation[length] = '\0';
+}
+
+static const char *
+grub_gettext_gettranslation_from_position (int position)
+{
+ int offsettranslation;
+ int internal_position;
+ grub_uint32_t length, offset;
+ char *translation;
+
+ offsettranslation = grub_gettext_get_info (GETTEXT_OFFSET_TRANSLATION);
+
+ internal_position = offsettranslation + position * 8;
+
+ grub_gettext_pread (fd_mo, (char *) &length, 4, internal_position);
+ length = grub_cpu_to_le32 (length);
+
+ grub_gettext_pread (fd_mo, (char *) &offset, 4, internal_position + 4);
+ offset = grub_cpu_to_le32 (offset);
+
+ translation = grub_malloc (length + 1);
+ grub_gettext_getstring_from_offset (offset, length, translation);
+
+ return translation;
+}
+
+static char *
+grub_gettext_getstring_from_position (int position)
+{
+ int internal_position;
+ int length, offset;
+ char *original;
+
+ /* Get position for string i. */
+ internal_position = grub_gettext_offsetoriginal + (position * 8);
+
+ /* Get the length of the string i. */
+ grub_gettext_pread (fd_mo, (char *) &length, 4, internal_position);
+
+ /* Get the offset of the string i. */
+ grub_gettext_pread (fd_mo, (char *) &offset, 4, internal_position + 4);
+
+ /* Get the string i. */
+ original = grub_malloc (length + 1);
+ grub_gettext_getstring_from_offset (offset, length, original);
+
+ return original;
+}
+
+static const char *
+grub_gettext_translate (const char *orig)
+{
+ char *current_string;
+ const char *ret;
+
+ int min, max, current;
+ int found = 0;
+
+ struct grub_gettext_msg *cur;
+
+ /* Make sure we can use grub_gettext_translate for error messages. Push
+ active error message to error stack and reset error message. */
+ grub_error_push ();
+
+ cur = grub_named_list_find (GRUB_AS_NAMED_LIST (grub_gettext_msg_list),
+ orig);
+
+ if (cur)
+ {
+ grub_error_pop ();
+ return cur->translated;
+ }
+
+ if (fd_mo == 0)
+ {
+ grub_error_pop ();
+ return orig;
+ }
+
+ min = 0;
+ max = grub_gettext_max;
+
+ current = (max + min) / 2;
+
+ while (current != min && current != max && found == 0)
+ {
+ current_string = grub_gettext_getstring_from_position (current);
+
+ /* Search by bisection. */
+ if (grub_strcmp (current_string, orig) < 0)
+ {
+ grub_free (current_string);
+ min = current;
+ }
+ else if (grub_strcmp (current_string, orig) > 0)
+ {
+ grub_free (current_string);
+ max = current;
+ }
+ else if (grub_strcmp (current_string, orig) == 0)
+ {
+ grub_free (current_string);
+ found = 1;
+ }
+ current = (max + min) / 2;
+ }
+
+ ret = found ? grub_gettext_gettranslation_from_position (current) : orig;
+
+ if (found)
+ {
+ cur = grub_zalloc (sizeof (*cur));
+
+ if (cur)
+ {
+ cur->name = grub_strdup (orig);
+ if (cur->name)
+ {
+ cur->translated = ret;
+ grub_list_push (GRUB_AS_LIST_P (&grub_gettext_msg_list),
+ GRUB_AS_LIST (cur));
+ }
+ }
+ else
+ grub_errno = GRUB_ERR_NONE;
+ }
+
+ grub_error_pop ();
+ return ret;
+}
+
+/* This is similar to grub_file_open. */
+static grub_file_t
+grub_mofile_open (const char *filename)
+{
+ int unsigned magic;
+ int version;
+
+ /* Using fd_mo and not another variable because
+ it's needed for grub_gettext_get_info. */
+
+ fd_mo = grub_file_open (filename);
+ grub_errno = GRUB_ERR_NONE;
+
+ if (!fd_mo)
+ {
+ grub_dprintf ("gettext", "Cannot read %s\n", filename);
+ return 0;
+ }
+
+ magic = grub_gettext_get_info (GETTEXT_MAGIC_NUMBER);
+
+ if (magic != MO_MAGIC_NUMBER)
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE, "mo: invalid mo file: %s",
+ filename);
+ grub_file_close (fd_mo);
+ fd_mo = 0;
+ return 0;
+ }
+
+ version = grub_gettext_get_info (GETTEXT_FILE_FORMAT);
+
+ if (version != 0)
+ {
+ grub_error (GRUB_ERR_BAD_FILE_TYPE,
+ "mo: invalid mo version in file: %s", filename);
+ fd_mo = 0;
+ return 0;
+ }
+
+ return fd_mo;
+}
+
+/* Returning grub_file_t would be more natural, but grub_mofile_open assigns
+ to fd_mo anyway ... */
+static void
+grub_mofile_open_lang (const char *locale_dir, const char *locale)
+{
+ char *mo_file;
+
+ /* mo_file e.g.: /boot/grub/locale/ca.mo */
+
+ mo_file = grub_xasprintf ("%s/%s.mo", locale_dir, locale);
+ if (!mo_file)
+ return;
+
+ fd_mo = grub_mofile_open (mo_file);
+
+ /* Will try adding .gz as well. */
+ if (fd_mo == NULL)
+ {
+ char *mo_file_old;
+ mo_file_old = mo_file;
+ mo_file = grub_xasprintf ("%s.gz", mo_file);
+ grub_free (mo_file_old);
+ if (!mo_file)
+ return;
+ fd_mo = grub_mofile_open (mo_file);
+ }
+}
+
+static void
+grub_gettext_init_ext (const char *locale)
+{
+ char *locale_dir;
+
+ locale_dir = grub_env_get ("locale_dir");
+ if (locale_dir == NULL)
+ {
+ grub_dprintf ("gettext", "locale_dir variable is not set up.\n");
+ return;
+ }
+
+ fd_mo = NULL;
+
+ grub_mofile_open_lang (locale_dir, locale);
+
+ /* ll_CC didn't work, so try ll. */
+ if (fd_mo == NULL)
+ {
+ char *lang = grub_strdup (locale);
+ char *underscore = grub_strchr (lang, '_');
+
+ if (underscore)
+ {
+ *underscore = '\0';
+ grub_mofile_open_lang (locale_dir, lang);
+ }
+
+ grub_free (lang);
+ }
+
+ if (fd_mo)
+ {
+ grub_gettext_offsetoriginal =
+ grub_gettext_get_info (GETTEXT_OFFSET_ORIGINAL);
+ grub_gettext_max = grub_gettext_get_info (GETTEXT_NUMBER_OF_STRINGS);
+
+ grub_gettext_original = grub_gettext;
+ grub_gettext = grub_gettext_translate;
+ }
+}
+
+static void
+grub_gettext_delete_list (void)
+{
+ while (grub_gettext_msg_list)
+ {
+ grub_free ((char *) grub_gettext_msg_list->name);
+ grub_gettext_msg_list = grub_gettext_msg_list->next;
+ /* Don't delete the translated message because could be in use. */
+ }
+}
+
+static char *
+grub_gettext_env_write_lang (struct grub_env_var *var
+ __attribute__ ((unused)), const char *val)
+{
+ grub_gettext_init_ext (val);
+
+ grub_gettext_delete_list ();
+
+ return grub_strdup (val);
+}
+
+static grub_err_t
+grub_cmd_translate (grub_command_t cmd __attribute__ ((unused)),
+ int argc, char **args)
+{
+ if (argc != 1)
+ return grub_error (GRUB_ERR_BAD_ARGUMENT, "text to translate required");
+
+ const char *translation;
+ translation = grub_gettext_translate (args[0]);
+ grub_printf ("%s\n", translation);
+ return 0;
+}
+
+GRUB_MOD_INIT (gettext)
+{
+ (void) mod; /* To stop warning. */
+
+ const char *lang;
+
+ lang = grub_env_get ("lang");
+
+ grub_gettext_init_ext (lang);
+
+ grub_register_command_p1 ("gettext", grub_cmd_translate,
+ N_("STRING"),
+ N_("Translates the string with the current settings."));
+
+ /* Reload .mo file information if lang changes. */
+ grub_register_variable_hook ("lang", NULL, grub_gettext_env_write_lang);
+
+ /* Preserve hooks after context changes. */
+ grub_env_export ("lang");
+}
+
+GRUB_MOD_FINI (gettext)
+{
+ if (fd_mo != 0)
+ grub_file_close (fd_mo);
+
+ grub_gettext_delete_list ();
+
+ grub_gettext = grub_gettext_original;
+}