Blame elf/stringtable.c

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