• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* Pass translations to a subprocess.
2    Copyright (C) 2001-2020 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 <https://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 "noreturn.h"
35 #include "closeout.h"
36 #include "dir-list.h"
37 #include "error.h"
38 #include "xvasprintf.h"
39 #include "error-progname.h"
40 #include "progname.h"
41 #include "relocatable.h"
42 #include "basename-lgpl.h"
43 #include "message.h"
44 #include "read-catalog.h"
45 #include "read-po.h"
46 #include "read-properties.h"
47 #include "read-stringtable.h"
48 #include "msgl-charset.h"
49 #include "xalloc.h"
50 #include "full-write.h"
51 #include "findprog.h"
52 #include "spawn-pipe.h"
53 #include "wait-process.h"
54 #include "xsetenv.h"
55 #include "propername.h"
56 #include "gettext.h"
57 
58 #define _(str) gettext (str)
59 
60 #ifndef STDOUT_FILENO
61 # define STDOUT_FILENO 1
62 #endif
63 
64 
65 /* Name of the subprogram.  */
66 static const char *sub_name;
67 
68 /* Pathname of the subprogram.  */
69 static const char *sub_path;
70 
71 /* Argument list for the subprogram.  */
72 static char **sub_argv;
73 static int sub_argc;
74 
75 static bool newline;
76 
77 /* Maximum exit code encountered.  */
78 static int exitcode;
79 
80 /* Long options.  */
81 static const struct option long_options[] =
82 {
83   { "directory", required_argument, NULL, 'D' },
84   { "help", no_argument, NULL, 'h' },
85   { "input", required_argument, NULL, 'i' },
86   { "newline", no_argument, NULL, CHAR_MAX + 2 },
87   { "properties-input", no_argument, NULL, 'P' },
88   { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 },
89   { "version", no_argument, NULL, 'V' },
90   { NULL, 0, NULL, 0 }
91 };
92 
93 
94 /* Forward declaration of local functions.  */
95 _GL_NORETURN_FUNC static void usage (int status);
96 static void process_msgdomain_list (const msgdomain_list_ty *mdlp);
97 
98 
99 int
main(int argc,char ** argv)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   /* Set locale via LC_ALL.  */
115   setlocale (LC_ALL, "");
116 
117   /* Set the text message domain.  */
118   bindtextdomain (PACKAGE, relocate (LOCALEDIR));
119   bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
120   textdomain (PACKAGE);
121 
122   /* Ensure that write errors on stdout are detected.  */
123   atexit (close_stdout);
124 
125   /* Set default values for variables.  */
126   do_help = false;
127   do_version = false;
128   input_file = NULL;
129 
130   /* The '+' in the options string causes option parsing to terminate when
131      the first non-option, i.e. the subprogram name, is encountered.  */
132   while ((opt = getopt_long (argc, argv, "+D:hi:PV", long_options, NULL))
133          != EOF)
134     switch (opt)
135       {
136       case '\0':                /* Long option.  */
137         break;
138 
139       case 'D':
140         dir_list_append (optarg);
141         break;
142 
143       case 'h':
144         do_help = true;
145         break;
146 
147       case 'i':
148         if (input_file != NULL)
149           {
150             error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
151             usage (EXIT_FAILURE);
152           }
153         input_file = optarg;
154         break;
155 
156       case 'P':
157         input_syntax = &input_format_properties;
158         break;
159 
160       case 'V':
161         do_version = true;
162         break;
163 
164       case CHAR_MAX + 1: /* --stringtable-input */
165         input_syntax = &input_format_stringtable;
166         break;
167 
168       case CHAR_MAX + 2: /* --newline */
169         newline = true;
170         break;
171 
172       default:
173         usage (EXIT_FAILURE);
174         break;
175       }
176 
177   /* Version information is requested.  */
178   if (do_version)
179     {
180       printf ("%s (GNU %s) %s\n", last_component (program_name),
181               PACKAGE, VERSION);
182       /* xgettext: no-wrap */
183       printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
184 License GPLv3+: GNU GPL version 3 or later <%s>\n\
185 This is free software: you are free to change and redistribute it.\n\
186 There is NO WARRANTY, to the extent permitted by law.\n\
187 "),
188               "2001-2020", "https://gnu.org/licenses/gpl.html");
189       printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
190       exit (EXIT_SUCCESS);
191     }
192 
193   /* Help is requested.  */
194   if (do_help)
195     usage (EXIT_SUCCESS);
196 
197   /* Test for the subprogram name.  */
198   if (optind == argc)
199     error (EXIT_FAILURE, 0, _("missing command name"));
200   sub_name = argv[optind];
201 
202   /* Build argument list for the program.  */
203   sub_argc = argc - optind;
204   sub_argv = XNMALLOC (sub_argc + 1, char *);
205   for (i = 0; i < sub_argc; i++)
206     sub_argv[i] = argv[optind + i];
207   sub_argv[i] = NULL;
208 
209   /* By default, input comes from standard input.  */
210   if (input_file == NULL)
211     input_file = "-";
212 
213   /* Read input file.  */
214   result = read_catalog_file (input_file, input_syntax);
215 
216   if (strcmp (sub_name, "0") != 0)
217     {
218       /* Warn if the current locale is not suitable for this PO file.  */
219       compare_po_locale_charsets (result);
220 
221       /* Block SIGPIPE for this process and for the subprocesses.
222          The subprogram may have side effects (additionally to producing some
223          output), therefore if there are no readers on stdout, processing of the
224          strings must continue nevertheless.  */
225       {
226         sigset_t sigpipe_set;
227 
228         sigemptyset (&sigpipe_set);
229         sigaddset (&sigpipe_set, SIGPIPE);
230         sigprocmask (SIG_UNBLOCK, &sigpipe_set, NULL);
231       }
232 
233       /* Attempt to locate the program.
234          This is an optimization, to avoid that spawn/exec searches the PATH
235          on every call.  */
236       sub_path = find_in_path (sub_name);
237 
238       /* Finish argument list for the program.  */
239       sub_argv[0] = (char *) sub_path;
240     }
241 
242   exitcode = 0; /* = EXIT_SUCCESS */
243 
244   /* Apply the subprogram.  */
245   process_msgdomain_list (result);
246 
247   exit (exitcode);
248 }
249 
250 
251 /* Display usage information and exit.  */
252 static void
usage(int status)253 usage (int status)
254 {
255   if (status != EXIT_SUCCESS)
256     fprintf (stderr, _("Try '%s --help' for more information.\n"),
257              program_name);
258   else
259     {
260       printf (_("\
261 Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\
262 "), program_name);
263       printf ("\n");
264       /* xgettext: no-wrap */
265       printf (_("\
266 Applies a command to all translations of a translation catalog.\n\
267 The COMMAND can be any program that reads a translation from standard\n\
268 input.  It is invoked once for each translation.  Its output becomes\n\
269 msgexec's output.  msgexec's return code is the maximum return code\n\
270 across all invocations.\n\
271 "));
272       printf ("\n");
273       /* xgettext: no-wrap */
274       printf (_("\
275 A special builtin command called '0' outputs the translation, followed by a\n\
276 null byte.  The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\
277 "));
278       printf ("\n");
279       printf (_("\
280 Command input:\n"));
281       printf (_("\
282   --newline                   add newline at the end of input\n"));
283       printf ("\n");
284       printf (_("\
285 Mandatory arguments to long options are mandatory for short options too.\n"));
286       printf ("\n");
287       printf (_("\
288 Input file location:\n"));
289       printf (_("\
290   -i, --input=INPUTFILE       input PO file\n"));
291       printf (_("\
292   -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
293       printf (_("\
294 If no input file is given or if it is -, standard input is read.\n"));
295       printf ("\n");
296       printf (_("\
297 Input file syntax:\n"));
298       printf (_("\
299   -P, --properties-input      input file is in Java .properties syntax\n"));
300       printf (_("\
301       --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
302       printf ("\n");
303       printf (_("\
304 Informative output:\n"));
305       printf (_("\
306   -h, --help                  display this help and exit\n"));
307       printf (_("\
308   -V, --version               output version information and exit\n"));
309       printf ("\n");
310       /* TRANSLATORS: The first placeholder is the web address of the Savannah
311          project of this package.  The second placeholder is the bug-reporting
312          email address for this package.  Please add _another line_ saying
313          "Report translation bugs to <...>\n" with the address for translation
314          bugs (typically your translation team's web or email address).  */
315       printf(_("\
316 Report bugs in the bug tracker at <%s>\n\
317 or by email to <%s>.\n"),
318              "https://savannah.gnu.org/projects/gettext",
319              "bug-gettext@gnu.org");
320     }
321 
322   exit (status);
323 }
324 
325 
326 #ifdef EINTR
327 
328 /* EINTR handling for close().
329    These functions can return -1/EINTR even though we don't have any
330    signal handlers set up, namely when we get interrupted via SIGSTOP.  */
331 
332 static inline int
nonintr_close(int fd)333 nonintr_close (int fd)
334 {
335   int retval;
336 
337   do
338     retval = close (fd);
339   while (retval < 0 && errno == EINTR);
340 
341   return retval;
342 }
343 #undef close
344 #define close nonintr_close
345 
346 #endif
347 
348 
349 /* Pipe a string STR of size LEN bytes to the subprogram.
350    The byte after STR is known to be a '\0' byte.  */
351 static void
process_string(const message_ty * mp,const char * str,size_t len)352 process_string (const message_ty *mp, const char *str, size_t len)
353 {
354   if (strcmp (sub_name, "0") == 0)
355     {
356       /* Built-in command "0".  */
357       if (full_write (STDOUT_FILENO, str, len + 1) < len + 1)
358         error (EXIT_FAILURE, errno, _("write to stdout failed"));
359     }
360   else
361     {
362       /* General command.  */
363       char *location;
364       pid_t child;
365       int fd[1];
366       void (*orig_sigpipe_handler)(int);
367       int exitstatus;
368       char *newstr;
369 
370       /* Set environment variables for the subprocess.
371          Note: These environment variables, especially MSGEXEC_MSGCTXT and
372          MSGEXEC_MSGID, may contain non-ASCII characters.  The subprocess
373          may not interpret these values correctly if the locale encoding is
374          different from the PO file's encoding.  We warned about this situation,
375          above.
376          On Unix, this problem is often harmless.  On Windows, however, - both
377          native Windows and Cygwin - the values of environment variables *must*
378          be in the encoding that is the value of GetACP(), because the system
379          may convert the environment from char** to wchar_t** before spawning
380          the subprocess and back from wchar_t** to char** in the subprocess,
381          and it does so using the GetACP() codepage.  */
382       if (mp->msgctxt != NULL)
383         xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1);
384       else
385         unsetenv ("MSGEXEC_MSGCTXT");
386       xsetenv ("MSGEXEC_MSGID", mp->msgid, 1);
387       if (mp->msgid_plural != NULL)
388         xsetenv ("MSGEXEC_MSGID_PLURAL", mp->msgid_plural, 1);
389       else
390         unsetenv ("MSGEXEC_MSGID_PLURAL");
391       location = xasprintf ("%s:%ld", mp->pos.file_name,
392                             (long) mp->pos.line_number);
393       xsetenv ("MSGEXEC_LOCATION", location, 1);
394       free (location);
395       if (mp->prev_msgctxt != NULL)
396         xsetenv ("MSGEXEC_PREV_MSGCTXT", mp->prev_msgctxt, 1);
397       else
398         unsetenv ("MSGEXEC_PREV_MSGCTXT");
399       if (mp->prev_msgid != NULL)
400         xsetenv ("MSGEXEC_PREV_MSGID", mp->prev_msgid, 1);
401       else
402         unsetenv ("MSGEXEC_PREV_MSGID");
403       if (mp->prev_msgid_plural != NULL)
404         xsetenv ("MSGEXEC_PREV_MSGID_PLURAL", mp->prev_msgid_plural, 1);
405       else
406         unsetenv ("MSGEXEC_PREV_MSGID_PLURAL");
407 
408       /* Open a pipe to a subprocess.  */
409       child = create_pipe_out (sub_name, sub_path, sub_argv, NULL, false, true,
410                                true, fd);
411 
412       /* Ignore SIGPIPE here.  We don't care if the subprocesses terminates
413          successfully without having read all of the input that we feed it.  */
414       orig_sigpipe_handler = signal (SIGPIPE, SIG_IGN);
415 
416       if (newline)
417         {
418           newstr = XNMALLOC (len + 1, char);
419           memcpy (newstr, str, len);
420           newstr[len++] = '\n';
421         }
422       else
423         newstr = (char *) str;
424 
425       if (full_write (fd[0], newstr, len) < len)
426         if (errno != EPIPE)
427           error (EXIT_FAILURE, errno,
428                  _("write to %s subprocess failed"), sub_name);
429 
430       if (newstr != str)
431         free (newstr);
432 
433       close (fd[0]);
434 
435       signal (SIGPIPE, orig_sigpipe_handler);
436 
437       /* Remove zombie process from process list, and retrieve exit status.  */
438       /* FIXME: Should ignore_sigpipe be set to true here? It depends on the
439          semantics of the subprogram...  */
440       exitstatus =
441         wait_subprocess (child, sub_name, false, false, true, true, NULL);
442       if (exitcode < exitstatus)
443         exitcode = exitstatus;
444     }
445 }
446 
447 
448 static void
process_message(const message_ty * mp)449 process_message (const message_ty *mp)
450 {
451   const char *msgstr = mp->msgstr;
452   size_t msgstr_len = mp->msgstr_len;
453   const char *p;
454   size_t k;
455 
456   /* Process each NUL delimited substring separately.  */
457   for (p = msgstr, k = 0; p < msgstr + msgstr_len; k++)
458     {
459       size_t length = strlen (p);
460 
461       if (mp->msgid_plural != NULL)
462         {
463           char *plural_form_string = xasprintf ("%lu", (unsigned long) k);
464 
465           xsetenv ("MSGEXEC_PLURAL_FORM", plural_form_string, 1);
466           free (plural_form_string);
467         }
468       else
469         unsetenv ("MSGEXEC_PLURAL_FORM");
470       process_string (mp, p, length);
471 
472       p += length + 1;
473     }
474 }
475 
476 
477 static void
process_message_list(const message_list_ty * mlp)478 process_message_list (const message_list_ty *mlp)
479 {
480   size_t j;
481 
482   for (j = 0; j < mlp->nitems; j++)
483     process_message (mlp->item[j]);
484 }
485 
486 
487 static void
process_msgdomain_list(const msgdomain_list_ty * mdlp)488 process_msgdomain_list (const msgdomain_list_ty *mdlp)
489 {
490   size_t k;
491 
492   for (k = 0; k < mdlp->nitems; k++)
493     process_message_list (mdlp->item[k]->messages);
494 }
495