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