]> git.ipfire.org Git - thirdparty/grub.git/commitdiff
* grub-core/gettext/gettext.c: Mostly rewritten to avoid using
authorVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sat, 4 Feb 2012 12:35:17 +0000 (13:35 +0100)
committerVladimir 'phcoder' Serbinenko <phcoder@gmail.com>
Sat, 4 Feb 2012 12:35:17 +0000 (13:35 +0100)
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
grub-core/gettext/gettext.c

index 3e5b954dddd2aee710882976a26202adba20575d..864aa727106126293c84fb09732799b1f4c45cf2 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -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.
index 2ff9d079bbea60c2c7d30720fb1def36e7b7e8ea..128bb973a8d8bd4b0c2d1484517d408c174761ee 100644 (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)
-{
-  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)
+static char *
+grub_gettext_getstr_from_position (grub_off_t off,
+                                  grub_size_t 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;
-
-  /* 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);
+  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;
+}
 
-  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);
-
-      /* Search by bisection.  */
-      if (grub_strcmp (current_string, orig) < 0)
-       {
-         grub_free (current_string);
-         min = current;
-       }
-      else if (grub_strcmp (current_string, orig) > 0)
+      if (!current_string)
        {
-         grub_free (current_string);
-         max = current;
+         grub_errno = GRUB_ERR_NONE;
+         grub_error_pop ();
+         depth--;
+         return orig;
        }
-      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)
+      /* Search by bisection.  */
+      cmp = grub_strcmp (current_string, orig);
+      if (cmp <= 0)
+       current = test;
+      if (cmp == 0)
        {
-         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;
+  depth--;
+  return orig;    
+}
+
+static void
+grub_gettext_delete_list (void)
+{
+  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_file_t
+static grub_err_t
 grub_mofile_open (const char *filename)
 {
-  int unsigned magic;
-  int version;
+  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_mo = grub_file_open (filename);
-  grub_errno = GRUB_ERR_NONE;
+  fd = grub_file_open (filename);
+
+  if (!fd)
+    return grub_errno;
 
-  if (!fd_mo)
+  err = grub_gettext_pread (fd, &head, sizeof (head), 0);
+  if (err)
     {
-      grub_dprintf ("gettext", "Cannot read %s\n", filename);
-      return 0;
+      grub_file_close (fd);
+      return err;
     }
 
-  magic = grub_gettext_get_info (GETTEXT_MAGIC_NUMBER);
+  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 (magic != MO_MAGIC_NUMBER)
+  if (head.version != 0)
     {
-      grub_error (GRUB_ERR_BAD_FILE_TYPE, "mo: invalid mo file: %s",
-                 filename);
-      grub_file_close (fd_mo);
-      fd_mo = 0;
-      return 0;
+      grub_file_close (fd);
+      return grub_error (GRUB_ERR_BAD_FILE_TYPE,
+                        "mo: invalid mo version in file: %s", filename);
     }
 
-  version = grub_gettext_get_info (GETTEXT_FILE_FORMAT);
+  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;
 
-  if (version != 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_error (GRUB_ERR_BAD_FILE_TYPE,
-                 "mo: invalid mo version in file: %s", filename);
-      fd_mo = 0;
-      return 0;
+      grub_file_close (fd);
+      return grub_errno;
     }
-
-  return fd_mo;
+  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 void
+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;
+    return grub_errno;
 
-  fd_mo = grub_mofile_open (mo_file);
+  err = grub_mofile_open (mo_file);
 
   /* Will try adding .gz as well.  */
-  if (fd_mo == NULL)
+  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;
-      fd_mo = grub_mofile_open (mo_file);
+       return grub_errno;
+      err = grub_mofile_open (mo_file);
     }
+  return err;
 }
 
-static void
+static grub_err_t
 grub_gettext_init_ext (const char *locale)
 {
   const char *locale_dir;
+  grub_err_t err;
 
   if (!locale)
-    return;
+    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;
+      return 0;
     }
 
-  fd_mo = NULL;
-
-  grub_mofile_open_lang (locale_dir, locale);
+  err = grub_mofile_open_lang (locale_dir, locale);
 
   /* ll_CC didn't work, so try ll.  */
-  if (fd_mo == NULL)
+  if (err)
     {
       char *lang = grub_strdup (locale);
-      char *underscore = grub_strchr (lang, '_');
+      char *underscore = lang ? grub_strchr (lang, '_') : 0;
 
       if (underscore)
        {
          *underscore = '\0';
-         grub_mofile_open_lang (locale_dir, lang);
+         grub_errno = GRUB_ERR_NONE;
+         err = 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.  */
-    }
+  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);
 }