Merge branch 'master' into multi-target-windows-api

This commit is contained in:
Greg Beard 2018-11-09 11:59:41 +00:00
commit 6125094190
8 changed files with 499 additions and 422 deletions

View File

@ -20,16 +20,25 @@ NOTE: the file must exist before creating a mapping.
There are three ways to map a file into memory: 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++ ```c++
mio::mmap_source mmap(path, offset, size_to_map); 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: - Using the factory function:
```c++ ```c++
std::error_code error; std::error_code error;
mio::mmap_source mmap = mio::make_mmap_source(path, offset, size_to_map, 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: - Using the `map` member function:
```c++ ```c++
@ -37,6 +46,10 @@ std::error_code error;
mio::mmap_source mmap; mio::mmap_source mmap;
mmap.map(path, offset, size_to_map, error); 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. 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++ ```c++
@ -69,19 +82,8 @@ types for functions where character strings are expected (e.g. path parameters).
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
int handle_error(const std::error_code& error) int handle_error(const std::error_code& error);
{ void allocate_file(const std::string& path, const int size);
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 main() int main()
{ {
@ -113,24 +115,41 @@ int main()
const int answer_index = rw_mmap.size() / 2; const int answer_index = rw_mmap.size() / 2;
rw_mmap[answer_index] = 42; rw_mmap[answer_index] = 42;
// Don't forget to flush changes to disk, which is NOT done by the destructor for // Don't forget to flush changes to disk before unmapping. However, if
// more explicit control of this potentially expensive operation. // `rw_mmap` were to go out of scope at this point, the destructor would also
// automatically invoke `sync` before `unmap`.
rw_mmap.sync(error); rw_mmap.sync(error);
if (error) { return handle_error(error); } if (error) { return handle_error(error); }
// We can then remove the mapping, after which rw_mmap will be in a default // 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 // constructed state, i.e. this and the above call to `sync` have the same
// invoked. // effect as if the destructor had been invoked.
rw_mmap.unmap(); rw_mmap.unmap();
// Now create the same mapping, but in read-only mode. // Now create the same mapping, but in read-only mode. Note that calling the
mio::mmap_source ro_mmap = mio::make_mmap_source( // overload without the offset and file length parameters maps the entire
path, 0, mio::map_entire_file, error); // file.
mio::mmap_source ro_mmap;
ro_mmap.map(path, error);
if (error) { return handle_error(error); } if (error) { return handle_error(error); }
const int the_answer_to_everything = ro_mmap[answer_index]; const int the_answer_to_everything = ro_mmap[answer_index];
assert(the_answer_to_everything == 42); 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`. `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`.

View File

@ -5,7 +5,6 @@
if(NOT subproject) if(NOT subproject)
target_sources(mio_base INTERFACE target_sources(mio_base INTERFACE
$<BUILD_INTERFACE: $<BUILD_INTERFACE:
${CMAKE_CURRENT_LIST_DIR}/basic_mmap.hpp ${CMAKE_CURRENT_LIST_DIR}/mmap.ipp
${CMAKE_CURRENT_LIST_DIR}/basic_mmap.ipp
${CMAKE_CURRENT_LIST_DIR}/string_util.hpp>) ${CMAKE_CURRENT_LIST_DIR}/string_util.hpp>)
endif() endif()

View File

@ -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 <iterator>
#include <string>
#include <system_error>
#ifdef _WIN32
# include <windows.h>
#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<typename ByteT> 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<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 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<typename String>
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<typename ByteT>
bool operator==(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator!=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator<(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator<=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator>(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
template<typename ByteT>
bool operator>=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b);
} // namespace detail
} // namespace mio
#include "mio/detail/basic_mmap.ipp"
#endif // MIO_BASIC_MMAP_HEADER

View File

@ -21,12 +21,11 @@
#ifndef MIO_BASIC_MMAP_IMPL #ifndef MIO_BASIC_MMAP_IMPL
#define MIO_BASIC_MMAP_IMPL #define MIO_BASIC_MMAP_IMPL
#include "mio/detail/basic_mmap.hpp" #include "mio/mmap.hpp"
#include "mio/detail/string_util.hpp"
#include "mio/page.hpp" #include "mio/page.hpp"
#include "mio/detail/string_util.hpp"
#include <algorithm> #include <algorithm>
#include <cstdint>
#ifndef _WIN32 #ifndef _WIN32
# include <unistd.h> # include <unistd.h>
@ -39,17 +38,23 @@ namespace mio {
namespace detail { namespace detail {
#ifdef _WIN32 #ifdef _WIN32
/** Returns the 4 upper bytes of a 8-byte integer. */
inline DWORD int64_high(int64_t n) noexcept inline DWORD int64_high(int64_t n) noexcept
{ {
return n >> 32; return n >> 32;
} }
/** Returns the 4 lower bytes of a 8-byte integer. */
inline DWORD int64_low(int64_t n) noexcept inline DWORD int64_low(int64_t n) noexcept
{ {
return n & 0xffffffff; return n & 0xffffffff;
} }
#endif #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 inline std::error_code last_error() noexcept
{ {
std::error_code error; std::error_code error;
@ -63,29 +68,29 @@ inline std::error_code last_error() noexcept
template<typename String> template<typename String>
file_handle_type open_file(const String& path, 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(); error.clear();
if(detail::empty(path)) if(detail::empty(path))
{ {
error = std::make_error_code(std::errc::invalid_argument); error = std::make_error_code(std::errc::invalid_argument);
return INVALID_HANDLE_VALUE; return invalid_handle;
} }
#ifdef _WIN32 #ifdef _WIN32
const auto handle = ::CreateFileA(c_str(path), const auto handle = ::CreateFileA(c_str(path),
mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
0, 0,
OPEN_EXISTING, OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL, FILE_ATTRIBUTE_NORMAL,
0); 0);
#else #else // POSIX
const auto handle = ::open(c_str(path), const auto handle = ::open(c_str(path),
mode == access_mode::read ? O_RDONLY : O_RDWR); mode == access_mode::read ? O_RDONLY : O_RDWR);
#endif #endif
if(handle == INVALID_HANDLE_VALUE) if(handle == invalid_handle)
{ {
error = last_error(); error = detail::last_error();
} }
return handle; return handle;
} }
@ -97,15 +102,15 @@ inline int64_t query_file_size(file_handle_type handle, std::error_code& error)
LARGE_INTEGER file_size; LARGE_INTEGER file_size;
if(::GetFileSizeEx(handle, &file_size) == 0) if(::GetFileSizeEx(handle, &file_size) == 0)
{ {
error = last_error(); error = detail::last_error();
return 0; return 0;
} }
return static_cast<int64_t>(file_size.QuadPart); return static_cast<int64_t>(file_size.QuadPart);
#else #else // POSIX
struct stat sbuf; struct stat sbuf;
if(::fstat(handle, &sbuf) == -1) if(::fstat(handle, &sbuf) == -1)
{ {
error = last_error(); error = detail::last_error();
return 0; return 0;
} }
return sbuf.st_size; return sbuf.st_size;
@ -130,39 +135,39 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t
#ifdef _WIN32 #ifdef _WIN32
const int64_t max_file_size = offset + length; const int64_t max_file_size = offset + length;
const auto file_mapping_handle = ::CreateFileMapping( const auto file_mapping_handle = ::CreateFileMapping(
file_handle, file_handle,
0, 0,
mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE,
int64_high(max_file_size), int64_high(max_file_size),
int64_low(max_file_size), int64_low(max_file_size),
0); 0);
if(file_mapping_handle == INVALID_HANDLE_VALUE) if(file_mapping_handle == invalid_handle)
{ {
error = last_error(); error = detail::last_error();
return {}; return {};
} }
char* mapping_start = static_cast<char*>(::MapViewOfFile( char* mapping_start = static_cast<char*>(::MapViewOfFile(
file_mapping_handle, file_mapping_handle,
mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE,
int64_high(aligned_offset), int64_high(aligned_offset),
int64_low(aligned_offset), int64_low(aligned_offset),
length_to_map)); length_to_map));
if(mapping_start == nullptr) if(mapping_start == nullptr)
{ {
error = last_error(); error = detail::last_error();
return {}; return {};
} }
#else #else // POSIX
char* mapping_start = static_cast<char*>(::mmap( char* mapping_start = static_cast<char*>(::mmap(
0, // Don't give hint as to where to map. 0, // Don't give hint as to where to map.
length_to_map, length_to_map,
mode == access_mode::read ? PROT_READ : PROT_WRITE, mode == access_mode::read ? PROT_READ : PROT_WRITE,
MAP_SHARED, MAP_SHARED,
file_handle, file_handle,
aligned_offset)); aligned_offset));
if(mapping_start == MAP_FAILED) if(mapping_start == MAP_FAILED)
{ {
error = last_error(); error = detail::last_error();
return {}; return {};
} }
#endif #endif
@ -176,16 +181,19 @@ inline mmap_context memory_map(const file_handle_type file_handle, const int64_t
return ctx; return ctx;
} }
} // namespace detail
// -- basic_mmap -- // -- basic_mmap --
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
basic_mmap<ByteT>::~basic_mmap() basic_mmap<AccessMode, ByteT>::~basic_mmap()
{ {
conditional_sync();
unmap(); unmap();
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
basic_mmap<ByteT>::basic_mmap(basic_mmap<ByteT>&& other) basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other)
: data_(std::move(other.data_)) : data_(std::move(other.data_))
, length_(std::move(other.length_)) , length_(std::move(other.length_))
, mapped_length_(std::move(other.mapped_length_)) , mapped_length_(std::move(other.mapped_length_))
@ -197,14 +205,15 @@ basic_mmap<ByteT>::basic_mmap(basic_mmap<ByteT>&& other)
{ {
other.data_ = nullptr; other.data_ = nullptr;
other.length_ = other.mapped_length_ = 0; other.length_ = other.mapped_length_ = 0;
other.file_handle_ = INVALID_HANDLE_VALUE; other.file_handle_ = invalid_handle;
#ifdef _WIN32 #ifdef _WIN32
other.file_mapping_handle_ = INVALID_HANDLE_VALUE; other.file_mapping_handle_ = invalid_handle;
#endif #endif
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
basic_mmap<ByteT>& basic_mmap<ByteT>::operator=(basic_mmap<ByteT>&& other) basic_mmap<AccessMode, ByteT>&
basic_mmap<AccessMode, ByteT>::operator=(basic_mmap&& other)
{ {
if(this != &other) if(this != &other)
{ {
@ -219,21 +228,23 @@ basic_mmap<ByteT>& basic_mmap<ByteT>::operator=(basic_mmap<ByteT>&& other)
#endif #endif
is_handle_internal_ = std::move(other.is_handle_internal_); is_handle_internal_ = std::move(other.is_handle_internal_);
// The moved from basic_mmap's fields need to be reset, because otherwise other's // The moved from basic_mmap's fields need to be reset, because
// destructor will unmap the same mapping that was just moved into this. // otherwise other's destructor will unmap the same mapping that was
// just moved into this.
other.data_ = nullptr; other.data_ = nullptr;
other.length_ = other.mapped_length_ = 0; other.length_ = other.mapped_length_ = 0;
other.file_handle_ = INVALID_HANDLE_VALUE; other.file_handle_ = invalid_handle;
#ifdef _WIN32 #ifdef _WIN32
other.file_mapping_handle_ = INVALID_HANDLE_VALUE; other.file_mapping_handle_ = invalid_handle;
#endif #endif
other.is_handle_internal_ = false; other.is_handle_internal_ = false;
} }
return *this; return *this;
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
typename basic_mmap<ByteT>::handle_type basic_mmap<ByteT>::mapping_handle() const noexcept typename basic_mmap<AccessMode, ByteT>::handle_type
basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept
{ {
#ifdef _WIN32 #ifdef _WIN32
return file_mapping_handle_; return file_mapping_handle_;
@ -242,10 +253,10 @@ typename basic_mmap<ByteT>::handle_type basic_mmap<ByteT>::mapping_handle() cons
#endif #endif
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
template<typename String> template<typename String>
void basic_mmap<ByteT>::map(String& path, size_type offset, void basic_mmap<AccessMode, ByteT>::map(const String& path, const size_type offset,
size_type length, access_mode mode, std::error_code& error) const size_type length, std::error_code& error)
{ {
error.clear(); error.clear();
if(detail::empty(path)) if(detail::empty(path))
@ -253,9 +264,13 @@ void basic_mmap<ByteT>::map(String& path, size_type offset,
error = std::make_error_code(std::errc::invalid_argument); error = std::make_error_code(std::errc::invalid_argument);
return; return;
} }
const auto handle = open_file(path, mode, error); const auto handle = detail::open_file(path, AccessMode, error);
if(error) { return; } if(error)
map(handle, offset, length, mode, error); {
return;
}
map(handle, offset, length, error);
// This MUST be after the call to map, as that sets this to true. // This MUST be after the call to map, as that sets this to true.
if(!error) if(!error)
{ {
@ -263,31 +278,32 @@ void basic_mmap<ByteT>::map(String& path, size_type offset,
} }
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
void basic_mmap<ByteT>::map(handle_type handle, size_type offset, void basic_mmap<AccessMode, ByteT>::map(const handle_type handle,
size_type length, access_mode mode, std::error_code& error) const size_type offset, const size_type length, std::error_code& error)
{ {
error.clear(); error.clear();
if(handle == INVALID_HANDLE_VALUE) if(handle == invalid_handle)
{ {
error = std::make_error_code(std::errc::bad_file_descriptor); error = std::make_error_code(std::errc::bad_file_descriptor);
return; return;
} }
const auto file_size = query_file_size(handle, error); const auto file_size = detail::query_file_size(handle, error);
if(error) { return; } if(error)
if(length <= map_entire_file)
{ {
length = file_size; return;
} }
else if(offset + length > file_size)
if(offset + length > file_size)
{ {
error = std::make_error_code(std::errc::invalid_argument); error = std::make_error_code(std::errc::invalid_argument);
return; 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) if(!error)
{ {
// We must unmap the previous mapping that may have existed prior to this call. // We must unmap the previous mapping that may have existed prior to this call.
@ -307,8 +323,10 @@ void basic_mmap<ByteT>::map(handle_type handle, size_type offset,
} }
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
void basic_mmap<ByteT>::sync(std::error_code& error) 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(); error.clear();
if(!is_open()) if(!is_open())
@ -317,29 +335,29 @@ void basic_mmap<ByteT>::sync(std::error_code& error)
return; return;
} }
if(data() != nullptr) if(data())
{ {
#ifdef _WIN32 #ifdef _WIN32
if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0
|| ::FlushFileBuffers(file_handle_) == 0) || ::FlushFileBuffers(file_handle_) == 0)
#else #else // POSIX
if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0)
#endif #endif
{ {
error = last_error(); error = detail::last_error();
return; return;
} }
} }
#ifdef _WIN32 #ifdef _WIN32
if(::FlushFileBuffers(file_handle_) == 0) if(::FlushFileBuffers(file_handle_) == 0)
{ {
error = last_error(); error = detail::last_error();
} }
#endif #endif
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
void basic_mmap<ByteT>::unmap() void basic_mmap<AccessMode, ByteT>::unmap()
{ {
if(!is_open()) { return; } if(!is_open()) { return; }
// TODO do we care about errors here? // TODO do we care about errors here?
@ -349,7 +367,7 @@ void basic_mmap<ByteT>::unmap()
::UnmapViewOfFile(get_mapping_start()); ::UnmapViewOfFile(get_mapping_start());
::CloseHandle(file_mapping_handle_); ::CloseHandle(file_mapping_handle_);
} }
#else #else // POSIX
if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); } if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); }
#endif #endif
@ -360,7 +378,7 @@ void basic_mmap<ByteT>::unmap()
{ {
#ifdef _WIN32 #ifdef _WIN32
::CloseHandle(file_handle_); ::CloseHandle(file_handle_);
#else #else // POSIX
::close(file_handle_); ::close(file_handle_);
#endif #endif
} }
@ -368,24 +386,24 @@ void basic_mmap<ByteT>::unmap()
// Reset fields to their default values. // Reset fields to their default values.
data_ = nullptr; data_ = nullptr;
length_ = mapped_length_ = 0; length_ = mapped_length_ = 0;
file_handle_ = INVALID_HANDLE_VALUE; file_handle_ = invalid_handle;
#ifdef _WIN32 #ifdef _WIN32
file_mapping_handle_ = INVALID_HANDLE_VALUE; file_mapping_handle_ = invalid_handle;
#endif #endif
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
bool basic_mmap<ByteT>::is_mapped() const noexcept bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept
{ {
#ifdef _WIN32 #ifdef _WIN32
return file_mapping_handle_ != INVALID_HANDLE_VALUE; return file_mapping_handle_ != invalid_handle;
#else #else // POSIX
return is_open(); return is_open();
#endif #endif
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
void basic_mmap<ByteT>::swap(basic_mmap<ByteT>& other) void basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other)
{ {
if(this != &other) if(this != &other)
{ {
@ -401,46 +419,70 @@ void basic_mmap<ByteT>::swap(basic_mmap<ByteT>& other)
} }
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
bool operator==(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b) 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() return a.data() == b.data()
&& a.size() == b.size(); && a.size() == b.size();
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
bool operator!=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b) bool operator!=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{ {
return !(a == b); return !(a == b);
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
bool operator<(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b) bool operator<(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{ {
if(a.data() == b.data()) { return a.size() < b.size(); } if(a.data() == b.data()) { return a.size() < b.size(); }
return a.data() < b.data(); return a.data() < b.data();
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
bool operator<=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b) bool operator<=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{ {
return !(a > b); return !(a > b);
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
bool operator>(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b) bool operator>(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{ {
if(a.data() == b.data()) { return a.size() > b.size(); } if(a.data() == b.data()) { return a.size() > b.size(); }
return a.data() > b.data(); return a.data() > b.data();
} }
template<typename ByteT> template<access_mode AccessMode, typename ByteT>
bool operator>=(const basic_mmap<ByteT>& a, const basic_mmap<ByteT>& b) bool operator>=(const basic_mmap<AccessMode, ByteT>& a,
const basic_mmap<AccessMode, ByteT>& b)
{ {
return !(a < b); return !(a < b);
} }
} // namespace detail
} // namespace mio } // namespace mio
#endif // MIO_BASIC_MMAP_IMPL #endif // MIO_BASIC_MMAP_IMPL

View File

@ -21,97 +21,148 @@
#ifndef MIO_MMAP_HEADER #ifndef MIO_MMAP_HEADER
#define MIO_MMAP_HEADER #define MIO_MMAP_HEADER
#include "detail/basic_mmap.hpp" #include "mio/page.hpp"
#include "page.hpp"
#include <iterator>
#include <string>
#include <system_error> #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 { namespace mio {
// This value may be provided as the `length` parameter to the constructor or // 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. // `map`, in which case a memory mapping of the entire file is created.
using detail::map_entire_file; enum { map_entire_file = 0 };
template< #ifdef _WIN32
access_mode AccessMode, using file_handle_type = HANDLE;
typename ByteT #else
> class basic_mmap 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 impl_type = detail::basic_mmap<ByteT>; using value_type = ByteT;
impl_type impl_; 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<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 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: 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 * The default constructed mmap object is in a non-mapped state, that is,
* operation that attempts to access nonexistent underlying data will result in * any operation that attempts to access nonexistent underlying data will
* undefined behaviour/segmentation faults. * result in undefined behaviour/segmentation faults.
*/ */
basic_mmap() = default; basic_mmap() = default;
/** /**
* The same as invoking the `map` function, except any error that may occur while * The same as invoking the `map` function, except any error that may occur
* establishing the mapping is thrown. * while establishing the mapping is wrapped in a `std::system_error` and is
* thrown.
*/ */
template<typename String> template<typename String>
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; std::error_code error;
map(path, offset, length, 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 * The same as invoking the `map` function, except any error that may occur
* establishing the mapping is thrown. * 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; std::error_code error;
map(handle, offset, length, 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 * `basic_mmap` has single-ownership semantics, so transferring ownership
* accomplished by moving the object. * may only be accomplished by moving the object.
*/ */
basic_mmap(basic_mmap&&) = default; basic_mmap(const basic_mmap&) = delete;
basic_mmap& operator=(basic_mmap&&) = default; 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, * 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 * however, a mapped region of a file gets its own handle, which is returned by
* 'mapping_handle'. * 'mapping_handle'.
*/ */
handle_type file_handle() const noexcept { return impl_.file_handle(); } handle_type file_handle() const noexcept { return file_handle_; }
handle_type mapping_handle() const noexcept { return impl_.mapping_handle(); } handle_type mapping_handle() const noexcept;
/** Returns whether a valid memory mapping has been created. */ /** 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 * Returns true if no mapping was established, that is, conceptually the
* same as though the length that was mapped was 0. This function is * same as though the length that was mapped was 0. This function is
* provided so that this class has Container semantics. * 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 * `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 * bytes that were mapped which is a multiple of the underlying operating system's
* page allocation granularity. * page allocation granularity.
*/ */
size_type size() const noexcept { return impl_.length(); } size_type size() const noexcept { return length(); }
size_type length() const noexcept { return impl_.length(); } size_type length() const noexcept { return length_; }
size_type mapped_length() const noexcept { return impl_.mapped_length(); } size_type mapped_length() const noexcept { return mapped_length_; }
/** /**
* Returns the offset, relative to the file's start, at which the mapping was * Returns the offset, relative to the file's start, at which the mapping was
* requested to be created. * 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 * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
@ -136,8 +187,8 @@ public:
template< template<
access_mode A = AccessMode, access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type typename = typename std::enable_if<A == access_mode::write>::type
> pointer data() noexcept { return impl_.data(); } > pointer data() noexcept { return data_; }
const_pointer data() const noexcept { return impl_.data(); } const_pointer data() const noexcept { return data_; }
/** /**
* Returns an iterator to the first requested byte, if a valid memory mapping * Returns an iterator to the first requested byte, if a valid memory mapping
@ -146,9 +197,9 @@ public:
template< template<
access_mode A = AccessMode, access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type typename = typename std::enable_if<A == access_mode::write>::type
> iterator begin() noexcept { return impl_.begin(); } > iterator begin() noexcept { return data(); }
const_iterator begin() const noexcept { return impl_.begin(); } const_iterator begin() const noexcept { return data(); }
const_iterator cbegin() const noexcept { return impl_.cbegin(); } const_iterator cbegin() const noexcept { return data(); }
/** /**
* Returns an iterator one past the last requested byte, if a valid memory mapping * Returns an iterator one past the last requested byte, if a valid memory mapping
@ -157,9 +208,9 @@ public:
template< template<
access_mode A = AccessMode, access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type typename = typename std::enable_if<A == access_mode::write>::type
> iterator end() noexcept { return impl_.end(); } > iterator end() noexcept { return data() + length(); }
const_iterator end() const noexcept { return impl_.end(); } const_iterator end() const noexcept { return data() + length(); }
const_iterator cend() const noexcept { return impl_.cend(); } const_iterator cend() const noexcept { return data() + length(); }
/** /**
* Returns a reverse iterator to the last memory mapped byte, if a valid * Returns a reverse iterator to the last memory mapped byte, if a valid
@ -169,9 +220,11 @@ public:
template< template<
access_mode A = AccessMode, access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type typename = typename std::enable_if<A == access_mode::write>::type
> reverse_iterator rbegin() noexcept { return impl_.rbegin(); } > reverse_iterator rbegin() noexcept { return reverse_iterator(end()); }
const_reverse_iterator rbegin() const noexcept { return impl_.rbegin(); } const_reverse_iterator rbegin() const noexcept
const_reverse_iterator crbegin() const noexcept { return impl_.crbegin(); } { 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 * Returns a reverse iterator past the first mapped byte, if a valid memory
@ -180,17 +233,19 @@ public:
template< template<
access_mode A = AccessMode, access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::type typename = typename std::enable_if<A == access_mode::write>::type
> reverse_iterator rend() noexcept { return impl_.rend(); } > reverse_iterator rend() noexcept { return reverse_iterator(begin()); }
const_reverse_iterator rend() const noexcept { return impl_.rend(); } const_reverse_iterator rend() const noexcept
const_reverse_iterator crend() const noexcept { return impl_.crend(); } { 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 * 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 * by `data`). If this is invoked when no valid memory mapping has been created
* prior to this call, undefined behaviour ensues. * prior to this call, undefined behaviour ensues.
*/ */
reference operator[](const size_type i) noexcept { return impl_[i]; } reference operator[](const size_type i) noexcept { return data_[i]; }
const_reference operator[](const size_type i) const noexcept { return impl_[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 * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the
@ -214,16 +269,31 @@ public:
*/ */
template<typename String> template<typename String>
void map(const String& path, const size_type offset, void map(const String& path, const size_type offset,
const size_type length, std::error_code& error) const size_type length, std::error_code& error);
{
impl_.map(path, offset, length, AccessMode, error);
}
/** /**
* Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the * 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 * reason is reported via `error` and the object remains in a state as if this
* function hadn't been called. * 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 * `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 * requested region. Upon failure, `error` is set to indicate the reason and the
* object remains in an unmapped state. * object remains in an unmapped state.
@ -239,9 +309,22 @@ public:
* case a mapping of the entire file is created. * case a mapping of the entire file is created.
*/ */
void map(const handle_type handle, const size_type offset, 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 * 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. * 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`. */ /** Flushes the memory mapped page to disk. Errors are reported via `error`. */
template< template<access_mode A = AccessMode>
access_mode A = AccessMode, typename std::enable_if<A == access_mode::write, void>::type
typename = typename std::enable_if<A == access_mode::write>::type sync(std::error_code& error);
> void sync(std::error_code& error) { impl_.sync(error); }
/** /**
* All operators compare the address of the first byte and size of the two mapped * All operators compare the address of the first byte and size of the two mapped
* regions. * regions.
*/ */
friend bool operator==(const basic_mmap& a, const basic_mmap& b) private:
template<
access_mode A = AccessMode,
typename = typename std::enable_if<A == access_mode::write>::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) /**
{ * The destructor syncs changes to disk if `AccessMode` is `write`, but not
return a.impl_ < b.impl_; * 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.
*/
friend bool operator<=(const basic_mmap& a, const basic_mmap& b) template<access_mode A = AccessMode>
{ typename std::enable_if<A == access_mode::write, void>::type
return a.impl_ <= b.impl_; conditional_sync();
} template<access_mode A = AccessMode>
typename std::enable_if<A == access_mode::read, void>::type conditional_sync();
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_;
}
}; };
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 * This is the basis for all read-only mmap objects and should be preferred over
* directly using `basic_mmap`. * directly using `basic_mmap`.
@ -323,12 +424,15 @@ using ummap_source = basic_mmap_source<unsigned char>;
using mmap_sink = basic_mmap_sink<char>; using mmap_sink = basic_mmap_sink<char>;
using ummap_sink = basic_mmap_sink<unsigned char>; using ummap_sink = basic_mmap_sink<unsigned char>;
/** 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< template<
typename MMap, typename MMap,
typename MappingToken typename MappingToken
> MMap make_mmap(const MappingToken& token, > 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 mmap;
mmap.map(token, offset, length, error); mmap.map(token, offset, length, error);
@ -344,11 +448,17 @@ template<
*/ */
template<typename MappingToken> template<typename MappingToken>
mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, 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<mmap_source>(token, offset, length, 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. * Convenience factory method.
* *
@ -358,11 +468,19 @@ mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type o
*/ */
template<typename MappingToken> template<typename MappingToken>
mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, 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<mmap_sink>(token, offset, length, 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 } // namespace mio
#include "detail/mmap.ipp"
#endif // MIO_MMAP_HEADER #endif // MIO_MMAP_HEADER

View File

@ -88,28 +88,35 @@ public:
} }
/** /**
* The same as invoking the `map` function, except any error that may occur while * The same as invoking the `map` function, except any error that may occur
* establishing the mapping is thrown. * while establishing the mapping is wrapped in a `std::system_error` and is
* thrown.
*/ */
template<typename String> template<typename String>
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; std::error_code error;
map(path, offset, length, 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 * The same as invoking the `map` function, except any error that may occur
* establishing the mapping is thrown. * 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; std::error_code error;
map(handle, offset, length, 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; ~basic_shared_mmap() = default;
/** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ /** 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 * however, a mapped region of a file gets its own handle, which is returned by
* 'mapping_handle'. * 'mapping_handle'.
*/ */
handle_type file_handle() const noexcept { return pimpl_->file_handle(); } handle_type file_handle() const noexcept
handle_type mapping_handle() const noexcept { return pimpl_->mapping_handle(); } {
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. */ /** Returns whether a valid memory mapping has been created. */
bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } 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 size() const noexcept { return pimpl_ ? pimpl_->length() : 0; }
size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; }
size_type mapped_length() const noexcept 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 * 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); 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 * 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 * 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); 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 * 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 * instructs the kernel to unmap the memory region and disassociate this object

View File

@ -5,19 +5,8 @@
#include <algorithm> #include <algorithm>
#include <fstream> #include <fstream>
int handle_error(const std::error_code& error) int handle_error(const std::error_code& error);
{ void allocate_file(const std::string& path, const int size);
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 main() int main()
{ {
@ -49,21 +38,38 @@ int main()
const int answer_index = rw_mmap.size() / 2; const int answer_index = rw_mmap.size() / 2;
rw_mmap[answer_index] = 42; rw_mmap[answer_index] = 42;
// Don't forget to flush changes to disk, which is NOT done by the destructor for // Don't forget to flush changes to disk before unmapping. However, if
// more explicit control of this potentially expensive operation. // `rw_mmap` were to go out of scope at this point, the destructor would also
// automatically invoke `sync` before `unmap`.
rw_mmap.sync(error); rw_mmap.sync(error);
if (error) { return handle_error(error); } if (error) { return handle_error(error); }
// We can then remove the mapping, after which rw_mmap will be in a default // 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 // constructed state, i.e. this and the above call to `sync` have the same
// invoked. // effect as if the destructor had been invoked.
rw_mmap.unmap(); rw_mmap.unmap();
// Now create the same mapping, but in read-only mode. // Now create the same mapping, but in read-only mode. Note that calling the
mio::mmap_source ro_mmap = mio::make_mmap_source( // overload without the offset and file length parameters maps the entire
path, 0, mio::map_entire_file, error); // file.
mio::mmap_source ro_mmap;
ro_mmap.map(path, error);
if (error) { return handle_error(error); } if (error) { return handle_error(error); }
const int the_answer_to_everything = ro_mmap[answer_index]; const int the_answer_to_everything = ro_mmap[answer_index];
assert(the_answer_to_everything == 42); 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;
}

View File

@ -89,8 +89,7 @@ int main()
CHECK_INVALID_MMAP(m); CHECK_INVALID_MMAP(m);
// Invalid handle? // Invalid handle?
m = mio::make_mmap_source( m = mio::make_mmap_source(mio::invalid_handle, 0, 0, error);
INVALID_HANDLE_VALUE/*Psst... This is an implementation detail!*/, 0, 0, error);
CHECK_INVALID_MMAP(m); CHECK_INVALID_MMAP(m);
// Invalid offset? // Invalid offset?
@ -102,6 +101,11 @@ int main()
// Make sure custom types compile. // Make sure custom types compile.
mio::ummap_source _1; mio::ummap_source _1;
mio::shared_ummap_source _2; 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<mio::shared_mmap_source>(path, 0, mio::map_entire_file, error);
} }
std::printf("all tests passed!\n"); std::printf("all tests passed!\n");