API changes, windows support, bugfixes

This commit is contained in:
mandreyel 2017-09-28 12:56:27 +02:00
parent cede64f865
commit 3a8722c27e
4 changed files with 230 additions and 106 deletions

View File

@ -3,6 +3,7 @@
#include <iterator>
#include <string>
#include <system_error>
#ifdef _WIN32
# ifndef WIN32_LEAN_AND_MEAN
@ -71,25 +72,10 @@ public:
mmap& operator=(mmap&&);
~mmap();
/**
* On *nix 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.
*/
bool is_open() const noexcept;
bool is_mapped() const noexcept;
bool empty() const noexcept { return length() == 0; }
/**
* size/length returns the logical length (i.e. the one user requested), while
* mapped_length returns the actual mapped length, which is usually a multiple of
* the OS' page size.
*/
size_type size() const noexcept { return length_; }
size_type length() const noexcept { return length_; }
size_type mapped_length() const noexcept { return mapped_length_; }
@ -116,8 +102,6 @@ public:
reference operator[](const size_type i) noexcept { return data_[i]; }
const_reference operator[](const size_type i) const noexcept { return data_[i]; }
void map(const std::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();
@ -131,9 +115,6 @@ public:
private:
static handle_type open_file(const std::string& path,
const access_mode mode, std::error_code& error);
pointer get_mapping_start() noexcept;
/** NOTE: file_handle_ must be valid. */
@ -145,6 +126,10 @@ private:
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

View File

@ -4,12 +4,15 @@
#include "mmap_impl.hpp"
#include <algorithm>
#include <type_traits>
#ifndef _WIN32
# include <unistd.h>
# include <fcntl.h>
# include <sys/mman.h>
# include <sys/stat.h>
# include <cassert>
# include <cstdint>
#endif
namespace mio {
@ -105,45 +108,6 @@ inline mmap& mmap::operator=(mmap&& other)
return *this;
}
inline void mmap::map(const std::string& path, const size_type offset,
const size_type length, const access_mode mode, std::error_code& error)
{
if(!path.empty() && !is_open())
{
error.clear();
const auto handle = open_file(path, mode, error);
if(error) { return; }
map(handle, offset, length, mode, error);
}
}
inline mmap::handle_type mmap::open_file(const std::string& path,
const mmap::access_mode mode, std::error_code& error)
{
#if defined(_WIN32)
const auto handle = ::CreateFile(path.c_str(),
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(path.c_str(),
mode == mmap::access_mode::read_only ? O_RDONLY : O_RDWR);
if(handle == -1)
{
error = last_error();
}
#endif
return handle;
}
inline void mmap::map(const handle_type handle, const size_type offset,
const size_type length, const access_mode mode, std::error_code& error)
{
@ -269,13 +233,13 @@ inline void mmap::unmap()
inline mmap::size_type mmap::query_file_size(std::error_code& error) noexcept
{
#ifdef _WIN32
PLARGE_INTEGER file_size;
LARGE_INTEGER file_size;
if(::GetFileSizeEx(file_handle_, &file_size) == 0)
{
error = last_error();
return 0;
}
return file_size;
return static_cast<size_type>(file_size.QuadPart);
#else
struct stat sbuf;
if(::fstat(file_handle_, &sbuf) == -1)
@ -345,6 +309,53 @@ 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

View File

@ -2,6 +2,7 @@
#define MIO_MMAP_HEADER
#include "detail/mmap_impl.hpp"
#include <system_error>
namespace mio {
@ -10,14 +11,11 @@ namespace mio {
* 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 std::unique_ptr<> semantics, thus only a single entity may own
* a mapping to a file at any given time.
* 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.
*
* For now, both classes may only be used with an existing open file by providing the
* file's handle.
*
* 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.
*/
@ -45,13 +43,18 @@ public:
using handle_type = impl_type::handle_type;
mmap_source() = default;
mmap_source(const std::string& path, const size_type offset, const size_type length)
{
std::error_code error;
map(path, offset, length, error);
if(error) { throw error; }
}
/**
* `handle` must be a valid file handle, which is then used to memory map the
* requested region. Upon failure a `std::error_code` is thrown, detailing the
* cause of the error, 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.
*/
mmap_source(const handle_type handle, const size_type offset, const size_type length)
{
std::error_code error;
@ -60,54 +63,92 @@ public:
}
/**
* On *nix systems is_open and is_mapped are the same and don't actually say if
* `mmap_source` has single-ownership semantics, so transferring ownership may only
* be accomplished by moving the instance.
*/
mmap_source(const mmap_source&) = delete;
mmap_source(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.
* mapping handle is required, so `is_open` checks for a valid file handle, while
* `is_mapped` checks for a valid mapping handle.
*/
bool is_open() const noexcept { return impl_.is_open(); }
bool is_mapped() const noexcept { return impl_.is_mapped(); }
bool empty() const noexcept { return impl_.empty(); }
/**
* size/length returns the logical length (i.e. the one user requested), while
* mapped_length returns the actual mapped length, which is usually a multiple of
* the OS' page size.
* `size` and `length` both return the logical length (i.e. the one user requested),
* while `mapped_length` returns the actual mapped length, which is usually a
* multiple of the underlying operating system's page allocation granularity.
*/
size_type size() const noexcept { return impl_.size(); }
size_type length() const noexcept { return impl_.length(); }
size_type mapped_length() const noexcept { return impl_.mapped_length(); }
/**
* Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
* exists.
*/
const_pointer data() const noexcept { return impl_.data(); }
/**
* Returns an iterator to the first requested byte, if a valid memory mapping
* exists, otherwise this function call is equivalent to invoking `end`.
*/
const_iterator begin() const noexcept { return impl_.begin(); }
const_iterator cbegin() const noexcept { return impl_.cbegin(); }
/** Returns an iterator one past the last requested byte. */
const_iterator end() const noexcept { return impl_.end(); }
const_iterator cend() const noexcept { return impl_.cend(); }
const_reverse_iterator rbegin() const noexcept { return impl_.rbegin(); }
const_reverse_iterator crbegin() const noexcept { return impl_.crbegin(); }
const_reverse_iterator rend() const noexcept { return impl_.rend(); }
const_reverse_iterator crend() const noexcept { return impl_.crend(); }
/**
* Returns a reference to the `i`th byte from the first requested byte (as returned
* by `data`). If this is invoked when no valid memory mapping has been established
* prior to this call, undefined behaviour ensues.
*/
const_reference operator[](const size_type i) const noexcept { return impl_[i]; }
void map(const std::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.
*
* `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
* 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.
*/
void map(const handle_type handle, const size_type offset,
const size_type length, std::error_code& error)
{
impl_.map(handle, offset, length, impl_type::access_mode::read_only, error);
}
/**
* 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
* from the file.
*/
void unmap() { impl_.unmap(); }
void swap(mmap_source& other) { impl_.swap(other.impl_); }
@ -146,13 +187,18 @@ public:
using handle_type = impl_type::handle_type;
mmap_sink() = default;
mmap_sink(const std::string& path, const size_type offset, const size_type length)
{
std::error_code error;
map(path, offset, length, error);
if(error) { throw error; }
}
/**
* `handle` must be a valid file handle, which is then used to memory map the
* requested region. Upon failure a `std::error_code` is thrown, detailing the
* cause of the error, 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.
*/
mmap_sink(const handle_type handle, const size_type offset, const size_type length)
{
std::error_code error;
@ -161,35 +207,58 @@ public:
}
/**
* On *nix systems is_open and is_mapped are the same and don't actually say if
* `mmap_sink` has single-ownership semantics, so transferring ownership may only
* be accomplished by moving the instance.
*/
mmap_sink(const mmap_sink&) = delete;
mmap_sink(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.
*/
~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.
* mapping handle is required, so `is_open` checks for a valid file handle, while
* `is_mapped` checks for a valid mapping handle.
*/
bool is_open() const noexcept { return impl_.is_open(); }
bool is_mapped() const noexcept { return impl_.is_mapped(); }
bool empty() const noexcept { return impl_.empty(); }
/**
* size/length returns the logical length (i.e. the one user requested), while
* mapped_length returns the actual mapped length, which is usually a multiple of
* the OS' page size.
* `size` and `length` both return the logical length (i.e. the one user requested),
* while `mapped_length` returns the actual mapped length, which is usually a
* multiple of the underlying operating system's page allocation granularity.
*/
size_type size() const noexcept { return impl_.size(); }
size_type length() const noexcept { return impl_.length(); }
size_type mapped_length() const noexcept { return impl_.mapped_length(); }
/**
* Returns a pointer to the first requested byte, or `nullptr` if no memory mapping
* exists.
*/
pointer data() noexcept { return impl_.data(); }
const_pointer data() const noexcept { return impl_.data(); }
/**
* Returns an iterator to the first requested byte, if a valid memory mapping
* exists, otherwise this function call is equivalent to invoking `end`.
*/
iterator begin() noexcept { return impl_.begin(); }
const_iterator begin() const noexcept { return impl_.begin(); }
const_iterator cbegin() const noexcept { return impl_.cbegin(); }
/** Returns an iterator one past the last requested byte. */
iterator end() noexcept { return impl_.end(); }
const_iterator end() const noexcept { return impl_.end(); }
const_iterator cend() const noexcept { return impl_.cend(); }
@ -202,21 +271,38 @@ public:
const_reverse_iterator rend() const noexcept { return impl_.rend(); }
const_reverse_iterator crend() const noexcept { return impl_.crend(); }
/**
* Returns a reference to the `i`th byte from the first requested byte (as returned
* by `data`). If this is invoked when no valid memory mapping has been established
* 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]; }
void map(const std::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.
*
* `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
* 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.
*/
void map(const handle_type handle, const size_type offset,
const size_type length, std::error_code& error)
{
impl_.map(handle, offset, length, impl_type::access_mode::read_write, error);
}
/**
* 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
* from the file.
*/
void unmap() { impl_.unmap(); }
/** Flushes the memory mapped page to disk. */
@ -235,6 +321,43 @@ 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.
*
* Path may be `std::string`, `std::string_view`, `const char*`,
* `std::filesystem::path`, `std::vector<char>`, or similar.
*/
template<typename Path>
mmap_source make_mmap_source(const Path& path, 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); }
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.
*
* Path may be `std::string`, `std::string_view`, `const char*`,
* `std::filesystem::path`, `std::vector<char>`, or similar.
*/
template<typename Path>
mmap_sink make_mmap_sink(const Path& path, 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); }
return mmap;
}
} // namespace mio
#endif // MIO_MMAP_HEADER

View File

@ -6,9 +6,9 @@
#include <cassert>
#include <system_error>
int main(int argc, char** argv)
int main()
{
const char* test_file_name = argc >= 2 ? argv[1] : "test-file";
const char* test_file_name = "test-file";
std::string buffer(0x4000 - 250, 'M');
@ -17,8 +17,7 @@ int main(int argc, char** argv)
file.close();
std::error_code error;
mio::mmap_source file_view;
file_view.map(test_file_name, 0, buffer.size(), error);
mio::mmap_source file_view = mio::make_mmap_source(test_file_name, 0, buffer.size(), error);
if(error)
{
const auto& errmsg = error.message();
@ -39,4 +38,10 @@ int main(int argc, char** argv)
assert(0);
}
}
// 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);
std::printf("all tests passed!\n");
}