1 //
2 // memory_buffer.cpp
3 //
4 // Copyright © 2024 Apple Inc. All rights reserved.
5 //
6 // Please refer to the license found in the LICENSE file in the root directory of the source tree.
7
8 #include "memory_buffer.hpp"
9
10 #include <assert.h>
11 #include <cstring>
12 #include <functional>
13 #include <iostream>
14 #include <mutex>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <sys/mman.h>
18 #include <sys/stat.h>
19 #include <unistd.h>
20
21 namespace {
22 using namespace inmemoryfs;
23
24 using MMAP_HANDLE = std::unique_ptr<void, std::function<void(void*)>>;
25
memory_map_file_range(FILE * file,Range range)26 MMAP_HANDLE memory_map_file_range(FILE* file, Range range) {
27 auto ptr = mmap(nullptr, range.size, PROT_READ, MAP_PRIVATE, fileno(file), range.offset);
28 return MMAP_HANDLE(ptr, [size = range.size](void* bufferPtr) mutable { munmap(bufferPtr, size); });
29 }
30
alloc_using_mmap(size_t size)31 MMAP_HANDLE alloc_using_mmap(size_t size) {
32 auto ptr = mmap(0, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
33 return MMAP_HANDLE(ptr, [size](void* bufferPtr) mutable { munmap(bufferPtr, size); });
34 }
35
memory_map_file_range_at_address(FILE * file,Range range,void * dst)36 void* memory_map_file_range_at_address(FILE* file, Range range, void* dst) {
37 return mmap(dst, range.size, PROT_READ, MAP_PRIVATE | MAP_FIXED, fileno(file), range.offset);
38 }
39
40 class MMappedBuffer : public MemoryBuffer {
41 public:
MMappedBuffer(std::shared_ptr<void> buffer,size_t size)42 explicit MMappedBuffer(std::shared_ptr<void> buffer, size_t size) noexcept
43 : MemoryBuffer(reinterpret_cast<uint8_t*>(buffer.get()), size, MemoryBuffer::Kind::MMap),
44 buffer_(std::move(buffer)) { }
45
46 MMappedBuffer(const MemoryBuffer&) = delete;
47 MMappedBuffer& operator=(const MemoryBuffer&) = delete;
48
~MMappedBuffer()49 ~MMappedBuffer() { }
50
51 private:
52 const std::shared_ptr<void> buffer_;
53 };
54
55 class LazyMMappedBuffer : public MemoryBuffer {
56 public:
LazyMMappedBuffer(std::shared_ptr<FILE> file,Range range)57 explicit LazyMMappedBuffer(std::shared_ptr<FILE> file, Range range) noexcept
58 : MemoryBuffer(nullptr, range.size, MemoryBuffer::Kind::MMap), file_(std::move(file)), range_(range),
59 page_size_(getpagesize()) { }
60
61 LazyMMappedBuffer(const LazyMMappedBuffer&) = delete;
62 LazyMMappedBuffer& operator=(const LazyMMappedBuffer&) = delete;
63
load(std::error_code & error)64 bool load(std::error_code& error) noexcept override {
65 std::lock_guard<std::mutex> guard(mutex_);
66 if (memory_mapped_data_ != nullptr) {
67 return true;
68 }
69 auto ptr = memory_map_file_range(file_.get(), range_);
70 if (!ptr || (reinterpret_cast<int*>(ptr.get()) == MAP_FAILED)) {
71 error = std::error_code(errno, std::system_category());
72 return false;
73 }
74
75 memory_mapped_data_ = std::move(ptr);
76 return true;
77 }
78
data()79 void* data() noexcept override {
80 std::error_code ec;
81 assert(load(ec) == true);
82 return memory_mapped_data_.get();
83 }
84
get_offset_range(size_t proposed_offset) const85 std::pair<size_t, size_t> get_offset_range(size_t proposed_offset) const noexcept override {
86 return { proposed_offset, proposed_offset + page_size_ - 1 };
87 }
88
get_revised_range_for_writing(void * dst,Range proposed_range) const89 Range get_revised_range_for_writing(void* dst, Range proposed_range) const noexcept override {
90 uint8_t* ptr = static_cast<uint8_t*>(dst) + proposed_range.offset;
91 size_t alignment = page_size_;
92 uintptr_t addr = (uintptr_t)ptr;
93 if (addr % alignment == 0) {
94 return proposed_range;
95 }
96
97 uintptr_t mask = alignment - 1;
98 uintptr_t aligned_addr = (addr + mask) & ~mask;
99 assert(aligned_addr >= addr);
100 return Range(proposed_range.offset + (aligned_addr - addr), proposed_range.size);
101 }
102
write(void * dst,size_t offset,std::error_code & error)103 bool write(void* dst, size_t offset, std::error_code& error) noexcept override {
104 uint8_t* ptr = static_cast<uint8_t*>(dst) + offset;
105 size_t alignment = page_size_;
106 uintptr_t addr = (uintptr_t)ptr;
107 if (addr % alignment != 0) {
108 error = std::error_code(EFAULT, std::system_category());
109 return false;
110 }
111
112 auto mmapped_ptr = memory_map_file_range_at_address(file_.get(), range_, ptr);
113 if (!mmapped_ptr || (reinterpret_cast<int*>(mmapped_ptr) == MAP_FAILED)) {
114 error = std::error_code(errno, std::system_category());
115 return false;
116 }
117
118 return true;
119 }
120
slice(Range range)121 std::shared_ptr<MemoryBuffer> slice(Range range) noexcept override {
122 if (range.length() > size()) {
123 return nullptr;
124 }
125
126 return std::make_shared<LazyMMappedBuffer>(file_, range);
127 }
128
~LazyMMappedBuffer()129 ~LazyMMappedBuffer() { }
130
131 private:
132 std::shared_ptr<FILE> file_;
133 Range range_;
134 size_t page_size_;
135 std::unique_ptr<void, std::function<void(void*)>> memory_mapped_data_;
136 std::mutex mutex_;
137 };
138
139 class MallocedBuffer : public MemoryBuffer {
140 public:
141 enum class Ownership : uint8_t { Unowned, Owned };
142
MallocedBuffer(void * data,size_t size,Ownership ownership)143 explicit MallocedBuffer(void* data, size_t size, Ownership ownership) noexcept
144 : MemoryBuffer(data, size, MemoryBuffer::Kind::Malloc), ownership_(ownership) { }
145
MallocedBuffer(std::vector<uint8_t> buffer)146 explicit MallocedBuffer(std::vector<uint8_t> buffer) noexcept
147 : MemoryBuffer(buffer.data(), buffer.size(), MemoryBuffer::Kind::Malloc), buffer_(std::move(buffer)),
148 ownership_(Ownership::Owned) { }
149
150 MallocedBuffer(const MallocedBuffer&) = delete;
151 MallocedBuffer& operator=(const MallocedBuffer&) = delete;
152
~MallocedBuffer()153 ~MallocedBuffer() {
154 if (buffer_.size() > 0) {
155 return;
156 }
157
158 if (!data()) {
159 return;
160 }
161
162 switch (ownership_) {
163 case Ownership::Owned:
164 free(data());
165 break;
166
167 default:
168 break;
169 }
170 }
171
172 private:
173 std::vector<uint8_t> buffer_;
174 Ownership ownership_;
175 };
176
get_file_length(const std::string & file_path,std::error_code & error)177 size_t get_file_length(const std::string& file_path, std::error_code& error) {
178 struct stat fileInfo;
179 if (stat(file_path.c_str(), &fileInfo) != 0) {
180 error = std::error_code(errno, std::generic_category());
181 return 0;
182 }
183
184 return static_cast<size_t>(fileInfo.st_size);
185 }
186
open_file(const std::string & file_path,std::error_code & error)187 std::unique_ptr<FILE, decltype(&fclose)> open_file(const std::string& file_path, std::error_code& error) {
188 std::unique_ptr<FILE, decltype(&fclose)> file(fopen(file_path.c_str(), "rb"), fclose);
189 if (!file) {
190 error = std::error_code(errno, std::system_category());
191 }
192 return file;
193 }
194
mmap_buffer_from_file(FILE * file,Range range,std::error_code & error)195 std::unique_ptr<MemoryBuffer> mmap_buffer_from_file(FILE* file, Range range, std::error_code& error) {
196 auto ptr = memory_map_file_range(file, range);
197 if (!ptr || (reinterpret_cast<int*>(ptr.get()) == MAP_FAILED)) {
198 error = std::error_code(errno, std::generic_category());
199 return {};
200 }
201
202 return std::make_unique<MMappedBuffer>(std::move(ptr), range.size);
203 }
204
205 std::unique_ptr<MemoryBuffer>
mmap_buffer_lazy_from_file(std::unique_ptr<FILE,decltype(& fclose) > file,Range range,std::error_code & error)206 mmap_buffer_lazy_from_file(std::unique_ptr<FILE, decltype(&fclose)> file, Range range, std::error_code& error) {
207 return std::make_unique<LazyMMappedBuffer>(std::move(file), range);
208 }
209
malloced_buffer_from_file(FILE * file,Range range,std::error_code & error)210 std::unique_ptr<MemoryBuffer> malloced_buffer_from_file(FILE* file, Range range, std::error_code& error) {
211 size_t offset = range.offset;
212 if (std::fseek(file, offset, SEEK_SET) != 0) {
213 error = std::error_code(errno, std::generic_category());
214 return nullptr;
215 }
216
217 std::vector<uint8_t> buffer(range.size, 0);
218 if (std::fread(buffer.data(), 1, buffer.size(), file) != range.size) {
219 error = std::error_code(errno, std::generic_category());
220 return nullptr;
221 }
222
223 return std::make_unique<MallocedBuffer>(std::move(buffer));
224 }
225
read_file_content(std::unique_ptr<FILE,decltype(& fclose) > file,Range range,MMappedBuffer::ReadOption option,std::error_code & error)226 std::unique_ptr<MemoryBuffer> read_file_content(std::unique_ptr<FILE, decltype(&fclose)> file,
227 Range range,
228 MMappedBuffer::ReadOption option,
229 std::error_code& error) {
230 switch (option) {
231 case MemoryBuffer::ReadOption::MMap: {
232 return ::mmap_buffer_from_file(file.get(), range, error);
233 }
234
235 case MemoryBuffer::ReadOption::LazyMMap: {
236 return ::mmap_buffer_lazy_from_file(std::move(file), range, error);
237 }
238
239 case MemoryBuffer::ReadOption::Malloc: {
240 return ::malloced_buffer_from_file(file.get(), range, error);
241 }
242 }
243 }
244
245 } // namespace
246
247 namespace inmemoryfs {
write(void * dst,size_t offset,std::error_code & error)248 bool MemoryBuffer::write(void* dst, size_t offset, std::error_code& error) noexcept {
249 auto ptr = static_cast<uint8_t*>(dst);
250 std::memcpy(ptr + offset, data(), size());
251 return true;
252 }
253
slice(Range range)254 std::shared_ptr<MemoryBuffer> MemoryBuffer::slice(Range range) noexcept {
255 if (range.length() > size()) {
256 return nullptr;
257 }
258
259 auto start = static_cast<void*>(static_cast<uint8_t*>(data()) + range.offset);
260 return std::make_shared<MemoryBuffer>(start, range.size, kind(), shared_from_this());
261 }
262
read_file_content(const std::string & file_path,const std::vector<Range> & ranges,ReadOption option,std::error_code & error)263 std::vector<std::shared_ptr<MemoryBuffer>> MemoryBuffer::read_file_content(const std::string& file_path,
264 const std::vector<Range>& ranges,
265 ReadOption option,
266 std::error_code& error) {
267 auto file = open_file(file_path, error);
268 if (!file) {
269 return {};
270 }
271
272 std::vector<std::shared_ptr<MemoryBuffer>> result;
273 result.reserve(ranges.size());
274 for (const auto& range: ranges) {
275 auto buffer = ::read_file_content(std::move(file), range, option, error);
276 if (!buffer) {
277 return {};
278 }
279
280 result.emplace_back(std::move(buffer));
281 }
282
283 return result;
284 }
285
286 std::shared_ptr<MemoryBuffer>
read_file_content(const std::string & file_path,ReadOption option,std::error_code & error)287 MemoryBuffer::read_file_content(const std::string& file_path, ReadOption option, std::error_code& error) {
288 const size_t length = ::get_file_length(file_path, error);
289 if (length == 0) {
290 return {};
291 }
292
293 auto file = open_file(file_path, error);
294 if (!file) {
295 return {};
296 }
297
298 auto buffer = ::read_file_content(std::move(file), Range(0, length), option, error);
299 return buffer;
300 }
301
make_using_malloc(size_t size,size_t alignment)302 std::unique_ptr<MemoryBuffer> MemoryBuffer::make_using_malloc(size_t size, size_t alignment) {
303 void* data = nullptr;
304 if (alignment > 1) {
305 assert(size % alignment == 0);
306 data = aligned_alloc(alignment, size);
307 } else {
308 data = malloc(size);
309 }
310
311 return std::make_unique<MallocedBuffer>(data, size, MallocedBuffer::Ownership::Owned);
312 }
313
make_using_mmap(size_t size)314 std::unique_ptr<MemoryBuffer> MemoryBuffer::make_using_mmap(size_t size) {
315 auto ptr = alloc_using_mmap(size);
316 if (!ptr || (reinterpret_cast<int*>(ptr.get()) == MAP_FAILED)) {
317 return nullptr;
318 }
319
320 return std::make_unique<MMappedBuffer>(std::move(ptr), size);
321 }
322
make_unowned(void * data,size_t size)323 std::unique_ptr<MemoryBuffer> MemoryBuffer::make_unowned(void* data, size_t size) {
324 return std::make_unique<MallocedBuffer>(data, size, MallocedBuffer::Ownership::Unowned);
325 }
326
make_copy(void * data,size_t size)327 std::unique_ptr<MemoryBuffer> MemoryBuffer::make_copy(void* data, size_t size) {
328 std::vector<uint8_t> buffer;
329 buffer.resize(size);
330 std::memcpy(buffer.data(), data, size);
331 return std::make_unique<MallocedBuffer>(std::move(buffer));
332 }
333
334 } // namespace inmemoryfs
335