From c39992a436c0dd3c464d67612c6b36083bcab1a7 Mon Sep 17 00:00:00 2001 From: Vladimir 'phcoder' Serbinenko Date: Sat, 4 Feb 2012 13:35:17 +0100 Subject: [PATCH] * grub-core/gettext/gettext.c: Mostly rewritten to avoid using lists (by always binsearching), improve caching (cache strings used for binsearch, not only results), improve maintainability (by using more structured binary search) and correct error handling. --- ChangeLog | 8 + grub-core/gettext/gettext.c | 502 ++++++++++++++++++------------------ 2 files changed, 262 insertions(+), 248 deletions(-) diff --git a/ChangeLog b/ChangeLog index 3e5b954dd..864aa7271 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,11 @@ +2012-02-04 Vladimir Serbinenko + + * grub-core/gettext/gettext.c: Mostly rewritten to avoid using + lists (by always binsearching), improve caching (cache strings + used for binsearch, not only results), improve + maintainability (by using more structured binary search) and correct + error handling. + 2012-02-04 Vladimir Serbinenko * grub-core/script/execute.c (grub_script_return): Fix warning. diff --git a/grub-core/gettext/gettext.c b/grub-core/gettext/gettext.c index 2ff9d079b..128bb973a 100644 --- a/grub-core/gettext/gettext.c +++ b/grub-core/gettext/gettext.c @@ -17,7 +17,6 @@ * along with GRUB. If not, see . */ -#include #include #include #include @@ -38,323 +37,330 @@ GRUB_MOD_LICENSE ("GPLv3+"); static grub_file_t fd_mo; -static int grub_gettext_offsetoriginal; -static int grub_gettext_max; +static grub_off_t grub_gettext_offset_original; +static grub_off_t grub_gettext_offset_translation; +static grub_size_t grub_gettext_max; +static int grub_gettext_max_log; static const char *(*grub_gettext_original) (const char *s); struct grub_gettext_msg { - struct grub_gettext_msg *next; - struct grub_gettext_msg *prev; - const char *name; - - const char *translated; + char *name; + 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 +struct header +{ + grub_uint32_t magic; + grub_uint32_t version; + grub_uint32_t number_of_strings; + grub_uint32_t offset_original; + grub_uint32_t offset_translation; +}; + +struct string_descriptor +{ + grub_uint32_t length; + grub_uint32_t offset; +}; #define MO_MAGIC_NUMBER 0x950412de -static grub_ssize_t +static grub_err_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 grub_errno; + if (grub_file_read (file, buf, len) != (grub_ssize_t) len) { - return -1; + if (!grub_errno) + grub_error (GRUB_ERR_READ_ERROR, N_("premature end of file")); + return grub_errno; } - return grub_file_read (file, buf, len); + return GRUB_ERR_NONE; } -static grub_uint32_t -grub_gettext_get_info (int offset) +static char * +grub_gettext_getstr_from_position (grub_off_t off, + grub_size_t position) { - 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; + grub_off_t internal_position; + grub_size_t length; + grub_off_t offset; char *translation; + struct string_descriptor desc; + grub_err_t err; - offsettranslation = grub_gettext_get_info (GETTEXT_OFFSET_TRANSLATION); + internal_position = (off + position * sizeof (desc)); - 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); + err = grub_gettext_pread (fd_mo, (char *) &desc, + sizeof (desc), internal_position); + if (err) + return NULL; + length = grub_cpu_to_le32 (desc.length); + offset = grub_cpu_to_le32 (desc.offset); translation = grub_malloc (length + 1); - grub_gettext_getstring_from_offset (offset, length, translation); + if (!translation) + return NULL; + + err = grub_gettext_pread (fd_mo, translation, length, offset); + if (err) + { + grub_free (translation); + return NULL; + } + translation[length] = '\0'; return translation; } -static char * -grub_gettext_getstring_from_position (int position) +static const char * +grub_gettext_gettranslation_from_position (grub_size_t position) { - int internal_position; - int length, offset; - char *original; + if (!grub_gettext_msg_list[position].translated) + grub_gettext_msg_list[position].translated + = grub_gettext_getstr_from_position (grub_gettext_offset_translation, + position); + return grub_gettext_msg_list[position].translated; +} - /* 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_getstring_from_position (grub_size_t position) +{ + if (!grub_gettext_msg_list[position].name) + grub_gettext_msg_list[position].name + = grub_gettext_getstr_from_position (grub_gettext_offset_original, + position); + return grub_gettext_msg_list[position].name; } static const char * grub_gettext_translate (const char *orig) { - char *current_string; - const char *ret; + grub_size_t current = 0; + int i; + const char *current_string; + static int depth = 0; - int min, max, current; - int found = 0; + if (!grub_gettext_msg_list || !fd_mo) + return orig; - struct grub_gettext_msg *cur; + /* Shouldn't happen. Just a precaution if our own code + calls gettext somehow. */ + if (depth > 2) + return orig; + depth++; /* 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) + for (i = grub_gettext_max_log; i >= 0; i--) { - grub_error_pop (); - return cur->translated; - } + grub_size_t test; + int cmp; - if (fd_mo == 0) - { - grub_error_pop (); - return orig; - } + test = current | (1 << i); + if (test >= grub_gettext_max) + continue; - min = 0; - max = grub_gettext_max; + current_string = grub_gettext_getstring_from_position (test); - current = (max + min) / 2; - - while (current != min && current != max && found == 0) - { - current_string = grub_gettext_getstring_from_position (current); + if (!current_string) + { + grub_errno = GRUB_ERR_NONE; + grub_error_pop (); + depth--; + return orig; + } /* Search by bisection. */ - if (grub_strcmp (current_string, orig) < 0) + cmp = grub_strcmp (current_string, orig); + if (cmp <= 0) + current = test; + if (cmp == 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) + const char *ret = 0; + ret = grub_gettext_gettranslation_from_position (current); + if (!ret) { - cur->translated = ret; - grub_list_push (GRUB_AS_LIST_P (&grub_gettext_msg_list), - GRUB_AS_LIST (cur)); + grub_errno = GRUB_ERR_NONE; + grub_error_pop (); + depth--; + return orig; } + grub_error_pop (); + depth--; + return ret; } - 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) -{ - const char *locale_dir; - - if (!locale) - return; - - 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; - } + depth--; + return orig; } static void grub_gettext_delete_list (void) { - while (grub_gettext_msg_list) + struct grub_gettext_msg *l = grub_gettext_msg_list; + grub_size_t i; + + if (!l) + return; + grub_gettext_msg_list = 0; + for (i = 0; i < grub_gettext_max; i++) + grub_free (l[i].name); + /* Don't delete the translated message because could be in use. */ + grub_free (l); +} + +/* This is similar to grub_file_open. */ +static grub_err_t +grub_mofile_open (const char *filename) +{ + struct header head; + grub_err_t err; + grub_file_t fd; + + /* Using fd_mo and not another variable because + it's needed for grub_gettext_get_info. */ + + fd = grub_file_open (filename); + + if (!fd) + return grub_errno; + + err = grub_gettext_pread (fd, &head, sizeof (head), 0); + if (err) { - 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. */ + grub_file_close (fd); + return err; } + + if (head.magic != grub_cpu_to_le32_compile_time (MO_MAGIC_NUMBER)) + { + grub_file_close (fd); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "mo: invalid mo magic in file: %s", filename); + } + + if (head.version != 0) + { + grub_file_close (fd); + return grub_error (GRUB_ERR_BAD_FILE_TYPE, + "mo: invalid mo version in file: %s", filename); + } + + grub_gettext_offset_original = grub_le_to_cpu32 (head.offset_original); + grub_gettext_offset_translation = grub_le_to_cpu32 (head.offset_translation); + grub_gettext_max = grub_le_to_cpu32 (head.number_of_strings); + for (grub_gettext_max_log = 0; grub_gettext_max >> grub_gettext_max_log; + grub_gettext_max_log++); + if (fd_mo) + grub_file_close (fd_mo); + fd_mo = 0; + + grub_gettext_delete_list (); + grub_gettext_msg_list = grub_zalloc (grub_gettext_max + * sizeof (grub_gettext_msg_list[0])); + if (!grub_gettext_msg_list) + { + grub_file_close (fd); + return grub_errno; + } + fd_mo = fd; + if (grub_gettext != grub_gettext_translate) + { + grub_gettext_original = grub_gettext; + grub_gettext = grub_gettext_translate; + } + return 0; +} + +/* Returning grub_file_t would be more natural, but grub_mofile_open assigns + to fd_mo anyway ... */ +static grub_err_t +grub_mofile_open_lang (const char *locale_dir, const char *locale) +{ + char *mo_file; + grub_err_t err; + + /* mo_file e.g.: /boot/grub/locale/ca.mo */ + + mo_file = grub_xasprintf ("%s/%s.mo", locale_dir, locale); + if (!mo_file) + return grub_errno; + + err = grub_mofile_open (mo_file); + + /* Will try adding .gz as well. */ + if (err) + { + char *mo_file_old; + grub_errno = GRUB_ERR_NONE; + mo_file_old = mo_file; + mo_file = grub_xasprintf ("%s.gz", mo_file); + grub_free (mo_file_old); + if (!mo_file) + return grub_errno; + err = grub_mofile_open (mo_file); + } + return err; +} + +static grub_err_t +grub_gettext_init_ext (const char *locale) +{ + const char *locale_dir; + grub_err_t err; + + if (!locale) + return 0; + + locale_dir = grub_env_get ("locale_dir"); + if (locale_dir == NULL) + { + grub_dprintf ("gettext", "locale_dir variable is not set up.\n"); + return 0; + } + + err = grub_mofile_open_lang (locale_dir, locale); + + /* ll_CC didn't work, so try ll. */ + if (err) + { + char *lang = grub_strdup (locale); + char *underscore = lang ? grub_strchr (lang, '_') : 0; + + if (underscore) + { + *underscore = '\0'; + grub_errno = GRUB_ERR_NONE; + err = grub_mofile_open_lang (locale_dir, lang); + } + + grub_free (lang); + } + return err; } 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 (); + grub_err_t err; + err = grub_gettext_init_ext (val); + if (err) + { + grub_print_error (); + return NULL; + } return grub_strdup (val); }