From 25bb22b5552112c217482b91668993becc7323ef Mon Sep 17 00:00:00 2001 From: StrikerX3 Date: Sun, 14 Nov 2021 16:22:04 -0300 Subject: [PATCH 1/3] Inline s_2_ws and include for it --- include/mio/detail/mmap.ipp | 3 +- single_include/mio/mio.hpp | 1287 +++++++++++++++++++++++++++++++---- 2 files changed, 1160 insertions(+), 130 deletions(-) diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp index 1b6dc4d..c9f4b8a 100644 --- a/include/mio/detail/mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -26,6 +26,7 @@ #include "mio/detail/string_util.hpp" #include +#include #ifndef _WIN32 # include @@ -52,7 +53,7 @@ inline DWORD int64_low(int64_t n) noexcept return n & 0xffffffff; } -std::wstring s_2_ws(const std::string& s) +inline std::wstring s_2_ws(const std::string& s) { if (s.empty()) return{}; diff --git a/single_include/mio/mio.hpp b/single_include/mio/mio.hpp index c568a46..faeb30e 100644 --- a/single_include/mio/mio.hpp +++ b/single_include/mio/mio.hpp @@ -591,10 +591,6 @@ mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) #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 @@ -615,159 +611,1192 @@ mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#ifndef MIO_STRING_UTIL_HEADER -#define MIO_STRING_UTIL_HEADER +#ifndef MIO_MMAP_HEADER +#define MIO_MMAP_HEADER -#include +// #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 { -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 +// 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 - || std::is_same::value +using file_handle_type = HANDLE; +#else +using file_handle_type = int; #endif - >::type -> struct char_type_helper { - using type = typename C::value_type; -}; -template -struct char_type { - using type = typename char_type_helper::type; -}; +// 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; -// TODO: can we avoid this brute force approach? -template<> -struct char_type { - using type = char; -}; +template +struct basic_mmap +{ + using value_type = ByteT; + using size_type = size_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using difference_type = std::ptrdiff_t; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using iterator_category = std::random_access_iterator_tag; + using handle_type = file_handle_type; -template<> -struct char_type { - using type = char; -}; + static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); -template -struct char_type { - using type = char; -}; +private: + // Points to the first requested byte, and not to the actual start of the mapping. + pointer data_ = nullptr; -template -struct char_type { - using type = char; -}; + // 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 -template<> -struct char_type { - using type = wchar_t; -}; - -template<> -struct char_type { - using type = wchar_t; -}; + handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; +#endif -template -struct char_type { - using type = wchar_t; -}; + // 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_; -template -struct char_type { - using type = wchar_t; -}; -#endif // _WIN32 +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; -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; -}; +#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); } + } -template -struct is_c_str -{ - static constexpr bool value = is_c_str_helper::value; -}; + /** + * 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 -#ifdef _WIN32 -template -struct is_c_wstr -{ - static constexpr bool value = is_c_str_helper::value; -}; -#endif // _WIN32 + /** + * `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&&); -template -struct is_c_str_or_c_wstr -{ - static constexpr bool value = is_c_str::value -#ifdef _WIN32 - || is_c_wstr::value -#endif - ; -}; + /** + * 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(); -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(); -} + /** + * 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; -template< - typename String, - typename = decltype(std::declval().empty()), - typename = typename std::enable_if::value>::type -> bool empty(const String& path) -{ - return path.empty(); -} + /** Returns whether a valid memory mapping has been created. */ + bool is_open() const noexcept { return file_handle_ != invalid_handle; } -template< - typename String, - typename = typename std::enable_if::value>::type -> const typename char_type::type* c_str(String path) -{ - return path; -} + /** + * 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; } -template< - typename String, - typename = typename std::enable_if::value>::type -> bool empty(String path) -{ - return !path || (*path == 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" +/* 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. + */ + +#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 + + +#include +#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; +} + +inline 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 detail } // namespace mio -#endif // MIO_STRING_UTIL_HEADER +#endif // MIO_BASIC_MMAP_IMPL + + +#endif // MIO_MMAP_HEADER + +// #include "mio/page.hpp" + +// #include "mio/detail/string_util.hpp" #include +#include #ifndef _WIN32 # include @@ -794,7 +1823,7 @@ inline DWORD int64_low(int64_t n) noexcept return n & 0xffffffff; } -std::wstring s_2_ws(const std::string& s) +inline std::wstring s_2_ws(const std::string& s) { if (s.empty()) return{}; From 2bc1e80e46c7bfc1683e33b6d4aed45a54dbc0f7 Mon Sep 17 00:00:00 2001 From: StrikerX3 Date: Sun, 14 Nov 2021 16:25:02 -0300 Subject: [PATCH 2/3] Add support for copy-on-write mapping --- include/mio/detail/mmap.ipp | 16 ++-- include/mio/mmap.hpp | 50 +++++++++-- include/mio/page.hpp | 7 +- include/mio/shared_mmap.hpp | 18 +++- single_include/mio/mio.hpp | 164 +++++++++++++++++++++++++++--------- 5 files changed, 190 insertions(+), 65 deletions(-) diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp index c9f4b8a..c0d6ff7 100644 --- a/include/mio/detail/mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -71,7 +71,7 @@ template< > 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, + mode == access_mode::write ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, @@ -86,7 +86,7 @@ typename std::enable_if< >::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, + mode == access_mode::write ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, @@ -126,7 +126,7 @@ file_handle_type open_file(const String& path, const access_mode mode, 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); + mode == access_mode::write ? O_RDWR : O_RDONLY); #endif if(handle == invalid_handle) { @@ -177,7 +177,8 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t const auto file_mapping_handle = ::CreateFileMapping( file_handle, 0, - mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, + mode == access_mode::read ? PAGE_READONLY : + mode == access_mode::write ? PAGE_READWRITE : PAGE_WRITECOPY, win::int64_high(max_file_size), win::int64_low(max_file_size), 0); @@ -188,7 +189,8 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t } char* mapping_start = static_cast(::MapViewOfFile( file_mapping_handle, - mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, + mode == access_mode::read ? FILE_MAP_READ : + mode == access_mode::write ? FILE_MAP_WRITE : FILE_MAP_COPY, win::int64_high(aligned_offset), win::int64_low(aligned_offset), length_to_map)); @@ -204,7 +206,7 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t 0, // Don't give hint as to where to map. length_to_map, mode == access_mode::read ? PROT_READ : PROT_WRITE, - MAP_SHARED, + mode == access_mode::copy_on_write ? MAP_PRIVATE : MAP_SHARED, file_handle, aligned_offset)); if(mapping_start == MAP_FAILED) @@ -475,7 +477,7 @@ basic_mmap::conditional_sync() template template -typename std::enable_if::type +typename std::enable_if::type basic_mmap::conditional_sync() { // noop diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index def559a..ca99c52 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -192,7 +192,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer data() noexcept { return data_; } const_pointer data() const noexcept { return data_; } @@ -202,7 +202,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -213,7 +213,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -225,7 +225,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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()); } @@ -238,7 +238,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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()); } @@ -359,7 +359,7 @@ struct basic_mmap private: template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - mapping_offset(); @@ -372,14 +372,15 @@ struct basic_mmap /** * 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. + * if it's `read` or `copy_on_write`, 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(); + typename std::enable_if::type conditional_sync(); }; template @@ -420,6 +421,13 @@ using basic_mmap_source = basic_mmap; template using basic_mmap_sink = basic_mmap; +/** + * This is the basis for all copy-on-write mmap objects and should be preferred over + * directly using `basic_mmap`. + */ +template +using basic_mmap_cow_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). @@ -430,6 +438,9 @@ using ummap_source = basic_mmap_source; using mmap_sink = basic_mmap_sink; using ummap_sink = basic_mmap_sink; +using mmap_cow_sink = basic_mmap_cow_sink; +using ummap_cow_sink = basic_mmap_cow_sink; + /** * Convenience factory method that constructs a mapping for any `basic_mmap` or * `basic_mmap` type. @@ -485,6 +496,27 @@ mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) return make_mmap_sink(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_cow_sink make_mmap_cow_sink(const MappingToken& token, + mmap_cow_sink::size_type offset, mmap_cow_sink::size_type length, + std::error_code& error) +{ + return make_mmap(token, offset, length, error); +} + +template +mmap_cow_sink make_mmap_cow_sink(const MappingToken& token, std::error_code& error) +{ + return make_mmap_cow_sink(token, 0, map_entire_file, error); +} + } // namespace mio #include "detail/mmap.ipp" diff --git a/include/mio/page.hpp b/include/mio/page.hpp index cae7377..ee03030 100644 --- a/include/mio/page.hpp +++ b/include/mio/page.hpp @@ -30,13 +30,14 @@ namespace mio { /** - * This is used by `basic_mmap` to determine whether to create a read-only or - * a read-write memory mapping. + * This is used by `basic_mmap` to determine whether to create a read-only, + * a read-write or a copy-on-write memory mapping. */ enum class access_mode { read, - write + write, + copy_on_write }; /** diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp index f125a59..e94757f 100644 --- a/include/mio/shared_mmap.hpp +++ b/include/mio/shared_mmap.hpp @@ -168,7 +168,7 @@ template< */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer data() noexcept { return pimpl_->data(); } const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } @@ -186,7 +186,7 @@ template< */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -198,7 +198,7 @@ template< */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -209,7 +209,7 @@ template< */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -391,6 +391,13 @@ using basic_shared_mmap_source = basic_shared_mmap; template using basic_shared_mmap_sink = basic_shared_mmap; +/** + * This is the basis for all copy-on-write mmap objects and should be preferred over + * directly using basic_shared_mmap. + */ +template +using basic_shared_mmap_cow_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). @@ -401,6 +408,9 @@ using shared_ummap_source = basic_shared_mmap_source; using shared_mmap_sink = basic_shared_mmap_sink; using shared_ummap_sink = basic_shared_mmap_sink; +using shared_mmap_cow_sink = basic_shared_mmap_cow_sink; +using shared_ummap_cow_sink = basic_shared_mmap_cow_sink; + } // namespace mio #endif // MIO_SHARED_MMAP_HEADER diff --git a/single_include/mio/mio.hpp b/single_include/mio/mio.hpp index faeb30e..1dedf9d 100644 --- a/single_include/mio/mio.hpp +++ b/single_include/mio/mio.hpp @@ -54,13 +54,14 @@ namespace mio { /** - * This is used by `basic_mmap` to determine whether to create a read-only or - * a read-write memory mapping. + * This is used by `basic_mmap` to determine whether to create a read-only, + * a read-write or a copy-on-write memory mapping. */ enum class access_mode { read, - write + write, + copy_on_write }; /** @@ -271,7 +272,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer data() noexcept { return data_; } const_pointer data() const noexcept { return data_; } @@ -281,7 +282,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -292,7 +293,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -304,7 +305,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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()); } @@ -317,7 +318,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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()); } @@ -438,7 +439,7 @@ struct basic_mmap private: template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - mapping_offset(); @@ -451,14 +452,15 @@ struct basic_mmap /** * 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. + * if it's `read` or `copy_on_write`, 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(); + typename std::enable_if::type conditional_sync(); }; template @@ -499,6 +501,13 @@ using basic_mmap_source = basic_mmap; template using basic_mmap_sink = basic_mmap; +/** + * This is the basis for all copy-on-write mmap objects and should be preferred over + * directly using `basic_mmap`. + */ +template +using basic_mmap_cow_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). @@ -509,6 +518,9 @@ using ummap_source = basic_mmap_source; using mmap_sink = basic_mmap_sink; using ummap_sink = basic_mmap_sink; +using mmap_cow_sink = basic_mmap_cow_sink; +using ummap_cow_sink = basic_mmap_cow_sink; + /** * Convenience factory method that constructs a mapping for any `basic_mmap` or * `basic_mmap` type. @@ -564,6 +576,27 @@ mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) return make_mmap_sink(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_cow_sink make_mmap_cow_sink(const MappingToken& token, + mmap_cow_sink::size_type offset, mmap_cow_sink::size_type length, + std::error_code& error) +{ + return make_mmap(token, offset, length, error); +} + +template +mmap_cow_sink make_mmap_cow_sink(const MappingToken& token, std::error_code& error) +{ + return make_mmap_cow_sink(token, 0, map_entire_file, error); +} + } // namespace mio // #include "detail/mmap.ipp" @@ -786,7 +819,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer data() noexcept { return data_; } const_pointer data() const noexcept { return data_; } @@ -796,7 +829,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -807,7 +840,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -819,7 +852,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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()); } @@ -832,7 +865,7 @@ struct basic_mmap */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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()); } @@ -953,7 +986,7 @@ struct basic_mmap private: template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - mapping_offset(); @@ -966,14 +999,15 @@ struct basic_mmap /** * 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. + * if it's `read` or `copy_on_write`, 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(); + typename std::enable_if::type conditional_sync(); }; template @@ -1014,6 +1048,13 @@ using basic_mmap_source = basic_mmap; template using basic_mmap_sink = basic_mmap; +/** + * This is the basis for all copy-on-write mmap objects and should be preferred over + * directly using `basic_mmap`. + */ +template +using basic_mmap_cow_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). @@ -1024,6 +1065,9 @@ using ummap_source = basic_mmap_source; using mmap_sink = basic_mmap_sink; using ummap_sink = basic_mmap_sink; +using mmap_cow_sink = basic_mmap_cow_sink; +using ummap_cow_sink = basic_mmap_cow_sink; + /** * Convenience factory method that constructs a mapping for any `basic_mmap` or * `basic_mmap` type. @@ -1079,6 +1123,27 @@ mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) return make_mmap_sink(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_cow_sink make_mmap_cow_sink(const MappingToken& token, + mmap_cow_sink::size_type offset, mmap_cow_sink::size_type length, + std::error_code& error) +{ + return make_mmap(token, offset, length, error); +} + +template +mmap_cow_sink make_mmap_cow_sink(const MappingToken& token, std::error_code& error) +{ + return make_mmap_cow_sink(token, 0, map_entire_file, error); +} + } // namespace mio // #include "detail/mmap.ipp" @@ -1328,7 +1393,7 @@ template< > 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, + mode == access_mode::write ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, @@ -1343,7 +1408,7 @@ typename std::enable_if< >::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, + mode == access_mode::write ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, @@ -1383,7 +1448,7 @@ file_handle_type open_file(const String& path, const access_mode mode, 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); + mode == access_mode::write ? O_RDWR : O_RDONLY); #endif if(handle == invalid_handle) { @@ -1434,7 +1499,8 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t const auto file_mapping_handle = ::CreateFileMapping( file_handle, 0, - mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, + mode == access_mode::read ? PAGE_READONLY : + mode == access_mode::write ? PAGE_READWRITE : PAGE_WRITECOPY, win::int64_high(max_file_size), win::int64_low(max_file_size), 0); @@ -1445,7 +1511,8 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t } char* mapping_start = static_cast(::MapViewOfFile( file_mapping_handle, - mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, + mode == access_mode::read ? FILE_MAP_READ : + mode == access_mode::write ? FILE_MAP_WRITE : FILE_MAP_COPY, win::int64_high(aligned_offset), win::int64_low(aligned_offset), length_to_map)); @@ -1461,7 +1528,7 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t 0, // Don't give hint as to where to map. length_to_map, mode == access_mode::read ? PROT_READ : PROT_WRITE, - MAP_SHARED, + mode == access_mode::copy_on_write ? MAP_PRIVATE : MAP_SHARED, file_handle, aligned_offset)); if(mapping_start == MAP_FAILED) @@ -1732,7 +1799,7 @@ basic_mmap::conditional_sync() template template -typename std::enable_if::type +typename std::enable_if::type basic_mmap::conditional_sync() { // noop @@ -1841,7 +1908,7 @@ template< > 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, + mode == access_mode::write ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, @@ -1856,7 +1923,7 @@ typename std::enable_if< >::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, + mode == access_mode::write ? GENERIC_READ | GENERIC_WRITE : GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, @@ -1896,7 +1963,7 @@ file_handle_type open_file(const String& path, const access_mode mode, 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); + mode == access_mode::write ? O_RDWR : O_RDONLY); #endif if(handle == invalid_handle) { @@ -1947,7 +2014,8 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t const auto file_mapping_handle = ::CreateFileMapping( file_handle, 0, - mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, + mode == access_mode::read ? PAGE_READONLY : + mode == access_mode::write ? PAGE_READWRITE : PAGE_WRITECOPY, win::int64_high(max_file_size), win::int64_low(max_file_size), 0); @@ -1958,7 +2026,8 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t } char* mapping_start = static_cast(::MapViewOfFile( file_mapping_handle, - mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, + mode == access_mode::read ? FILE_MAP_READ : + mode == access_mode::write ? FILE_MAP_WRITE : FILE_MAP_COPY, win::int64_high(aligned_offset), win::int64_low(aligned_offset), length_to_map)); @@ -1974,7 +2043,7 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t 0, // Don't give hint as to where to map. length_to_map, mode == access_mode::read ? PROT_READ : PROT_WRITE, - MAP_SHARED, + mode == access_mode::copy_on_write ? MAP_PRIVATE : MAP_SHARED, file_handle, aligned_offset)); if(mapping_start == MAP_FAILED) @@ -2245,7 +2314,7 @@ basic_mmap::conditional_sync() template template -typename std::enable_if::type +typename std::enable_if::type basic_mmap::conditional_sync() { // noop @@ -2334,13 +2403,14 @@ bool operator>=(const basic_mmap& a, namespace mio { /** - * This is used by `basic_mmap` to determine whether to create a read-only or - * a read-write memory mapping. + * This is used by `basic_mmap` to determine whether to create a read-only, + * a read-write or a copy-on-write memory mapping. */ enum class access_mode { read, - write + write, + copy_on_write }; /** @@ -2551,7 +2621,7 @@ template< */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + typename = typename std::enable_if::type > pointer data() noexcept { return pimpl_->data(); } const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } @@ -2569,7 +2639,7 @@ template< */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -2581,7 +2651,7 @@ template< */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -2592,7 +2662,7 @@ template< */ template< access_mode A = AccessMode, - typename = typename std::enable_if::type + 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(); } @@ -2774,6 +2844,13 @@ using basic_shared_mmap_source = basic_shared_mmap; template using basic_shared_mmap_sink = basic_shared_mmap; +/** + * This is the basis for all copy-on-write mmap objects and should be preferred over + * directly using basic_shared_mmap. + */ +template +using basic_shared_mmap_cow_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). @@ -2784,6 +2861,9 @@ using shared_ummap_source = basic_shared_mmap_source; using shared_mmap_sink = basic_shared_mmap_sink; using shared_ummap_sink = basic_shared_mmap_sink; +using shared_mmap_cow_sink = basic_shared_mmap_cow_sink; +using shared_ummap_cow_sink = basic_shared_mmap_cow_sink; + } // namespace mio #endif // MIO_SHARED_MMAP_HEADER From 22394fdd799756648f5769488e0ed55562e4312e Mon Sep 17 00:00:00 2001 From: StrikerX3 Date: Sun, 14 Nov 2021 18:53:01 -0300 Subject: [PATCH 3/3] only needs to be included on Windows --- include/mio/detail/mmap.ipp | 5 ++++- single_include/mio/mio.hpp | 10 ++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/include/mio/detail/mmap.ipp b/include/mio/detail/mmap.ipp index c0d6ff7..11838aa 100644 --- a/include/mio/detail/mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -26,7 +26,10 @@ #include "mio/detail/string_util.hpp" #include -#include + +#ifdef _WIN32 +# include +#endif #ifndef _WIN32 # include diff --git a/single_include/mio/mio.hpp b/single_include/mio/mio.hpp index 1dedf9d..f423eec 100644 --- a/single_include/mio/mio.hpp +++ b/single_include/mio/mio.hpp @@ -1348,7 +1348,10 @@ template< #include -#include + +#ifdef _WIN32 +# include +#endif #ifndef _WIN32 # include @@ -1863,7 +1866,10 @@ bool operator>=(const basic_mmap& a, #include -#include + +#ifdef _WIN32 +# include +#endif #ifndef _WIN32 # include