1 /* Pass translations to a subprocess.
2 Copyright (C) 2001-2006 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 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
18
19
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23
24 #include <errno.h>
25 #include <getopt.h>
26 #include <limits.h>
27 #include <locale.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 "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. */
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
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 #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 This is free software; see the source for copying conditions. There is NO\n\
182 warranty; 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. */
233 static void
usage(int status)234 usage (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 (_("\
242 Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\
243 "), program_name);
244 printf ("\n");
245 /* xgettext: no-wrap */
246 printf (_("\
247 Applies a command to all translations of a translation catalog.\n\
248 The COMMAND can be any program that reads a translation from standard\n\
249 input. It is invoked once for each translation. Its output becomes\n\
250 msgexec's output. msgexec's return code is the maximum return code\n\
251 across all invocations.\n\
252 "));
253 printf ("\n");
254 /* xgettext: no-wrap */
255 printf (_("\
256 A special builtin command called '0' outputs the translation, followed by a\n\
257 null byte. The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\
258 "));
259 printf ("\n");
260 printf (_("\
261 Mandatory arguments to long options are mandatory for short options too.\n"));
262 printf ("\n");
263 printf (_("\
264 Input 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 (_("\
270 If no input file is given or if it is -, standard input is read.\n"));
271 printf ("\n");
272 printf (_("\
273 Input 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 (_("\
280 Informative 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
300 static inline int
nonintr_close(int fd)301 nonintr_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. */
318 static void
process_string(const message_ty * mp,const char * str,size_t len)319 process_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
366 static void
process_message(const message_ty * mp)367 process_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
385 static void
process_message_list(const message_list_ty * mlp)386 process_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
395 static void
process_msgdomain_list(const msgdomain_list_ty * mdlp)396 process_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