diff --git a/include/mio/detail/mmap_impl.hpp b/include/mio/detail/mmap_impl.hpp index 9695cfb..26fa79c 100644 --- a/include/mio/detail/mmap_impl.hpp +++ b/include/mio/detail/mmap_impl.hpp @@ -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 + 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 -mmap::handle_type open_file(const Path& path, - const mmap::access_mode mode, std::error_code& error); - } // namespace detail } // namespace mio diff --git a/include/mio/detail/mmap_impl.ipp b/include/mio/detail/mmap_impl.ipp index b72331c..2b90800 100644 --- a/include/mio/detail/mmap_impl.ipp +++ b/include/mio/detail/mmap_impl.ipp @@ -2,17 +2,16 @@ #define MIO_BASIC_MMAP_IMPL #include "mmap_impl.hpp" +#include "type_traits.hpp" #include -#include +#include #ifndef _WIN32 # include # include # include # include -# include -# include #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 +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(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 +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(::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(get_mapping_start()), mapped_length_); + if(data_) { ::munmap(const_cast(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(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().data()), - typename = typename std::enable_if::value>::type -> const char* c_str(const String& path) -{ - return path.data(); -} - -template< - typename String, - typename = typename std::enable_if< - std::is_same::type>::value - >::type -> const char* c_str(String path) -{ - return path; -} - -template -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 diff --git a/include/mio/detail/type_traits.hpp b/include/mio/detail/type_traits.hpp new file mode 100644 index 0000000..dd95e35 --- /dev/null +++ b/include/mio/detail/type_traits.hpp @@ -0,0 +1,53 @@ +#ifndef MIO_TYPE_TRAITS_HEADER +#define MIO_TYPE_TRAITS_HEADER + +#include + +namespace mio { +namespace detail { + +template struct is_c_str +{ + static constexpr bool value = std::is_same< + const char*, typename std::decay::type + >::value; +}; + +template< + typename String, + typename = decltype(std::declval().data()), + typename = typename std::enable_if::value>::type +> const char* c_str(const String& path) +{ + return path.data(); +} + +template< + typename String, + typename = decltype(std::declval().empty()), + typename = typename std::enable_if::value>::type +> bool empty(const String& path) +{ + return path.empty(); +} + +template< + typename String, + typename = typename std::enable_if::value>::type +> const char* c_str(String path) +{ + return path; +} + +template< + typename String, + typename = typename std::enable_if::value>::type +> bool empty(String path) +{ + return !path || (*path == 0); +} + +} // namespace detail +} // namespace mio + +#endif // MIO_TYPE_TRAITS_HEADER diff --git a/include/mio/mmap.hpp b/include/mio/mmap.hpp index 5f45dc9..e04ca94 100644 --- a/include/mio/mmap.hpp +++ b/include/mio/mmap.hpp @@ -2,24 +2,11 @@ #define MIO_MMAP_HEADER #include "detail/mmap_impl.hpp" + #include 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 + 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(); } @@ -185,7 +220,18 @@ public: using const_reverse_iterator = std::reverse_iterator; 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 + 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`, or similar. + * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, + * `std::filesystem::path`, `std::vector`, or similar), or a + * mmap_source::file_handle. */ -template -mmap_source make_mmap_source(const Path& path, mmap_source::size_type offset, +template +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`, or similar. + * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, + * `std::filesystem::path`, `std::vector`, or similar), or a + * mmap_sink::file_handle. */ -template -mmap_sink make_mmap_sink(const Path& path, mmap_sink::size_type offset, +template +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; } diff --git a/test/test.cpp b/test/test.cpp index 4839c67..074293e 100644 --- a/test/test.cpp +++ b/test/test.cpp @@ -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(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);