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

realm / realm-core / 2422

14 Jun 2024 09:11AM UTC coverage: 90.968% (-0.01%) from 90.978%
2422

push

Evergreen

web-flow
[bindgen] Expose `Obj::add_int()` (#7797)

* Expose 'add_int()'.

* Add CHANGELOG entry.

102154 of 180444 branches covered (56.61%)

214768 of 236093 relevant lines covered (90.97%)

5712495.38 hits per line

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

84.84
/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 <realm/util/file.hpp>
20

21
#include <realm/unicode.hpp>
22
#include <realm/util/errno.hpp>
23
#include <realm/util/encrypted_file_mapping.hpp>
24
#include <realm/util/file_mapper.hpp>
25

26
#include <algorithm>
27
#include <atomic>
28
#include <cerrno>
29
#include <climits>
30
#include <cstddef>
31
#include <cstdio>
32
#include <cstdlib>
33
#include <cstring>
34
#include <fcntl.h>
35
#include <iostream>
36
#include <limits>
37
#include <vector>
38

39
#ifndef _WIN32
40
#include <unistd.h>
41
#include <sys/mman.h>
42
#include <sys/file.h> // BSD / Linux flock()
43
#include <sys/statvfs.h>
44
#endif
45

46
#if REALM_PLATFORM_APPLE
47
#include <sys/attr.h>
48
#include <sys/clonefile.h>
49
#endif
50

51
using namespace realm::util;
52

53
#ifndef _WIN32
54
// All mainstream platforms other than Windows migrated to 64-bit off_t many
55
// years ago. Supporting 32-bit off_t is possible, but not currently implemented.
56
static_assert(sizeof(off_t) == 8 || sizeof(size_t) == 4);
57
#endif
58

59
namespace {
60
bool for_each_helper(const std::string& path, const std::string& dir, realm::util::File::ForEachHandler& handler)
61
{
30✔
62
    using File = realm::util::File;
30✔
63
    realm::util::DirScanner ds{path}; // Throws
30✔
64
    std::string name;
30✔
65
    while (ds.next(name)) {                              // Throws
90✔
66
        std::string subpath = File::resolve(name, path); // Throws
60✔
67
        bool go_on;
60✔
68
        if (File::is_dir(subpath)) {                           // Throws
60✔
69
            std::string subdir = File::resolve(name, dir);     // Throws
24✔
70
            go_on = for_each_helper(subpath, subdir, handler); // Throws
24✔
71
        }
24✔
72
        else {
36✔
73
            go_on = handler(name, dir); // Throws
36✔
74
        }
36✔
75
        if (!go_on)
60✔
76
            return false;
×
77
    }
60✔
78
    return true;
30✔
79
}
30✔
80

81
#ifdef _WIN32
82

83
std::string get_last_error_msg(const char* prefix, DWORD err)
84
{
85
    std::string buffer;
86
    buffer.append(prefix);
87
    size_t offset = buffer.size();
88
    size_t max_msg_size = 1024;
89
    buffer.resize(offset + max_msg_size);
90
    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
91
    DWORD language_id = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
92
    DWORD size =
93
        FormatMessageA(flags, 0, err, language_id, buffer.data() + offset, static_cast<DWORD>(max_msg_size), 0);
94
    if (0 < size)
95
        return buffer;
96
    buffer.resize(offset);
97
    buffer.append("Unknown error");
98
    return buffer;
99
}
100

101
struct WindowsFileHandleHolder {
102
    WindowsFileHandleHolder() = default;
103
    explicit WindowsFileHandleHolder(HANDLE h)
104
        : handle(h)
105
    {
106
    }
107

108
    WindowsFileHandleHolder(WindowsFileHandleHolder&&) = delete;
109
    WindowsFileHandleHolder(const WindowsFileHandleHolder&) = delete;
110
    WindowsFileHandleHolder& operator=(WindowsFileHandleHolder&&) = delete;
111
    WindowsFileHandleHolder& operator=(const WindowsFileHandleHolder&) = delete;
112

113
    operator HANDLE() const noexcept
114
    {
115
        return handle;
116
    }
117

118
    ~WindowsFileHandleHolder()
119
    {
120
        if (handle != INVALID_HANDLE_VALUE) {
121
            ::CloseHandle(handle);
122
        }
123
    }
124

125
    HANDLE handle = INVALID_HANDLE_VALUE;
126
};
127

128
#endif
129

130
#if REALM_HAVE_STD_FILESYSTEM
131
using std::filesystem::u8path;
132

133
void throwIfCreateDirectoryError(std::error_code error, const std::string& path)
134
{
135
    if (!error)
136
        return;
137

138
    // create_directory doesn't raise an error if the path already exists
139
    using std::errc;
140
    if (error == errc::permission_denied || error == errc::read_only_file_system) {
141
        throw realm::FileAccessError(realm::ErrorCodes::PermissionDenied, error.message(), path);
142
    }
143
    else {
144
        throw realm::FileAccessError(realm::ErrorCodes::FileOperationFailed, error.message(), path);
145
    }
146
}
147

148
void throwIfFileError(std::error_code error, const std::string& path)
149
{
150
    if (!error)
151
        return;
152

153
    using std::errc;
154
    if (error == errc::permission_denied || error == errc::read_only_file_system ||
155
        error == errc::device_or_resource_busy || error == errc::operation_not_permitted ||
156
        error == errc::file_exists || error == errc::directory_not_empty) {
157
        throw realm::FileAccessError(realm::ErrorCodes::PermissionDenied, error.message(), path);
158
    }
159
    else {
160
        throw realm::FileAccessError(realm::ErrorCodes::FileOperationFailed, error.message(), path);
161
    }
162
}
163
#endif
164

165
} // anonymous namespace
166

167

168
namespace realm::util {
169
namespace {
170

171
/// Thrown if create_Always was specified and the file did already
172
/// exist.
173
class Exists : public FileAccessError {
174
public:
175
    Exists(const std::string& msg, const std::string& path)
176
        : FileAccessError(ErrorCodes::FileAlreadyExists, msg, path)
24✔
177
    {
48✔
178
    }
48✔
179
};
180

181
} // anonymous namespace
182

183

184
bool try_make_dir(const std::string& path)
185
{
241,746✔
186
#if REALM_HAVE_STD_FILESYSTEM
187
    std::error_code error;
188
    bool result = std::filesystem::create_directory(u8path(path), error);
189
    throwIfCreateDirectoryError(error, path);
190
    return result;
191
#else // POSIX
192
    if (::mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
241,746✔
193
        return true;
75,624✔
194

195
    int err = errno; // Eliminate any risk of clobbering
166,122✔
196
    if (err == EEXIST)
166,122✔
197
        return false;
166,119✔
198

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

201
    switch (err) {
3✔
202
        case EACCES:
3✔
203
        case EROFS:
6✔
204
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
205
        default:
✔
206
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
207
    }
3✔
208
#endif
3✔
209
}
3✔
210

211

212
void make_dir(const std::string& path)
213
{
108✔
214
    if (try_make_dir(path)) // Throws
108✔
215
        return;
90✔
216
    throw Exists(format_errno("Failed to create directory at '%2': %1", EEXIST, path), path);
18✔
217
}
108✔
218

219

220
void make_dir_recursive(std::string path)
221
{
219✔
222
#if REALM_HAVE_STD_FILESYSTEM
223
    std::error_code error;
224
    std::filesystem::create_directories(u8path(path), error);
225
    throwIfCreateDirectoryError(error, path);
226
#else
227
    // Skip the first separator as we're assuming an absolute path
228
    size_t pos = path.find_first_of("/\\");
219✔
229
    if (pos == std::string::npos)
219✔
230
        return;
×
231
    pos += 1;
219✔
232

233
    while (pos < path.size()) {
2,172✔
234
        auto sep = path.find_first_of("/\\", pos);
1,971✔
235
        char c = 0;
1,971✔
236
        if (sep < path.size()) {
1,971✔
237
            c = path[sep];
1,953✔
238
            path[sep] = 0;
1,953✔
239
        }
1,953✔
240
        try_make_dir(path);
1,971✔
241
        if (c) {
1,971✔
242
            path[sep] = c;
1,953✔
243
            pos = sep + 1;
1,953✔
244
        }
1,953✔
245
        else {
18✔
246
            break;
18✔
247
        }
18✔
248
    }
1,971✔
249
#endif
219✔
250
}
219✔
251

252

253
void remove_dir(const std::string& path)
254
{
10,587✔
255
    if (try_remove_dir(path)) // Throws
10,587✔
256
        return;
10,569✔
257
    int err = ENOENT;
18✔
258
    std::string msg = format_errno("Failed to remove directory '%2': %1", err, path);
18✔
259
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
18✔
260
}
10,587✔
261

262

263
bool try_remove_dir(const std::string& path)
264
{
116,304✔
265
#if REALM_HAVE_STD_FILESYSTEM
266
    std::error_code error;
267
    bool result = std::filesystem::remove(u8path(path), error);
268
    throwIfFileError(error, path);
269
    return result;
270
#else // POSIX
271
    if (::rmdir(path.c_str()) == 0)
116,304✔
272
        return true;
116,037✔
273

274
    int err = errno; // Eliminate any risk of clobbering
267✔
275
    if (err == ENOENT)
267✔
276
        return false;
261✔
277

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

293

294
bool try_remove_dir_recursive(const std::string& path)
295
{
105,690✔
296
#if REALM_HAVE_STD_FILESYSTEM
297
    std::error_code error;
298
    auto removed_count = std::filesystem::remove_all(u8path(path), error);
299
    throwIfFileError(error, path);
300
    return removed_count > 0;
301
#else
302
    {
105,690✔
303
        bool allow_missing = true;
105,690✔
304
        DirScanner ds{path, allow_missing}; // Throws
105,690✔
305
        std::string name;
105,690✔
306
        while (ds.next(name)) {                              // Throws
297,678✔
307
            std::string subpath = File::resolve(name, path); // Throws
191,988✔
308
            if (File::is_dir(subpath)) {                     // Throws
191,988✔
309
                try_remove_dir_recursive(subpath);           // Throws
61,344✔
310
            }
61,344✔
311
            else {
130,644✔
312
                File::remove(subpath); // Throws
130,644✔
313
            }
130,644✔
314
        }
191,988✔
315
    }
105,690✔
316
    return try_remove_dir(path); // Throws
105,690✔
317
#endif
105,690✔
318
}
105,690✔
319

320

321
std::string make_temp_dir()
322
{
44,559✔
323
#ifdef _WIN32 // Windows version
324
    std::filesystem::path temp = std::filesystem::temp_directory_path();
325

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

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

345
#else // POSIX.1-2008 version
346

347
#if REALM_ANDROID
348
    std::string buffer = "/data/local/tmp/realm_XXXXXX";
349
#else
350
    char* tmp_dir_env = getenv("TMPDIR");
44,559✔
351
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
44,559✔
352
    if (!buffer.empty() && buffer.back() != '/') {
44,559✔
353
        buffer += "/";
44,559✔
354
    }
44,559✔
355
    buffer += "realm_XXXXXX";
44,559✔
356
#endif
44,559✔
357

358
    if (mkdtemp(buffer.data()) == 0) {
44,559✔
359
        int err = errno;
×
360
        throw SystemError(err, util::format("Failed to create temporary directory: %1", err)); // LCOV_EXCL_LINE
361
    }
×
362
    return buffer;
44,559✔
363
#endif
44,559✔
364
}
44,559✔
365

366
std::string make_temp_file(const char* prefix)
367
{
38,388✔
368
#ifdef _WIN32 // Windows version
369
    std::filesystem::path temp = std::filesystem::temp_directory_path();
370

371
    wchar_t buffer[MAX_PATH];
372
    if (GetTempFileNameW(temp.c_str(), L"rlm", 0, buffer) == 0) {
373
        DWORD error = GetLastError();
374
        throw SystemError(error, get_last_error_msg("GetTempFileName() failed: ", error));
375
    }
376

377
    return std::filesystem::path(buffer).u8string();
378

379
#else // POSIX.1-2008 version
380

381
#if REALM_ANDROID
382
    std::string base_dir = "/data/local/tmp/";
383
#else
384
    char* tmp_dir_env = getenv("TMPDIR");
38,388✔
385
    std::string base_dir = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
38,388✔
386
    if (!base_dir.empty() && base_dir[base_dir.length() - 1] != '/') {
38,388✔
387
        base_dir += '/';
38,388✔
388
    }
38,388✔
389
#endif
38,388✔
390
    std::string filename = util::format("%1%2_XXXXXX", base_dir, prefix);
38,388✔
391
    auto fd = mkstemp(filename.data());
38,388✔
392
    if (fd == -1) {
38,388✔
393
        throw std::system_error(errno, std::system_category(), "mkstemp() failed"); // LCOV_EXCL_LINE
394
    }
×
395
    close(fd);
38,388✔
396
    return filename;
38,388✔
397
#endif
38,388✔
398
}
38,388✔
399

400
size_t page_size()
401
{
26,613,687✔
402
    static constexpr size_t c_min_supported_page_size = 4096;
26,613,687✔
403
    static size_t page_size = [] {
26,613,687✔
404
#ifdef _WIN32
405
        SYSTEM_INFO sysinfo;
406
        GetNativeSystemInfo(&sysinfo);
407
        // DWORD size = sysinfo.dwPageSize;
408
        // On windows we use the allocation granularity instead
409
        DWORD size = sysinfo.dwAllocationGranularity;
410
#else
411
        long size = sysconf(_SC_PAGESIZE);
24✔
412
#endif
24✔
413
        REALM_ASSERT(size > 0 && size % c_min_supported_page_size == 0);
24✔
414
        return static_cast<size_t>(size);
24✔
415
    }();
24✔
416
    return page_size;
26,613,687✔
417
}
26,613,687✔
418

419
File::File() = default;
438,021✔
420
File::File(std::string_view path, Mode m)
421
{
2,847✔
422
    open(path, m);
2,847✔
423
}
2,847✔
424

425
File::~File() noexcept
426
{
440,838✔
427
    close();
440,838✔
428
}
440,838✔
429

430
File::File(File&& f) noexcept
431
{
12✔
432
    m_fd = std::exchange(f.m_fd, invalid_fd);
12✔
433
#ifdef REALM_FILELOCK_EMULATION
434
    m_pipe_fd = std::exchange(f.m_pipe_fd, invalid_fd);
435
    m_has_exclusive_lock = std::exchange(f.m_has_exclusive_lock, false);
436
#endif
437
    m_have_lock = std::exchange(f.m_have_lock, false);
12✔
438
    m_encryption = std::move(f.m_encryption);
12✔
439
}
12✔
440

441
File& File::operator=(File&& f) noexcept
442
{
6✔
443
    close();
6✔
444

445
    m_fd = std::exchange(f.m_fd, invalid_fd);
6✔
446
#ifdef REALM_FILELOCK_EMULATION
447
    m_pipe_fd = std::exchange(f.m_pipe_fd, invalid_fd);
448
    m_has_exclusive_lock = std::exchange(f.m_has_exclusive_lock, false);
449
#endif
450
    m_have_lock = std::exchange(f.m_have_lock, false);
6✔
451
    m_encryption = std::move(f.m_encryption);
6✔
452
    return *this;
6✔
453
}
6✔
454

455

456
void File::open_internal(std::string_view path, AccessMode a, CreateMode c, int flags, bool* success)
457
{
492,390✔
458
    REALM_ASSERT_RELEASE(!is_attached());
492,390✔
459
    m_path = path; // for error reporting and debugging
492,390✔
460

461
#ifdef _WIN32 // Windows version
462

463
    DWORD desired_access = GENERIC_READ;
464
    switch (a) {
465
        case access_ReadOnly:
466
            break;
467
        case access_ReadWrite:
468
            if (flags & flag_Append) {
469
                desired_access = FILE_APPEND_DATA;
470
            }
471
            else {
472
                desired_access |= GENERIC_WRITE;
473
            }
474
            break;
475
    }
476
    // FIXME: Should probably be zero if we are called on behalf of a
477
    // Group instance that is not managed by a SharedGroup instance,
478
    // since in this case concurrenct access is prohibited anyway.
479
    DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
480
    DWORD creation_disposition = 0;
481
    switch (c) {
482
        case create_Auto:
483
            creation_disposition = flags & flag_Trunc ? CREATE_ALWAYS : OPEN_ALWAYS;
484
            break;
485
        case create_Never:
486
            creation_disposition = flags & flag_Trunc ? TRUNCATE_EXISTING : OPEN_EXISTING;
487
            break;
488
        case create_Must:
489
            creation_disposition = CREATE_NEW;
490
            break;
491
    }
492
    DWORD flags_and_attributes = 0;
493
    m_fd = CreateFile2(u8path(m_path).c_str(), desired_access, share_mode, creation_disposition, nullptr);
494
    if (m_fd != INVALID_HANDLE_VALUE) {
495
        m_have_lock = false;
496
        if (success)
497
            *success = true;
498
        return;
499
    }
500

501
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
502
    if (success && err == ERROR_FILE_EXISTS && c == create_Must) {
503
        *success = false;
504
        return;
505
    }
506
    if (success && err == ERROR_FILE_NOT_FOUND && c == create_Never) {
507
        *success = false;
508
        return;
509
    }
510
    std::string msg = get_last_error_msg("CreateFile() failed: ", err);
511
    switch (err) {
512
        case ERROR_SHARING_VIOLATION:
513
        case ERROR_ACCESS_DENIED:
514
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, m_path, int(err));
515
        case ERROR_FILE_NOT_FOUND:
516
        case ERROR_PATH_NOT_FOUND:
517
            throw FileAccessError(ErrorCodes::FileNotFound, msg, m_path, int(err));
518
        case ERROR_FILE_EXISTS:
519
            throw Exists(msg, m_path);
520
        default:
521
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, m_path, int(err));
522
    }
523

524
#else // POSIX version
525

526
    int flags2 = 0;
492,390✔
527
    switch (a) {
492,390✔
528
        case access_ReadOnly:
2,967✔
529
            flags2 = O_RDONLY;
2,967✔
530
            break;
2,967✔
531
        case access_ReadWrite:
489,399✔
532
            flags2 = O_RDWR;
489,399✔
533
            break;
489,399✔
534
    }
492,390✔
535
    switch (c) {
492,369✔
536
        case create_Auto:
462,753✔
537
            flags2 |= O_CREAT;
462,753✔
538
            break;
462,753✔
539
        case create_Never:
28,791✔
540
            break;
28,791✔
541
        case create_Must:
828✔
542
            flags2 |= O_CREAT | O_EXCL;
828✔
543
            break;
828✔
544
    }
492,369✔
545
    if (flags & flag_Trunc)
492,378✔
546
        flags2 |= O_TRUNC;
654✔
547
    if (flags & flag_Append)
492,378✔
548
        flags2 |= O_APPEND;
187,029✔
549
    int fd = ::open(m_path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
492,378✔
550
    if (0 <= fd) {
492,378✔
551
        m_fd = fd;
492,243✔
552
        m_have_lock = false;
492,243✔
553
        if (success)
492,243✔
554
            *success = true;
204✔
555
        return;
492,243✔
556
    }
492,243✔
557

558
    int err = errno; // Eliminate any risk of clobbering
135✔
559
    if (success && err == EEXIST && c == create_Must) {
135✔
560
        *success = false;
30✔
561
        return;
30✔
562
    }
30✔
563
    if (success && err == ENOENT && c == create_Never) {
105!
564
        *success = false;
×
565
        return;
×
566
    }
×
567
    std::string msg = format_errno("Failed to open file at path '%2': %1", err, path);
105✔
568
    switch (err) {
105✔
569
        case EACCES:
6✔
570
        case EPERM:
6✔
571
        case EROFS:
6✔
572
        case ETXTBSY:
6✔
573
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
574
        case ENOENT:
30✔
575
            if (c != create_Never)
30✔
576
                msg = util::format("Failed to open file at path '%1': parent directory does not exist", path);
6✔
577
            throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
30✔
578
        case EEXIST:
36✔
579
            throw Exists(msg, m_path);
36✔
580
        case ENOTDIR:
✔
581
            msg = format("Failed to open file at path '%1': parent path is not a directory", path);
×
582
            [[fallthrough]];
×
583
        default:
36✔
584
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
585
    }
105✔
586

587
#endif
105✔
588
}
105✔
589

590

591
void File::close() noexcept
592
{
929,373✔
593
    m_encryption.reset();
929,373✔
594
    if (m_fd == invalid_fd)
929,373✔
595
        return;
437,226✔
596
    if (m_have_lock)
492,147✔
597
        unlock();
153✔
598
#ifdef _WIN32 // Windows version
599
    BOOL r = CloseHandle(m_fd);
600
    REALM_ASSERT_RELEASE(r);
601
#else // POSIX version
602
    int r = ::close(m_fd);
492,147✔
603
    REALM_ASSERT_RELEASE(r == 0);
492,147✔
604
#endif
492,147✔
605

606
    m_fd = invalid_fd;
492,147✔
607
}
492,147✔
608

609
size_t File::read_static(FileDesc fd, SizeType pos, char* data, size_t size)
610
{
879,750✔
611
#ifdef _WIN32 // Windows version
612
    char* const data_0 = data;
613
    while (0 < size) {
614
        DWORD n = std::numeric_limits<DWORD>::max();
615
        if (int_less_than(size, n))
616
            n = static_cast<DWORD>(size);
617
        DWORD r = 0;
618
        OVERLAPPED o{};
619
        o.Offset = static_cast<DWORD>(pos);
620
        o.OffsetHigh = static_cast<DWORD>(pos >> 32);
621
        if (!ReadFile(fd, data, n, &r, &o)) {
622
            DWORD err = GetLastError();
623
            if (err == ERROR_HANDLE_EOF)
624
                break;
625
            throw SystemError(int(err), "ReadFile() failed");
626
        }
627
        REALM_ASSERT_RELEASE(r > 0 && r <= n);
628
        size -= size_t(r);
629
        data += size_t(r);
630
        pos += r;
631
    }
632
    return data - data_0;
633

634
#else // POSIX version
635

636
    char* const data_0 = data;
879,750✔
637
    while (0 < size) {
1,759,305✔
638
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
639
        size_t n = std::min(size, size_t(SSIZE_MAX));
879,849✔
640
        ssize_t r = pread(fd, data, n, pos);
879,849✔
641
        if (r == 0)
879,849✔
642
            break;
294✔
643
        if (r < 0)
879,555✔
644
            goto error; // LCOV_EXCL_LINE
645
        REALM_ASSERT_RELEASE(size_t(r) <= n);
879,555✔
646
        size -= size_t(r);
879,555✔
647
        data += size_t(r);
879,555✔
648
        pos += r;
879,555✔
649
    }
879,555✔
650
    return data - data_0;
879,750✔
651

652
error:
×
653
    // LCOV_EXCL_START
654
    throw SystemError(errno, "read() failed");
×
655
// LCOV_EXCL_STOP
656
#endif
879,750✔
657
}
879,750✔
658

659

660
size_t File::read(SizeType pos, char* data, size_t size)
661
{
7,032✔
662
    REALM_ASSERT_RELEASE(is_attached());
7,032✔
663

664
    if (m_encryption) {
7,032✔
665
        Map<char> read_map(*this, pos, access_ReadOnly, size);
×
666
        util::encryption_read_barrier(read_map, 0, size);
×
667
        memcpy(data, read_map.get_addr(), size);
×
668
        return size;
×
669
    }
×
670

671
    return read_static(m_fd, pos, data, size);
7,032✔
672
}
7,032✔
673

674
void File::write_static(FileDesc fd, SizeType pos, const char* data, size_t size)
675
{
623,895✔
676
#ifdef _WIN32
677
    while (0 < size) {
678
        DWORD n = std::numeric_limits<DWORD>::max();
679
        if (int_less_than(size, n))
680
            n = static_cast<DWORD>(size);
681
        DWORD r = 0;
682
        OVERLAPPED o{};
683
        o.Offset = static_cast<DWORD>(pos);
684
        o.OffsetHigh = static_cast<DWORD>(pos >> 32);
685
        if (!WriteFile(fd, data, n, &r, &o))
686
            goto error;
687
        REALM_ASSERT_RELEASE(r == n); // Partial writes are not possible.
688
        size -= size_t(r);
689
        data += size_t(r);
690
        pos += r;
691
    }
692
    return;
693

694
error:
695
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
696
    if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
697
        std::string msg = get_last_error_msg("WriteFile() failed: ", err);
698
        throw OutOfDiskSpace(msg);
699
    }
700
    throw SystemError(err, "WriteFile() failed");
701
#else
702
    while (0 < size) {
1,247,673✔
703
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
704
        size_t n = std::min(size, size_t(SSIZE_MAX));
623,778✔
705
        ssize_t r = pwrite(fd, data, n, pos);
623,778✔
706
        if (r < 0)
623,778✔
707
            goto error; // LCOV_EXCL_LINE
708
        REALM_ASSERT_RELEASE(r != 0);
623,778✔
709
        REALM_ASSERT_RELEASE(size_t(r) <= n);
623,778✔
710
        size -= size_t(r);
623,778✔
711
        data += size_t(r);
623,778✔
712
        pos += off_t(r);
623,778✔
713
    }
623,778✔
714
    return;
623,895✔
715

716
error:
623,895✔
717
    // LCOV_EXCL_START
718
    int err = errno; // Eliminate any risk of clobbering
×
719
    auto msg = format_errno("write() failed: %1", err);
×
720
    if (err == ENOSPC || err == EDQUOT) {
×
721
        throw OutOfDiskSpace(msg);
×
722
    }
×
723
    throw SystemError(err, msg);
×
724
    // LCOV_EXCL_STOP
725

726
#endif
×
727
}
×
728

729
void File::write(SizeType pos, const char* data, size_t size)
730
{
53,958✔
731
    REALM_ASSERT_RELEASE(is_attached());
53,958✔
732

733
    if (m_encryption) {
53,958✔
734
        Map<char> write_map(*this, pos, access_ReadWrite, size);
852✔
735
        util::encryption_read_barrier(write_map, 0, size);
852✔
736
        memcpy(write_map.get_addr(), data, size);
852✔
737
        realm::util::encryption_write_barrier(write_map, 0, size);
852✔
738
        return;
852✔
739
    }
852✔
740

741
    write_static(m_fd, pos, data, size);
53,106✔
742
}
53,106✔
743

744
File::SizeType File::get_file_pos()
745
{
6,852✔
746
#ifdef _WIN32
747
    LONG high_dword = 0;
748
    LARGE_INTEGER li;
749
    LARGE_INTEGER res;
750
    li.QuadPart = 0;
751
    bool ok = SetFilePointerEx(m_fd, li, &res, FILE_CURRENT);
752
    if (!ok)
753
        throw SystemError(GetLastError(), "SetFilePointer() failed");
754

755
    return SizeType(res.QuadPart);
756
#else
757
    auto pos = lseek(m_fd, 0, SEEK_CUR);
6,852✔
758
    if (pos < 0) {
6,852✔
759
        throw SystemError(errno, "lseek() failed");
×
760
    }
×
761
    return SizeType(pos);
6,852✔
762
#endif
6,852✔
763
}
6,852✔
764

765
File::SizeType File::get_size_static(const std::string& path)
766
{
1,353✔
767
    File f(path);
1,353✔
768
    return f.get_size();
1,353✔
769
}
1,353✔
770

771
File::SizeType File::get_size_static(FileDesc fd)
772
{
1,881,879✔
773
#ifdef _WIN32
774
    LARGE_INTEGER large_int;
775
    if (GetFileSizeEx(fd, &large_int)) {
776
        File::SizeType size;
777
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
778
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
779

780
        return size;
781
    }
782
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
783

784
#else // POSIX version
785

786
    struct stat statbuf;
1,881,879✔
787
    if (::fstat(fd, &statbuf) == 0) {
1,881,960✔
788
        SizeType size;
1,881,960✔
789
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,881,960✔
790
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
791

792
        return size;
1,881,960✔
793
    }
1,881,960✔
794
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
795

796
#endif
1,881,879✔
797
}
1,881,879✔
798

799
File::SizeType File::get_size() const
800
{
1,881,381✔
801
    REALM_ASSERT_RELEASE(is_attached());
1,881,381✔
802
    File::SizeType size = get_size_static(m_fd);
1,881,381✔
803

804
    if (m_encryption) {
1,881,381✔
805
        return encrypted_size_to_data_size(size);
3,993✔
806
    }
3,993✔
807
    return size;
1,877,388✔
808
}
1,881,381✔
809

810

811
void File::resize(SizeType size)
812
{
278,511✔
813
    REALM_ASSERT_RELEASE(is_attached());
278,511✔
814

815
    if (m_encryption)
278,511✔
816
        size = data_size_to_encrypted_size(size);
150✔
817

818
#ifdef _WIN32 // Windows version
819
    FILE_END_OF_FILE_INFO info;
820
    info.EndOfFile.QuadPart = size;
821
    if (!SetFileInformationByHandle(m_fd, FileEndOfFileInfo, &info, sizeof(info))) {
822
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
823
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
824
            throw OutOfDiskSpace(get_last_error_msg("SetFileInformationByHandle() failed: ", err));
825
        }
826
        throw SystemError(int(err), "SetFileInformationByHandle() failed");
827
    }
828
#else // POSIX version
829

830
    off_t size2;
278,511✔
831
    if (int_cast_with_overflow_detect(size, size2))
278,511✔
832
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
833

834
    // POSIX specifies that introduced bytes read as zero. This is not
835
    // required by File::resize().
836
    if (::ftruncate(m_fd, size2) != 0) {
278,511✔
837
        int err = errno; // Eliminate any risk of clobbering
×
838
        auto msg = format_errno("ftruncate() failed: %1", err);
×
839
        if (err == ENOSPC || err == EDQUOT) {
×
840
            throw OutOfDiskSpace(msg);
×
841
        }
×
842
        throw SystemError(err, msg);
×
843
    }
×
844

845
#endif
278,511✔
846
}
278,511✔
847

848

849
void File::prealloc(SizeType size)
850
{
170,673✔
851
    REALM_ASSERT_RELEASE(is_attached());
170,673✔
852
    if (size <= get_size()) {
170,673✔
853
        return;
39,561✔
854
    }
39,561✔
855

856
    SizeType new_size = size;
131,112✔
857
    if (m_encryption) {
131,112✔
858
        new_size = data_size_to_encrypted_size(size);
639✔
859
        REALM_ASSERT(new_size > size);
639✔
860
        REALM_ASSERT(size == encrypted_size_to_data_size(new_size));
639✔
861
    }
639✔
862

863
    auto manually_consume_space = [&]() {
131,112✔
864
        constexpr uint16_t chunk_size = 4096;
×
865
        SizeType write_pos = get_size_static(m_fd); // raw size
×
866
        SizeType num_bytes = new_size - write_pos;
×
867
        std::string zeros(chunk_size, '\0');
×
868
        while (num_bytes > 0) {
×
869
            uint16_t t = uint16_t(std::min<SizeType>(num_bytes, chunk_size));
×
870
            write_static(m_fd, write_pos, zeros.c_str(), t);
×
871
            num_bytes -= t;
×
872
            write_pos += t;
×
873
        }
×
874
    };
×
875

876
#if REALM_HAVE_POSIX_FALLOCATE
71,031✔
877
    // Mostly Linux only
878
    if (!prealloc_if_supported(0, new_size)) {
71,031✔
879
        manually_consume_space();
880
    }
881
#elif REALM_PLATFORM_APPLE // Non-atomic fallback
882
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
883
    // ftruncate().
884

885
    struct stat statbuf;
60,081✔
886
    if (::fstat(m_fd, &statbuf) != 0) {
60,081✔
887
        int err = errno;
888
        throw SystemError(err, "fstat() inside prealloc() failed");
889
    }
890

891
    SizeType allocated_size;
60,081✔
892
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
60,081✔
893
        throw RuntimeError(ErrorCodes::RangeError,
894
                           util::format("Overflow on block conversion to SizeType %1", statbuf.st_blocks));
895
    }
896
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
60,081✔
897
        throw RuntimeError(ErrorCodes::RangeError,
898
                           util::format("Overflow computing existing file space allocation blocks: %1 block size %2",
899
                                        allocated_size, S_BLKSIZE));
900
    }
901

902
    // Only attempt to preallocate space if there's not already sufficient free space in the file.
903
    // APFS would fail with EINVAL if we attempted it, and HFS+ would preallocate extra space unnecessarily.
904
    // See <https://github.com/realm/realm-core/issues/3005> for details.
905
    if (new_size > allocated_size) {
60,081✔
906
        off_t to_allocate = static_cast<off_t>(new_size - statbuf.st_size);
60,045✔
907
        fstore_t store = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, to_allocate, 0};
60,045✔
908
        int ret = 0;
60,045✔
909
        do {
60,045✔
910
            ret = fcntl(m_fd, F_PREALLOCATE, &store);
60,045✔
911
        } while (ret == -1 && errno == EINTR);
60,045!
912
        if (ret == -1) {
60,045✔
913
            // Consider fcntl() as an optimization on Apple devices, where if it fails,
914
            // we fall back to manually consuming space which is slower but may succeed in some
915
            // cases where fcntl fails. Some known cases are:
916
            // 1) There's a timing sensitive bug on APFS which causes fcntl to sometimes throw EINVAL.
917
            // This might not be the case, but we'll fall back and attempt to manually allocate all the requested
918
            // space. Worst case, this might also fail, but there is also a chance it will succeed. We don't
919
            // call this in the first place because using fcntl(F_PREALLOCATE) will be faster if it works (it has
920
            // been reliable on HSF+).
921
            // 2) fcntl will fail with ENOTSUP on non-supported file systems such as ExFAT. In this case
922
            // the fallback should succeed.
923
            // 3) if there is some other error such as no space left (ENOSPC) we will expect to fail again later
924
            manually_consume_space();
925
        }
926
    }
60,045✔
927

928
    int ret = 0;
60,081✔
929

930
    do {
60,081✔
931
        ret = ftruncate(m_fd, new_size);
60,081✔
932
    } while (ret == -1 && errno == EINTR);
60,081!
933

934
    if (ret != 0) {
60,081✔
935
        int err = errno;
936
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
937
        // so this is some other runtime error and not OutOfDiskSpace
938
        throw SystemError(err, "ftruncate() inside prealloc() failed");
939
    }
940
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
941
    manually_consume_space();
942
#else
943
#error Please check if/how your OS supports file preallocation
944
#endif // REALM_HAVE_POSIX_FALLOCATE
945
}
131,112✔
946

947

948
bool File::prealloc_if_supported(SizeType offset, SizeType size)
949
{
71,031✔
950
    REALM_ASSERT_RELEASE(is_attached());
71,031✔
951

952
#if REALM_HAVE_POSIX_FALLOCATE
71,031✔
953

954
    REALM_ASSERT_RELEASE(is_prealloc_supported());
71,031✔
955

956
    if (size == 0) {
71,031✔
957
        // calling posix_fallocate with a size of 0 will cause a return of EINVAL
958
        // since this is a meaningless operation anyway, we just return early here
959
        return true;
960
    }
961

962
    // posix_fallocate() does not set errno, it returns the error (if any) or zero.
963
    // It is also possible for it to be interrupted by EINTR according to some man pages (ex fedora 24)
964
    int status;
71,031✔
965
    do {
71,031✔
966
        status = ::posix_fallocate(m_fd, offset, size);
71,031✔
967
    } while (status == EINTR);
71,031✔
968

969
    if (REALM_LIKELY(status == 0)) {
71,031✔
970
        return true;
71,031✔
971
    }
71,031✔
972

973
    if (status == EINVAL || status == EPERM || status == EOPNOTSUPP) {
×
974
        return false; // Retry with non-atomic version
975
    }
976

977
    auto msg = format_errno("posix_fallocate() failed: %1", status);
978
    if (status == ENOSPC || status == EDQUOT) {
×
979
        throw OutOfDiskSpace(msg);
980
    }
981
    throw SystemError(status, msg);
982

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

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

991
#else
992

993
    static_cast<void>(offset);
994
    static_cast<void>(size);
995

996
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
997

998
#endif
999
    return false;
×
1000
}
×
1001

1002

1003
bool File::is_prealloc_supported()
1004
{
71,031✔
1005
#if REALM_HAVE_POSIX_FALLOCATE
71,031✔
1006
    return true;
71,031✔
1007
#else
1008
    return false;
1009
#endif
1010
}
71,031✔
1011

1012
void File::seek(SizeType position)
1013
{
6,858✔
1014
    REALM_ASSERT_RELEASE(is_attached());
6,858✔
1015
    seek_static(m_fd, position);
6,858✔
1016
}
6,858✔
1017

1018
void File::seek_static(FileDesc fd, SizeType position)
1019
{
6,858✔
1020
#ifdef _WIN32 // Windows version
1021

1022
    LARGE_INTEGER large_int;
1023
    if (int_cast_with_overflow_detect(position, large_int.QuadPart))
1024
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1025

1026
    if (!SetFilePointerEx(fd, large_int, 0, FILE_BEGIN))
1027
        throw SystemError(GetLastError(), "SetFilePointerEx() failed");
1028

1029
#else // POSIX version
1030

1031
    off_t position2;
6,858✔
1032
    if (int_cast_with_overflow_detect(position, position2))
6,858✔
1033
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1034

1035
    if (0 <= ::lseek(fd, position2, SEEK_SET))
6,858✔
1036
        return;
6,858✔
1037
    throw SystemError(errno, "lseek() failed");
×
1038

1039
#endif
6,858✔
1040
}
6,858✔
1041

1042
// FIXME: The current implementation may not guarantee that data is
1043
// actually written to disk. POSIX is rather vague on what fsync() has
1044
// to do unless _POSIX_SYNCHRONIZED_IO is defined. See also
1045
// http://www.humboldt.co.uk/2009/03/fsync-across-platforms.html.
1046
void File::sync()
1047
{
783✔
1048
    REALM_ASSERT_RELEASE(is_attached());
783✔
1049

1050
#if defined _WIN32 // Windows version
1051

1052
    if (FlushFileBuffers(m_fd))
1053
        return;
1054
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1055

1056
#elif REALM_PLATFORM_APPLE
1057

1058
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
303✔
1059
        return;
303✔
1060
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1061

1062
#else // POSIX version
1063

1064
    if (::fsync(m_fd) == 0)
480✔
1065
        return;
480✔
1066
    throw SystemError(errno, "fsync() failed");
1067

1068
#endif
480✔
1069
}
783✔
1070

1071
void File::barrier()
1072
{
348✔
1073
#if REALM_PLATFORM_APPLE
174✔
1074
    if (::fcntl(m_fd, F_BARRIERFSYNC) == 0)
174✔
1075
        return;
174✔
1076
        // If fcntl fails, we fallback to full sync.
1077
        // This is known to occur on exFAT which does not support F_BARRIERSYNC.
1078
#endif
1079
    sync();
174✔
1080
}
174✔
1081

1082
#ifndef _WIN32
1083
// little helper
1084
static void _unlock(int m_fd)
1085
{
4,071,351✔
1086
    int r;
4,071,351✔
1087
    do {
4,071,351✔
1088
        r = flock(m_fd, LOCK_UN);
4,071,351✔
1089
    } while (r != 0 && errno == EINTR);
4,071,351!
1090
    if (r) {
4,071,351✔
1091
        throw SystemError(errno, "File::unlock() has failed");
×
1092
    }
×
1093
}
4,071,351✔
1094
#endif
1095

1096
bool File::rw_lock(bool exclusive, bool non_blocking)
1097
{
425,619✔
1098
    // exclusive blocking rw locks not implemented for emulation
1099
    REALM_ASSERT(!exclusive || non_blocking);
425,619✔
1100

1101
#ifndef REALM_FILELOCK_EMULATION
425,619✔
1102
    return lock(exclusive, non_blocking);
425,619✔
1103
#else
1104
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1105

1106
    // First obtain an exclusive lock on the file proper
1107
    int operation = LOCK_EX;
1108
    if (non_blocking)
1109
        operation |= LOCK_NB;
1110
    int status;
1111
    do {
1112
        status = flock(m_fd, operation);
1113
    } while (status != 0 && errno == EINTR);
1114
    if (status != 0 && errno == EWOULDBLOCK)
1115
        return false;
1116
    if (status != 0)
1117
        throw SystemError(errno, "flock() failed");
1118
    m_has_exclusive_lock = true;
1119

1120
    // Every path through this function except for successfully acquiring an
1121
    // exclusive lock needs to release the flock() before returning.
1122
    UnlockGuard ulg(*this);
1123

1124
    // now use a named pipe to emulate locking in conjunction with using exclusive lock
1125
    // on the file proper.
1126
    // exclusively locked: we can't sucessfully write to the pipe.
1127
    //                     AND we continue to hold the exclusive lock.
1128
    //                     (unlock must lift the exclusive lock).
1129
    // shared locked: we CAN succesfully write to the pipe. We open the pipe for reading
1130
    //                before releasing the exclusive lock.
1131
    //                (unlock must close the pipe for reading)
1132
    REALM_ASSERT_RELEASE(m_pipe_fd == -1);
1133
    if (m_fifo_path.empty())
1134
        m_fifo_path = m_path + ".fifo";
1135

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

1140
    // Optimistically try to open the fifo. This may fail due to the fifo not
1141
    // existing, but since it usually exists this is faster than trying to create
1142
    // it first.
1143
    int fd = ::open(m_fifo_path.c_str(), mode);
1144
    if (fd == -1) {
1145
        int err = errno;
1146
        if (exclusive) {
1147
            if (err == ENOENT || err == ENXIO) {
1148
                // If the fifo either doesn't exist or there's no readers with the
1149
                // other end of the pipe open (ENXIO) then we have an exclusive lock
1150
                // and are done.
1151
                ulg.release();
1152
                return true;
1153
            }
1154

1155
            // Otherwise we got an unexpected error
1156
            throw std::system_error(err, std::system_category(), "opening lock fifo for writing failed");
1157
        }
1158

1159
        if (err == ENOENT) {
1160
            // The fifo doesn't exist and we're opening in shared mode, so we
1161
            // need to create it.
1162
            if (!m_fifo_dir_path.empty())
1163
                try_make_dir(m_fifo_dir_path);
1164
            status = mkfifo(m_fifo_path.c_str(), 0666);
1165
            if (status != 0)
1166
                throw std::system_error(errno, std::system_category(), "creating lock fifo for reading failed");
1167

1168
            // Try again to open the fifo now that it exists
1169
            fd = ::open(m_fifo_path.c_str(), mode);
1170
            err = errno;
1171
        }
1172

1173
        if (fd == -1)
1174
            throw std::system_error(err, std::system_category(), "opening lock fifo for reading failed");
1175
    }
1176

1177
    // We successfully opened the pipe. If we're trying to acquire an exclusive
1178
    // lock that means there's a reader (aka a shared lock) and we've failed.
1179
    // Release the exclusive lock and back out.
1180
    if (exclusive) {
1181
        ::close(fd);
1182
        return false;
1183
    }
1184

1185
    // We're in shared mode, so opening the fifo means we've successfully acquired
1186
    // a shared lock and are done.
1187
    ulg.release();
1188
    rw_unlock();
1189
    m_pipe_fd = fd;
1190
    return true;
1191
#endif // REALM_FILELOCK_EMULATION
1192
}
425,619✔
1193

1194
bool File::lock(bool exclusive, bool non_blocking)
1195
{
4,213,041✔
1196
    REALM_ASSERT_RELEASE(is_attached());
4,213,041✔
1197
    REALM_ASSERT_RELEASE(!m_have_lock);
4,213,041✔
1198

1199
#ifdef _WIN32 // Windows version
1200

1201
    // Under Windows a file lock must be explicitely released before
1202
    // the file is closed. It will eventually be released by the
1203
    // system, but there is no guarantees on the timing.
1204

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

1256
void File::unlock() noexcept
1257
{
4,071,501✔
1258
    if (!m_have_lock)
4,071,501✔
1259
        return;
×
1260

1261
#ifdef _WIN32 // Windows version
1262
    OVERLAPPED overlapped;
1263
    overlapped.hEvent = 0;
1264
    overlapped.OffsetHigh = 0;
1265
    overlapped.Offset = 0;
1266
    overlapped.Pointer = 0;
1267
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1268
    REALM_ASSERT_RELEASE(r);
1269
#else
1270
    _unlock(m_fd);
4,071,501✔
1271
#endif
4,071,501✔
1272
    m_have_lock = false;
4,071,501✔
1273
}
4,071,501✔
1274

1275
void File::rw_unlock() noexcept
1276
{
283,560✔
1277
#ifndef REALM_FILELOCK_EMULATION
283,560✔
1278
    unlock();
283,560✔
1279
#else
1280
    // Coming here with an exclusive lock, we must release that lock.
1281
    // Coming here with a shared lock, we must close the pipe that we have opened for reading.
1282
    //   - we have to do that under the protection of a proper exclusive lock to serialize
1283
    //     with anybody trying to obtain a lock concurrently.
1284
    if (has_shared_lock()) {
1285
        // shared lock. We need to reacquire the exclusive lock on the file
1286
        int status;
1287
        do {
1288
            status = flock(m_fd, LOCK_EX);
1289
        } while (status != 0 && errno == EINTR);
1290
        REALM_ASSERT(status == 0);
1291
        // close the pipe (== release the shared lock)
1292
        ::close(m_pipe_fd);
1293
        m_pipe_fd = -1;
1294
    }
1295
    else {
1296
        REALM_ASSERT(m_has_exclusive_lock);
1297
    }
1298
    _unlock(m_fd);
1299
    m_has_exclusive_lock = false;
1300
#endif // REALM_FILELOCK_EMULATION
1301
}
283,560✔
1302

1303
bool File::exists(const std::string& path)
1304
{
850,587✔
1305
#if REALM_HAVE_STD_FILESYSTEM
1306
    return std::filesystem::exists(u8path(path));
1307
#else // POSIX
1308
    if (::access(path.c_str(), F_OK) == 0)
850,587✔
1309
        return true;
30,735✔
1310
    int err = errno; // Eliminate any risk of clobbering
819,852✔
1311
    switch (err) {
819,852✔
1312
        case EACCES:
✔
1313
        case ENOENT:
819,837✔
1314
        case ENOTDIR:
819,837✔
1315
            return false;
819,837✔
1316
    }
819,852✔
1317
    throw SystemError(err, "access() failed");
24✔
1318
#endif
819,852✔
1319
}
819,852✔
1320

1321

1322
bool File::is_dir(const std::string& path)
1323
{
231,909✔
1324
#if REALM_HAVE_STD_FILESYSTEM
1325
    return std::filesystem::is_directory(u8path(path));
1326
#elif !defined(_WIN32)
1327
    struct stat statbuf;
1328
    if (::stat(path.c_str(), &statbuf) == 0)
231,909✔
1329
        return S_ISDIR(statbuf.st_mode);
224,934✔
1330
    int err = errno; // Eliminate any risk of clobbering
6,975✔
1331
    switch (err) {
6,975✔
1332
        case EACCES:
✔
1333
        case ENOENT:
6,975✔
1334
        case ENOTDIR:
6,975✔
1335
            return false;
6,975✔
1336
    }
6,975✔
1337
    throw SystemError(err, "stat() failed");
×
1338
#else
1339
    static_cast<void>(path);
1340
    throw NotImplemented();
1341
#endif
1342
}
6,975✔
1343

1344

1345
void File::remove(const std::string& path)
1346
{
174,462✔
1347
    if (try_remove(path))
174,462✔
1348
        return;
174,447✔
1349
    int err = ENOENT;
15✔
1350
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
15✔
1351
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
15✔
1352
}
174,462✔
1353

1354

1355
bool File::try_remove(const std::string& path)
1356
{
235,911✔
1357
#if REALM_HAVE_STD_FILESYSTEM
1358
    std::error_code error;
1359
    bool result = std::filesystem::remove(u8path(path), error);
1360
    throwIfFileError(error, path);
1361
    return result;
1362
#else // POSIX
1363
    if (::unlink(path.c_str()) == 0)
235,911✔
1364
        return true;
196,206✔
1365

1366
    int err = errno; // Eliminate any risk of clobbering
39,705✔
1367
    if (err == ENOENT)
39,705✔
1368
        return false;
39,660✔
1369

1370
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
45✔
1371
    switch (err) {
45✔
1372
        case EACCES:
✔
1373
        case EROFS:
✔
1374
        case ETXTBSY:
✔
1375
        case EBUSY:
✔
1376
        case EPERM:
21✔
1377
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
21✔
1378
        case ENOENT:
✔
1379
            return false;
×
1380
        default:
24✔
1381
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
24✔
1382
    }
45✔
1383
#endif
45✔
1384
}
45✔
1385

1386

1387
void File::move(const std::string& old_path, const std::string& new_path)
1388
{
300✔
1389
#if REALM_HAVE_STD_FILESYSTEM
1390
    std::error_code error;
1391
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1392

1393
    if (error == std::errc::no_such_file_or_directory) {
1394
        throw FileAccessError(ErrorCodes::FileNotFound, error.message(), old_path);
1395
    }
1396
    throwIfFileError(error, old_path);
1397
#else
1398
    int r = rename(old_path.c_str(), new_path.c_str());
300✔
1399
    if (r == 0)
300✔
1400
        return;
300✔
1401
    int err = errno; // Eliminate any risk of clobbering
×
1402
    std::string msg = format_errno("Failed to rename file from '%2' to '%3': %1", err, old_path, new_path);
×
1403
    switch (err) {
×
1404
        case EACCES:
×
1405
        case EROFS:
×
1406
        case ETXTBSY:
×
1407
        case EBUSY:
×
1408
        case EPERM:
×
1409
        case EEXIST:
×
1410
        case ENOTEMPTY:
×
1411
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, old_path, err);
×
1412
        case ENOENT:
×
1413
            throw FileAccessError(ErrorCodes::FileNotFound, msg, old_path, err);
×
1414
        default:
×
1415
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, old_path, err);
×
1416
    }
×
1417
#endif
×
1418
}
×
1419

1420

1421
bool File::copy(const std::string& origin_path, const std::string& target_path, bool overwrite_existing)
1422
{
390✔
1423
#if REALM_HAVE_STD_FILESYSTEM
1424
    auto options = overwrite_existing ? std::filesystem::copy_options::overwrite_existing
1425
                                      : std::filesystem::copy_options::skip_existing;
1426
    return std::filesystem::copy_file(u8path(origin_path), u8path(target_path), options); // Throws
1427
#else
1428
#if REALM_PLATFORM_APPLE
198✔
1429
    // Try to use clonefile and fall back to manual copying if it fails
1430
    if (clonefile(origin_path.c_str(), target_path.c_str(), 0) == 0) {
198✔
1431
        return true;
180✔
1432
    }
180✔
1433
    if (errno == EEXIST && !overwrite_existing) {
18✔
1434
        return false;
6✔
1435
    }
6✔
1436
#endif
12✔
1437

1438
    File origin_file{origin_path, mode_Read}; // Throws
204✔
1439
    File target_file;
204✔
1440
    bool did_create = false;
204✔
1441
    target_file.open(target_path, did_create); // Throws
204✔
1442
    if (!did_create && !overwrite_existing) {
204✔
1443
        return false;
6✔
1444
    }
6✔
1445

1446
    size_t buffer_size = 4096;
198✔
1447
    off_t pos = 0;
198✔
1448
    auto buffer = std::make_unique<char[]>(buffer_size); // Throws
198✔
1449
    for (;;) {
576✔
1450
        size_t n = origin_file.read(pos, buffer.get(), buffer_size); // Throws
576✔
1451
        target_file.write(pos, buffer.get(), n);                     // Throws
576✔
1452
        pos += n;
576✔
1453
        if (n < buffer_size)
576✔
1454
            break;
198✔
1455
    }
576✔
1456

1457
    return true;
198✔
1458
#endif
204✔
1459
}
204✔
1460

1461

1462
bool File::is_same_file_static(FileDesc f1, FileDesc f2, const std::string& path1, const std::string& path2)
1463
{
24✔
1464
    return get_unique_id(f1, path1) == get_unique_id(f2, path2);
24✔
1465
}
24✔
1466

1467
bool File::is_same_file(const File& f) const
1468
{
24✔
1469
    REALM_ASSERT_RELEASE(is_attached());
24✔
1470
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1471
    return is_same_file_static(m_fd, f.m_fd, m_path, f.m_path);
24✔
1472
}
24✔
1473

1474
FileDesc File::dup_file_desc(FileDesc fd)
1475
{
×
1476
    FileDesc fd_duped;
×
1477
#ifdef _WIN32
1478
    if (!DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &fd_duped, 0, FALSE, DUPLICATE_SAME_ACCESS))
1479
        throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
1480
#else
1481
    fd_duped = dup(fd);
×
1482

1483
    if (fd_duped == -1) {
×
1484
        int err = errno; // Eliminate any risk of clobbering
×
1485
        throw std::system_error(err, std::system_category(), "dup() failed");
×
1486
    }
×
1487
#endif // conditonal on _WIN32
×
1488
    return fd_duped;
×
1489
}
×
1490

1491
FileDesc File::get_descriptor() const
1492
{
98,853✔
1493
    return m_fd;
98,853✔
1494
}
98,853✔
1495

1496
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1497
{
216✔
1498
#ifdef _WIN32 // Windows version
1499
    // CreateFile2 with creationDisposition OPEN_EXISTING will return a file handle only if the file exists
1500
    // otherwise it will raise ERROR_FILE_NOT_FOUND. This call will never create a new file.
1501
    WindowsFileHandleHolder fileHandle(::CreateFile2(u8path(path).c_str(), FILE_READ_ATTRIBUTES,
1502
                                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1503
                                                     OPEN_EXISTING, nullptr));
1504

1505
    if (fileHandle == INVALID_HANDLE_VALUE) {
1506
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1507
            return none;
1508
        }
1509
        throw SystemError(GetLastError(), "CreateFileW failed");
1510
    }
1511

1512
    return get_unique_id(fileHandle, path);
1513
#else // POSIX version
1514
    struct stat statbuf;
216✔
1515
    if (::stat(path.c_str(), &statbuf) == 0) {
216✔
1516
        if (statbuf.st_size == 0) {
216✔
1517
            // On exFAT systems the inode and device are not populated correctly until the file
1518
            // has been allocated some space. The uid can also be reassigned if the file is
1519
            // truncated to zero. This has led to bugs where a unique id returned here was
1520
            // reused by different files. The following check ensures that this is not
1521
            // happening anywhere else in future code.
1522
            return none;
×
1523
        }
×
1524
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
216✔
1525
    }
216✔
1526
    int err = errno; // Eliminate any risk of clobbering
×
1527
    // File doesn't exist
1528
    if (err == ENOENT)
×
1529
        return none;
×
1530
    throw SystemError(err, format_errno("fstat() failed: %1 for '%2'", err, path));
×
1531
#endif
×
1532
}
×
1533

1534
File::UniqueID File::get_unique_id(FileDesc file, const std::string& debug_path)
1535
{
48✔
1536
#ifdef _WIN32 // Windows version
1537
    REALM_ASSERT(file != nullptr);
1538
    File::UniqueID ret;
1539
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1540
        throw std::system_error(GetLastError(), std::system_category(),
1541
                                util::format("GetFileInformationByHandleEx() failed for '%1'", debug_path));
1542
    }
1543

1544
    return ret;
1545
#else // POSIX version
1546
    REALM_ASSERT(file >= 0);
48✔
1547
    struct stat statbuf;
48✔
1548
    if (::fstat(file, &statbuf) == 0) {
48✔
1549
        // On exFAT systems the inode and device are not populated correctly until the file
1550
        // has been allocated some space. The uid can also be reassigned if the file is
1551
        // truncated to zero. This has led to bugs where a unique id returned here was
1552
        // reused by different files. The following check ensures that this is not
1553
        // happening anywhere else in future code.
1554
        if (statbuf.st_size == 0) {
48✔
1555
            throw FileAccessError(
×
1556
                ErrorCodes::FileOperationFailed,
×
1557
                util::format("Attempt to get unique id on an empty file. This could be due to an external "
×
1558
                             "process modifying Realm files. '%1'",
×
1559
                             debug_path),
×
1560
                debug_path);
×
1561
        }
×
1562
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
48✔
1563
    }
48✔
1564
    throw std::system_error(errno, std::system_category(), util::format("fstat() failed for '%1'", debug_path));
×
1565
#endif
48✔
1566
}
48✔
1567

1568
std::string File::get_path() const
1569
{
30✔
1570
    return m_path;
30✔
1571
}
30✔
1572

1573
std::string File::resolve(const std::string& path, const std::string& base_dir)
1574
{
230,742✔
1575
#if REALM_HAVE_STD_FILESYSTEM
1576
    return (u8path(base_dir) / u8path(path)).lexically_normal().u8string();
1577
#else
1578
    char dir_sep = '/';
230,742✔
1579
    std::string path_2 = path;
230,742✔
1580
    std::string base_dir_2 = base_dir;
230,742✔
1581
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
230,742✔
1582
    if (is_absolute)
230,742✔
1583
        return path_2;
6✔
1584
    if (path_2.empty())
230,736✔
1585
        path_2 = ".";
6✔
1586
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
230,736✔
1587
        base_dir_2.push_back(dir_sep);
229,161✔
1588
    /*
1589
    // Abbreviate
1590
    for (;;) {
1591
        if (base_dir_2.empty()) {
1592
            if (path_2.empty())
1593
                return "./";
1594
            return path_2;
1595
        }
1596
        if (path_2 == ".") {
1597
            remove_trailing_dir_seps(base_dir_2);
1598
            return base_dir_2;
1599
        }
1600
        if (has_prefix(path_2, "./")) {
1601
            remove_trailing_dir_seps(base_dir_2);
1602
            // drop dot
1603
            // transfer slashes
1604
        }
1605

1606
        if (path_2.size() < 2 || path_2[1] != '.')
1607
            break;
1608
        if (path_2.size())
1609
    }
1610
    */
1611
    return base_dir_2 + path_2;
230,736✔
1612
#endif
230,742✔
1613
}
230,742✔
1614

1615
std::string File::parent_dir(const std::string& path)
1616
{
90✔
1617
#if REALM_HAVE_STD_FILESYSTEM
1618
    return u8path(path).parent_path().u8string(); // Throws
1619
#else
1620
    auto is_sep = [](char c) -> bool {
1,200✔
1621
        return c == '/' || c == '\\';
1,200✔
1622
    };
1,200✔
1623
    auto it = std::find_if(path.rbegin(), path.rend(), is_sep);
90✔
1624
    while (it != path.rend() && is_sep(*it))
192✔
1625
        ++it;
102✔
1626
    return path.substr(0, path.rend() - it);
90✔
1627
#endif
90✔
1628
}
90✔
1629

1630
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1631
{
6✔
1632
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1633
}
6✔
1634

1635

1636
void File::set_encryption_key(const char* key)
1637
{
99,279✔
1638
#if REALM_ENABLE_ENCRYPTION
99,279✔
1639
    if (key) {
99,279✔
1640
        m_encryption = std::make_unique<util::EncryptedFile>(key, m_fd);
597✔
1641
    }
597✔
1642
    else {
98,682✔
1643
        m_encryption.reset();
98,682✔
1644
    }
98,682✔
1645
#else
1646
    if (key) {
1647
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1648
    }
1649
#endif
1650
}
99,279✔
1651

1652
EncryptedFile* File::get_encryption() const noexcept
1653
{
3,351,996✔
1654
#if REALM_ENABLE_ENCRYPTION
3,351,996✔
1655
    return m_encryption.get();
3,351,996✔
1656
#else
1657
    return nullptr;
1658
#endif
1659
}
3,351,996✔
1660

1661
File::MapBase::MapBase() noexcept = default;
1,655,280✔
1662
File::MapBase::~MapBase() noexcept
1663
{
1,852,809✔
1664
    unmap();
1,852,809✔
1665
}
1,852,809✔
1666

1667
File::MapBase::MapBase(MapBase&& other) noexcept
1668
{
197,502✔
1669
    *this = std::move(other);
197,502✔
1670
}
197,502✔
1671

1672
File::MapBase& File::MapBase::operator=(MapBase&& other) noexcept
1673
{
198,144✔
1674
    REALM_ASSERT(this != &other);
198,144✔
1675
    if (m_addr)
198,144✔
1676
        unmap();
×
1677
    m_addr = std::exchange(other.m_addr, nullptr);
198,144✔
1678
    m_size = std::exchange(other.m_size, 0);
198,144✔
1679
    m_access_mode = other.m_access_mode;
198,144✔
1680
    m_reservation_size = std::exchange(other.m_reservation_size, 0);
198,144✔
1681
    m_offset = std::exchange(other.m_offset, 0);
198,144✔
1682
    m_fd = std::exchange(other.m_fd, invalid_fd);
198,144✔
1683
#if REALM_ENABLE_ENCRYPTION
198,144✔
1684
    m_encrypted_mapping = std::move(other.m_encrypted_mapping);
198,144✔
1685
#endif
198,144✔
1686
    return *this;
198,144✔
1687
}
198,144✔
1688

1689
void File::MapBase::map(const File& f, AccessMode a, size_t size, SizeType offset, util::WriteObserver* observer)
1690
{
1,424,241✔
1691
    REALM_ASSERT(!m_addr);
1,424,241✔
1692
#if REALM_ENABLE_ENCRYPTION
1,424,241✔
1693
    m_addr = mmap({f.m_fd, a, f.m_encryption.get()}, size, offset, m_encrypted_mapping);
1,424,241✔
1694
    if (observer && m_encrypted_mapping) {
1,424,241✔
1695
        m_encrypted_mapping->set_observer(observer);
435✔
1696
    }
435✔
1697
#else
1698
    std::unique_ptr<util::EncryptedFileMapping> dummy_encrypted_mapping;
1699
    m_addr = mmap({f.m_fd, a, nullptr}, size, offset, dummy_encrypted_mapping);
1700
    static_cast<void>(observer);
1701
#endif
1702
    m_size = m_reservation_size = size;
1,424,241✔
1703
    m_fd = f.m_fd;
1,424,241✔
1704
    m_offset = offset;
1,424,241✔
1705
    m_access_mode = a;
1,424,241✔
1706
}
1,424,241✔
1707

1708

1709
void File::MapBase::unmap() noexcept
1710
{
3,115,344✔
1711
    if (!m_addr)
3,115,344✔
1712
        return;
1,592,640✔
1713
    REALM_ASSERT(m_reservation_size);
1,522,704✔
1714
#if REALM_ENABLE_ENCRYPTION
1,522,704✔
1715
    m_encrypted_mapping = nullptr;
1,522,704✔
1716
#endif
1,522,704✔
1717
    munmap(m_addr, m_reservation_size);
1,522,704✔
1718
    m_addr = nullptr;
1,522,704✔
1719
    m_size = 0;
1,522,704✔
1720
    m_reservation_size = 0;
1,522,704✔
1721
}
1,522,704✔
1722

1723
bool File::MapBase::try_reserve(const File& file, AccessMode a, size_t size, SizeType offset,
1724
                                util::WriteObserver* observer)
1725
{
98,415✔
1726
#ifdef _WIN32
1727
    static_cast<void>(observer);
1728
    // unsupported for now
1729
    return false;
1730
#else
1731
    void* addr = ::mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
98,415✔
1732
    if (addr == MAP_FAILED)
98,415✔
1733
        return false;
×
1734
    m_addr = addr;
98,415✔
1735
    REALM_ASSERT(m_size == 0);
98,415✔
1736
    m_access_mode = a;
98,415✔
1737
    m_reservation_size = size;
98,415✔
1738
    m_fd = file.get_descriptor();
98,415✔
1739
    m_offset = offset;
98,415✔
1740
#if REALM_ENABLE_ENCRYPTION
98,415✔
1741
    if (file.m_encryption) {
98,415✔
1742
        m_encrypted_mapping = util::reserve_mapping(addr, {m_fd, a, file.m_encryption.get()}, offset);
303✔
1743
        if (observer) {
303✔
1744
            m_encrypted_mapping->set_observer(observer);
297✔
1745
        }
297✔
1746
    }
303✔
1747
#else
1748
    static_cast<void>(observer);
1749
#endif
1750
#endif
98,415✔
1751
    return true;
98,415✔
1752
}
98,415✔
1753

1754
bool File::MapBase::try_extend_to(size_t size) noexcept
1755
{
118,896✔
1756
    if (size > m_reservation_size) {
118,896✔
1757
        return false;
×
1758
    }
×
1759
#ifndef _WIN32
118,896✔
1760
    char* extension_start_addr = (char*)m_addr + m_size;
118,896✔
1761
    size_t extension_size = size - m_size;
118,896✔
1762
    size_t extension_start_offset = m_offset + m_size;
118,896✔
1763
#if REALM_ENABLE_ENCRYPTION
118,896✔
1764
    if (m_encrypted_mapping) {
118,896✔
1765
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
336✔
1766
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
336✔
1767
        if (got_addr == MAP_FAILED)
336✔
1768
            return false;
×
1769
        REALM_ASSERT(got_addr == extension_start_addr);
336✔
1770
        m_size = size;
336✔
1771
        m_encrypted_mapping->extend_to(m_offset, size);
336✔
1772
        return true;
336✔
1773
    }
336✔
1774
#endif
118,560✔
1775
    try {
118,560✔
1776
        void* got_addr =
118,560✔
1777
            util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode, extension_start_offset);
118,560✔
1778
        if (got_addr == extension_start_addr) {
118,560✔
1779
            m_size = size;
118,482✔
1780
            return true;
118,482✔
1781
        }
118,482✔
1782
    }
118,560✔
1783
    catch (...) {
118,560✔
1784
        return false;
18✔
1785
    }
18✔
1786
#endif
×
1787
    return false;
60✔
1788
}
118,560✔
1789

1790
void File::MapBase::sync()
1791
{
1,396,401✔
1792
    REALM_ASSERT(m_addr);
1,396,401✔
1793
#if REALM_ENABLE_ENCRYPTION
1,396,401✔
1794
    if (m_encrypted_mapping) {
1,396,401✔
1795
        m_encrypted_mapping->sync();
1,614✔
1796
        return;
1,614✔
1797
    }
1,614✔
1798
#endif
1,394,787✔
1799

1800
    realm::util::msync(m_fd, m_addr, m_size);
1,394,787✔
1801
}
1,394,787✔
1802

1803
void File::MapBase::flush(bool skip_validate)
1804
{
1,556,430✔
1805
    REALM_ASSERT(m_addr);
1,556,430✔
1806
#if REALM_ENABLE_ENCRYPTION
1,556,430✔
1807
    if (m_encrypted_mapping) {
1,556,430✔
1808
        m_encrypted_mapping->flush(skip_validate);
142,668✔
1809
    }
142,668✔
1810
#else
1811
    static_cast<void>(skip_validate);
1812
#endif
1813
}
1,556,430✔
1814

1815
std::time_t File::last_write_time(const std::string& path)
1816
{
879✔
1817
#if REALM_HAVE_STD_FILESYSTEM
1818
    auto time = std::filesystem::last_write_time(u8path(path));
1819

1820
    using namespace std::chrono;
1821
#if __cplusplus >= 202002L
1822
    auto system_time = clock_cast<system_clock>(time);
1823
#else
1824
    auto system_time =
1825
        time_point_cast<system_clock::duration>(time - decltype(time)::clock::now() + system_clock::now());
1826
#endif
1827
    return system_clock::to_time_t(system_time);
1828
#else
1829
    struct stat statbuf;
879✔
1830
    if (::stat(path.c_str(), &statbuf) != 0) {
879✔
1831
        throw SystemError(errno, "stat() failed");
×
1832
    }
×
1833
    return statbuf.st_mtime;
879✔
1834
#endif
879✔
1835
}
879✔
1836

1837
File::SizeType File::get_free_space(const std::string& path)
1838
{
144✔
1839
#if REALM_HAVE_STD_FILESYSTEM
1840
    return std::filesystem::space(u8path(path)).available;
1841
#else
1842
    struct statvfs stat;
144✔
1843
    if (statvfs(path.c_str(), &stat) != 0) {
144✔
1844
        throw SystemError(errno, "statvfs() failed");
×
1845
    }
×
1846
    return SizeType(stat.f_bavail) * stat.f_bsize;
144✔
1847
#endif
144✔
1848
}
144✔
1849

1850
#if REALM_HAVE_STD_FILESYSTEM
1851

1852
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1853
{
1854
    std::error_code ec;
1855
    m_iterator = std::filesystem::directory_iterator(u8path(path), ec);
1856
    if (ec && (ec != std::errc::no_such_file_or_directory || !allow_missing))
1857
        throw std::filesystem::filesystem_error("directory_iterator::directory_iterator", u8path(path), ec);
1858
}
1859

1860
DirScanner::~DirScanner() = default;
1861

1862
bool DirScanner::next(std::string& name)
1863
{
1864
    const std::filesystem::directory_iterator end;
1865
    if (m_iterator == end)
1866
        return false;
1867
    name = m_iterator->path().filename().u8string();
1868
    m_iterator++;
1869
    return true;
1870
}
1871

1872
#elif !defined(_WIN32)
1873

1874
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1875
{
125,991✔
1876
    m_dirp = opendir(path.c_str());
125,991✔
1877
    if (!m_dirp) {
125,991✔
1878
        int err = errno; // Eliminate any risk of clobbering
7,197✔
1879
        if (allow_missing && err == ENOENT)
7,197✔
1880
            return;
7,197✔
1881

1882
        std::string msg = format_errno("opendir() failed: %1", err);
×
1883
        switch (err) {
×
1884
            case EACCES:
×
1885
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
1886
            case ENOENT:
×
1887
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
1888
            default:
×
1889
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
1890
        }
×
1891
    }
×
1892
}
125,991✔
1893

1894
DirScanner::~DirScanner() noexcept
1895
{
125,991✔
1896
    if (m_dirp) {
125,991✔
1897
        int r = closedir(m_dirp);
118,794✔
1898
        REALM_ASSERT_RELEASE(r == 0);
118,794✔
1899
    }
118,794✔
1900
}
125,991✔
1901

1902
bool DirScanner::next(std::string& name)
1903
{
346,467✔
1904
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
1905
    !defined(__EMSCRIPTEN__)
1906
#error "readdir() is not known to be thread-safe on this platform"
1907
#endif
1908

1909
    if (!m_dirp)
346,467✔
1910
        return false;
7,197✔
1911

1912
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
1913
// in 32-bits.
1914
#if REALM_HAVE_READDIR64
115,965✔
1915
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
232,203✔
1916
#else
1917
#define REALM_READDIR(...) readdir(__VA_ARGS__)
344,655✔
1918
#endif
223,305✔
1919

1920
    for (;;) {
576,858✔
1921
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
576,858✔
1922
        DirentPtr dirent;
576,858✔
1923
        do {
576,858✔
1924
            // readdir() signals both errors and end-of-stream by returning a
1925
            // null pointer. To distinguish between end-of-stream and errors,
1926
            // the manpage recommends setting errno specifically to 0 before
1927
            // calling it...
1928
            errno = 0;
576,858✔
1929

1930
            dirent = REALM_READDIR(m_dirp);
576,858✔
1931
        } while (!dirent && errno == EAGAIN);
576,858✔
1932

1933
        if (!dirent) {
576,858✔
1934
            if (errno != 0)
118,596✔
1935
                throw SystemError(errno, "readdir() failed");
×
1936
            return false; // End of stream
118,596✔
1937
        }
118,596✔
1938
        const char* name_1 = dirent->d_name;
458,262✔
1939
        std::string name_2 = name_1;
458,262✔
1940
        if (name_2 != "." && name_2 != "..") {
458,262✔
1941
            name = name_2;
220,674✔
1942
            return true;
220,674✔
1943
        }
220,674✔
1944
    }
458,262✔
1945
}
339,270✔
1946

1947
#else
1948

1949
DirScanner::DirScanner(const std::string&, bool)
1950
{
1951
    throw NotImplemented();
1952
}
1953

1954
DirScanner::~DirScanner() noexcept {}
1955

1956
bool DirScanner::next(std::string&)
1957
{
1958
    return false;
1959
}
1960

1961
#endif
1962

1963
} // 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