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

realm / realm-core / 2036

14 Feb 2024 05:06PM UTC coverage: 91.881% (+0.03%) from 91.851%
2036

push

Evergreen

web-flow
Mitigate races in accessing `m_initated` and `m_finalized` in various REALM_ASSERTs (#7338)

93026 of 171514 branches covered (54.24%)

46 of 48 new or added lines in 1 file covered. (95.83%)

66 existing lines in 10 files now uncovered.

235483 of 256292 relevant lines covered (91.88%)

6194496.88 hits per line

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

80.56
/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
{
214,515✔
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)
214,515✔
202
        return true;
85,701✔
203

62,403✔
204
    int err = errno; // Eliminate any risk of clobbering
128,814✔
205
    if (err == EEXIST)
128,814✔
206
        return false;
128,811✔
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 (c) {
1,809✔
251
            path[sep] = c;
1,809✔
252
            pos = sep + 1;
1,809✔
253
        }
1,809✔
254
        else {
×
255
            break;
×
256
        }
×
257
    }
1,809✔
258
#endif
201✔
259
}
201✔
260

261

262
void remove_dir(const std::string& path)
263
{
7,518✔
264
    if (try_remove_dir(path)) // Throws
7,518✔
265
        return;
7,500✔
266
    int err = ENOENT;
18✔
267
    std::string msg = format_errno("Failed to remove directory '%2': %1", err, path);
18✔
268
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
18✔
269
}
18✔
270

271

272
bool try_remove_dir(const std::string& path)
273
{
111,627✔
274
#if REALM_HAVE_STD_FILESYSTEM
275
    std::error_code error;
276
    bool result = std::filesystem::remove(u8path(path), error);
277
    throwIfFileError(error, path);
278
    return result;
279
#else // POSIX
280
    if (::rmdir(path.c_str()) == 0)
111,627✔
281
        return true;
111,507✔
282

60✔
283
    int err = errno; // Eliminate any risk of clobbering
120✔
284
    if (err == ENOENT)
120✔
285
        return false;
117✔
286

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

302

303
bool try_remove_dir_recursive(const std::string& path)
304
{
104,082✔
305
#if REALM_HAVE_STD_FILESYSTEM
306
    std::error_code error;
307
    auto removed_count = std::filesystem::remove_all(u8path(path), error);
308
    throwIfFileError(error, path);
309
    return removed_count > 0;
310
#else
311
    {
104,082✔
312
        bool allow_missing = true;
104,082✔
313
        DirScanner ds{path, allow_missing}; // Throws
104,082✔
314
        std::string name;
104,082✔
315
        while (ds.next(name)) {                              // Throws
279,486✔
316
            std::string subpath = File::resolve(name, path); // Throws
175,404✔
317
            if (File::is_dir(subpath)) {                     // Throws
175,404✔
318
                try_remove_dir_recursive(subpath);           // Throws
48,378✔
319
            }
48,378✔
320
            else {
127,026✔
321
                File::remove(subpath); // Throws
127,026✔
322
            }
127,026✔
323
        }
175,404✔
324
    }
104,082✔
325
    return try_remove_dir(path); // Throws
104,082✔
326
#endif
104,082✔
327
}
104,082✔
328

329

330
std::string make_temp_dir()
331
{
53,673✔
332
#ifdef _WIN32 // Windows version
333
    std::filesystem::path temp = std::filesystem::temp_directory_path();
334

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

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

354
#else // POSIX.1-2008 version
355

26,526✔
356
#if REALM_ANDROID
357
    std::string buffer = "/data/local/tmp/realm_XXXXXX";
358
#else
359
    char* tmp_dir_env = getenv("TMPDIR");
53,673✔
360
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
53,673✔
361
    if (!buffer.empty() && buffer.back() != '/') {
53,673✔
362
        buffer += "/";
53,673✔
363
    }
53,673✔
364
    buffer += "realm_XXXXXX";
53,673✔
365
#endif
53,673✔
366

26,526✔
367
    if (mkdtemp(buffer.data()) == 0) {
53,673✔
368
        int err = errno;
×
369
        throw SystemError(err, util::format("Failed to create temporary directory: %1", err)); // LCOV_EXCL_LINE
370
    }
×
371
    return buffer;
53,673✔
372
#endif
53,673✔
373
}
53,673✔
374

375
std::string make_temp_file(const char* prefix)
376
{
37,959✔
377
#ifdef _WIN32 // Windows version
378
    std::filesystem::path temp = std::filesystem::temp_directory_path();
379

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

386
    return std::filesystem::path(buffer).u8string();
387

388
#else // POSIX.1-2008 version
389

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

412
size_t page_size()
413
{
3,866,562✔
414
    return cached_page_size;
3,866,562✔
415
}
3,866,562✔
416

417
void File::open_internal(const std::string& path, AccessMode a, CreateMode c, int flags, bool* success)
418
{
382,425✔
419
    REALM_ASSERT_RELEASE(!is_attached());
382,425✔
420
    m_path = path; // for error reporting and debugging
382,425✔
421
    m_cached_unique_id = {};
382,425✔
422

124,689✔
423
#ifdef _WIN32 // Windows version
424

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

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

487
#else // POSIX version
488

124,689✔
489
    int flags2 = 0;
382,425✔
490
    switch (a) {
382,425✔
491
        case access_ReadOnly:
3,255✔
492
            flags2 = O_RDONLY;
3,255✔
493
            break;
3,255✔
494
        case access_ReadWrite:
379,176✔
495
            flags2 = O_RDWR;
379,176✔
496
            break;
379,176✔
497
    }
382,428✔
498
    switch (c) {
382,428✔
499
        case create_Auto:
351,681✔
500
            flags2 |= O_CREAT;
351,681✔
501
            break;
351,681✔
502
        case create_Never:
30,126✔
503
            break;
30,126✔
504
        case create_Must:
624✔
505
            flags2 |= O_CREAT | O_EXCL;
624✔
506
            break;
624✔
507
    }
382,428✔
508
    if (flags & flag_Trunc)
382,428✔
509
        flags2 |= O_TRUNC;
1,380✔
510
    if (flags & flag_Append)
382,428✔
511
        flags2 |= O_APPEND;
126,513✔
512
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
382,428✔
513
    if (0 <= fd) {
382,428✔
514
        m_fd = fd;
382,314✔
515
        m_have_lock = false;
382,314✔
516
        if (success)
382,314✔
517
            *success = true;
×
518
        return;
382,314✔
519
    }
382,314✔
520

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

57✔
550
#endif
114✔
551
}
114✔
552

553

554
void File::close() noexcept
555
{
736,947✔
556
#ifdef _WIN32 // Windows version
557

558
    if (!m_fd)
559
        return;
560
    if (m_have_lock)
561
        unlock();
562

563
    BOOL r = CloseHandle(m_fd);
564
    REALM_ASSERT_RELEASE(r);
565
    m_fd = nullptr;
566

567
#else // POSIX version
568

237,189✔
569
    if (m_fd < 0)
736,947✔
570
        return;
354,633✔
571
    if (m_have_lock)
382,314✔
572
        unlock();
153✔
573
    int r = ::close(m_fd);
382,314✔
574
    REALM_ASSERT_RELEASE(r == 0);
382,314✔
575
    m_fd = -1;
382,314✔
576

124,635✔
577
#endif
382,314✔
578
}
382,314✔
579

580
void File::close_static(FileDesc fd)
581
{
1,932✔
582
#ifdef _WIN32
583
    if (!fd)
584
        return;
585

586
    if (!CloseHandle(fd))
587
        throw std::system_error(GetLastError(), std::system_category(),
588
                                "CloseHandle() failed from File::close_static()");
589
#else
590
    if (fd < 0)
1,932✔
591
        return;
×
592

789✔
593
    int ret = -1;
1,932✔
594
    do {
1,932✔
595
        ret = ::close(fd);
1,932✔
596
    } while (ret == -1 && errno == EINTR);
1,932!
597

789✔
598
    if (ret != 0) {
1,932✔
599
        int err = errno; // Eliminate any risk of clobbering
×
600
        if (err == EBADF || err == EIO)
×
601
            throw SystemError(err, "File::close_static() failed");
×
602
    }
×
603
#endif
1,932✔
604
}
1,932✔
605

606
size_t File::read_static(FileDesc fd, char* data, size_t size)
607
{
506,415✔
608
#ifdef _WIN32 // Windows version
609
    char* const data_0 = data;
610
    while (0 < size) {
611
        DWORD n = std::numeric_limits<DWORD>::max();
612
        if (int_less_than(size, n))
613
            n = static_cast<DWORD>(size);
614
        DWORD r = 0;
615
        if (!ReadFile(fd, data, n, &r, 0))
616
            goto error;
617
        if (r == 0)
618
            break;
619
        REALM_ASSERT_RELEASE(r <= n);
620
        size -= size_t(r);
621
        data += size_t(r);
622
    }
623
    return data - data_0;
624

625
error:
626
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
627
    throw SystemError(int(err), "ReadFile() failed");
628

629
#else // POSIX version
630

262,140✔
631
    char* const data_0 = data;
506,415✔
632
    while (0 < size) {
1,011,732✔
633
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
262,248✔
634
        size_t n = std::min(size, size_t(SSIZE_MAX));
506,628✔
635
        ssize_t r = ::read(fd, data, n);
506,628✔
636
        if (r == 0)
506,628✔
637
            break;
1,311✔
638
        if (r < 0)
505,317✔
639
            goto error; // LCOV_EXCL_LINE
640
        REALM_ASSERT_RELEASE(size_t(r) <= n);
505,317✔
641
        size -= size_t(r);
505,317✔
642
        data += size_t(r);
505,317✔
643
    }
505,317✔
644
    return data - data_0;
506,415✔
645

646
error:
×
647
    // LCOV_EXCL_START
648
    throw SystemError(errno, "read() failed");
×
649
// LCOV_EXCL_STOP
262,140✔
650
#endif
506,415✔
651
}
506,415✔
652

653

654
size_t File::read(char* data, size_t size)
655
{
2,103✔
656
    REALM_ASSERT_RELEASE(is_attached());
2,103✔
657

936✔
658
    if (m_encryption_key) {
2,103✔
659
        uint64_t pos_original = File::get_file_pos(m_fd);
×
660
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
×
661
        size_t pos = size_t(pos_original);
×
662
        Map<char> read_map(*this, access_ReadOnly, static_cast<size_t>(pos + size));
×
663
        realm::util::encryption_read_barrier(read_map, pos, size);
×
664
        memcpy(data, read_map.get_addr() + pos, size);
×
665
        uint64_t cur = File::get_file_pos(m_fd);
×
666
        seek_static(m_fd, cur + size);
×
667
        return read_map.get_size() - pos;
×
668
    }
×
669

936✔
670
    return read_static(m_fd, data, size);
2,103✔
671
}
2,103✔
672

673
void File::write_static(FileDesc fd, const char* data, size_t size)
674
{
262,128✔
675
#ifdef _WIN32
676
    while (0 < size) {
677
        DWORD n = std::numeric_limits<DWORD>::max();
678
        if (int_less_than(size, n))
679
            n = static_cast<DWORD>(size);
680
        DWORD r = 0;
681
        if (!WriteFile(fd, data, n, &r, 0))
682
            goto error;
683
        REALM_ASSERT_RELEASE(r == n); // Partial writes are not possible.
684
        size -= size_t(r);
685
        data += size_t(r);
686
    }
687
    return;
688

689
error:
690
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
691
    if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
692
        std::string msg = get_last_error_msg("WriteFile() failed: ", err);
693
        throw OutOfDiskSpace(msg);
694
    }
695
    throw SystemError(err, "WriteFile() failed");
696
#else
697
    while (0 < size) {
523,926✔
698
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
124,662✔
699
        size_t n = std::min(size, size_t(SSIZE_MAX));
261,798✔
700
        ssize_t r = ::write(fd, data, n);
261,798✔
701
        if (r < 0)
261,798✔
702
            goto error; // LCOV_EXCL_LINE
703
        REALM_ASSERT_RELEASE(r != 0);
261,798✔
704
        REALM_ASSERT_RELEASE(size_t(r) <= n);
261,798✔
705
        size -= size_t(r);
261,798✔
706
        data += size_t(r);
261,798✔
707
    }
261,798✔
708
    return;
262,128✔
709

710
error:
×
711
    // LCOV_EXCL_START
712
    int err = errno; // Eliminate any risk of clobbering
×
713
    auto msg = format_errno("write() failed: %1", err);
×
714
    if (err == ENOSPC || err == EDQUOT) {
×
715
        throw OutOfDiskSpace(msg);
×
716
    }
×
717
    throw SystemError(err, msg);
×
718
    // LCOV_EXCL_STOP
719

720
#endif
×
721
}
×
722

723
void File::write(const char* data, size_t size)
724
{
46,176✔
725
    REALM_ASSERT_RELEASE(is_attached());
46,176✔
726

22,380✔
727
    if (m_encryption_key) {
46,176✔
728
        uint64_t pos_original = get_file_pos(m_fd);
1,458✔
729
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
1,458✔
730
        size_t pos = size_t(pos_original);
1,458✔
731
        Map<char> write_map(*this, access_ReadWrite, static_cast<size_t>(pos + size));
1,458✔
732
        realm::util::encryption_read_barrier(write_map, pos, size);
1,458✔
733
        memcpy(write_map.get_addr() + pos, data, size);
1,458✔
734
        realm::util::encryption_write_barrier(write_map, pos, size);
1,458✔
735
        uint64_t cur = get_file_pos(m_fd);
1,458✔
736
        seek(cur + size);
1,458✔
737
        return;
1,458✔
738
    }
1,458✔
739

21,807✔
740
    write_static(m_fd, data, size);
44,718✔
741
}
44,718✔
742

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

754
    return uint64_t(res.QuadPart);
755
#else
756
    auto pos = lseek(fd, 0, SEEK_CUR);
724,635✔
757
    if (pos < 0) {
724,635✔
758
        throw SystemError(errno, "lseek() failed");
×
759
    }
×
760
    return uint64_t(pos);
724,635✔
761
#endif
724,635✔
762
}
724,635✔
763

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

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

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

783
#else // POSIX version
784

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

881,196✔
791
        return size;
1,716,753✔
792
    }
1,716,753✔
793
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
794

2,147,483,647✔
795
#endif
4,294,967,294✔
796
}
4,294,967,294✔
797

798
File::SizeType File::get_size() const
799
{
1,713,324✔
800
    REALM_ASSERT_RELEASE(is_attached());
1,713,324✔
801
    File::SizeType size = get_size_static(m_fd);
1,713,324✔
802

879,639✔
803
    if (m_encryption_key) {
1,713,324✔
804
        File::SizeType ret_size = encrypted_size_to_data_size(size);
3,597✔
805
        return ret_size;
3,597✔
806
    }
3,597✔
807
    else
1,709,727✔
808
        return size;
1,709,727✔
809
}
1,713,324✔
810

811

812
void File::resize(SizeType size)
813
{
184,653✔
814
    REALM_ASSERT_RELEASE(is_attached());
184,653✔
815

28,623✔
816
#ifdef _WIN32 // Windows version
817

818
    // Save file position
819
    SizeType p = get_file_pos(m_fd);
820

821
    if (m_encryption_key)
822
        size = data_size_to_encrypted_size(size);
823

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

829
    if (!SetEndOfFile(m_fd)) {
830
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
831
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
832
            std::string msg = get_last_error_msg("SetEndOfFile() failed: ", err);
833
            throw OutOfDiskSpace(msg);
834
        }
835
        throw SystemError(int(err), "SetEndOfFile() failed");
836
    }
837

838
    // Restore file position
839
    seek(p);
840

841
#else // POSIX version
842

28,623✔
843
    if (m_encryption_key)
184,653✔
844
        size = data_size_to_encrypted_size(size);
18✔
845

28,623✔
846
    off_t size2;
184,653✔
847
    if (int_cast_with_overflow_detect(size, size2))
184,653✔
848
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
849

28,623✔
850
    // POSIX specifies that introduced bytes read as zero. This is not
28,623✔
851
    // required by File::resize().
28,623✔
852
    if (::ftruncate(m_fd, size2) != 0) {
184,653✔
853
        int err = errno; // Eliminate any risk of clobbering
×
854
        auto msg = format_errno("ftruncate() failed: %1", err);
×
855
        if (err == ENOSPC || err == EDQUOT) {
×
856
            throw OutOfDiskSpace(msg);
×
857
        }
×
858
        throw SystemError(err, msg);
×
859
    }
×
860

28,623✔
861
#endif
184,653✔
862
}
184,653✔
863

864

865
void File::prealloc(size_t size)
866
{
152,889✔
867
    REALM_ASSERT_RELEASE(is_attached());
152,889✔
868

80,847✔
869
    if (size <= to_size_t(get_size())) {
152,889✔
870
        return;
37,071✔
871
    }
37,071✔
872

62,745✔
873
    size_t new_size = size;
115,818✔
874
    if (m_encryption_key) {
115,818✔
875
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
624✔
876
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
624✔
877
        if (new_size < size) {
624✔
878
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow: data_size_to_encrypted_size(" +
×
879
                                                           realm::util::to_string(size) +
×
880
                                                           ") == " + realm::util::to_string(new_size));
×
881
        }
×
882
    }
115,818✔
883

62,745✔
884
    auto manually_consume_space = [&]() {
115,818✔
885
        constexpr size_t chunk_size = 4096;
×
886
        int64_t original_size = get_size_static(m_fd); // raw size
×
887
        seek(original_size);
×
888
        size_t num_bytes = size_t(new_size - original_size);
×
889
        std::string zeros(chunk_size, '\0');
×
890
        while (num_bytes > 0) {
×
891
            size_t t = num_bytes > chunk_size ? chunk_size : num_bytes;
×
892
            write_static(m_fd, zeros.c_str(), t);
×
893
            num_bytes -= t;
×
894
        }
×
895
    };
×
896

62,745✔
897
    auto consume_space_interlocked = [&] {
62,745✔
898
#if REALM_ENABLE_ENCRYPTION
×
899
        if (m_encryption_key) {
×
900
            // We need to prevent concurrent calls to lseek from the encryption layer
901
            // while we're writing to the file to extend it. Otherwise an intervening
902
            // lseek may redirect the writing process, causing file corruption.
903
            UniqueLock lck(util::mapping_mutex);
×
904
            manually_consume_space();
×
905
        }
×
906
        else {
×
907
            manually_consume_space();
×
908
        }
×
909
#else
910
        manually_consume_space();
911
#endif
912
    };
×
913

62,745✔
914
#if REALM_HAVE_POSIX_FALLOCATE
62,745✔
915
    // Mostly Linux only
62,745✔
916
    if (!prealloc_if_supported(0, new_size)) {
62,745✔
917
        consume_space_interlocked();
918
    }
919
#else // Non-atomic fallback
920
#if REALM_PLATFORM_APPLE
53,073✔
921
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
922
    // ftruncate().
923

924
    struct stat statbuf;
53,073✔
925
    if (::fstat(m_fd, &statbuf) != 0) {
53,073✔
926
        int err = errno;
927
        throw SystemError(err, "fstat() inside prealloc() failed");
928
    }
929

930
    size_t allocated_size;
53,073✔
931
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
53,073✔
932
        throw RuntimeError(ErrorCodes::RangeError,
933
                           "Overflow on block conversion to size_t " + realm::util::to_string(statbuf.st_blocks));
934
    }
935
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
53,073✔
936
        throw RuntimeError(ErrorCodes::RangeError, "Overflow computing existing file space allocation blocks: " +
937
                                                       realm::util::to_string(allocated_size) +
938
                                                       " block size: " + realm::util::to_string(S_BLKSIZE));
939
    }
940

941
    // Only attempt to preallocate space if there's not already sufficient free space in the file.
942
    // APFS would fail with EINVAL if we attempted it, and HFS+ would preallocate extra space unnecessarily.
943
    // See <https://github.com/realm/realm-core/issues/3005> for details.
944
    if (new_size > allocated_size) {
53,073✔
945

946
        off_t to_allocate = static_cast<off_t>(new_size - statbuf.st_size);
53,034✔
947
        fstore_t store = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, to_allocate, 0};
53,034✔
948
        int ret = 0;
53,034✔
949
        do {
53,034✔
950
            ret = fcntl(m_fd, F_PREALLOCATE, &store);
53,034✔
951
        } while (ret == -1 && errno == EINTR);
53,034!
952
        if (ret == -1) {
53,034✔
953
            // Consider fcntl() as an optimization on Apple devices, where if it fails,
954
            // we fall back to manually consuming space which is slower but may succeed in some
955
            // cases where fcntl fails. Some known cases are:
956
            // 1) There's a timing sensitive bug on APFS which causes fcntl to sometimes throw EINVAL.
957
            // This might not be the case, but we'll fall back and attempt to manually allocate all the requested
958
            // space. Worst case, this might also fail, but there is also a chance it will succeed. We don't
959
            // call this in the first place because using fcntl(F_PREALLOCATE) will be faster if it works (it has
960
            // been reliable on HSF+).
961
            // 2) fcntl will fail with ENOTSUP on non-supported file systems such as ExFAT. In this case
962
            // the fallback should succeed.
963
            // 3) if there is some other error such as no space left (ENOSPC) we will expect to fail again later
964
            consume_space_interlocked();
965
        }
966
    }
53,034✔
967

968
    int ret = 0;
53,073✔
969

970
    do {
53,073✔
971
        ret = ftruncate(m_fd, new_size);
53,073✔
972
    } while (ret == -1 && errno == EINTR);
53,073!
973

974
    if (ret != 0) {
53,073✔
975
        int err = errno;
976
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
977
        // so this is some other runtime error and not OutOfDiskSpace
978
        throw SystemError(err, "ftruncate() inside prealloc() failed");
979
    }
980
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
981

982
    consume_space_interlocked();
983

984
#else
985
#error Please check if/how your OS supports file preallocation
986
#endif
987

988
#endif // REALM_HAVE_POSIX_FALLOCATE
53,073✔
989
}
115,818✔
990

991

992
bool File::prealloc_if_supported(SizeType offset, size_t size)
993
{
62,745✔
994
    REALM_ASSERT_RELEASE(is_attached());
62,745!
995

62,745✔
996
#if REALM_HAVE_POSIX_FALLOCATE
62,745✔
997

62,745✔
998
    REALM_ASSERT_RELEASE(is_prealloc_supported());
62,745✔
999

62,745✔
1000
    if (size == 0) {
62,745✔
1001
        // calling posix_fallocate with a size of 0 will cause a return of EINVAL
1002
        // since this is a meaningless operation anyway, we just return early here
1003
        return true;
1004
    }
1005

62,745✔
1006
    // posix_fallocate() does not set errno, it returns the error (if any) or zero.
62,745✔
1007
    // It is also possible for it to be interrupted by EINTR according to some man pages (ex fedora 24)
62,745✔
1008
    int status;
62,745✔
1009
    do {
62,745✔
1010
        status = ::posix_fallocate(m_fd, offset, size);
62,745✔
1011
    } while (status == EINTR);
62,745✔
1012

62,745✔
1013
    if (REALM_LIKELY(status == 0)) {
62,745✔
1014
        return true;
62,745✔
1015
    }
62,745✔
1016

1017
    if (status == EINVAL || status == EPERM || status == EOPNOTSUPP) {
1018
        return false; // Retry with non-atomic version
1019
    }
1020

1021
    auto msg = format_errno("posix_fallocate() failed: %1", status);
1022
    if (status == ENOSPC || status == EDQUOT) {
1023
        throw OutOfDiskSpace(msg);
1024
    }
1025
    throw SystemError(status, msg);
1026

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

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

1035
#else
1036

1037
    static_cast<void>(offset);
1038
    static_cast<void>(size);
1039

1040
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1041

1042
#endif
UNCOV
1043
    return false;
×
UNCOV
1044
}
×
1045

1046

1047
bool File::is_prealloc_supported()
1048
{
62,745✔
1049
#if REALM_HAVE_POSIX_FALLOCATE
62,745✔
1050
    return true;
62,745✔
1051
#else
1052
    return false;
1053
#endif
1054
}
62,745✔
1055

1056
void File::seek(SizeType position)
1057
{
39,303✔
1058
    REALM_ASSERT_RELEASE(is_attached());
39,303✔
1059
    seek_static(m_fd, position);
39,303✔
1060
}
39,303✔
1061

1062
void File::seek_static(FileDesc fd, SizeType position)
1063
{
1,482,744✔
1064
#ifdef _WIN32 // Windows version
1065

1066
    LARGE_INTEGER large_int;
1067
    if (int_cast_with_overflow_detect(position, large_int.QuadPart))
1068
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1069

1070
    if (!SetFilePointerEx(fd, large_int, 0, FILE_BEGIN))
1071
        throw SystemError(GetLastError(), "SetFilePointerEx() failed");
1072

1073
#else // POSIX version
1074

747,516✔
1075
    off_t position2;
1,482,744✔
1076
    if (int_cast_with_overflow_detect(position, position2))
1,482,744✔
1077
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1078

747,516✔
1079
    if (0 <= ::lseek(fd, position2, SEEK_SET))
1,482,744✔
1080
        return;
1,482,744✔
UNCOV
1081
    throw SystemError(errno, "lseek() failed");
×
1082

UNCOV
1083
#endif
×
UNCOV
1084
}
×
1085

1086
// FIXME: The current implementation may not guarantee that data is
1087
// actually written to disk. POSIX is rather vague on what fsync() has
1088
// to do unless _POSIX_SYNCHRONIZED_IO is defined. See also
1089
// http://www.humboldt.co.uk/2009/03/fsync-across-platforms.html.
1090
void File::sync()
1091
{
840✔
1092
    REALM_ASSERT_RELEASE(is_attached());
840✔
1093

531✔
1094
#if defined _WIN32 // Windows version
1095

1096
    if (FlushFileBuffers(m_fd))
1097
        return;
1098
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1099

1100
#elif REALM_PLATFORM_APPLE
1101

1102
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
309✔
1103
        return;
309✔
1104
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1105

1106
#else // POSIX version
1107

531✔
1108
    if (::fsync(m_fd) == 0)
531✔
1109
        return;
531✔
1110
    throw SystemError(errno, "fsync() failed");
1111

1112
#endif
1113
}
×
1114

1115
void File::barrier()
1116
{
432✔
1117
#if REALM_PLATFORM_APPLE
216✔
1118
    if (::fcntl(m_fd, F_BARRIERFSYNC) == 0)
216✔
1119
        return;
216✔
1120
        // If fcntl fails, we fallback to full sync.
1121
        // This is known to occur on exFAT which does not support F_BARRIERSYNC.
1122
#endif
1123
    sync();
216✔
1124
}
216✔
1125

1126
#ifndef _WIN32
1127
// little helper
1128
static void _unlock(int m_fd)
1129
{
3,738,036✔
1130
    int r;
3,738,036✔
1131
    do {
3,738,036✔
1132
        r = flock(m_fd, LOCK_UN);
3,738,036✔
1133
    } while (r != 0 && errno == EINTR);
3,738,036!
1134
    if (r) {
3,738,036✔
1135
        throw SystemError(errno, "File::unlock() has failed");
×
1136
    }
×
1137
}
3,738,036✔
1138
#endif
1139

1140
bool File::rw_lock(bool exclusive, bool non_blocking)
1141
{
341,103✔
1142
    // exclusive blocking rw locks not implemented for emulation
166,200✔
1143
    REALM_ASSERT(!exclusive || non_blocking);
341,103✔
1144

166,200✔
1145
#ifndef REALM_FILELOCK_EMULATION
341,103✔
1146
    return lock(exclusive, non_blocking);
341,103✔
1147
#else
1148
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1149

1150
    // First obtain an exclusive lock on the file proper
1151
    int operation = LOCK_EX;
1152
    if (non_blocking)
1153
        operation |= LOCK_NB;
1154
    int status;
1155
    do {
1156
        status = flock(m_fd, operation);
1157
    } while (status != 0 && errno == EINTR);
1158
    if (status != 0 && errno == EWOULDBLOCK)
1159
        return false;
1160
    if (status != 0)
1161
        throw SystemError(errno, "flock() failed");
1162
    m_has_exclusive_lock = true;
1163

1164
    // Every path through this function except for successfully acquiring an
1165
    // exclusive lock needs to release the flock() before returning.
1166
    UnlockGuard ulg(*this);
1167

1168
    // now use a named pipe to emulate locking in conjunction with using exclusive lock
1169
    // on the file proper.
1170
    // exclusively locked: we can't sucessfully write to the pipe.
1171
    //                     AND we continue to hold the exclusive lock.
1172
    //                     (unlock must lift the exclusive lock).
1173
    // shared locked: we CAN succesfully write to the pipe. We open the pipe for reading
1174
    //                before releasing the exclusive lock.
1175
    //                (unlock must close the pipe for reading)
1176
    REALM_ASSERT_RELEASE(m_pipe_fd == -1);
1177
    if (m_fifo_path.empty())
1178
        m_fifo_path = m_path + ".fifo";
1179

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

1184
    // Optimistically try to open the fifo. This may fail due to the fifo not
1185
    // existing, but since it usually exists this is faster than trying to create
1186
    // it first.
1187
    int fd = ::open(m_fifo_path.c_str(), mode);
1188
    if (fd == -1) {
1189
        int err = errno;
1190
        if (exclusive) {
1191
            if (err == ENOENT || err == ENXIO) {
1192
                // If the fifo either doesn't exist or there's no readers with the
1193
                // other end of the pipe open (ENXIO) then we have an exclusive lock
1194
                // and are done.
1195
                ulg.release();
1196
                return true;
1197
            }
1198

1199
            // Otherwise we got an unexpected error
1200
            throw std::system_error(err, std::system_category(), "opening lock fifo for writing failed");
1201
        }
1202

1203
        if (err == ENOENT) {
1204
            // The fifo doesn't exist and we're opening in shared mode, so we
1205
            // need to create it.
1206
            if (!m_fifo_dir_path.empty())
1207
                try_make_dir(m_fifo_dir_path);
1208
            status = mkfifo(m_fifo_path.c_str(), 0666);
1209
            if (status != 0)
1210
                throw std::system_error(errno, std::system_category(), "creating lock fifo for reading failed");
1211

1212
            // Try again to open the fifo now that it exists
1213
            fd = ::open(m_fifo_path.c_str(), mode);
1214
            err = errno;
1215
        }
1216

1217
        if (fd == -1)
1218
            throw std::system_error(err, std::system_category(), "opening lock fifo for reading failed");
1219
    }
1220

1221
    // We successfully opened the pipe. If we're trying to acquire an exclusive
1222
    // lock that means there's a reader (aka a shared lock) and we've failed.
1223
    // Release the exclusive lock and back out.
1224
    if (exclusive) {
1225
        ::close(fd);
1226
        return false;
1227
    }
1228

1229
    // We're in shared mode, so opening the fifo means we've successfully acquired
1230
    // a shared lock and are done.
1231
    ulg.release();
1232
    rw_unlock();
1233
    m_pipe_fd = fd;
1234
    return true;
1235
#endif // REALM_FILELOCK_EMULATION
1236
}
341,103✔
1237

1238
bool File::lock(bool exclusive, bool non_blocking)
1239
{
3,846,747✔
1240
    REALM_ASSERT_RELEASE(is_attached());
3,846,747✔
1241
    REALM_ASSERT_RELEASE(!m_have_lock);
3,846,747✔
1242

166,203✔
1243
#ifdef _WIN32 // Windows version
1244

1245
    // Under Windows a file lock must be explicitely released before
1246
    // the file is closed. It will eventually be released by the
1247
    // system, but there is no guarantees on the timing.
1248

1249
    DWORD flags = 0;
1250
    if (exclusive)
1251
        flags |= LOCKFILE_EXCLUSIVE_LOCK;
1252
    if (non_blocking)
1253
        flags |= LOCKFILE_FAIL_IMMEDIATELY;
1254
    OVERLAPPED overlapped;
1255
    memset(&overlapped, 0, sizeof overlapped);
1256
    overlapped.Offset = 0;     // Just for clarity
1257
    overlapped.OffsetHigh = 0; // Just for clarity
1258
    if (LockFileEx(m_fd, flags, 0, 1, 0, &overlapped)) {
1259
        m_have_lock = true;
1260
        return true;
1261
    }
1262
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
1263
    if (err == ERROR_LOCK_VIOLATION)
1264
        return false;
1265
    throw std::system_error(err, std::system_category(), "LockFileEx() failed");
1266
#else // _WIN32
1267
    // NOTE: It would probably have been more portable to use fcntl()
166,203✔
1268
    // based POSIX locks, however these locks are not recursive within
166,203✔
1269
    // a single process, and since a second attempt to acquire such a
166,203✔
1270
    // lock will always appear to succeed, one will easily suffer the
166,203✔
1271
    // 'spurious unlocking issue'. It remains to be determined whether
166,203✔
1272
    // this also applies across distinct threads inside a single
166,203✔
1273
    // process.
166,203✔
1274
    //
166,203✔
1275
    // To make matters worse, flock() may be a simple wrapper around
166,203✔
1276
    // fcntl() based locks on some systems. This is bad news, because
166,203✔
1277
    // the robustness of the Realm API relies in part by the
166,203✔
1278
    // assumption that a single process (even a single thread) can
166,203✔
1279
    // hold multiple overlapping independent shared locks on a single
166,203✔
1280
    // file as long as they are placed via distinct file descriptors.
166,203✔
1281
    //
166,203✔
1282
    // Fortunately, on both Linux and Darwin, flock() does not suffer
166,203✔
1283
    // from this 'spurious unlocking issue'.
166,203✔
1284
    int operation = exclusive ? LOCK_EX : LOCK_SH;
3,846,747✔
1285
    if (non_blocking)
3,846,747✔
1286
        operation |= LOCK_NB;
261,042✔
1287
    do {
3,846,747✔
1288
        if (flock(m_fd, operation) == 0) {
3,846,747✔
1289
            m_have_lock = true;
3,740,316✔
1290
            return true;
3,740,316✔
1291
        }
3,740,316✔
1292
    } while (errno == EINTR);
106,431✔
1293
    int err = errno; // Eliminate any risk of clobbering
220,449✔
1294
    if (err == EWOULDBLOCK)
106,431✔
1295
        return false;
108,843✔
1296
    throw SystemError(err, "flock() failed");
4,294,967,294✔
1297
#endif
4,294,967,294✔
1298
}
4,294,967,294✔
1299

1300
void File::unlock() noexcept
1301
{
3,738,135✔
1302
    if (!m_have_lock)
3,738,135✔
1303
        return;
×
1304

114,018✔
1305
#ifdef _WIN32 // Windows version
1306
    OVERLAPPED overlapped;
1307
    overlapped.hEvent = 0;
1308
    overlapped.OffsetHigh = 0;
1309
    overlapped.Offset = 0;
1310
    overlapped.Pointer = 0;
1311
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1312
    REALM_ASSERT_RELEASE(r);
1313
#else
1314
    _unlock(m_fd);
3,738,135✔
1315
#endif
3,738,135✔
1316
    m_have_lock = false;
3,738,135✔
1317
}
3,738,135✔
1318

1319
void File::rw_unlock() noexcept
1320
{
232,116✔
1321
#ifndef REALM_FILELOCK_EMULATION
232,116✔
1322
    unlock();
232,116✔
1323
#else
1324
    // Coming here with an exclusive lock, we must release that lock.
1325
    // Coming here with a shared lock, we must close the pipe that we have opened for reading.
1326
    //   - we have to do that under the protection of a proper exclusive lock to serialize
1327
    //     with anybody trying to obtain a lock concurrently.
1328
    if (has_shared_lock()) {
1329
        // shared lock. We need to reacquire the exclusive lock on the file
1330
        int status;
1331
        do {
1332
            status = flock(m_fd, LOCK_EX);
1333
        } while (status != 0 && errno == EINTR);
1334
        REALM_ASSERT(status == 0);
1335
        // close the pipe (== release the shared lock)
1336
        ::close(m_pipe_fd);
1337
        m_pipe_fd = -1;
1338
    }
1339
    else {
1340
        REALM_ASSERT(m_has_exclusive_lock);
1341
    }
1342
    _unlock(m_fd);
1343
    m_has_exclusive_lock = false;
1344
#endif // REALM_FILELOCK_EMULATION
1345
}
232,116✔
1346

1347
void* File::map(AccessMode a, size_t size, int /*map_flags*/, size_t offset) const
1348
{
6✔
1349
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset);
6✔
1350
}
6✔
1351

1352
void* File::map_fixed(AccessMode a, void* address, size_t size, int /* map_flags */, size_t offset) const
1353
{
×
1354
    if (m_encryption_key.get()) {
×
1355
        // encryption enabled - this is not supported - see explanation in alloc_slab.cpp
1356
        REALM_ASSERT(false);
×
1357
    }
×
1358
#ifdef _WIN32
1359
    // windows, no encryption - this is not supported, see explanation in alloc_slab.cpp,
1360
    // above the method 'update_reader_view()'
1361
    REALM_ASSERT(false);
1362
    return nullptr;
1363
#else
1364
    // unencrypted - mmap part of already reserved space
1365
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, m_encryption_key.get());
×
1366
#endif
×
1367
}
×
1368

1369
void* File::map_reserve(AccessMode a, size_t size, size_t offset) const
1370
{
×
1371
    static_cast<void>(a); // FIXME: Consider removing this argument
×
1372
    return realm::util::mmap_reserve(m_fd, size, offset);
×
1373
}
×
1374

1375
#if REALM_ENABLE_ENCRYPTION
1376
void* File::map(AccessMode a, size_t size, EncryptedFileMapping*& mapping, int /*map_flags*/, size_t offset) const
1377
{
1,330,740✔
1378
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
1,330,740✔
1379
}
1,330,740✔
1380

1381
void* File::map_fixed(AccessMode a, void* address, size_t size, EncryptedFileMapping* mapping, int /* map_flags */,
1382
                      size_t offset) const
1383
{
×
1384
    if (m_encryption_key.get()) {
×
1385
        // encryption enabled - we shouldn't be here, all memory was allocated by reserve
1386
        REALM_ASSERT_RELEASE(false);
×
1387
    }
×
1388
#ifndef _WIN32
×
1389
    // no encryption. On Unixes, map relevant part of reserved virtual address range
1390
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, nullptr, mapping);
×
1391
#else
1392
    // no encryption - unsupported on windows
1393
    REALM_ASSERT(false);
1394
    return nullptr;
1395
#endif
1396
}
×
1397

1398
void* File::map_reserve(AccessMode a, size_t size, size_t offset, EncryptedFileMapping*& mapping) const
1399
{
×
1400
    if (m_encryption_key.get()) {
×
1401
        // encrypted file - just mmap it, the encryption layer handles if the mapping extends beyond eof
1402
        return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
×
1403
    }
×
1404
#ifndef _WIN32
×
1405
    // not encrypted, do a proper reservation on Unixes'
1406
    return realm::util::mmap_reserve({m_fd, m_path, a, nullptr}, size, offset, mapping);
×
1407
#else
1408
    // on windows, this is a no-op
1409
    return nullptr;
1410
#endif
1411
}
×
1412

1413
#endif // REALM_ENABLE_ENCRYPTION
1414

1415
void File::unmap(void* addr, size_t size) noexcept
1416
{
×
1417
    realm::util::munmap(addr, size);
×
1418
}
×
1419

1420

1421
void* File::remap(void* old_addr, size_t old_size, AccessMode a, size_t new_size, int /*map_flags*/,
1422
                  size_t file_offset) const
1423
{
×
1424
    return realm::util::mremap({m_fd, m_path, a, m_encryption_key.get()}, file_offset, old_addr, old_size, new_size);
×
1425
}
×
1426

1427

1428
void File::sync_map(FileDesc fd, void* addr, size_t size)
1429
{
1,380,411✔
1430
    realm::util::msync(fd, addr, size);
1,380,411✔
1431
}
1,380,411✔
1432

1433

1434
bool File::exists(const std::string& path)
1435
{
978,552✔
1436
#if REALM_HAVE_STD_FILESYSTEM
1437
    return std::filesystem::exists(u8path(path));
1438
#else // POSIX
1439
    if (::access(path.c_str(), F_OK) == 0)
978,552✔
1440
        return true;
31,458✔
1441
    int err = errno; // Eliminate any risk of clobbering
947,094✔
1442
    switch (err) {
947,094✔
1443
        case EACCES:
505,629✔
1444
        case ENOENT:
947,058✔
1445
        case ENOTDIR:
947,058✔
1446
            return false;
947,058✔
1447
    }
24✔
1448
    throw SystemError(err, "access() failed");
24✔
1449
#endif
24✔
1450
}
24✔
1451

1452

1453
bool File::is_dir(const std::string& path)
1454
{
207,516✔
1455
#if REALM_HAVE_STD_FILESYSTEM
1456
    return std::filesystem::is_directory(u8path(path));
1457
#elif !defined(_WIN32)
1458
    struct stat statbuf;
152,214✔
1459
    if (::stat(path.c_str(), &statbuf) == 0)
207,516✔
1460
        return S_ISDIR(statbuf.st_mode);
201,858✔
1461
    int err = errno; // Eliminate any risk of clobbering
5,658✔
1462
    switch (err) {
5,658✔
1463
        case EACCES:
2,832✔
1464
        case ENOENT:
5,661✔
1465
        case ENOTDIR:
5,661✔
1466
            return false;
5,661✔
1467
    }
×
1468
    throw SystemError(err, "stat() failed");
×
1469
#else
1470
    static_cast<void>(path);
1471
    throw NotImplemented();
1472
#endif
1473
}
×
1474

1475

1476
void File::remove(const std::string& path)
1477
{
165,603✔
1478
    if (try_remove(path))
165,603✔
1479
        return;
165,588✔
1480
    int err = ENOENT;
15✔
1481
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
15✔
1482
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
15✔
1483
}
15✔
1484

1485

1486
bool File::try_remove(const std::string& path)
1487
{
220,662✔
1488
#if REALM_HAVE_STD_FILESYSTEM
1489
    std::error_code error;
1490
    bool result = std::filesystem::remove(u8path(path), error);
1491
    throwIfFileError(error, path);
1492
    return result;
1493
#else // POSIX
1494
    if (::unlink(path.c_str()) == 0)
220,662✔
1495
        return true;
185,052✔
1496

17,802✔
1497
    int err = errno; // Eliminate any risk of clobbering
35,610✔
1498
    if (err == ENOENT)
35,610✔
1499
        return false;
35,589✔
1500

18✔
1501
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
39✔
1502
    switch (err) {
39✔
1503
        case EACCES:
6✔
1504
        case EROFS:
6✔
1505
        case ETXTBSY:
6✔
1506
        case EBUSY:
6✔
1507
        case EPERM:
18✔
1508
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
18✔
1509
        case ENOENT:
3✔
1510
            return false;
×
1511
        default:
24✔
1512
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
24✔
1513
    }
39✔
1514
#endif
39✔
1515
}
39✔
1516

1517

1518
void File::move(const std::string& old_path, const std::string& new_path)
1519
{
342✔
1520
#if REALM_HAVE_STD_FILESYSTEM
1521
    std::error_code error;
1522
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1523

1524
    if (error == std::errc::no_such_file_or_directory) {
1525
        throw FileAccessError(ErrorCodes::FileNotFound, error.message(), old_path);
1526
    }
1527
    throwIfFileError(error, old_path);
1528
#else
1529
    int r = rename(old_path.c_str(), new_path.c_str());
342✔
1530
    if (r == 0)
342✔
1531
        return;
342✔
1532
    int err = errno; // Eliminate any risk of clobbering
×
1533
    std::string msg = format_errno("Failed to rename file from '%2' to '%3': %1", err, old_path, new_path);
×
1534
    switch (err) {
×
1535
        case EACCES:
×
1536
        case EROFS:
×
1537
        case ETXTBSY:
×
1538
        case EBUSY:
×
1539
        case EPERM:
×
1540
        case EEXIST:
×
1541
        case ENOTEMPTY:
×
1542
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, old_path, err);
×
1543
        case ENOENT:
×
1544
            throw FileAccessError(ErrorCodes::FileNotFound, msg, old_path, err);
×
1545
        default:
×
1546
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, old_path, err);
×
1547
    }
×
1548
#endif
×
1549
}
×
1550

1551

1552
void File::copy(const std::string& origin_path, const std::string& target_path)
1553
{
534✔
1554
#if REALM_HAVE_STD_FILESYSTEM
1555
    std::filesystem::copy_file(u8path(origin_path), u8path(target_path),
1556
                               std::filesystem::copy_options::overwrite_existing); // Throws
1557
#else
1558
    File origin_file{origin_path, mode_Read};  // Throws
534✔
1559
    File target_file{target_path, mode_Write}; // Throws
534✔
1560
    size_t buffer_size = 4096;
534✔
1561
    std::unique_ptr<char[]> buffer = std::make_unique<char[]>(buffer_size); // Throws
534✔
1562
    for (;;) {
1,413✔
1563
        size_t n = origin_file.read(buffer.get(), buffer_size); // Throws
1,413✔
1564
        target_file.write(buffer.get(), n);                     // Throws
1,413✔
1565
        if (n < buffer_size)
1,413✔
1566
            break;
534✔
1567
    }
1,413✔
1568
#endif
534✔
1569
}
534✔
1570

1571

1572
bool File::compare(const std::string& path_1, const std::string& path_2)
1573
{
×
1574
    File file_1{path_1}; // Throws
×
1575
    File file_2{path_2}; // Throws
×
1576
    size_t buffer_size = 4096;
×
1577
    std::unique_ptr<char[]> buffer_1 = std::make_unique<char[]>(buffer_size); // Throws
×
1578
    std::unique_ptr<char[]> buffer_2 = std::make_unique<char[]>(buffer_size); // Throws
×
1579
    for (;;) {
×
1580
        size_t n_1 = file_1.read(buffer_1.get(), buffer_size); // Throws
×
1581
        size_t n_2 = file_2.read(buffer_2.get(), buffer_size); // Throws
×
1582
        if (n_1 != n_2)
×
1583
            return false;
×
1584
        if (!std::equal(buffer_1.get(), buffer_1.get() + n_1, buffer_2.get()))
×
1585
            return false;
×
1586
        if (n_1 < buffer_size)
×
1587
            break;
×
1588
    }
×
1589
    return true;
×
1590
}
×
1591

1592
bool File::is_same_file_static(FileDesc f1, FileDesc f2, const std::string& path1, const std::string& path2)
1593
{
24✔
1594
    return get_unique_id(f1, path1) == get_unique_id(f2, path2);
24✔
1595
}
24✔
1596

1597
bool File::is_same_file(const File& f) const
1598
{
24✔
1599
    REALM_ASSERT_RELEASE(is_attached());
24✔
1600
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1601
    return is_same_file_static(m_fd, f.m_fd, m_path, f.m_path);
24✔
1602
}
24✔
1603

1604
FileDesc File::dup_file_desc(FileDesc fd)
1605
{
1,932✔
1606
    FileDesc fd_duped;
1,932✔
1607
#ifdef _WIN32
1608
    if (!DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &fd_duped, 0, FALSE, DUPLICATE_SAME_ACCESS))
1609
        throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
1610
#else
1611
    fd_duped = dup(fd);
1,932✔
1612

789✔
1613
    if (fd_duped == -1) {
1,932✔
1614
        int err = errno; // Eliminate any risk of clobbering
×
1615
        throw std::system_error(err, std::system_category(), "dup() failed");
×
1616
    }
×
1617
#endif // conditonal on _WIN32
1,932✔
1618
    return fd_duped;
1,932✔
1619
}
1,932✔
1620

1621
File::UniqueID File::get_unique_id()
1622
{
213,558✔
1623
    REALM_ASSERT_RELEASE(is_attached());
213,558✔
1624
    File::UniqueID uid = File::get_unique_id(m_fd, m_path);
213,558✔
1625
    if (!m_cached_unique_id) {
213,558✔
1626
        m_cached_unique_id = std::make_optional(uid);
213,543✔
1627
    }
213,543✔
1628
    if (m_cached_unique_id != uid) {
213,558✔
1629
        throw FileAccessError(ErrorCodes::FileOperationFailed,
×
1630
                              util::format("The unique id of this Realm file has changed unexpectedly, this could be "
×
1631
                                           "due to modifications by an external process '%1'",
×
1632
                                           m_path),
×
1633
                              m_path);
×
1634
    }
×
1635
    return uid;
213,558✔
1636
}
213,558✔
1637

1638
FileDesc File::get_descriptor() const
1639
{
87,669✔
1640
    return m_fd;
87,669✔
1641
}
87,669✔
1642

1643
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1644
{
170,061✔
1645
#ifdef _WIN32 // Windows version
1646
    // CreateFile2 with creationDisposition OPEN_EXISTING will return a file handle only if the file exists
1647
    // otherwise it will raise ERROR_FILE_NOT_FOUND. This call will never create a new file.
1648
    WindowsFileHandleHolder fileHandle(::CreateFile2(u8path(path).c_str(), FILE_READ_ATTRIBUTES,
1649
                                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1650
                                                     OPEN_EXISTING, nullptr));
1651

1652
    if (fileHandle == INVALID_HANDLE_VALUE) {
1653
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1654
            return none;
1655
        }
1656
        throw SystemError(GetLastError(), "CreateFileW failed");
1657
    }
1658

1659
    return get_unique_id(fileHandle, path);
1660
#else // POSIX version
1661
    struct stat statbuf;
170,061✔
1662
    if (::stat(path.c_str(), &statbuf) == 0) {
170,061✔
1663
        if (statbuf.st_size == 0) {
111,609✔
1664
            // On exFAT systems the inode and device are not populated correctly until the file
1665
            // has been allocated some space. The uid can also be reassigned if the file is
1666
            // truncated to zero. This has led to bugs where a unique id returned here was
1667
            // reused by different files. The following check ensures that this is not
1668
            // happening anywhere else in future code.
1669
            return none;
37,953✔
1670
        }
37,953✔
1671
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
73,656✔
1672
    }
73,656✔
1673
    int err = errno; // Eliminate any risk of clobbering
58,452✔
1674
    // File doesn't exist
3✔
1675
    if (err == ENOENT)
58,452✔
1676
        return none;
58,452✔
1677
    throw SystemError(err, format_errno("fstat() failed: %1 for '%2'", err, path));
×
1678
#endif
×
1679
}
×
1680

1681
File::UniqueID File::get_unique_id(FileDesc file, const std::string& debug_path)
1682
{
216,861✔
1683
#ifdef _WIN32 // Windows version
1684
    REALM_ASSERT(file != nullptr);
1685
    File::UniqueID ret;
1686
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1687
        throw std::system_error(GetLastError(), std::system_category(),
1688
                                util::format("GetFileInformationByHandleEx() failed for '%1'", debug_path));
1689
    }
1690

1691
    return ret;
1692
#else // POSIX version
1693
    REALM_ASSERT(file >= 0);
216,861✔
1694
    struct stat statbuf;
216,861✔
1695
    if (::fstat(file, &statbuf) == 0) {
216,861✔
1696
        // On exFAT systems the inode and device are not populated correctly until the file
44,271✔
1697
        // has been allocated some space. The uid can also be reassigned if the file is
44,271✔
1698
        // truncated to zero. This has led to bugs where a unique id returned here was
44,271✔
1699
        // reused by different files. The following check ensures that this is not
44,271✔
1700
        // happening anywhere else in future code.
44,271✔
1701
        if (statbuf.st_size == 0) {
216,861✔
1702
            throw FileAccessError(
×
1703
                ErrorCodes::FileOperationFailed,
×
1704
                util::format("Attempt to get unique id on an empty file. This could be due to an external "
×
1705
                             "process modifying Realm files. '%1'",
×
1706
                             debug_path),
×
1707
                debug_path);
×
1708
        }
×
1709
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
216,861✔
1710
    }
216,861✔
1711
    throw std::system_error(errno, std::system_category(), util::format("fstat() failed for '%1'", debug_path));
×
1712
#endif
×
1713
}
×
1714

1715
std::string File::get_path() const
1716
{
43,986✔
1717
    return m_path;
43,986✔
1718
}
43,986✔
1719

1720
std::string File::resolve(const std::string& path, const std::string& base_dir)
1721
{
209,223✔
1722
#if REALM_HAVE_STD_FILESYSTEM
1723
    return (u8path(base_dir) / u8path(path)).lexically_normal().u8string();
1724
#else
1725
    char dir_sep = '/';
209,223✔
1726
    std::string path_2 = path;
209,223✔
1727
    std::string base_dir_2 = base_dir;
209,223✔
1728
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
209,223✔
1729
    if (is_absolute)
209,223✔
1730
        return path_2;
6✔
1731
    if (path_2.empty())
209,217✔
1732
        path_2 = ".";
6✔
1733
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
209,217✔
1734
        base_dir_2.push_back(dir_sep);
194,685✔
1735
    /*
55,215✔
1736
    // Abbreviate
55,215✔
1737
    for (;;) {
55,215✔
1738
        if (base_dir_2.empty()) {
55,215✔
1739
            if (path_2.empty())
55,215✔
1740
                return "./";
55,215✔
1741
            return path_2;
55,215✔
1742
        }
55,215✔
1743
        if (path_2 == ".") {
55,215✔
1744
            remove_trailing_dir_seps(base_dir_2);
55,215✔
1745
            return base_dir_2;
55,215✔
1746
        }
55,215✔
1747
        if (has_prefix(path_2, "./")) {
55,215✔
1748
            remove_trailing_dir_seps(base_dir_2);
55,215✔
1749
            // drop dot
55,215✔
1750
            // transfer slashes
55,215✔
1751
        }
55,215✔
1752

55,215✔
1753
        if (path_2.size() < 2 || path_2[1] != '.')
55,215✔
1754
            break;
55,215✔
1755
        if (path_2.size())
55,215✔
1756
    }
55,215✔
1757
    */
55,215✔
1758
    return base_dir_2 + path_2;
209,217✔
1759
#endif
209,217✔
1760
}
209,217✔
1761

1762
std::string File::parent_dir(const std::string& path)
1763
{
78✔
1764
#if REALM_HAVE_STD_FILESYSTEM
1765
    return u8path(path).parent_path().u8string(); // Throws
1766
#else
1767
    auto is_sep = [](char c) -> bool {
1,119✔
1768
        return c == '/' || c == '\\';
1,119✔
1769
    };
1,119✔
1770
    auto it = std::find_if(path.rbegin(), path.rend(), is_sep);
78✔
1771
    while (it != path.rend() && is_sep(*it))
168✔
1772
        ++it;
90✔
1773
    return path.substr(0, path.rend() - it);
78✔
1774
#endif
78✔
1775
}
78✔
1776

1777
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1778
{
6✔
1779
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1780
}
6✔
1781

1782

1783
void File::set_encryption_key(const char* key)
1784
{
88,221✔
1785
#if REALM_ENABLE_ENCRYPTION
88,221✔
1786
    if (key) {
88,221✔
1787
        auto buffer = std::make_unique<char[]>(64);
312✔
1788
        memcpy(buffer.get(), key, 64);
312✔
1789
        m_encryption_key = std::move(buffer);
312✔
1790
    }
312✔
1791
    else {
87,909✔
1792
        m_encryption_key.reset();
87,909✔
1793
    }
87,909✔
1794
#else
1795
    if (key) {
1796
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1797
    }
1798
#endif
1799
}
88,221✔
1800

1801
const char* File::get_encryption_key() const
1802
{
138✔
1803
    return m_encryption_key.get();
138✔
1804
}
138✔
1805

1806
void File::MapBase::map(const File& f, AccessMode a, size_t size, int map_flags, size_t offset,
1807
                        util::WriteObserver* observer)
1808
{
1,330,743✔
1809
    REALM_ASSERT(!m_addr);
1,330,743✔
1810
#if REALM_ENABLE_ENCRYPTION
1,330,743✔
1811
    m_addr = f.map(a, size, m_encrypted_mapping, map_flags, offset);
1,330,743✔
1812
    if (observer && m_encrypted_mapping) {
1,330,743✔
1813
        m_encrypted_mapping->set_observer(observer);
351✔
1814
    }
351✔
1815
#else
1816
    m_addr = f.map(a, size, map_flags, offset);
1817
    static_cast<void>(observer);
1818
#endif
1819
    m_size = m_reservation_size = size;
1,330,743✔
1820
    m_fd = f.m_fd;
1,330,743✔
1821
    m_offset = offset;
1,330,743✔
1822
    m_access_mode = a;
1,330,743✔
1823
}
1,330,743✔
1824

1825

1826
void File::MapBase::unmap() noexcept
1827
{
3,083,607✔
1828
    if (!m_addr)
3,083,607✔
1829
        return;
1,665,186✔
1830
    REALM_ASSERT(m_reservation_size);
1,418,421✔
1831
#if REALM_ENABLE_ENCRYPTION
1,418,421✔
1832
    if (m_encrypted_mapping) {
1,418,421✔
1833
        m_encrypted_mapping = nullptr;
3,249✔
1834
        util::remove_encrypted_mapping(m_addr, m_size);
3,249✔
1835
    }
3,249✔
1836
#endif
1,418,421✔
1837
    ::munmap(m_addr, m_reservation_size);
1,418,421✔
1838
    m_addr = nullptr;
1,418,421✔
1839
    m_size = 0;
1,418,421✔
1840
    m_reservation_size = 0;
1,418,421✔
1841
}
1,418,421✔
1842

1843
void File::MapBase::remap(const File& f, AccessMode a, size_t size, int map_flags)
1844
{
×
1845
    REALM_ASSERT(m_addr);
×
1846
    m_addr = f.remap(m_addr, m_size, a, size, map_flags);
×
1847
    m_size = m_reservation_size = size;
×
1848
}
×
1849

1850
bool File::MapBase::try_reserve(const File& file, AccessMode a, size_t size, size_t offset,
1851
                                util::WriteObserver* observer)
1852
{
87,603✔
1853
#ifdef _WIN32
1854
    static_cast<void>(observer);
1855
    // unsupported for now
1856
    return false;
1857
#else
1858
    void* addr = ::mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
87,603✔
1859
    if (addr == MAP_FAILED)
87,603✔
1860
        return false;
42,888✔
1861
    m_addr = addr;
87,603✔
1862
    REALM_ASSERT(m_size == 0);
87,603✔
1863
    m_access_mode = a;
87,603✔
1864
    m_reservation_size = size;
87,603✔
1865
    m_fd = file.get_descriptor();
87,603✔
1866
    m_offset = offset;
87,603✔
1867
#if REALM_ENABLE_ENCRYPTION
87,603✔
1868
    if (file.m_encryption_key) {
87,603✔
1869
        m_encrypted_mapping =
246✔
1870
            util::reserve_mapping(addr, {m_fd, file.get_path(), a, file.m_encryption_key.get()}, offset);
246✔
1871
        if (observer) {
246✔
1872
            m_encrypted_mapping->set_observer(observer);
240✔
1873
        }
240✔
1874
    }
246✔
1875
#else
1876
    static_cast<void>(observer);
1877
#endif
1878
#endif
87,603✔
1879
    return true;
87,603✔
1880
}
87,603✔
1881

1882
bool File::MapBase::try_extend_to(size_t size) noexcept
1883
{
106,071✔
1884
    if (size > m_reservation_size) {
106,071✔
1885
        return false;
×
1886
    }
×
1887
    // return false;
57,690✔
1888
#ifndef _WIN32
106,071✔
1889
    char* extension_start_addr = (char*)m_addr + m_size;
106,071✔
1890
    size_t extension_size = size - m_size;
106,071✔
1891
    size_t extension_start_offset = m_offset + m_size;
106,071✔
1892
#if REALM_ENABLE_ENCRYPTION
106,071✔
1893
    if (m_encrypted_mapping) {
106,071✔
1894
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
279✔
1895
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
279✔
1896
        if (got_addr == MAP_FAILED)
279✔
1897
            return false;
135✔
1898
        REALM_ASSERT(got_addr == extension_start_addr);
279✔
1899
        util::extend_encrypted_mapping(m_encrypted_mapping, m_addr, m_offset, m_size, size);
279✔
1900
        m_size = size;
279✔
1901
        return true;
279✔
1902
    }
279✔
1903
#endif
105,792✔
1904
    try {
105,792✔
1905
        void* got_addr = util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode,
105,792✔
1906
                                          extension_start_offset, nullptr);
105,792✔
1907
        if (got_addr == extension_start_addr) {
105,792✔
1908
            m_size = size;
105,714✔
1909
            return true;
105,714✔
1910
        }
105,714✔
1911
    }
18✔
1912
    catch (...) {
18✔
1913
        return false;
18✔
1914
    }
18✔
1915
#endif
66✔
1916
    return false;
66✔
1917
}
66✔
1918

1919
void File::MapBase::sync()
1920
{
1,380,411✔
1921
    REALM_ASSERT(m_addr);
1,380,411✔
1922

708,213✔
1923
    File::sync_map(m_fd, m_addr, m_size);
1,380,411✔
1924
}
1,380,411✔
1925

1926
void File::MapBase::flush()
1927
{
1,354,779✔
1928
    REALM_ASSERT(m_addr);
1,354,779✔
1929
#if REALM_ENABLE_ENCRYPTION
1,354,779✔
1930
    if (m_encrypted_mapping) {
1,354,779✔
1931
        realm::util::encryption_flush(m_encrypted_mapping);
972✔
1932
    }
972✔
1933
#endif
1,354,779✔
1934
}
1,354,779✔
1935

1936
std::time_t File::last_write_time(const std::string& path)
1937
{
948✔
1938
#if REALM_HAVE_STD_FILESYSTEM
1939
    auto time = std::filesystem::last_write_time(u8path(path));
1940

1941
    using namespace std::chrono;
1942
#if __cplusplus >= 202002L
1943
    auto system_time = clock_cast<system_clock>(time);
1944
#else
1945
    auto system_time =
1946
        time_point_cast<system_clock::duration>(time - decltype(time)::clock::now() + system_clock::now());
1947
#endif
1948
    return system_clock::to_time_t(system_time);
1949
#else
1950
    struct stat statbuf;
948✔
1951
    if (::stat(path.c_str(), &statbuf) != 0) {
948✔
1952
        throw SystemError(errno, "stat() failed");
×
1953
    }
×
1954
    return statbuf.st_mtime;
948✔
1955
#endif
948✔
1956
}
948✔
1957

1958
File::SizeType File::get_free_space(const std::string& path)
1959
{
186✔
1960
#if REALM_HAVE_STD_FILESYSTEM
1961
    return std::filesystem::space(u8path(path)).available;
1962
#else
1963
    struct statvfs stat;
186✔
1964
    if (statvfs(path.c_str(), &stat) != 0) {
186✔
1965
        throw SystemError(errno, "statvfs() failed");
×
1966
    }
×
1967
    return SizeType(stat.f_bavail) * stat.f_bsize;
186✔
1968
#endif
186✔
1969
}
186✔
1970

1971
#if REALM_HAVE_STD_FILESYSTEM
1972

1973
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1974
{
1975
    try {
1976
        m_iterator = std::filesystem::directory_iterator(u8path(path));
1977
    }
1978
    catch (const std::filesystem::filesystem_error& e) {
1979
        if (e.code() != std::errc::no_such_file_or_directory || !allow_missing)
1980
            throw;
1981
    }
1982
}
1983

1984
DirScanner::~DirScanner() = default;
1985

1986
bool DirScanner::next(std::string& name)
1987
{
1988
    const std::filesystem::directory_iterator end;
1989
    if (m_iterator == end)
1990
        return false;
1991
    name = m_iterator->path().filename().u8string();
1992
    m_iterator++;
1993
    return true;
1994
}
1995

1996
#elif !defined(_WIN32)
1997

1998
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1999
{
130,773✔
2000
    m_dirp = opendir(path.c_str());
130,773✔
2001
    if (!m_dirp) {
130,773✔
2002
        int err = errno; // Eliminate any risk of clobbering
5,736✔
2003
        if (allow_missing && err == ENOENT)
5,736✔
2004
            return;
5,736✔
2005

2006
        std::string msg = format_errno("opendir() failed: %1", err);
×
2007
        switch (err) {
×
2008
            case EACCES:
×
2009
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
2010
            case ENOENT:
×
2011
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
2012
            default:
×
2013
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
2014
        }
×
2015
    }
×
2016
}
130,773✔
2017

2018
DirScanner::~DirScanner() noexcept
2019
{
130,770✔
2020
    if (m_dirp) {
130,770✔
2021
        int r = closedir(m_dirp);
125,037✔
2022
        REALM_ASSERT_RELEASE(r == 0);
125,037✔
2023
    }
125,037✔
2024
}
130,770✔
2025

2026
bool DirScanner::next(std::string& name)
2027
{
329,277✔
2028
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
2029
    !defined(__EMSCRIPTEN__)
2030
#error "readdir() is not known to be thread-safe on this platform"
2031
#endif
2032

113,937✔
2033
    if (!m_dirp)
329,277✔
2034
        return false;
5,733✔
2035

111,069✔
2036
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
111,069✔
2037
// in 32-bits.
111,069✔
2038
#if REALM_HAVE_READDIR64
111,069✔
2039
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
233,271✔
2040
#else
2041
#define REALM_READDIR(...) readdir(__VA_ARGS__)
340,344✔
2042
#endif
212,475✔
2043

111,069✔
2044
    for (;;) {
573,615✔
2045
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
573,615✔
2046
        DirentPtr dirent;
573,615✔
2047
        do {
573,615✔
2048
            // readdir() signals both errors and end-of-stream by returning a
233,271✔
2049
            // null pointer. To distinguish between end-of-stream and errors,
233,271✔
2050
            // the manpage recommends setting errno specifically to 0 before
233,271✔
2051
            // calling it...
233,271✔
2052
            errno = 0;
573,615✔
2053

233,271✔
2054
            dirent = REALM_READDIR(m_dirp);
573,615✔
2055
        } while (!dirent && errno == EAGAIN);
573,615✔
2056

233,271✔
2057
        if (!dirent) {
573,615✔
2058
            if (errno != 0)
124,986✔
2059
                throw SystemError(errno, "readdir() failed");
×
2060
            return false; // End of stream
124,986✔
2061
        }
124,986✔
2062
        const char* name_1 = dirent->d_name;
448,629✔
2063
        std::string name_2 = name_1;
448,629✔
2064
        if (name_2 != "." && name_2 != "..") {
448,629✔
2065
            name = name_2;
198,558✔
2066
            return true;
198,558✔
2067
        }
198,558✔
2068
    }
448,629✔
2069
}
323,544✔
2070

2071
#else
2072

2073
DirScanner::DirScanner(const std::string&, bool)
2074
{
2075
    throw NotImplemented();
2076
}
2077

2078
DirScanner::~DirScanner() noexcept {}
2079

2080
bool DirScanner::next(std::string&)
2081
{
2082
    return false;
2083
}
2084

2085
#endif
2086

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

© 2026 Coveralls, Inc