diff --git a/README.md b/README.md index b9128e6..3e79430 100644 --- a/README.md +++ b/README.md @@ -20,16 +20,25 @@ NOTE: the file must exist before creating a mapping. There are three ways to map a file into memory: -- Using the constructor, which throws on failure: +- Using the constructor, which throws a `std::system_error` on failure: ```c++ mio::mmap_source mmap(path, offset, size_to_map); ``` +or you can omit the `offset` and `size_to_map` arguments, in which case the +entire file is mapped: +```c++ +mio::mmap_source mmap(path); +``` - Using the factory function: ```c++ std::error_code error; mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, error); ``` +or: +```c++ +mio::mmap_source mmap = mio::make_mmap_source(path, error); +``` - Using the `map` member function: ```c++ @@ -37,6 +46,10 @@ std::error_code error; mio::mmap_source mmap; mmap.map(path, offset, size_to_map, error); ``` +or: +```c++ +mmap.map(path, error); +``` Moreover, in each case, you can provide either some string type for the file's path, or you can use an existing, valid file handle. ```c++ @@ -69,19 +82,8 @@ types for functions where character strings are expected (e.g. path parameters). #include #include -int handle_error(const std::error_code& error) -{ - const auto& errmsg = error.message(); - std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); - return error.value(); -} - -void allocate_file(const std::string& path, const int size) -{ - std::ofstream file(path); - std::string s(size, '0'); - file << s; -} +int handle_error(const std::error_code& error); +void allocate_file(const std::string& path, const int size); int main() { @@ -113,24 +115,41 @@ int main() const int answer_index = rw_mmap.size() / 2; rw_mmap[answer_index] = 42; - // Don't forget to flush changes to disk, which is NOT done by the destructor for - // more explicit control of this potentially expensive operation. + // Don't forget to flush changes to disk before unmapping. However, if + // `rw_mmap` were to go out of scope at this point, the destructor would also + // automatically invoke `sync` before `unmap`. rw_mmap.sync(error); if (error) { return handle_error(error); } // We can then remove the mapping, after which rw_mmap will be in a default - // constructed state, i.e. this has the same effect as if the destructor had been - // invoked. + // constructed state, i.e. this and the above call to `sync` have the same + // effect as if the destructor had been invoked. rw_mmap.unmap(); - // Now create the same mapping, but in read-only mode. - mio::mmap_source ro_mmap = mio::make_mmap_source( - path, 0, mio::map_entire_file, error); + // Now create the same mapping, but in read-only mode. Note that calling the + // overload without the offset and file length parameters maps the entire + // file. + mio::mmap_source ro_mmap; + ro_mmap.map(path, error); if (error) { return handle_error(error); } const int the_answer_to_everything = ro_mmap[answer_index]; assert(the_answer_to_everything == 42); } + +int handle_error(const std::error_code& error) +{ + const auto& errmsg = error.message(); + std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); + return error.value(); +} + +void allocate_file(const std::string& path, const int size) +{ + std::ofstream file(path); + std::string s(size, '0'); + file << s; +} ``` `mio::basic_mmap` is move-only, but if multiple copies to the same mapping are needed, use `mio::basic_shared_mmap` which has `std::shared_ptr` semantics and has the same interface as `mio::basic_mmap`. diff --git a/include/mio/detail/CMakeLists.txt b/include/mio/detail/CMakeLists.txt index f2693bd..d4f818c 100644 --- a/include/mio/detail/CMakeLists.txt +++ b/include/mio/detail/CMakeLists.txt @@ -5,7 +5,6 @@ if(NOT subproject) target_sources(mio_base INTERFACE $) endif() diff --git a/include/mio/detail/basic_mmap.hpp b/include/mio/detail/basic_mmap.hpp deleted file mode 100644 index e1ee6e5..0000000 --- a/include/mio/detail/basic_mmap.hpp +++ /dev/null @@ -1,161 +0,0 @@ -/* Copyright 2017 https://github.com/mandreyel - * - * Permission is hereby granted, free of charge, to any person obtaining a copy of this - * software and associated documentation files (the "Software"), to deal in the Software - * without restriction, including without limitation the rights to use, copy, modify, - * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to - * permit persons to whom the Software is furnished to do so, subject to the following - * conditions: - * - * The above copyright notice and this permission notice shall be included in all copies - * or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, - * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A - * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF - * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE - * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - */ - -#ifndef MIO_BASIC_MMAP_HEADER -#define MIO_BASIC_MMAP_HEADER - -#include "mio/page.hpp" - -#include -#include -#include - -#ifdef _WIN32 -# include -#else // ifdef _WIN32 -# define INVALID_HANDLE_VALUE -1 -#endif // ifdef _WIN32 - -namespace mio { -namespace detail { - -enum { map_entire_file = 0 }; - -#ifdef _WIN32 - using file_handle_type = HANDLE; -#else - using file_handle_type = int; -#endif - -template struct basic_mmap -{ - using value_type = ByteT; - using size_type = int64_t; - using reference = value_type&; - using const_reference = const value_type&; - using pointer = value_type*; - using const_pointer = const value_type*; - using difference_type = std::ptrdiff_t; - using iterator = pointer; - using const_iterator = const_pointer; - using reverse_iterator = std::reverse_iterator; - using const_reverse_iterator = std::reverse_iterator; - using iterator_category = std::random_access_iterator_tag; - using handle_type = file_handle_type; - - static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); - -private: - // Points to the first requested byte, and not to the actual start of the mapping. - pointer data_ = nullptr; - - // Length, in bytes, requested by user, which may not be the length of the full - // mapping, and the entire 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 - // 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 - - // 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: - basic_mmap() = default; - basic_mmap(const basic_mmap&) = delete; - basic_mmap& operator=(const basic_mmap&) = delete; - basic_mmap(basic_mmap&&); - basic_mmap& operator=(basic_mmap&&); - ~basic_mmap(); - - handle_type file_handle() const noexcept { return file_handle_; } - handle_type mapping_handle() const noexcept; - - bool is_open() const noexcept { return file_handle_ != INVALID_HANDLE_VALUE; } - bool is_mapped() const noexcept; - bool empty() const noexcept { return length() == 0; } - - size_type offset() const noexcept { return mapped_length_ - length_; } - size_type length() const noexcept { return length_; } - size_type mapped_length() const noexcept { return mapped_length_; } - - pointer data() noexcept { return data_; } - const_pointer data() const noexcept { return data_; } - - iterator begin() noexcept { return data(); } - const_iterator begin() const noexcept { return data(); } - const_iterator cbegin() const noexcept { return data(); } - - iterator end() noexcept { return begin() + length(); } - const_iterator end() const noexcept { return begin() + length(); } - const_iterator cend() const noexcept { return begin() + length(); } - - reverse_iterator rbegin() { return reverse_iterator(end()); } - const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } - const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); } - - reverse_iterator rend() { return reverse_iterator(begin()); } - const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } - const_reverse_iterator crend() const { return const_reverse_iterator(begin()); } - - reference operator[](const size_type i) noexcept { return data_[i]; } - const_reference operator[](const size_type i) const noexcept { return data_[i]; } - - template - void map(String& path, size_type offset, size_type length, - access_mode mode, std::error_code& error); - void map(handle_type handle, size_type offset, size_type length, - access_mode mode, std::error_code& error); - void unmap(); - void sync(std::error_code& error); - - void swap(basic_mmap& other); - -private: - pointer get_mapping_start() noexcept { return !data() ? nullptr : data() - offset(); } -}; - -template -bool operator==(const basic_mmap& a, const basic_mmap& b); -template -bool operator!=(const basic_mmap& a, const basic_mmap& b); -template -bool operator<(const basic_mmap& a, const basic_mmap& b); -template -bool operator<=(const basic_mmap& a, const basic_mmap& b); -template -bool operator>(const basic_mmap& a, const basic_mmap& b); -template -bool operator>=(const basic_mmap& a, const basic_mmap& b); - -} // namespace detail -} // namespace mio - -#include "mio/detail/basic_mmap.ipp" - -#endif // MIO_BASIC_MMAP_HEADER diff --git a/include/mio/detail/basic_mmap.ipp b/include/mio/detail/mmap.ipp similarity index 59% rename from include/mio/detail/basic_mmap.ipp rename to include/mio/detail/mmap.ipp index 08e4bd4..be6ef92 100644 --- a/include/mio/detail/basic_mmap.ipp +++ b/include/mio/detail/mmap.ipp @@ -21,12 +21,11 @@ #ifndef MIO_BASIC_MMAP_IMPL #define MIO_BASIC_MMAP_IMPL -#include "mio/detail/basic_mmap.hpp" -#include "mio/detail/string_util.hpp" +#include "mio/mmap.hpp" #include "mio/page.hpp" +#include "mio/detail/string_util.hpp" #include -#include #ifndef _WIN32 # include @@ -39,17 +38,23 @@ namespace mio { namespace detail { #ifdef _WIN32 +/** Returns the 4 upper bytes of a 8-byte integer. */ inline DWORD int64_high(int64_t n) noexcept { return n >> 32; } +/** Returns the 4 lower bytes of a 8-byte integer. */ inline DWORD int64_low(int64_t n) noexcept { return n & 0xffffffff; } #endif +/** + * 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; @@ -63,29 +68,29 @@ inline std::error_code last_error() noexcept template file_handle_type open_file(const String& path, - const access_mode mode, std::error_code& error) + 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_VALUE; + return invalid_handle; } #ifdef _WIN32 const auto handle = ::CreateFileA(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 + mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE, + 0, + OPEN_EXISTING, + FILE_ATTRIBUTE_NORMAL, + 0); +#else // POSIX const auto handle = ::open(c_str(path), - mode == access_mode::read ? O_RDONLY : O_RDWR); + mode == access_mode::read ? O_RDONLY : O_RDWR); #endif - if(handle == INVALID_HANDLE_VALUE) + if(handle == invalid_handle) { - error = last_error(); + error = detail::last_error(); } return handle; } @@ -97,15 +102,15 @@ inline int64_t query_file_size(file_handle_type handle, std::error_code& error) LARGE_INTEGER file_size; if(::GetFileSizeEx(handle, &file_size) == 0) { - error = last_error(); + error = detail::last_error(); return 0; } return static_cast(file_size.QuadPart); -#else +#else // POSIX struct stat sbuf; if(::fstat(handle, &sbuf) == -1) { - error = last_error(); + error = detail::last_error(); return 0; } return sbuf.st_size; @@ -130,39 +135,39 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t #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, - int64_high(max_file_size), - int64_low(max_file_size), - 0); - if(file_mapping_handle == INVALID_HANDLE_VALUE) + file_handle, + 0, + mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, + int64_high(max_file_size), + int64_low(max_file_size), + 0); + if(file_mapping_handle == invalid_handle) { - error = last_error(); + error = detail::last_error(); return {}; } char* mapping_start = static_cast(::MapViewOfFile( - file_mapping_handle, - mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, - int64_high(aligned_offset), - int64_low(aligned_offset), - length_to_map)); + file_mapping_handle, + mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, + int64_high(aligned_offset), + int64_low(aligned_offset), + length_to_map)); if(mapping_start == nullptr) { - error = last_error(); + error = detail::last_error(); return {}; } -#else +#else // POSIX char* mapping_start = static_cast(::mmap( - 0, // Don't give hint as to where to map. - length_to_map, - mode == access_mode::read ? PROT_READ : PROT_WRITE, - MAP_SHARED, - file_handle, - aligned_offset)); + 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 = last_error(); + error = detail::last_error(); return {}; } #endif @@ -176,16 +181,19 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t return ctx; } +} // namespace detail + // -- basic_mmap -- -template -basic_mmap::~basic_mmap() +template +basic_mmap::~basic_mmap() { + conditional_sync(); unmap(); } -template -basic_mmap::basic_mmap(basic_mmap&& other) +template +basic_mmap::basic_mmap(basic_mmap&& other) : data_(std::move(other.data_)) , length_(std::move(other.length_)) , mapped_length_(std::move(other.mapped_length_)) @@ -197,14 +205,15 @@ basic_mmap::basic_mmap(basic_mmap&& other) { other.data_ = nullptr; other.length_ = other.mapped_length_ = 0; - other.file_handle_ = INVALID_HANDLE_VALUE; + other.file_handle_ = invalid_handle; #ifdef _WIN32 - other.file_mapping_handle_ = INVALID_HANDLE_VALUE; + other.file_mapping_handle_ = invalid_handle; #endif } -template -basic_mmap& basic_mmap::operator=(basic_mmap&& other) +template +basic_mmap& +basic_mmap::operator=(basic_mmap&& other) { if(this != &other) { @@ -219,21 +228,23 @@ basic_mmap& basic_mmap::operator=(basic_mmap&& other) #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. + // 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_VALUE; + other.file_handle_ = invalid_handle; #ifdef _WIN32 - other.file_mapping_handle_ = INVALID_HANDLE_VALUE; + other.file_mapping_handle_ = invalid_handle; #endif other.is_handle_internal_ = false; } return *this; } -template -typename basic_mmap::handle_type basic_mmap::mapping_handle() const noexcept +template +typename basic_mmap::handle_type +basic_mmap::mapping_handle() const noexcept { #ifdef _WIN32 return file_mapping_handle_; @@ -242,10 +253,10 @@ typename basic_mmap::handle_type basic_mmap::mapping_handle() cons #endif } -template +template template -void basic_mmap::map(String& path, size_type offset, - size_type length, access_mode mode, std::error_code& error) +void basic_mmap::map(const String& path, const size_type offset, + const size_type length, std::error_code& error) { error.clear(); if(detail::empty(path)) @@ -253,9 +264,13 @@ void basic_mmap::map(String& path, size_type offset, error = std::make_error_code(std::errc::invalid_argument); return; } - const auto handle = open_file(path, mode, error); - if(error) { return; } - map(handle, offset, length, mode, error); + 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) { @@ -263,31 +278,32 @@ void basic_mmap::map(String& path, size_type offset, } } -template -void basic_mmap::map(handle_type handle, size_type offset, - size_type length, access_mode mode, std::error_code& error) +template +void basic_mmap::map(const handle_type handle, + const size_type offset, const size_type length, std::error_code& error) { error.clear(); - if(handle == INVALID_HANDLE_VALUE) + if(handle == invalid_handle) { error = std::make_error_code(std::errc::bad_file_descriptor); return; } - const auto file_size = query_file_size(handle, error); - if(error) { return; } - - if(length <= map_entire_file) + const auto file_size = detail::query_file_size(handle, error); + if(error) { - length = file_size; + return; } - else if(offset + length > file_size) + + if(offset + length > file_size) { error = std::make_error_code(std::errc::invalid_argument); return; } - const mmap_context ctx = memory_map(handle, offset, length, mode, error); + const auto ctx = detail::memory_map(handle, offset, + length == map_entire_file ? file_size : length, + AccessMode, error); if(!error) { // We must unmap the previous mapping that may have existed prior to this call. @@ -307,8 +323,10 @@ void basic_mmap::map(handle_type handle, size_type offset, } } -template -void basic_mmap::sync(std::error_code& error) +template +template +typename std::enable_if::type +basic_mmap::sync(std::error_code& error) { error.clear(); if(!is_open()) @@ -317,29 +335,29 @@ void basic_mmap::sync(std::error_code& error) return; } - if(data() != nullptr) + if(data()) { #ifdef _WIN32 if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 || ::FlushFileBuffers(file_handle_) == 0) -#else +#else // POSIX if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) #endif { - error = last_error(); + error = detail::last_error(); return; } } #ifdef _WIN32 if(::FlushFileBuffers(file_handle_) == 0) { - error = last_error(); + error = detail::last_error(); } #endif } -template -void basic_mmap::unmap() +template +void basic_mmap::unmap() { if(!is_open()) { return; } // TODO do we care about errors here? @@ -349,7 +367,7 @@ void basic_mmap::unmap() ::UnmapViewOfFile(get_mapping_start()); ::CloseHandle(file_mapping_handle_); } -#else +#else // POSIX if(data_) { ::munmap(const_cast(get_mapping_start()), mapped_length_); } #endif @@ -360,7 +378,7 @@ void basic_mmap::unmap() { #ifdef _WIN32 ::CloseHandle(file_handle_); -#else +#else // POSIX ::close(file_handle_); #endif } @@ -368,24 +386,24 @@ void basic_mmap::unmap() // Reset fields to their default values. data_ = nullptr; length_ = mapped_length_ = 0; - file_handle_ = INVALID_HANDLE_VALUE; + file_handle_ = invalid_handle; #ifdef _WIN32 - file_mapping_handle_ = INVALID_HANDLE_VALUE; + file_mapping_handle_ = invalid_handle; #endif } -template -bool basic_mmap::is_mapped() const noexcept +template +bool basic_mmap::is_mapped() const noexcept { #ifdef _WIN32 - return file_mapping_handle_ != INVALID_HANDLE_VALUE; -#else + return file_mapping_handle_ != invalid_handle; +#else // POSIX return is_open(); #endif } -template -void basic_mmap::swap(basic_mmap& other) +template +void basic_mmap::swap(basic_mmap& other) { if(this != &other) { @@ -401,46 +419,70 @@ void basic_mmap::swap(basic_mmap& other) } } -template -bool operator==(const basic_mmap& a, const basic_mmap& b) +template +template +typename std::enable_if::type +basic_mmap::conditional_sync() +{ + // This is invoked from the destructor, so not much we can do about + // failures here. + std::error_code ec; + sync(ec); +} + +template +template +typename std::enable_if::type +basic_mmap::conditional_sync() +{ + // noop +} + +template +bool operator==(const basic_mmap& a, + const basic_mmap& b) { return a.data() == b.data() && a.size() == b.size(); } -template -bool operator!=(const basic_mmap& a, const basic_mmap& b) +template +bool operator!=(const basic_mmap& a, + const basic_mmap& b) { return !(a == b); } -template -bool operator<(const basic_mmap& a, const basic_mmap& b) +template +bool operator<(const basic_mmap& a, + const basic_mmap& b) { if(a.data() == b.data()) { return a.size() < b.size(); } return a.data() < b.data(); } -template -bool operator<=(const basic_mmap& a, const basic_mmap& b) +template +bool operator<=(const basic_mmap& a, + const basic_mmap& b) { return !(a > b); } -template -bool operator>(const basic_mmap& a, const basic_mmap& b) +template +bool operator>(const basic_mmap& a, + const basic_mmap& b) { if(a.data() == b.data()) { return a.size() > b.size(); } return a.data() > b.data(); } -template -bool operator>=(const basic_mmap& a, const basic_mmap& b) +template +bool operator>=(const basic_mmap& a, + const basic_mmap& b) { return !(a < b); } -} // namespace detail } // namespace mio #endif // MIO_BASIC_MMAP_IMPL diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 419509e..2e69e63 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -21,97 +21,148 @@ #ifndef MIO_MMAP_HEADER #define MIO_MMAP_HEADER -#include "detail/basic_mmap.hpp" -#include "page.hpp" +#include "mio/page.hpp" +#include +#include #include +#include + +#ifdef _WIN32 +# ifndef WIN32_LEAN_AND_MEAN +# define WIN32_LEAN_AND_MEAN +# endif // WIN32_LEAN_AND_MEAN +# include +#else // ifdef _WIN32 +# define INVALID_HANDLE_VALUE -1 +#endif // ifdef _WIN32 namespace mio { // This value may be provided as the `length` parameter to the constructor or // `map`, in which case a memory mapping of the entire file is created. -using detail::map_entire_file; +enum { map_entire_file = 0 }; -template< - access_mode AccessMode, - typename ByteT -> class basic_mmap +#ifdef _WIN32 +using file_handle_type = HANDLE; +#else +using file_handle_type = int; +#endif + +// This value represents an invalid file handle type. This can be used to +// determine whether `basic_mmap::file_handle` is valid, for example. +const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; + +template +struct basic_mmap { - using impl_type = detail::basic_mmap; - impl_type impl_; + using value_type = ByteT; + using size_type = int64_t; + using reference = value_type&; + using const_reference = const value_type&; + using pointer = value_type*; + using const_pointer = const value_type*; + using difference_type = std::ptrdiff_t; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + using iterator_category = std::random_access_iterator_tag; + using handle_type = file_handle_type; + + static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); + +private: + // Points to the first requested byte, and not to the actual start of the mapping. + pointer data_ = nullptr; + + // Length, in bytes, requested by user, which may not be the length of the full + // mapping, and the entire 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 + // 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 + + // 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: - 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; - /** - * 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. + * 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; /** - * The same as invoking the `map` function, except any error that may occur while - * establishing the mapping is thrown. + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. */ template - basic_mmap(const String& path, const size_type offset, const size_type length) + 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 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 thrown. + * 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, const size_type length) + 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 error; } + if(error) { throw std::system_error(error); } } /** - * This class has single-ownership semantics, so transferring ownership may only be - * accomplished by moving the object. + * `basic_mmap` has single-ownership semantics, so transferring ownership + * may only be accomplished by moving the object. */ - basic_mmap(basic_mmap&&) = default; - basic_mmap& operator=(basic_mmap&&) = default; + basic_mmap(const basic_mmap&) = delete; + basic_mmap(basic_mmap&&); + basic_mmap& operator=(const basic_mmap&) = delete; + basic_mmap& operator=(basic_mmap&&); - /** The destructor invokes unmap. */ - ~basic_mmap() = default; + /** + * 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 impl_.file_handle(); } - handle_type mapping_handle() const noexcept { return impl_.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 impl_.is_open(); } + 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 impl_.empty(); } + 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 @@ -119,15 +170,15 @@ public: * bytes that were mapped which is a multiple of the underlying operating system's * page allocation granularity. */ - size_type size() const noexcept { return impl_.length(); } - size_type length() const noexcept { return impl_.length(); } - size_type mapped_length() const noexcept { return impl_.mapped_length(); } + 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 file's start, at which the mapping was * requested to be created. */ - size_type offset() const noexcept { return impl_.offset(); } + size_type offset() const noexcept { return mapped_length_ - length_; } /** * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping @@ -136,8 +187,8 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > pointer data() noexcept { return impl_.data(); } - const_pointer data() const noexcept { return impl_.data(); } + > 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 @@ -146,9 +197,9 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > iterator begin() noexcept { return impl_.begin(); } - const_iterator begin() const noexcept { return impl_.begin(); } - const_iterator cbegin() const noexcept { return impl_.cbegin(); } + > 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 @@ -157,9 +208,9 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > iterator end() noexcept { return impl_.end(); } - const_iterator end() const noexcept { return impl_.end(); } - const_iterator cend() const noexcept { return impl_.cend(); } + > 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 @@ -169,9 +220,11 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > reverse_iterator rbegin() noexcept { return impl_.rbegin(); } - const_reverse_iterator rbegin() const noexcept { return impl_.rbegin(); } - const_reverse_iterator crbegin() const noexcept { return impl_.crbegin(); } + > 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 @@ -180,17 +233,19 @@ public: template< access_mode A = AccessMode, typename = typename std::enable_if::type - > reverse_iterator rend() noexcept { return impl_.rend(); } - const_reverse_iterator rend() const noexcept { return impl_.rend(); } - const_reverse_iterator crend() const noexcept { return impl_.crend(); } + > 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 impl_[i]; } - const_reference operator[](const size_type i) const noexcept { return impl_[i]; } + 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 @@ -214,16 +269,31 @@ public: */ template void map(const String& path, const size_type offset, - const size_type length, std::error_code& error) - { - impl_.map(path, offset, length, AccessMode, error); - } + const size_type length, std::error_code& error); /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this * function hadn't been called. * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * The entire file is mapped. + */ + template + void map(const String& path, std::error_code& error) + { + map(path, 0, map_entire_file, error); + } + + /** + * Establishes a memory mapping with AccessMode. If the mapping is + * unsuccesful, the reason is reported via `error` and the object remains in + * a state as if this function hadn't been called. + * * `handle`, which must be a valid file handle, which is used to memory map the * requested region. Upon failure, `error` is set to indicate the reason and the * object remains in an unmapped state. @@ -239,9 +309,22 @@ public: * case a mapping of the entire file is created. */ void map(const handle_type handle, const size_type offset, - const size_type length, std::error_code& error) + const size_type length, 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) { - impl_.map(handle, offset, length, AccessMode, error); + map(handle, 0, map_entire_file, error); } /** @@ -253,52 +336,70 @@ public: * 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() { impl_.unmap(); } + void unmap(); - void swap(basic_mmap& other) { impl_.swap(other.impl_); } + void swap(basic_mmap& other); /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ - template< - access_mode A = AccessMode, - typename = typename std::enable_if::type - > void sync(std::error_code& error) { impl_.sync(error); } + template + typename std::enable_if::type + sync(std::error_code& error); /** * All operators compare the address of the first byte and size of the two mapped * regions. */ - friend bool operator==(const basic_mmap& a, const basic_mmap& b) +private: + template< + access_mode A = AccessMode, + typename = typename std::enable_if::type + > pointer get_mapping_start() noexcept { - return a.impl_ == b.impl_; + return !data() ? nullptr : data() - offset(); } - friend bool operator!=(const basic_mmap& a, const basic_mmap& b) + const_pointer get_mapping_start() const noexcept { - return !(a == b); + return !data() ? nullptr : data() - offset(); } - friend bool operator<(const basic_mmap& a, const basic_mmap& b) - { - return a.impl_ < b.impl_; - } - - friend bool operator<=(const basic_mmap& a, const basic_mmap& b) - { - return a.impl_ <= b.impl_; - } - - friend bool operator>(const basic_mmap& a, const basic_mmap& b) - { - return a.impl_ > b.impl_; - } - - friend bool operator>=(const basic_mmap& a, const basic_mmap& b) - { - return a.impl_ >= b.impl_; - } + /** + * The destructor syncs changes to disk if `AccessMode` is `write`, but not + * if it's `read`, but since the destructor cannot be templated, we need to + * do SFINAE in a dedicated function, where one syncs and the other is a noop. + */ + template + typename std::enable_if::type + conditional_sync(); + template + typename std::enable_if::type conditional_sync(); }; +template +bool operator==(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator!=(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator<(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator<=(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator>(const basic_mmap& a, + const basic_mmap& b); + +template +bool operator>=(const basic_mmap& a, + const basic_mmap& b); + /** * This is the basis for all read-only mmap objects and should be preferred over * directly using `basic_mmap`. @@ -323,12 +424,15 @@ using ummap_source = basic_mmap_source; using mmap_sink = basic_mmap_sink; using ummap_sink = basic_mmap_sink; -/** Convenience factory method that constructs a mapping for any `basic_mmap` type. */ +/** + * 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) + int64_t offset, int64_t length, std::error_code& error) { MMap mmap; mmap.map(token, offset, length, error); @@ -344,11 +448,17 @@ template< */ template mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, - mmap_source::size_type length, std::error_code& error) + mmap_source::size_type length, std::error_code& error) { return make_mmap(token, offset, length, error); } +template +mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) +{ + return make_mmap_source(token, 0, map_entire_file, error); +} + /** * Convenience factory method. * @@ -358,11 +468,19 @@ mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type o */ template mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, - mmap_sink::size_type length, std::error_code& error) + mmap_sink::size_type length, std::error_code& error) { return make_mmap(token, offset, length, error); } +template +mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) +{ + return make_mmap_sink(token, 0, map_entire_file, error); +} + } // namespace mio +#include "detail/mmap.ipp" + #endif // MIO_MMAP_HEADER diff --git a/include/mio/shared_mmap.hpp b/include/mio/shared_mmap.hpp index 64f6a1b..6ce187f 100644 --- a/include/mio/shared_mmap.hpp +++ b/include/mio/shared_mmap.hpp @@ -88,28 +88,35 @@ public: } /** - * The same as invoking the `map` function, except any error that may occur while - * establishing the mapping is thrown. + * The same as invoking the `map` function, except any error that may occur + * while establishing the mapping is wrapped in a `std::system_error` and is + * thrown. */ template - basic_shared_mmap(const String& path, const size_type offset, const size_type length) + 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 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 thrown. + * 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, const size_type length) + 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 error; } + if(error) { throw std::system_error(error); } } + /** + * 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. */ @@ -120,8 +127,15 @@ public: * 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_->file_handle(); } - handle_type mapping_handle() const noexcept { return pimpl_->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(); } @@ -142,7 +156,9 @@ public: 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; } + { + return pimpl_ ? pimpl_->mapped_length() : 0; + } /** * Returns the offset, relative to the file's start, at which the mapping was @@ -237,6 +253,24 @@ public: map_impl(path, offset, length, error); } + /** + * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the + * reason is reported via `error` and the object remains in a state as if this + * function hadn't been called. + * + * `path`, which must be a path to an existing file, is used to retrieve a file + * handle (which is closed when the object destructs or `unmap` is called), which is + * then used to memory map the requested region. Upon failure, `error` is set to + * indicate the reason and the object remains in an unmapped state. + * + * The entire file is mapped. + */ + template + void map(const String& path, std::error_code& error) + { + map_impl(path, 0, map_entire_file, error); + } + /** * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * reason is reported via `error` and the object remains in a state as if this @@ -262,6 +296,22 @@ public: 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 diff --git a/test/example.cpp b/test/example.cpp index efeb67a..841a57f 100644 --- a/test/example.cpp +++ b/test/example.cpp @@ -5,19 +5,8 @@ #include #include -int handle_error(const std::error_code& error) -{ - const auto& errmsg = error.message(); - std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); - return error.value(); -} - -void allocate_file(const std::string& path, const int size) -{ - std::ofstream file(path); - std::string s(size, '0'); - file << s; -} +int handle_error(const std::error_code& error); +void allocate_file(const std::string& path, const int size); int main() { @@ -49,21 +38,38 @@ int main() const int answer_index = rw_mmap.size() / 2; rw_mmap[answer_index] = 42; - // Don't forget to flush changes to disk, which is NOT done by the destructor for - // more explicit control of this potentially expensive operation. + // Don't forget to flush changes to disk before unmapping. However, if + // `rw_mmap` were to go out of scope at this point, the destructor would also + // automatically invoke `sync` before `unmap`. rw_mmap.sync(error); if (error) { return handle_error(error); } // We can then remove the mapping, after which rw_mmap will be in a default - // constructed state, i.e. this has the same effect as if the destructor had been - // invoked. + // constructed state, i.e. this and the above call to `sync` have the same + // effect as if the destructor had been invoked. rw_mmap.unmap(); - // Now create the same mapping, but in read-only mode. - mio::mmap_source ro_mmap = mio::make_mmap_source( - path, 0, mio::map_entire_file, error); + // Now create the same mapping, but in read-only mode. Note that calling the + // overload without the offset and file length parameters maps the entire + // file. + mio::mmap_source ro_mmap; + ro_mmap.map(path, error); if (error) { return handle_error(error); } const int the_answer_to_everything = ro_mmap[answer_index]; assert(the_answer_to_everything == 42); } + +int handle_error(const std::error_code& error) +{ + const auto& errmsg = error.message(); + std::printf("error mapping file: %s, exiting...\n", errmsg.c_str()); + return error.value(); +} + +void allocate_file(const std::string& path, const int size) +{ + std::ofstream file(path); + std::string s(size, '0'); + file << s; +} diff --git a/test/test.cpp b/test/test.cpp index aacae81..36d350d 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -89,8 +89,7 @@ int main() CHECK_INVALID_MMAP(m); // Invalid handle? - m = mio::make_mmap_source( - INVALID_HANDLE_VALUE/*Psst... This is an implementation detail!*/, 0, 0, error); + m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error); CHECK_INVALID_MMAP(m); // Invalid offset? @@ -102,6 +101,11 @@ int main() // Make sure custom types compile. mio::ummap_source _1; mio::shared_ummap_source _2; + // Make sure shared_mmap mapping compiles as all testing was done on + // normal mmaps. + mio::shared_mmap_source _3(path, 0, mio::map_entire_file); + auto _4 = mio::make_mmap_source(path, error); + auto _5 = mio::make_mmap(path, 0, mio::map_entire_file, error); } std::printf("all tests passed!\n");