xref: /netbsd-src/external/gpl2/gettext/dist/gettext-tools/src/msgexec.c (revision 946379e7b37692fc43f68eb0d1c10daa0a7f3b6c)
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