mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-25 06:20:06 +00:00
30891f35fa
We stopped adding "Contributed by" or similar lines in sources in 2012 in favour of git logs and keeping the Contributors section of the glibc manual up to date. Removing these lines makes the license header a bit more consistent across files and also removes the possibility of error in attribution when license blocks or files are copied across since the contributed-by lines don't actually reflect reality in those cases. Move all "Contributed by" and similar lines (Written by, Test by, etc.) into a new file CONTRIBUTED-BY to retain record of these contributions. These contributors are also mentioned in manual/contrib.texi, so we just maintain this additional record as a courtesy to the earlier developers. The following scripts were used to filter a list of files to edit in place and to clean up the CONTRIBUTED-BY file respectively. These were not added to the glibc sources because they're not expected to be of any use in future given that this is a one time task: https://gist.github.com/siddhesh/b5ecac94eabfd72ed2916d6d8157e7dc https://gist.github.com/siddhesh/15ea1f5e435ace9774f485030695ee02 Reviewed-by: Carlos O'Donell <carlos@redhat.com>
1011 lines
27 KiB
C
1011 lines
27 KiB
C
/* Copyright (C) 1996-2021 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
|
|
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 <https://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 = NULL;
|
|
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.
|
|
If the user specified --no-hard-links to localedef then hard_links
|
|
is false, other_paths remains NULL and we skip the optimization
|
|
below. The use of --no-hard-links is distribution specific since
|
|
some distros have post-processing hard-link steps and so doing this
|
|
here is a waste of time. Worse than a waste of time in rpm-based
|
|
distributions it can result in build determinism issues from
|
|
build-to-build since some files may get a hard link in one pass but
|
|
not in another (if the files happened to be created in parallel). */
|
|
if (hard_links)
|
|
other_paths = siblings (output_path);
|
|
|
|
/* If there are other paths, then walk the sibling paths looking for
|
|
files with the same content so we can hard link and reduce disk
|
|
space usage. */
|
|
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);
|
|
}
|