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

realm / realm-core / 1737

06 Oct 2023 12:38PM UTC coverage: 91.567% (-0.02%) from 91.591%
1737

push

Evergreen

web-flow
Enable `REALM_ENABLE_GEOSPATIAL`, geoWithin on points for SPM Installation (#7032)

94320 of 173524 branches covered (0.0%)

230508 of 251736 relevant lines covered (91.57%)

6802705.57 hits per line

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

81.25
/src/realm/util/file.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 <climits>
20
#include <limits>
21
#include <algorithm>
22
#include <vector>
23

24
#include <cerrno>
25
#include <cstring>
26
#include <cstddef>
27
#include <cstdio>
28
#include <cstdlib>
29
#include <iostream>
30
#include <fcntl.h>
31

32
#ifndef _WIN32
33
#include <unistd.h>
34
#include <sys/mman.h>
35
#include <sys/file.h> // BSD / Linux flock()
36
#include <sys/statvfs.h>
37
#endif
38

39
#include <realm/exceptions.hpp>
40
#include <realm/unicode.hpp>
41
#include <realm/util/errno.hpp>
42
#include <realm/util/file_mapper.hpp>
43
#include <realm/util/safe_int_ops.hpp>
44
#include <realm/util/features.h>
45
#include <realm/util/file.hpp>
46

47
using namespace realm::util;
48

49
namespace {
50
size_t get_page_size()
51
{
24✔
52
#ifdef _WIN32
53
    SYSTEM_INFO sysinfo;
54
    GetNativeSystemInfo(&sysinfo);
55
    // DWORD size = sysinfo.dwPageSize;
56
    // On windows we use the allocation granularity instead
57
    DWORD size = sysinfo.dwAllocationGranularity;
58
#else
59
    long size = sysconf(_SC_PAGESIZE);
24✔
60
#endif
24✔
61
    REALM_ASSERT(size > 0 && size % 4096 == 0);
24✔
62
    return static_cast<size_t>(size);
24✔
63
}
24✔
64

65
// This variable exists such that page_size() can return the page size without having to make any system calls.
66
// It could also have been a static local variable, but Valgrind/Helgrind gives a false error on that.
67
size_t cached_page_size = get_page_size();
68

69
bool for_each_helper(const std::string& path, const std::string& dir, realm::util::File::ForEachHandler& handler)
70
{
30✔
71
    using File = realm::util::File;
30✔
72
    realm::util::DirScanner ds{path}; // Throws
30✔
73
    std::string name;
30✔
74
    while (ds.next(name)) {                              // Throws
90✔
75
        std::string subpath = File::resolve(name, path); // Throws
60✔
76
        bool go_on;
60✔
77
        if (File::is_dir(subpath)) {                           // Throws
60✔
78
            std::string subdir = File::resolve(name, dir);     // Throws
24✔
79
            go_on = for_each_helper(subpath, subdir, handler); // Throws
24✔
80
        }
24✔
81
        else {
36✔
82
            go_on = handler(name, dir); // Throws
36✔
83
        }
36✔
84
        if (!go_on)
60✔
85
            return false;
×
86
    }
60✔
87
    return true;
30✔
88
}
30✔
89

90
#ifdef _WIN32
91

92
std::string get_last_error_msg(const char* prefix, DWORD err)
93
{
94
    std::string buffer;
95
    buffer.append(prefix);
96
    size_t offset = buffer.size();
97
    size_t max_msg_size = 1024;
98
    buffer.resize(offset + max_msg_size);
99
    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
100
    DWORD language_id = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
101
    DWORD size =
102
        FormatMessageA(flags, 0, err, language_id, buffer.data() + offset, static_cast<DWORD>(max_msg_size), 0);
103
    if (0 < size)
104
        return buffer;
105
    buffer.resize(offset);
106
    buffer.append("Unknown error");
107
    return buffer;
108
}
109

110
struct WindowsFileHandleHolder {
111
    WindowsFileHandleHolder() = default;
112
    explicit WindowsFileHandleHolder(HANDLE h)
113
        : handle(h)
114
    {
115
    }
116

117
    WindowsFileHandleHolder(WindowsFileHandleHolder&&) = delete;
118
    WindowsFileHandleHolder(const WindowsFileHandleHolder&) = delete;
119
    WindowsFileHandleHolder& operator=(WindowsFileHandleHolder&&) = delete;
120
    WindowsFileHandleHolder& operator=(const WindowsFileHandleHolder&) = delete;
121

122
    operator HANDLE() const noexcept
123
    {
124
        return handle;
125
    }
126

127
    ~WindowsFileHandleHolder()
128
    {
129
        if (handle != INVALID_HANDLE_VALUE) {
130
            ::CloseHandle(handle);
131
        }
132
    }
133

134
    HANDLE handle = INVALID_HANDLE_VALUE;
135
};
136

137
#endif
138

139
#if REALM_HAVE_STD_FILESYSTEM
140
using std::filesystem::u8path;
141

142
void throwIfCreateDirectoryError(std::error_code error, const std::string& path)
143
{
144
    if (!error)
145
        return;
146

147
    // create_directory doesn't raise an error if the path already exists
148
    using std::errc;
149
    if (error == errc::permission_denied || error == errc::read_only_file_system) {
150
        throw realm::FileAccessError(realm::ErrorCodes::PermissionDenied, error.message(), path);
151
    }
152
    else {
153
        throw realm::FileAccessError(realm::ErrorCodes::FileOperationFailed, error.message(), path);
154
    }
155
}
156

157
void throwIfFileError(std::error_code error, const std::string& path)
158
{
159
    if (!error)
160
        return;
161

162
    using std::errc;
163
    if (error == errc::permission_denied || error == errc::read_only_file_system ||
164
        error == errc::device_or_resource_busy || error == errc::operation_not_permitted ||
165
        error == errc::file_exists || error == errc::directory_not_empty) {
166
        throw realm::FileAccessError(realm::ErrorCodes::PermissionDenied, error.message(), path);
167
    }
168
    else {
169
        throw realm::FileAccessError(realm::ErrorCodes::FileOperationFailed, error.message(), path);
170
    }
171
}
172
#endif
173

174
} // anonymous namespace
175

176

177
namespace realm::util {
178
namespace {
179

180
/// Thrown if create_Always was specified and the file did already
181
/// exist.
182
class Exists : public FileAccessError {
183
public:
184
    Exists(const std::string& msg, const std::string& path)
185
        : FileAccessError(ErrorCodes::FileAlreadyExists, msg, path)
186
    {
48✔
187
    }
48✔
188
};
189

190
} // anonymous namespace
191

192

193
bool try_make_dir(const std::string& path)
194
{
292,389✔
195
#if REALM_HAVE_STD_FILESYSTEM
196
    std::error_code error;
197
    bool result = std::filesystem::create_directory(u8path(path), error);
198
    throwIfCreateDirectoryError(error, path);
199
    return result;
200
#else // POSIX
201
    if (::mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
292,389✔
202
        return true;
114,306✔
203

86,961✔
204
    int err = errno; // Eliminate any risk of clobbering
178,083✔
205
    if (err == EEXIST)
178,083✔
206
        return false;
178,080✔
207

3✔
208
    auto msg = format_errno("Failed to create directory at '%2': %1", err, path);
6✔
209

3✔
210
    switch (err) {
6✔
211
        case EACCES:
3✔
212
        case EROFS:
6✔
213
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
214
        default:
3✔
215
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
×
216
    }
6✔
217
#endif
6✔
218
}
6✔
219

220

221
void make_dir(const std::string& path)
222
{
168✔
223
    if (try_make_dir(path)) // Throws
168✔
224
        return;
150✔
225
    throw Exists(format_errno("Failed to create directory at '%2': %1", EEXIST, path), path);
18✔
226
}
18✔
227

228

229
void make_dir_recursive(std::string path)
230
{
201✔
231
#if REALM_HAVE_STD_FILESYSTEM
232
    std::error_code error;
233
    std::filesystem::create_directories(u8path(path), error);
234
    throwIfCreateDirectoryError(error, path);
235
#else
236
    // Skip the first separator as we're assuming an absolute path
237
    size_t pos = path.find_first_of("/\\");
201✔
238
    if (pos == std::string::npos)
201✔
239
        return;
×
240
    pos += 1;
201✔
241

242
    while (pos < path.size()) {
2,010✔
243
        auto sep = path.find_first_of("/\\", pos);
1,809✔
244
        char c = 0;
1,809✔
245
        if (sep < path.size()) {
1,809✔
246
            c = path[sep];
1,809✔
247
            path[sep] = 0;
1,809✔
248
        }
1,809✔
249
        try_make_dir(path);
1,809✔
250
        if (sep < path.size())
1,809✔
251
            path[sep] = c;
1,809✔
252
        pos = sep + 1;
1,809✔
253
    }
1,809✔
254
#endif
201✔
255
}
201✔
256

257

258
void remove_dir(const std::string& path)
259
{
10,176✔
260
    if (try_remove_dir(path)) // Throws
10,176✔
261
        return;
10,158✔
262
    int err = ENOENT;
18✔
263
    std::string msg = format_errno("Failed to remove directory '%2': %1", err, path);
18✔
264
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
18✔
265
}
18✔
266

267

268
bool try_remove_dir(const std::string& path)
269
{
138,666✔
270
#if REALM_HAVE_STD_FILESYSTEM
271
    std::error_code error;
272
    bool result = std::filesystem::remove(u8path(path), error);
273
    throwIfFileError(error, path);
274
    return result;
275
#else // POSIX
276
    if (::rmdir(path.c_str()) == 0)
138,666✔
277
        return true;
138,546✔
278

60✔
279
    int err = errno; // Eliminate any risk of clobbering
120✔
280
    if (err == ENOENT)
120✔
281
        return false;
117✔
282

3✔
283
    std::string msg = format_errno("Failed to remove directory '%2': %1", err, path);
6✔
284
    switch (err) {
6✔
285
        case EACCES:
3✔
286
        case EROFS:
6✔
287
        case EBUSY:
6✔
288
        case EPERM:
6✔
289
        case EEXIST:
6✔
290
        case ENOTEMPTY:
6✔
291
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
292
        default:
3✔
293
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
×
294
    }
6✔
295
#endif
6✔
296
}
6✔
297

298

299
bool try_remove_dir_recursive(const std::string& path)
300
{
128,478✔
301
#if REALM_HAVE_STD_FILESYSTEM
302
    std::error_code error;
303
    auto removed_count = std::filesystem::remove_all(u8path(path), error);
304
    throwIfFileError(error, path);
305
    return removed_count > 0;
306
#else
307
    {
128,478✔
308
        bool allow_missing = true;
128,478✔
309
        DirScanner ds{path, allow_missing}; // Throws
128,478✔
310
        std::string name;
128,478✔
311
        while (ds.next(name)) {                              // Throws
372,069✔
312
            std::string subpath = File::resolve(name, path); // Throws
243,591✔
313
            if (File::is_dir(subpath)) {                     // Throws
243,591✔
314
                try_remove_dir_recursive(subpath);           // Throws
76,722✔
315
            }
76,722✔
316
            else {
166,869✔
317
                File::remove(subpath); // Throws
166,869✔
318
            }
166,869✔
319
        }
243,591✔
320
    }
128,478✔
321
    return try_remove_dir(path); // Throws
128,478✔
322
#endif
128,478✔
323
}
128,478✔
324

325

326
std::string make_temp_dir()
327
{
50,019✔
328
#ifdef _WIN32 // Windows version
329
    std::filesystem::path temp = std::filesystem::temp_directory_path();
330

331
    wchar_t buffer[MAX_PATH];
332
    std::filesystem::path path;
333
    for (;;) {
334
        if (GetTempFileNameW(temp.c_str(), L"rlm", 0, buffer) == 0) {
335
            DWORD error = GetLastError();
336
            throw SystemError(error, get_last_error_msg("GetTempFileName() failed: ", error));
337
        }
338
        path = buffer;
339
        std::filesystem::remove(path);
340

341
        std::error_code error;
342
        std::filesystem::create_directory(path, error);
343
        if (error && error != std::errc::file_exists) {
344
            throw SystemError(error, util::format("Failed to create temporary directory: %1", error.message()));
345
        }
346
        break;
347
    }
348
    return path.u8string();
349

350
#else // POSIX.1-2008 version
351

24,699✔
352
#if REALM_ANDROID
353
    std::string buffer = "/data/local/tmp/realm_XXXXXX";
354
#else
355
    char* tmp_dir_env = getenv("TMPDIR");
50,019✔
356
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
50,019✔
357
    if (!buffer.empty() && buffer.back() != '/') {
50,019✔
358
        buffer += "/";
50,019✔
359
    }
50,019✔
360
    buffer += "realm_XXXXXX";
50,019✔
361
#endif
50,019✔
362

24,699✔
363
    if (mkdtemp(buffer.data()) == 0) {
50,019✔
364
        int err = errno;
×
365
        throw SystemError(err, util::format("Failed to create temporary directory: %1", err)); // LCOV_EXCL_LINE
×
366
    }
×
367
    return buffer;
50,019✔
368
#endif
50,019✔
369
}
50,019✔
370

371
std::string make_temp_file(const char* prefix)
372
{
37,932✔
373
#ifdef _WIN32 // Windows version
374
    std::filesystem::path temp = std::filesystem::temp_directory_path();
375

376
    wchar_t buffer[MAX_PATH];
377
    if (GetTempFileNameW(temp.c_str(), L"rlm", 0, buffer) == 0) {
378
        DWORD error = GetLastError();
379
        throw SystemError(error, get_last_error_msg("GetTempFileName() failed: ", error));
380
    }
381

382
    return std::filesystem::path(buffer).u8string();
383

384
#else // POSIX.1-2008 version
385

3✔
386
#if REALM_ANDROID
387
    std::string base_dir = "/data/local/tmp/";
388
#else
389
    char* tmp_dir_env = getenv("TMPDIR");
37,932✔
390
    std::string base_dir = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
37,932✔
391
    if (!base_dir.empty() && base_dir[base_dir.length() - 1] != '/') {
37,932✔
392
        base_dir = base_dir + "/";
37,932✔
393
    }
37,932✔
394
#endif
37,932✔
395
    std::string tmp = base_dir + prefix + std::string("_XXXXXX") + std::string("\0", 1);
37,932✔
396
    std::unique_ptr<char[]> buffer = std::make_unique<char[]>(tmp.size()); // Throws
37,932✔
397
    memcpy(buffer.get(), tmp.c_str(), tmp.size());
37,932✔
398
    char* filename = buffer.get();
37,932✔
399
    auto fd = mkstemp(filename);
37,932✔
400
    if (fd == -1) {
37,932✔
401
        throw std::system_error(errno, std::system_category(), "mkstemp() failed"); // LCOV_EXCL_LINE
×
402
    }
×
403
    close(fd);
37,932✔
404
    return std::string(filename);
37,932✔
405
#endif
37,932✔
406
}
37,932✔
407

408
size_t page_size()
409
{
8,812,998✔
410
    return cached_page_size;
8,812,998✔
411
}
8,812,998✔
412

413
void File::open_internal(const std::string& path, AccessMode a, CreateMode c, int flags, bool* success)
414
{
578,268✔
415
    REALM_ASSERT_RELEASE(!is_attached());
578,268✔
416
    m_path = path; // for error reporting and debugging
578,268✔
417

180,555✔
418
#ifdef _WIN32 // Windows version
419

420
    DWORD desired_access = GENERIC_READ;
421
    switch (a) {
422
        case access_ReadOnly:
423
            break;
424
        case access_ReadWrite:
425
            if (flags & flag_Append) {
426
                desired_access = FILE_APPEND_DATA;
427
            }
428
            else {
429
                desired_access |= GENERIC_WRITE;
430
            }
431
            break;
432
    }
433
    // FIXME: Should probably be zero if we are called on behalf of a
434
    // Group instance that is not managed by a SharedGroup instance,
435
    // since in this case concurrenct access is prohibited anyway.
436
    DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
437
    DWORD creation_disposition = 0;
438
    switch (c) {
439
        case create_Auto:
440
            creation_disposition = flags & flag_Trunc ? CREATE_ALWAYS : OPEN_ALWAYS;
441
            break;
442
        case create_Never:
443
            creation_disposition = flags & flag_Trunc ? TRUNCATE_EXISTING : OPEN_EXISTING;
444
            break;
445
        case create_Must:
446
            creation_disposition = CREATE_NEW;
447
            break;
448
    }
449
    DWORD flags_and_attributes = 0;
450
    HANDLE handle = CreateFile2(u8path(path).c_str(), desired_access, share_mode, creation_disposition, nullptr);
451
    if (handle != INVALID_HANDLE_VALUE) {
452
        m_fd = handle;
453
        m_have_lock = false;
454
        if (success)
455
            *success = true;
456
        return;
457
    }
458

459
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
460
    if (success && err == ERROR_FILE_EXISTS && c == create_Must) {
461
        *success = false;
462
        return;
463
    }
464
    if (success && err == ERROR_FILE_NOT_FOUND && c == create_Never) {
465
        *success = false;
466
        return;
467
    }
468
    std::string msg = get_last_error_msg("CreateFile() failed: ", err);
469
    switch (err) {
470
        case ERROR_SHARING_VIOLATION:
471
        case ERROR_ACCESS_DENIED:
472
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, int(err));
473
        case ERROR_FILE_NOT_FOUND:
474
        case ERROR_PATH_NOT_FOUND:
475
            throw FileAccessError(ErrorCodes::FileNotFound, msg, path, int(err));
476
        case ERROR_FILE_EXISTS:
477
            throw Exists(msg, path);
478
        default:
479
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, int(err));
480
    }
481

482
#else // POSIX version
483

180,555✔
484
    int flags2 = 0;
578,268✔
485
    switch (a) {
578,268✔
486
        case access_ReadOnly:
3,204✔
487
            flags2 = O_RDONLY;
3,204✔
488
            break;
3,204✔
489
        case access_ReadWrite:
575,067✔
490
            flags2 = O_RDWR;
575,067✔
491
            break;
575,067✔
492
    }
578,265✔
493
    switch (c) {
578,265✔
494
        case create_Auto:
551,043✔
495
            flags2 |= O_CREAT;
551,043✔
496
            break;
551,043✔
497
        case create_Never:
26,754✔
498
            break;
26,754✔
499
        case create_Must:
468✔
500
            flags2 |= O_CREAT | O_EXCL;
468✔
501
            break;
468✔
502
    }
578,265✔
503
    if (flags & flag_Trunc)
578,265✔
504
        flags2 |= O_TRUNC;
210,468✔
505
    if (flags & flag_Append)
578,265✔
506
        flags2 |= O_APPEND;
414✔
507
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
578,265✔
508
    if (0 <= fd) {
578,265✔
509
        m_fd = fd;
578,157✔
510
        m_have_lock = false;
578,157✔
511
        if (success)
578,157✔
512
            *success = true;
×
513
        return;
578,157✔
514
    }
578,157✔
515

57✔
516
    int err = errno; // Eliminate any risk of clobbering
108✔
517
    if (success && err == EEXIST && c == create_Must) {
108!
518
        *success = false;
×
519
        return;
×
520
    }
×
521
    if (success && err == ENOENT && c == create_Never) {
108!
522
        *success = false;
×
523
        return;
×
524
    }
×
525
    std::string msg = format_errno("Failed to open file at path '%2': %1", err, path);
108✔
526
    switch (err) {
108✔
527
        case EACCES:
6✔
528
        case EPERM:
6✔
529
        case EROFS:
6✔
530
        case ETXTBSY:
6✔
531
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
532
        case ENOENT:
36✔
533
            if (c != create_Never)
36✔
534
                msg = util::format("Failed to open file at path '%1': parent directory does not exist", path);
18✔
535
            throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
36✔
536
        case EEXIST:
36✔
537
            throw Exists(msg, path);
36✔
538
        case ENOTDIR:
3✔
539
            msg = format("Failed to open file at path '%1': parent path is not a directory", path);
×
540
            [[fallthrough]];
×
541
        default:
36✔
542
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
36✔
543
    }
108✔
544

57✔
545
#endif
108✔
546
}
108✔
547

548

549
void File::close() noexcept
550
{
1,120,425✔
551
#ifdef _WIN32 // Windows version
552

553
    if (!m_fd)
554
        return;
555
    if (m_have_lock)
556
        unlock();
557

558
    BOOL r = CloseHandle(m_fd);
559
    REALM_ASSERT_RELEASE(r);
560
    m_fd = nullptr;
561

562
#else // POSIX version
563

344,571✔
564
    if (m_fd < 0)
1,120,425✔
565
        return;
542,301✔
566
    if (m_have_lock)
578,124✔
567
        unlock();
657✔
568
    int r = ::close(m_fd);
578,124✔
569
    REALM_ASSERT_RELEASE(r == 0);
578,124✔
570
    m_fd = -1;
578,124✔
571

180,471✔
572
#endif
578,124✔
573
}
578,124✔
574

575
size_t File::read_static(FileDesc fd, char* data, size_t size)
576
{
504,126✔
577
#ifdef _WIN32 // Windows version
578
    char* const data_0 = data;
579
    while (0 < size) {
580
        DWORD n = std::numeric_limits<DWORD>::max();
581
        if (int_less_than(size, n))
582
            n = static_cast<DWORD>(size);
583
        DWORD r = 0;
584
        if (!ReadFile(fd, data, n, &r, 0))
585
            goto error;
586
        if (r == 0)
587
            break;
588
        REALM_ASSERT_RELEASE(r <= n);
589
        size -= size_t(r);
590
        data += size_t(r);
591
    }
592
    return data - data_0;
593

594
error:
595
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
596
    throw SystemError(int(err), "ReadFile() failed");
597

598
#else // POSIX version
599

261,996✔
600
    char* const data_0 = data;
504,126✔
601
    while (0 < size) {
1,006,902✔
602
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
262,104✔
603
        size_t n = std::min(size, size_t(SSIZE_MAX));
504,339✔
604
        ssize_t r = ::read(fd, data, n);
504,339✔
605
        if (r == 0)
504,339✔
606
            break;
1,563✔
607
        if (r < 0)
502,776✔
608
            goto error; // LCOV_EXCL_LINE
×
609
        REALM_ASSERT_RELEASE(size_t(r) <= n);
502,776✔
610
        size -= size_t(r);
502,776✔
611
        data += size_t(r);
502,776✔
612
    }
502,776✔
613
    return data - data_0;
504,126✔
614

615
error:
×
616
    // LCOV_EXCL_START
617
    throw SystemError(errno, "read() failed");
×
618
// LCOV_EXCL_STOP
261,996✔
619
#endif
504,126✔
620
}
504,126✔
621

622

623
size_t File::read(char* data, size_t size)
624
{
2,103✔
625
    REALM_ASSERT_RELEASE(is_attached());
2,103✔
626

936✔
627
    if (m_encryption_key) {
2,103✔
628
        uint64_t pos_original = File::get_file_pos(m_fd);
×
629
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
×
630
        size_t pos = size_t(pos_original);
×
631
        Map<char> read_map(*this, access_ReadOnly, static_cast<size_t>(pos + size));
×
632
        realm::util::encryption_read_barrier(read_map, pos, size);
×
633
        memcpy(data, read_map.get_addr() + pos, size);
×
634
        uint64_t cur = File::get_file_pos(m_fd);
×
635
        seek_static(m_fd, cur + size);
×
636
        return read_map.get_size() - pos;
×
637
    }
×
638

936✔
639
    return read_static(m_fd, data, size);
2,103✔
640
}
2,103✔
641

642
void File::write_static(FileDesc fd, const char* data, size_t size)
643
{
272,232✔
644
#ifdef _WIN32
645
    while (0 < size) {
646
        DWORD n = std::numeric_limits<DWORD>::max();
647
        if (int_less_than(size, n))
648
            n = static_cast<DWORD>(size);
649
        DWORD r = 0;
650
        if (!WriteFile(fd, data, n, &r, 0))
651
            goto error;
652
        REALM_ASSERT_RELEASE(r == n); // Partial writes are not possible.
653
        size -= size_t(r);
654
        data += size_t(r);
655
    }
656
    return;
657

658
error:
659
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
660
    if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
661
        std::string msg = get_last_error_msg("WriteFile() failed: ", err);
662
        throw OutOfDiskSpace(msg);
663
    }
664
    throw SystemError(err, "WriteFile() failed");
665
#else
666
    while (0 < size) {
544,134✔
667
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
130,023✔
668
        size_t n = std::min(size, size_t(SSIZE_MAX));
271,902✔
669
        ssize_t r = ::write(fd, data, n);
271,902✔
670
        if (r < 0)
271,902✔
671
            goto error; // LCOV_EXCL_LINE
×
672
        REALM_ASSERT_RELEASE(r != 0);
271,902✔
673
        REALM_ASSERT_RELEASE(size_t(r) <= n);
271,902✔
674
        size -= size_t(r);
271,902✔
675
        data += size_t(r);
271,902✔
676
    }
271,902✔
677
    return;
272,232✔
678

679
error:
×
680
    // LCOV_EXCL_START
681
    int err = errno; // Eliminate any risk of clobbering
×
682
    auto msg = format_errno("write() failed: %1", err);
×
683
    if (err == ENOSPC || err == EDQUOT) {
×
684
        throw OutOfDiskSpace(msg);
×
685
    }
×
686
    throw SystemError(err, msg);
×
687
    // LCOV_EXCL_STOP
688

689
#endif
×
690
}
×
691

692
void File::write(const char* data, size_t size)
693
{
57,129✔
694
    REALM_ASSERT_RELEASE(is_attached());
57,129✔
695

27,777✔
696
    if (m_encryption_key) {
57,129✔
697
        uint64_t pos_original = get_file_pos(m_fd);
1,455✔
698
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
1,455✔
699
        size_t pos = size_t(pos_original);
1,455✔
700
        Map<char> write_map(*this, access_ReadWrite, static_cast<size_t>(pos + size));
1,455✔
701
        realm::util::encryption_read_barrier(write_map, pos, size);
1,455✔
702
        memcpy(write_map.get_addr() + pos, data, size);
1,455✔
703
        realm::util::encryption_write_barrier(write_map, pos, size);
1,455✔
704
        uint64_t cur = get_file_pos(m_fd);
1,455✔
705
        seek(cur + size);
1,455✔
706
        return;
1,455✔
707
    }
1,455✔
708

27,204✔
709
    write_static(m_fd, data, size);
55,674✔
710
}
55,674✔
711

712
uint64_t File::get_file_pos(FileDesc fd)
713
{
721,491✔
714
#ifdef _WIN32
715
    LONG high_dword = 0;
716
    LARGE_INTEGER li;
717
    LARGE_INTEGER res;
718
    li.QuadPart = 0;
719
    bool ok = SetFilePointerEx(fd, li, &res, FILE_CURRENT);
720
    if (!ok)
721
        throw SystemError(GetLastError(), "SetFilePointer() failed");
722

723
    return uint64_t(res.QuadPart);
724
#else
725
    auto pos = lseek(fd, 0, SEEK_CUR);
721,491✔
726
    if (pos < 0) {
721,491✔
727
        throw SystemError(errno, "lseek() failed");
×
728
    }
×
729
    return uint64_t(pos);
721,491✔
730
#endif
721,491✔
731
}
721,491✔
732

733
File::SizeType File::get_size_static(const std::string& path)
734
{
1,401✔
735
    File f(path);
1,401✔
736
    return f.get_size();
1,401✔
737
}
1,401✔
738

739
File::SizeType File::get_size_static(FileDesc fd)
740
{
3,639,450✔
741
#ifdef _WIN32
742
    LARGE_INTEGER large_int;
743
    if (GetFileSizeEx(fd, &large_int)) {
744
        File::SizeType size;
745
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
746
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
747

748
        return size;
749
    }
750
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
751

752
#else // POSIX version
753

1,850,628✔
754
    struct stat statbuf;
3,639,450✔
755
    if (::fstat(fd, &statbuf) == 0) {
3,639,723✔
756
        SizeType size;
3,639,723✔
757
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
3,639,723✔
758
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
759

1,850,760✔
760
        return size;
3,639,723✔
761
    }
3,639,723✔
762
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
763

2,147,483,647✔
764
#endif
4,294,967,294✔
765
}
4,294,967,294✔
766

767
File::SizeType File::get_size() const
768
{
3,636,360✔
769
    REALM_ASSERT_RELEASE(is_attached());
3,636,360✔
770
    File::SizeType size = get_size_static(m_fd);
3,636,360✔
771

1,849,224✔
772
    if (m_encryption_key) {
3,636,360✔
773
        File::SizeType ret_size = encrypted_size_to_data_size(size);
3,489✔
774
        return ret_size;
3,489✔
775
    }
3,489✔
776
    else
3,632,871✔
777
        return size;
3,632,871✔
778
}
3,636,360✔
779

780

781
void File::resize(SizeType size)
782
{
321,591✔
783
    REALM_ASSERT_RELEASE(is_attached());
321,591✔
784

55,242✔
785
#ifdef _WIN32 // Windows version
786

787
    // Save file position
788
    SizeType p = get_file_pos(m_fd);
789

790
    if (m_encryption_key)
791
        size = data_size_to_encrypted_size(size);
792

793
    // Windows docs say "it is not an error to set the file pointer to a position beyond the end of the file."
794
    // so seeking with SetFilePointerEx() will not error out even if there is no disk space left.
795
    // In this scenario though, the following call to SedEndOfFile() will fail if there is no disk space left.
796
    seek(size);
797

798
    if (!SetEndOfFile(m_fd)) {
799
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
800
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
801
            std::string msg = get_last_error_msg("SetEndOfFile() failed: ", err);
802
            throw OutOfDiskSpace(msg);
803
        }
804
        throw SystemError(int(err), "SetEndOfFile() failed");
805
    }
806

807
    // Restore file position
808
    seek(p);
809

810
#else // POSIX version
811

55,242✔
812
    if (m_encryption_key)
321,591✔
813
        size = data_size_to_encrypted_size(size);
×
814

55,242✔
815
    off_t size2;
321,591✔
816
    if (int_cast_with_overflow_detect(size, size2))
321,591✔
817
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
818

55,242✔
819
    // POSIX specifies that introduced bytes read as zero. This is not
55,242✔
820
    // required by File::resize().
55,242✔
821
    if (::ftruncate(m_fd, size2) != 0) {
321,591✔
822
        int err = errno; // Eliminate any risk of clobbering
×
823
        auto msg = format_errno("ftruncate() failed: %1", err);
×
824
        if (err == ENOSPC || err == EDQUOT) {
×
825
            throw OutOfDiskSpace(msg);
×
826
        }
×
827
        throw SystemError(err, msg);
×
828
    }
×
829

55,242✔
830
#endif
321,591✔
831
}
321,591✔
832

833

834
void File::prealloc(size_t size)
835
{
234,324✔
836
    REALM_ASSERT_RELEASE(is_attached());
234,324✔
837

123,612✔
838
    if (size <= to_size_t(get_size())) {
234,324✔
839
        return;
48,219✔
840
    }
48,219✔
841

100,017✔
842
    size_t new_size = size;
186,105✔
843
    if (m_encryption_key) {
186,105✔
844
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
513✔
845
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
513✔
846
        if (new_size < size) {
513✔
847
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow: data_size_to_encrypted_size(" +
×
848
                                                           realm::util::to_string(size) +
×
849
                                                           ") == " + realm::util::to_string(new_size));
×
850
        }
×
851
    }
186,105✔
852

100,017✔
853
    auto manually_consume_space = [&]() {
186,105✔
854
        constexpr size_t chunk_size = 4096;
×
855
        int64_t original_size = get_size_static(m_fd); // raw size
×
856
        seek(original_size);
×
857
        size_t num_bytes = size_t(new_size - original_size);
×
858
        std::string zeros(chunk_size, '\0');
×
859
        while (num_bytes > 0) {
×
860
            size_t t = num_bytes > chunk_size ? chunk_size : num_bytes;
×
861
            write_static(m_fd, zeros.c_str(), t);
×
862
            num_bytes -= t;
×
863
        }
×
864
    };
×
865

100,017✔
866
    auto consume_space_interlocked = [&] {
100,017✔
867
#if REALM_ENABLE_ENCRYPTION
×
868
        if (m_encryption_key) {
×
869
            // We need to prevent concurrent calls to lseek from the encryption layer
870
            // while we're writing to the file to extend it. Otherwise an intervening
871
            // lseek may redirect the writing process, causing file corruption.
872
            UniqueLock lck(util::mapping_mutex);
×
873
            manually_consume_space();
×
874
        }
×
875
        else {
×
876
            manually_consume_space();
×
877
        }
×
878
#else
879
        manually_consume_space();
880
#endif
881
    };
×
882

100,017✔
883
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version
100,017✔
884
    // Mostly Linux only
100,017✔
885
    if (!prealloc_if_supported(0, new_size)) {
100,017✔
886
        consume_space_interlocked();
887
    }
888
#else // Non-atomic fallback
889
#if REALM_PLATFORM_APPLE
86,088✔
890
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
891
    // ftruncate().
892

893
    struct stat statbuf;
86,088✔
894
    if (::fstat(m_fd, &statbuf) != 0) {
86,088✔
895
        int err = errno;
896
        throw SystemError(err, "fstat() inside prealloc() failed");
897
    }
898

899
    size_t allocated_size;
86,088✔
900
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
86,088✔
901
        throw RuntimeError(ErrorCodes::RangeError,
902
                           "Overflow on block conversion to size_t " + realm::util::to_string(statbuf.st_blocks));
903
    }
904
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
86,088✔
905
        throw RuntimeError(ErrorCodes::RangeError, "Overflow computing existing file space allocation blocks: " +
906
                                                       realm::util::to_string(allocated_size) +
907
                                                       " block size: " + realm::util::to_string(S_BLKSIZE));
908
    }
909

910
    // Only attempt to preallocate space if there's not already sufficient free space in the file.
911
    // APFS would fail with EINVAL if we attempted it, and HFS+ would preallocate extra space unnecessarily.
912
    // See <https://github.com/realm/realm-core/issues/3005> for details.
913
    if (new_size > allocated_size) {
86,088✔
914

915
        off_t to_allocate = static_cast<off_t>(new_size - statbuf.st_size);
86,073✔
916
        fstore_t store = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, to_allocate, 0};
86,073✔
917
        int ret = 0;
86,073✔
918
        do {
86,073✔
919
            ret = fcntl(m_fd, F_PREALLOCATE, &store);
86,073✔
920
        } while (ret == -1 && errno == EINTR);
86,073!
921
        if (ret == -1) {
86,073✔
922
            // Consider fcntl() as an optimization on Apple devices, where if it fails,
923
            // we fall back to manually consuming space which is slower but may succeed in some
924
            // cases where fcntl fails. Some known cases are:
925
            // 1) There's a timing sensitive bug on APFS which causes fcntl to sometimes throw EINVAL.
926
            // This might not be the case, but we'll fall back and attempt to manually allocate all the requested
927
            // space. Worst case, this might also fail, but there is also a chance it will succeed. We don't
928
            // call this in the first place because using fcntl(F_PREALLOCATE) will be faster if it works (it has
929
            // been reliable on HSF+).
930
            // 2) fcntl will fail with ENOTSUP on non-supported file systems such as ExFAT. In this case
931
            // the fallback should succeed.
932
            // 3) if there is some other error such as no space left (ENOSPC) we will expect to fail again later
933
            consume_space_interlocked();
934
        }
935
    }
86,073✔
936

937
    int ret = 0;
86,088✔
938

939
    do {
86,088✔
940
        ret = ftruncate(m_fd, new_size);
86,088✔
941
    } while (ret == -1 && errno == EINTR);
86,088!
942

943
    if (ret != 0) {
86,088✔
944
        int err = errno;
945
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
946
        // so this is some other runtime error and not OutOfDiskSpace
947
        throw SystemError(err, "ftruncate() inside prealloc() failed");
948
    }
949
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
950

951
    consume_space_interlocked();
952

953
#else
954
#error Please check if/how your OS supports file preallocation
955
#endif
956

957
#endif // !(_POSIX_C_SOURCE >= 200112L)
86,088✔
958
}
186,105✔
959

960

961
bool File::prealloc_if_supported(SizeType offset, size_t size)
962
{
100,017✔
963
    REALM_ASSERT_RELEASE(is_attached());
100,017!
964

100,017✔
965
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version
100,017✔
966

100,017✔
967
    REALM_ASSERT_RELEASE(is_prealloc_supported());
100,017✔
968

100,017✔
969
    if (size == 0) {
100,017✔
970
        // calling posix_fallocate with a size of 0 will cause a return of EINVAL
971
        // since this is a meaningless operation anyway, we just return early here
972
        return true;
973
    }
974

100,017✔
975
    // posix_fallocate() does not set errno, it returns the error (if any) or zero.
100,017✔
976
    // It is also possible for it to be interrupted by EINTR according to some man pages (ex fedora 24)
100,017✔
977
    int status;
100,017✔
978
    do {
100,017✔
979
        status = ::posix_fallocate(m_fd, offset, size);
100,017✔
980
    } while (status == EINTR);
100,017✔
981

100,017✔
982
    if (REALM_LIKELY(status == 0)) {
100,017✔
983
        return true;
100,017✔
984
    }
100,017✔
985

986
    if (status == EINVAL || status == EPERM) {
987
        return false; // Retry with non-atomic version
988
    }
989

990
    auto msg = format_errno("posix_fallocate() failed: %1", status);
991
    if (status == ENOSPC || status == EDQUOT) {
992
        throw OutOfDiskSpace(msg);
993
    }
994
    throw SystemError(status, msg);
995

996
    // FIXME: OS X does not have any version of fallocate, but see
997
    // http://stackoverflow.com/questions/11497567/fallocate-command-equivalent-in-os-x
998

999
    // FIXME: On Windows one could use a call to CreateFileMapping()
1000
    // since it will grow the file if necessary, but never shrink it,
1001
    // just like posix_fallocate(). The advantage would be that it
1002
    // then becomes an atomic operation (probably).
1003

1004
#else
1005

1006
    static_cast<void>(offset);
1007
    static_cast<void>(size);
1008

1009
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1010

1011
#endif
1012
    return false;
×
1013
}
×
1014

1015

1016
bool File::is_prealloc_supported()
1017
{
100,014✔
1018
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version
100,014✔
1019
    return true;
100,014✔
1020
#else
1021
    return false;
1022
#endif
1023
}
100,014✔
1024

1025
void File::seek(SizeType position)
1026
{
1,527✔
1027
    REALM_ASSERT_RELEASE(is_attached());
1,527✔
1028
    seek_static(m_fd, position);
1,527✔
1029
}
1,527✔
1030

1031
void File::seek_static(FileDesc fd, SizeType position)
1032
{
1,438,689✔
1033
#ifdef _WIN32 // Windows version
1034

1035
    LARGE_INTEGER large_int;
1036
    if (int_cast_with_overflow_detect(position, large_int.QuadPart))
1037
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1038

1039
    if (!SetFilePointerEx(fd, large_int, 0, FILE_BEGIN))
1040
        throw SystemError(GetLastError(), "SetFilePointerEx() failed");
1041

1042
#else // POSIX version
1043

728,697✔
1044
    off_t position2;
1,438,689✔
1045
    if (int_cast_with_overflow_detect(position, position2))
1,438,689✔
1046
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1047

728,697✔
1048
    if (0 <= ::lseek(fd, position2, SEEK_SET))
1,438,689✔
1049
        return;
1,438,689✔
1050
    throw SystemError(errno, "lseek() failed");
×
1051

1052
#endif
×
1053
}
×
1054

1055
// FIXME: The current implementation may not guarantee that data is
1056
// actually written to disk. POSIX is rather vague on what fsync() has
1057
// to do unless _POSIX_SYNCHRONIZED_IO is defined. See also
1058
// http://www.humboldt.co.uk/2009/03/fsync-across-platforms.html.
1059
void File::sync()
1060
{
846✔
1061
    REALM_ASSERT_RELEASE(is_attached());
846✔
1062

534✔
1063
#if defined _WIN32 // Windows version
1064

1065
    if (FlushFileBuffers(m_fd))
1066
        return;
1067
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1068

1069
#elif REALM_PLATFORM_APPLE
1070

1071
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
312✔
1072
        return;
312✔
1073
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1074

1075
#else // POSIX version
1076

534✔
1077
    if (::fsync(m_fd) == 0)
534✔
1078
        return;
534✔
1079
    throw SystemError(errno, "fsync() failed");
1080

1081
#endif
1082
}
×
1083

1084
void File::barrier()
1085
{
432✔
1086
#if REALM_PLATFORM_APPLE
210✔
1087
    if (::fcntl(m_fd, F_BARRIERFSYNC) == 0)
210✔
1088
        return;
210✔
1089
        // If fcntl fails, we fallback to full sync.
1090
        // This is known to occur on exFAT which does not support F_BARRIERSYNC.
1091
#endif
1092
    sync();
222✔
1093
}
222✔
1094

1095
#ifndef _WIN32
1096
// little helper
1097
static void _unlock(int m_fd)
1098
{
7,456,809✔
1099
    int r;
7,456,809✔
1100
    do {
7,456,809✔
1101
        r = flock(m_fd, LOCK_UN);
7,456,809✔
1102
    } while (r != 0 && errno == EINTR);
7,456,809!
1103
    if (r) {
7,456,809✔
1104
        throw SystemError(errno, "File::unlock() has failed");
×
1105
    }
×
1106
}
7,456,809✔
1107
#endif
1108

1109
bool File::rw_lock(bool exclusive, bool non_blocking)
1110
{
463,461✔
1111
    // exclusive blocking rw locks not implemented for emulation
226,623✔
1112
    REALM_ASSERT(!exclusive || non_blocking);
463,461✔
1113

226,623✔
1114
#ifndef REALM_FILELOCK_EMULATION
463,461✔
1115
    return lock(exclusive, non_blocking);
463,461✔
1116
#else
1117
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1118

1119
    // First obtain an exclusive lock on the file proper
1120
    int operation = LOCK_EX;
1121
    if (non_blocking)
1122
        operation |= LOCK_NB;
1123
    int status;
1124
    do {
1125
        status = flock(m_fd, operation);
1126
    } while (status != 0 && errno == EINTR);
1127
    if (status != 0 && errno == EWOULDBLOCK)
1128
        return false;
1129
    if (status != 0)
1130
        throw SystemError(errno, "flock() failed");
1131
    m_has_exclusive_lock = true;
1132

1133
    // Every path through this function except for successfully acquiring an
1134
    // exclusive lock needs to release the flock() before returning.
1135
    UnlockGuard ulg(*this);
1136

1137
    // now use a named pipe to emulate locking in conjunction with using exclusive lock
1138
    // on the file proper.
1139
    // exclusively locked: we can't sucessfully write to the pipe.
1140
    //                     AND we continue to hold the exclusive lock.
1141
    //                     (unlock must lift the exclusive lock).
1142
    // shared locked: we CAN succesfully write to the pipe. We open the pipe for reading
1143
    //                before releasing the exclusive lock.
1144
    //                (unlock must close the pipe for reading)
1145
    REALM_ASSERT_RELEASE(m_pipe_fd == -1);
1146
    if (m_fifo_path.empty())
1147
        m_fifo_path = m_path + ".fifo";
1148

1149
    // Due to a bug in iOS 10-12 we need to open in read-write mode for shared
1150
    // or the OS will deadlock when un-suspending the app.
1151
    int mode = exclusive ? O_WRONLY | O_NONBLOCK : O_RDWR | O_NONBLOCK;
1152

1153
    // Optimistically try to open the fifo. This may fail due to the fifo not
1154
    // existing, but since it usually exists this is faster than trying to create
1155
    // it first.
1156
    int fd = ::open(m_fifo_path.c_str(), mode);
1157
    if (fd == -1) {
1158
        int err = errno;
1159
        if (exclusive) {
1160
            if (err == ENOENT || err == ENXIO) {
1161
                // If the fifo either doesn't exist or there's no readers with the
1162
                // other end of the pipe open (ENXIO) then we have an exclusive lock
1163
                // and are done.
1164
                ulg.release();
1165
                return true;
1166
            }
1167

1168
            // Otherwise we got an unexpected error
1169
            throw std::system_error(err, std::system_category(), "opening lock fifo for writing failed");
1170
        }
1171

1172
        if (err == ENOENT) {
1173
            // The fifo doesn't exist and we're opening in shared mode, so we
1174
            // need to create it.
1175
            if (!m_fifo_dir_path.empty())
1176
                try_make_dir(m_fifo_dir_path);
1177
            status = mkfifo(m_fifo_path.c_str(), 0666);
1178
            if (status != 0)
1179
                throw std::system_error(errno, std::system_category(), "creating lock fifo for reading failed");
1180

1181
            // Try again to open the fifo now that it exists
1182
            fd = ::open(m_fifo_path.c_str(), mode);
1183
            err = errno;
1184
        }
1185

1186
        if (fd == -1)
1187
            throw std::system_error(err, std::system_category(), "opening lock fifo for reading failed");
1188
    }
1189

1190
    // We successfully opened the pipe. If we're trying to acquire an exclusive
1191
    // lock that means there's a reader (aka a shared lock) and we've failed.
1192
    // Release the exclusive lock and back out.
1193
    if (exclusive) {
1194
        ::close(fd);
1195
        return false;
1196
    }
1197

1198
    // We're in shared mode, so opening the fifo means we've successfully acquired
1199
    // a shared lock and are done.
1200
    ulg.release();
1201
    rw_unlock();
1202
    m_pipe_fd = fd;
1203
    return true;
1204
#endif // REALM_FILELOCK_EMULATION
1205
}
463,461✔
1206

1207
bool File::lock(bool exclusive, bool non_blocking)
1208
{
7,573,176✔
1209
    REALM_ASSERT_RELEASE(is_attached());
7,573,176✔
1210
    REALM_ASSERT_RELEASE(!m_have_lock);
7,573,176✔
1211

226,626✔
1212
#ifdef _WIN32 // Windows version
1213

1214
    // Under Windows a file lock must be explicitely released before
1215
    // the file is closed. It will eventually be released by the
1216
    // system, but there is no guarantees on the timing.
1217

1218
    DWORD flags = 0;
1219
    if (exclusive)
1220
        flags |= LOCKFILE_EXCLUSIVE_LOCK;
1221
    if (non_blocking)
1222
        flags |= LOCKFILE_FAIL_IMMEDIATELY;
1223
    OVERLAPPED overlapped;
1224
    memset(&overlapped, 0, sizeof overlapped);
1225
    overlapped.Offset = 0;     // Just for clarity
1226
    overlapped.OffsetHigh = 0; // Just for clarity
1227
    if (LockFileEx(m_fd, flags, 0, 1, 0, &overlapped)) {
1228
        m_have_lock = true;
1229
        return true;
1230
    }
1231
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
1232
    if (err == ERROR_LOCK_VIOLATION)
1233
        return false;
1234
    throw std::system_error(err, std::system_category(), "LockFileEx() failed");
1235
#else // _WIN32
1236
    // NOTE: It would probably have been more portable to use fcntl()
226,626✔
1237
    // based POSIX locks, however these locks are not recursive within
226,626✔
1238
    // a single process, and since a second attempt to acquire such a
226,626✔
1239
    // lock will always appear to succeed, one will easily suffer the
226,626✔
1240
    // 'spurious unlocking issue'. It remains to be determined whether
226,626✔
1241
    // this also applies across distinct threads inside a single
226,626✔
1242
    // process.
226,626✔
1243
    //
226,626✔
1244
    // To make matters worse, flock() may be a simple wrapper around
226,626✔
1245
    // fcntl() based locks on some systems. This is bad news, because
226,626✔
1246
    // the robustness of the Realm API relies in part by the
226,626✔
1247
    // assumption that a single process (even a single thread) can
226,626✔
1248
    // hold multiple overlapping independent shared locks on a single
226,626✔
1249
    // file as long as they are placed via distinct file descriptors.
226,626✔
1250
    //
226,626✔
1251
    // Fortunately, on both Linux and Darwin, flock() does not suffer
226,626✔
1252
    // from this 'spurious unlocking issue'.
226,626✔
1253
    int operation = exclusive ? LOCK_EX : LOCK_SH;
7,573,176✔
1254
    if (non_blocking)
7,573,176✔
1255
        operation |= LOCK_NB;
353,316✔
1256
    do {
7,573,176✔
1257
        if (flock(m_fd, operation) == 0) {
7,573,176✔
1258
            m_have_lock = true;
7,458,636✔
1259
            return true;
7,458,636✔
1260
        }
7,458,636✔
1261
    } while (errno == EINTR);
114,540✔
1262
    int err = errno; // Eliminate any risk of clobbering
285,921✔
1263
    if (err == EWOULDBLOCK)
114,540✔
1264
        return false;
116,469✔
1265
    throw SystemError(err, "flock() failed");
4,294,967,294✔
1266
#endif
4,294,967,294✔
1267
}
4,294,967,294✔
1268

1269
void File::unlock() noexcept
1270
{
7,456,776✔
1271
    if (!m_have_lock)
7,456,776✔
1272
        return;
×
1273

171,381✔
1274
#ifdef _WIN32 // Windows version
1275
    OVERLAPPED overlapped;
1276
    overlapped.hEvent = 0;
1277
    overlapped.OffsetHigh = 0;
1278
    overlapped.Offset = 0;
1279
    overlapped.Pointer = 0;
1280
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1281
    REALM_ASSERT_RELEASE(r);
1282
#else
1283
    _unlock(m_fd);
7,456,776✔
1284
#endif
7,456,776✔
1285
    m_have_lock = false;
7,456,776✔
1286
}
7,456,776✔
1287

1288
void File::rw_unlock() noexcept
1289
{
346,353✔
1290
#ifndef REALM_FILELOCK_EMULATION
346,353✔
1291
    unlock();
346,353✔
1292
#else
1293
    // Coming here with an exclusive lock, we must release that lock.
1294
    // Coming here with a shared lock, we must close the pipe that we have opened for reading.
1295
    //   - we have to do that under the protection of a proper exclusive lock to serialize
1296
    //     with anybody trying to obtain a lock concurrently.
1297
    if (has_shared_lock()) {
1298
        // shared lock. We need to reacquire the exclusive lock on the file
1299
        int status;
1300
        do {
1301
            status = flock(m_fd, LOCK_EX);
1302
        } while (status != 0 && errno == EINTR);
1303
        REALM_ASSERT(status == 0);
1304
        // close the pipe (== release the shared lock)
1305
        ::close(m_pipe_fd);
1306
        m_pipe_fd = -1;
1307
    }
1308
    else {
1309
        REALM_ASSERT(m_has_exclusive_lock);
1310
    }
1311
    _unlock(m_fd);
1312
    m_has_exclusive_lock = false;
1313
#endif // REALM_FILELOCK_EMULATION
1314
}
346,353✔
1315

1316
void* File::map(AccessMode a, size_t size, int /*map_flags*/, size_t offset) const
1317
{
6✔
1318
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset);
6✔
1319
}
6✔
1320

1321
void* File::map_fixed(AccessMode a, void* address, size_t size, int /* map_flags */, size_t offset) const
1322
{
×
1323
    if (m_encryption_key.get()) {
×
1324
        // encryption enabled - this is not supported - see explanation in alloc_slab.cpp
1325
        REALM_ASSERT(false);
×
1326
    }
×
1327
#ifdef _WIN32
1328
    // windows, no encryption - this is not supported, see explanation in alloc_slab.cpp,
1329
    // above the method 'update_reader_view()'
1330
    REALM_ASSERT(false);
1331
    return nullptr;
1332
#else
1333
    // unencrypted - mmap part of already reserved space
1334
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, m_encryption_key.get());
×
1335
#endif
×
1336
}
×
1337

1338
void* File::map_reserve(AccessMode a, size_t size, size_t offset) const
1339
{
×
1340
    static_cast<void>(a); // FIXME: Consider removing this argument
×
1341
    return realm::util::mmap_reserve(m_fd, size, offset);
×
1342
}
×
1343

1344
#if REALM_ENABLE_ENCRYPTION
1345
void* File::map(AccessMode a, size_t size, EncryptedFileMapping*& mapping, int /*map_flags*/, size_t offset) const
1346
{
3,127,356✔
1347
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
3,127,356✔
1348
}
3,127,356✔
1349

1350
void* File::map_fixed(AccessMode a, void* address, size_t size, EncryptedFileMapping* mapping, int /* map_flags */,
1351
                      size_t offset) const
1352
{
×
1353
    if (m_encryption_key.get()) {
×
1354
        // encryption enabled - we shouldn't be here, all memory was allocated by reserve
1355
        REALM_ASSERT_RELEASE(false);
×
1356
    }
×
1357
#ifndef _WIN32
×
1358
    // no encryption. On Unixes, map relevant part of reserved virtual address range
1359
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, nullptr, mapping);
×
1360
#else
1361
    // no encryption - unsupported on windows
1362
    REALM_ASSERT(false);
1363
    return nullptr;
1364
#endif
1365
}
×
1366

1367
void* File::map_reserve(AccessMode a, size_t size, size_t offset, EncryptedFileMapping*& mapping) const
1368
{
×
1369
    if (m_encryption_key.get()) {
×
1370
        // encrypted file - just mmap it, the encryption layer handles if the mapping extends beyond eof
1371
        return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
×
1372
    }
×
1373
#ifndef _WIN32
×
1374
    // not encrypted, do a proper reservation on Unixes'
1375
    return realm::util::mmap_reserve({m_fd, m_path, a, nullptr}, size, offset, mapping);
×
1376
#else
1377
    // on windows, this is a no-op
1378
    return nullptr;
1379
#endif
1380
}
×
1381

1382
#endif // REALM_ENABLE_ENCRYPTION
1383

1384
void File::unmap(void* addr, size_t size) noexcept
1385
{
×
1386
    realm::util::munmap(addr, size);
×
1387
}
×
1388

1389

1390
void* File::remap(void* old_addr, size_t old_size, AccessMode a, size_t new_size, int /*map_flags*/,
1391
                  size_t file_offset) const
1392
{
×
1393
    return realm::util::mremap({m_fd, m_path, a, m_encryption_key.get()}, file_offset, old_addr, old_size, new_size);
×
1394
}
×
1395

1396

1397
void File::sync_map(FileDesc fd, void* addr, size_t size)
1398
{
3,696,441✔
1399
    realm::util::msync(fd, addr, size);
3,696,441✔
1400
}
3,696,441✔
1401

1402

1403
bool File::exists(const std::string& path)
1404
{
5,936,781✔
1405
#if REALM_HAVE_STD_FILESYSTEM
1406
    return std::filesystem::exists(u8path(path));
1407
#else // POSIX
1408
    if (::access(path.c_str(), F_OK) == 0)
5,936,781✔
1409
        return true;
29,838✔
1410
    int err = errno; // Eliminate any risk of clobbering
5,906,943✔
1411
    switch (err) {
5,906,943✔
1412
        case EACCES:
5,114,649✔
1413
        case ENOENT:
5,906,916✔
1414
        case ENOTDIR:
5,906,916✔
1415
            return false;
5,906,916✔
1416
    }
24✔
1417
    throw SystemError(err, "access() failed");
24✔
1418
#endif
24✔
1419
}
24✔
1420

1421

1422
bool File::is_dir(const std::string& path)
1423
{
287,352✔
1424
#if REALM_HAVE_STD_FILESYSTEM
1425
    return std::filesystem::is_directory(u8path(path));
1426
#elif !defined(_WIN32)
1427
    struct stat statbuf;
206,610✔
1428
    if (::stat(path.c_str(), &statbuf) == 0)
287,352✔
1429
        return S_ISDIR(statbuf.st_mode);
279,273✔
1430
    int err = errno; // Eliminate any risk of clobbering
8,079✔
1431
    switch (err) {
8,079✔
1432
        case EACCES:
4,041✔
1433
        case ENOENT:
8,076✔
1434
        case ENOTDIR:
8,076✔
1435
            return false;
8,076✔
1436
    }
×
1437
    throw SystemError(err, "stat() failed");
×
1438
#else
1439
    static_cast<void>(path);
1440
    throw NotImplemented();
1441
#endif
1442
}
×
1443

1444

1445
void File::remove(const std::string& path)
1446
{
210,426✔
1447
    if (try_remove(path))
210,426✔
1448
        return;
210,402✔
1449
    int err = ENOENT;
24✔
1450
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
24✔
1451
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
24✔
1452
}
24✔
1453

1454

1455
bool File::try_remove(const std::string& path)
1456
{
272,547✔
1457
#if REALM_HAVE_STD_FILESYSTEM
1458
    std::error_code error;
1459
    bool result = std::filesystem::remove(u8path(path), error);
1460
    throwIfFileError(error, path);
1461
    return result;
1462
#else // POSIX
1463
    if (::unlink(path.c_str()) == 0)
272,547✔
1464
        return true;
234,087✔
1465

19,233✔
1466
    int err = errno; // Eliminate any risk of clobbering
38,460✔
1467
    if (err == ENOENT)
38,460✔
1468
        return false;
38,442✔
1469

21✔
1470
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
39✔
1471
    switch (err) {
39✔
1472
        case EACCES:
6✔
1473
        case EROFS:
6✔
1474
        case ETXTBSY:
6✔
1475
        case EBUSY:
6✔
1476
        case EPERM:
18✔
1477
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
18✔
1478
        case ENOENT:
3✔
1479
            return false;
×
1480
        default:
24✔
1481
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
24✔
1482
    }
39✔
1483
#endif
39✔
1484
}
39✔
1485

1486

1487
void File::move(const std::string& old_path, const std::string& new_path)
1488
{
342✔
1489
#if REALM_HAVE_STD_FILESYSTEM
1490
    std::error_code error;
1491
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1492

1493
    if (error == std::errc::no_such_file_or_directory) {
1494
        throw FileAccessError(ErrorCodes::FileNotFound, error.message(), old_path);
1495
    }
1496
    throwIfFileError(error, old_path);
1497
#else
1498
    int r = rename(old_path.c_str(), new_path.c_str());
342✔
1499
    if (r == 0)
342✔
1500
        return;
342✔
1501
    int err = errno; // Eliminate any risk of clobbering
×
1502
    std::string msg = format_errno("Failed to rename file from '%2' to '%3': %1", err, old_path, new_path);
×
1503
    switch (err) {
×
1504
        case EACCES:
×
1505
        case EROFS:
×
1506
        case ETXTBSY:
×
1507
        case EBUSY:
×
1508
        case EPERM:
×
1509
        case EEXIST:
×
1510
        case ENOTEMPTY:
×
1511
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, old_path, err);
×
1512
        case ENOENT:
×
1513
            throw FileAccessError(ErrorCodes::FileNotFound, msg, old_path, err);
×
1514
        default:
×
1515
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, old_path, err);
×
1516
    }
×
1517
#endif
×
1518
}
×
1519

1520

1521
void File::copy(const std::string& origin_path, const std::string& target_path)
1522
{
534✔
1523
#if REALM_HAVE_STD_FILESYSTEM
1524
    std::filesystem::copy_file(u8path(origin_path), u8path(target_path),
1525
                               std::filesystem::copy_options::overwrite_existing); // Throws
1526
#else
1527
    File origin_file{origin_path, mode_Read};  // Throws
534✔
1528
    File target_file{target_path, mode_Write}; // Throws
534✔
1529
    size_t buffer_size = 4096;
534✔
1530
    std::unique_ptr<char[]> buffer = std::make_unique<char[]>(buffer_size); // Throws
534✔
1531
    for (;;) {
1,413✔
1532
        size_t n = origin_file.read(buffer.get(), buffer_size); // Throws
1,413✔
1533
        target_file.write(buffer.get(), n);                     // Throws
1,413✔
1534
        if (n < buffer_size)
1,413✔
1535
            break;
534✔
1536
    }
1,413✔
1537
#endif
534✔
1538
}
534✔
1539

1540

1541
bool File::compare(const std::string& path_1, const std::string& path_2)
1542
{
×
1543
    File file_1{path_1}; // Throws
×
1544
    File file_2{path_2}; // Throws
×
1545
    size_t buffer_size = 4096;
×
1546
    std::unique_ptr<char[]> buffer_1 = std::make_unique<char[]>(buffer_size); // Throws
×
1547
    std::unique_ptr<char[]> buffer_2 = std::make_unique<char[]>(buffer_size); // Throws
×
1548
    for (;;) {
×
1549
        size_t n_1 = file_1.read(buffer_1.get(), buffer_size); // Throws
×
1550
        size_t n_2 = file_2.read(buffer_2.get(), buffer_size); // Throws
×
1551
        if (n_1 != n_2)
×
1552
            return false;
×
1553
        if (!std::equal(buffer_1.get(), buffer_1.get() + n_1, buffer_2.get()))
×
1554
            return false;
×
1555
        if (n_1 < buffer_size)
×
1556
            break;
×
1557
    }
×
1558
    return true;
×
1559
}
×
1560

1561
bool File::is_same_file_static(FileDesc f1, FileDesc f2)
1562
{
24✔
1563
    return get_unique_id(f1) == get_unique_id(f2);
24✔
1564
}
24✔
1565

1566
bool File::is_same_file(const File& f) const
1567
{
24✔
1568
    REALM_ASSERT_RELEASE(is_attached());
24✔
1569
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1570
    return is_same_file_static(m_fd, f.m_fd);
24✔
1571
}
24✔
1572

1573
File::UniqueID File::get_unique_id() const
1574
{
348,567✔
1575
    REALM_ASSERT_RELEASE(is_attached());
348,567✔
1576
    return File::get_unique_id(m_fd);
348,567✔
1577
}
348,567✔
1578

1579
FileDesc File::get_descriptor() const
1580
{
139,677✔
1581
    return m_fd;
139,677✔
1582
}
139,677✔
1583

1584
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1585
{
249,288✔
1586
#ifdef _WIN32 // Windows version
1587
    // CreateFile2 with creationDisposition OPEN_EXISTING will return a file handle only if the file exists
1588
    // otherwise it will raise ERROR_FILE_NOT_FOUND. This call will never create a new file.
1589
    WindowsFileHandleHolder fileHandle(::CreateFile2(u8path(path).c_str(), FILE_READ_ATTRIBUTES,
1590
                                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1591
                                                     OPEN_EXISTING, nullptr));
1592

1593
    if (fileHandle == INVALID_HANDLE_VALUE) {
1594
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1595
            return none;
1596
        }
1597
        throw SystemError(GetLastError(), "CreateFileW failed");
1598
    }
1599

1600
    return get_unique_id(fileHandle);
1601
#else // POSIX version
1602
    struct stat statbuf;
249,288✔
1603
    if (::stat(path.c_str(), &statbuf) == 0) {
249,288✔
1604
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
174,060✔
1605
    }
174,060✔
1606
    int err = errno; // Eliminate any risk of clobbering
75,228✔
1607
    // File doesn't exist
3✔
1608
    if (err == ENOENT)
75,228✔
1609
        return none;
75,228✔
1610
    throw SystemError(err, format_errno("fstat() failed: %1", err));
×
1611
#endif
×
1612
}
×
1613

1614
File::UniqueID File::get_unique_id(FileDesc file)
1615
{
348,615✔
1616
#ifdef _WIN32 // Windows version
1617
    REALM_ASSERT(file != nullptr);
1618
    File::UniqueID ret;
1619
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1620
        throw std::system_error(GetLastError(), std::system_category(), "GetFileInformationByHandleEx() failed");
1621
    }
1622

1623
    return ret;
1624
#else // POSIX version
1625
    REALM_ASSERT(file >= 0);
348,615✔
1626
    struct stat statbuf;
348,615✔
1627
    if (::fstat(file, &statbuf) == 0) {
348,615✔
1628
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
348,615✔
1629
    }
348,615✔
1630
    throw std::system_error(errno, std::system_category(), "fstat() failed");
×
1631
#endif
×
1632
}
×
1633

1634
std::string File::get_path() const
1635
{
240✔
1636
    return m_path;
240✔
1637
}
240✔
1638

1639
std::string File::resolve(const std::string& path, const std::string& base_dir)
1640
{
285,297✔
1641
#if REALM_HAVE_STD_FILESYSTEM
1642
    return (u8path(base_dir) / u8path(path)).lexically_normal().u8string();
1643
#else
1644
    char dir_sep = '/';
285,297✔
1645
    std::string path_2 = path;
285,297✔
1646
    std::string base_dir_2 = base_dir;
285,297✔
1647
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
285,297✔
1648
    if (is_absolute)
285,297✔
1649
        return path_2;
6✔
1650
    if (path_2.empty())
285,291✔
1651
        path_2 = ".";
6✔
1652
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
285,291✔
1653
        base_dir_2.push_back(dir_sep);
271,701✔
1654
    /*
78,720✔
1655
    // Abbreviate
78,720✔
1656
    for (;;) {
78,720✔
1657
        if (base_dir_2.empty()) {
78,720✔
1658
            if (path_2.empty())
78,720✔
1659
                return "./";
78,720✔
1660
            return path_2;
78,720✔
1661
        }
78,720✔
1662
        if (path_2 == ".") {
78,720✔
1663
            remove_trailing_dir_seps(base_dir_2);
78,720✔
1664
            return base_dir_2;
78,720✔
1665
        }
78,720✔
1666
        if (has_prefix(path_2, "./")) {
78,720✔
1667
            remove_trailing_dir_seps(base_dir_2);
78,720✔
1668
            // drop dot
78,720✔
1669
            // transfer slashes
78,720✔
1670
        }
78,720✔
1671

78,720✔
1672
        if (path_2.size() < 2 || path_2[1] != '.')
78,720✔
1673
            break;
78,720✔
1674
        if (path_2.size())
78,720✔
1675
    }
78,720✔
1676
    */
78,720✔
1677
    return base_dir_2 + path_2;
285,291✔
1678
#endif
285,291✔
1679
}
285,291✔
1680

1681
std::string File::parent_dir(const std::string& path)
1682
{
78✔
1683
#if REALM_HAVE_STD_FILESYSTEM
1684
    return u8path(path).parent_path().u8string(); // Throws
1685
#else
1686
    auto is_sep = [](char c) -> bool {
1,119✔
1687
        return c == '/' || c == '\\';
1,119✔
1688
    };
1,119✔
1689
    auto it = std::find_if(path.rbegin(), path.rend(), is_sep);
78✔
1690
    while (it != path.rend() && is_sep(*it))
168✔
1691
        ++it;
90✔
1692
    return path.substr(0, path.rend() - it);
78✔
1693
#endif
78✔
1694
}
78✔
1695

1696
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1697
{
6✔
1698
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1699
}
6✔
1700

1701

1702
void File::set_encryption_key(const char* key)
1703
{
140,073✔
1704
#if REALM_ENABLE_ENCRYPTION
140,073✔
1705
    if (key) {
140,073✔
1706
        auto buffer = std::make_unique<char[]>(64);
300✔
1707
        memcpy(buffer.get(), key, 64);
300✔
1708
        m_encryption_key = std::move(buffer);
300✔
1709
    }
300✔
1710
    else {
139,773✔
1711
        m_encryption_key.reset();
139,773✔
1712
    }
139,773✔
1713
#else
1714
    if (key) {
1715
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1716
    }
1717
#endif
1718
}
140,073✔
1719

1720
const char* File::get_encryption_key() const
1721
{
138✔
1722
    return m_encryption_key.get();
138✔
1723
}
138✔
1724

1725
void File::MapBase::map(const File& f, AccessMode a, size_t size, int map_flags, size_t offset,
1726
                        util::WriteObserver* observer)
1727
{
3,127,365✔
1728
    REALM_ASSERT(!m_addr);
3,127,365✔
1729
#if REALM_ENABLE_ENCRYPTION
3,127,365✔
1730
    m_addr = f.map(a, size, m_encrypted_mapping, map_flags, offset);
3,127,365✔
1731
    if (observer && m_encrypted_mapping) {
3,127,365✔
1732
        m_encrypted_mapping->set_observer(observer);
321✔
1733
    }
321✔
1734
#else
1735
    m_addr = f.map(a, size, map_flags, offset);
1736
    static_cast<void>(observer);
1737
#endif
1738
    m_size = m_reservation_size = size;
3,127,365✔
1739
    m_fd = f.m_fd;
3,127,365✔
1740
    m_offset = offset;
3,127,365✔
1741
    m_access_mode = a;
3,127,365✔
1742
}
3,127,365✔
1743

1744

1745
void File::MapBase::unmap() noexcept
1746
{
6,881,226✔
1747
    if (!m_addr)
6,881,226✔
1748
        return;
3,614,355✔
1749
    REALM_ASSERT(m_reservation_size);
3,266,871✔
1750
#if REALM_ENABLE_ENCRYPTION
3,266,871✔
1751
    if (m_encrypted_mapping) {
3,266,871✔
1752
        m_encrypted_mapping = nullptr;
3,132✔
1753
        util::remove_encrypted_mapping(m_addr, m_size);
3,132✔
1754
    }
3,132✔
1755
#endif
3,266,871✔
1756
    ::munmap(m_addr, m_reservation_size);
3,266,871✔
1757
    m_addr = nullptr;
3,266,871✔
1758
    m_size = 0;
3,266,871✔
1759
    m_reservation_size = 0;
3,266,871✔
1760
}
3,266,871✔
1761

1762
void File::MapBase::remap(const File& f, AccessMode a, size_t size, int map_flags)
1763
{
×
1764
    REALM_ASSERT(m_addr);
×
1765
    m_addr = f.remap(m_addr, m_size, a, size, map_flags);
×
1766
    m_size = m_reservation_size = size;
×
1767
}
×
1768

1769
bool File::MapBase::try_reserve(const File& file, AccessMode a, size_t size, size_t offset,
1770
                                util::WriteObserver* observer)
1771
{
139,611✔
1772
#ifdef _WIN32
1773
    static_cast<void>(observer);
1774
    // unsupported for now
1775
    return false;
1776
#else
1777
    void* addr = ::mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
139,611✔
1778
    if (addr == MAP_FAILED)
139,611✔
1779
        return false;
68,502✔
1780
    m_addr = addr;
139,611✔
1781
    REALM_ASSERT(m_size == 0);
139,611✔
1782
    m_access_mode = a;
139,611✔
1783
    m_reservation_size = size;
139,611✔
1784
    m_fd = file.get_descriptor();
139,611✔
1785
    m_offset = offset;
139,611✔
1786
#if REALM_ENABLE_ENCRYPTION
139,611✔
1787
    if (file.m_encryption_key) {
139,611✔
1788
        m_encrypted_mapping =
234✔
1789
            util::reserve_mapping(addr, {m_fd, file.get_path(), a, file.m_encryption_key.get()}, offset);
234✔
1790
        if (observer) {
234✔
1791
            m_encrypted_mapping->set_observer(observer);
228✔
1792
        }
228✔
1793
    }
234✔
1794
#else
1795
    static_cast<void>(observer);
1796
#endif
1797
#endif
139,611✔
1798
    return true;
139,611✔
1799
}
139,611✔
1800

1801
bool File::MapBase::try_extend_to(size_t size) noexcept
1802
{
158,802✔
1803
    if (size > m_reservation_size) {
158,802✔
1804
        return false;
×
1805
    }
×
1806
    // return false;
86,160✔
1807
#ifndef _WIN32
158,802✔
1808
    char* extension_start_addr = (char*)m_addr + m_size;
158,802✔
1809
    size_t extension_size = size - m_size;
158,802✔
1810
    size_t extension_start_offset = m_offset + m_size;
158,802✔
1811
#if REALM_ENABLE_ENCRYPTION
158,802✔
1812
    if (m_encrypted_mapping) {
158,802✔
1813
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
267✔
1814
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
267✔
1815
        if (got_addr == MAP_FAILED)
267✔
1816
            return false;
135✔
1817
        REALM_ASSERT(got_addr == extension_start_addr);
267✔
1818
        util::extend_encrypted_mapping(m_encrypted_mapping, m_addr, m_offset, m_size, size);
267✔
1819
        m_size = size;
267✔
1820
        return true;
267✔
1821
    }
267✔
1822
#endif
158,535✔
1823
    try {
158,535✔
1824
        void* got_addr = util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode,
158,535✔
1825
                                          extension_start_offset, nullptr);
158,535✔
1826
        if (got_addr == extension_start_addr) {
158,535✔
1827
            m_size = size;
158,451✔
1828
            return true;
158,451✔
1829
        }
158,451✔
1830
    }
18✔
1831
    catch (...) {
18✔
1832
        return false;
18✔
1833
    }
18✔
1834
#endif
66✔
1835
    return false;
66✔
1836
}
66✔
1837

1838
void File::MapBase::sync()
1839
{
3,696,471✔
1840
    REALM_ASSERT(m_addr);
3,696,471✔
1841

1,869,195✔
1842
    File::sync_map(m_fd, m_addr, m_size);
3,696,471✔
1843
}
3,696,471✔
1844

1845
void File::MapBase::flush()
1846
{
3,665,901✔
1847
    REALM_ASSERT(m_addr);
3,665,901✔
1848
#if REALM_ENABLE_ENCRYPTION
3,665,901✔
1849
    if (m_encrypted_mapping) {
3,665,901✔
1850
        realm::util::encryption_flush(m_encrypted_mapping);
864✔
1851
    }
864✔
1852
#endif
3,665,901✔
1853
}
3,665,901✔
1854

1855
std::time_t File::last_write_time(const std::string& path)
1856
{
951✔
1857
#if REALM_HAVE_STD_FILESYSTEM
1858
    auto time = std::filesystem::last_write_time(u8path(path));
1859

1860
    using namespace std::chrono;
1861
#if __cplusplus >= 202002L
1862
    auto system_time = clock_cast<system_clock>(time);
1863
#else
1864
    auto system_time =
1865
        time_point_cast<system_clock::duration>(time - decltype(time)::clock::now() + system_clock::now());
1866
#endif
1867
    return system_clock::to_time_t(system_time);
1868
#else
1869
    struct stat statbuf;
951✔
1870
    if (::stat(path.c_str(), &statbuf) != 0) {
951✔
1871
        throw SystemError(errno, "stat() failed");
×
1872
    }
×
1873
    return statbuf.st_mtime;
951✔
1874
#endif
951✔
1875
}
951✔
1876

1877
File::SizeType File::get_free_space(const std::string& path)
1878
{
186✔
1879
#if REALM_HAVE_STD_FILESYSTEM
1880
    return std::filesystem::space(u8path(path)).available;
1881
#else
1882
    struct statvfs stat;
186✔
1883
    if (statvfs(path.c_str(), &stat) != 0) {
186✔
1884
        throw SystemError(errno, "statvfs() failed");
×
1885
    }
×
1886
    return SizeType(stat.f_bavail) * stat.f_bsize;
186✔
1887
#endif
186✔
1888
}
186✔
1889

1890
#if REALM_HAVE_STD_FILESYSTEM
1891

1892
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1893
{
1894
    try {
1895
        m_iterator = std::filesystem::directory_iterator(u8path(path));
1896
    }
1897
    catch (const std::filesystem::filesystem_error& e) {
1898
        if (e.code() != std::errc::no_such_file_or_directory || !allow_missing)
1899
            throw;
1900
    }
1901
}
1902

1903
DirScanner::~DirScanner() = default;
1904

1905
bool DirScanner::next(std::string& name)
1906
{
1907
    const std::filesystem::directory_iterator end;
1908
    if (m_iterator == end)
1909
        return false;
1910
    name = m_iterator->path().filename().u8string();
1911
    m_iterator++;
1912
    return true;
1913
}
1914

1915
#elif !defined(_WIN32)
1916

1917
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1918
{
159,375✔
1919
    m_dirp = opendir(path.c_str());
159,375✔
1920
    if (!m_dirp) {
159,375✔
1921
        int err = errno; // Eliminate any risk of clobbering
8,142✔
1922
        if (allow_missing && err == ENOENT)
8,142✔
1923
            return;
8,142✔
1924

1925
        std::string msg = format_errno("opendir() failed: %1", err);
×
1926
        switch (err) {
×
1927
            case EACCES:
×
1928
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
1929
            case ENOENT:
×
1930
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
1931
            default:
×
1932
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
1933
        }
×
1934
    }
×
1935
}
159,375✔
1936

1937
DirScanner::~DirScanner() noexcept
1938
{
159,375✔
1939
    if (m_dirp) {
159,375✔
1940
        int r = closedir(m_dirp);
151,233✔
1941
        REALM_ASSERT_RELEASE(r == 0);
151,233✔
1942
    }
151,233✔
1943
}
159,375✔
1944

1945
bool DirScanner::next(std::string& name)
1946
{
432,675✔
1947
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
1948
    !defined(__EMSCRIPTEN__)
1949
#error "readdir() is not known to be thread-safe on this platform"
1950
#endif
1951

150,903✔
1952
    if (!m_dirp)
432,675✔
1953
        return false;
8,142✔
1954

146,829✔
1955
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
146,829✔
1956
// in 32-bits.
146,829✔
1957
#if REALM_HAVE_READDIR64
146,829✔
1958
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
294,735✔
1959
#else
1960
#define REALM_READDIR(...) readdir(__VA_ARGS__)
432,264✔
1961
#endif
277,704✔
1962

146,829✔
1963
    for (;;) {
726,999✔
1964
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
726,999✔
1965
        DirentPtr dirent;
726,999✔
1966
        do {
726,999✔
1967
            // readdir() signals both errors and end-of-stream by returning a
294,735✔
1968
            // null pointer. To distinguish between end-of-stream and errors,
294,735✔
1969
            // the manpage recommends setting errno specifically to 0 before
294,735✔
1970
            // calling it...
294,735✔
1971
            errno = 0;
726,999✔
1972

294,735✔
1973
            dirent = REALM_READDIR(m_dirp);
726,999✔
1974
        } while (!dirent && errno == EAGAIN);
726,999✔
1975

294,735✔
1976
        if (!dirent) {
726,999✔
1977
            if (errno != 0)
151,167✔
1978
                throw SystemError(errno, "readdir() failed");
×
1979
            return false; // End of stream
151,167✔
1980
        }
151,167✔
1981
        const char* name_1 = dirent->d_name;
575,832✔
1982
        std::string name_2 = name_1;
575,832✔
1983
        if (name_2 != "." && name_2 != "..") {
575,832✔
1984
            name = name_2;
273,366✔
1985
            return true;
273,366✔
1986
        }
273,366✔
1987
    }
575,832✔
1988
}
424,533✔
1989

1990
#else
1991

1992
DirScanner::DirScanner(const std::string&, bool)
1993
{
1994
    throw NotImplemented();
1995
}
1996

1997
DirScanner::~DirScanner() noexcept {}
1998

1999
bool DirScanner::next(std::string&)
2000
{
2001
    return false;
2002
}
2003

2004
#endif
2005

2006
} // namespace realm::util
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