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

realm / realm-core / kirill.burtsev_130

14 Feb 2024 07:28PM UTC coverage: 91.891% (+0.01%) from 91.881%
kirill.burtsev_130

Pull #7344

Evergreen

kiburtse
fix some gcc build warnings
Pull Request #7344: Allow to set logging level through env var for object store tests

93086 of 171520 branches covered (54.27%)

36 of 40 new or added lines in 5 files covered. (90.0%)

47 existing lines in 10 files now uncovered.

235529 of 256314 relevant lines covered (91.89%)

6267890.8 hits per line

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

76.26
/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
#endif
29

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

36
#if REALM_ENABLE_ENCRYPTION
37

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

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

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

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

62
#endif // enable encryption
63

64
namespace {
65

66
inline bool is_mmap_memory_error(int err)
67
{
×
68
    return (err == EAGAIN || err == EMFILE || err == ENOMEM);
×
69
}
×
70

71
} // Unnamed namespace
72

73
using namespace realm;
74
using namespace realm::util;
75

76
namespace realm {
77
namespace util {
78

79
size_t round_up_to_page_size(size_t size) noexcept
80
{
1,604,943✔
81
    return (size + page_size() - 1) & ~(page_size() - 1);
1,604,943✔
82
}
1,604,943✔
83

84

85
#if REALM_ENABLE_ENCRYPTION
86

87
// A list of all of the active encrypted mappings for a single file
88
struct mappings_for_file {
89
    File::UniqueID file_unique_id;
90
    std::shared_ptr<SharedFileInfo> info;
91
};
92

93
// Group the information we need to map a SIGSEGV address to an
94
// EncryptedFileMapping for the sake of cache-friendliness with 3+ active
95
// mappings (and no worse with only two)
96
struct mapping_and_addr {
97
    std::shared_ptr<EncryptedFileMapping> mapping;
98
    void* addr;
99
    size_t size;
100
};
101

102
util::Mutex& mapping_mutex = *(new util::Mutex);
103
namespace {
104
std::vector<mapping_and_addr>& mappings_by_addr = *new std::vector<mapping_and_addr>;
105
std::vector<mappings_for_file>& mappings_by_file = *new std::vector<mappings_for_file>;
106
static unsigned int file_reclaim_index = 0;
107
static std::atomic<size_t> num_decrypted_pages(0); // this is for statistical purposes
108
static std::atomic<size_t> reclaimer_target(0);    // do.
109
static std::atomic<size_t> reclaimer_workload(0);  // do.
110
// helpers
111

112
int64_t fetch_value_in_file(const std::string& fname, const char* scan_pattern)
113
{
2,352✔
114
    std::ifstream file(fname);
2,352✔
115
    if (file) {
2,352✔
116
        std::stringstream buffer;
846✔
117
        buffer << file.rdbuf();
846✔
118

846✔
119
        std::string s = buffer.str();
846✔
120
        std::smatch m;
846✔
121
        std::regex e(scan_pattern);
846✔
122

846✔
123
        if (std::regex_search(s, m, e)) {
846!
124
            std::string ibuf = m[1];
846✔
125
            return strtol(ibuf.c_str(), nullptr, 10);
846✔
126
        }
846✔
127
    }
1,506✔
128
    return PageReclaimGovernor::no_match;
1,506✔
129
}
1,506✔
130

131

132
/* Default reclaim governor
133
 *
134
 */
135

136
class DefaultGovernor : public PageReclaimGovernor {
137
public:
138
    static int64_t pick_lowest_valid(int64_t a, int64_t b)
139
    {
1,176✔
140
        if (a == PageReclaimGovernor::no_match)
1,176✔
141
            return b;
×
142
        if (b == PageReclaimGovernor::no_match)
1,176✔
143
            return a;
612✔
144
        return std::min(a, b);
564✔
145
    }
564✔
146

147
    static int64_t pick_if_valid(int64_t source, int64_t target)
148
    {
1,764✔
149
        if (source == PageReclaimGovernor::no_match)
1,764✔
150
            return PageReclaimGovernor::no_match;
612✔
151
        return target;
1,152✔
152
    }
1,152✔
153

154
    static int64_t get_target_from_system(const std::string& cfg_file_name)
155
    {
588✔
156
        int64_t target;
588✔
157
        auto local_spec = fetch_value_in_file(cfg_file_name, "target ([[:digit:]]+)");
588✔
158
        if (local_spec != no_match) { // overrides everything!
588✔
159
            target = local_spec;
×
160
        }
×
161
        else {
588✔
162
            // no local spec, try to deduce something reasonable from platform info
282✔
163
            auto from_proc = fetch_value_in_file("/proc/meminfo", "MemTotal:[[:space:]]+([[:digit:]]+) kB") * 1024;
588✔
164
            auto from_cgroup = fetch_value_in_file("/sys/fs/cgroup/memory/memory.limit_in_bytes", "^([[:digit:]]+)");
588✔
165
            auto cache_use = fetch_value_in_file("/sys/fs/cgroup/memory/memory.stat", "cache ([[:digit:]]+)");
588✔
166
            target = pick_if_valid(from_proc, from_proc / 4);
588✔
167
            target = pick_lowest_valid(target, pick_if_valid(from_cgroup, from_cgroup / 4));
588✔
168
            target = pick_lowest_valid(target, pick_if_valid(cache_use, cache_use));
588✔
169
        }
588✔
170
        return target;
588✔
171
    }
588✔
172

173
    util::UniqueFunction<int64_t()> current_target_getter(size_t load) override
174
    {
6,309✔
175
        static_cast<void>(load);
6,309✔
176
        if (m_refresh_count > 0) {
6,309✔
177
            --m_refresh_count;
5,721✔
178
            return [target = m_target] {
5,721✔
179
                return target;
5,721✔
180
            };
5,721✔
181
        }
5,721✔
182
        m_refresh_count = 10;
588✔
183

282✔
184
        return [file_name = m_cfg_file_name] {
588✔
185
            return get_target_from_system(file_name);
588✔
186
        };
588✔
187
    }
588✔
188

189
    void report_target_result(int64_t target) override
190
    {
6,309✔
191
        m_target = target;
6,309✔
192
    }
6,309✔
193

194
    DefaultGovernor()
195
    {
24✔
196
        auto cfg_name = getenv("REALM_PAGE_GOVERNOR_CFG");
24✔
197
        if (cfg_name) {
24✔
198
            m_cfg_file_name = cfg_name;
×
199
        }
×
200
    }
24✔
201

202
private:
203
    std::string m_cfg_file_name;
204
    int64_t m_target = 0;
205
    int m_refresh_count = 0;
206
};
207

208
static DefaultGovernor default_governor;
209
static PageReclaimGovernor* governor = &default_governor;
210

211
void reclaim_pages();
212

213
#if !REALM_PLATFORM_APPLE
214
static std::atomic<bool> reclaimer_shutdown(false);
215
static std::unique_ptr<std::thread> reclaimer_thread;
216

217
static void ensure_reclaimer_thread_runs()
218
{
585✔
219
    if (reclaimer_thread == nullptr) {
585✔
220
        reclaimer_thread = std::make_unique<std::thread>([] {
12✔
221
            while (!reclaimer_shutdown) {
3,021✔
222
                reclaim_pages();
3,009✔
223
                millisleep(1000);
3,009✔
224
            }
3,009✔
225
        });
12✔
226
    }
12✔
227
}
585✔
228

229
struct ReclaimerThreadStopper {
230
    ~ReclaimerThreadStopper()
231
    {
232
        if (reclaimer_thread) {
233
            reclaimer_shutdown = true;
234
            reclaimer_thread->join();
235
        }
236
    }
237
} reclaimer_thread_stopper;
238
#else // REALM_PLATFORM_APPLE
239
static dispatch_source_t reclaimer_timer;
240
static dispatch_queue_t reclaimer_queue;
241

242
static void ensure_reclaimer_thread_runs()
243
{
537✔
244
    if (!reclaimer_timer) {
537✔
245
        if (__builtin_available(iOS 10, macOS 12, tvOS 10, watchOS 3, *)) {
12✔
246
            reclaimer_queue = dispatch_queue_create_with_target("io.realm.page-reclaimer", DISPATCH_QUEUE_SERIAL,
247
                                                                dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0));
248
        }
249
        else {
12✔
250
            reclaimer_queue = dispatch_queue_create("io.realm.page-reclaimer", DISPATCH_QUEUE_SERIAL);
12✔
251
        }
12✔
252
        reclaimer_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, reclaimer_queue);
12✔
253
        dispatch_source_set_timer(reclaimer_timer, DISPATCH_TIME_NOW, NSEC_PER_SEC, NSEC_PER_SEC);
12✔
254
        dispatch_source_set_event_handler(reclaimer_timer, ^{
3,300✔
255
            reclaim_pages();
3,300✔
256
        });
3,300✔
257
        dispatch_resume(reclaimer_timer);
12✔
258
    }
12✔
259
}
537✔
260

261
struct ReclaimerThreadStopper {
262
    ~ReclaimerThreadStopper()
263
    {
12✔
264
        if (reclaimer_timer) {
12✔
265
            dispatch_source_cancel(reclaimer_timer);
12✔
266
            // Block until any currently-running timer tasks are done
267
            dispatch_sync(reclaimer_queue, ^{
12✔
268
                          });
12✔
269
            dispatch_release(reclaimer_timer);
12✔
270
            dispatch_release(reclaimer_queue);
12✔
271
        }
12✔
272
    }
12✔
273
} reclaimer_thread_stopper;
274
#endif
275
} // anonymous namespace
276

277
void set_page_reclaim_governor(PageReclaimGovernor* new_governor)
278
{
×
279
    UniqueLock lock(mapping_mutex);
×
280
    governor = new_governor ? new_governor : &default_governor;
×
281
    ensure_reclaimer_thread_runs();
×
282
}
×
283

284
size_t get_num_decrypted_pages()
285
{
×
286
    return num_decrypted_pages.load();
×
287
}
×
288

289
void encryption_note_reader_start(SharedFileInfo& info, const void* reader_id)
290
{
1,122✔
291
    UniqueLock lock(mapping_mutex);
1,122✔
292
    ensure_reclaimer_thread_runs();
1,122✔
293
    auto j = std::find_if(info.readers.begin(), info.readers.end(), [=](auto& reader) {
759✔
294
        return reader.reader_ID == reader_id;
372✔
295
    });
372✔
296
    if (j == info.readers.end()) {
1,122✔
297
        ReaderInfo i = {reader_id, info.current_version};
1,050✔
298
        info.readers.push_back(i);
1,050✔
299
    }
1,050✔
300
    else {
72✔
301
        j->version = info.current_version;
72✔
302
    }
72✔
303
    ++info.current_version;
1,122✔
304
}
1,122✔
305

306
void encryption_note_reader_end(SharedFileInfo& info, const void* reader_id) noexcept
307
{
1,338✔
308
    UniqueLock lock(mapping_mutex);
1,338✔
309
    for (auto j = info.readers.begin(); j != info.readers.end(); ++j)
1,620✔
310
        if (j->reader_ID == reader_id) {
1,326✔
311
            // move last over
540✔
312
            *j = info.readers.back();
1,044✔
313
            info.readers.pop_back();
1,044✔
314
            return;
1,044✔
315
        }
1,044✔
316
}
1,338✔
317

318
void encryption_mark_pages_for_IV_check(EncryptedFileMapping* mapping)
319
{
1,278✔
320
    UniqueLock lock(mapping_mutex);
1,278✔
321
    mapping->mark_pages_for_IV_check();
1,278✔
322
}
1,278✔
323

324
namespace {
325
size_t collect_total_workload() // must be called under lock
326
{
6,309✔
327
    size_t total = 0;
6,309✔
328
    for (auto i = mappings_by_file.begin(); i != mappings_by_file.end(); ++i) {
6,360✔
329
        SharedFileInfo& info = *i->info;
51✔
330
        info.num_decrypted_pages = 0;
51✔
331
        for (auto it = info.mappings.begin(); it != info.mappings.end(); ++it) {
108✔
332
            info.num_decrypted_pages += (*it)->collect_decryption_count();
57✔
333
        }
57✔
334
        total += info.num_decrypted_pages;
51✔
335
    }
51✔
336
    return total;
6,309✔
337
}
6,309✔
338

339
/* Compute the amount of work allowed in an attempt to reclaim pages.
340
 * please refer to EncryptedFileMapping::reclaim_untouched() for more details.
341
 *
342
 * The function starts slowly when the load is 0.5 of target, then turns
343
 * up the volume as the load nears 1.0 - where it sets a work limit of 10%.
344
 * Since the work is expressed (roughly) in terms of pages released, this means
345
 * that about 10 runs has to take place to reclaim all pages possible - though
346
 * if successful the load will rapidly decrease, turning down the work limit.
347
 */
348

349
struct work_limit_desc {
350
    float base;
351
    float effort;
352
};
353
const std::vector<work_limit_desc> control_table = {{0.5f, 0.001f},  {0.75f, 0.002f}, {0.8f, 0.003f},
354
                                                    {0.85f, 0.005f}, {0.9f, 0.01f},   {0.95f, 0.03f},
355
                                                    {1.0f, 0.1f},    {1.5f, 0.2f},    {2.0f, 0.3f}};
356

357
size_t get_work_limit(size_t decrypted_pages, size_t target)
358
{
45✔
359
    if (target == 0)
45✔
360
        target = 1;
×
361
    float load = 1.0f * decrypted_pages / target;
45✔
362
    float akku = 0.0f;
45✔
363
    for (const auto& e : control_table) {
48✔
364
        if (load <= e.base)
48✔
365
            break;
45✔
366
        akku += (load - e.base) * e.effort;
3✔
367
    }
3✔
368
    size_t work_limit = size_t(target * akku);
45✔
369
    return work_limit;
45✔
370
}
45✔
371

372
/* Find the oldest version that is still of interest to somebody */
373
uint64_t get_oldest_version(SharedFileInfo& info) // must be called under lock
374
{
6✔
375
    auto oldest_version = info.current_version;
6✔
376
    for (const auto& e : info.readers) {
6!
377
        if (e.version < oldest_version) {
6!
378
            oldest_version = e.version;
6✔
379
        }
6✔
380
    }
6✔
381
    return oldest_version;
6✔
382
}
6✔
383

384
// Reclaim pages for ONE file, limited by a given work limit.
385
void reclaim_pages_for_file(SharedFileInfo& info, size_t& work_limit)
386
{
6✔
387
    uint64_t oldest_version = get_oldest_version(info);
6✔
388
    if (info.last_scanned_version < oldest_version || info.mappings.empty()) {
6!
389
        // locate the mapping matching the progress index. No such mapping may
6✔
390
        // exist, and if so, we'll update the index to the next mapping
6✔
391
        for (auto& e : info.mappings) {
12!
392
            auto start_index = e->get_start_index();
12✔
393
            if (info.progress_index < start_index) {
12!
394
                info.progress_index = start_index;
×
395
            }
×
396
            if (info.progress_index <= e->get_end_index()) {
12!
397
                e->reclaim_untouched(info.progress_index, work_limit);
12✔
398
                if (work_limit == 0)
12!
UNCOV
399
                    return;
×
400
            }
12✔
401
        }
12✔
402
        // if we get here, all mappings have been considered
6✔
403
        info.progress_index = 0;
6✔
404
        info.last_scanned_version = info.current_version;
6✔
405
        ++info.current_version;
6✔
406
    }
6✔
407
}
6✔
408

409
// Reclaim pages from all files, limited by a work limit that is derived
410
// from a target for the amount of dirty (decrypted) pages. The target is
411
// set by the governor function.
412
void reclaim_pages()
413
{
6,309✔
414
    size_t load;
6,309✔
415
    util::UniqueFunction<int64_t()> runnable;
6,309✔
416
    {
6,309✔
417
        UniqueLock lock(mapping_mutex);
6,309✔
418
        load = collect_total_workload();
6,309✔
419
        num_decrypted_pages = load;
6,309✔
420
        runnable = governor->current_target_getter(load * page_size());
6,309✔
421
    }
6,309✔
422
    // callback to governor defined function without mutex held
3,009✔
423
    int64_t target = PageReclaimGovernor::no_match;
6,309✔
424
    if (runnable) {
6,309✔
425
        target = runnable();
6,309✔
426
    }
6,309✔
427
    {
6,309✔
428
        UniqueLock lock(mapping_mutex);
6,309✔
429
        reclaimer_workload = 0;
6,309✔
430
        reclaimer_target = size_t(target / page_size());
6,309✔
431
        // Putting the target back into the govenor object will allow the govenor
3,009✔
432
        // to return a getter producing this value again next time it is called
3,009✔
433
        governor->report_target_result(target);
6,309✔
434

3,009✔
435
        if (target == PageReclaimGovernor::no_match) // temporarily disabled by governor returning no_match
6,309✔
436
            return;
×
437

3,009✔
438
        if (mappings_by_file.size() == 0)
6,309✔
439
            return;
6,264✔
440

27✔
441
        size_t work_limit = get_work_limit(load, reclaimer_target);
45✔
442
        reclaimer_workload = work_limit;
45✔
443
        if (file_reclaim_index >= mappings_by_file.size())
45✔
444
            file_reclaim_index = 0;
3✔
445

27✔
446
        while (work_limit > 0) {
48✔
447
            SharedFileInfo& info = *mappings_by_file[file_reclaim_index].info;
6✔
448
            reclaim_pages_for_file(info, work_limit);
6✔
449
            if (work_limit > 0) { // consider next file:
6!
450
                ++file_reclaim_index;
6✔
451
                if (file_reclaim_index >= mappings_by_file.size())
6!
452
                    return;
3✔
453
            }
6✔
454
        }
6✔
455
    }
45✔
456
}
45✔
457

458

459
mapping_and_addr* find_mapping_for_addr(void* addr, size_t size)
460
{
1,378,278✔
461
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
1,413,489✔
462
        mapping_and_addr& m = mappings_by_addr[i];
40,077✔
463
        if (m.addr == addr && m.size == size)
40,077✔
464
            return &m;
4,866✔
465
        REALM_ASSERT(m.addr != addr);
35,211✔
466
    }
35,211✔
467

705,339✔
468
    return 0;
1,375,740✔
469
}
1,378,278✔
470
} // anonymous namespace
471

472
SharedFileInfo* get_file_info_for_file(File& file)
473
{
87,447✔
474
    LockGuard lock(mapping_mutex);
87,447✔
475
    File::UniqueID id = file.get_unique_id();
87,447✔
476
    std::vector<mappings_for_file>::iterator it;
87,447✔
477
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
88,272✔
478
        if (it->file_unique_id == id) {
1,047✔
479
            break;
222✔
480
        }
222✔
481
    }
1,047✔
482
    if (it == mappings_by_file.end())
87,447✔
483
        return nullptr;
87,225✔
484
    else
222✔
485
        return it->info.get();
222✔
486
}
87,447✔
487

488

489
namespace {
490
EncryptedFileMapping* add_mapping(void* addr, size_t size, const FileAttributes& file, size_t file_offset)
491
{
3,171✔
492
    size_t fs = to_size_t(File::get_size_static(file.fd));
3,171✔
493
    if (fs > 0 && fs < page_size())
3,171✔
494
        throw DecryptionFailed();
6✔
495

1,455✔
496
    LockGuard lock(mapping_mutex);
3,165✔
497

1,455✔
498
    File::UniqueID fuid = File::get_unique_id(file.fd, file.path);
3,165✔
499

1,455✔
500
    std::vector<mappings_for_file>::iterator it;
3,165✔
501
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
4,689✔
502
        if (it->file_unique_id == fuid) {
2,796✔
503
            break;
1,272✔
504
        }
1,272✔
505
    }
2,796✔
506

1,455✔
507
    // Get the potential memory allocation out of the way so that mappings_by_addr.push_back can't throw
1,455✔
508
    mappings_by_addr.reserve(mappings_by_addr.size() + 1);
3,165✔
509

1,455✔
510
    if (it == mappings_by_file.end()) {
3,165✔
511
        mappings_by_file.reserve(mappings_by_file.size() + 1);
1,893✔
512
        mappings_for_file f;
1,893✔
513
        f.info = std::make_shared<SharedFileInfo>(reinterpret_cast<const uint8_t*>(file.encryption_key));
1,893✔
514
        f.info->fd = File::dup_file_desc(file.fd);
1,893✔
515
        f.file_unique_id = fuid;
1,893✔
516

801✔
517
        mappings_by_file.push_back(f); // can't throw due to reserve() above
1,893✔
518
        it = mappings_by_file.end() - 1;
1,893✔
519
    }
1,893✔
520
    else {
1,272✔
521
        it->info->cryptor.check_key(reinterpret_cast<const uint8_t*>(file.encryption_key));
1,272✔
522
    }
1,272✔
523

1,455✔
524
    try {
3,165✔
525
        mapping_and_addr m;
3,165✔
526
        m.addr = addr;
3,165✔
527
        m.size = size;
3,165✔
528
        m.mapping = std::make_shared<EncryptedFileMapping>(*it->info, file_offset, addr, size, file.access);
3,165✔
529
        mappings_by_addr.push_back(m); // can't throw due to reserve() above
3,165✔
530
        return m.mapping.get();
3,165✔
531
    }
3,165✔
532
    catch (...) {
×
533
        if (it->info->mappings.empty()) {
×
534
            FileDesc fd_to_close = it->info->fd;
×
535
            mappings_by_file.erase(it);
×
536
            File::close_static(fd_to_close); // Throws
×
537
        }
×
538
        throw;
×
539
    }
×
540
}
3,165✔
541

542
void remove_mapping(void* addr, size_t size)
543
{
3,159✔
544
    size = round_up_to_page_size(size);
3,159✔
545
    LockGuard lock(mapping_mutex);
3,159✔
546
    mapping_and_addr* m = find_mapping_for_addr(addr, size);
3,159✔
547
    if (!m)
3,159✔
548
        return;
×
549

1,452✔
550
    mappings_by_addr.erase(mappings_by_addr.begin() + (m - &mappings_by_addr[0]));
3,159✔
551

1,452✔
552
    for (std::vector<mappings_for_file>::iterator it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
6,018✔
553
        if (it->info->mappings.empty()) {
4,752✔
554
            FileDesc fd_to_close = it->info->fd;
1,893✔
555
            mappings_by_file.erase(it);
1,893✔
556
            File::close_static(fd_to_close); // Throws
1,893✔
557
            break;
1,893✔
558
        }
1,893✔
559
    }
4,752✔
560
}
3,159✔
561
} // anonymous namespace
562

563
void* mmap(const FileAttributes& file, size_t size, size_t offset, EncryptedFileMapping*& mapping)
564
{
1,319,628✔
565
    _impl::SimulatedFailure::trigger_mmap(size);
1,319,628✔
566
    if (file.encryption_key) {
1,319,628✔
567
        size = round_up_to_page_size(size);
2,943✔
568
        void* addr = mmap_anon(size);
2,943✔
569
        mapping = add_mapping(addr, size, file, offset);
2,943✔
570
        return addr;
2,943✔
571
    }
2,943✔
572
    else {
1,316,685✔
573
        mapping = nullptr;
1,316,685✔
574
        return mmap(file, size, offset);
1,316,685✔
575
    }
1,316,685✔
576
}
1,319,628✔
577

578

579
EncryptedFileMapping* reserve_mapping(void* addr, const FileAttributes& file, size_t offset)
580
{
228✔
581
    return add_mapping(addr, 0, file, offset);
228✔
582
}
228✔
583

584
void extend_encrypted_mapping(EncryptedFileMapping* mapping, void* addr, size_t offset, size_t old_size,
585
                              size_t new_size)
586
{
261✔
587
    LockGuard lock(mapping_mutex);
261✔
588
    auto m = find_mapping_for_addr(addr, old_size);
261✔
589
    REALM_ASSERT(m);
261✔
590
    m->size = new_size;
261✔
591
    mapping->extend_to(offset, new_size);
261✔
592
}
261✔
593

594
void remove_encrypted_mapping(void* addr, size_t size)
595
{
3,159✔
596
    remove_mapping(addr, size);
3,159✔
597
}
3,159✔
598

599
void* mmap_reserve(const FileAttributes& file, size_t reservation_size, size_t offset_in_file,
600
                   EncryptedFileMapping*& mapping)
601
{
×
602
    auto addr = mmap_reserve(file.fd, reservation_size, offset_in_file);
×
603
    if (file.encryption_key) {
×
604
        REALM_ASSERT(reservation_size == round_up_to_page_size(reservation_size));
×
605
        // we create a mapping for the entire reserved area. This causes full initialization of some fairly
606
        // large std::vectors, which it would be nice to avoid. This is left as a future optimization.
607
        mapping = add_mapping(addr, reservation_size, file, offset_in_file);
×
608
    }
×
609
    else {
×
610
        mapping = nullptr;
×
611
    }
×
612
    return addr;
×
613
}
×
614

615
void* mmap_fixed(FileDesc fd, void* address_request, size_t size, File::AccessMode access, size_t offset,
616
                 const char* enc_key, EncryptedFileMapping* encrypted_mapping)
617
{
×
618
    REALM_ASSERT((enc_key == nullptr) ==
×
619
                 (encrypted_mapping == nullptr)); // Mapping must already have been set if encryption is used
×
620
    if (encrypted_mapping) {
×
621
// Since the encryption layer must be able to WRITE into the memory area,
622
// we have to map it read/write regardless of the request.
623
// FIXME: Make this work for windows!
624
#ifdef _WIN32
625
        return nullptr;
626
#else
627
        return ::mmap(address_request, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
×
628
#endif
×
629
    }
×
630
    else {
×
631
        return mmap_fixed(fd, address_request, size, access, offset, enc_key);
×
632
    }
×
633
}
×
634

635

636
#endif // REALM_ENABLE_ENCRYPTION
637

638
void* mmap_anon(size_t size)
639
{
2,943✔
640
#ifdef _WIN32
641
    HANDLE hMapFile;
642
    LPCTSTR pBuf;
643

644
    ULARGE_INTEGER s;
645
    s.QuadPart = size;
646

647
    hMapFile = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, s.HighPart, s.LowPart, nullptr);
648
    if (hMapFile == NULL) {
649
        throw std::system_error(GetLastError(), std::system_category(), "CreateFileMapping() failed");
650
    }
651

652
    pBuf = (LPTSTR)MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, size);
653
    if (pBuf == nullptr) {
654
        throw std::system_error(GetLastError(), std::system_category(), "MapViewOfFile() failed");
655
    }
656

657
    CloseHandle(hMapFile);
658
    return (void*)pBuf;
659
#else
660
    void* addr = ::mmap(nullptr, size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
2,943✔
661
    if (addr == MAP_FAILED) {
2,943✔
662
        int err = errno; // Eliminate any risk of clobbering
×
663
        if (is_mmap_memory_error(err)) {
×
664
            throw AddressSpaceExhausted(get_errno_msg("mmap() failed: ", err) + " size: " + util::to_string(size));
×
665
        }
×
666
        throw std::system_error(err, std::system_category(),
×
667
                                std::string("mmap() failed (size: ") + util::to_string(size) + ", offset is 0)");
×
668
    }
×
669
    return addr;
2,943✔
670
#endif
2,943✔
671
}
2,943✔
672

673
void* mmap_fixed(FileDesc fd, void* address_request, size_t size, File::AccessMode access, size_t offset,
674
                 const char* enc_key)
675
{
105,831✔
676
    _impl::SimulatedFailure::trigger_mmap(size);
105,831✔
677
    static_cast<void>(enc_key); // FIXME: Consider removing this parameter
105,831✔
678
#ifdef _WIN32
679
    REALM_ASSERT(false);
680
    return nullptr; // silence warning
681
#else
682
    auto prot = PROT_READ;
105,831✔
683
    if (access == File::access_ReadWrite)
105,831✔
684
        prot |= PROT_WRITE;
×
685
    auto addr = ::mmap(address_request, size, prot, MAP_SHARED | MAP_FIXED, fd, offset);
105,831✔
686
    if (addr != MAP_FAILED && addr != address_request) {
105,831✔
687
        throw std::runtime_error(get_errno_msg("mmap() failed: ", errno) +
×
688
                                 ", when mapping an already reserved memory area");
×
689
    }
×
690
    return addr;
105,831✔
691
#endif
105,831✔
692
}
105,831✔
693

694
void* mmap_reserve(FileDesc fd, size_t reservation_size, size_t offset_in_file)
695
{
×
696
    // The other mmap operations take an fd as a parameter, so we do too.
697
    // We're not using it for anything currently, but this may change.
698
    // Similarly for offset_in_file.
699
    static_cast<void>(fd);
×
700
    static_cast<void>(offset_in_file);
×
701
#ifdef _WIN32
702
    REALM_ASSERT(false); // unsupported on windows
703
    return nullptr;
704
#else
705
    auto addr = ::mmap(0, reservation_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
×
706
    if (addr == MAP_FAILED) {
×
707
        throw std::runtime_error(get_errno_msg("mmap() failed: ", errno));
×
708
    }
×
709
    return addr;
×
710
#endif
×
711
}
×
712

713

714
void* mmap(const FileAttributes& file, size_t size, size_t offset)
715
{
1,316,652✔
716
    _impl::SimulatedFailure::trigger_mmap(size);
1,316,652✔
717
#if REALM_ENABLE_ENCRYPTION
1,316,652✔
718
    if (file.encryption_key) {
1,316,652✔
719
        size = round_up_to_page_size(size);
×
720
        void* addr = mmap_anon(size);
×
721
        add_mapping(addr, size, file, offset);
×
722
        return addr;
×
723
    }
×
724
    else
1,316,652✔
725
#else
726
    REALM_ASSERT(!file.encryption_key);
727
#endif
728
    {
1,316,652✔
729

661,452✔
730
#ifndef _WIN32
1,316,652✔
731
        int prot = PROT_READ;
1,316,652✔
732
        switch (file.access) {
1,316,652✔
733
            case File::access_ReadWrite:
1,204,425✔
734
                prot |= PROT_WRITE;
1,204,425✔
735
                break;
1,204,425✔
736
            case File::access_ReadOnly:
112,266✔
737
                break;
112,266✔
738
        }
1,316,667✔
739

661,437✔
740
        void* addr = ::mmap(nullptr, size, prot, MAP_SHARED, file.fd, offset);
1,316,667✔
741
        if (addr != MAP_FAILED)
1,316,667✔
742
            return addr;
1,316,961✔
743

2,147,483,647✔
744
        int err = errno; // Eliminate any risk of clobbering
4,294,967,294✔
745
        if (is_mmap_memory_error(err)) {
4,294,967,294✔
746
            throw AddressSpaceExhausted(get_errno_msg("mmap() failed: ", err) + " size: " + util::to_string(size) +
×
747
                                        " offset: " + util::to_string(offset));
×
748
        }
×
749

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

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

756
        DWORD protect = PAGE_READONLY;
757
        DWORD desired_access = FILE_MAP_READ;
758
        switch (file.access) {
759
            case File::access_ReadOnly:
760
                break;
761
            case File::access_ReadWrite:
762
                protect = PAGE_READWRITE;
763
                desired_access = FILE_MAP_WRITE;
764
                break;
765
        }
766
        LARGE_INTEGER large_int;
767
        if (int_cast_with_overflow_detect(offset + size, large_int.QuadPart))
768
            throw std::runtime_error("Map size is too large");
769
        HANDLE map_handle = CreateFileMappingFromApp(file.fd, 0, protect, offset + size, nullptr);
770
        if (!map_handle)
771
            throw AddressSpaceExhausted(get_errno_msg("CreateFileMapping() failed: ", GetLastError()) +
772
                                        " size: " + util::to_string(size) + " offset: " + util::to_string(offset));
773

774
        if (int_cast_with_overflow_detect(offset, large_int.QuadPart))
775
            throw RuntimeError(ErrorCodes::RangeError, "Map offset is too large");
776

777
        SIZE_T _size = size;
778
        void* addr = MapViewOfFileFromApp(map_handle, desired_access, offset, _size);
779
        BOOL r = CloseHandle(map_handle);
780
        REALM_ASSERT_RELEASE(r);
781
        if (!addr)
782
            throw AddressSpaceExhausted(get_errno_msg("MapViewOfFileFromApp() failed: ", GetLastError()) +
783
                                        " size: " + util::to_string(_size) + " offset: " + util::to_string(offset));
784

785
        return addr;
786
#endif
787
    }
4,294,967,294✔
788
}
1,316,652✔
789

790
void munmap(void* addr, size_t size)
791
{
×
792
#if REALM_ENABLE_ENCRYPTION
×
793
    remove_mapping(addr, size);
×
794
#endif
×
795

796
#ifdef _WIN32
797
    if (!UnmapViewOfFile(addr))
798
        throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
799

800
#else
801
    if (::munmap(addr, size) != 0) {
×
802
        int err = errno;
×
803
        throw std::system_error(err, std::system_category(), "munmap() failed");
×
804
    }
×
805
#endif
×
806
}
×
807

808
void* mremap(const FileAttributes& file, size_t file_offset, void* old_addr, size_t old_size, size_t new_size)
809
{
×
810
#if REALM_ENABLE_ENCRYPTION
×
811
    if (file.encryption_key) {
×
812
        LockGuard lock(mapping_mutex);
×
813
        size_t rounded_old_size = round_up_to_page_size(old_size);
×
814
        if (mapping_and_addr* m = find_mapping_for_addr(old_addr, rounded_old_size)) {
×
815
            size_t rounded_new_size = round_up_to_page_size(new_size);
×
816
            if (rounded_old_size == rounded_new_size)
×
817
                return old_addr;
×
818

819
            void* new_addr = mmap_anon(rounded_new_size);
×
820
            m->mapping->set(new_addr, rounded_new_size, file_offset);
×
821
            m->addr = new_addr;
×
822
            m->size = rounded_new_size;
×
823
#ifdef _WIN32
824
            if (!UnmapViewOfFile(old_addr))
825
                throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
826
#else
827
            if (::munmap(old_addr, rounded_old_size)) {
×
828
                int err = errno;
×
829
                throw std::system_error(err, std::system_category(), "munmap() failed");
×
830
            }
×
831
#endif
×
832
            return new_addr;
×
833
        }
×
834
        // If we are using encryption, we must have used mmap and the mapping
835
        // must have been added to the cache therefore find_mapping_for_addr()
836
        // will succeed. Otherwise we would continue to mmap it below without
837
        // the encryption key which is an error.
838
        REALM_UNREACHABLE();
839
    }
×
840
#endif
×
841

842
#ifdef _GNU_SOURCE
843
    {
844
        void* new_addr = ::mremap(old_addr, old_size, new_size, MREMAP_MAYMOVE);
845
        if (new_addr != MAP_FAILED)
846
            return new_addr;
847
        int err = errno; // Eliminate any risk of clobbering
848
        // Do not throw here if mremap is declared as "not supported" by the
849
        // platform Eg. When compiling with GNU libc on OSX, iOS.
850
        // In this case fall through to no-mremap case below.
851
        if (err != ENOTSUP && err != ENOSYS) {
852
            if (is_mmap_memory_error(err)) {
853
                throw AddressSpaceExhausted(get_errno_msg("mremap() failed: ", err) + " old size: " +
854
                                            util::to_string(old_size) + " new size: " + util::to_string(new_size));
855
            }
856
            throw std::system_error(err, std::system_category(),
857
                                    std::string("_gnu_src mmap() failed (") + "old_size: " +
858
                                        util::to_string(old_size) + ", new_size: " + util::to_string(new_size) + ")");
859
        }
860
    }
861
#endif
862

863
    void* new_addr = mmap(file, new_size, file_offset);
×
864

865
#ifdef _WIN32
866
    if (!UnmapViewOfFile(old_addr))
867
        throw std::system_error(GetLastError(), std::system_category(), "UnmapViewOfFile() failed");
868
#else
869
    if (::munmap(old_addr, old_size) != 0) {
×
870
        int err = errno;
×
871
        throw std::system_error(err, std::system_category(), "munmap() failed");
×
872
    }
×
873
#endif
×
874

875
    return new_addr;
×
876
}
×
877

878
void msync(FileDesc fd, void* addr, size_t size)
879
{
1,374,381✔
880
#if REALM_ENABLE_ENCRYPTION
1,374,381✔
881
    {
1,374,381✔
882
        // first check the encrypted mappings
703,458✔
883
        LockGuard lock(mapping_mutex);
1,374,381✔
884
        if (mapping_and_addr* m = find_mapping_for_addr(addr, round_up_to_page_size(size))) {
1,374,381✔
885
            m->mapping->flush();
1,446✔
886
            m->mapping->sync();
1,446✔
887
            return;
1,446✔
888
        }
1,446✔
889
    }
1,372,935✔
890
#endif
1,372,935✔
891

702,723✔
892
    // not an encrypted mapping
702,723✔
893

702,723✔
894
    // FIXME: on iOS/OSX fsync may not be enough to ensure crash safety.
702,723✔
895
    // Consider adding fcntl(F_FULLFSYNC). This most likely also applies to msync.
702,723✔
896
    //
702,723✔
897
    // See description of fsync on iOS here:
702,723✔
898
    // https://developer.apple.com/library/ios/documentation/System/Conceptual/ManPages_iPhoneOS/man2/fsync.2.html
702,723✔
899
    //
702,723✔
900
    // See also
702,723✔
901
    // https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/CoreData/Articles/cdPersistentStores.html
702,723✔
902
    // for a discussion of this related to core data.
702,723✔
903

702,723✔
904
#ifdef _WIN32
905
    // FlushViewOfFile() is asynchronous and won't flush metadata (file size, etc)
906
    if (!FlushViewOfFile(addr, size)) {
907
        throw std::system_error(GetLastError(), std::system_category(), "FlushViewOfFile() failed");
908
    }
909
    // Block until data and metadata is written physically to the media
910
    if (!FlushFileBuffers(fd)) {
911
        throw std::system_error(GetLastError(), std::system_category(), "FlushFileBuffers() failed");
912
    }
913
    return;
914
#else
915
    static_cast<void>(fd);
1,372,935✔
916
    int retries_left = 1000;
1,372,935✔
917
    while (::msync(addr, size, MS_SYNC) != 0) {
1,372,935✔
918
        int err = errno; // Eliminate any risk of clobbering
×
919
        if (--retries_left < 0)
×
920
            throw std::system_error(err, std::system_category(), "msync() retries exhausted");
×
921
        if (err != EINTR)
×
922
            throw std::system_error(err, std::system_category(), "msync() failed");
×
923
    }
×
924
#endif
1,372,935✔
925
}
1,372,935✔
926
} // namespace util
927
} // 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

© 2026 Coveralls, Inc