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 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 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 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 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 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 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 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