From e8c2433fe09421682dca643f2306c507f9120418 Mon Sep 17 00:00:00 2001 From: Twilight-Dream-Of-Magical <31034152+Twilight-Dream-Of-Magic@users.noreply.github.com> Date: Mon, 31 Mar 2025 11:20:11 +0800 Subject: [PATCH] Neat code: single_include/mio.hpp Refactor everything code --- CMakeLists.txt | 241 +- cmake/CTestCustom.cmake | 3 - cmake/mio-config.cmake.in | 13 - include/mio/CMakeLists.txt | 11 - include/mio/detail/CMakeLists.txt | 3 - include/mio/detail/mmap.ipp | 530 ---- include/mio/detail/string_util.hpp | 170 -- include/mio/mmap.hpp | 492 ---- include/mio/page.hpp | 78 - include/mio/shared_mmap.hpp | 406 --- single_include/mio/mio.hpp | 3770 ++++++++++++++-------------- test/CMakeLists.txt | 25 - test/example.cpp | 1 - test/test.cpp | 308 ++- third_party/LICENSE.md | 28 - third_party/amalgamate.py | 300 --- third_party/config.json | 11 - 17 files changed, 2136 insertions(+), 4254 deletions(-) delete mode 100644 cmake/CTestCustom.cmake delete mode 100644 cmake/mio-config.cmake.in delete mode 100644 include/mio/CMakeLists.txt delete mode 100644 include/mio/detail/CMakeLists.txt delete mode 100644 include/mio/detail/mmap.ipp delete mode 100644 include/mio/detail/string_util.hpp delete mode 100644 include/mio/mmap.hpp delete mode 100644 include/mio/page.hpp delete mode 100644 include/mio/shared_mmap.hpp delete mode 100644 test/CMakeLists.txt delete mode 100644 third_party/LICENSE.md delete mode 100644 third_party/amalgamate.py delete mode 100644 third_party/config.json diff --git a/CMakeLists.txt b/CMakeLists.txt index 54f8768..629c6d6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,197 +1,53 @@ -cmake_minimum_required(VERSION 3.8) +cmake_minimum_required(VERSION 3.26) +project(mio LANGUAGES CXX) -# -# Here we check whether mio is being configured in isolation or as a component -# of a larger project. To do so, we query whether the `PROJECT_NAME` CMake -# variable has been defined. In the case it has, we can conclude mio is a -# subproject. -# -# This convention has been borrowed from the Catch C++ unit testing library. -# -if(DEFINED PROJECT_NAME) - set(subproject ON) - if(NOT DEFINED INSTALL_SUBPROJECTS) - set(INSTALL_SUBPROJECTS ON CACHE BOOL "Install subproject dependencies") - endif() -else() - set(subproject OFF) - set_property(GLOBAL PROPERTY USE_FOLDERS ON) -endif() - -project(mio VERSION 1.1.0 LANGUAGES C CXX) -list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake") -include(CMakeDependentOption) -include(CMakePackageConfigHelpers) -include(CTest) -include(GNUInstallDirs) - -# Generate 'compile_commands.json' for clang_complete -set(CMAKE_EXPORT_COMPILE_COMMANDS ON) - -# -# mio requires C++ 11 support, at a minimum. The `CMAKE_CXX_STANDARD` variable -# is referenced when a target is created to initialize the CXX_STANDARD target -# property. -# -# ** NOTE ** -# This is a directory scope variable. This has several implicitations. -# -# 1. It DOES NOT propegate to parent scopes (such as parent projects) -# 2. It hides cache variables of the same name for this directory and below -# -set(CMAKE_CXX_STANDARD 11) +# Set C++20 as the minimum required standard +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) -# -# The `mio.testing` options only appear as cmake-gui and ccmake options iff -# mio is the highest level project. In the case that mio is a subproject, these -# options are hidden from the user interface and set to `OFF` -# -# Iff mio is the highest level project, this option is defaulted to the value -# of the traditional course grain testing option `BUILD_TESTING` established by -# the CTest module -# -CMAKE_DEPENDENT_OPTION(mio.tests - "Build the mio tests and integrate with ctest" - ON "BUILD_TESTING; NOT subproject" OFF) +# Enable CTest support if BUILD_TESTING is enabled +include(CTest) +if(BUILD_TESTING) + enable_testing() +endif() -# -# On Windows, so as to be a "good citizen", mio offers two mechanisms to control -# the imported surface area of the Windows API. The default `mio` target sets -# the necessary flags for a minimal Win API (`WIN32_LEAN_AND_MEAN`, etc.) on -# linking targets. This is, in our view, the conservative behavior. -# -# However, an option is published in the cache allowing client to opt out of -# these compiler definintions. This preference will persist in the installed -# cmake configuration file, but can be modified by downstream users by way of -# the same cmake cache variable. This allows intermediate consumers (e.g. other -# libraries) to defer this decision making to downstream clients. -# -# Beyond the option-based mechanism, two additional targets, -# mio::mio_min_winapi and mio::mio_full_winapi, are specified below for those -# that expressly requiring either the minimal or full windows API, respectively. -# -CMAKE_DEPENDENT_OPTION(mio.windows.full_api - "Configure mio without WIN32_LEAN_AND_MEAN and NOMINMAX definitions" - OFF "WIN32" ON) - -# -# When the end user is consuming mio as a nested subproject, an option -# is provided such that the user may exlude mio from the set of installed -# cmake projects. This accomodates end users building executables or -# compiled libraries which privately link to mio, but wish to only ship their -# artifacts in an installation -# -CMAKE_DEPENDENT_OPTION(mio.installation - "Include mio in the install set" - "${INSTALL_SUBPROJECTS}" "subproject" ON) -mark_as_advanced(mio.installation) - -# -# mio has no compiled components. As such, we declare it as an `INTERFACE` -# library, which denotes a collection of target properties to be applied -# transitively to linking targets. In our case, this amounts to an include -# directory and (potentially) some preprocessor definitions. -# +# Create an interface library "mio" to provide the include directory. +# It is assumed that the headers are located in "single_include/mio". add_library(mio INTERFACE) -add_library(mio::mio ALIAS mio) +target_include_directories(mio INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/single_include) -# -# The include directory for mio can be expected to vary between build -# and installaion. Here we use a CMake generator expression to dispatch -# on how the configuration under which this library is being consumed. -# -# We define the generator expression as a variable, such that the logic -# need not be repeated when populating source file paths. -# -string(CONCAT prefix - "$" - "$") - -target_include_directories(mio INTERFACE ${prefix}) - -if(NOT mio.windows.full_api) - target_compile_definitions(mio INTERFACE - $ - $) +# Add a basic test executable that is built on all platforms. +# Note that the test source file is assumed to be located at "test/test.cpp". +add_executable(mio.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp) +target_link_libraries(mio.test PRIVATE mio) +if(BUILD_TESTING) + add_test(NAME mio.test COMMAND mio.test) endif() +# Windows-specific targets (only built on Windows) if(WIN32) - add_library(mio_full_winapi INTERFACE) - add_library(mio::mio_full_winapi ALIAS mio_full_winapi) - target_include_directories(mio_full_winapi INTERFACE ${prefix}) - - add_library(mio_min_winapi INTERFACE) - add_library(mio::mio_min_winapi ALIAS mio_full_winapi) - target_compile_definitions(mio INTERFACE WIN32_LEAN_AND_MEAN NOMINMAX) - target_include_directories(mio_min_winapi INTERFACE ${prefix}) -endif() - -# -# In order to collect mio's header files in IDE tools such as XCode or Visual -# Studio, there must exist a target adding any such header files as source files. -# -# Given mio is an interface target, source files may only be added with the -# INTERFACE keyword, which consequently propegate to linking targets. This -# behavior isn't desirable to all clients. -# -# To accomodate, a second target is declared which collects the header files, -# which links to the primary mio target. As such, the header files are available -# in IDE tools. -# -add_library(mio-headers INTERFACE) -add_library(mio::mio-headers ALIAS mio-headers) -target_link_libraries(mio-headers INTERFACE mio) - -add_subdirectory(include/mio) - -if(mio.tests) - add_subdirectory(test) -endif() - -if(mio.installation) - # - # Non-testing header files (preserving relative paths) are installed to the - # `include` subdirectory of the `$INSTALL_DIR/${CMAKE_INSTALL_PREFIX}` - # directory. Source file permissions preserved. - # - install(DIRECTORY include/ - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - FILES_MATCHING PATTERN "*.*pp") - - # - # As a header-only library, there are no target components to be installed - # directly (the PUBLIC_HEADER property is not white listed for INTERFACE - # targets for some reason). - # - # However, it is worthwhile export our target description in order to later - # generate a CMake configuration file for consumption by CMake's `find_package` - # intrinsic - # - install(TARGETS mio mio-headers EXPORT mioTargets) - - if(WIN32) - install(TARGETS mio_full_winapi mio_min_winapi EXPORT mioTargets) + # Unicode version: add the UNICODE compile definition. + add_executable(mio.unicode.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp) + target_link_libraries(mio.unicode.test PRIVATE mio) + target_compile_definitions(mio.unicode.test PRIVATE UNICODE) + if(BUILD_TESTING) + add_test(NAME mio.unicode.test COMMAND mio.unicode.test) endif() - install(EXPORT mioTargets - FILE mio-targets.cmake - NAMESPACE mio:: - DESTINATION share/cmake/mio) + # Full WinAPI version (adjust additional settings as needed). + add_executable(mio.fullwinapi.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp) + target_link_libraries(mio.fullwinapi.test PRIVATE mio) + if(BUILD_TESTING) + add_test(NAME mio.fullwinapi.test COMMAND mio.fullwinapi.test) + endif() - write_basic_package_version_file("mio-config-version.cmake" - VERSION ${PROJECT_VERSION} - COMPATIBILITY SameMajorVersion) - - configure_file( - "${PROJECT_SOURCE_DIR}/cmake/mio-config.cmake.in" - "${PROJECT_BINARY_DIR}/mio-config.cmake" - @ONLY) - - install(FILES - "${PROJECT_BINARY_DIR}/mio-config-version.cmake" - "${PROJECT_BINARY_DIR}/mio-config.cmake" - DESTINATION share/cmake/mio) + # Minimal WinAPI version: minimal Windows API configuration. + add_executable(mio.minwinapi.test ${CMAKE_CURRENT_SOURCE_DIR}/test/test.cpp) + target_link_libraries(mio.minwinapi.test PRIVATE mio) + if(BUILD_TESTING) + add_test(NAME mio.minwinapi.test COMMAND mio.minwinapi.test) + endif() +endif() # # Rudimentary CPack support. @@ -209,15 +65,14 @@ if(mio.installation) # # See `cpack --help` or the CPack documentation for more information. # - if(NOT subproject) - set(CPACK_PACKAGE_VENDOR "mandreyel") - set(CPACK_PACKAGE_DESCRIPTION_SUMMARY - "Cross-platform C++11 header-only library for memory mapped file IO") - set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/mandreyel/mio") - set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") - set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") - set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") - set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") - include(CPack) - endif() +if(NOT subproject) + set(CPACK_PACKAGE_VENDOR "mandreyel") + set(CPACK_PACKAGE_DESCRIPTION_SUMMARY + "Cross-platform C++20 header-only library for memory mapped file IO") + set(CMAKE_PROJECT_HOMEPAGE_URL "https://github.com/Twilight-Dream-Of-Magic/mio") + set(CPACK_RESOURCE_FILE_LICENSE "${PROJECT_SOURCE_DIR}/LICENSE") + set(CPACK_PACKAGE_VERSION_MAJOR "${PROJECT_VERSION_MAJOR}") + set(CPACK_PACKAGE_VERSION_MINOR "${PROJECT_VERSION_MINOR}") + set(CPACK_PACKAGE_VERSION_PATCH "${PROJECT_VERSION_PATCH}") + include(CPack) endif() diff --git a/cmake/CTestCustom.cmake b/cmake/CTestCustom.cmake deleted file mode 100644 index 1e68e7f..0000000 --- a/cmake/CTestCustom.cmake +++ /dev/null @@ -1,3 +0,0 @@ -set(CTEST_CUSTOM_COVERAGE_EXCLUDE - ".*test.*" - ".*c[+][+].*") diff --git a/cmake/mio-config.cmake.in b/cmake/mio-config.cmake.in deleted file mode 100644 index 43c59dd..0000000 --- a/cmake/mio-config.cmake.in +++ /dev/null @@ -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() diff --git a/include/mio/CMakeLists.txt b/include/mio/CMakeLists.txt deleted file mode 100644 index 1da153e..0000000 --- a/include/mio/CMakeLists.txt +++ /dev/null @@ -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) diff --git a/include/mio/detail/CMakeLists.txt b/include/mio/detail/CMakeLists.txt deleted file mode 100644 index de957ea..0000000 --- a/include/mio/detail/CMakeLists.txt +++ /dev/null @@ -1,3 +0,0 @@ -target_sources(mio-headers INTERFACE - "${prefix}/mio/detail/mmap.ipp" - "${prefix}/mio/detail/string_util.hpp") diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp deleted file mode 100644 index 1b6dc4d..0000000 --- a/include/mio/detail/mmap.ipp +++ /dev/null @@ -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 - -#ifndef _WIN32 -# include -# include -# include -# include -#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(s.length()); - auto buf = std::vector(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::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 std::enable_if< - std::is_same::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 -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(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(::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(::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 -basic_mmap::~basic_mmap() -{ - conditional_sync(); - unmap(); -} - -template -basic_mmap::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 -basic_mmap& -basic_mmap::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 -typename basic_mmap::handle_type -basic_mmap::mapping_handle() const noexcept -{ -#ifdef _WIN32 - return file_mapping_handle_; -#else - return file_handle_; -#endif -} - -template -template -void basic_mmap::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 -void basic_mmap::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(ctx.data); - length_ = ctx.length; - mapped_length_ = ctx.mapped_length; -#ifdef _WIN32 - file_mapping_handle_ = ctx.file_mapping_handle; -#endif - } -} - -template -template -typename std::enable_if::type -basic_mmap::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 -void basic_mmap::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(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 -bool basic_mmap::is_mapped() const noexcept -{ -#ifdef _WIN32 - return file_mapping_handle_ != invalid_handle; -#else // POSIX - return is_open(); -#endif -} - -template -void basic_mmap::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 -template -typename std::enable_if::type -basic_mmap::conditional_sync() -{ - // This is invoked from the destructor, so not much we can do about - // failures here. - std::error_code ec; - sync(ec); -} - -template -template -typename std::enable_if::type -basic_mmap::conditional_sync() -{ - // noop -} - -template -bool operator==(const basic_mmap& a, - const basic_mmap& b) -{ - return a.data() == b.data() - && a.size() == b.size(); -} - -template -bool operator!=(const basic_mmap& a, - const basic_mmap& b) -{ - return !(a == b); -} - -template -bool operator<(const basic_mmap& a, - const basic_mmap& b) -{ - if(a.data() == b.data()) { return a.size() < b.size(); } - return a.data() < b.data(); -} - -template -bool operator<=(const basic_mmap& a, - const basic_mmap& b) -{ - return !(a > b); -} - -template -bool operator>(const basic_mmap& a, - const basic_mmap& b) -{ - if(a.data() == b.data()) { return a.size() > b.size(); } - return a.data() > b.data(); -} - -template -bool operator>=(const basic_mmap& a, - const basic_mmap& b) -{ - return !(a < b); -} - -} // namespace mio - -#endif // MIO_BASIC_MMAP_IMPL diff --git a/include/mio/detail/string_util.hpp b/include/mio/detail/string_util.hpp deleted file mode 100644 index 2f375aa..0000000 --- a/include/mio/detail/string_util.hpp +++ /dev/null @@ -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 - -namespace mio { -namespace detail { - -template< - typename S, - typename C = typename std::decay::type, - typename = decltype(std::declval().data()), - typename = typename std::enable_if< - std::is_same::value -#ifdef _WIN32 - || std::is_same::value -#endif - >::type -> struct char_type_helper { - using type = typename C::value_type; -}; - -template -struct char_type { - using type = typename char_type_helper::type; -}; - -// TODO: can we avoid this brute force approach? -template<> -struct char_type { - using type = char; -}; - -template<> -struct char_type { - using type = char; -}; - -template -struct char_type { - using type = char; -}; - -template -struct char_type { - using type = char; -}; - -#ifdef _WIN32 -template<> -struct char_type { - using type = wchar_t; -}; - -template<> -struct char_type { - using type = wchar_t; -}; - -template -struct char_type { - using type = wchar_t; -}; - -template -struct char_type { - using type = wchar_t; -}; -#endif // _WIN32 - -template -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 -struct is_c_str -{ - static constexpr bool value = is_c_str_helper::value; -}; - -#ifdef _WIN32 -template -struct is_c_wstr -{ - static constexpr bool value = is_c_str_helper::value; -}; -#endif // _WIN32 - -template -struct is_c_str_or_c_wstr -{ - static constexpr bool value = is_c_str::value -#ifdef _WIN32 - || is_c_wstr::value -#endif - ; -}; - -template< - typename String, - typename = decltype(std::declval().data()), - typename = typename std::enable_if::value>::type -> const typename char_type::type* c_str(const String& path) -{ - return path.data(); -} - -template< - typename String, - typename = decltype(std::declval().empty()), - typename = typename std::enable_if::value>::type -> bool empty(const String& path) -{ - return path.empty(); -} - -template< - typename String, - typename = typename std::enable_if::value>::type -> const typename char_type::type* c_str(String path) -{ - return path; -} - -template< - typename String, - typename = typename std::enable_if::value>::type -> bool empty(String path) -{ - return !path || (*path == 0); -} - -} // namespace detail -} // namespace mio - -#endif // MIO_STRING_UTIL_HEADER diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp deleted file mode 100644 index def559a..0000000 --- a/include/mio/mmap.hpp +++ /dev/null @@ -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 -#include -#include -#include - -#ifdef _WIN32 -# ifndef WIN32_LEAN_AND_MEAN -# define WIN32_LEAN_AND_MEAN -# endif // WIN32_LEAN_AND_MEAN -# include -#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 -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; - using const_reverse_iterator = std::reverse_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 - 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::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::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::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::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::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 - 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 - 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 - typename std::enable_if::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::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 - typename std::enable_if::type - conditional_sync(); - template - typename std::enable_if::type conditional_sync(); -}; - -template -bool operator==(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator!=(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator<(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator<=(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator>(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator>=(const basic_mmap& a, - const basic_mmap& b); - -/** - * This is the basis for all read-only mmap objects and should be preferred over - * directly using `basic_mmap`. - */ -template -using basic_mmap_source = basic_mmap; - -/** - * This is the basis for all read-write mmap objects and should be preferred over - * directly using `basic_mmap`. - */ -template -using basic_mmap_sink = basic_mmap; - -/** - * 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; -using ummap_source = basic_mmap_source; - -using mmap_sink = basic_mmap_sink; -using ummap_sink = basic_mmap_sink; - -/** - * 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`, or similar), or a - * `mmap_source::handle_type`. - */ -template -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(token, offset, length, error); -} - -template -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`, or similar), or a - * `mmap_sink::handle_type`. - */ -template -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(token, offset, length, error); -} - -template -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 diff --git a/include/mio/page.hpp b/include/mio/page.hpp deleted file mode 100644 index cae7377..0000000 --- a/include/mio/page.hpp +++ /dev/null @@ -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 -#else -# include -#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 diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp deleted file mode 100644 index f125a59..0000000 --- a/include/mio/shared_mmap.hpp +++ /dev/null @@ -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 // std::error_code -#include // 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; - std::shared_ptr 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(std::move(mmap))) - {} - - /** Takes ownership of an existing mmap object. */ - basic_shared_mmap& operator=(mmap_type&& mmap) - { - pimpl_ = std::make_shared(std::move(mmap)); - return *this; - } - - /** Initializes this object with an already established shared mmap. */ - basic_shared_mmap(std::shared_ptr mmap) : pimpl_(std::move(mmap)) {} - - /** Initializes this object with an already established shared mmap. */ - basic_shared_mmap& operator=(std::shared_ptr 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 - 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 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::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::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::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::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 - 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 - 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::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 - 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(token, offset, length, error); - if(error) { return; } - pimpl_ = std::make_shared(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 -using basic_shared_mmap_source = basic_shared_mmap; - -/** - * This is the basis for all read-write mmap objects and should be preferred over - * directly using basic_shared_mmap. - */ -template -using basic_shared_mmap_sink = basic_shared_mmap; - -/** - * 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; -using shared_ummap_source = basic_shared_mmap_source; - -using shared_mmap_sink = basic_shared_mmap_sink; -using shared_ummap_sink = basic_shared_mmap_sink; - -} // namespace mio - -#endif // MIO_SHARED_MMAP_HEADER diff --git a/single_include/mio/mio.hpp b/single_include/mio/mio.hpp index e7f6c30..c6ef4a4 100644 --- a/single_include/mio/mio.hpp +++ b/single_include/mio/mio.hpp @@ -1,4 +1,8 @@ -/* Copyright 2017 https://github.com/mandreyel +/* This is the mio library, using C++20 standard with clean code practices. + * Replaced system error codes with exceptions and `std::source_location` for error messages. + * Assertions are used for validation. + * Copyright 2017-2030 https://github.com/mandreyel + * Modified by https://github.com/Twilight-Dream-Of-Magic * * 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 @@ -21,253 +25,29 @@ #ifndef MIO_MMAP_HEADER #define MIO_MMAP_HEADER -// #include "mio/page.hpp" -/* 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. - */ - -#if __cplusplus >= 201103L && __cplusplus <= 201703L -inline std::wstring cpp2017_string2wstring(const std::string &_string) -{ - using convert_typeX = std::codecvt_utf8; - std::wstring_convert converterX; - - return converterX.from_bytes(_string); -} - -inline std::string cpp2017_wstring2string(const std::wstring &_wstring) -{ - using convert_typeX = std::codecvt_utf8; - std::wstring_convert converterX; - - return converterX.to_bytes(_wstring); -} -#endif - -inline std::wstring string2wstring(const std::string& _string) -{ - ::setlocale(LC_ALL, ""); - std::vector wide_character_buffer; - std::size_t source_string_count = 1; - std::size_t found_not_ascii_count = 0; - for(auto begin = _string.begin(), end = _string.end(); begin != end; begin++) - { - if(static_cast(*begin) > 0) - { - ++source_string_count; - } - else if (static_cast(*begin) < 0) - { - ++found_not_ascii_count; - } - } - - std::size_t target_wstring_count = source_string_count + (found_not_ascii_count / 2); - - wide_character_buffer.resize(target_wstring_count); - - #if defined(_MSC_VER) - std::size_t _converted_count = 0; - ::mbstowcs_s(&_converted_count, &wide_character_buffer[0], target_wstring_count, _string.c_str(), ((size_t)-1)); - #else - ::mbstowcs(&wide_character_buffer[0], _string.c_str(), target_wstring_count); - #endif - - std::size_t _target_wstring_size = 0; - for(auto begin = wide_character_buffer.begin(), end = wide_character_buffer.end(); begin != end && *begin != L'\0'; begin++) - { - ++_target_wstring_size; - } - std::wstring _wstring{ wide_character_buffer.data(), _target_wstring_size }; - - #if defined(_MSC_VER) - if(_converted_count == 0) - { - throw std::runtime_error("The function string2wstring is not work !"); - } - #endif - - if(found_not_ascii_count > 0) - { - //Need Contains character('\0') then check size - if(((_target_wstring_size + 1) - source_string_count) != (found_not_ascii_count / 2)) - { - throw std::runtime_error("The function string2wstring, An error occurs during conversion !"); - } - else - { - return _wstring; - } - } - else - { - //Need Contains character('\0') then check size - if((_target_wstring_size + 1) != source_string_count) - { - throw std::runtime_error("The function string2wstring, An error occurs during conversion !"); - } - else - { - return _wstring; - } - } - -} - -inline std::string wstring2string(const std::wstring& _wstring) -{ - ::setlocale(LC_ALL, ""); - std::vector character_buffer; - std::size_t source_wstring_count = 1; - std::size_t found_not_ascii_count = 0; - for(auto begin = _wstring.begin(), end = _wstring.end(); begin != end; begin++) - { - if(static_cast(*begin) < 256) - { - ++source_wstring_count; - } - else if (static_cast(*begin) >= 256) - { - ++found_not_ascii_count; - } - } - std::size_t target_string_count = source_wstring_count + found_not_ascii_count * 2; - - character_buffer.resize(target_string_count); - - #if defined(_MSC_VER) - std::size_t _converted_count = 0; - ::wcstombs_s(&_converted_count, &character_buffer[0], target_string_count, _wstring.c_str(), ((size_t)-1)); - #else - ::wcstombs(&character_buffer[0], _wstring.c_str(), target_string_count); - #endif - - std::size_t _target_string_size = 0; - for(auto begin = character_buffer.begin(), end = character_buffer.end(); begin != end && *begin != '\0'; begin++) - { - ++_target_string_size; - } - std::string _string{ character_buffer.data(), _target_string_size }; - - #if defined(_MSC_VER) - if(_converted_count == 0) - { - throw std::runtime_error("The function wstring2string is not work !"); - } - #endif - - if(found_not_ascii_count > 0) - { - if(((_target_string_size + 1) - source_wstring_count) != (found_not_ascii_count * 2)) - { - throw std::runtime_error("The function wstring2string, An error occurs during conversion !"); - } - else - { - return _string; - } - } - else - { - if((_target_string_size + 1) != source_wstring_count) - { - throw std::runtime_error("The function wstring2string, An error occurs during conversion !"); - } - else - { - return _string; - } - } -} - #ifndef MIO_PAGE_HEADER #define MIO_PAGE_HEADER #ifdef _WIN32 # include #else -# include +#include +#include +#include +#include +#include #endif -namespace mio { - - #ifdef min - #undef min - #endif //! min - - #ifdef max - #undef max - #endif //! max - -/** - * 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 - - #include #include -#include #include +#include +#include + +#include + +#include // std::error_code +#include // std::shared_ptr #ifdef _WIN32 # ifndef WIN32_LEAN_AND_MEAN @@ -278,1781 +58,2045 @@ inline size_t make_offset_page_aligned(size_t offset) noexcept # define INVALID_HANDLE_VALUE -1 #endif // ifdef _WIN32 -namespace mio { +#include -// 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 }; +#include +#include + + +namespace mio +{ + +#ifdef min +#undef min +#endif //! min + +#ifdef max +#undef max +#endif //! max + +#if __cplusplus >= 202002L + + inline void my_cpp2020_assert(const bool JudgmentCondition, const char* ErrorMessage, std::source_location AssertExceptionDetailTrackingObject) + { + if(!JudgmentCondition) + { + std::system("dhcp 65001"); + + std::cout << "The error message is(错误信息是):\n" << ErrorMessage << std::endl; + + std::cout << "Oh, crap, some of the code already doesn't match the conditions at runtime.(哦,糟糕,有些代码在运行时已经不匹配条件。)\n\n\n" << std::endl; + std::cout << "Here is the trace before the assertion occurred(下面是发生断言之前的追踪信息):\n\n" << std::endl; + std::cout << "The condition determines the code file that appears to be a mismatch(条件判断出现不匹配的代码文件):\n" << AssertExceptionDetailTrackingObject.file_name() << std::endl; + std::cout << "Name of the function where this assertion is located(该断言所在的函数的名字):\n" << AssertExceptionDetailTrackingObject.function_name() << std::endl; + std::cout << "Number of lines of code where the assertion is located(该断言所在的代码行数):\n" << AssertExceptionDetailTrackingObject.line() << std::endl; + std::cout << "Number of columns of code where the assertion is located(该断言所在的代码列数):\n" << AssertExceptionDetailTrackingObject.column() << std::endl; + + throw std::runtime_error(ErrorMessage); + } + else + { + return; + } + } + +#endif + + /** + * 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, + private_page + }; + + /** + * 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 -using file_handle_type = HANDLE; + SYSTEM_INFO SystemInfo; + GetSystemInfo(&SystemInfo); + return SystemInfo.dwAllocationGranularity; #else -using file_handle_type = int; + return sysconf(_SC_PAGE_SIZE); #endif + }(); + return page_size; + } -// 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 -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; - using const_reverse_iterator = std::reverse_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 - 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::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::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::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::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::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 - 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 - 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 - typename std::enable_if::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::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 - typename std::enable_if::type - conditional_sync(); - template - typename std::enable_if::type conditional_sync(); -}; - -template -bool operator==(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator!=(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator<(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator<=(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator>(const basic_mmap& a, - const basic_mmap& b); - -template -bool operator>=(const basic_mmap& a, - const basic_mmap& b); - -/** - * This is the basis for all read-only mmap objects and should be preferred over - * directly using `basic_mmap`. - */ -template -using basic_mmap_source = basic_mmap; - -/** - * This is the basis for all read-write mmap objects and should be preferred over - * directly using `basic_mmap`. - */ -template -using basic_mmap_sink = basic_mmap; - -/** - * 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; -using ummap_source = basic_mmap_source; - -using mmap_sink = basic_mmap_sink; -using ummap_sink = basic_mmap_sink; - -/** - * 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`, or similar), or a - * `mmap_source::handle_type`. - */ -template -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(token, offset, length, error); -} - -template -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`, or similar), or a - * `mmap_sink::handle_type`. - */ -template -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(token, offset, length, error); -} - -template -mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) -{ - return make_mmap_sink(token, 0, map_entire_file, error); -} + /** + * 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 -// #include "detail/mmap.ipp" -/* 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" -/* 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. - */ +#endif // MIO_PAGE_HEADER #ifndef MIO_STRING_UTIL_HEADER #define MIO_STRING_UTIL_HEADER -#include +namespace mio { + namespace detail { + #if __cplusplus >= 201103L && __cplusplus <= 201703L + #include + inline std::wstring cpp2017_string2wstring(const std::string& _string) + { + using convert_typeX = std::codecvt_utf8; + std::wstring_convert converterX; + + return converterX.from_bytes(_string); + } + + inline std::string cpp2017_wstring2string(const std::wstring& _wstring) + { + using convert_typeX = std::codecvt_utf8; + std::wstring_convert converterX; + + return converterX.to_bytes(_wstring); + } + #endif + + inline std::wstring string2wstring(const std::string& _string) + { + ::setlocale(LC_ALL, ""); + std::vector wide_character_buffer; + std::size_t source_string_count = 1; + std::size_t found_not_ascii_count = 0; + for (auto begin = _string.begin(), end = _string.end(); begin != end; begin++) + { + if (static_cast(*begin) > 0) + { + ++source_string_count; + } + else if (static_cast(*begin) < 0) + { + ++found_not_ascii_count; + } + } + + std::size_t target_wstring_count = source_string_count + (found_not_ascii_count / 2); + + wide_character_buffer.resize(target_wstring_count); + + #if defined(_MSC_VER) + std::size_t _converted_count = 0; + ::mbstowcs_s(&_converted_count, &wide_character_buffer[0], target_wstring_count, _string.c_str(), ((size_t)-1)); + #else + ::mbstowcs(&wide_character_buffer[0], _string.c_str(), target_wstring_count); + #endif + + std::size_t _target_wstring_size = 0; + for (auto begin = wide_character_buffer.begin(), end = wide_character_buffer.end(); begin != end && *begin != L'\0'; begin++) + { + ++_target_wstring_size; + } + std::wstring _wstring{ wide_character_buffer.data(), _target_wstring_size }; + + #if defined(_MSC_VER) + if (_converted_count == 0) + { + throw std::runtime_error("The function string2wstring is not work !"); + } + #endif + + if (found_not_ascii_count > 0) + { + //Need Contains character('\0') then check size + if (((_target_wstring_size + 1) - source_string_count) != (found_not_ascii_count / 2)) + { + throw std::runtime_error("The function string2wstring, An error occurs during conversion !"); + } + else + { + return _wstring; + } + } + else + { + //Need Contains character('\0') then check size + if ((_target_wstring_size + 1) != source_string_count) + { + throw std::runtime_error("The function string2wstring, An error occurs during conversion !"); + } + else + { + return _wstring; + } + } + + } + + inline std::string wstring2string(const std::wstring& _wstring) + { + ::setlocale(LC_ALL, ""); + std::vector character_buffer; + std::size_t source_wstring_count = 1; + std::size_t found_not_ascii_count = 0; + for (auto begin = _wstring.begin(), end = _wstring.end(); begin != end; begin++) + { + if (static_cast(*begin) < 256) + { + ++source_wstring_count; + } + else if (static_cast(*begin) >= 256) + { + ++found_not_ascii_count; + } + } + std::size_t target_string_count = source_wstring_count + found_not_ascii_count * 2; + + character_buffer.resize(target_string_count); + + #if defined(_MSC_VER) + std::size_t _converted_count = 0; + ::wcstombs_s(&_converted_count, &character_buffer[0], target_string_count, _wstring.c_str(), ((size_t)-1)); + #else + ::wcstombs(&character_buffer[0], _wstring.c_str(), target_string_count); + #endif + + std::size_t _target_string_size = 0; + for (auto begin = character_buffer.begin(), end = character_buffer.end(); begin != end && *begin != '\0'; begin++) + { + ++_target_string_size; + } + std::string _string{ character_buffer.data(), _target_string_size }; + + #if defined(_MSC_VER) + if (_converted_count == 0) + { + throw std::runtime_error("The function wstring2string is not work !"); + } + #endif + + if (found_not_ascii_count > 0) + { + if (((_target_string_size + 1) - source_wstring_count) != (found_not_ascii_count * 2)) + { + throw std::runtime_error("The function wstring2string, An error occurs during conversion !"); + } + else + { + return _string; + } + } + else + { + if ((_target_string_size + 1) != source_wstring_count) + { + throw std::runtime_error("The function wstring2string, An error occurs during conversion !"); + } + else + { + return _string; + } + } + } + } +} namespace mio { -namespace detail { - -#if __cplusplus >= 201103L && __cplusplus < 202002L - -template< - typename S, - typename C = typename std::decay::type, - typename = decltype(std::declval().data()), - typename = typename std::enable_if< - std::is_same::value -#ifdef _WIN32 - || std::is_same::value -#endif - >::type -> struct char_type_helper { - using type = typename C::value_type; -}; - -template -struct char_type { - using type = typename char_type_helper::type; -}; - -// TODO: can we avoid this brute force approach? -template<> -struct char_type { - using type = char; -}; - -template<> -struct char_type { - using type = char; -}; - -template -struct char_type { - using type = char; -}; - -template -struct char_type { - using type = char; -}; - -#ifdef _WIN32 -template<> -struct char_type { - using type = wchar_t; -}; - -template<> -struct char_type { - using type = wchar_t; -}; - -template -struct char_type { - using type = wchar_t; -}; - -template -struct char_type { - using type = wchar_t; -}; -#endif // _WIN32 - -template -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 -struct is_c_str -{ - static constexpr bool value = is_c_str_helper::value; -}; - -#ifdef _WIN32 -template -struct is_c_wstr -{ - static constexpr bool value = is_c_str_helper::value; -}; -#endif // _WIN32 - -template -struct is_c_str_or_c_wstr -{ - static constexpr bool value = is_c_str::value -#ifdef _WIN32 - || is_c_wstr::value -#endif - ; -}; - -template< - typename String, - typename = decltype(std::declval().data()), - typename = typename std::enable_if::value>::type -> const typename char_type::type* c_str(const String& path) -{ - return path.data(); -} - -template< - typename String, - typename = decltype(std::declval().empty()), - typename = typename std::enable_if::value>::type -> bool empty(const String& path) -{ - return path.empty(); -} - -template< - typename String, - typename = typename std::enable_if::value>::type -> const typename char_type::type* c_str(String path) -{ - return path; -} - -template< - typename String, - typename = typename std::enable_if::value>::type -> bool empty(String path) -{ - return !path || (*path == 0); -} - -#else + namespace detail { #include -template requires std::same_as -#ifdef _WIN32 - || std::same_as -#endif -struct type_helper -{ - static constexpr bool is_character_type() - { - if constexpr(std::is_pointer_v) - { - return std::same_as>>>; - } - else if constexpr(std::is_array_v) - { - return std::same_as>>>; - } - else - { - return std::same_as>>; - } - } -}; + template + struct normalized { + using type = std::remove_cvref_t< + std::conditional_t, + std::remove_pointer_t, + T> + >; + }; -template -constexpr bool is_char_type = type_helper::is_character_type(); + template + struct normalized { + using type = std::remove_cvref_t; + }; + + template + using normalized_t = typename normalized::type; + + template + struct type_helper { + static constexpr bool is_character_type() { + return std::same_as>; + } + }; + + template + constexpr bool is_char_type = type_helper::is_character_type(); #ifdef _WIN32 -template -constexpr bool is_wchar_type = type_helper::is_character_type(); + template + constexpr bool is_wchar_type = type_helper::is_character_type(); -template -constexpr bool is_char_or_wchar_type = is_wchar_type || is_char_type; + template + constexpr bool is_char_or_wchar_type = is_char_type || is_wchar_type; #else -template -constexpr bool is_char_or_wchar_type = is_char_type; + template + constexpr bool is_char_or_wchar_type = is_char_type; #endif -template -concept have_string_function_type = requires(AnyType object) -{ - object.data(); - object.c_str(); - std::convertible_to; -}; - -template -concept is_string_type = have_string_function_type && std::is_base_of_v>; + template + concept narrow_string_like = + std::is_class_v> && // 必须是类类型 + requires(T t) { + { t.data() } -> std::convertible_to; // data() 返回 const char* + { t.c_str() } -> std::same_as; // c_str() 必须是 const char* + { t.empty() } -> std::convertible_to; // empty() 返回 bool + } && std::is_same_v::value_type, char>; // 内部字符类型必须为 char #ifdef _WIN32 -template -concept is_wstring_type = have_string_function_type && std::is_base_of_v>; - -template requires is_wstring_type -const wchar_t* c_str(const StringType& path) -{ - return path.data(); -} + template + concept wide_string_like = + std::is_class_v> && // 必须是类类型 + requires(T t) { + { t.data() } -> std::convertible_to; // data() 返回 const wchar_t* + { t.c_str() } -> std::same_as; // c_str() 必须是 const wchar_t* + { t.empty() } -> std::convertible_to; // empty() 返回 bool + } && std::is_same_v::value_type, wchar_t>; // 内部字符类型必须为 wchar_t #endif -template requires is_string_type -const char* c_str(const StringType& path) -{ - return path.data(); -} + template + concept filesystem_path_like = std::same_as>, std::filesystem::path>; -template requires is_string_type #ifdef _WIN32 - || is_wstring_type + // Windows 平台:允许窄字符、宽字符字符串以及文件系统路径 + template + concept stringable_path = narrow_string_like || + wide_string_like || + filesystem_path_like; +#else + // 非 Windows 平台:仅允许窄字符字符串和文件系统路径 + template + concept stringable_path = narrow_string_like || + filesystem_path_like; #endif -bool empty(StringType path) -{ - return path.empty(); -} -#endif // __cplusplus >= 201103L && __cplusplus < 202002L +#ifdef _WIN32 + template + const wchar_t* c_str(const StringType& s) { + return s.data(); + } +#endif + template + const char* c_str(const StringType& s) { + return s.data(); + } -} // namespace detail + template +#ifdef _WIN32 + requires (narrow_string_like || wide_string_like) +#else + requires (narrow_string_like) +#endif + bool empty(const StringType& s) { + if(stringable_path) + { + return s.empty(); + } + else + { + return s == nullptr; + } + } + + } // namespace detail } // namespace mio #endif // MIO_STRING_UTIL_HEADER - -#include - -#ifndef _WIN32 -# include -# include -# include -# include -#endif +// -- Base Class -- namespace mio { -namespace detail { + // 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 }; + + // This value represents an invalid file handle type. This can be used to + // determine whether `basic_mmap::file_handle` is valid, for example. #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(s.length()); -// auto buffer = std::vector(s_length); -// const auto wide_char_count = MultiByteToWideChar(CP_UTF8, 0, s.c_str(), s_length, buffer.data(), s_length); -// if (wide_char_count == 0) -// { -// const auto error = GetLastError(); -// DebugBreak(); -// } -// return std::wstring(buffer.data(), wide_char_count); -//} - -//std::string ws_2_s(const std::wstring& ws) -//{ -// if (ws.empty()) -// return{}; -// -// const auto ws_length = static_cast(ws.length()); -// auto buffer = std::vector(ws_length); -// const auto char_count = WideCharToMultiByte(CP_UTF8, 0, ws.c_str(), ws_length, buffer.data(), ws_length); -// if (char_count == 0) -// { -// const auto error = GetLastError(); -// DebugBreak(); -// } -// return std::string(buffer.data(), char_count); -//} - -#if __cplusplus >= 201103L && __cplusplus < 202002L - -template< - typename String, - typename = typename std::enable_if< - std::is_same::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 std::enable_if< - std::is_same::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); -} - + using file_handle_type = HANDLE; + static const file_handle_type invalid_handle = INVALID_HANDLE_VALUE; #else + using file_handle_type = int; + constexpr file_handle_type invalid_handle = -1; +#endif -template -file_handle_type open_file_helper(const StringType& path, const access_mode mode) -{ - if constexpr (is_string_type) - { - std::wstring ws_path { string2wstring(path) }; - return open_file_helper(ws_path, mode); - } - if constexpr (is_wstring_type) - { - std::wstring ws_path { std::move(path) }; - return open_file_helper(ws_path, mode); - } -} + template + 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; + using const_reverse_iterator = std::reverse_iterator; + using iterator_category = std::random_access_iterator_tag; + using handle_type = file_handle_type; -template<> -inline file_handle_type open_file_helper(const std::wstring& 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); -} + static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); -#endif // __cplusplus >= 201103L && __cplusplus < 202002L + private: + // Points to the first requested byte, and not to the actual start of the mapping. + pointer data_ = nullptr; -} // win -#endif // _WIN32 + // 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; -/** - * 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; + // 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 - error.assign(GetLastError(), std::system_category()); + 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 + 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::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::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::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::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::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]; } + + void map_sp(const char* path, const size_type offset, const size_type length, std::error_code& error); +#ifdef _WIN32 + void map_wsp(const wchar_t* path, const size_type offset, const size_type length, std::error_code& error); +#endif + + /** + * 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 + 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 + void map(const StringPath & 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 + typename std::enable_if::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::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 + typename std::enable_if::type + conditional_sync(); + template + typename std::enable_if::type conditional_sync(); + }; + + template + bool operator==(const basic_mmap& a, + const basic_mmap& b); + + template + bool operator!=(const basic_mmap& a, + const basic_mmap& b); + + template + bool operator<(const basic_mmap& a, + const basic_mmap& b); + + template + bool operator<=(const basic_mmap& a, + const basic_mmap& b); + + template + bool operator>(const basic_mmap& a, + const basic_mmap& b); + + template + bool operator>=(const basic_mmap& a, + const basic_mmap& b); + + /** + * This is the basis for all read-only mmap objects and should be preferred over + * directly using `basic_mmap`. + */ + template + using basic_mmap_source = basic_mmap; + + /** + * This is the basis for all read-write mmap objects and should be preferred over + * directly using `basic_mmap`. + */ + template + using basic_mmap_sink = basic_mmap; + + /** + * 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; + using ummap_source = basic_mmap_source; + + using mmap_sink = basic_mmap_sink; + using ummap_sink = basic_mmap_sink; + + /** + * 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; + } + + // Generic template for mmap_source for various MappingToken types + template + 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(token, offset, length, error); + } + + // Overload specialized for std::filesystem::path + inline mmap_source make_mmap_source(const std::filesystem::path& path, + mmap_source::size_type offset, + mmap_source::size_type length, + std::error_code& error) + { + // Convert filesystem path to std::string and call the generic make_mmap function. + return make_mmap(path, offset, length, error); + } + + // Overload for mmap_source that omits offset and length parameters (defaults to 0 and map_entire_file). + template + mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) + { + return make_mmap_source(token, 0, map_entire_file, error); + } + + // Generic template for mmap_sink for various MappingToken types + template + 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(token, offset, length, error); + } + + // Overload specialized for std::filesystem::path for mmap_sink + inline mmap_sink make_mmap_sink(const std::filesystem::path& path, + mmap_sink::size_type offset, + mmap_sink::size_type length, + std::error_code& error) + { + return make_mmap(path.string(), offset, length, error); + } + + // Overload for mmap_sink that omits offset and length parameters (defaults to 0 and map_entire_file). + template + mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) + { + return make_mmap_sink(token, 0, map_entire_file, error); + } + +} // namespace mio + +#ifndef MIO_BASIC_MMAP_IMPL +#define MIO_BASIC_MMAP_IMPL + +namespace mio +{ + // -- OS API --- + + namespace detail + { + + 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()); + error.assign(errno, std::system_category()); #endif - return error; + return error; + } + + // Struct providing cross-platform file opening utilities + struct mio_open_file_helper + { + + #ifdef _WIN32 + /** + * Windows-specific helper function to open a file given a wide-character (wstring) path. + * @param path The file path as a wide-character string. + * @param mode The access mode (read or write). + * @return A file handle if successful, invalid_handle if an error occurs. + */ + static file_handle_type open_file_helper_wstring(const std::wstring& path, const access_mode mode) + { + DWORD desired_access = (mode == access_mode::read) ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); + DWORD flags_and_attributes = (mode == access_mode::private_page) ? FILE_ATTRIBUTE_NORMAL : (mode == access_mode::read ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_TEMPORARY); + return ::CreateFileW + ( + path.c_str(), + desired_access, + FILE_SHARE_READ | FILE_SHARE_WRITE, //ShareMode + nullptr, //SecurityAttributes + OPEN_EXISTING, //CreationDisposition + flags_and_attributes, //FlagsAndAttributes + nullptr + ); + } + + /** + * Windows-specific helper function to open a file given a narrow-character (string) path. + * @param path The file path as a narrow-character string. + * @param mode The access mode (read or write). + * @return A file handle if successful, invalid_handle if an error occurs. + */ + static file_handle_type open_file_helper(const std::string& path, const access_mode mode) + { + std::wstring ws_path{ mio::detail::string2wstring(path) }; + return open_file_helper_wstring(ws_path, mode); + } + + /** + * Windows-specific helper function to open a file given a std::filesystem::path. + * @param path The file path as a std::filesystem::path object. + * @param mode The access mode (read or write). + * @return A file handle if successful, invalid_handle if an error occurs. + */ + static file_handle_type open_file_helper_filesystem_path(const std::filesystem::path& path, const access_mode mode) + { + return open_file_helper_wstring(path.wstring(), mode); + } + #endif // _WIN32 + + /** + * Internal helper function to check if a given path is empty. + * The function is specialized for stringable paths. + * @param path The path to check. + * @return True if the path is empty, false otherwise. + */ + template + static bool is_empty(const Path& path) + { + if constexpr (detail::stringable_path) + return path.empty(); + else + return path == nullptr; + } + + /** + * Internal platform-specific implementation to open a file based on the path type. + * This function determines the appropriate platform-specific API to call. + * @param path The file path to open. + * @param mode The access mode (read or write). + * @return A file handle if successful, invalid_handle if an error occurs. + */ + template + static file_handle_type open_file_implement(const Path& path, const access_mode mode) + { + file_handle_type handle = invalid_handle; + + //For windows, we need to use CreateFileW to open a file with a wide-character path. + #ifdef _WIN32 + if constexpr (std::same_as) + handle = open_file_helper(path, mode); + else if constexpr (std::same_as) + handle = open_file_helper_wstring(path, mode); + else if constexpr (std::same_as) + handle = open_file_helper_filesystem_path(path, mode); + else + // Attempt to convert other types to std::string + handle = open_file_helper(std::string(path), mode); + #else + //For macos and linux with POSIX stdandard, we can use the open() function to open a file with a narrow-character path. + + int flags = mode == access_mode::read ? O_RDONLY : O_RDWR; + #ifdef _LARGEFILE64_SOURCE + flags |= O_LARGEFILE; + #endif + if constexpr (std::same_as) + handle = ::open(path.c_str(), flags, S_IRWXU); + else + handle = ::open(c_str(path), flags, S_IRWXU); + #endif + return handle; + } + + /** + * Public interface to open a file with various path types. + * It returns a file handle and also sets an error code if any issues arise. + * @param path The file path to open (can be string, wstring, or filesystem::path). + * @param mode The access mode (read or write). + * @param error The error code to capture any errors. + * @return A file handle if successful, invalid_handle if an error occurs. + */ + template + static file_handle_type open_file(const Path& path, const access_mode mode, std::error_code& error) + { + error.clear(); + if (is_empty(path)) { + error = std::make_error_code(std::errc::invalid_argument); + return invalid_handle; + } + file_handle_type handle = open_file_implement(path, mode); + + if (handle == invalid_handle) + error = last_error(); + + return handle; + } + + /** + * Internal struct to support file literal types (narrow or wide strings) as file paths. + * This struct helps handle string literals with different character encodings. + */ + struct file_literal + { + enum class literal_type { narrow, wide } type; + union { + const char* narrow; + const wchar_t* wide; + }; + + file_literal(const char* s) : type(literal_type::narrow), narrow(s) {} + file_literal(const wchar_t* s) : type(literal_type::wide), wide(s) {} + + /** + * Converts the file literal to a std::string (narrow string) representation. + * @return The converted narrow string. + */ + std::string to_string() const { + if (type == literal_type::narrow) + return std::string(narrow); + else + return mio::detail::wstring2string(std::wstring(wide)); + } + + /** + * Converts the file literal to a std::wstring (wide string) representation. + * @return The converted wide string. + */ + std::wstring to_wstring() const { + if (type == literal_type::wide) + return std::wstring(wide); + else + return mio::detail::string2wstring(std::string(narrow)); + } + }; + + /** + * Overloaded function to open a file using file literal types (either narrow or wide strings). + * This helps open files using string literals directly. + * @param literal The file literal (either narrow or wide string). + * @param mode The access mode (read or write). + * @param error The error code to capture any errors. + * @return A file handle if successful, invalid_handle if an error occurs. + */ + static file_handle_type open_file(const file_literal& literal, const access_mode mode, std::error_code& error) + { + error.clear(); + bool empty_literal = false; + if (literal.type == file_literal::literal_type::narrow) + empty_literal = (literal.narrow == nullptr || literal.narrow[0] == '\0'); + else + empty_literal = (literal.wide == nullptr || literal.wide[0] == L'\0'); + + if (empty_literal) { + error = std::make_error_code(std::errc::invalid_argument); + return invalid_handle; + } + + #ifdef _WIN32 + file_handle_type handle = (literal.type == file_literal::literal_type::narrow) + ? open_file_helper(literal.to_string(), mode) + : open_file_helper_wstring(literal.to_wstring(), mode); + #else + // On POSIX platforms, treat all literals as narrow strings + std::string pathStr = literal.to_string(); + file_handle_type handle = ::open(pathStr.c_str(), mode == access_mode::read ? O_RDONLY : O_RDWR); + #endif + if (handle == invalid_handle) + error = last_error(); + return handle; + } + + /** + * Overloaded function to support opening files using a const char* path literal. + * @param path The file path as a const char* string. + * @param mode The access mode (read or write). + * @param error The error code to capture any errors. + * @return A file handle if successful, invalid_handle if an error occurs. + */ + static file_handle_type open_file(const char* path, const access_mode mode, std::error_code& error) + { + return open_file(file_literal(path), mode, error); + } + + /** + * Overloaded function to support opening files using a const wchar_t* path literal. + * @param path The file path as a const wchar_t* string. + * @param mode The access mode (read or write). + * @param error The error code to capture any errors. + * @return A file handle if successful, invalid_handle if an error occurs. + */ + static file_handle_type open_file(const wchar_t* path, const access_mode mode, std::error_code& error) + { + return open_file(file_literal(path), mode, error); + } + }; + + static inline mio_open_file_helper open_file_helper; + + /** + * Queries the size of the file associated with the given file handle. + * This function retrieves the file size in a platform-dependent manner (Windows or POSIX). + * @param handle The file handle to query. + * @param error The error code to capture any errors that occur during the operation. + * @return The size of the file in bytes, or 0 if an error occurs. + */ + 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(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 representing the context for memory-mapped file access. + * It holds the mapped data and related information such as file length and the mapping handle. + */ + struct mmap_context + { + char* data; ///< Pointer to the start of the mapped memory + int64_t length; ///< The length of the memory-mapped region + int64_t mapped_length; ///< The actual length of the memory-mapped region + #ifdef _WIN32 + file_handle_type file_mapping_handle; ///< Handle to the file mapping object on Windows + #endif + }; + + /** + * Maps a file into memory at a specified offset and length. + * This function creates a memory-mapped region of the file to allow efficient access to its contents. + * @param file_handle The handle to the file to be mapped. + * @param offset The offset within the file where the mapping should start. + * @param length The length of the region to map, starting from the offset. + * @param mode The access mode (read or write) for the memory-mapped region. + * @param error The error code to capture any errors that occur during the mapping operation. + * @return An `mmap_context` struct containing information about the memory-mapped region, or an empty context if an error occurs. + */ + 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) + { + // Align the offset to the page boundary + const int64_t aligned_offset = make_offset_page_aligned(offset); + // Calculate the length of the region to map + const int64_t length_to_map = offset - aligned_offset + length; + + #ifdef _WIN32 + DWORD protect = (mode == access_mode::private_page) ? PAGE_WRITECOPY : (mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE); + const int64_t max_file_size = offset + length; + // Create a file mapping object on Windows + const auto file_mapping_handle = ::CreateFileMapping + ( + file_handle, + 0, //FileMappingAttributes + protect, //Protect + 0, //MaximumSizeHigh + 0, //MaximumSizeLow + 0 //Name + ); + + if (file_mapping_handle == invalid_handle) + { + error = detail::last_error(); + return {}; + } + + DWORD desired_access = (mode == access_mode::private_page) ? FILE_MAP_COPY : (mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE); + // Map the file view into memory + char* mapping_start = static_cast + ( + ::MapViewOfFileEx + ( + file_mapping_handle, //FileMappingObject + desired_access, //DesiredAccess + static_cast(aligned_offset >> 32), //FileOffsetHigh + static_cast(aligned_offset & 0xffffffff), //FileOffsetLow + static_cast(length_to_map) != static_cast(-1) ? length_to_map : 0, //NumberOfBytesToMap + reinterpret_cast(0) //BaseAddress + ) + ); + + //char* mapping_start = static_cast + //( + // ::MapViewOfFile + // ( + // file_mapping_handle, //FileMappingObject + // desired_access, //DesiredAccess + // static_cast(aligned_offset >> 32), //FileOffsetHigh + // static_cast(aligned_offset & 0xffffffff), //FileOffsetLow + // static_cast(length_to_map) != static_cast(-1) ? length_to_map : 0 //NumberOfBytesToMap + // ) + //); + + if (mapping_start == nullptr) + { + // Close file handle if mapping failed + ::CloseHandle(file_mapping_handle); + error = detail::last_error(); + return {}; + } + #else // POSIX + // Create a memory mapping on POSIX + char* mapping_start = static_cast + ( + ::mmap + ( + const_cast(0), // No hint on where to map + length_to_map, + mode == access_mode::read ? PROT_READ : (PROT_READ | PROT_WRITE), + mode == access_mode::private_page ? MAP_PRIVATE : MAP_SHARED, + file_handle, + aligned_offset + ) + ); + if (mapping_start == MAP_FAILED) + { + error = detail::last_error(); + return {}; + } + #endif + + // Return a context containing the memory-mapped region details + mmap_context ctx; + ctx.data = mapping_start + offset - aligned_offset; // Adjust the pointer to the original offset + ctx.length = length; + ctx.mapped_length = length_to_map; + #ifdef _WIN32 + ctx.file_mapping_handle = file_mapping_handle; // Store file mapping handle on Windows + #endif + return ctx; + } + + inline void sync_memory_map(auto* data_pointer, auto* mapping_pointer, size_t mapped_length, mio::file_handle_type file_handle) + { + if (data_pointer != nullptr) + { + + #ifdef _WIN32 + if (::FlushViewOfFile(mapping_pointer, mapped_length) == 0 || ::FlushFileBuffers(file_handle) == 0) + #else // POSIX + if (::msync(mapping_pointer, mapped_length, MS_SYNC) != 0) + #endif + { + std::string error_message = "FlushViewOfFile or Sync failed: " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); + } + } + + #ifdef _WIN32 + if (::FlushFileBuffers(file_handle) == 0) + { + std::string error_message = "FlushFileBuffers failed: " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); + } + #endif + } + } } -template -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; - } +namespace mio { + + // -- basic_mmap -- + + template + basic_mmap::~basic_mmap() + { + conditional_sync(); + unmap(); + } + + template + basic_mmap::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 - 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); + , file_mapping_handle_(std::move(other.file_mapping_handle_)) #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(); + , 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 - LARGE_INTEGER file_size; - if(::GetFileSizeEx(handle, &file_size) == 0) - { - error = detail::last_error(); - return 0; - } - return static_cast(file_size.QuadPart); -#else // POSIX - struct stat sbuf; - if(::fstat(handle, &sbuf) == -1) - { - error = detail::last_error(); - return 0; - } - return sbuf.st_size; + other.file_mapping_handle_ = invalid_handle; #endif -} + } -struct mmap_context -{ - char* data; - int64_t length; - int64_t mapped_length; + template + basic_mmap& + basic_mmap::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_handle_type file_mapping_handle; + file_mapping_handle_ = std::move(other.file_mapping_handle_); #endif -}; + is_handle_internal_ = std::move(other.is_handle_internal_); -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; + // 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 - 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(::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(::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 {}; - } + other.file_mapping_handle_ = invalid_handle; #endif - mmap_context ctx; - ctx.data = mapping_start + offset - aligned_offset; - ctx.length = length; - ctx.mapped_length = length_to_map; + other.is_handle_internal_ = false; + } + return *this; + } + + template + typename basic_mmap::handle_type + basic_mmap::mapping_handle() const noexcept + { #ifdef _WIN32 - ctx.file_mapping_handle = file_mapping_handle; -#endif - return ctx; -} - -} // namespace detail - -// -- basic_mmap -- - -template -basic_mmap::~basic_mmap() -{ - conditional_sync(); - unmap(); -} - -template -basic_mmap::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 -basic_mmap& -basic_mmap::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 -typename basic_mmap::handle_type -basic_mmap::mapping_handle() const noexcept -{ -#ifdef _WIN32 - return file_mapping_handle_; + return file_mapping_handle_; #else - return file_handle_; + return file_handle_; #endif -} + } -template -template -void basic_mmap::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; - } + template + void basic_mmap::map_sp(const char* path, const size_type offset, + const size_type length, std::error_code& error) + { + error.clear(); + if (path == nullptr || path[0] == '\0') + { + error = std::make_error_code(std::errc::invalid_argument); + return; + } + const auto handle = mio::detail::mio_open_file_helper::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; - } -} + map(handle, offset, length, error); + if (!error) + { + is_handle_internal_ = true; + } + } -template -void basic_mmap::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(ctx.data); - length_ = ctx.length; - mapped_length_ = ctx.mapped_length; #ifdef _WIN32 - file_mapping_handle_ = ctx.file_mapping_handle; + template + void basic_mmap::map_wsp(const wchar_t* path, const size_type offset, + const size_type length, std::error_code& error) + { + error.clear(); + if (path == nullptr || path[0] == L'\0') + { + error = std::make_error_code(std::errc::invalid_argument); + return; + } + const auto handle = mio::detail::mio_open_file_helper::open_file(path, AccessMode, error); + if (error) + { + return; + } + + map(handle, offset, length, error); + if (!error) + { + is_handle_internal_ = true; + } + } #endif - } -} -template -template -typename std::enable_if::type -basic_mmap::sync(std::error_code& error) -{ - error.clear(); - if(!is_open()) - { - error = std::make_error_code(std::errc::bad_file_descriptor); - return; - } + /** + * Maps a file into memory using a path specified as a string-like object. + * + * This overload supports various path types (e.g. std::string, std::wstring, std::filesystem::path) + * via the underlying mio_open_file_helper::open_file() function. It first opens the file using the + * provided path and access mode. If the file is successfully opened, it then performs the memory mapping + * by calling the overload that accepts a file handle. + * + * @tparam String The type of the string representing the file path. + * @param path The file path to open and map. + * @param offset The offset within the file where the mapping should begin. + * @param length The length of the region to map. If set to a special value (e.g., map_entire_file), + * the mapping will extend to the end of the file. + * @param error A reference to a std::error_code object that will be set if any error occurs. + */ + template + template + void basic_mmap::map(const String& path, const size_type offset, + const size_type length, std::error_code& error) + { + error.clear(); - if(data()) - { + // Open the file using the underlying platform-specific API. + // The mio_open_file_helper::open_file() supports std::string, std::wstring, and std::filesystem::path. + const auto handle = mio::detail::mio_open_file_helper::open_file(path, AccessMode, error); + if (error) + return; + + // Proceed to map the file into memory using the obtained file handle. + map(handle, offset, length, error); + if (error) + return; + + // Set internal flag indicating that the file handle was not internally managed + // (i.e., it was provided externally by the user through the file opening process). + is_handle_internal_ = true; + } + + /** + * Maps a file into memory using an already opened file handle. + * + * This function performs several operations: + * 1. It clears the error code and validates that the provided file handle is not invalid. + * 2. It queries the file size using a platform-specific query_file_size() function. + * 3. It verifies that the requested mapping region [offset, offset+length) lies within the file bounds. + * 4. It creates a memory-mapped region of the file by calling the platform-specific memory_map() function. + * + * Note that: + * // 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. + * + * If a previous mapping exists, it is unmapped only after the new mapping has been successfully created, + * ensuring that if the new mapping fails, the internal state remains unchanged. + * + * 5. It updates the internal state (such as file_handle_, data pointer, mapping lengths, + * and, on Windows, the file_mapping_handle_) to reflect the new mapping. + * + * @param handle The file handle of the file to be mapped. + * @param offset The offset within the file from which to start mapping. + * @param length The length of the memory region to map. If length is equal to map_entire_file, + * then the mapping will extend from the offset to the end of the file. + * @param error A reference to a std::error_code object that will be set if an error occurs during + * any of the operations. + */ + template + void basic_mmap::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; + } + + // Query the size of the file using a platform-specific implementation. + const auto file_size = detail::query_file_size(handle, error); + if (error) + { + return; + } + + // Validate that the requested mapping region [offset, offset+length) is within the file bounds. + if (offset + length > file_size) + { + error = std::make_error_code(std::errc::invalid_argument); + return; + } + + // Create a memory mapping of the file region. + // If length equals map_entire_file, the mapping extends from offset to the end of the file. + 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(); + + // Update internal state with the new mapping details. + file_handle_ = handle; + is_handle_internal_ = false; + data_ = reinterpret_cast(ctx.data); + length_ = ctx.length; + mapped_length_ = ctx.mapped_length; + #ifdef _WIN32 + file_mapping_handle_ = ctx.file_mapping_handle; + #endif + } + } + + template + template + typename std::enable_if::type + basic_mmap::sync(std::error_code& error) + { + error.clear(); + if (!is_open()) + { + error = std::make_error_code(std::errc::bad_file_descriptor); + return; + } + + auto* data_pointer = data(); + auto* mapping_pointer = get_mapping_start(); + + detail::sync_memory_map(data_pointer, mapping_pointer, mapped_length_, file_handle_); + } + + template + void basic_mmap::unmap() + { + if (!is_open()) { return; } + // TODO do we care about errors here? #ifdef _WIN32 - if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 - || ::FlushFileBuffers(file_handle_) == 0) + if (is_mapped()) + { + ::UnmapViewOfFile(get_mapping_start()); + ::CloseHandle(file_mapping_handle_); + } #else // POSIX - if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) + if (data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } #endif - { - error = detail::last_error(); - return; - } - } -#ifdef _WIN32 - if(::FlushFileBuffers(file_handle_) == 0) - { - error = detail::last_error(); - } -#endif -} -template -void basic_mmap::unmap() -{ - if(!is_open()) { return; } - // TODO do we care about errors here? + // 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 - if(is_mapped()) - { - ::UnmapViewOfFile(get_mapping_start()); - ::CloseHandle(file_mapping_handle_); - } + ::CloseHandle(file_handle_); #else // POSIX - if(data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } + ::close(file_handle_); #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_) - { + // Reset fields to their default values. + data_ = nullptr; + length_ = mapped_length_ = 0; + file_handle_ = invalid_handle; #ifdef _WIN32 - ::CloseHandle(file_handle_); + file_mapping_handle_ = invalid_handle; +#endif + } + + template + bool basic_mmap::is_mapped() const noexcept + { +#ifdef _WIN32 + return file_mapping_handle_ != invalid_handle; #else // POSIX - ::close(file_handle_); + return this->is_open(); #endif - } + } - // Reset fields to their default values. - data_ = nullptr; - length_ = mapped_length_ = 0; - file_handle_ = invalid_handle; + template + void basic_mmap::swap(basic_mmap& other) + { + if (this != &other) + { + using std::swap; + swap(data_, other.data_); + swap(file_handle_, other.file_handle_); #ifdef _WIN32 - file_mapping_handle_ = invalid_handle; + 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 -bool basic_mmap::is_mapped() const noexcept -{ -#ifdef _WIN32 - return file_mapping_handle_ != invalid_handle; -#else // POSIX - return is_open(); -#endif -} + template + template + typename std::enable_if::type + basic_mmap::conditional_sync() + { + // This is invoked from the destructor, so not much we can do about + // failures here. + std::error_code ec; + sync(ec); + } -template -void basic_mmap::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 + template + typename std::enable_if::type + basic_mmap::conditional_sync() + { + // noop + } -template -template -typename std::enable_if::type -basic_mmap::conditional_sync() -{ - // This is invoked from the destructor, so not much we can do about - // failures here. - std::error_code ec; - sync(ec); -} + template + bool operator==(const basic_mmap& a, + const basic_mmap& b) + { + return a.data() == b.data() + && a.size() == b.size(); + } -template -template -typename std::enable_if::type -basic_mmap::conditional_sync() -{ - // noop -} + template + bool operator!=(const basic_mmap& a, + const basic_mmap& b) + { + return !(a == b); + } -template -bool operator==(const basic_mmap& a, - const basic_mmap& b) -{ - return a.data() == b.data() - && a.size() == b.size(); -} + template + bool operator<(const basic_mmap& a, + const basic_mmap& b) + { + if (a.data() == b.data()) { return a.size() < b.size(); } + return a.data() < b.data(); + } -template -bool operator!=(const basic_mmap& a, - const basic_mmap& b) -{ - return !(a == b); -} + template + bool operator<=(const basic_mmap& a, + const basic_mmap& b) + { + return !(a > b); + } -template -bool operator<(const basic_mmap& a, - const basic_mmap& b) -{ - if(a.data() == b.data()) { return a.size() < b.size(); } - return a.data() < b.data(); -} + template + bool operator>(const basic_mmap& a, + const basic_mmap& b) + { + if (a.data() == b.data()) { return a.size() > b.size(); } + return a.data() > b.data(); + } -template -bool operator<=(const basic_mmap& a, - const basic_mmap& b) -{ - return !(a > b); -} - -template -bool operator>(const basic_mmap& a, - const basic_mmap& b) -{ - if(a.data() == b.data()) { return a.size() > b.size(); } - return a.data() > b.data(); -} - -template -bool operator>=(const basic_mmap& a, - const basic_mmap& b) -{ - return !(a < b); -} + template + bool operator>=(const basic_mmap& a, + const basic_mmap& b) + { + return !(a < b); + } } // namespace mio #endif // MIO_BASIC_MMAP_IMPL - #endif // MIO_MMAP_HEADER -/* 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 -#else -# include -#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 -/* 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 // std::error_code -#include // 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; - std::shared_ptr pimpl_; + /** + * 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; + std::shared_ptr 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; + 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; + 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(std::move(mmap))) - {} + /** Takes ownership of an existing mmap object. */ + basic_shared_mmap(mmap_type&& mmap) + : pimpl_(std::make_shared(std::move(mmap))) + { + } - /** Takes ownership of an existing mmap object. */ - basic_shared_mmap& operator=(mmap_type&& mmap) - { - pimpl_ = std::make_shared(std::move(mmap)); - return *this; - } + /** Takes ownership of an existing mmap object. */ + basic_shared_mmap& operator=(mmap_type&& mmap) + { + pimpl_ = std::make_shared(std::move(mmap)); + return *this; + } - /** Initializes this object with an already established shared mmap. */ - basic_shared_mmap(std::shared_ptr mmap) : pimpl_(std::move(mmap)) {} + /** Initializes this object with an already established shared mmap. */ + basic_shared_mmap(std::shared_ptr mmap) : pimpl_(std::move(mmap)) {} - /** Initializes this object with an already established shared mmap. */ - basic_shared_mmap& operator=(std::shared_ptr mmap) - { - pimpl_ = std::move(mmap); - return *this; - } + /** Initializes this object with an already established shared mmap. */ + basic_shared_mmap& operator=(std::shared_ptr 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 - 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. + */ + template + 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); } - } + /** + * 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; + /** + * 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 get_shared_ptr() { return pimpl_; } + /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ + std::shared_ptr 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; - } + /** + * 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; - } + 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 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(); } + /** + * 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; - } + /** + * `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::type - > pointer data() noexcept { return pimpl_->data(); } - const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } + /** + * 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::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 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::type - > iterator end() noexcept { return pimpl_->end(); } - const_iterator end() const noexcept { return pimpl_->end(); } - const_iterator cend() const noexcept { return pimpl_->cend(); } + /** + * 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::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::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 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::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::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 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::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]; } + /** + * 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 - 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. + * + * `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 + 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 - 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. + * + * `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 + 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. + * + * `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); - } + /** + * 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(); } + /** + * 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_); } + 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::type - > void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); } + /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > void sync(std::error_code& error) { if (pimpl_) pimpl_->sync(error); } - /** All operators compare the underlying `basic_mmap`'s addresses. */ + /** 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.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 == 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_; + } - 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 - 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(token, offset, length, error); - if(error) { return; } - pimpl_ = std::make_shared(std::move(mmap)); - } - else - { - pimpl_->map(token, offset, length, error); - } - } -}; + private: + template + 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(token, offset, length, error); + if (error) { return; } + pimpl_ = std::make_shared(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 -using basic_shared_mmap_source = basic_shared_mmap; + /** + * This is the basis for all read-only mmap objects and should be preferred over + * directly using basic_shared_mmap. + */ + template + using basic_shared_mmap_source = basic_shared_mmap; -/** - * This is the basis for all read-write mmap objects and should be preferred over - * directly using basic_shared_mmap. - */ -template -using basic_shared_mmap_sink = basic_shared_mmap; + /** + * This is the basis for all read-write mmap objects and should be preferred over + * directly using basic_shared_mmap. + */ + template + using basic_shared_mmap_sink = basic_shared_mmap; -/** - * 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; -using shared_ummap_source = basic_shared_mmap_source; + /** + * 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; + using shared_ummap_source = basic_shared_mmap_source; -using shared_mmap_sink = basic_shared_mmap_sink; -using shared_ummap_sink = basic_shared_mmap_sink; + using shared_mmap_sink = basic_shared_mmap_sink; + using shared_ummap_sink = basic_shared_mmap_sink; } // namespace mio diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt deleted file mode 100644 index 652ede4..0000000 --- a/test/CMakeLists.txt +++ /dev/null @@ -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() diff --git a/test/example.cpp b/test/example.cpp index 841a57f..4fbbaed 100644 --- a/test/example.cpp +++ b/test/example.cpp @@ -1,4 +1,3 @@ -#include #include // for std::error_code #include // for std::printf #include diff --git a/test/test.cpp b/test/test.cpp index 82827f9..f51c364 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -1,182 +1,236 @@ -#include -#include - #include #include -#include #include #include #include #include +#include -#ifndef _WIN32 -#include -#include -#include -#endif +#include "../single_include/mio/mio.hpp" // Just make sure this compiles. -#ifdef CXX17 # include using mmap_source = mio::basic_mmap_source; -#endif template void test_at_offset(const MMap& file_view, const std::string& buffer, - const size_t offset); -void test_at_offset(const std::string& buffer, const char* path, - const size_t offset, std::error_code& error); -int handle_error(const std::error_code& error); + const size_t offset); +inline void test_at_offset(const std::string& buffer, const char* path, + const size_t offset, std::error_code& error); +inline int handle_error(const std::error_code& error); + +inline void allocate_file(const std::string& path, const int size) +{ + std::ofstream file(path); + std::string s(size, '0'); + file << s; +} + +inline int handle_error(const std::error_code& error) +{ + const auto& errmsg = error.message(); + std::printf("Error mapping file: %s, exiting...\n", errmsg.c_str()); + return error.value(); +} + +inline int test_rewrite_file() +{ + const auto path = "test_rewrite.txt"; + + // NOTE: mio does *not* create the file for you if it doesn't exist! You + // must ensure that the file exists before establishing a mapping. It + // must also be non-empty. So for illustrative purposes the file is + // created now. + allocate_file(path, 204800); + + // Read-write memory map the whole file by using `map_entire_file` where the + // length of the mapping is otherwise expected, with the factory method. + std::error_code error; + mio::mmap_sink rw_mmap = mio::make_mmap_sink( + path, 0, mio::map_entire_file, error); + if (error) { return handle_error(error); } + + // You can use any iterator based function. + std::fill(rw_mmap.begin(), rw_mmap.end(), 'a'); + + // Or manually iterate through the mapped region just as if it were any other + // container, and change each byte's value (since this is a read-write mapping). + for (auto& b : rw_mmap) + { + b += 10; + } + + // Or just change one value with the subscript operator. + const int answer_index = rw_mmap.size() / 2; + rw_mmap[answer_index] = 42; + + // Don't forget to flush changes to disk before unmapping. However, if + // `rw_mmap` were to go out of scope at this point, the destructor would also + // automatically invoke `sync` before `unmap`. + rw_mmap.sync(error); + if (error) { return handle_error(error); } + + // We can then remove the mapping, after which rw_mmap will be in a default + // constructed state, i.e. this and the above call to `sync` have the same + // effect as if the destructor had been invoked. + rw_mmap.unmap(); + + // Now create the same mapping, but in read-only mode. Note that calling the + // overload without the offset and file length parameters maps the entire + // file. + mio::mmap_source ro_mmap; + ro_mmap.map(path, error); + if (error) { return handle_error(error); } + + const int the_answer_to_everything = ro_mmap[answer_index]; + assert(the_answer_to_everything == 42); +} int main() { - std::error_code error; + std::error_code error; - // Make sure mio compiles with non-const char* strings too. - const char _path[] = "test-file"; - const int path_len = sizeof(_path); - char* path = new char[path_len]; - std::copy(_path, _path + path_len, path); + // Make sure mio compiles with non-const char* strings too. + const char _path[] = "test-file"; + const int path_len = sizeof(_path); + char* path = new char[path_len]; + std::copy(_path, _path + path_len, path); - const auto page_size = mio::page_size(); - // Fill buffer, then write it to file. - const int file_size = 4 * page_size - 250; // 16134, if page size is 4KiB - std::string buffer(file_size, 0); - // Start at first printable ASCII character. - char v = 33; - for (auto& b : buffer) { - b = v; - ++v; - // Limit to last printable ASCII character. - v %= 126; - if(v == 0) { - v = 33; - } - } + const auto page_size = mio::page_size(); + // Fill buffer, then write it to file. + const int file_size = 4 * page_size - 250; // 16134, if page size is 4KiB + std::string buffer(file_size, 0); + // Start at first printable ASCII character. + char v = 33; + for (auto& b : buffer) { + b = v; + ++v; + // Limit to last printable ASCII character. + v %= 126; + if (v == 0) { + v = 33; + } + } - std::ofstream file(path); - file << buffer; - file.close(); + std::ofstream file(path); + file << buffer; + file.close(); - // Test whole file mapping. - test_at_offset(buffer, path, 0, error); - if (error) { return handle_error(error); } + // Test whole file mapping. + test_at_offset(buffer, path, 0, error); + if (error) { return handle_error(error); } - // Test starting from below the page size. - test_at_offset(buffer, path, page_size - 3, error); - if (error) { return handle_error(error); } + //Test starting from below the page size. + test_at_offset(buffer, path, page_size - 3, error); + if (error) { return handle_error(error); } - // Test starting from above the page size. - test_at_offset(buffer, path, page_size + 3, error); - if (error) { return handle_error(error); } + // Test starting from above the page size. + test_at_offset(buffer, path, page_size + 3, error); + if (error) { return handle_error(error); } - // Test starting from above the page size. - test_at_offset(buffer, path, 2 * page_size + 3, error); - if (error) { return handle_error(error); } + // Test starting from above the page size. + test_at_offset(buffer, path, 2 * page_size + 3, error); + if (error) { return handle_error(error); } - { + { #define CHECK_INVALID_MMAP(m) do { \ assert(error); \ assert(m.empty()); \ assert(!m.is_open()); \ error.clear(); } while(0) - mio::mmap_source m; + mio::mmap_source m; - // See if mapping an invalid file results in an error. - m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error); - CHECK_INVALID_MMAP(m); + // See if mapping an invalid file results in an error. + m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error); + CHECK_INVALID_MMAP(m); - // Empty path? - m = mio::make_mmap_source(static_cast(0), 0, 0, error); - CHECK_INVALID_MMAP(m); - m = mio::make_mmap_source(std::string(), 0, 0, error); - CHECK_INVALID_MMAP(m); + // Empty path? + m = mio::make_mmap_source(static_cast(0), 0, 0, error); + CHECK_INVALID_MMAP(m); + m = mio::make_mmap_source(std::string(), 0, 0, error); + CHECK_INVALID_MMAP(m); - // Invalid handle? - m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error); - CHECK_INVALID_MMAP(m); + // Invalid handle? + m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error); + CHECK_INVALID_MMAP(m); - // Invalid offset? - m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error); - CHECK_INVALID_MMAP(m); - } + // Invalid offset? + m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error); + CHECK_INVALID_MMAP(m); + } - // Make sure these compile. - { - mio::ummap_source _1; - mio::shared_ummap_source _2; - // Make sure shared_mmap mapping compiles as all testing was done on - // normal mmaps. - mio::shared_mmap_source _3(path, 0, mio::map_entire_file); - auto _4 = mio::make_mmap_source(path, error); - auto _5 = mio::make_mmap(path, 0, mio::map_entire_file, error); + // Make sure these compile. + { + mio::ummap_source _1; + mio::shared_ummap_source _2; + // Make sure shared_mmap mapping compiles as all testing was done on + // normal mmaps. + mio::shared_mmap_source _3(path, 0, mio::map_entire_file); + auto _4 = mio::make_mmap_source(path, error); + auto _5 = mio::make_mmap(path, 0, mio::map_entire_file, error); #ifdef _WIN32 - const wchar_t* wpath1 = L"dasfsf"; - auto _6 = mio::make_mmap_source(wpath1, error); - mio::mmap_source _7; - _7.map(wpath1, error); - const std::wstring wpath2 = wpath1; - auto _8 = mio::make_mmap_source(wpath2, error); - mio::mmap_source _9; - _9.map(wpath1, error); + const wchar_t* wpath1 = L"dasfsf"; + auto _6 = mio::make_mmap_source(wpath1, error); + mio::mmap_source _7; + _7.map(wpath1, error); + const std::wstring wpath2 = wpath1; + auto _8 = mio::make_mmap_source(wpath2, error); + mio::mmap_source _9; + _9.map(wpath1, error); #else - const int fd = open(path, O_RDONLY); - mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file); - _fdmmap.unmap(); - _fdmmap.map(fd, error); + const int fd = open(path, O_RDONLY); + mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file); + _fdmmap.unmap(); + _fdmmap.map(fd, error); #endif - } + } - std::printf("all tests passed!\n"); + if (test_rewrite_file()) + throw std::runtime_error("test_rewrite_file failed"); + + std::printf("all tests passed!\n"); } void test_at_offset(const std::string& buffer, const char* path, - const size_t offset, std::error_code& error) + const size_t offset, std::error_code& error) { - // Sanity check. - assert(offset < buffer.size()); + // Sanity check. + assert(offset < buffer.size()); - // Map the region of the file to which buffer was written. - mio::mmap_source file_view = mio::make_mmap_source( - path, offset, mio::map_entire_file, error); - if(error) { return; } + // Map the region of the file to which buffer was written. + mio::mmap_source file_view = mio::make_mmap_source( + path, offset, mio::map_entire_file, error); + if (error) { return; } - assert(file_view.is_open()); - const size_t mapped_size = buffer.size() - offset; - assert(file_view.size() == mapped_size); + assert(file_view.is_open()); + const size_t mapped_size = buffer.size() - offset; + assert(file_view.size() == mapped_size); - test_at_offset(file_view, buffer, offset); + test_at_offset(file_view, buffer, offset); - // Turn file_view into a shared mmap. - mio::shared_mmap_source shared_file_view(std::move(file_view)); - assert(!file_view.is_open()); - assert(shared_file_view.is_open()); - assert(shared_file_view.size() == mapped_size); + // Turn file_view into a shared mmap. + mio::shared_mmap_source shared_file_view(std::move(file_view)); + assert(!file_view.is_open()); + assert(shared_file_view.is_open()); + assert(shared_file_view.size() == mapped_size); - //test_at_offset(shared_file_view, buffer, offset); + //test_at_offset(shared_file_view, buffer, offset); } template void test_at_offset(const MMap& file_view, const std::string& buffer, - const size_t offset) + const size_t offset) { - // Then verify that mmap's bytes correspond to that of buffer. - for(size_t buf_idx = offset, view_idx = 0; - buf_idx < buffer.size() && view_idx < file_view.size(); - ++buf_idx, ++view_idx) { - if(file_view[view_idx] != buffer[buf_idx]) { - std::printf("%luth byte mismatch: expected(%d) <> actual(%d)", - buf_idx, buffer[buf_idx], file_view[view_idx]); - std::cout << std::flush; - assert(0); - } - } -} - -int handle_error(const std::error_code& error) -{ - const auto& errmsg = error.message(); - std::printf("Error mapping file: %s, exiting...\n", errmsg.c_str()); - return error.value(); + // Then verify that mmap's bytes correspond to that of buffer. + for (size_t buf_idx = offset, view_idx = 0; + buf_idx < buffer.size() && view_idx < file_view.size(); + ++buf_idx, ++view_idx) { + if (file_view[view_idx] != buffer[buf_idx]) { + std::printf("%luth byte mismatch: expected(%d) <> actual(%d)", + buf_idx, buffer[buf_idx], file_view[view_idx]); + std::cout << std::flush; + assert(0); + } + } } diff --git a/third_party/LICENSE.md b/third_party/LICENSE.md deleted file mode 100644 index 23edca5..0000000 --- a/third_party/LICENSE.md +++ /dev/null @@ -1,28 +0,0 @@ -amalgamate.py - Amalgamate C source and header files -Copyright (c) 2012, Erik Edlund - -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. - diff --git a/third_party/amalgamate.py b/third_party/amalgamate.py deleted file mode 100644 index 174e1c5..0000000 --- a/third_party/amalgamate.py +++ /dev/null @@ -1,300 +0,0 @@ -#!/usr/bin/env python -# coding=utf-8 - -# amalgamate.py - Amalgamate C source and header files. -# Copyright (c) 2012, Erik Edlund -# -# 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.*?)("|>)', 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 "): - 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 "): - 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() - diff --git a/third_party/config.json b/third_party/config.json deleted file mode 100644 index fa2e71a..0000000 --- a/third_party/config.json +++ /dev/null @@ -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"] -} -