mirror of
https://github.com/vimpunk/mio.git
synced 2026-10-28 18:07:20 +08:00
Neat code: single_include/mio.hpp
Refactor everything code
This commit is contained in:
parent
f05574baf6
commit
e8c2433fe0
241
CMakeLists.txt
241
CMakeLists.txt
@ -1,197 +1,53 @@
|
|||||||
cmake_minimum_required(VERSION 3.8)
|
cmake_minimum_required(VERSION 3.26)
|
||||||
|
project(mio LANGUAGES CXX)
|
||||||
|
|
||||||
#
|
# Set C++20 as the minimum required standard
|
||||||
# Here we check whether mio is being configured in isolation or as a component
|
set(CMAKE_CXX_STANDARD 20)
|
||||||
# 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(CMAKE_CXX_STANDARD_REQUIRED ON)
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
|
||||||
#
|
# Enable CTest support if BUILD_TESTING is enabled
|
||||||
# The `mio.testing` options only appear as cmake-gui and ccmake options iff
|
include(CTest)
|
||||||
# mio is the highest level project. In the case that mio is a subproject, these
|
if(BUILD_TESTING)
|
||||||
# options are hidden from the user interface and set to `OFF`
|
enable_testing()
|
||||||
#
|
endif()
|
||||||
# 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)
|
|
||||||
|
|
||||||
#
|
# Create an interface library "mio" to provide the include directory.
|
||||||
# On Windows, so as to be a "good citizen", mio offers two mechanisms to control
|
# It is assumed that the headers are located in "single_include/mio".
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
add_library(mio INTERFACE)
|
add_library(mio INTERFACE)
|
||||||
add_library(mio::mio ALIAS mio)
|
target_include_directories(mio INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/single_include)
|
||||||
|
|
||||||
#
|
# Add a basic test executable that is built on all platforms.
|
||||||
# The include directory for mio can be expected to vary between build
|
# Note that the test source file is assumed to be located at "test/test.cpp".
|
||||||
# and installaion. Here we use a CMake generator expression to dispatch
|
add_executable(mio.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp)
|
||||||
# on how the configuration under which this library is being consumed.
|
target_link_libraries(mio.test PRIVATE mio)
|
||||||
#
|
if(BUILD_TESTING)
|
||||||
# We define the generator expression as a variable, such that the logic
|
add_test(NAME mio.test COMMAND mio.test)
|
||||||
# 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>)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
# Windows-specific targets (only built on Windows)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
add_library(mio_full_winapi INTERFACE)
|
# Unicode version: add the UNICODE compile definition.
|
||||||
add_library(mio::mio_full_winapi ALIAS mio_full_winapi)
|
add_executable(mio.unicode.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp)
|
||||||
target_include_directories(mio_full_winapi INTERFACE ${prefix})
|
target_link_libraries(mio.unicode.test PRIVATE mio)
|
||||||
|
target_compile_definitions(mio.unicode.test PRIVATE UNICODE)
|
||||||
add_library(mio_min_winapi INTERFACE)
|
if(BUILD_TESTING)
|
||||||
add_library(mio::mio_min_winapi ALIAS mio_full_winapi)
|
add_test(NAME mio.unicode.test COMMAND mio.unicode.test)
|
||||||
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)
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
install(EXPORT mioTargets
|
# Full WinAPI version (adjust additional settings as needed).
|
||||||
FILE mio-targets.cmake
|
add_executable(mio.fullwinapi.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp)
|
||||||
NAMESPACE mio::
|
target_link_libraries(mio.fullwinapi.test PRIVATE mio)
|
||||||
DESTINATION share/cmake/mio)
|
if(BUILD_TESTING)
|
||||||
|
add_test(NAME mio.fullwinapi.test COMMAND mio.fullwinapi.test)
|
||||||
|
endif()
|
||||||
|
|
||||||
write_basic_package_version_file("mio-config-version.cmake"
|
# Minimal WinAPI version: minimal Windows API configuration.
|
||||||
VERSION ${PROJECT_VERSION}
|
add_executable(mio.minwinapi.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp)
|
||||||
COMPATIBILITY SameMajorVersion)
|
target_link_libraries(mio.minwinapi.test PRIVATE mio)
|
||||||
|
if(BUILD_TESTING)
|
||||||
configure_file(
|
add_test(NAME mio.minwinapi.test COMMAND mio.minwinapi.test)
|
||||||
"${PROJECT_SOURCE_DIR}/cmake/mio-config.cmake.in"
|
endif()
|
||||||
"${PROJECT_BINARY_DIR}/mio-config.cmake"
|
endif()
|
||||||
@ONLY)
|
|
||||||
|
|
||||||
install(FILES
|
|
||||||
"${PROJECT_BINARY_DIR}/mio-config-version.cmake"
|
|
||||||
"${PROJECT_BINARY_DIR}/mio-config.cmake"
|
|
||||||
DESTINATION share/cmake/mio)
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rudimentary CPack support.
|
# Rudimentary CPack support.
|
||||||
@ -209,15 +65,14 @@ if(mio.installation)
|
|||||||
#
|
#
|
||||||
# See `cpack --help` or the CPack documentation for more information.
|
# See `cpack --help` or the CPack documentation for more information.
|
||||||
#
|
#
|
||||||
if(NOT subproject)
|
if(NOT subproject)
|
||||||
set(CPACK_PACKAGE_VENDOR "mandreyel")
|
set(CPACK_PACKAGE_VENDOR "mandreyel")
|
||||||
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
|
set(CPACK_PACKAGE_DESCRIPTION_SUMMARY
|
||||||
"Cross-platform C++11 header-only library for memory mapped file IO")
|
"Cross-platform C++20 header-only library for memory mapped file IO")
|
||||||
set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/mandreyel/mio")
|
set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/Twilight-Dream-Of-Magic/mio")
|
||||||
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
|
set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE")
|
||||||
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
|
set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}")
|
||||||
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
|
set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}")
|
||||||
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
|
set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}")
|
||||||
include(CPack)
|
include(CPack)
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
set(CTEST_CUSTOM_COVERAGE_EXCLUDE
|
|
||||||
".*test.*"
|
|
||||||
".*c[+][+].*")
|
|
||||||
@ -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()
|
|
||||||
@ -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)
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
target_sources(mio-headers INTERFACE
|
|
||||||
"${prefix}/mio/detail/mmap.ipp"
|
|
||||||
"${prefix}/mio/detail/string_util.hpp")
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
|
|
||||||
@ -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
@ -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()
|
|
||||||
@ -1,4 +1,3 @@
|
|||||||
#include <mio/mmap.hpp>
|
|
||||||
#include <system_error> // for std::error_code
|
#include <system_error> // for std::error_code
|
||||||
#include <cstdio> // for std::printf
|
#include <cstdio> // for std::printf
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
|
|||||||
308
test/test.cpp
308
test/test.cpp
@ -1,182 +1,236 @@
|
|||||||
#include <mio/mmap.hpp>
|
|
||||||
#include <mio/shared_mmap.hpp>
|
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <cstdlib>
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
#ifndef _WIN32
|
#include "../single_include/mio/mio.hpp"
|
||||||
#include <sys/types.h>
|
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <fcntl.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Just make sure this compiles.
|
// Just make sure this compiles.
|
||||||
#ifdef CXX17
|
|
||||||
# include <cstddef>
|
# include <cstddef>
|
||||||
using mmap_source = mio::basic_mmap_source<std::byte>;
|
using mmap_source = mio::basic_mmap_source<std::byte>;
|
||||||
#endif
|
|
||||||
|
|
||||||
template<class MMap>
|
template<class MMap>
|
||||||
void test_at_offset(const MMap& file_view, const std::string& buffer,
|
void test_at_offset(const MMap& file_view, const std::string& buffer,
|
||||||
const size_t offset);
|
const size_t offset);
|
||||||
void test_at_offset(const std::string& buffer, const char* path,
|
inline 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);
|
||||||
int handle_error(const 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()
|
int main()
|
||||||
{
|
{
|
||||||
std::error_code error;
|
std::error_code error;
|
||||||
|
|
||||||
// Make sure mio compiles with non-const char* strings too.
|
// Make sure mio compiles with non-const char* strings too.
|
||||||
const char _path[] = "test-file";
|
const char _path[] = "test-file";
|
||||||
const int path_len = sizeof(_path);
|
const int path_len = sizeof(_path);
|
||||||
char* path = new char[path_len];
|
char* path = new char[path_len];
|
||||||
std::copy(_path, _path + path_len, path);
|
std::copy(_path, _path + path_len, path);
|
||||||
|
|
||||||
const auto page_size = mio::page_size();
|
const auto page_size = mio::page_size();
|
||||||
// Fill buffer, then write it to file.
|
// Fill buffer, then write it to file.
|
||||||
const int file_size = 4 * page_size - 250; // 16134, if page size is 4KiB
|
const int file_size = 4 * page_size - 250; // 16134, if page size is 4KiB
|
||||||
std::string buffer(file_size, 0);
|
std::string buffer(file_size, 0);
|
||||||
// Start at first printable ASCII character.
|
// Start at first printable ASCII character.
|
||||||
char v = 33;
|
char v = 33;
|
||||||
for (auto& b : buffer) {
|
for (auto& b : buffer) {
|
||||||
b = v;
|
b = v;
|
||||||
++v;
|
++v;
|
||||||
// Limit to last printable ASCII character.
|
// Limit to last printable ASCII character.
|
||||||
v %= 126;
|
v %= 126;
|
||||||
if(v == 0) {
|
if (v == 0) {
|
||||||
v = 33;
|
v = 33;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream file(path);
|
std::ofstream file(path);
|
||||||
file << buffer;
|
file << buffer;
|
||||||
file.close();
|
file.close();
|
||||||
|
|
||||||
// Test whole file mapping.
|
// Test whole file mapping.
|
||||||
test_at_offset(buffer, path, 0, error);
|
test_at_offset(buffer, path, 0, error);
|
||||||
if (error) { return handle_error(error); }
|
if (error) { return handle_error(error); }
|
||||||
|
|
||||||
// Test starting from below the page size.
|
//Test starting from below the page size.
|
||||||
test_at_offset(buffer, path, page_size - 3, error);
|
test_at_offset(buffer, path, page_size - 3, error);
|
||||||
if (error) { return handle_error(error); }
|
if (error) { return handle_error(error); }
|
||||||
|
|
||||||
// Test starting from above the page size.
|
// Test starting from above the page size.
|
||||||
test_at_offset(buffer, path, page_size + 3, error);
|
test_at_offset(buffer, path, page_size + 3, error);
|
||||||
if (error) { return handle_error(error); }
|
if (error) { return handle_error(error); }
|
||||||
|
|
||||||
// Test starting from above the page size.
|
// Test starting from above the page size.
|
||||||
test_at_offset(buffer, path, 2 * page_size + 3, error);
|
test_at_offset(buffer, path, 2 * page_size + 3, error);
|
||||||
if (error) { return handle_error(error); }
|
if (error) { return handle_error(error); }
|
||||||
|
|
||||||
{
|
{
|
||||||
#define CHECK_INVALID_MMAP(m) do { \
|
#define CHECK_INVALID_MMAP(m) do { \
|
||||||
assert(error); \
|
assert(error); \
|
||||||
assert(m.empty()); \
|
assert(m.empty()); \
|
||||||
assert(!m.is_open()); \
|
assert(!m.is_open()); \
|
||||||
error.clear(); } while(0)
|
error.clear(); } while(0)
|
||||||
|
|
||||||
mio::mmap_source m;
|
mio::mmap_source m;
|
||||||
|
|
||||||
// See if mapping an invalid file results in an error.
|
// See if mapping an invalid file results in an error.
|
||||||
m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error);
|
m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error);
|
||||||
CHECK_INVALID_MMAP(m);
|
CHECK_INVALID_MMAP(m);
|
||||||
|
|
||||||
// Empty path?
|
// Empty path?
|
||||||
m = mio::make_mmap_source(static_cast<const char*>(0), 0, 0, error);
|
m = mio::make_mmap_source(static_cast<const char*>(0), 0, 0, error);
|
||||||
CHECK_INVALID_MMAP(m);
|
CHECK_INVALID_MMAP(m);
|
||||||
m = mio::make_mmap_source(std::string(), 0, 0, error);
|
m = mio::make_mmap_source(std::string(), 0, 0, error);
|
||||||
CHECK_INVALID_MMAP(m);
|
CHECK_INVALID_MMAP(m);
|
||||||
|
|
||||||
// Invalid handle?
|
// Invalid handle?
|
||||||
m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error);
|
m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error);
|
||||||
CHECK_INVALID_MMAP(m);
|
CHECK_INVALID_MMAP(m);
|
||||||
|
|
||||||
// Invalid offset?
|
// Invalid offset?
|
||||||
m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error);
|
m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error);
|
||||||
CHECK_INVALID_MMAP(m);
|
CHECK_INVALID_MMAP(m);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make sure these compile.
|
// Make sure these compile.
|
||||||
{
|
{
|
||||||
mio::ummap_source _1;
|
mio::ummap_source _1;
|
||||||
mio::shared_ummap_source _2;
|
mio::shared_ummap_source _2;
|
||||||
// Make sure shared_mmap mapping compiles as all testing was done on
|
// Make sure shared_mmap mapping compiles as all testing was done on
|
||||||
// normal mmaps.
|
// normal mmaps.
|
||||||
mio::shared_mmap_source _3(path, 0, mio::map_entire_file);
|
mio::shared_mmap_source _3(path, 0, mio::map_entire_file);
|
||||||
auto _4 = mio::make_mmap_source(path, error);
|
auto _4 = mio::make_mmap_source(path, error);
|
||||||
auto _5 = mio::make_mmap<mio::shared_mmap_source>(path, 0, mio::map_entire_file, error);
|
auto _5 = mio::make_mmap<mio::shared_mmap_source>(path, 0, mio::map_entire_file, error);
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
const wchar_t* wpath1 = L"dasfsf";
|
const wchar_t* wpath1 = L"dasfsf";
|
||||||
auto _6 = mio::make_mmap_source(wpath1, error);
|
auto _6 = mio::make_mmap_source(wpath1, error);
|
||||||
mio::mmap_source _7;
|
mio::mmap_source _7;
|
||||||
_7.map(wpath1, error);
|
_7.map(wpath1, error);
|
||||||
const std::wstring wpath2 = wpath1;
|
const std::wstring wpath2 = wpath1;
|
||||||
auto _8 = mio::make_mmap_source(wpath2, error);
|
auto _8 = mio::make_mmap_source(wpath2, error);
|
||||||
mio::mmap_source _9;
|
mio::mmap_source _9;
|
||||||
_9.map(wpath1, error);
|
_9.map(wpath1, error);
|
||||||
#else
|
#else
|
||||||
const int fd = open(path, O_RDONLY);
|
const int fd = open(path, O_RDONLY);
|
||||||
mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file);
|
mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file);
|
||||||
_fdmmap.unmap();
|
_fdmmap.unmap();
|
||||||
_fdmmap.map(fd, error);
|
_fdmmap.map(fd, error);
|
||||||
#endif
|
#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,
|
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.
|
// Sanity check.
|
||||||
assert(offset < buffer.size());
|
assert(offset < buffer.size());
|
||||||
|
|
||||||
// Map the region of the file to which buffer was written.
|
// Map the region of the file to which buffer was written.
|
||||||
mio::mmap_source file_view = mio::make_mmap_source(
|
mio::mmap_source file_view = mio::make_mmap_source(
|
||||||
path, offset, mio::map_entire_file, error);
|
path, offset, mio::map_entire_file, error);
|
||||||
if(error) { return; }
|
if (error) { return; }
|
||||||
|
|
||||||
assert(file_view.is_open());
|
assert(file_view.is_open());
|
||||||
const size_t mapped_size = buffer.size() - offset;
|
const size_t mapped_size = buffer.size() - offset;
|
||||||
assert(file_view.size() == mapped_size);
|
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.
|
// Turn file_view into a shared mmap.
|
||||||
mio::shared_mmap_source shared_file_view(std::move(file_view));
|
mio::shared_mmap_source shared_file_view(std::move(file_view));
|
||||||
assert(!file_view.is_open());
|
assert(!file_view.is_open());
|
||||||
assert(shared_file_view.is_open());
|
assert(shared_file_view.is_open());
|
||||||
assert(shared_file_view.size() == mapped_size);
|
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>
|
template<class MMap>
|
||||||
void test_at_offset(const MMap& file_view, const std::string& buffer,
|
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.
|
// Then verify that mmap's bytes correspond to that of buffer.
|
||||||
for(size_t buf_idx = offset, view_idx = 0;
|
for (size_t buf_idx = offset, view_idx = 0;
|
||||||
buf_idx < buffer.size() && view_idx < file_view.size();
|
buf_idx < buffer.size() && view_idx < file_view.size();
|
||||||
++buf_idx, ++view_idx) {
|
++buf_idx, ++view_idx) {
|
||||||
if(file_view[view_idx] != buffer[buf_idx]) {
|
if (file_view[view_idx] != buffer[buf_idx]) {
|
||||||
std::printf("%luth byte mismatch: expected(%d) <> actual(%d)",
|
std::printf("%luth byte mismatch: expected(%d) <> actual(%d)",
|
||||||
buf_idx, buffer[buf_idx], file_view[view_idx]);
|
buf_idx, buffer[buf_idx], file_view[view_idx]);
|
||||||
std::cout << std::flush;
|
std::cout << std::flush;
|
||||||
assert(0);
|
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();
|
|
||||||
}
|
}
|
||||||
|
|||||||
28
third_party/LICENSE.md
vendored
28
third_party/LICENSE.md
vendored
@ -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.
|
|
||||||
|
|
||||||
300
third_party/amalgamate.py
vendored
300
third_party/amalgamate.py
vendored
@ -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()
|
|
||||||
|
|
||||||
11
third_party/config.json
vendored
11
third_party/config.json
vendored
@ -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"]
|
|
||||||
}
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user