about summary refs log tree commit diff
path: root/locale/programs/locarchive.c
diff options
context:
space:
mode:
Diffstat (limited to 'locale/programs/locarchive.c')
-rw-r--r--locale/programs/locarchive.c416
1 files changed, 342 insertions, 74 deletions
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;
 	    }