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); }