Everett
Loading...
Searching...
No Matches
mapped_file.h
Go to the documentation of this file.
1
13#pragma once
14
15#include <cstddef>
16#include <cstdint>
17#include <filesystem>
18#include <limits>
19#include <memory>
20#include <span>
21#include <stdexcept>
22#include <system_error>
23#include <utility>
24
25#if defined(_WIN32)
26#ifndef NOMINMAX
27#define NOMINMAX
28#define EVERETT_MAPPED_FILE_UNDEF_NOMINMAX
29#endif
30#include <windows.h>
31#ifdef EVERETT_MAPPED_FILE_UNDEF_NOMINMAX
32#undef EVERETT_MAPPED_FILE_UNDEF_NOMINMAX
33#undef NOMINMAX
34#endif
35#elif defined(__unix__) || defined(__APPLE__)
36#include <cerrno>
37#include <fcntl.h>
38#include <sys/mman.h>
39#include <sys/stat.h>
40#include <unistd.h>
41#else
42#error "Everett mapped_file currently supports Windows and POSIX platforms"
43#endif
44
45namespace everett {
46 namespace mapped_file_detail {
47 struct region {
48 void const * data = nullptr;
49 std::uint64_t length = 0;
50#if defined(_WIN32)
51 HANDLE file = INVALID_HANDLE_VALUE;
52 HANDLE mapping = nullptr;
53#else
54 int file = -1;
55#endif
56 region() = default;
57 region(region const &) = delete;
58 region & operator=(region const &) = delete;
59 ~region() noexcept {
60#if defined(_WIN32)
61 if (data) ::UnmapViewOfFile(data);
62 if (mapping) ::CloseHandle(mapping);
63 if (file != INVALID_HANDLE_VALUE) ::CloseHandle(file);
64#else
65 if (data) ::munmap(const_cast<void *>(data), static_cast<std::size_t>(length));
66 if (file != -1) ::close(file);
67#endif
68 }
69 };
70
71 inline void check_slice(std::uint64_t size, std::uint64_t offset, std::uint64_t length) {
72 if (offset > size || length > size - offset)
73 throw std::out_of_range("mapped slice exceeds file extent");
74 }
75 }
76
77 struct mapped_file;
78
79 // Owns a mapping pin as well as a bounded range. Keep this object alive for
80 // every span obtained from bytes(); the span alone does not own a pin.
81 struct mapped_slice {
82 mapped_slice() = default;
83 mapped_slice(mapped_slice const &) = default;
84 mapped_slice & operator=(mapped_slice const &) = default;
85 mapped_slice(mapped_slice && other) noexcept
86 : owner_(std::move(other.owner_)), offset_(std::exchange(other.offset_, 0)),
87 length_(std::exchange(other.length_, 0)) {}
88 mapped_slice & operator=(mapped_slice && other) noexcept {
89 if (this != &other) {
90 owner_ = std::move(other.owner_);
91 offset_ = std::exchange(other.offset_, 0);
92 length_ = std::exchange(other.length_, 0);
93 }
94 return *this;
95 }
96 std::uint64_t size() const noexcept { return length_; }
97 bool empty() const noexcept { return length_ == 0; }
98
99 std::span<std::byte const> bytes() const & noexcept {
100 auto data = owner_ ? static_cast<std::byte const *>(owner_->data) : nullptr;
101 return {data ? data + static_cast<std::size_t>(offset_) : nullptr,
102 static_cast<std::size_t>(length_)};
103 }
104 std::span<std::byte const> bytes() const && = delete;
105
106 mapped_slice slice(std::uint64_t offset, std::uint64_t length) const {
108 // Both offsets were already bounded by the mapping, so this addition
109 // cannot exceed the checked whole-file length or wrap.
110 return {owner_, offset_ + offset, length};
111 }
112
113 private:
114 friend struct mapped_file;
115 mapped_slice(std::shared_ptr<mapped_file_detail::region const> owner,
116 std::uint64_t offset, std::uint64_t length)
117 : owner_(std::move(owner)), offset_(offset), length_(length) {}
118 std::shared_ptr<mapped_file_detail::region const> owner_;
119 std::uint64_t offset_ = 0;
120 std::uint64_t length_ = 0;
121 };
122
123 // Read-only whole-file mapping, with native POSIX and Windows branches.
124 // The caller must keep the backing object immutable while any pin lives:
125 // external modification/truncation is not made safe by MAP_PRIVATE or by a
126 // shared_ptr. File type and size are checked on the opened handle, not via
127 // a separate path stat. This read backend makes no crash-durability claim.
128 struct mapped_file {
129 mapped_file() = default;
130
131 static mapped_file open(std::filesystem::path const & path) {
132 auto state = std::make_shared<mapped_file_detail::region>();
133#if defined(_WIN32)
134 state->file = ::CreateFileW(path.c_str(), GENERIC_READ,
135 FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr,
136 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);
137 if (state->file == INVALID_HANDLE_VALUE)
138 throw std::system_error(static_cast<int>(::GetLastError()), std::system_category(), "open mapped file");
139 BY_HANDLE_FILE_INFORMATION info{};
140 if (!::GetFileInformationByHandle(state->file, &info))
141 throw std::system_error(static_cast<int>(::GetLastError()), std::system_category(), "inspect mapped file");
142 if (::GetFileType(state->file) != FILE_TYPE_DISK || (info.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
143 throw std::invalid_argument("mapped file must be a regular file");
144 LARGE_INTEGER size{};
145 if (!::GetFileSizeEx(state->file, &size))
146 throw std::system_error(static_cast<int>(::GetLastError()), std::system_category(), "size mapped file");
147 if (size.QuadPart < 0) throw std::length_error("negative mapped file size");
148 state->length = static_cast<std::uint64_t>(size.QuadPart);
149#else
150 // Nonblocking open permits rejecting FIFOs rather than waiting for a
151 // writer before fstat can establish that the object is not regular.
152 int flags = O_RDONLY | O_NONBLOCK;
153#ifdef O_CLOEXEC
154 flags |= O_CLOEXEC;
155#endif
156 state->file = ::open(path.c_str(), flags);
157 if (state->file == -1) throw std::system_error(errno, std::generic_category(), "open mapped file");
158#ifndef O_CLOEXEC
159 if (::fcntl(state->file, F_SETFD, FD_CLOEXEC) == -1)
160 throw std::system_error(errno, std::generic_category(), "protect mapped descriptor");
161#endif
162 struct stat info{};
163 if (::fstat(state->file, &info) == -1)
164 throw std::system_error(errno, std::generic_category(), "inspect mapped file");
165 if (!S_ISREG(info.st_mode)) throw std::invalid_argument("mapped file must be a regular file");
166 if (info.st_size < 0) throw std::length_error("negative mapped file size");
167 state->length = static_cast<std::uint64_t>(info.st_size);
168#endif
169 if (state->length > (std::numeric_limits<std::size_t>::max)())
170 throw std::length_error("mapped file exceeds addressable span size");
171 if (state->length) {
172#if defined(_WIN32)
173 state->mapping = ::CreateFileMappingW(state->file, nullptr, PAGE_READONLY, 0, 0, nullptr);
174 if (!state->mapping)
175 throw std::system_error(static_cast<int>(::GetLastError()), std::system_category(), "create file mapping");
176 state->data = ::MapViewOfFile(state->mapping, FILE_MAP_READ, 0, 0, static_cast<SIZE_T>(state->length));
177 if (!state->data)
178 throw std::system_error(static_cast<int>(::GetLastError()), std::system_category(), "map file view");
179#else
180 auto address = ::mmap(nullptr, static_cast<std::size_t>(state->length), PROT_READ, MAP_PRIVATE, state->file, 0);
181 if (address == MAP_FAILED) throw std::system_error(errno, std::generic_category(), "map file view");
182 state->data = address;
183#endif
184 }
185 return mapped_file(std::move(state));
186 }
187
188 std::uint64_t size() const noexcept { return owner_ ? owner_->length : 0; }
189 mapped_slice slice(std::uint64_t offset, std::uint64_t length) const {
190 mapped_file_detail::check_slice(size(), offset, length);
191 return {owner_, offset, length};
192 }
193
194 private:
195 explicit mapped_file(std::shared_ptr<mapped_file_detail::region const> owner) : owner_(std::move(owner)) {}
196 std::shared_ptr<mapped_file_detail::region const> owner_;
197 };
198}
void check_slice(std::uint64_t size, std::uint64_t offset, std::uint64_t length)
Definition mapped_file.h:71
Definition active_engine.h:18
Definition file.h:242
Definition mapped_file.h:47
void const * data
Definition mapped_file.h:48
region(region const &)=delete
std::uint64_t length
Definition mapped_file.h:49
~region() noexcept
Definition mapped_file.h:59
region & operator=(region const &)=delete
Definition mapped_file.h:128
mapped_file(std::shared_ptr< mapped_file_detail::region const > owner)
Definition mapped_file.h:195
mapped_slice slice(std::uint64_t offset, std::uint64_t length) const
Definition mapped_file.h:189
std::shared_ptr< mapped_file_detail::region const > owner_
Definition mapped_file.h:196
std::uint64_t size() const noexcept
Definition mapped_file.h:188
static mapped_file open(std::filesystem::path const &path)
Definition mapped_file.h:131
Definition mapped_file.h:81
std::uint64_t size() const noexcept
Definition mapped_file.h:96
std::span< std::byte const > bytes() const &noexcept
Definition mapped_file.h:99
std::span< std::byte const > bytes() const &&=delete
std::uint64_t length_
Definition mapped_file.h:120
mapped_slice(mapped_slice &&other) noexcept
Definition mapped_file.h:85
std::uint64_t offset_
Definition mapped_file.h:119
mapped_slice(std::shared_ptr< mapped_file_detail::region const > owner, std::uint64_t offset, std::uint64_t length)
Definition mapped_file.h:115
std::shared_ptr< mapped_file_detail::region const > owner_
Definition mapped_file.h:118
mapped_slice(mapped_slice const &)=default
mapped_slice & operator=(mapped_slice const &)=default
bool empty() const noexcept
Definition mapped_file.h:97
mapped_slice & operator=(mapped_slice &&other) noexcept
Definition mapped_file.h:88
mapped_slice slice(std::uint64_t offset, std::uint64_t length) const
Definition mapped_file.h:106