]> git.ipfire.org Git - thirdparty/glibc.git/blobdiff - locale/loadarchive.c
Fix locale/tst-locale-locpath cross-testing when sshd sets LANG.
[thirdparty/glibc.git] / locale / loadarchive.c
index a9b5386ce87408f697f92209c223409a7beba9d2..ba0fe45648e111022c9b62dd82d04554d300cd54 100644 (file)
@@ -1,5 +1,5 @@
 /* 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
@@ -66,8 +84,8 @@ static struct stat64 archive_stat; /* stat of archive when header mapped.  */
 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;
 
@@ -111,8 +129,7 @@ calculate_head_size (const struct locarhead *h)
    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;
@@ -167,22 +184,25 @@ _nl_load_locale_from_archive (int category, const char **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;
@@ -191,58 +211,59 @@ _nl_load_locale_from_archive (int category, const char **namep)
        {
          /* 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.  */
@@ -252,6 +273,10 @@ _nl_load_locale_from_archive (int category, const char **namep)
   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);
 
@@ -261,7 +286,7 @@ _nl_load_locale_from_archive (int category, const char **namep)
     {
       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)
@@ -275,7 +300,7 @@ _nl_load_locale_from_archive (int category, const char **namep)
 
   /* 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);
 
@@ -289,7 +314,7 @@ _nl_load_locale_from_archive (int category, const char **namep)
            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;
          }
@@ -323,7 +348,8 @@ _nl_load_locale_from_archive (int category, const char **namep)
 
          /* 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;
@@ -332,8 +358,8 @@ _nl_load_locale_from_archive (int category, const char **namep)
          /* 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
@@ -352,22 +378,27 @@ _nl_load_locale_from_archive (int category, const char **namep)
          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;
@@ -379,20 +410,21 @@ _nl_load_locale_from_archive (int category, const char **namep)
                  || 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.  */
@@ -417,14 +449,25 @@ _nl_load_locale_from_archive (int category, const char **namep)
        }
     }
 
+  /* 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;
 
@@ -434,11 +477,20 @@ _nl_load_locale_from_archive (int category, const char **namep)
        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;
          }
       }
 
@@ -446,7 +498,7 @@ _nl_load_locale_from_archive (int category, const char **namep)
   return lia->data[category];
 }
 
-void
+void __libc_freeres_fn_section
 _nl_archive_subfreeres (void)
 {
   struct locale_in_archive *lia;
@@ -460,10 +512,16 @@ _nl_archive_subfreeres (void)
       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;
@@ -475,13 +533,13 @@ _nl_archive_subfreeres (void)
 
       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);
        }
     }