Loading...
Searching...
No Matches
memory_mapping.hpp
Go to the documentation of this file.
#ifndef OSMIUM_UTIL_MEMORY_MAPPING_HPP
#define OSMIUM_UTIL_MEMORY_MAPPING_HPP
/*
This file is part of Osmium (https://osmcode.org/libosmium).
Copyright 2013-2025 Jochen Topf <jochen@topf.org> and others (see README).
Boost Software License - Version 1.0 - August 17th, 2003
Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:
The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/
#include <osmium/util/file.hpp>
#include <cassert>
#include <cerrno>
#include <cstddef>
#include <stdexcept>
#include <system_error>
#ifndef _WIN32
# include <sys/mman.h>
# include <sys/statvfs.h>
#else
# include <fcntl.h>
# include <io.h>
# include <windows.h>
# include <sys/types.h>
#endif
namespace osmium {
inline namespace util {
class MemoryMapping {
public:
enum class mapping_mode {
readonly = 0,
write_private = 1,
write_shared = 2
};
private:
std::size_t m_size;
off_t m_offset;
int m_fd;
mapping_mode m_mapping_mode;
#ifdef _WIN32
HANDLE m_handle;
#endif
void* m_addr;
bool is_valid() const noexcept;
void make_invalid() noexcept;
#ifdef _WIN32
using flag_type = DWORD;
#else
using flag_type = int;
#endif
flag_type get_protection() const noexcept;
flag_type get_flags() const noexcept;
static std::size_t check_size(std::size_t size) {
if (size == 0) {
return osmium::get_pagesize();
}
return size;
}
#ifdef _WIN32
HANDLE get_handle() const noexcept;
HANDLE create_file_mapping() const noexcept;
void* map_view_of_file() const noexcept;
#endif
// Get the available space on the file system where the file
// behind fd is on. Return 0 if it can't be determined.
static std::size_t available_space(int fd) {
#ifdef _WIN32
return 0;
#else
struct statvfs stat{};
const int result = ::fstatvfs(fd, &stat);
if (result != 0) {
return 0;
}
return stat.f_bsize * stat.f_bavail;
#endif
}
int resize_fd(int fd) const {
// Anonymous mapping doesn't need resizing.
if (fd == -1) {
return -1;
}
// Make sure the file backing this mapping is large enough.
auto const current_file_size = osmium::file_size(fd);
if (current_file_size < m_size + m_offset) {
const auto available = available_space(fd);
if (available > 0 && current_file_size + available <= m_size) {
throw std::system_error{ENOSPC, std::system_category(), "Could not resize file: Not enough space on filesystem"};
}
osmium::resize_file(fd, m_size + m_offset);
}
return fd;
}
public:
MemoryMapping(std::size_t size, mapping_mode mode, int fd = -1, off_t offset = 0);
MemoryMapping(const MemoryMapping&) = delete;
MemoryMapping& operator=(const MemoryMapping&) = delete;
MemoryMapping(MemoryMapping&& other) noexcept;
MemoryMapping& operator=(MemoryMapping&& other) noexcept;
~MemoryMapping() noexcept {
try {
unmap();
} catch (const std::system_error&) { // NOLINT(bugprone-empty-catch)
// Ignore any exceptions because destructor must not throw.
}
}
void unmap();
void resize(std::size_t new_size);
explicit operator bool() const noexcept {
return is_valid();
}
std::size_t size() const noexcept {
return m_size;
}
int fd() const noexcept {
return m_fd;
}
bool writable() const noexcept {
return m_mapping_mode != mapping_mode::readonly;
}
template <typename T = void>
T* get_addr() const noexcept {
return reinterpret_cast<T*>(m_addr);
}
}; // class MemoryMapping
class AnonymousMemoryMapping : public MemoryMapping {
public:
explicit AnonymousMemoryMapping(std::size_t size) :
MemoryMapping(size, mapping_mode::write_private) {
}
#ifndef __linux__
void resize(std::size_t) = delete;
#endif
}; // class AnonymousMemoryMapping
template <typename T>
class TypedMemoryMapping {
MemoryMapping m_mapping;
public:
explicit TypedMemoryMapping(std::size_t size) :
m_mapping(sizeof(T) * size, MemoryMapping::mapping_mode::write_private) {
}
TypedMemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset = 0) :
m_mapping(sizeof(T) * size, mode, fd, sizeof(T) * offset) {
}
TypedMemoryMapping(const TypedMemoryMapping&) = delete;
TypedMemoryMapping& operator=(const TypedMemoryMapping&) = delete;
TypedMemoryMapping(TypedMemoryMapping&& other) noexcept = default;
TypedMemoryMapping& operator=(TypedMemoryMapping&& other) noexcept = default;
~TypedMemoryMapping() noexcept = default;
void unmap() {
m_mapping.unmap();
}
void resize(std::size_t new_size) {
m_mapping.resize(sizeof(T) * new_size);
}
explicit operator bool() const noexcept {
return !!m_mapping;
}
std::size_t size() const noexcept {
assert(m_mapping.size() % sizeof(T) == 0);
return m_mapping.size() / sizeof(T);
}
int fd() const noexcept {
return m_mapping.fd();
}
bool writable() const noexcept {
return m_mapping.writable();
}
T* begin() noexcept {
return m_mapping.get_addr<T>();
}
T* end() noexcept {
return m_mapping.get_addr<T>() + size();
}
const T* cbegin() const noexcept {
return m_mapping.get_addr<T>();
}
const T* cend() const noexcept {
return m_mapping.get_addr<T>() + size();
}
const T* begin() const noexcept {
return m_mapping.get_addr<T>();
}
const T* end() const noexcept {
return m_mapping.get_addr<T>() + size();
}
}; // class TypedMemoryMapping
template <typename T>
class AnonymousTypedMemoryMapping : public TypedMemoryMapping<T> {
public:
explicit AnonymousTypedMemoryMapping(std::size_t size) :
TypedMemoryMapping<T>(size) {
}
#ifndef __linux__
void resize(std::size_t) = delete;
#endif
}; // class AnonymousTypedMemoryMapping
} // namespace util
} // namespace osmium
#ifndef _WIN32
// =========== Unix implementation =============
// MAP_FAILED is often a macro containing an old style cast
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
inline bool osmium::util::MemoryMapping::is_valid() const noexcept {
return m_addr != MAP_FAILED; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
}
inline void osmium::util::MemoryMapping::make_invalid() noexcept {
m_addr = MAP_FAILED; // NOLINT(cppcoreguidelines-pro-type-cstyle-cast,performance-no-int-to-ptr)
}
#pragma GCC diagnostic pop
// for BSD systems
#ifndef MAP_ANONYMOUS
# define MAP_ANONYMOUS MAP_ANON
#endif
inline int osmium::util::MemoryMapping::get_protection() const noexcept {
if (m_mapping_mode == mapping_mode::readonly) {
return PROT_READ;
}
return PROT_READ | PROT_WRITE; // NOLINT(hicpp-signed-bitwise)
}
inline int osmium::util::MemoryMapping::get_flags() const noexcept {
if (m_fd == -1) {
return MAP_PRIVATE | MAP_ANONYMOUS; // NOLINT(hicpp-signed-bitwise)
}
if (m_mapping_mode == mapping_mode::write_shared) {
return MAP_SHARED;
}
return MAP_PRIVATE;
}
inline osmium::util::MemoryMapping::MemoryMapping(std::size_t size, mapping_mode mode, int fd, off_t offset) :
m_size(check_size(size)),
m_offset(offset),
m_fd(resize_fd(fd)),
m_mapping_mode(mode),
m_addr(::mmap(nullptr, m_size, get_protection(), get_flags(), m_fd, m_offset)) {
assert(!(fd == -1 && mode == mapping_mode::readonly));
if (!is_valid()) {
throw std::system_error{errno, std::system_category(), "mmap failed"};
}
}
inline osmium::util::MemoryMapping::MemoryMapping(MemoryMapping&& other) noexcept :
m_size(other.m_size),
m_offset(other.m_offset),
m_fd(other.m_fd),
m_mapping_mode(other.m_mapping_mode),
m_addr(other.m_addr) {
other.make_invalid();
}
inline osmium::util::MemoryMapping& osmium::util::MemoryMapping::operator=(osmium::util::MemoryMapping&& other) noexcept {
try {
unmap();
} catch (const std::system_error&) { // NOLINT(bugprone-empty-catch)
// Ignore unmap error. It should never happen anyway and we can't do
// anything about it here.
}
m_size = other.m_size;
m_offset = other.m_offset;
m_fd = other.m_fd;
m_mapping_mode = other.m_mapping_mode;
m_addr = other.m_addr;
other.make_invalid();
return *this;
}
inline void osmium::util::MemoryMapping::unmap() {
if (is_valid()) {
if (::munmap(m_addr, m_size) != 0) {
throw std::system_error{errno, std::system_category(), "munmap failed"};
}
make_invalid();
}
}
inline void osmium::util::MemoryMapping::resize(std::size_t new_size) {
assert(new_size > 0 && "can not resize to zero size");
if (m_fd == -1) { // anonymous mapping
#ifdef __linux__
m_addr = ::mremap(m_addr, m_size, new_size, MREMAP_MAYMOVE);
if (!is_valid()) {
throw std::system_error{errno, std::system_category(), "mremap failed"};
}
m_size = new_size;
#else
assert(false && "can't resize anonymous mappings on non-linux systems");
#endif
} else { // file-based mapping
unmap();
m_size = new_size;
resize_fd(m_fd);
m_addr = ::mmap(nullptr, new_size, get_protection(), get_flags(), m_fd, m_offset);
if (!is_valid()) {
throw std::system_error{errno, std::system_category(), "mmap (remap) failed"};
}
}
}
#else
// =========== Windows implementation =============
/* References:
* CreateFileMapping: https://msdn.microsoft.com/en-us/library/aa366537(VS.85).aspx
* CloseHandle: https://msdn.microsoft.com/en-us/library/ms724211(VS.85).aspx
* MapViewOfFile: https://msdn.microsoft.com/en-us/library/aa366761(VS.85).aspx
* UnmapViewOfFile: https://msdn.microsoft.com/en-us/library/aa366882(VS.85).aspx
*/
namespace osmium {
inline namespace util {
inline DWORD dword_hi(uint64_t x) {
return static_cast<DWORD>(x >> 32);
}
inline DWORD dword_lo(uint64_t x) {
return static_cast<DWORD>(x & 0xffffffff);
}
} // namespace util
} // namespace osmium
inline DWORD osmium::util::MemoryMapping::get_protection() const noexcept {
switch (m_mapping_mode) {
case mapping_mode::readonly:
return PAGE_READONLY;
case mapping_mode::write_private:
return PAGE_WRITECOPY;
default: // mapping_mode::write_shared
break;
}
return PAGE_READWRITE;
}
inline DWORD osmium::util::MemoryMapping::get_flags() const noexcept {
switch (m_mapping_mode) {
case mapping_mode::readonly:
return FILE_MAP_READ;
case mapping_mode::write_private:
return FILE_MAP_COPY;
default: // mapping_mode::write_shared
break;
}
return FILE_MAP_WRITE;
}
inline HANDLE osmium::util::MemoryMapping::get_handle() const noexcept {
if (m_fd == -1) {
return INVALID_HANDLE_VALUE;
}
return reinterpret_cast<HANDLE>(_get_osfhandle(m_fd));
}
inline HANDLE osmium::util::MemoryMapping::create_file_mapping() const noexcept {
if (m_fd != -1) {
_setmode(m_fd, _O_BINARY);
}
return CreateFileMapping(get_handle(),
nullptr,
get_protection(),
osmium::dword_hi(static_cast<uint64_t>(m_size) + m_offset),
osmium::dword_lo(static_cast<uint64_t>(m_size) + m_offset),
nullptr);
}
inline void* osmium::util::MemoryMapping::map_view_of_file() const noexcept {
return MapViewOfFile(m_handle,
get_flags(),
osmium::dword_hi(m_offset),
osmium::dword_lo(m_offset),
m_size);
}
inline bool osmium::util::MemoryMapping::is_valid() const noexcept {
return m_addr != nullptr;
}
inline void osmium::util::MemoryMapping::make_invalid() noexcept {
m_addr = nullptr;
}
// GetLastError() returns a DWORD (A 32-bit unsigned integer), but the error
// code for std::system_error is an int. So we convert this here and hope
// it all works.
inline int last_error() noexcept {
return static_cast<int>(GetLastError());
}
inline osmium::util::MemoryMapping::MemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset) :
m_size(check_size(size)),
m_offset(offset),
m_fd(resize_fd(fd)),
m_mapping_mode(mode),
m_handle(create_file_mapping()),
m_addr(nullptr) {
if (!m_handle) {
throw std::system_error{last_error(), std::system_category(), "CreateFileMapping failed"};
}
m_addr = map_view_of_file();
if (!is_valid()) {
throw std::system_error{last_error(), std::system_category(), "MapViewOfFile failed"};
}
}
inline osmium::util::MemoryMapping::MemoryMapping(MemoryMapping&& other) noexcept :
m_size(other.m_size),
m_offset(other.m_offset),
m_fd(other.m_fd),
m_mapping_mode(other.m_mapping_mode),
m_handle(std::move(other.m_handle)),
m_addr(other.m_addr) {
other.make_invalid();
other.m_handle = nullptr;
}
inline osmium::util::MemoryMapping& osmium::util::MemoryMapping::operator=(osmium::util::MemoryMapping&& other) noexcept {
try {
unmap();
} catch (const std::system_error&) { // NOLINT(bugprone-empty-catch)
// Ignore unmap error. It should never happen anyway and we can't do
// anything about it here.
}
m_size = other.m_size;
m_offset = other.m_offset;
m_fd = other.m_fd;
m_mapping_mode = other.m_mapping_mode;
m_handle = std::move(other.m_handle);
m_addr = other.m_addr;
other.make_invalid();
other.m_handle = nullptr;
return *this;
}
inline void osmium::util::MemoryMapping::unmap() {
if (is_valid()) {
if (!UnmapViewOfFile(m_addr)) {
throw std::system_error{last_error(), std::system_category(), "UnmapViewOfFile failed"};
}
make_invalid();
}
if (m_handle) {
if (!CloseHandle(m_handle)) {
throw std::system_error{last_error(), std::system_category(), "CloseHandle failed"};
}
m_handle = nullptr;
}
}
inline void osmium::util::MemoryMapping::resize(std::size_t new_size) {
unmap();
m_size = new_size;
resize_fd(m_fd);
m_handle = create_file_mapping();
if (!m_handle) {
throw std::system_error{last_error(), std::system_category(), "CreateFileMapping failed"};
}
m_addr = map_view_of_file();
if (!is_valid()) {
throw std::system_error{last_error(), std::system_category(), "MapViewOfFile failed"};
}
}
#endif
#endif // OSMIUM_UTIL_MEMORY_MAPPING_HPP
osmium::AnonymousMemoryMapping
Definition memory_mapping.hpp:312
osmium::util::AnonymousMemoryMapping::AnonymousMemoryMapping
AnonymousMemoryMapping(std::size_t size)
Definition memory_mapping.hpp:316
osmium::AnonymousTypedMemoryMapping
Definition memory_mapping.hpp:508
osmium::util::AnonymousTypedMemoryMapping::AnonymousTypedMemoryMapping
AnonymousTypedMemoryMapping(std::size_t size)
Definition memory_mapping.hpp:512
osmium::MemoryMapping
Definition memory_mapping.hpp:95
osmium::util::MemoryMapping::resize_fd
int resize_fd(int fd) const
Definition memory_mapping.hpp:168
osmium::util::MemoryMapping::unmap
void unmap()
Definition memory_mapping.hpp:607
osmium::util::MemoryMapping::is_valid
bool is_valid() const noexcept
Definition memory_mapping.hpp:538
osmium::util::MemoryMapping::operator=
MemoryMapping & operator=(const MemoryMapping &)=delete
You can not copy a MemoryMapping.
osmium::util::MemoryMapping::mapping_mode
mapping_mode
Definition memory_mapping.hpp:99
osmium::util::MemoryMapping::mapping_mode::readonly
@ readonly
Definition memory_mapping.hpp:100
osmium::util::MemoryMapping::mapping_mode::write_shared
@ write_shared
Definition memory_mapping.hpp:102
osmium::util::MemoryMapping::mapping_mode::write_private
@ write_private
Definition memory_mapping.hpp:101
osmium::util::MemoryMapping::size
std::size_t size() const noexcept
Definition memory_mapping.hpp:270
osmium::util::MemoryMapping::MemoryMapping
MemoryMapping(const MemoryMapping &)=delete
You can not copy construct a MemoryMapping.
osmium::util::MemoryMapping::get_protection
flag_type get_protection() const noexcept
Definition memory_mapping.hpp:553
osmium::util::MemoryMapping::m_offset
off_t m_offset
Offset into the file.
Definition memory_mapping.hpp:111
osmium::util::MemoryMapping::~MemoryMapping
~MemoryMapping() noexcept
Definition memory_mapping.hpp:228
osmium::util::MemoryMapping::m_size
std::size_t m_size
The size of the mapping.
Definition memory_mapping.hpp:108
osmium::util::MemoryMapping::available_space
static std::size_t available_space(int fd)
Definition memory_mapping.hpp:155
osmium::util::MemoryMapping::m_fd
int m_fd
File handle we got the mapping from.
Definition memory_mapping.hpp:114
osmium::util::MemoryMapping::flag_type
int flag_type
Definition memory_mapping.hpp:133
osmium::util::MemoryMapping::resize
void resize(std::size_t new_size)
Definition memory_mapping.hpp:616
osmium::util::MemoryMapping::writable
bool writable() const noexcept
Definition memory_mapping.hpp:286
osmium::util::MemoryMapping::get_addr
T * get_addr() const noexcept
Definition memory_mapping.hpp:296
osmium::util::MemoryMapping::get_flags
flag_type get_flags() const noexcept
Definition memory_mapping.hpp:560
osmium::util::MemoryMapping::m_mapping_mode
mapping_mode m_mapping_mode
Mapping mode.
Definition memory_mapping.hpp:117
osmium::util::MemoryMapping::check_size
static std::size_t check_size(std::size_t size)
Definition memory_mapping.hpp:140
osmium::util::MemoryMapping::make_invalid
void make_invalid() noexcept
Definition memory_mapping.hpp:542
osmium::util::MemoryMapping::m_addr
void * m_addr
The address where the memory is mapped.
Definition memory_mapping.hpp:124
osmium::util::MemoryMapping::fd
int fd() const noexcept
Definition memory_mapping.hpp:279
osmium::util::MemoryMapping::MemoryMapping
MemoryMapping(std::size_t size, mapping_mode mode, int fd=-1, off_t offset=0)
Definition memory_mapping.hpp:570
osmium::TypedMemoryMapping
Definition memory_mapping.hpp:340
osmium::util::TypedMemoryMapping::~TypedMemoryMapping
~TypedMemoryMapping() noexcept=default
osmium::util::TypedMemoryMapping::fd
int fd() const noexcept
Definition memory_mapping.hpp:440
osmium::util::TypedMemoryMapping::resize
void resize(std::size_t new_size)
Definition memory_mapping.hpp:413
osmium::util::TypedMemoryMapping::operator=
TypedMemoryMapping & operator=(TypedMemoryMapping &&other) noexcept=default
osmium::util::TypedMemoryMapping::end
const T * end() const noexcept
Definition memory_mapping.hpp:501
osmium::util::TypedMemoryMapping::begin
const T * begin() const noexcept
Definition memory_mapping.hpp:492
osmium::util::TypedMemoryMapping::unmap
void unmap()
Definition memory_mapping.hpp:399
osmium::util::TypedMemoryMapping::TypedMemoryMapping
TypedMemoryMapping(std::size_t size)
Definition memory_mapping.hpp:352
osmium::util::TypedMemoryMapping::writable
bool writable() const noexcept
Definition memory_mapping.hpp:447
osmium::util::TypedMemoryMapping::TypedMemoryMapping
TypedMemoryMapping(const TypedMemoryMapping &)=delete
You can not copy construct a TypedMemoryMapping.
osmium::util::TypedMemoryMapping::cend
const T * cend() const noexcept
Definition memory_mapping.hpp:483
osmium::util::TypedMemoryMapping::size
std::size_t size() const noexcept
Definition memory_mapping.hpp:430
osmium::util::TypedMemoryMapping::TypedMemoryMapping
TypedMemoryMapping(std::size_t size, MemoryMapping::mapping_mode mode, int fd, off_t offset=0)
Definition memory_mapping.hpp:366
osmium::util::TypedMemoryMapping::m_mapping
MemoryMapping m_mapping
Definition memory_mapping.hpp:342
osmium::util::TypedMemoryMapping::cbegin
const T * cbegin() const noexcept
Definition memory_mapping.hpp:474
osmium::util::TypedMemoryMapping::TypedMemoryMapping
TypedMemoryMapping(TypedMemoryMapping &&other) noexcept=default
osmium::util::TypedMemoryMapping::end
T * end() noexcept
Definition memory_mapping.hpp:465
osmium::util::TypedMemoryMapping::operator=
TypedMemoryMapping & operator=(const TypedMemoryMapping &)=delete
You can not copy a TypedMemoryMapping.
osmium::util::TypedMemoryMapping::begin
T * begin() noexcept
Definition memory_mapping.hpp:456
MAP_ANONYMOUS
#define MAP_ANONYMOUS
Definition memory_mapping.hpp:550
osmium::util
Helpful utility classes and functions not strictly OSM related.
Definition crc.hpp:55
osmium::util::resize_file
void resize_file(int fd, std::size_t new_size)
Definition file.hpp:177
osmium::util::file_size
std::size_t file_size(int fd)
Definition file.hpp:109
osmium::util::get_pagesize
std::size_t get_pagesize() noexcept
Definition file.hpp:193
osmium
Namespace for everything in the Osmium library.
Definition assembler.hpp:53
std
Definition location.hpp:555
file.hpp