Blame support/blob_repeat.c

Packit Service 6b2d43
/* Repeating a memory blob, with alias mapping optimization.
Packit Service 6b2d43
   Copyright (C) 2018 Free Software Foundation, Inc.
Packit Service 6b2d43
   This file is part of the GNU C Library.
Packit Service 6b2d43
Packit Service 6b2d43
   The GNU C Library is free software; you can redistribute it and/or
Packit Service 6b2d43
   modify it under the terms of the GNU Lesser General Public
Packit Service 6b2d43
   License as published by the Free Software Foundation; either
Packit Service 6b2d43
   version 2.1 of the License, or (at your option) any later version.
Packit Service 6b2d43
Packit Service 6b2d43
   The GNU C Library is distributed in the hope that it will be useful,
Packit Service 6b2d43
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 6b2d43
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit Service 6b2d43
   Lesser General Public License for more details.
Packit Service 6b2d43
Packit Service 6b2d43
   You should have received a copy of the GNU Lesser General Public
Packit Service 6b2d43
   License along with the GNU C Library; if not, see
Packit Service 6b2d43
   <http://www.gnu.org/licenses/>.  */
Packit Service 6b2d43
Packit Service 6b2d43
#include <errno.h>
Packit Service 6b2d43
#include <fcntl.h>
Packit Service 6b2d43
#include <stdbool.h>
Packit Service 6b2d43
#include <stdlib.h>
Packit Service 6b2d43
#include <string.h>
Packit Service 6b2d43
#include <support/blob_repeat.h>
Packit Service 6b2d43
#include <support/check.h>
Packit Service 6b2d43
#include <support/test-driver.h>
Packit Service 6b2d43
#include <support/support.h>
Packit Service 6b2d43
#include <support/xunistd.h>
Packit Service 6b2d43
#include <sys/mman.h>
Packit Service 6b2d43
#include <unistd.h>
Packit Service 6b2d43
#include <wchar.h>
Packit Service 6b2d43
Packit Service 6b2d43
/* Small allocations should use malloc directly instead of the mmap
Packit Service 6b2d43
   optimization because mappings carry a lot of overhead.  */
Packit Service 6b2d43
static const size_t maximum_small_size = 4 * 1024 * 1024;
Packit Service 6b2d43
Packit Service 6b2d43
/* Internal helper for fill.  */
Packit Service 6b2d43
static void
Packit Service 6b2d43
fill0 (char *target, const char *element, size_t element_size,
Packit Service 6b2d43
       size_t count)
Packit Service 6b2d43
{
Packit Service 6b2d43
  while (count > 0)
Packit Service 6b2d43
    {
Packit Service 6b2d43
      memcpy (target, element, element_size);
Packit Service 6b2d43
      target += element_size;
Packit Service 6b2d43
      --count;
Packit Service 6b2d43
    }
Packit Service 6b2d43
}
Packit Service 6b2d43
Packit Service 6b2d43
/* Fill the buffer at TARGET with COUNT copies of the ELEMENT_SIZE
Packit Service 6b2d43
   bytes starting at ELEMENT.  */
Packit Service 6b2d43
static void
Packit Service 6b2d43
fill (char *target, const char *element, size_t element_size,
Packit Service 6b2d43
      size_t count)
Packit Service 6b2d43
{
Packit Service 6b2d43
  if (element_size == 0 || count == 0)
Packit Service 6b2d43
    return;
Packit Service 6b2d43
  else if (element_size == 1)
Packit Service 6b2d43
    memset (target, element[0], count);
Packit Service 6b2d43
  else if (element_size == sizeof (wchar_t))
Packit Service 6b2d43
    {
Packit Service 6b2d43
      wchar_t wc;
Packit Service 6b2d43
      memcpy (&wc, element, sizeof (wc));
Packit Service 6b2d43
      wmemset ((wchar_t *) target, wc, count);
Packit Service 6b2d43
    }
Packit Service 6b2d43
  else if (element_size < 1024 && count > 4096)
Packit Service 6b2d43
    {
Packit Service 6b2d43
      /* Use larger copies for really small element sizes.  */
Packit Service 6b2d43
      char buffer[8192];
Packit Service 6b2d43
      size_t buffer_count = sizeof (buffer) / element_size;
Packit Service 6b2d43
      fill0 (buffer, element, element_size, buffer_count);
Packit Service 6b2d43
      while (count > 0)
Packit Service 6b2d43
        {
Packit Service 6b2d43
          size_t copy_count = buffer_count;
Packit Service 6b2d43
          if (copy_count > count)
Packit Service 6b2d43
            copy_count = count;
Packit Service 6b2d43
          size_t copy_bytes = copy_count * element_size;
Packit Service 6b2d43
          memcpy (target, buffer, copy_bytes);
Packit Service 6b2d43
          target += copy_bytes;
Packit Service 6b2d43
          count -= copy_count;
Packit Service 6b2d43
        }
Packit Service 6b2d43
    }
Packit Service 6b2d43
  else
Packit Service 6b2d43
    fill0 (target, element, element_size, count);
Packit Service 6b2d43
}
Packit Service 6b2d43
Packit Service 6b2d43
/* Use malloc instead of mmap for small allocations and unusual size
Packit Service 6b2d43
   combinations.  */
Packit Service 6b2d43
static struct support_blob_repeat
Packit Service 6b2d43
allocate_malloc (size_t total_size, const void *element, size_t element_size,
Packit Service 6b2d43
                 size_t count)
Packit Service 6b2d43
{
Packit Service 6b2d43
  void *buffer = malloc (total_size);
Packit Service 6b2d43
  if (buffer == NULL)
Packit Service 6b2d43
    return (struct support_blob_repeat) { 0 };
Packit Service 6b2d43
  fill (buffer, element, element_size, count);
Packit Service 6b2d43
  return (struct support_blob_repeat)
Packit Service 6b2d43
    {
Packit Service 6b2d43
      .start = buffer,
Packit Service 6b2d43
      .size = total_size,
Packit Service 6b2d43
      .use_malloc = true
Packit Service 6b2d43
    };
Packit Service 6b2d43
}
Packit Service 6b2d43
Packit Service 6b2d43
/* Return the least common multiple of PAGE_SIZE and ELEMENT_SIZE,
Packit Service 6b2d43
   avoiding overflow.  This assumes that PAGE_SIZE is a power of
Packit Service 6b2d43
   two.  */
Packit Service 6b2d43
static size_t
Packit Service 6b2d43
minimum_stride_size (size_t page_size, size_t element_size)
Packit Service 6b2d43
{
Packit Service 6b2d43
  TEST_VERIFY_EXIT (page_size > 0);
Packit Service 6b2d43
  TEST_VERIFY_EXIT (element_size > 0);
Packit Service 6b2d43
Packit Service 6b2d43
  /* Compute the number of trailing zeros common to both sizes.  */
Packit Service 6b2d43
  unsigned int common_zeros = __builtin_ctzll (page_size | element_size);
Packit Service 6b2d43
Packit Service 6b2d43
  /* In the product, this power of two appears twice, but in the least
Packit Service 6b2d43
     common multiple, it appears only once.  Therefore, shift one
Packit Service 6b2d43
     factor.  */
Packit Service 6b2d43
  size_t multiple;
Packit Service 6b2d43
  if (__builtin_mul_overflow (page_size >> common_zeros, element_size,
Packit Service 6b2d43
                              &multiple))
Packit Service 6b2d43
    return 0;
Packit Service 6b2d43
  return multiple;
Packit Service 6b2d43
}
Packit Service 6b2d43
Packit Service 6b2d43
/* Allocations larger than maximum_small_size potentially use mmap
Packit Service 6b2d43
   with alias mappings.  */
Packit Service 6b2d43
static struct support_blob_repeat
Packit Service 6b2d43
allocate_big (size_t total_size, const void *element, size_t element_size,
Packit Service 6b2d43
              size_t count)
Packit Service 6b2d43
{
Packit Service 6b2d43
  unsigned long page_size = xsysconf (_SC_PAGESIZE);
Packit Service 6b2d43
  size_t stride_size = minimum_stride_size (page_size, element_size);
Packit Service 6b2d43
  if (stride_size == 0)
Packit Service 6b2d43
    {
Packit Service 6b2d43
      errno = EOVERFLOW;
Packit Service 6b2d43
      return (struct support_blob_repeat) { 0 };
Packit Service 6b2d43
    }
Packit Service 6b2d43
Packit Service 6b2d43
  /* Ensure that the stride size is at least maximum_small_size.  This
Packit Service 6b2d43
     is necessary to reduce the number of distinct mappings.  */
Packit Service 6b2d43
  if (stride_size < maximum_small_size)
Packit Service 6b2d43
    stride_size
Packit Service 6b2d43
      = ((maximum_small_size + stride_size - 1) / stride_size) * stride_size;
Packit Service 6b2d43
Packit Service 6b2d43
  if (stride_size > total_size)
Packit Service 6b2d43
    /* The mmap optimization would not save anything.  */
Packit Service 6b2d43
    return allocate_malloc (total_size, element, element_size, count);
Packit Service 6b2d43
Packit Service 6b2d43
  /* Reserve the memory region.  If we cannot create the mapping,
Packit Service 6b2d43
     there is no reason to set up the backing file.  */
Packit Service 6b2d43
  void *target = mmap (NULL, total_size, PROT_NONE,
Packit Service 6b2d43
                       MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
Packit Service 6b2d43
  if (target == MAP_FAILED)
Packit Service 6b2d43
    return (struct support_blob_repeat) { 0 };
Packit Service 6b2d43
Packit Service 6b2d43
  /* Create the backing file for the repeated mapping.  Call mkstemp
Packit Service 6b2d43
     directly to remove the resources backing the temporary file
Packit Service 6b2d43
     immediately, once support_blob_repeat_free is called.  Using
Packit Service 6b2d43
     create_temp_file would result in a warning during post-test
Packit Service 6b2d43
     cleanup.  */
Packit Service 6b2d43
  int fd;
Packit Service 6b2d43
  {
Packit Service 6b2d43
    char *temppath = xasprintf ("%s/support_blob_repeat-XXXXXX", test_dir);
Packit Service 6b2d43
    fd = mkstemp (temppath);
Packit Service 6b2d43
    if (fd < 0)
Packit Service 6b2d43
      FAIL_EXIT1 ("mkstemp (\"%s\"): %m", temppath);
Packit Service 6b2d43
    xunlink (temppath);
Packit Service 6b2d43
    free (temppath);
Packit Service 6b2d43
  }
Packit Service 6b2d43
Packit Service 6b2d43
  /* Make sure that there is backing storage, so that the fill
Packit Service 6b2d43
     operation will not fault.  */
Packit Service 6b2d43
  if (posix_fallocate (fd, 0, stride_size) != 0)
Packit Service 6b2d43
    FAIL_EXIT1 ("posix_fallocate (%zu): %m", stride_size);
Packit Service 6b2d43
Packit Service 6b2d43
  /* The stride size must still be a multiple of the page size and
Packit Service 6b2d43
     element size.  */
Packit Service 6b2d43
  TEST_VERIFY_EXIT ((stride_size % page_size) == 0);
Packit Service 6b2d43
  TEST_VERIFY_EXIT ((stride_size % element_size) == 0);
Packit Service 6b2d43
Packit Service 6b2d43
  /* Fill the backing store.  */
Packit Service 6b2d43
  {
Packit Service 6b2d43
    void *ptr = mmap (target, stride_size, PROT_READ | PROT_WRITE,
Packit Service 6b2d43
                      MAP_FIXED | MAP_FILE | MAP_SHARED, fd, 0);
Packit Service 6b2d43
    if (ptr == MAP_FAILED)
Packit Service 6b2d43
      {
Packit Service 6b2d43
        int saved_errno = errno;
Packit Service 6b2d43
        xmunmap (target, total_size);
Packit Service 6b2d43
        xclose (fd);
Packit Service 6b2d43
        errno = saved_errno;
Packit Service 6b2d43
        return (struct support_blob_repeat) { 0 };
Packit Service 6b2d43
      }
Packit Service 6b2d43
    if (ptr != target)
Packit Service 6b2d43
      FAIL_EXIT1 ("mapping of %zu bytes moved from %p to %p",
Packit Service 6b2d43
                  stride_size, target, ptr);
Packit Service 6b2d43
Packit Service 6b2d43
    /* Write the repeating data.  */
Packit Service 6b2d43
    fill (target, element, element_size, stride_size / element_size);
Packit Service 6b2d43
Packit Service 6b2d43
    /* Return to a PROT_NONE mapping, just to be on the safe side.  */
Packit Service 6b2d43
    ptr = mmap (target, stride_size, PROT_NONE,
Packit Service 6b2d43
                MAP_FIXED | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
Packit Service 6b2d43
    if (ptr == MAP_FAILED)
Packit Service 6b2d43
      FAIL_EXIT1 ("Failed to reinstate PROT_NONE mapping: %m");
Packit Service 6b2d43
    if (ptr != target)
Packit Service 6b2d43
      FAIL_EXIT1 ("PROT_NONE mapping of %zu bytes moved from %p to %p",
Packit Service 6b2d43
                  stride_size, target, ptr);
Packit Service 6b2d43
  }
Packit Service 6b2d43
Packit Service 6b2d43
  /* Create the alias mappings.  */
Packit Service 6b2d43
  {
Packit Service 6b2d43
    size_t remaining_size = total_size;
Packit Service 6b2d43
    char *current = target;
Packit Service 6b2d43
    int flags = MAP_FIXED | MAP_FILE | MAP_PRIVATE;
Packit Service 6b2d43
#ifdef MAP_NORESERVE
Packit Service 6b2d43
    flags |= MAP_NORESERVE;
Packit Service 6b2d43
#endif
Packit Service 6b2d43
    while (remaining_size > 0)
Packit Service 6b2d43
      {
Packit Service 6b2d43
        size_t to_map = stride_size;
Packit Service 6b2d43
        if (to_map > remaining_size)
Packit Service 6b2d43
          to_map = remaining_size;
Packit Service 6b2d43
        void *ptr = mmap (current, to_map, PROT_READ | PROT_WRITE,
Packit Service 6b2d43
                          flags, fd, 0);
Packit Service 6b2d43
        if (ptr == MAP_FAILED)
Packit Service 6b2d43
          {
Packit Service 6b2d43
            int saved_errno = errno;
Packit Service 6b2d43
            xmunmap (target, total_size);
Packit Service 6b2d43
            xclose (fd);
Packit Service 6b2d43
            errno = saved_errno;
Packit Service 6b2d43
            return (struct support_blob_repeat) { 0 };
Packit Service 6b2d43
          }
Packit Service 6b2d43
        if (ptr != current)
Packit Service 6b2d43
          FAIL_EXIT1 ("MAP_PRIVATE mapping of %zu bytes moved from %p to %p",
Packit Service 6b2d43
                      to_map, target, ptr);
Packit Service 6b2d43
        remaining_size -= to_map;
Packit Service 6b2d43
        current += to_map;
Packit Service 6b2d43
      }
Packit Service 6b2d43
  }
Packit Service 6b2d43
Packit Service 6b2d43
  xclose (fd);
Packit Service 6b2d43
Packit Service 6b2d43
  return (struct support_blob_repeat)
Packit Service 6b2d43
    {
Packit Service 6b2d43
      .start = target,
Packit Service 6b2d43
      .size = total_size,
Packit Service 6b2d43
      .use_malloc = false
Packit Service 6b2d43
    };
Packit Service 6b2d43
}
Packit Service 6b2d43
Packit Service 6b2d43
struct support_blob_repeat
Packit Service 6b2d43
support_blob_repeat_allocate (const void *element, size_t element_size,
Packit Service 6b2d43
                              size_t count)
Packit Service 6b2d43
{
Packit Service 6b2d43
  size_t total_size;
Packit Service 6b2d43
  if (__builtin_mul_overflow (element_size, count, &total_size))
Packit Service 6b2d43
    {
Packit Service 6b2d43
      errno = EOVERFLOW;
Packit Service 6b2d43
      return (struct support_blob_repeat) { 0 };
Packit Service 6b2d43
    }
Packit Service 6b2d43
  if (total_size <= maximum_small_size)
Packit Service 6b2d43
    return allocate_malloc (total_size, element, element_size, count);
Packit Service 6b2d43
  else
Packit Service 6b2d43
    return allocate_big (total_size, element, element_size, count);
Packit Service 6b2d43
}
Packit Service 6b2d43
Packit Service 6b2d43
void
Packit Service 6b2d43
support_blob_repeat_free (struct support_blob_repeat *blob)
Packit Service 6b2d43
{
Packit Service 6b2d43
  if (blob->size > 0)
Packit Service 6b2d43
    {
Packit Service 6b2d43
      int saved_errno = errno;
Packit Service 6b2d43
      if (blob->use_malloc)
Packit Service 6b2d43
        free (blob->start);
Packit Service 6b2d43
      else
Packit Service 6b2d43
        xmunmap (blob->start, blob->size);
Packit Service 6b2d43
      errno = saved_errno;
Packit Service 6b2d43
    }
Packit Service 6b2d43
  *blob = (struct support_blob_repeat) { 0 };
Packit Service 6b2d43
}