facf0c92e0
Red Bear OS is a full fork. All sources must be available from git clone with zero network access. Removed gitignore rules that excluded fetched source trees under recipes/*/source/, local/recipes/kde/*/source/, local/recipes/qt/*/source/, and vendor source trees. Build artifacts (target/, build/, source.tar, *.o, *.so) remain excluded. 127291 files added — kernel, relibc, base, bootloader, pkgar, all KDE/Qt frameworks, mesa, wayland, DRM drivers, and every other recipe source.
495 lines
14 KiB
C
495 lines
14 KiB
C
/* Pass translations to a subprocess.
|
|
Copyright (C) 2001-2023 Free Software Foundation, Inc.
|
|
Written by Bruno Haible <haible@clisp.cons.org>, 2001.
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <https://www.gnu.org/licenses/>. */
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include "config.h"
|
|
#endif
|
|
|
|
#include <errno.h>
|
|
#include <getopt.h>
|
|
#include <limits.h>
|
|
#include <locale.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/types.h>
|
|
#include <unistd.h>
|
|
|
|
#include "noreturn.h"
|
|
#include "closeout.h"
|
|
#include "dir-list.h"
|
|
#include "error.h"
|
|
#include "xvasprintf.h"
|
|
#include "error-progname.h"
|
|
#include "progname.h"
|
|
#include "relocatable.h"
|
|
#include "basename-lgpl.h"
|
|
#include "message.h"
|
|
#include "read-catalog.h"
|
|
#include "read-po.h"
|
|
#include "read-properties.h"
|
|
#include "read-stringtable.h"
|
|
#include "msgl-charset.h"
|
|
#include "xalloc.h"
|
|
#include "full-write.h"
|
|
#include "findprog.h"
|
|
#include "spawn-pipe.h"
|
|
#include "wait-process.h"
|
|
#include "xsetenv.h"
|
|
#include "propername.h"
|
|
#include "gettext.h"
|
|
|
|
#define _(str) gettext (str)
|
|
|
|
#ifndef STDOUT_FILENO
|
|
# define STDOUT_FILENO 1
|
|
#endif
|
|
|
|
|
|
/* Name of the subprogram. */
|
|
static const char *sub_name;
|
|
|
|
/* Pathname of the subprogram. */
|
|
static const char *sub_path;
|
|
|
|
/* Argument list for the subprogram. */
|
|
static const char **sub_argv;
|
|
static int sub_argc;
|
|
|
|
static bool newline;
|
|
|
|
/* Maximum exit code encountered. */
|
|
static int exitcode;
|
|
|
|
/* Long options. */
|
|
static const struct option long_options[] =
|
|
{
|
|
{ "directory", required_argument, NULL, 'D' },
|
|
{ "help", no_argument, NULL, 'h' },
|
|
{ "input", required_argument, NULL, 'i' },
|
|
{ "newline", no_argument, NULL, CHAR_MAX + 2 },
|
|
{ "properties-input", no_argument, NULL, 'P' },
|
|
{ "stringtable-input", no_argument, NULL, CHAR_MAX + 1 },
|
|
{ "version", no_argument, NULL, 'V' },
|
|
{ NULL, 0, NULL, 0 }
|
|
};
|
|
|
|
|
|
/* Forward declaration of local functions. */
|
|
_GL_NORETURN_FUNC static void usage (int status);
|
|
static void process_msgdomain_list (const msgdomain_list_ty *mdlp);
|
|
|
|
|
|
int
|
|
main (int argc, char **argv)
|
|
{
|
|
int opt;
|
|
bool do_help;
|
|
bool do_version;
|
|
const char *input_file;
|
|
msgdomain_list_ty *result;
|
|
catalog_input_format_ty input_syntax = &input_format_po;
|
|
size_t i;
|
|
|
|
/* Set program name for messages. */
|
|
set_program_name (argv[0]);
|
|
error_print_progname = maybe_print_progname;
|
|
|
|
/* Set locale via LC_ALL. */
|
|
setlocale (LC_ALL, "");
|
|
|
|
/* Set the text message domain. */
|
|
bindtextdomain (PACKAGE, relocate (LOCALEDIR));
|
|
bindtextdomain ("bison-runtime", relocate (BISON_LOCALEDIR));
|
|
textdomain (PACKAGE);
|
|
|
|
/* Ensure that write errors on stdout are detected. */
|
|
atexit (close_stdout);
|
|
|
|
/* Set default values for variables. */
|
|
do_help = false;
|
|
do_version = false;
|
|
input_file = NULL;
|
|
|
|
/* The '+' in the options string causes option parsing to terminate when
|
|
the first non-option, i.e. the subprogram name, is encountered. */
|
|
while ((opt = getopt_long (argc, argv, "+D:hi:PV", long_options, NULL))
|
|
!= EOF)
|
|
switch (opt)
|
|
{
|
|
case '\0': /* Long option. */
|
|
break;
|
|
|
|
case 'D':
|
|
dir_list_append (optarg);
|
|
break;
|
|
|
|
case 'h':
|
|
do_help = true;
|
|
break;
|
|
|
|
case 'i':
|
|
if (input_file != NULL)
|
|
{
|
|
error (EXIT_SUCCESS, 0, _("at most one input file allowed"));
|
|
usage (EXIT_FAILURE);
|
|
}
|
|
input_file = optarg;
|
|
break;
|
|
|
|
case 'P':
|
|
input_syntax = &input_format_properties;
|
|
break;
|
|
|
|
case 'V':
|
|
do_version = true;
|
|
break;
|
|
|
|
case CHAR_MAX + 1: /* --stringtable-input */
|
|
input_syntax = &input_format_stringtable;
|
|
break;
|
|
|
|
case CHAR_MAX + 2: /* --newline */
|
|
newline = true;
|
|
break;
|
|
|
|
default:
|
|
usage (EXIT_FAILURE);
|
|
break;
|
|
}
|
|
|
|
/* Version information is requested. */
|
|
if (do_version)
|
|
{
|
|
printf ("%s (GNU %s) %s\n", last_component (program_name),
|
|
PACKAGE, VERSION);
|
|
/* xgettext: no-wrap */
|
|
printf (_("Copyright (C) %s Free Software Foundation, Inc.\n\
|
|
License GPLv3+: GNU GPL version 3 or later <%s>\n\
|
|
This is free software: you are free to change and redistribute it.\n\
|
|
There is NO WARRANTY, to the extent permitted by law.\n\
|
|
"),
|
|
"2001-2023", "https://gnu.org/licenses/gpl.html");
|
|
printf (_("Written by %s.\n"), proper_name ("Bruno Haible"));
|
|
exit (EXIT_SUCCESS);
|
|
}
|
|
|
|
/* Help is requested. */
|
|
if (do_help)
|
|
usage (EXIT_SUCCESS);
|
|
|
|
/* Test for the subprogram name. */
|
|
if (optind == argc)
|
|
error (EXIT_FAILURE, 0, _("missing command name"));
|
|
sub_name = argv[optind];
|
|
|
|
/* Build argument list for the program. */
|
|
sub_argc = argc - optind;
|
|
sub_argv = XNMALLOC (sub_argc + 1, const char *);
|
|
for (i = 0; i < sub_argc; i++)
|
|
sub_argv[i] = argv[optind + i];
|
|
sub_argv[i] = NULL;
|
|
|
|
/* By default, input comes from standard input. */
|
|
if (input_file == NULL)
|
|
input_file = "-";
|
|
|
|
/* Read input file. */
|
|
result = read_catalog_file (input_file, input_syntax);
|
|
|
|
if (strcmp (sub_name, "0") != 0)
|
|
{
|
|
/* Warn if the current locale is not suitable for this PO file. */
|
|
compare_po_locale_charsets (result);
|
|
|
|
/* Block SIGPIPE for this process and for the subprocesses.
|
|
The subprogram may have side effects (additionally to producing some
|
|
output), therefore if there are no readers on stdout, processing of the
|
|
strings must continue nevertheless. */
|
|
{
|
|
sigset_t sigpipe_set;
|
|
|
|
sigemptyset (&sigpipe_set);
|
|
sigaddset (&sigpipe_set, SIGPIPE);
|
|
sigprocmask (SIG_UNBLOCK, &sigpipe_set, NULL);
|
|
}
|
|
|
|
/* Attempt to locate the program.
|
|
This is an optimization, to avoid that spawn/exec searches the PATH
|
|
on every call. */
|
|
sub_path = find_in_path (sub_name);
|
|
|
|
/* Finish argument list for the program. */
|
|
sub_argv[0] = sub_path;
|
|
}
|
|
|
|
exitcode = 0; /* = EXIT_SUCCESS */
|
|
|
|
/* Apply the subprogram. */
|
|
process_msgdomain_list (result);
|
|
|
|
exit (exitcode);
|
|
}
|
|
|
|
|
|
/* Display usage information and exit. */
|
|
static void
|
|
usage (int status)
|
|
{
|
|
if (status != EXIT_SUCCESS)
|
|
fprintf (stderr, _("Try '%s --help' for more information.\n"),
|
|
program_name);
|
|
else
|
|
{
|
|
printf (_("\
|
|
Usage: %s [OPTION] COMMAND [COMMAND-OPTION]\n\
|
|
"), program_name);
|
|
printf ("\n");
|
|
/* xgettext: no-wrap */
|
|
printf (_("\
|
|
Applies a command to all translations of a translation catalog.\n\
|
|
The COMMAND can be any program that reads a translation from standard\n\
|
|
input. It is invoked once for each translation. Its output becomes\n\
|
|
msgexec's output. msgexec's return code is the maximum return code\n\
|
|
across all invocations.\n\
|
|
"));
|
|
printf ("\n");
|
|
/* xgettext: no-wrap */
|
|
printf (_("\
|
|
A special builtin command called '0' outputs the translation, followed by a\n\
|
|
null byte. The output of \"msgexec 0\" is suitable as input for \"xargs -0\".\n\
|
|
"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Command input:\n"));
|
|
printf (_("\
|
|
--newline add newline at the end of input\n"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Mandatory arguments to long options are mandatory for short options too.\n"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Input file location:\n"));
|
|
printf (_("\
|
|
-i, --input=INPUTFILE input PO file\n"));
|
|
printf (_("\
|
|
-D, --directory=DIRECTORY add DIRECTORY to list for input files search\n"));
|
|
printf (_("\
|
|
If no input file is given or if it is -, standard input is read.\n"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Input file syntax:\n"));
|
|
printf (_("\
|
|
-P, --properties-input input file is in Java .properties syntax\n"));
|
|
printf (_("\
|
|
--stringtable-input input file is in NeXTstep/GNUstep .strings syntax\n"));
|
|
printf ("\n");
|
|
printf (_("\
|
|
Informative output:\n"));
|
|
printf (_("\
|
|
-h, --help display this help and exit\n"));
|
|
printf (_("\
|
|
-V, --version output version information and exit\n"));
|
|
printf ("\n");
|
|
/* TRANSLATORS: The first placeholder is the web address of the Savannah
|
|
project of this package. The second placeholder is the bug-reporting
|
|
email address for this package. Please add _another line_ saying
|
|
"Report translation bugs to <...>\n" with the address for translation
|
|
bugs (typically your translation team's web or email address). */
|
|
printf(_("\
|
|
Report bugs in the bug tracker at <%s>\n\
|
|
or by email to <%s>.\n"),
|
|
"https://savannah.gnu.org/projects/gettext",
|
|
"bug-gettext@gnu.org");
|
|
}
|
|
|
|
exit (status);
|
|
}
|
|
|
|
|
|
#ifdef EINTR
|
|
|
|
/* EINTR handling for close().
|
|
These functions can return -1/EINTR even though we don't have any
|
|
signal handlers set up, namely when we get interrupted via SIGSTOP. */
|
|
|
|
static inline int
|
|
nonintr_close (int fd)
|
|
{
|
|
int retval;
|
|
|
|
do
|
|
retval = close (fd);
|
|
while (retval < 0 && errno == EINTR);
|
|
|
|
return retval;
|
|
}
|
|
#undef close
|
|
#define close nonintr_close
|
|
|
|
#endif
|
|
|
|
|
|
/* Pipe a string STR of size LEN bytes to the subprogram.
|
|
The byte after STR is known to be a '\0' byte. */
|
|
static void
|
|
process_string (const message_ty *mp, const char *str, size_t len)
|
|
{
|
|
if (strcmp (sub_name, "0") == 0)
|
|
{
|
|
/* Built-in command "0". */
|
|
if (full_write (STDOUT_FILENO, str, len + 1) < len + 1)
|
|
error (EXIT_FAILURE, errno, _("write to stdout failed"));
|
|
}
|
|
else
|
|
{
|
|
/* General command. */
|
|
char *location;
|
|
pid_t child;
|
|
int fd[1];
|
|
void (*orig_sigpipe_handler)(int);
|
|
int exitstatus;
|
|
char *newstr;
|
|
|
|
/* Set environment variables for the subprocess.
|
|
Note: These environment variables, especially MSGEXEC_MSGCTXT and
|
|
MSGEXEC_MSGID, may contain non-ASCII characters. The subprocess
|
|
may not interpret these values correctly if the locale encoding is
|
|
different from the PO file's encoding. We warned about this situation,
|
|
above.
|
|
On Unix, this problem is often harmless. On Windows, however, - both
|
|
native Windows and Cygwin - the values of environment variables *must*
|
|
be in the encoding that is the value of GetACP(), because the system
|
|
may convert the environment from char** to wchar_t** before spawning
|
|
the subprocess and back from wchar_t** to char** in the subprocess,
|
|
and it does so using the GetACP() codepage. */
|
|
if (mp->msgctxt != NULL)
|
|
xsetenv ("MSGEXEC_MSGCTXT", mp->msgctxt, 1);
|
|
else
|
|
unsetenv ("MSGEXEC_MSGCTXT");
|
|
xsetenv ("MSGEXEC_MSGID", mp->msgid, 1);
|
|
if (mp->msgid_plural != NULL)
|
|
xsetenv ("MSGEXEC_MSGID_PLURAL", mp->msgid_plural, 1);
|
|
else
|
|
unsetenv ("MSGEXEC_MSGID_PLURAL");
|
|
location = xasprintf ("%s:%ld", mp->pos.file_name,
|
|
(long) mp->pos.line_number);
|
|
xsetenv ("MSGEXEC_LOCATION", location, 1);
|
|
free (location);
|
|
if (mp->prev_msgctxt != NULL)
|
|
xsetenv ("MSGEXEC_PREV_MSGCTXT", mp->prev_msgctxt, 1);
|
|
else
|
|
unsetenv ("MSGEXEC_PREV_MSGCTXT");
|
|
if (mp->prev_msgid != NULL)
|
|
xsetenv ("MSGEXEC_PREV_MSGID", mp->prev_msgid, 1);
|
|
else
|
|
unsetenv ("MSGEXEC_PREV_MSGID");
|
|
if (mp->prev_msgid_plural != NULL)
|
|
xsetenv ("MSGEXEC_PREV_MSGID_PLURAL", mp->prev_msgid_plural, 1);
|
|
else
|
|
unsetenv ("MSGEXEC_PREV_MSGID_PLURAL");
|
|
|
|
/* Open a pipe to a subprocess. */
|
|
child = create_pipe_out (sub_name, sub_path, sub_argv, NULL,
|
|
NULL, false, true, true, fd);
|
|
|
|
/* Ignore SIGPIPE here. We don't care if the subprocesses terminates
|
|
successfully without having read all of the input that we feed it. */
|
|
orig_sigpipe_handler = signal (SIGPIPE, SIG_IGN);
|
|
|
|
if (newline)
|
|
{
|
|
newstr = XNMALLOC (len + 1, char);
|
|
memcpy (newstr, str, len);
|
|
newstr[len++] = '\n';
|
|
}
|
|
else
|
|
newstr = (char *) str;
|
|
|
|
if (full_write (fd[0], newstr, len) < len)
|
|
if (errno != EPIPE)
|
|
error (EXIT_FAILURE, errno,
|
|
_("write to %s subprocess failed"), sub_name);
|
|
|
|
if (newstr != str)
|
|
free (newstr);
|
|
|
|
close (fd[0]);
|
|
|
|
signal (SIGPIPE, orig_sigpipe_handler);
|
|
|
|
/* Remove zombie process from process list, and retrieve exit status. */
|
|
/* FIXME: Should ignore_sigpipe be set to true here? It depends on the
|
|
semantics of the subprogram... */
|
|
exitstatus =
|
|
wait_subprocess (child, sub_name, false, false, true, true, NULL);
|
|
if (exitcode < exitstatus)
|
|
exitcode = exitstatus;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
process_message (const message_ty *mp)
|
|
{
|
|
const char *msgstr = mp->msgstr;
|
|
size_t msgstr_len = mp->msgstr_len;
|
|
const char *p;
|
|
size_t k;
|
|
|
|
/* Process each NUL delimited substring separately. */
|
|
for (p = msgstr, k = 0; p < msgstr + msgstr_len; k++)
|
|
{
|
|
size_t length = strlen (p);
|
|
|
|
if (mp->msgid_plural != NULL)
|
|
{
|
|
char *plural_form_string = xasprintf ("%lu", (unsigned long) k);
|
|
|
|
xsetenv ("MSGEXEC_PLURAL_FORM", plural_form_string, 1);
|
|
free (plural_form_string);
|
|
}
|
|
else
|
|
unsetenv ("MSGEXEC_PLURAL_FORM");
|
|
process_string (mp, p, length);
|
|
|
|
p += length + 1;
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
process_message_list (const message_list_ty *mlp)
|
|
{
|
|
size_t j;
|
|
|
|
for (j = 0; j < mlp->nitems; j++)
|
|
process_message (mlp->item[j]);
|
|
}
|
|
|
|
|
|
static void
|
|
process_msgdomain_list (const msgdomain_list_ty *mdlp)
|
|
{
|
|
size_t k;
|
|
|
|
for (k = 0; k < mdlp->nitems; k++)
|
|
process_message_list (mdlp->item[k]->messages);
|
|
}
|