further API changes and bugfixes

This commit is contained in:
mandreyel 2017-09-29 12:42:14 +02:00
parent 3a8722c27e
commit 2525a2e28b
5 changed files with 386 additions and 178 deletions

View File

@ -39,6 +39,8 @@ struct mmap
using handle_type = int; using handle_type = int;
#endif #endif
static constexpr size_type use_full_file_size = 0;
enum class access_mode enum class access_mode
{ {
read_only, read_only,
@ -63,6 +65,12 @@ private:
size_type length_ = 0; size_type length_ = 0;
size_type mapped_length_ = 0; size_type mapped_length_ = 0;
// Letting user map a file using both an existing file handle and a path introcudes
// some complexity in that we must not close the file handle if user provided it,
// but we must close it if we obtained it using the provided path. For this reason,
// this flag is used to determine when to close file_handle_.
bool is_handle_internal_;
public: public:
mmap() = default; mmap() = default;
@ -72,6 +80,9 @@ public:
mmap& operator=(mmap&&); mmap& operator=(mmap&&);
~mmap(); ~mmap();
handle_type file_handle() const noexcept { return file_handle_; }
handle_type mapping_handle() const noexcept;
bool is_open() const noexcept; bool is_open() const noexcept;
bool is_mapped() const noexcept; bool is_mapped() const noexcept;
bool empty() const noexcept { return length() == 0; } bool empty() const noexcept { return length() == 0; }
@ -102,9 +113,13 @@ public:
reference operator[](const size_type i) noexcept { return data_[i]; } reference operator[](const size_type i) noexcept { return data_[i]; }
const_reference operator[](const size_type i) const noexcept { return data_[i]; } const_reference operator[](const size_type i) const noexcept { return data_[i]; }
template<typename String>
void map(const String& path, const size_type offset, const size_type length,
const access_mode mode, std::error_code& error);
void map(const handle_type handle, const size_type offset, const size_type length, void map(const handle_type handle, const size_type offset, const size_type length,
const access_mode mode, std::error_code& error); const access_mode mode, std::error_code& error);
void unmap(); void unmap();
void close();
void sync(std::error_code& error); void sync(std::error_code& error);
@ -117,19 +132,10 @@ private:
pointer get_mapping_start() noexcept; pointer get_mapping_start() noexcept;
/** NOTE: file_handle_ must be valid. */ void map(const size_type offset, size_type length,
size_type query_file_size(std::error_code& error) noexcept;
void map(const size_type offset, const size_type length,
const access_mode mode, std::error_code& error); const access_mode mode, std::error_code& error);
void verify_file_handle(std::error_code& error) const noexcept;
}; };
template<typename Path>
mmap::handle_type open_file(const Path& path,
const mmap::access_mode mode, std::error_code& error);
} // namespace detail } // namespace detail
} // namespace mio } // namespace mio

View File

@ -2,17 +2,16 @@
#define MIO_BASIC_MMAP_IMPL #define MIO_BASIC_MMAP_IMPL
#include "mmap_impl.hpp" #include "mmap_impl.hpp"
#include "type_traits.hpp"
#include <algorithm> #include <algorithm>
#include <type_traits> #include <cstdint>
#ifndef _WIN32 #ifndef _WIN32
# include <unistd.h> # include <unistd.h>
# include <fcntl.h> # include <fcntl.h>
# include <sys/mman.h> # include <sys/mman.h>
# include <sys/stat.h> # include <sys/stat.h>
# include <cassert>
# include <cstdint>
#endif #endif
namespace mio { namespace mio {
@ -48,7 +47,7 @@ inline size_t page_size()
inline size_t make_page_aligned(size_t offset) noexcept inline size_t make_page_aligned(size_t offset) noexcept
{ {
const static size_t page_size_ = page_size(); const static size_t page_size_ = page_size();
// use integer division to round down to the nearest page alignment // Use integer division to round down to the nearest page alignment.
return offset / page_size_ * page_size_; return offset / page_size_ * page_size_;
} }
@ -63,6 +62,58 @@ inline std::error_code last_error() noexcept
return error; return error;
} }
template<typename Path>
mmap::handle_type open_file(const Path& path,
const mmap::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;
}
#if defined(_WIN32)
const auto handle = ::CreateFile(c_str(path),
mode == mmap::access_mode::read_only
? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
#else
const auto handle = ::open(c_str(path),
mode == mmap::access_mode::read_only ? O_RDONLY : O_RDWR);
#endif
if(handle == INVALID_HANDLE_VALUE)
{
error = last_error();
}
return handle;
}
inline mmap::size_type query_file_size(mmap::handle_type handle, std::error_code& error)
{
error.clear();
#ifdef _WIN32
LARGE_INTEGER file_size;
if(::GetFileSizeEx(handle, &file_size) == 0)
{
error = last_error();
return 0;
}
return static_cast<mmap::size_type>(file_size.QuadPart);
#else
struct stat sbuf;
if(::fstat(handle, &sbuf) == -1)
{
error = last_error();
return 0;
}
return sbuf.st_size;
#endif
}
// -- mmap -- // -- mmap --
inline mmap::~mmap() inline mmap::~mmap()
@ -78,6 +129,7 @@ inline mmap::mmap(mmap&& other)
#ifdef _WIN32 #ifdef _WIN32
, file_mapping_handle_(std::move(other.file_mapping_handle_)) , file_mapping_handle_(std::move(other.file_mapping_handle_))
#endif #endif
, is_handle_internal_(std::move(other.is_handle_internal_))
{ {
other.data_ = nullptr; other.data_ = nullptr;
other.length_ = other.mapped_length_ = 0; other.length_ = other.mapped_length_ = 0;
@ -91,6 +143,8 @@ inline mmap& mmap::operator=(mmap&& other)
{ {
if(this != &other) if(this != &other)
{ {
// First the existing mapping needs to be removed.
unmap();
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_);
@ -98,18 +152,52 @@ inline mmap& mmap::operator=(mmap&& other)
#ifdef _WIN32 #ifdef _WIN32
file_mapping_handle_ = std::move(other.file_mapping_handle_); file_mapping_handle_ = std::move(other.file_mapping_handle_);
#endif #endif
is_handle_internal_ = std::move(other.is_handle_internal_);
// The moved from 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.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_VALUE;
#ifdef _WIN32 #ifdef _WIN32
other.file_mapping_handle_ = INVALID_HANDLE_VALUE; other.file_mapping_handle_ = INVALID_HANDLE_VALUE;
#endif #endif
other.is_handle_internal_ = false;
} }
return *this; return *this;
} }
inline mmap::handle_type mmap::mapping_handle() const noexcept
{
#ifdef _WIN32
return file_mapping_handle_;
#else
return file_handle_;
#endif
}
template<typename String>
void mmap::map(const String& path, const size_type offset, const size_type length,
const access_mode mode, std::error_code& error)
{
error.clear();
if(detail::empty(path))
{
error = std::make_error_code(std::errc::invalid_argument);
return;
}
if(!is_open())
{
const auto handle = open_file(path, mode, error);
if(error) { return; }
map(handle, offset, length, mode, error);
// MUST be after the call to map, as that sets this to true.
is_handle_internal_ = true;
}
}
inline void mmap::map(const handle_type handle, const size_type offset, inline void mmap::map(const handle_type handle, const size_type offset,
const size_type length, const access_mode mode, std::error_code& error) size_type length, const access_mode mode, std::error_code& error)
{ {
error.clear(); error.clear();
if(handle == INVALID_HANDLE_VALUE) if(handle == INVALID_HANDLE_VALUE)
@ -118,17 +206,21 @@ inline void mmap::map(const handle_type handle, const size_type offset,
return; return;
} }
file_handle_ = handle; const auto file_size = query_file_size(handle, error);
const auto file_size = query_file_size(error);
if(error) { return; } if(error) { return; }
if(offset + length > file_size) if(length <= 0)
{
length = file_size;
}
else 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;
} }
file_handle_ = handle;
is_handle_internal_ = false;
map(offset, length, mode, error); map(offset, length, mode, error);
} }
@ -165,7 +257,7 @@ inline void mmap::map(const size_type offset, const size_type length,
} }
#else #else
const pointer mapping_start = static_cast<pointer>(::mmap( const pointer mapping_start = static_cast<pointer>(::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 == mmap::access_mode::read_only ? PROT_READ : PROT_WRITE, mode == mmap::access_mode::read_only ? PROT_READ : PROT_WRITE,
MAP_SHARED, // TODO do we want to share it? MAP_SHARED, // TODO do we want to share it?
@ -185,8 +277,11 @@ inline void mmap::map(const size_type offset, const size_type length,
inline void mmap::sync(std::error_code& error) inline void mmap::sync(std::error_code& error)
{ {
error.clear(); error.clear();
verify_file_handle(error); if(!is_open())
if(error) { return; } {
error = std::make_error_code(std::errc::bad_file_descriptor);
return;
}
if(data() != nullptr) if(data() != nullptr)
{ {
@ -211,17 +306,32 @@ inline void mmap::sync(std::error_code& error)
inline void mmap::unmap() inline void mmap::unmap()
{ {
if(!is_open()) { return; }
// TODO do we care about errors here? // TODO do we care about errors here?
if((data_ != nullptr) && (file_handle_ != INVALID_HANDLE_VALUE))
{
#ifdef _WIN32 #ifdef _WIN32
if(is_mapped())
{
::UnmapViewOfFile(get_mapping_start()); ::UnmapViewOfFile(get_mapping_start());
::CloseHandle(file_mapping_handle_); ::CloseHandle(file_mapping_handle_);
file_mapping_handle_ = INVALID_HANDLE_VALUE; file_mapping_handle_ = INVALID_HANDLE_VALUE;
}
#else #else
::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); }
#endif
// If file handle was obtained by our opening it (when map is called with a path,
// rather than an existing file handle), we need to close it, otherwise it must not
// be closed as it may still be used outside of this mmap instance.
if(is_handle_internal_)
{
#ifdef _WIN32
::CloseHandle(file_handle_);
#else
::close(file_handle_);
#endif #endif
} }
// 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_VALUE;
@ -230,27 +340,6 @@ inline void mmap::unmap()
#endif #endif
} }
inline mmap::size_type mmap::query_file_size(std::error_code& error) noexcept
{
#ifdef _WIN32
LARGE_INTEGER file_size;
if(::GetFileSizeEx(file_handle_, &file_size) == 0)
{
error = last_error();
return 0;
}
return static_cast<size_type>(file_size.QuadPart);
#else
struct stat sbuf;
if(::fstat(file_handle_, &sbuf) == -1)
{
error = last_error();
return 0;
}
return sbuf.st_size;
#endif
}
inline mmap::pointer mmap::get_mapping_start() noexcept inline mmap::pointer mmap::get_mapping_start() noexcept
{ {
if(!data_) { return nullptr; } if(!data_) { return nullptr; }
@ -258,14 +347,6 @@ inline mmap::pointer mmap::get_mapping_start() noexcept
return data_ - offset; return data_ - offset;
} }
inline void mmap::verify_file_handle(std::error_code& error) const noexcept
{
if(!is_open() || !is_mapped())
{
error = std::make_error_code(std::errc::bad_file_descriptor);
}
}
inline bool mmap::is_open() const noexcept inline bool mmap::is_open() const noexcept
{ {
return file_handle_ != INVALID_HANDLE_VALUE; return file_handle_ != INVALID_HANDLE_VALUE;
@ -292,6 +373,7 @@ inline void mmap::swap(mmap& other)
#endif #endif
swap(length_, other.length_); swap(length_, other.length_);
swap(mapped_length_, other.mapped_length_); swap(mapped_length_, other.mapped_length_);
swap(is_handle_internal_, other.is_handle_internal_);
} }
} }
@ -309,53 +391,6 @@ inline bool operator!=(const mmap& a, const mmap& b)
return !(a == b); return !(a == b);
} }
template<
typename String,
typename = decltype(std::declval<String>().data()),
typename = typename std::enable_if<!std::is_same<const char*, String>::value>::type
> const char* c_str(const String& path)
{
return path.data();
}
template<
typename String,
typename = typename std::enable_if<
std::is_same<const char*, typename std::decay<String>::type>::value
>::type
> const char* c_str(String path)
{
return path;
}
template<typename Path>
mmap::handle_type open_file(const Path& path,
const mmap::access_mode mode, std::error_code& error)
{
#if defined(_WIN32)
const auto handle = ::CreateFile(c_str(path),
mode == mmap::access_mode::read_only
? GENERIC_READ : GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
0,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
0);
if(handle == INVALID_HANDLE_VALUE)
{
error = last_error();
}
#else
const auto handle = ::open(c_str(path),
mode == mmap::access_mode::read_only ? O_RDONLY : O_RDWR);
if(handle == -1)
{
error = last_error();
}
#endif
return handle;
}
} // namespace detail } // namespace detail
} // namespace mio } // namespace mio

View File

@ -0,0 +1,53 @@
#ifndef MIO_TYPE_TRAITS_HEADER
#define MIO_TYPE_TRAITS_HEADER
#include <type_traits>
namespace mio {
namespace detail {
template<typename String> struct is_c_str
{
static constexpr bool value = std::is_same<
const char*, typename std::decay<String>::type
>::value;
};
template<
typename String,
typename = decltype(std::declval<String>().data()),
typename = typename std::enable_if<!is_c_str<String>::value>::type
> const char* c_str(const String& path)
{
return path.data();
}
template<
typename String,
typename = decltype(std::declval<String>().empty()),
typename = typename std::enable_if<!is_c_str<String>::value>::type
> bool empty(const String& path)
{
return path.empty();
}
template<
typename String,
typename = typename std::enable_if<is_c_str<String>::value>::type
> const char* c_str(String path)
{
return path;
}
template<
typename String,
typename = typename std::enable_if<is_c_str<String>::value>::type
> bool empty(String path)
{
return !path || (*path == 0);
}
} // namespace detail
} // namespace mio
#endif // MIO_TYPE_TRAITS_HEADER

View File

@ -2,24 +2,11 @@
#define MIO_MMAP_HEADER #define MIO_MMAP_HEADER
#include "detail/mmap_impl.hpp" #include "detail/mmap_impl.hpp"
#include <system_error> #include <system_error>
namespace mio { namespace mio {
/**
* When specifying a file to map, there is no need to worry about providing
* offsets that are aligned with the operating system's page granularity, this is taken
* care of within the class in both cases.
*
* Both classes have single-ownership semantics, and transferring ownership may be
* accomplished by moving the mmap instance.
*
* Remapping a file is possible, but unmap must be called before that.
*
* Both classes' destructors unmap the file. However, mmap_sink's destructor does not
* sync the mapped file view to disk, this has to be done manually with a call tosink.
*/
/** A read-only file memory mapping. */ /** A read-only file memory mapping. */
class mmap_source class mmap_source
{ {
@ -42,6 +29,17 @@ public:
using iterator_category = impl_type::iterator_category; using iterator_category = impl_type::iterator_category;
using handle_type = impl_type::handle_type; using handle_type = impl_type::handle_type;
/**
* 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.
*/
static constexpr size_type use_full_file_size = impl_type::use_full_file_size;
/**
* The default constructed mmap object is in a non-mapped state, that is, any
* operations that attempt to access nonexistent underlying date will result in
* undefined behaviour/segmentation faults.
*/
mmap_source() = default; mmap_source() = default;
/** /**
@ -63,27 +61,31 @@ public:
} }
/** /**
* `mmap_source` has single-ownership semantics, so transferring ownership may only * This class has single-ownership semantics, so transferring ownership may only be
* be accomplished by moving the instance. * accomplished by moving the object.
*/ */
mmap_source(const mmap_source&) = delete;
mmap_source(mmap_source&&) = default; mmap_source(mmap_source&&) = default;
mmap_source& operator=(mmap_source&&) = default;
/** The destructor invokes unmap. */ /** The destructor invokes unmap. */
~mmap_source() = default; ~mmap_source() = default;
/** /**
* On UNIX systems `is_open` and `is_mapped` are the same and don't actually say if * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
* the file itself is open or closed, they only refer to the mapping. This is * however, a mapped region of a file gets its own handle, which is returned by
* because a mapping remains valid (as long as it's not unmapped) even if another * 'mapping_handle'.
* entity closes the file which is being mapped.
*
* On Windows, however, in order to map a file, both an active file handle and a
* mapping handle is required, so `is_open` checks for a valid file handle, while
* `is_mapped` checks for a valid mapping handle.
*/ */
handle_type file_handle() const noexcept { return impl_.file_handle(); }
handle_type mapping_handle() const noexcept { return impl_.mapping_handle(); }
/** Returns whether a valid memory mapping has been created. */
bool is_open() const noexcept { return impl_.is_open(); } bool is_open() const noexcept { return impl_.is_open(); }
bool is_mapped() const noexcept { return impl_.is_mapped(); }
/**
* Returns if the length that was mapped was 0, in which case no mapping was
* established, i.e. `is_open` returns false. This function is provided so that
* this class has some Container semantics.
*/
bool empty() const noexcept { return impl_.empty(); } bool empty() const noexcept { return impl_.empty(); }
/** /**
@ -120,13 +122,40 @@ public:
/** /**
* 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 established * 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.
*/ */
const_reference operator[](const size_type i) const noexcept { return impl_[i]; } const_reference operator[](const size_type i) const noexcept { return impl_[i]; }
/** /**
* Establishes a read-only memory mapping. * Establishes a read-only memory mapping. 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.
*
* When specifying `offset`, there is no need to worry about providing
* a value that is aligned with the operating system's page allocation granularity.
* This is adjusted by the implementation such that the first requested byte (as
* returned by `data` or `begin`), so long as `offset` is valid, will be at `offset`
* from the start of the file.
*
* If `length` is `use_full_file_size`, a mapping of the entire file is created.
*/
template<typename String>
void map(const String& path, const size_type offset,
const size_type length, std::error_code& error)
{
impl_.map(path, offset, length, impl_type::access_mode::read_only, error);
}
/**
* Establishes a read-only memory mapping. 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` must be a valid file handle, which is then used to memory map the * `handle` must be a valid file handle, which is then 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
@ -137,6 +166,8 @@ public:
* This is adjusted by the implementation such that the first requested byte (as * This is adjusted by the implementation such that the first requested byte (as
* returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset`
* from the start of the file. * from the start of the file.
*
* If `length` is `use_full_file_size`, 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)
@ -145,9 +176,13 @@ public:
} }
/** /**
* If a valid memory mapping has been established 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 dissasociate this object * instructs the kernel to unmap the memory region and disassociate this object
* from the file. * from the file.
*
* The file handle associated with the file that is mapped is only closed if the
* mapping was created using a file path. If, on the other hand, an existing
* file handle was used to create the mapping, the file handle is not closed.
*/ */
void unmap() { impl_.unmap(); } void unmap() { impl_.unmap(); }
@ -185,7 +220,18 @@ public:
using const_reverse_iterator = std::reverse_iterator<const_iterator>; using const_reverse_iterator = std::reverse_iterator<const_iterator>;
using iterator_category = impl_type::iterator_category; using iterator_category = impl_type::iterator_category;
using handle_type = impl_type::handle_type; using handle_type = impl_type::handle_type;
/**
* 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.
*/
static constexpr size_type use_full_file_size = impl_type::use_full_file_size;
/**
* The default constructed mmap object is in a non-mapped state, that is, any
* operations that attempt to access nonexistent underlying date will result in
* undefined behaviour/segmentation faults.
*/
mmap_sink() = default; mmap_sink() = default;
/** /**
@ -207,31 +253,35 @@ public:
} }
/** /**
* `mmap_sink` has single-ownership semantics, so transferring ownership may only * This class has single-ownership semantics, so transferring ownership may only be
* be accomplished by moving the instance. * accomplished by moving the object.
*/ */
mmap_sink(const mmap_sink&) = delete;
mmap_sink(mmap_sink&&) = default; mmap_sink(mmap_sink&&) = default;
mmap_sink& operator=(mmap_sink&&) = default;
/** /**
* The destructor invokes unmap, but does NOT invoke `sync`. Thus, if changes have * The destructor invokes unmap, but does NOT invoke `sync`. Thus, if the mapped
* been made to the mapped region, `sync` needs to be called in order to persist * region has been written to, `sync` needs to be called in order to persist the
* any writes to disk. * changes to disk.
*/ */
~mmap_sink() = default; ~mmap_sink() = default;
/** /**
* On UNIX systems `is_open` and `is_mapped` are the same and don't actually say if * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
* the file itself is open or closed, they only refer to the mapping. This is * however, a mapped region of a file gets its own handle, which is returned by
* because a mapping remains valid (as long as it's not unmapped) even if another * 'mapping_handle'.
* entity closes the file which is being mapped.
*
* On Windows, however, in order to map a file, both an active file handle and a
* mapping handle is required, so `is_open` checks for a valid file handle, while
* `is_mapped` checks for a valid mapping handle.
*/ */
handle_type file_handle() const noexcept { return impl_.file_handle(); }
handle_type mapping_handle() const noexcept { return impl_.mapping_handle(); }
/** Returns whether a valid memory mapping has been created. */
bool is_open() const noexcept { return impl_.is_open(); } bool is_open() const noexcept { return impl_.is_open(); }
bool is_mapped() const noexcept { return impl_.is_mapped(); }
/**
* Returns if the length that was mapped was 0, in which case no mapping was
* established, i.e. `is_open` returns false. This function is provided so that
* this class has some Container semantics.
*/
bool empty() const noexcept { return impl_.empty(); } bool empty() const noexcept { return impl_.empty(); }
/** /**
@ -273,14 +323,41 @@ public:
/** /**
* 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 established * 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 impl_[i]; }
const_reference operator[](const size_type i) const noexcept { return impl_[i]; } const_reference operator[](const size_type i) const noexcept { return impl_[i]; }
/** /**
* Establishes a read-only memory mapping. * Establishes a read-write memory mapping. 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.
*
* When specifying `offset`, there is no need to worry about providing
* a value that is aligned with the operating system's page allocation granularity.
* This is adjusted by the implementation such that the first requested byte (as
* returned by `data` or `begin`), so long as `offset` is valid, will be at `offset`
* from the start of the file.
*
* If `length` is `use_full_file_size`, a mapping of the entire file is created.
*/
template<typename String>
void map(const String& path, const size_type offset,
const size_type length, std::error_code& error)
{
impl_.map(path, offset, length, impl_type::access_mode::read_only, error);
}
/**
* Establishes a read-write memory mapping. 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` must be a valid file handle, which is then used to memory map the * `handle` must be a valid file handle, which is then 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
@ -291,6 +368,8 @@ public:
* This is adjusted by the implementation such that the first requested byte (as * This is adjusted by the implementation such that the first requested byte (as
* returned by `data` or `begin`), so long as `offset` is valid, will be at `offset` * returned by `data` or `begin`), so long as `offset` is valid, will be at `offset`
* from the start of the file. * from the start of the file.
*
* If `length` is `use_full_file_size`, 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)
@ -299,9 +378,13 @@ public:
} }
/** /**
* If a valid memory mapping has been established 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 dissasociate this object * instructs the kernel to unmap the memory region and disassociate this object
* from the file. * from the file.
*
* The file handle associated with the file that is mapped is only closed if the
* mapping was created using a file path. If, on the other hand, an existing
* file handle was used to create the mapping, the file handle is not closed.
*/ */
void unmap() { impl_.unmap(); } void unmap() { impl_.unmap(); }
@ -322,39 +405,34 @@ public:
}; };
/** /**
* Since `mmap_source` works on the file descriptor/handle level of abstraction, a * Convenience factory method.
* factory method is provided for the case when a file needs to be mapped using a file
* path.
* *
* Path may be `std::string`, `std::string_view`, `const char*`, * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
* `std::filesystem::path`, `std::vector<char>`, or similar. * `std::filesystem::path`, `std::vector<char>`, or similar), or a
* mmap_source::file_handle.
*/ */
template<typename Path> template<typename MappingToken>
mmap_source make_mmap_source(const Path& path, 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)
{ {
const auto handle = detail::open_file(path,
detail::mmap::access_mode::read_only, error);
mmap_source mmap; mmap_source mmap;
if(!error) { mmap.map(handle, offset, length, error); } mmap.map(token, offset, length, error);
return mmap; return mmap;
} }
/** /**
* Since `mmap_sink` works on the file descriptor/handle level of abstraction, a factory * Convenience factory method.
* method is provided for the case when a file needs to be mapped using a file path.
* *
* Path may be `std::string`, `std::string_view`, `const char*`, * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
* `std::filesystem::path`, `std::vector<char>`, or similar. * `std::filesystem::path`, `std::vector<char>`, or similar), or a
* mmap_sink::file_handle.
*/ */
template<typename Path> template<typename MappingToken>
mmap_sink make_mmap_sink(const Path& path, 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)
{ {
const auto handle = detail::open_file(path,
detail::mmap::access_mode::read_write, error);
mmap_sink mmap; mmap_sink mmap;
if(!error) { mmap.map(handle, offset, length, error); } mmap.map(token, offset, length, error);
return mmap; return mmap;
} }

View File

@ -8,27 +8,33 @@
int main() int main()
{ {
const char* test_file_name = "test-file"; // Both are 40 bytes on UNIX, 48 on Windows (TODO verify).
//std::printf("sizeof(mio::mmap_source) = %i bytes\n", sizeof(mio::mmap_source));
//std::printf("sizeof(mio::mmap_sink) = %i bytes\n", sizeof(mio::mmap_source));
const char* file_path = "test-file";
// Fill buffer, then write it to file.
std::string buffer(0x4000 - 250, 'M'); std::string buffer(0x4000 - 250, 'M');
std::ofstream file(file_path);
std::ofstream file(test_file_name);
file << buffer; file << buffer;
file.close(); file.close();
// Map the region of the file to which buffer was written.
std::error_code error; std::error_code error;
mio::mmap_source file_view = mio::make_mmap_source(test_file_name, 0, buffer.size(), error); mio::mmap_source file_view = mio::make_mmap_source(file_path,
0, mio::mmap_source::use_full_file_size, error);
if(error) if(error)
{ {
const auto& errmsg = error.message(); const auto& errmsg = error.message();
std::printf("error: %s, exiting...\n", errmsg.c_str()); std::printf("error mapping file: %s, exiting...\n", errmsg.c_str());
return error.value(); return error.value();
} }
assert(file_view.is_open()); assert(file_view.is_open());
assert(file_view.is_mapped());
assert(file_view.size() == buffer.size()); assert(file_view.size() == buffer.size());
// Then verify that mmap's bytes correspond to that of buffer.
for(auto i = 0; i < buffer.size(); ++i) for(auto i = 0; i < buffer.size(); ++i)
{ {
if(file_view[i] != buffer[i]) if(file_view[i] != buffer[i])
@ -39,9 +45,39 @@ int main()
} }
} }
// see if mapping an invalid file results in an error #define CHECK_INVALID_MMAP(m) do { \
mio::make_mmap_source("garbage-that-hopefully-doesn't exist", 0, 0, error); assert(error); \
assert(error); assert(m.empty()); \
assert(!m.is_open()); \
error.clear(); } while(0)
mio::mmap_source m;
// FIXME move assignment DOES NOT WORK!
// See if mapping an invalid file results in an error.
m = mio::make_mmap_source("garbage-that-hopefully-doesnt-exist", 0, 0, error);
CHECK_INVALID_MMAP(m);
// Empty path?
m = mio::make_mmap_source(static_cast<const char*>(0), 0, 0, error);
CHECK_INVALID_MMAP(m);
m = mio::make_mmap_source(std::string(), 0, 0, error);
CHECK_INVALID_MMAP(m);
// Invalid handle?
m = mio::make_mmap_source(
INVALID_HANDLE_VALUE/*Psst... This is an implementation detail!*/, 0, 0, error);
CHECK_INVALID_MMAP(m);
// Invalid offset?
m = mio::make_mmap_source(file_path, 100 * buffer.size(), buffer.size(), error);
CHECK_INVALID_MMAP(m);
std::printf("all tests passed!\n"); std::printf("all tests passed!\n");
} }
// TODO consider the following API: type safe length and offset parameters, as these
// two are easily interchanged due to the same type, with the optional feature of
// arbitrary ordering.
//mio::mmap_source file_view = mio::make_mmap_source(file_path,
// mio::offset(0), mio::length(buffer.size()), error);