mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-26 23:10:06 +00:00
f16491eb8e
The error and warning handling in localedef, locale, and iconv is a bit of a mess. We use ugly constructs like this: WITH_CUR_LOCALE (error (1, errno, gettext ("\ cannot read character map directory `%s'"), directory)); to issue errors, and read error_message_count directly from the error API to detect errors. The problem with that is that the code also uses error to print warnings, and informative messages. All of this leads to problems where just having warnings will produce an exit status as-if errors had been seen. To fix this situation I have adopted the following high-level changes: * All errors are counted distinctly. * All warnings are counted distinctly. * All informative messages are not counted. * Increasing verbosity cannot generate *more* errors, and it previously did for errors conditional on verbose, this is now fixed. * Increasing verbosity *can* generate *more* warnings. * Making the output quiet cannot generate *fewer* errors, and it previously did for errors conditional on be_quiet, this is now fixed. * Each of error, warning, and informative message has it's own function to call defined in record-status.h, and they are: record_error, record_warning, and record_verbose. * The record_error function always records an error, but conditional on be_quiet may not print it. * The record_warning function always records a warning, but conditional on be_quiet may not print it. * The record_verbose function only prints the verbose message if verbose is true and be_quiet is false. This has allowed the following fix: * Previously any warnings were being treated as errors because they incremented error_message_count, but now we properly return an exit status of 1 if there are warnings but output was generated. All of this allows localedef to correctly decide if errors, or warnings were present, and produce the correct exit code. The locale and iconv programs now also use record-status.h and we have removed the WITH_CUR_LOCALE hack, and instead have internal push_locale/pop_locale functions centralized in the record routines. Signed-off-by: Carlos O'Donell <carlos@redhat.com>
999 lines
26 KiB
C
999 lines
26 KiB
C
/* Copyright (C) 1996-2017 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
Contributed by Ulrich Drepper <drepper@gnu.org>, 1996.
|
|
|
|
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; version 2 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 <http://www.gnu.org/licenses/>. */
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
# include <config.h>
|
|
#endif
|
|
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <stdbool.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <unistd.h>
|
|
#include <sys/param.h>
|
|
#include <sys/stat.h>
|
|
#include <assert.h>
|
|
#include <wchar.h>
|
|
|
|
#include "../../crypt/md5.h"
|
|
#include "localedef.h"
|
|
#include "localeinfo.h"
|
|
#include "locfile.h"
|
|
#include "simple-hash.h"
|
|
|
|
#include "locfile-kw.h"
|
|
|
|
#define obstack_chunk_alloc xmalloc
|
|
#define obstack_chunk_free free
|
|
|
|
/* Temporary storage of the locale data before writing it to the archive. */
|
|
static locale_data_t to_archive;
|
|
|
|
|
|
int
|
|
locfile_read (struct localedef_t *result, const struct charmap_t *charmap)
|
|
{
|
|
const char *filename = result->name;
|
|
const char *repertoire_name = result->repertoire_name;
|
|
int locale_mask = result->needed & ~result->avail;
|
|
struct linereader *ldfile;
|
|
int not_here = ALL_LOCALES;
|
|
|
|
/* If no repertoire name was specified use the global one. */
|
|
if (repertoire_name == NULL)
|
|
repertoire_name = repertoire_global;
|
|
|
|
/* Open the locale definition file. */
|
|
ldfile = lr_open (filename, locfile_hash);
|
|
if (ldfile == NULL)
|
|
{
|
|
if (filename != NULL && filename[0] != '/')
|
|
{
|
|
char *i18npath = getenv ("I18NPATH");
|
|
if (i18npath != NULL && *i18npath != '\0')
|
|
{
|
|
const size_t pathlen = strlen (i18npath);
|
|
char i18npathbuf[pathlen + 1];
|
|
char path[strlen (filename) + 1 + pathlen
|
|
+ sizeof ("/locales/") - 1];
|
|
char *next;
|
|
i18npath = memcpy (i18npathbuf, i18npath, pathlen + 1);
|
|
|
|
while (ldfile == NULL
|
|
&& (next = strsep (&i18npath, ":")) != NULL)
|
|
{
|
|
stpcpy (stpcpy (stpcpy (path, next), "/locales/"), filename);
|
|
|
|
ldfile = lr_open (path, locfile_hash);
|
|
|
|
if (ldfile == NULL)
|
|
{
|
|
stpcpy (stpcpy (stpcpy (path, next), "/"), filename);
|
|
|
|
ldfile = lr_open (path, locfile_hash);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Test in the default directory. */
|
|
if (ldfile == NULL)
|
|
{
|
|
char path[strlen (filename) + 1 + sizeof (LOCSRCDIR)];
|
|
|
|
stpcpy (stpcpy (stpcpy (path, LOCSRCDIR), "/"), filename);
|
|
ldfile = lr_open (path, locfile_hash);
|
|
}
|
|
}
|
|
|
|
if (ldfile == NULL)
|
|
return 1;
|
|
}
|
|
|
|
/* Parse locale definition file and store result in RESULT. */
|
|
while (1)
|
|
{
|
|
struct token *now = lr_token (ldfile, charmap, NULL, NULL, verbose);
|
|
enum token_t nowtok = now->tok;
|
|
struct token *arg;
|
|
|
|
if (nowtok == tok_eof)
|
|
break;
|
|
|
|
if (nowtok == tok_eol)
|
|
/* Ignore empty lines. */
|
|
continue;
|
|
|
|
switch (nowtok)
|
|
{
|
|
case tok_escape_char:
|
|
case tok_comment_char:
|
|
/* We need an argument. */
|
|
arg = lr_token (ldfile, charmap, NULL, NULL, verbose);
|
|
|
|
if (arg->tok != tok_ident)
|
|
{
|
|
SYNTAX_ERROR (_("bad argument"));
|
|
continue;
|
|
}
|
|
|
|
if (arg->val.str.lenmb != 1)
|
|
{
|
|
lr_error (ldfile, _("\
|
|
argument to `%s' must be a single character"),
|
|
nowtok == tok_escape_char
|
|
? "escape_char" : "comment_char");
|
|
|
|
lr_ignore_rest (ldfile, 0);
|
|
continue;
|
|
}
|
|
|
|
if (nowtok == tok_escape_char)
|
|
ldfile->escape_char = *arg->val.str.startmb;
|
|
else
|
|
ldfile->comment_char = *arg->val.str.startmb;
|
|
break;
|
|
|
|
case tok_repertoiremap:
|
|
/* We need an argument. */
|
|
arg = lr_token (ldfile, charmap, NULL, NULL, verbose);
|
|
|
|
if (arg->tok != tok_ident)
|
|
{
|
|
SYNTAX_ERROR (_("bad argument"));
|
|
continue;
|
|
}
|
|
|
|
if (repertoire_name == NULL)
|
|
{
|
|
char *newp = alloca (arg->val.str.lenmb + 1);
|
|
|
|
*((char *) mempcpy (newp, arg->val.str.startmb,
|
|
arg->val.str.lenmb)) = '\0';
|
|
repertoire_name = newp;
|
|
}
|
|
break;
|
|
|
|
case tok_lc_ctype:
|
|
ctype_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & CTYPE_LOCALE) == 0);
|
|
result->avail |= locale_mask & CTYPE_LOCALE;
|
|
not_here ^= CTYPE_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_collate:
|
|
collate_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & COLLATE_LOCALE) == 0);
|
|
result->avail |= locale_mask & COLLATE_LOCALE;
|
|
not_here ^= COLLATE_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_monetary:
|
|
monetary_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & MONETARY_LOCALE) == 0);
|
|
result->avail |= locale_mask & MONETARY_LOCALE;
|
|
not_here ^= MONETARY_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_numeric:
|
|
numeric_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & NUMERIC_LOCALE) == 0);
|
|
result->avail |= locale_mask & NUMERIC_LOCALE;
|
|
not_here ^= NUMERIC_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_time:
|
|
time_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & TIME_LOCALE) == 0);
|
|
result->avail |= locale_mask & TIME_LOCALE;
|
|
not_here ^= TIME_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_messages:
|
|
messages_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & MESSAGES_LOCALE) == 0);
|
|
result->avail |= locale_mask & MESSAGES_LOCALE;
|
|
not_here ^= MESSAGES_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_paper:
|
|
paper_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & PAPER_LOCALE) == 0);
|
|
result->avail |= locale_mask & PAPER_LOCALE;
|
|
not_here ^= PAPER_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_name:
|
|
name_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & NAME_LOCALE) == 0);
|
|
result->avail |= locale_mask & NAME_LOCALE;
|
|
not_here ^= NAME_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_address:
|
|
address_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & ADDRESS_LOCALE) == 0);
|
|
result->avail |= locale_mask & ADDRESS_LOCALE;
|
|
not_here ^= ADDRESS_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_telephone:
|
|
telephone_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & TELEPHONE_LOCALE) == 0);
|
|
result->avail |= locale_mask & TELEPHONE_LOCALE;
|
|
not_here ^= TELEPHONE_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_measurement:
|
|
measurement_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & MEASUREMENT_LOCALE) == 0);
|
|
result->avail |= locale_mask & MEASUREMENT_LOCALE;
|
|
not_here ^= MEASUREMENT_LOCALE;
|
|
continue;
|
|
|
|
case tok_lc_identification:
|
|
identification_read (ldfile, result, charmap, repertoire_name,
|
|
(locale_mask & IDENTIFICATION_LOCALE) == 0);
|
|
result->avail |= locale_mask & IDENTIFICATION_LOCALE;
|
|
not_here ^= IDENTIFICATION_LOCALE;
|
|
continue;
|
|
|
|
default:
|
|
SYNTAX_ERROR (_("\
|
|
syntax error: not inside a locale definition section"));
|
|
continue;
|
|
}
|
|
|
|
/* The rest of the line must be empty. */
|
|
lr_ignore_rest (ldfile, 1);
|
|
}
|
|
|
|
/* We read all of the file. */
|
|
lr_close (ldfile);
|
|
|
|
/* Mark the categories which are not contained in the file. We assume
|
|
them to be available and the default data will be used. */
|
|
result->avail |= not_here;
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* Semantic checking of locale specifications. */
|
|
|
|
static void (*const check_funcs[]) (struct localedef_t *,
|
|
const struct charmap_t *) =
|
|
{
|
|
[LC_CTYPE] = ctype_finish,
|
|
[LC_COLLATE] = collate_finish,
|
|
[LC_MESSAGES] = messages_finish,
|
|
[LC_MONETARY] = monetary_finish,
|
|
[LC_NUMERIC] = numeric_finish,
|
|
[LC_TIME] = time_finish,
|
|
[LC_PAPER] = paper_finish,
|
|
[LC_NAME] = name_finish,
|
|
[LC_ADDRESS] = address_finish,
|
|
[LC_TELEPHONE] = telephone_finish,
|
|
[LC_MEASUREMENT] = measurement_finish,
|
|
[LC_IDENTIFICATION] = identification_finish
|
|
};
|
|
|
|
void
|
|
check_all_categories (struct localedef_t *definitions,
|
|
const struct charmap_t *charmap)
|
|
{
|
|
int cnt;
|
|
|
|
for (cnt = 0; cnt < sizeof (check_funcs) / sizeof (check_funcs[0]); ++cnt)
|
|
if (check_funcs[cnt] != NULL)
|
|
check_funcs[cnt] (definitions, charmap);
|
|
}
|
|
|
|
|
|
/* Writing the locale data files. All files use the same output_path. */
|
|
|
|
static void (*const write_funcs[]) (struct localedef_t *,
|
|
const struct charmap_t *, const char *) =
|
|
{
|
|
[LC_CTYPE] = ctype_output,
|
|
[LC_COLLATE] = collate_output,
|
|
[LC_MESSAGES] = messages_output,
|
|
[LC_MONETARY] = monetary_output,
|
|
[LC_NUMERIC] = numeric_output,
|
|
[LC_TIME] = time_output,
|
|
[LC_PAPER] = paper_output,
|
|
[LC_NAME] = name_output,
|
|
[LC_ADDRESS] = address_output,
|
|
[LC_TELEPHONE] = telephone_output,
|
|
[LC_MEASUREMENT] = measurement_output,
|
|
[LC_IDENTIFICATION] = identification_output
|
|
};
|
|
|
|
|
|
void
|
|
write_all_categories (struct localedef_t *definitions,
|
|
const struct charmap_t *charmap, const char *locname,
|
|
const char *output_path)
|
|
{
|
|
int cnt;
|
|
|
|
for (cnt = 0; cnt < sizeof (write_funcs) / sizeof (write_funcs[0]); ++cnt)
|
|
if (write_funcs[cnt] != NULL)
|
|
write_funcs[cnt] (definitions, charmap, output_path);
|
|
|
|
if (! no_archive)
|
|
{
|
|
/* The data has to be added to the archive. Do this now. */
|
|
struct locarhandle ah;
|
|
|
|
/* Open the archive. This call never returns if we cannot
|
|
successfully open the archive. */
|
|
ah.fname = NULL;
|
|
open_archive (&ah, false);
|
|
|
|
if (add_locale_to_archive (&ah, locname, to_archive, true) != 0)
|
|
error (EXIT_FAILURE, errno, _("cannot add to locale archive"));
|
|
|
|
/* We are done. */
|
|
close_archive (&ah);
|
|
}
|
|
}
|
|
|
|
|
|
/* Return a NULL terminated list of the directories next to output_path
|
|
that have the same owner, group, permissions and device as output_path. */
|
|
static const char **
|
|
siblings_uncached (const char *output_path)
|
|
{
|
|
size_t len;
|
|
char *base, *p;
|
|
struct stat64 output_stat;
|
|
DIR *dirp;
|
|
int nelems;
|
|
const char **elems;
|
|
|
|
/* Remove trailing slashes and trailing pathname component. */
|
|
len = strlen (output_path);
|
|
base = (char *) alloca (len);
|
|
memcpy (base, output_path, len);
|
|
p = base + len;
|
|
while (p > base && p[-1] == '/')
|
|
p--;
|
|
if (p == base)
|
|
return NULL;
|
|
do
|
|
p--;
|
|
while (p > base && p[-1] != '/');
|
|
if (p == base)
|
|
return NULL;
|
|
*--p = '\0';
|
|
len = p - base;
|
|
|
|
/* Get the properties of output_path. */
|
|
if (lstat64 (output_path, &output_stat) < 0 || !S_ISDIR (output_stat.st_mode))
|
|
return NULL;
|
|
|
|
/* Iterate through the directories in base directory. */
|
|
dirp = opendir (base);
|
|
if (dirp == NULL)
|
|
return NULL;
|
|
nelems = 0;
|
|
elems = NULL;
|
|
for (;;)
|
|
{
|
|
struct dirent64 *other_dentry;
|
|
const char *other_name;
|
|
char *other_path;
|
|
struct stat64 other_stat;
|
|
|
|
other_dentry = readdir64 (dirp);
|
|
if (other_dentry == NULL)
|
|
break;
|
|
|
|
other_name = other_dentry->d_name;
|
|
if (strcmp (other_name, ".") == 0 || strcmp (other_name, "..") == 0)
|
|
continue;
|
|
|
|
other_path = (char *) xmalloc (len + 1 + strlen (other_name) + 2);
|
|
memcpy (other_path, base, len);
|
|
other_path[len] = '/';
|
|
strcpy (other_path + len + 1, other_name);
|
|
|
|
if (lstat64 (other_path, &other_stat) >= 0
|
|
&& S_ISDIR (other_stat.st_mode)
|
|
&& other_stat.st_uid == output_stat.st_uid
|
|
&& other_stat.st_gid == output_stat.st_gid
|
|
&& other_stat.st_mode == output_stat.st_mode
|
|
&& other_stat.st_dev == output_stat.st_dev)
|
|
{
|
|
/* Found a subdirectory. Add a trailing slash and store it. */
|
|
p = other_path + len + 1 + strlen (other_name);
|
|
*p++ = '/';
|
|
*p = '\0';
|
|
elems = (const char **) xrealloc ((char *) elems,
|
|
(nelems + 2) * sizeof (char **));
|
|
elems[nelems++] = other_path;
|
|
}
|
|
else
|
|
free (other_path);
|
|
}
|
|
closedir (dirp);
|
|
|
|
if (elems != NULL)
|
|
elems[nelems] = NULL;
|
|
return elems;
|
|
}
|
|
|
|
|
|
/* Return a NULL terminated list of the directories next to output_path
|
|
that have the same owner, group, permissions and device as output_path.
|
|
Cache the result for future calls. */
|
|
static const char **
|
|
siblings (const char *output_path)
|
|
{
|
|
static const char *last_output_path;
|
|
static const char **last_result;
|
|
|
|
if (output_path != last_output_path)
|
|
{
|
|
if (last_result != NULL)
|
|
{
|
|
const char **p;
|
|
|
|
for (p = last_result; *p != NULL; p++)
|
|
free ((char *) *p);
|
|
free (last_result);
|
|
}
|
|
|
|
last_output_path = output_path;
|
|
last_result = siblings_uncached (output_path);
|
|
}
|
|
return last_result;
|
|
}
|
|
|
|
|
|
/* Read as many bytes from a file descriptor as possible. */
|
|
static ssize_t
|
|
full_read (int fd, void *bufarea, size_t nbyte)
|
|
{
|
|
char *buf = (char *) bufarea;
|
|
|
|
while (nbyte > 0)
|
|
{
|
|
ssize_t retval = read (fd, buf, nbyte);
|
|
|
|
if (retval == 0)
|
|
break;
|
|
else if (retval > 0)
|
|
{
|
|
buf += retval;
|
|
nbyte -= retval;
|
|
}
|
|
else if (errno != EINTR)
|
|
return retval;
|
|
}
|
|
return buf - (char *) bufarea;
|
|
}
|
|
|
|
|
|
/* Compare the contents of two regular files of the same size. Return 0
|
|
if they are equal, 1 if they are different, or -1 if an error occurs. */
|
|
static int
|
|
compare_files (const char *filename1, const char *filename2, size_t size,
|
|
size_t blocksize)
|
|
{
|
|
int fd1, fd2;
|
|
int ret = -1;
|
|
|
|
fd1 = open (filename1, O_RDONLY);
|
|
if (fd1 >= 0)
|
|
{
|
|
fd2 = open (filename2, O_RDONLY);
|
|
if (fd2 >= 0)
|
|
{
|
|
char *buf1 = (char *) xmalloc (2 * blocksize);
|
|
char *buf2 = buf1 + blocksize;
|
|
|
|
ret = 0;
|
|
while (size > 0)
|
|
{
|
|
size_t bytes = (size < blocksize ? size : blocksize);
|
|
|
|
if (full_read (fd1, buf1, bytes) < (ssize_t) bytes)
|
|
{
|
|
ret = -1;
|
|
break;
|
|
}
|
|
if (full_read (fd2, buf2, bytes) < (ssize_t) bytes)
|
|
{
|
|
ret = -1;
|
|
break;
|
|
}
|
|
if (memcmp (buf1, buf2, bytes) != 0)
|
|
{
|
|
ret = 1;
|
|
break;
|
|
}
|
|
size -= bytes;
|
|
}
|
|
|
|
free (buf1);
|
|
close (fd2);
|
|
}
|
|
close (fd1);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
/* True if the locale files use the opposite endianness to the
|
|
machine running localedef. */
|
|
bool swap_endianness_p;
|
|
|
|
/* When called outside a start_locale_structure/end_locale_structure
|
|
or start_locale_prelude/end_locale_prelude block, record that the
|
|
next byte in FILE's obstack will be the first byte of a new element.
|
|
Do likewise for the first call inside a start_locale_structure/
|
|
end_locale_structure block. */
|
|
static void
|
|
record_offset (struct locale_file *file)
|
|
{
|
|
if (file->structure_stage < 2)
|
|
{
|
|
assert (file->next_element < file->n_elements);
|
|
file->offsets[file->next_element++]
|
|
= (obstack_object_size (&file->data)
|
|
+ (file->n_elements + 2) * sizeof (uint32_t));
|
|
if (file->structure_stage == 1)
|
|
file->structure_stage = 2;
|
|
}
|
|
}
|
|
|
|
/* Initialize FILE for a new output file. N_ELEMENTS is the number
|
|
of elements in the file. */
|
|
void
|
|
init_locale_data (struct locale_file *file, size_t n_elements)
|
|
{
|
|
file->n_elements = n_elements;
|
|
file->next_element = 0;
|
|
file->offsets = xmalloc (sizeof (uint32_t) * n_elements);
|
|
obstack_init (&file->data);
|
|
file->structure_stage = 0;
|
|
}
|
|
|
|
/* Align the size of FILE's obstack object to BOUNDARY bytes. */
|
|
void
|
|
align_locale_data (struct locale_file *file, size_t boundary)
|
|
{
|
|
size_t size = -obstack_object_size (&file->data) & (boundary - 1);
|
|
obstack_blank (&file->data, size);
|
|
memset (obstack_next_free (&file->data) - size, 0, size);
|
|
}
|
|
|
|
/* Record that FILE's next element contains no data. */
|
|
void
|
|
add_locale_empty (struct locale_file *file)
|
|
{
|
|
record_offset (file);
|
|
}
|
|
|
|
/* Record that FILE's next element consists of SIZE bytes starting at DATA. */
|
|
void
|
|
add_locale_raw_data (struct locale_file *file, const void *data, size_t size)
|
|
{
|
|
record_offset (file);
|
|
obstack_grow (&file->data, data, size);
|
|
}
|
|
|
|
/* Finish the current object on OBSTACK and use it as the data for FILE's
|
|
next element. */
|
|
void
|
|
add_locale_raw_obstack (struct locale_file *file, struct obstack *obstack)
|
|
{
|
|
size_t size = obstack_object_size (obstack);
|
|
record_offset (file);
|
|
obstack_grow (&file->data, obstack_finish (obstack), size);
|
|
}
|
|
|
|
/* Use STRING as FILE's next element. */
|
|
void
|
|
add_locale_string (struct locale_file *file, const char *string)
|
|
{
|
|
record_offset (file);
|
|
obstack_grow (&file->data, string, strlen (string) + 1);
|
|
}
|
|
|
|
/* Likewise for wide strings. */
|
|
void
|
|
add_locale_wstring (struct locale_file *file, const uint32_t *string)
|
|
{
|
|
add_locale_uint32_array (file, string, wcslen ((const wchar_t *) string) + 1);
|
|
}
|
|
|
|
/* Record that FILE's next element is the 32-bit integer VALUE. */
|
|
void
|
|
add_locale_uint32 (struct locale_file *file, uint32_t value)
|
|
{
|
|
align_locale_data (file, LOCFILE_ALIGN);
|
|
record_offset (file);
|
|
value = maybe_swap_uint32 (value);
|
|
obstack_grow (&file->data, &value, sizeof (value));
|
|
}
|
|
|
|
/* Record that FILE's next element is an array of N_ELEMS integers
|
|
starting at DATA. */
|
|
void
|
|
add_locale_uint32_array (struct locale_file *file,
|
|
const uint32_t *data, size_t n_elems)
|
|
{
|
|
align_locale_data (file, LOCFILE_ALIGN);
|
|
record_offset (file);
|
|
obstack_grow (&file->data, data, n_elems * sizeof (uint32_t));
|
|
maybe_swap_uint32_obstack (&file->data, n_elems);
|
|
}
|
|
|
|
/* Record that FILE's next element is the single byte given by VALUE. */
|
|
void
|
|
add_locale_char (struct locale_file *file, char value)
|
|
{
|
|
record_offset (file);
|
|
obstack_1grow (&file->data, value);
|
|
}
|
|
|
|
/* Start building an element that contains several different pieces of data.
|
|
Subsequent calls to add_locale_* will add data to the same element up
|
|
till the next call to end_locale_structure. The element's alignment
|
|
is dictated by the first piece of data added to it. */
|
|
void
|
|
start_locale_structure (struct locale_file *file)
|
|
{
|
|
assert (file->structure_stage == 0);
|
|
file->structure_stage = 1;
|
|
}
|
|
|
|
/* Finish a structure element that was started by start_locale_structure.
|
|
Empty structures are OK and behave like add_locale_empty. */
|
|
void
|
|
end_locale_structure (struct locale_file *file)
|
|
{
|
|
record_offset (file);
|
|
assert (file->structure_stage == 2);
|
|
file->structure_stage = 0;
|
|
}
|
|
|
|
/* Start building data that goes before the next element's recorded offset.
|
|
Subsequent calls to add_locale_* will add data to the file without
|
|
treating any of it as the start of a new element. Calling
|
|
end_locale_prelude switches back to the usual behavior. */
|
|
void
|
|
start_locale_prelude (struct locale_file *file)
|
|
{
|
|
assert (file->structure_stage == 0);
|
|
file->structure_stage = 3;
|
|
}
|
|
|
|
/* End a block started by start_locale_prelude. */
|
|
void
|
|
end_locale_prelude (struct locale_file *file)
|
|
{
|
|
assert (file->structure_stage == 3);
|
|
file->structure_stage = 0;
|
|
}
|
|
|
|
/* Write a locale file, with contents given by FILE. */
|
|
void
|
|
write_locale_data (const char *output_path, int catidx, const char *category,
|
|
struct locale_file *file)
|
|
{
|
|
size_t cnt, step, maxiov;
|
|
int fd;
|
|
char *fname;
|
|
const char **other_paths;
|
|
uint32_t header[2];
|
|
size_t n_elem;
|
|
struct iovec vec[3];
|
|
|
|
assert (file->n_elements == file->next_element);
|
|
header[0] = LIMAGIC (catidx);
|
|
header[1] = file->n_elements;
|
|
vec[0].iov_len = sizeof (header);
|
|
vec[0].iov_base = header;
|
|
vec[1].iov_len = sizeof (uint32_t) * file->n_elements;
|
|
vec[1].iov_base = file->offsets;
|
|
vec[2].iov_len = obstack_object_size (&file->data);
|
|
vec[2].iov_base = obstack_finish (&file->data);
|
|
maybe_swap_uint32_array (vec[0].iov_base, 2);
|
|
maybe_swap_uint32_array (vec[1].iov_base, file->n_elements);
|
|
n_elem = 3;
|
|
if (! no_archive)
|
|
{
|
|
/* The data will be added to the archive. For now we simply
|
|
generate the image which will be written. First determine
|
|
the size. */
|
|
int cnt;
|
|
void *endp;
|
|
|
|
to_archive[catidx].size = 0;
|
|
for (cnt = 0; cnt < n_elem; ++cnt)
|
|
to_archive[catidx].size += vec[cnt].iov_len;
|
|
|
|
/* Allocate the memory for it. */
|
|
to_archive[catidx].addr = xmalloc (to_archive[catidx].size);
|
|
|
|
/* Fill it in. */
|
|
for (cnt = 0, endp = to_archive[catidx].addr; cnt < n_elem; ++cnt)
|
|
endp = mempcpy (endp, vec[cnt].iov_base, vec[cnt].iov_len);
|
|
|
|
/* Compute the MD5 sum for the data. */
|
|
__md5_buffer (to_archive[catidx].addr, to_archive[catidx].size,
|
|
to_archive[catidx].sum);
|
|
|
|
return;
|
|
}
|
|
|
|
fname = xmalloc (strlen (output_path) + 2 * strlen (category) + 7);
|
|
|
|
/* Normally we write to the directory pointed to by the OUTPUT_PATH.
|
|
But for LC_MESSAGES we have to take care for the translation
|
|
data. This means we need to have a directory LC_MESSAGES in
|
|
which we place the file under the name SYS_LC_MESSAGES. */
|
|
sprintf (fname, "%s%s", output_path, category);
|
|
fd = -2;
|
|
if (strcmp (category, "LC_MESSAGES") == 0)
|
|
{
|
|
struct stat64 st;
|
|
|
|
if (stat64 (fname, &st) < 0)
|
|
{
|
|
if (mkdir (fname, 0777) >= 0)
|
|
{
|
|
fd = -1;
|
|
errno = EISDIR;
|
|
}
|
|
}
|
|
else if (!S_ISREG (st.st_mode))
|
|
{
|
|
fd = -1;
|
|
errno = EISDIR;
|
|
}
|
|
}
|
|
|
|
/* Create the locale file with nlinks == 1; this avoids crashing processes
|
|
which currently use the locale and damaging files belonging to other
|
|
locales as well. */
|
|
if (fd == -2)
|
|
{
|
|
unlink (fname);
|
|
fd = creat (fname, 0666);
|
|
}
|
|
|
|
if (fd == -1)
|
|
{
|
|
int save_err = errno;
|
|
|
|
if (errno == EISDIR)
|
|
{
|
|
sprintf (fname, "%1$s%2$s/SYS_%2$s", output_path, category);
|
|
unlink (fname);
|
|
fd = creat (fname, 0666);
|
|
if (fd == -1)
|
|
save_err = errno;
|
|
}
|
|
|
|
if (fd == -1)
|
|
{
|
|
record_error (0, save_err, _("\
|
|
cannot open output file `%s' for category `%s'"), fname, category);
|
|
free (fname);
|
|
return;
|
|
}
|
|
}
|
|
|
|
#ifdef UIO_MAXIOV
|
|
maxiov = UIO_MAXIOV;
|
|
#else
|
|
maxiov = sysconf (_SC_UIO_MAXIOV);
|
|
#endif
|
|
|
|
/* Write the data using writev. But we must take care for the
|
|
limitation of the implementation. */
|
|
for (cnt = 0; cnt < n_elem; cnt += step)
|
|
{
|
|
step = n_elem - cnt;
|
|
if (maxiov > 0)
|
|
step = MIN (maxiov, step);
|
|
|
|
if (writev (fd, &vec[cnt], step) < 0)
|
|
{
|
|
record_error (0, errno, _("\
|
|
failure while writing data for category `%s'"), category);
|
|
break;
|
|
}
|
|
}
|
|
|
|
close (fd);
|
|
|
|
/* Compare the file with the locale data files for the same category in
|
|
other locales, and see if we can reuse it, to save disk space. */
|
|
other_paths = siblings (output_path);
|
|
if (other_paths != NULL)
|
|
{
|
|
struct stat64 fname_stat;
|
|
|
|
if (lstat64 (fname, &fname_stat) >= 0
|
|
&& S_ISREG (fname_stat.st_mode))
|
|
{
|
|
const char *fname_tail = fname + strlen (output_path);
|
|
const char **other_p;
|
|
int seen_count;
|
|
ino_t *seen_inodes;
|
|
|
|
seen_count = 0;
|
|
for (other_p = other_paths; *other_p; other_p++)
|
|
seen_count++;
|
|
seen_inodes = (ino_t *) xmalloc (seen_count * sizeof (ino_t));
|
|
seen_count = 0;
|
|
|
|
for (other_p = other_paths; *other_p; other_p++)
|
|
{
|
|
const char *other_path = *other_p;
|
|
size_t other_path_len = strlen (other_path);
|
|
char *other_fname;
|
|
struct stat64 other_fname_stat;
|
|
|
|
other_fname =
|
|
(char *) xmalloc (other_path_len + strlen (fname_tail) + 1);
|
|
memcpy (other_fname, other_path, other_path_len);
|
|
strcpy (other_fname + other_path_len, fname_tail);
|
|
|
|
if (lstat64 (other_fname, &other_fname_stat) >= 0
|
|
&& S_ISREG (other_fname_stat.st_mode)
|
|
/* Consider only files on the same device.
|
|
Otherwise hard linking won't work anyway. */
|
|
&& other_fname_stat.st_dev == fname_stat.st_dev
|
|
/* Consider only files with the same permissions.
|
|
Otherwise there are security risks. */
|
|
&& other_fname_stat.st_uid == fname_stat.st_uid
|
|
&& other_fname_stat.st_gid == fname_stat.st_gid
|
|
&& other_fname_stat.st_mode == fname_stat.st_mode
|
|
/* Don't compare fname with itself. */
|
|
&& other_fname_stat.st_ino != fname_stat.st_ino
|
|
/* Files must have the same size, otherwise they
|
|
cannot be the same. */
|
|
&& other_fname_stat.st_size == fname_stat.st_size)
|
|
{
|
|
/* Skip this file if we have already read it (under a
|
|
different name). */
|
|
int i;
|
|
|
|
for (i = seen_count - 1; i >= 0; i--)
|
|
if (seen_inodes[i] == other_fname_stat.st_ino)
|
|
break;
|
|
if (i < 0)
|
|
{
|
|
/* Now compare fname and other_fname for real. */
|
|
blksize_t blocksize;
|
|
|
|
#ifdef _STATBUF_ST_BLKSIZE
|
|
blocksize = MAX (fname_stat.st_blksize,
|
|
other_fname_stat.st_blksize);
|
|
if (blocksize > 8 * 1024)
|
|
blocksize = 8 * 1024;
|
|
#else
|
|
blocksize = 8 * 1024;
|
|
#endif
|
|
|
|
if (compare_files (fname, other_fname,
|
|
fname_stat.st_size, blocksize) == 0)
|
|
{
|
|
/* Found! other_fname is identical to fname. */
|
|
/* Link other_fname to fname. But use a temporary
|
|
file, in case hard links don't work on the
|
|
particular filesystem. */
|
|
char * tmp_fname =
|
|
(char *) xmalloc (strlen (fname) + 4 + 1);
|
|
|
|
strcpy (stpcpy (tmp_fname, fname), ".tmp");
|
|
|
|
if (link (other_fname, tmp_fname) >= 0)
|
|
{
|
|
unlink (fname);
|
|
if (rename (tmp_fname, fname) < 0)
|
|
{
|
|
record_error (0, errno, _("\
|
|
cannot create output file `%s' for category `%s'"), fname, category);
|
|
}
|
|
free (tmp_fname);
|
|
free (other_fname);
|
|
break;
|
|
}
|
|
free (tmp_fname);
|
|
}
|
|
|
|
/* Don't compare with this file a second time. */
|
|
seen_inodes[seen_count++] = other_fname_stat.st_ino;
|
|
}
|
|
}
|
|
free (other_fname);
|
|
}
|
|
free (seen_inodes);
|
|
}
|
|
}
|
|
|
|
free (fname);
|
|
}
|
|
|
|
|
|
/* General handling of `copy'. */
|
|
void
|
|
handle_copy (struct linereader *ldfile, const struct charmap_t *charmap,
|
|
const char *repertoire_name, struct localedef_t *result,
|
|
enum token_t token, int locale, const char *locale_name,
|
|
int ignore_content)
|
|
{
|
|
struct token *now;
|
|
int warned = 0;
|
|
|
|
now = lr_token (ldfile, charmap, result, NULL, verbose);
|
|
if (now->tok != tok_string)
|
|
lr_error (ldfile, _("expecting string argument for `copy'"));
|
|
else if (!ignore_content)
|
|
{
|
|
if (now->val.str.startmb == NULL)
|
|
lr_error (ldfile, _("\
|
|
locale name should consist only of portable characters"));
|
|
else
|
|
{
|
|
(void) add_to_readlist (locale, now->val.str.startmb,
|
|
repertoire_name, 1, NULL);
|
|
result->copy_name[locale] = now->val.str.startmb;
|
|
}
|
|
}
|
|
|
|
lr_ignore_rest (ldfile, now->tok == tok_string);
|
|
|
|
/* The rest of the line must be empty and the next keyword must be
|
|
`END xxx'. */
|
|
while ((now = lr_token (ldfile, charmap, result, NULL, verbose))->tok
|
|
!= tok_end && now->tok != tok_eof)
|
|
{
|
|
if (warned == 0)
|
|
{
|
|
lr_error (ldfile, _("\
|
|
no other keyword shall be specified when `copy' is used"));
|
|
warned = 1;
|
|
}
|
|
|
|
lr_ignore_rest (ldfile, 0);
|
|
}
|
|
|
|
if (now->tok != tok_eof)
|
|
{
|
|
/* Handle `END xxx'. */
|
|
now = lr_token (ldfile, charmap, result, NULL, verbose);
|
|
|
|
if (now->tok != token)
|
|
lr_error (ldfile, _("\
|
|
`%1$s' definition does not end with `END %1$s'"), locale_name);
|
|
|
|
lr_ignore_rest (ldfile, now->tok == token);
|
|
}
|
|
else
|
|
/* When we come here we reached the end of the file. */
|
|
lr_error (ldfile, _("%s: premature end of file"), locale_name);
|
|
}
|