191976Sjake/* Pass translations to a subprocess.
291976Sjake   Copyright (C) 2001-2006 Free Software Foundation, Inc.
391976Sjake   Written by Bruno Haible <haible@clisp.cons.org>, 2001.
491976Sjake
591976Sjake   This program is free software; you can redistribute it and/or modify
691976Sjake   it under the terms of the GNU General Public License as published by
791976Sjake   the Free Software Foundation; either version 2, or (at your option)
891976Sjake   any later version.
991976Sjake
1091976Sjake   This program is distributed in the hope that it will be useful,
1191976Sjake   but WITHOUT ANY WARRANTY; without even the implied warranty of
1291976Sjake   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1391976Sjake   GNU General Public License for more details.
1491976Sjake
1591976Sjake   You should have received a copy of the GNU General Public License
1691976Sjake   along with this program; if not, write to the Free Software Foundation,
1791976Sjake   Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.  */
1891976Sjake
1991976Sjake
2091976Sjake#ifdef HAVE_CONFIG_H
2191976Sjake# include "config.h"
2291976Sjake#endif
2391976Sjake
2491976Sjake#include <errno.h>
2591976Sjake#include <getopt.h>
2691976Sjake#include <limits.h>
2791976Sjake#include <locale.h>
2891976Sjake#include <stdio.h>
2991976Sjake#include <stdlib.h>
3091976Sjake#include <string.h>
3191976Sjake#include <sys/types.h>
3291976Sjake#include <unistd.h>
3391976Sjake
3491976Sjake#include "closeout.h"
3591976Sjake#include "dir-list.h"
3691976Sjake#include "error.h"
3791976Sjake#include "xvasprintf.h"
3891976Sjake#include "error-progname.h"
3991976Sjake#include "progname.h"
4091976Sjake#include "relocatable.h"
41#include "basename.h"
42#include "message.h"
43#include "read-catalog.h"
44#include "read-po.h"
45#include "read-properties.h"
46#include "read-stringtable.h"
47#include "xalloc.h"
48#include "exit.h"
49#include "full-write.h"
50#include "findprog.h"
51#include "pipe.h"
52#include "wait-process.h"
53#include "xsetenv.h"
54#include "propername.h"
55#include "gettext.h"
56
57#define _(str) gettext (str)
58
59#ifndef STDOUT_FILENO
60# define STDOUT_FILENO 1
61#endif
62
63
64/* Name of the subprogram.  */
65static const char *sub_name;
66
67/* Pathname of the subprogram.  */
68static const char *sub_path;
69
70/* Argument list for the subprogram.  */
71static char **sub_argv;
72static int sub_argc;
73
74/* Maximum exit code encountered.  */
75static int exitcode;
76
77/* Long options.  */
78static const struct option long_options[] =
79{
80  { "directory", required_argument, NULL, 'D' },
81  { "help", no_argument, NULL, 'h' },
82  { "input", required_argument, NULL, 'i' },
83  { "properties-input", no_argument, NULL, 'P' },
84  { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 },
85  { "version", no_argument, NULL, 'V' },
86  { NULL, 0, NULL, 0 }
87};
88
89
90/* Forward declaration of local functions.  */
91static void usage (int status)
92#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
93	__attribute__ ((noreturn))
94#endif
95;
96static void process_msgdomain_list (const msgdomain_list_ty *mdlp);
97
98
99int
100main (int argc, char **argv)
101{
102  int opt;
103  bool do_help;
104  bool do_version;
105  const char *input_file;
106  msgdomain_list_ty *result;
107  catalog_input_format_ty input_syntax = &input_format_po;
108  size_t i;
109
110  /* Set program name for messages.  */
111  set_program_name (argv[0]);
112  error_print_progname = maybe_print_progname;
113
114#ifdef HAVE_SETLOCALE
115  /* Set locale via LC_ALL.  */
116  setlocale (LC_ALL, "");
117#endif
118
119  /* Set the text message domain.  */
120  bindtextdomain (PACKAGE, relocate (LOCALEDIR));
121  bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
122  textdomain (PACKAGE);
123
124  /* Ensure that write errors on stdout are detected.  */
125  atexit (close_stdout);
126
127  /* Set default values for variables.  */
128  do_help = false;
129  do_version = false;
130  input_file = NULL;
131
132  /* The '+' in the options string causes option parsing to terminate when
133     the first non-option, i.e. the subprogram name, is encountered.  */
134  while ((opt = getopt_long (argc, argv, "+D:hi:PV", long_options, NULL))
135	 != EOF)
136    switch (opt)
137      {
138      case '\0':		/* Long option.  */
139	break;
140
141      case 'D':
142	dir_list_append (optarg);
143	break;
144
145      case 'h':
146	do_help = true;
147	break;
148
149      case 'i':
150	if (input_file != NULL)
151	  {
152	    error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
153	    usage (EXIT_FAILURE);
154	  }
155	input_file = optarg;
156	break;
157
158      case 'P':
159	input_syntax = &input_format_properties;
160	break;
161
162      case 'V':
163	do_version = true;
164	break;
165
166      case CHAR_MAX + 1: /* --stringtable-input */
167	input_syntax = &input_format_stringtable;
168	break;
169
170      default:
171	usage (EXIT_FAILURE);
172	break;
173      }
174
175  /* Version information is requested.  */
176  if (do_version)
177    {
178      printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION);
179      /* xgettext: no-wrap */
180      printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
181This is free software; see the source for copying conditions.  There is NO\n\
182warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\
183"),
184	      "2001-2006");
185      printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
186      exit (EXIT_SUCCESS);
187    }
188
189  /* Help is requested.  */
190  if (do_help)
191    usage (EXIT_SUCCESS);
192
193  /* Test for the subprogram name.  */
194  if (optind == argc)
195    error (EXIT_FAILURE, 0, _("missing command name"));
196  sub_name = argv[optind];
197
198  /* Build argument list for the program.  */
199  sub_argc = argc - optind;
200  sub_argv = (char **) xmalloc ((sub_argc + 1) * sizeof (char *));
201  for (i = 0; i < sub_argc; i++)
202    sub_argv[i] = argv[optind + i];
203  sub_argv[i] = NULL;
204
205  /* By default, input comes from standard input.  */
206  if (input_file == NULL)
207    input_file = "-";
208
209  /* Read input file.  */
210  result = read_catalog_file (input_file, input_syntax);
211
212  if (strcmp (sub_name, "0") != 0)
213    {
214      /* Attempt to locate the program.
215	 This is an optimization, to avoid that spawn/exec searches the PATH
216	 on every call.  */
217      sub_path = find_in_path (sub_name);
218
219      /* Finish argument list for the program.  */
220      sub_argv[0] = (char *) sub_path;
221    }
222
223  exitcode = 0; /* = EXIT_SUCCESS */
224
225  /* Apply the subprogram.  */
226  process_msgdomain_list (result);
227
228  exit (exitcode);
229}
230
231
232/* Display usage information and exit.  */
233static void
234usage (int status)
235{
236  if (status != EXIT_SUCCESS)
237    fprintf (stderr, _("Try `%s --help' for more information.\n"),
238	     program_name);
239  else
240    {
241      printf (_("\
242Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\
243"), program_name);
244      printf ("\n");
245      /* xgettext: no-wrap */
246      printf (_("\
247Applies a command to all translations of a translation catalog.\n\
248The COMMAND can be any program that reads a translation from standard\n\
249input.  It is invoked once for each translation.  Its output becomes\n\
250msgexec's output.  msgexec's return code is the maximum return code\n\
251across all invocations.\n\
252"));
253      printf ("\n");
254      /* xgettext: no-wrap */
255      printf (_("\
256A special builtin command called '0' outputs the translation, followed by a\n\
257null byte.  The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\
258"));
259      printf ("\n");
260      printf (_("\
261Mandatory arguments to long options are mandatory for short options too.\n"));
262      printf ("\n");
263      printf (_("\
264Input file location:\n"));
265      printf (_("\
266  -i, --input=INPUTFILE       input PO file\n"));
267      printf (_("\
268  -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
269      printf (_("\
270If no input file is given or if it is -, standard input is read.\n"));
271      printf ("\n");
272      printf (_("\
273Input file syntax:\n"));
274      printf (_("\
275  -P, --properties-input      input file is in Java .properties syntax\n"));
276      printf (_("\
277      --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
278      printf ("\n");
279      printf (_("\
280Informative output:\n"));
281      printf (_("\
282  -h, --help                  display this help and exit\n"));
283      printf (_("\
284  -V, --version               output version information and exit\n"));
285      printf ("\n");
286      fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
287	     stdout);
288    }
289
290  exit (status);
291}
292
293
294#ifdef EINTR
295
296/* EINTR handling for close().
297   These functions can return -1/EINTR even though we don't have any
298   signal handlers set up, namely when we get interrupted via SIGSTOP.  */
299
300static inline int
301nonintr_close (int fd)
302{
303  int retval;
304
305  do
306    retval = close (fd);
307  while (retval < 0 && errno == EINTR);
308
309  return retval;
310}
311#define close nonintr_close
312
313#endif
314
315
316/* Pipe a string STR of size LEN bytes to the subprogram.
317   The byte after STR is known to be a '\0' byte.  */
318static void
319process_string (const message_ty *mp, const char *str, size_t len)
320{
321  if (strcmp (sub_name, "0") == 0)
322    {
323      /* Built-in command "0".  */
324      if (full_write (STDOUT_FILENO, str, len + 1) < len + 1)
325	error (EXIT_FAILURE, errno, _("write to stdout failed"));
326    }
327  else
328    {
329      /* General command.  */
330      char *location;
331      pid_t child;
332      int fd[1];
333      int exitstatus;
334
335      /* Set environment variables for the subprocess.  */
336      if (mp->msgctxt != NULL)
337	xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1);
338      else
339	unsetenv ("MSGEXEC_MSGCTXT");
340      xsetenv ("MSGEXEC_MSGID", mp->msgid, 1);
341      location = xasprintf ("%s:%ld", mp->pos.file_name,
342			    (long) mp->pos.line_number);
343      xsetenv ("MSGEXEC_LOCATION", location, 1);
344      free (location);
345
346      /* Open a pipe to a subprocess.  */
347      child = create_pipe_out (sub_name, sub_path, sub_argv, NULL, false, true,
348			       true, fd);
349
350      if (full_write (fd[0], str, len) < len)
351	error (EXIT_FAILURE, errno,
352	       _("write to %s subprocess failed"), sub_name);
353
354      close (fd[0]);
355
356      /* Remove zombie process from process list, and retrieve exit status.  */
357      /* FIXME: Should ignore_sigpipe be set to true here? It depends on the
358	 semantics of the subprogram...  */
359      exitstatus = wait_subprocess (child, sub_name, false, false, true, true);
360      if (exitcode < exitstatus)
361	exitcode = exitstatus;
362    }
363}
364
365
366static void
367process_message (const message_ty *mp)
368{
369  const char *msgstr = mp->msgstr;
370  size_t msgstr_len = mp->msgstr_len;
371  const char *p;
372
373  /* Process each NUL delimited substring separately.  */
374  for (p = msgstr; p < msgstr + msgstr_len; )
375    {
376      size_t length = strlen (p);
377
378      process_string (mp, p, length);
379
380      p += length + 1;
381    }
382}
383
384
385static void
386process_message_list (const message_list_ty *mlp)
387{
388  size_t j;
389
390  for (j = 0; j < mlp->nitems; j++)
391    process_message (mlp->item[j]);
392}
393
394
395static void
396process_msgdomain_list (const msgdomain_list_ty *mdlp)
397{
398  size_t k;
399
400  for (k = 0; k < mdlp->nitems; k++)
401    process_message_list (mdlp->item[k]->messages);
402}
403