about summary refs log tree commit diff
path: root/locale
diff options
context:
space:
mode:
Diffstat (limited to 'locale')
-rw-r--r--locale/C-address.c1
-rw-r--r--locale/C-collate.c1
-rw-r--r--locale/C-ctype.c1
-rw-r--r--locale/C-identification.c1
-rw-r--r--locale/C-measurement.c1
-rw-r--r--locale/C-messages.c1
-rw-r--r--locale/C-monetary.c1
-rw-r--r--locale/C-name.c1
-rw-r--r--locale/C-numeric.c1
-rw-r--r--locale/C-paper.c1
-rw-r--r--locale/C-telephone.c1
-rw-r--r--locale/C-time.c1
-rw-r--r--locale/Makefile4
-rw-r--r--locale/findlocale.c21
-rw-r--r--locale/hashval.h3
-rw-r--r--locale/loadarchive.c447
-rw-r--r--locale/loadlocale.c188
-rw-r--r--locale/localeinfo.h52
-rw-r--r--locale/newlocale.c17
-rw-r--r--locale/programs/localedef.c8
-rw-r--r--locale/programs/localedef.h1
-rw-r--r--locale/programs/locarchive.c416
-rw-r--r--locale/setlocale.c16
23 files changed, 990 insertions, 195 deletions
diff --git a/locale/C-address.c b/locale/C-address.c
index 12c9dde8d7..78b5a28468 100644
--- a/locale/C-address.c
+++ b/locale/C-address.c
@@ -30,7 +30,6 @@ const struct locale_data _nl_C_LC_ADDRESS attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   13,
   {
     { string: "%a%N%f%N%d%N%b%N%s %h %e %r%N%C-%z %T%N%c%N" },
diff --git a/locale/C-collate.c b/locale/C-collate.c
index 0f31b08d10..f6db63feb7 100644
--- a/locale/C-collate.c
+++ b/locale/C-collate.c
@@ -104,7 +104,6 @@ const struct locale_data _nl_C_LC_COLLATE attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   19,
   {
     /* _NL_COLLATE_NRULES */
diff --git a/locale/C-ctype.c b/locale/C-ctype.c
index 3dcd5fe121..57234844ce 100644
--- a/locale/C-ctype.c
+++ b/locale/C-ctype.c
@@ -544,7 +544,6 @@ const struct locale_data _nl_C_LC_CTYPE attribute_hidden =
   NULL, 0, 0,	/* no file mapped */
   UNDELETABLE,
   1,		/* Enable transliteration by default.  */
-  NULL,
   NR_FIXED + NR_CLASSES + NR_MAPS,
   {
     /* _NL_CTYPE_CLASS */
diff --git a/locale/C-identification.c b/locale/C-identification.c
index c2fd478289..542b33ccaf 100644
--- a/locale/C-identification.c
+++ b/locale/C-identification.c
@@ -30,7 +30,6 @@ const struct locale_data _nl_C_LC_IDENTIFICATION attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   16,
   {
     { string: "ISO/IEC 14652 i18n FDCC-set" },
diff --git a/locale/C-measurement.c b/locale/C-measurement.c
index 590432146f..0a9c9831af 100644
--- a/locale/C-measurement.c
+++ b/locale/C-measurement.c
@@ -30,7 +30,6 @@ const struct locale_data _nl_C_LC_MEASUREMENT attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   2,
   {
     { string: "\1" },
diff --git a/locale/C-messages.c b/locale/C-messages.c
index a118398394..f446e3dda6 100644
--- a/locale/C-messages.c
+++ b/locale/C-messages.c
@@ -30,7 +30,6 @@ const struct locale_data _nl_C_LC_MESSAGES attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   5,
   {
     { string: "^[yY]" },
diff --git a/locale/C-monetary.c b/locale/C-monetary.c
index 0c91145f5c..643a1c4198 100644
--- a/locale/C-monetary.c
+++ b/locale/C-monetary.c
@@ -34,7 +34,6 @@ const struct locale_data _nl_C_LC_MONETARY attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   46,
   {
     { string: "" },
diff --git a/locale/C-name.c b/locale/C-name.c
index 8526ec076f..7e714b3a37 100644
--- a/locale/C-name.c
+++ b/locale/C-name.c
@@ -30,7 +30,6 @@ const struct locale_data _nl_C_LC_NAME attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   7,
   {
     { string: "%p%t%g%t%m%t%f" },
diff --git a/locale/C-numeric.c b/locale/C-numeric.c
index 3c096c6479..1171a4b973 100644
--- a/locale/C-numeric.c
+++ b/locale/C-numeric.c
@@ -27,7 +27,6 @@ const struct locale_data _nl_C_LC_NUMERIC attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   6,
   {
     { string: "." },
diff --git a/locale/C-paper.c b/locale/C-paper.c
index fb3e619ad3..62d1a7dec7 100644
--- a/locale/C-paper.c
+++ b/locale/C-paper.c
@@ -30,7 +30,6 @@ const struct locale_data _nl_C_LC_PAPER attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   3,
   {
     { word: 297 },
diff --git a/locale/C-telephone.c b/locale/C-telephone.c
index 795de3c1f3..abd667ea8a 100644
--- a/locale/C-telephone.c
+++ b/locale/C-telephone.c
@@ -30,7 +30,6 @@ const struct locale_data _nl_C_LC_TELEPHONE attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   5,
   {
     { string: "+%c %a %l" },
diff --git a/locale/C-time.c b/locale/C-time.c
index 9ce133a6ce..b50fea57b9 100644
--- a/locale/C-time.c
+++ b/locale/C-time.c
@@ -29,7 +29,6 @@ const struct locale_data _nl_C_LC_TIME attribute_hidden =
   NULL, 0, 0, /* no file mapped */
   UNDELETABLE,
   0,
-  NULL,
   111,
   {
     { string: "Sun" },
diff --git a/locale/Makefile b/locale/Makefile
index d3c62adc07..9509c1f7f7 100644
--- a/locale/Makefile
+++ b/locale/Makefile
@@ -35,8 +35,8 @@ distribute	= localeinfo.h categories.def iso-639.def iso-3166.def \
 			      locfile-kw.gperf locfile-kw.h linereader.h \
 			      locfile.h charmap.h repertoire.h localedef.h \
 			      3level.h charmap-dir.h locarchive.c)
-routines	= setlocale findlocale loadlocale localeconv nl_langinfo \
-		  nl_langinfo_l mb_cur_max \
+routines	= setlocale findlocale loadlocale loadarchive \
+		  localeconv nl_langinfo nl_langinfo_l mb_cur_max \
 		  newlocale duplocale freelocale uselocale
 tests		= tst-C-locale
 categories	= ctype messages monetary numeric time paper name \
diff --git a/locale/findlocale.c b/locale/findlocale.c
index 75784bab9f..80480113c0 100644
--- a/locale/findlocale.c
+++ b/locale/findlocale.c
@@ -42,8 +42,11 @@ extern struct locale_data *const _nl_C[] attribute_hidden;
    which are somehow addressed.  */
 struct loaded_l10nfile *_nl_locale_file_list[__LC_LAST];
 
+const char _nl_default_locale_path[] attribute_hidden = LOCALEDIR;
+
 
 struct locale_data *
+internal_function
 _nl_find_locale (const char *locale_path, size_t locale_path_len,
 		 int category, const char **name)
 {
@@ -82,6 +85,19 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
       return _nl_C[category];
     }
 
+  /* We really have to load some data.  First we try the archive,
+     but only if there was no LOCPATH environment variable specified.  */
+  if (__builtin_expect (locale_path == NULL, 1))
+    {
+      struct locale_data *data = _nl_load_locale_from_archive (category, name);
+      if (__builtin_expect (data != NULL, 1))
+	return data;
+
+      /* Nothing in the archive.  Set the default path to search below.  */
+      locale_path = _nl_default_locale_path;
+      locale_path_len = sizeof _nl_default_locale_path;
+    }
+
   /* We really have to load some data.  First see whether the name is
      an alias.  Please note that this makes it impossible to have "C"
      or "POSIX" as aliases.  */
@@ -99,7 +115,7 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
 
      Beside the first all of them are allowed to be missing.  If the
      full specified locale is not found, the less specific one are
-     looked for.  The various part will be stripped of according to
+     looked for.  The various part will be stripped off according to
      the following order:
 		(1) codeset
 		(2) normalized codeset
@@ -236,6 +252,7 @@ _nl_find_locale (const char *locale_path, size_t locale_path_len,
 /* Calling this function assumes the lock for handling global locale data
    is acquired.  */
 void
+internal_function
 _nl_remove_locale (int locale, struct locale_data *data)
 {
   if (--data->usage_count == 0)
@@ -258,7 +275,7 @@ _nl_remove_locale (int locale, struct locale_data *data)
 
 #ifdef _POSIX_MAPPED_FILES
       /* Really delete the data.  First delete the real data.  */
-      if (__builtin_expect (data->mmaped, 1))
+      if (__builtin_expect (data->alloc == ld_mapped, 1))
 	{
 	  /* Try to unmap the area.  If this fails we mark the area as
 	     permanent.  */
diff --git a/locale/hashval.h b/locale/hashval.h
index e35957dde3..f846cdfb27 100644
--- a/locale/hashval.h
+++ b/locale/hashval.h
@@ -19,7 +19,8 @@
    02111-1307 USA.  */
 
 #ifndef	LONGBITS
-# define LONGBITS (sizeof (long int) * BITSPERBYTE)
+# include <limits.h>
+# define LONGBITS (sizeof (long int) * CHAR_BIT)
 #endif
 
 unsigned long int compute_hashval (const void *key, size_t keylen);
diff --git a/locale/loadarchive.c b/locale/loadarchive.c
new file mode 100644
index 0000000000..08e5f94905
--- /dev/null
+++ b/locale/loadarchive.c
@@ -0,0 +1,447 @@
+/* Code to load locale data from the locale archive file.
+   Copyright (C) 2002 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 <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"
+
+/* Define the hash function.  We define the function as static inline.  */
+#define compute_hashval static inline compute_hashval
+#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";
+
+
+/* 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;
+  const 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);
+	    free ((char *) normalized_codeset);
+	    name = newname;
+	  }
+      }
+  }
+
+  /* Make sure the archive is loaded.  */
+  if (archmapped == NULL)
+    {
+      /* 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);
+      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:
+	  __close (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;
+
+	  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)
+	    goto close_and_out;
+	  result = __mmap64 (NULL, head_size, PROT_READ, MAP_SHARED, 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 there is no archive or it cannot be loaded for some reason fail.  */
+  if (__builtin_expect (headmap.ptr == NULL, 0))
+    return NULL;
+
+  /* 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.  */
+	return NULL;
+
+      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)
+    return NULL;
+
+  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.  */
+	      return NULL;
+	    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)
+	    {
+	      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))
+	    {
+	      /* 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 + ps - 1)
+		    & ~(ps - 1));
+	      ++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);
+	      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)
+		return NULL;
+	    }
+
+	  /* Map the range from the archive.  */
+	  addr = __mmap64 (NULL, to - from, PROT_READ, MAP_SHARED, fd, from);
+	  if (addr == MAP_FAILED)
+	    return NULL;
+
+	  /* Allocate a record for this mapping.  */
+	  newp = (struct archmapped *) malloc (sizeof (struct archmapped));
+	  if (newp == NULL)
+	    {
+	      (void) munmap (addr, to - from);
+	      return NULL;
+	    }
+
+	  /* 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 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 = headmap.ptr + namehashtab[idx].name_offset;
+  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;
+	  }
+      }
+
+  *namep = lia->name;
+  return lia->data[category];
+}
diff --git a/locale/loadlocale.c b/locale/loadlocale.c
index 53962babac..14f3773c47 100644
--- a/locale/loadlocale.c
+++ b/locale/loadlocale.c
@@ -60,21 +60,82 @@ static const enum value_type *_nl_value_types[] =
 };
 
 
-void
-_nl_load_locale (struct loaded_l10nfile *file, int category)
+struct locale_data *
+internal_function
+_nl_intern_locale_data (int category, const void *data, size_t datasize)
 {
-  int fd;
-  struct
+  const struct
     {
       unsigned int magic;
       unsigned int nstrings;
       unsigned int strindex[0];
-    } *filedata;
+    } *const filedata = data;
+  struct locale_data *newdata;
+  size_t cnt;
+
+  if (__builtin_expect (datasize < sizeof *filedata, 0)
+      || __builtin_expect (filedata->magic != LIMAGIC (category), 0))
+    {
+      /* Bad data file.  */
+      __set_errno (EINVAL);
+      return NULL;
+    }
+
+  if (__builtin_expect (filedata->nstrings < _nl_category_num_items[category],
+			0)
+      || (__builtin_expect (sizeof *filedata
+			    + filedata->nstrings * sizeof (unsigned int)
+			    >= datasize, 0)))
+    {
+      /* Insufficient data.  */
+      __set_errno (EINVAL);
+      return NULL;
+    }
+
+  newdata = malloc (sizeof *newdata
+		    + filedata->nstrings * sizeof (union locale_data_value));
+  if (newdata == NULL)
+    return NULL;
+
+  newdata->filedata = (void *) filedata;
+  newdata->filesize = datasize;
+  newdata->usage_count = 0;
+  newdata->use_translit = 0;
+  newdata->nstrings = filedata->nstrings;
+  for (cnt = 0; cnt < newdata->nstrings; ++cnt)
+    {
+      size_t idx = filedata->strindex[cnt];
+      if (__builtin_expect (idx > newdata->filesize, 0))
+	{
+	puntdata:
+	  free (newdata);
+	  __set_errno (EINVAL);
+	  return NULL;
+	}
+      if (__builtin_expect (_nl_value_types[category][cnt] == word, 0))
+	{
+	  if (idx % __alignof__ (u_int32_t) != 0)
+	    goto puntdata;
+	  newdata->values[cnt].word =
+	    *((const u_int32_t *) (newdata->filedata + idx));
+	}
+      else
+	newdata->values[cnt].string = newdata->filedata + idx;
+    }
+
+  return newdata;
+}
+
+void
+internal_function
+_nl_load_locale (struct loaded_l10nfile *file, int category)
+{
+  int fd;
+  void *filedata;
   struct stat64 st;
   struct locale_data *newdata;
   int save_err;
-  int mmaped = 1;
-  size_t cnt;
+  int alloc = ld_mapped;
 
   file->decided = 1;
   file->data = NULL;
@@ -85,7 +146,11 @@ _nl_load_locale (struct loaded_l10nfile *file, int category)
     return;
 
   if (__builtin_expect (__fxstat64 (_STAT_VER, fd, &st), 0) < 0)
-    goto puntfd;
+    {
+    puntfd:
+      __close (fd);
+      return;
+    }
   if (__builtin_expect (S_ISDIR (st.st_mode), 0))
     {
       /* LOCALE/LC_foo is a directory; open LOCALE/LC_foo/SYS_LC_foo
@@ -122,25 +187,15 @@ _nl_load_locale (struct loaded_l10nfile *file, int category)
   /* Some systems do not have this flag; it is superfluous.  */
 #  define MAP_FILE 0
 # endif
-# ifndef MAP_INHERIT
-  /* Some systems might lack this; they lose.  */
-#  define MAP_INHERIT 0
-# endif
-  filedata = (void *) __mmap ((caddr_t) 0, st.st_size, PROT_READ,
-			      MAP_FILE|MAP_COPY|MAP_INHERIT, fd, 0);
-  if (__builtin_expect ((void *) filedata != MAP_FAILED, 1))
-    {
-      if (__builtin_expect (st.st_size < sizeof (*filedata), 0))
-	/* This cannot be a locale data file since it's too small.  */
-	goto puntfd;
-    }
-  else
+  filedata = __mmap ((caddr_t) 0, st.st_size,
+		     PROT_READ, MAP_FILE|MAP_COPY, fd, 0);
+  if (__builtin_expect (filedata == MAP_FAILED, 0))
     {
       if (__builtin_expect (errno, ENOSYS) == ENOSYS)
 	{
 #endif	/* _POSIX_MAPPED_FILES */
 	  /* No mmap; allocate a buffer and read from the file.  */
-	  mmaped = 0;
+	  alloc = ld_malloced;
 	  filedata = malloc (st.st_size);
 	  if (filedata != NULL)
 	    {
@@ -160,87 +215,58 @@ _nl_load_locale (struct loaded_l10nfile *file, int category)
 		  p += nread;
 		  to_read -= nread;
 		}
+	      __set_errno (save_err);
 	    }
-	  else
-	    goto puntfd;
-	  __set_errno (save_err);
 #ifdef _POSIX_MAPPED_FILES
 	}
-      else
-	goto puntfd;
     }
 #endif	/* _POSIX_MAPPED_FILES */
 
-  if (__builtin_expect (filedata->magic != LIMAGIC (category), 0))
-    /* Bad data file in either byte order.  */
+  /* We have mapped the data, so we no longer need the descriptor.  */
+  __close (fd);
+
+  if (__builtin_expect (filedata == NULL, 0))
+    /* We failed to map or read the data.  */
+    return;
+
+  newdata = _nl_intern_locale_data (category, filedata, st.st_size);
+  if (__builtin_expect (newdata == NULL, 0))
+    /* Bad data.  */
     {
-    puntmap:
 #ifdef _POSIX_MAPPED_FILES
-      __munmap ((caddr_t) filedata, st.st_size);
+      if (alloc == ld_mapped)
+	__munmap ((caddr_t) filedata, st.st_size);
 #endif
-    puntfd:
-      __close (fd);
       return;
     }
 
-  if (__builtin_expect (filedata->nstrings < _nl_category_num_items[category],
-			0)
-      || (__builtin_expect (sizeof *filedata
-			    + filedata->nstrings * sizeof (unsigned int)
-			    >= (size_t) st.st_size, 0)))
-    {
-      /* Insufficient data.  */
-      __set_errno (EINVAL);
-      goto puntmap;
-    }
-
-  newdata = malloc (sizeof *newdata
-		    + filedata->nstrings * sizeof (union locale_data_value));
-  if (newdata == NULL)
-    goto puntmap;
-
+  /* _nl_intern_locale_data leaves us these fields to initialize.  */
   newdata->name = NULL;	/* This will be filled if necessary in findlocale.c. */
-  newdata->filedata = (void *) filedata;
-  newdata->filesize = st.st_size;
-  newdata->mmaped = mmaped;
-  newdata->usage_count = 0;
-  newdata->use_translit = 0;
-  newdata->options = NULL;
-  newdata->nstrings = filedata->nstrings;
-  for (cnt = 0; cnt < newdata->nstrings; ++cnt)
-    {
-      off_t idx = filedata->strindex[cnt];
-      if (__builtin_expect (idx > newdata->filesize, 0))
-	{
-	  free (newdata);
-	  __set_errno (EINVAL);
-	  goto puntmap;
-	}
-      if (__builtin_expect (_nl_value_types[category][cnt] == word, 0))
-	{
-	  assert (idx % __alignof__ (u_int32_t) == 0);
-	  newdata->values[cnt].word =
-	    *((u_int32_t *) (newdata->filedata + idx));
-	}
-      else
-	newdata->values[cnt].string = newdata->filedata + idx;
-    }
+  newdata->alloc = alloc;
 
-  __close (fd);
   file->data = newdata;
 }
 
 void
+internal_function
 _nl_unload_locale (struct locale_data *locale)
 {
+  switch (__builtin_expect (locale->alloc, ld_mapped))
+    {
+    case ld_malloced:
+      free ((void *) locale->filedata);
+      break;
+    case ld_mapped:
 #ifdef _POSIX_MAPPED_FILES
-  if (__builtin_expect (locale->mmaped, 1))
-    __munmap ((caddr_t) locale->filedata, locale->filesize);
-  else
+      __munmap ((caddr_t) locale->filedata, locale->filesize);
+      break;
 #endif
-    free ((void *) locale->filedata);
+    case ld_archive:		/* Nothing to do.  */
+      break;
+    }
+
+  if (__builtin_expect (locale->alloc, ld_mapped) != ld_archive)
+    free ((char *) locale->name);
 
-  free ((char *) locale->options);
-  free ((char *) locale->name);
   free (locale);
 }
diff --git a/locale/localeinfo.h b/locale/localeinfo.h
index 97471e9be5..31de4d0974 100644
--- a/locale/localeinfo.h
+++ b/locale/localeinfo.h
@@ -47,14 +47,17 @@ struct locale_data
   const char *name;
   const char *filedata;		/* Region mapping the file data.  */
   off_t filesize;		/* Size of the file (and the region).  */
-  int mmaped;			/* If nonzero the data is mmaped.  */
+  enum				/* Flavor of storage used for those.  */
+  {
+    ld_malloced,		/* Both are malloc'd.  */
+    ld_mapped,			/* name is malloc'd, filedata mmap'd */
+    ld_archive			/* Both point into mmap'd archive regions.  */
+  } alloc;
 
   unsigned int usage_count;	/* Counter for users.  */
 
   int use_translit;		/* Nonzero if the mb*towv*() and wc*tomb()
 				   functions should use transliteration.  */
-  const char *options;		/* Extra options from the locale name,
-				   not used in the path to the locale data.  */
 
   unsigned int nstrings;	/* Number of strings below.  */
   union locale_data_value
@@ -152,6 +155,7 @@ extern const char _nl_C_codeset[] attribute_hidden;
    Each is malloc'd unless it is _nl_C_name.  */
 extern const char *_nl_current_names[] attribute_hidden;
 
+
 #ifndef SHARED
 
 /* For each category declare the variable for the current locale data.  */
@@ -222,22 +226,50 @@ extern struct __locale_struct _nl_global_locale attribute_hidden;
 #endif
 
 
+/* Default search path if no LOCPATH environment variable.  */
+extern const char _nl_default_locale_path[] attribute_hidden;
+
 /* Load the locale data for CATEGORY from the file specified by *NAME.
-   If *NAME is "", use environment variables as specified by POSIX,
-   and fill in *NAME with the actual name used.  The directories
-   listed in LOCALE_PATH are searched for the locale files.  */
+   If *NAME is "", use environment variables as specified by POSIX, and
+   fill in *NAME with the actual name used.  If LOCALE_PATH is not null,
+   those directories are searched for the locale files.  If it's null,
+   the locale archive is checked first and then _nl_default_locale_path
+   is searched for locale files.  */
 extern struct locale_data *_nl_find_locale (const char *locale_path,
 					    size_t locale_path_len,
-					    int category, const char **name);
+					    int category, const char **name)
+     internal_function attribute_hidden;
 
 /* Try to load the file described by FILE.  */
-extern void _nl_load_locale (struct loaded_l10nfile *file, int category);
+extern void _nl_load_locale (struct loaded_l10nfile *file, int category)
+     internal_function attribute_hidden;
 
 /* Free all resource.  */
-extern void _nl_unload_locale (struct locale_data *locale);
+extern void _nl_unload_locale (struct locale_data *locale)
+     internal_function attribute_hidden;
 
 /* Free the locale and give back all memory if the usage count is one.  */
-extern void _nl_remove_locale (int locale, struct locale_data *data);
+extern void _nl_remove_locale (int locale, struct locale_data *data)
+     internal_function attribute_hidden;
+
+/* 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.  */
+extern struct locale_data *_nl_load_locale_from_archive (int category,
+							 const char **namep)
+     internal_function attribute_hidden;
+
+/* Validate the contents of a locale file and set up the in-core
+   data structure to point into the data.  This leaves the `alloc'
+   and `name' fields uninitialized, for the caller to fill in.
+   If any bogons are detected in the data, this will refuse to
+   intern it, and return a null pointer instead.  */
+extern struct locale_data *_nl_intern_locale_data (int category,
+						   const void *data,
+						   size_t datasize)
+     internal_function attribute_hidden;
 
 
 /* Return `era' entry which corresponds to TP.  Used in strftime.  */
diff --git a/locale/newlocale.c b/locale/newlocale.c
index 6bab98e219..14c116beb1 100644
--- a/locale/newlocale.c
+++ b/locale/newlocale.c
@@ -80,19 +80,22 @@ __newlocale (int category_mask, const char *locale, __locale_t base)
   /* We perhaps really have to load some data.  So we determine the
      path in which to look for the data now.  The environment variable
      `LOCPATH' must only be used when the binary has no SUID or SGID
-     bit set.  */
+     bit set.  If using the default path, we tell _nl_find_locale
+     by passing null and it can check the canonical locale archive.  */
   locale_path = NULL;
   locale_path_len = 0;
 
   locpath_var = getenv ("LOCPATH");
   if (locpath_var != NULL && locpath_var[0] != '\0')
-    if (__argz_create_sep (locpath_var, ':',
-			   &locale_path, &locale_path_len) != 0)
-      return NULL;
+    {
+      if (__argz_create_sep (locpath_var, ':',
+			     &locale_path, &locale_path_len) != 0)
+	return NULL;
 
-  if (__argz_append (&locale_path, &locale_path_len,
-		     LOCALEDIR, sizeof (LOCALEDIR)) != 0)
-    return NULL;
+      if (__argz_add_sep (&locale_path, &locale_path_len,
+			  _nl_default_locale_path, ':') != 0)
+	return NULL;
+    }
 
   /* Get the names for the locales we are interested in.  We either
      allow a composite name or a single name.  */
diff --git a/locale/programs/localedef.c b/locale/programs/localedef.c
index c1d347b06a..5bbf0bf7e3 100644
--- a/locale/programs/localedef.c
+++ b/locale/programs/localedef.c
@@ -76,6 +76,9 @@ static const char *input_file;
 /* Name of the repertoire map file.  */
 const char *repertoire_global;
 
+/* Name of the locale.alias file.  */
+const char *alias_file;
+
 /* List of all locales.  */
 static struct localedef_t *locales;
 
@@ -140,6 +143,8 @@ static const struct argp_option options[] =
   { "delete-from-archive", OPT_DELETE_FROM_ARCHIVE, NULL, 0,
     N_("Remove locales named by parameters from archive") },
   { "list-archive", OPT_LIST_ARCHIVE, NULL, 0, N_("List content of archive") },
+  { "alias-file", 'A', "FILE", 0,
+    N_("locale.alias file to consult when making archive")},
   { NULL, 0, NULL, 0, NULL }
 };
 
@@ -331,6 +336,9 @@ parse_opt (int key, char *arg, struct argp_state *state)
     case 'f':
       charmap_file = arg;
       break;
+    case 'A':
+      alias_file = arg;
+      break;
     case 'i':
       input_file = arg;
       break;
diff --git a/locale/programs/localedef.h b/locale/programs/localedef.h
index 3217338ee7..41f72860d4 100644
--- a/locale/programs/localedef.h
+++ b/locale/programs/localedef.h
@@ -118,6 +118,7 @@ extern int oldstyle_tables;
 extern const char *repertoire_global;
 extern int max_locarchive_open_retry;
 extern bool no_archive;
+extern const char *alias_file;
 
 
 /* Prototypes for a few program-wide used functions.  */
diff --git a/locale/programs/locarchive.c b/locale/programs/locarchive.c
index de026b2a74..267d7baaa4 100644
--- a/locale/programs/locarchive.c
+++ b/locale/programs/locarchive.c
@@ -31,6 +31,7 @@
 #include <locale.h>
 #include <stdbool.h>
 #include <stdio.h>
+#include <stdio_ext.h>
 #include <stdlib.h>
 #include <string.h>
 #include <time.h>
@@ -59,7 +60,7 @@ static const char *locnames[] =
 
 
 /* Size of the initial archive header.  */
-#define INITIAL_NUM_NANES	450
+#define INITIAL_NUM_NAMES	450
 #define INITIAL_SIZE_STRINGS	3500
 #define INITIAL_NUM_LOCREC	350
 #define INITIAL_NUM_SUMS	2000
@@ -85,7 +86,7 @@ create_archive (const char *archivefname, struct locarhandle *ah)
   head.magic = AR_MAGIC;
   head.namehash_offset = sizeof (struct locarhead);
   head.namehash_used = 0;
-  head.namehash_size = next_prime (INITIAL_NUM_NANES);
+  head.namehash_size = next_prime (INITIAL_NUM_NAMES);
 
   head.string_offset = (head.namehash_offset
 			+ head.namehash_size * sizeof (struct namehashent));
@@ -166,6 +167,9 @@ create_archive (const char *archivefname, struct locarhandle *ah)
   ah->len = total;
 }
 
+/* forward decl for below */
+static uint32_t add_locale (struct locarhandle *ah, const char *name,
+			    locale_data_t data, bool replace);
 
 static void
 enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
@@ -300,10 +304,9 @@ enlarge_archive (struct locarhandle *ah, const struct locarhead *head)
 			    old_data[idx].sum);
 	    }
 
-	if (add_locale_to_archive (&new_ah,
-				   ((char *) ah->addr
-				    + oldnamehashtab[cnt].name_offset),
-				   old_data, 0) != 0)
+	if (add_locale (&new_ah,
+			((char *) ah->addr + oldnamehashtab[cnt].name_offset),
+			old_data, 0) == 0)
 	  error (EXIT_FAILURE, 0, _("cannot extend locale archive file"));
       }
 
@@ -446,16 +449,123 @@ close_archive (struct locarhandle *ah)
     }
 }
 
+#include "../../intl/explodename.c"
+#include "../../intl/l10nflist.c"
+
+static struct namehashent *
+insert_name (struct locarhandle *ah,
+	     const char *name, size_t name_len, bool replace)
+{
+  const struct locarhead *const head = ah->addr;
+  struct namehashent *namehashtab
+    = (struct namehashent *) ((char *) ah->addr + head->namehash_offset);
+  unsigned int insert_idx, idx, incr;
+
+  /* Hash value of the locale name.  */
+  uint32_t hval = compute_hashval (name, name_len);
+
+  insert_idx = -1;
+  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 (namehashtab[idx].name_offset != 0)
+    {
+      if (namehashtab[idx].hashval == hval
+	  && strcmp (name,
+		     (char *) ah->addr + namehashtab[idx].name_offset) == 0)
+	{
+	  /* Found the entry.  */
+	  if (namehashtab[idx].locrec_offset != 0 && ! replace)
+	    {
+	      if (! be_quiet)
+		error (0, 0, _("locale '%s' already exists"), name);
+	      return NULL;
+	    }
+
+	  break;
+	}
+
+      /* Remember the first place we can insert the new entry.  */
+      if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1)
+	insert_idx = idx;
+
+      idx += incr;
+      if (idx >= head->namehash_size)
+	idx -= head->namehash_size;
+    }
+
+  /* Add as early as possible.  */
+  if (insert_idx != -1)
+    idx = insert_idx;
+
+  namehashtab[idx].hashval = hval; /* no-op if replacing an old entry.  */
+  return &namehashtab[idx];
+}
+
+static void
+add_alias (struct locarhandle *ah, const char *alias, bool replace,
+	   const char *oldname, uint32_t locrec_offset)
+{
+  struct locarhead *head = ah->addr;
+  const size_t name_len = strlen (alias);
+  struct namehashent *namehashent = insert_name (ah, alias, strlen (alias),
+						 replace);
+  if (namehashent == NULL && ! replace)
+    return;
+
+  if (namehashent->name_offset == 0)
+    {
+      /* We are adding a new hash entry for this alias.
+	 Determine whether we have to resize the file.  */
+      if (head->string_used + name_len + 1 > head->string_size
+	  || 100 * head->namehash_used > 75 * head->namehash_size)
+	{
+	  /* The current archive is not large enough.  */
+	  enlarge_archive (ah, head);
+
+	  /* The locrecent might have moved, so we have to look up
+	     the old name afresh.  */
+	  namehashent = insert_name (ah, oldname, strlen (oldname), true);
+	  assert (namehashent->name_offset != 0);
+	  assert (namehashent->locrec_offset != 0);
+	  locrec_offset = namehashent->locrec_offset;
+
+	  /* Tail call to try the whole thing again.  */
+	  add_alias (ah, alias, replace, oldname, locrec_offset);
+	  return;
+	}
+
+      /* Add the name string.  */
+      memcpy (ah->addr + head->string_offset + head->string_used,
+	      alias, name_len + 1);
+      namehashent->name_offset = head->string_offset + head->string_used;
+      head->string_used += name_len + 1;
+
+      ++head->namehash_used;
+    }
+
+  if (namehashent->locrec_offset != 0)
+    {
+      /* Replacing an existing entry.
+	 Mark that we are no longer using the old locrecent.  */
+      struct locrecent *locrecent
+	= (struct locrecent *) ((char *) ah->addr
+				+ namehashent->locrec_offset);
+      --locrecent->refs;
+    }
+
+  /* Point this entry at the locrecent installed for the main name.  */
+  namehashent->locrec_offset = locrec_offset;
+}
+
 
 /* Check the content of the archive for duplicates.  Add the content
-   of the files if necessary.  Add all the names, possibly overwriting
-   old files.  */
-int
-add_locale_to_archive (ah, name, data, replace)
-     struct locarhandle *ah;
-     const char *name;
-     locale_data_t data;
-     bool replace;
+   of the files if necessary.  Returns the locrec_offset.  */
+static uint32_t
+add_locale (struct locarhandle *ah,
+	    const char *name, locale_data_t data, bool replace)
 {
   /* First look for the name.  If it already exists and we are not
      supposed to replace it don't do anything.  If it does not exist
@@ -467,9 +577,7 @@ add_locale_to_archive (ah, name, data, replace)
   uint32_t hval;
   unsigned int cnt;
   unsigned int idx;
-  unsigned int insert_idx;
   struct locarhead *head;
-  struct namehashent *namehashtab;
   struct namehashent *namehashent;
   unsigned int incr;
   struct locrecent *locrecent;
@@ -477,8 +585,6 @@ add_locale_to_archive (ah, name, data, replace)
   head = ah->addr;
   sumhashtab = (struct sumhashent *) ((char *) ah->addr
 				      + head->sumhash_offset);
-  namehashtab = (struct namehashent *) ((char *) ah->addr
-					+ head->namehash_offset);
 
 
   /* For each locale category data set determine whether the same data
@@ -514,47 +620,10 @@ add_locale_to_archive (ah, name, data, replace)
 	  }
       }
 
-
-  /* Hash value of the locale name.  */
-  hval = compute_hashval (name, name_len);
-
-  insert_idx = -1;
-  idx = hval % head->namehash_size;
-  incr = 1 + hval % (head->namehash_size - 2);
-
-  /* If the name_offset field is zero this means this is no
-     deleted entry and therefore no entry can be found.  */
-  while (namehashtab[idx].name_offset != 0)
-    {
-      if (namehashtab[idx].hashval == hval
-	  && strcmp (name,
-		     (char *) ah->addr + namehashtab[idx].name_offset) == 0)
-	{
-	  /* Found the entry.  */
-	  if (namehashtab[idx].locrec_offset != 0 && ! replace)
-	    {
-	      if (! be_quiet)
-		error (0, 0, _("locale '%s' already exists"), name);
-	      return 1;
-	    }
-
-	  break;
-	}
-
-      /* Remember the first place we can insert the new entry.  */
-      if (namehashtab[idx].locrec_offset == 0 && insert_idx == -1)
-	insert_idx = idx;
-
-      idx += incr;
-      if (idx >= head->namehash_size)
-	idx -= head->namehash_size;
-    }
-
-  /* Add as early as possible.  */
-  if (insert_idx != -1)
-    idx = insert_idx;
-
-  namehashent = &namehashtab[idx];
+  /* Find a slot for the locale name in the hash table.  */
+  namehashent = insert_name (ah, name, name_len, replace);
+  if (namehashent == NULL)	/* Already exists and !REPLACE.  */
+    return 0;
 
   /* Determine whether we have to resize the file.  */
   if (100 * (head->sumhash_used + num_new_offsets) > 75 * head->sumhash_size
@@ -565,7 +634,7 @@ add_locale_to_archive (ah, name, data, replace)
     {
       /* The current archive is not large enough.  */
       enlarge_archive (ah, head);
-      return add_locale_to_archive (ah, name, data, replace);
+      return add_locale (ah, name, data, replace);
     }
 
   /* Add the locale data which is not yet in the archive.  */
@@ -620,29 +689,46 @@ add_locale_to_archive (ah, name, data, replace)
 	++head->sumhash_used;
       }
 
-
-  if (namehashent->locrec_offset == 0)
+  if (namehashent->name_offset == 0)
     {
       /* Add the name string.  */
       memcpy ((char *) ah->addr + head->string_offset + head->string_used,
 	      name, name_len + 1);
       namehashent->name_offset = head->string_offset + head->string_used;
       head->string_used += name_len + 1;
+      ++head->namehash_used;
+    }
 
+  if (namehashent->locrec_offset == 0)
+    {
       /* Allocate a name location record.  */
       namehashent->locrec_offset = (head->locrectab_offset
 				    + (head->locrectab_used++
 				       * sizeof (struct locrecent)));
-
-      namehashent->hashval = hval;
-
-      ++head->namehash_used;
+      locrecent = (struct locrecent *) ((char *) ah->addr
+					+ namehashent->locrec_offset);
+      locrecent->refs = 1;
     }
+  else
+    {
+      /* If there are other aliases pointing to this locrecent,
+	 we still need a new one.  If not, reuse the old one.  */
 
+      locrecent = (struct locrecent *) ((char *) ah->addr
+					+ namehashent->locrec_offset);
+      if (locrecent->refs > 1)
+	{
+	  --locrecent->refs;
+	  namehashent->locrec_offset = (head->locrectab_offset
+					+ (head->locrectab_used++
+					   * sizeof (struct locrecent)));
+	  locrecent = (struct locrecent *) ((char *) ah->addr
+					    + namehashent->locrec_offset);
+	  locrecent->refs = 1;
+	}
+    }
 
   /* Fill in the table with the locations of the locale data.  */
-  locrecent = (struct locrecent *) ((char *) ah->addr
-				    + namehashent->locrec_offset);
   for (cnt = 0; cnt < __LC_LAST; ++cnt)
     if (cnt != LC_ALL)
       {
@@ -650,13 +736,196 @@ add_locale_to_archive (ah, name, data, replace)
 	locrecent->record[cnt].len = data[cnt].size;
       }
 
+  return namehashent->locrec_offset;
+}
 
-  /* Read the locale.alias file to see whether any matching record is
-     found.  If an entry is available check whether it is already in
-     the archive.  If this is the case check whether the new locale's
-     name is more specific than the one currently referred to by the
-     alias.  */
 
+/* Check the content of the archive for duplicates.  Add the content
+   of the files if necessary.  Add all the names, possibly overwriting
+   old files.  */
+int
+add_locale_to_archive (ah, name, data, replace)
+     struct locarhandle *ah;
+     const char *name;
+     locale_data_t data;
+     bool replace;
+{
+  char *normalized_name = NULL;
+  uint32_t locrec_offset;
+
+  /* First analyze the name to decide how to archive it.  */
+  const char *language;
+  const char *modifier;
+  const char *territory;
+  const char *codeset;
+  const char *normalized_codeset;
+  int mask = _nl_explode_name (strdupa (name),
+			       &language, &modifier, &territory,
+			       &codeset, &normalized_codeset);
+
+  if (mask & XPG_NORM_CODESET)
+    /* This name contains a codeset in unnormalized form.
+       We will store it in the archive with a normalized name.  */
+    asprintf (&normalized_name, "%s%s%s.%s%s%s",
+	      language, territory == NULL ? "" : "_", territory ?: "",
+	      (mask & XPG_NORM_CODESET) ? normalized_codeset : codeset,
+	      modifier == NULL ? "" : "@", modifier ?: "");
+
+  /* This call does the main work.  */
+  locrec_offset = add_locale (ah, normalized_name ?: name, data, replace);
+  free (normalized_name);
+  if (locrec_offset == 0)
+    {
+      if (mask & XPG_NORM_CODESET)
+	free ((char *) normalized_codeset);
+      return -1;
+    }
+
+  if ((mask & XPG_CODESET) == 0)
+    {
+      /* This name lacks a codeset, so determine the locale's codeset and
+	 add an alias for its name with normalized codeset appended.  */
+
+      const struct
+      {
+	unsigned int magic;
+	unsigned int nstrings;
+	unsigned int strindex[0];
+      } *filedata = data[LC_CTYPE].addr;
+      codeset = (char *) filedata
+	+ filedata->strindex[_NL_ITEM_INDEX (_NL_CTYPE_CODESET_NAME)];
+
+      normalized_codeset = _nl_normalize_codeset (codeset, strlen (codeset));
+      mask |= XPG_NORM_CODESET;
+
+      asprintf (&normalized_name, "%s%s%s.%s%s%s",
+		language, territory == NULL ? "" : "_", territory ?: "",
+		normalized_codeset,
+		modifier == NULL ? "" : "@", modifier ?: "");
+
+      add_alias (ah, normalized_name, replace, name, locrec_offset);
+      free (normalized_name);
+    }
+
+  /* Now read the locale.alias files looking for lines whose
+     right hand side matches our name after normalization.  */
+  if (alias_file != NULL)
+    {
+      FILE *fp;
+      fp = fopen (alias_file, "r");
+      if (fp == NULL)
+	error (1, errno, _("locale alias file `%s' not found"),
+	       alias_file);
+
+      /* No threads present.  */
+      __fsetlocking (fp, FSETLOCKING_BYCALLER);
+
+      while (! feof_unlocked (fp))
+	{
+	  /* It is a reasonable approach to use a fix buffer here
+	     because
+	     a) we are only interested in the first two fields
+	     b) these fields must be usable as file names and so must
+	     not be that long  */
+	  char buf[BUFSIZ];
+	  char *alias;
+	  char *value;
+	  char *cp;
+
+	  if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
+	    /* EOF reached.  */
+	    break;
+
+	  cp = buf;
+	  /* Ignore leading white space.  */
+	  while (isspace (cp[0]) && cp[0] != '\n')
+	    ++cp;
+
+	  /* A leading '#' signals a comment line.  */
+	  if (cp[0] != '\0' && cp[0] != '#' && cp[0] != '\n')
+	    {
+	      alias = cp++;
+	      while (cp[0] != '\0' && !isspace (cp[0]))
+		++cp;
+	      /* Terminate alias name.  */
+	      if (cp[0] != '\0')
+		*cp++ = '\0';
+
+	      /* Now look for the beginning of the value.  */
+	      while (isspace (cp[0]))
+		++cp;
+
+	      if (cp[0] != '\0')
+		{
+		  value = cp++;
+		  while (cp[0] != '\0' && !isspace (cp[0]))
+		    ++cp;
+		  /* Terminate value.  */
+		  if (cp[0] == '\n')
+		    {
+		      /* This has to be done to make the following
+			 test for the end of line possible.  We are
+			 looking for the terminating '\n' which do not
+			 overwrite here.  */
+		      *cp++ = '\0';
+		      *cp = '\n';
+		    }
+		  else if (cp[0] != '\0')
+		    *cp++ = '\0';
+
+		  /* Does this alias refer to our locale?  We will
+		     normalize the right hand side and compare the
+		     elements of the normalized form.  */
+		  {
+		    const char *rhs_language;
+		    const char *rhs_modifier;
+		    const char *rhs_territory;
+		    const char *rhs_codeset;
+		    const char *rhs_normalized_codeset;
+		    int rhs_mask = _nl_explode_name (value,
+						     &rhs_language,
+						     &rhs_modifier,
+						     &rhs_territory,
+						     &rhs_codeset,
+						     &rhs_normalized_codeset);
+		    if (!strcmp (language, rhs_language)
+			&& ((rhs_mask & XPG_CODESET)
+			    /* He has a codeset, it must match normalized.  */
+			    ? !strcmp ((mask & XPG_NORM_CODESET)
+				       ? normalized_codeset : codeset,
+				       (rhs_mask & XPG_NORM_CODESET)
+				       ? rhs_normalized_codeset : rhs_codeset)
+			    /* He has no codeset, we must also have none.  */
+			    : (mask & XPG_CODESET) == 0)
+			/* Codeset (or lack thereof) matches.  */
+			&& !strcmp (territory ?: "", rhs_territory ?: "")
+			&& !strcmp (modifier ?: "", rhs_modifier ?: ""))
+		      /* We have a winner.  */
+		      add_alias (ah, alias, replace,
+				 normalized_name ?: name, locrec_offset);
+		    if (rhs_mask & XPG_NORM_CODESET)
+		      free ((char *) rhs_normalized_codeset);
+		  }
+		}
+	    }
+
+	  /* Possibly not the whole line fits into the buffer.
+	     Ignore the rest of the line.  */
+	  while (strchr (cp, '\n') == NULL)
+	    {
+	      cp = buf;
+	      if (fgets_unlocked (buf, BUFSIZ, fp) == NULL)
+		/* Make sure the inner loop will be left.  The outer
+		   loop will exit at the `feof' test.  */
+		*cp = '\n';
+	    }
+	}
+
+      fclose (fp);
+    }
+
+  if (mask & XPG_NORM_CODESET)
+    free ((char *) normalized_codeset);
 
   return 0;
 }
@@ -903,7 +1172,6 @@ delete_locales_from_archive (nlist, list)
 	      /* Found the entry.  Now mark it as removed by zero-ing
 		 the reference to the locale record.  */
 	      namehashtab[idx].locrec_offset = 0;
-	      --head->namehash_used;
 	      break;
 	    }
 
diff --git a/locale/setlocale.c b/locale/setlocale.c
index 50963d152c..46af339eae 100644
--- a/locale/setlocale.c
+++ b/locale/setlocale.c
@@ -247,18 +247,22 @@ setlocale (int category, const char *locale)
   /* We perhaps really have to load some data.  So we determine the
      path in which to look for the data now.  The environment variable
      `LOCPATH' must only be used when the binary has no SUID or SGID
-     bit set.  */
+     bit set.  If using the default path, we tell _nl_find_locale
+     by passing null and it can check the canonical locale archive.  */
   locale_path = NULL;
   locale_path_len = 0;
 
   locpath_var = getenv ("LOCPATH");
   if (locpath_var != NULL && locpath_var[0] != '\0')
-    if (__argz_create_sep (locpath_var, ':',
-			   &locale_path, &locale_path_len) != 0)
-      return NULL;
+    {
+      if (__argz_create_sep (locpath_var, ':',
+			     &locale_path, &locale_path_len) != 0)
+	return NULL;
 
-  if (__argz_add_sep (&locale_path, &locale_path_len, LOCALEDIR, ':') != 0)
-    return NULL;
+      if (__argz_add_sep (&locale_path, &locale_path_len,
+			  _nl_default_locale_path, ':') != 0)
+	return NULL;
+    }
 
   if (category == LC_ALL)
     {