* 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.
This commit is contained in:
Vladimir 'phcoder' Serbinenko 2012-02-04 13:35:17 +01:00
parent 1e5ec32f2d
commit c39992a436
2 changed files with 262 additions and 248 deletions

View file

@ -1,3 +1,11 @@
2012-02-04 Vladimir Serbinenko <phcoder@gmail.com>
* 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 <phcoder@gmail.com>
* grub-core/script/execute.c (grub_script_return): Fix warning.

View file

@ -17,7 +17,6 @@
* 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>
@ -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);
}