1/* Pass translations to a subprocess. 2 Copyright (C) 2001-2007 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 <http://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 <stdio.h> 28#include <stdlib.h> 29#include <string.h> 30#include <sys/types.h> 31#include <unistd.h> 32 33#include "closeout.h" 34#include "dir-list.h" 35#include "error.h" 36#include "xvasprintf.h" 37#include "error-progname.h" 38#include "progname.h" 39#include "relocatable.h" 40#include "basename.h" 41#include "message.h" 42#include "read-catalog.h" 43#include "read-po.h" 44#include "read-properties.h" 45#include "read-stringtable.h" 46#include "xalloc.h" 47#include "full-write.h" 48#include "findprog.h" 49#include "pipe.h" 50#include "wait-process.h" 51#include "xsetenv.h" 52#include "propername.h" 53#include "gettext.h" 54 55#define _(str) gettext (str) 56 57#ifndef STDOUT_FILENO 58# define STDOUT_FILENO 1 59#endif 60 61 62/* Name of the subprogram. */ 63static const char *sub_name; 64 65/* Pathname of the subprogram. */ 66static const char *sub_path; 67 68/* Argument list for the subprogram. */ 69static char **sub_argv; 70static int sub_argc; 71 72/* Maximum exit code encountered. */ 73static int exitcode; 74 75/* Long options. */ 76static const struct option long_options[] = 77{ 78 { "directory", required_argument, NULL, 'D' }, 79 { "help", no_argument, NULL, 'h' }, 80 { "input", required_argument, NULL, 'i' }, 81 { "properties-input", no_argument, NULL, 'P' }, 82 { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 }, 83 { "version", no_argument, NULL, 'V' }, 84 { NULL, 0, NULL, 0 } 85}; 86 87 88/* Forward declaration of local functions. */ 89static void usage (int status) 90#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) 91 __attribute__ ((noreturn)) 92#endif 93; 94static void process_msgdomain_list (const msgdomain_list_ty *mdlp); 95 96 97int 98main (int argc, char **argv) 99{ 100 int opt; 101 bool do_help; 102 bool do_version; 103 const char *input_file; 104 msgdomain_list_ty *result; 105 catalog_input_format_ty input_syntax = &input_format_po; 106 size_t i; 107 108 /* Set program name for messages. */ 109 set_program_name (argv[0]); 110 error_print_progname = maybe_print_progname; 111 112#ifdef HAVE_SETLOCALE 113 /* Set locale via LC_ALL. */ 114 setlocale (LC_ALL, ""); 115#endif 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 default: 169 usage (EXIT_FAILURE); 170 break; 171 } 172 173 /* Version information is requested. */ 174 if (do_version) 175 { 176 printf ("%s (GNU %s) %s\n", basename (program_name), PACKAGE, VERSION); 177 /* xgettext: no-wrap */ 178 printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\ 179License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>\n\ 180This is free software: you are free to change and redistribute it.\n\ 181There is NO WARRANTY, to the extent permitted by law.\n\ 182"), 183 "2001-2007"); 184 printf (_("Written by %s.\n"), proper_name ("Bruno Haible")); 185 exit (EXIT_SUCCESS); 186 } 187 188 /* Help is requested. */ 189 if (do_help) 190 usage (EXIT_SUCCESS); 191 192 /* Test for the subprogram name. */ 193 if (optind == argc) 194 error (EXIT_FAILURE, 0, _("missing command name")); 195 sub_name = argv[optind]; 196 197 /* Build argument list for the program. */ 198 sub_argc = argc - optind; 199 sub_argv = XNMALLOC (sub_argc + 1, char *); 200 for (i = 0; i < sub_argc; i++) 201 sub_argv[i] = argv[optind + i]; 202 sub_argv[i] = NULL; 203 204 /* By default, input comes from standard input. */ 205 if (input_file == NULL) 206 input_file = "-"; 207 208 /* Read input file. */ 209 result = read_catalog_file (input_file, input_syntax); 210 211 if (strcmp (sub_name, "0") != 0) 212 { 213 /* Attempt to locate the program. 214 This is an optimization, to avoid that spawn/exec searches the PATH 215 on every call. */ 216 sub_path = find_in_path (sub_name); 217 218 /* Finish argument list for the program. */ 219 sub_argv[0] = (char *) sub_path; 220 } 221 222 exitcode = 0; /* = EXIT_SUCCESS */ 223 224 /* Apply the subprogram. */ 225 process_msgdomain_list (result); 226 227 exit (exitcode); 228} 229 230 231/* Display usage information and exit. */ 232static void 233usage (int status) 234{ 235 if (status != EXIT_SUCCESS) 236 fprintf (stderr, _("Try `%s --help' for more information.\n"), 237 program_name); 238 else 239 { 240 printf (_("\ 241Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\ 242"), program_name); 243 printf ("\n"); 244 /* xgettext: no-wrap */ 245 printf (_("\ 246Applies a command to all translations of a translation catalog.\n\ 247The COMMAND can be any program that reads a translation from standard\n\ 248input. It is invoked once for each translation. Its output becomes\n\ 249msgexec's output. msgexec's return code is the maximum return code\n\ 250across all invocations.\n\ 251")); 252 printf ("\n"); 253 /* xgettext: no-wrap */ 254 printf (_("\ 255A special builtin command called '0' outputs the translation, followed by a\n\ 256null byte. The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\ 257")); 258 printf ("\n"); 259 printf (_("\ 260Mandatory arguments to long options are mandatory for short options too.\n")); 261 printf ("\n"); 262 printf (_("\ 263Input file location:\n")); 264 printf (_("\ 265 -i, --input=INPUTFILE input PO file\n")); 266 printf (_("\ 267 -D, --directory=DIRECTORY add DIRECTORY to list for input files search\n")); 268 printf (_("\ 269If no input file is given or if it is -, standard input is read.\n")); 270 printf ("\n"); 271 printf (_("\ 272Input file syntax:\n")); 273 printf (_("\ 274 -P, --properties-input input file is in Java .properties syntax\n")); 275 printf (_("\ 276 --stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n")); 277 printf ("\n"); 278 printf (_("\ 279Informative output:\n")); 280 printf (_("\ 281 -h, --help display this help and exit\n")); 282 printf (_("\ 283 -V, --version output version information and exit\n")); 284 printf ("\n"); 285 /* TRANSLATORS: The placeholder indicates the bug-reporting address 286 for this package. Please add _another line_ saying 287 "Report translation bugs to <...>\n" with the address for translation 288 bugs (typically your translation team's web or email address). */ 289 fputs (_("Report bugs to <bug-gnu-gettext@gnu.org>.\n"), 290 stdout); 291 } 292 293 exit (status); 294} 295 296 297#ifdef EINTR 298 299/* EINTR handling for close(). 300 These functions can return -1/EINTR even though we don't have any 301 signal handlers set up, namely when we get interrupted via SIGSTOP. */ 302 303static inline int 304nonintr_close (int fd) 305{ 306 int retval; 307 308 do 309 retval = close (fd); 310 while (retval < 0 && errno == EINTR); 311 312 return retval; 313} 314#define close nonintr_close 315 316#endif 317 318 319/* Pipe a string STR of size LEN bytes to the subprogram. 320 The byte after STR is known to be a '\0' byte. */ 321static void 322process_string (const message_ty *mp, const char *str, size_t len) 323{ 324 if (strcmp (sub_name, "0") == 0) 325 { 326 /* Built-in command "0". */ 327 if (full_write (STDOUT_FILENO, str, len + 1) < len + 1) 328 error (EXIT_FAILURE, errno, _("write to stdout failed")); 329 } 330 else 331 { 332 /* General command. */ 333 char *location; 334 pid_t child; 335 int fd[1]; 336 int exitstatus; 337 338 /* Set environment variables for the subprocess. */ 339 if (mp->msgctxt != NULL) 340 xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1); 341 else 342 unsetenv ("MSGEXEC_MSGCTXT"); 343 xsetenv ("MSGEXEC_MSGID", mp->msgid, 1); 344 location = xasprintf ("%s:%ld", mp->pos.file_name, 345 (long) mp->pos.line_number); 346 xsetenv ("MSGEXEC_LOCATION", location, 1); 347 free (location); 348 349 /* Open a pipe to a subprocess. */ 350 child = create_pipe_out (sub_name, sub_path, sub_argv, NULL, false, true, 351 true, fd); 352 353 if (full_write (fd[0], str, len) < len) 354 error (EXIT_FAILURE, errno, 355 _("write to %s subprocess failed"), sub_name); 356 357 close (fd[0]); 358 359 /* Remove zombie process from process list, and retrieve exit status. */ 360 /* FIXME: Should ignore_sigpipe be set to true here? It depends on the 361 semantics of the subprogram... */ 362 exitstatus = wait_subprocess (child, sub_name, false, false, true, true); 363 if (exitcode < exitstatus) 364 exitcode = exitstatus; 365 } 366} 367 368 369static void 370process_message (const message_ty *mp) 371{ 372 const char *msgstr = mp->msgstr; 373 size_t msgstr_len = mp->msgstr_len; 374 const char *p; 375 376 /* Process each NUL delimited substring separately. */ 377 for (p = msgstr; p < msgstr + msgstr_len; ) 378 { 379 size_t length = strlen (p); 380 381 process_string (mp, p, length); 382 383 p += length + 1; 384 } 385} 386 387 388static void 389process_message_list (const message_list_ty *mlp) 390{ 391 size_t j; 392 393 for (j = 0; j < mlp->nitems; j++) 394 process_message (mlp->item[j]); 395} 396 397 398static void 399process_msgdomain_list (const msgdomain_list_ty *mdlp) 400{ 401 size_t k; 402 403 for (k = 0; k < mdlp->nitems; k++) 404 process_message_list (mdlp->item[k]->messages); 405} 406