Imported Upstream version 0.18.3.2
[platform/upstream/gettext.git] / gettext-tools / src / msgexec.c
1 /* Pass translations to a subprocess.
2    Copyright (C) 2001-2012 Free Software Foundation, Inc.
3    Written by Bruno Haible <haible@clisp.cons.org>, 2001.
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 3 of the License, or
8    (at your option) 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, see <http://www.gnu.org/licenses/>.  */
17
18
19 #ifdef HAVE_CONFIG_H
20 # include "config.h"
21 #endif
22
23 #include <errno.h>
24 #include <getopt.h>
25 #include <limits.h>
26 #include <locale.h>
27 #include <signal.h>
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <sys/types.h>
32 #include <unistd.h>
33
34 #include "closeout.h"
35 #include "dir-list.h"
36 #include "error.h"
37 #include "xvasprintf.h"
38 #include "error-progname.h"
39 #include "progname.h"
40 #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 "msgl-charset.h"
48 #include "xalloc.h"
49 #include "full-write.h"
50 #include "findprog.h"
51 #include "spawn-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.  */
65 static const char *sub_name;
66
67 /* Pathname of the subprogram.  */
68 static const char *sub_path;
69
70 /* Argument list for the subprogram.  */
71 static char **sub_argv;
72 static int sub_argc;
73
74 /* Maximum exit code encountered.  */
75 static int exitcode;
76
77 /* Long options.  */
78 static 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.  */
91 static void usage (int status)
92 #if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2)
93         __attribute__ ((noreturn))
94 #endif
95 ;
96 static void process_msgdomain_list (const msgdomain_list_ty *mdlp);
97
98
99 int
100 main (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\
181 License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\
182 This is free software: you are free to change and redistribute it.\n\
183 There is NO WARRANTY, to the extent permitted by law.\n\
184 "),
185               "2001-2010");
186       printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
187       exit (EXIT_SUCCESS);
188     }
189
190   /* Help is requested.  */
191   if (do_help)
192     usage (EXIT_SUCCESS);
193
194   /* Test for the subprogram name.  */
195   if (optind == argc)
196     error (EXIT_FAILURE, 0, _("missing command name"));
197   sub_name = argv[optind];
198
199   /* Build argument list for the program.  */
200   sub_argc = argc - optind;
201   sub_argv = XNMALLOC (sub_argc + 1, char *);
202   for (i = 0; i < sub_argc; i++)
203     sub_argv[i] = argv[optind + i];
204   sub_argv[i] = NULL;
205
206   /* By default, input comes from standard input.  */
207   if (input_file == NULL)
208     input_file = "-";
209
210   /* Read input file.  */
211   result = read_catalog_file (input_file, input_syntax);
212
213   if (strcmp (sub_name, "0") != 0)
214     {
215       /* Warn if the current locale is not suitable for this PO file.  */
216       compare_po_locale_charsets (result);
217
218       /* Block SIGPIPE for this process and for the subprocesses.
219          The subprogram may have side effects (additionally to producing some
220          output), therefore if there are no readers on stdout, processing of the
221          strings must continue nevertheless.  */
222       {
223         sigset_t sigpipe_set;
224
225         sigemptyset (&sigpipe_set);
226         sigaddset (&sigpipe_set, SIGPIPE);
227         sigprocmask (SIG_UNBLOCK, &sigpipe_set, NULL);
228       }
229
230       /* Attempt to locate the program.
231          This is an optimization, to avoid that spawn/exec searches the PATH
232          on every call.  */
233       sub_path = find_in_path (sub_name);
234
235       /* Finish argument list for the program.  */
236       sub_argv[0] = (char *) sub_path;
237     }
238
239   exitcode = 0; /* = EXIT_SUCCESS */
240
241   /* Apply the subprogram.  */
242   process_msgdomain_list (result);
243
244   exit (exitcode);
245 }
246
247
248 /* Display usage information and exit.  */
249 static void
250 usage (int status)
251 {
252   if (status != EXIT_SUCCESS)
253     fprintf (stderr, _("Try '%s --help' for more information.\n"),
254              program_name);
255   else
256     {
257       printf (_("\
258 Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\
259 "), program_name);
260       printf ("\n");
261       /* xgettext: no-wrap */
262       printf (_("\
263 Applies a command to all translations of a translation catalog.\n\
264 The COMMAND can be any program that reads a translation from standard\n\
265 input.  It is invoked once for each translation.  Its output becomes\n\
266 msgexec's output.  msgexec's return code is the maximum return code\n\
267 across all invocations.\n\
268 "));
269       printf ("\n");
270       /* xgettext: no-wrap */
271       printf (_("\
272 A special builtin command called '0' outputs the translation, followed by a\n\
273 null byte.  The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\
274 "));
275       printf ("\n");
276       printf (_("\
277 Mandatory arguments to long options are mandatory for short options too.\n"));
278       printf ("\n");
279       printf (_("\
280 Input file location:\n"));
281       printf (_("\
282   -i, --input=INPUTFILE       input PO file\n"));
283       printf (_("\
284   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
285       printf (_("\
286 If no input file is given or if it is -, standard input is read.\n"));
287       printf ("\n");
288       printf (_("\
289 Input file syntax:\n"));
290       printf (_("\
291   -P, --properties-input      input file is in Java .properties syntax\n"));
292       printf (_("\
293       --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
294       printf ("\n");
295       printf (_("\
296 Informative output:\n"));
297       printf (_("\
298   -h, --help                  display this help and exit\n"));
299       printf (_("\
300   -V, --version               output version information and exit\n"));
301       printf ("\n");
302       /* TRANSLATORS: The placeholder indicates the bug-reporting address
303          for this package.  Please add _another line_ saying
304          "Report translation bugs to <...>\n" with the address for translation
305          bugs (typically your translation team's web or email address).  */
306       fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"),
307              stdout);
308     }
309
310   exit (status);
311 }
312
313
314 #ifdef EINTR
315
316 /* EINTR handling for close().
317    These functions can return -1/EINTR even though we don't have any
318    signal handlers set up, namely when we get interrupted via SIGSTOP.  */
319
320 static inline int
321 nonintr_close (int fd)
322 {
323   int retval;
324
325   do
326     retval = close (fd);
327   while (retval < 0 && errno == EINTR);
328
329   return retval;
330 }
331 #define close nonintr_close
332
333 #endif
334
335
336 /* Pipe a string STR of size LEN bytes to the subprogram.
337    The byte after STR is known to be a '\0' byte.  */
338 static void
339 process_string (const message_ty *mp, const char *str, size_t len)
340 {
341   if (strcmp (sub_name, "0") == 0)
342     {
343       /* Built-in command "0".  */
344       if (full_write (STDOUT_FILENO, str, len + 1) < len + 1)
345         error (EXIT_FAILURE, errno, _("write to stdout failed"));
346     }
347   else
348     {
349       /* General command.  */
350       char *location;
351       pid_t child;
352       int fd[1];
353       void (*orig_sigpipe_handler)(int);
354       int exitstatus;
355
356       /* Set environment variables for the subprocess.
357          Note: These environment variables, especially MSGEXEC_MSGCTXT and
358          MSGEXEC_MSGCTXT, may contain non-ASCII characters.  The subprocess
359          may not interpret these values correctly if the locale encoding is
360          different from the PO file's encoding.  We want about this situation,
361          above.
362          On Unix, this problem is often harmless.  On Windows, however, - both
363          native Windows and Cygwin - the values of environment variables *must*
364          be in the encoding that is the value of GetACP(), because the system
365          may convert the environment from char** to wchar_t** before spawning
366          the subprocess and back from wchar_t** to char** in the subprocess,
367          and it does so using the GetACP() codepage.  */
368       if (mp->msgctxt != NULL)
369         xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1);
370       else
371         unsetenv ("MSGEXEC_MSGCTXT");
372       xsetenv ("MSGEXEC_MSGID", mp->msgid, 1);
373       location = xasprintf ("%s:%ld", mp->pos.file_name,
374                             (long) mp->pos.line_number);
375       xsetenv ("MSGEXEC_LOCATION", location, 1);
376       free (location);
377
378       /* Open a pipe to a subprocess.  */
379       child = create_pipe_out (sub_name, sub_path, sub_argv, NULL, false, true,
380                                true, fd);
381
382       /* Ignore SIGPIPE here.  We don't care if the subprocesses terminates
383          successfully without having read all of the input that we feed it.  */
384       orig_sigpipe_handler = signal (SIGPIPE, SIG_IGN);
385
386       if (full_write (fd[0], str, len) < len)
387         if (errno != EPIPE)
388           error (EXIT_FAILURE, errno,
389                  _("write to %s subprocess failed"), sub_name);
390
391       close (fd[0]);
392
393       signal (SIGPIPE, orig_sigpipe_handler);
394
395       /* Remove zombie process from process list, and retrieve exit status.  */
396       /* FIXME: Should ignore_sigpipe be set to true here? It depends on the
397          semantics of the subprogram...  */
398       exitstatus =
399         wait_subprocess (child, sub_name, false, false, true, true, NULL);
400       if (exitcode < exitstatus)
401         exitcode = exitstatus;
402     }
403 }
404
405
406 static void
407 process_message (const message_ty *mp)
408 {
409   const char *msgstr = mp->msgstr;
410   size_t msgstr_len = mp->msgstr_len;
411   const char *p;
412
413   /* Process each NUL delimited substring separately.  */
414   for (p = msgstr; p < msgstr + msgstr_len; )
415     {
416       size_t length = strlen (p);
417
418       process_string (mp, p, length);
419
420       p += length + 1;
421     }
422 }
423
424
425 static void
426 process_message_list (const message_list_ty *mlp)
427 {
428   size_t j;
429
430   for (j = 0; j < mlp->nitems; j++)
431     process_message (mlp->item[j]);
432 }
433
434
435 static void
436 process_msgdomain_list (const msgdomain_list_ty *mdlp)
437 {
438   size_t k;
439
440   for (k = 0; k < mdlp->nitems; k++)
441     process_message_list (mdlp->item[k]->messages);
442 }