Last active
August 10, 2022 05:10
-
-
Save Twilight-Dream-Of-Magic/31fb5b2e9d876c26c37ee9d0b2fa7494 to your computer and use it in GitHub Desktop.
[C++ 2020 template concept support] with repository https://github.com/mandreyel/mio/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* 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" | |
/* 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. | |
*/ | |
#include <vector> | |
#include <string> | |
#if __cplusplus >= 201103L && __cplusplus <= 201703L | |
#include <charconv> | |
inline std::wstring cpp2017_string2wstring(const std::string &_string) | |
{ | |
using convert_typeX = std::codecvt_utf8<wchar_t>; | |
std::wstring_convert<convert_typeX, wchar_t> converterX; | |
return converterX.from_bytes(_string); | |
} | |
inline std::string cpp2017_wstring2string(const std::wstring &_wstring) | |
{ | |
using convert_typeX = std::codecvt_utf8<wchar_t>; | |
std::wstring_convert<convert_typeX, wchar_t> converterX; | |
return converterX.to_bytes(_wstring); | |
} | |
#endif | |
inline std::wstring string2wstring(const std::string& _string) | |
{ | |
::setlocale(LC_ALL, ""); | |
std::vector<wchar_t> 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<const long long>(*begin) > 0) | |
{ | |
++source_string_count; | |
} | |
else if (static_cast<const long long>(*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<char> 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<const long long>(*begin) < 256) | |
{ | |
++source_wstring_count; | |
} | |
else if (static_cast<const long long>(*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 <windows.h> | |
#else | |
# include <unistd.h> | |
#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 <iterator> | |
#include <string> | |
#include <system_error> | |
#include <cstdint> | |
#ifdef _WIN32 | |
# ifndef WIN32_LEAN_AND_MEAN | |
# define WIN32_LEAN_AND_MEAN | |
# endif // WIN32_LEAN_AND_MEAN | |
# include <windows.h> | |
#else // ifdef _WIN32 | |
# define INVALID_HANDLE_VALUE -1 | |
#endif // ifdef _WIN32 | |
namespace mio { | |
// This value may be provided as the `length` parameter to the constructor or | |
// `map`, in which case a memory mapping of the entire file is created. | |
enum { map_entire_file = 0 }; | |
#ifdef _WIN32 | |
using file_handle_type = HANDLE; | |
#else | |
using file_handle_type = int; | |
#endif | |
// This value represents an invalid file handle type. This can be used to | |
// determine whether `basic_mmap::file_handle` is valid, for example. | |
const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; | |
template<access_mode AccessMode, typename ByteT> | |
struct basic_mmap | |
{ | |
using value_type = ByteT; | |
using size_type = size_t; | |
using reference = value_type&; | |
using const_reference = const value_type&; | |
using pointer = value_type*; | |
using const_pointer = const value_type*; | |
using difference_type = std::ptrdiff_t; | |
using iterator = pointer; | |
using const_iterator = const_pointer; | |
using reverse_iterator = std::reverse_iterator<iterator>; | |
using const_reverse_iterator = std::reverse_iterator<const_iterator>; | |
using iterator_category = std::random_access_iterator_tag; | |
using handle_type = file_handle_type; | |
static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); | |
private: | |
// Points to the first requested byte, and not to the actual start of the mapping. | |
pointer data_ = nullptr; | |
// Length--in bytes--requested by user (which may not be the length of the | |
// full mapping) and the length of the full mapping. | |
size_type length_ = 0; | |
size_type mapped_length_ = 0; | |
// Letting user map a file using both an existing file handle and a path | |
// introcudes some complexity (see `is_handle_internal_`). | |
// On POSIX, we only need a file handle to create a mapping, while on | |
// Windows systems the file handle is necessary to retrieve a file mapping | |
// handle, but any subsequent operations on the mapped region must be done | |
// through the latter. | |
handle_type file_handle_ = INVALID_HANDLE_VALUE; | |
#ifdef _WIN32 | |
handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; | |
#endif | |
// Letting user map a file using both an existing file handle and a path | |
// introcudes some complexity in that we must not close the file handle if | |
// user provided it, but we must close it if we obtained it using the | |
// provided path. For this reason, this flag is used to determine when to | |
// close `file_handle_`. | |
bool is_handle_internal_; | |
public: | |
/** | |
* The default constructed mmap object is in a non-mapped state, that is, | |
* any operation that attempts to access nonexistent underlying data will | |
* result in undefined behaviour/segmentation faults. | |
*/ | |
basic_mmap() = default; | |
#ifdef __cpp_exceptions | |
/** | |
* The same as invoking the `map` function, except any error that may occur | |
* while establishing the mapping is wrapped in a `std::system_error` and is | |
* thrown. | |
*/ | |
template<typename String> | |
basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) | |
{ | |
std::error_code error; | |
map(path, offset, length, error); | |
if(error) { throw std::system_error(error); } | |
} | |
/** | |
* The same as invoking the `map` function, except any error that may occur | |
* while establishing the mapping is wrapped in a `std::system_error` and is | |
* thrown. | |
*/ | |
basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) | |
{ | |
std::error_code error; | |
map(handle, offset, length, error); | |
if(error) { throw std::system_error(error); } | |
} | |
#endif // __cpp_exceptions | |
/** | |
* `basic_mmap` has single-ownership semantics, so transferring ownership | |
* may only be accomplished by moving the object. | |
*/ | |
basic_mmap(const basic_mmap&) = delete; | |
basic_mmap(basic_mmap&&); | |
basic_mmap& operator=(const basic_mmap&) = delete; | |
basic_mmap& operator=(basic_mmap&&); | |
/** | |
* If this is a read-write mapping, the destructor invokes sync. Regardless | |
* of the access mode, unmap is invoked as a final step. | |
*/ | |
~basic_mmap(); | |
/** | |
* On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, | |
* however, a mapped region of a file gets its own handle, which is returned by | |
* 'mapping_handle'. | |
*/ | |
handle_type file_handle() const noexcept { return file_handle_; } | |
handle_type mapping_handle() const noexcept; | |
/** Returns whether a valid memory mapping has been created. */ | |
bool is_open() const noexcept { return file_handle_ != invalid_handle; } | |
/** | |
* Returns true if no mapping was established, that is, conceptually the | |
* same as though the length that was mapped was 0. This function is | |
* provided so that this class has Container semantics. | |
*/ | |
bool empty() const noexcept { return length() == 0; } | |
/** Returns true if a mapping was established. */ | |
bool is_mapped() const noexcept; | |
/** | |
* `size` and `length` both return the logical length, i.e. the number of bytes | |
* user requested to be mapped, while `mapped_length` returns the actual number of | |
* bytes that were mapped which is a multiple of the underlying operating system's | |
* page allocation granularity. | |
*/ | |
size_type size() const noexcept { return length(); } | |
size_type length() const noexcept { return length_; } | |
size_type mapped_length() const noexcept { return mapped_length_; } | |
/** Returns the offset relative to the start of the mapping. */ | |
size_type mapping_offset() const noexcept | |
{ | |
return mapped_length_ - length_; | |
} | |
/** | |
* Returns a pointer to the first requested byte, or `nullptr` if no memory mapping | |
* exists. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> pointer data() noexcept { return data_; } | |
const_pointer data() const noexcept { return data_; } | |
/** | |
* Returns an iterator to the first requested byte, if a valid memory mapping | |
* exists, otherwise this function call is undefined behaviour. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> iterator begin() noexcept { return data(); } | |
const_iterator begin() const noexcept { return data(); } | |
const_iterator cbegin() const noexcept { return data(); } | |
/** | |
* Returns an iterator one past the last requested byte, if a valid memory mapping | |
* exists, otherwise this function call is undefined behaviour. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> iterator end() noexcept { return data() + length(); } | |
const_iterator end() const noexcept { return data() + length(); } | |
const_iterator cend() const noexcept { return data() + length(); } | |
/** | |
* Returns a reverse iterator to the last memory mapped byte, if a valid | |
* memory mapping exists, otherwise this function call is undefined | |
* behaviour. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } | |
const_reverse_iterator rbegin() const noexcept | |
{ return const_reverse_iterator(end()); } | |
const_reverse_iterator crbegin() const noexcept | |
{ return const_reverse_iterator(end()); } | |
/** | |
* Returns a reverse iterator past the first mapped byte, if a valid memory | |
* mapping exists, otherwise this function call is undefined behaviour. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> reverse_iterator rend() noexcept { return reverse_iterator(begin()); } | |
const_reverse_iterator rend() const noexcept | |
{ return const_reverse_iterator(begin()); } | |
const_reverse_iterator crend() const noexcept | |
{ return const_reverse_iterator(begin()); } | |
/** | |
* Returns a reference to the `i`th byte from the first requested byte (as returned | |
* by `data`). If this is invoked when no valid memory mapping has been created | |
* prior to this call, undefined behaviour ensues. | |
*/ | |
reference operator[](const size_type i) noexcept { return data_[i]; } | |
const_reference operator[](const size_type i) const noexcept { return data_[i]; } | |
/** | |
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the | |
* reason is reported via `error` and the object remains in a state as if this | |
* function hadn't been called. | |
* | |
* `path`, which must be a path to an existing file, is used to retrieve a file | |
* handle (which is closed when the object destructs or `unmap` is called), which is | |
* then used to memory map the requested region. Upon failure, `error` is set to | |
* indicate the reason and the object remains in an unmapped state. | |
* | |
* `offset` is the number of bytes, relative to the start of the file, where the | |
* mapping should begin. When specifying it, there is no need to worry about | |
* providing a value that is aligned with the operating system's page allocation | |
* granularity. This is adjusted by the implementation such that the first requested | |
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at | |
* `offset` from the start of the file. | |
* | |
* `length` is the number of bytes to map. It may be `map_entire_file`, in which | |
* case a mapping of the entire file is created. | |
*/ | |
template<typename String> | |
void map(const String& path, const size_type offset, | |
const size_type length, std::error_code& error); | |
/** | |
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the | |
* reason is reported via `error` and the object remains in a state as if this | |
* function hadn't been called. | |
* | |
* `path`, which must be a path to an existing file, is used to retrieve a file | |
* handle (which is closed when the object destructs or `unmap` is called), which is | |
* then used to memory map the requested region. Upon failure, `error` is set to | |
* indicate the reason and the object remains in an unmapped state. | |
* | |
* The entire file is mapped. | |
*/ | |
template<typename String> | |
void map(const String& path, std::error_code& error) | |
{ | |
map(path, 0, map_entire_file, error); | |
} | |
/** | |
* Establishes a memory mapping with AccessMode. If the mapping is | |
* unsuccesful, the reason is reported via `error` and the object remains in | |
* a state as if this function hadn't been called. | |
* | |
* `handle`, which must be a valid file handle, which is used to memory map the | |
* requested region. Upon failure, `error` is set to indicate the reason and the | |
* object remains in an unmapped state. | |
* | |
* `offset` is the number of bytes, relative to the start of the file, where the | |
* mapping should begin. When specifying it, there is no need to worry about | |
* providing a value that is aligned with the operating system's page allocation | |
* granularity. This is adjusted by the implementation such that the first requested | |
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at | |
* `offset` from the start of the file. | |
* | |
* `length` is the number of bytes to map. It may be `map_entire_file`, in which | |
* case a mapping of the entire file is created. | |
*/ | |
void map(const handle_type handle, const size_type offset, | |
const size_type length, std::error_code& error); | |
/** | |
* Establishes a memory mapping with AccessMode. If the mapping is | |
* unsuccesful, the reason is reported via `error` and the object remains in | |
* a state as if this function hadn't been called. | |
* | |
* `handle`, which must be a valid file handle, which is used to memory map the | |
* requested region. Upon failure, `error` is set to indicate the reason and the | |
* object remains in an unmapped state. | |
* | |
* The entire file is mapped. | |
*/ | |
void map(const handle_type handle, std::error_code& error) | |
{ | |
map(handle, 0, map_entire_file, error); | |
} | |
/** | |
* If a valid memory mapping has been created prior to this call, this call | |
* instructs the kernel to unmap the memory region and disassociate this object | |
* from the file. | |
* | |
* The file handle associated with the file that is mapped is only closed if the | |
* mapping was created using a file path. If, on the other hand, an existing | |
* file handle was used to create the mapping, the file handle is not closed. | |
*/ | |
void unmap(); | |
void swap(basic_mmap& other); | |
/** Flushes the memory mapped page to disk. Errors are reported via `error`. */ | |
template<access_mode A = AccessMode> | |
typename std::enable_if<A == access_mode::write, void>::type | |
sync(std::error_code& error); | |
/** | |
* All operators compare the address of the first byte and size of the two mapped | |
* regions. | |
*/ | |
private: | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> pointer get_mapping_start() noexcept | |
{ | |
return !data() ? nullptr : data() - mapping_offset(); | |
} | |
const_pointer get_mapping_start() const noexcept | |
{ | |
return !data() ? nullptr : data() - mapping_offset(); | |
} | |
/** | |
* The destructor syncs changes to disk if `AccessMode` is `write`, but not | |
* if it's `read`, but since the destructor cannot be templated, we need to | |
* do SFINAE in a dedicated function, where one syncs and the other is a noop. | |
*/ | |
template<access_mode A = AccessMode> | |
typename std::enable_if<A == access_mode::write, void>::type | |
conditional_sync(); | |
template<access_mode A = AccessMode> | |
typename std::enable_if<A == access_mode::read, void>::type conditional_sync(); | |
}; | |
template<access_mode AccessMode, typename ByteT> | |
bool operator==(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b); | |
template<access_mode AccessMode, typename ByteT> | |
bool operator!=(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b); | |
template<access_mode AccessMode, typename ByteT> | |
bool operator<(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b); | |
template<access_mode AccessMode, typename ByteT> | |
bool operator<=(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b); | |
template<access_mode AccessMode, typename ByteT> | |
bool operator>(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b); | |
template<access_mode AccessMode, typename ByteT> | |
bool operator>=(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b); | |
/** | |
* This is the basis for all read-only mmap objects and should be preferred over | |
* directly using `basic_mmap`. | |
*/ | |
template<typename ByteT> | |
using basic_mmap_source = basic_mmap<access_mode::read, ByteT>; | |
/** | |
* This is the basis for all read-write mmap objects and should be preferred over | |
* directly using `basic_mmap`. | |
*/ | |
template<typename ByteT> | |
using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>; | |
/** | |
* These aliases cover the most common use cases, both representing a raw byte stream | |
* (either with a char or an unsigned char/uint8_t). | |
*/ | |
using mmap_source = basic_mmap_source<char>; | |
using ummap_source = basic_mmap_source<unsigned char>; | |
using mmap_sink = basic_mmap_sink<char>; | |
using ummap_sink = basic_mmap_sink<unsigned char>; | |
/** | |
* Convenience factory method that constructs a mapping for any `basic_mmap` or | |
* `basic_mmap` type. | |
*/ | |
template< | |
typename MMap, | |
typename MappingToken | |
> MMap make_mmap(const MappingToken& token, | |
int64_t offset, int64_t length, std::error_code& error) | |
{ | |
MMap mmap; | |
mmap.map(token, offset, length, error); | |
return mmap; | |
} | |
/** | |
* Convenience factory method. | |
* | |
* MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, | |
* `std::filesystem::path`, `std::vector<char>`, or similar), or a | |
* `mmap_source::handle_type`. | |
*/ | |
template<typename MappingToken> | |
mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, | |
mmap_source::size_type length, std::error_code& error) | |
{ | |
return make_mmap<mmap_source>(token, offset, length, error); | |
} | |
template<typename MappingToken> | |
mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) | |
{ | |
return make_mmap_source(token, 0, map_entire_file, error); | |
} | |
/** | |
* Convenience factory method. | |
* | |
* MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, | |
* `std::filesystem::path`, `std::vector<char>`, or similar), or a | |
* `mmap_sink::handle_type`. | |
*/ | |
template<typename MappingToken> | |
mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, | |
mmap_sink::size_type length, std::error_code& error) | |
{ | |
return make_mmap<mmap_sink>(token, offset, length, error); | |
} | |
template<typename MappingToken> | |
mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) | |
{ | |
return make_mmap_sink(token, 0, map_entire_file, error); | |
} | |
} // namespace mio | |
// #include "detail/mmap.ipp" | |
/* 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 <type_traits> | |
namespace mio { | |
namespace detail { | |
#if __cplusplus >= 201103L && __cplusplus < 202002L | |
template< | |
typename S, | |
typename C = typename std::decay<S>::type, | |
typename = decltype(std::declval<C>().data()), | |
typename = typename std::enable_if< | |
std::is_same<typename C::value_type, char>::value | |
#ifdef _WIN32 | |
|| std::is_same<typename C::value_type, wchar_t>::value | |
#endif | |
>::type | |
> struct char_type_helper { | |
using type = typename C::value_type; | |
}; | |
template<class T> | |
struct char_type { | |
using type = typename char_type_helper<T>::type; | |
}; | |
// TODO: can we avoid this brute force approach? | |
template<> | |
struct char_type<char*> { | |
using type = char; | |
}; | |
template<> | |
struct char_type<const char*> { | |
using type = char; | |
}; | |
template<size_t N> | |
struct char_type<char[N]> { | |
using type = char; | |
}; | |
template<size_t N> | |
struct char_type<const char[N]> { | |
using type = char; | |
}; | |
#ifdef _WIN32 | |
template<> | |
struct char_type<wchar_t*> { | |
using type = wchar_t; | |
}; | |
template<> | |
struct char_type<const wchar_t*> { | |
using type = wchar_t; | |
}; | |
template<size_t N> | |
struct char_type<wchar_t[N]> { | |
using type = wchar_t; | |
}; | |
template<size_t N> | |
struct char_type<const wchar_t[N]> { | |
using type = wchar_t; | |
}; | |
#endif // _WIN32 | |
template<typename CharT, typename S> | |
struct is_c_str_helper | |
{ | |
static constexpr bool value = std::is_same< | |
CharT*, | |
// TODO: I'm so sorry for this... Can this be made cleaner? | |
typename std::add_pointer< | |
typename std::remove_cv< | |
typename std::remove_pointer< | |
typename std::decay< | |
S | |
>::type | |
>::type | |
>::type | |
>::type | |
>::value; | |
}; | |
template<typename S> | |
struct is_c_str | |
{ | |
static constexpr bool value = is_c_str_helper<char, S>::value; | |
}; | |
#ifdef _WIN32 | |
template<typename S> | |
struct is_c_wstr | |
{ | |
static constexpr bool value = is_c_str_helper<wchar_t, S>::value; | |
}; | |
#endif // _WIN32 | |
template<typename S> | |
struct is_c_str_or_c_wstr | |
{ | |
static constexpr bool value = is_c_str<S>::value | |
#ifdef _WIN32 | |
|| is_c_wstr<S>::value | |
#endif | |
; | |
}; | |
template< | |
typename String, | |
typename = decltype(std::declval<String>().data()), | |
typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type | |
> const typename char_type<String>::type* c_str(const String& path) | |
{ | |
return path.data(); | |
} | |
template< | |
typename String, | |
typename = decltype(std::declval<String>().empty()), | |
typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type | |
> bool empty(const String& path) | |
{ | |
return path.empty(); | |
} | |
template< | |
typename String, | |
typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type | |
> const typename char_type<String>::type* c_str(String path) | |
{ | |
return path; | |
} | |
template< | |
typename String, | |
typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type | |
> bool empty(String path) | |
{ | |
return !path || (*path == 0); | |
} | |
#else | |
#include <concepts> | |
template<typename CharacterType, typename AnyType> requires std::same_as<char, CharacterType> | |
#ifdef _WIN32 | |
|| std::same_as<wchar_t, CharacterType> | |
#endif | |
struct type_helper | |
{ | |
static constexpr bool is_character_type() | |
{ | |
if constexpr(std::is_pointer_v<AnyType>) | |
{ | |
return std::same_as<CharacterType, std::remove_cvref_t<std::remove_pointer_t<std::decay_t<AnyType>>>>; | |
} | |
else if constexpr(std::is_array_v<AnyType>) | |
{ | |
return std::same_as<CharacterType, std::remove_cvref_t<std::decay_t<std::remove_extent_t<AnyType>>>>; | |
} | |
else | |
{ | |
return std::same_as<CharacterType, std::remove_cvref_t<std::decay_t<AnyType>>>; | |
} | |
} | |
}; | |
template<typename AnyType> | |
constexpr bool is_char_type = type_helper<char, AnyType>::is_character_type(); | |
#ifdef _WIN32 | |
template<typename AnyType> | |
constexpr bool is_wchar_type = type_helper<wchar_t, AnyType>::is_character_type(); | |
template<typename AnyType> | |
constexpr bool is_char_or_wchar_type = is_wchar_type<AnyType> || is_char_type<AnyType>; | |
#else | |
template<typename AnyType> | |
constexpr bool is_char_or_wchar_type = is_char_type<AnyType>; | |
#endif | |
template<typename AnyType> | |
concept have_string_function_type = requires(AnyType object) | |
{ | |
object.data(); | |
object.c_str(); | |
std::convertible_to<decltype(object.empty()), bool>; | |
}; | |
template<typename AnyType> | |
concept is_string_type = have_string_function_type<AnyType> && std::is_base_of_v<std::string, std::remove_cvref_t<AnyType>>; | |
#ifdef _WIN32 | |
template<typename AnyType> | |
concept is_wstring_type = have_string_function_type<AnyType> && std::is_base_of_v<std::wstring, std::remove_cvref_t<AnyType>>; | |
template<typename StringType> requires is_wstring_type<StringType> | |
const wchar_t* c_str(const StringType& path) | |
{ | |
return path.data(); | |
} | |
#endif | |
template<typename StringType> requires is_string_type<StringType> | |
const char* c_str(const StringType& path) | |
{ | |
return path.data(); | |
} | |
template<typename StringType> requires is_string_type<StringType> | |
#ifdef _WIN32 | |
|| is_wstring_type<StringType> | |
#endif | |
bool empty(StringType path) | |
{ | |
return path.empty(); | |
} | |
#endif // __cplusplus >= 201103L && __cplusplus < 202002L | |
} // namespace detail | |
} // namespace mio | |
#endif // MIO_STRING_UTIL_HEADER | |
#include <algorithm> | |
#ifndef _WIN32 | |
# include <unistd.h> | |
# include <fcntl.h> | |
# include <sys/mman.h> | |
# include <sys/stat.h> | |
#endif | |
namespace mio { | |
namespace detail { | |
#ifdef _WIN32 | |
namespace win { | |
/** Returns the 4 upper bytes of an 8-byte integer. */ | |
inline DWORD int64_high(int64_t n) noexcept | |
{ | |
return n >> 32; | |
} | |
/** Returns the 4 lower bytes of an 8-byte integer. */ | |
inline DWORD int64_low(int64_t n) noexcept | |
{ | |
return n & 0xffffffff; | |
} | |
//std::wstring s_2_ws(const std::string& s) | |
//{ | |
// if (s.empty()) | |
// return{}; | |
// | |
// const auto s_length = static_cast<int>(s.length()); | |
// auto buffer = std::vector<wchar_t>(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<int>(ws.length()); | |
// auto buffer = std::vector<char>(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<typename char_type<String>::type, char>::value | |
>::type | |
> file_handle_type open_file_helper(const String& path, const access_mode mode) | |
{ | |
return ::CreateFileW(s_2_ws(path).c_str(), | |
mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, | |
FILE_SHARE_READ | FILE_SHARE_WRITE, | |
0, | |
OPEN_EXISTING, | |
FILE_ATTRIBUTE_NORMAL, | |
0); | |
} | |
template<typename String> | |
typename std::enable_if< | |
std::is_same<typename char_type<String>::type, wchar_t>::value, | |
file_handle_type | |
>::type open_file_helper(const String& path, const access_mode mode) | |
{ | |
return ::CreateFileW(c_str(path), | |
mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, | |
FILE_SHARE_READ | FILE_SHARE_WRITE, | |
0, | |
OPEN_EXISTING, | |
FILE_ATTRIBUTE_NORMAL, | |
0); | |
} | |
#else | |
template<typename StringType> | |
file_handle_type open_file_helper(const StringType& path, const access_mode mode) | |
{ | |
if constexpr (is_string_type<StringType>) | |
{ | |
std::wstring ws_path { string2wstring(path) }; | |
return open_file_helper<std::wstring>(ws_path, mode); | |
} | |
if constexpr (is_wstring_type<StringType>) | |
{ | |
std::wstring ws_path { std::move(path) }; | |
return open_file_helper<std::wstring>(ws_path, mode); | |
} | |
} | |
template<> | |
inline file_handle_type open_file_helper<std::wstring>(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 | |
/** | |
* Returns the last platform specific system error (errno on POSIX and | |
* GetLastError on Win) as a `std::error_code`. | |
*/ | |
inline std::error_code last_error() noexcept | |
{ | |
std::error_code error; | |
#ifdef _WIN32 | |
error.assign(GetLastError(), std::system_category()); | |
#else | |
error.assign(errno, std::system_category()); | |
#endif | |
return error; | |
} | |
template<typename String> | |
file_handle_type open_file(const String& path, const access_mode mode, | |
std::error_code& error) | |
{ | |
error.clear(); | |
if(detail::empty(path)) | |
{ | |
error = std::make_error_code(std::errc::invalid_argument); | |
return invalid_handle; | |
} | |
#ifdef _WIN32 | |
const auto handle = win::open_file_helper(path, mode); | |
#else // POSIX | |
const auto handle = ::open(c_str(path), | |
mode == access_mode::read ? O_RDONLY : O_RDWR); | |
#endif | |
if(handle == invalid_handle) | |
{ | |
error = detail::last_error(); | |
} | |
return handle; | |
} | |
inline size_t query_file_size(file_handle_type handle, std::error_code& error) | |
{ | |
error.clear(); | |
#ifdef _WIN32 | |
LARGE_INTEGER file_size; | |
if(::GetFileSizeEx(handle, &file_size) == 0) | |
{ | |
error = detail::last_error(); | |
return 0; | |
} | |
return static_cast<int64_t>(file_size.QuadPart); | |
#else // POSIX | |
struct stat sbuf; | |
if(::fstat(handle, &sbuf) == -1) | |
{ | |
error = detail::last_error(); | |
return 0; | |
} | |
return sbuf.st_size; | |
#endif | |
} | |
struct mmap_context | |
{ | |
char* data; | |
int64_t length; | |
int64_t mapped_length; | |
#ifdef _WIN32 | |
file_handle_type file_mapping_handle; | |
#endif | |
}; | |
inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset, | |
const int64_t length, const access_mode mode, std::error_code& error) | |
{ | |
const int64_t aligned_offset = make_offset_page_aligned(offset); | |
const int64_t length_to_map = offset - aligned_offset + length; | |
#ifdef _WIN32 | |
const int64_t max_file_size = offset + length; | |
const auto file_mapping_handle = ::CreateFileMapping( | |
file_handle, | |
0, | |
mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, | |
win::int64_high(max_file_size), | |
win::int64_low(max_file_size), | |
0); | |
if(file_mapping_handle == invalid_handle) | |
{ | |
error = detail::last_error(); | |
return {}; | |
} | |
char* mapping_start = static_cast<char*>(::MapViewOfFile( | |
file_mapping_handle, | |
mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, | |
win::int64_high(aligned_offset), | |
win::int64_low(aligned_offset), | |
length_to_map)); | |
if(mapping_start == nullptr) | |
{ | |
// Close file handle if mapping it failed. | |
::CloseHandle(file_mapping_handle); | |
error = detail::last_error(); | |
return {}; | |
} | |
#else // POSIX | |
char* mapping_start = static_cast<char*>(::mmap( | |
0, // Don't give hint as to where to map. | |
length_to_map, | |
mode == access_mode::read ? PROT_READ : PROT_WRITE, | |
MAP_SHARED, | |
file_handle, | |
aligned_offset)); | |
if(mapping_start == MAP_FAILED) | |
{ | |
error = detail::last_error(); | |
return {}; | |
} | |
#endif | |
mmap_context ctx; | |
ctx.data = mapping_start + offset - aligned_offset; | |
ctx.length = length; | |
ctx.mapped_length = length_to_map; | |
#ifdef _WIN32 | |
ctx.file_mapping_handle = file_mapping_handle; | |
#endif | |
return ctx; | |
} | |
} // namespace detail | |
// -- basic_mmap -- | |
template<access_mode AccessMode, typename ByteT> | |
basic_mmap<AccessMode, ByteT>::~basic_mmap() | |
{ | |
conditional_sync(); | |
unmap(); | |
} | |
template<access_mode AccessMode, typename ByteT> | |
basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other) | |
: data_(std::move(other.data_)) | |
, length_(std::move(other.length_)) | |
, mapped_length_(std::move(other.mapped_length_)) | |
, file_handle_(std::move(other.file_handle_)) | |
#ifdef _WIN32 | |
, file_mapping_handle_(std::move(other.file_mapping_handle_)) | |
#endif | |
, is_handle_internal_(std::move(other.is_handle_internal_)) | |
{ | |
other.data_ = nullptr; | |
other.length_ = other.mapped_length_ = 0; | |
other.file_handle_ = invalid_handle; | |
#ifdef _WIN32 | |
other.file_mapping_handle_ = invalid_handle; | |
#endif | |
} | |
template<access_mode AccessMode, typename ByteT> | |
basic_mmap<AccessMode, ByteT>& | |
basic_mmap<AccessMode, ByteT>::operator=(basic_mmap&& other) | |
{ | |
if(this != &other) | |
{ | |
// First the existing mapping needs to be removed. | |
unmap(); | |
data_ = std::move(other.data_); | |
length_ = std::move(other.length_); | |
mapped_length_ = std::move(other.mapped_length_); | |
file_handle_ = std::move(other.file_handle_); | |
#ifdef _WIN32 | |
file_mapping_handle_ = std::move(other.file_mapping_handle_); | |
#endif | |
is_handle_internal_ = std::move(other.is_handle_internal_); | |
// The moved from basic_mmap's fields need to be reset, because | |
// otherwise other's destructor will unmap the same mapping that was | |
// just moved into this. | |
other.data_ = nullptr; | |
other.length_ = other.mapped_length_ = 0; | |
other.file_handle_ = invalid_handle; | |
#ifdef _WIN32 | |
other.file_mapping_handle_ = invalid_handle; | |
#endif | |
other.is_handle_internal_ = false; | |
} | |
return *this; | |
} | |
template<access_mode AccessMode, typename ByteT> | |
typename basic_mmap<AccessMode, ByteT>::handle_type | |
basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept | |
{ | |
#ifdef _WIN32 | |
return file_mapping_handle_; | |
#else | |
return file_handle_; | |
#endif | |
} | |
template<access_mode AccessMode, typename ByteT> | |
template<typename String> | |
void basic_mmap<AccessMode, ByteT>::map(const String& path, const size_type offset, | |
const size_type length, std::error_code& error) | |
{ | |
error.clear(); | |
if(detail::empty(path)) | |
{ | |
error = std::make_error_code(std::errc::invalid_argument); | |
return; | |
} | |
const auto handle = detail::open_file(path, AccessMode, error); | |
if(error) | |
{ | |
return; | |
} | |
map(handle, offset, length, error); | |
// This MUST be after the call to map, as that sets this to true. | |
if(!error) | |
{ | |
is_handle_internal_ = true; | |
} | |
} | |
template<access_mode AccessMode, typename ByteT> | |
void basic_mmap<AccessMode, ByteT>::map(const handle_type handle, | |
const size_type offset, const size_type length, std::error_code& error) | |
{ | |
error.clear(); | |
if(handle == invalid_handle) | |
{ | |
error = std::make_error_code(std::errc::bad_file_descriptor); | |
return; | |
} | |
const auto file_size = detail::query_file_size(handle, error); | |
if(error) | |
{ | |
return; | |
} | |
if(offset + length > file_size) | |
{ | |
error = std::make_error_code(std::errc::invalid_argument); | |
return; | |
} | |
const auto ctx = detail::memory_map(handle, offset, | |
length == map_entire_file ? (file_size - offset) : length, | |
AccessMode, error); | |
if(!error) | |
{ | |
// We must unmap the previous mapping that may have existed prior to this call. | |
// Note that this must only be invoked after a new mapping has been created in | |
// order to provide the strong guarantee that, should the new mapping fail, the | |
// `map` function leaves this instance in a state as though the function had | |
// never been invoked. | |
unmap(); | |
file_handle_ = handle; | |
is_handle_internal_ = false; | |
data_ = reinterpret_cast<pointer>(ctx.data); | |
length_ = ctx.length; | |
mapped_length_ = ctx.mapped_length; | |
#ifdef _WIN32 | |
file_mapping_handle_ = ctx.file_mapping_handle; | |
#endif | |
} | |
} | |
template<access_mode AccessMode, typename ByteT> | |
template<access_mode A> | |
typename std::enable_if<A == access_mode::write, void>::type | |
basic_mmap<AccessMode, ByteT>::sync(std::error_code& error) | |
{ | |
error.clear(); | |
if(!is_open()) | |
{ | |
error = std::make_error_code(std::errc::bad_file_descriptor); | |
return; | |
} | |
if(data()) | |
{ | |
#ifdef _WIN32 | |
if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 | |
|| ::FlushFileBuffers(file_handle_) == 0) | |
#else // POSIX | |
if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) | |
#endif | |
{ | |
error = detail::last_error(); | |
return; | |
} | |
} | |
#ifdef _WIN32 | |
if(::FlushFileBuffers(file_handle_) == 0) | |
{ | |
error = detail::last_error(); | |
} | |
#endif | |
} | |
template<access_mode AccessMode, typename ByteT> | |
void basic_mmap<AccessMode, ByteT>::unmap() | |
{ | |
if(!is_open()) { return; } | |
// TODO do we care about errors here? | |
#ifdef _WIN32 | |
if(is_mapped()) | |
{ | |
::UnmapViewOfFile(get_mapping_start()); | |
::CloseHandle(file_mapping_handle_); | |
} | |
#else // POSIX | |
if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); } | |
#endif | |
// If `file_handle_` was obtained by our opening it (when map is called with | |
// a path, rather than an existing file handle), we need to close it, | |
// otherwise it must not be closed as it may still be used outside this | |
// instance. | |
if(is_handle_internal_) | |
{ | |
#ifdef _WIN32 | |
::CloseHandle(file_handle_); | |
#else // POSIX | |
::close(file_handle_); | |
#endif | |
} | |
// Reset fields to their default values. | |
data_ = nullptr; | |
length_ = mapped_length_ = 0; | |
file_handle_ = invalid_handle; | |
#ifdef _WIN32 | |
file_mapping_handle_ = invalid_handle; | |
#endif | |
} | |
template<access_mode AccessMode, typename ByteT> | |
bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept | |
{ | |
#ifdef _WIN32 | |
return file_mapping_handle_ != invalid_handle; | |
#else // POSIX | |
return is_open(); | |
#endif | |
} | |
template<access_mode AccessMode, typename ByteT> | |
void basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other) | |
{ | |
if(this != &other) | |
{ | |
using std::swap; | |
swap(data_, other.data_); | |
swap(file_handle_, other.file_handle_); | |
#ifdef _WIN32 | |
swap(file_mapping_handle_, other.file_mapping_handle_); | |
#endif | |
swap(length_, other.length_); | |
swap(mapped_length_, other.mapped_length_); | |
swap(is_handle_internal_, other.is_handle_internal_); | |
} | |
} | |
template<access_mode AccessMode, typename ByteT> | |
template<access_mode A> | |
typename std::enable_if<A == access_mode::write, void>::type | |
basic_mmap<AccessMode, ByteT>::conditional_sync() | |
{ | |
// This is invoked from the destructor, so not much we can do about | |
// failures here. | |
std::error_code ec; | |
sync(ec); | |
} | |
template<access_mode AccessMode, typename ByteT> | |
template<access_mode A> | |
typename std::enable_if<A == access_mode::read, void>::type | |
basic_mmap<AccessMode, ByteT>::conditional_sync() | |
{ | |
// noop | |
} | |
template<access_mode AccessMode, typename ByteT> | |
bool operator==(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b) | |
{ | |
return a.data() == b.data() | |
&& a.size() == b.size(); | |
} | |
template<access_mode AccessMode, typename ByteT> | |
bool operator!=(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b) | |
{ | |
return !(a == b); | |
} | |
template<access_mode AccessMode, typename ByteT> | |
bool operator<(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b) | |
{ | |
if(a.data() == b.data()) { return a.size() < b.size(); } | |
return a.data() < b.data(); | |
} | |
template<access_mode AccessMode, typename ByteT> | |
bool operator<=(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b) | |
{ | |
return !(a > b); | |
} | |
template<access_mode AccessMode, typename ByteT> | |
bool operator>(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b) | |
{ | |
if(a.data() == b.data()) { return a.size() > b.size(); } | |
return a.data() > b.data(); | |
} | |
template<access_mode AccessMode, typename ByteT> | |
bool operator>=(const basic_mmap<AccessMode, ByteT>& a, | |
const basic_mmap<AccessMode, ByteT>& b) | |
{ | |
return !(a < b); | |
} | |
} // namespace mio | |
#endif // MIO_BASIC_MMAP_IMPL | |
#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 <windows.h> | |
#else | |
# include <unistd.h> | |
#endif | |
namespace mio { | |
/** | |
* This is used by `basic_mmap` to determine whether to create a read-only or | |
* a read-write memory mapping. | |
*/ | |
enum class access_mode | |
{ | |
read, | |
write | |
}; | |
/** | |
* Determines the operating system's page allocation granularity. | |
* | |
* On the first call to this function, it invokes the operating system specific syscall | |
* to determine the page size, caches the value, and returns it. Any subsequent call to | |
* this function serves the cached value, so no further syscalls are made. | |
*/ | |
inline size_t page_size() | |
{ | |
static const size_t page_size = [] | |
{ | |
#ifdef _WIN32 | |
SYSTEM_INFO SystemInfo; | |
GetSystemInfo(&SystemInfo); | |
return SystemInfo.dwAllocationGranularity; | |
#else | |
return sysconf(_SC_PAGE_SIZE); | |
#endif | |
}(); | |
return page_size; | |
} | |
/** | |
* Alligns `offset` to the operating's system page size such that it subtracts the | |
* difference until the nearest page boundary before `offset`, or does nothing if | |
* `offset` is already page aligned. | |
*/ | |
inline size_t make_offset_page_aligned(size_t offset) noexcept | |
{ | |
const size_t page_size_ = page_size(); | |
// Use integer division to round down to the nearest page alignment. | |
return offset / page_size_ * page_size_; | |
} | |
} // namespace mio | |
#endif // MIO_PAGE_HEADER | |
/* Copyright 2017 https://github.com/mandreyel | |
* | |
* Permission is hereby granted, free of charge, to any person obtaining a copy of this | |
* software and associated documentation files (the "Software"), to deal in the Software | |
* without restriction, including without limitation the rights to use, copy, modify, | |
* merge, publish, distribute, sublicense, and/or sell copies of the Software, and to | |
* permit persons to whom the Software is furnished to do so, subject to the following | |
* conditions: | |
* | |
* The above copyright notice and this permission notice shall be included in all copies | |
* or substantial portions of the Software. | |
* | |
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, | |
* INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A | |
* PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT | |
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF | |
* CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE | |
* OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. | |
*/ | |
#ifndef MIO_SHARED_MMAP_HEADER | |
#define MIO_SHARED_MMAP_HEADER | |
// #include "mio/mmap.hpp" | |
#include <system_error> // std::error_code | |
#include <memory> // std::shared_ptr | |
namespace mio { | |
/** | |
* Exposes (nearly) the same interface as `basic_mmap`, but endowes it with | |
* `std::shared_ptr` semantics. | |
* | |
* This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if | |
* shared semantics are not required. | |
*/ | |
template< | |
access_mode AccessMode, | |
typename ByteT | |
> class basic_shared_mmap | |
{ | |
using impl_type = basic_mmap<AccessMode, ByteT>; | |
std::shared_ptr<impl_type> pimpl_; | |
public: | |
using value_type = typename impl_type::value_type; | |
using size_type = typename impl_type::size_type; | |
using reference = typename impl_type::reference; | |
using const_reference = typename impl_type::const_reference; | |
using pointer = typename impl_type::pointer; | |
using const_pointer = typename impl_type::const_pointer; | |
using difference_type = typename impl_type::difference_type; | |
using iterator = typename impl_type::iterator; | |
using const_iterator = typename impl_type::const_iterator; | |
using reverse_iterator = typename impl_type::reverse_iterator; | |
using const_reverse_iterator = typename impl_type::const_reverse_iterator; | |
using iterator_category = typename impl_type::iterator_category; | |
using handle_type = typename impl_type::handle_type; | |
using mmap_type = impl_type; | |
basic_shared_mmap() = default; | |
basic_shared_mmap(const basic_shared_mmap&) = default; | |
basic_shared_mmap& operator=(const basic_shared_mmap&) = default; | |
basic_shared_mmap(basic_shared_mmap&&) = default; | |
basic_shared_mmap& operator=(basic_shared_mmap&&) = default; | |
/** Takes ownership of an existing mmap object. */ | |
basic_shared_mmap(mmap_type&& mmap) | |
: pimpl_(std::make_shared<mmap_type>(std::move(mmap))) | |
{} | |
/** Takes ownership of an existing mmap object. */ | |
basic_shared_mmap& operator=(mmap_type&& mmap) | |
{ | |
pimpl_ = std::make_shared<mmap_type>(std::move(mmap)); | |
return *this; | |
} | |
/** Initializes this object with an already established shared mmap. */ | |
basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(std::move(mmap)) {} | |
/** Initializes this object with an already established shared mmap. */ | |
basic_shared_mmap& operator=(std::shared_ptr<mmap_type> mmap) | |
{ | |
pimpl_ = std::move(mmap); | |
return *this; | |
} | |
#ifdef __cpp_exceptions | |
/** | |
* The same as invoking the `map` function, except any error that may occur | |
* while establishing the mapping is wrapped in a `std::system_error` and is | |
* thrown. | |
*/ | |
template<typename String> | |
basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) | |
{ | |
std::error_code error; | |
map(path, offset, length, error); | |
if(error) { throw std::system_error(error); } | |
} | |
/** | |
* The same as invoking the `map` function, except any error that may occur | |
* while establishing the mapping is wrapped in a `std::system_error` and is | |
* thrown. | |
*/ | |
basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) | |
{ | |
std::error_code error; | |
map(handle, offset, length, error); | |
if(error) { throw std::system_error(error); } | |
} | |
#endif // __cpp_exceptions | |
/** | |
* If this is a read-write mapping and the last reference to the mapping, | |
* the destructor invokes sync. Regardless of the access mode, unmap is | |
* invoked as a final step. | |
*/ | |
~basic_shared_mmap() = default; | |
/** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ | |
std::shared_ptr<mmap_type> get_shared_ptr() { return pimpl_; } | |
/** | |
* On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, | |
* however, a mapped region of a file gets its own handle, which is returned by | |
* 'mapping_handle'. | |
*/ | |
handle_type file_handle() const noexcept | |
{ | |
return pimpl_ ? pimpl_->file_handle() : invalid_handle; | |
} | |
handle_type mapping_handle() const noexcept | |
{ | |
return pimpl_ ? pimpl_->mapping_handle() : invalid_handle; | |
} | |
/** Returns whether a valid memory mapping has been created. */ | |
bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } | |
/** | |
* Returns true if no mapping was established, that is, conceptually the | |
* same as though the length that was mapped was 0. This function is | |
* provided so that this class has Container semantics. | |
*/ | |
bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); } | |
/** | |
* `size` and `length` both return the logical length, i.e. the number of bytes | |
* user requested to be mapped, while `mapped_length` returns the actual number of | |
* bytes that were mapped which is a multiple of the underlying operating system's | |
* page allocation granularity. | |
*/ | |
size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; } | |
size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } | |
size_type mapped_length() const noexcept | |
{ | |
return pimpl_ ? pimpl_->mapped_length() : 0; | |
} | |
/** | |
* Returns a pointer to the first requested byte, or `nullptr` if no memory mapping | |
* exists. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> pointer data() noexcept { return pimpl_->data(); } | |
const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } | |
/** | |
* Returns an iterator to the first requested byte, if a valid memory mapping | |
* exists, otherwise this function call is undefined behaviour. | |
*/ | |
iterator begin() noexcept { return pimpl_->begin(); } | |
const_iterator begin() const noexcept { return pimpl_->begin(); } | |
const_iterator cbegin() const noexcept { return pimpl_->cbegin(); } | |
/** | |
* Returns an iterator one past the last requested byte, if a valid memory mapping | |
* exists, otherwise this function call is undefined behaviour. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> iterator end() noexcept { return pimpl_->end(); } | |
const_iterator end() const noexcept { return pimpl_->end(); } | |
const_iterator cend() const noexcept { return pimpl_->cend(); } | |
/** | |
* Returns a reverse iterator to the last memory mapped byte, if a valid | |
* memory mapping exists, otherwise this function call is undefined | |
* behaviour. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); } | |
const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); } | |
const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); } | |
/** | |
* Returns a reverse iterator past the first mapped byte, if a valid memory | |
* mapping exists, otherwise this function call is undefined behaviour. | |
*/ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> reverse_iterator rend() noexcept { return pimpl_->rend(); } | |
const_reverse_iterator rend() const noexcept { return pimpl_->rend(); } | |
const_reverse_iterator crend() const noexcept { return pimpl_->crend(); } | |
/** | |
* Returns a reference to the `i`th byte from the first requested byte (as returned | |
* by `data`). If this is invoked when no valid memory mapping has been created | |
* prior to this call, undefined behaviour ensues. | |
*/ | |
reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; } | |
const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; } | |
/** | |
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the | |
* reason is reported via `error` and the object remains in a state as if this | |
* function hadn't been called. | |
* | |
* `path`, which must be a path to an existing file, is used to retrieve a file | |
* handle (which is closed when the object destructs or `unmap` is called), which is | |
* then used to memory map the requested region. Upon failure, `error` is set to | |
* indicate the reason and the object remains in an unmapped state. | |
* | |
* `offset` is the number of bytes, relative to the start of the file, where the | |
* mapping should begin. When specifying it, there is no need to worry about | |
* providing a value that is aligned with the operating system's page allocation | |
* granularity. This is adjusted by the implementation such that the first requested | |
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at | |
* `offset` from the start of the file. | |
* | |
* `length` is the number of bytes to map. It may be `map_entire_file`, in which | |
* case a mapping of the entire file is created. | |
*/ | |
template<typename String> | |
void map(const String& path, const size_type offset, | |
const size_type length, std::error_code& error) | |
{ | |
map_impl(path, offset, length, error); | |
} | |
/** | |
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the | |
* reason is reported via `error` and the object remains in a state as if this | |
* function hadn't been called. | |
* | |
* `path`, which must be a path to an existing file, is used to retrieve a file | |
* handle (which is closed when the object destructs or `unmap` is called), which is | |
* then used to memory map the requested region. Upon failure, `error` is set to | |
* indicate the reason and the object remains in an unmapped state. | |
* | |
* The entire file is mapped. | |
*/ | |
template<typename String> | |
void map(const String& path, std::error_code& error) | |
{ | |
map_impl(path, 0, map_entire_file, error); | |
} | |
/** | |
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the | |
* reason is reported via `error` and the object remains in a state as if this | |
* function hadn't been called. | |
* | |
* `handle`, which must be a valid file handle, which is used to memory map the | |
* requested region. Upon failure, `error` is set to indicate the reason and the | |
* object remains in an unmapped state. | |
* | |
* `offset` is the number of bytes, relative to the start of the file, where the | |
* mapping should begin. When specifying it, there is no need to worry about | |
* providing a value that is aligned with the operating system's page allocation | |
* granularity. This is adjusted by the implementation such that the first requested | |
* byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at | |
* `offset` from the start of the file. | |
* | |
* `length` is the number of bytes to map. It may be `map_entire_file`, in which | |
* case a mapping of the entire file is created. | |
*/ | |
void map(const handle_type handle, const size_type offset, | |
const size_type length, std::error_code& error) | |
{ | |
map_impl(handle, offset, length, error); | |
} | |
/** | |
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the | |
* reason is reported via `error` and the object remains in a state as if this | |
* function hadn't been called. | |
* | |
* `handle`, which must be a valid file handle, which is used to memory map the | |
* requested region. Upon failure, `error` is set to indicate the reason and the | |
* object remains in an unmapped state. | |
* | |
* The entire file is mapped. | |
*/ | |
void map(const handle_type handle, std::error_code& error) | |
{ | |
map_impl(handle, 0, map_entire_file, error); | |
} | |
/** | |
* If a valid memory mapping has been created prior to this call, this call | |
* instructs the kernel to unmap the memory region and disassociate this object | |
* from the file. | |
* | |
* The file handle associated with the file that is mapped is only closed if the | |
* mapping was created using a file path. If, on the other hand, an existing | |
* file handle was used to create the mapping, the file handle is not closed. | |
*/ | |
void unmap() { if(pimpl_) pimpl_->unmap(); } | |
void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); } | |
/** Flushes the memory mapped page to disk. Errors are reported via `error`. */ | |
template< | |
access_mode A = AccessMode, | |
typename = typename std::enable_if<A == access_mode::write>::type | |
> void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); } | |
/** All operators compare the underlying `basic_mmap`'s addresses. */ | |
friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) | |
{ | |
return a.pimpl_ == b.pimpl_; | |
} | |
friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) | |
{ | |
return !(a == b); | |
} | |
friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) | |
{ | |
return a.pimpl_ < b.pimpl_; | |
} | |
friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) | |
{ | |
return a.pimpl_ <= b.pimpl_; | |
} | |
friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) | |
{ | |
return a.pimpl_ > b.pimpl_; | |
} | |
friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) | |
{ | |
return a.pimpl_ >= b.pimpl_; | |
} | |
private: | |
template<typename MappingToken> | |
void map_impl(const MappingToken& token, const size_type offset, | |
const size_type length, std::error_code& error) | |
{ | |
if(!pimpl_) | |
{ | |
mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error); | |
if(error) { return; } | |
pimpl_ = std::make_shared<mmap_type>(std::move(mmap)); | |
} | |
else | |
{ | |
pimpl_->map(token, offset, length, error); | |
} | |
} | |
}; | |
/** | |
* This is the basis for all read-only mmap objects and should be preferred over | |
* directly using basic_shared_mmap. | |
*/ | |
template<typename ByteT> | |
using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>; | |
/** | |
* This is the basis for all read-write mmap objects and should be preferred over | |
* directly using basic_shared_mmap. | |
*/ | |
template<typename ByteT> | |
using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>; | |
/** | |
* These aliases cover the most common use cases, both representing a raw byte stream | |
* (either with a char or an unsigned char/uint8_t). | |
*/ | |
using shared_mmap_source = basic_shared_mmap_source<char>; | |
using shared_ummap_source = basic_shared_mmap_source<unsigned char>; | |
using shared_mmap_sink = basic_shared_mmap_sink<char>; | |
using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>; | |
} // namespace mio | |
#endif // MIO_SHARED_MMAP_HEADER |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment