Blame elf/stringtable.c

Packit Bot 9b6a8e
/* String tables for ld.so.cache construction.  Implementation.
Packit Bot 9b6a8e
   Copyright (C) 2020 Free Software Foundation, Inc.
Packit Bot 9b6a8e
   This file is part of the GNU C Library.
Packit Bot 9b6a8e
Packit Bot 9b6a8e
   This program is free software; you can redistribute it and/or modify
Packit Bot 9b6a8e
   it under the terms of the GNU General Public License as published
Packit Bot 9b6a8e
   by the Free Software Foundation; version 2 of the License, or
Packit Bot 9b6a8e
   (at your option) any later version.
Packit Bot 9b6a8e
Packit Bot 9b6a8e
   This program is distributed in the hope that it will be useful,
Packit Bot 9b6a8e
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Bot 9b6a8e
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Bot 9b6a8e
   GNU General Public License for more details.
Packit Bot 9b6a8e
Packit Bot 9b6a8e
   You should have received a copy of the GNU General Public License
Packit Bot 9b6a8e
   along with this program; if not, see <https://www.gnu.org/licenses/>.  */
Packit Bot 9b6a8e
Packit Bot 9b6a8e
#include <assert.h>
Packit Bot 9b6a8e
#include <error.h>
Packit Bot 9b6a8e
#include <ldconfig.h>
Packit Bot 9b6a8e
#include <libintl.h>
Packit Bot 9b6a8e
#include <stdlib.h>
Packit Bot 9b6a8e
#include <string.h>
Packit Bot 9b6a8e
#include <stringtable.h>
Packit Bot 9b6a8e
Packit Bot 9b6a8e
static void
Packit Bot 9b6a8e
stringtable_init (struct stringtable *table)
Packit Bot 9b6a8e
{
Packit Bot 9b6a8e
  table->count = 0;
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  /* This needs to be a power of two.  128 is sufficient to keep track
Packit Bot 9b6a8e
     of 42 DSOs without resizing (assuming two strings per DSOs).
Packit Bot 9b6a8e
     glibc itself comes with more than 20 DSOs, so 64 would likely to
Packit Bot 9b6a8e
     be too small.  */
Packit Bot 9b6a8e
  table->allocated = 128;
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  table->entries = xcalloc (table->allocated, sizeof (table->entries[0]));
Packit Bot 9b6a8e
}
Packit Bot 9b6a8e
Packit Bot 9b6a8e
/* 32-bit FNV-1a hash function.  */
Packit Bot 9b6a8e
static uint32_t
Packit Bot 9b6a8e
fnv1a (const char *string, size_t length)
Packit Bot 9b6a8e
{
Packit Bot 9b6a8e
  const unsigned char *p = (const unsigned char *) string;
Packit Bot 9b6a8e
  uint32_t hash = 2166136261U;
Packit Bot 9b6a8e
  for (size_t i = 0; i < length; ++i)
Packit Bot 9b6a8e
    {
Packit Bot 9b6a8e
      hash ^= p[i];
Packit Bot 9b6a8e
      hash *= 16777619U;
Packit Bot 9b6a8e
    }
Packit Bot 9b6a8e
  return hash;
Packit Bot 9b6a8e
}
Packit Bot 9b6a8e
Packit Bot 9b6a8e
/* Double the capacity of the hash table.  */
Packit Bot 9b6a8e
static void
Packit Bot 9b6a8e
stringtable_rehash (struct stringtable *table)
Packit Bot 9b6a8e
{
Packit Bot 9b6a8e
  /* This computation cannot overflow because the old total in-memory
Packit Bot 9b6a8e
     size of the hash table is larger than the computed value.  */
Packit Bot 9b6a8e
  uint32_t new_allocated = table->allocated * 2;
Packit Bot 9b6a8e
  struct stringtable_entry **new_entries
Packit Bot 9b6a8e
    = xcalloc (new_allocated, sizeof (table->entries[0]));
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  uint32_t mask = new_allocated - 1;
Packit Bot 9b6a8e
  for (uint32_t i = 0; i < table->allocated; ++i)
Packit Bot 9b6a8e
    for (struct stringtable_entry *e = table->entries[i]; e != NULL; )
Packit Bot 9b6a8e
      {
Packit Bot 9b6a8e
        struct stringtable_entry *next = e->next;
Packit Bot 9b6a8e
        uint32_t hash = fnv1a (e->string, e->length);
Packit Bot 9b6a8e
        uint32_t new_index = hash & mask;
Packit Bot 9b6a8e
        e->next = new_entries[new_index];
Packit Bot 9b6a8e
        new_entries[new_index] = e;
Packit Bot 9b6a8e
        e = next;
Packit Bot 9b6a8e
      }
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  free (table->entries);
Packit Bot 9b6a8e
  table->entries = new_entries;
Packit Bot 9b6a8e
  table->allocated = new_allocated;
Packit Bot 9b6a8e
}
Packit Bot 9b6a8e
Packit Bot 9b6a8e
struct stringtable_entry *
Packit Bot 9b6a8e
stringtable_add (struct stringtable *table, const char *string)
Packit Bot 9b6a8e
{
Packit Bot 9b6a8e
  /* Check for a zero-initialized table.  */
Packit Bot 9b6a8e
  if (table->allocated == 0)
Packit Bot 9b6a8e
    stringtable_init (table);
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  size_t length = strlen (string);
Packit Bot 9b6a8e
  if (length > (1U << 30))
Packit Bot 9b6a8e
    error (EXIT_FAILURE, 0, _("String table string is too long"));
Packit Bot 9b6a8e
  uint32_t hash = fnv1a (string, length);
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  /* Return a previously-existing entry.  */
Packit Bot 9b6a8e
  for (struct stringtable_entry *e
Packit Bot 9b6a8e
         = table->entries[hash & (table->allocated - 1)];
Packit Bot 9b6a8e
       e != NULL; e = e->next)
Packit Bot 9b6a8e
    if (e->length == length && memcmp (e->string, string, length) == 0)
Packit Bot 9b6a8e
      return e;
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  /* Increase the size of the table if necessary.  Keep utilization
Packit Bot 9b6a8e
     below two thirds.  */
Packit Bot 9b6a8e
  if (table->count >= (1U << 30))
Packit Bot 9b6a8e
    error (EXIT_FAILURE, 0, _("String table has too many entries"));
Packit Bot 9b6a8e
  if (table->count * 3 > table->allocated * 2)
Packit Bot 9b6a8e
    stringtable_rehash (table);
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  /* Add the new table entry.  */
Packit Bot 9b6a8e
  ++table->count;
Packit Bot 9b6a8e
  struct stringtable_entry *e
Packit Bot 9b6a8e
    = xmalloc (offsetof (struct stringtable_entry, string) + length + 1);
Packit Bot 9b6a8e
  uint32_t index = hash & (table->allocated - 1);
Packit Bot 9b6a8e
  e->next = table->entries[index];
Packit Bot 9b6a8e
  table->entries[index] = e;
Packit Bot 9b6a8e
  e->length = length;
Packit Bot 9b6a8e
  e->offset = 0;
Packit Bot 9b6a8e
  memcpy (e->string, string, length + 1);
Packit Bot 9b6a8e
  return e;
Packit Bot 9b6a8e
}
Packit Bot 9b6a8e
Packit Bot 9b6a8e
/* Sort reversed strings in reverse lexicographic order.  This is used
Packit Bot 9b6a8e
   for tail merging.  */
Packit Bot 9b6a8e
static int
Packit Bot 9b6a8e
finalize_compare (const void *l, const void *r)
Packit Bot 9b6a8e
{
Packit Bot 9b6a8e
  struct stringtable_entry *left = *(struct stringtable_entry **) l;
Packit Bot 9b6a8e
  struct stringtable_entry *right = *(struct stringtable_entry **) r;
Packit Bot 9b6a8e
  size_t to_compare;
Packit Bot 9b6a8e
  if (left->length < right->length)
Packit Bot 9b6a8e
    to_compare = left->length;
Packit Bot 9b6a8e
  else
Packit Bot 9b6a8e
    to_compare = right->length;
Packit Bot 9b6a8e
  for (size_t i = 1; i <= to_compare; ++i)
Packit Bot 9b6a8e
    {
Packit Bot 9b6a8e
      unsigned char lch = left->string[left->length - i];
Packit Bot 9b6a8e
      unsigned char rch = right->string[right->length - i];
Packit Bot 9b6a8e
      if (lch != rch)
Packit Bot 9b6a8e
        return rch - lch;
Packit Bot 9b6a8e
    }
Packit Bot 9b6a8e
  if (left->length == right->length)
Packit Bot 9b6a8e
    return 0;
Packit Bot 9b6a8e
  else if (left->length < right->length)
Packit Bot 9b6a8e
    /* Longer strings should come first.  */
Packit Bot 9b6a8e
    return 1;
Packit Bot 9b6a8e
  else
Packit Bot 9b6a8e
    return -1;
Packit Bot 9b6a8e
}
Packit Bot 9b6a8e
Packit Bot 9b6a8e
void
Packit Bot 9b6a8e
stringtable_finalize (struct stringtable *table,
Packit Bot 9b6a8e
                      struct stringtable_finalized *result)
Packit Bot 9b6a8e
{
Packit Bot 9b6a8e
  if (table->count == 0)
Packit Bot 9b6a8e
    {
Packit Bot 9b6a8e
      result->strings = xstrdup ("");
Packit Bot 9b6a8e
      result->size = 0;
Packit Bot 9b6a8e
      return;
Packit Bot 9b6a8e
    }
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  /* Optimize the order of the strings.  */
Packit Bot 9b6a8e
  struct stringtable_entry **array = xcalloc (table->count, sizeof (*array));
Packit Bot 9b6a8e
  {
Packit Bot 9b6a8e
    size_t j = 0;
Packit Bot 9b6a8e
    for (uint32_t i = 0; i < table->allocated; ++i)
Packit Bot 9b6a8e
      for (struct stringtable_entry *e = table->entries[i]; e != NULL;
Packit Bot 9b6a8e
           e = e->next)
Packit Bot 9b6a8e
        {
Packit Bot 9b6a8e
          array[j] = e;
Packit Bot 9b6a8e
          ++j;
Packit Bot 9b6a8e
        }
Packit Bot 9b6a8e
    assert (j == table->count);
Packit Bot 9b6a8e
  }
Packit Bot 9b6a8e
  qsort (array, table->count, sizeof (*array), finalize_compare);
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  /* Assign offsets, using tail merging (sharing suffixes) if possible.  */
Packit Bot 9b6a8e
  array[0]->offset = 0;
Packit Bot 9b6a8e
  for (uint32_t j = 1; j < table->count; ++j)
Packit Bot 9b6a8e
    {
Packit Bot 9b6a8e
      struct stringtable_entry *previous = array[j - 1];
Packit Bot 9b6a8e
      struct stringtable_entry *current = array[j];
Packit Bot 9b6a8e
      if (previous->length >= current->length
Packit Bot 9b6a8e
          && memcmp (&previous->string[previous->length - current->length],
Packit Bot 9b6a8e
                     current->string, current->length) == 0)
Packit Bot 9b6a8e
        current->offset = (previous->offset + previous->length
Packit Bot 9b6a8e
                           - current->length);
Packit Bot 9b6a8e
      else if (__builtin_add_overflow (previous->offset,
Packit Bot 9b6a8e
                                       previous->length + 1,
Packit Bot 9b6a8e
                                       &current->offset))
Packit Bot 9b6a8e
        error (EXIT_FAILURE, 0, _("String table is too large"));
Packit Bot 9b6a8e
    }
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  /* Allocate the result string.  */
Packit Bot 9b6a8e
  {
Packit Bot 9b6a8e
    struct stringtable_entry *last = array[table->count - 1];
Packit Bot 9b6a8e
    if (__builtin_add_overflow (last->offset, last->length + 1,
Packit Bot 9b6a8e
                                &result->size))
Packit Bot 9b6a8e
      error (EXIT_FAILURE, 0, _("String table is too large"));
Packit Bot 9b6a8e
  }
Packit Bot 9b6a8e
  /* The strings are copied from the hash table, so the array is no
Packit Bot 9b6a8e
     longer needed.  */
Packit Bot 9b6a8e
  free (array);
Packit Bot 9b6a8e
  result->strings = xcalloc (result->size, 1);
Packit Bot 9b6a8e
Packit Bot 9b6a8e
  /* Copy the strings.  */
Packit Bot 9b6a8e
  for (uint32_t i = 0; i < table->allocated; ++i)
Packit Bot 9b6a8e
    for (struct stringtable_entry *e = table->entries[i]; e != NULL;
Packit Bot 9b6a8e
         e = e->next)
Packit Bot 9b6a8e
      if (result->strings[e->offset] == '\0')
Packit Bot 9b6a8e
        memcpy (&result->strings[e->offset], e->string, e->length + 1);
Packit Bot 9b6a8e
}