Neat code: single_include/mio.hpp

Refactor everything code
This commit is contained in:
Twilight-Dream-Of-Magical 2025-03-31 11:20:11 +08:00 committed by Twilight-Dream-Of-Magic
parent f05574baf6
commit e8c2433fe0
No known key found for this signature in database
GPG Key ID: 9360D8EFFE22143C
17 changed files with 2136 additions and 4254 deletions

View File

@ -1,197 +1,53 @@
cmake_minimum_required(VERSION 3.8)
cmake_minimum_required(VERSION 3.26)
project(mio LANGUAGES CXX)
#
# Here we check whether mio is being configured in isolation or as a component
# of a larger project. To do so, we query whether the `PROJECT_NAME` CMake
# variable has been defined. In the case it has, we can conclude mio is a
# subproject.
#
# This convention has been borrowed from the Catch C++ unit testing library.
#
if(DEFINED PROJECT_NAME)
set(subproject ON)
if(NOT DEFINED INSTALL_SUBPROJECTS)
set(INSTALL_SUBPROJECTS ON CACHE BOOL "Install subproject dependencies")
endif()
else()
set(subproject OFF)
set_property(GLOBAL PROPERTY USE_FOLDERS ON)
endif()
project(mio VERSION 1.1.0 LANGUAGES C CXX)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
include(CMakeDependentOption)
include(CMakePackageConfigHelpers)
include(CTest)
include(GNUInstallDirs)
# Generate 'compile_commands.json' for clang_complete
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
#
# mio requires C++ 11 support, at a minimum. The `CMAKE_CXX_STANDARD` variable
# is referenced when a target is created to initialize the CXX_STANDARD target
# property.
#
# ** NOTE **
# This is a directory scope variable. This has several implicitations.
#
# 1. It DOES NOT propegate to parent scopes (such as parent projects)
# 2. It hides cache variables of the same name for this directory and below
#
set(CMAKE_CXX_STANDARD 11)
# Set C++20 as the minimum required standard
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
#
# The `mio.testing` options only appear as cmake-gui and ccmake options iff
# mio is the highest level project. In the case that mio is a subproject, these
# options are hidden from the user interface and set to `OFF`
#
# Iff mio is the highest level project, this option is defaulted to the value
# of the traditional course grain testing option `BUILD_TESTING` established by
# the CTest module
#
CMAKE_DEPENDENT_OPTION(mio.tests
"Build the mio tests and integrate with ctest"
ON "BUILD_TESTING; NOT subproject" OFF)
# Enable CTest support if BUILD_TESTING is enabled
include(CTest)
if(BUILD_TESTING)
enable_testing()
endif()
#
# On Windows, so as to be a "good citizen", mio offers two mechanisms to control
# the imported surface area of the Windows API. The default `mio` target sets
# the necessary flags for a minimal Win API (`WIN32_LEAN_AND_MEAN`, etc.) on
# linking targets. This is, in our view, the conservative behavior.
#
# However, an option is published in the cache allowing client to opt out of
# these compiler definintions. This preference will persist in the installed
# cmake configuration file, but can be modified by downstream users by way of
# the same cmake cache variable. This allows intermediate consumers (e.g. other
# libraries) to defer this decision making to downstream clients.
#
# Beyond the option-based mechanism, two additional targets,
# mio::mio_min_winapi and mio::mio_full_winapi, are specified below for those
# that expressly requiring either the minimal or full windows API, respectively.
#
CMAKE_DEPENDENT_OPTION(mio.windows.full_api
"Configure mio without WIN32_LEAN_AND_MEAN and NOMINMAX definitions"
OFF "WIN32" ON)
#
# When the end user is consuming mio as a nested subproject, an option
# is provided such that the user may exlude mio from the set of installed
# cmake projects. This accomodates end users building executables or
# compiled libraries which privately link to mio, but wish to only ship their
# artifacts in an installation
#
CMAKE_DEPENDENT_OPTION(mio.installation
"Include mio in the install set"
"${INSTALL_SUBPROJECTS}" "subproject" ON)
mark_as_advanced(mio.installation)
#
# mio has no compiled components. As such, we declare it as an `INTERFACE`
# library, which denotes a collection of target properties to be applied
# transitively to linking targets. In our case, this amounts to an include
# directory and (potentially) some preprocessor definitions.
#
# Create an interface library "mio" to provide the include directory.
# It is assumed that the headers are located in "single_include/mio".
add_library(mio INTERFACE)
add_library(mio::mio ALIAS mio)
target_include_directories(mio INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/single_include)
#
# The include directory for mio can be expected to vary between build
# and installaion. Here we use a CMake generator expression to dispatch
# on how the configuration under which this library is being consumed.
#
# We define the generator expression as a variable, such that the logic
# need not be repeated when populating source file paths.
#
string(CONCAT prefix
"$<BUILD_INTERFACE:${CMAKE_CURRENT_LIST_DIR}/include>"
"$<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}>")
target_include_directories(mio INTERFACE ${prefix})
if(NOT mio.windows.full_api)
target_compile_definitions(mio INTERFACE
$<BUILD_INTERFACE:WIN32_LEAN_AND_MEAN>
$<BUILD_INTERFACE:NOMINMAX>)
# Add a basic test executable that is built on all platforms.
# Note that the test source file is assumed to be located at "test/test.cpp".
add_executable(mio.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp)
target_link_libraries(mio.test PRIVATE mio)
if(BUILD_TESTING)
add_test(NAME mio.test COMMAND mio.test)
endif()
# Windows-specific targets (only built on Windows)
if(WIN32)
add_library(mio_full_winapi INTERFACE)
add_library(mio::mio_full_winapi ALIAS mio_full_winapi)
target_include_directories(mio_full_winapi INTERFACE ${prefix})
add_library(mio_min_winapi INTERFACE)
add_library(mio::mio_min_winapi ALIAS mio_full_winapi)
target_compile_definitions(mio INTERFACE WIN32_LEAN_AND_MEAN NOMINMAX)
target_include_directories(mio_min_winapi INTERFACE ${prefix})
endif()
#
# In order to collect mio's header files in IDE tools such as XCode or Visual
# Studio, there must exist a target adding any such header files as source files.
#
# Given mio is an interface target, source files may only be added with the
# INTERFACE keyword, which consequently propegate to linking targets. This
# behavior isn't desirable to all clients.
#
# To accomodate, a second target is declared which collects the header files,
# which links to the primary mio target. As such, the header files are available
# in IDE tools.
#
add_library(mio-headers INTERFACE)
add_library(mio::mio-headers ALIAS mio-headers)
target_link_libraries(mio-headers INTERFACE mio)
add_subdirectory(include/mio)
if(mio.tests)
add_subdirectory(test)
endif()
if(mio.installation)
#
# Non-testing header files (preserving relative paths) are installed to the
# `include` subdirectory of the `$INSTALL_DIR/${CMAKE_INSTALL_PREFIX}`
# directory. Source file permissions preserved.
#
install(DIRECTORY include/
DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}"
FILES_MATCHING PATTERN "*.*pp")
#
# As a header-only library, there are no target components to be installed
# directly (the PUBLIC_HEADER property is not white listed for INTERFACE
# targets for some reason).
#
# However, it is worthwhile export our target description in order to later
# generate a CMake configuration file for consumption by CMake's `find_package`
# intrinsic
#
install(TARGETS mio mio-headers EXPORT mioTargets)
if(WIN32)
install(TARGETS mio_full_winapi mio_min_winapi EXPORT mioTargets)
# Unicode version: add the UNICODE compile definition.
add_executable(mio.unicode.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp)
target_link_libraries(mio.unicode.test PRIVATE mio)
target_compile_definitions(mio.unicode.test PRIVATE UNICODE)
if(BUILD_TESTING)
add_test(NAME mio.unicode.test COMMAND mio.unicode.test)
endif()
install(EXPORT mioTargets
FILE mio-targets.cmake
NAMESPACE mio::
DESTINATION share/cmake/mio)
# Full WinAPI version (adjust additional settings as needed).
add_executable(mio.fullwinapi.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp)
target_link_libraries(mio.fullwinapi.test PRIVATE mio)
if(BUILD_TESTING)
add_test(NAME mio.fullwinapi.test COMMAND mio.fullwinapi.test)
endif()
write_basic_package_version_file("mio-config-version.cmake"
VERSION ${PROJECT_VERSION}
COMPATIBILITY SameMajorVersion)
configure_file(
"${PROJECT_SOURCE_DIR}/cmake/mio-config.cmake.in"
"${PROJECT_BINARY_DIR}/mio-config.cmake"
@ONLY)
install(FILES
"${PROJECT_BINARY_DIR}/mio-config-version.cmake"
"${PROJECT_BINARY_DIR}/mio-config.cmake"
DESTINATION share/cmake/mio)
# Minimal WinAPI version: minimal Windows API configuration.
add_executable(mio.minwinapi.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp)
target_link_libraries(mio.minwinapi.test PRIVATE mio)
if(BUILD_TESTING)
add_test(NAME mio.minwinapi.test COMMAND mio.minwinapi.test)
endif()
endif()
#
# Rudimentary CPack support.
@ -209,15 +65,14 @@ if(mio.installation)
#
# See `cpack --help` or the CPack documentation for more information.
#
if(NOT subproject)
set(CPACK_PACKAGE_VENDOR "mandreyel")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
"Cross-platform C++11 header-only library for memory mapped file IO")
set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/mandreyel/mio")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
include(CPack)
endif()
if(NOT subproject)
set(CPACK_PACKAGE_VENDOR "mandreyel")
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
"Cross-platform C++20 header-only library for memory mapped file IO")
set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/Twilight-Dream-Of-Magic/mio")
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
include(CPack)
endif()

View File

@ -1,3 +0,0 @@
set(CTEST_CUSTOM_COVERAGE_EXCLUDE
".*test.*"
".*c[+][+].*")

View File

@ -1,13 +0,0 @@
include(CMakeDependentOption)
CMAKE_DEPENDENT_OPTION(mio.windows.full_api
"Configure mio without WIN32_LEAN_AND_MEAN and NOMINMAX definitions"
@mio.windows.full_api@ "WIN32" ON)
include("${CMAKE_CURRENT_LIST_DIR}/mio-targets.cmake")
if(NOT mio.windows.full_api)
set_property(TARGET mio::mio APPEND PROPERTY INTERFACE_COMPILE_DEFINITIONS
WIN32_LEAN_AND_MEAN
NOMINMAX)
endif()

View File

@ -1,11 +0,0 @@
#
# While not strictly necessary to specify header files as target sources,
# doing so populates these files in the source listing when CMake is used
# to generate XCode and Visual Studios projects
#
target_sources(mio-headers INTERFACE
"${prefix}/mio/mmap.hpp"
"${prefix}/mio/page.hpp"
"${prefix}/mio/shared_mmap.hpp")
add_subdirectory(detail)

View File

@ -1,3 +0,0 @@
target_sources(mio-headers INTERFACE
"${prefix}/mio/detail/mmap.ipp"
"${prefix}/mio/detail/string_util.hpp")

View File

@ -1,530 +0,0 @@
/* Copyright 2017 https://github.com/mandreyel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MIO_BASIC_MMAP_IMPL
#define MIO_BASIC_MMAP_IMPL
#include "mio/mmap.hpp"
#include "mio/page.hpp"
#include "mio/detail/string_util.hpp"
#include <algorithm>
#ifndef _WIN32
# include <unistd.h>
# include <fcntl.h>
# include <sys/mman.h>
# include <sys/stat.h>
#endif
namespace mio {
namespace detail {
#ifdef _WIN32
namespace win {
/** Returns the 4 upper bytes of an 8-byte integer. */
inline DWORD int64_high(int64_t n) noexcept
{
return n >> 32;
}
/** Returns the 4 lower bytes of an 8-byte integer. */
inline DWORD int64_low(int64_t n) noexcept
{
return n & 0xffffffff;
}
std::wstring s_2_ws(const std::string& s)
{
if (s.empty())
return{};
const auto s_length = static_cast<int>(s.length());
auto buf = std::vector<wchar_t>(s_length);
const auto wide_char_count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s_length, buf.data(), s_length);
return std::wstring(buf.data(), wide_char_count);
}
template<
typename String,
typename = typename std::enable_if<
std::is_same<typename char_type<String>::type, char>::value
>::type
> file_handle_type open_file_helper(const String& path, const access_mode mode)
{
return ::CreateFileW(s_2_ws(path).c_str(),
mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
}
template<typename String>
typename std::enable_if<
std::is_same<typename char_type<String>::type, wchar_t>::value,
file_handle_type
>::type open_file_helper(const String& path, const access_mode mode)
{
return ::CreateFileW(c_str(path),
mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
}
} // win
#endif // _WIN32
/**
* Returns the last platform specific system error (errno on POSIX and
* GetLastError on Win) as a `std::error_code`.
*/
inline std::error_code last_error() noexcept
{
std::error_code error;
#ifdef _WIN32
error.assign(GetLastError(), std::system_category());
#else
error.assign(errno, std::system_category());
#endif
return error;
}
template<typename String>
file_handle_type open_file(const String& path, const access_mode mode,
std::error_code& error)
{
error.clear();
if(detail::empty(path))
{
error = std::make_error_code(std::errc::invalid_argument);
return invalid_handle;
}
#ifdef _WIN32
const auto handle = win::open_file_helper(path, mode);
#else // POSIX
const auto handle = ::open(c_str(path),
mode == access_mode::read ? O_RDONLY : O_RDWR);
#endif
if(handle == invalid_handle)
{
error = detail::last_error();
}
return handle;
}
inline size_t query_file_size(file_handle_type handle, std::error_code& error)
{
error.clear();
#ifdef _WIN32
LARGE_INTEGER file_size;
if(::GetFileSizeEx(handle, &file_size) == 0)
{
error = detail::last_error();
return 0;
}
return static_cast<int64_t>(file_size.QuadPart);
#else // POSIX
struct stat sbuf;
if(::fstat(handle, &sbuf) == -1)
{
error = detail::last_error();
return 0;
}
return sbuf.st_size;
#endif
}
struct mmap_context
{
char* data;
int64_t length;
int64_t mapped_length;
#ifdef _WIN32
file_handle_type file_mapping_handle;
#endif
};
inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset,
const int64_t length, const access_mode mode, std::error_code& error)
{
const int64_t aligned_offset = make_offset_page_aligned(offset);
const int64_t length_to_map = offset - aligned_offset + length;
#ifdef _WIN32
const int64_t max_file_size = offset + length;
const auto file_mapping_handle = ::CreateFileMapping(
file_handle,
0,
mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE,
win::int64_high(max_file_size),
win::int64_low(max_file_size),
0);
if(file_mapping_handle == invalid_handle)
{
error = detail::last_error();
return {};
}
char* mapping_start = static_cast<char*>(::MapViewOfFile(
file_mapping_handle,
mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE,
win::int64_high(aligned_offset),
win::int64_low(aligned_offset),
length_to_map));
if(mapping_start == nullptr)
{
// Close file handle if mapping it failed.
::CloseHandle(file_mapping_handle);
error = detail::last_error();
return {};
}
#else // POSIX
char* mapping_start = static_cast<char*>(::mmap(
0, // Don't give hint as to where to map.
length_to_map,
mode == access_mode::read ? PROT_READ : PROT_WRITE,
MAP_SHARED,
file_handle,
aligned_offset));
if(mapping_start == MAP_FAILED)
{
error = detail::last_error();
return {};
}
#endif
mmap_context ctx;
ctx.data = mapping_start + offset - aligned_offset;
ctx.length = length;
ctx.mapped_length = length_to_map;
#ifdef _WIN32
ctx.file_mapping_handle = file_mapping_handle;
#endif
return ctx;
}
} // namespace detail
// -- basic_mmap --
template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>::~basic_mmap()
{
conditional_sync();
unmap();
}
template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other)
: data_(std::move(other.data_))
, length_(std::move(other.length_))
, mapped_length_(std::move(other.mapped_length_))
, file_handle_(std::move(other.file_handle_))
#ifdef _WIN32
, file_mapping_handle_(std::move(other.file_mapping_handle_))
#endif
, is_handle_internal_(std::move(other.is_handle_internal_))
{
other.data_ = nullptr;
other.length_ = other.mapped_length_ = 0;
other.file_handle_ = invalid_handle;
#ifdef _WIN32
other.file_mapping_handle_ = invalid_handle;
#endif
}
template<access_mode AccessMode, typename ByteT>
basic_mmap<AccessMode, ByteT>&
basic_mmap<AccessMode, ByteT>::operator=(basic_mmap&& other)
{
if(this != &other)
{
// First the existing mapping needs to be removed.
unmap();
data_ = std::move(other.data_);
length_ = std::move(other.length_);
mapped_length_ = std::move(other.mapped_length_);
file_handle_ = std::move(other.file_handle_);
#ifdef _WIN32
file_mapping_handle_ = std::move(other.file_mapping_handle_);
#endif
is_handle_internal_ = std::move(other.is_handle_internal_);
// The moved from basic_mmap's fields need to be reset, because
// otherwise other's destructor will unmap the same mapping that was
// just moved into this.
other.data_ = nullptr;
other.length_ = other.mapped_length_ = 0;
other.file_handle_ = invalid_handle;
#ifdef _WIN32
other.file_mapping_handle_ = invalid_handle;
#endif
other.is_handle_internal_ = false;
}
return *this;
}
template<access_mode AccessMode, typename ByteT>
typename basic_mmap<AccessMode, ByteT>::handle_type
basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept
{
#ifdef _WIN32
return file_mapping_handle_;
#else
return file_handle_;
#endif
}
template<access_mode AccessMode, typename ByteT>
template<typename String>
void basic_mmap<AccessMode, ByteT>::map(const String& path, const size_type offset,
const size_type length, std::error_code& error)
{
error.clear();
if(detail::empty(path))
{
error = std::make_error_code(std::errc::invalid_argument);
return;
}
const auto handle = detail::open_file(path, AccessMode, error);
if(error)
{
return;
}
map(handle, offset, length, error);
// This MUST be after the call to map, as that sets this to true.
if(!error)
{
is_handle_internal_ = true;
}
}
template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::map(const handle_type handle,
const size_type offset, const size_type length, std::error_code& error)
{
error.clear();
if(handle == invalid_handle)
{
error = std::make_error_code(std::errc::bad_file_descriptor);
return;
}
const auto file_size = detail::query_file_size(handle, error);
if(error)
{
return;
}
if(offset + length > file_size)
{
error = std::make_error_code(std::errc::invalid_argument);
return;
}
const auto ctx = detail::memory_map(handle, offset,
length == map_entire_file ? (file_size - offset) : length,
AccessMode, error);
if(!error)
{
// We must unmap the previous mapping that may have existed prior to this call.
// Note that this must only be invoked after a new mapping has been created in
// order to provide the strong guarantee that, should the new mapping fail, the
// `map` function leaves this instance in a state as though the function had
// never been invoked.
unmap();
file_handle_ = handle;
is_handle_internal_ = false;
data_ = reinterpret_cast<pointer>(ctx.data);
length_ = ctx.length;
mapped_length_ = ctx.mapped_length;
#ifdef _WIN32
file_mapping_handle_ = ctx.file_mapping_handle;
#endif
}
}
template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::write, void>::type
basic_mmap<AccessMode, ByteT>::sync(std::error_code& error)
{
error.clear();
if(!is_open())
{
error = std::make_error_code(std::errc::bad_file_descriptor);
return;
}
if(data())
{
#ifdef _WIN32
if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0
|| ::FlushFileBuffers(file_handle_) == 0)
#else // POSIX
if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0)
#endif
{
error = detail::last_error();
return;
}
}
#ifdef _WIN32
if(::FlushFileBuffers(file_handle_) == 0)
{
error = detail::last_error();
}
#endif
}
template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::unmap()
{
if(!is_open()) { return; }
// TODO do we care about errors here?
#ifdef _WIN32
if(is_mapped())
{
::UnmapViewOfFile(get_mapping_start());
::CloseHandle(file_mapping_handle_);
}
#else // POSIX
if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); }
#endif
// If `file_handle_` was obtained by our opening it (when map is called with
// a path, rather than an existing file handle), we need to close it,
// otherwise it must not be closed as it may still be used outside this
// instance.
if(is_handle_internal_)
{
#ifdef _WIN32
::CloseHandle(file_handle_);
#else // POSIX
::close(file_handle_);
#endif
}
// Reset fields to their default values.
data_ = nullptr;
length_ = mapped_length_ = 0;
file_handle_ = invalid_handle;
#ifdef _WIN32
file_mapping_handle_ = invalid_handle;
#endif
}
template<access_mode AccessMode, typename ByteT>
bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept
{
#ifdef _WIN32
return file_mapping_handle_ != invalid_handle;
#else // POSIX
return is_open();
#endif
}
template<access_mode AccessMode, typename ByteT>
void basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other)
{
if(this != &other)
{
using std::swap;
swap(data_, other.data_);
swap(file_handle_, other.file_handle_);
#ifdef _WIN32
swap(file_mapping_handle_, other.file_mapping_handle_);
#endif
swap(length_, other.length_);
swap(mapped_length_, other.mapped_length_);
swap(is_handle_internal_, other.is_handle_internal_);
}
}
template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::write, void>::type
basic_mmap<AccessMode, ByteT>::conditional_sync()
{
// This is invoked from the destructor, so not much we can do about
// failures here.
std::error_code ec;
sync(ec);
}
template<access_mode AccessMode, typename ByteT>
template<access_mode A>
typename std::enable_if<A == access_mode::read, void>::type
basic_mmap<AccessMode, ByteT>::conditional_sync()
{
// noop
}
template<access_mode AccessMode, typename ByteT>
bool operator==(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{
return a.data() == b.data()
&& a.size() == b.size();
}
template<access_mode AccessMode, typename ByteT>
bool operator!=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{
return !(a == b);
}
template<access_mode AccessMode, typename ByteT>
bool operator<(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{
if(a.data() == b.data()) { return a.size() < b.size(); }
return a.data() < b.data();
}
template<access_mode AccessMode, typename ByteT>
bool operator<=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{
return !(a > b);
}
template<access_mode AccessMode, typename ByteT>
bool operator>(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{
if(a.data() == b.data()) { return a.size() > b.size(); }
return a.data() > b.data();
}
template<access_mode AccessMode, typename ByteT>
bool operator>=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{
return !(a < b);
}
} // namespace mio
#endif // MIO_BASIC_MMAP_IMPL

View File

@ -1,170 +0,0 @@
/* Copyright 2017 https://github.com/mandreyel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MIO_STRING_UTIL_HEADER
#define MIO_STRING_UTIL_HEADER
#include <type_traits>
namespace mio {
namespace detail {
template<
typename S,
typename C = typename std::decay<S>::type,
typename = decltype(std::declval<C>().data()),
typename = typename std::enable_if<
std::is_same<typename C::value_type, char>::value
#ifdef _WIN32
|| std::is_same<typename C::value_type, wchar_t>::value
#endif
>::type
> struct char_type_helper {
using type = typename C::value_type;
};
template<class T>
struct char_type {
using type = typename char_type_helper<T>::type;
};
// TODO: can we avoid this brute force approach?
template<>
struct char_type<char*> {
using type = char;
};
template<>
struct char_type<const char*> {
using type = char;
};
template<size_t N>
struct char_type<char[N]> {
using type = char;
};
template<size_t N>
struct char_type<const char[N]> {
using type = char;
};
#ifdef _WIN32
template<>
struct char_type<wchar_t*> {
using type = wchar_t;
};
template<>
struct char_type<const wchar_t*> {
using type = wchar_t;
};
template<size_t N>
struct char_type<wchar_t[N]> {
using type = wchar_t;
};
template<size_t N>
struct char_type<const wchar_t[N]> {
using type = wchar_t;
};
#endif // _WIN32
template<typename CharT, typename S>
struct is_c_str_helper
{
static constexpr bool value = std::is_same<
CharT*,
// TODO: I'm so sorry for this... Can this be made cleaner?
typename std::add_pointer<
typename std::remove_cv<
typename std::remove_pointer<
typename std::decay<
S
>::type
>::type
>::type
>::type
>::value;
};
template<typename S>
struct is_c_str
{
static constexpr bool value = is_c_str_helper<char, S>::value;
};
#ifdef _WIN32
template<typename S>
struct is_c_wstr
{
static constexpr bool value = is_c_str_helper<wchar_t, S>::value;
};
#endif // _WIN32
template<typename S>
struct is_c_str_or_c_wstr
{
static constexpr bool value = is_c_str<S>::value
#ifdef _WIN32
|| is_c_wstr<S>::value
#endif
;
};
template<
typename String,
typename = decltype(std::declval<String>().data()),
typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type
> const typename char_type<String>::type* c_str(const String& path)
{
return path.data();
}
template<
typename String,
typename = decltype(std::declval<String>().empty()),
typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type
> bool empty(const String& path)
{
return path.empty();
}
template<
typename String,
typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type
> const typename char_type<String>::type* c_str(String path)
{
return path;
}
template<
typename String,
typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type
> bool empty(String path)
{
return !path || (*path == 0);
}
} // namespace detail
} // namespace mio
#endif // MIO_STRING_UTIL_HEADER

View File

@ -1,492 +0,0 @@
/* Copyright 2017 https://github.com/mandreyel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MIO_MMAP_HEADER
#define MIO_MMAP_HEADER
#include "mio/page.hpp"
#include <iterator>
#include <string>
#include <system_error>
#include <cstdint>
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif // WIN32_LEAN_AND_MEAN
# include <windows.h>
#else // ifdef _WIN32
# define INVALID_HANDLE_VALUE -1
#endif // ifdef _WIN32
namespace mio {
// This value may be provided as the `length` parameter to the constructor or
// `map`, in which case a memory mapping of the entire file is created.
enum { map_entire_file = 0 };
#ifdef _WIN32
using file_handle_type = HANDLE;
#else
using file_handle_type = int;
#endif
// This value represents an invalid file handle type. This can be used to
// determine whether `basic_mmap::file_handle` is valid, for example.
const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE;
template<access_mode AccessMode, typename ByteT>
struct basic_mmap
{
using value_type = ByteT;
using size_type = size_t;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using difference_type = std::ptrdiff_t;
using iterator = pointer;
using const_iterator = const_pointer;
using reverse_iterator = std::reverse_iterator<iterator>;
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using iterator_category = std::random_access_iterator_tag;
using handle_type = file_handle_type;
static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char.");
private:
// Points to the first requested byte, and not to the actual start of the mapping.
pointer data_ = nullptr;
// Length--in bytes--requested by user (which may not be the length of the
// full mapping) and the length of the full mapping.
size_type length_ = 0;
size_type mapped_length_ = 0;
// Letting user map a file using both an existing file handle and a path
// introcudes some complexity (see `is_handle_internal_`).
// On POSIX, we only need a file handle to create a mapping, while on
// Windows systems the file handle is necessary to retrieve a file mapping
// handle, but any subsequent operations on the mapped region must be done
// through the latter.
handle_type file_handle_ = INVALID_HANDLE_VALUE;
#ifdef _WIN32
handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE;
#endif
// Letting user map a file using both an existing file handle and a path
// introcudes some complexity in that we must not close the file handle if
// user provided it, but we must close it if we obtained it using the
// provided path. For this reason, this flag is used to determine when to
// close `file_handle_`.
bool is_handle_internal_;
public:
/**
* The default constructed mmap object is in a non-mapped state, that is,
* any operation that attempts to access nonexistent underlying data will
* result in undefined behaviour/segmentation faults.
*/
basic_mmap() = default;
#ifdef __cpp_exceptions
/**
* The same as invoking the `map` function, except any error that may occur
* while establishing the mapping is wrapped in a `std::system_error` and is
* thrown.
*/
template<typename String>
basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file)
{
std::error_code error;
map(path, offset, length, error);
if(error) { throw std::system_error(error); }
}
/**
* The same as invoking the `map` function, except any error that may occur
* while establishing the mapping is wrapped in a `std::system_error` and is
* thrown.
*/
basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file)
{
std::error_code error;
map(handle, offset, length, error);
if(error) { throw std::system_error(error); }
}
#endif // __cpp_exceptions
/**
* `basic_mmap` has single-ownership semantics, so transferring ownership
* may only be accomplished by moving the object.
*/
basic_mmap(const basic_mmap&) = delete;
basic_mmap(basic_mmap&&);
basic_mmap& operator=(const basic_mmap&) = delete;
basic_mmap& operator=(basic_mmap&&);
/**
* If this is a read-write mapping, the destructor invokes sync. Regardless
* of the access mode, unmap is invoked as a final step.
*/
~basic_mmap();
/**
* On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
* however, a mapped region of a file gets its own handle, which is returned by
* 'mapping_handle'.
*/
handle_type file_handle() const noexcept { return file_handle_; }
handle_type mapping_handle() const noexcept;
/** Returns whether a valid memory mapping has been created. */
bool is_open() const noexcept { return file_handle_ != invalid_handle; }
/**
* Returns true if no mapping was established, that is, conceptually the
* same as though the length that was mapped was 0. This function is
* provided so that this class has Container semantics.
*/
bool empty() const noexcept { return length() == 0; }
/** Returns true if a mapping was established. */
bool is_mapped() const noexcept;
/**
* `size` and `length` both return the logical length, i.e. the number of bytes
* user requested to be mapped, while `mapped_length` returns the actual number of
* bytes that were mapped which is a multiple of the underlying operating system's
* page allocation granularity.
*/
size_type size() const noexcept { return length(); }
size_type length() const noexcept { return length_; }
size_type mapped_length() const noexcept { return mapped_length_; }
/** Returns the offset relative to the start of the mapping. */
size_type mapping_offset() const noexcept
{
return mapped_length_ - length_;
}
/**
* Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
* exists.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> pointer data() noexcept { return data_; }
const_pointer data() const noexcept { return data_; }
/**
* Returns an iterator to the first requested byte, if a valid memory mapping
* exists, otherwise this function call is undefined behaviour.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> iterator begin() noexcept { return data(); }
const_iterator begin() const noexcept { return data(); }
const_iterator cbegin() const noexcept { return data(); }
/**
* Returns an iterator one past the last requested byte, if a valid memory mapping
* exists, otherwise this function call is undefined behaviour.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> iterator end() noexcept { return data() + length(); }
const_iterator end() const noexcept { return data() + length(); }
const_iterator cend() const noexcept { return data() + length(); }
/**
* Returns a reverse iterator to the last memory mapped byte, if a valid
* memory mapping exists, otherwise this function call is undefined
* behaviour.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const noexcept
{ return const_reverse_iterator(end()); }
const_reverse_iterator crbegin() const noexcept
{ return const_reverse_iterator(end()); }
/**
* Returns a reverse iterator past the first mapped byte, if a valid memory
* mapping exists, otherwise this function call is undefined behaviour.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
const_reverse_iterator rend() const noexcept
{ return const_reverse_iterator(begin()); }
const_reverse_iterator crend() const noexcept
{ return const_reverse_iterator(begin()); }
/**
* Returns a reference to the `i`th byte from the first requested byte (as returned
* by `data`). If this is invoked when no valid memory mapping has been created
* prior to this call, undefined behaviour ensues.
*/
reference operator[](const size_type i) noexcept { return data_[i]; }
const_reference operator[](const size_type i) const noexcept { return data_[i]; }
/**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
* reason is reported via `error` and the object remains in a state as if this
* function hadn't been called.
*
* `path`, which must be a path to an existing file, is used to retrieve a file
* handle (which is closed when the object destructs or `unmap` is called), which is
* then used to memory map the requested region. Upon failure, `error` is set to
* indicate the reason and the object remains in an unmapped state.
*
* `offset` is the number of bytes, relative to the start of the file, where the
* mapping should begin. When specifying it, there is no need to worry about
* providing a value that is aligned with the operating system's page allocation
* granularity. This is adjusted by the implementation such that the first requested
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
* `offset` from the start of the file.
*
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
* case a mapping of the entire file is created.
*/
template<typename String>
void map(const String& path, const size_type offset,
const size_type length, std::error_code& error);
/**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
* reason is reported via `error` and the object remains in a state as if this
* function hadn't been called.
*
* `path`, which must be a path to an existing file, is used to retrieve a file
* handle (which is closed when the object destructs or `unmap` is called), which is
* then used to memory map the requested region. Upon failure, `error` is set to
* indicate the reason and the object remains in an unmapped state.
*
* The entire file is mapped.
*/
template<typename String>
void map(const String& path, std::error_code& error)
{
map(path, 0, map_entire_file, error);
}
/**
* Establishes a memory mapping with AccessMode. If the mapping is
* unsuccesful, the reason is reported via `error` and the object remains in
* a state as if this function hadn't been called.
*
* `handle`, which must be a valid file handle, which is used to memory map the
* requested region. Upon failure, `error` is set to indicate the reason and the
* object remains in an unmapped state.
*
* `offset` is the number of bytes, relative to the start of the file, where the
* mapping should begin. When specifying it, there is no need to worry about
* providing a value that is aligned with the operating system's page allocation
* granularity. This is adjusted by the implementation such that the first requested
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
* `offset` from the start of the file.
*
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
* case a mapping of the entire file is created.
*/
void map(const handle_type handle, const size_type offset,
const size_type length, std::error_code& error);
/**
* Establishes a memory mapping with AccessMode. If the mapping is
* unsuccesful, the reason is reported via `error` and the object remains in
* a state as if this function hadn't been called.
*
* `handle`, which must be a valid file handle, which is used to memory map the
* requested region. Upon failure, `error` is set to indicate the reason and the
* object remains in an unmapped state.
*
* The entire file is mapped.
*/
void map(const handle_type handle, std::error_code& error)
{
map(handle, 0, map_entire_file, error);
}
/**
* If a valid memory mapping has been created prior to this call, this call
* instructs the kernel to unmap the memory region and disassociate this object
* from the file.
*
* The file handle associated with the file that is mapped is only closed if the
* mapping was created using a file path. If, on the other hand, an existing
* file handle was used to create the mapping, the file handle is not closed.
*/
void unmap();
void swap(basic_mmap& other);
/** Flushes the memory mapped page to disk. Errors are reported via `error`. */
template<access_mode A = AccessMode>
typename std::enable_if<A == access_mode::write, void>::type
sync(std::error_code& error);
/**
* All operators compare the address of the first byte and size of the two mapped
* regions.
*/
private:
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> pointer get_mapping_start() noexcept
{
return !data() ? nullptr : data() - mapping_offset();
}
const_pointer get_mapping_start() const noexcept
{
return !data() ? nullptr : data() - mapping_offset();
}
/**
* The destructor syncs changes to disk if `AccessMode` is `write`, but not
* if it's `read`, but since the destructor cannot be templated, we need to
* do SFINAE in a dedicated function, where one syncs and the other is a noop.
*/
template<access_mode A = AccessMode>
typename std::enable_if<A == access_mode::write, void>::type
conditional_sync();
template<access_mode A = AccessMode>
typename std::enable_if<A == access_mode::read, void>::type conditional_sync();
};
template<access_mode AccessMode, typename ByteT>
bool operator==(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b);
template<access_mode AccessMode, typename ByteT>
bool operator!=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b);
template<access_mode AccessMode, typename ByteT>
bool operator<(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b);
template<access_mode AccessMode, typename ByteT>
bool operator<=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b);
template<access_mode AccessMode, typename ByteT>
bool operator>(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b);
template<access_mode AccessMode, typename ByteT>
bool operator>=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b);
/**
* This is the basis for all read-only mmap objects and should be preferred over
* directly using `basic_mmap`.
*/
template<typename ByteT>
using basic_mmap_source = basic_mmap<access_mode::read, ByteT>;
/**
* This is the basis for all read-write mmap objects and should be preferred over
* directly using `basic_mmap`.
*/
template<typename ByteT>
using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>;
/**
* These aliases cover the most common use cases, both representing a raw byte stream
* (either with a char or an unsigned char/uint8_t).
*/
using mmap_source = basic_mmap_source<char>;
using ummap_source = basic_mmap_source<unsigned char>;
using mmap_sink = basic_mmap_sink<char>;
using ummap_sink = basic_mmap_sink<unsigned char>;
/**
* Convenience factory method that constructs a mapping for any `basic_mmap` or
* `basic_mmap` type.
*/
template<
typename MMap,
typename MappingToken
> MMap make_mmap(const MappingToken& token,
int64_t offset, int64_t length, std::error_code& error)
{
MMap mmap;
mmap.map(token, offset, length, error);
return mmap;
}
/**
* Convenience factory method.
*
* MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
* `std::filesystem::path`, `std::vector<char>`, or similar), or a
* `mmap_source::handle_type`.
*/
template<typename MappingToken>
mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset,
mmap_source::size_type length, std::error_code& error)
{
return make_mmap<mmap_source>(token, offset, length, error);
}
template<typename MappingToken>
mmap_source make_mmap_source(const MappingToken& token, std::error_code& error)
{
return make_mmap_source(token, 0, map_entire_file, error);
}
/**
* Convenience factory method.
*
* MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
* `std::filesystem::path`, `std::vector<char>`, or similar), or a
* `mmap_sink::handle_type`.
*/
template<typename MappingToken>
mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset,
mmap_sink::size_type length, std::error_code& error)
{
return make_mmap<mmap_sink>(token, offset, length, error);
}
template<typename MappingToken>
mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error)
{
return make_mmap_sink(token, 0, map_entire_file, error);
}
} // namespace mio
#include "detail/mmap.ipp"
#endif // MIO_MMAP_HEADER

View File

@ -1,78 +0,0 @@
/* Copyright 2017 https://github.com/mandreyel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MIO_PAGE_HEADER
#define MIO_PAGE_HEADER
#ifdef _WIN32
# include <windows.h>
#else
# include <unistd.h>
#endif
namespace mio {
/**
* This is used by `basic_mmap` to determine whether to create a read-only or
* a read-write memory mapping.
*/
enum class access_mode
{
read,
write
};
/**
* Determines the operating system's page allocation granularity.
*
* On the first call to this function, it invokes the operating system specific syscall
* to determine the page size, caches the value, and returns it. Any subsequent call to
* this function serves the cached value, so no further syscalls are made.
*/
inline size_t page_size()
{
static const size_t page_size = []
{
#ifdef _WIN32
SYSTEM_INFO SystemInfo;
GetSystemInfo(&SystemInfo);
return SystemInfo.dwAllocationGranularity;
#else
return sysconf(_SC_PAGE_SIZE);
#endif
}();
return page_size;
}
/**
* Alligns `offset` to the operating's system page size such that it subtracts the
* difference until the nearest page boundary before `offset`, or does nothing if
* `offset` is already page aligned.
*/
inline size_t make_offset_page_aligned(size_t offset) noexcept
{
const size_t page_size_ = page_size();
// Use integer division to round down to the nearest page alignment.
return offset / page_size_ * page_size_;
}
} // namespace mio
#endif // MIO_PAGE_HEADER

View File

@ -1,406 +0,0 @@
/* Copyright 2017 https://github.com/mandreyel
*
* Permission is hereby granted, free of charge, to any person obtaining a copy of this
* software and associated documentation files (the "Software"), to deal in the Software
* without restriction, including without limitation the rights to use, copy, modify,
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be included in all copies
* or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#ifndef MIO_SHARED_MMAP_HEADER
#define MIO_SHARED_MMAP_HEADER
#include "mio/mmap.hpp"
#include <system_error> // std::error_code
#include <memory> // std::shared_ptr
namespace mio {
/**
* Exposes (nearly) the same interface as `basic_mmap`, but endowes it with
* `std::shared_ptr` semantics.
*
* This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if
* shared semantics are not required.
*/
template<
access_mode AccessMode,
typename ByteT
> class basic_shared_mmap
{
using impl_type = basic_mmap<AccessMode, ByteT>;
std::shared_ptr<impl_type> pimpl_;
public:
using value_type = typename impl_type::value_type;
using size_type = typename impl_type::size_type;
using reference = typename impl_type::reference;
using const_reference = typename impl_type::const_reference;
using pointer = typename impl_type::pointer;
using const_pointer = typename impl_type::const_pointer;
using difference_type = typename impl_type::difference_type;
using iterator = typename impl_type::iterator;
using const_iterator = typename impl_type::const_iterator;
using reverse_iterator = typename impl_type::reverse_iterator;
using const_reverse_iterator = typename impl_type::const_reverse_iterator;
using iterator_category = typename impl_type::iterator_category;
using handle_type = typename impl_type::handle_type;
using mmap_type = impl_type;
basic_shared_mmap() = default;
basic_shared_mmap(const basic_shared_mmap&) = default;
basic_shared_mmap& operator=(const basic_shared_mmap&) = default;
basic_shared_mmap(basic_shared_mmap&&) = default;
basic_shared_mmap& operator=(basic_shared_mmap&&) = default;
/** Takes ownership of an existing mmap object. */
basic_shared_mmap(mmap_type&& mmap)
: pimpl_(std::make_shared<mmap_type>(std::move(mmap)))
{}
/** Takes ownership of an existing mmap object. */
basic_shared_mmap& operator=(mmap_type&& mmap)
{
pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
return *this;
}
/** Initializes this object with an already established shared mmap. */
basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(std::move(mmap)) {}
/** Initializes this object with an already established shared mmap. */
basic_shared_mmap& operator=(std::shared_ptr<mmap_type> mmap)
{
pimpl_ = std::move(mmap);
return *this;
}
#ifdef __cpp_exceptions
/**
* The same as invoking the `map` function, except any error that may occur
* while establishing the mapping is wrapped in a `std::system_error` and is
* thrown.
*/
template<typename String>
basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file)
{
std::error_code error;
map(path, offset, length, error);
if(error) { throw std::system_error(error); }
}
/**
* The same as invoking the `map` function, except any error that may occur
* while establishing the mapping is wrapped in a `std::system_error` and is
* thrown.
*/
basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file)
{
std::error_code error;
map(handle, offset, length, error);
if(error) { throw std::system_error(error); }
}
#endif // __cpp_exceptions
/**
* If this is a read-write mapping and the last reference to the mapping,
* the destructor invokes sync. Regardless of the access mode, unmap is
* invoked as a final step.
*/
~basic_shared_mmap() = default;
/** Returns the underlying `std::shared_ptr` instance that holds the mmap. */
std::shared_ptr<mmap_type> get_shared_ptr() { return pimpl_; }
/**
* On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
* however, a mapped region of a file gets its own handle, which is returned by
* 'mapping_handle'.
*/
handle_type file_handle() const noexcept
{
return pimpl_ ? pimpl_->file_handle() : invalid_handle;
}
handle_type mapping_handle() const noexcept
{
return pimpl_ ? pimpl_->mapping_handle() : invalid_handle;
}
/** Returns whether a valid memory mapping has been created. */
bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); }
/**
* Returns true if no mapping was established, that is, conceptually the
* same as though the length that was mapped was 0. This function is
* provided so that this class has Container semantics.
*/
bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); }
/**
* `size` and `length` both return the logical length, i.e. the number of bytes
* user requested to be mapped, while `mapped_length` returns the actual number of
* bytes that were mapped which is a multiple of the underlying operating system's
* page allocation granularity.
*/
size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; }
size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; }
size_type mapped_length() const noexcept
{
return pimpl_ ? pimpl_->mapped_length() : 0;
}
/**
* Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
* exists.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> pointer data() noexcept { return pimpl_->data(); }
const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; }
/**
* Returns an iterator to the first requested byte, if a valid memory mapping
* exists, otherwise this function call is undefined behaviour.
*/
iterator begin() noexcept { return pimpl_->begin(); }
const_iterator begin() const noexcept { return pimpl_->begin(); }
const_iterator cbegin() const noexcept { return pimpl_->cbegin(); }
/**
* Returns an iterator one past the last requested byte, if a valid memory mapping
* exists, otherwise this function call is undefined behaviour.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> iterator end() noexcept { return pimpl_->end(); }
const_iterator end() const noexcept { return pimpl_->end(); }
const_iterator cend() const noexcept { return pimpl_->cend(); }
/**
* Returns a reverse iterator to the last memory mapped byte, if a valid
* memory mapping exists, otherwise this function call is undefined
* behaviour.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); }
const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); }
const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); }
/**
* Returns a reverse iterator past the first mapped byte, if a valid memory
* mapping exists, otherwise this function call is undefined behaviour.
*/
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> reverse_iterator rend() noexcept { return pimpl_->rend(); }
const_reverse_iterator rend() const noexcept { return pimpl_->rend(); }
const_reverse_iterator crend() const noexcept { return pimpl_->crend(); }
/**
* Returns a reference to the `i`th byte from the first requested byte (as returned
* by `data`). If this is invoked when no valid memory mapping has been created
* prior to this call, undefined behaviour ensues.
*/
reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; }
const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; }
/**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
* reason is reported via `error` and the object remains in a state as if this
* function hadn't been called.
*
* `path`, which must be a path to an existing file, is used to retrieve a file
* handle (which is closed when the object destructs or `unmap` is called), which is
* then used to memory map the requested region. Upon failure, `error` is set to
* indicate the reason and the object remains in an unmapped state.
*
* `offset` is the number of bytes, relative to the start of the file, where the
* mapping should begin. When specifying it, there is no need to worry about
* providing a value that is aligned with the operating system's page allocation
* granularity. This is adjusted by the implementation such that the first requested
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
* `offset` from the start of the file.
*
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
* case a mapping of the entire file is created.
*/
template<typename String>
void map(const String& path, const size_type offset,
const size_type length, std::error_code& error)
{
map_impl(path, offset, length, error);
}
/**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
* reason is reported via `error` and the object remains in a state as if this
* function hadn't been called.
*
* `path`, which must be a path to an existing file, is used to retrieve a file
* handle (which is closed when the object destructs or `unmap` is called), which is
* then used to memory map the requested region. Upon failure, `error` is set to
* indicate the reason and the object remains in an unmapped state.
*
* The entire file is mapped.
*/
template<typename String>
void map(const String& path, std::error_code& error)
{
map_impl(path, 0, map_entire_file, error);
}
/**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
* reason is reported via `error` and the object remains in a state as if this
* function hadn't been called.
*
* `handle`, which must be a valid file handle, which is used to memory map the
* requested region. Upon failure, `error` is set to indicate the reason and the
* object remains in an unmapped state.
*
* `offset` is the number of bytes, relative to the start of the file, where the
* mapping should begin. When specifying it, there is no need to worry about
* providing a value that is aligned with the operating system's page allocation
* granularity. This is adjusted by the implementation such that the first requested
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at
* `offset` from the start of the file.
*
* `length` is the number of bytes to map. It may be `map_entire_file`, in which
* case a mapping of the entire file is created.
*/
void map(const handle_type handle, const size_type offset,
const size_type length, std::error_code& error)
{
map_impl(handle, offset, length, error);
}
/**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
* reason is reported via `error` and the object remains in a state as if this
* function hadn't been called.
*
* `handle`, which must be a valid file handle, which is used to memory map the
* requested region. Upon failure, `error` is set to indicate the reason and the
* object remains in an unmapped state.
*
* The entire file is mapped.
*/
void map(const handle_type handle, std::error_code& error)
{
map_impl(handle, 0, map_entire_file, error);
}
/**
* If a valid memory mapping has been created prior to this call, this call
* instructs the kernel to unmap the memory region and disassociate this object
* from the file.
*
* The file handle associated with the file that is mapped is only closed if the
* mapping was created using a file path. If, on the other hand, an existing
* file handle was used to create the mapping, the file handle is not closed.
*/
void unmap() { if(pimpl_) pimpl_->unmap(); }
void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); }
/** Flushes the memory mapped page to disk. Errors are reported via `error`. */
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type
> void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); }
/** All operators compare the underlying `basic_mmap`'s addresses. */
friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b)
{
return a.pimpl_ == b.pimpl_;
}
friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b)
{
return !(a == b);
}
friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b)
{
return a.pimpl_ < b.pimpl_;
}
friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b)
{
return a.pimpl_ <= b.pimpl_;
}
friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b)
{
return a.pimpl_ > b.pimpl_;
}
friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b)
{
return a.pimpl_ >= b.pimpl_;
}
private:
template<typename MappingToken>
void map_impl(const MappingToken& token, const size_type offset,
const size_type length, std::error_code& error)
{
if(!pimpl_)
{
mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error);
if(error) { return; }
pimpl_ = std::make_shared<mmap_type>(std::move(mmap));
}
else
{
pimpl_->map(token, offset, length, error);
}
}
};
/**
* This is the basis for all read-only mmap objects and should be preferred over
* directly using basic_shared_mmap.
*/
template<typename ByteT>
using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>;
/**
* This is the basis for all read-write mmap objects and should be preferred over
* directly using basic_shared_mmap.
*/
template<typename ByteT>
using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>;
/**
* These aliases cover the most common use cases, both representing a raw byte stream
* (either with a char or an unsigned char/uint8_t).
*/
using shared_mmap_source = basic_shared_mmap_source<char>;
using shared_ummap_source = basic_shared_mmap_source<unsigned char>;
using shared_mmap_sink = basic_shared_mmap_sink<char>;
using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>;
} // namespace mio
#endif // MIO_SHARED_MMAP_HEADER

File diff suppressed because it is too large Load Diff

View File

@ -1,25 +0,0 @@
configure_file(
"${PROJECT_SOURCE_DIR}/cmake/CTestCustom.cmake"
"${PROJECT_BINARY_DIR}/CTestCustom.cmake"
COPYONLY)
add_executable(mio.test test.cpp)
target_link_libraries(mio.test PRIVATE mio::mio)
add_test(NAME mio.test COMMAND mio.test)
if(WIN32)
add_executable(mio.unicode.test test.cpp)
target_link_libraries(mio.unicode.test PRIVATE mio::mio)
target_compile_definitions(mio.unicode.test PRIVATE UNICODE)
add_test(NAME mio.unicode.test COMMAND mio.test)
add_executable(mio.fullwinapi.test test.cpp)
target_link_libraries(mio.fullwinapi.test
PRIVATE mio::mio_full_winapi)
add_test(NAME mio.fullwinapi.test COMMAND mio.fullwinapi.test)
add_executable(mio.minwinapi.test test.cpp)
target_link_libraries(mio.minwinapi.test
PRIVATE mio::mio_min_winapi)
add_test(NAME mio.minwinapi.test COMMAND mio.minwinapi.test)
endif()

View File

@ -1,4 +1,3 @@
#include <mio/mmap.hpp>
#include <system_error> // for std::error_code
#include <cstdio> // for std::printf
#include <cassert>

View File

@ -1,182 +1,236 @@
#include <mio/mmap.hpp>
#include <mio/shared_mmap.hpp>
#include <string>
#include <fstream>
#include <cstdlib>
#include <iostream>
#include <cassert>
#include <system_error>
#include <numeric>
#include <vector>
#ifndef _WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#endif
#include "../single_include/mio/mio.hpp"
// Just make sure this compiles.
#ifdef CXX17
# include <cstddef>
using mmap_source = mio::basic_mmap_source<std::byte>;
#endif
template<class MMap>
void test_at_offset(const MMap& file_view, const std::string& buffer,
const size_t offset);
void test_at_offset(const std::string& buffer, const char* path,
const size_t offset, std::error_code& error);
int handle_error(const std::error_code& error);
const size_t offset);
inline void test_at_offset(const std::string& buffer, const char* path,
const size_t offset, std::error_code& error);
inline int handle_error(const std::error_code& error);
inline void allocate_file(const std::string& path, const int size)
{
std::ofstream file(path);
std::string s(size, '0');
file << s;
}
inline int handle_error(const std::error_code& error)
{
const auto& errmsg = error.message();
std::printf("Error mapping file: %s, exiting...\n", errmsg.c_str());
return error.value();
}
inline int test_rewrite_file()
{
const auto path = "test_rewrite.txt";
// NOTE: mio does *not* create the file for you if it doesn't exist! You
// must ensure that the file exists before establishing a mapping. It
// must also be non-empty. So for illustrative purposes the file is
// created now.
allocate_file(path, 204800);
// Read-write memory map the whole file by using `map_entire_file` where the
// length of the mapping is otherwise expected, with the factory method.
std::error_code error;
mio::mmap_sink rw_mmap = mio::make_mmap_sink(
path, 0, mio::map_entire_file, error);
if (error) { return handle_error(error); }
// You can use any iterator based function.
std::fill(rw_mmap.begin(), rw_mmap.end(), 'a');
// Or manually iterate through the mapped region just as if it were any other
// container, and change each byte's value (since this is a read-write mapping).
for (auto& b : rw_mmap)
{
b += 10;
}
// Or just change one value with the subscript operator.
const int answer_index = rw_mmap.size() / 2;
rw_mmap[answer_index] = 42;
// Don't forget to flush changes to disk before unmapping. However, if
// `rw_mmap` were to go out of scope at this point, the destructor would also
// automatically invoke `sync` before `unmap`.
rw_mmap.sync(error);
if (error) { return handle_error(error); }
// We can then remove the mapping, after which rw_mmap will be in a default
// constructed state, i.e. this and the above call to `sync` have the same
// effect as if the destructor had been invoked.
rw_mmap.unmap();
// Now create the same mapping, but in read-only mode. Note that calling the
// overload without the offset and file length parameters maps the entire
// file.
mio::mmap_source ro_mmap;
ro_mmap.map(path, error);
if (error) { return handle_error(error); }
const int the_answer_to_everything = ro_mmap[answer_index];
assert(the_answer_to_everything == 42);
}
int main()
{
std::error_code error;
std::error_code error;
// Make sure mio compiles with non-const char* strings too.
const char _path[] = "test-file";
const int path_len = sizeof(_path);
char* path = new char[path_len];
std::copy(_path, _path + path_len, path);
// Make sure mio compiles with non-const char* strings too.
const char _path[] = "test-file";
const int path_len = sizeof(_path);
char* path = new char[path_len];
std::copy(_path, _path + path_len, path);
const auto page_size = mio::page_size();
// Fill buffer, then write it to file.
const int file_size = 4 * page_size - 250; // 16134, if page size is 4KiB
std::string buffer(file_size, 0);
// Start at first printable ASCII character.
char v = 33;
for (auto& b : buffer) {
b = v;
++v;
// Limit to last printable ASCII character.
v %= 126;
if(v == 0) {
v = 33;
}
}
const auto page_size = mio::page_size();
// Fill buffer, then write it to file.
const int file_size = 4 * page_size - 250; // 16134, if page size is 4KiB
std::string buffer(file_size, 0);
// Start at first printable ASCII character.
char v = 33;
for (auto& b : buffer) {
b = v;
++v;
// Limit to last printable ASCII character.
v %= 126;
if (v == 0) {
v = 33;
}
}
std::ofstream file(path);
file << buffer;
file.close();
std::ofstream file(path);
file << buffer;
file.close();
// Test whole file mapping.
test_at_offset(buffer, path, 0, error);
if (error) { return handle_error(error); }
// Test whole file mapping.
test_at_offset(buffer, path, 0, error);
if (error) { return handle_error(error); }
// Test starting from below the page size.
test_at_offset(buffer, path, page_size - 3, error);
if (error) { return handle_error(error); }
//Test starting from below the page size.
test_at_offset(buffer, path, page_size - 3, error);
if (error) { return handle_error(error); }
// Test starting from above the page size.
test_at_offset(buffer, path, page_size + 3, error);
if (error) { return handle_error(error); }
// Test starting from above the page size.
test_at_offset(buffer, path, page_size + 3, error);
if (error) { return handle_error(error); }
// Test starting from above the page size.
test_at_offset(buffer, path, 2 * page_size + 3, error);
if (error) { return handle_error(error); }
// Test starting from above the page size.
test_at_offset(buffer, path, 2 * page_size + 3, error);
if (error) { return handle_error(error); }
{
{
#define CHECK_INVALID_MMAP(m) do { \
assert(error); \
assert(m.empty()); \
assert(!m.is_open()); \
error.clear(); } while(0)
mio::mmap_source m;
mio::mmap_source m;
// See if mapping an invalid file results in an error.
m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error);
CHECK_INVALID_MMAP(m);
// See if mapping an invalid file results in an error.
m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error);
CHECK_INVALID_MMAP(m);
// Empty path?
m = mio::make_mmap_source(static_cast<const char*>(0), 0, 0, error);
CHECK_INVALID_MMAP(m);
m = mio::make_mmap_source(std::string(), 0, 0, error);
CHECK_INVALID_MMAP(m);
// Empty path?
m = mio::make_mmap_source(static_cast<const char*>(0), 0, 0, error);
CHECK_INVALID_MMAP(m);
m = mio::make_mmap_source(std::string(), 0, 0, error);
CHECK_INVALID_MMAP(m);
// Invalid handle?
m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error);
CHECK_INVALID_MMAP(m);
// Invalid handle?
m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error);
CHECK_INVALID_MMAP(m);
// Invalid offset?
m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error);
CHECK_INVALID_MMAP(m);
}
// Invalid offset?
m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error);
CHECK_INVALID_MMAP(m);
}
// Make sure these compile.
{
mio::ummap_source _1;
mio::shared_ummap_source _2;
// Make sure shared_mmap mapping compiles as all testing was done on
// normal mmaps.
mio::shared_mmap_source _3(path, 0, mio::map_entire_file);
auto _4 = mio::make_mmap_source(path, error);
auto _5 = mio::make_mmap<mio::shared_mmap_source>(path, 0, mio::map_entire_file, error);
// Make sure these compile.
{
mio::ummap_source _1;
mio::shared_ummap_source _2;
// Make sure shared_mmap mapping compiles as all testing was done on
// normal mmaps.
mio::shared_mmap_source _3(path, 0, mio::map_entire_file);
auto _4 = mio::make_mmap_source(path, error);
auto _5 = mio::make_mmap<mio::shared_mmap_source>(path, 0, mio::map_entire_file, error);
#ifdef _WIN32
const wchar_t* wpath1 = L"dasfsf";
auto _6 = mio::make_mmap_source(wpath1, error);
mio::mmap_source _7;
_7.map(wpath1, error);
const std::wstring wpath2 = wpath1;
auto _8 = mio::make_mmap_source(wpath2, error);
mio::mmap_source _9;
_9.map(wpath1, error);
const wchar_t* wpath1 = L"dasfsf";
auto _6 = mio::make_mmap_source(wpath1, error);
mio::mmap_source _7;
_7.map(wpath1, error);
const std::wstring wpath2 = wpath1;
auto _8 = mio::make_mmap_source(wpath2, error);
mio::mmap_source _9;
_9.map(wpath1, error);
#else
const int fd = open(path, O_RDONLY);
mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file);
_fdmmap.unmap();
_fdmmap.map(fd, error);
const int fd = open(path, O_RDONLY);
mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file);
_fdmmap.unmap();
_fdmmap.map(fd, error);
#endif
}
}
std::printf("all tests passed!\n");
if (test_rewrite_file())
throw std::runtime_error("test_rewrite_file failed");
std::printf("all tests passed!\n");
}
void test_at_offset(const std::string& buffer, const char* path,
const size_t offset, std::error_code& error)
const size_t offset, std::error_code& error)
{
// Sanity check.
assert(offset < buffer.size());
// Sanity check.
assert(offset < buffer.size());
// Map the region of the file to which buffer was written.
mio::mmap_source file_view = mio::make_mmap_source(
path, offset, mio::map_entire_file, error);
if(error) { return; }
// Map the region of the file to which buffer was written.
mio::mmap_source file_view = mio::make_mmap_source(
path, offset, mio::map_entire_file, error);
if (error) { return; }
assert(file_view.is_open());
const size_t mapped_size = buffer.size() - offset;
assert(file_view.size() == mapped_size);
assert(file_view.is_open());
const size_t mapped_size = buffer.size() - offset;
assert(file_view.size() == mapped_size);
test_at_offset(file_view, buffer, offset);
test_at_offset(file_view, buffer, offset);
// Turn file_view into a shared mmap.
mio::shared_mmap_source shared_file_view(std::move(file_view));
assert(!file_view.is_open());
assert(shared_file_view.is_open());
assert(shared_file_view.size() == mapped_size);
// Turn file_view into a shared mmap.
mio::shared_mmap_source shared_file_view(std::move(file_view));
assert(!file_view.is_open());
assert(shared_file_view.is_open());
assert(shared_file_view.size() == mapped_size);
//test_at_offset(shared_file_view, buffer, offset);
//test_at_offset(shared_file_view, buffer, offset);
}
template<class MMap>
void test_at_offset(const MMap& file_view, const std::string& buffer,
const size_t offset)
const size_t offset)
{
// Then verify that mmap's bytes correspond to that of buffer.
for(size_t buf_idx = offset, view_idx = 0;
buf_idx < buffer.size() && view_idx < file_view.size();
++buf_idx, ++view_idx) {
if(file_view[view_idx] != buffer[buf_idx]) {
std::printf("%luth byte mismatch: expected(%d) <> actual(%d)",
buf_idx, buffer[buf_idx], file_view[view_idx]);
std::cout << std::flush;
assert(0);
}
}
}
int handle_error(const std::error_code& error)
{
const auto& errmsg = error.message();
std::printf("Error mapping file: %s, exiting...\n", errmsg.c_str());
return error.value();
// Then verify that mmap's bytes correspond to that of buffer.
for (size_t buf_idx = offset, view_idx = 0;
buf_idx < buffer.size() && view_idx < file_view.size();
++buf_idx, ++view_idx) {
if (file_view[view_idx] != buffer[buf_idx]) {
std::printf("%luth byte mismatch: expected(%d) <> actual(%d)",
buf_idx, buffer[buf_idx], file_view[view_idx]);
std::cout << std::flush;
assert(0);
}
}
}

View File

@ -1,28 +0,0 @@
amalgamate.py - Amalgamate C source and header files
Copyright (c) 2012, Erik Edlund <erik.edlund@32767.se>
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Erik Edlund, nor the names of its contributors may
be used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,300 +0,0 @@
#!/usr/bin/env python
# coding=utf-8
# amalgamate.py - Amalgamate C source and header files.
# Copyright (c) 2012, Erik Edlund <erik.edlund@32767.se>
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
#
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
#
# * Neither the name of Erik Edlund, nor the names of its contributors may
# be used to endorse or promote products derived from this software without
# specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
import argparse
import datetime
import json
import os
import re
class Amalgamation(object):
# Prepends self.source_path to file_path if needed.
def actual_path(self, file_path):
if not os.path.isabs(file_path):
file_path = os.path.join(self.source_path, file_path)
return file_path
# Search included file_path in self.include_paths and
# in source_dir if specified.
def find_included_file(self, file_path, source_dir):
search_dirs = self.include_paths[:]
if source_dir:
search_dirs.insert(0, source_dir)
for search_dir in search_dirs:
search_path = os.path.join(search_dir, file_path)
if os.path.isfile(self.actual_path(search_path)):
return search_path
return None
def __init__(self, args):
with open(args.config, 'r') as f:
config = json.loads(f.read())
for key in config:
setattr(self, key, config[key])
self.verbose = args.verbose == "yes"
self.prologue = args.prologue
self.source_path = args.source_path
self.included_files = []
# Generate the amalgamation and write it to the target file.
def generate(self):
amalgamation = ""
if self.prologue:
with open(self.prologue, 'r') as f:
amalgamation += datetime.datetime.now().strftime(f.read())
if self.verbose:
print("Config:")
print(" target = {0}".format(self.target))
print(" working_dir = {0}".format(os.getcwd()))
print(" include_paths = {0}".format(self.include_paths))
print("Creating amalgamation:")
for file_path in self.sources:
# Do not check the include paths while processing the source
# list, all given source paths must be correct.
# actual_path = self.actual_path(file_path)
print(" - processing \"{0}\"".format(file_path))
t = TranslationUnit(file_path, self, True)
amalgamation += t.content
with open(self.target, 'w') as f:
f.write(amalgamation)
print("...done!\n")
if self.verbose:
print("Files processed: {0}".format(self.sources))
print("Files included: {0}".format(self.included_files))
print("")
def _is_within(match, matches):
for m in matches:
if match.start() > m.start() and \
match.end() < m.end():
return True
return False
class TranslationUnit(object):
# // C++ comment.
cpp_comment_pattern = re.compile(r"//.*?\n")
# /* C comment. */
c_comment_pattern = re.compile(r"/\*.*?\*/", re.S)
# "complex \"stri\\\ng\" value".
string_pattern = re.compile("[^']" r'".*?(?<=[^\\])"', re.S)
# Handle simple include directives. Support for advanced
# directives where macros and defines needs to expanded is
# not a concern right now.
include_pattern = re.compile(
r'#\s*include\s+(<|")(?P<path>.*?)("|>)', re.S)
# #pragma once
pragma_once_pattern = re.compile(r'#\s*pragma\s+once', re.S)
# Search for pattern in self.content, add the match to
# contexts if found and update the index accordingly.
def _search_content(self, index, pattern, contexts):
match = pattern.search(self.content, index)
if match:
contexts.append(match)
return match.end()
return index + 2
# Return all the skippable contexts, i.e., comments and strings
def _find_skippable_contexts(self):
# Find contexts in the content in which a found include
# directive should not be processed.
skippable_contexts = []
# Walk through the content char by char, and try to grab
# skippable contexts using regular expressions when found.
i = 1
content_len = len(self.content)
while i < content_len:
j = i - 1
current = self.content[i]
previous = self.content[j]
if current == '"':
# String value.
i = self._search_content(j, self.string_pattern,
skippable_contexts)
elif current == '*' and previous == '/':
# C style comment.
i = self._search_content(j, self.c_comment_pattern,
skippable_contexts)
elif current == '/' and previous == '/':
# C++ style comment.
i = self._search_content(j, self.cpp_comment_pattern,
skippable_contexts)
else:
# Skip to the next char.
i += 1
return skippable_contexts
# Returns True if the match is within list of other matches
# Removes pragma once from content
def _process_pragma_once(self):
content_len = len(self.content)
if content_len < len("#include <x>"):
return 0
# Find contexts in the content in which a found include
# directive should not be processed.
skippable_contexts = self._find_skippable_contexts()
pragmas = []
pragma_once_match = self.pragma_once_pattern.search(self.content)
while pragma_once_match:
if not _is_within(pragma_once_match, skippable_contexts):
pragmas.append(pragma_once_match)
pragma_once_match = self.pragma_once_pattern.search(self.content,
pragma_once_match.end())
# Handle all collected pragma once directives.
prev_end = 0
tmp_content = ''
for pragma_match in pragmas:
tmp_content += self.content[prev_end:pragma_match.start()]
prev_end = pragma_match.end()
tmp_content += self.content[prev_end:]
self.content = tmp_content
# Include all trivial #include directives into self.content.
def _process_includes(self):
content_len = len(self.content)
if content_len < len("#include <x>"):
return 0
# Find contexts in the content in which a found include
# directive should not be processed.
skippable_contexts = self._find_skippable_contexts()
# Search for include directives in the content, collect those
# which should be included into the content.
includes = []
include_match = self.include_pattern.search(self.content)
while include_match:
if not _is_within(include_match, skippable_contexts):
include_path = include_match.group("path")
search_same_dir = include_match.group(1) == '"'
found_included_path = self.amalgamation.find_included_file(
include_path, self.file_dir if search_same_dir else None)
if found_included_path:
includes.append((include_match, found_included_path))
include_match = self.include_pattern.search(self.content,
include_match.end())
# Handle all collected include directives.
prev_end = 0
tmp_content = ''
for include in includes:
include_match, found_included_path = include
tmp_content += self.content[prev_end:include_match.start()]
tmp_content += "// {0}\n".format(include_match.group(0))
if found_included_path not in self.amalgamation.included_files:
t = TranslationUnit(found_included_path, self.amalgamation, False)
tmp_content += t.content
prev_end = include_match.end()
tmp_content += self.content[prev_end:]
self.content = tmp_content
return len(includes)
# Make all content processing
def _process(self):
if not self.is_root:
self._process_pragma_once()
self._process_includes()
def __init__(self, file_path, amalgamation, is_root):
self.file_path = file_path
self.file_dir = os.path.dirname(file_path)
self.amalgamation = amalgamation
self.is_root = is_root
self.amalgamation.included_files.append(self.file_path)
actual_path = self.amalgamation.actual_path(file_path)
if not os.path.isfile(actual_path):
raise IOError("File not found: \"{0}\"".format(file_path))
with open(actual_path, 'r') as f:
self.content = f.read()
self._process()
def main():
description = "Amalgamate C source and header files."
usage = " ".join([
"amalgamate.py",
"[-v]",
"-c path/to/config.json",
"-s path/to/source/dir",
"[-p path/to/prologue.(c|h)]"
])
argsparser = argparse.ArgumentParser(
description=description, usage=usage)
argsparser.add_argument("-v", "--verbose", dest="verbose",
choices=["yes", "no"], metavar="", help="be verbose")
argsparser.add_argument("-c", "--config", dest="config",
required=True, metavar="", help="path to a JSON config file")
argsparser.add_argument("-s", "--source", dest="source_path",
required=True, metavar="", help="source code path")
argsparser.add_argument("-p", "--prologue", dest="prologue",
required=False, metavar="", help="path to a C prologue file")
amalgamation = Amalgamation(argsparser.parse_args())
amalgamation.generate()
if __name__ == "__main__":
main()

View File

@ -1,11 +0,0 @@
{
"project": "Cross-platform C++11 header-only library for memory mapped file IO",
"target": "../single_include/mio/mio.hpp",
"sources": [
"../include/mio/mmap.hpp",
"../include/mio/page.hpp",
"../include/mio/shared_mmap.hpp"
],
"include_paths": ["../include"]
}