e354a5
commit 785969a047ad2f23f758901c6816422573544453
e354a5
Author: Florian Weimer <fweimer@redhat.com>
e354a5
Date:   Fri Dec 4 09:13:43 2020 +0100
e354a5
e354a5
    elf: Implement a string table for ldconfig, with tail merging
e354a5
    
e354a5
    This will be used in ldconfig to reduce the ld.so.cache size slightly.
e354a5
    
e354a5
    Tail merging is an optimization where a pointer points into another
e354a5
    string if the first string is a suffix of the second string.
e354a5
    
e354a5
    The hash function FNV-1a was chosen because it is simple and achieves
e354a5
    good dispersion even for short strings (so that the hash table bucket
e354a5
    count can be a power of two).  It is clearly superior to the hsearch
e354a5
    hash and the ELF hash in this regard.
e354a5
    
e354a5
    The hash table uses chaining for collision resolution.
e354a5
    
e354a5
    Reviewed-by: Adhemerval Zanella  <adhemerval.zanella@linaro.org>
e354a5
e354a5
diff --git a/elf/Makefile b/elf/Makefile
e354a5
index f795617780b393ec..abb3e9d1179ef5cd 100644
e354a5
--- a/elf/Makefile
e354a5
+++ b/elf/Makefile
e354a5
@@ -163,7 +163,7 @@ tests-container = \
e354a5
 
e354a5
 tests := tst-tls9 tst-leaks1 \
e354a5
 	tst-array1 tst-array2 tst-array3 tst-array4 tst-array5 \
e354a5
-	tst-auxv
e354a5
+	tst-auxv tst-stringtable
e354a5
 tests-internal := tst-tls1 tst-tls2 $(tests-static-internal)
e354a5
 tests-static := $(tests-static-normal) $(tests-static-internal)
e354a5
 
e354a5
diff --git a/elf/stringtable.c b/elf/stringtable.c
e354a5
new file mode 100644
e354a5
index 0000000000000000..099347d73ee70b8f
e354a5
--- /dev/null
e354a5
+++ b/elf/stringtable.c
e354a5
@@ -0,0 +1,209 @@
e354a5
+/* String tables for ld.so.cache construction.  Implementation.
e354a5
+   Copyright (C) 2020 Free Software Foundation, Inc.
e354a5
+   This file is part of the GNU C Library.
e354a5
+
e354a5
+   This program is free software; you can redistribute it and/or modify
e354a5
+   it under the terms of the GNU General Public License as published
e354a5
+   by the Free Software Foundation; version 2 of the License, or
e354a5
+   (at your option) any later version.
e354a5
+
e354a5
+   This program is distributed in the hope that it will be useful,
e354a5
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
e354a5
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
e354a5
+   GNU General Public License for more details.
e354a5
+
e354a5
+   You should have received a copy of the GNU General Public License
e354a5
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
e354a5
+
e354a5
+#include <assert.h>
e354a5
+#include <error.h>
e354a5
+#include <ldconfig.h>
e354a5
+#include <libintl.h>
e354a5
+#include <stdlib.h>
e354a5
+#include <string.h>
e354a5
+#include <stringtable.h>
e354a5
+
e354a5
+static void
e354a5
+stringtable_init (struct stringtable *table)
e354a5
+{
e354a5
+  table->count = 0;
e354a5
+
e354a5
+  /* This needs to be a power of two.  128 is sufficient to keep track
e354a5
+     of 42 DSOs without resizing (assuming two strings per DSOs).
e354a5
+     glibc itself comes with more than 20 DSOs, so 64 would likely to
e354a5
+     be too small.  */
e354a5
+  table->allocated = 128;
e354a5
+
e354a5
+  table->entries = xcalloc (table->allocated, sizeof (table->entries[0]));
e354a5
+}
e354a5
+
e354a5
+/* 32-bit FNV-1a hash function.  */
e354a5
+static uint32_t
e354a5
+fnv1a (const char *string, size_t length)
e354a5
+{
e354a5
+  const unsigned char *p = (const unsigned char *) string;
e354a5
+  uint32_t hash = 2166136261U;
e354a5
+  for (size_t i = 0; i < length; ++i)
e354a5
+    {
e354a5
+      hash ^= p[i];
e354a5
+      hash *= 16777619U;
e354a5
+    }
e354a5
+  return hash;
e354a5
+}
e354a5
+
e354a5
+/* Double the capacity of the hash table.  */
e354a5
+static void
e354a5
+stringtable_rehash (struct stringtable *table)
e354a5
+{
e354a5
+  /* This computation cannot overflow because the old total in-memory
e354a5
+     size of the hash table is larger than the computed value.  */
e354a5
+  uint32_t new_allocated = table->allocated * 2;
e354a5
+  struct stringtable_entry **new_entries
e354a5
+    = xcalloc (new_allocated, sizeof (table->entries[0]));
e354a5
+
e354a5
+  uint32_t mask = new_allocated - 1;
e354a5
+  for (uint32_t i = 0; i < table->allocated; ++i)
e354a5
+    for (struct stringtable_entry *e = table->entries[i]; e != NULL; )
e354a5
+      {
e354a5
+        struct stringtable_entry *next = e->next;
e354a5
+        uint32_t hash = fnv1a (e->string, e->length);
e354a5
+        uint32_t new_index = hash & mask;
e354a5
+        e->next = new_entries[new_index];
e354a5
+        new_entries[new_index] = e;
e354a5
+        e = next;
e354a5
+      }
e354a5
+
e354a5
+  free (table->entries);
e354a5
+  table->entries = new_entries;
e354a5
+  table->allocated = new_allocated;
e354a5
+}
e354a5
+
e354a5
+struct stringtable_entry *
e354a5
+stringtable_add (struct stringtable *table, const char *string)
e354a5
+{
e354a5
+  /* Check for a zero-initialized table.  */
e354a5
+  if (table->allocated == 0)
e354a5
+    stringtable_init (table);
e354a5
+
e354a5
+  size_t length = strlen (string);
e354a5
+  if (length > (1U << 30))
e354a5
+    error (EXIT_FAILURE, 0, _("String table string is too long"));
e354a5
+  uint32_t hash = fnv1a (string, length);
e354a5
+
e354a5
+  /* Return a previously-existing entry.  */
e354a5
+  for (struct stringtable_entry *e
e354a5
+         = table->entries[hash & (table->allocated - 1)];
e354a5
+       e != NULL; e = e->next)
e354a5
+    if (e->length == length && memcmp (e->string, string, length) == 0)
e354a5
+      return e;
e354a5
+
e354a5
+  /* Increase the size of the table if necessary.  Keep utilization
e354a5
+     below two thirds.  */
e354a5
+  if (table->count >= (1U << 30))
e354a5
+    error (EXIT_FAILURE, 0, _("String table has too many entries"));
e354a5
+  if (table->count * 3 > table->allocated * 2)
e354a5
+    stringtable_rehash (table);
e354a5
+
e354a5
+  /* Add the new table entry.  */
e354a5
+  ++table->count;
e354a5
+  struct stringtable_entry *e
e354a5
+    = xmalloc (offsetof (struct stringtable_entry, string) + length + 1);
e354a5
+  uint32_t index = hash & (table->allocated - 1);
e354a5
+  e->next = table->entries[index];
e354a5
+  table->entries[index] = e;
e354a5
+  e->length = length;
e354a5
+  e->offset = 0;
e354a5
+  memcpy (e->string, string, length + 1);
e354a5
+  return e;
e354a5
+}
e354a5
+
e354a5
+/* Sort reversed strings in reverse lexicographic order.  This is used
e354a5
+   for tail merging.  */
e354a5
+static int
e354a5
+finalize_compare (const void *l, const void *r)
e354a5
+{
e354a5
+  struct stringtable_entry *left = *(struct stringtable_entry **) l;
e354a5
+  struct stringtable_entry *right = *(struct stringtable_entry **) r;
e354a5
+  size_t to_compare;
e354a5
+  if (left->length < right->length)
e354a5
+    to_compare = left->length;
e354a5
+  else
e354a5
+    to_compare = right->length;
e354a5
+  for (size_t i = 1; i <= to_compare; ++i)
e354a5
+    {
e354a5
+      unsigned char lch = left->string[left->length - i];
e354a5
+      unsigned char rch = right->string[right->length - i];
e354a5
+      if (lch != rch)
e354a5
+        return rch - lch;
e354a5
+    }
e354a5
+  if (left->length == right->length)
e354a5
+    return 0;
e354a5
+  else if (left->length < right->length)
e354a5
+    /* Longer strings should come first.  */
e354a5
+    return 1;
e354a5
+  else
e354a5
+    return -1;
e354a5
+}
e354a5
+
e354a5
+void
e354a5
+stringtable_finalize (struct stringtable *table,
e354a5
+                      struct stringtable_finalized *result)
e354a5
+{
e354a5
+  if (table->count == 0)
e354a5
+    {
e354a5
+      result->strings = xstrdup ("");
e354a5
+      result->size = 0;
e354a5
+      return;
e354a5
+    }
e354a5
+
e354a5
+  /* Optimize the order of the strings.  */
e354a5
+  struct stringtable_entry **array = xcalloc (table->count, sizeof (*array));
e354a5
+  {
e354a5
+    size_t j = 0;
e354a5
+    for (uint32_t i = 0; i < table->allocated; ++i)
e354a5
+      for (struct stringtable_entry *e = table->entries[i]; e != NULL;
e354a5
+           e = e->next)
e354a5
+        {
e354a5
+          array[j] = e;
e354a5
+          ++j;
e354a5
+        }
e354a5
+    assert (j == table->count);
e354a5
+  }
e354a5
+  qsort (array, table->count, sizeof (*array), finalize_compare);
e354a5
+
e354a5
+  /* Assign offsets, using tail merging (sharing suffixes) if possible.  */
e354a5
+  array[0]->offset = 0;
e354a5
+  for (uint32_t j = 1; j < table->count; ++j)
e354a5
+    {
e354a5
+      struct stringtable_entry *previous = array[j - 1];
e354a5
+      struct stringtable_entry *current = array[j];
e354a5
+      if (previous->length >= current->length
e354a5
+          && memcmp (&previous->string[previous->length - current->length],
e354a5
+                     current->string, current->length) == 0)
e354a5
+        current->offset = (previous->offset + previous->length
e354a5
+                           - current->length);
e354a5
+      else if (__builtin_add_overflow (previous->offset,
e354a5
+                                       previous->length + 1,
e354a5
+                                       &current->offset))
e354a5
+        error (EXIT_FAILURE, 0, _("String table is too large"));
e354a5
+    }
e354a5
+
e354a5
+  /* Allocate the result string.  */
e354a5
+  {
e354a5
+    struct stringtable_entry *last = array[table->count - 1];
e354a5
+    if (__builtin_add_overflow (last->offset, last->length + 1,
e354a5
+                                &result->size))
e354a5
+      error (EXIT_FAILURE, 0, _("String table is too large"));
e354a5
+  }
e354a5
+  /* The strings are copied from the hash table, so the array is no
e354a5
+     longer needed.  */
e354a5
+  free (array);
e354a5
+  result->strings = xcalloc (result->size, 1);
e354a5
+
e354a5
+  /* Copy the strings.  */
e354a5
+  for (uint32_t i = 0; i < table->allocated; ++i)
e354a5
+    for (struct stringtable_entry *e = table->entries[i]; e != NULL;
e354a5
+         e = e->next)
e354a5
+      if (result->strings[e->offset] == '\0')
e354a5
+        memcpy (&result->strings[e->offset], e->string, e->length + 1);
e354a5
+}
e354a5
diff --git a/elf/stringtable.h b/elf/stringtable.h
e354a5
new file mode 100644
e354a5
index 0000000000000000..7d57d1bda9602947
e354a5
--- /dev/null
e354a5
+++ b/elf/stringtable.h
e354a5
@@ -0,0 +1,64 @@
e354a5
+/* String tables for ld.so.cache construction.
e354a5
+   Copyright (C) 2020 Free Software Foundation, Inc.
e354a5
+   This file is part of the GNU C Library.
e354a5
+
e354a5
+   This program is free software; you can redistribute it and/or modify
e354a5
+   it under the terms of the GNU General Public License as published
e354a5
+   by the Free Software Foundation; version 2 of the License, or
e354a5
+   (at your option) any later version.
e354a5
+
e354a5
+   This program is distributed in the hope that it will be useful,
e354a5
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
e354a5
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
e354a5
+   GNU General Public License for more details.
e354a5
+
e354a5
+   You should have received a copy of the GNU General Public License
e354a5
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
e354a5
+
e354a5
+#ifndef _STRINGTABLE_H
e354a5
+#define _STRINGTABLE_H
e354a5
+
e354a5
+#include <stddef.h>
e354a5
+#include <stdint.h>
e354a5
+
e354a5
+/* An entry in the string table.  Only the length and string fields are
e354a5
+   expected to be used outside the string table code.  */
e354a5
+struct stringtable_entry
e354a5
+{
e354a5
+  struct stringtable_entry *next; /* For collision resolution.  */
e354a5
+  uint32_t length;                /* Length of then string.  */
e354a5
+  uint32_t offset;                /* From start of finalized table.  */
e354a5
+  char string[];                  /* Null-terminated string.  */
e354a5
+};
e354a5
+
e354a5
+/* A string table.  Zero-initialization produces a valid atable.  */
e354a5
+struct stringtable
e354a5
+{
e354a5
+  struct stringtable_entry **entries;  /* Array of hash table buckets.  */
e354a5
+  uint32_t count;                 /* Number of elements in the table.  */
e354a5
+  uint32_t allocated;             /* Length of the entries array.  */
e354a5
+};
e354a5
+
e354a5
+/* Adds STRING to TABLE.  May return the address of an existing entry.  */
e354a5
+struct stringtable_entry *stringtable_add (struct stringtable *table,
e354a5
+                                           const char *string);
e354a5
+
e354a5
+/* Result of stringtable_finalize.  SIZE bytes at STRINGS should be
e354a5
+   written to the file.  */
e354a5
+struct stringtable_finalized
e354a5
+{
e354a5
+  char *strings;
e354a5
+  size_t size;
e354a5
+};
e354a5
+
e354a5
+/* Assigns offsets to string table entries and computes the serialized
e354a5
+   form of the string table.  */
e354a5
+void stringtable_finalize (struct stringtable *table,
e354a5
+                           struct stringtable_finalized *result);
e354a5
+
e354a5
+/* Deallocate the string table (but not the TABLE pointer itself).
e354a5
+   (The table can be re-used for adding more strings without
e354a5
+   initialization.)  */
e354a5
+void stringtable_free (struct stringtable *table);
e354a5
+
e354a5
+#endif /* _STRINGTABLE_H */
e354a5
diff --git a/elf/stringtable_free.c b/elf/stringtable_free.c
e354a5
new file mode 100644
e354a5
index 0000000000000000..8588a254705d4df8
e354a5
--- /dev/null
e354a5
+++ b/elf/stringtable_free.c
e354a5
@@ -0,0 +1,33 @@
e354a5
+/* String tables for ld.so.cache construction.  Deallocation (for tests only).
e354a5
+   Copyright (C) 2020 Free Software Foundation, Inc.
e354a5
+   This file is part of the GNU C Library.
e354a5
+
e354a5
+   This program is free software; you can redistribute it and/or modify
e354a5
+   it under the terms of the GNU General Public License as published
e354a5
+   by the Free Software Foundation; version 2 of the License, or
e354a5
+   (at your option) any later version.
e354a5
+
e354a5
+   This program is distributed in the hope that it will be useful,
e354a5
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
e354a5
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
e354a5
+   GNU General Public License for more details.
e354a5
+
e354a5
+   You should have received a copy of the GNU General Public License
e354a5
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
e354a5
+
e354a5
+#include <stdlib.h>
e354a5
+#include <stringtable.h>
e354a5
+
e354a5
+void
e354a5
+stringtable_free (struct stringtable *table)
e354a5
+{
e354a5
+  for (uint32_t i = 0; i < table->allocated; ++i)
e354a5
+    for (struct stringtable_entry *e = table->entries[i]; e != NULL; )
e354a5
+      {
e354a5
+        struct stringtable_entry *next = e->next;
e354a5
+        free (e);
e354a5
+        e = next;
e354a5
+      }
e354a5
+  free (table->entries);
e354a5
+  *table = (struct stringtable) { 0, };
e354a5
+}
e354a5
diff --git a/elf/tst-stringtable.c b/elf/tst-stringtable.c
e354a5
new file mode 100644
e354a5
index 0000000000000000..3731086037567d57
e354a5
--- /dev/null
e354a5
+++ b/elf/tst-stringtable.c
e354a5
@@ -0,0 +1,181 @@
e354a5
+/* Unit test for ldconfig string tables.
e354a5
+   Copyright (C) 2020 Free Software Foundation, Inc.
e354a5
+   This file is part of the GNU C Library.
e354a5
+
e354a5
+   This program is free software; you can redistribute it and/or modify
e354a5
+   it under the terms of the GNU General Public License as published
e354a5
+   by the Free Software Foundation; version 2 of the License, or
e354a5
+   (at your option) any later version.
e354a5
+
e354a5
+   This program is distributed in the hope that it will be useful,
e354a5
+   but WITHOUT ANY WARRANTY; without even the implied warranty of
e354a5
+   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
e354a5
+   GNU General Public License for more details.
e354a5
+
e354a5
+   You should have received a copy of the GNU General Public License
e354a5
+   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
e354a5
+
e354a5
+#include <array_length.h>
e354a5
+#include <stdlib.h>
e354a5
+#include <string.h>
e354a5
+#include <stringtable.h>
e354a5
+#include <support/check.h>
e354a5
+#include <support/support.h>
e354a5
+
e354a5
+static int
e354a5
+do_test (void)
e354a5
+{
e354a5
+  /* Empty string table.  */
e354a5
+  {
e354a5
+    struct stringtable s = { 0, };
e354a5
+    struct stringtable_finalized f;
e354a5
+    stringtable_finalize (&s, &f);
e354a5
+    TEST_COMPARE_STRING (f.strings, "");
e354a5
+    TEST_COMPARE (f.size, 0);
e354a5
+    free (f.strings);
e354a5
+    stringtable_free (&s);
e354a5
+  }
e354a5
+
e354a5
+  /* String table with one empty string.  */
e354a5
+  {
e354a5
+    struct stringtable s = { 0, };
e354a5
+    struct stringtable_entry *e = stringtable_add (&s, "");
e354a5
+    TEST_COMPARE_STRING (e->string, "");
e354a5
+    TEST_COMPARE (e->length, 0);
e354a5
+    TEST_COMPARE (s.count, 1);
e354a5
+
e354a5
+    struct stringtable_finalized f;
e354a5
+    stringtable_finalize (&s, &f);
e354a5
+    TEST_COMPARE (e->offset, 0);
e354a5
+    TEST_COMPARE_STRING (f.strings, "");
e354a5
+    TEST_COMPARE (f.size, 1);
e354a5
+    free (f.strings);
e354a5
+    stringtable_free (&s);
e354a5
+  }
e354a5
+
e354a5
+  /* String table with one non-empty string.  */
e354a5
+  {
e354a5
+    struct stringtable s = { 0, };
e354a5
+    struct stringtable_entry *e = stringtable_add (&s, "name");
e354a5
+    TEST_COMPARE_STRING (e->string, "name");
e354a5
+    TEST_COMPARE (e->length, 4);
e354a5
+    TEST_COMPARE (s.count, 1);
e354a5
+
e354a5
+    struct stringtable_finalized f;
e354a5
+    stringtable_finalize (&s, &f);
e354a5
+    TEST_COMPARE (e->offset, 0);
e354a5
+    TEST_COMPARE_STRING (f.strings, "name");
e354a5
+    TEST_COMPARE (f.size, 5);
e354a5
+    free (f.strings);
e354a5
+    stringtable_free (&s);
e354a5
+  }
e354a5
+
e354a5
+  /* Two strings, one is a prefix of the other.  Tail-merging can only
e354a5
+     happen in one way in this case.  */
e354a5
+  {
e354a5
+    struct stringtable s = { 0, };
e354a5
+    struct stringtable_entry *suffix = stringtable_add (&s, "suffix");
e354a5
+    TEST_COMPARE_STRING (suffix->string, "suffix");
e354a5
+    TEST_COMPARE (suffix->length, 6);
e354a5
+    TEST_COMPARE (s.count, 1);
e354a5
+
e354a5
+    struct stringtable_entry *prefix
e354a5
+      = stringtable_add (&s, "prefix-suffix");
e354a5
+    TEST_COMPARE_STRING (prefix->string, "prefix-suffix");
e354a5
+    TEST_COMPARE (prefix->length, strlen ("prefix-suffix"));
e354a5
+    TEST_COMPARE (s.count, 2);
e354a5
+
e354a5
+    struct stringtable_finalized f;
e354a5
+    stringtable_finalize (&s, &f);
e354a5
+    TEST_COMPARE (prefix->offset, 0);
e354a5
+    TEST_COMPARE (suffix->offset, strlen ("prefix-"));
e354a5
+    TEST_COMPARE_STRING (f.strings, "prefix-suffix");
e354a5
+    TEST_COMPARE (f.size, sizeof ("prefix-suffix"));
e354a5
+    free (f.strings);
e354a5
+    stringtable_free (&s);
e354a5
+  }
e354a5
+
e354a5
+  /* String table with various shared prefixes.  Triggers hash
e354a5
+     resizing.  */
e354a5
+  {
e354a5
+    enum { count = 1500 };
e354a5
+    char *strings[2 * count];
e354a5
+    struct stringtable_entry *entries[2 * count];
e354a5
+    struct stringtable s = { 0, };
e354a5
+    for (int i = 0; i < count; ++i)
e354a5
+      {
e354a5
+        strings[i] = xasprintf ("%d", i);
e354a5
+        entries[i] = stringtable_add (&s, strings[i]);
e354a5
+        TEST_COMPARE (entries[i]->length, strlen (strings[i]));
e354a5
+        TEST_COMPARE_STRING (entries[i]->string, strings[i]);
e354a5
+        strings[i + count] = xasprintf ("prefix/%d", i);
e354a5
+        entries[i + count] = stringtable_add (&s, strings[i + count]);
e354a5
+        TEST_COMPARE (entries[i + count]->length, strlen (strings[i + count]));
e354a5
+        TEST_COMPARE_STRING (entries[i + count]->string, strings[i + count]);
e354a5
+      }
e354a5
+
e354a5
+    struct stringtable_finalized f;
e354a5
+    stringtable_finalize (&s, &f);
e354a5
+
e354a5
+    for (int i = 0; i < 2 * count; ++i)
e354a5
+      {
e354a5
+        TEST_COMPARE (entries[i]->length, strlen (strings[i]));
e354a5
+        TEST_COMPARE_STRING (entries[i]->string, strings[i]);
e354a5
+        TEST_COMPARE_STRING (f.strings + entries[i]->offset, strings[i]);
e354a5
+        free (strings[i]);
e354a5
+      }
e354a5
+
e354a5
+    free (f.strings);
e354a5
+    stringtable_free (&s);
e354a5
+  }
e354a5
+
e354a5
+  /* Verify that maximum tail merging happens.  */
e354a5
+  {
e354a5
+    struct stringtable s = { 0, };
e354a5
+    const char *strings[] = {
e354a5
+      "",
e354a5
+      "a",
e354a5
+      "b",
e354a5
+      "aa",
e354a5
+      "aaa",
e354a5
+      "aa",
e354a5
+      "bb",
e354a5
+      "b",
e354a5
+      "a",
e354a5
+      "ba",
e354a5
+      "baa",
e354a5
+    };
e354a5
+    struct stringtable_entry *entries[array_length (strings)];
e354a5
+    for (int i = 0; i < array_length (strings); ++i)
e354a5
+      entries[i] = stringtable_add (&s, strings[i]);
e354a5
+    for (int i = 0; i < array_length (strings); ++i)
e354a5
+      TEST_COMPARE_STRING (entries[i]->string, strings[i]);
e354a5
+
e354a5
+    struct stringtable_finalized f;
e354a5
+    stringtable_finalize (&s, &f);
e354a5
+
e354a5
+    /* There are only four different strings, "aaa", "ba", "baa",
e354a5
+       "bb".  The rest is shared in an unspecified fashion.  */
e354a5
+    TEST_COMPARE (f.size, 4 + 3 + 4 + 3);
e354a5
+
e354a5
+    for (int i = 0; i < array_length (strings); ++i)
e354a5
+      {
e354a5
+        TEST_COMPARE_STRING (entries[i]->string, strings[i]);
e354a5
+        TEST_COMPARE_STRING (f.strings + entries[i]->offset, strings[i]);
e354a5
+      }
e354a5
+
e354a5
+    free (f.strings);
e354a5
+    stringtable_free (&s);
e354a5
+  }
e354a5
+
e354a5
+  return 0;
e354a5
+}
e354a5
+
e354a5
+#include <support/test-driver.c>
e354a5
+
e354a5
+/* Re-compile the string table implementation here.  It is not
e354a5
+   possible to link against the actual build because it was built for
e354a5
+   use in ldconfig.  */
e354a5
+#define _(arg) arg
e354a5
+#include "stringtable.c"
e354a5
+#include "stringtable_free.c"