/* gettext.c - gettext module */ /* * VAS_EBOOT -- GRand Unified Bootloader * Copyright (C) 2009 Free Software Foundation, Inc. * * VAS_EBOOT 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. * * VAS_EBOOT 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 VAS_EBOOT. If not, see . */ #include #include #include #include #include #include #include #include #include #include VAS_EBOOT_MOD_LICENSE ("GPLv3+"); /* .mo file information from: http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html . */ static const char *(*VasEBoot_gettext_original) (const char *s); struct VasEBoot_gettext_msg { char *name; char *translated; }; struct header { VasEBoot_uint32_t magic; VasEBoot_uint32_t version; VasEBoot_uint32_t number_of_strings; VasEBoot_uint32_t offset_original; VasEBoot_uint32_t offset_translation; }; struct string_descriptor { VasEBoot_uint32_t length; VasEBoot_uint32_t offset; }; struct VasEBoot_gettext_context { VasEBoot_file_t fd_mo; VasEBoot_off_t VasEBoot_gettext_offset_original; VasEBoot_off_t VasEBoot_gettext_offset_translation; VasEBoot_size_t VasEBoot_gettext_max; int VasEBoot_gettext_max_log; struct VasEBoot_gettext_msg *VasEBoot_gettext_msg_list; }; static struct VasEBoot_gettext_context main_context, secondary_context; #define MO_MAGIC_NUMBER 0x950412de static VasEBoot_err_t VasEBoot_gettext_pread (VasEBoot_file_t file, void *buf, VasEBoot_size_t len, VasEBoot_off_t offset) { if (len == 0) return VAS_EBOOT_ERR_NONE; if (VasEBoot_file_seek (file, offset) == (VasEBoot_off_t) - 1) return VasEBoot_errno; if (VasEBoot_file_read (file, buf, len) != (VasEBoot_ssize_t) len) { if (!VasEBoot_errno) VasEBoot_error (VAS_EBOOT_ERR_READ_ERROR, N_("premature end of file")); return VasEBoot_errno; } return VAS_EBOOT_ERR_NONE; } static char * VasEBoot_gettext_getstr_from_position (struct VasEBoot_gettext_context *ctx, VasEBoot_off_t off, VasEBoot_size_t position) { VasEBoot_off_t internal_position; VasEBoot_size_t length; VasEBoot_off_t offset; char *translation; struct string_descriptor desc; VasEBoot_err_t err; VasEBoot_size_t alloc_sz; internal_position = (off + position * sizeof (desc)); err = VasEBoot_gettext_pread (ctx->fd_mo, (char *) &desc, sizeof (desc), internal_position); if (err) return NULL; length = VasEBoot_cpu_to_le32 (desc.length); offset = VasEBoot_cpu_to_le32 (desc.offset); if (VasEBoot_add (length, 1, &alloc_sz)) return NULL; translation = VasEBoot_malloc (alloc_sz); if (!translation) return NULL; err = VasEBoot_gettext_pread (ctx->fd_mo, translation, length, offset); if (err) { VasEBoot_free (translation); return NULL; } translation[length] = '\0'; return translation; } static const char * VasEBoot_gettext_gettranslation_from_position (struct VasEBoot_gettext_context *ctx, VasEBoot_size_t position) { if (!ctx->VasEBoot_gettext_msg_list[position].translated) ctx->VasEBoot_gettext_msg_list[position].translated = VasEBoot_gettext_getstr_from_position (ctx, ctx->VasEBoot_gettext_offset_translation, position); return ctx->VasEBoot_gettext_msg_list[position].translated; } static const char * VasEBoot_gettext_getstring_from_position (struct VasEBoot_gettext_context *ctx, VasEBoot_size_t position) { if (!ctx->VasEBoot_gettext_msg_list[position].name) ctx->VasEBoot_gettext_msg_list[position].name = VasEBoot_gettext_getstr_from_position (ctx, ctx->VasEBoot_gettext_offset_original, position); return ctx->VasEBoot_gettext_msg_list[position].name; } static const char * VasEBoot_gettext_translate_real (struct VasEBoot_gettext_context *ctx, const char *orig) { VasEBoot_size_t current = 0; int i; const char *current_string; static int depth = 0; if (!ctx->VasEBoot_gettext_msg_list || !ctx->fd_mo) return NULL; /* Shouldn't happen. Just a precaution if our own code calls gettext somehow. */ if (depth > 2) return NULL; depth++; /* Make sure we can use VasEBoot_gettext_translate for error messages. Push active error message to error stack and reset error message. */ VasEBoot_error_push (); for (i = ctx->VasEBoot_gettext_max_log; i >= 0; i--) { VasEBoot_size_t test; int cmp; test = current | (1 << i); if (test >= ctx->VasEBoot_gettext_max) continue; current_string = VasEBoot_gettext_getstring_from_position (ctx, test); if (!current_string) { VasEBoot_errno = VAS_EBOOT_ERR_NONE; VasEBoot_error_pop (); depth--; return NULL; } /* Search by bisection. */ cmp = VasEBoot_strcmp (current_string, orig); if (cmp <= 0) current = test; if (cmp == 0) { const char *ret = 0; ret = VasEBoot_gettext_gettranslation_from_position (ctx, current); if (!ret) { VasEBoot_errno = VAS_EBOOT_ERR_NONE; VasEBoot_error_pop (); depth--; return NULL; } VasEBoot_error_pop (); depth--; return ret; } } if (current == 0 && ctx->VasEBoot_gettext_max != 0) { current_string = VasEBoot_gettext_getstring_from_position (ctx, 0); if (!current_string) { VasEBoot_errno = VAS_EBOOT_ERR_NONE; VasEBoot_error_pop (); depth--; return NULL; } if (VasEBoot_strcmp (current_string, orig) == 0) { const char *ret = 0; ret = VasEBoot_gettext_gettranslation_from_position (ctx, current); if (!ret) { VasEBoot_errno = VAS_EBOOT_ERR_NONE; VasEBoot_error_pop (); depth--; return NULL; } VasEBoot_error_pop (); depth--; return ret; } } VasEBoot_error_pop (); depth--; return NULL; } static const char * VasEBoot_gettext_translate (const char *orig) { const char *ret; if (orig[0] == 0) return orig; ret = VasEBoot_gettext_translate_real (&main_context, orig); if (ret) return ret; ret = VasEBoot_gettext_translate_real (&secondary_context, orig); if (ret) return ret; return orig; } static void VasEBoot_gettext_delete_list (struct VasEBoot_gettext_context *ctx) { struct VasEBoot_gettext_msg *l = ctx->VasEBoot_gettext_msg_list; VasEBoot_size_t i; if (!l) return; ctx->VasEBoot_gettext_msg_list = 0; for (i = 0; i < ctx->VasEBoot_gettext_max; i++) VasEBoot_free (l[i].name); /* Don't delete the translated message because could be in use. */ VasEBoot_free (l); if (ctx->fd_mo) VasEBoot_file_close (ctx->fd_mo); ctx->fd_mo = 0; VasEBoot_memset (ctx, 0, sizeof (*ctx)); } /* This is similar to VasEBoot_file_open. */ static VasEBoot_err_t VasEBoot_mofile_open (struct VasEBoot_gettext_context *ctx, const char *filename) { struct header head; VasEBoot_err_t err; VasEBoot_file_t fd; /* Using fd_mo and not another variable because it's needed for VasEBoot_gettext_get_info. */ fd = VasEBoot_file_open (filename, VAS_EBOOT_FILE_TYPE_GETTEXT_CATALOG); if (!fd) return VasEBoot_errno; err = VasEBoot_gettext_pread (fd, &head, sizeof (head), 0); if (err) { VasEBoot_file_close (fd); return err; } if (head.magic != VasEBoot_cpu_to_le32_compile_time (MO_MAGIC_NUMBER)) { VasEBoot_file_close (fd); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FILE_TYPE, "mo: invalid mo magic in file: %s", filename); } if (head.version != 0) { VasEBoot_file_close (fd); return VasEBoot_error (VAS_EBOOT_ERR_BAD_FILE_TYPE, "mo: invalid mo version in file: %s", filename); } ctx->VasEBoot_gettext_offset_original = VasEBoot_le_to_cpu32 (head.offset_original); ctx->VasEBoot_gettext_offset_translation = VasEBoot_le_to_cpu32 (head.offset_translation); ctx->VasEBoot_gettext_max = VasEBoot_le_to_cpu32 (head.number_of_strings); for (ctx->VasEBoot_gettext_max_log = 0; ctx->VasEBoot_gettext_max >> ctx->VasEBoot_gettext_max_log; ctx->VasEBoot_gettext_max_log++); ctx->VasEBoot_gettext_msg_list = VasEBoot_calloc (ctx->VasEBoot_gettext_max, sizeof (ctx->VasEBoot_gettext_msg_list[0])); if (!ctx->VasEBoot_gettext_msg_list) { VasEBoot_file_close (fd); return VasEBoot_errno; } ctx->fd_mo = fd; if (VasEBoot_gettext != VasEBoot_gettext_translate) { VasEBoot_gettext_original = VasEBoot_gettext; VasEBoot_gettext = VasEBoot_gettext_translate; } return 0; } /* Returning VasEBoot_file_t would be more natural, but VasEBoot_mofile_open assigns to fd_mo anyway ... */ static VasEBoot_err_t VasEBoot_mofile_open_lang (struct VasEBoot_gettext_context *ctx, const char *part1, const char *part2, const char *locale) { char *mo_file; VasEBoot_err_t err; /* mo_file e.g.: /boot/VasEBoot/locale/ca.mo */ mo_file = VasEBoot_xasprintf ("%s%s/%s.mo", part1, part2, locale); if (!mo_file) return VasEBoot_errno; err = VasEBoot_mofile_open (ctx, mo_file); VasEBoot_free (mo_file); /* Will try adding .gz as well. */ if (err) { VasEBoot_errno = VAS_EBOOT_ERR_NONE; mo_file = VasEBoot_xasprintf ("%s%s/%s.mo.gz", part1, part2, locale); if (!mo_file) return VasEBoot_errno; err = VasEBoot_mofile_open (ctx, mo_file); VasEBoot_free (mo_file); } /* Will try adding .gmo as well. */ if (err) { VasEBoot_errno = VAS_EBOOT_ERR_NONE; mo_file = VasEBoot_xasprintf ("%s%s/%s.gmo", part1, part2, locale); if (!mo_file) return VasEBoot_errno; err = VasEBoot_mofile_open (ctx, mo_file); VasEBoot_free (mo_file); } return err; } static VasEBoot_err_t VasEBoot_gettext_init_ext (struct VasEBoot_gettext_context *ctx, const char *locale, const char *locale_dir, const char *prefix) { const char *part1, *part2; VasEBoot_err_t err; VasEBoot_gettext_delete_list (ctx); if (!locale || locale[0] == 0) return 0; part1 = locale_dir; part2 = ""; if (!part1 || part1[0] == 0) { part1 = prefix; part2 = "/locale"; } if (!part1 || part1[0] == 0) return 0; err = VasEBoot_mofile_open_lang (ctx, part1, part2, locale); /* ll_CC didn't work, so try ll. */ if (err) { char *lang = VasEBoot_strdup (locale); char *underscore = lang ? VasEBoot_strchr (lang, '_') : 0; if (underscore) { *underscore = '\0'; VasEBoot_errno = VAS_EBOOT_ERR_NONE; err = VasEBoot_mofile_open_lang (ctx, part1, part2, lang); } VasEBoot_free (lang); } if (locale[0] == 'e' && locale[1] == 'n' && (locale[2] == '\0' || locale[2] == '_')) VasEBoot_errno = err = VAS_EBOOT_ERR_NONE; return err; } static char * VasEBoot_gettext_env_write_lang (struct VasEBoot_env_var *var __attribute__ ((unused)), const char *val) { VasEBoot_err_t err; err = VasEBoot_gettext_init_ext (&main_context, val, VasEBoot_env_get ("locale_dir"), VasEBoot_env_get ("prefix")); if (err) VasEBoot_print_error (); err = VasEBoot_gettext_init_ext (&secondary_context, val, VasEBoot_env_get ("secondary_locale_dir"), 0); if (err) VasEBoot_print_error (); return VasEBoot_strdup (val); } void VasEBoot_gettext_reread_prefix (const char *val) { VasEBoot_err_t err; err = VasEBoot_gettext_init_ext (&main_context, VasEBoot_env_get ("lang"), VasEBoot_env_get ("locale_dir"), val); if (err) VasEBoot_print_error (); } static char * read_main (struct VasEBoot_env_var *var __attribute__ ((unused)), const char *val) { VasEBoot_err_t err; err = VasEBoot_gettext_init_ext (&main_context, VasEBoot_env_get ("lang"), val, VasEBoot_env_get ("prefix")); if (err) VasEBoot_print_error (); return VasEBoot_strdup (val); } static char * read_secondary (struct VasEBoot_env_var *var __attribute__ ((unused)), const char *val) { VasEBoot_err_t err; err = VasEBoot_gettext_init_ext (&secondary_context, VasEBoot_env_get ("lang"), val, 0); if (err) VasEBoot_print_error (); return VasEBoot_strdup (val); } static VasEBoot_err_t VasEBoot_cmd_translate (VasEBoot_command_t cmd __attribute__ ((unused)), int argc, char **args) { if (argc != 1) return VasEBoot_error (VAS_EBOOT_ERR_BAD_ARGUMENT, N_("one argument expected")); const char *translation; translation = VasEBoot_gettext_translate (args[0]); VasEBoot_printf ("%s\n", translation); return 0; } static VasEBoot_command_t cmd; VAS_EBOOT_MOD_INIT (gettext) { const char *lang; VasEBoot_err_t err; lang = VasEBoot_env_get ("lang"); err = VasEBoot_gettext_init_ext (&main_context, lang, VasEBoot_env_get ("locale_dir"), VasEBoot_env_get ("prefix")); if (err) VasEBoot_print_error (); err = VasEBoot_gettext_init_ext (&secondary_context, lang, VasEBoot_env_get ("secondary_locale_dir"), 0); if (err) VasEBoot_print_error (); VasEBoot_register_variable_hook ("locale_dir", NULL, read_main); VasEBoot_register_variable_hook ("secondary_locale_dir", NULL, read_secondary); cmd = VasEBoot_register_command_p1 ("gettext", VasEBoot_cmd_translate, N_("STRING"), /* * TRANSLATORS: It refers to passing the string through gettext. * So it's "translate" in the same meaning as in what you're * doing now. */ N_("Translates the string with the current settings.")); /* Reload .mo file information if lang changes. */ VasEBoot_register_variable_hook ("lang", NULL, VasEBoot_gettext_env_write_lang); /* Preserve hooks after context changes. */ VasEBoot_env_export ("lang"); VasEBoot_env_export ("locale_dir"); VasEBoot_env_export ("secondary_locale_dir"); } VAS_EBOOT_MOD_FINI (gettext) { VasEBoot_register_variable_hook ("locale_dir", NULL, NULL); VasEBoot_register_variable_hook ("secondary_locale_dir", NULL, NULL); VasEBoot_register_variable_hook ("lang", NULL, NULL); VasEBoot_unregister_command (cmd); VasEBoot_gettext_delete_list (&main_context); VasEBoot_gettext_delete_list (&secondary_context); VasEBoot_gettext = VasEBoot_gettext_original; }