(root)/
gettext-0.22.4/
gettext-tools/
src/
msgexec.c
       1  /* Pass translations to a subprocess.
       2     Copyright (C) 2001-2023 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 <https://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 <signal.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 "noreturn.h"
      35  #include "closeout.h"
      36  #include "dir-list.h"
      37  #include "error.h"
      38  #include "xvasprintf.h"
      39  #include "error-progname.h"
      40  #include "progname.h"
      41  #include "relocatable.h"
      42  #include "basename-lgpl.h"
      43  #include "message.h"
      44  #include "read-catalog.h"
      45  #include "read-po.h"
      46  #include "read-properties.h"
      47  #include "read-stringtable.h"
      48  #include "msgl-charset.h"
      49  #include "xalloc.h"
      50  #include "full-write.h"
      51  #include "findprog.h"
      52  #include "spawn-pipe.h"
      53  #include "wait-process.h"
      54  #include "xsetenv.h"
      55  #include "propername.h"
      56  #include "gettext.h"
      57  
      58  #define _(str) gettext (str)
      59  
      60  #ifndef STDOUT_FILENO
      61  # define STDOUT_FILENO 1
      62  #endif
      63  
      64  
      65  /* Name of the subprogram.  */
      66  static const char *sub_name;
      67  
      68  /* Pathname of the subprogram.  */
      69  static const char *sub_path;
      70  
      71  /* Argument list for the subprogram.  */
      72  static const char **sub_argv;
      73  static int sub_argc;
      74  
      75  static bool newline;
      76  
      77  /* Maximum exit code encountered.  */
      78  static int exitcode;
      79  
      80  /* Long options.  */
      81  static const struct option long_options[] =
      82  {
      83    { "directory", required_argument, NULL, 'D' },
      84    { "help", no_argument, NULL, 'h' },
      85    { "input", required_argument, NULL, 'i' },
      86    { "newline", no_argument, NULL, CHAR_MAX + 2 },
      87    { "properties-input", no_argument, NULL, 'P' },
      88    { "stringtable-input", no_argument, NULL, CHAR_MAX + 1 },
      89    { "version", no_argument, NULL, 'V' },
      90    { NULL, 0, NULL, 0 }
      91  };
      92  
      93  
      94  /* Forward declaration of local functions.  */
      95  _GL_NORETURN_FUNC static void usage (int status);
      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    /* Set locale via LC_ALL.  */
     115    setlocale (LC_ALL, "");
     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        case CHAR_MAX + 2: /* --newline */
     169          newline = true;
     170          break;
     171  
     172        default:
     173          usage (EXIT_FAILURE);
     174          break;
     175        }
     176  
     177    /* Version information is requested.  */
     178    if (do_version)
     179      {
     180        printf ("%s (GNU %s) %s\n", last_component (program_name),
     181                PACKAGE, VERSION);
     182        /* xgettext: no-wrap */
     183        printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
     184  License GPLv3+: GNU GPL version 3 or later <%s>\n\
     185  This is free software: you are free to change and redistribute it.\n\
     186  There is NO WARRANTY, to the extent permitted by law.\n\
     187  "),
     188                "2001-2023", "https://gnu.org/licenses/gpl.html");
     189        printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
     190        exit (EXIT_SUCCESS);
     191      }
     192  
     193    /* Help is requested.  */
     194    if (do_help)
     195      usage (EXIT_SUCCESS);
     196  
     197    /* Test for the subprogram name.  */
     198    if (optind == argc)
     199      error (EXIT_FAILURE, 0, _("missing command name"));
     200    sub_name = argv[optind];
     201  
     202    /* Build argument list for the program.  */
     203    sub_argc = argc - optind;
     204    sub_argv = XNMALLOC (sub_argc + 1, const char *);
     205    for (i = 0; i < sub_argc; i++)
     206      sub_argv[i] = argv[optind + i];
     207    sub_argv[i] = NULL;
     208  
     209    /* By default, input comes from standard input.  */
     210    if (input_file == NULL)
     211      input_file = "-";
     212  
     213    /* Read input file.  */
     214    result = read_catalog_file (input_file, input_syntax);
     215  
     216    if (strcmp (sub_name, "0") != 0)
     217      {
     218        /* Warn if the current locale is not suitable for this PO file.  */
     219        compare_po_locale_charsets (result);
     220  
     221        /* Block SIGPIPE for this process and for the subprocesses.
     222           The subprogram may have side effects (additionally to producing some
     223           output), therefore if there are no readers on stdout, processing of the
     224           strings must continue nevertheless.  */
     225        {
     226          sigset_t sigpipe_set;
     227  
     228          sigemptyset (&sigpipe_set);
     229          sigaddset (&sigpipe_set, SIGPIPE);
     230          sigprocmask (SIG_UNBLOCK, &sigpipe_set, NULL);
     231        }
     232  
     233        /* Attempt to locate the program.
     234           This is an optimization, to avoid that spawn/exec searches the PATH
     235           on every call.  */
     236        sub_path = find_in_path (sub_name);
     237  
     238        /* Finish argument list for the program.  */
     239        sub_argv[0] = sub_path;
     240      }
     241  
     242    exitcode = 0; /* = EXIT_SUCCESS */
     243  
     244    /* Apply the subprogram.  */
     245    process_msgdomain_list (result);
     246  
     247    exit (exitcode);
     248  }
     249  
     250  
     251  /* Display usage information and exit.  */
     252  static void
     253  usage (int status)
     254  {
     255    if (status != EXIT_SUCCESS)
     256      fprintf (stderr, _("Try '%s --help' for more information.\n"),
     257               program_name);
     258    else
     259      {
     260        printf (_("\
     261  Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\
     262  "), program_name);
     263        printf ("\n");
     264        /* xgettext: no-wrap */
     265        printf (_("\
     266  Applies a command to all translations of a translation catalog.\n\
     267  The COMMAND can be any program that reads a translation from standard\n\
     268  input.  It is invoked once for each translation.  Its output becomes\n\
     269  msgexec's output.  msgexec's return code is the maximum return code\n\
     270  across all invocations.\n\
     271  "));
     272        printf ("\n");
     273        /* xgettext: no-wrap */
     274        printf (_("\
     275  A special builtin command called '0' outputs the translation, followed by a\n\
     276  null byte.  The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\
     277  "));
     278        printf ("\n");
     279        printf (_("\
     280  Command input:\n"));
     281        printf (_("\
     282    --newline                   add newline at the end of input\n"));
     283        printf ("\n");
     284        printf (_("\
     285  Mandatory arguments to long options are mandatory for short options too.\n"));
     286        printf ("\n");
     287        printf (_("\
     288  Input file location:\n"));
     289        printf (_("\
     290    -i, --input=INPUTFILE       input PO file\n"));
     291        printf (_("\
     292    -D, --directory=DIRECTORY   add DIRECTORY to list for input files search\n"));
     293        printf (_("\
     294  If no input file is given or if it is -, standard input is read.\n"));
     295        printf ("\n");
     296        printf (_("\
     297  Input file syntax:\n"));
     298        printf (_("\
     299    -P, --properties-input      input file is in Java .properties syntax\n"));
     300        printf (_("\
     301        --stringtable-input     input file is in NeXTstep/GNUstep .strings syntax\n"));
     302        printf ("\n");
     303        printf (_("\
     304  Informative output:\n"));
     305        printf (_("\
     306    -h, --help                  display this help and exit\n"));
     307        printf (_("\
     308    -V, --version               output version information and exit\n"));
     309        printf ("\n");
     310        /* TRANSLATORS: The first placeholder is the web address of the Savannah
     311           project of this package.  The second placeholder is the bug-reporting
     312           email address for this package.  Please add _another line_ saying
     313           "Report translation bugs to <...>\n" with the address for translation
     314           bugs (typically your translation team's web or email address).  */
     315        printf(_("\
     316  Report bugs in the bug tracker at <%s>\n\
     317  or by email to <%s>.\n"),
     318               "https://savannah.gnu.org/projects/gettext",
     319               "bug-gettext@gnu.org");
     320      }
     321  
     322    exit (status);
     323  }
     324  
     325  
     326  #ifdef EINTR
     327  
     328  /* EINTR handling for close().
     329     These functions can return -1/EINTR even though we don't have any
     330     signal handlers set up, namely when we get interrupted via SIGSTOP.  */
     331  
     332  static inline int
     333  nonintr_close (int fd)
     334  {
     335    int retval;
     336  
     337    do
     338      retval = close (fd);
     339    while (retval < 0 && errno == EINTR);
     340  
     341    return retval;
     342  }
     343  #undef close
     344  #define close nonintr_close
     345  
     346  #endif
     347  
     348  
     349  /* Pipe a string STR of size LEN bytes to the subprogram.
     350     The byte after STR is known to be a '\0' byte.  */
     351  static void
     352  process_string (const message_ty *mp, const char *str, size_t len)
     353  {
     354    if (strcmp (sub_name, "0") == 0)
     355      {
     356        /* Built-in command "0".  */
     357        if (full_write (STDOUT_FILENO, str, len + 1) < len + 1)
     358          error (EXIT_FAILURE, errno, _("write to stdout failed"));
     359      }
     360    else
     361      {
     362        /* General command.  */
     363        char *location;
     364        pid_t child;
     365        int fd[1];
     366        void (*orig_sigpipe_handler)(int);
     367        int exitstatus;
     368        char *newstr;
     369  
     370        /* Set environment variables for the subprocess.
     371           Note: These environment variables, especially MSGEXEC_MSGCTXT and
     372           MSGEXEC_MSGID, may contain non-ASCII characters.  The subprocess
     373           may not interpret these values correctly if the locale encoding is
     374           different from the PO file's encoding.  We warned about this situation,
     375           above.
     376           On Unix, this problem is often harmless.  On Windows, however, - both
     377           native Windows and Cygwin - the values of environment variables *must*
     378           be in the encoding that is the value of GetACP(), because the system
     379           may convert the environment from char** to wchar_t** before spawning
     380           the subprocess and back from wchar_t** to char** in the subprocess,
     381           and it does so using the GetACP() codepage.  */
     382        if (mp->msgctxt != NULL)
     383          xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1);
     384        else
     385          unsetenv ("MSGEXEC_MSGCTXT");
     386        xsetenv ("MSGEXEC_MSGID", mp->msgid, 1);
     387        if (mp->msgid_plural != NULL)
     388          xsetenv ("MSGEXEC_MSGID_PLURAL", mp->msgid_plural, 1);
     389        else
     390          unsetenv ("MSGEXEC_MSGID_PLURAL");
     391        location = xasprintf ("%s:%ld", mp->pos.file_name,
     392                              (long) mp->pos.line_number);
     393        xsetenv ("MSGEXEC_LOCATION", location, 1);
     394        free (location);
     395        if (mp->prev_msgctxt != NULL)
     396          xsetenv ("MSGEXEC_PREV_MSGCTXT", mp->prev_msgctxt, 1);
     397        else
     398          unsetenv ("MSGEXEC_PREV_MSGCTXT");
     399        if (mp->prev_msgid != NULL)
     400          xsetenv ("MSGEXEC_PREV_MSGID", mp->prev_msgid, 1);
     401        else
     402          unsetenv ("MSGEXEC_PREV_MSGID");
     403        if (mp->prev_msgid_plural != NULL)
     404          xsetenv ("MSGEXEC_PREV_MSGID_PLURAL", mp->prev_msgid_plural, 1);
     405        else
     406          unsetenv ("MSGEXEC_PREV_MSGID_PLURAL");
     407  
     408        /* Open a pipe to a subprocess.  */
     409        child = create_pipe_out (sub_name, sub_path, sub_argv, NULL,
     410                                 NULL, false, true, true, fd);
     411  
     412        /* Ignore SIGPIPE here.  We don't care if the subprocesses terminates
     413           successfully without having read all of the input that we feed it.  */
     414        orig_sigpipe_handler = signal (SIGPIPE, SIG_IGN);
     415  
     416        if (newline)
     417          {
     418            newstr = XNMALLOC (len + 1, char);
     419            memcpy (newstr, str, len);
     420            newstr[len++] = '\n';
     421          }
     422        else
     423          newstr = (char *) str;
     424  
     425        if (full_write (fd[0], newstr, len) < len)
     426          if (errno != EPIPE)
     427            error (EXIT_FAILURE, errno,
     428                   _("write to %s subprocess failed"), sub_name);
     429  
     430        if (newstr != str)
     431          free (newstr);
     432  
     433        close (fd[0]);
     434  
     435        signal (SIGPIPE, orig_sigpipe_handler);
     436  
     437        /* Remove zombie process from process list, and retrieve exit status.  */
     438        /* FIXME: Should ignore_sigpipe be set to true here? It depends on the
     439           semantics of the subprogram...  */
     440        exitstatus =
     441          wait_subprocess (child, sub_name, false, false, true, true, NULL);
     442        if (exitcode < exitstatus)
     443          exitcode = exitstatus;
     444      }
     445  }
     446  
     447  
     448  static void
     449  process_message (const message_ty *mp)
     450  {
     451    const char *msgstr = mp->msgstr;
     452    size_t msgstr_len = mp->msgstr_len;
     453    const char *p;
     454    size_t k;
     455  
     456    /* Process each NUL delimited substring separately.  */
     457    for (p = msgstr, k = 0; p < msgstr + msgstr_len; k++)
     458      {
     459        size_t length = strlen (p);
     460  
     461        if (mp->msgid_plural != NULL)
     462          {
     463            char *plural_form_string = xasprintf ("%lu", (unsigned long) k);
     464  
     465            xsetenv ("MSGEXEC_PLURAL_FORM", plural_form_string, 1);
     466            free (plural_form_string);
     467          }
     468        else
     469          unsetenv ("MSGEXEC_PLURAL_FORM");
     470        process_string (mp, p, length);
     471  
     472        p += length + 1;
     473      }
     474  }
     475  
     476  
     477  static void
     478  process_message_list (const message_list_ty *mlp)
     479  {
     480    size_t j;
     481  
     482    for (j = 0; j < mlp->nitems; j++)
     483      process_message (mlp->item[j]);
     484  }
     485  
     486  
     487  static void
     488  process_msgdomain_list (const msgdomain_list_ty *mdlp)
     489  {
     490    size_t k;
     491  
     492    for (k = 0; k < mdlp->nitems; k++)
     493      process_message_list (mdlp->item[k]->messages);
     494  }