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

realm / realm-core / 2214

10 Apr 2024 11:21PM UTC coverage: 91.813% (-0.8%) from 92.623%
2214

push

Evergreen

web-flow
Add missing availability checks for SecCopyErrorMessageString (#7577)

This requires iOS 11.3 and we currently target iOS 11.

94848 of 175770 branches covered (53.96%)

7 of 22 new or added lines in 2 files covered. (31.82%)

1815 existing lines in 77 files now uncovered.

242945 of 264608 relevant lines covered (91.81%)

6136478.37 hits per line

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

76.07
/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,611,666✔
81
    return (size + page_size() - 1) & ~(page_size() - 1);
1,611,666✔
82
}
1,611,666✔
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,736✔
114
    std::ifstream file(fname);
2,736✔
115
    if (file) {
2,736✔
116
        std::stringstream buffer;
954✔
117
        buffer << file.rdbuf();
954✔
118

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

954✔
123
        if (std::regex_search(s, m, e)) {
954!
124
            std::string ibuf = m[1];
954✔
125
            return strtol(ibuf.c_str(), nullptr, 10);
954✔
126
        }
954✔
127
    }
1,782✔
128
    return PageReclaimGovernor::no_match;
1,782✔
129
}
1,782✔
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,368✔
140
        if (a == PageReclaimGovernor::no_match)
1,368✔
141
            return b;
×
142
        if (b == PageReclaimGovernor::no_match)
1,368✔
143
            return a;
732✔
144
        return std::min(a, b);
636✔
145
    }
636✔
146

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

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

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

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

189
    void report_target_result(int64_t target) override
190
    {
7,416✔
191
        m_target = target;
7,416✔
192
    }
7,416✔
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
{
747✔
219
    if (reclaimer_thread == nullptr) {
747✔
220
        reclaimer_thread = std::make_unique<std::thread>([] {
12✔
221
            while (!reclaimer_shutdown) {
3,459✔
222
                reclaim_pages();
3,447✔
223
                millisleep(1000);
3,447✔
224
            }
3,447✔
225
        });
12✔
226
    }
12✔
227
}
747✔
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
{
627✔
244
    if (!reclaimer_timer) {
627✔
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,969✔
255
            reclaim_pages();
3,969✔
256
        });
3,969✔
257
        dispatch_resume(reclaimer_timer);
12✔
258
    }
12✔
259
}
627✔
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,374✔
291
    UniqueLock lock(mapping_mutex);
1,374✔
292
    ensure_reclaimer_thread_runs();
1,374✔
293
    auto j = std::find_if(info.readers.begin(), info.readers.end(), [=](auto& reader) {
930✔
294
        return reader.reader_ID == reader_id;
420✔
295
    });
420✔
296
    if (j == info.readers.end()) {
1,374✔
297
        ReaderInfo i = {reader_id, info.current_version};
1,239✔
298
        info.readers.push_back(i);
1,239✔
299
    }
1,239✔
300
    else {
135✔
301
        j->version = info.current_version;
135✔
302
    }
135✔
303
    ++info.current_version;
1,374✔
304
}
1,374✔
305

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

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

324
namespace {
325
size_t collect_total_workload() // must be called under lock
326
{
7,416✔
327
    size_t total = 0;
7,416✔
328
    for (auto i = mappings_by_file.begin(); i != mappings_by_file.end(); ++i) {
7,569✔
329
        SharedFileInfo& info = *i->info;
153✔
330
        info.num_decrypted_pages = 0;
153✔
331
        for (auto it = info.mappings.begin(); it != info.mappings.end(); ++it) {
372✔
332
            info.num_decrypted_pages += (*it)->collect_decryption_count();
219✔
333
        }
219✔
334
        total += info.num_decrypted_pages;
153✔
335
    }
153✔
336
    return total;
7,416✔
337
}
7,416✔
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
{
141✔
359
    if (target == 0)
141✔
360
        target = 1;
×
361
    float load = 1.0f * decrypted_pages / target;
141✔
362
    float akku = 0.0f;
141✔
363
    for (const auto& e : control_table) {
147✔
364
        if (load <= e.base)
147✔
365
            break;
141✔
366
        akku += (load - e.base) * e.effort;
6✔
367
    }
6✔
368
    size_t work_limit = size_t(target * akku);
141✔
369
    return work_limit;
141✔
370
}
141✔
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
{
3✔
375
    auto oldest_version = info.current_version;
3✔
376
    for (const auto& e : info.readers) {
3!
377
        if (e.version < oldest_version) {
3!
378
            oldest_version = e.version;
3✔
379
        }
3✔
380
    }
3✔
381
    return oldest_version;
3✔
382
}
3✔
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
{
3✔
387
    uint64_t oldest_version = get_oldest_version(info);
3✔
388
    if (info.last_scanned_version < oldest_version || info.mappings.empty()) {
3!
389
        // locate the mapping matching the progress index. No such mapping may
3✔
390
        // exist, and if so, we'll update the index to the next mapping
3✔
391
        for (auto& e : info.mappings) {
6!
392
            auto start_index = e->get_start_index();
6✔
393
            if (info.progress_index < start_index) {
6!
394
                info.progress_index = start_index;
×
395
            }
×
396
            if (info.progress_index <= e->get_end_index()) {
6!
397
                e->reclaim_untouched(info.progress_index, work_limit);
6✔
398
                if (work_limit == 0)
6!
399
                    return;
×
400
            }
6✔
401
        }
6✔
402
        // if we get here, all mappings have been considered
3✔
403
        info.progress_index = 0;
3✔
404
        info.last_scanned_version = info.current_version;
3✔
405
        ++info.current_version;
3✔
406
    }
3✔
407
}
3✔
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
{
7,416✔
414
    size_t load;
7,416✔
415
    util::UniqueFunction<int64_t()> runnable;
7,416✔
416
    {
7,416✔
417
        UniqueLock lock(mapping_mutex);
7,416✔
418
        load = collect_total_workload();
7,416✔
419
        num_decrypted_pages = load;
7,416✔
420
        runnable = governor->current_target_getter(load * page_size());
7,416✔
421
    }
7,416✔
422
    // callback to governor defined function without mutex held
3,447✔
423
    int64_t target = PageReclaimGovernor::no_match;
7,416✔
424
    if (runnable) {
7,416✔
425
        target = runnable();
7,416✔
426
    }
7,416✔
427
    {
7,416✔
428
        UniqueLock lock(mapping_mutex);
7,416✔
429
        reclaimer_workload = 0;
7,416✔
430
        reclaimer_target = size_t(target / page_size());
7,416✔
431
        // Putting the target back into the govenor object will allow the govenor
3,447✔
432
        // to return a getter producing this value again next time it is called
3,447✔
433
        governor->report_target_result(target);
7,416✔
434

3,447✔
435
        if (target == PageReclaimGovernor::no_match) // temporarily disabled by governor returning no_match
7,416✔
436
            return;
×
437

3,447✔
438
        if (mappings_by_file.size() == 0)
7,416✔
439
            return;
7,275✔
440

120✔
441
        size_t work_limit = get_work_limit(load, reclaimer_target);
141✔
442
        reclaimer_workload = work_limit;
141✔
443
        if (file_reclaim_index >= mappings_by_file.size())
141✔
UNCOV
444
            file_reclaim_index = 0;
×
445

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

458

459
mapping_and_addr* find_mapping_for_addr(void* addr, size_t size)
460
{
1,360,863✔
461
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
1,414,614✔
462
        mapping_and_addr& m = mappings_by_addr[i];
58,836✔
463
        if (m.addr == addr && m.size == size)
58,836✔
464
            return &m;
5,085✔
465
        REALM_ASSERT(m.addr != addr);
53,751✔
466
    }
53,751✔
467

702,825✔
468
    return 0;
1,358,625✔
469
}
1,360,863✔
470
} // anonymous namespace
471

472
SharedFileInfo* get_file_info_for_file(File& file)
473
{
95,808✔
474
    LockGuard lock(mapping_mutex);
95,808✔
475
    File::UniqueID id = file.get_unique_id();
95,808✔
476
    std::vector<mappings_for_file>::iterator it;
95,808✔
477
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
105,060✔
478
        if (it->file_unique_id == id) {
9,567✔
479
            break;
315✔
480
        }
315✔
481
    }
9,567✔
482
    if (it == mappings_by_file.end())
95,808✔
483
        return nullptr;
95,493✔
484
    else
315✔
485
        return it->info.get();
315✔
486
}
95,808✔
487

488

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

1,761✔
496
    LockGuard lock(mapping_mutex);
3,060✔
497

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

1,761✔
500
    std::vector<mappings_for_file>::iterator it;
3,060✔
501
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
4,479✔
502
        if (it->file_unique_id == fuid) {
2,904✔
503
            break;
1,485✔
504
        }
1,485✔
505
    }
2,904✔
506

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

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

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

1,761✔
524
    try {
3,060✔
525
        mapping_and_addr m;
3,060✔
526
        m.addr = addr;
3,060✔
527
        m.size = size;
3,060✔
528
        m.mapping = std::make_shared<EncryptedFileMapping>(*it->info, file_offset, addr, size, file.access);
3,060✔
529
        mappings_by_addr.push_back(m); // can't throw due to reserve() above
3,060✔
530
        return m.mapping.get();
3,060✔
531
    }
3,060✔
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,060✔
541

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

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

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

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

578

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

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

594
void remove_encrypted_mapping(void* addr, size_t size)
595
{
3,054✔
596
    remove_mapping(addr, size);
3,054✔
597
}
3,054✔
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,745✔
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,745✔
661
    if (addr == MAP_FAILED) {
2,745✔
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,745✔
670
#endif
2,745✔
671
}
2,745✔
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
{
114,939✔
676
    _impl::SimulatedFailure::trigger_mmap(size);
114,939✔
677
    static_cast<void>(enc_key); // FIXME: Consider removing this parameter
114,939✔
678
#ifdef _WIN32
679
    REALM_ASSERT(false);
680
    return nullptr; // silence warning
681
#else
682
    auto prot = PROT_READ;
114,939✔
683
    if (access == File::access_ReadWrite)
114,939✔
684
        prot |= PROT_WRITE;
×
685
    auto addr = ::mmap(address_request, size, prot, MAP_SHARED | MAP_FIXED, fd, offset);
114,939✔
686
    if (addr != MAP_FAILED && addr != address_request) {
114,939✔
687
        throw std::runtime_error(get_errno_msg("mmap() failed: ", errno) +
×
688
                                 ", when mapping an already reserved memory area");
×
689
    }
×
690
    return addr;
114,939✔
691
#endif
114,939✔
692
}
114,939✔
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,371,636✔
716
    _impl::SimulatedFailure::trigger_mmap(size);
1,371,636✔
717
#if REALM_ENABLE_ENCRYPTION
1,371,636✔
718
    if (file.encryption_key) {
1,371,636✔
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,371,636✔
725
#else
726
    REALM_ASSERT(!file.encryption_key);
727
#endif
728
    {
1,371,636✔
729

690,870✔
730
#ifndef _WIN32
1,371,636✔
731
        int prot = PROT_READ;
1,371,636✔
732
        switch (file.access) {
1,371,636✔
733
            case File::access_ReadWrite:
1,219,278✔
734
                prot |= PROT_WRITE;
1,219,278✔
735
                break;
1,219,278✔
736
            case File::access_ReadOnly:
152,355✔
737
                break;
152,355✔
738
        }
1,371,552✔
739

690,789✔
740
        void* addr = ::mmap(nullptr, size, prot, MAP_SHARED, file.fd, offset);
1,371,552✔
741
        if (addr != MAP_FAILED)
1,371,552✔
742
            return addr;
1,372,038✔
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,371,636✔
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,357,020✔
880
#if REALM_ENABLE_ENCRYPTION
1,357,020✔
881
    {
1,357,020✔
882
        // first check the encrypted mappings
700,605✔
883
        LockGuard lock(mapping_mutex);
1,357,020✔
884
        if (mapping_and_addr* m = find_mapping_for_addr(addr, round_up_to_page_size(size))) {
1,357,020✔
885
            m->mapping->flush();
1,671✔
886
            m->mapping->sync();
1,671✔
887
            return;
1,671✔
888
        }
1,671✔
889
    }
1,355,349✔
890
#endif
1,355,349✔
891

699,717✔
892
    // not an encrypted mapping
699,717✔
893

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

699,717✔
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,355,349✔
916
    int retries_left = 1000;
1,355,349✔
917
    while (::msync(addr, size, MS_SYNC) != 0) {
1,355,349✔
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,355,349✔
925
}
1,355,349✔
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