Libosmium  2.17.3
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-2022 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 
37 #include <osmium/util/file.hpp>
38 
39 #include <cassert>
40 #include <cerrno>
41 #include <cstddef>
42 #include <stdexcept>
43 #include <system_error>
44 
45 #ifndef _WIN32
46 # include <sys/mman.h>
47 # include <sys/statvfs.h>
48 #else
49 # include <fcntl.h>
50 # include <io.h>
51 # include <windows.h>
52 # include <sys/types.h>
53 #endif
54 
55 namespace osmium {
56 
57  inline namespace util {
58 
96  class MemoryMapping {
97 
98  public:
99 
100  enum class mapping_mode {
101  readonly = 0,
102  write_private = 1,
103  write_shared = 2
104  };
105 
106  private:
107 
109  std::size_t m_size;
110 
112  off_t m_offset;
113 
115  int m_fd;
116 
119 
120 #ifdef _WIN32
121  HANDLE m_handle;
122 #endif
123 
125  void* m_addr;
126 
127  bool is_valid() const noexcept;
128 
129  void make_invalid() noexcept;
130 
131 #ifdef _WIN32
132  using flag_type = DWORD;
133 #else
134  using flag_type = int;
135 #endif
136 
137  flag_type get_protection() const noexcept;
138 
139  flag_type get_flags() const noexcept;
140 
141  static std::size_t check_size(std::size_t size) {
142  if (size == 0) {
143  return osmium::get_pagesize();
144  }
145  return size;
146  }
147 
148 #ifdef _WIN32
149  HANDLE get_handle() const noexcept;
150  HANDLE create_file_mapping() const noexcept;
151  void* map_view_of_file() const noexcept;
152 #endif
153 
154  // Get the available space on the file system where the file
155  // behind fd is on. Return 0 if it can't be determined.
156  static std::size_t available_space(int fd) {
157 #ifdef _WIN32
158  return 0;
159 #else
160  struct statvfs stat{};
161  const int result = ::fstatvfs(fd, &stat);
162  if (result != 0) {
163  return 0;
164  }
165  return stat.f_bsize * stat.f_bavail;
166 #endif
167  }
168 
169  int resize_fd(int fd) const {
170  // Anonymous mapping doesn't need resizing.
171  if (fd == -1) {
172  return -1;
173  }
174 
175  // Make sure the file backing this mapping is large enough.
176  auto const current_file_size = osmium::file_size(fd);
177  if (current_file_size < m_size + m_offset) {
178  const auto available = available_space(fd);
179  if (available > 0 && current_file_size + available <= m_size) {
180  throw std::system_error{ENOSPC, std::system_category(), "Could not resize file: Not enough space on filesystem"};
181  }
182 
184  }
185  return fd;
186  }
187 
188  public:
189 
206  MemoryMapping(std::size_t size, mapping_mode mode, int fd = -1, off_t offset = 0);
207 
213  // cppcheck-suppress noExplicitConstructor
214  OSMIUM_DEPRECATED MemoryMapping(std::size_t size, bool writable = true, int fd = -1, off_t offset = 0) : // NOLINT(google-explicit-constructor, hicpp-explicit-conversions)
215  MemoryMapping(size, writable ? mapping_mode::write_shared : mapping_mode::readonly, fd, offset) {
216  }
217 
219  MemoryMapping(const MemoryMapping&) = delete;
220 
223 
228  MemoryMapping(MemoryMapping&& other) noexcept;
229 
233  MemoryMapping& operator=(MemoryMapping&& other) noexcept;
234 
239  ~MemoryMapping() noexcept {
240  try {
241  unmap();
242  } catch (const std::system_error&) {
243  // Ignore any exceptions because destructor must not throw.
244  }
245  }
246 
253  void unmap();
254 
266  void resize(std::size_t new_size);
267 
272  explicit operator bool() const noexcept {
273  return is_valid();
274  }
275 
281  std::size_t size() const noexcept {
282  return m_size;
283  }
284 
290  int fd() const noexcept {
291  return m_fd;
292  }
293 
297  bool writable() const noexcept {
299  }
300 
306  template <typename T = void>
307  T* get_addr() const noexcept {
308  return reinterpret_cast<T*>(m_addr);
309  }
310 
311  }; // class MemoryMapping
312 
324 
325  public:
326 
327  explicit AnonymousMemoryMapping(std::size_t size) :
328  MemoryMapping(size, mapping_mode::write_private) {
329  }
330 
331 #ifndef __linux__
336  void resize(std::size_t) = delete;
337 #endif
338 
339  }; // class AnonymousMemoryMapping
340 
350  template <typename T>
352 
354 
355  public:
356 
363  explicit TypedMemoryMapping(std::size_t size) :
364  m_mapping(sizeof(T) * size, MemoryMapping::mapping_mode::write_private) {
365  }
366 
377  TypedMemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset = 0) :
378  m_mapping(sizeof(T) * size, mode, fd, sizeof(T) * offset) {
379  }
380 
386  OSMIUM_DEPRECATED TypedMemoryMapping(std::size_t size, bool writable, int fd, off_t offset = 0) :
387  m_mapping(sizeof(T) * size,
388  writable ? MemoryMapping::mapping_mode::write_shared : MemoryMapping::mapping_mode::readonly,
389  fd,
390  sizeof(T) * offset) {
391  }
392 
395 
398 
403  TypedMemoryMapping(TypedMemoryMapping&& other) noexcept = default;
404 
408  TypedMemoryMapping& operator=(TypedMemoryMapping&& other) noexcept = default;
409 
414  ~TypedMemoryMapping() noexcept = default;
415 
422  void unmap() {
423  m_mapping.unmap();
424  }
425 
436  void resize(std::size_t new_size) {
437  m_mapping.resize(sizeof(T) * new_size);
438  }
439 
444  explicit operator bool() const noexcept {
445  return !!m_mapping;
446  }
447 
453  std::size_t size() const noexcept {
454  assert(m_mapping.size() % sizeof(T) == 0);
455  return m_mapping.size() / sizeof(T);
456  }
457 
463  int fd() const noexcept {
464  return m_mapping.fd();
465  }
466 
470  bool writable() const noexcept {
471  return m_mapping.writable();
472  }
473 
479  T* begin() noexcept {
480  return m_mapping.get_addr<T>();
481  }
482 
488  T* end() noexcept {
489  return m_mapping.get_addr<T>() + size();
490  }
491 
497  const T* cbegin() const noexcept {
498  return m_mapping.get_addr<T>();
499  }
500 
506  const T* cend() const noexcept {
507  return m_mapping.get_addr<T>() + size();
508  }
509 
515  const T* begin() const noexcept {
516  return m_mapping.get_addr<T>();
517  }
518 
524  const T* end() const noexcept {
525  return m_mapping.get_addr<T>() + size();
526  }
527 
528  }; // class TypedMemoryMapping
529 
530  template <typename T>
532 
533  public:
534 
535  explicit AnonymousTypedMemoryMapping(std::size_t size) :
537  }
538 
539 #ifndef __linux__
544  void resize(std::size_t) = delete;
545 #endif
546 
547  }; // class AnonymousTypedMemoryMapping
548 
549  } // namespace util
550 
551 } // namespace osmium
552 
553 #ifndef _WIN32
554 
555 // =========== Unix implementation =============
556 
557 // MAP_FAILED is often a macro containing an old style cast
558 #pragma GCC diagnostic push
559 #pragma GCC diagnostic ignored "-Wold-style-cast"
560 
561 inline bool osmium::util::MemoryMapping::is_valid() const noexcept {
562  return m_addr != MAP_FAILED; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
563 }
564 
566  m_addr = MAP_FAILED; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
567 }
568 
569 #pragma GCC diagnostic pop
570 
571 // for BSD systems
572 #ifndef MAP_ANONYMOUS
573 # define MAP_ANONYMOUS MAP_ANON
574 #endif
575 
576 inline int osmium::util::MemoryMapping::get_protection() const noexcept {
577  if (m_mapping_mode == mapping_mode::readonly) {
578  return PROT_READ;
579  }
580  return PROT_READ | PROT_WRITE; // NOLINT(hicpp-signed-bitwise)
581 }
582 
583 inline int osmium::util::MemoryMapping::get_flags() const noexcept {
584  if (m_fd == -1) {
585  return MAP_PRIVATE | MAP_ANONYMOUS; // NOLINT(hicpp-signed-bitwise)
586  }
587  if (m_mapping_mode == mapping_mode::write_shared) {
588  return MAP_SHARED;
589  }
590  return MAP_PRIVATE;
591 }
592 
593 inline osmium::util::MemoryMapping::MemoryMapping(std::size_t size, mapping_mode mode, int fd, off_t offset) :
594  m_size(check_size(size)),
595  m_offset(offset),
596  m_fd(resize_fd(fd)),
597  m_mapping_mode(mode),
598  m_addr(::mmap(nullptr, m_size, get_protection(), get_flags(), m_fd, m_offset)) {
599  assert(!(fd == -1 && mode == mapping_mode::readonly));
600  if (!is_valid()) {
601  throw std::system_error{errno, std::system_category(), "mmap failed"};
602  }
603 }
604 
606  m_size(other.m_size),
607  m_offset(other.m_offset),
608  m_fd(other.m_fd),
609  m_mapping_mode(other.m_mapping_mode),
610  m_addr(other.m_addr) {
611  other.make_invalid();
612 }
613 
615  try {
616  unmap();
617  } catch (const std::system_error&) {
618  // Ignore unmap error. It should never happen anyway and we can't do
619  // anything about it here.
620  }
621  m_size = other.m_size;
622  m_offset = other.m_offset;
623  m_fd = other.m_fd;
624  m_mapping_mode = other.m_mapping_mode;
625  m_addr = other.m_addr;
626  other.make_invalid();
627  return *this;
628 }
629 
631  if (is_valid()) {
632  if (::munmap(m_addr, m_size) != 0) {
633  throw std::system_error{errno, std::system_category(), "munmap failed"};
634  }
635  make_invalid();
636  }
637 }
638 
639 inline void osmium::util::MemoryMapping::resize(std::size_t new_size) {
640  assert(new_size > 0 && "can not resize to zero size");
641  if (m_fd == -1) { // anonymous mapping
642 #ifdef __linux__
643  m_addr = ::mremap(m_addr, m_size, new_size, MREMAP_MAYMOVE);
644  if (!is_valid()) {
645  throw std::system_error{errno, std::system_category(), "mremap failed"};
646  }
647  m_size = new_size;
648 #else
649  assert(false && "can't resize anonymous mappings on non-linux systems");
650 #endif
651  } else { // file-based mapping
652  unmap();
653  m_size = new_size;
654  resize_fd(m_fd);
655  m_addr = ::mmap(nullptr, new_size, get_protection(), get_flags(), m_fd, m_offset);
656  if (!is_valid()) {
657  throw std::system_error{errno, std::system_category(), "mmap (remap) failed"};
658  }
659  }
660 }
661 
662 #else
663 
664 // =========== Windows implementation =============
665 
666 /* References:
667  * CreateFileMapping: https://msdn.microsoft.com/en-us/library/aa366537(VS.85).aspx
668  * CloseHandle: https://msdn.microsoft.com/en-us/library/ms724211(VS.85).aspx
669  * MapViewOfFile: https://msdn.microsoft.com/en-us/library/aa366761(VS.85).aspx
670  * UnmapViewOfFile: https://msdn.microsoft.com/en-us/library/aa366882(VS.85).aspx
671  */
672 
673 namespace osmium {
674 
675  inline namespace util {
676 
677  inline DWORD dword_hi(uint64_t x) {
678  return static_cast<DWORD>(x >> 32);
679  }
680 
681  inline DWORD dword_lo(uint64_t x) {
682  return static_cast<DWORD>(x & 0xffffffff);
683  }
684 
685  } // namespace util
686 
687 } // namespace osmium
688 
689 inline DWORD osmium::util::MemoryMapping::get_protection() const noexcept {
690  switch (m_mapping_mode) {
691  case mapping_mode::readonly:
692  return PAGE_READONLY;
693  case mapping_mode::write_private:
694  return PAGE_WRITECOPY;
695  default: // mapping_mode::write_shared
696  break;
697  }
698  return PAGE_READWRITE;
699 }
700 
701 inline DWORD osmium::util::MemoryMapping::get_flags() const noexcept {
702  switch (m_mapping_mode) {
703  case mapping_mode::readonly:
704  return FILE_MAP_READ;
705  case mapping_mode::write_private:
706  return FILE_MAP_COPY;
707  default: // mapping_mode::write_shared
708  break;
709  }
710  return FILE_MAP_WRITE;
711 }
712 
713 inline HANDLE osmium::util::MemoryMapping::get_handle() const noexcept {
714  if (m_fd == -1) {
715  return INVALID_HANDLE_VALUE;
716  }
717  return reinterpret_cast<HANDLE>(_get_osfhandle(m_fd));
718 }
719 
720 inline HANDLE osmium::util::MemoryMapping::create_file_mapping() const noexcept {
721  if (m_fd != -1) {
722  _setmode(m_fd, _O_BINARY);
723  }
724  return CreateFileMapping(get_handle(),
725  nullptr,
726  get_protection(),
727  osmium::dword_hi(static_cast<uint64_t>(m_size) + m_offset),
728  osmium::dword_lo(static_cast<uint64_t>(m_size) + m_offset),
729  nullptr);
730 }
731 
732 inline void* osmium::util::MemoryMapping::map_view_of_file() const noexcept {
733  return MapViewOfFile(m_handle,
734  get_flags(),
735  osmium::dword_hi(m_offset),
736  osmium::dword_lo(m_offset),
737  m_size);
738 }
739 
740 inline bool osmium::util::MemoryMapping::is_valid() const noexcept {
741  return m_addr != nullptr;
742 }
743 
744 inline void osmium::util::MemoryMapping::make_invalid() noexcept {
745  m_addr = nullptr;
746 }
747 
748 // GetLastError() returns a DWORD (A 32-bit unsigned integer), but the error
749 // code for std::system_error is an int. So we convert this here and hope
750 // it all works.
751 inline int last_error() noexcept {
752  return static_cast<int>(GetLastError());
753 }
754 
755 inline osmium::util::MemoryMapping::MemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset) :
756  m_size(check_size(size)),
757  m_offset(offset),
758  m_fd(resize_fd(fd)),
759  m_mapping_mode(mode),
760  m_handle(create_file_mapping()),
761  m_addr(nullptr) {
762 
763  if (!m_handle) {
764  throw std::system_error{last_error(), std::system_category(), "CreateFileMapping failed"};
765  }
766 
767  m_addr = map_view_of_file();
768  if (!is_valid()) {
769  throw std::system_error{last_error(), std::system_category(), "MapViewOfFile failed"};
770  }
771 }
772 
773 inline osmium::util::MemoryMapping::MemoryMapping(MemoryMapping&& other) noexcept :
774  m_size(other.m_size),
775  m_offset(other.m_offset),
776  m_fd(other.m_fd),
777  m_mapping_mode(other.m_mapping_mode),
778  m_handle(std::move(other.m_handle)),
779  m_addr(other.m_addr) {
780  other.make_invalid();
781  other.m_handle = nullptr;
782 }
783 
785  try {
786  unmap();
787  } catch (const std::system_error&) {
788  // Ignore unmap error. It should never happen anyway and we can't do
789  // anything about it here.
790  }
791  m_size = other.m_size;
792  m_offset = other.m_offset;
793  m_fd = other.m_fd;
794  m_mapping_mode = other.m_mapping_mode;
795  m_handle = std::move(other.m_handle);
796  m_addr = other.m_addr;
797  other.make_invalid();
798  other.m_handle = nullptr;
799  return *this;
800 }
801 
803  if (is_valid()) {
804  if (!UnmapViewOfFile(m_addr)) {
805  throw std::system_error{last_error(), std::system_category(), "UnmapViewOfFile failed"};
806  }
807  make_invalid();
808  }
809 
810  if (m_handle) {
811  if (!CloseHandle(m_handle)) {
812  throw std::system_error{last_error(), std::system_category(), "CloseHandle failed"};
813  }
814  m_handle = nullptr;
815  }
816 }
817 
818 inline void osmium::util::MemoryMapping::resize(std::size_t new_size) {
819  unmap();
820 
821  m_size = new_size;
822  resize_fd(m_fd);
823 
824  m_handle = create_file_mapping();
825  if (!m_handle) {
826  throw std::system_error{last_error(), std::system_category(), "CreateFileMapping failed"};
827  }
828 
829  m_addr = map_view_of_file();
830  if (!is_valid()) {
831  throw std::system_error{last_error(), std::system_category(), "MapViewOfFile failed"};
832  }
833 }
834 
835 #endif
836 
837 #endif // OSMIUM_UTIL_MEMORY_MAPPING_HPP
Definition: memory_mapping.hpp:323
AnonymousMemoryMapping(std::size_t size)
Definition: memory_mapping.hpp:327
Definition: memory_mapping.hpp:531
AnonymousTypedMemoryMapping(std::size_t size)
Definition: memory_mapping.hpp:535
Definition: memory_mapping.hpp:96
int resize_fd(int fd) const
Definition: memory_mapping.hpp:169
void unmap()
Definition: memory_mapping.hpp:630
bool is_valid() const noexcept
Definition: memory_mapping.hpp:561
mapping_mode
Definition: memory_mapping.hpp:100
std::size_t size() const noexcept
Definition: memory_mapping.hpp:281
OSMIUM_DEPRECATED MemoryMapping(std::size_t size, bool writable=true, int fd=-1, off_t offset=0)
Definition: memory_mapping.hpp:214
MemoryMapping(const MemoryMapping &)=delete
You can not copy construct a MemoryMapping.
flag_type get_protection() const noexcept
Definition: memory_mapping.hpp:576
off_t m_offset
Offset into the file.
Definition: memory_mapping.hpp:112
~MemoryMapping() noexcept
Definition: memory_mapping.hpp:239
MemoryMapping & operator=(const MemoryMapping &)=delete
You can not copy a MemoryMapping.
T * get_addr() const noexcept
Definition: memory_mapping.hpp:307
std::size_t m_size
The size of the mapping.
Definition: memory_mapping.hpp:109
static std::size_t available_space(int fd)
Definition: memory_mapping.hpp:156
int m_fd
File handle we got the mapping from.
Definition: memory_mapping.hpp:115
int flag_type
Definition: memory_mapping.hpp:134
void resize(std::size_t new_size)
Definition: memory_mapping.hpp:639
bool writable() const noexcept
Definition: memory_mapping.hpp:297
flag_type get_flags() const noexcept
Definition: memory_mapping.hpp:583
mapping_mode m_mapping_mode
Mapping mode.
Definition: memory_mapping.hpp:118
static std::size_t check_size(std::size_t size)
Definition: memory_mapping.hpp:141
void make_invalid() noexcept
Definition: memory_mapping.hpp:565
void * m_addr
The address where the memory is mapped.
Definition: memory_mapping.hpp:125
int fd() const noexcept
Definition: memory_mapping.hpp:290
MemoryMapping(std::size_t size, mapping_mode mode, int fd=-1, off_t offset=0)
Definition: memory_mapping.hpp:593
Definition: memory_mapping.hpp:351
~TypedMemoryMapping() noexcept=default
int fd() const noexcept
Definition: memory_mapping.hpp:463
void resize(std::size_t new_size)
Definition: memory_mapping.hpp:436
const T * end() const noexcept
Definition: memory_mapping.hpp:524
const T * cbegin() const noexcept
Definition: memory_mapping.hpp:497
TypedMemoryMapping & operator=(const TypedMemoryMapping &)=delete
You can not copy a TypedMemoryMapping.
const T * cend() const noexcept
Definition: memory_mapping.hpp:506
void unmap()
Definition: memory_mapping.hpp:422
TypedMemoryMapping(std::size_t size)
Definition: memory_mapping.hpp:363
const T * begin() const noexcept
Definition: memory_mapping.hpp:515
bool writable() const noexcept
Definition: memory_mapping.hpp:470
T * end() noexcept
Definition: memory_mapping.hpp:488
T * begin() noexcept
Definition: memory_mapping.hpp:479
TypedMemoryMapping(const TypedMemoryMapping &)=delete
You can not copy construct a TypedMemoryMapping.
std::size_t size() const noexcept
Definition: memory_mapping.hpp:453
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:377
MemoryMapping m_mapping
Definition: memory_mapping.hpp:353
TypedMemoryMapping(TypedMemoryMapping &&other) noexcept=default
OSMIUM_DEPRECATED TypedMemoryMapping(std::size_t size, bool writable, int fd, off_t offset=0)
Definition: memory_mapping.hpp:386
#define OSMIUM_DEPRECATED
Definition: compatibility.hpp:51
#define MAP_ANONYMOUS
Definition: memory_mapping.hpp:573
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