/*
 * 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 "paged.hpp"

#include "../cast.hpp"
#include "../maths.hpp"
#include "../pointer.hpp"
#include "../posix/except.hpp"
#include "../memory/system.hpp"

#include <sys/mman.h>

using cruft::buffer::paged;


///////////////////////////////////////////////////////////////////////////////
paged::paged (size_t bytes)
    : m_data (nullptr)
{
    auto const allocated_bytes = round_up (bytes, memory::pagesize ());

    // reserve the address region with no access permissions
    auto ptr = reinterpret_cast<value_type*> (
        mmap (nullptr, allocated_bytes, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)
    );

    if (ptr == MAP_FAILED)
        posix::error::throw_code ();

    m_data = { ptr, allocated_bytes };
}


//-----------------------------------------------------------------------------
paged::~paged ()
{
    // ignore errors in production; we don't want to double throw.
    auto res = munmap (m_data.data (), capacity ());
    (void)res;
    CHECK_ZERO (res);
}


///////////////////////////////////////////////////////////////////////////////
paged::value_type *
paged::begin (void)&
{
    return m_data.begin ();
}


//-----------------------------------------------------------------------------
paged::value_type *
paged::end (void)&
{
    return m_data.end ();
}


///////////////////////////////////////////////////////////////////////////////
void
paged::commit (cruft::view<value_type*> region)
{
    apply_prot (region, PROT_READ | PROT_WRITE);
}


//-----------------------------------------------------------------------------
void
paged::release (cruft::view<value_type*> region)
{
    apply_prot (region, PROT_NONE);
}


//-----------------------------------------------------------------------------
void
paged::apply_prot (cruft::view<value_type*> region, int prot)
{
    if (!covers (m_data, region))
        throw std::out_of_range ("invalid commit region");

    // bump the request up to page aligned
    static_assert (sizeof (value_type) == 1);
    auto const alignment = memory::pagesize ();
    auto const first = align::down (region.begin (), alignment);
    auto const last  = align::up   (region.end   (), alignment);

    if (MAP_FAILED == mmap (first, last - first, prot,
                            MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS,
                            -1, 0))
        posix::error::throw_code ();
}