Skip to content

Instantly share code, notes, and snippets.

@Twilight-Dream-Of-Magic
Last active August 10, 2022 05:10
Show Gist options
  • Save Twilight-Dream-Of-Magic/31fb5b2e9d876c26c37ee9d0b2fa7494 to your computer and use it in GitHub Desktop.
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/
/* 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