| /* Code to load locale data from the locale archive file. |
| Copyright (C) 2002, 2003, 2005, 2010 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 |
| modify it under the terms of the GNU Lesser General Public |
| License as published by the Free Software Foundation; either |
| version 2.1 of the License, or (at your option) any later version. |
| |
| The GNU C Library 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 |
| 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. */ |
| |
| #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 <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 |
| |
| |
| /* Name of the locale archive file. */ |
| static const char archfname[] = LOCALEDIR "/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 |
| { |
| void *ptr; |
| uint32_t from; |
| uint32_t len; |
| struct archmapped *next; |
| }; |
| static struct archmapped *archmapped; |
| |
| /* This describes the mapping at the beginning of the file that contains |
| the header data. There could be data in the following partial page, |
| so this is searched like any other. Once the archive has been used, |
| ARCHMAPPED points to this; if mapping the archive header failed, |
| then headmap.ptr is null. */ |
| static struct archmapped headmap; |
| static struct stat64 archive_stat; /* stat of archive when header mapped. */ |
| |
| /* Record of locales that we have already loaded from the archive. */ |
| struct locale_in_archive |
| { |
| struct locale_in_archive *next; |
| char *name; |
| struct __locale_data *data[__LC_LAST]; |
| }; |
| static struct locale_in_archive *archloaded; |
| |
| |
| /* Local structure and subroutine of _nl_load_archive, see below. */ |
| struct range |
| { |
| uint32_t from; |
| uint32_t len; |
| int category; |
| void *result; |
| }; |
| |
| static int |
| rangecmp (const void *p1, const void *p2) |
| { |
| return ((struct range *) p1)->from - ((struct range *) p2)->from; |
| } |
| |
| |
| /* Calculate the amount of space needed for all the tables described |
| by the given header. Note we do not include the empty table space |
| that has been preallocated in the file, so our mapping may not be |
| large enough if localedef adds data to the file in place. However, |
| doing that would permute the header fields while we are accessing |
| them and thus not be safe anyway, so we don't allow for that. */ |
| static inline off_t |
| calculate_head_size (const struct locarhead *h) |
| { |
| off_t namehash_end = (h->namehash_offset |
| + h->namehash_size * sizeof (struct namehashent)); |
| off_t string_end = h->string_offset + h->string_used; |
| off_t locrectab_end = (h->locrectab_offset |
| + h->locrectab_used * sizeof (struct locrecent)); |
| return MAX (namehash_end, MAX (string_end, locrectab_end)); |
| } |
| |
| |
| /* Find the locale *NAMEP in the locale archive, and return the |
| internalized data structure for its CATEGORY data. If this locale has |
| 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 |
| _nl_load_locale_from_archive (int category, const char **namep) |
| { |
| const char *name = *namep; |
| struct |
| { |
| void *addr; |
| size_t len; |
| } results[__LC_LAST]; |
| struct locale_in_archive *lia; |
| struct locarhead *head; |
| struct namehashent *namehashtab; |
| struct locrecent *locrec; |
| struct archmapped *mapped; |
| struct archmapped *last; |
| unsigned long int hval; |
| size_t idx; |
| size_t incr; |
| struct range ranges[__LC_LAST - 1]; |
| int nranges; |
| int cnt; |
| size_t ps = __sysconf (_SC_PAGE_SIZE); |
| int fd = -1; |
| |
| /* Check if we have already loaded this locale from the archive. |
| If we previously loaded the locale but found bogons in the data, |
| then we will have stored a null pointer to return here. */ |
| for (lia = archloaded; lia != NULL; lia = lia->next) |
| if (name == lia->name || !strcmp (name, lia->name)) |
| { |
| *namep = lia->name; |
| return lia->data[category]; |
| } |
| |
| { |
| /* If the name contains a codeset, then we normalize the name before |
| doing the lookup. */ |
| const char *p = strchr (name, '.'); |
| if (p != NULL && p[1] != '@' && p[1] != '\0') |
| { |
| const char *rest = __strchrnul (++p, '@'); |
| const char *normalized_codeset = _nl_normalize_codeset (p, rest - p); |
| if (normalized_codeset == NULL) /* malloc failure */ |
| return NULL; |
| if (strncmp (normalized_codeset, p, rest - p) != 0 |
| || normalized_codeset[rest - p] != '\0') |
| { |
| /* There is a normalized codeset name that is different from |
| what was specified; reconstruct a new locale name using it. */ |
| size_t normlen = strlen (normalized_codeset); |
| size_t restlen = strlen (rest) + 1; |
| char *newname = alloca (p - name + normlen + restlen); |
| memcpy (__mempcpy (__mempcpy (newname, name, p - name), |
| normalized_codeset, normlen), |
| rest, restlen); |
| 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 = open_not_cancel_2 (archfname, O_RDONLY|O_LARGEFILE); |
| if (fd < 0) |
| /* Cannot open the archive, for whatever reason. */ |
| return NULL; |
| |
| if (__fxstat64 (_STAT_VER, fd, &archive_stat) == -1) |
| { |
| /* stat failed, very strange. */ |
| close_and_out: |
| if (fd >= 0) |
| close_not_cancel_no_status (fd); |
| return NULL; |
| } |
| |
| |
| /* 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. */ |
| |
| 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; |
| |
| /* 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; |
| } |
| |
| 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_not_cancel_no_status (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)) |
| goto close_and_out; |
| |
| /* We have the archive available. To find the name we first have to |
| determine its hash value. */ |
| hval = compute_hashval (name, strlen (name)); |
| |
| head = headmap.ptr; |
| namehashtab = (struct namehashent *) ((char *) head |
| + head->namehash_offset); |
| |
| idx = hval % head->namehash_size; |
| incr = 1 + hval % (head->namehash_size - 2); |
| |
| /* If the name_offset field is zero this means this is a |
| deleted entry and therefore no entry can be found. */ |
| while (1) |
| { |
| if (namehashtab[idx].name_offset == 0) |
| /* Not found. */ |
| goto close_and_out; |
| |
| if (namehashtab[idx].hashval == hval |
| && strcmp (name, headmap.ptr + namehashtab[idx].name_offset) == 0) |
| /* Found the entry. */ |
| break; |
| |
| idx += incr; |
| if (idx >= head->namehash_size) |
| idx -= head->namehash_size; |
| } |
| |
| /* We found an entry. It might be a placeholder for a removed one. */ |
| if (namehashtab[idx].locrec_offset == 0) |
| goto close_and_out; |
| |
| locrec = (struct locrecent *) (headmap.ptr + namehashtab[idx].locrec_offset); |
| |
| if (sizeof (void *) > 4 /* || headmap.len == archive_stat.st_size */) |
| { |
| /* We already have the whole locale archive mapped in. */ |
| assert (headmap.len == archive_stat.st_size); |
| for (cnt = 0; cnt < __LC_LAST; ++cnt) |
| if (cnt != LC_ALL) |
| { |
| if (locrec->record[cnt].offset + locrec->record[cnt].len |
| > headmap.len) |
| /* The archive locrectab contains bogus offsets. */ |
| goto close_and_out; |
| results[cnt].addr = headmap.ptr + locrec->record[cnt].offset; |
| results[cnt].len = locrec->record[cnt].len; |
| } |
| } |
| else |
| { |
| /* Get the offsets of the data files and sort them. */ |
| for (cnt = nranges = 0; cnt < __LC_LAST; ++cnt) |
| if (cnt != LC_ALL) |
| { |
| ranges[nranges].from = locrec->record[cnt].offset; |
| ranges[nranges].len = locrec->record[cnt].len; |
| ranges[nranges].category = cnt; |
| ranges[nranges].result = NULL; |
| |
| ++nranges; |
| } |
| |
| qsort (ranges, nranges, sizeof (ranges[0]), rangecmp); |
| |
| /* The information about mmap'd blocks is kept in a list. |
| Skip over the blocks which are before the data we need. */ |
| last = mapped = archmapped; |
| for (cnt = 0; cnt < nranges; ++cnt) |
| { |
| int upper; |
| size_t from; |
| size_t to; |
| void *addr; |
| struct archmapped *newp; |
| |
| /* Determine whether the appropriate page is already mapped. */ |
| while (mapped != NULL |
| && (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 |
| && (ranges[cnt].from + ranges[cnt].len |
| <= mapped->from + mapped->len)) |
| { |
| /* Yep, already loaded. */ |
| results[ranges[cnt].category].addr = ((char *) mapped->ptr |
| + ranges[cnt].from |
| - mapped->from); |
| results[ranges[cnt].category].len = ranges[cnt].len; |
| continue; |
| } |
| |
| /* Map the range with the locale data from the file. We will |
| try to cover as much of the locale as possible. I.e., if the |
| next category (next as in "next offset") is on the current or |
| immediately following page we use it as well. */ |
| assert (powerof2 (ps)); |
| from = ranges[cnt].from & ~(ps - 1); |
| upper = cnt; |
| do |
| { |
| 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); |
| |
| /* Open the file if it hasn't happened yet. */ |
| if (fd == -1) |
| { |
| struct stat64 st; |
| fd = open_not_cancel_2 (archfname, O_RDONLY|O_LARGEFILE); |
| if (fd == -1) |
| /* Cannot open the archive, for whatever reason. */ |
| return NULL; |
| /* Now verify we think this is really the same archive file |
| we opened before. If it has been changed we cannot trust |
| the header we read previously. */ |
| if (__fxstat64 (_STAT_VER, fd, &st) < 0 |
| || st.st_size != archive_stat.st_size |
| || st.st_mtime != archive_stat.st_mtime |
| || st.st_dev != archive_stat.st_dev |
| || st.st_ino != archive_stat.st_ino) |
| goto close_and_out; |
| } |
| |
| /* Map the range from the archive. */ |
| addr = __mmap64 (NULL, to - from, PROT_READ, MAP_FILE|MAP_COPY, |
| fd, from); |
| if (addr == MAP_FAILED) |
| 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); |
| goto close_and_out; |
| } |
| |
| /* And queue it. */ |
| newp->ptr = addr; |
| newp->from = from; |
| newp->len = to - from; |
| assert (last->next == mapped); |
| newp->next = mapped; |
| last->next = newp; |
| last = newp; |
| |
| /* Determine the load addresses for the category data. */ |
| do |
| { |
| assert (ranges[cnt].from >= from); |
| results[ranges[cnt].category].addr = ((char *) addr |
| + ranges[cnt].from - from); |
| results[ranges[cnt].category].len = ranges[cnt].len; |
| } |
| while (++cnt < upper); |
| --cnt; /* The 'for' will increase 'cnt' again. */ |
| } |
| } |
| |
| /* We don't need the file descriptor any longer. */ |
| if (fd >= 0) |
| close_not_cancel_no_status (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)) |
| return NULL; |
| |
| lia->name = strdup (*namep); |
| if (__builtin_expect (lia->name == NULL, 0)) |
| { |
| free (lia); |
| return NULL; |
| } |
| |
| lia->next = archloaded; |
| archloaded = lia; |
| |
| for (cnt = 0; cnt < __LC_LAST; ++cnt) |
| if (cnt != LC_ALL) |
| { |
| lia->data[cnt] = _nl_intern_locale_data (cnt, |
| results[cnt].addr, |
| results[cnt].len); |
| if (__builtin_expect (lia->data[cnt] != NULL, 1)) |
| { |
| /* _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; |
| } |
| } |
| |
| *namep = lia->name; |
| return lia->data[category]; |
| } |
| |
| void __libc_freeres_fn_section |
| _nl_archive_subfreeres (void) |
| { |
| struct locale_in_archive *lia; |
| struct archmapped *am; |
| |
| /* Toss out our cached locales. */ |
| lia = archloaded; |
| while (lia != NULL) |
| { |
| int category; |
| 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. */ |
| if (dead->data[category]->private.cleanup) |
| (*dead->data[category]->private.cleanup) (dead->data[category]); |
| |
| free (dead->data[category]); |
| } |
| free (dead); |
| } |
| archloaded = NULL; |
| |
| if (archmapped != NULL) |
| { |
| /* Now toss all the mapping windows, which we know nothing is using any |
| more because we just tossed all the locales that point into them. */ |
| |
| assert (archmapped == &headmap); |
| archmapped = NULL; |
| (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); |
| free (dead); |
| } |
| } |
| } |