gettext.c 13 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
/* gettext.c - gettext module */
/*
 *  GRUB  --  GRand Unified Bootloader
 *  Copyright (C) 2009 Free Software Foundation, Inc.
 *
 *  GRUB 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.
 *
 *  GRUB 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 GRUB.  If not, see <http://www.gnu.org/licenses/>.
 */

#include <grub/types.h>
#include <grub/misc.h>
#include <grub/mm.h>
#include <grub/err.h>
#include <grub/dl.h>
#include <grub/normal.h>
#include <grub/file.h>
#include <grub/kernel.h>
28
#include <grub/i18n.h>
29

30 31
GRUB_MOD_LICENSE ("GPLv3+");

32 33
/*
   .mo file information from:
34 35 36
   http://www.gnu.org/software/autoconf/manual/gettext/MO-Files.html .
*/

37
static const char *(*grub_gettext_original) (const char *s);
38

39 40
struct grub_gettext_msg
{
41 42
  char *name;
  char *translated;
43 44
};

45 46 47 48 49 50 51 52 53 54 55 56 57 58
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;
};
59

60 61 62 63 64 65 66 67 68 69
struct grub_gettext_context
{
  grub_file_t fd_mo;
  grub_off_t grub_gettext_offset_original;
  grub_off_t grub_gettext_offset_translation;
  grub_size_t grub_gettext_max;
  int grub_gettext_max_log;
  struct grub_gettext_msg *grub_gettext_msg_list;
};

70 71
static struct grub_gettext_context main_context, secondary_context;

72
#define MO_MAGIC_NUMBER 		0x950412de
73

74
static grub_err_t
75 76 77
grub_gettext_pread (grub_file_t file, void *buf, grub_size_t len,
		    grub_off_t offset)
{
78 79
  if (len == 0)
    return GRUB_ERR_NONE;
80
  if (grub_file_seek (file, offset) == (grub_off_t) - 1)
81 82
    return grub_errno;
  if (grub_file_read (file, buf, len) != (grub_ssize_t) len)
83
    {
84 85 86
      if (!grub_errno)
	grub_error (GRUB_ERR_READ_ERROR, N_("premature end of file"));
      return grub_errno;
87
    }
88
  return GRUB_ERR_NONE;
89 90
}

91
static char *
92 93
grub_gettext_getstr_from_position (struct grub_gettext_context *ctx,
				   grub_off_t off,
94
				   grub_size_t position)
95
{
96 97 98
  grub_off_t internal_position;
  grub_size_t length;
  grub_off_t offset;
99
  char *translation;
100 101
  struct string_descriptor desc;
  grub_err_t err;
102

103
  internal_position = (off + position * sizeof (desc));
104

105
  err = grub_gettext_pread (ctx->fd_mo, (char *) &desc,
106 107 108 109 110
			    sizeof (desc), internal_position);
  if (err)
    return NULL;
  length = grub_cpu_to_le32 (desc.length);
  offset = grub_cpu_to_le32 (desc.offset);
111

112
  translation = grub_malloc (length + 1);
113 114 115
  if (!translation)
    return NULL;

116
  err = grub_gettext_pread (ctx->fd_mo, translation, length, offset);
117 118 119 120 121 122
  if (err)
    {
      grub_free (translation);
      return NULL;
    }
  translation[length] = '\0';
123 124 125 126

  return translation;
}

127
static const char *
128 129
grub_gettext_gettranslation_from_position (struct grub_gettext_context *ctx,
					   grub_size_t position)
130
{
131 132 133 134
  if (!ctx->grub_gettext_msg_list[position].translated)
    ctx->grub_gettext_msg_list[position].translated
      = grub_gettext_getstr_from_position (ctx,
					   ctx->grub_gettext_offset_translation,
135
					   position);
136
  return ctx->grub_gettext_msg_list[position].translated;
137
}
138

139
static const char *
140 141
grub_gettext_getstring_from_position (struct grub_gettext_context *ctx,
				      grub_size_t position)
142
{
143 144 145 146
  if (!ctx->grub_gettext_msg_list[position].name)
    ctx->grub_gettext_msg_list[position].name
      = grub_gettext_getstr_from_position (ctx,
					   ctx->grub_gettext_offset_original,
147
					   position);
148
  return ctx->grub_gettext_msg_list[position].name;
149 150
}

151
static const char *
152 153
grub_gettext_translate_real (struct grub_gettext_context *ctx,
			     const char *orig)
154
{
155 156 157 158
  grub_size_t current = 0;
  int i;
  const char *current_string;
  static int depth = 0;
159

160 161
  if (!ctx->grub_gettext_msg_list || !ctx->fd_mo)
    return NULL;
162

163 164 165
  /* Shouldn't happen. Just a precaution if our own code
     calls gettext somehow.  */
  if (depth > 2)
166
    return NULL;
167
  depth++;
168

169 170 171 172
  /* 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 ();

173
  for (i = ctx->grub_gettext_max_log; i >= 0; i--)
174
    {
175 176
      grub_size_t test;
      int cmp;
177

178
      test = current | (1 << i);
179
      if (test >= ctx->grub_gettext_max)
180
	continue;
181

182
      current_string = grub_gettext_getstring_from_position (ctx, test);
183

184
      if (!current_string)
185
	{
186 187 188
	  grub_errno = GRUB_ERR_NONE;
	  grub_error_pop ();
	  depth--;
189
	  return NULL;
190
	}
191

192 193 194 195 196
      /* Search by bisection.  */
      cmp = grub_strcmp (current_string, orig);
      if (cmp <= 0)
	current = test;
      if (cmp == 0)
197
	{
198
	  const char *ret = 0;
199
	  ret = grub_gettext_gettranslation_from_position (ctx, current);
200
	  if (!ret)
201
	    {
202 203 204
	      grub_errno = GRUB_ERR_NONE;
	      grub_error_pop ();
	      depth--;
205
	      return NULL;
206
	    }
207 208 209
	  grub_error_pop ();
	  depth--;
	  return ret;      
210 211 212
	}
    }

213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241
  if (current == 0 && ctx->grub_gettext_max != 0)
    {
      current_string = grub_gettext_getstring_from_position (ctx, 0);

      if (!current_string)
	{
	  grub_errno = GRUB_ERR_NONE;
	  grub_error_pop ();
	  depth--;
	  return NULL;
	}

      if (grub_strcmp (current_string, orig) == 0)
	{
	  const char *ret = 0;
	  ret = grub_gettext_gettranslation_from_position (ctx, current);
	  if (!ret)
	    {
	      grub_errno = GRUB_ERR_NONE;
	      grub_error_pop ();
	      depth--;
	      return NULL;
	    }
	  grub_error_pop ();
	  depth--;
	  return ret;      
	}
    }

242
  grub_error_pop ();
243
  depth--;
244 245 246 247 248 249 250
  return NULL;
}

static const char *
grub_gettext_translate (const char *orig)
{
  const char *ret;
251 252 253
  if (orig[0] == 0)
    return orig;

254 255 256 257 258 259 260
  ret = grub_gettext_translate_real (&main_context, orig);
  if (ret)
    return ret;
  ret = grub_gettext_translate_real (&secondary_context, orig);
  if (ret)
    return ret;
  return orig;
261 262 263
}

static void
264
grub_gettext_delete_list (struct grub_gettext_context *ctx)
265
{
266
  struct grub_gettext_msg *l = ctx->grub_gettext_msg_list;
267 268 269 270
  grub_size_t i;

  if (!l)
    return;
271 272
  ctx->grub_gettext_msg_list = 0;
  for (i = 0; i < ctx->grub_gettext_max; i++)
273 274 275
    grub_free (l[i].name);
  /* Don't delete the translated message because could be in use.  */
  grub_free (l);
276 277 278 279
  if (ctx->fd_mo)
    grub_file_close (ctx->fd_mo);
  ctx->fd_mo = 0;
  grub_memset (ctx, 0, sizeof (*ctx));
280 281
}

282
/* This is similar to grub_file_open. */
283
static grub_err_t
284 285
grub_mofile_open (struct grub_gettext_context *ctx,
		  const char *filename)
286
{
287 288 289
  struct header head;
  grub_err_t err;
  grub_file_t fd;
290 291 292 293

  /* Using fd_mo and not another variable because
     it's needed for grub_gettext_get_info.  */

294 295 296 297
  fd = grub_file_open (filename);

  if (!fd)
    return grub_errno;
298

299 300
  err = grub_gettext_pread (fd, &head, sizeof (head), 0);
  if (err)
301
    {
302 303
      grub_file_close (fd);
      return err;
304 305
    }

306 307 308 309 310 311
  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);
    }
312

313
  if (head.version != 0)
314
    {
315 316 317
      grub_file_close (fd);
      return grub_error (GRUB_ERR_BAD_FILE_TYPE,
			 "mo: invalid mo version in file: %s", filename);
318
    }
319

320 321 322 323 324
  ctx->grub_gettext_offset_original = grub_le_to_cpu32 (head.offset_original);
  ctx->grub_gettext_offset_translation = grub_le_to_cpu32 (head.offset_translation);
  ctx->grub_gettext_max = grub_le_to_cpu32 (head.number_of_strings);
  for (ctx->grub_gettext_max_log = 0; ctx->grub_gettext_max >> ctx->grub_gettext_max_log;
       ctx->grub_gettext_max_log++);
325

326 327 328
  ctx->grub_gettext_msg_list = grub_zalloc (ctx->grub_gettext_max
					    * sizeof (ctx->grub_gettext_msg_list[0]));
  if (!ctx->grub_gettext_msg_list)
329
    {
330 331
      grub_file_close (fd);
      return grub_errno;
332
    }
333
  ctx->fd_mo = fd;
334 335 336 337 338 339
  if (grub_gettext != grub_gettext_translate)
    {
      grub_gettext_original = grub_gettext;
      grub_gettext = grub_gettext_translate;
    }
  return 0;
340 341
}

342 343
/* Returning grub_file_t would be more natural, but grub_mofile_open assigns
   to fd_mo anyway ...  */
344
static grub_err_t
345 346
grub_mofile_open_lang (struct grub_gettext_context *ctx,
		       const char *part1, const char *part2, const char *locale)
347 348
{
  char *mo_file;
349
  grub_err_t err;
350

351
  /* mo_file e.g.: /boot/grub/locale/ca.mo   */
352

353
  mo_file = grub_xasprintf ("%s%s/%s.mo", part1, part2, locale);
354
  if (!mo_file)
355
    return grub_errno;
Carles Pina i Estany's avatar
Carles Pina i Estany committed
356

357
  err = grub_mofile_open (ctx, mo_file);
358
  grub_free (mo_file);
359

360
  /* Will try adding .gz as well.  */
361
  if (err)
362
    {
363
      grub_errno = GRUB_ERR_NONE;
364
      mo_file = grub_xasprintf ("%s%s/%s.mo.gz", part1, part2, locale);
365
      if (!mo_file)
366
	return grub_errno;
367
      err = grub_mofile_open (ctx, mo_file);
368
      grub_free (mo_file);
369
    }
370 371 372 373 374 375 376 377 378 379 380 381

  /* Will try adding .gmo as well.  */
  if (err)
    {
      grub_errno = GRUB_ERR_NONE;
      mo_file = grub_xasprintf ("%s%s/%s.gmo", part1, part2, locale);
      if (!mo_file)
	return grub_errno;
      err = grub_mofile_open (ctx, mo_file);
      grub_free (mo_file);
    }

382
  return err;
383 384
}

385
static grub_err_t
386 387 388
grub_gettext_init_ext (struct grub_gettext_context *ctx,
		       const char *locale,
		       const char *locale_dir, const char *prefix)
389
{
390
  const char *part1, *part2;
391
  grub_err_t err;
392

393 394 395
  grub_gettext_delete_list (ctx);

  if (!locale || locale[0] == 0)
396
    return 0;
397

398
  part1 = locale_dir;
399
  part2 = "";
400
  if (!part1 || part1[0] == 0)
401
    {
402
      part1 = prefix;
403
      part2 = "/locale";
404 405
    }

406 407 408 409
  if (!part1 || part1[0] == 0)
    return 0;

  err = grub_mofile_open_lang (ctx, part1, part2, locale);
410 411

  /* ll_CC didn't work, so try ll.  */
412
  if (err)
413 414
    {
      char *lang = grub_strdup (locale);
415
      char *underscore = lang ? grub_strchr (lang, '_') : 0;
416 417 418 419

      if (underscore)
	{
	  *underscore = '\0';
420
	  grub_errno = GRUB_ERR_NONE;
421
	  err = grub_mofile_open_lang (ctx, part1, part2, lang);
422 423 424 425
	}

      grub_free (lang);
    }
426 427 428 429

  if (locale[0] == 'e' && locale[1] == 'n'
      && (locale[2] == '\0' || locale[2] == '_'))
    grub_errno = err = GRUB_ERR_NONE;
430
  return err;
431 432
}

433 434 435
static char *
grub_gettext_env_write_lang (struct grub_env_var *var
			     __attribute__ ((unused)), const char *val)
436
{
437
  grub_err_t err;
438 439
  err = grub_gettext_init_ext (&main_context, val, grub_env_get ("locale_dir"),
			       grub_env_get ("prefix"));
440
  if (err)
441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482
    grub_print_error ();

  err = grub_gettext_init_ext (&secondary_context, val,
			       grub_env_get ("secondary_locale_dir"), 0);
  if (err)
    grub_print_error ();

  return grub_strdup (val);
}

void
grub_gettext_reread_prefix (const char *val)
{
  grub_err_t err;
  err = grub_gettext_init_ext (&main_context, grub_env_get ("lang"), 
			       grub_env_get ("locale_dir"),
			       val);
  if (err)
    grub_print_error ();
}

static char *
read_main (struct grub_env_var *var
	   __attribute__ ((unused)), const char *val)
{
  grub_err_t err;
  err = grub_gettext_init_ext (&main_context, grub_env_get ("lang"), val,
			       grub_env_get ("prefix"));
  if (err)
    grub_print_error ();
  return grub_strdup (val);
}

static char *
read_secondary (struct grub_env_var *var
		__attribute__ ((unused)), const char *val)
{
  grub_err_t err;
  err = grub_gettext_init_ext (&secondary_context, grub_env_get ("lang"), val,
			       0);
  if (err)
    grub_print_error ();
483

484 485 486
  return grub_strdup (val);
}

487 488 489 490 491
static grub_err_t
grub_cmd_translate (grub_command_t cmd __attribute__ ((unused)),
		    int argc, char **args)
{
  if (argc != 1)
492
    return grub_error (GRUB_ERR_BAD_ARGUMENT, N_("one argument expected"));
493 494 495 496 497 498 499

  const char *translation;
  translation = grub_gettext_translate (args[0]);
  grub_printf ("%s\n", translation);
  return 0;
}

500
GRUB_MOD_INIT (gettext)
501 502
{
  const char *lang;
503
  grub_err_t err;
504

505
  lang = grub_env_get ("lang");
506

507 508 509 510 511 512 513 514
  err = grub_gettext_init_ext (&main_context, lang, grub_env_get ("locale_dir"),
			       grub_env_get ("prefix"));
  if (err)
    grub_print_error ();
  err = grub_gettext_init_ext (&secondary_context, lang,
			       grub_env_get ("secondary_locale_dir"), 0);
  if (err)
    grub_print_error ();
515 516 517

  grub_register_variable_hook ("locale_dir", NULL, read_main);
  grub_register_variable_hook ("secondary_locale_dir", NULL, read_secondary);
518

519
  grub_register_command_p1 ("gettext", grub_cmd_translate,
520
			    N_("STRING"),
521 522 523 524
			    /* TRANSLATORS: It refers to passing the string through gettext.
			       So it's "translate" in the same meaning as in what you're 
			       doing now.
			     */
525
			    N_("Translates the string with the current settings."));
526

527 528 529 530 531
  /* Reload .mo file information if lang changes.  */
  grub_register_variable_hook ("lang", NULL, grub_gettext_env_write_lang);

  /* Preserve hooks after context changes.  */
  grub_env_export ("lang");
532 533
  grub_env_export ("locale_dir");
  grub_env_export ("secondary_locale_dir");
534 535
}

536
GRUB_MOD_FINI (gettext)
537
{
538 539
  grub_gettext_delete_list (&main_context);
  grub_gettext_delete_list (&secondary_context);
540

541 542
  grub_gettext = grub_gettext_original;
}