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

realm / realm-core / 1852

20 Nov 2023 07:46PM UTC coverage: 91.656% (-0.04%) from 91.699%
1852

push

Evergreen

web-flow
Fix client reset cycle detection for PBS recovery errors (#7149)

Tracking that a client reset was in progress was done in the same write
transaction as the recovery operation, so if recovery failed the tracking was
rolled back too. This worked for FLX due to that codepath committing before
beginning recovery.

92266 of 169120 branches covered (0.0%)

31 of 31 new or added lines in 3 files covered. (100.0%)

155 existing lines in 15 files now uncovered.

231226 of 252275 relevant lines covered (91.66%)

6144671.4 hits per line

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

69.01
/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,077,012✔
87
    return (size + page_size() - 1) & ~(page_size() - 1);
4,077,012✔
88
}
4,077,012✔
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,256✔
125
    std::ifstream file(fname);
2,256✔
126
    if (file) {
2,256✔
127
        std::stringstream buffer;
927✔
128
        buffer << file.rdbuf();
927✔
129

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

927✔
134
        if (std::regex_search(s, m, e)) {
927!
135
            std::string ibuf = m[1];
927✔
136
            return strtol(ibuf.c_str(), nullptr, 10);
927✔
137
        }
927✔
138
    }
1,329✔
139
    return PageReclaimGovernor::no_match;
1,329✔
140
}
1,329✔
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,128✔
151
        if (a == PageReclaimGovernor::no_match)
1,128✔
152
            return b;
×
153
        if (b == PageReclaimGovernor::no_match)
1,128✔
154
            return a;
510✔
155
        return std::min(a, b);
618✔
156
    }
618✔
157

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

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

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

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

200
    void report_target_result(int64_t target) override
201
    {
6,099✔
202
        m_target = target;
6,099✔
203
    }
6,099✔
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
{
669✔
230
    if (reclaimer_thread == nullptr) {
669✔
231
        reclaimer_thread = std::make_unique<std::thread>([] {
12✔
232
            while (!reclaimer_shutdown) {
3,375✔
233
                reclaim_pages();
3,363✔
234
                millisleep(1000);
3,363✔
235
            }
3,363✔
236
        });
12✔
237
    }
12✔
238
}
669✔
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
{
684✔
255
    if (!reclaimer_timer) {
684✔
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,736✔
266
            reclaim_pages();
2,736✔
267
        });
2,736✔
268
        dispatch_resume(reclaimer_timer);
12✔
269
    }
12✔
270
}
684✔
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,353✔
302
    UniqueLock lock(mapping_mutex);
1,353✔
303
    ensure_reclaimer_thread_runs();
1,353✔
304
    auto j = std::find_if(info.readers.begin(), info.readers.end(), [=](auto& reader) {
912✔
305
        return reader.reader_ID == reader_id;
483✔
306
    });
483✔
307
    if (j == info.readers.end()) {
1,353✔
308
        ReaderInfo i = {reader_id, info.current_version};
1,236✔
309
        info.readers.push_back(i);
1,236✔
310
    }
1,236✔
311
    else {
117✔
312
        j->version = info.current_version;
117✔
313
    }
117✔
314
    ++info.current_version;
1,353✔
315
}
1,353✔
316

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

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

335
namespace {
336
size_t collect_total_workload() // must be called under lock
337
{
6,099✔
338
    size_t total = 0;
6,099✔
339
    for (auto i = mappings_by_file.begin(); i != mappings_by_file.end(); ++i) {
6,150✔
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) {
108✔
343
            info.num_decrypted_pages += (*it)->collect_decryption_count();
57✔
344
        }
57✔
345
        total += info.num_decrypted_pages;
51✔
346
    }
51✔
347
    return total;
6,099✔
348
}
6,099✔
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
{
45✔
370
    if (target == 0)
45✔
371
        target = 1;
×
372
    float load = 1.0f * decrypted_pages / target;
45✔
373
    float akku = 0.0f;
45✔
374
    for (const auto& e : control_table) {
45✔
375
        if (load <= e.base)
45✔
376
            break;
45✔
UNCOV
377
        akku += (load - e.base) * e.effort;
×
UNCOV
378
    }
×
379
    size_t work_limit = size_t(target * akku);
45✔
380
    return work_limit;
45✔
381
}
45✔
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
UNCOV
385
{
×
UNCOV
386
    auto oldest_version = info.current_version;
×
UNCOV
387
    for (const auto& e : info.readers) {
×
388
        if (e.version < oldest_version) {
×
389
            oldest_version = e.version;
×
390
        }
×
391
    }
×
UNCOV
392
    return oldest_version;
×
UNCOV
393
}
×
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)
UNCOV
397
{
×
UNCOV
398
    uint64_t oldest_version = get_oldest_version(info);
×
UNCOV
399
    if (info.last_scanned_version < oldest_version || info.mappings.empty()) {
×
400
        // locate the mapping matching the progress index. No such mapping may
401
        // exist, and if so, we'll update the index to the next mapping
UNCOV
402
        for (auto& e : info.mappings) {
×
UNCOV
403
            auto start_index = e->get_start_index();
×
UNCOV
404
            if (info.progress_index < start_index) {
×
405
                info.progress_index = start_index;
×
406
            }
×
UNCOV
407
            if (info.progress_index <= e->get_end_index()) {
×
UNCOV
408
                e->reclaim_untouched(info.progress_index, work_limit);
×
UNCOV
409
                if (work_limit == 0)
×
410
                    return;
×
UNCOV
411
            }
×
UNCOV
412
        }
×
413
        // if we get here, all mappings have been considered
UNCOV
414
        info.progress_index = 0;
×
UNCOV
415
        info.last_scanned_version = info.current_version;
×
UNCOV
416
        ++info.current_version;
×
UNCOV
417
    }
×
UNCOV
418
}
×
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
{
6,099✔
425
    size_t load;
6,099✔
426
    util::UniqueFunction<int64_t()> runnable;
6,099✔
427
    {
6,099✔
428
        UniqueLock lock(mapping_mutex);
6,099✔
429
        load = collect_total_workload();
6,099✔
430
        num_decrypted_pages = load;
6,099✔
431
        runnable = governor->current_target_getter(load * page_size());
6,099✔
432
    }
6,099✔
433
    // callback to governor defined function without mutex held
3,363✔
434
    int64_t target = PageReclaimGovernor::no_match;
6,099✔
435
    if (runnable) {
6,099✔
436
        target = runnable();
6,099✔
437
    }
6,099✔
438
    {
6,099✔
439
        UniqueLock lock(mapping_mutex);
6,099✔
440
        reclaimer_workload = 0;
6,099✔
441
        reclaimer_target = size_t(target / page_size());
6,099✔
442
        // Putting the target back into the govenor object will allow the govenor
3,363✔
443
        // to return a getter producing this value again next time it is called
3,363✔
444
        governor->report_target_result(target);
6,099✔
445

3,363✔
446
        if (target == PageReclaimGovernor::no_match) // temporarily disabled by governor returning no_match
6,099✔
447
            return;
×
448

3,363✔
449
        if (mappings_by_file.size() == 0)
6,099✔
450
            return;
6,054✔
451

27✔
452
        size_t work_limit = get_work_limit(load, reclaimer_target);
45✔
453
        reclaimer_workload = work_limit;
45✔
454
        if (file_reclaim_index >= mappings_by_file.size())
45✔
UNCOV
455
            file_reclaim_index = 0;
×
456

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

469

470
mapping_and_addr* find_mapping_for_addr(void* addr, size_t size)
471
{
3,718,899✔
472
    for (size_t i = 0; i < mappings_by_addr.size(); ++i) {
3,747,918✔
473
        mapping_and_addr& m = mappings_by_addr[i];
34,374✔
474
        if (m.addr == addr && m.size == size)
34,374✔
475
            return &m;
5,355✔
476
        REALM_ASSERT(m.addr != addr);
29,019✔
477
    }
29,019✔
478

1,879,980✔
479
    return 0;
3,716,130✔
480
}
3,718,899✔
481
} // anonymous namespace
482

483
SharedFileInfo* get_file_info_for_file(File& file)
484
{
140,118✔
485
    LockGuard lock(mapping_mutex);
140,118✔
486
#ifndef _WIN32
140,118✔
487
    File::UniqueID id = file.get_unique_id();
140,118✔
488
#endif
140,118✔
489
    std::vector<mappings_for_file>::iterator it;
140,118✔
490
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
140,646✔
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)
816✔
497
            break;
288✔
498
#endif
816✔
499
    }
816✔
500
    if (it == mappings_by_file.end())
140,118✔
501
        return nullptr;
139,830✔
502
    else
288✔
503
        return it->info.get();
288✔
504
}
140,118✔
505

506

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

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

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

1,590✔
523
    LockGuard lock(mapping_mutex);
3,444✔
524

1,590✔
525
    std::vector<mappings_for_file>::iterator it;
3,444✔
526
    for (it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
5,052✔
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)
3,021✔
532
            break;
1,413✔
533
#endif
3,021✔
534
    }
3,021✔
535

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

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

852✔
544
        FileDesc fd_duped;
2,031✔
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);
2,031✔
552

852✔
553
        if (fd_duped == -1) {
2,031✔
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;
2,031✔
558
        f.device = st.st_dev;
2,031✔
559
        f.inode = st.st_ino;
2,031✔
560
#endif // conditonal on _WIN32
2,031✔
561

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

1,590✔
569
    try {
3,444✔
570
        mapping_and_addr m;
3,444✔
571
        m.addr = addr;
3,444✔
572
        m.size = size;
3,444✔
573
        m.mapping = std::make_shared<EncryptedFileMapping>(*it->info, file_offset, addr, size, file.access);
3,444✔
574
        mappings_by_addr.push_back(m); // can't throw due to reserve() above
3,444✔
575
        return m.mapping.get();
3,444✔
576
    }
3,444✔
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,444✔
590

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

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

1,587✔
601
    for (std::vector<mappings_for_file>::iterator it = mappings_by_file.begin(); it != mappings_by_file.end(); ++it) {
6,804✔
602
        if (it->info->mappings.empty()) {
5,397✔
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) {
2,031✔
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
            }
2,031✔
612
#endif
2,031✔
613
            mappings_by_file.erase(it);
2,031✔
614
            break;
2,031✔
615
        }
2,031✔
616
    }
5,397✔
617
}
3,438✔
618
} // anonymous namespace
619

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

635

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

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

651
void remove_encrypted_mapping(void* addr, size_t size)
652
{
3,438✔
653
    remove_mapping(addr, size);
3,438✔
654
}
3,438✔
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,156✔
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,156✔
718
    if (addr == MAP_FAILED) {
3,156✔
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,156✔
727
#endif
3,156✔
728
}
3,156✔
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
{
159,525✔
733
    _impl::SimulatedFailure::trigger_mmap(size);
159,525✔
734
    static_cast<void>(enc_key); // FIXME: Consider removing this parameter
159,525✔
735
#ifdef _WIN32
736
    REALM_ASSERT(false);
737
    return nullptr; // silence warning
738
#else
739
    auto prot = PROT_READ;
159,525✔
740
    if (access == File::access_ReadWrite)
159,525✔
741
        prot |= PROT_WRITE;
×
742
    auto addr = ::mmap(address_request, size, prot, MAP_SHARED | MAP_FIXED, fd, offset);
159,525✔
743
    if (addr != MAP_FAILED && addr != address_request) {
159,525✔
744
        throw std::runtime_error(get_errno_msg("mmap() failed: ", errno) +
×
745
                                 ", when mapping an already reserved memory area");
×
746
    }
×
747
    return addr;
159,525✔
748
#endif
159,525✔
749
}
159,525✔
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,138,531✔
773
    _impl::SimulatedFailure::trigger_mmap(size);
3,138,531✔
774
#if REALM_ENABLE_ENCRYPTION
3,138,531✔
775
    if (file.encryption_key) {
3,138,531✔
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,138,531✔
782
#else
783
    REALM_ASSERT(!file.encryption_key);
784
#endif
785
    {
3,138,531✔
786

1,572,405✔
787
#ifndef _WIN32
3,138,531✔
788
        int prot = PROT_READ;
3,138,531✔
789
        switch (file.access) {
3,138,531✔
790
            case File::access_ReadWrite:
2,930,160✔
791
                prot |= PROT_WRITE;
2,930,160✔
792
                break;
2,930,160✔
793
            case File::access_ReadOnly:
208,371✔
794
                break;
208,371✔
795
        }
3,138,510✔
796

1,572,396✔
797
        void* addr = ::mmap(nullptr, size, prot, MAP_SHARED, file.fd, offset);
3,138,510✔
798
        if (addr != MAP_FAILED)
3,138,510✔
799
            return addr;
3,138,813✔
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,138,531✔
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,714,813✔
937
#if REALM_ENABLE_ENCRYPTION
3,714,813✔
938
    {
3,714,813✔
939
        // first check the encrypted mappings
1,878,012✔
940
        LockGuard lock(mapping_mutex);
3,714,813✔
941
        if (mapping_and_addr* m = find_mapping_for_addr(addr, round_up_to_page_size(size))) {
3,714,813✔
942
            m->mapping->flush();
1,590✔
943
            m->mapping->sync();
1,590✔
944
            return;
1,590✔
945
        }
1,590✔
946
    }
3,713,223✔
947
#endif
3,713,223✔
948

1,877,178✔
949
    // not an encrypted mapping
1,877,178✔
950

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

1,877,178✔
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,713,223✔
973
    int retries_left = 1000;
3,713,223✔
974
    while (::msync(addr, size, MS_SYNC) != 0) {
3,713,223✔
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,713,223✔
982
}
3,713,223✔
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