Blame include/alloc_buffer.h

Packit 6c4009
/* Allocation from a fixed-size buffer.
Packit 6c4009
   Copyright (C) 2017-2018 Free Software Foundation, Inc.
Packit 6c4009
   This file is part of the GNU C Library.
Packit 6c4009
Packit 6c4009
   The GNU C Library is free software; you can redistribute it and/or
Packit 6c4009
   modify it under the terms of the GNU Lesser General Public
Packit 6c4009
   License as published by the Free Software Foundation; either
Packit 6c4009
   version 2.1 of the License, or (at your option) any later version.
Packit 6c4009
Packit 6c4009
   The GNU C Library is distributed in the hope that it will be useful,
Packit 6c4009
   but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 6c4009
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Packit 6c4009
   Lesser General Public License for more details.
Packit 6c4009
Packit 6c4009
   You should have received a copy of the GNU Lesser General Public
Packit 6c4009
   License along with the GNU C Library; if not, see
Packit 6c4009
   <http://www.gnu.org/licenses/>.  */
Packit 6c4009
Packit 6c4009
/* Allocation buffers are used to carve out sub-allocations from a
Packit 6c4009
   larger allocation.  Their primary application is in writing NSS
Packit 6c4009
   modules, which receive a caller-allocated buffer in which they are
Packit 6c4009
   expected to store variable-length results:
Packit 6c4009
Packit 6c4009
     void *buffer = ...;
Packit 6c4009
     size_t buffer_size = ...;
Packit 6c4009
Packit 6c4009
     struct alloc_buffer buf = alloc_buffer_create (buffer, buffer_size);
Packit 6c4009
     result->gr_name = alloc_buffer_copy_string (&buf, name);
Packit 6c4009
Packit 6c4009
     // Allocate a list of group_count groups and copy strings into it.
Packit 6c4009
     char **group_list = alloc_buffer_alloc_array
Packit 6c4009
       (&buf, char *, group_count  + 1);
Packit 6c4009
     if (group_list == NULL)
Packit 6c4009
       return ...; // Request a larger buffer.
Packit 6c4009
     for (int i = 0; i < group_count; ++i)
Packit 6c4009
       group_list[i] = alloc_buffer_copy_string (&buf, group_list_src[i]);
Packit 6c4009
     group_list[group_count] = NULL;
Packit 6c4009
     ...
Packit 6c4009
Packit 6c4009
     if (alloc_buffer_has_failed (&buf))
Packit 6c4009
       return ...; // Request a larger buffer.
Packit 6c4009
     result->gr_mem = group_list;
Packit 6c4009
     ...
Packit 6c4009
Packit 6c4009
   Note that it is not necessary to check the results of individual
Packit 6c4009
   allocation operations if the returned pointer is not dereferenced.
Packit 6c4009
   Allocation failure is sticky, so one check using
Packit 6c4009
   alloc_buffer_has_failed at the end covers all previous failures.
Packit 6c4009
Packit 6c4009
   A different use case involves combining multiple heap allocations
Packit 6c4009
   into a single, large one.  In the following example, an array of
Packit 6c4009
   doubles and an array of ints is allocated:
Packit 6c4009
Packit 6c4009
     size_t double_array_size = ...;
Packit 6c4009
     size_t int_array_size = ...;
Packit 6c4009
Packit 6c4009
     void *heap_ptr;
Packit 6c4009
     struct alloc_buffer buf = alloc_buffer_allocate
Packit 6c4009
       (double_array_size * sizeof (double) + int_array_size * sizeof (int),
Packit 6c4009
        &heap_ptr);
Packit 6c4009
     _Static_assert (__alignof__ (double) >= __alignof__ (int),
Packit 6c4009
                     "no padding after double array");
Packit 6c4009
     double *double_array = alloc_buffer_alloc_array
Packit 6c4009
       (&buf, double, double_array_size);
Packit 6c4009
     int *int_array = alloc_buffer_alloc_array (&buf, int, int_array_size);
Packit 6c4009
     if (alloc_buffer_has_failed (&buf))
Packit 6c4009
       return ...; // Report error.
Packit 6c4009
     ...
Packit 6c4009
     free (heap_ptr);
Packit 6c4009
Packit 6c4009
   The advantage over manual coding is that the computation of the
Packit 6c4009
   allocation size does not need an overflow check.  In case of an
Packit 6c4009
   overflow, one of the subsequent allocations from the buffer will
Packit 6c4009
   fail.  The initial size computation is checked for consistency at
Packit 6c4009
   run time, too.  */
Packit 6c4009
Packit 6c4009
#ifndef _ALLOC_BUFFER_H
Packit 6c4009
#define _ALLOC_BUFFER_H
Packit 6c4009
Packit 6c4009
#include <inttypes.h>
Packit 6c4009
#include <stdbool.h>
Packit 6c4009
#include <stddef.h>
Packit 6c4009
#include <stdlib.h>
Packit 6c4009
#include <sys/param.h>
Packit 6c4009
Packit 6c4009
/* struct alloc_buffer objects refer to a region of bytes in memory of a
Packit 6c4009
   fixed size.  The functions below can be used to allocate single
Packit 6c4009
   objects and arrays from this memory region, or write to its end.
Packit 6c4009
   On allocation failure (or if an attempt to write beyond the end of
Packit 6c4009
   the buffer with one of the copy functions), the buffer enters a
Packit 6c4009
   failed state.
Packit 6c4009
Packit 6c4009
   struct alloc_buffer objects can be copied.  The backing buffer will
Packit 6c4009
   be shared, but the current write position will be independent.
Packit 6c4009
Packit 6c4009
   Conceptually, the memory region consists of a current write pointer
Packit 6c4009
   and a limit, beyond which the write pointer cannot move.  */
Packit 6c4009
struct alloc_buffer
Packit 6c4009
{
Packit 6c4009
  /* uintptr_t is used here to simplify the alignment code, and to
Packit 6c4009
     avoid issues undefined subtractions if the buffer covers more
Packit 6c4009
     than half of the address space (which would result in differences
Packit 6c4009
     which could not be represented as a ptrdiff_t value).  */
Packit 6c4009
  uintptr_t __alloc_buffer_current;
Packit 6c4009
  uintptr_t __alloc_buffer_end;
Packit 6c4009
};
Packit 6c4009
Packit 6c4009
enum
Packit 6c4009
  {
Packit 6c4009
    /* The value for the __alloc_buffer_current member which marks the
Packit 6c4009
       buffer as invalid (together with a zero-length buffer).  */
Packit 6c4009
    __ALLOC_BUFFER_INVALID_POINTER = 0,
Packit 6c4009
  };
Packit 6c4009
Packit 6c4009
/* Internal function.  Terminate the process using __libc_fatal.  */
Packit 6c4009
void __libc_alloc_buffer_create_failure (void *start, size_t size);
Packit 6c4009
Packit 6c4009
/* Create a new allocation buffer.  The byte range from START to START
Packit 6c4009
   + SIZE - 1 must be valid, and the allocation buffer allocates
Packit 6c4009
   objects from that range.  If START is NULL (so that SIZE must be
Packit 6c4009
   0), the buffer is marked as failed immediately.  */
Packit 6c4009
static inline struct alloc_buffer
Packit 6c4009
alloc_buffer_create (void *start, size_t size)
Packit 6c4009
{
Packit 6c4009
  uintptr_t current = (uintptr_t) start;
Packit 6c4009
  uintptr_t end = (uintptr_t) start + size;
Packit 6c4009
  if (end < current)
Packit 6c4009
    __libc_alloc_buffer_create_failure (start, size);
Packit 6c4009
  return (struct alloc_buffer) { current, end };
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Internal function.  See alloc_buffer_allocate below.  */
Packit 6c4009
struct alloc_buffer __libc_alloc_buffer_allocate (size_t size, void **pptr)
Packit 6c4009
  __attribute__ ((nonnull (2)));
Packit 6c4009
Packit 6c4009
/* Allocate a buffer of SIZE bytes using malloc.  The returned buffer
Packit 6c4009
   is in a failed state if malloc fails.  *PPTR points to the start of
Packit 6c4009
   the buffer and can be used to free it later, after the returned
Packit 6c4009
   buffer has been freed.  */
Packit 6c4009
static __always_inline __attribute__ ((nonnull (2)))
Packit 6c4009
struct alloc_buffer alloc_buffer_allocate (size_t size, void **pptr)
Packit 6c4009
{
Packit 6c4009
  return __libc_alloc_buffer_allocate (size, pptr);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Mark the buffer as failed.  */
Packit 6c4009
static inline void __attribute__ ((nonnull (1)))
Packit 6c4009
alloc_buffer_mark_failed (struct alloc_buffer *buf)
Packit 6c4009
{
Packit 6c4009
  buf->__alloc_buffer_current = __ALLOC_BUFFER_INVALID_POINTER;
Packit 6c4009
  buf->__alloc_buffer_end = __ALLOC_BUFFER_INVALID_POINTER;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Return the remaining number of bytes in the buffer.  */
Packit 6c4009
static __always_inline __attribute__ ((nonnull (1))) size_t
Packit 6c4009
alloc_buffer_size (const struct alloc_buffer *buf)
Packit 6c4009
{
Packit 6c4009
  return buf->__alloc_buffer_end - buf->__alloc_buffer_current;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Return true if the buffer has been marked as failed.  */
Packit 6c4009
static inline bool __attribute__ ((nonnull (1)))
Packit 6c4009
alloc_buffer_has_failed (const struct alloc_buffer *buf)
Packit 6c4009
{
Packit 6c4009
  return buf->__alloc_buffer_current == __ALLOC_BUFFER_INVALID_POINTER;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Add a single byte to the buffer (consuming the space for this
Packit 6c4009
   byte).  Mark the buffer as failed if there is not enough room.  */
Packit 6c4009
static inline void __attribute__ ((nonnull (1)))
Packit 6c4009
alloc_buffer_add_byte (struct alloc_buffer *buf, unsigned char b)
Packit 6c4009
{
Packit 6c4009
  if (__glibc_likely (buf->__alloc_buffer_current < buf->__alloc_buffer_end))
Packit 6c4009
    {
Packit 6c4009
      *(unsigned char *) buf->__alloc_buffer_current = b;
Packit 6c4009
      ++buf->__alloc_buffer_current;
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    alloc_buffer_mark_failed (buf);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Obtain a pointer to LENGTH bytes in BUF, and consume these bytes.
Packit 6c4009
   NULL is returned if there is not enough room, and the buffer is
Packit 6c4009
   marked as failed, or if the buffer has already failed.
Packit 6c4009
   (Zero-length allocations from an empty buffer which has not yet
Packit 6c4009
   failed succeed.)  */
Packit 6c4009
static inline __attribute__ ((nonnull (1))) void *
Packit 6c4009
alloc_buffer_alloc_bytes (struct alloc_buffer *buf, size_t length)
Packit 6c4009
{
Packit 6c4009
  if (length <= alloc_buffer_size (buf))
Packit 6c4009
    {
Packit 6c4009
      void *result = (void *) buf->__alloc_buffer_current;
Packit 6c4009
      buf->__alloc_buffer_current += length;
Packit 6c4009
      return result;
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      alloc_buffer_mark_failed (buf);
Packit 6c4009
      return NULL;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Internal function.  Statically assert that the type size is
Packit 6c4009
   constant and valid.  */
Packit 6c4009
static __always_inline size_t
Packit 6c4009
__alloc_buffer_assert_size (size_t size)
Packit 6c4009
{
Packit 6c4009
  if (!__builtin_constant_p (size))
Packit 6c4009
    {
Packit 6c4009
      __errordecl (error, "type size is not constant");
Packit 6c4009
      error ();
Packit 6c4009
    }
Packit 6c4009
  else if (size == 0)
Packit 6c4009
    {
Packit 6c4009
      __errordecl (error, "type size is zero");
Packit 6c4009
      error ();
Packit 6c4009
    }
Packit 6c4009
  return size;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Internal function.  Statically assert that the type alignment is
Packit 6c4009
   constant and valid.  */
Packit 6c4009
static __always_inline size_t
Packit 6c4009
__alloc_buffer_assert_align (size_t align)
Packit 6c4009
{
Packit 6c4009
  if (!__builtin_constant_p (align))
Packit 6c4009
    {
Packit 6c4009
      __errordecl (error, "type alignment is not constant");
Packit 6c4009
      error ();
Packit 6c4009
    }
Packit 6c4009
  else if (align == 0)
Packit 6c4009
    {
Packit 6c4009
      __errordecl (error, "type alignment is zero");
Packit 6c4009
      error ();
Packit 6c4009
    }
Packit 6c4009
  else if (!powerof2 (align))
Packit 6c4009
    {
Packit 6c4009
      __errordecl (error, "type alignment is not a power of two");
Packit 6c4009
      error ();
Packit 6c4009
    }
Packit 6c4009
  return align;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Internal function.  Obtain a pointer to an object.  */
Packit 6c4009
static inline __attribute__ ((nonnull (1))) void *
Packit 6c4009
__alloc_buffer_alloc (struct alloc_buffer *buf, size_t size, size_t align)
Packit 6c4009
{
Packit 6c4009
  if (size == 1 && align == 1)
Packit 6c4009
    return alloc_buffer_alloc_bytes (buf, size);
Packit 6c4009
Packit 6c4009
  size_t current = buf->__alloc_buffer_current;
Packit 6c4009
  size_t aligned = roundup (current, align);
Packit 6c4009
  size_t new_current = aligned + size;
Packit 6c4009
  if (aligned >= current        /* No overflow in align step.  */
Packit 6c4009
      && new_current >= size    /* No overflow in size computation.  */
Packit 6c4009
      && new_current <= buf->__alloc_buffer_end) /* Room in buffer.  */
Packit 6c4009
    {
Packit 6c4009
      buf->__alloc_buffer_current = new_current;
Packit 6c4009
      return (void *) aligned;
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      alloc_buffer_mark_failed (buf);
Packit 6c4009
      return NULL;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Obtain a TYPE * pointer to an object in BUF of TYPE.  Consume these
Packit 6c4009
   bytes from the buffer.  Return NULL and mark the buffer as failed
Packit 6c4009
   if there is not enough room in the buffer, or if the buffer has
Packit 6c4009
   failed before.  */
Packit 6c4009
#define alloc_buffer_alloc(buf, type)				\
Packit 6c4009
  ((type *) __alloc_buffer_alloc				\
Packit 6c4009
   (buf, __alloc_buffer_assert_size (sizeof (type)),		\
Packit 6c4009
    __alloc_buffer_assert_align (__alignof__ (type))))
Packit 6c4009
Packit 6c4009
/* Internal function.  Obtain a pointer to an object which is
Packit 6c4009
   subsequently added.  */
Packit 6c4009
static inline const __attribute__ ((nonnull (1))) void *
Packit 6c4009
__alloc_buffer_next (struct alloc_buffer *buf, size_t align)
Packit 6c4009
{
Packit 6c4009
  if (align == 1)
Packit 6c4009
    return (const void *) buf->__alloc_buffer_current;
Packit 6c4009
Packit 6c4009
  size_t current = buf->__alloc_buffer_current;
Packit 6c4009
  size_t aligned = roundup (current, align);
Packit 6c4009
  if (aligned >= current        /* No overflow in align step.  */
Packit 6c4009
      && aligned <= buf->__alloc_buffer_end) /* Room in buffer.  */
Packit 6c4009
    {
Packit 6c4009
      buf->__alloc_buffer_current = aligned;
Packit 6c4009
      return (const void *) aligned;
Packit 6c4009
    }
Packit 6c4009
  else
Packit 6c4009
    {
Packit 6c4009
      alloc_buffer_mark_failed (buf);
Packit 6c4009
      return NULL;
Packit 6c4009
    }
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Like alloc_buffer_alloc, but do not advance the pointer beyond the
Packit 6c4009
   object (so a subseqent call to alloc_buffer_next or
Packit 6c4009
   alloc_buffer_alloc returns the same pointer).  Note that the buffer
Packit 6c4009
   is still aligned according to the requirements of TYPE.  The effect
Packit 6c4009
   of this function is similar to allocating a zero-length array from
Packit 6c4009
   the buffer.  */
Packit 6c4009
#define alloc_buffer_next(buf, type)				\
Packit 6c4009
  ((const type *) __alloc_buffer_next				\
Packit 6c4009
   (buf, __alloc_buffer_assert_align (__alignof__ (type))))
Packit 6c4009
Packit 6c4009
/* Internal function.  Allocate an array.  */
Packit 6c4009
void * __libc_alloc_buffer_alloc_array (struct alloc_buffer *buf,
Packit 6c4009
					size_t size, size_t align,
Packit 6c4009
					size_t count)
Packit 6c4009
  __attribute__ ((nonnull (1)));
Packit 6c4009
Packit 6c4009
/* Obtain a TYPE * pointer to an array of COUNT objects in BUF of
Packit 6c4009
   TYPE.  Consume these bytes from the buffer.  Return NULL and mark
Packit 6c4009
   the buffer as failed if there is not enough room in the buffer,
Packit 6c4009
   or if the buffer has failed before.  (Zero-length allocations from
Packit 6c4009
   an empty buffer which has not yet failed succeed.)  */
Packit 6c4009
#define alloc_buffer_alloc_array(buf, type, count)       \
Packit 6c4009
  ((type *) __libc_alloc_buffer_alloc_array		 \
Packit 6c4009
   (buf, __alloc_buffer_assert_size (sizeof (type)),	 \
Packit 6c4009
    __alloc_buffer_assert_align (__alignof__ (type)),	 \
Packit 6c4009
    count))
Packit 6c4009
Packit 6c4009
/* Internal function.  See alloc_buffer_copy_bytes below.  */
Packit 6c4009
struct alloc_buffer __libc_alloc_buffer_copy_bytes (struct alloc_buffer,
Packit 6c4009
						    const void *, size_t)
Packit 6c4009
  __attribute__ ((nonnull (2)));
Packit 6c4009
Packit 6c4009
/* Copy SIZE bytes starting at SRC into the buffer.  If there is not
Packit 6c4009
   enough room in the buffer, the buffer is marked as failed.  No
Packit 6c4009
   alignment of the buffer is performed.  */
Packit 6c4009
static inline __attribute__ ((nonnull (1, 2))) void
Packit 6c4009
alloc_buffer_copy_bytes (struct alloc_buffer *buf, const void *src, size_t size)
Packit 6c4009
{
Packit 6c4009
  *buf = __libc_alloc_buffer_copy_bytes (*buf, src, size);
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
/* Internal function.  See alloc_buffer_copy_string below.  */
Packit 6c4009
struct alloc_buffer __libc_alloc_buffer_copy_string (struct alloc_buffer,
Packit 6c4009
						     const char *)
Packit 6c4009
  __attribute__ ((nonnull (2)));
Packit 6c4009
Packit 6c4009
/* Copy the string at SRC into the buffer, including its null
Packit 6c4009
   terminator.  If there is not enough room in the buffer, the buffer
Packit 6c4009
   is marked as failed.  Return a pointer to the string.  */
Packit 6c4009
static inline __attribute__ ((nonnull (1, 2))) char *
Packit 6c4009
alloc_buffer_copy_string (struct alloc_buffer *buf, const char *src)
Packit 6c4009
{
Packit 6c4009
  char *result = (char *) buf->__alloc_buffer_current;
Packit 6c4009
  *buf = __libc_alloc_buffer_copy_string (*buf, src);
Packit 6c4009
  if (alloc_buffer_has_failed (buf))
Packit 6c4009
    result = NULL;
Packit 6c4009
  return result;
Packit 6c4009
}
Packit 6c4009
Packit 6c4009
#ifndef _ISOMAC
Packit 6c4009
libc_hidden_proto (__libc_alloc_buffer_alloc_array)
Packit 6c4009
libc_hidden_proto (__libc_alloc_buffer_allocate)
Packit 6c4009
libc_hidden_proto (__libc_alloc_buffer_copy_bytes)
Packit 6c4009
libc_hidden_proto (__libc_alloc_buffer_copy_string)
Packit 6c4009
libc_hidden_proto (__libc_alloc_buffer_create_failure)
Packit 6c4009
#endif
Packit 6c4009
Packit 6c4009
#endif /* _ALLOC_BUFFER_H */