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