191976Sjake/* Pass translations to a subprocess. 291976Sjake Copyright (C) 2001-2006 Free Software Foundation, Inc. 391976Sjake Written by Bruno Haible <haible@clisp.cons.org>, 2001. 491976Sjake 591976Sjake This program is free software; you can redistribute it and/or modify 691976Sjake it under the terms of the GNU General Public License as published by 791976Sjake the Free Software Foundation; either version 2, or (at your option) 891976Sjake any later version. 991976Sjake 1091976Sjake This program is distributed in the hope that it will be useful, 1191976Sjake but WITHOUT ANY WARRANTY; without even the implied warranty of 1291976Sjake MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 1391976Sjake GNU General Public License for more details. 1491976Sjake 1591976Sjake You should have received a copy of the GNU General Public License 1691976Sjake along with this program; if not, write to the Free Software Foundation, 1791976Sjake Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ 1891976Sjake 1991976Sjake 2091976Sjake#ifdef HAVE_CONFIG_H 2191976Sjake# include "config.h" 2291976Sjake#endif 2391976Sjake 2491976Sjake#include <errno.h> 2591976Sjake#include <getopt.h> 2691976Sjake#include <limits.h> 2791976Sjake#include <locale.h> 2891976Sjake#include <stdio.h> 2991976Sjake#include <stdlib.h> 3091976Sjake#include <string.h> 3191976Sjake#include <sys/types.h> 3291976Sjake#include <unistd.h> 3391976Sjake 3491976Sjake#include "closeout.h" 3591976Sjake#include "dir-list.h" 3691976Sjake#include "error.h" 3791976Sjake#include "xvasprintf.h" 3891976Sjake#include "error-progname.h" 3991976Sjake#include "progname.h" 4091976Sjake#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. */ 65static const char *sub_name; 66 67/* Pathname of the subprogram. */ 68static const char *sub_path; 69 70/* Argument list for the subprogram. */ 71static char **sub_argv; 72static int sub_argc; 73 74/* Maximum exit code encountered. */ 75static int exitcode; 76 77/* Long options. */ 78static 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. */ 91static void usage (int status) 92#if defined __GNUC__ && ((__GNUC__ == 2 && __GNUC_MINOR__ >= 5) || __GNUC__ > 2) 93 __attribute__ ((noreturn)) 94#endif 95; 96static void process_msgdomain_list (const msgdomain_list_ty *mdlp); 97 98 99int 100main (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\ 181This is free software; see the source for copying conditions. There is NO\n\ 182warranty; 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. */ 233static void 234usage (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 (_("\ 242Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\ 243"), program_name); 244 printf ("\n"); 245 /* xgettext: no-wrap */ 246 printf (_("\ 247Applies a command to all translations of a translation catalog.\n\ 248The COMMAND can be any program that reads a translation from standard\n\ 249input. It is invoked once for each translation. Its output becomes\n\ 250msgexec's output. msgexec's return code is the maximum return code\n\ 251across all invocations.\n\ 252")); 253 printf ("\n"); 254 /* xgettext: no-wrap */ 255 printf (_("\ 256A special builtin command called '0' outputs the translation, followed by a\n\ 257null byte. The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\ 258")); 259 printf ("\n"); 260 printf (_("\ 261Mandatory arguments to long options are mandatory for short options too.\n")); 262 printf ("\n"); 263 printf (_("\ 264Input 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 (_("\ 270If no input file is given or if it is -, standard input is read.\n")); 271 printf ("\n"); 272 printf (_("\ 273Input 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 (_("\ 280Informative 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 300static inline int 301nonintr_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. */ 318static void 319process_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 366static void 367process_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 385static void 386process_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 395static void 396process_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