From 6a0e66b9127b8f9dff8baefbacc7982bfbe75111 Mon Sep 17 00:00:00 2001 From: Twilight-Dream-Of-Magic <31034152+Twilight-Dream-Of-Magic@users.noreply.github.com> Date: Thu, 9 Jun 2022 07:56:31 +0800 Subject: [PATCH 1/5] Forward To C++ 2020 and Compatible C++ 2011 [C++ 2020 template concept support] with repository https://github.com/mandreyel/mio/ Compatible with c++ standard 2011, and add c++ standard 2020 code. --- single_include/mio/mio.hpp | 312 +++++++++++++++++++++++++++++++++++-- 1 file changed, 303 insertions(+), 9 deletions(-) diff --git a/single_include/mio/mio.hpp b/single_include/mio/mio.hpp index c568a46..1b5d762 100644 --- a/single_include/mio/mio.hpp +++ b/single_include/mio/mio.hpp @@ -42,6 +42,155 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +#include +#include + +#if __cplusplus >= 201103L && __cplusplus <= 201703L + +#include + +std::wstring cpp2017_string2wstring(const std::string &_string) +{ + using convert_typeX = std::codecvt_utf8; + std::wstring_convert converterX; + + return converterX.from_bytes(_string); +} + +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); + std::size_t _converted_count = 0; + ::mbstowcs_s(&_converted_count, &wide_character_buffer[0], target_wstring_count, _string.c_str(), ((size_t)-1)); + + 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(_converted_count == 0) + { + throw std::runtime_error("The function string2wstring is not work !"); + } + else + { + 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); + ::size_t _converted_count = 0; + ::wcstombs_s(&_converted_count, &character_buffer[0], target_string_count, _wstring.c_str(), ((size_t)-1)); + + 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(_converted_count == 0) + { + throw std::runtime_error("The function wstring2string is not work !"); + } + else + { + 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 @@ -53,6 +202,14 @@ 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. @@ -623,6 +780,8 @@ mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) namespace mio { namespace detail { +#if __cplusplus >= 201103L && __cplusplus < 202002L + template< typename S, typename C = typename std::decay::type, @@ -761,6 +920,86 @@ template< return !path || (*path == 0); } +#else + +#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 +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_char_or_wchar_type = is_wchar_type || is_char_type; +#else +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>; + +#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(); +} +#endif + +template requires is_string_type +const char* c_str(const StringType& path) +{ + return path.data(); +} + +template requires is_string_type +#ifdef _WIN32 + || is_wstring_type +#endif +bool empty(StringType path) +{ + return path.empty(); +} + +#endif // __cplusplus >= 201103L && __cplusplus < 202002L + } // namespace detail } // namespace mio @@ -794,15 +1033,39 @@ 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); -} +//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, @@ -835,6 +1098,37 @@ typename std::enable_if< 0); } +#else + +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<> +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); +} + +#endif // __cplusplus >= 201103L && __cplusplus < 202002L + } // win #endif // _WIN32 From 6d020039c72403f97f9499398f4ce8a486c64945 Mon Sep 17 00:00:00 2001 From: Twilight-Dream-Of-Magic <31034152+Twilight-Dream-Of-Magic@users.noreply.github.com> Date: Thu, 9 Jun 2022 08:04:53 +0800 Subject: [PATCH 2/5] Update mio.hpp --- single_include/mio/mio.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/single_include/mio/mio.hpp b/single_include/mio/mio.hpp index 1b5d762..85c3c42 100644 --- a/single_include/mio/mio.hpp +++ b/single_include/mio/mio.hpp @@ -49,7 +49,7 @@ #include -std::wstring cpp2017_string2wstring(const std::string &_string) +inline std::wstring cpp2017_string2wstring(const std::string &_string) { using convert_typeX = std::codecvt_utf8; std::wstring_convert converterX; @@ -57,7 +57,7 @@ std::wstring cpp2017_string2wstring(const std::string &_string) return converterX.from_bytes(_string); } -std::string cpp2017_wstring2string(const std::wstring &_wstring) +inline std::string cpp2017_wstring2string(const std::wstring &_wstring) { using convert_typeX = std::codecvt_utf8; std::wstring_convert converterX; From f05574baf6fe51e15f4ffa3f73dde49c31bf365a Mon Sep 17 00:00:00 2001 From: Twilight-Dream-Of-Magic <31034152+Twilight-Dream-Of-Magic@users.noreply.github.com> Date: Sat, 13 Aug 2022 05:28:15 +0800 Subject: [PATCH 3/5] Updated UTF-8 conversion functions The GCC compiler is now supported --- single_include/mio/mio.hpp | 93 ++++++++++++++++++++------------------ 1 file changed, 49 insertions(+), 44 deletions(-) diff --git a/single_include/mio/mio.hpp b/single_include/mio/mio.hpp index 85c3c42..e7f6c30 100644 --- a/single_include/mio/mio.hpp +++ b/single_include/mio/mio.hpp @@ -42,13 +42,7 @@ * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#include -#include - #if __cplusplus >= 201103L && __cplusplus <= 201703L - -#include - inline std::wstring cpp2017_string2wstring(const std::string &_string) { using convert_typeX = std::codecvt_utf8; @@ -87,8 +81,13 @@ inline std::wstring string2wstring(const std::string& _string) 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++) @@ -97,37 +96,38 @@ inline std::wstring string2wstring(const std::string& _string) } 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 { - if(found_not_ascii_count > 0) + //Need Contains character('\0') then check size + if((_target_wstring_size + 1) != source_string_count) { - //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; - } + throw std::runtime_error("The function string2wstring, An error occurs during conversion !"); } 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; - } + return _wstring; } } + } inline std::string wstring2string(const std::wstring& _wstring) @@ -147,11 +147,16 @@ inline std::string wstring2string(const std::wstring& _wstring) ++found_not_ascii_count; } } - std::size_t target_string_count = source_wstring_count + found_not_ascii_count * 2; + std::size_t target_string_count = source_wstring_count + found_not_ascii_count * 2; character_buffer.resize(target_string_count); - ::size_t _converted_count = 0; + + #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++) @@ -160,33 +165,33 @@ inline std::string wstring2string(const std::wstring& _wstring) } 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(found_not_ascii_count > 0) + if((_target_string_size + 1) != source_wstring_count) { - 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; - } + throw std::runtime_error("The function wstring2string, An error occurs during conversion !"); } else { - if((_target_string_size + 1) != source_wstring_count) - { - throw std::runtime_error("The function wstring2string, An error occurs during conversion !"); - } - else - { - return _string; - } + return _string; } } } 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 4/5] Neat code: single_include/mio.hpp Refactor everything code --- CMakeLists.txt | 243 +- 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 | 3848 ++++++++++++++-------------- test/CMakeLists.txt | 25 - test/example.cpp | 1 - test/test.cpp | 334 ++- third_party/LICENSE.md | 28 - third_party/amalgamate.py | 300 --- third_party/config.json | 11 - 17 files changed, 2189 insertions(+), 4307 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) - -# -# 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) +# Enable CTest support if BUILD_TESTING is enabled +include(CTest) +if(BUILD_TESTING) + enable_testing() +endif() -# -# 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) - -# -# 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 - $ - $) +target_include_directories(mio INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/single_include) + +# 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) - - 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) + # 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() - 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 { - -// 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; +#include -/** - * 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; +#include -using mmap_sink = basic_mmap_sink; -using ummap_sink = basic_mmap_sink; +#include -/** - * 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) +namespace mio { - 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); -} +#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; + } + } -/** - * 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); -} +#endif -template -mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) -{ - return make_mmap_sink(token, 0, map_entire_file, error); -} + /** + * 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 + 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 -// #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 < 202002L + 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; + } + } + } + } +} -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; -}; +namespace mio { + namespace detail { -#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; -}; +#include -#ifdef _WIN32 -template -struct is_c_wstr -{ - static constexpr bool value = is_c_str_helper::value; -}; -#endif // _WIN32 + template + struct normalized { + using type = std::remove_cvref_t< + std::conditional_t, + std::remove_pointer_t, + T> + >; + }; + + 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(); -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); -} + template + constexpr bool is_wchar_type = type_helper::is_character_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; +#endif -#include + 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 -template requires std::same_as #ifdef _WIN32 - || std::same_as + 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 -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 -constexpr bool is_char_type = type_helper::is_character_type(); -#ifdef _WIN32 -template -constexpr bool is_wchar_type = type_helper::is_character_type(); + template + concept filesystem_path_like = std::same_as>, std::filesystem::path>; -template -constexpr bool is_char_or_wchar_type = is_wchar_type || is_char_type; +#ifdef _WIN32 + // Windows 平台:允许窄字符、宽字符字符串以及文件系统路径 + template + concept stringable_path = narrow_string_like || + wide_string_like || + filesystem_path_like; #else -template -constexpr bool is_char_or_wchar_type = is_char_type; + // 非 Windows 平台:仅允许窄字符字符串和文件系统路径 + template + concept stringable_path = narrow_string_like || + filesystem_path_like; #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>; - #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 + const wchar_t* c_str(const StringType& s) { + return s.data(); + } #endif + template + const char* c_str(const StringType& s) { + return s.data(); + } -template requires is_string_type -const char* c_str(const StringType& path) -{ - return path.data(); -} - -template requires is_string_type + template #ifdef _WIN32 - || is_wstring_type + requires (narrow_string_like || wide_string_like) +#else + requires (narrow_string_like) #endif -bool empty(StringType path) -{ - return path.empty(); -} - -#endif // __cplusplus >= 201103L && __cplusplus < 202002L - -} // namespace detail + 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 { - -#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); -} - -#else - -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<> -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); -} - -#endif // __cplusplus >= 201103L && __cplusplus < 202002L + // 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 }; -} // 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; + // 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 - error.assign(GetLastError(), std::system_category()); + using file_handle_type = HANDLE; + static const file_handle_type invalid_handle = INVALID_HANDLE_VALUE; #else - error.assign(errno, std::system_category()); + using file_handle_type = int; + constexpr file_handle_type invalid_handle = -1; #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; - } + 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 - 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); + handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; #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 -} + // 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; -struct mmap_context -{ - char* data; - int64_t length; - int64_t mapped_length; -#ifdef _WIN32 - file_handle_type file_mapping_handle; -#endif -}; +#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 -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; + /** + * `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 - ctx.file_mapping_handle = file_mapping_handle; + void map_wsp(const wchar_t* path, const size_type offset, const size_type length, std::error_code& error); #endif - return ctx; -} -} // namespace detail + /** + * 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); + } -// -- basic_mmap -- +} // namespace mio -template -basic_mmap::~basic_mmap() -{ - conditional_sync(); - unmap(); -} +#ifndef MIO_BASIC_MMAP_IMPL +#define MIO_BASIC_MMAP_IMPL -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_)) +namespace mio { - other.data_ = nullptr; - other.length_ = other.mapped_length_ = 0; - other.file_handle_ = invalid_handle; + // -- OS API --- + + namespace detail + { + + inline std::error_code last_error() noexcept + { + std::error_code error; #ifdef _WIN32 - other.file_mapping_handle_ = invalid_handle; + error.assign(GetLastError(), std::system_category()); +#else + error.assign(errno, std::system_category()); #endif + 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 -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_); +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 - file_mapping_handle_ = std::move(other.file_mapping_handle_); + , 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; + , 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; + 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 -{ + } + + 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 - return file_mapping_handle_; -#else - return file_handle_; + file_mapping_handle_ = std::move(other.file_mapping_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; + 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 - file_mapping_handle_ = ctx.file_mapping_handle; + other.file_mapping_handle_ = invalid_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()) - { + other.is_handle_internal_ = false; + } + return *this; + } + + template + typename basic_mmap::handle_type + basic_mmap::mapping_handle() const noexcept + { #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) + return file_mapping_handle_; +#else + return file_handle_; #endif - { - error = detail::last_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); + if (!error) + { + is_handle_internal_ = true; + } + } + #ifdef _WIN32 - if(::FlushFileBuffers(file_handle_) == 0) - { - error = detail::last_error(); - } + 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 -void basic_mmap::unmap() -{ - if(!is_open()) { return; } - // TODO do we care about errors here? + /** + * 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(); + + // 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(is_mapped()) - { - ::UnmapViewOfFile(get_mapping_start()); - ::CloseHandle(file_mapping_handle_); - } + if (is_mapped()) + { + ::UnmapViewOfFile(get_mapping_start()); + ::CloseHandle(file_mapping_handle_); + } #else // POSIX - if(data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } + 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_) - { + // 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_); + ::CloseHandle(file_handle_); #else // POSIX - ::close(file_handle_); + ::close(file_handle_); #endif - } + } - // Reset fields to their default values. - data_ = nullptr; - length_ = mapped_length_ = 0; - file_handle_ = invalid_handle; + // Reset fields to their default values. + data_ = nullptr; + length_ = mapped_length_ = 0; + file_handle_ = invalid_handle; #ifdef _WIN32 - file_mapping_handle_ = invalid_handle; + file_mapping_handle_ = invalid_handle; #endif -} + } -template -bool basic_mmap::is_mapped() const noexcept -{ + template + bool basic_mmap::is_mapped() const noexcept + { #ifdef _WIN32 - return file_mapping_handle_ != invalid_handle; + return file_mapping_handle_ != invalid_handle; #else // POSIX - return is_open(); + return this->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_); + } + + 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_); + 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); -} + 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 - #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_; - -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; - } + /** + * 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); } - } + /** + * 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; + /** + * 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 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; - - // 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; - } - } - - 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 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, 2 * page_size + 3, error); - if (error) { return handle_error(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); + + 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(); + + // 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 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); } + + { #define CHECK_INVALID_MMAP(m) do { \ assert(error); \ assert(m.empty()); \ assert(!m.is_open()); \ error.clear(); } while(0) - 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); - - // 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 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); + 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); + + // 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 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); #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) -{ - // 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 size_t offset) { - 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"] -} - From 5c237fbca3a5e1bbae8f5f0254ffea0263d3890f Mon Sep 17 00:00:00 2001 From: Twilight-Dream-Of-Magic <1187791459@qq.com> Date: Tue, 1 Apr 2025 23:25:01 +0800 Subject: [PATCH 5/5] Further clean up all code, use the exception mechanism across the board, and make error codes only used internally, when asserting a problem, and not externally represented. Calling the operating system's API makes a lot more sense. Exceptions are definitely thrown for non-existent files. Formatting the source code makes it more readable. Remove the direct use of the c++ 2011 type trait, All switched to c++2020 concepts and type constraints. Update README.md --- README.md | 534 ++++++++++++++---- single_include/MemoryMappingByFile.hpp | 347 ++++++++++++ single_include/mio/mio.hpp | 739 +++++++++++++------------ test/test.cpp | 181 +++--- 4 files changed, 1253 insertions(+), 548 deletions(-) create mode 100644 single_include/MemoryMappingByFile.hpp diff --git a/README.md b/README.md index a9ab409..c606d70 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ # mio -An easy to use header-only cross-platform C++11 memory mapping library with an MIT license. +An easy to use header-only cross-platform C++20 memory mapping library with an MIT license. mio has been created with the goal to be easily includable (i.e. no dependencies) in any C++ project that needs memory mapped file IO without the need to pull in Boost. @@ -15,178 +15,516 @@ Furthermore, Boost.Iostreams' solution requires that the user pick offsets exact Albeit a minor nitpick, Boost.Iostreams implements memory mapped file IO with a `std::shared_ptr` to provide shared semantics, even if not needed, and the overhead of the heap allocation may be unnecessary and/or unwanted. In mio, there are two classes to cover the two use-cases: one that is move-only (basically a zero-cost abstraction over the system specific mmapping functions), and the other that acts just like its Boost.Iostreams counterpart, with shared semantics. -### How to create a mapping -NOTE: the file must exist before creating a mapping. +### How to Create a Memory Mapping -There are three ways to map a file into memory: +> **Note:** The file must exist and be non-empty before mapping. + +There are two primary ways to create a memory mapping: + +#### 1. Using the Constructor + +Directly construct a memory mapping. On failure, a `std::system_error` is thrown. -- Using the constructor, which throws a `std::system_error` on failure: ```c++ mio::mmap_source mmap(path, offset, size_to_map); ``` -or you can omit the `offset` and `size_to_map` arguments, in which case the -entire file is mapped: + +You can also omit the `offset` and `size_to_map` parameters to map the entire file: + ```c++ mio::mmap_source mmap(path); ``` -- Using the factory function: -```c++ -std::error_code error; -mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, error); -``` -or: -```c++ -mio::mmap_source mmap = mio::make_mmap_source(path, error); -``` +#### 2. Using the Member Function + +Alternatively, create an uninitialized mapping and then invoke the `map` member function. Like the constructor, this API throws an exception on error. -- Using the `map` member function: ```c++ -std::error_code error; mio::mmap_source mmap; -mmap.map(path, offset, size_to_map, error); +mmap.map(path, offset, size_to_map); ``` -or: + +Or simply map the entire file: + ```c++ -mmap.map(path, error); +mmap.map(path); ``` -**NOTE:** The constructors **require** exceptions to be enabled. If you prefer -to build your projects with `-fno-exceptions`, you can still use the other ways. -Moreover, in each case, you can provide either some string type for the file's path, or you can use an existing, valid file handle. +**Important:** All these APIs now use exceptions for error reporting. Internally, error codes are still used, but they are hidden from the external interface. + +Moreover, you may provide either a file path (as any common string type) or an existing valid file handle. For example: + ```c++ #include #include #include #include -// #include if using single header #include -int main() -{ - // NOTE: error handling omitted for brevity. +int main() { + // Ensure the file exists before mapping. const int fd = open("file.txt", O_RDONLY); mio::mmap_source mmap(fd, 0, mio::map_entire_file); // ... } ``` -However, mio does not check whether the provided file descriptor has the same access permissions as the desired mapping, so the mapping may fail. Such errors are reported via the `std::error_code` out parameter that is passed to the mapping function. -**WINDOWS USERS**: This library *does* support the use of wide character types -for functions where character strings are expected (e.g. path parameters). +**Windows Users:** Wide character types are supported for path parameters. + +--- ### Example +Below is a sample program that demonstrates both read-write and read-only mappings. Notice that no error code parameters are needed—the APIs throw exceptions upon failure. + ```c++ #include -// #include if using single header -#include // for std::error_code -#include // for std::printf +#include #include #include #include +#include + +void allocate_file(const std::string& path, int size) { + std::ofstream file(path); + file << std::string(size, '0'); +} -int handle_error(const std::error_code& error); -void allocate_file(const std::string& path, const int size); +int main() { + const std::string path = "file.txt"; + allocate_file(path, 155); -int main() + // Create a read-write mapping for the entire file. + mio::mmap_sink rw_mmap = mio::make_mmap_sink(path, 0, mio::map_entire_file); + + // Fill the mapping with 'a' characters. + std::fill(rw_mmap.begin(), rw_mmap.end(), 'a'); + + // Modify each byte. + for (auto& b : rw_mmap) { + b += 10; + } + + // Change a single byte in the middle. + const int mid = rw_mmap.size() / 2; + rw_mmap[mid] = 42; + + // Flush changes and unmap. If the mapping goes out of scope, the destructor will also flush. + rw_mmap.sync(); + rw_mmap.unmap(); + + // Create a read-only mapping of the entire file. + mio::mmap_source ro_mmap; + ro_mmap.map(path); + + // Verify that the modification was successful. + assert(ro_mmap[mid] == 42); + + std::printf("All tests passed!\n"); + return 0; +} +``` + +--- + +### Additional Features + +This version of `mio` takes advantage of modern C++ features: + +- **Source Location and Filesystem:** + Debugging is improved by leveraging `std::source_location` to report detailed context (file name, function name, line, and column) when assertions fail. Filesystem support now enables more natural path handling. + +- **Future C++23 Support:** + In C++23 and above, assertions will include a full stack trace using `std::stacktrace`, making it easier to trace runtime errors. + +For example, the new assertion function might look like: + +```c++ +#if __cplusplus >= 202002L +inline void cpp2020_assert(bool condition, const char* errorMessage, + std::source_location location = std::source_location::current()) { + if (!condition) { + std::cout << "Error: " << errorMessage << "\n" + << "File: " << location.file_name() << "\n" + << "Function: " << location.function_name() << "\n" + << "Line: " << location.line() << "\n" + << "Column: " << location.column() << std::endl; +#if __cplusplus >= 202300L + std::cout << "Stack trace:\n"; + for (const auto& frame : std::stacktrace::current()) + std::cout << frame << std::endl; +#endif + throw std::runtime_error(errorMessage); + } +} +#endif +``` + +--- + +### Test Suite Overview + +The test suite now combines both the new features and the core mapping functionality. It demonstrates: + +- Mapping an entire file or a portion of it. +- Both read-write and read-only mappings. +- Mapping with a file descriptor as well as a file path. +- Validation of mapped content against expected data. +- Handling of invalid mapping cases without exposing internal error codes. + +For example, one test case maps the file at various offsets to verify that the correct segment of the file is mapped: + +```c++ +void test_at_offset(const std::string& buffer, const char* path, size_t offset) { + // Map the file region starting at the given offset. + mio::mmap_source file_view = mio::make_mmap_source(path, offset, mio::map_entire_file); + assert(file_view.is_open()); + // Compare the mapped content to the original buffer... +} +``` + +This comprehensive test case confirms that the modernized `mio` library is robust and adheres to modern C++ best practices. + +### Test Suite + +The test code below serves a dual purpose. It demonstrates the standard usage of the library (mapping, reading, writing, and unmapping files) while also showcasing intentional error cases. In the error cases, invalid inputs (such as an invalid file path, empty path, or incorrect file handle) will trigger internal assertion exceptions. This change highlights that internal error checking no longer uses `std::error_code` in the public API. + +```c++ +#include +#include +#include +#include +#include +#include + +#include "../single_include/mio/mio.hpp" + +// Just make sure this compiles. +#include +using mmap_source = mio::basic_mmap_source; + +template +void test_at_offset(const MMap& file_view, const std::string& buffer, const size_t offset); +inline void test_at_offset(const std::string& buffer, const char* path, const size_t offset); + +inline void allocate_file(const std::string& path, const int size) { - const auto path = "file.txt"; + std::ofstream file(path); + std::string s(size, '0'); + file << s; +} - // 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, 155); +inline void 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 and is non-empty before mapping. + 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; + // Create a read-write mapping for the entire file. mio::mmap_sink rw_mmap = mio::make_mmap_sink( - path, 0, mio::map_entire_file, error); - if (error) { return handle_error(error); } + path, 0, mio::map_entire_file); - // You can use any iterator based function. + // Use any iterator-based function to modify the mapping. 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) { + // Or manually iterate through the mapped region and change each byte. + for (auto& b : rw_mmap) + { b += 10; } - // Or just change one value with the subscript operator. + // Change one specific byte using 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. + // Flush changes and unmap. + rw_mmap.sync(); 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. + // Create a read-only mapping for the entire file. mio::mmap_source ro_mmap; - ro_mmap.map(path, error); - if (error) { return handle_error(error); } + ro_mmap.map(path); const int the_answer_to_everything = ro_mmap[answer_index]; assert(the_answer_to_everything == 42); } -int handle_error(const std::error_code& error) +inline void test_error_case(char* path, const std::string& buffer) { - const auto& errmsg = error.message(); - std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); - return error.value(); + // Macro to check that an invalid mapping results in an empty mapping. +#define CHECK_INVALID_MMAP(m) do { \ + assert(m.empty()); \ + assert(!m.is_open()); \ + } while(0) + + mio::mmap_source m; + + // Attempt mapping an invalid file name. + m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0); + CHECK_INVALID_MMAP(m); + + // Attempt mapping with an empty path. + m = mio::make_mmap_source(static_cast(0), 0, 0); + CHECK_INVALID_MMAP(m); + m = mio::make_mmap_source(std::string(), 0, 0); + CHECK_INVALID_MMAP(m); + + // Attempt mapping with an invalid handle. + m = mio::make_mmap_source(mio::invalid_handle, 0, 0); + CHECK_INVALID_MMAP(m); + + // Attempt mapping with an invalid offset. + m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size()); + CHECK_INVALID_MMAP(m); } -void allocate_file(const std::string& path, const int size) +int main() { + std::system("chcp 65001"); + + // Verify that 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(); + // Prepare a buffer and write it to a file. + const int file_size = 4 * page_size - 250; // For example, 16134 bytes for a 4KiB page size. + std::string buffer(file_size, 0); + // Fill buffer starting at the first printable ASCII character. + char v = 33; + for (auto& b : buffer) { + b = v; + ++v; + // Cycle back after reaching the last printable ASCII character. + v %= 126; + if (v == 0) { + v = 33; + } + } + std::ofstream file(path); - std::string s(size, '0'); - file << s; + file << buffer; + file.close(); + + // Test mapping the whole file. + test_at_offset(buffer, path, 0); + + // Test mapping starting from an offset just below the page size. + test_at_offset(buffer, path, page_size - 3); + + // Test mapping starting from an offset just above the page size. + test_at_offset(buffer, path, page_size + 3); + + // Test mapping starting from an offset further ahead. + test_at_offset(buffer, path, 2 * page_size + 3); + + std::cout << "Continuing with tests..." << std::endl; + + // Uncomment the next line to run tests for error cases. + // Note: In these cases, the internal assertion exceptions will be thrown. + // test_error_case(path, buffer); + + // The following code ensures that all API variations compile correctly. + { + mio::ummap_source _1; + mio::shared_ummap_source _2; + // shared_mmap mapping compiles as well. + mio::shared_mmap_source _3(path, 0, mio::map_entire_file); + auto _4 = mio::make_mmap_source(path); + auto _5 = mio::make_mmap(path, 0, mio::map_entire_file); +#ifdef _WIN32 + const std::wstring wpath1 = L"file"; + + // If the file exists, perform mapping. + if (std::filesystem::exists(wpath1)) + { + auto _6 = mio::make_mmap_source(wpath1); + mio::mmap_source _7; + _7.map(wpath1); + } + else + { + std::wcerr << L"Cannot open file: " << wpath1 << std::endl; + } + + // Even if the file cannot be opened, the following lines are executed. + const std::wstring wpath2 = wpath1 + L"000"; + if (std::filesystem::exists(wpath2)) + { + auto _8 = mio::make_mmap_source(wpath2); + mio::mmap_source _9; + _9.map(wpath1); + } + else + { + std::wcerr << L"Cannot open file: " << wpath2 << std::endl; + } +#else + const char* path = "path_to_file"; // Replace with an actual file path + const int fd = open(path, O_RDONLY); + + if (fd < 0) + { + std::cerr << "Failed to open file: " << path << std::endl; + } + else + { + // File opened successfully; perform mmap operations. + mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file); + // Unmap if needed. + _fdmmap.unmap(); + // Remap using the same file descriptor. + _fdmmap.map(fd); + + // Close the file descriptor if it's no longer needed. + close(fd); + } +#endif + } + + std::printf("all tests passed!\n"); + return 0; } -``` -`mio::basic_mmap` is move-only, but if multiple copies to the same mapping are needed, use `mio::basic_shared_mmap` which has `std::shared_ptr` semantics and has the same interface as `mio::basic_mmap`. -```c++ -#include +void test_at_offset(const std::string& buffer, const char* path, const size_t offset) +{ + // Sanity check. + assert(offset < buffer.size()); -mio::shared_mmap_source shared_mmap1("path", offset, size_to_map); -mio::shared_mmap_source shared_mmap2(std::move(mmap1)); // or use operator= -mio::shared_mmap_source shared_mmap3(std::make_shared(mmap1)); // or use operator= -mio::shared_mmap_source shared_mmap4; -shared_mmap4.map("path", offset, size_to_map, error); -``` + // Map the region of the file starting at the given offset. + mio::mmap_source file_view = mio::make_mmap_source(path, offset, mio::map_entire_file); -It's possible to define the type of a byte (which has to be the same width as `char`), though aliases for the most common ones are provided by default: -```c++ -using mmap_source = basic_mmap_source; -using ummap_source = basic_mmap_source; + 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); + + // Convert the mapping to 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); -using mmap_sink = basic_mmap_sink; -using ummap_sink = basic_mmap_sink; + // Optionally, you can run the test on the shared mapping as well. + // 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) +{ + // Verify that the bytes in the mapping match those in the 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); + } + } +} ``` -But it may be useful to define your own types, say when using the new `std::byte` type in C++17: + ```c++ -using mmap_source = mio::basic_mmap_source; -using mmap_sink = mio::basic_mmap_sink; -``` +#include +#include +#include +#include +#include +#include -Though generally not needed, since mio maps users requested offsets to page boundaries, you can query the underlying system's page allocation granularity by invoking `mio::page_size()`, which is located in `mio/page.hpp`. +// Make sure to include your mio header. +#include "../single_include/mio/mio.hpp" -### Single Header File -Mio can be added to your project as a single header file simply by including `\single_include\mio\mio.hpp`. Single header files can be regenerated at any time by running the `amalgamate.py` script within `\third_party`. +inline void test_rewrite_random_file() +{ + const auto path = "test_rewrite_random.dat"; + + // Ensure the file exists and is non-empty. + // 20MB = 20971520 Bytes + allocate_file(path, 20971520); + + // Seed the random number generator. + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution<> dis(0, 255); + + // Create a read-write mapping for the entire file. + mio::mmap_sink rw_mmap = mio::make_mmap_sink(path, 0, mio::map_entire_file); + + // Fill the mapping with random binary bytes. + for (auto& byte : rw_mmap) + { + byte = static_cast(dis(gen)); + } + + // Choose an offset near the end (e.g., 75% into the file) and write the value 42. + const size_t answer_index = rw_mmap.size() * 3 / 4; + rw_mmap[answer_index] = static_cast(42); + + // Flush changes and unmap. + rw_mmap.sync(); + rw_mmap.unmap(); + + // Reopen the file as a read-only mapping. + mio::mmap_source ro_mmap; + ro_mmap.map(path); + + // Verify that the byte at the chosen offset is 42. + const int the_answer = static_cast(ro_mmap[answer_index]); + assert(the_answer == 42); + + // Print the entire file content in hexadecimal format. + std::cout << "Hex dump of " << path << ":\n"; + for (size_t i = 0; i < ro_mmap.size(); ++i) + { + //last 10240 bytes are printed in one line + if (ro_mmap.size() - 1 - i < 10240) + { + std::cout << std::hex << std::setw(2) << std::setfill('0') + << static_cast(static_cast(ro_mmap[i])) << " "; + if ((i + 1) % 16 == 0) + std::cout << "\n"; + } + } + std::cout << std::dec << "\n"; // Restore default number format. +} + +int main() +{ + test_rewrite_random_file(); +} ``` -python amalgamate.py -c config.json -s ../include + +--- + +### Key Points + +- **Error Handling via Exceptions:** + The revised API now uses internal assertions that throw exceptions when encountering errors. This design change replaces the previous use of `std::error_code` in the public API. In the test cases, mapping failures (for example, due to an invalid path) trigger these assertion exceptions. + +- **Demonstrated Error Cases:** + The `test_error_case` function illustrates various invalid inputs (non-existent files, empty paths, invalid handles, and incorrect offsets). These cases are now shown explicitly to guide developers on how the library responds to erroneous usage. (By default, these tests are commented out to avoid interrupting the normal flow; they can be enabled for debugging.) + +- **Modern C++ Features:** + The test suite (along with the rest of the library) leverages modern C++ features such as source location for better debug messages. Future updates may also incorporate stack traces when using C++23 or later. + +--- + +### Single Header File + +`mio` can be added to your project as a single header file by including: + +```c++ +#include "single_include/mio/mio.hpp" ``` ## CMake diff --git a/single_include/MemoryMappingByFile.hpp b/single_include/MemoryMappingByFile.hpp new file mode 100644 index 0000000..a6c5544 --- /dev/null +++ b/single_include/MemoryMappingByFile.hpp @@ -0,0 +1,347 @@ +/* + * Copyright (C) 2022-2030 Twilight-Dream + * + * 本文件是 mio 的一部分。 + * + * mio 是自由软件:你可以再分发之和/或依照由自由软件基金会发布的 GNU 通用公共许可证修改之,无论是版本 3 许可证,还是(按你的决定)任何以后版都可以。 + * + * 发布 TDOM-EncryptOrDecryptFile-Reborn 是希望它能有用,但是并无保障;甚至连可销售和符合某个特定的目的都不保证。请参看 GNU 通用公共许可证,了解详情。 + * 你应该随程序获得一份 GNU 通用公共许可证的复本。如果没有,请看 。 + */ + + /* + * Copyright (C) 2022-2030 Twilight-Dream + * + * This file is part of mio. + * + * mio is free software: you may redistribute it and/or modify it under the GNU General Public License as published by the Free Software Foundation, either under the Version 3 license, or (at your discretion) any later version. + * + * mio is released in the hope that it will be useful, but there are no guarantees; not even that it will be marketable and fit a particular purpose. Please see the GNU General Public License for details. + * You should get a copy of the GNU General Public License with your program. If not, see . + */ +#pragma once + +#include "mio/mio.hpp" + +//将文件数据进行镜像(映射)到操作系统的内存对象。以实现对磁盘的大文件(这里规定大小大于4GB)的字节流模拟访问和修改 +//Mirroring (mapping) file data to the operating system's memory objects. to enable byte stream emulation access and modification of large files (here specified size > 4 giga byte) on disk +namespace MemoryObjectConfrontationDiskFileData +{ + /* + + Use C++ project mio + + Github https://github.com/mandreyel/mio + + An easy to use header-only cross-platform C++11 memory mapping library with an MIT license. + mio has been created with the goal to be easily includable (i.e. no dependencies) in any C++ project that needs memory mapped file IO without the need to pull in C++ Boost library. + Please feel free to open an issue, I'll try to address any concerns as best I can. + + 一个易于使用的头文件跨平台的C++11内存映射库,拥有MIT许可证。 + 创建mio的目的是为了在任何需要内存映射文件IO的C++项目中都能方便地包含(即没有依赖性),而不需要拉入C++ Boost 库。 + 请随时提出问题,我将尽力解决任何问题。 + + */ + + template + concept TemplateConcept_MemoryMap = std::is_same_v || std::is_same_v || std::is_same_v || std::is_same_v; + + template + concept TemplateConcept_MemoryMap_ReadAndWrite = std::is_same_v || std::is_same_v; + + enum class MemoryMapTypes + { + SIGNED_READ_AND_WRITE = 0, + SIGNED_READ_ONLY = 1, + UNSIGNED_READ_AND_WRITE = 2, + UNSIGNED_READ_ONLY = 3 + }; + + class MemoryMapPointers + { + + private: + + std::unique_ptr pointer_signed_rw; + std::unique_ptr pointer_signed_ro; + std::unique_ptr pointer_unsigned_rw; + std::unique_ptr pointer_unsigned_ro; + + public: + + std::unique_ptr& signed_rw() + { + return pointer_signed_rw; + } + + std::unique_ptr& signed_ro() + { + return pointer_signed_ro; + } + + std::unique_ptr& unsigned_rw() + { + return pointer_unsigned_rw; + } + + std::unique_ptr& unsigned_ro() + { + return pointer_unsigned_ro; + } + + MemoryMapPointers() noexcept : pointer_signed_rw( nullptr ), pointer_signed_ro( nullptr ), pointer_unsigned_rw( nullptr ), pointer_unsigned_ro( nullptr ) {} + + MemoryMapPointers(MemoryMapPointers& _object) = delete; + MemoryMapPointers& operator=(const MemoryMapPointers _object) = delete; + + MemoryMapPointers( MemoryMapTypes map_types ) + { + switch ( map_types ) + { + case MemoryMapTypes::SIGNED_READ_AND_WRITE: + { + this->pointer_signed_rw = std::unique_ptr>( new mio::mmap_sink, std::default_delete() ); + break; + } + case MemoryMapTypes::SIGNED_READ_ONLY: + { + this->pointer_signed_ro = std::unique_ptr>( new mio::mmap_source, std::default_delete() ); + break; + } + case MemoryMapTypes::UNSIGNED_READ_AND_WRITE: + { + this->pointer_unsigned_rw = std::unique_ptr>( new mio::ummap_sink, std::default_delete() ); + break; + } + case MemoryMapTypes::UNSIGNED_READ_ONLY: + { + this->pointer_unsigned_ro = std::unique_ptr>( new mio::ummap_source, std::default_delete() ); + break; + } + default: + break; + } + } + + ~MemoryMapPointers() + { + if ( !( pointer_signed_rw == nullptr ) ) + { + auto* pointer = pointer_signed_rw.release(); + pointer = nullptr; + } + + if ( !( pointer_signed_ro == nullptr ) ) + { + auto* pointer = pointer_signed_ro.release(); + pointer = nullptr; + } + + if ( !( pointer_unsigned_rw == nullptr ) ) + { + auto* pointer = pointer_unsigned_rw.release(); + pointer = nullptr; + } + + if ( !( pointer_unsigned_ro == nullptr ) ) + { + auto* pointer = pointer_unsigned_ro.release(); + pointer = nullptr; + } + } + }; + + template + requires TemplateConcept_MemoryMap + bool MMMO_CheckIsAssocisatedFile( MemoryMapType& mapped_object ); + + inline MemoryMapPointers MakeDefaultMemoryMappingObject( MemoryMapTypes map_types ); + + template + requires TemplateConcept_MemoryMap + std::tuple MMMO_TryAssociateFile_ToPack( const std::filesystem::path& file_path_name, MemoryMapType* memory_map_pointer ); + + template + requires TemplateConcept_MemoryMap + bool MMMO_FromUnpack( std::tuple& associated_data, MemoryMapType& default_memory_map_object); + + template + requires TemplateConcept_MemoryMap_ReadAndWrite + void MMMO_TrySyncDiskFile( MemoryMapType_ReadAndWrite& mapped ); + + template + requires TemplateConcept_MemoryMap + bool UnmappingMemoryMapObject( MemoryMapType& mapped ); + + template + requires TemplateConcept_MemoryMap + inline bool MMMO_CheckIsAssocisatedFile( MemoryMapType& mapped_object ) + { + if ( !mapped_object.is_mapped() ) + { + std::cerr << CommonToolkit::from_u8string(u8"你开玩笑呢?这个内存映射对象根本就没有关联一个文件。") << std::endl; + std::cerr << "Are you kidding me? This memory mapped object is not associated with a file at all." << std::endl; + return false; + } + else + { + return true; + } + } + + //创建一个内存映射对象 + //Create a memory map object + inline MemoryMapPointers MakeDefaultMemoryMappingObject( MemoryMapTypes map_types ) + { + return MemoryMapPointers( map_types ); + } + + //提供一个内存映射对象,然后尝试关联文件 + //Provide a memory map object and then try to associate the file + template + requires TemplateConcept_MemoryMap + std::tuple MMMO_TryAssociateFile_ToPack( const std::filesystem::path& file_path_name, MemoryMapType* memory_map_pointer ) + { + auto this_file_path_name = std::move( file_path_name ); + auto& memory_map_reference = *memory_map_pointer; + + if constexpr( std::same_as, mio::mmap_sink> || std::same_as, mio::ummap_sink> ) + { + std::fstream file_stream_object; + + if ( std::filesystem::exists( file_path_name ) ) + { + std::u8string u8string_extension_name = u8".newfile"; + this_file_path_name += CommonToolkit::from_u8string(u8string_extension_name); + std::filesystem::copy(file_path_name, this_file_path_name); + + //文件打开后立即寻找流的末端 + //seek to the end of stream immediately after open + file_stream_object.open( this_file_path_name, std::ios::in | std::ios::out | std::ios::ate | std::ios::binary ); + + if(file_stream_object.is_open()) + { + //立即关闭文件 + //Close the file now + file_stream_object.close(); + } + } + } + + memory_map_reference.map( this_file_path_name.string(), 0, mio::map_entire_file ); + + if ( !memory_map_reference.is_open() ) + { + std::cerr << CommonToolkit::from_u8string(u8"呃,你确定那个文件的路径确实存在吗?你需要好好检查一下。") << std::endl; + std::cerr << "Uh, are you sure the path to that file actually exists? You need to check it properly." << std::endl; + + std::cerr << CommonToolkit::from_u8string(u8"内存映射对象无效,可能是文件无法访问或者内存不足。\n文件路径是: ") + << "[" << CommonToolkit::from_u8string(file_path_name.u8string()) << "]" << std::endl; + std::cerr << "The memory mapped object is invalid, probably because the file is inaccessible or out of memory.\nThe file path is. " + << "[" << CommonToolkit::from_u8string(file_path_name.u8string()) << "]" << std::endl; + UnmappingMemoryMapObject( memory_map_reference ); + return std::make_tuple( std::move( false ), std::move( memory_map_reference ) ); + } + else + { + if ( MMMO_CheckIsAssocisatedFile( memory_map_reference ) ) + { + std::cout << CommonToolkit::from_u8string(u8"内存映射对象已经关联文件\n文件路径是: ") + << "[" << CommonToolkit::from_u8string(file_path_name.u8string()) << "]" << std::endl; + std::cout << "The memory mapped object has associated files.\nThe file path is: " + << "[" << CommonToolkit::from_u8string(file_path_name.u8string()) << "]" << std::endl; + return std::make_tuple( std::move( true ), std::move( memory_map_reference ) ); + } + else + { + std::cerr << CommonToolkit::from_u8string(u8"内存映射对象不能关联文件,文件路径是: ") + << "[" << CommonToolkit::from_u8string(file_path_name.u8string()) << "]" << std::endl; + std::cerr << "The memory mapped objects cannot be associated with files.\nThe file path is. " + << "[" << CommonToolkit::from_u8string(file_path_name.u8string()) << "]" << std::endl; + UnmappingMemoryMapObject( memory_map_reference ); + return std::make_tuple( std::move( false ), std::move( memory_map_reference ) ); + } + } + } + + template + requires TemplateConcept_MemoryMap + bool MMMO_FromUnpack( std::tuple& associated_data, MemoryMapType& default_memory_map_object ) + { + #if __cplusplus >= 201703L + + auto& [associated_mmap_data_package_status, memory_map_object] = associated_data; + + if ( associated_mmap_data_package_status ) + { + default_memory_map_object = std::move( memory_map_object ); + return associated_mmap_data_package_status; + } + else + { + return associated_mmap_data_package_status; + } + + #else + + bool associated_mmap_data_package_status = std::get( associated_data ); + + if ( associated_mmap_data_package_status ) + { + default_memory_map_object = std::move( std::get( associated_data ) ); + return associated_mmap_data_package_status; + } + else + { + return associated_mmap_data_package_status; + } + + #endif + } + + //已经映射完成的内存对象,只要内存对象管理的数据发生改变时,就需要同步磁盘文件数据进行写入 + //Whenever the memory object is mapped, the data managed by the memory object is changed, the disk file data needs to be synchronized for writing. + template + requires TemplateConcept_MemoryMap_ReadAndWrite + void MMMO_TrySyncDiskFile( MemoryMapType_ReadAndWrite& mapped_object ) + { + if ( mapped_object.is_open() && mapped_object.is_mapped() ) + { + std::cout << CommonToolkit::from_u8string(u8"好了,试着把内存映射对象管理的数据的变化同步到磁盘上。") << std::endl; + std::cout << "OK, try synchronizing the changes in the data managed by the memory mapped object to the disk." << std::endl; + mapped_object.sync(); + } + } + + //提供一个内存映射对象,然后解除关联文件。 + //Provide a memory map object and then unassociate the file + template + requires TemplateConcept_MemoryMap + inline bool UnmappingMemoryMapObject( MemoryMapType& mapped_object ) + { + if ( MMMO_CheckIsAssocisatedFile( mapped_object ) ) + { + mapped_object.unmap(); + return true; + } + else + { + return false; + } + } + + + //测试代码是否可以被编译 + //Test if the code can be compiled + #if 0 + + MemoryMapPointers mmp_pointer_object = MakeDefaultMemoryMappingObject(MIO_LibraryHelper::MemoryMapTypes::SIGNED_READ_AND_WRITE); + auto* managed_pointer = mmp_pointer_object.signed_rw().get(); + auto associated_mmap_data_package = MMMO_TryAssociateFile_ToPack(std::string("./filename.dat"), managed_pointer); + bool associated_mmap_data_unpackage_status; + auto mapped_object = MMMO_FromUnpack(associated_mmap_data_package, associated_mmap_data_unpackage_status); + MMMO_TrySyncDiskFile(mapped_object); + bool unmake_status = UnmappingMemoryMapObject(mapped_object); + + #endif +} // namespace MemoryObjectConfrontationDiskFileData diff --git a/single_include/mio/mio.hpp b/single_include/mio/mio.hpp index c6ef4a4..0c65d45 100644 --- a/single_include/mio/mio.hpp +++ b/single_include/mio/mio.hpp @@ -44,8 +44,6 @@ #include #include -#include - #include // std::error_code #include // std::shared_ptr @@ -64,6 +62,14 @@ #include +#if __cplusplus >= 202002L +#include +#endif + +#if __cplusplus >= 202300L +#include +#endif + namespace mio { @@ -80,19 +86,28 @@ namespace mio inline void my_cpp2020_assert(const bool JudgmentCondition, const char* ErrorMessage, std::source_location AssertExceptionDetailTrackingObject) { - if(!JudgmentCondition) + if (!JudgmentCondition) { - std::system("dhcp 65001"); + std::system("chcp 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; - + + // Print stack trace for C++23 and above +#if __cplusplus >= 202300L + std::cout << "Stack trace before assertion:\n"; + + for (const auto& frame : std::stacktrace::current()) + { + std::cout << frame << std::endl; + } +#endif + throw std::runtime_error(ErrorMessage); } else @@ -157,8 +172,8 @@ namespace mio namespace mio { namespace detail { - #if __cplusplus >= 201103L && __cplusplus <= 201703L - #include +#if __cplusplus >= 201103L && __cplusplus <= 201703L +#include inline std::wstring cpp2017_string2wstring(const std::string& _string) { using convert_typeX = std::codecvt_utf8; @@ -174,7 +189,7 @@ namespace mio { return converterX.to_bytes(_wstring); } - #endif +#endif inline std::wstring string2wstring(const std::string& _string) { @@ -198,12 +213,12 @@ namespace mio { wide_character_buffer.resize(target_wstring_count); - #if defined(_MSC_VER) +#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 +#else ::mbstowcs(&wide_character_buffer[0], _string.c_str(), target_wstring_count); - #endif +#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++) @@ -212,19 +227,16 @@ namespace mio { } 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 defined(_MSC_VER) + my_cpp2020_assert(_converted_count != 0, "The function string2wstring is not work !", std::source_location::current()); +#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 !"); + my_cpp2020_assert(false, "The function string2wstring, An error occurs during conversion !", std::source_location::current()); } else { @@ -236,7 +248,7 @@ namespace mio { //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 !"); + my_cpp2020_assert(false, "The function string2wstring, An error occurs during conversion !", std::source_location::current()); } else { @@ -267,12 +279,12 @@ namespace mio { character_buffer.resize(target_string_count); - #if defined(_MSC_VER) +#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 +#else ::wcstombs(&character_buffer[0], _wstring.c_str(), target_string_count); - #endif +#endif std::size_t _target_string_size = 0; for (auto begin = character_buffer.begin(), end = character_buffer.end(); begin != end && *begin != '\0'; begin++) @@ -281,18 +293,18 @@ namespace mio { } std::string _string{ character_buffer.data(), _target_string_size }; - #if defined(_MSC_VER) +#if defined(_MSC_VER) if (_converted_count == 0) { - throw std::runtime_error("The function wstring2string is not work !"); + my_cpp2020_assert(_converted_count != 0, "The function wstring2string is not work !", std::source_location::current()); } - #endif +#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 !"); + my_cpp2020_assert(false, "The function wstring2string, An error occurs during conversion !", std::source_location::current()); } else { @@ -303,7 +315,7 @@ namespace mio { { if ((_target_string_size + 1) != source_wstring_count) { - throw std::runtime_error("The function wstring2string, An error occurs during conversion !"); + my_cpp2020_assert(false, "The function wstring2string, An error occurs during conversion !", std::source_location::current()); } else { @@ -320,16 +332,18 @@ namespace mio { #include template - struct normalized { + struct normalized + { using type = std::remove_cvref_t< std::conditional_t, - std::remove_pointer_t, - T> + std::remove_pointer_t, + T> >; }; template - struct normalized { + struct normalized + { using type = std::remove_cvref_t; }; @@ -337,8 +351,10 @@ namespace mio { using normalized_t = typename normalized::type; template - struct type_helper { - static constexpr bool is_character_type() { + struct type_helper + { + static constexpr bool is_character_type() + { return std::same_as>; } }; @@ -357,24 +373,24 @@ namespace mio { constexpr bool is_char_or_wchar_type = is_char_type; #endif - 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 + 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 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 + 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 @@ -384,23 +400,25 @@ namespace mio { // Windows 平台:允许窄字符、宽字符字符串以及文件系统路径 template concept stringable_path = narrow_string_like || - wide_string_like || - filesystem_path_like; + wide_string_like || + filesystem_path_like; #else // 非 Windows 平台:仅允许窄字符字符串和文件系统路径 template concept stringable_path = narrow_string_like || - filesystem_path_like; + filesystem_path_like; #endif #ifdef _WIN32 template - const wchar_t* c_str(const StringType& s) { + const wchar_t* c_str(const StringType& s) + { return s.data(); } #endif template - const char* c_str(const StringType& s) { + const char* c_str(const StringType& s) + { return s.data(); } @@ -410,8 +428,9 @@ namespace mio { #else requires (narrow_string_like) #endif - bool empty(const StringType& s) { - if(stringable_path) + bool empty(const StringType& s) + { + if (stringable_path) { return s.empty(); } @@ -421,6 +440,15 @@ namespace mio { } } + template + concept FileReadAccess = (Mode == access_mode::read); + + template + concept FileWriteAccess = (Mode == access_mode::write); + + template + concept FilePrivateAccess = (Mode == access_mode::private_page); + } // namespace detail } // namespace mio @@ -437,11 +465,11 @@ namespace mio { // 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 - using file_handle_type = HANDLE; - static const file_handle_type invalid_handle = INVALID_HANDLE_VALUE; + 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; + using file_handle_type = int; + constexpr file_handle_type invalid_handle = -1; #endif template @@ -507,9 +535,7 @@ namespace mio { 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); } + map(path, offset, length); } /** @@ -519,9 +545,7 @@ namespace mio { */ 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); } + map(handle, offset, length); } #endif // __cpp_exceptions @@ -581,20 +605,18 @@ namespace mio { * 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_; } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + 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(); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + iterator begin() noexcept { return data(); } const_iterator begin() const noexcept { return data(); } const_iterator cbegin() const noexcept { return data(); } @@ -602,10 +624,9 @@ namespace mio { * 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(); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + iterator end() noexcept { return data() + length(); } const_iterator end() const noexcept { return data() + length(); } const_iterator cend() const noexcept { return data() + length(); } @@ -614,10 +635,9 @@ namespace mio { * 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()); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } const_reverse_iterator rbegin() const noexcept { return const_reverse_iterator(end()); @@ -631,10 +651,9 @@ namespace mio { * 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()); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + reverse_iterator rend() noexcept { return reverse_iterator(begin()); } const_reverse_iterator rend() const noexcept { return const_reverse_iterator(begin()); @@ -644,6 +663,43 @@ namespace mio { return const_reverse_iterator(begin()); } + /** + * 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); + + /** + * 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) + { + map(handle, 0, map_entire_file); + } + /** * 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 @@ -652,9 +708,9 @@ namespace mio { 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); + void map_sp(const char* path, const size_type offset, const size_type length); #ifdef _WIN32 - void map_wsp(const wchar_t* path, const size_type offset, const size_type length, std::error_code& error); + void map_wsp(const wchar_t* path, const size_type offset, const size_type length); #endif /** @@ -678,7 +734,7 @@ namespace mio { * 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); + void map(const String& path, const size_type offset, const size_type length); /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the @@ -693,47 +749,9 @@ namespace mio { * 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) + void map(const StringPath& path) { - map(handle, 0, map_entire_file, error); + map(path, 0, map_entire_file); } /** @@ -750,9 +768,9 @@ namespace mio { 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); + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + void sync(); /** * All operators compare the address of the first byte and size of the two mapped @@ -760,10 +778,9 @@ namespace mio { */ private: - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > pointer get_mapping_start() noexcept + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - mapping_offset(); } @@ -778,11 +795,12 @@ namespace mio { * 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 + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + void conditional_sync(); + template + requires (detail::FileReadAccess) + void conditional_sync(); }; template @@ -841,66 +859,54 @@ namespace mio { typename MMap, typename MappingToken > MMap make_mmap(const MappingToken& token, - int64_t offset, int64_t length, std::error_code& error) + int64_t offset, int64_t length) { MMap mmap; - mmap.map(token, offset, length, error); + mmap.map(token, offset, length); 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) + mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, mmap_source::size_type length) { - return make_mmap(token, offset, length, error); + return make_mmap(token, offset, length); } // 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) + inline mmap_source make_mmap_source(const std::filesystem::path& path, mmap_source::size_type offset, mmap_source::size_type length) { // Convert filesystem path to std::string and call the generic make_mmap function. - return make_mmap(path, offset, length, error); + return make_mmap(path, offset, length); } // 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) + mmap_source make_mmap_source(const MappingToken& token) { - return make_mmap_source(token, 0, map_entire_file, error); + return make_mmap_source(token, 0, map_entire_file); } // 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) + mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, mmap_sink::size_type length) { - return make_mmap(token, offset, length, error); + return make_mmap(token, offset, length); } // 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) + inline mmap_sink make_mmap_sink(const std::filesystem::path& path, mmap_sink::size_type offset, mmap_sink::size_type length) { - return make_mmap(path.string(), offset, length, error); + return make_mmap(path.string(), offset, length); } // 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) + mmap_sink make_mmap_sink(const MappingToken& token) { - return make_mmap_sink(token, 0, map_entire_file, error); + return make_mmap_sink(token, 0, map_entire_file); } } // namespace mio @@ -930,7 +936,7 @@ namespace mio struct mio_open_file_helper { - #ifdef _WIN32 +#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. @@ -939,13 +945,17 @@ namespace mio */ static file_handle_type open_file_helper_wstring(const std::wstring& path, const access_mode mode) { + //File read or read/write mode. DWORD desired_access = (mode == access_mode::read) ? GENERIC_READ : (GENERIC_READ | GENERIC_WRITE); + //Private files do not have any attributes. DWORD flags_and_attributes = (mode == access_mode::private_page) ? FILE_ATTRIBUTE_NORMAL : (mode == access_mode::read ? FILE_ATTRIBUTE_READONLY : FILE_ATTRIBUTE_TEMPORARY); + //Is the file inaccessible to other processes? + DWORD file_share_mode = (mode == access_mode::private_page) ? 0 : (FILE_SHARE_READ | FILE_SHARE_WRITE); return ::CreateFileW ( path.c_str(), desired_access, - FILE_SHARE_READ | FILE_SHARE_WRITE, //ShareMode + file_share_mode, //ShareMode nullptr, //SecurityAttributes OPEN_EXISTING, //CreationDisposition flags_and_attributes, //FlagsAndAttributes @@ -975,7 +985,7 @@ namespace mio { return open_file_helper_wstring(path.wstring(), mode); } - #endif // _WIN32 +#endif // _WIN32 /** * Internal helper function to check if a given path is empty. @@ -1005,7 +1015,7 @@ namespace mio file_handle_type handle = invalid_handle; //For windows, we need to use CreateFileW to open a file with a wide-character path. - #ifdef _WIN32 +#ifdef _WIN32 if constexpr (std::same_as) handle = open_file_helper(path, mode); else if constexpr (std::same_as) @@ -1015,42 +1025,18 @@ namespace mio 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. +#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 +#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(); - +#endif return handle; } @@ -1060,8 +1046,13 @@ namespace mio */ struct file_literal { - enum class literal_type { narrow, wide } type; - union { + enum class literal_type + { + narrow, wide + } type; + + union + { const char* narrow; const wchar_t* wide; }; @@ -1073,7 +1064,8 @@ namespace mio * Converts the file literal to a std::string (narrow string) representation. * @return The converted narrow string. */ - std::string to_string() const { + std::string to_string() const + { if (type == literal_type::narrow) return std::string(narrow); else @@ -1084,7 +1076,8 @@ namespace mio * Converts the file literal to a std::wstring (wide string) representation. * @return The converted wide string. */ - std::wstring to_wstring() const { + std::wstring to_wstring() const + { if (type == literal_type::wide) return std::wstring(wide); else @@ -1092,39 +1085,59 @@ namespace mio } }; + /** + * 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). + * @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) + { + if (is_empty(path)) + { + std::error_code error = std::make_error_code(std::errc::invalid_argument); + std::string error_message = "String literal path is empty! " + error.message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); + } + + file_handle_type handle = open_file_implement(path, mode); + + return handle; + } + /** * 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) + static file_handle_type open_file(const file_literal& literal, const access_mode mode) { - 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; + if (empty_literal) + { + std::error_code error = std::make_error_code(std::errc::invalid_argument); + std::string error_message = "String literal path is empty! " + error.message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); } - #ifdef _WIN32 +#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 +#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(); +#endif return handle; } @@ -1135,9 +1148,9 @@ namespace mio * @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) + static file_handle_type open_file(const char* path, const access_mode mode) { - return open_file(file_literal(path), mode, error); + return open_file(file_literal(path), mode); } /** @@ -1147,9 +1160,9 @@ namespace mio * @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) + static file_handle_type open_file(const wchar_t* path, const access_mode mode) { - return open_file(file_literal(path), mode, error); + return open_file(file_literal(path), mode); } }; @@ -1162,26 +1175,23 @@ namespace mio * @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) + inline size_t query_file_size(file_handle_type handle) { - error.clear(); - #ifdef _WIN32 +#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 +#else // POSIX struct stat sbuf; if (::fstat(handle, &sbuf) == -1) { - error = detail::last_error(); return 0; } return sbuf.st_size; - #endif +#endif } /** @@ -1193,9 +1203,9 @@ namespace mio 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 +#ifdef _WIN32 file_handle_type file_mapping_handle; ///< Handle to the file mapping object on Windows - #endif +#endif }; /** @@ -1209,14 +1219,14 @@ namespace mio * @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) + const int64_t length, const access_mode mode) { // 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 + +#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 @@ -1232,7 +1242,8 @@ namespace mio if (file_mapping_handle == invalid_handle) { - error = detail::last_error(); + std::string error_message = "CreateFileMapping failed: " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); return {}; } @@ -1242,12 +1253,12 @@ namespace mio ( ::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 + 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 ) ); @@ -1267,21 +1278,24 @@ namespace mio { // Close file handle if mapping failed ::CloseHandle(file_mapping_handle); - error = detail::last_error(); + + std::string error_message = "MapViewOfFile failed: " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); + return {}; } - #else // POSIX +#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 + 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) @@ -1289,16 +1303,16 @@ namespace mio error = detail::last_error(); return {}; } - #endif +#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.data = mapping_start + (offset - aligned_offset); // Adjust the pointer to the original offset ctx.length = length; ctx.mapped_length = length_to_map; - #ifdef _WIN32 +#ifdef _WIN32 ctx.file_mapping_handle = file_mapping_handle; // Store file mapping handle on Windows - #endif +#endif return ctx; } @@ -1307,24 +1321,24 @@ namespace mio if (data_pointer != nullptr) { - #ifdef _WIN32 +#ifdef _WIN32 if (::FlushViewOfFile(mapping_pointer, mapped_length) == 0 || ::FlushFileBuffers(file_handle) == 0) - #else // POSIX +#else // POSIX if (::msync(mapping_pointer, mapped_length, MS_SYNC) != 0) - #endif +#endif { - std::string error_message = "FlushViewOfFile or Sync failed: " + detail::last_error().message(); + std::string error_message = "FlushViewOfFile or mync failed: " + detail::last_error().message(); my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); } } - #ifdef _WIN32 +#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 +#endif } } } @@ -1403,22 +1417,25 @@ namespace mio { template void basic_mmap::map_sp(const char* path, const size_type offset, - const size_type length, std::error_code& error) + const size_type length) { - error.clear(); if (path == nullptr || path[0] == '\0') { - error = std::make_error_code(std::errc::invalid_argument); + std::error_code error = std::make_error_code(std::errc::invalid_argument); + std::string error_message = "Path is empty! " + error.message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); return; } - const auto handle = mio::detail::mio_open_file_helper::open_file(path, AccessMode, error); - if (error) + + const auto handle = mio::detail::mio_open_file_helper::open_file(path, AccessMode); + if (handle == invalid_handle) { - return; + std::string error_message = "Open file failed, file handle is invalid! " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); } - map(handle, offset, length, error); - if (!error) + map(handle, offset, length); + if (file_handle_ != invalid_handle) { is_handle_internal_ = true; } @@ -1426,23 +1443,24 @@ namespace mio { #ifdef _WIN32 template - void basic_mmap::map_wsp(const wchar_t* path, const size_type offset, - const size_type length, std::error_code& error) + void basic_mmap::map_wsp(const wchar_t* path, const size_type offset, const size_type length) { - error.clear(); if (path == nullptr || path[0] == L'\0') { - error = std::make_error_code(std::errc::invalid_argument); - return; + std::error_code error = std::make_error_code(std::errc::invalid_argument); + std::string error_message = "Path is empty! " + error.message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); } - const auto handle = mio::detail::mio_open_file_helper::open_file(path, AccessMode, error); - if (error) + + const auto handle = mio::detail::mio_open_file_helper::open_file(path, AccessMode); + if (handle == invalid_handle) { - return; + std::string error_message = "Open file failed, file handle is invalid! " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); } - map(handle, offset, length, error); - if (!error) + map(handle, offset, length); + if (file_handle_ != invalid_handle) { is_handle_internal_ = true; } @@ -1462,25 +1480,23 @@ namespace mio { * @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) + const size_type length) { - error.clear(); - // 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; + const auto handle = mio::detail::mio_open_file_helper::open_file(path, AccessMode); + if (handle == invalid_handle) + { + std::string error_message = "Open file failed, file handle is invalid! " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); + } // Proceed to map the file into memory using the obtained file handle. - map(handle, offset, length, error); - if (error) - return; + map(handle, offset, length); // 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). @@ -1513,40 +1529,50 @@ namespace mio { * @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) + void basic_mmap::map(const handle_type handle, const size_type offset, const size_type length) { - error.clear(); if (handle == invalid_handle) { - error = std::make_error_code(std::errc::bad_file_descriptor); + std::error_code error = std::make_error_code(std::errc::bad_file_descriptor); + std::string error_message = "file handle is invalid. " + error.message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); return; } // Query the size of the file using a platform-specific implementation. - const auto file_size = detail::query_file_size(handle, error); - if (error) + const auto file_size = detail::query_file_size(handle); + if (file_size == 0) { - return; +#ifdef _WIN32 + std::string error_message = "Query file size is failed - GetFileSizeEx: " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); +#else // POSIX + std::string error_message = "Query file size is failed - fstat: " + detail::last_error().message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); +#endif } // 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); + std::error_code error = std::make_error_code(std::errc::invalid_argument); + std::string error_message = "requested large mapping region [offset, offset+length) outside of file size bounds. " + error.message(); + my_cpp2020_assert(false, error_message.c_str(), std::source_location::current()); 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, + const auto context = detail::memory_map + ( + handle, offset, length == map_entire_file ? (file_size - offset) : length, - AccessMode, error); - if (!error) + AccessMode + ); + + if (context.data != nullptr) { // 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 @@ -1558,24 +1584,23 @@ namespace mio { // 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 + data_ = reinterpret_cast(context.data); + length_ = context.length; + mapped_length_ = context.mapped_length; +#ifdef _WIN32 + file_mapping_handle_ = context.file_mapping_handle; +#endif } } template - template - typename std::enable_if::type - basic_mmap::sync(std::error_code& error) + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + void basic_mmap::sync() { - error.clear(); if (!is_open()) { - error = std::make_error_code(std::errc::bad_file_descriptor); + //Current file is not open or closed return; } @@ -1650,20 +1675,18 @@ namespace mio { } template - template - typename std::enable_if::type - basic_mmap::conditional_sync() + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + void 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); + // This is invoked from the destructor, so not much we can do about failures here. + sync(); } template - template - typename std::enable_if::type - basic_mmap::conditional_sync() + template + requires (detail::FileReadAccess) + void basic_mmap::conditional_sync() { // noop } @@ -1785,29 +1808,15 @@ namespace mio { } #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); } + map(path, offset, length); } - /** - * 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); } + map(handle, offset, length); } #endif // __cpp_exceptions @@ -1863,10 +1872,9 @@ namespace mio { * 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(); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + pointer data() noexcept { return pimpl_->data(); } const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } /** @@ -1881,10 +1889,9 @@ namespace mio { * 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(); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + iterator end() noexcept { return pimpl_->end(); } const_iterator end() const noexcept { return pimpl_->end(); } const_iterator cend() const noexcept { return pimpl_->cend(); } @@ -1893,10 +1900,9 @@ namespace mio { * 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(); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + 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(); } @@ -1904,10 +1910,9 @@ namespace mio { * 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(); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + 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(); } @@ -1941,9 +1946,9 @@ namespace mio { */ template void map(const String& path, const size_type offset, - const size_type length, std::error_code& error) + const size_type length) { - map_impl(path, offset, length, error); + map_impl(path, offset, length); } /** @@ -1959,9 +1964,9 @@ namespace mio { * The entire file is mapped. */ template - void map(const String& path, std::error_code& error) + void map(const String& path) { - map_impl(path, 0, map_entire_file, error); + map_impl(path, 0, map_entire_file); } /** @@ -1984,9 +1989,9 @@ namespace mio { * 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) + const size_type length) { - map_impl(handle, offset, length, error); + map_impl(handle, offset, length); } /** @@ -2000,9 +2005,9 @@ namespace mio { * * The entire file is mapped. */ - void map(const handle_type handle, std::error_code& error) + void map(const handle_type handle) { - map_impl(handle, 0, map_entire_file, error); + map_impl(handle, 0, map_entire_file); } /** @@ -2019,10 +2024,9 @@ namespace mio { 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); } + template + requires (detail::FileWriteAccess || detail::FilePrivateAccess) + void sync() { if (pimpl_) pimpl_->sync(); } /** All operators compare the underlying `basic_mmap`'s addresses. */ @@ -2059,17 +2063,16 @@ namespace mio { private: template void map_impl(const MappingToken& token, const size_type offset, - const size_type length, std::error_code& error) + const size_type length) { if (!pimpl_) { - mmap_type mmap = make_mmap(token, offset, length, error); - if (error) { return; } + mmap_type mmap = make_mmap(token, offset, length); pimpl_ = std::make_shared(std::move(mmap)); } else { - pimpl_->map(token, offset, length, error); + pimpl_->map(token, offset, length); } } }; diff --git a/test/test.cpp b/test/test.cpp index f51c364..35f5b95 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -2,22 +2,18 @@ #include #include #include -#include #include #include #include "../single_include/mio/mio.hpp" // Just make sure this compiles. -# include -using mmap_source = mio::basic_mmap_source; +#include +using mmap_source_bytes = mio::basic_mmap_source; template -void test_at_offset(const MMap& file_view, const std::string& buffer, - 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); +void test_at_offset(const MMap& file_view, const std::string& buffer, const size_t offset); +inline void test_at_offset(const std::string& buffer, const char* path, const size_t offset); inline void allocate_file(const std::string& path, const int size) { @@ -26,14 +22,7 @@ inline void allocate_file(const std::string& path, const int size) 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() +inline void test_rewrite_file() { const auto path = "test_rewrite.txt"; @@ -45,10 +34,8 @@ inline int test_rewrite_file() // 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); } + path, 0, mio::map_entire_file); // You can use any iterator based function. std::fill(rw_mmap.begin(), rw_mmap.end(), 'a'); @@ -67,8 +54,7 @@ inline int test_rewrite_file() // 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); } + rw_mmap.sync(); // 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 @@ -79,16 +65,44 @@ inline int test_rewrite_file() // 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); } + ro_mmap.map(path); const int the_answer_to_everything = ro_mmap[answer_index]; assert(the_answer_to_everything == 42); } +inline void test_error_case(char* path, const std::string& buffer) +{ + +#define CHECK_INVALID_MMAP(m) do { \ + assert(m.empty()); \ + assert(!m.is_open()); \ + } while(0) + + 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); + CHECK_INVALID_MMAP(m); + + // Empty path? + m = mio::make_mmap_source(static_cast(0), 0, 0); + CHECK_INVALID_MMAP(m); + m = mio::make_mmap_source(std::string(), 0, 0); + CHECK_INVALID_MMAP(m); + + // Invalid handle? + m = mio::make_mmap_source(mio::invalid_handle, 0, 0); + CHECK_INVALID_MMAP(m); + + // Invalid offset? + m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size()); + CHECK_INVALID_MMAP(m); +} + int main() { - std::error_code error; + std::system("chcp 65001"); // Make sure mio compiles with non-const char* strings too. const char _path[] = "test-file"; @@ -117,48 +131,21 @@ int main() file.close(); // Test whole file mapping. - test_at_offset(buffer, path, 0, error); - if (error) { return handle_error(error); } + test_at_offset(buffer, path, 0); //Test starting from below the page size. - test_at_offset(buffer, path, page_size - 3, error); - if (error) { return handle_error(error); } + test_at_offset(buffer, path, page_size - 3); // Test starting from above the page size. - test_at_offset(buffer, path, page_size + 3, error); - if (error) { return handle_error(error); } + test_at_offset(buffer, path, page_size + 3); // 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; + test_at_offset(buffer, path, 2 * page_size + 3); - // 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); + std::cout << "Continuing with tests..." << std::endl; - // 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 offset? - m = mio::make_mmap_source(path, 100 * buffer.size(), buffer.size(), error); - CHECK_INVALID_MMAP(m); - } + //Uncomment this line code for checking test error cases. + //test_error_case(path, buffer); // Make sure these compile. { @@ -167,41 +154,70 @@ int main() // 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); + auto _4 = mio::make_mmap_source(path); + auto _5 = mio::make_mmap(path, 0, mio::map_entire_file); #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 std::wstring wpath1 = L"file"; + + // If the file can be opened, perform make_mmap_source and mapping. + if (std::filesystem::exists(wpath1)) + { + auto _6 = mio::make_mmap_source(wpath1); + mio::mmap_source _7; + _7.map(wpath1); + } + else + { + std::wcerr << L"Cannot open file: " << wpath1 << std::endl; + } + + // Other operations that must execute regardless. + // Even if the file is not openable, these lines are executed. + const std::wstring wpath2 = wpath1 + L"000"; + if (std::filesystem::exists(wpath2)) + { + auto _8 = mio::make_mmap_source(wpath2); + mio::mmap_source _9; + _9.map(wpath1); + } + else + { + std::wcerr << L"Cannot open file: " << wpath2 << std::endl; + } #else + const char* path = "path_to_file"; // Replace with your actual file path const int fd = open(path, O_RDONLY); - mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file); - _fdmmap.unmap(); - _fdmmap.map(fd, error); + + if (fd < 0) + { + std::cerr << "Failed to open file: " << path << std::endl; + } + else + { + // File opened successfully, proceed with mmap operations + mio::mmap_source _fdmmap(fd, 0, mio::map_entire_file); + // Unmap first if needed + _fdmmap.unmap(); + // Remap using the same file descriptor + _fdmmap.map(fd); + + // Close the file descriptor if it's no longer needed + close(fd); + } #endif } - 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) { // 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; } + mio::mmap_source file_view = mio::make_mmap_source(path, offset, mio::map_entire_file); assert(file_view.is_open()); const size_t mapped_size = buffer.size() - offset; @@ -219,14 +235,15 @@ void test_at_offset(const std::string& buffer, const char* path, } template -void test_at_offset(const MMap& file_view, const std::string& buffer, - const size_t offset) +void test_at_offset(const MMap& file_view, const std::string& buffer, 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]) { + ++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;