/* * 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 */ #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 #include #include #include 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 circular::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 ( 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 (mmap (m_begin, bytes, prot, flag, fd, 0)); m_end = reinterpret_cast (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 circular::~circular () { auto res = munmap (m_begin, 2 * (m_end - m_begin)); (void)res; CHECK_ZERO (res); } /////////////////////////////////////////////////////////////////////////////// template ValueT* circular::begin (void)& { return m_begin; } //----------------------------------------------------------------------------- template ValueT* circular::end (void)& { return m_end; } /////////////////////////////////////////////////////////////////////////////// template size_t circular::size (void) const { return m_end - m_begin; } //----------------------------------------------------------------------------- template typename circular::iterator circular::constrain (iterator cursor) { CHECK_INCLUSIVE (cursor, m_begin, m_begin + 2 * size ()); return m_begin + (cursor - m_begin) % size (); } //----------------------------------------------------------------------------- template cruft::view::iterator> circular::constrain (cruft::view 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;