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