libcruft-util/buffer/circular.cpp
Danny Robson fdaa5e1392 assert: split CHECK_LIMIT into INCLUSIVE and INDEX
LIMIT hid an off-by-one bug when tests used end iterators. We rename the
assertion to uncover all uses of the flawed implementation, and split it
into an identical assertion, and one intended to protect against
iterator ends.
2020-09-24 08:03:41 +10:00

183 lines
5.3 KiB
C++

/*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
* Copyright 2015 Danny Robson <danny@nerdcruft.net>
*/
#include "circular.hpp"
#include "../debug/assert.hpp"
#include "../maths.hpp"
#include "../memory/system.hpp"
#include "../posix/except.hpp"
#include "../random.hpp"
#include "../scoped.hpp"
#include "../std.hpp"
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <algorithm>
using cruft::buffer::circular;
///////////////////////////////////////////////////////////////////////////////
// generate a random string that could be used as a path leaf
//
// it looks a lot like a shitty tmpnam replacement because it is. we can't use
// tmpnam without security warnings being emitted by binutils linker, despite
// using it safely in this particular scenario.
static void
tmpname (std::string &str, size_t length)
{
static const char alphanum[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
str.resize (length);
std::generate_n (str.begin (), length, [&] (void) {
return *cruft::random::choose (alphanum);
});
}
///////////////////////////////////////////////////////////////////////////////
template <typename ValueT>
circular<ValueT>::circular (size_t bytes)
{
bytes = max (bytes, sizeof (value_type));
bytes = round_up (bytes, memory::pagesize ());
int fd = -1;
constexpr size_t RETRIES = 128;
constexpr size_t NAME_LENGTH = 16;
std::string name (NAME_LENGTH, '\0');
// keep generating paths and attempting to create the shm backing. we may
// fall through this loop upon failure, so be sure to check the validity
// of the fd at the end.
for (size_t i = 0; fd < 0 && i < RETRIES; ++i) {
tmpname (name, NAME_LENGTH);
name[0] = '/';
fd = shm_open (name.c_str (), O_EXCL | O_CREAT | O_TRUNC | O_RDWR, 0600);
}
if (fd < 0)
throw std::runtime_error ("unable to generate shm name");
// setup a desctructor for the shm data. mmap retains a reference, so do
// this whether we succeed or fail in the next phase.
cruft::scoped::function unlink_callback ([&name] (void) {
shm_unlink (name.c_str ());
});
// embiggen to the desired size
if (ftruncate (fd, bytes))
posix::error::throw_code ();
// pre-allocate a sufficiently large virtual memory block. it doesn't
// matter much what flags we use because we'll just be overwriting it
// shortly.
m_begin = reinterpret_cast<ValueT*> (
mmap (nullptr, bytes * 2, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
);
if (MAP_FAILED == m_begin)
posix::error::throw_code ();
// preemptively setup an unmapping object in case the remapping fails
cruft::scoped::function unmapper (
[this, bytes] (void) noexcept { munmap (m_begin, bytes); }
);
// overwrite the map with two adjacent copies of the memory object. this
// must be a shared mapping for the values to propogate across segments.
auto prot = PROT_READ | PROT_WRITE;
auto flag = MAP_FIXED | MAP_SHARED;
m_begin = reinterpret_cast<ValueT*> (mmap (m_begin, bytes, prot, flag, fd, 0));
m_end = reinterpret_cast<ValueT*> (mmap (m_begin + bytes, bytes, prot, flag, fd, 0));
if (m_begin == MAP_FAILED || m_end == MAP_FAILED)
posix::error::throw_code ();
// all went well, disarm the failsafe
unmapper.release ();
}
//-----------------------------------------------------------------------------
template <typename ValueT>
circular<ValueT>::~circular ()
{
auto res = munmap (m_begin, 2 * (m_end - m_begin));
(void)res;
CHECK_ZERO (res);
}
///////////////////////////////////////////////////////////////////////////////
template <typename ValueT>
ValueT*
circular<ValueT>::begin (void)&
{
return m_begin;
}
//-----------------------------------------------------------------------------
template <typename ValueT>
ValueT*
circular<ValueT>::end (void)&
{
return m_end;
}
///////////////////////////////////////////////////////////////////////////////
template <typename ValueT>
size_t
circular<ValueT>::size (void) const
{
return m_end - m_begin;
}
//-----------------------------------------------------------------------------
template <typename ValueT>
typename circular<ValueT>::iterator
circular<ValueT>::constrain (iterator cursor)
{
CHECK_INCLUSIVE (cursor, m_begin, m_begin + 2 * size ());
return m_begin + (cursor - m_begin) % size ();
}
//-----------------------------------------------------------------------------
template <typename ValueT>
cruft::view<typename circular<ValueT>::iterator>
circular<ValueT>::constrain (cruft::view<iterator> window)
{
CHECK_INCLUSIVE (window.begin (), m_begin, m_begin + 2 * size ());
CHECK_INCLUSIVE (window.end (), m_begin, m_begin + 2 * size ());
auto first = window.begin ();
auto last = first + window.size ();
cruft::view res { first, last };
CHECK_EQ (res.size (), window.size ());
CHECK_LE (res.begin (), res.end ());
return res;
}
///////////////////////////////////////////////////////////////////////////////
template class cruft::buffer::circular<u08>;