mio.hpp (59393B)
1 /* Copyright 2017 https://github.com/mandreyel 2 * 3 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 4 * software and associated documentation files (the "Software"), to deal in the Software 5 * without restriction, including without limitation the rights to use, copy, modify, 6 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 7 * permit persons to whom the Software is furnished to do so, subject to the following 8 * conditions: 9 * 10 * The above copyright notice and this permission notice shall be included in all copies 11 * or substantial portions of the Software. 12 * 13 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 14 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 15 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 16 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 17 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 18 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 19 */ 20 21 #ifndef MIO_MMAP_HEADER 22 #define MIO_MMAP_HEADER 23 24 // #include "mio/page.hpp" 25 /* Copyright 2017 https://github.com/mandreyel 26 * 27 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 28 * software and associated documentation files (the "Software"), to deal in the Software 29 * without restriction, including without limitation the rights to use, copy, modify, 30 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 31 * permit persons to whom the Software is furnished to do so, subject to the following 32 * conditions: 33 * 34 * The above copyright notice and this permission notice shall be included in all copies 35 * or substantial portions of the Software. 36 * 37 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 38 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 39 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 40 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 41 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 42 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 43 */ 44 45 #ifndef MIO_PAGE_HEADER 46 #define MIO_PAGE_HEADER 47 48 #ifdef _WIN32 49 # include <windows.h> 50 #else 51 # include <unistd.h> 52 #endif 53 54 namespace mio { 55 56 /** 57 * This is used by `basic_mmap` to determine whether to create a read-only or 58 * a read-write memory mapping. 59 */ 60 enum class access_mode 61 { 62 read, 63 write 64 }; 65 66 /** 67 * Determines the operating system's page allocation granularity. 68 * 69 * On the first call to this function, it invokes the operating system specific syscall 70 * to determine the page size, caches the value, and returns it. Any subsequent call to 71 * this function serves the cached value, so no further syscalls are made. 72 */ 73 inline size_t page_size() 74 { 75 static const size_t page_size = [] 76 { 77 #ifdef _WIN32 78 SYSTEM_INFO SystemInfo; 79 GetSystemInfo(&SystemInfo); 80 return SystemInfo.dwAllocationGranularity; 81 #else 82 return sysconf(_SC_PAGE_SIZE); 83 #endif 84 }(); 85 return page_size; 86 } 87 88 /** 89 * Alligns `offset` to the operating's system page size such that it subtracts the 90 * difference until the nearest page boundary before `offset`, or does nothing if 91 * `offset` is already page aligned. 92 */ 93 inline size_t make_offset_page_aligned(size_t offset) noexcept 94 { 95 const size_t page_size_ = page_size(); 96 // Use integer division to round down to the nearest page alignment. 97 return offset / page_size_ * page_size_; 98 } 99 100 } // namespace mio 101 102 #endif // MIO_PAGE_HEADER 103 104 105 #include <iterator> 106 #include <string> 107 #include <system_error> 108 #include <cstdint> 109 110 #ifdef _WIN32 111 # ifndef WIN32_LEAN_AND_MEAN 112 # define WIN32_LEAN_AND_MEAN 113 # endif // WIN32_LEAN_AND_MEAN 114 # include <windows.h> 115 #else // ifdef _WIN32 116 # define INVALID_HANDLE_VALUE -1 117 #endif // ifdef _WIN32 118 119 namespace mio { 120 121 // This value may be provided as the `length` parameter to the constructor or 122 // `map`, in which case a memory mapping of the entire file is created. 123 enum { map_entire_file = 0 }; 124 125 #ifdef _WIN32 126 using file_handle_type = HANDLE; 127 #else 128 using file_handle_type = int; 129 #endif 130 131 // This value represents an invalid file handle type. This can be used to 132 // determine whether `basic_mmap::file_handle` is valid, for example. 133 const static file_handle_type invalid_handle = INVALID_HANDLE_VALUE; 134 135 template<access_mode AccessMode, typename ByteT> 136 struct basic_mmap 137 { 138 using value_type = ByteT; 139 using size_type = size_t; 140 using reference = value_type&; 141 using const_reference = const value_type&; 142 using pointer = value_type*; 143 using const_pointer = const value_type*; 144 using difference_type = std::ptrdiff_t; 145 using iterator = pointer; 146 using const_iterator = const_pointer; 147 using reverse_iterator = std::reverse_iterator<iterator>; 148 using const_reverse_iterator = std::reverse_iterator<const_iterator>; 149 using iterator_category = std::random_access_iterator_tag; 150 using handle_type = file_handle_type; 151 152 static_assert(sizeof(ByteT) == sizeof(char), "ByteT must be the same size as char."); 153 154 private: 155 // Points to the first requested byte, and not to the actual start of the mapping. 156 pointer data_ = nullptr; 157 158 // Length--in bytes--requested by user (which may not be the length of the 159 // full mapping) and the length of the full mapping. 160 size_type length_ = 0; 161 size_type mapped_length_ = 0; 162 163 // Letting user map a file using both an existing file handle and a path 164 // introcudes some complexity (see `is_handle_internal_`). 165 // On POSIX, we only need a file handle to create a mapping, while on 166 // Windows systems the file handle is necessary to retrieve a file mapping 167 // handle, but any subsequent operations on the mapped region must be done 168 // through the latter. 169 handle_type file_handle_ = INVALID_HANDLE_VALUE; 170 #ifdef _WIN32 171 handle_type file_mapping_handle_ = INVALID_HANDLE_VALUE; 172 #endif 173 174 // Letting user map a file using both an existing file handle and a path 175 // introcudes some complexity in that we must not close the file handle if 176 // user provided it, but we must close it if we obtained it using the 177 // provided path. For this reason, this flag is used to determine when to 178 // close `file_handle_`. 179 bool is_handle_internal_; 180 181 public: 182 /** 183 * The default constructed mmap object is in a non-mapped state, that is, 184 * any operation that attempts to access nonexistent underlying data will 185 * result in undefined behaviour/segmentation faults. 186 */ 187 basic_mmap() = default; 188 189 #ifdef __cpp_exceptions 190 /** 191 * The same as invoking the `map` function, except any error that may occur 192 * while establishing the mapping is wrapped in a `std::system_error` and is 193 * thrown. 194 */ 195 template<typename String> 196 basic_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) 197 { 198 std::error_code error; 199 map(path, offset, length, error); 200 if(error) { throw std::system_error(error); } 201 } 202 203 /** 204 * The same as invoking the `map` function, except any error that may occur 205 * while establishing the mapping is wrapped in a `std::system_error` and is 206 * thrown. 207 */ 208 basic_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) 209 { 210 std::error_code error; 211 map(handle, offset, length, error); 212 if(error) { throw std::system_error(error); } 213 } 214 #endif // __cpp_exceptions 215 216 /** 217 * `basic_mmap` has single-ownership semantics, so transferring ownership 218 * may only be accomplished by moving the object. 219 */ 220 basic_mmap(const basic_mmap&) = delete; 221 basic_mmap(basic_mmap&&); 222 basic_mmap& operator=(const basic_mmap&) = delete; 223 basic_mmap& operator=(basic_mmap&&); 224 225 /** 226 * If this is a read-write mapping, the destructor invokes sync. Regardless 227 * of the access mode, unmap is invoked as a final step. 228 */ 229 ~basic_mmap(); 230 231 /** 232 * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, 233 * however, a mapped region of a file gets its own handle, which is returned by 234 * 'mapping_handle'. 235 */ 236 handle_type file_handle() const noexcept { return file_handle_; } 237 handle_type mapping_handle() const noexcept; 238 239 /** Returns whether a valid memory mapping has been created. */ 240 bool is_open() const noexcept { return file_handle_ != invalid_handle; } 241 242 /** 243 * Returns true if no mapping was established, that is, conceptually the 244 * same as though the length that was mapped was 0. This function is 245 * provided so that this class has Container semantics. 246 */ 247 bool empty() const noexcept { return length() == 0; } 248 249 /** Returns true if a mapping was established. */ 250 bool is_mapped() const noexcept; 251 252 /** 253 * `size` and `length` both return the logical length, i.e. the number of bytes 254 * user requested to be mapped, while `mapped_length` returns the actual number of 255 * bytes that were mapped which is a multiple of the underlying operating system's 256 * page allocation granularity. 257 */ 258 size_type size() const noexcept { return length(); } 259 size_type length() const noexcept { return length_; } 260 size_type mapped_length() const noexcept { return mapped_length_; } 261 262 /** Returns the offset relative to the start of the mapping. */ 263 size_type mapping_offset() const noexcept 264 { 265 return mapped_length_ - length_; 266 } 267 268 /** 269 * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping 270 * exists. 271 */ 272 template< 273 access_mode A = AccessMode, 274 typename = typename std::enable_if<A == access_mode::write>::type 275 > pointer data() noexcept { return data_; } 276 const_pointer data() const noexcept { return data_; } 277 278 /** 279 * Returns an iterator to the first requested byte, if a valid memory mapping 280 * exists, otherwise this function call is undefined behaviour. 281 */ 282 template< 283 access_mode A = AccessMode, 284 typename = typename std::enable_if<A == access_mode::write>::type 285 > iterator begin() noexcept { return data(); } 286 const_iterator begin() const noexcept { return data(); } 287 const_iterator cbegin() const noexcept { return data(); } 288 289 /** 290 * Returns an iterator one past the last requested byte, if a valid memory mapping 291 * exists, otherwise this function call is undefined behaviour. 292 */ 293 template< 294 access_mode A = AccessMode, 295 typename = typename std::enable_if<A == access_mode::write>::type 296 > iterator end() noexcept { return data() + length(); } 297 const_iterator end() const noexcept { return data() + length(); } 298 const_iterator cend() const noexcept { return data() + length(); } 299 300 /** 301 * Returns a reverse iterator to the last memory mapped byte, if a valid 302 * memory mapping exists, otherwise this function call is undefined 303 * behaviour. 304 */ 305 template< 306 access_mode A = AccessMode, 307 typename = typename std::enable_if<A == access_mode::write>::type 308 > reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } 309 const_reverse_iterator rbegin() const noexcept 310 { return const_reverse_iterator(end()); } 311 const_reverse_iterator crbegin() const noexcept 312 { return const_reverse_iterator(end()); } 313 314 /** 315 * Returns a reverse iterator past the first mapped byte, if a valid memory 316 * mapping exists, otherwise this function call is undefined behaviour. 317 */ 318 template< 319 access_mode A = AccessMode, 320 typename = typename std::enable_if<A == access_mode::write>::type 321 > reverse_iterator rend() noexcept { return reverse_iterator(begin()); } 322 const_reverse_iterator rend() const noexcept 323 { return const_reverse_iterator(begin()); } 324 const_reverse_iterator crend() const noexcept 325 { return const_reverse_iterator(begin()); } 326 327 /** 328 * Returns a reference to the `i`th byte from the first requested byte (as returned 329 * by `data`). If this is invoked when no valid memory mapping has been created 330 * prior to this call, undefined behaviour ensues. 331 */ 332 reference operator[](const size_type i) noexcept { return data_[i]; } 333 const_reference operator[](const size_type i) const noexcept { return data_[i]; } 334 335 /** 336 * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 337 * reason is reported via `error` and the object remains in a state as if this 338 * function hadn't been called. 339 * 340 * `path`, which must be a path to an existing file, is used to retrieve a file 341 * handle (which is closed when the object destructs or `unmap` is called), which is 342 * then used to memory map the requested region. Upon failure, `error` is set to 343 * indicate the reason and the object remains in an unmapped state. 344 * 345 * `offset` is the number of bytes, relative to the start of the file, where the 346 * mapping should begin. When specifying it, there is no need to worry about 347 * providing a value that is aligned with the operating system's page allocation 348 * granularity. This is adjusted by the implementation such that the first requested 349 * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at 350 * `offset` from the start of the file. 351 * 352 * `length` is the number of bytes to map. It may be `map_entire_file`, in which 353 * case a mapping of the entire file is created. 354 */ 355 template<typename String> 356 void map(const String& path, const size_type offset, 357 const size_type length, std::error_code& error); 358 359 /** 360 * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 361 * reason is reported via `error` and the object remains in a state as if this 362 * function hadn't been called. 363 * 364 * `path`, which must be a path to an existing file, is used to retrieve a file 365 * handle (which is closed when the object destructs or `unmap` is called), which is 366 * then used to memory map the requested region. Upon failure, `error` is set to 367 * indicate the reason and the object remains in an unmapped state. 368 * 369 * The entire file is mapped. 370 */ 371 template<typename String> 372 void map(const String& path, std::error_code& error) 373 { 374 map(path, 0, map_entire_file, error); 375 } 376 377 /** 378 * Establishes a memory mapping with AccessMode. If the mapping is 379 * unsuccesful, the reason is reported via `error` and the object remains in 380 * a state as if this function hadn't been called. 381 * 382 * `handle`, which must be a valid file handle, which is used to memory map the 383 * requested region. Upon failure, `error` is set to indicate the reason and the 384 * object remains in an unmapped state. 385 * 386 * `offset` is the number of bytes, relative to the start of the file, where the 387 * mapping should begin. When specifying it, there is no need to worry about 388 * providing a value that is aligned with the operating system's page allocation 389 * granularity. This is adjusted by the implementation such that the first requested 390 * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at 391 * `offset` from the start of the file. 392 * 393 * `length` is the number of bytes to map. It may be `map_entire_file`, in which 394 * case a mapping of the entire file is created. 395 */ 396 void map(const handle_type handle, const size_type offset, 397 const size_type length, std::error_code& error); 398 399 /** 400 * Establishes a memory mapping with AccessMode. If the mapping is 401 * unsuccesful, the reason is reported via `error` and the object remains in 402 * a state as if this function hadn't been called. 403 * 404 * `handle`, which must be a valid file handle, which is used to memory map the 405 * requested region. Upon failure, `error` is set to indicate the reason and the 406 * object remains in an unmapped state. 407 * 408 * The entire file is mapped. 409 */ 410 void map(const handle_type handle, std::error_code& error) 411 { 412 map(handle, 0, map_entire_file, error); 413 } 414 415 /** 416 * If a valid memory mapping has been created prior to this call, this call 417 * instructs the kernel to unmap the memory region and disassociate this object 418 * from the file. 419 * 420 * The file handle associated with the file that is mapped is only closed if the 421 * mapping was created using a file path. If, on the other hand, an existing 422 * file handle was used to create the mapping, the file handle is not closed. 423 */ 424 void unmap(); 425 426 void swap(basic_mmap& other); 427 428 /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ 429 template<access_mode A = AccessMode> 430 typename std::enable_if<A == access_mode::write, void>::type 431 sync(std::error_code& error); 432 433 /** 434 * All operators compare the address of the first byte and size of the two mapped 435 * regions. 436 */ 437 438 private: 439 template< 440 access_mode A = AccessMode, 441 typename = typename std::enable_if<A == access_mode::write>::type 442 > pointer get_mapping_start() noexcept 443 { 444 return !data() ? nullptr : data() - mapping_offset(); 445 } 446 447 const_pointer get_mapping_start() const noexcept 448 { 449 return !data() ? nullptr : data() - mapping_offset(); 450 } 451 452 /** 453 * The destructor syncs changes to disk if `AccessMode` is `write`, but not 454 * if it's `read`, but since the destructor cannot be templated, we need to 455 * do SFINAE in a dedicated function, where one syncs and the other is a noop. 456 */ 457 template<access_mode A = AccessMode> 458 typename std::enable_if<A == access_mode::write, void>::type 459 conditional_sync(); 460 template<access_mode A = AccessMode> 461 typename std::enable_if<A == access_mode::read, void>::type conditional_sync(); 462 }; 463 464 template<access_mode AccessMode, typename ByteT> 465 bool operator==(const basic_mmap<AccessMode, ByteT>& a, 466 const basic_mmap<AccessMode, ByteT>& b); 467 468 template<access_mode AccessMode, typename ByteT> 469 bool operator!=(const basic_mmap<AccessMode, ByteT>& a, 470 const basic_mmap<AccessMode, ByteT>& b); 471 472 template<access_mode AccessMode, typename ByteT> 473 bool operator<(const basic_mmap<AccessMode, ByteT>& a, 474 const basic_mmap<AccessMode, ByteT>& b); 475 476 template<access_mode AccessMode, typename ByteT> 477 bool operator<=(const basic_mmap<AccessMode, ByteT>& a, 478 const basic_mmap<AccessMode, ByteT>& b); 479 480 template<access_mode AccessMode, typename ByteT> 481 bool operator>(const basic_mmap<AccessMode, ByteT>& a, 482 const basic_mmap<AccessMode, ByteT>& b); 483 484 template<access_mode AccessMode, typename ByteT> 485 bool operator>=(const basic_mmap<AccessMode, ByteT>& a, 486 const basic_mmap<AccessMode, ByteT>& b); 487 488 /** 489 * This is the basis for all read-only mmap objects and should be preferred over 490 * directly using `basic_mmap`. 491 */ 492 template<typename ByteT> 493 using basic_mmap_source = basic_mmap<access_mode::read, ByteT>; 494 495 /** 496 * This is the basis for all read-write mmap objects and should be preferred over 497 * directly using `basic_mmap`. 498 */ 499 template<typename ByteT> 500 using basic_mmap_sink = basic_mmap<access_mode::write, ByteT>; 501 502 /** 503 * These aliases cover the most common use cases, both representing a raw byte stream 504 * (either with a char or an unsigned char/uint8_t). 505 */ 506 using mmap_source = basic_mmap_source<char>; 507 using ummap_source = basic_mmap_source<unsigned char>; 508 509 using mmap_sink = basic_mmap_sink<char>; 510 using ummap_sink = basic_mmap_sink<unsigned char>; 511 512 /** 513 * Convenience factory method that constructs a mapping for any `basic_mmap` or 514 * `basic_mmap` type. 515 */ 516 template< 517 typename MMap, 518 typename MappingToken 519 > MMap make_mmap(const MappingToken& token, 520 int64_t offset, int64_t length, std::error_code& error) 521 { 522 MMap mmap; 523 mmap.map(token, offset, length, error); 524 return mmap; 525 } 526 527 /** 528 * Convenience factory method. 529 * 530 * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, 531 * `std::filesystem::path`, `std::vector<char>`, or similar), or a 532 * `mmap_source::handle_type`. 533 */ 534 template<typename MappingToken> 535 mmap_source make_mmap_source(const MappingToken& token, mmap_source::size_type offset, 536 mmap_source::size_type length, std::error_code& error) 537 { 538 return make_mmap<mmap_source>(token, offset, length, error); 539 } 540 541 template<typename MappingToken> 542 mmap_source make_mmap_source(const MappingToken& token, std::error_code& error) 543 { 544 return make_mmap_source(token, 0, map_entire_file, error); 545 } 546 547 /** 548 * Convenience factory method. 549 * 550 * MappingToken may be a String (`std::string`, `std::string_view`, `const char*`, 551 * `std::filesystem::path`, `std::vector<char>`, or similar), or a 552 * `mmap_sink::handle_type`. 553 */ 554 template<typename MappingToken> 555 mmap_sink make_mmap_sink(const MappingToken& token, mmap_sink::size_type offset, 556 mmap_sink::size_type length, std::error_code& error) 557 { 558 return make_mmap<mmap_sink>(token, offset, length, error); 559 } 560 561 template<typename MappingToken> 562 mmap_sink make_mmap_sink(const MappingToken& token, std::error_code& error) 563 { 564 return make_mmap_sink(token, 0, map_entire_file, error); 565 } 566 567 } // namespace mio 568 569 // #include "detail/mmap.ipp" 570 /* Copyright 2017 https://github.com/mandreyel 571 * 572 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 573 * software and associated documentation files (the "Software"), to deal in the Software 574 * without restriction, including without limitation the rights to use, copy, modify, 575 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 576 * permit persons to whom the Software is furnished to do so, subject to the following 577 * conditions: 578 * 579 * The above copyright notice and this permission notice shall be included in all copies 580 * or substantial portions of the Software. 581 * 582 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 583 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 584 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 585 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 586 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 587 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 588 */ 589 590 #ifndef MIO_BASIC_MMAP_IMPL 591 #define MIO_BASIC_MMAP_IMPL 592 593 // #include "mio/mmap.hpp" 594 595 // #include "mio/page.hpp" 596 597 // #include "mio/detail/string_util.hpp" 598 /* Copyright 2017 https://github.com/mandreyel 599 * 600 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 601 * software and associated documentation files (the "Software"), to deal in the Software 602 * without restriction, including without limitation the rights to use, copy, modify, 603 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 604 * permit persons to whom the Software is furnished to do so, subject to the following 605 * conditions: 606 * 607 * The above copyright notice and this permission notice shall be included in all copies 608 * or substantial portions of the Software. 609 * 610 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 611 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 612 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 613 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 614 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 615 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 616 */ 617 618 #ifndef MIO_STRING_UTIL_HEADER 619 #define MIO_STRING_UTIL_HEADER 620 621 #include <type_traits> 622 623 namespace mio { 624 namespace detail { 625 626 template< 627 typename S, 628 typename C = typename std::decay<S>::type, 629 typename = decltype(std::declval<C>().data()), 630 typename = typename std::enable_if< 631 std::is_same<typename C::value_type, char>::value 632 #ifdef _WIN32 633 || std::is_same<typename C::value_type, wchar_t>::value 634 #endif 635 >::type 636 > struct char_type_helper { 637 using type = typename C::value_type; 638 }; 639 640 template<class T> 641 struct char_type { 642 using type = typename char_type_helper<T>::type; 643 }; 644 645 // TODO: can we avoid this brute force approach? 646 template<> 647 struct char_type<char*> { 648 using type = char; 649 }; 650 651 template<> 652 struct char_type<const char*> { 653 using type = char; 654 }; 655 656 template<size_t N> 657 struct char_type<char[N]> { 658 using type = char; 659 }; 660 661 template<size_t N> 662 struct char_type<const char[N]> { 663 using type = char; 664 }; 665 666 #ifdef _WIN32 667 template<> 668 struct char_type<wchar_t*> { 669 using type = wchar_t; 670 }; 671 672 template<> 673 struct char_type<const wchar_t*> { 674 using type = wchar_t; 675 }; 676 677 template<size_t N> 678 struct char_type<wchar_t[N]> { 679 using type = wchar_t; 680 }; 681 682 template<size_t N> 683 struct char_type<const wchar_t[N]> { 684 using type = wchar_t; 685 }; 686 #endif // _WIN32 687 688 template<typename CharT, typename S> 689 struct is_c_str_helper 690 { 691 static constexpr bool value = std::is_same< 692 CharT*, 693 // TODO: I'm so sorry for this... Can this be made cleaner? 694 typename std::add_pointer< 695 typename std::remove_cv< 696 typename std::remove_pointer< 697 typename std::decay< 698 S 699 >::type 700 >::type 701 >::type 702 >::type 703 >::value; 704 }; 705 706 template<typename S> 707 struct is_c_str 708 { 709 static constexpr bool value = is_c_str_helper<char, S>::value; 710 }; 711 712 #ifdef _WIN32 713 template<typename S> 714 struct is_c_wstr 715 { 716 static constexpr bool value = is_c_str_helper<wchar_t, S>::value; 717 }; 718 #endif // _WIN32 719 720 template<typename S> 721 struct is_c_str_or_c_wstr 722 { 723 static constexpr bool value = is_c_str<S>::value 724 #ifdef _WIN32 725 || is_c_wstr<S>::value 726 #endif 727 ; 728 }; 729 730 template< 731 typename String, 732 typename = decltype(std::declval<String>().data()), 733 typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type 734 > const typename char_type<String>::type* c_str(const String& path) 735 { 736 return path.data(); 737 } 738 739 template< 740 typename String, 741 typename = decltype(std::declval<String>().empty()), 742 typename = typename std::enable_if<!is_c_str_or_c_wstr<String>::value>::type 743 > bool empty(const String& path) 744 { 745 return path.empty(); 746 } 747 748 template< 749 typename String, 750 typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type 751 > const typename char_type<String>::type* c_str(String path) 752 { 753 return path; 754 } 755 756 template< 757 typename String, 758 typename = typename std::enable_if<is_c_str_or_c_wstr<String>::value>::type 759 > bool empty(String path) 760 { 761 return !path || (*path == 0); 762 } 763 764 } // namespace detail 765 } // namespace mio 766 767 #endif // MIO_STRING_UTIL_HEADER 768 769 770 #include <algorithm> 771 772 #ifndef _WIN32 773 # include <unistd.h> 774 # include <fcntl.h> 775 # include <sys/mman.h> 776 # include <sys/stat.h> 777 #endif 778 779 namespace mio { 780 namespace detail { 781 782 #ifdef _WIN32 783 namespace win { 784 785 /** Returns the 4 upper bytes of an 8-byte integer. */ 786 inline DWORD int64_high(int64_t n) noexcept 787 { 788 return n >> 32; 789 } 790 791 /** Returns the 4 lower bytes of an 8-byte integer. */ 792 inline DWORD int64_low(int64_t n) noexcept 793 { 794 return n & 0xffffffff; 795 } 796 797 template< 798 typename String, 799 typename = typename std::enable_if< 800 std::is_same<typename char_type<String>::type, char>::value 801 >::type 802 > file_handle_type open_file_helper(const String& path, const access_mode mode) 803 { 804 return ::CreateFileA(c_str(path), 805 mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, 806 FILE_SHARE_READ | FILE_SHARE_WRITE, 807 0, 808 OPEN_EXISTING, 809 FILE_ATTRIBUTE_NORMAL, 810 0); 811 } 812 813 template<typename String> 814 typename std::enable_if< 815 std::is_same<typename char_type<String>::type, wchar_t>::value, 816 file_handle_type 817 >::type open_file_helper(const String& path, const access_mode mode) 818 { 819 return ::CreateFileW(c_str(path), 820 mode == access_mode::read ? GENERIC_READ : GENERIC_READ | GENERIC_WRITE, 821 FILE_SHARE_READ | FILE_SHARE_WRITE, 822 0, 823 OPEN_EXISTING, 824 FILE_ATTRIBUTE_NORMAL, 825 0); 826 } 827 828 } // win 829 #endif // _WIN32 830 831 /** 832 * Returns the last platform specific system error (errno on POSIX and 833 * GetLastError on Win) as a `std::error_code`. 834 */ 835 inline std::error_code last_error() noexcept 836 { 837 std::error_code error; 838 #ifdef _WIN32 839 error.assign(GetLastError(), std::system_category()); 840 #else 841 error.assign(errno, std::system_category()); 842 #endif 843 return error; 844 } 845 846 template<typename String> 847 file_handle_type open_file(const String& path, const access_mode mode, 848 std::error_code& error) 849 { 850 error.clear(); 851 if(detail::empty(path)) 852 { 853 error = std::make_error_code(std::errc::invalid_argument); 854 return invalid_handle; 855 } 856 #ifdef _WIN32 857 const auto handle = win::open_file_helper(path, mode); 858 #else // POSIX 859 const auto handle = ::open(c_str(path), 860 mode == access_mode::read ? O_RDONLY : O_RDWR); 861 #endif 862 if(handle == invalid_handle) 863 { 864 error = detail::last_error(); 865 } 866 return handle; 867 } 868 869 inline size_t query_file_size(file_handle_type handle, std::error_code& error) 870 { 871 error.clear(); 872 #ifdef _WIN32 873 LARGE_INTEGER file_size; 874 if(::GetFileSizeEx(handle, &file_size) == 0) 875 { 876 error = detail::last_error(); 877 return 0; 878 } 879 return static_cast<int64_t>(file_size.QuadPart); 880 #else // POSIX 881 struct stat sbuf; 882 if(::fstat(handle, &sbuf) == -1) 883 { 884 error = detail::last_error(); 885 return 0; 886 } 887 return sbuf.st_size; 888 #endif 889 } 890 891 struct mmap_context 892 { 893 char* data; 894 int64_t length; 895 int64_t mapped_length; 896 #ifdef _WIN32 897 file_handle_type file_mapping_handle; 898 #endif 899 }; 900 901 inline mmap_context memory_map(const file_handle_type file_handle, const int64_t offset, 902 const int64_t length, const access_mode mode, std::error_code& error) 903 { 904 const int64_t aligned_offset = make_offset_page_aligned(offset); 905 const int64_t length_to_map = offset - aligned_offset + length; 906 #ifdef _WIN32 907 const int64_t max_file_size = offset + length; 908 const auto file_mapping_handle = ::CreateFileMapping( 909 file_handle, 910 0, 911 mode == access_mode::read ? PAGE_READONLY : PAGE_READWRITE, 912 win::int64_high(max_file_size), 913 win::int64_low(max_file_size), 914 0); 915 if(file_mapping_handle == invalid_handle) 916 { 917 error = detail::last_error(); 918 return {}; 919 } 920 char* mapping_start = static_cast<char*>(::MapViewOfFile( 921 file_mapping_handle, 922 mode == access_mode::read ? FILE_MAP_READ : FILE_MAP_WRITE, 923 win::int64_high(aligned_offset), 924 win::int64_low(aligned_offset), 925 length_to_map)); 926 if(mapping_start == nullptr) 927 { 928 error = detail::last_error(); 929 return {}; 930 } 931 #else // POSIX 932 char* mapping_start = static_cast<char*>(::mmap( 933 0, // Don't give hint as to where to map. 934 length_to_map, 935 mode == access_mode::read ? PROT_READ : PROT_WRITE, 936 MAP_SHARED, 937 file_handle, 938 aligned_offset)); 939 if(mapping_start == MAP_FAILED) 940 { 941 error = detail::last_error(); 942 return {}; 943 } 944 #endif 945 mmap_context ctx; 946 ctx.data = mapping_start + offset - aligned_offset; 947 ctx.length = length; 948 ctx.mapped_length = length_to_map; 949 #ifdef _WIN32 950 ctx.file_mapping_handle = file_mapping_handle; 951 #endif 952 return ctx; 953 } 954 955 } // namespace detail 956 957 // -- basic_mmap -- 958 959 template<access_mode AccessMode, typename ByteT> 960 basic_mmap<AccessMode, ByteT>::~basic_mmap() 961 { 962 conditional_sync(); 963 unmap(); 964 } 965 966 template<access_mode AccessMode, typename ByteT> 967 basic_mmap<AccessMode, ByteT>::basic_mmap(basic_mmap&& other) 968 : data_(std::move(other.data_)) 969 , length_(std::move(other.length_)) 970 , mapped_length_(std::move(other.mapped_length_)) 971 , file_handle_(std::move(other.file_handle_)) 972 #ifdef _WIN32 973 , file_mapping_handle_(std::move(other.file_mapping_handle_)) 974 #endif 975 , is_handle_internal_(std::move(other.is_handle_internal_)) 976 { 977 other.data_ = nullptr; 978 other.length_ = other.mapped_length_ = 0; 979 other.file_handle_ = invalid_handle; 980 #ifdef _WIN32 981 other.file_mapping_handle_ = invalid_handle; 982 #endif 983 } 984 985 template<access_mode AccessMode, typename ByteT> 986 basic_mmap<AccessMode, ByteT>& 987 basic_mmap<AccessMode, ByteT>::operator=(basic_mmap&& other) 988 { 989 if(this != &other) 990 { 991 // First the existing mapping needs to be removed. 992 unmap(); 993 data_ = std::move(other.data_); 994 length_ = std::move(other.length_); 995 mapped_length_ = std::move(other.mapped_length_); 996 file_handle_ = std::move(other.file_handle_); 997 #ifdef _WIN32 998 file_mapping_handle_ = std::move(other.file_mapping_handle_); 999 #endif 1000 is_handle_internal_ = std::move(other.is_handle_internal_); 1001 1002 // The moved from basic_mmap's fields need to be reset, because 1003 // otherwise other's destructor will unmap the same mapping that was 1004 // just moved into this. 1005 other.data_ = nullptr; 1006 other.length_ = other.mapped_length_ = 0; 1007 other.file_handle_ = invalid_handle; 1008 #ifdef _WIN32 1009 other.file_mapping_handle_ = invalid_handle; 1010 #endif 1011 other.is_handle_internal_ = false; 1012 } 1013 return *this; 1014 } 1015 1016 template<access_mode AccessMode, typename ByteT> 1017 typename basic_mmap<AccessMode, ByteT>::handle_type 1018 basic_mmap<AccessMode, ByteT>::mapping_handle() const noexcept 1019 { 1020 #ifdef _WIN32 1021 return file_mapping_handle_; 1022 #else 1023 return file_handle_; 1024 #endif 1025 } 1026 1027 template<access_mode AccessMode, typename ByteT> 1028 template<typename String> 1029 void basic_mmap<AccessMode, ByteT>::map(const String& path, const size_type offset, 1030 const size_type length, std::error_code& error) 1031 { 1032 error.clear(); 1033 if(detail::empty(path)) 1034 { 1035 error = std::make_error_code(std::errc::invalid_argument); 1036 return; 1037 } 1038 const auto handle = detail::open_file(path, AccessMode, error); 1039 if(error) 1040 { 1041 return; 1042 } 1043 1044 map(handle, offset, length, error); 1045 // This MUST be after the call to map, as that sets this to true. 1046 if(!error) 1047 { 1048 is_handle_internal_ = true; 1049 } 1050 } 1051 1052 template<access_mode AccessMode, typename ByteT> 1053 void basic_mmap<AccessMode, ByteT>::map(const handle_type handle, 1054 const size_type offset, const size_type length, std::error_code& error) 1055 { 1056 error.clear(); 1057 if(handle == invalid_handle) 1058 { 1059 error = std::make_error_code(std::errc::bad_file_descriptor); 1060 return; 1061 } 1062 1063 const auto file_size = detail::query_file_size(handle, error); 1064 if(error) 1065 { 1066 return; 1067 } 1068 1069 if(offset + length > file_size) 1070 { 1071 error = std::make_error_code(std::errc::invalid_argument); 1072 return; 1073 } 1074 1075 const auto ctx = detail::memory_map(handle, offset, 1076 length == map_entire_file ? (file_size - offset) : length, 1077 AccessMode, error); 1078 if(!error) 1079 { 1080 // We must unmap the previous mapping that may have existed prior to this call. 1081 // Note that this must only be invoked after a new mapping has been created in 1082 // order to provide the strong guarantee that, should the new mapping fail, the 1083 // `map` function leaves this instance in a state as though the function had 1084 // never been invoked. 1085 unmap(); 1086 file_handle_ = handle; 1087 is_handle_internal_ = false; 1088 data_ = reinterpret_cast<pointer>(ctx.data); 1089 length_ = ctx.length; 1090 mapped_length_ = ctx.mapped_length; 1091 #ifdef _WIN32 1092 file_mapping_handle_ = ctx.file_mapping_handle; 1093 #endif 1094 } 1095 } 1096 1097 template<access_mode AccessMode, typename ByteT> 1098 template<access_mode A> 1099 typename std::enable_if<A == access_mode::write, void>::type 1100 basic_mmap<AccessMode, ByteT>::sync(std::error_code& error) 1101 { 1102 error.clear(); 1103 if(!is_open()) 1104 { 1105 error = std::make_error_code(std::errc::bad_file_descriptor); 1106 return; 1107 } 1108 1109 if(data()) 1110 { 1111 #ifdef _WIN32 1112 if(::FlushViewOfFile(get_mapping_start(), mapped_length_) == 0 1113 || ::FlushFileBuffers(file_handle_) == 0) 1114 #else // POSIX 1115 if(::msync(get_mapping_start(), mapped_length_, MS_SYNC) != 0) 1116 #endif 1117 { 1118 error = detail::last_error(); 1119 return; 1120 } 1121 } 1122 #ifdef _WIN32 1123 if(::FlushFileBuffers(file_handle_) == 0) 1124 { 1125 error = detail::last_error(); 1126 } 1127 #endif 1128 } 1129 1130 template<access_mode AccessMode, typename ByteT> 1131 void basic_mmap<AccessMode, ByteT>::unmap() 1132 { 1133 if(!is_open()) { return; } 1134 // TODO do we care about errors here? 1135 #ifdef _WIN32 1136 if(is_mapped()) 1137 { 1138 ::UnmapViewOfFile(get_mapping_start()); 1139 ::CloseHandle(file_mapping_handle_); 1140 } 1141 #else // POSIX 1142 if(data_) { ::munmap(const_cast<pointer>(get_mapping_start()), mapped_length_); } 1143 #endif 1144 1145 // If `file_handle_` was obtained by our opening it (when map is called with 1146 // a path, rather than an existing file handle), we need to close it, 1147 // otherwise it must not be closed as it may still be used outside this 1148 // instance. 1149 if(is_handle_internal_) 1150 { 1151 #ifdef _WIN32 1152 ::CloseHandle(file_handle_); 1153 #else // POSIX 1154 ::close(file_handle_); 1155 #endif 1156 } 1157 1158 // Reset fields to their default values. 1159 data_ = nullptr; 1160 length_ = mapped_length_ = 0; 1161 file_handle_ = invalid_handle; 1162 #ifdef _WIN32 1163 file_mapping_handle_ = invalid_handle; 1164 #endif 1165 } 1166 1167 template<access_mode AccessMode, typename ByteT> 1168 bool basic_mmap<AccessMode, ByteT>::is_mapped() const noexcept 1169 { 1170 #ifdef _WIN32 1171 return file_mapping_handle_ != invalid_handle; 1172 #else // POSIX 1173 return is_open(); 1174 #endif 1175 } 1176 1177 template<access_mode AccessMode, typename ByteT> 1178 void basic_mmap<AccessMode, ByteT>::swap(basic_mmap& other) 1179 { 1180 if(this != &other) 1181 { 1182 using std::swap; 1183 swap(data_, other.data_); 1184 swap(file_handle_, other.file_handle_); 1185 #ifdef _WIN32 1186 swap(file_mapping_handle_, other.file_mapping_handle_); 1187 #endif 1188 swap(length_, other.length_); 1189 swap(mapped_length_, other.mapped_length_); 1190 swap(is_handle_internal_, other.is_handle_internal_); 1191 } 1192 } 1193 1194 template<access_mode AccessMode, typename ByteT> 1195 template<access_mode A> 1196 typename std::enable_if<A == access_mode::write, void>::type 1197 basic_mmap<AccessMode, ByteT>::conditional_sync() 1198 { 1199 // This is invoked from the destructor, so not much we can do about 1200 // failures here. 1201 std::error_code ec; 1202 sync(ec); 1203 } 1204 1205 template<access_mode AccessMode, typename ByteT> 1206 template<access_mode A> 1207 typename std::enable_if<A == access_mode::read, void>::type 1208 basic_mmap<AccessMode, ByteT>::conditional_sync() 1209 { 1210 // noop 1211 } 1212 1213 template<access_mode AccessMode, typename ByteT> 1214 bool operator==(const basic_mmap<AccessMode, ByteT>& a, 1215 const basic_mmap<AccessMode, ByteT>& b) 1216 { 1217 return a.data() == b.data() 1218 && a.size() == b.size(); 1219 } 1220 1221 template<access_mode AccessMode, typename ByteT> 1222 bool operator!=(const basic_mmap<AccessMode, ByteT>& a, 1223 const basic_mmap<AccessMode, ByteT>& b) 1224 { 1225 return !(a == b); 1226 } 1227 1228 template<access_mode AccessMode, typename ByteT> 1229 bool operator<(const basic_mmap<AccessMode, ByteT>& a, 1230 const basic_mmap<AccessMode, ByteT>& b) 1231 { 1232 if(a.data() == b.data()) { return a.size() < b.size(); } 1233 return a.data() < b.data(); 1234 } 1235 1236 template<access_mode AccessMode, typename ByteT> 1237 bool operator<=(const basic_mmap<AccessMode, ByteT>& a, 1238 const basic_mmap<AccessMode, ByteT>& b) 1239 { 1240 return !(a > b); 1241 } 1242 1243 template<access_mode AccessMode, typename ByteT> 1244 bool operator>(const basic_mmap<AccessMode, ByteT>& a, 1245 const basic_mmap<AccessMode, ByteT>& b) 1246 { 1247 if(a.data() == b.data()) { return a.size() > b.size(); } 1248 return a.data() > b.data(); 1249 } 1250 1251 template<access_mode AccessMode, typename ByteT> 1252 bool operator>=(const basic_mmap<AccessMode, ByteT>& a, 1253 const basic_mmap<AccessMode, ByteT>& b) 1254 { 1255 return !(a < b); 1256 } 1257 1258 } // namespace mio 1259 1260 #endif // MIO_BASIC_MMAP_IMPL 1261 1262 1263 #endif // MIO_MMAP_HEADER 1264 /* Copyright 2017 https://github.com/mandreyel 1265 * 1266 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 1267 * software and associated documentation files (the "Software"), to deal in the Software 1268 * without restriction, including without limitation the rights to use, copy, modify, 1269 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 1270 * permit persons to whom the Software is furnished to do so, subject to the following 1271 * conditions: 1272 * 1273 * The above copyright notice and this permission notice shall be included in all copies 1274 * or substantial portions of the Software. 1275 * 1276 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 1277 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 1278 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 1279 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 1280 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 1281 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1282 */ 1283 1284 #ifndef MIO_PAGE_HEADER 1285 #define MIO_PAGE_HEADER 1286 1287 #ifdef _WIN32 1288 # include <windows.h> 1289 #else 1290 # include <unistd.h> 1291 #endif 1292 1293 namespace mio { 1294 1295 /** 1296 * This is used by `basic_mmap` to determine whether to create a read-only or 1297 * a read-write memory mapping. 1298 */ 1299 enum class access_mode 1300 { 1301 read, 1302 write 1303 }; 1304 1305 /** 1306 * Determines the operating system's page allocation granularity. 1307 * 1308 * On the first call to this function, it invokes the operating system specific syscall 1309 * to determine the page size, caches the value, and returns it. Any subsequent call to 1310 * this function serves the cached value, so no further syscalls are made. 1311 */ 1312 inline size_t page_size() 1313 { 1314 static const size_t page_size = [] 1315 { 1316 #ifdef _WIN32 1317 SYSTEM_INFO SystemInfo; 1318 GetSystemInfo(&SystemInfo); 1319 return SystemInfo.dwAllocationGranularity; 1320 #else 1321 return sysconf(_SC_PAGE_SIZE); 1322 #endif 1323 }(); 1324 return page_size; 1325 } 1326 1327 /** 1328 * Alligns `offset` to the operating's system page size such that it subtracts the 1329 * difference until the nearest page boundary before `offset`, or does nothing if 1330 * `offset` is already page aligned. 1331 */ 1332 inline size_t make_offset_page_aligned(size_t offset) noexcept 1333 { 1334 const size_t page_size_ = page_size(); 1335 // Use integer division to round down to the nearest page alignment. 1336 return offset / page_size_ * page_size_; 1337 } 1338 1339 } // namespace mio 1340 1341 #endif // MIO_PAGE_HEADER 1342 /* Copyright 2017 https://github.com/mandreyel 1343 * 1344 * Permission is hereby granted, free of charge, to any person obtaining a copy of this 1345 * software and associated documentation files (the "Software"), to deal in the Software 1346 * without restriction, including without limitation the rights to use, copy, modify, 1347 * merge, publish, distribute, sublicense, and/or sell copies of the Software, and to 1348 * permit persons to whom the Software is furnished to do so, subject to the following 1349 * conditions: 1350 * 1351 * The above copyright notice and this permission notice shall be included in all copies 1352 * or substantial portions of the Software. 1353 * 1354 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, 1355 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 1356 * PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 1357 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF 1358 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE 1359 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 1360 */ 1361 1362 #ifndef MIO_SHARED_MMAP_HEADER 1363 #define MIO_SHARED_MMAP_HEADER 1364 1365 // #include "mio/mmap.hpp" 1366 1367 1368 #include <system_error> // std::error_code 1369 #include <memory> // std::shared_ptr 1370 1371 namespace mio { 1372 1373 /** 1374 * Exposes (nearly) the same interface as `basic_mmap`, but endowes it with 1375 * `std::shared_ptr` semantics. 1376 * 1377 * This is not the default behaviour of `basic_mmap` to avoid allocating on the heap if 1378 * shared semantics are not required. 1379 */ 1380 template< 1381 access_mode AccessMode, 1382 typename ByteT 1383 > class basic_shared_mmap 1384 { 1385 using impl_type = basic_mmap<AccessMode, ByteT>; 1386 std::shared_ptr<impl_type> pimpl_; 1387 1388 public: 1389 using value_type = typename impl_type::value_type; 1390 using size_type = typename impl_type::size_type; 1391 using reference = typename impl_type::reference; 1392 using const_reference = typename impl_type::const_reference; 1393 using pointer = typename impl_type::pointer; 1394 using const_pointer = typename impl_type::const_pointer; 1395 using difference_type = typename impl_type::difference_type; 1396 using iterator = typename impl_type::iterator; 1397 using const_iterator = typename impl_type::const_iterator; 1398 using reverse_iterator = typename impl_type::reverse_iterator; 1399 using const_reverse_iterator = typename impl_type::const_reverse_iterator; 1400 using iterator_category = typename impl_type::iterator_category; 1401 using handle_type = typename impl_type::handle_type; 1402 using mmap_type = impl_type; 1403 1404 basic_shared_mmap() = default; 1405 basic_shared_mmap(const basic_shared_mmap&) = default; 1406 basic_shared_mmap& operator=(const basic_shared_mmap&) = default; 1407 basic_shared_mmap(basic_shared_mmap&&) = default; 1408 basic_shared_mmap& operator=(basic_shared_mmap&&) = default; 1409 1410 /** Takes ownership of an existing mmap object. */ 1411 basic_shared_mmap(mmap_type&& mmap) 1412 : pimpl_(std::make_shared<mmap_type>(std::move(mmap))) 1413 {} 1414 1415 /** Takes ownership of an existing mmap object. */ 1416 basic_shared_mmap& operator=(mmap_type&& mmap) 1417 { 1418 pimpl_ = std::make_shared<mmap_type>(std::move(mmap)); 1419 return *this; 1420 } 1421 1422 /** Initializes this object with an already established shared mmap. */ 1423 basic_shared_mmap(std::shared_ptr<mmap_type> mmap) : pimpl_(std::move(mmap)) {} 1424 1425 /** Initializes this object with an already established shared mmap. */ 1426 basic_shared_mmap& operator=(std::shared_ptr<mmap_type> mmap) 1427 { 1428 pimpl_ = std::move(mmap); 1429 return *this; 1430 } 1431 1432 #ifdef __cpp_exceptions 1433 /** 1434 * The same as invoking the `map` function, except any error that may occur 1435 * while establishing the mapping is wrapped in a `std::system_error` and is 1436 * thrown. 1437 */ 1438 template<typename String> 1439 basic_shared_mmap(const String& path, const size_type offset = 0, const size_type length = map_entire_file) 1440 { 1441 std::error_code error; 1442 map(path, offset, length, error); 1443 if(error) { throw std::system_error(error); } 1444 } 1445 1446 /** 1447 * The same as invoking the `map` function, except any error that may occur 1448 * while establishing the mapping is wrapped in a `std::system_error` and is 1449 * thrown. 1450 */ 1451 basic_shared_mmap(const handle_type handle, const size_type offset = 0, const size_type length = map_entire_file) 1452 { 1453 std::error_code error; 1454 map(handle, offset, length, error); 1455 if(error) { throw std::system_error(error); } 1456 } 1457 #endif // __cpp_exceptions 1458 1459 /** 1460 * If this is a read-write mapping and the last reference to the mapping, 1461 * the destructor invokes sync. Regardless of the access mode, unmap is 1462 * invoked as a final step. 1463 */ 1464 ~basic_shared_mmap() = default; 1465 1466 /** Returns the underlying `std::shared_ptr` instance that holds the mmap. */ 1467 std::shared_ptr<mmap_type> get_shared_ptr() { return pimpl_; } 1468 1469 /** 1470 * On UNIX systems 'file_handle' and 'mapping_handle' are the same. On Windows, 1471 * however, a mapped region of a file gets its own handle, which is returned by 1472 * 'mapping_handle'. 1473 */ 1474 handle_type file_handle() const noexcept 1475 { 1476 return pimpl_ ? pimpl_->file_handle() : invalid_handle; 1477 } 1478 1479 handle_type mapping_handle() const noexcept 1480 { 1481 return pimpl_ ? pimpl_->mapping_handle() : invalid_handle; 1482 } 1483 1484 /** Returns whether a valid memory mapping has been created. */ 1485 bool is_open() const noexcept { return pimpl_ && pimpl_->is_open(); } 1486 1487 /** 1488 * Returns true if no mapping was established, that is, conceptually the 1489 * same as though the length that was mapped was 0. This function is 1490 * provided so that this class has Container semantics. 1491 */ 1492 bool empty() const noexcept { return !pimpl_ || pimpl_->empty(); } 1493 1494 /** 1495 * `size` and `length` both return the logical length, i.e. the number of bytes 1496 * user requested to be mapped, while `mapped_length` returns the actual number of 1497 * bytes that were mapped which is a multiple of the underlying operating system's 1498 * page allocation granularity. 1499 */ 1500 size_type size() const noexcept { return pimpl_ ? pimpl_->length() : 0; } 1501 size_type length() const noexcept { return pimpl_ ? pimpl_->length() : 0; } 1502 size_type mapped_length() const noexcept 1503 { 1504 return pimpl_ ? pimpl_->mapped_length() : 0; 1505 } 1506 1507 /** 1508 * Returns a pointer to the first requested byte, or `nullptr` if no memory mapping 1509 * exists. 1510 */ 1511 template< 1512 access_mode A = AccessMode, 1513 typename = typename std::enable_if<A == access_mode::write>::type 1514 > pointer data() noexcept { return pimpl_->data(); } 1515 const_pointer data() const noexcept { return pimpl_ ? pimpl_->data() : nullptr; } 1516 1517 /** 1518 * Returns an iterator to the first requested byte, if a valid memory mapping 1519 * exists, otherwise this function call is undefined behaviour. 1520 */ 1521 iterator begin() noexcept { return pimpl_->begin(); } 1522 const_iterator begin() const noexcept { return pimpl_->begin(); } 1523 const_iterator cbegin() const noexcept { return pimpl_->cbegin(); } 1524 1525 /** 1526 * Returns an iterator one past the last requested byte, if a valid memory mapping 1527 * exists, otherwise this function call is undefined behaviour. 1528 */ 1529 template< 1530 access_mode A = AccessMode, 1531 typename = typename std::enable_if<A == access_mode::write>::type 1532 > iterator end() noexcept { return pimpl_->end(); } 1533 const_iterator end() const noexcept { return pimpl_->end(); } 1534 const_iterator cend() const noexcept { return pimpl_->cend(); } 1535 1536 /** 1537 * Returns a reverse iterator to the last memory mapped byte, if a valid 1538 * memory mapping exists, otherwise this function call is undefined 1539 * behaviour. 1540 */ 1541 template< 1542 access_mode A = AccessMode, 1543 typename = typename std::enable_if<A == access_mode::write>::type 1544 > reverse_iterator rbegin() noexcept { return pimpl_->rbegin(); } 1545 const_reverse_iterator rbegin() const noexcept { return pimpl_->rbegin(); } 1546 const_reverse_iterator crbegin() const noexcept { return pimpl_->crbegin(); } 1547 1548 /** 1549 * Returns a reverse iterator past the first mapped byte, if a valid memory 1550 * mapping exists, otherwise this function call is undefined behaviour. 1551 */ 1552 template< 1553 access_mode A = AccessMode, 1554 typename = typename std::enable_if<A == access_mode::write>::type 1555 > reverse_iterator rend() noexcept { return pimpl_->rend(); } 1556 const_reverse_iterator rend() const noexcept { return pimpl_->rend(); } 1557 const_reverse_iterator crend() const noexcept { return pimpl_->crend(); } 1558 1559 /** 1560 * Returns a reference to the `i`th byte from the first requested byte (as returned 1561 * by `data`). If this is invoked when no valid memory mapping has been created 1562 * prior to this call, undefined behaviour ensues. 1563 */ 1564 reference operator[](const size_type i) noexcept { return (*pimpl_)[i]; } 1565 const_reference operator[](const size_type i) const noexcept { return (*pimpl_)[i]; } 1566 1567 /** 1568 * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 1569 * reason is reported via `error` and the object remains in a state as if this 1570 * function hadn't been called. 1571 * 1572 * `path`, which must be a path to an existing file, is used to retrieve a file 1573 * handle (which is closed when the object destructs or `unmap` is called), which is 1574 * then used to memory map the requested region. Upon failure, `error` is set to 1575 * indicate the reason and the object remains in an unmapped state. 1576 * 1577 * `offset` is the number of bytes, relative to the start of the file, where the 1578 * mapping should begin. When specifying it, there is no need to worry about 1579 * providing a value that is aligned with the operating system's page allocation 1580 * granularity. This is adjusted by the implementation such that the first requested 1581 * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at 1582 * `offset` from the start of the file. 1583 * 1584 * `length` is the number of bytes to map. It may be `map_entire_file`, in which 1585 * case a mapping of the entire file is created. 1586 */ 1587 template<typename String> 1588 void map(const String& path, const size_type offset, 1589 const size_type length, std::error_code& error) 1590 { 1591 map_impl(path, offset, length, error); 1592 } 1593 1594 /** 1595 * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 1596 * reason is reported via `error` and the object remains in a state as if this 1597 * function hadn't been called. 1598 * 1599 * `path`, which must be a path to an existing file, is used to retrieve a file 1600 * handle (which is closed when the object destructs or `unmap` is called), which is 1601 * then used to memory map the requested region. Upon failure, `error` is set to 1602 * indicate the reason and the object remains in an unmapped state. 1603 * 1604 * The entire file is mapped. 1605 */ 1606 template<typename String> 1607 void map(const String& path, std::error_code& error) 1608 { 1609 map_impl(path, 0, map_entire_file, error); 1610 } 1611 1612 /** 1613 * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 1614 * reason is reported via `error` and the object remains in a state as if this 1615 * function hadn't been called. 1616 * 1617 * `handle`, which must be a valid file handle, which is used to memory map the 1618 * requested region. Upon failure, `error` is set to indicate the reason and the 1619 * object remains in an unmapped state. 1620 * 1621 * `offset` is the number of bytes, relative to the start of the file, where the 1622 * mapping should begin. When specifying it, there is no need to worry about 1623 * providing a value that is aligned with the operating system's page allocation 1624 * granularity. This is adjusted by the implementation such that the first requested 1625 * byte (as returned by `data` or `begin`), so long as `offset` is valid, will be at 1626 * `offset` from the start of the file. 1627 * 1628 * `length` is the number of bytes to map. It may be `map_entire_file`, in which 1629 * case a mapping of the entire file is created. 1630 */ 1631 void map(const handle_type handle, const size_type offset, 1632 const size_type length, std::error_code& error) 1633 { 1634 map_impl(handle, offset, length, error); 1635 } 1636 1637 /** 1638 * Establishes a memory mapping with AccessMode. If the mapping is unsuccesful, the 1639 * reason is reported via `error` and the object remains in a state as if this 1640 * function hadn't been called. 1641 * 1642 * `handle`, which must be a valid file handle, which is used to memory map the 1643 * requested region. Upon failure, `error` is set to indicate the reason and the 1644 * object remains in an unmapped state. 1645 * 1646 * The entire file is mapped. 1647 */ 1648 void map(const handle_type handle, std::error_code& error) 1649 { 1650 map_impl(handle, 0, map_entire_file, error); 1651 } 1652 1653 /** 1654 * If a valid memory mapping has been created prior to this call, this call 1655 * instructs the kernel to unmap the memory region and disassociate this object 1656 * from the file. 1657 * 1658 * The file handle associated with the file that is mapped is only closed if the 1659 * mapping was created using a file path. If, on the other hand, an existing 1660 * file handle was used to create the mapping, the file handle is not closed. 1661 */ 1662 void unmap() { if(pimpl_) pimpl_->unmap(); } 1663 1664 void swap(basic_shared_mmap& other) { pimpl_.swap(other.pimpl_); } 1665 1666 /** Flushes the memory mapped page to disk. Errors are reported via `error`. */ 1667 template< 1668 access_mode A = AccessMode, 1669 typename = typename std::enable_if<A == access_mode::write>::type 1670 > void sync(std::error_code& error) { if(pimpl_) pimpl_->sync(error); } 1671 1672 /** All operators compare the underlying `basic_mmap`'s addresses. */ 1673 1674 friend bool operator==(const basic_shared_mmap& a, const basic_shared_mmap& b) 1675 { 1676 return a.pimpl_ == b.pimpl_; 1677 } 1678 1679 friend bool operator!=(const basic_shared_mmap& a, const basic_shared_mmap& b) 1680 { 1681 return !(a == b); 1682 } 1683 1684 friend bool operator<(const basic_shared_mmap& a, const basic_shared_mmap& b) 1685 { 1686 return a.pimpl_ < b.pimpl_; 1687 } 1688 1689 friend bool operator<=(const basic_shared_mmap& a, const basic_shared_mmap& b) 1690 { 1691 return a.pimpl_ <= b.pimpl_; 1692 } 1693 1694 friend bool operator>(const basic_shared_mmap& a, const basic_shared_mmap& b) 1695 { 1696 return a.pimpl_ > b.pimpl_; 1697 } 1698 1699 friend bool operator>=(const basic_shared_mmap& a, const basic_shared_mmap& b) 1700 { 1701 return a.pimpl_ >= b.pimpl_; 1702 } 1703 1704 private: 1705 template<typename MappingToken> 1706 void map_impl(const MappingToken& token, const size_type offset, 1707 const size_type length, std::error_code& error) 1708 { 1709 if(!pimpl_) 1710 { 1711 mmap_type mmap = make_mmap<mmap_type>(token, offset, length, error); 1712 if(error) { return; } 1713 pimpl_ = std::make_shared<mmap_type>(std::move(mmap)); 1714 } 1715 else 1716 { 1717 pimpl_->map(token, offset, length, error); 1718 } 1719 } 1720 }; 1721 1722 /** 1723 * This is the basis for all read-only mmap objects and should be preferred over 1724 * directly using basic_shared_mmap. 1725 */ 1726 template<typename ByteT> 1727 using basic_shared_mmap_source = basic_shared_mmap<access_mode::read, ByteT>; 1728 1729 /** 1730 * This is the basis for all read-write mmap objects and should be preferred over 1731 * directly using basic_shared_mmap. 1732 */ 1733 template<typename ByteT> 1734 using basic_shared_mmap_sink = basic_shared_mmap<access_mode::write, ByteT>; 1735 1736 /** 1737 * These aliases cover the most common use cases, both representing a raw byte stream 1738 * (either with a char or an unsigned char/uint8_t). 1739 */ 1740 using shared_mmap_source = basic_shared_mmap_source<char>; 1741 using shared_ummap_source = basic_shared_mmap_source<unsigned char>; 1742 1743 using shared_mmap_sink = basic_shared_mmap_sink<char>; 1744 using shared_ummap_sink = basic_shared_mmap_sink<unsigned char>; 1745 1746 } // namespace mio 1747 1748 #endif // MIO_SHARED_MMAP_HEADER