/* Code to load locale data from the locale archive file.
- Copyright (C) 2002 Free Software Foundation, Inc.
+ Copyright (C) 2002-2020 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
- License along with the GNU C Library; if not, write to the Free
- Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
- 02111-1307 USA. */
+ License along with the GNU C Library; if not, see
+ <https://www.gnu.org/licenses/>. */
#include <locale.h>
#include <stddef.h>
+#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <assert.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
+#include <stdint.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <sys/param.h>
#include "localeinfo.h"
#include "locarchive.h"
+#include <not-cancel.h>
/* Define the hash function. We define the function as static inline. */
#define compute_hashval static inline compute_hashval
+#define hashval_t uint32_t
#include "hashval.h"
#undef compute_hashval
-#undef LOCALEDIR
-#define LOCALEDIR "/spare/roland/tmp/usr/lib/locale/"
/* Name of the locale archive file. */
-static const char archfname[] = LOCALEDIR "locale-archive";
-
+static const char archfname[] = COMPLOCALEDIR "/locale-archive";
+
+/* Size of initial mapping window, optimal if large enough to
+ cover the header plus the initial locale. */
+#define ARCHIVE_MAPPING_WINDOW (2 * 1024 * 1024)
+
+#ifndef MAP_COPY
+/* This is not quite as good as MAP_COPY since unexamined pages
+ can change out from under us and give us inconsistent data.
+ But we rely on the user not to diddle the system's live archive.
+ Even though we only ever use PROT_READ, using MAP_SHARED would
+ not give the system sufficient freedom to e.g. let the on disk
+ file go away because it doesn't know we won't call mprotect later. */
+# define MAP_COPY MAP_PRIVATE
+#endif
+#ifndef MAP_FILE
+ /* Some systems do not have this flag; it is superfluous. */
+# define MAP_FILE 0
+#endif
/* Record of contiguous pages already mapped from the locale archive. */
struct archmapped
struct locale_in_archive
{
struct locale_in_archive *next;
- const char *name;
- struct locale_data *data[__LC_LAST];
+ char *name;
+ struct __locale_data *data[__LC_LAST];
};
static struct locale_in_archive *archloaded;
already been loaded from the archive, just returns the existing data
structure. If successful, sets *NAMEP to point directly into the mapped
archive string table; that way, the next call can short-circuit strcmp. */
-struct locale_data *
-internal_function
+struct __locale_data *
_nl_load_locale_from_archive (int category, const char **namep)
{
const char *name = *namep;
memcpy (__mempcpy (__mempcpy (newname, name, p - name),
normalized_codeset, normlen),
rest, restlen);
- free ((char *) normalized_codeset);
name = newname;
}
+ free ((char *) normalized_codeset);
}
}
/* Make sure the archive is loaded. */
if (archmapped == NULL)
{
+ void *result;
+ size_t headsize, mapsize;
+
/* We do this early as a sign that we have tried to open the archive.
If headmap.ptr remains null, that's an indication that we tried
and failed, so we won't try again. */
archmapped = &headmap;
/* The archive has never been opened. */
- fd = __open64 (archfname, O_RDONLY);
+ fd = __open_nocancel (archfname, O_RDONLY|O_LARGEFILE|O_CLOEXEC);
if (fd < 0)
/* Cannot open the archive, for whatever reason. */
return NULL;
{
/* stat failed, very strange. */
close_and_out:
- __close (fd);
+ if (fd >= 0)
+ __close_nocancel_nostatus (fd);
return NULL;
}
- if (sizeof (void *) > 4)
- {
- /* We will just map the whole file, what the hell. */
- void *result = __mmap64 (NULL, archive_stat.st_size,
- PROT_READ, MAP_SHARED, fd, 0);
- if (result == MAP_FAILED)
- goto close_and_out;
- /* Check whether the file is large enough for the sizes given in the
- header. */
- if (calculate_head_size ((const struct locarhead *) result)
- > archive_stat.st_size)
- {
- (void) munmap (result, archive_stat.st_size);
- goto close_and_out;
- }
- __close (fd);
- fd = -1;
- headmap.ptr = result;
- /* headmap.from already initialized to zero. */
- headmap.len = archive_stat.st_size;
- }
- else
- {
- struct locarhead head;
- off_t head_size;
- void *result;
+ /* Map an initial window probably large enough to cover the header
+ and the first locale's data. With a large address space, we can
+ just map the whole file and be sure everything is covered. */
- if (TEMP_FAILURE_RETRY (__read (fd, &head, sizeof (head)))
- != sizeof (head))
- goto close_and_out;
- head_size = calculate_head_size (&head);
- if (head_size > archive_stat.st_size)
+ mapsize = (sizeof (void *) > 4 ? archive_stat.st_size
+ : MIN (archive_stat.st_size, ARCHIVE_MAPPING_WINDOW));
+
+ result = __mmap64 (NULL, mapsize, PROT_READ, MAP_FILE|MAP_COPY, fd, 0);
+ if (result == MAP_FAILED)
+ goto close_and_out;
+
+ /* Check whether the file is large enough for the sizes given in
+ the header. Theoretically an archive could be so large that
+ just the header fails to fit in our initial mapping window. */
+ headsize = calculate_head_size ((const struct locarhead *) result);
+ if (headsize > mapsize)
+ {
+ (void) __munmap (result, mapsize);
+ if (sizeof (void *) > 4 || headsize > archive_stat.st_size)
+ /* The file is not big enough for the header. Bogus. */
goto close_and_out;
- result = __mmap64 (NULL, head_size, PROT_READ, MAP_SHARED, fd, 0);
+
+ /* Freakishly long header. */
+ /* XXX could use mremap when available */
+ mapsize = (headsize + ps - 1) & ~(ps - 1);
+ result = __mmap64 (NULL, mapsize, PROT_READ, MAP_FILE|MAP_COPY,
+ fd, 0);
if (result == MAP_FAILED)
goto close_and_out;
+ }
- /* Record that we have mapped the initial pages of the file. */
- headmap.ptr = result;
- headmap.len = MIN ((head_size + ps - 1) & ~(ps - 1),
- archive_stat.st_size);
+ if (sizeof (void *) > 4 || mapsize >= archive_stat.st_size)
+ {
+ /* We've mapped the whole file already, so we can be
+ sure we won't need this file descriptor later. */
+ __close_nocancel_nostatus (fd);
+ fd = -1;
}
+
+ headmap.ptr = result;
+ /* headmap.from already initialized to zero. */
+ headmap.len = mapsize;
}
/* If there is no archive or it cannot be loaded for some reason fail. */
- if (__builtin_expect (headmap.ptr == NULL, 0))
- return NULL;
+ if (__glibc_unlikely (headmap.ptr == NULL))
+ goto close_and_out;
/* We have the archive available. To find the name we first have to
determine its hash value. */
namehashtab = (struct namehashent *) ((char *) head
+ head->namehash_offset);
+ /* Avoid division by 0 if the file is corrupted. */
+ if (__glibc_unlikely (head->namehash_size <= 2))
+ goto close_and_out;
+
idx = hval % head->namehash_size;
incr = 1 + hval % (head->namehash_size - 2);
{
if (namehashtab[idx].name_offset == 0)
/* Not found. */
- return NULL;
+ goto close_and_out;
if (namehashtab[idx].hashval == hval
&& strcmp (name, headmap.ptr + namehashtab[idx].name_offset) == 0)
/* We found an entry. It might be a placeholder for a removed one. */
if (namehashtab[idx].locrec_offset == 0)
- return NULL;
+ goto close_and_out;
locrec = (struct locrecent *) (headmap.ptr + namehashtab[idx].locrec_offset);
if (locrec->record[cnt].offset + locrec->record[cnt].len
> headmap.len)
/* The archive locrectab contains bogus offsets. */
- return NULL;
+ goto close_and_out;
results[cnt].addr = headmap.ptr + locrec->record[cnt].offset;
results[cnt].len = locrec->record[cnt].len;
}
/* Determine whether the appropriate page is already mapped. */
while (mapped != NULL
- && mapped->from + mapped->len <= ranges[cnt].from)
+ && (mapped->from + mapped->len
+ <= ranges[cnt].from + ranges[cnt].len))
{
last = mapped;
mapped = mapped->next;
/* Do we have a match? */
if (mapped != NULL
&& mapped->from <= ranges[cnt].from
- && ((char *) ranges[cnt].from + ranges[cnt].len
- <= (char *) mapped->from + mapped->len))
+ && (ranges[cnt].from + ranges[cnt].len
+ <= mapped->from + mapped->len))
{
/* Yep, already loaded. */
results[ranges[cnt].category].addr = ((char *) mapped->ptr
upper = cnt;
do
{
- to = ((ranges[upper].from + ranges[upper].len + ps - 1)
- & ~(ps - 1));
+ to = ranges[upper].from + ranges[upper].len;
+ if (to > (size_t) archive_stat.st_size)
+ /* The archive locrectab contains bogus offsets. */
+ goto close_and_out;
+ to = (to + ps - 1) & ~(ps - 1);
+
+ /* If a range is already mmaped in, stop. */
+ if (mapped != NULL && ranges[upper].from >= mapped->from)
+ break;
+
++upper;
}
/* Loop while still in contiguous pages. */
while (upper < nranges && ranges[upper].from < to + ps);
- if (to > archive_stat.st_size)
- /* The archive locrectab contains bogus offsets. */
- return NULL;
-
/* Open the file if it hasn't happened yet. */
if (fd == -1)
{
struct stat64 st;
- fd = __open64 (archfname, O_RDONLY);
+ fd = __open_nocancel (archfname,
+ O_RDONLY|O_LARGEFILE|O_CLOEXEC);
if (fd == -1)
/* Cannot open the archive, for whatever reason. */
return NULL;
|| st.st_mtime != archive_stat.st_mtime
|| st.st_dev != archive_stat.st_dev
|| st.st_ino != archive_stat.st_ino)
- return NULL;
+ goto close_and_out;
}
/* Map the range from the archive. */
- addr = __mmap64 (NULL, to - from, PROT_READ, MAP_SHARED, fd, from);
+ addr = __mmap64 (NULL, to - from, PROT_READ, MAP_FILE|MAP_COPY,
+ fd, from);
if (addr == MAP_FAILED)
- return NULL;
+ goto close_and_out;
/* Allocate a record for this mapping. */
newp = (struct archmapped *) malloc (sizeof (struct archmapped));
if (newp == NULL)
{
- (void) munmap (addr, to - from);
- return NULL;
+ (void) __munmap (addr, to - from);
+ goto close_and_out;
}
/* And queue it. */
}
}
+ /* We don't need the file descriptor any longer. */
+ if (fd >= 0)
+ __close_nocancel_nostatus (fd);
+ fd = -1;
+
/* We succeeded in mapping all the necessary regions of the archive.
Now we need the expected data structures to point into the data. */
lia = malloc (sizeof *lia);
- if (__builtin_expect (lia == NULL, 0))
+ if (__glibc_unlikely (lia == NULL))
return NULL;
- lia->name = headmap.ptr + namehashtab[idx].name_offset;
+ lia->name = __strdup (*namep);
+ if (__glibc_unlikely (lia->name == NULL))
+ {
+ free (lia);
+ return NULL;
+ }
+
lia->next = archloaded;
archloaded = lia;
lia->data[cnt] = _nl_intern_locale_data (cnt,
results[cnt].addr,
results[cnt].len);
- if (__builtin_expect (lia->data[cnt] != NULL, 1))
+ if (__glibc_likely (lia->data[cnt] != NULL))
{
/* _nl_intern_locale_data leaves us these fields to initialize. */
lia->data[cnt]->alloc = ld_archive;
lia->data[cnt]->name = lia->name;
+
+ /* We do this instead of bumping the count each time we return
+ this data because the mappings stay around forever anyway
+ and we might as well hold on to a little more memory and not
+ have to rebuild it on the next lookup of the same thing.
+ If we were to maintain the usage_count normally and let the
+ structures be freed, we would have to remove the elements
+ from archloaded too. */
+ lia->data[cnt]->usage_count = UNDELETABLE;
}
}
return lia->data[category];
}
-void
+void __libc_freeres_fn_section
_nl_archive_subfreeres (void)
{
struct locale_in_archive *lia;
struct locale_in_archive *dead = lia;
lia = lia->next;
+ free (dead->name);
for (category = 0; category < __LC_LAST; ++category)
- if (category != LC_ALL)
- /* _nl_unload_locale just does this free for the archive case. */
- free (dead->data[category]);
+ if (category != LC_ALL && dead->data[category] != NULL)
+ {
+ /* _nl_unload_locale just does this free for the archive case. */
+ if (dead->data[category]->private.cleanup)
+ (*dead->data[category]->private.cleanup) (dead->data[category]);
+
+ free (dead->data[category]);
+ }
free (dead);
}
archloaded = NULL;
assert (archmapped == &headmap);
archmapped = NULL;
- (void) munmap (headmap.ptr, headmap.len);
+ (void) __munmap (headmap.ptr, headmap.len);
am = headmap.next;
while (am != NULL)
{
struct archmapped *dead = am;
am = am->next;
- (void) munmap (dead->ptr, dead->len);
+ (void) __munmap (dead->ptr, dead->len);
free (dead);
}
}