1/* Writing binary .mo files. 2 Copyright (C) 1995-1998, 2000-2005 Free Software Foundation, Inc. 3 Written by Ulrich Drepper <drepper@gnu.ai.mit.edu>, April 1995. 4 5 This program is free software; you can redistribute it and/or modify 6 it under the terms of the GNU General Public License as published by 7 the Free Software Foundation; either version 2, or (at your option) 8 any later version. 9 10 This program is distributed in the hope that it will be useful, 11 but WITHOUT ANY WARRANTY; without even the implied warranty of 12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13 GNU General Public License for more details. 14 15 You should have received a copy of the GNU General Public License 16 along with this program; if not, write to the Free Software Foundation, 17 Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 18 19#ifdef HAVE_CONFIG_H 20# include <config.h> 21#endif 22#include <alloca.h> 23 24/* Specification. */ 25#include "write-mo.h" 26 27#include <errno.h> 28#include <stdbool.h> 29#include <stdio.h> 30#include <stdlib.h> 31#include <string.h> 32 33#if HAVE_SYS_PARAM_H 34# include <sys/param.h> 35#endif 36 37/* These two include files describe the binary .mo format. */ 38#include "gmo.h" 39#include "hash-string.h" 40 41#include "error.h" 42#include "hash.h" 43#include "message.h" 44#include "format.h" 45#include "xalloc.h" 46#include "xallocsa.h" 47#include "binary-io.h" 48#include "fwriteerror.h" 49#include "exit.h" 50#include "gettext.h" 51 52#define _(str) gettext (str) 53 54#define freea(p) /* nothing */ 55 56/* Usually defined in <sys/param.h>. */ 57#ifndef roundup 58# if defined __GNUC__ && __GNUC__ >= 2 59# define roundup(x, y) ({typeof(x) _x = (x); typeof(y) _y = (y); \ 60 ((_x + _y - 1) / _y) * _y; }) 61# else 62# define roundup(x, y) ((((x)+((y)-1))/(y))*(y)) 63# endif /* GNU CC2 */ 64#endif /* roundup */ 65 66 67/* Alignment of strings in resulting .mo file. */ 68size_t alignment; 69 70/* True if no hash table in .mo is wanted. */ 71bool no_hash_table; 72 73 74/* Indices into the strings contained in 'struct pre_message' and 75 'struct pre_sysdep_message'. */ 76enum 77{ 78 M_ID = 0, /* msgid - the original string */ 79 M_STR = 1 /* msgstr - the translated string */ 80}; 81 82/* An intermediate data structure representing a 'struct string_desc'. */ 83struct pre_string 84{ 85 size_t length; 86 const char *pointer; 87}; 88 89/* An intermediate data structure representing a message. */ 90struct pre_message 91{ 92 struct pre_string str[2]; 93 const char *id_plural; 94 size_t id_plural_len; 95}; 96 97static int 98compare_id (const void *pval1, const void *pval2) 99{ 100 return strcmp (((struct pre_message *) pval1)->str[M_ID].pointer, 101 ((struct pre_message *) pval2)->str[M_ID].pointer); 102} 103 104 105/* An intermediate data structure representing a 'struct sysdep_segment'. */ 106struct pre_sysdep_segment 107{ 108 size_t length; 109 const char *pointer; 110}; 111 112/* An intermediate data structure representing a 'struct segment_pair'. */ 113struct pre_segment_pair 114{ 115 size_t segsize; 116 const char *segptr; 117 size_t sysdepref; 118}; 119 120/* An intermediate data structure representing a 'struct sysdep_string'. */ 121struct pre_sysdep_string 122{ 123 unsigned int segmentcount; 124 struct pre_segment_pair segments[1]; 125}; 126 127/* An intermediate data structure representing a message with system dependent 128 strings. */ 129struct pre_sysdep_message 130{ 131 struct pre_sysdep_string *str[2]; 132 const char *id_plural; 133 size_t id_plural_len; 134}; 135 136/* Write the message list to the given open file. */ 137static void 138write_table (FILE *output_file, message_list_ty *mlp) 139{ 140 size_t nstrings; 141 struct pre_message *msg_arr; 142 size_t n_sysdep_strings; 143 struct pre_sysdep_message *sysdep_msg_arr; 144 size_t n_sysdep_segments; 145 struct pre_sysdep_segment *sysdep_segments; 146 bool have_outdigits; 147 int major_revision; 148 int minor_revision; 149 bool omit_hash_table; 150 nls_uint32 hash_tab_size; 151 struct mo_file_header header; /* Header of the .mo file to be written. */ 152 size_t header_size; 153 size_t offset; 154 struct string_desc *orig_tab; 155 struct string_desc *trans_tab; 156 size_t sysdep_tab_offset = 0; 157 size_t end_offset; 158 char *null; 159 size_t j, m; 160 161 /* First pass: Move the static string pairs into an array, for sorting, 162 and at the same time, compute the segments of the system dependent 163 strings. */ 164 nstrings = 0; 165 msg_arr = 166 (struct pre_message *) 167 xmalloc (mlp->nitems * sizeof (struct pre_message)); 168 n_sysdep_strings = 0; 169 sysdep_msg_arr = 170 (struct pre_sysdep_message *) 171 xmalloc (mlp->nitems * sizeof (struct pre_sysdep_message)); 172 n_sysdep_segments = 0; 173 sysdep_segments = NULL; 174 have_outdigits = false; 175 for (j = 0; j < mlp->nitems; j++) 176 { 177 message_ty *mp = mlp->item[j]; 178 struct interval *intervals[2]; 179 size_t nintervals[2]; 180 181 intervals[M_ID] = NULL; 182 nintervals[M_ID] = 0; 183 intervals[M_STR] = NULL; 184 nintervals[M_STR] = 0; 185 186 /* Test if mp contains system dependent strings and thus 187 requires the use of the .mo file minor revision 1. */ 188 if (possible_format_p (mp->is_format[format_c]) 189 || possible_format_p (mp->is_format[format_objc])) 190 { 191 /* Check whether msgid or msgstr contain ISO C 99 <inttypes.h> 192 format string directives. No need to check msgid_plural, because 193 it is not accessed by the [n]gettext() function family. */ 194 const char *p_end; 195 const char *p; 196 197 get_sysdep_c_format_directives (mp->msgid, false, 198 &intervals[M_ID], &nintervals[M_ID]); 199 200 p_end = mp->msgstr + mp->msgstr_len; 201 for (p = mp->msgstr; p < p_end; p += strlen (p) + 1) 202 { 203 struct interval *part_intervals; 204 size_t part_nintervals; 205 206 get_sysdep_c_format_directives (p, true, 207 &part_intervals, 208 &part_nintervals); 209 if (part_nintervals > 0) 210 { 211 size_t d = p - mp->msgstr; 212 unsigned int i; 213 214 intervals[M_STR] = 215 (struct interval *) 216 xrealloc (intervals[M_STR], 217 (nintervals[M_STR] + part_nintervals) 218 * sizeof (struct interval)); 219 for (i = 0; i < part_nintervals; i++) 220 { 221 intervals[M_STR][nintervals[M_STR] + i].startpos = 222 d + part_intervals[i].startpos; 223 intervals[M_STR][nintervals[M_STR] + i].endpos = 224 d + part_intervals[i].endpos; 225 } 226 nintervals[M_STR] += part_nintervals; 227 } 228 } 229 } 230 231 if (nintervals[M_ID] > 0 || nintervals[M_STR] > 0) 232 { 233 /* System dependent string pair. */ 234 for (m = 0; m < 2; m++) 235 { 236 struct pre_sysdep_string *pre = 237 (struct pre_sysdep_string *) 238 xmalloc (sizeof (struct pre_sysdep_string) 239 + nintervals[m] * sizeof (struct pre_segment_pair)); 240 const char *str; 241 size_t str_len; 242 size_t lastpos; 243 unsigned int i; 244 245 if (m == M_ID) 246 { 247 str = mp->msgid; 248 str_len = strlen (mp->msgid) + 1; 249 } 250 else 251 { 252 str = mp->msgstr; 253 str_len = mp->msgstr_len; 254 } 255 256 lastpos = 0; 257 pre->segmentcount = nintervals[m]; 258 for (i = 0; i < nintervals[m]; i++) 259 { 260 size_t length; 261 const char *pointer; 262 size_t r; 263 264 pre->segments[i].segptr = str + lastpos; 265 pre->segments[i].segsize = intervals[m][i].startpos - lastpos; 266 267 length = intervals[m][i].endpos - intervals[m][i].startpos; 268 pointer = str + intervals[m][i].startpos; 269 if (length >= 2 270 && pointer[0] == '<' && pointer[length - 1] == '>') 271 { 272 /* Skip the '<' and '>' markers. */ 273 length -= 2; 274 pointer += 1; 275 } 276 277 for (r = 0; r < n_sysdep_segments; r++) 278 if (sysdep_segments[r].length == length 279 && memcmp (sysdep_segments[r].pointer, pointer, length) 280 == 0) 281 break; 282 if (r == n_sysdep_segments) 283 { 284 n_sysdep_segments++; 285 sysdep_segments = 286 (struct pre_sysdep_segment *) 287 xrealloc (sysdep_segments, 288 n_sysdep_segments 289 * sizeof (struct pre_sysdep_segment)); 290 sysdep_segments[r].length = length; 291 sysdep_segments[r].pointer = pointer; 292 } 293 294 pre->segments[i].sysdepref = r; 295 296 if (length == 1 && *pointer == 'I') 297 have_outdigits = true; 298 299 lastpos = intervals[m][i].endpos; 300 } 301 pre->segments[i].segptr = str + lastpos; 302 pre->segments[i].segsize = str_len - lastpos; 303 pre->segments[i].sysdepref = SEGMENTS_END; 304 305 sysdep_msg_arr[n_sysdep_strings].str[m] = pre; 306 } 307 308 sysdep_msg_arr[n_sysdep_strings].id_plural = mp->msgid_plural; 309 sysdep_msg_arr[n_sysdep_strings].id_plural_len = 310 (mp->msgid_plural != NULL ? strlen (mp->msgid_plural) + 1 : 0); 311 n_sysdep_strings++; 312 } 313 else 314 { 315 /* Static string pair. */ 316 msg_arr[nstrings].str[M_ID].pointer = mp->msgid; 317 msg_arr[nstrings].str[M_ID].length = strlen (mp->msgid) + 1; 318 msg_arr[nstrings].str[M_STR].pointer = mp->msgstr; 319 msg_arr[nstrings].str[M_STR].length = mp->msgstr_len; 320 msg_arr[nstrings].id_plural = mp->msgid_plural; 321 msg_arr[nstrings].id_plural_len = 322 (mp->msgid_plural != NULL ? strlen (mp->msgid_plural) + 1 : 0); 323 nstrings++; 324 } 325 326 for (m = 0; m < 2; m++) 327 if (intervals[m] != NULL) 328 free (intervals[m]); 329 } 330 331 /* Sort the table according to original string. */ 332 if (nstrings > 0) 333 qsort (msg_arr, nstrings, sizeof (struct pre_message), compare_id); 334 335 /* We need major revision 1 if there are system dependent strings that use 336 "I" because older versions of gettext() crash when this occurs in a .mo 337 file. Otherwise use major revision 0. */ 338 major_revision = 339 (have_outdigits ? MO_REVISION_NUMBER_WITH_SYSDEP_I : MO_REVISION_NUMBER); 340 341 /* We need minor revision 1 if there are system dependent strings. 342 Otherwise we choose minor revision 0 because it's supported by older 343 versions of libintl and revision 1 isn't. */ 344 minor_revision = (n_sysdep_strings > 0 ? 1 : 0); 345 346 /* In minor revision >= 1, the hash table is obligatory. */ 347 omit_hash_table = (no_hash_table && minor_revision == 0); 348 349 /* This should be explained: 350 Each string has an associate hashing value V, computed by a fixed 351 function. To locate the string we use open addressing with double 352 hashing. The first index will be V % M, where M is the size of the 353 hashing table. If no entry is found, iterating with a second, 354 independent hashing function takes place. This second value will 355 be 1 + V % (M - 2). 356 The approximate number of probes will be 357 358 for unsuccessful search: (1 - N / M) ^ -1 359 for successful search: - (N / M) ^ -1 * ln (1 - N / M) 360 361 where N is the number of keys. 362 363 If we now choose M to be the next prime bigger than 4 / 3 * N, 364 we get the values 365 4 and 1.85 resp. 366 Because unsuccessful searches are unlikely this is a good value. 367 Formulas: [Knuth, The Art of Computer Programming, Volume 3, 368 Sorting and Searching, 1973, Addison Wesley] */ 369 if (!omit_hash_table) 370 { 371 hash_tab_size = next_prime ((mlp->nitems * 4) / 3); 372 /* Ensure M > 2. */ 373 if (hash_tab_size <= 2) 374 hash_tab_size = 3; 375 } 376 else 377 hash_tab_size = 0; 378 379 380 /* Second pass: Fill the structure describing the header. At the same time, 381 compute the sizes and offsets of the non-string parts of the file. */ 382 383 /* Magic number. */ 384 header.magic = _MAGIC; 385 /* Revision number of file format. */ 386 header.revision = (major_revision << 16) + minor_revision; 387 388 header_size = 389 (minor_revision == 0 390 ? offsetof (struct mo_file_header, n_sysdep_segments) 391 : sizeof (struct mo_file_header)); 392 offset = header_size; 393 394 /* Number of static string pairs. */ 395 header.nstrings = nstrings; 396 397 /* Offset of table for original string offsets. */ 398 header.orig_tab_offset = offset; 399 offset += nstrings * sizeof (struct string_desc); 400 orig_tab = 401 (struct string_desc *) xmalloc (nstrings * sizeof (struct string_desc)); 402 403 /* Offset of table for translated string offsets. */ 404 header.trans_tab_offset = offset; 405 offset += nstrings * sizeof (struct string_desc); 406 trans_tab = 407 (struct string_desc *) xmalloc (nstrings * sizeof (struct string_desc)); 408 409 /* Size of hash table. */ 410 header.hash_tab_size = hash_tab_size; 411 /* Offset of hash table. */ 412 header.hash_tab_offset = offset; 413 offset += hash_tab_size * sizeof (nls_uint32); 414 415 if (minor_revision >= 1) 416 { 417 /* Size of table describing system dependent segments. */ 418 header.n_sysdep_segments = n_sysdep_segments; 419 /* Offset of table describing system dependent segments. */ 420 header.sysdep_segments_offset = offset; 421 offset += n_sysdep_segments * sizeof (struct sysdep_segment); 422 423 /* Number of system dependent string pairs. */ 424 header.n_sysdep_strings = n_sysdep_strings; 425 426 /* Offset of table for original sysdep string offsets. */ 427 header.orig_sysdep_tab_offset = offset; 428 offset += n_sysdep_strings * sizeof (nls_uint32); 429 430 /* Offset of table for translated sysdep string offsets. */ 431 header.trans_sysdep_tab_offset = offset; 432 offset += n_sysdep_strings * sizeof (nls_uint32); 433 434 /* System dependent string descriptors. */ 435 sysdep_tab_offset = offset; 436 for (m = 0; m < 2; m++) 437 for (j = 0; j < n_sysdep_strings; j++) 438 offset += sizeof (struct sysdep_string) 439 + sysdep_msg_arr[j].str[m]->segmentcount 440 * sizeof (struct segment_pair); 441 } 442 443 end_offset = offset; 444 445 446 /* Third pass: Write the non-string parts of the file. At the same time, 447 compute the offsets of each string, including the proper alignment. */ 448 449 /* Write the header out. */ 450 fwrite (&header, header_size, 1, output_file); 451 452 /* Table for original string offsets. */ 453 /* Here output_file is at position header.orig_tab_offset. */ 454 455 for (j = 0; j < nstrings; j++) 456 { 457 offset = roundup (offset, alignment); 458 orig_tab[j].length = 459 msg_arr[j].str[M_ID].length + msg_arr[j].id_plural_len; 460 orig_tab[j].offset = offset; 461 offset += orig_tab[j].length; 462 /* Subtract 1 because of the terminating NUL. */ 463 orig_tab[j].length--; 464 } 465 fwrite (orig_tab, nstrings * sizeof (struct string_desc), 1, output_file); 466 467 /* Table for translated string offsets. */ 468 /* Here output_file is at position header.trans_tab_offset. */ 469 470 for (j = 0; j < nstrings; j++) 471 { 472 offset = roundup (offset, alignment); 473 trans_tab[j].length = msg_arr[j].str[M_STR].length; 474 trans_tab[j].offset = offset; 475 offset += trans_tab[j].length; 476 /* Subtract 1 because of the terminating NUL. */ 477 trans_tab[j].length--; 478 } 479 fwrite (trans_tab, nstrings * sizeof (struct string_desc), 1, output_file); 480 481 /* Skip this part when no hash table is needed. */ 482 if (!omit_hash_table) 483 { 484 nls_uint32 *hash_tab; 485 unsigned int j; 486 487 /* Here output_file is at position header.hash_tab_offset. */ 488 489 /* Allocate room for the hashing table to be written out. */ 490 hash_tab = (nls_uint32 *) xmalloc (hash_tab_size * sizeof (nls_uint32)); 491 memset (hash_tab, '\0', hash_tab_size * sizeof (nls_uint32)); 492 493 /* Insert all value in the hash table, following the algorithm described 494 above. */ 495 for (j = 0; j < nstrings; j++) 496 { 497 nls_uint32 hash_val = hash_string (msg_arr[j].str[M_ID].pointer); 498 nls_uint32 idx = hash_val % hash_tab_size; 499 500 if (hash_tab[idx] != 0) 501 { 502 /* We need the second hashing function. */ 503 nls_uint32 incr = 1 + (hash_val % (hash_tab_size - 2)); 504 505 do 506 if (idx >= hash_tab_size - incr) 507 idx -= hash_tab_size - incr; 508 else 509 idx += incr; 510 while (hash_tab[idx] != 0); 511 } 512 513 hash_tab[idx] = j + 1; 514 } 515 516 /* Write the hash table out. */ 517 fwrite (hash_tab, hash_tab_size * sizeof (nls_uint32), 1, output_file); 518 519 free (hash_tab); 520 } 521 522 if (minor_revision >= 1) 523 { 524 struct sysdep_segment *sysdep_segments_tab; 525 nls_uint32 *sysdep_tab; 526 size_t stoffset; 527 unsigned int i; 528 529 /* Here output_file is at position header.sysdep_segments_offset. */ 530 531 sysdep_segments_tab = 532 (struct sysdep_segment *) 533 xmalloc (n_sysdep_segments * sizeof (struct sysdep_segment)); 534 for (i = 0; i < n_sysdep_segments; i++) 535 { 536 offset = roundup (offset, alignment); 537 /* The "+ 1" accounts for the trailing NUL byte. */ 538 sysdep_segments_tab[i].length = sysdep_segments[i].length + 1; 539 sysdep_segments_tab[i].offset = offset; 540 offset += sysdep_segments_tab[i].length; 541 } 542 543 fwrite (sysdep_segments_tab, 544 n_sysdep_segments * sizeof (struct sysdep_segment), 1, 545 output_file); 546 547 free (sysdep_segments_tab); 548 549 sysdep_tab = 550 (nls_uint32 *) xmalloc (n_sysdep_strings * sizeof (nls_uint32)); 551 stoffset = sysdep_tab_offset; 552 553 for (m = 0; m < 2; m++) 554 { 555 /* Here output_file is at position 556 m == M_ID -> header.orig_sysdep_tab_offset, 557 m == M_STR -> header.trans_sysdep_tab_offset. */ 558 559 for (j = 0; j < n_sysdep_strings; j++) 560 { 561 sysdep_tab[j] = stoffset; 562 stoffset += sizeof (struct sysdep_string) 563 + sysdep_msg_arr[j].str[m]->segmentcount 564 * sizeof (struct segment_pair); 565 } 566 /* Write the table for original/translated sysdep string offsets. */ 567 fwrite (sysdep_tab, n_sysdep_strings * sizeof (nls_uint32), 1, 568 output_file); 569 } 570 571 free (sysdep_tab); 572 573 /* Here output_file is at position sysdep_tab_offset. */ 574 575 for (m = 0; m < 2; m++) 576 for (j = 0; j < n_sysdep_strings; j++) 577 { 578 struct pre_sysdep_message *msg = &sysdep_msg_arr[j]; 579 struct pre_sysdep_string *pre = msg->str[m]; 580 struct sysdep_string *str = 581 (struct sysdep_string *) 582 xallocsa (sizeof (struct sysdep_string) 583 + pre->segmentcount * sizeof (struct segment_pair)); 584 unsigned int i; 585 586 offset = roundup (offset, alignment); 587 str->offset = offset; 588 for (i = 0; i <= pre->segmentcount; i++) 589 { 590 str->segments[i].segsize = pre->segments[i].segsize; 591 str->segments[i].sysdepref = pre->segments[i].sysdepref; 592 offset += str->segments[i].segsize; 593 } 594 if (m == M_ID && msg->id_plural_len > 0) 595 { 596 str->segments[pre->segmentcount].segsize += msg->id_plural_len; 597 offset += msg->id_plural_len; 598 } 599 fwrite (str, 600 sizeof (struct sysdep_string) 601 + pre->segmentcount * sizeof (struct segment_pair), 602 1, output_file); 603 604 freesa (str); 605 } 606 } 607 608 /* Here output_file is at position end_offset. */ 609 610 free (trans_tab); 611 free (orig_tab); 612 613 614 /* Fourth pass: Write the strings. */ 615 616 offset = end_offset; 617 618 /* A few zero bytes for padding. */ 619 null = alloca (alignment); 620 memset (null, '\0', alignment); 621 622 /* Now write the original strings. */ 623 for (j = 0; j < nstrings; j++) 624 { 625 fwrite (null, roundup (offset, alignment) - offset, 1, output_file); 626 offset = roundup (offset, alignment); 627 628 fwrite (msg_arr[j].str[M_ID].pointer, msg_arr[j].str[M_ID].length, 1, 629 output_file); 630 if (msg_arr[j].id_plural_len > 0) 631 fwrite (msg_arr[j].id_plural, msg_arr[j].id_plural_len, 1, 632 output_file); 633 offset += msg_arr[j].str[M_ID].length + msg_arr[j].id_plural_len; 634 } 635 636 /* Now write the translated strings. */ 637 for (j = 0; j < nstrings; j++) 638 { 639 fwrite (null, roundup (offset, alignment) - offset, 1, output_file); 640 offset = roundup (offset, alignment); 641 642 fwrite (msg_arr[j].str[M_STR].pointer, msg_arr[j].str[M_STR].length, 1, 643 output_file); 644 offset += msg_arr[j].str[M_STR].length; 645 } 646 647 if (minor_revision >= 1) 648 { 649 unsigned int i; 650 651 for (i = 0; i < n_sysdep_segments; i++) 652 { 653 fwrite (null, roundup (offset, alignment) - offset, 1, output_file); 654 offset = roundup (offset, alignment); 655 656 fwrite (sysdep_segments[i].pointer, sysdep_segments[i].length, 1, 657 output_file); 658 fwrite (null, 1, 1, output_file); 659 offset += sysdep_segments[i].length + 1; 660 } 661 662 for (m = 0; m < 2; m++) 663 for (j = 0; j < n_sysdep_strings; j++) 664 { 665 struct pre_sysdep_message *msg = &sysdep_msg_arr[j]; 666 struct pre_sysdep_string *pre = msg->str[m]; 667 668 fwrite (null, roundup (offset, alignment) - offset, 1, 669 output_file); 670 offset = roundup (offset, alignment); 671 672 for (i = 0; i <= pre->segmentcount; i++) 673 { 674 fwrite (pre->segments[i].segptr, pre->segments[i].segsize, 1, 675 output_file); 676 offset += pre->segments[i].segsize; 677 } 678 if (m == M_ID && msg->id_plural_len > 0) 679 { 680 fwrite (msg->id_plural, msg->id_plural_len, 1, output_file); 681 offset += msg->id_plural_len; 682 } 683 684 free (pre); 685 } 686 } 687 688 freea (null); 689 free (sysdep_msg_arr); 690 free (msg_arr); 691} 692 693 694int 695msgdomain_write_mo (message_list_ty *mlp, 696 const char *domain_name, 697 const char *file_name) 698{ 699 FILE *output_file; 700 701 /* If no entry for this domain don't even create the file. */ 702 if (mlp->nitems != 0) 703 { 704 if (strcmp (domain_name, "-") == 0) 705 { 706 output_file = stdout; 707 SET_BINARY (fileno (output_file)); 708 } 709 else 710 { 711 output_file = fopen (file_name, "wb"); 712 if (output_file == NULL) 713 { 714 error (0, errno, _("error while opening \"%s\" for writing"), 715 file_name); 716 return 1; 717 } 718 } 719 720 if (output_file != NULL) 721 { 722 write_table (output_file, mlp); 723 724 /* Make sure nothing went wrong. */ 725 if (fwriteerror (output_file)) 726 error (EXIT_FAILURE, errno, _("error while writing \"%s\" file"), 727 file_name); 728 } 729 } 730 731 return 0; 732} 733