Blame elf/stringtable.c

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