mirror of
https://github.com/vimpunk/mio.git
synced 2025-12-06 16:57:01 +08:00
further API changes and bugfixes
This commit is contained in:
parent
3a8722c27e
commit
2525a2e28b
@ -39,6 +39,8 @@ struct mmap
|
||||
using handle_type = int;
|
||||
#endif
|
||||
|
||||
static constexpr size_type use_full_file_size = 0;
|
||||
|
||||
enum class access_mode
|
||||
{
|
||||
read_only,
|
||||
@ -63,6 +65,12 @@ private:
|
||||
size_type length_ = 0;
|
||||
size_type mapped_length_ = 0;
|
||||
|
||||
// Letting user map a file using both an existing file handle and a path introcudes
|
||||
// some complexity 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:
|
||||
|
||||
mmap() = default;
|
||||
@ -72,6 +80,9 @@ public:
|
||||
mmap& operator=(mmap&&);
|
||||
~mmap();
|
||||
|
||||
handle_type file_handle() const noexcept { return file_handle_; }
|
||||
handle_type mapping_handle() const noexcept;
|
||||
|
||||
bool is_open() const noexcept;
|
||||
bool is_mapped() const noexcept;
|
||||
bool empty() const noexcept { return length() == 0; }
|
||||
@ -102,9 +113,13 @@ public:
|
||||
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(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,
|
||||
const access_mode mode, std::error_code& error);
|
||||
void unmap();
|
||||
void close();
|
||||
|
||||
void sync(std::error_code& error);
|
||||
|
||||
@ -117,19 +132,10 @@ private:
|
||||
|
||||
pointer get_mapping_start() noexcept;
|
||||
|
||||
/** NOTE: file_handle_ must be valid. */
|
||||
size_type query_file_size(std::error_code& error) noexcept;
|
||||
|
||||
void map(const size_type offset, const size_type length,
|
||||
void map(const size_type offset, size_type length,
|
||||
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 mio
|
||||
|
||||
|
||||
@ -2,17 +2,16 @@
|
||||
#define MIO_BASIC_MMAP_IMPL
|
||||
|
||||
#include "mmap_impl.hpp"
|
||||
#include "type_traits.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <type_traits>
|
||||
#include <cstdint>
|
||||
|
||||
#ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# include <fcntl.h>
|
||||
# include <sys/mman.h>
|
||||
# include <sys/stat.h>
|
||||
# include <cassert>
|
||||
# include <cstdint>
|
||||
#endif
|
||||
|
||||
namespace mio {
|
||||
@ -48,7 +47,7 @@ inline size_t page_size()
|
||||
inline size_t make_page_aligned(size_t offset) noexcept
|
||||
{
|
||||
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_;
|
||||
}
|
||||
|
||||
@ -63,6 +62,58 @@ inline std::error_code last_error() noexcept
|
||||
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 --
|
||||
|
||||
inline mmap::~mmap()
|
||||
@ -78,6 +129,7 @@ inline mmap::mmap(mmap&& other)
|
||||
#ifdef _WIN32
|
||||
, file_mapping_handle_(std::move(other.file_mapping_handle_))
|
||||
#endif
|
||||
, is_handle_internal_(std::move(other.is_handle_internal_))
|
||||
{
|
||||
other.data_ = nullptr;
|
||||
other.length_ = other.mapped_length_ = 0;
|
||||
@ -91,6 +143,8 @@ inline mmap& mmap::operator=(mmap&& other)
|
||||
{
|
||||
if(this != &other)
|
||||
{
|
||||
// First the existing mapping needs to be removed.
|
||||
unmap();
|
||||
data_ = std::move(other.data_);
|
||||
length_ = std::move(other.length_);
|
||||
mapped_length_ = std::move(other.mapped_length_);
|
||||
@ -98,18 +152,52 @@ inline mmap& mmap::operator=(mmap&& other)
|
||||
#ifdef _WIN32
|
||||
file_mapping_handle_ = std::move(other.file_mapping_handle_);
|
||||
#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.length_ = other.mapped_length_ = 0;
|
||||
other.file_handle_ = INVALID_HANDLE_VALUE;
|
||||
#ifdef _WIN32
|
||||
other.file_mapping_handle_ = INVALID_HANDLE_VALUE;
|
||||
#endif
|
||||
other.is_handle_internal_ = false;
|
||||
}
|
||||
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,
|
||||
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();
|
||||
if(handle == INVALID_HANDLE_VALUE)
|
||||
@ -118,17 +206,21 @@ inline void mmap::map(const handle_type handle, const size_type offset,
|
||||
return;
|
||||
}
|
||||
|
||||
file_handle_ = handle;
|
||||
|
||||
const auto file_size = query_file_size(error);
|
||||
const auto file_size = query_file_size(handle, error);
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
file_handle_ = handle;
|
||||
is_handle_internal_ = false;
|
||||
map(offset, length, mode, error);
|
||||
}
|
||||
|
||||
@ -165,7 +257,7 @@ inline void mmap::map(const size_type offset, const size_type length,
|
||||
}
|
||||
#else
|
||||
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,
|
||||
mode == mmap::access_mode::read_only ? PROT_READ : PROT_WRITE,
|
||||
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)
|
||||
{
|
||||
error.clear();
|
||||
verify_file_handle(error);
|
||||
if(error) { return; }
|
||||
if(!is_open())
|
||||
{
|
||||
error = std::make_error_code(std::errc::bad_file_descriptor);
|
||||
return;
|
||||
}
|
||||
|
||||
if(data() != nullptr)
|
||||
{
|
||||
@ -211,17 +306,32 @@ inline void mmap::sync(std::error_code& error)
|
||||
|
||||
inline void mmap::unmap()
|
||||
{
|
||||
if(!is_open()) { return; }
|
||||
// TODO do we care about errors here?
|
||||
if((data_ != nullptr) && (file_handle_ != INVALID_HANDLE_VALUE))
|
||||
{
|
||||
#ifdef _WIN32
|
||||
if(is_mapped())
|
||||
{
|
||||
::UnmapViewOfFile(get_mapping_start());
|
||||
::CloseHandle(file_mapping_handle_);
|
||||
file_mapping_handle_ = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
#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
|
||||
}
|
||||
|
||||
// Reset fields to their default values.
|
||||
data_ = nullptr;
|
||||
length_ = mapped_length_ = 0;
|
||||
file_handle_ = INVALID_HANDLE_VALUE;
|
||||
@ -230,27 +340,6 @@ inline void mmap::unmap()
|
||||
#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
|
||||
{
|
||||
if(!data_) { return nullptr; }
|
||||
@ -258,14 +347,6 @@ inline mmap::pointer mmap::get_mapping_start() noexcept
|
||||
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
|
||||
{
|
||||
return file_handle_ != INVALID_HANDLE_VALUE;
|
||||
@ -292,6 +373,7 @@ inline void mmap::swap(mmap& other)
|
||||
#endif
|
||||
swap(length_, other.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);
|
||||
}
|
||||
|
||||
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 mio
|
||||
|
||||
|
||||
53
include/mio/detail/type_traits.hpp
Normal file
53
include/mio/detail/type_traits.hpp
Normal 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
|
||||
@ -2,24 +2,11 @@
|
||||
#define MIO_MMAP_HEADER
|
||||
|
||||
#include "detail/mmap_impl.hpp"
|
||||
|
||||
#include <system_error>
|
||||
|
||||
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. */
|
||||
class mmap_source
|
||||
{
|
||||
@ -42,6 +29,17 @@ public:
|
||||
using iterator_category = impl_type::iterator_category;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -63,27 +61,31 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* `mmap_source` has single-ownership semantics, so transferring ownership may only
|
||||
* be accomplished by moving the instance.
|
||||
* This class has single-ownership semantics, so transferring ownership may only be
|
||||
* accomplished by moving the object.
|
||||
*/
|
||||
mmap_source(const mmap_source&) = delete;
|
||||
mmap_source(mmap_source&&) = default;
|
||||
mmap_source& operator=(mmap_source&&) = default;
|
||||
|
||||
/** The destructor invokes unmap. */
|
||||
~mmap_source() = default;
|
||||
|
||||
/**
|
||||
* On UNIX systems `is_open` and `is_mapped` are the same and don't actually say if
|
||||
* the file itself is open or closed, they only refer to the mapping. This is
|
||||
* because a mapping remains valid (as long as it's not unmapped) even if another
|
||||
* 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.
|
||||
* On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
|
||||
* however, a mapped region of a file gets its own handle, which is returned by
|
||||
* 'mapping_handle'.
|
||||
*/
|
||||
handle_type file_handle() const noexcept { return impl_.file_handle(); }
|
||||
handle_type mapping_handle() const noexcept { return impl_.mapping_handle(); }
|
||||
|
||||
/** Returns whether a valid memory mapping has been created. */
|
||||
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(); }
|
||||
|
||||
/**
|
||||
@ -120,13 +122,40 @@ public:
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
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
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
void map(const handle_type handle, const size_type offset,
|
||||
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
|
||||
* instructs the kernel to unmap the memory region and dissasociate this object
|
||||
* If a valid memory mapping has been created prior to this call, this call
|
||||
* instructs the kernel to unmap the memory region and disassociate this object
|
||||
* from the file.
|
||||
*
|
||||
* The file handle associated with the file that is mapped is only closed if the
|
||||
* mapping was created using a file path. If, on the other hand, an existing
|
||||
* file handle was used to create the mapping, the file handle is not closed.
|
||||
*/
|
||||
void unmap() { impl_.unmap(); }
|
||||
|
||||
@ -186,6 +221,17 @@ public:
|
||||
using iterator_category = impl_type::iterator_category;
|
||||
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;
|
||||
|
||||
/**
|
||||
@ -207,31 +253,35 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* `mmap_sink` has single-ownership semantics, so transferring ownership may only
|
||||
* be accomplished by moving the instance.
|
||||
* This class has single-ownership semantics, so transferring ownership may only be
|
||||
* accomplished by moving the object.
|
||||
*/
|
||||
mmap_sink(const mmap_sink&) = delete;
|
||||
mmap_sink(mmap_sink&&) = default;
|
||||
mmap_sink& operator=(mmap_sink&&) = default;
|
||||
|
||||
/**
|
||||
* The destructor invokes unmap, but does NOT invoke `sync`. Thus, if changes have
|
||||
* been made to the mapped region, `sync` needs to be called in order to persist
|
||||
* any writes to disk.
|
||||
* The destructor invokes unmap, but does NOT invoke `sync`. Thus, if the mapped
|
||||
* region has been written to, `sync` needs to be called in order to persist the
|
||||
* changes to disk.
|
||||
*/
|
||||
~mmap_sink() = default;
|
||||
|
||||
/**
|
||||
* On UNIX systems `is_open` and `is_mapped` are the same and don't actually say if
|
||||
* the file itself is open or closed, they only refer to the mapping. This is
|
||||
* because a mapping remains valid (as long as it's not unmapped) even if another
|
||||
* 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.
|
||||
* On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows,
|
||||
* however, a mapped region of a file gets its own handle, which is returned by
|
||||
* 'mapping_handle'.
|
||||
*/
|
||||
handle_type file_handle() const noexcept { return impl_.file_handle(); }
|
||||
handle_type mapping_handle() const noexcept { return impl_.mapping_handle(); }
|
||||
|
||||
/** Returns whether a valid memory mapping has been created. */
|
||||
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(); }
|
||||
|
||||
/**
|
||||
@ -273,14 +323,41 @@ public:
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
reference operator[](const size_type i) 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
|
||||
* 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
|
||||
* 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.
|
||||
*/
|
||||
void map(const handle_type handle, const size_type offset,
|
||||
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
|
||||
* instructs the kernel to unmap the memory region and dissasociate this object
|
||||
* If a valid memory mapping has been created prior to this call, this call
|
||||
* instructs the kernel to unmap the memory region and disassociate this object
|
||||
* from the file.
|
||||
*
|
||||
* The file handle associated with the file that is mapped is only closed if the
|
||||
* mapping was created using a file path. If, on the other hand, an existing
|
||||
* file handle was used to create the mapping, the file handle is not closed.
|
||||
*/
|
||||
void unmap() { impl_.unmap(); }
|
||||
|
||||
@ -322,39 +405,34 @@ public:
|
||||
};
|
||||
|
||||
/**
|
||||
* Since `mmap_source` works on the file descriptor/handle level of abstraction, a
|
||||
* factory method is provided for the case when a file needs to be mapped using a file
|
||||
* path.
|
||||
* Convenience factory method.
|
||||
*
|
||||
* Path may be `std::string`, `std::string_view`, `const char*`,
|
||||
* `std::filesystem::path`, `std::vector<char>`, or similar.
|
||||
* MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
|
||||
* `std::filesystem::path`, `std::vector<char>`, or similar), or a
|
||||
* mmap_source::file_handle.
|
||||
*/
|
||||
template<typename Path>
|
||||
mmap_source make_mmap_source(const Path& path, mmap_source::size_type offset,
|
||||
template<typename MappingToken>
|
||||
mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset,
|
||||
mmap_source::size_type length, std::error_code& error)
|
||||
{
|
||||
const auto handle = detail::open_file(path,
|
||||
detail::mmap::access_mode::read_only, error);
|
||||
mmap_source mmap;
|
||||
if(!error) { mmap.map(handle, offset, length, error); }
|
||||
mmap.map(token, offset, length, error);
|
||||
return mmap;
|
||||
}
|
||||
|
||||
/**
|
||||
* Since `mmap_sink` works on the file descriptor/handle level of abstraction, a factory
|
||||
* method is provided for the case when a file needs to be mapped using a file path.
|
||||
* Convenience factory method.
|
||||
*
|
||||
* Path may be `std::string`, `std::string_view`, `const char*`,
|
||||
* `std::filesystem::path`, `std::vector<char>`, or similar.
|
||||
* MappingToken may be a String (`std::string`, `std::string_view`, `const char*`,
|
||||
* `std::filesystem::path`, `std::vector<char>`, or similar), or a
|
||||
* mmap_sink::file_handle.
|
||||
*/
|
||||
template<typename Path>
|
||||
mmap_sink make_mmap_sink(const Path& path, mmap_sink::size_type offset,
|
||||
template<typename MappingToken>
|
||||
mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset,
|
||||
mmap_sink::size_type length, std::error_code& error)
|
||||
{
|
||||
const auto handle = detail::open_file(path,
|
||||
detail::mmap::access_mode::read_write, error);
|
||||
mmap_sink mmap;
|
||||
if(!error) { mmap.map(handle, offset, length, error); }
|
||||
mmap.map(token, offset, length, error);
|
||||
return mmap;
|
||||
}
|
||||
|
||||
|
||||
@ -8,27 +8,33 @@
|
||||
|
||||
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::ofstream file(test_file_name);
|
||||
std::ofstream file(file_path);
|
||||
file << buffer;
|
||||
file.close();
|
||||
|
||||
// Map the region of the file to which buffer was written.
|
||||
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)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
assert(file_view.is_open());
|
||||
assert(file_view.is_mapped());
|
||||
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)
|
||||
{
|
||||
if(file_view[i] != buffer[i])
|
||||
@ -39,9 +45,39 @@ int main()
|
||||
}
|
||||
}
|
||||
|
||||
// see if mapping an invalid file results in an error
|
||||
mio::make_mmap_source("garbage-that-hopefully-doesn't exist", 0, 0, error);
|
||||
assert(error);
|
||||
#define CHECK_INVALID_MMAP(m) do { \
|
||||
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");
|
||||
}
|
||||
|
||||
// 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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user