Libosmium  2.20.0
Fast and flexible C++ library for working with OpenStreetMap data
memory_mapping.hpp
Go to the documentation of this file.
1 #ifndef OSMIUM_UTIL_MEMORY_MAPPING_HPP
2 #define OSMIUM_UTIL_MEMORY_MAPPING_HPP
3 
4 /*
5 
6 This file is part of Osmium (https://osmcode.org/libosmium).
7 
8 Copyright 2013-2023 Jochen Topf <jochen@topf.org> and others (see README).
9 
10 Boost Software License - Version 1.0 - August 17th, 2003
11 
12 Permission is hereby granted, free of charge, to any person or organization
13 obtaining a copy of the software and accompanying documentation covered by
14 this license (the "Software") to use, reproduce, display, distribute,
15 execute, and transmit the Software, and to prepare derivative works of the
16 Software, and to permit third-parties to whom the Software is furnished to
17 do so, all subject to the following:
18 
19 The copyright notices in the Software and this entire statement, including
20 the above license grant, this restriction and the following disclaimer,
21 must be included in all copies of the Software, in whole or in part, and
22 all derivative works of the Software, unless such copies or derivative
23 works are solely in the form of machine-executable object code generated by
24 a source language processor.
25 
26 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
27 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
28 FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
29 SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
30 FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
31 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
32 DEALINGS IN THE SOFTWARE.
33 
34 */
35 
36 #include <osmium/util/file.hpp>
37 
38 #include <cassert>
39 #include <cerrno>
40 #include <cstddef>
41 #include <stdexcept>
42 #include <system_error>
43 
44 #ifndef _WIN32
45 # include <sys/mman.h>
46 # include <sys/statvfs.h>
47 #else
48 # include <fcntl.h>
49 # include <io.h>
50 # include <windows.h>
51 # include <sys/types.h>
52 #endif
53 
54 namespace osmium {
55 
56  inline namespace util {
57 
95  class MemoryMapping {
96 
97  public:
98 
99  enum class mapping_mode {
100  readonly = 0,
101  write_private = 1,
102  write_shared = 2
103  };
104 
105  private:
106 
108  std::size_t m_size;
109 
111  off_t m_offset;
112 
114  int m_fd;
115 
118 
119 #ifdef _WIN32
120  HANDLE m_handle;
121 #endif
122 
124  void* m_addr;
125 
126  bool is_valid() const noexcept;
127 
128  void make_invalid() noexcept;
129 
130 #ifdef _WIN32
131  using flag_type = DWORD;
132 #else
133  using flag_type = int;
134 #endif
135 
136  flag_type get_protection() const noexcept;
137 
138  flag_type get_flags() const noexcept;
139 
140  static std::size_t check_size(std::size_t size) {
141  if (size == 0) {
142  return osmium::get_pagesize();
143  }
144  return size;
145  }
146 
147 #ifdef _WIN32
148  HANDLE get_handle() const noexcept;
149  HANDLE create_file_mapping() const noexcept;
150  void* map_view_of_file() const noexcept;
151 #endif
152 
153  // Get the available space on the file system where the file
154  // behind fd is on. Return 0 if it can't be determined.
155  static std::size_t available_space(int fd) {
156 #ifdef _WIN32
157  return 0;
158 #else
159  struct statvfs stat{};
160  const int result = ::fstatvfs(fd, &stat);
161  if (result != 0) {
162  return 0;
163  }
164  return stat.f_bsize * stat.f_bavail;
165 #endif
166  }
167 
168  int resize_fd(int fd) const {
169  // Anonymous mapping doesn't need resizing.
170  if (fd == -1) {
171  return -1;
172  }
173 
174  // Make sure the file backing this mapping is large enough.
175  auto const current_file_size = osmium::file_size(fd);
176  if (current_file_size < m_size + m_offset) {
177  const auto available = available_space(fd);
178  if (available > 0 && current_file_size + available <= m_size) {
179  throw std::system_error{ENOSPC, std::system_category(), "Could not resize file: Not enough space on filesystem"};
180  }
181 
183  }
184  return fd;
185  }
186 
187  public:
188 
205  MemoryMapping(std::size_t size, mapping_mode mode, int fd = -1, off_t offset = 0);
206 
208  MemoryMapping(const MemoryMapping&) = delete;
209 
212 
217  MemoryMapping(MemoryMapping&& other) noexcept;
218 
222  MemoryMapping& operator=(MemoryMapping&& other) noexcept;
223 
228  ~MemoryMapping() noexcept {
229  try {
230  unmap();
231  } catch (const std::system_error&) {
232  // Ignore any exceptions because destructor must not throw.
233  }
234  }
235 
242  void unmap();
243 
255  void resize(std::size_t new_size);
256 
261  explicit operator bool() const noexcept {
262  return is_valid();
263  }
264 
270  std::size_t size() const noexcept {
271  return m_size;
272  }
273 
279  int fd() const noexcept {
280  return m_fd;
281  }
282 
286  bool writable() const noexcept {
288  }
289 
295  template <typename T = void>
296  T* get_addr() const noexcept {
297  return reinterpret_cast<T*>(m_addr);
298  }
299 
300  }; // class MemoryMapping
301 
313 
314  public:
315 
316  explicit AnonymousMemoryMapping(std::size_t size) :
317  MemoryMapping(size, mapping_mode::write_private) {
318  }
319 
320 #ifndef __linux__
325  void resize(std::size_t) = delete;
326 #endif
327 
328  }; // class AnonymousMemoryMapping
329 
339  template <typename T>
341 
343 
344  public:
345 
352  explicit TypedMemoryMapping(std::size_t size) :
353  m_mapping(sizeof(T) * size, MemoryMapping::mapping_mode::write_private) {
354  }
355 
366  TypedMemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset = 0) :
367  m_mapping(sizeof(T) * size, mode, fd, sizeof(T) * offset) {
368  }
369 
372 
375 
380  TypedMemoryMapping(TypedMemoryMapping&& other) noexcept = default;
381 
385  TypedMemoryMapping& operator=(TypedMemoryMapping&& other) noexcept = default;
386 
391  ~TypedMemoryMapping() noexcept = default;
392 
399  void unmap() {
400  m_mapping.unmap();
401  }
402 
413  void resize(std::size_t new_size) {
414  m_mapping.resize(sizeof(T) * new_size);
415  }
416 
421  explicit operator bool() const noexcept {
422  return !!m_mapping;
423  }
424 
430  std::size_t size() const noexcept {
431  assert(m_mapping.size() % sizeof(T) == 0);
432  return m_mapping.size() / sizeof(T);
433  }
434 
440  int fd() const noexcept {
441  return m_mapping.fd();
442  }
443 
447  bool writable() const noexcept {
448  return m_mapping.writable();
449  }
450 
456  T* begin() noexcept {
457  return m_mapping.get_addr<T>();
458  }
459 
465  T* end() noexcept {
466  return m_mapping.get_addr<T>() + size();
467  }
468 
474  const T* cbegin() const noexcept {
475  return m_mapping.get_addr<T>();
476  }
477 
483  const T* cend() const noexcept {
484  return m_mapping.get_addr<T>() + size();
485  }
486 
492  const T* begin() const noexcept {
493  return m_mapping.get_addr<T>();
494  }
495 
501  const T* end() const noexcept {
502  return m_mapping.get_addr<T>() + size();
503  }
504 
505  }; // class TypedMemoryMapping
506 
507  template <typename T>
509 
510  public:
511 
512  explicit AnonymousTypedMemoryMapping(std::size_t size) :
514  }
515 
516 #ifndef __linux__
521  void resize(std::size_t) = delete;
522 #endif
523 
524  }; // class AnonymousTypedMemoryMapping
525 
526  } // namespace util
527 
528 } // namespace osmium
529 
530 #ifndef _WIN32
531 
532 // =========== Unix implementation =============
533 
534 // MAP_FAILED is often a macro containing an old style cast
535 #pragma GCC diagnostic push
536 #pragma GCC diagnostic ignored "-Wold-style-cast"
537 
538 inline bool osmium::util::MemoryMapping::is_valid() const noexcept {
539  return m_addr != MAP_FAILED; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
540 }
541 
543  m_addr = MAP_FAILED; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
544 }
545 
546 #pragma GCC diagnostic pop
547 
548 // for BSD systems
549 #ifndef MAP_ANONYMOUS
550 # define MAP_ANONYMOUS MAP_ANON
551 #endif
552 
553 inline int osmium::util::MemoryMapping::get_protection() const noexcept {
554  if (m_mapping_mode == mapping_mode::readonly) {
555  return PROT_READ;
556  }
557  return PROT_READ | PROT_WRITE; // NOLINT(hicpp-signed-bitwise)
558 }
559 
560 inline int osmium::util::MemoryMapping::get_flags() const noexcept {
561  if (m_fd == -1) {
562  return MAP_PRIVATE | MAP_ANONYMOUS; // NOLINT(hicpp-signed-bitwise)
563  }
564  if (m_mapping_mode == mapping_mode::write_shared) {
565  return MAP_SHARED;
566  }
567  return MAP_PRIVATE;
568 }
569 
570 inline osmium::util::MemoryMapping::MemoryMapping(std::size_t size, mapping_mode mode, int fd, off_t offset) :
571  m_size(check_size(size)),
572  m_offset(offset),
573  m_fd(resize_fd(fd)),
574  m_mapping_mode(mode),
575  m_addr(::mmap(nullptr, m_size, get_protection(), get_flags(), m_fd, m_offset)) {
576  assert(!(fd == -1 && mode == mapping_mode::readonly));
577  if (!is_valid()) {
578  throw std::system_error{errno, std::system_category(), "mmap failed"};
579  }
580 }
581 
583  m_size(other.m_size),
584  m_offset(other.m_offset),
585  m_fd(other.m_fd),
586  m_mapping_mode(other.m_mapping_mode),
587  m_addr(other.m_addr) {
588  other.make_invalid();
589 }
590 
592  try {
593  unmap();
594  } catch (const std::system_error&) {
595  // Ignore unmap error. It should never happen anyway and we can't do
596  // anything about it here.
597  }
598  m_size = other.m_size;
599  m_offset = other.m_offset;
600  m_fd = other.m_fd;
601  m_mapping_mode = other.m_mapping_mode;
602  m_addr = other.m_addr;
603  other.make_invalid();
604  return *this;
605 }
606 
608  if (is_valid()) {
609  if (::munmap(m_addr, m_size) != 0) {
610  throw std::system_error{errno, std::system_category(), "munmap failed"};
611  }
612  make_invalid();
613  }
614 }
615 
616 inline void osmium::util::MemoryMapping::resize(std::size_t new_size) {
617  assert(new_size > 0 && "can not resize to zero size");
618  if (m_fd == -1) { // anonymous mapping
619 #ifdef __linux__
620  m_addr = ::mremap(m_addr, m_size, new_size, MREMAP_MAYMOVE);
621  if (!is_valid()) {
622  throw std::system_error{errno, std::system_category(), "mremap failed"};
623  }
624  m_size = new_size;
625 #else
626  assert(false && "can't resize anonymous mappings on non-linux systems");
627 #endif
628  } else { // file-based mapping
629  unmap();
630  m_size = new_size;
631  resize_fd(m_fd);
632  m_addr = ::mmap(nullptr, new_size, get_protection(), get_flags(), m_fd, m_offset);
633  if (!is_valid()) {
634  throw std::system_error{errno, std::system_category(), "mmap (remap) failed"};
635  }
636  }
637 }
638 
639 #else
640 
641 // =========== Windows implementation =============
642 
643 /* References:
644  * CreateFileMapping: https://msdn.microsoft.com/en-us/library/aa366537(VS.85).aspx
645  * CloseHandle: https://msdn.microsoft.com/en-us/library/ms724211(VS.85).aspx
646  * MapViewOfFile: https://msdn.microsoft.com/en-us/library/aa366761(VS.85).aspx
647  * UnmapViewOfFile: https://msdn.microsoft.com/en-us/library/aa366882(VS.85).aspx
648  */
649 
650 namespace osmium {
651 
652  inline namespace util {
653 
654  inline DWORD dword_hi(uint64_t x) {
655  return static_cast<DWORD>(x >> 32);
656  }
657 
658  inline DWORD dword_lo(uint64_t x) {
659  return static_cast<DWORD>(x & 0xffffffff);
660  }
661 
662  } // namespace util
663 
664 } // namespace osmium
665 
666 inline DWORD osmium::util::MemoryMapping::get_protection() const noexcept {
667  switch (m_mapping_mode) {
668  case mapping_mode::readonly:
669  return PAGE_READONLY;
670  case mapping_mode::write_private:
671  return PAGE_WRITECOPY;
672  default: // mapping_mode::write_shared
673  break;
674  }
675  return PAGE_READWRITE;
676 }
677 
678 inline DWORD osmium::util::MemoryMapping::get_flags() const noexcept {
679  switch (m_mapping_mode) {
680  case mapping_mode::readonly:
681  return FILE_MAP_READ;
682  case mapping_mode::write_private:
683  return FILE_MAP_COPY;
684  default: // mapping_mode::write_shared
685  break;
686  }
687  return FILE_MAP_WRITE;
688 }
689 
690 inline HANDLE osmium::util::MemoryMapping::get_handle() const noexcept {
691  if (m_fd == -1) {
692  return INVALID_HANDLE_VALUE;
693  }
694  return reinterpret_cast<HANDLE>(_get_osfhandle(m_fd));
695 }
696 
697 inline HANDLE osmium::util::MemoryMapping::create_file_mapping() const noexcept {
698  if (m_fd != -1) {
699  _setmode(m_fd, _O_BINARY);
700  }
701  return CreateFileMapping(get_handle(),
702  nullptr,
703  get_protection(),
704  osmium::dword_hi(static_cast<uint64_t>(m_size) + m_offset),
705  osmium::dword_lo(static_cast<uint64_t>(m_size) + m_offset),
706  nullptr);
707 }
708 
709 inline void* osmium::util::MemoryMapping::map_view_of_file() const noexcept {
710  return MapViewOfFile(m_handle,
711  get_flags(),
712  osmium::dword_hi(m_offset),
713  osmium::dword_lo(m_offset),
714  m_size);
715 }
716 
717 inline bool osmium::util::MemoryMapping::is_valid() const noexcept {
718  return m_addr != nullptr;
719 }
720 
721 inline void osmium::util::MemoryMapping::make_invalid() noexcept {
722  m_addr = nullptr;
723 }
724 
725 // GetLastError() returns a DWORD (A 32-bit unsigned integer), but the error
726 // code for std::system_error is an int. So we convert this here and hope
727 // it all works.
728 inline int last_error() noexcept {
729  return static_cast<int>(GetLastError());
730 }
731 
732 inline osmium::util::MemoryMapping::MemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset) :
733  m_size(check_size(size)),
734  m_offset(offset),
735  m_fd(resize_fd(fd)),
736  m_mapping_mode(mode),
737  m_handle(create_file_mapping()),
738  m_addr(nullptr) {
739 
740  if (!m_handle) {
741  throw std::system_error{last_error(), std::system_category(), "CreateFileMapping failed"};
742  }
743 
744  m_addr = map_view_of_file();
745  if (!is_valid()) {
746  throw std::system_error{last_error(), std::system_category(), "MapViewOfFile failed"};
747  }
748 }
749 
750 inline osmium::util::MemoryMapping::MemoryMapping(MemoryMapping&& other) noexcept :
751  m_size(other.m_size),
752  m_offset(other.m_offset),
753  m_fd(other.m_fd),
754  m_mapping_mode(other.m_mapping_mode),
755  m_handle(std::move(other.m_handle)),
756  m_addr(other.m_addr) {
757  other.make_invalid();
758  other.m_handle = nullptr;
759 }
760 
762  try {
763  unmap();
764  } catch (const std::system_error&) {
765  // Ignore unmap error. It should never happen anyway and we can't do
766  // anything about it here.
767  }
768  m_size = other.m_size;
769  m_offset = other.m_offset;
770  m_fd = other.m_fd;
771  m_mapping_mode = other.m_mapping_mode;
772  m_handle = std::move(other.m_handle);
773  m_addr = other.m_addr;
774  other.make_invalid();
775  other.m_handle = nullptr;
776  return *this;
777 }
778 
780  if (is_valid()) {
781  if (!UnmapViewOfFile(m_addr)) {
782  throw std::system_error{last_error(), std::system_category(), "UnmapViewOfFile failed"};
783  }
784  make_invalid();
785  }
786 
787  if (m_handle) {
788  if (!CloseHandle(m_handle)) {
789  throw std::system_error{last_error(), std::system_category(), "CloseHandle failed"};
790  }
791  m_handle = nullptr;
792  }
793 }
794 
795 inline void osmium::util::MemoryMapping::resize(std::size_t new_size) {
796  unmap();
797 
798  m_size = new_size;
799  resize_fd(m_fd);
800 
801  m_handle = create_file_mapping();
802  if (!m_handle) {
803  throw std::system_error{last_error(), std::system_category(), "CreateFileMapping failed"};
804  }
805 
806  m_addr = map_view_of_file();
807  if (!is_valid()) {
808  throw std::system_error{last_error(), std::system_category(), "MapViewOfFile failed"};
809  }
810 }
811 
812 #endif
813 
814 #endif // OSMIUM_UTIL_MEMORY_MAPPING_HPP
Definition: memory_mapping.hpp:312
AnonymousMemoryMapping(std::size_t size)
Definition: memory_mapping.hpp:316
Definition: memory_mapping.hpp:508
AnonymousTypedMemoryMapping(std::size_t size)
Definition: memory_mapping.hpp:512
Definition: memory_mapping.hpp:95
int resize_fd(int fd) const
Definition: memory_mapping.hpp:168
void unmap()
Definition: memory_mapping.hpp:607
bool is_valid() const noexcept
Definition: memory_mapping.hpp:538
mapping_mode
Definition: memory_mapping.hpp:99
std::size_t size() const noexcept
Definition: memory_mapping.hpp:270
MemoryMapping(const MemoryMapping &)=delete
You can not copy construct a MemoryMapping.
flag_type get_protection() const noexcept
Definition: memory_mapping.hpp:553
off_t m_offset
Offset into the file.
Definition: memory_mapping.hpp:111
~MemoryMapping() noexcept
Definition: memory_mapping.hpp:228
MemoryMapping & operator=(const MemoryMapping &)=delete
You can not copy a MemoryMapping.
T * get_addr() const noexcept
Definition: memory_mapping.hpp:296
std::size_t m_size
The size of the mapping.
Definition: memory_mapping.hpp:108
static std::size_t available_space(int fd)
Definition: memory_mapping.hpp:155
int m_fd
File handle we got the mapping from.
Definition: memory_mapping.hpp:114
int flag_type
Definition: memory_mapping.hpp:133
void resize(std::size_t new_size)
Definition: memory_mapping.hpp:616
bool writable() const noexcept
Definition: memory_mapping.hpp:286
flag_type get_flags() const noexcept
Definition: memory_mapping.hpp:560
mapping_mode m_mapping_mode
Mapping mode.
Definition: memory_mapping.hpp:117
static std::size_t check_size(std::size_t size)
Definition: memory_mapping.hpp:140
void make_invalid() noexcept
Definition: memory_mapping.hpp:542
void * m_addr
The address where the memory is mapped.
Definition: memory_mapping.hpp:124
int fd() const noexcept
Definition: memory_mapping.hpp:279
MemoryMapping(std::size_t size, mapping_mode mode, int fd=-1, off_t offset=0)
Definition: memory_mapping.hpp:570
Definition: memory_mapping.hpp:340
~TypedMemoryMapping() noexcept=default
int fd() const noexcept
Definition: memory_mapping.hpp:440
void resize(std::size_t new_size)
Definition: memory_mapping.hpp:413
const T * end() const noexcept
Definition: memory_mapping.hpp:501
const T * cbegin() const noexcept
Definition: memory_mapping.hpp:474
TypedMemoryMapping & operator=(const TypedMemoryMapping &)=delete
You can not copy a TypedMemoryMapping.
const T * cend() const noexcept
Definition: memory_mapping.hpp:483
void unmap()
Definition: memory_mapping.hpp:399
TypedMemoryMapping(std::size_t size)
Definition: memory_mapping.hpp:352
const T * begin() const noexcept
Definition: memory_mapping.hpp:492
bool writable() const noexcept
Definition: memory_mapping.hpp:447
T * end() noexcept
Definition: memory_mapping.hpp:465
T * begin() noexcept
Definition: memory_mapping.hpp:456
TypedMemoryMapping(const TypedMemoryMapping &)=delete
You can not copy construct a TypedMemoryMapping.
std::size_t size() const noexcept
Definition: memory_mapping.hpp:430
TypedMemoryMapping & operator=(TypedMemoryMapping &&other) noexcept=default
TypedMemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset=0)
Definition: memory_mapping.hpp:366
MemoryMapping m_mapping
Definition: memory_mapping.hpp:342
TypedMemoryMapping(TypedMemoryMapping &&other) noexcept=default
#define MAP_ANONYMOUS
Definition: memory_mapping.hpp:550
void resize_file(int fd, std::size_t new_size)
Definition: file.hpp:177
std::size_t file_size(int fd)
Definition: file.hpp:109
std::size_t get_pagesize() noexcept
Definition: file.hpp:193
Namespace for everything in the Osmium library.
Definition: assembler.hpp:53
Definition: location.hpp:555