• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

realm / realm-core / 1743

06 Oct 2023 04:08PM UTC coverage: 91.621% (+0.01%) from 91.61%
1743

push

Evergreen

realm-ci
Updated release notes

94318 of 173524 branches covered (0.0%)

230654 of 251749 relevant lines covered (91.62%)

6530623.67 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

75.49
/src/realm/util/file_mapper.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <realm/util/features.h>
20

21
#include <realm/util/file_mapper.hpp>
22

23
#ifdef _WIN32
24
#include <windows.h>
25
#else
26
#include <cerrno>
27
#include <sys/mman.h>
28
#include <unistd.h>
29
#endif
30

31
#include <realm/exceptions.hpp>
32
#include <realm/impl/simulated_failure.hpp>
33
#include <realm/util/errno.hpp>
34
#include <realm/util/to_string.hpp>
35
#include <system_error>
36

37
#if REALM_ENABLE_ENCRYPTION
38

39
#include <realm/util/encrypted_file_mapping.hpp>
40
#include <realm/util/aes_cryptor.hpp>
41

42
#include <atomic>
43
#include <memory>
44
#include <csignal>
45
#include <sys/stat.h>
46
#include <cstring>
47
#include <atomic>
48
#include <fstream>
49
#include <sstream>
50
#include <regex>
51
#include <thread>
52

53
#include <realm/util/file.hpp>
54
#include <realm/util/errno.hpp>
55
#include <realm/util/terminate.hpp>
56
#include <realm/util/thread.hpp>
57
#include <cstring> // for memset
58

59
#if REALM_PLATFORM_APPLE
60
#include <dispatch/dispatch.h>
61
#endif
62

63
#if REALM_ANDROID
64
#include <linux/unistd.h>
65
#include <sys/syscall.h>
66
#endif
67

68
#endif // enable encryption
69

70
namespace {
71

72
inline bool is_mmap_memory_error(int err)
73
{
×
74
    return (err == EAGAIN || err == EMFILE || err == ENOMEM);
×
75
}
×
76

77
} // Unnamed namespace
78

79
using namespace realm;
80
using namespace realm::util;
81

82
namespace realm {
83
namespace util {
84

85
size_t round_up_to_page_size(size_t size) noexcept
86
{
4,069,047✔
87
    return (size + page_size() - 1) & ~(page_size() - 1);
4,069,047✔
88
}
4,069,047✔
89

90

91
#if REALM_ENABLE_ENCRYPTION
92

93
// A list of all of the active encrypted mappings for a single file
94
struct mappings_for_file {
95
#ifdef _WIN32
96
    HANDLE handle;
97
#else
98
    dev_t device;
99
    ino_t inode;
100
#endif
101
    std::shared_ptr<SharedFileInfo> info;
102
};
103

104
// Group the information we need to map a SIGSEGV address to an
105
// EncryptedFileMapping for the sake of cache-friendliness with 3+ active
106
// mappings (and no worse with only two)
107
struct mapping_and_addr {
108
    std::shared_ptr<EncryptedFileMapping> mapping;
109
    void* addr;
110
    size_t size;
111
};
112

113
util::Mutex& mapping_mutex = *(new util::Mutex);
114
namespace {
115
std::vector<mapping_and_addr>& mappings_by_addr = *new std::vector<mapping_and_addr>;
116
std::vector<mappings_for_file>& mappings_by_file = *new std::vector<mappings_for_file>;
117
static unsigned int file_reclaim_index = 0;
118
static std::atomic<size_t> num_decrypted_pages(0); // this is for statistical purposes
119
static std::atomic<size_t> reclaimer_target(0);    // do.
120
static std::atomic<size_t> reclaimer_workload(0);  // do.
121
// helpers
122

123
int64_t fetch_value_in_file(const std::string& fname, const char* scan_pattern)
124
{
2,052✔
125
    std::ifstream file(fname);
2,052✔
126
    if (file) {
2,052✔
127
        std::stringstream buffer;
882✔
128
        buffer << file.rdbuf();
882✔
129

882✔
130
        std::string s = buffer.str();
882✔
131
        std::smatch m;
882✔
132
        std::regex e(scan_pattern);
882✔
133

882✔
134
        if (std::regex_search(s, m, e)) {
882!
135
            std::string ibuf = m[1];
882✔
136
            return strtol(ibuf.c_str(), nullptr, 10);
882✔
137
        }
882✔
138
    }
1,170✔
139
    return PageReclaimGovernor::no_match;
1,170✔
140
}
1,170✔
141

142

143
/* Default reclaim governor
144
 *
145
 */
146

147
class DefaultGovernor : public PageReclaimGovernor {
148
public:
149
    static int64_t pick_lowest_valid(int64_t a, int64_t b)
150
    {
1,026✔
151
        if (a == PageReclaimGovernor::no_match)
1,026✔
152
            return b;
×
153
        if (b == PageReclaimGovernor::no_match)
1,026✔
154
            return a;
438✔
155
        return std::min(a, b);
588✔
156
    }
588✔
157

158
    static int64_t pick_if_valid(int64_t source, int64_t target)
159
    {
1,539✔
160
        if (source == PageReclaimGovernor::no_match)
1,539✔
161
            return PageReclaimGovernor::no_match;
438✔
162
        return target;
1,101✔
163
    }
1,101✔
164

165
    static int64_t get_target_from_system(const std::string& cfg_file_name)
166
    {
513✔
167
        int64_t target;
513✔
168
        auto local_spec = fetch_value_in_file(cfg_file_name, "target ([[:digit:]]+)");
513✔
169
        if (local_spec != no_match) { // overrides everything!
513✔
170
            target = local_spec;
×
171
        }
×
172
        else {
513✔
173
            // no local spec, try to deduce something reasonable from platform info
294✔
174
            auto from_proc = fetch_value_in_file("/proc/meminfo", "MemTotal:[[:space:]]+([[:digit:]]+) kB") * 1024;
513✔
175
            auto from_cgroup = fetch_value_in_file("/sys/fs/cgroup/memory/memory.limit_in_bytes", "^([[:digit:]]+)");
513✔
176
            auto cache_use = fetch_value_in_file("/sys/fs/cgroup/memory/memory.stat", "cache ([[:digit:]]+)");
513✔
177
            target = pick_if_valid(from_proc, from_proc / 4);
513✔
178
            target = pick_lowest_valid(target, pick_if_valid(from_cgroup, from_cgroup / 4));
513✔
179
            target = pick_lowest_valid(target, pick_if_valid(cache_use, cache_use));
513✔
180
        }
513✔
181
        return target;
513✔
182
    }
513✔
183

184
    util::UniqueFunction<int64_t()> current_target_getter(size_t load) override
185
    {
5,499✔
186
        static_cast<void>(load);
5,499✔
187
        if (m_refresh_count > 0) {
5,499✔
188
            --m_refresh_count;
4,986✔
189
            return [target = m_target] {
4,986✔
190
                return target;
4,986✔
191
            };
4,986✔
192
        }
4,986✔
193
        m_refresh_count = 10;
513✔
194

294✔
195
        return [file_name = m_cfg_file_name] {
513✔
196
            return get_target_from_system(file_name);
513✔
197
        };
513✔
198
    }
513✔
199

200
    void report_target_result(int64_t target) override
201
    {
5,499✔
202
        m_target = target;
5,499✔
203
    }
5,499✔
204

205
    DefaultGovernor()
206
    {
24✔
207
        auto cfg_name = getenv("REALM_PAGE_GOVERNOR_CFG");
24✔
208
        if (cfg_name) {
24✔
209
            m_cfg_file_name = cfg_name;
×
210
        }
×
211
    }
24✔
212

213
private:
214
    std::string m_cfg_file_name;
215
    int64_t m_target = 0;
216
    int m_refresh_count = 0;
217
};
218

219
static DefaultGovernor default_governor;
220
static PageReclaimGovernor* governor = &default_governor;
221

222
void reclaim_pages();
223

224
#if !REALM_PLATFORM_APPLE
225
static std::atomic<bool> reclaimer_shutdown(false);
226
static std::unique_ptr<std::thread> reclaimer_thread;
227

228
static void ensure_reclaimer_thread_runs()
229
{
597✔
230
    if (reclaimer_thread == nullptr) {
597✔
231
        reclaimer_thread = std::make_unique<std::thread>([] {
12✔
232
            while (!reclaimer_shutdown) {
3,165✔
233
                reclaim_pages();
3,153✔
234
                millisleep(1000);
3,153✔
235
            }
3,153✔
236
        });
12✔
237
    }
12✔
238
}
597✔
239

240
struct ReclaimerThreadStopper {
241
    ~ReclaimerThreadStopper()
242
    {
243
        if (reclaimer_thread) {
244
            reclaimer_shutdown = true;
245
            reclaimer_thread->join();
246
        }
247
    }
248
} reclaimer_thread_stopper;
249
#else // REALM_PLATFORM_APPLE
250
static dispatch_source_t reclaimer_timer;
251
static dispatch_queue_t reclaimer_queue;
252

253
static void ensure_reclaimer_thread_runs()
254
{
594✔
255
    if (!reclaimer_timer) {
594✔
256
        if (__builtin_available(iOS 10, macOS 12, tvOS 10, watchOS 3, *)) {
12✔
257
            reclaimer_queue = dispatch_queue_create_with_target("io.realm.page-reclaimer", DISPATCH_QUEUE_SERIAL,
258
                                                                dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
259
        }
260
        else {
12✔
261
            reclaimer_queue = dispatch_queue_create("io.realm.page-reclaimer", DISPATCH_QUEUE_SERIAL);
12✔
262
        }
12✔
263
        reclaimer_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, reclaimer_queue);
12✔
264
        dispatch_source_set_timer(reclaimer_timer, DISPATCH_TIME_NOW, NSEC_PER_SEC, NSEC_PER_SEC);
12✔
265
        dispatch_source_set_event_handler(reclaimer_timer, ^{
2,346✔
266
            reclaim_pages();
2,346✔
267
        });
2,346✔
268
        dispatch_resume(reclaimer_timer);
12✔
269
    }
12✔
270
}
594✔
271

272
struct ReclaimerThreadStopper {
273
    ~ReclaimerThreadStopper()
274
    {
12✔
275
        if (reclaimer_timer) {
12✔
276
            dispatch_source_cancel(reclaimer_timer);
12✔
277
            // Block until any currently-running timer tasks are done
278
            dispatch_sync(reclaimer_queue, ^{
12✔
279
                          });
12✔
280
            dispatch_release(reclaimer_timer);
12✔
281
            dispatch_release(reclaimer_queue);
12✔
282
        }
12✔
283
    }
12✔
284
} reclaimer_thread_stopper;
285
#endif
286
} // anonymous namespace
287

288
void set_page_reclaim_governor(PageReclaimGovernor* new_governor)
289
{
×
290
    UniqueLock lock(mapping_mutex);
×
291
    governor = new_governor ? new_governor : &default_governor;
×
292
    ensure_reclaimer_thread_runs();
×
293
}
×
294

295
size_t get_num_decrypted_pages()
296
{
×
297
    return num_decrypted_pages.load();
×
298
}
×
299

300
void encryption_note_reader_start(SharedFileInfo& info, const void* reader_id)
301
{
1,191✔
302
    UniqueLock lock(mapping_mutex);
1,191✔
303
    ensure_reclaimer_thread_runs();
1,191✔
304
    auto j = std::find_if(info.readers.begin(), info.readers.end(), [=](auto& reader) {
816✔
305
        return reader.reader_ID == reader_id;
441✔
306
    });
441✔
307
    if (j == info.readers.end()) {
1,191✔
308
        ReaderInfo i = {reader_id, info.current_version};
1,098✔
309
        info.readers.push_back(i);
1,098✔
310
    }
1,098✔
311
    else {
93✔
312
        j->version = info.current_version;
93✔
313
    }
93✔
314
    ++info.current_version;
1,191✔
315
}
1,191✔
316

317
void encryption_note_reader_end(SharedFileInfo& info, const void* reader_id) noexcept
318
{
1,443✔
319
    UniqueLock lock(mapping_mutex);
1,443✔
320
    for (auto j = info.readers.begin(); j != info.readers.end(); ++j)
1,755✔
321
        if (j->reader_ID == reader_id) {
1,404✔
322
            // move last over
546✔
323
            *j = info.readers.back();
1,092✔
324
            info.readers.pop_back();
1,092✔
325
            return;
1,092✔
326
        }
1,092✔
327
}
1,443✔
328

329
void encryption_mark_pages_for_IV_check(EncryptedFileMapping* mapping)
330
{
1,455✔
331
    UniqueLock lock(mapping_mutex);
1,455✔
332
    mapping->mark_pages_for_IV_check();
1,455✔
333
}
1,455✔
334

335
namespace {
336
size_t collect_total_workload() // must be called under lock
337
{
5,499✔
338
    size_t total = 0;
5,499✔
339
    for (auto i = mappings_by_file.begin(); i != mappings_by_file.end(); ++i) {
5,550✔
340
        SharedFileInfo& info = *i->info;
51✔
341
        info.num_decrypted_pages = 0;
51✔
342
        for (auto it = info.mappings.begin(); it != info.mappings.end(); ++it) {
105✔
343
            info.num_decrypted_pages += (*it)->collect_decryption_count();
54✔
344
        }
54✔
345
        total += info.num_decrypted_pages;
51✔
346
    }
51✔
347
    return total;
5,499✔
348
}
5,499✔
349

350
/* Compute the amount of work allowed in an attempt to reclaim pages.
351
 * please refer to EncryptedFileMapping::reclaim_untouched() for more details.
352
 *
353
 * The function starts slowly when the load is 0.5 of target, then turns
354
 * up the volume as the load nears 1.0 - where it sets a work limit of 10%.
355
 * Since the work is expressed (roughly) in terms of pages released, this means
356
 * that about 10 runs has to take place to reclaim all pages possible - though
357
 * if successful the load will rapidly decrease, turning down the work limit.
358
 */
359

360
struct work_limit_desc {
361
    float base;
362
    float effort;
363
};
364
const std::vector<work_limit_desc> control_table = {{0.5f, 0.001f},  {0.75f, 0.002f}, {0.8f, 0.003f},
365
                                                    {0.85f, 0.005f}, {0.9f, 0.01f},   {0.95f, 0.03f},
366
                                                    {1.0f, 0.1f},    {1.5f, 0.2f},    {2.0f, 0.3f}};
367

368
size_t get_work_limit(size_t decrypted_pages, size_t target)
369
{
51✔
370
    if (target == 0)
51✔
371
        target = 1;
×
372
    float load = 1.0f * decrypted_pages / target;
51✔
373
    float akku = 0.0f;
51✔
374
    for (const auto& e : control_table) {
60✔
375
        if (load <= e.base)
60✔
376
            break;
51✔
377
        akku += (load - e.base) * e.effort;
9✔
378
    }
9✔
379
    size_t work_limit = size_t(target * akku);
51✔
380
    return work_limit;
51✔
381
}
51✔
382

383
/* Find the oldest version that is still of interest to somebody */
384
uint64_t get_oldest_version(SharedFileInfo& info) // must be called under lock
385
{
3✔
386
    auto oldest_version = info.current_version;
3✔
387
    for (const auto& e : info.readers) {
3!
388
        if (e.version < oldest_version) {
3!
389
            oldest_version = e.version;
3✔
390
        }
3✔
391
    }
3✔
392
    return oldest_version;
3✔
393
}
3✔
394

395
// Reclaim pages for ONE file, limited by a given work limit.
396
void reclaim_pages_for_file(SharedFileInfo& info, size_t& work_limit)
397
{
3✔
398
    uint64_t oldest_version = get_oldest_version(info);
3✔
399
    if (info.last_scanned_version < oldest_version || info.mappings.empty()) {
3!
400
        // locate the mapping matching the progress index. No such mapping may
3✔
401
        // exist, and if so, we'll update the index to the next mapping
3✔
402
        for (auto& e : info.mappings) {
6!
403
            auto start_index = e->get_start_index();
6✔
404
            if (info.progress_index < start_index) {
6!
405
                info.progress_index = start_index;
×
406
            }
×
407
            if (info.progress_index <= e->get_end_index()) {
6!
408
                e->reclaim_untouched(info.progress_index, work_limit);
6✔
409
                if (work_limit == 0)
6!
410
                    return;
×
411
            }
6✔
412
        }
6✔
413
        // if we get here, all mappings have been considered
3✔
414
        info.progress_index = 0;
3✔
415
        info.last_scanned_version = info.current_version;
3✔
416
        ++info.current_version;
3✔
417
    }
3✔
418
}
3✔
419

420
// Reclaim pages from all files, limited by a work limit that is derived
421
// from a target for the amount of dirty (decrypted) pages. The target is
422
// set by the governor function.
423
void reclaim_pages()
424
{
5,499✔
425
    size_t load;
5,499✔
426
    util::UniqueFunction<int64_t()> runnable;
5,499✔
427
    {
5,499✔
428
        UniqueLock lock(mapping_mutex);
5,499✔
429
        load = collect_total_workload();
5,499✔
430
        num_decrypted_pages = load;
5,499✔
431
        runnable = governor->current_target_getter(load * page_size());
5,499✔
432
    }
5,499✔
433
    // callback to governor defined function without mutex held
3,153✔
434
    int64_t target = PageReclaimGovernor::no_match;
5,499✔
435
    if (runnable) {
5,499✔
436
        target = runnable();
5,499✔
437
    }
5,499✔
438
    {
5,499✔
439
        UniqueLock lock(mapping_mutex);
5,499✔
440
        reclaimer_workload = 0;
5,499✔
441
        reclaimer_target = size_t(target / page_size());
5,499✔
442
        // Putting the target back into the govenor object will allow the govenor
3,153✔
443
        // to return a getter producing this value again next time it is called
3,153✔
444
        governor->report_target_result(target);
5,499✔
445

3,153✔
446
        if (target == PageReclaimGovernor::no_match) // temporarily disabled by governor returning no_match
5,499✔
447
            return;
×
448

3,153✔
449
        if (mappings_by_file.size() == 0)
5,499✔
450
            return;
5,448✔
451

36✔
452
        size_t work_limit = get_work_limit(load, reclaimer_target);
51✔
453
        reclaimer_workload = work_limit;
51✔
454
        if (file_reclaim_index >= mappings_by_file.size())
51✔
455
            file_reclaim_index = 0;
3✔
456

36✔
457
        while (work_limit > 0) {
51✔
458
            SharedFileInfo& info = *mappings_by_file[file_reclaim_index].info;
3✔
459
            reclaim_pages_for_file(info, work_limit);
3✔
460
            if (work_limit > 0) { // consider next file:
3!
461
                ++file_reclaim_index;
3✔
462
                if (file_reclaim_index >= mappings_by_file.size())
3!
463
                    return;
3✔
464
            }
3✔
465
        }
3✔
466
    }
51✔
467
}
51✔
468

469

470
mapping_and_addr* find_mapping_for_addr(void* addr, size_t size)
471
{
3,713,934✔
472
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
3,740,946✔
473
        mapping_and_addr& m = mappings_by_addr[i];
32,238✔
474
        if (m.addr == addr && m.size == size)
32,238✔
475
            return &m;
5,226✔
476
        REALM_ASSERT(m.addr != addr);
27,012✔
477
    }
27,012✔
478

1,879,185✔
479
    return 0;
3,711,231✔
480
}
3,713,934✔
481
} // anonymous namespace
482

483
SharedFileInfo* get_file_info_for_file(File& file)
484
{
139,434✔
485
    LockGuard lock(mapping_mutex);
139,434✔
486
#ifndef _WIN32
139,434✔
487
    File::UniqueID id = file.get_unique_id();
139,434✔
488
#endif
139,434✔
489
    std::vector<mappings_for_file>::iterator it;
139,434✔
490
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
141,771✔
491
#ifdef _WIN32
492
        auto fd = file.get_descriptor();
493
        if (File::is_same_file_static(it->handle, fd))
494
            break;
495
#else
496
        if (it->inode == id.inode && it->device == id.device)
2,595✔
497
            break;
258✔
498
#endif
2,595✔
499
    }
2,595✔
500
    if (it == mappings_by_file.end())
139,434✔
501
        return nullptr;
139,176✔
502
    else
258✔
503
        return it->info.get();
258✔
504
}
139,434✔
505

506

507
namespace {
508
EncryptedFileMapping* add_mapping(void* addr, size_t size, const FileAttributes& file, size_t file_offset)
509
{
3,360✔
510
#ifndef _WIN32
3,360✔
511
    struct stat st;
3,360✔
512

1,551✔
513
    if (fstat(file.fd, &st)) {
3,360✔
514
        int err = errno; // Eliminate any risk of clobbering
×
515
        throw std::system_error(err, std::system_category(), "fstat() failed");
×
516
    }
×
517
#endif
3,360✔
518

1,551✔
519
    size_t fs = to_size_t(File::get_size_static(file.fd));
3,360✔
520
    if (fs > 0 && fs < page_size())
3,360✔
521
        throw DecryptionFailed();
6✔
522

1,548✔
523
    LockGuard lock(mapping_mutex);
3,354✔
524

1,548✔
525
    std::vector<mappings_for_file>::iterator it;
3,354✔
526
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
4,770✔
527
#ifdef _WIN32
528
        if (File::is_same_file_static(it->handle, file.fd))
529
            break;
530
#else
531
        if (it->inode == st.st_ino && it->device == st.st_dev)
2,799✔
532
            break;
1,383✔
533
#endif
2,799✔
534
    }
2,799✔
535

1,548✔
536
    // Get the potential memory allocation out of the way so that mappings_by_addr.push_back can't throw
1,548✔
537
    mappings_by_addr.reserve(mappings_by_addr.size() + 1);
3,354✔
538

1,548✔
539
    if (it == mappings_by_file.end()) {
3,354✔
540
        mappings_by_file.reserve(mappings_by_file.size() + 1);
1,971✔
541
        mappings_for_file f;
1,971✔
542
        f.info = std::make_shared<SharedFileInfo>(reinterpret_cast<const uint8_t*>(file.encryption_key));
1,971✔
543

828✔
544
        FileDesc fd_duped;
1,971✔
545
#ifdef _WIN32
546
        if (!DuplicateHandle(GetCurrentProcess(), file.fd, GetCurrentProcess(), &fd_duped, 0, FALSE,
547
                             DUPLICATE_SAME_ACCESS))
548
            throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
549
        f.info->fd = f.handle = fd_duped;
550
#else
551
        fd_duped = dup(file.fd);
1,971✔
552

828✔
553
        if (fd_duped == -1) {
1,971✔
554
            int err = errno; // Eliminate any risk of clobbering
×
555
            throw std::system_error(err, std::system_category(), "dup() failed");
×
556
        }
×
557
        f.info->fd = fd_duped;
1,971✔
558
        f.device = st.st_dev;
1,971✔
559
        f.inode = st.st_ino;
1,971✔
560
#endif // conditonal on _WIN32
1,971✔
561

828✔
562
        mappings_by_file.push_back(f); // can't throw due to reserve() above
1,971✔
563
        it = mappings_by_file.end() - 1;
1,971✔
564
    }
1,971✔
565
    else {
1,383✔
566
        it->info->cryptor.check_key(reinterpret_cast<const uint8_t*>(file.encryption_key));
1,383✔
567
    }
1,383✔
568

1,548✔
569
    try {
3,354✔
570
        mapping_and_addr m;
3,354✔
571
        m.addr = addr;
3,354✔
572
        m.size = size;
3,354✔
573
        m.mapping = std::make_shared<EncryptedFileMapping>(*it->info, file_offset, addr, size, file.access);
3,354✔
574
        mappings_by_addr.push_back(m); // can't throw due to reserve() above
3,354✔
575
        return m.mapping.get();
3,354✔
576
    }
3,354✔
577
    catch (...) {
×
578
        if (it->info->mappings.empty()) {
×
579
#ifdef _WIN32
580
            bool b = CloseHandle(it->info->fd);
581
            REALM_ASSERT_RELEASE(b);
582
#else
583
            ::close(it->info->fd);
×
584
#endif
×
585
            mappings_by_file.erase(it);
×
586
        }
×
587
        throw;
×
588
    }
×
589
}
3,354✔
590

591
void remove_mapping(void* addr, size_t size)
592
{
3,348✔
593
    size = round_up_to_page_size(size);
3,348✔
594
    LockGuard lock(mapping_mutex);
3,348✔
595
    mapping_and_addr* m = find_mapping_for_addr(addr, size);
3,348✔
596
    if (!m)
3,348✔
597
        return;
×
598

1,545✔
599
    mappings_by_addr.erase(mappings_by_addr.begin() + (m - &mappings_by_addr[0]));
3,348✔
600

1,545✔
601
    for (std::vector<mappings_for_file>::iterator it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
6,216✔
602
        if (it->info->mappings.empty()) {
4,839✔
603
#ifdef _WIN32
604
            if (!CloseHandle(it->info->fd))
605
                throw std::system_error(GetLastError(), std::system_category(), "CloseHandle() failed");
606
#else
607
            if (::close(it->info->fd) != 0) {
1,971✔
608
                int err = errno;                // Eliminate any risk of clobbering
×
609
                if (err == EBADF || err == EIO) // FIXME: how do we handle EINTR?
×
610
                    throw std::system_error(err, std::system_category(), "close() failed");
×
611
            }
1,971✔
612
#endif
1,971✔
613
            mappings_by_file.erase(it);
1,971✔
614
            break;
1,971✔
615
        }
1,971✔
616
    }
4,839✔
617
}
3,348✔
618
} // anonymous namespace
619

620
void* mmap(const FileAttributes& file, size_t size, size_t offset, EncryptedFileMapping*& mapping)
621
{
3,132,111✔
622
    _impl::SimulatedFailure::trigger_mmap(size);
3,132,111✔
623
    if (file.encryption_key) {
3,132,111✔
624
        size = round_up_to_page_size(size);
3,096✔
625
        void* addr = mmap_anon(size);
3,096✔
626
        mapping = add_mapping(addr, size, file, offset);
3,096✔
627
        return addr;
3,096✔
628
    }
3,096✔
629
    else {
3,129,015✔
630
        mapping = nullptr;
3,129,015✔
631
        return mmap(file, size, offset);
3,129,015✔
632
    }
3,129,015✔
633
}
3,132,111✔
634

635

636
EncryptedFileMapping* reserve_mapping(void* addr, const FileAttributes& file, size_t offset)
637
{
264✔
638
    return add_mapping(addr, 0, file, offset);
264✔
639
}
264✔
640

641
void extend_encrypted_mapping(EncryptedFileMapping* mapping, void* addr, size_t offset, size_t old_size,
642
                              size_t new_size)
643
{
297✔
644
    LockGuard lock(mapping_mutex);
297✔
645
    auto m = find_mapping_for_addr(addr, old_size);
297✔
646
    REALM_ASSERT(m);
297✔
647
    m->size = new_size;
297✔
648
    mapping->extend_to(offset, new_size);
297✔
649
}
297✔
650

651
void remove_encrypted_mapping(void* addr, size_t size)
652
{
3,348✔
653
    remove_mapping(addr, size);
3,348✔
654
}
3,348✔
655

656
void* mmap_reserve(const FileAttributes& file, size_t reservation_size, size_t offset_in_file,
657
                   EncryptedFileMapping*& mapping)
658
{
×
659
    auto addr = mmap_reserve(file.fd, reservation_size, offset_in_file);
×
660
    if (file.encryption_key) {
×
661
        REALM_ASSERT(reservation_size == round_up_to_page_size(reservation_size));
×
662
        // we create a mapping for the entire reserved area. This causes full initialization of some fairly
663
        // large std::vectors, which it would be nice to avoid. This is left as a future optimization.
664
        mapping = add_mapping(addr, reservation_size, file, offset_in_file);
×
665
    }
×
666
    else {
×
667
        mapping = nullptr;
×
668
    }
×
669
    return addr;
×
670
}
×
671

672
void* mmap_fixed(FileDesc fd, void* address_request, size_t size, File::AccessMode access, size_t offset,
673
                 const char* enc_key, EncryptedFileMapping* encrypted_mapping)
674
{
×
675
    REALM_ASSERT((enc_key == nullptr) ==
×
676
                 (encrypted_mapping == nullptr)); // Mapping must already have been set if encryption is used
×
677
    if (encrypted_mapping) {
×
678
// Since the encryption layer must be able to WRITE into the memory area,
679
// we have to map it read/write regardless of the request.
680
// FIXME: Make this work for windows!
681
#ifdef _WIN32
682
        return nullptr;
683
#else
684
        return ::mmap(address_request, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
×
685
#endif
×
686
    }
×
687
    else {
×
688
        return mmap_fixed(fd, address_request, size, access, offset, enc_key);
×
689
    }
×
690
}
×
691

692

693
#endif // REALM_ENABLE_ENCRYPTION
694

695
void* mmap_anon(size_t size)
696
{
3,096✔
697
#ifdef _WIN32
698
    HANDLE hMapFile;
699
    LPCTSTR pBuf;
700

701
    ULARGE_INTEGER s;
702
    s.QuadPart = size;
703

704
    hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, s.HighPart, s.LowPart, nullptr);
705
    if (hMapFile == NULL) {
706
        throw std::system_error(GetLastError(), std::system_category(), "CreateFileMapping() failed");
707
    }
708

709
    pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, size);
710
    if (pBuf == nullptr) {
711
        throw std::system_error(GetLastError(), std::system_category(), "MapViewOfFile() failed");
712
    }
713

714
    CloseHandle(hMapFile);
715
    return (void*)pBuf;
716
#else
717
    void* addr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
3,096✔
718
    if (addr == MAP_FAILED) {
3,096✔
719
        int err = errno; // Eliminate any risk of clobbering
×
720
        if (is_mmap_memory_error(err)) {
×
721
            throw AddressSpaceExhausted(get_errno_msg("mmap() failed: ", err) + " size: " + util::to_string(size));
×
722
        }
×
723
        throw std::system_error(err, std::system_category(),
×
724
                                std::string("mmap() failed (size: ") + util::to_string(size) + ", offset is 0)");
×
725
    }
×
726
    return addr;
3,096✔
727
#endif
3,096✔
728
}
3,096✔
729

730
void* mmap_fixed(FileDesc fd, void* address_request, size_t size, File::AccessMode access, size_t offset,
731
                 const char* enc_key)
732
{
158,490✔
733
    _impl::SimulatedFailure::trigger_mmap(size);
158,490✔
734
    static_cast<void>(enc_key); // FIXME: Consider removing this parameter
158,490✔
735
#ifdef _WIN32
736
    REALM_ASSERT(false);
737
    return nullptr; // silence warning
738
#else
739
    auto prot = PROT_READ;
158,490✔
740
    if (access == File::access_ReadWrite)
158,490✔
741
        prot |= PROT_WRITE;
×
742
    auto addr = ::mmap(address_request, size, prot, MAP_SHARED | MAP_FIXED, fd, offset);
158,490✔
743
    if (addr != MAP_FAILED && addr != address_request) {
158,490✔
744
        throw std::runtime_error(get_errno_msg("mmap() failed: ", errno) +
×
745
                                 ", when mapping an already reserved memory area");
×
746
    }
×
747
    return addr;
158,490✔
748
#endif
158,490✔
749
}
158,490✔
750

751
void* mmap_reserve(FileDesc fd, size_t reservation_size, size_t offset_in_file)
752
{
×
753
    // The other mmap operations take an fd as a parameter, so we do too.
754
    // We're not using it for anything currently, but this may change.
755
    // Similarly for offset_in_file.
756
    static_cast<void>(fd);
×
757
    static_cast<void>(offset_in_file);
×
758
#ifdef _WIN32
759
    REALM_ASSERT(false); // unsupported on windows
760
    return nullptr;
761
#else
762
    auto addr = ::mmap(0, reservation_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
×
763
    if (addr == MAP_FAILED) {
×
764
        throw std::runtime_error(get_errno_msg("mmap() failed: ", errno));
×
765
    }
×
766
    return addr;
×
767
#endif
×
768
}
×
769

770

771
void* mmap(const FileAttributes& file, size_t size, size_t offset)
772
{
3,128,952✔
773
    _impl::SimulatedFailure::trigger_mmap(size);
3,128,952✔
774
#if REALM_ENABLE_ENCRYPTION
3,128,952✔
775
    if (file.encryption_key) {
3,128,952✔
776
        size = round_up_to_page_size(size);
×
777
        void* addr = mmap_anon(size);
×
778
        add_mapping(addr, size, file, offset);
×
779
        return addr;
×
780
    }
×
781
    else
3,128,952✔
782
#else
783
    REALM_ASSERT(!file.encryption_key);
784
#endif
785
    {
3,128,952✔
786

1,572,897✔
787
#ifndef _WIN32
3,128,952✔
788
        int prot = PROT_READ;
3,128,952✔
789
        switch (file.access) {
3,128,952✔
790
            case File::access_ReadWrite:
2,922,054✔
791
                prot |= PROT_WRITE;
2,922,054✔
792
                break;
2,922,054✔
793
            case File::access_ReadOnly:
206,904✔
794
                break;
206,904✔
795
        }
3,128,943✔
796

1,572,876✔
797
        void* addr = ::mmap(nullptr, size, prot, MAP_SHARED, file.fd, offset);
3,128,943✔
798
        if (addr != MAP_FAILED)
3,128,943✔
799
            return addr;
3,129,219✔
800

2,147,483,647✔
801
        int err = errno; // Eliminate any risk of clobbering
4,294,967,294✔
802
        if (is_mmap_memory_error(err)) {
4,294,967,294✔
803
            throw AddressSpaceExhausted(get_errno_msg("mmap() failed: ", err) + " size: " + util::to_string(size) +
×
804
                                        " offset: " + util::to_string(offset));
×
805
        }
×
806

2,147,483,647✔
807
        throw SystemError(err, std::string("mmap() failed (size: ") + util::to_string(size) +
4,294,967,294✔
808
                                   ", offset: " + util::to_string(offset));
4,294,967,294✔
809

2,147,483,647✔
810
#else
811
        // FIXME: Is there anything that we must do on Windows to honor map_NoSync?
812

813
        DWORD protect = PAGE_READONLY;
814
        DWORD desired_access = FILE_MAP_READ;
815
        switch (file.access) {
816
            case File::access_ReadOnly:
817
                break;
818
            case File::access_ReadWrite:
819
                protect = PAGE_READWRITE;
820
                desired_access = FILE_MAP_WRITE;
821
                break;
822
        }
823
        LARGE_INTEGER large_int;
824
        if (int_cast_with_overflow_detect(offset + size, large_int.QuadPart))
825
            throw std::runtime_error("Map size is too large");
826
        HANDLE map_handle = CreateFileMappingFromApp(file.fd, 0, protect, offset + size, nullptr);
827
        if (!map_handle)
828
            throw AddressSpaceExhausted(get_errno_msg("CreateFileMapping() failed: ", GetLastError()) +
829
                                        " size: " + util::to_string(size) + " offset: " + util::to_string(offset));
830

831
        if (int_cast_with_overflow_detect(offset, large_int.QuadPart))
832
            throw RuntimeError(ErrorCodes::RangeError, "Map offset is too large");
833

834
        SIZE_T _size = size;
835
        void* addr = MapViewOfFileFromApp(map_handle, desired_access, offset, _size);
836
        BOOL r = CloseHandle(map_handle);
837
        REALM_ASSERT_RELEASE(r);
838
        if (!addr)
839
            throw AddressSpaceExhausted(get_errno_msg("MapViewOfFileFromApp() failed: ", GetLastError()) +
840
                                        " size: " + util::to_string(_size) + " offset: " + util::to_string(offset));
841

842
        return addr;
843
#endif
844
    }
4,294,967,294✔
845
}
3,128,952✔
846

847
void munmap(void* addr, size_t size)
848
{
×
849
#if REALM_ENABLE_ENCRYPTION
×
850
    remove_mapping(addr, size);
×
851
#endif
×
852

853
#ifdef _WIN32
854
    if (!UnmapViewOfFile(addr))
855
        throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
856

857
#else
858
    if (::munmap(addr, size) != 0) {
×
859
        int err = errno;
×
860
        throw std::system_error(err, std::system_category(), "munmap() failed");
×
861
    }
×
862
#endif
×
863
}
×
864

865
void* mremap(const FileAttributes& file, size_t file_offset, void* old_addr, size_t old_size, size_t new_size)
866
{
×
867
#if REALM_ENABLE_ENCRYPTION
×
868
    if (file.encryption_key) {
×
869
        LockGuard lock(mapping_mutex);
×
870
        size_t rounded_old_size = round_up_to_page_size(old_size);
×
871
        if (mapping_and_addr* m = find_mapping_for_addr(old_addr, rounded_old_size)) {
×
872
            size_t rounded_new_size = round_up_to_page_size(new_size);
×
873
            if (rounded_old_size == rounded_new_size)
×
874
                return old_addr;
×
875

876
            void* new_addr = mmap_anon(rounded_new_size);
×
877
            m->mapping->set(new_addr, rounded_new_size, file_offset);
×
878
            m->addr = new_addr;
×
879
            m->size = rounded_new_size;
×
880
#ifdef _WIN32
881
            if (!UnmapViewOfFile(old_addr))
882
                throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
883
#else
884
            if (::munmap(old_addr, rounded_old_size)) {
×
885
                int err = errno;
×
886
                throw std::system_error(err, std::system_category(), "munmap() failed");
×
887
            }
×
888
#endif
×
889
            return new_addr;
×
890
        }
×
891
        // If we are using encryption, we must have used mmap and the mapping
892
        // must have been added to the cache therefore find_mapping_for_addr()
893
        // will succeed. Otherwise we would continue to mmap it below without
894
        // the encryption key which is an error.
895
        REALM_UNREACHABLE();
×
896
    }
×
897
#endif
×
898

899
#ifdef _GNU_SOURCE
900
    {
901
        void* new_addr = ::mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE);
902
        if (new_addr != MAP_FAILED)
903
            return new_addr;
904
        int err = errno; // Eliminate any risk of clobbering
905
        // Do not throw here if mremap is declared as "not supported" by the
906
        // platform Eg. When compiling with GNU libc on OSX, iOS.
907
        // In this case fall through to no-mremap case below.
908
        if (err != ENOTSUP && err != ENOSYS) {
909
            if (is_mmap_memory_error(err)) {
910
                throw AddressSpaceExhausted(get_errno_msg("mremap() failed: ", err) + " old size: " +
911
                                            util::to_string(old_size) + " new size: " + util::to_string(new_size));
912
            }
913
            throw std::system_error(err, std::system_category(),
914
                                    std::string("_gnu_src mmap() failed (") + "old_size: " +
915
                                        util::to_string(old_size) + ", new_size: " + util::to_string(new_size) + ")");
916
        }
917
    }
918
#endif
919

920
    void* new_addr = mmap(file, new_size, file_offset);
×
921

922
#ifdef _WIN32
923
    if (!UnmapViewOfFile(old_addr))
924
        throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
925
#else
926
    if (::munmap(old_addr, old_size) != 0) {
×
927
        int err = errno;
×
928
        throw std::system_error(err, std::system_category(), "munmap() failed");
×
929
    }
×
930
#endif
×
931

932
    return new_addr;
×
933
}
×
934

935
void msync(FileDesc fd, void* addr, size_t size)
936
{
3,709,983✔
937
#if REALM_ENABLE_ENCRYPTION
3,709,983✔
938
    {
3,709,983✔
939
        // first check the encrypted mappings
1,877,274✔
940
        LockGuard lock(mapping_mutex);
3,709,983✔
941
        if (mapping_and_addr* m = find_mapping_for_addr(addr, round_up_to_page_size(size))) {
3,709,983✔
942
            m->mapping->flush();
1,581✔
943
            m->mapping->sync();
1,581✔
944
            return;
1,581✔
945
        }
1,581✔
946
    }
3,708,402✔
947
#endif
3,708,402✔
948

1,876,449✔
949
    // not an encrypted mapping
1,876,449✔
950

1,876,449✔
951
    // FIXME: on iOS/OSX fsync may not be enough to ensure crash safety.
1,876,449✔
952
    // Consider adding fcntl(F_FULLFSYNC). This most likely also applies to msync.
1,876,449✔
953
    //
1,876,449✔
954
    // See description of fsync on iOS here:
1,876,449✔
955
    // https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html
1,876,449✔
956
    //
1,876,449✔
957
    // See also
1,876,449✔
958
    // https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdPersistentStores.html
1,876,449✔
959
    // for a discussion of this related to core data.
1,876,449✔
960

1,876,449✔
961
#ifdef _WIN32
962
    // FlushViewOfFile() is asynchronous and won't flush metadata (file size, etc)
963
    if (!FlushViewOfFile(addr, size)) {
964
        throw std::system_error(GetLastError(), std::system_category(), "FlushViewOfFile() failed");
965
    }
966
    // Block until data and metadata is written physically to the media
967
    if (!FlushFileBuffers(fd)) {
968
        throw std::system_error(GetLastError(), std::system_category(), "FlushFileBuffers() failed");
969
    }
970
    return;
971
#else
972
    static_cast<void>(fd);
3,708,402✔
973
    int retries_left = 1000;
3,708,402✔
974
    while (::msync(addr, size, MS_SYNC) != 0) {
3,708,402✔
975
        int err = errno; // Eliminate any risk of clobbering
×
976
        if (--retries_left < 0)
×
977
            throw std::system_error(err, std::system_category(), "msync() retries exhausted");
×
978
        if (err != EINTR)
×
979
            throw std::system_error(err, std::system_category(), "msync() failed");
×
980
    }
×
981
#endif
3,708,402✔
982
}
3,708,402✔
983
} // namespace util
984
} // namespace realm
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc