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

realm / realm-core / 2288

01 May 2024 08:17PM UTC coverage: 90.738% (-0.02%) from 90.756%
2288

push

Evergreen

web-flow
[bindgen] fix signature of update_base_url (#7665)

101892 of 180214 branches covered (56.54%)

212458 of 234145 relevant lines covered (90.74%)

5757351.37 hits per line

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

78.73
/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/file_mapper.hpp>
24

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

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

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

50
using namespace realm::util;
51

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

69
// This variable exists such that page_size() can return the page size without having to make any system calls.
70
// It could also have been a static local variable, but Valgrind/Helgrind gives a false error on that.
71
std::atomic<size_t> cached_page_size = get_page_size();
72

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

94
#ifdef _WIN32
95

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

114
struct WindowsFileHandleHolder {
115
    WindowsFileHandleHolder() = default;
116
    explicit WindowsFileHandleHolder(HANDLE h)
117
        : handle(h)
118
    {
119
    }
120

121
    WindowsFileHandleHolder(WindowsFileHandleHolder&&) = delete;
122
    WindowsFileHandleHolder(const WindowsFileHandleHolder&) = delete;
123
    WindowsFileHandleHolder& operator=(WindowsFileHandleHolder&&) = delete;
124
    WindowsFileHandleHolder& operator=(const WindowsFileHandleHolder&) = delete;
125

126
    operator HANDLE() const noexcept
127
    {
128
        return handle;
129
    }
130

131
    ~WindowsFileHandleHolder()
132
    {
133
        if (handle != INVALID_HANDLE_VALUE) {
134
            ::CloseHandle(handle);
135
        }
136
    }
137

138
    HANDLE handle = INVALID_HANDLE_VALUE;
139
};
140

141
#endif
142

143
#if REALM_HAVE_STD_FILESYSTEM
144
using std::filesystem::u8path;
145

146
void throwIfCreateDirectoryError(std::error_code error, const std::string& path)
147
{
148
    if (!error)
149
        return;
150

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

161
void throwIfFileError(std::error_code error, const std::string& path)
162
{
163
    if (!error)
164
        return;
165

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

178
} // anonymous namespace
179

180

181
namespace realm::util {
182
namespace {
183

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

194
} // anonymous namespace
195

196

197
bool try_make_dir(const std::string& path)
198
{
183,264✔
199
#if REALM_HAVE_STD_FILESYSTEM
200
    std::error_code error;
201
    bool result = std::filesystem::create_directory(u8path(path), error);
202
    throwIfCreateDirectoryError(error, path);
203
    return result;
204
#else // POSIX
205
    if (::mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
183,264✔
206
        return true;
74,217✔
207

208
    int err = errno; // Eliminate any risk of clobbering
109,047✔
209
    if (err == EEXIST)
109,047✔
210
        return false;
109,038✔
211

212
    auto msg = format_errno("Failed to create directory at '%2': %1", err, path);
9✔
213

214
    switch (err) {
9✔
215
        case EACCES:
3✔
216
        case EROFS:
6✔
217
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
218
        default:
✔
219
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
220
    }
9✔
221
#endif
9✔
222
}
9✔
223

224

225
void make_dir(const std::string& path)
226
{
108✔
227
    if (try_make_dir(path)) // Throws
108✔
228
        return;
90✔
229
    throw Exists(format_errno("Failed to create directory at '%2': %1", EEXIST, path), path);
18✔
230
}
108✔
231

232

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

246
    while (pos < path.size()) {
2,172✔
247
        auto sep = path.find_first_of("/\\", pos);
1,971✔
248
        char c = 0;
1,971✔
249
        if (sep < path.size()) {
1,971✔
250
            c = path[sep];
1,953✔
251
            path[sep] = 0;
1,953✔
252
        }
1,953✔
253
        try_make_dir(path);
1,971✔
254
        if (c) {
1,971✔
255
            path[sep] = c;
1,953✔
256
            pos = sep + 1;
1,953✔
257
        }
1,953✔
258
        else {
18✔
259
            break;
18✔
260
        }
18✔
261
    }
1,971✔
262
#endif
219✔
263
}
219✔
264

265

266
void remove_dir(const std::string& path)
267
{
10,569✔
268
    if (try_remove_dir(path)) // Throws
10,569✔
269
        return;
10,551✔
270
    int err = ENOENT;
18✔
271
    std::string msg = format_errno("Failed to remove directory '%2': %1", err, path);
18✔
272
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
18✔
273
}
10,569✔
274

275

276
bool try_remove_dir(const std::string& path)
277
{
113,988✔
278
#if REALM_HAVE_STD_FILESYSTEM
279
    std::error_code error;
280
    bool result = std::filesystem::remove(u8path(path), error);
281
    throwIfFileError(error, path);
282
    return result;
283
#else // POSIX
284
    if (::rmdir(path.c_str()) == 0)
113,988✔
285
        return true;
113,721✔
286

287
    int err = errno; // Eliminate any risk of clobbering
267✔
288
    if (err == ENOENT)
267✔
289
        return false;
261✔
290

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

306

307
bool try_remove_dir_recursive(const std::string& path)
308
{
103,392✔
309
#if REALM_HAVE_STD_FILESYSTEM
310
    std::error_code error;
311
    auto removed_count = std::filesystem::remove_all(u8path(path), error);
312
    throwIfFileError(error, path);
313
    return removed_count > 0;
314
#else
315
    {
103,392✔
316
        bool allow_missing = true;
103,392✔
317
        DirScanner ds{path, allow_missing}; // Throws
103,392✔
318
        std::string name;
103,392✔
319
        while (ds.next(name)) {                              // Throws
291,378✔
320
            std::string subpath = File::resolve(name, path); // Throws
187,986✔
321
            if (File::is_dir(subpath)) {                     // Throws
187,986✔
322
                try_remove_dir_recursive(subpath);           // Throws
59,994✔
323
            }
59,994✔
324
            else {
127,992✔
325
                File::remove(subpath); // Throws
127,992✔
326
            }
127,992✔
327
        }
187,986✔
328
    }
103,392✔
329
    return try_remove_dir(path); // Throws
103,392✔
330
#endif
103,392✔
331
}
103,392✔
332

333

334
std::string make_temp_dir()
335
{
43,617✔
336
#ifdef _WIN32 // Windows version
337
    std::filesystem::path temp = std::filesystem::temp_directory_path();
338

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

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

358
#else // POSIX.1-2008 version
359

360
#if REALM_ANDROID
361
    std::string buffer = "/data/local/tmp/realm_XXXXXX";
362
#else
363
    char* tmp_dir_env = getenv("TMPDIR");
43,617✔
364
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
43,617✔
365
    if (!buffer.empty() && buffer.back() != '/') {
43,617✔
366
        buffer += "/";
43,617✔
367
    }
43,617✔
368
    buffer += "realm_XXXXXX";
43,617✔
369
#endif
43,617✔
370

371
    if (mkdtemp(buffer.data()) == 0) {
43,617✔
372
        int err = errno;
×
373
        throw SystemError(err, util::format("Failed to create temporary directory: %1", err)); // LCOV_EXCL_LINE
374
    }
×
375
    return buffer;
43,617✔
376
#endif
43,617✔
377
}
43,617✔
378

379
std::string make_temp_file(const char* prefix)
380
{
38,388✔
381
#ifdef _WIN32 // Windows version
382
    std::filesystem::path temp = std::filesystem::temp_directory_path();
383

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

390
    return std::filesystem::path(buffer).u8string();
391

392
#else // POSIX.1-2008 version
393

394
#if REALM_ANDROID
395
    std::string base_dir = "/data/local/tmp/";
396
#else
397
    char* tmp_dir_env = getenv("TMPDIR");
38,388✔
398
    std::string base_dir = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
38,388✔
399
    if (!base_dir.empty() && base_dir[base_dir.length() - 1] != '/') {
38,388✔
400
        base_dir += '/';
38,388✔
401
    }
38,388✔
402
#endif
38,388✔
403
    std::string filename = util::format("%1%2_XXXXXX", base_dir, prefix);
38,388✔
404
    auto fd = mkstemp(filename.data());
38,388✔
405
    if (fd == -1) {
38,388✔
406
        throw std::system_error(errno, std::system_category(), "mkstemp() failed"); // LCOV_EXCL_LINE
407
    }
×
408
    close(fd);
38,388✔
409
    return filename;
38,388✔
410
#endif
38,388✔
411
}
38,388✔
412

413
size_t page_size()
414
{
4,027,251✔
415
    return cached_page_size.load(std::memory_order::memory_order_relaxed);
4,027,251✔
416
}
4,027,251✔
417

418
OnlyForTestingPageSizeChange::OnlyForTestingPageSizeChange(size_t new_page_size)
419
{
1,818✔
420
    REALM_ASSERT(new_page_size % c_min_supported_page_size == 0);
1,818✔
421
    cached_page_size = new_page_size;
1,818✔
422
}
1,818✔
423

424
OnlyForTestingPageSizeChange::~OnlyForTestingPageSizeChange()
425
{
1,818✔
426
    cached_page_size = get_page_size();
1,818✔
427
}
1,818✔
428

429
void File::open_internal(const std::string& path, AccessMode a, CreateMode c, int flags, bool* success)
430
{
439,707✔
431
    REALM_ASSERT_RELEASE(!is_attached());
439,707✔
432
    m_path = path; // for error reporting and debugging
439,707✔
433
    m_cached_unique_id = {};
439,707✔
434

435
#ifdef _WIN32 // Windows version
436

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

476
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
477
    if (success && err == ERROR_FILE_EXISTS && c == create_Must) {
478
        *success = false;
479
        return;
480
    }
481
    if (success && err == ERROR_FILE_NOT_FOUND && c == create_Never) {
482
        *success = false;
483
        return;
484
    }
485
    std::string msg = get_last_error_msg("CreateFile() failed: ", err);
486
    switch (err) {
487
        case ERROR_SHARING_VIOLATION:
488
        case ERROR_ACCESS_DENIED:
489
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, int(err));
490
        case ERROR_FILE_NOT_FOUND:
491
        case ERROR_PATH_NOT_FOUND:
492
            throw FileAccessError(ErrorCodes::FileNotFound, msg, path, int(err));
493
        case ERROR_FILE_EXISTS:
494
            throw Exists(msg, path);
495
        default:
496
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, int(err));
497
    }
498

499
#else // POSIX version
500

501
    int flags2 = 0;
439,707✔
502
    switch (a) {
439,707✔
503
        case access_ReadOnly:
2,883✔
504
            flags2 = O_RDONLY;
2,883✔
505
            break;
2,883✔
506
        case access_ReadWrite:
436,872✔
507
            flags2 = O_RDWR;
436,872✔
508
            break;
436,872✔
509
    }
439,707✔
510
    switch (c) {
439,758✔
511
        case create_Auto:
408,711✔
512
            flags2 |= O_CREAT;
408,711✔
513
            break;
408,711✔
514
        case create_Never:
29,613✔
515
            break;
29,613✔
516
        case create_Must:
1,434✔
517
            flags2 |= O_CREAT | O_EXCL;
1,434✔
518
            break;
1,434✔
519
    }
439,758✔
520
    if (flags & flag_Trunc)
439,752✔
521
        flags2 |= O_TRUNC;
558✔
522
    if (flags & flag_Append)
439,752✔
523
        flags2 |= O_APPEND;
189,792✔
524
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
439,752✔
525
    if (0 <= fd) {
439,752✔
526
        m_fd = fd;
439,611✔
527
        m_have_lock = false;
439,611✔
528
        if (success)
439,611✔
529
            *success = true;
204✔
530
        return;
439,611✔
531
    }
439,611✔
532

533
    int err = errno; // Eliminate any risk of clobbering
141✔
534
    if (success && err == EEXIST && c == create_Must) {
141✔
535
        *success = false;
30✔
536
        return;
30✔
537
    }
30✔
538
    if (success && err == ENOENT && c == create_Never) {
111!
539
        *success = false;
×
540
        return;
×
541
    }
×
542
    std::string msg = format_errno("Failed to open file at path '%2': %1", err, path);
111✔
543
    switch (err) {
111✔
544
        case EACCES:
6✔
545
        case EPERM:
6✔
546
        case EROFS:
6✔
547
        case ETXTBSY:
6✔
548
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
549
        case ENOENT:
30✔
550
            if (c != create_Never)
30✔
551
                msg = util::format("Failed to open file at path '%1': parent directory does not exist", path);
6✔
552
            throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
30✔
553
        case EEXIST:
36✔
554
            throw Exists(msg, path);
36✔
555
        case ENOTDIR:
✔
556
            msg = format("Failed to open file at path '%1': parent path is not a directory", path);
×
557
            [[fallthrough]];
×
558
        default:
36✔
559
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
560
    }
111✔
561

562
#endif
111✔
563
}
111✔
564

565

566
void File::close() noexcept
567
{
882,645✔
568
#ifdef _WIN32 // Windows version
569

570
    if (!m_fd)
571
        return;
572
    if (m_have_lock)
573
        unlock();
574

575
    BOOL r = CloseHandle(m_fd);
576
    REALM_ASSERT_RELEASE(r);
577
    m_fd = nullptr;
578

579
#else // POSIX version
580

581
    if (m_fd < 0)
882,645✔
582
        return;
443,214✔
583
    if (m_have_lock)
439,431✔
584
        unlock();
153✔
585
    int r = ::close(m_fd);
439,431✔
586
    REALM_ASSERT_RELEASE(r == 0);
439,431✔
587
    m_fd = -1;
439,431✔
588

589
#endif
439,431✔
590
}
439,431✔
591

592
void File::close_static(FileDesc fd)
593
{
8,658✔
594
#ifdef _WIN32
595
    if (!fd)
596
        return;
597

598
    if (!CloseHandle(fd))
599
        throw std::system_error(GetLastError(), std::system_category(),
600
                                "CloseHandle() failed from File::close_static()");
601
#else
602
    if (fd < 0)
8,658✔
603
        return;
×
604

605
    int ret = -1;
8,658✔
606
    do {
8,658✔
607
        ret = ::close(fd);
8,658✔
608
    } while (ret == -1 && errno == EINTR);
8,658!
609

610
    if (ret != 0) {
8,658✔
611
        int err = errno; // Eliminate any risk of clobbering
×
612
        if (err == EBADF || err == EIO)
×
613
            throw SystemError(err, "File::close_static() failed");
×
614
    }
×
615
#endif
8,658✔
616
}
8,658✔
617

618
size_t File::read_static(FileDesc fd, char* data, size_t size)
619
{
589,152✔
620
#ifdef _WIN32 // Windows version
621
    char* const data_0 = data;
622
    while (0 < size) {
623
        DWORD n = std::numeric_limits<DWORD>::max();
624
        if (int_less_than(size, n))
625
            n = static_cast<DWORD>(size);
626
        DWORD r = 0;
627
        if (!ReadFile(fd, data, n, &r, 0))
628
            goto error;
629
        if (r == 0)
630
            break;
631
        REALM_ASSERT_RELEASE(r <= n);
632
        size -= size_t(r);
633
        data += size_t(r);
634
    }
635
    return data - data_0;
636

637
error:
638
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
639
    throw SystemError(int(err), "ReadFile() failed");
640

641
#else // POSIX version
642

643
    char* const data_0 = data;
589,152✔
644
    while (0 < size) {
1,175,355✔
645
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
646
        size_t n = std::min(size, size_t(SSIZE_MAX));
589,239✔
647
        ssize_t r = ::read(fd, data, n);
589,239✔
648
        if (r == 0)
589,239✔
649
            break;
3,036✔
650
        if (r < 0)
586,203✔
651
            goto error; // LCOV_EXCL_LINE
652
        REALM_ASSERT_RELEASE(size_t(r) <= n);
586,203✔
653
        size -= size_t(r);
586,203✔
654
        data += size_t(r);
586,203✔
655
    }
586,203✔
656
    return data - data_0;
589,152✔
657

658
error:
×
659
    // LCOV_EXCL_START
660
    throw SystemError(errno, "read() failed");
×
661
// LCOV_EXCL_STOP
662
#endif
589,152✔
663
}
589,152✔
664

665

666
size_t File::read(char* data, size_t size)
667
{
1,266✔
668
    REALM_ASSERT_RELEASE(is_attached());
1,266✔
669

670
    if (m_encryption_key) {
1,266✔
671
        uint64_t pos_original = File::get_file_pos(m_fd);
×
672
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
×
673
        size_t pos = size_t(pos_original);
×
674
        Map<char> read_map(*this, access_ReadOnly, static_cast<size_t>(pos + size));
×
675
        realm::util::encryption_read_barrier(read_map, pos, size);
×
676
        memcpy(data, read_map.get_addr() + pos, size);
×
677
        uint64_t cur = File::get_file_pos(m_fd);
×
678
        seek_static(m_fd, cur + size);
×
679
        return read_map.get_size() - pos;
×
680
    }
×
681

682
    return read_static(m_fd, data, size);
1,266✔
683
}
1,266✔
684

685
void File::write_static(FileDesc fd, const char* data, size_t size)
686
{
283,467✔
687
#ifdef _WIN32
688
    while (0 < size) {
689
        DWORD n = std::numeric_limits<DWORD>::max();
690
        if (int_less_than(size, n))
691
            n = static_cast<DWORD>(size);
692
        DWORD r = 0;
693
        if (!WriteFile(fd, data, n, &r, 0))
694
            goto error;
695
        REALM_ASSERT_RELEASE(r == n); // Partial writes are not possible.
696
        size -= size_t(r);
697
        data += size_t(r);
698
    }
699
    return;
700

701
error:
702
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
703
    if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
704
        std::string msg = get_last_error_msg("WriteFile() failed: ", err);
705
        throw OutOfDiskSpace(msg);
706
    }
707
    throw SystemError(err, "WriteFile() failed");
708
#else
709
    while (0 < size) {
566,817✔
710
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
711
        size_t n = std::min(size, size_t(SSIZE_MAX));
283,350✔
712
        ssize_t r = ::write(fd, data, n);
283,350✔
713
        if (r < 0)
283,350✔
714
            goto error; // LCOV_EXCL_LINE
715
        REALM_ASSERT_RELEASE(r != 0);
283,350✔
716
        REALM_ASSERT_RELEASE(size_t(r) <= n);
283,350✔
717
        size -= size_t(r);
283,350✔
718
        data += size_t(r);
283,350✔
719
    }
283,350✔
720
    return;
283,467✔
721

722
error:
283,467✔
723
    // LCOV_EXCL_START
724
    int err = errno; // Eliminate any risk of clobbering
×
725
    auto msg = format_errno("write() failed: %1", err);
×
726
    if (err == ENOSPC || err == EDQUOT) {
×
727
        throw OutOfDiskSpace(msg);
×
728
    }
×
729
    throw SystemError(err, msg);
×
730
    // LCOV_EXCL_STOP
731

732
#endif
×
733
}
×
734

735
void File::write(const char* data, size_t size)
736
{
47,748✔
737
    REALM_ASSERT_RELEASE(is_attached());
47,748✔
738

739
    if (m_encryption_key) {
47,748✔
740
        uint64_t pos_original = get_file_pos(m_fd);
3,021✔
741
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
3,021✔
742
        size_t pos = size_t(pos_original);
3,021✔
743
        Map<char> write_map(*this, access_ReadWrite, static_cast<size_t>(pos + size));
3,021✔
744
        realm::util::encryption_read_barrier(write_map, pos, size);
3,021✔
745
        memcpy(write_map.get_addr() + pos, data, size);
3,021✔
746
        realm::util::encryption_write_barrier(write_map, pos, size);
3,021✔
747
        uint64_t cur = get_file_pos(m_fd);
3,021✔
748
        seek(cur + size);
3,021✔
749
        return;
3,021✔
750
    }
3,021✔
751

752
    write_static(m_fd, data, size);
44,727✔
753
}
44,727✔
754

755
uint64_t File::get_file_pos(FileDesc fd)
756
{
832,668✔
757
#ifdef _WIN32
758
    LONG high_dword = 0;
759
    LARGE_INTEGER li;
760
    LARGE_INTEGER res;
761
    li.QuadPart = 0;
762
    bool ok = SetFilePointerEx(fd, li, &res, FILE_CURRENT);
763
    if (!ok)
764
        throw SystemError(GetLastError(), "SetFilePointer() failed");
765

766
    return uint64_t(res.QuadPart);
767
#else
768
    auto pos = lseek(fd, 0, SEEK_CUR);
832,668✔
769
    if (pos < 0) {
832,668✔
770
        throw SystemError(errno, "lseek() failed");
×
771
    }
×
772
    return uint64_t(pos);
832,668✔
773
#endif
832,668✔
774
}
832,668✔
775

776
File::SizeType File::get_size_static(const std::string& path)
777
{
1,353✔
778
    File f(path);
1,353✔
779
    return f.get_size();
1,353✔
780
}
1,353✔
781

782
File::SizeType File::get_size_static(FileDesc fd)
783
{
1,793,319✔
784
#ifdef _WIN32
785
    LARGE_INTEGER large_int;
786
    if (GetFileSizeEx(fd, &large_int)) {
787
        File::SizeType size;
788
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
789
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
790

791
        return size;
792
    }
793
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
794

795
#else // POSIX version
796

797
    struct stat statbuf;
1,793,319✔
798
    if (::fstat(fd, &statbuf) == 0) {
1,793,475✔
799
        SizeType size;
1,793,475✔
800
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,793,475✔
801
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
802

803
        return size;
1,793,475✔
804
    }
1,793,475✔
805
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
806

807
#endif
1,793,319✔
808
}
1,793,319✔
809

810
File::SizeType File::get_size() const
811
{
1,778,367✔
812
    REALM_ASSERT_RELEASE(is_attached());
1,778,367✔
813
    File::SizeType size = get_size_static(m_fd);
1,778,367✔
814

815
    if (m_encryption_key) {
1,778,367✔
816
        File::SizeType ret_size = encrypted_size_to_data_size(size);
14,919✔
817
        return ret_size;
14,919✔
818
    }
14,919✔
819
    else
1,763,448✔
820
        return size;
1,763,448✔
821
}
1,778,367✔
822

823

824
void File::resize(SizeType size)
825
{
283,227✔
826
    REALM_ASSERT_RELEASE(is_attached());
283,227✔
827

828
#ifdef _WIN32 // Windows version
829

830
    // Save file position
831
    SizeType p = get_file_pos(m_fd);
832

833
    if (m_encryption_key)
834
        size = data_size_to_encrypted_size(size);
835

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

841
    if (!SetEndOfFile(m_fd)) {
842
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
843
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
844
            std::string msg = get_last_error_msg("SetEndOfFile() failed: ", err);
845
            throw OutOfDiskSpace(msg);
846
        }
847
        throw SystemError(int(err), "SetEndOfFile() failed");
848
    }
849

850
    // Restore file position
851
    seek(p);
852

853
#else // POSIX version
854

855
    if (m_encryption_key)
283,227✔
856
        size = data_size_to_encrypted_size(size);
774✔
857

858
    off_t size2;
283,227✔
859
    if (int_cast_with_overflow_detect(size, size2))
283,227✔
860
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
861

862
    // POSIX specifies that introduced bytes read as zero. This is not
863
    // required by File::resize().
864
    if (::ftruncate(m_fd, size2) != 0) {
283,227✔
865
        int err = errno; // Eliminate any risk of clobbering
×
866
        auto msg = format_errno("ftruncate() failed: %1", err);
×
867
        if (err == ENOSPC || err == EDQUOT) {
×
868
            throw OutOfDiskSpace(msg);
×
869
        }
×
870
        throw SystemError(err, msg);
×
871
    }
×
872

873
#endif
283,227✔
874
}
283,227✔
875

876

877
void File::prealloc(size_t size)
878
{
170,946✔
879
    REALM_ASSERT_RELEASE(is_attached());
170,946✔
880

881
    if (size <= to_size_t(get_size())) {
170,946✔
882
        return;
38,883✔
883
    }
38,883✔
884

885
    size_t new_size = size;
132,063✔
886
    if (m_encryption_key) {
132,063✔
887
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
1,740✔
888
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
1,740✔
889
        if (new_size < size) {
1,740✔
890
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow: data_size_to_encrypted_size(" +
×
891
                                                           realm::util::to_string(size) +
×
892
                                                           ") == " + realm::util::to_string(new_size));
×
893
        }
×
894
    }
1,740✔
895

896
    auto manually_consume_space = [&]() {
132,063✔
897
        constexpr size_t chunk_size = 4096;
×
898
        int64_t original_size = get_size_static(m_fd); // raw size
×
899
        seek(original_size);
×
900
        size_t num_bytes = size_t(new_size - original_size);
×
901
        std::string zeros(chunk_size, '\0');
×
902
        while (num_bytes > 0) {
×
903
            size_t t = num_bytes > chunk_size ? chunk_size : num_bytes;
×
904
            write_static(m_fd, zeros.c_str(), t);
×
905
            num_bytes -= t;
×
906
        }
×
907
    };
×
908

909
    auto consume_space_interlocked = [&] {
132,063✔
910
#if REALM_ENABLE_ENCRYPTION
×
911
        if (m_encryption_key) {
×
912
            // We need to prevent concurrent calls to lseek from the encryption layer
913
            // while we're writing to the file to extend it. Otherwise an intervening
914
            // lseek may redirect the writing process, causing file corruption.
915
            UniqueLock lck(util::mapping_mutex);
×
916
            manually_consume_space();
×
917
        }
×
918
        else {
×
919
            manually_consume_space();
×
920
        }
×
921
#else
922
        manually_consume_space();
923
#endif
924
    };
×
925

926
#if REALM_HAVE_POSIX_FALLOCATE
70,986✔
927
    // Mostly Linux only
928
    if (!prealloc_if_supported(0, new_size)) {
70,986✔
929
        consume_space_interlocked();
930
    }
931
#else // Non-atomic fallback
932
#if REALM_PLATFORM_APPLE
61,077✔
933
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
934
    // ftruncate().
935

936
    struct stat statbuf;
61,077✔
937
    if (::fstat(m_fd, &statbuf) != 0) {
61,077✔
938
        int err = errno;
939
        throw SystemError(err, "fstat() inside prealloc() failed");
940
    }
941

942
    size_t allocated_size;
61,077✔
943
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
61,077✔
944
        throw RuntimeError(ErrorCodes::RangeError,
945
                           "Overflow on block conversion to size_t " + realm::util::to_string(statbuf.st_blocks));
946
    }
947
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
61,077✔
948
        throw RuntimeError(ErrorCodes::RangeError, "Overflow computing existing file space allocation blocks: " +
949
                                                       realm::util::to_string(allocated_size) +
950
                                                       " block size: " + realm::util::to_string(S_BLKSIZE));
951
    }
952

953
    // Only attempt to preallocate space if there's not already sufficient free space in the file.
954
    // APFS would fail with EINVAL if we attempted it, and HFS+ would preallocate extra space unnecessarily.
955
    // See <https://github.com/realm/realm-core/issues/3005> for details.
956
    if (new_size > allocated_size) {
61,077✔
957

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

980
    int ret = 0;
61,077✔
981

982
    do {
61,077✔
983
        ret = ftruncate(m_fd, new_size);
61,077✔
984
    } while (ret == -1 && errno == EINTR);
61,077!
985

986
    if (ret != 0) {
61,077✔
987
        int err = errno;
988
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
989
        // so this is some other runtime error and not OutOfDiskSpace
990
        throw SystemError(err, "ftruncate() inside prealloc() failed");
991
    }
992
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
993

994
    consume_space_interlocked();
995

996
#else
997
#error Please check if/how your OS supports file preallocation
998
#endif
999

1000
#endif // REALM_HAVE_POSIX_FALLOCATE
61,077✔
1001
}
132,063✔
1002

1003

1004
bool File::prealloc_if_supported(SizeType offset, size_t size)
1005
{
70,992✔
1006
    REALM_ASSERT_RELEASE(is_attached());
70,992✔
1007

1008
#if REALM_HAVE_POSIX_FALLOCATE
70,992✔
1009

1010
    REALM_ASSERT_RELEASE(is_prealloc_supported());
70,992✔
1011

1012
    if (size == 0) {
70,992✔
1013
        // calling posix_fallocate with a size of 0 will cause a return of EINVAL
1014
        // since this is a meaningless operation anyway, we just return early here
1015
        return true;
1016
    }
1017

1018
    // posix_fallocate() does not set errno, it returns the error (if any) or zero.
1019
    // It is also possible for it to be interrupted by EINTR according to some man pages (ex fedora 24)
1020
    int status;
70,992✔
1021
    do {
70,992✔
1022
        status = ::posix_fallocate(m_fd, offset, size);
70,992✔
1023
    } while (status == EINTR);
70,992✔
1024

1025
    if (REALM_LIKELY(status == 0)) {
70,992✔
1026
        return true;
70,992✔
1027
    }
70,992✔
1028

1029
    if (status == EINVAL || status == EPERM || status == EOPNOTSUPP) {
×
1030
        return false; // Retry with non-atomic version
1031
    }
1032

1033
    auto msg = format_errno("posix_fallocate() failed: %1", status);
1034
    if (status == ENOSPC || status == EDQUOT) {
×
1035
        throw OutOfDiskSpace(msg);
1036
    }
1037
    throw SystemError(status, msg);
1038

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

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

1047
#else
1048

1049
    static_cast<void>(offset);
1050
    static_cast<void>(size);
1051

1052
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1053

1054
#endif
1055
    return false;
×
1056
}
×
1057

1058

1059
bool File::is_prealloc_supported()
1060
{
70,992✔
1061
#if REALM_HAVE_POSIX_FALLOCATE
70,992✔
1062
    return true;
70,992✔
1063
#else
1064
    return false;
1065
#endif
1066
}
70,992✔
1067

1068
void File::seek(SizeType position)
1069
{
42,486✔
1070
    REALM_ASSERT_RELEASE(is_attached());
42,486✔
1071
    seek_static(m_fd, position);
42,486✔
1072
}
42,486✔
1073

1074
void File::seek_static(FileDesc fd, SizeType position)
1075
{
1,695,738✔
1076
#ifdef _WIN32 // Windows version
1077

1078
    LARGE_INTEGER large_int;
1079
    if (int_cast_with_overflow_detect(position, large_int.QuadPart))
1080
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1081

1082
    if (!SetFilePointerEx(fd, large_int, 0, FILE_BEGIN))
1083
        throw SystemError(GetLastError(), "SetFilePointerEx() failed");
1084

1085
#else // POSIX version
1086

1087
    off_t position2;
1,695,738✔
1088
    if (int_cast_with_overflow_detect(position, position2))
1,695,738✔
1089
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1090

1091
    if (0 <= ::lseek(fd, position2, SEEK_SET))
1,695,738✔
1092
        return;
1,695,738✔
1093
    throw SystemError(errno, "lseek() failed");
×
1094

1095
#endif
1,695,738✔
1096
}
1,695,738✔
1097

1098
// FIXME: The current implementation may not guarantee that data is
1099
// actually written to disk. POSIX is rather vague on what fsync() has
1100
// to do unless _POSIX_SYNCHRONIZED_IO is defined. See also
1101
// http://www.humboldt.co.uk/2009/03/fsync-across-platforms.html.
1102
void File::sync()
1103
{
795✔
1104
    REALM_ASSERT_RELEASE(is_attached());
795✔
1105

1106
#if defined _WIN32 // Windows version
1107

1108
    if (FlushFileBuffers(m_fd))
1109
        return;
1110
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1111

1112
#elif REALM_PLATFORM_APPLE
1113

1114
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
309✔
1115
        return;
309✔
1116
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1117

1118
#else // POSIX version
1119

1120
    if (::fsync(m_fd) == 0)
486✔
1121
        return;
486✔
1122
    throw SystemError(errno, "fsync() failed");
1123

1124
#endif
486✔
1125
}
795✔
1126

1127
void File::barrier()
1128
{
348✔
1129
#if REALM_PLATFORM_APPLE
174✔
1130
    if (::fcntl(m_fd, F_BARRIERFSYNC) == 0)
174✔
1131
        return;
174✔
1132
        // If fcntl fails, we fallback to full sync.
1133
        // This is known to occur on exFAT which does not support F_BARRIERSYNC.
1134
#endif
1135
    sync();
174✔
1136
}
174✔
1137

1138
#ifndef _WIN32
1139
// little helper
1140
static void _unlock(int m_fd)
1141
{
3,900,576✔
1142
    int r;
3,900,576✔
1143
    do {
3,900,576✔
1144
        r = flock(m_fd, LOCK_UN);
3,900,576✔
1145
    } while (r != 0 && errno == EINTR);
3,900,576!
1146
    if (r) {
3,900,576✔
1147
        throw SystemError(errno, "File::unlock() has failed");
×
1148
    }
×
1149
}
3,900,576✔
1150
#endif
1151

1152
bool File::rw_lock(bool exclusive, bool non_blocking)
1153
{
338,019✔
1154
    // exclusive blocking rw locks not implemented for emulation
1155
    REALM_ASSERT(!exclusive || non_blocking);
338,019✔
1156

1157
#ifndef REALM_FILELOCK_EMULATION
338,019✔
1158
    return lock(exclusive, non_blocking);
338,019✔
1159
#else
1160
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1161

1162
    // First obtain an exclusive lock on the file proper
1163
    int operation = LOCK_EX;
1164
    if (non_blocking)
1165
        operation |= LOCK_NB;
1166
    int status;
1167
    do {
1168
        status = flock(m_fd, operation);
1169
    } while (status != 0 && errno == EINTR);
1170
    if (status != 0 && errno == EWOULDBLOCK)
1171
        return false;
1172
    if (status != 0)
1173
        throw SystemError(errno, "flock() failed");
1174
    m_has_exclusive_lock = true;
1175

1176
    // Every path through this function except for successfully acquiring an
1177
    // exclusive lock needs to release the flock() before returning.
1178
    UnlockGuard ulg(*this);
1179

1180
    // now use a named pipe to emulate locking in conjunction with using exclusive lock
1181
    // on the file proper.
1182
    // exclusively locked: we can't sucessfully write to the pipe.
1183
    //                     AND we continue to hold the exclusive lock.
1184
    //                     (unlock must lift the exclusive lock).
1185
    // shared locked: we CAN succesfully write to the pipe. We open the pipe for reading
1186
    //                before releasing the exclusive lock.
1187
    //                (unlock must close the pipe for reading)
1188
    REALM_ASSERT_RELEASE(m_pipe_fd == -1);
1189
    if (m_fifo_path.empty())
1190
        m_fifo_path = m_path + ".fifo";
1191

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

1196
    // Optimistically try to open the fifo. This may fail due to the fifo not
1197
    // existing, but since it usually exists this is faster than trying to create
1198
    // it first.
1199
    int fd = ::open(m_fifo_path.c_str(), mode);
1200
    if (fd == -1) {
1201
        int err = errno;
1202
        if (exclusive) {
1203
            if (err == ENOENT || err == ENXIO) {
1204
                // If the fifo either doesn't exist or there's no readers with the
1205
                // other end of the pipe open (ENXIO) then we have an exclusive lock
1206
                // and are done.
1207
                ulg.release();
1208
                return true;
1209
            }
1210

1211
            // Otherwise we got an unexpected error
1212
            throw std::system_error(err, std::system_category(), "opening lock fifo for writing failed");
1213
        }
1214

1215
        if (err == ENOENT) {
1216
            // The fifo doesn't exist and we're opening in shared mode, so we
1217
            // need to create it.
1218
            if (!m_fifo_dir_path.empty())
1219
                try_make_dir(m_fifo_dir_path);
1220
            status = mkfifo(m_fifo_path.c_str(), 0666);
1221
            if (status != 0)
1222
                throw std::system_error(errno, std::system_category(), "creating lock fifo for reading failed");
1223

1224
            // Try again to open the fifo now that it exists
1225
            fd = ::open(m_fifo_path.c_str(), mode);
1226
            err = errno;
1227
        }
1228

1229
        if (fd == -1)
1230
            throw std::system_error(err, std::system_category(), "opening lock fifo for reading failed");
1231
    }
1232

1233
    // We successfully opened the pipe. If we're trying to acquire an exclusive
1234
    // lock that means there's a reader (aka a shared lock) and we've failed.
1235
    // Release the exclusive lock and back out.
1236
    if (exclusive) {
1237
        ::close(fd);
1238
        return false;
1239
    }
1240

1241
    // We're in shared mode, so opening the fifo means we've successfully acquired
1242
    // a shared lock and are done.
1243
    ulg.release();
1244
    rw_unlock();
1245
    m_pipe_fd = fd;
1246
    return true;
1247
#endif // REALM_FILELOCK_EMULATION
1248
}
338,019✔
1249

1250
bool File::lock(bool exclusive, bool non_blocking)
1251
{
4,011,477✔
1252
    REALM_ASSERT_RELEASE(is_attached());
4,011,477✔
1253
    REALM_ASSERT_RELEASE(!m_have_lock);
4,011,477✔
1254

1255
#ifdef _WIN32 // Windows version
1256

1257
    // Under Windows a file lock must be explicitely released before
1258
    // the file is closed. It will eventually be released by the
1259
    // system, but there is no guarantees on the timing.
1260

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

1312
void File::unlock() noexcept
1313
{
3,900,723✔
1314
    if (!m_have_lock)
3,900,723✔
1315
        return;
×
1316

1317
#ifdef _WIN32 // Windows version
1318
    OVERLAPPED overlapped;
1319
    overlapped.hEvent = 0;
1320
    overlapped.OffsetHigh = 0;
1321
    overlapped.Offset = 0;
1322
    overlapped.Pointer = 0;
1323
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1324
    REALM_ASSERT_RELEASE(r);
1325
#else
1326
    _unlock(m_fd);
3,900,723✔
1327
#endif
3,900,723✔
1328
    m_have_lock = false;
3,900,723✔
1329
}
3,900,723✔
1330

1331
void File::rw_unlock() noexcept
1332
{
226,440✔
1333
#ifndef REALM_FILELOCK_EMULATION
226,440✔
1334
    unlock();
226,440✔
1335
#else
1336
    // Coming here with an exclusive lock, we must release that lock.
1337
    // Coming here with a shared lock, we must close the pipe that we have opened for reading.
1338
    //   - we have to do that under the protection of a proper exclusive lock to serialize
1339
    //     with anybody trying to obtain a lock concurrently.
1340
    if (has_shared_lock()) {
1341
        // shared lock. We need to reacquire the exclusive lock on the file
1342
        int status;
1343
        do {
1344
            status = flock(m_fd, LOCK_EX);
1345
        } while (status != 0 && errno == EINTR);
1346
        REALM_ASSERT(status == 0);
1347
        // close the pipe (== release the shared lock)
1348
        ::close(m_pipe_fd);
1349
        m_pipe_fd = -1;
1350
    }
1351
    else {
1352
        REALM_ASSERT(m_has_exclusive_lock);
1353
    }
1354
    _unlock(m_fd);
1355
    m_has_exclusive_lock = false;
1356
#endif // REALM_FILELOCK_EMULATION
1357
}
226,440✔
1358

1359
void* File::map(AccessMode a, size_t size, int /*map_flags*/, size_t offset) const
1360
{
6✔
1361
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset);
6✔
1362
}
6✔
1363

1364
void* File::map_fixed(AccessMode a, void* address, size_t size, int /* map_flags */, size_t offset) const
1365
{
×
1366
    if (m_encryption_key.get()) {
×
1367
        // encryption enabled - this is not supported - see explanation in alloc_slab.cpp
1368
        REALM_ASSERT(false);
×
1369
    }
×
1370
#ifdef _WIN32
1371
    // windows, no encryption - this is not supported, see explanation in alloc_slab.cpp,
1372
    // above the method 'update_reader_view()'
1373
    REALM_ASSERT(false);
1374
    return nullptr;
1375
#else
1376
    // unencrypted - mmap part of already reserved space
1377
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, m_encryption_key.get());
×
1378
#endif
×
1379
}
×
1380

1381
void* File::map_reserve(AccessMode a, size_t size, size_t offset) const
1382
{
×
1383
    static_cast<void>(a); // FIXME: Consider removing this argument
×
1384
    return realm::util::mmap_reserve(m_fd, size, offset);
×
1385
}
×
1386

1387
#if REALM_ENABLE_ENCRYPTION
1388
void* File::map(AccessMode a, size_t size, EncryptedFileMapping*& mapping, int /*map_flags*/, size_t offset) const
1389
{
1,397,352✔
1390
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
1,397,352✔
1391
}
1,397,352✔
1392

1393
void* File::map_fixed(AccessMode a, void* address, size_t size, EncryptedFileMapping* mapping, int /* map_flags */,
1394
                      size_t offset) const
1395
{
×
1396
    if (m_encryption_key.get()) {
×
1397
        // encryption enabled - we shouldn't be here, all memory was allocated by reserve
1398
        REALM_ASSERT_RELEASE(false);
×
1399
    }
×
1400
#ifndef _WIN32
×
1401
    // no encryption. On Unixes, map relevant part of reserved virtual address range
1402
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, nullptr, mapping);
×
1403
#else
1404
    // no encryption - unsupported on windows
1405
    REALM_ASSERT(false);
1406
    return nullptr;
1407
#endif
1408
}
×
1409

1410
void* File::map_reserve(AccessMode a, size_t size, size_t offset, EncryptedFileMapping*& mapping) const
1411
{
×
1412
    if (m_encryption_key.get()) {
×
1413
        // encrypted file - just mmap it, the encryption layer handles if the mapping extends beyond eof
1414
        return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
×
1415
    }
×
1416
#ifndef _WIN32
×
1417
    // not encrypted, do a proper reservation on Unixes'
1418
    return realm::util::mmap_reserve({m_fd, m_path, a, nullptr}, size, offset, mapping);
×
1419
#else
1420
    // on windows, this is a no-op
1421
    return nullptr;
1422
#endif
1423
}
×
1424

1425
#endif // REALM_ENABLE_ENCRYPTION
1426

1427
void File::unmap(void* addr, size_t size) noexcept
1428
{
×
1429
    realm::util::munmap(addr, size);
×
1430
}
×
1431

1432

1433
void* File::remap(void* old_addr, size_t old_size, AccessMode a, size_t new_size, int /*map_flags*/,
1434
                  size_t file_offset) const
1435
{
×
1436
    return realm::util::mremap({m_fd, m_path, a, m_encryption_key.get()}, file_offset, old_addr, old_size, new_size);
×
1437
}
×
1438

1439

1440
void File::sync_map(FileDesc fd, void* addr, size_t size)
1441
{
1,388,169✔
1442
    realm::util::msync(fd, addr, size);
1,388,169✔
1443
}
1,388,169✔
1444

1445

1446
bool File::exists(const std::string& path)
1447
{
765,525✔
1448
#if REALM_HAVE_STD_FILESYSTEM
1449
    return std::filesystem::exists(u8path(path));
1450
#else // POSIX
1451
    if (::access(path.c_str(), F_OK) == 0)
765,525✔
1452
        return true;
30,834✔
1453
    int err = errno; // Eliminate any risk of clobbering
734,691✔
1454
    switch (err) {
734,691✔
1455
        case EACCES:
✔
1456
        case ENOENT:
734,664✔
1457
        case ENOTDIR:
734,664✔
1458
            return false;
734,664✔
1459
    }
734,691✔
1460
    throw SystemError(err, "access() failed");
24✔
1461
#endif
734,691✔
1462
}
734,691✔
1463

1464

1465
bool File::is_dir(const std::string& path)
1466
{
227,814✔
1467
#if REALM_HAVE_STD_FILESYSTEM
1468
    return std::filesystem::is_directory(u8path(path));
1469
#elif !defined(_WIN32)
1470
    struct stat statbuf;
1471
    if (::stat(path.c_str(), &statbuf) == 0)
227,814✔
1472
        return S_ISDIR(statbuf.st_mode);
220,881✔
1473
    int err = errno; // Eliminate any risk of clobbering
6,933✔
1474
    switch (err) {
6,933✔
1475
        case EACCES:
✔
1476
        case ENOENT:
6,933✔
1477
        case ENOTDIR:
6,933✔
1478
            return false;
6,933✔
1479
    }
6,933✔
1480
    throw SystemError(err, "stat() failed");
×
1481
#else
1482
    static_cast<void>(path);
1483
    throw NotImplemented();
1484
#endif
1485
}
6,933✔
1486

1487

1488
void File::remove(const std::string& path)
1489
{
171,204✔
1490
    if (try_remove(path))
171,204✔
1491
        return;
171,189✔
1492
    int err = ENOENT;
15✔
1493
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
15✔
1494
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
15✔
1495
}
171,204✔
1496

1497

1498
bool File::try_remove(const std::string& path)
1499
{
232,866✔
1500
#if REALM_HAVE_STD_FILESYSTEM
1501
    std::error_code error;
1502
    bool result = std::filesystem::remove(u8path(path), error);
1503
    throwIfFileError(error, path);
1504
    return result;
1505
#else // POSIX
1506
    if (::unlink(path.c_str()) == 0)
232,866✔
1507
        return true;
193,335✔
1508

1509
    int err = errno; // Eliminate any risk of clobbering
39,531✔
1510
    if (err == ENOENT)
39,531✔
1511
        return false;
39,489✔
1512

1513
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
42✔
1514
    switch (err) {
42✔
1515
        case EACCES:
✔
1516
        case EROFS:
✔
1517
        case ETXTBSY:
✔
1518
        case EBUSY:
✔
1519
        case EPERM:
21✔
1520
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
21✔
1521
        case ENOENT:
✔
1522
            return false;
×
1523
        default:
24✔
1524
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
24✔
1525
    }
42✔
1526
#endif
42✔
1527
}
42✔
1528

1529

1530
void File::move(const std::string& old_path, const std::string& new_path)
1531
{
300✔
1532
#if REALM_HAVE_STD_FILESYSTEM
1533
    std::error_code error;
1534
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1535

1536
    if (error == std::errc::no_such_file_or_directory) {
1537
        throw FileAccessError(ErrorCodes::FileNotFound, error.message(), old_path);
1538
    }
1539
    throwIfFileError(error, old_path);
1540
#else
1541
    int r = rename(old_path.c_str(), new_path.c_str());
300✔
1542
    if (r == 0)
300✔
1543
        return;
300✔
1544
    int err = errno; // Eliminate any risk of clobbering
×
1545
    std::string msg = format_errno("Failed to rename file from '%2' to '%3': %1", err, old_path, new_path);
×
1546
    switch (err) {
×
1547
        case EACCES:
×
1548
        case EROFS:
×
1549
        case ETXTBSY:
×
1550
        case EBUSY:
×
1551
        case EPERM:
×
1552
        case EEXIST:
×
1553
        case ENOTEMPTY:
×
1554
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, old_path, err);
×
1555
        case ENOENT:
×
1556
            throw FileAccessError(ErrorCodes::FileNotFound, msg, old_path, err);
×
1557
        default:
×
1558
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, old_path, err);
×
1559
    }
×
1560
#endif
×
1561
}
×
1562

1563

1564
bool File::copy(const std::string& origin_path, const std::string& target_path, bool overwrite_existing)
1565
{
390✔
1566
#if REALM_HAVE_STD_FILESYSTEM
1567
    auto options = overwrite_existing ? std::filesystem::copy_options::overwrite_existing
1568
                                      : std::filesystem::copy_options::skip_existing;
1569
    return std::filesystem::copy_file(u8path(origin_path), u8path(target_path), options); // Throws
1570
#else
1571
#if REALM_PLATFORM_APPLE
198✔
1572
    // Try to use clonefile and fall back to manual copying if it fails
1573
    if (clonefile(origin_path.c_str(), target_path.c_str(), 0) == 0) {
198✔
1574
        return true;
180✔
1575
    }
180✔
1576
    if (errno == EEXIST && !overwrite_existing) {
18✔
1577
        return false;
6✔
1578
    }
6✔
1579
#endif
12✔
1580

1581
    File origin_file{origin_path, mode_Read}; // Throws
204✔
1582
    File target_file;
204✔
1583
    bool did_create = false;
204✔
1584
    target_file.open(target_path, did_create); // Throws
204✔
1585
    if (!did_create && !overwrite_existing) {
204✔
1586
        return false;
6✔
1587
    }
6✔
1588

1589
    size_t buffer_size = 4096;
198✔
1590
    auto buffer = std::make_unique<char[]>(buffer_size); // Throws
198✔
1591
    for (;;) {
576✔
1592
        size_t n = origin_file.read(buffer.get(), buffer_size); // Throws
576✔
1593
        target_file.write(buffer.get(), n);                     // Throws
576✔
1594
        if (n < buffer_size)
576✔
1595
            break;
198✔
1596
    }
576✔
1597

1598
    return true;
198✔
1599
#endif
204✔
1600
}
204✔
1601

1602

1603
bool File::compare(const std::string& path_1, const std::string& path_2)
1604
{
×
1605
    File file_1{path_1}; // Throws
×
1606
    File file_2{path_2}; // Throws
×
1607
    size_t buffer_size = 4096;
×
1608
    std::unique_ptr<char[]> buffer_1 = std::make_unique<char[]>(buffer_size); // Throws
×
1609
    std::unique_ptr<char[]> buffer_2 = std::make_unique<char[]>(buffer_size); // Throws
×
1610
    for (;;) {
×
1611
        size_t n_1 = file_1.read(buffer_1.get(), buffer_size); // Throws
×
1612
        size_t n_2 = file_2.read(buffer_2.get(), buffer_size); // Throws
×
1613
        if (n_1 != n_2)
×
1614
            return false;
×
1615
        if (!std::equal(buffer_1.get(), buffer_1.get() + n_1, buffer_2.get()))
×
1616
            return false;
×
1617
        if (n_1 < buffer_size)
×
1618
            break;
×
1619
    }
×
1620
    return true;
×
1621
}
×
1622

1623
bool File::is_same_file_static(FileDesc f1, FileDesc f2, const std::string& path1, const std::string& path2)
1624
{
24✔
1625
    return get_unique_id(f1, path1) == get_unique_id(f2, path2);
24✔
1626
}
24✔
1627

1628
bool File::is_same_file(const File& f) const
1629
{
24✔
1630
    REALM_ASSERT_RELEASE(is_attached());
24✔
1631
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1632
    return is_same_file_static(m_fd, f.m_fd, m_path, f.m_path);
24✔
1633
}
24✔
1634

1635
FileDesc File::dup_file_desc(FileDesc fd)
1636
{
8,658✔
1637
    FileDesc fd_duped;
8,658✔
1638
#ifdef _WIN32
1639
    if (!DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &fd_duped, 0, FALSE, DUPLICATE_SAME_ACCESS))
1640
        throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
1641
#else
1642
    fd_duped = dup(fd);
8,658✔
1643

1644
    if (fd_duped == -1) {
8,658✔
1645
        int err = errno; // Eliminate any risk of clobbering
×
1646
        throw std::system_error(err, std::system_category(), "dup() failed");
×
1647
    }
×
1648
#endif // conditonal on _WIN32
8,658✔
1649
    return fd_duped;
8,658✔
1650
}
8,658✔
1651

1652
File::UniqueID File::get_unique_id()
1653
{
100,269✔
1654
    REALM_ASSERT_RELEASE(is_attached());
100,269✔
1655
    File::UniqueID uid = File::get_unique_id(m_fd, m_path);
100,269✔
1656
    if (!m_cached_unique_id) {
100,269✔
1657
        m_cached_unique_id = std::make_optional(uid);
100,257✔
1658
    }
100,257✔
1659
    if (m_cached_unique_id != uid) {
100,269✔
1660
        throw FileAccessError(ErrorCodes::FileOperationFailed,
×
1661
                              util::format("The unique id of this Realm file has changed unexpectedly, this could be "
×
1662
                                           "due to modifications by an external process '%1'",
×
1663
                                           m_path),
×
1664
                              m_path);
×
1665
    }
×
1666
    return uid;
100,269✔
1667
}
100,269✔
1668

1669
FileDesc File::get_descriptor() const
1670
{
100,479✔
1671
    return m_fd;
100,479✔
1672
}
100,479✔
1673

1674
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1675
{
228✔
1676
#ifdef _WIN32 // Windows version
1677
    // CreateFile2 with creationDisposition OPEN_EXISTING will return a file handle only if the file exists
1678
    // otherwise it will raise ERROR_FILE_NOT_FOUND. This call will never create a new file.
1679
    WindowsFileHandleHolder fileHandle(::CreateFile2(u8path(path).c_str(), FILE_READ_ATTRIBUTES,
1680
                                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1681
                                                     OPEN_EXISTING, nullptr));
1682

1683
    if (fileHandle == INVALID_HANDLE_VALUE) {
1684
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1685
            return none;
1686
        }
1687
        throw SystemError(GetLastError(), "CreateFileW failed");
1688
    }
1689

1690
    return get_unique_id(fileHandle, path);
1691
#else // POSIX version
1692
    struct stat statbuf;
228✔
1693
    if (::stat(path.c_str(), &statbuf) == 0) {
228✔
1694
        if (statbuf.st_size == 0) {
222✔
1695
            // On exFAT systems the inode and device are not populated correctly until the file
1696
            // has been allocated some space. The uid can also be reassigned if the file is
1697
            // truncated to zero. This has led to bugs where a unique id returned here was
1698
            // reused by different files. The following check ensures that this is not
1699
            // happening anywhere else in future code.
1700
            return none;
×
1701
        }
×
1702
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
222✔
1703
    }
222✔
1704
    int err = errno; // Eliminate any risk of clobbering
6✔
1705
    // File doesn't exist
1706
    if (err == ENOENT)
6✔
1707
        return none;
6✔
1708
    throw SystemError(err, format_errno("fstat() failed: %1 for '%2'", err, path));
×
1709
#endif
6✔
1710
}
6✔
1711

1712
File::UniqueID File::get_unique_id(FileDesc file, const std::string& debug_path)
1713
{
115,245✔
1714
#ifdef _WIN32 // Windows version
1715
    REALM_ASSERT(file != nullptr);
1716
    File::UniqueID ret;
1717
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1718
        throw std::system_error(GetLastError(), std::system_category(),
1719
                                util::format("GetFileInformationByHandleEx() failed for '%1'", debug_path));
1720
    }
1721

1722
    return ret;
1723
#else // POSIX version
1724
    REALM_ASSERT(file >= 0);
115,245✔
1725
    struct stat statbuf;
115,245✔
1726
    if (::fstat(file, &statbuf) == 0) {
115,245✔
1727
        // On exFAT systems the inode and device are not populated correctly until the file
1728
        // has been allocated some space. The uid can also be reassigned if the file is
1729
        // truncated to zero. This has led to bugs where a unique id returned here was
1730
        // reused by different files. The following check ensures that this is not
1731
        // happening anywhere else in future code.
1732
        if (statbuf.st_size == 0) {
115,245✔
1733
            throw FileAccessError(
×
1734
                ErrorCodes::FileOperationFailed,
×
1735
                util::format("Attempt to get unique id on an empty file. This could be due to an external "
×
1736
                             "process modifying Realm files. '%1'",
×
1737
                             debug_path),
×
1738
                debug_path);
×
1739
        }
×
1740
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
115,245✔
1741
    }
115,245✔
1742
    throw std::system_error(errno, std::system_category(), util::format("fstat() failed for '%1'", debug_path));
×
1743
#endif
115,245✔
1744
}
115,245✔
1745

1746
std::string File::get_path() const
1747
{
3,417✔
1748
    return m_path;
3,417✔
1749
}
3,417✔
1750

1751
std::string File::resolve(const std::string& path, const std::string& base_dir)
1752
{
225,927✔
1753
#if REALM_HAVE_STD_FILESYSTEM
1754
    return (u8path(base_dir) / u8path(path)).lexically_normal().u8string();
1755
#else
1756
    char dir_sep = '/';
225,927✔
1757
    std::string path_2 = path;
225,927✔
1758
    std::string base_dir_2 = base_dir;
225,927✔
1759
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
225,927✔
1760
    if (is_absolute)
225,927✔
1761
        return path_2;
6✔
1762
    if (path_2.empty())
225,921✔
1763
        path_2 = ".";
6✔
1764
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
225,921✔
1765
        base_dir_2.push_back(dir_sep);
224,370✔
1766
    /*
1767
    // Abbreviate
1768
    for (;;) {
1769
        if (base_dir_2.empty()) {
1770
            if (path_2.empty())
1771
                return "./";
1772
            return path_2;
1773
        }
1774
        if (path_2 == ".") {
1775
            remove_trailing_dir_seps(base_dir_2);
1776
            return base_dir_2;
1777
        }
1778
        if (has_prefix(path_2, "./")) {
1779
            remove_trailing_dir_seps(base_dir_2);
1780
            // drop dot
1781
            // transfer slashes
1782
        }
1783

1784
        if (path_2.size() < 2 || path_2[1] != '.')
1785
            break;
1786
        if (path_2.size())
1787
    }
1788
    */
1789
    return base_dir_2 + path_2;
225,921✔
1790
#endif
225,927✔
1791
}
225,927✔
1792

1793
std::string File::parent_dir(const std::string& path)
1794
{
90✔
1795
#if REALM_HAVE_STD_FILESYSTEM
1796
    return u8path(path).parent_path().u8string(); // Throws
1797
#else
1798
    auto is_sep = [](char c) -> bool {
1,215✔
1799
        return c == '/' || c == '\\';
1,215✔
1800
    };
1,215✔
1801
    auto it = std::find_if(path.rbegin(), path.rend(), is_sep);
90✔
1802
    while (it != path.rend() && is_sep(*it))
192✔
1803
        ++it;
102✔
1804
    return path.substr(0, path.rend() - it);
90✔
1805
#endif
90✔
1806
}
90✔
1807

1808
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1809
{
6✔
1810
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1811
}
6✔
1812

1813

1814
void File::set_encryption_key(const char* key)
1815
{
101,661✔
1816
#if REALM_ENABLE_ENCRYPTION
101,661✔
1817
    if (key) {
101,661✔
1818
        auto buffer = std::make_unique<char[]>(64);
4,089✔
1819
        memcpy(buffer.get(), key, 64);
4,089✔
1820
        m_encryption_key = std::move(buffer);
4,089✔
1821
    }
4,089✔
1822
    else {
97,572✔
1823
        m_encryption_key.reset();
97,572✔
1824
    }
97,572✔
1825
#else
1826
    if (key) {
1827
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1828
    }
1829
#endif
1830
}
101,661✔
1831

1832
const char* File::get_encryption_key() const
1833
{
123,888✔
1834
    return m_encryption_key.get();
123,888✔
1835
}
123,888✔
1836

1837
void File::MapBase::map(const File& f, AccessMode a, size_t size, int map_flags, size_t offset,
1838
                        util::WriteObserver* observer)
1839
{
1,397,334✔
1840
    REALM_ASSERT(!m_addr);
1,397,334✔
1841
#if REALM_ENABLE_ENCRYPTION
1,397,334✔
1842
    m_addr = f.map(a, size, m_encrypted_mapping, map_flags, offset);
1,397,334✔
1843
    if (observer && m_encrypted_mapping) {
1,397,334✔
1844
        m_encrypted_mapping->set_observer(observer);
4,119✔
1845
    }
4,119✔
1846
#else
1847
    m_addr = f.map(a, size, map_flags, offset);
1848
    static_cast<void>(observer);
1849
#endif
1850
    m_size = m_reservation_size = size;
1,397,334✔
1851
    m_fd = f.m_fd;
1,397,334✔
1852
    m_offset = offset;
1,397,334✔
1853
    m_access_mode = a;
1,397,334✔
1854
}
1,397,334✔
1855

1856

1857
void File::MapBase::unmap() noexcept
1858
{
3,291,855✔
1859
    if (!m_addr)
3,291,855✔
1860
        return;
1,794,174✔
1861
    REALM_ASSERT(m_reservation_size);
1,497,681✔
1862
#if REALM_ENABLE_ENCRYPTION
1,497,681✔
1863
    if (m_encrypted_mapping) {
1,497,681✔
1864
        m_encrypted_mapping = nullptr;
14,922✔
1865
        util::remove_encrypted_mapping(m_addr, m_size);
14,922✔
1866
    }
14,922✔
1867
#endif
1,497,681✔
1868
    ::munmap(m_addr, m_reservation_size);
1,497,681✔
1869
    m_addr = nullptr;
1,497,681✔
1870
    m_size = 0;
1,497,681✔
1871
    m_reservation_size = 0;
1,497,681✔
1872
}
1,497,681✔
1873

1874
void File::MapBase::remap(const File& f, AccessMode a, size_t size, int map_flags)
1875
{
×
1876
    REALM_ASSERT(m_addr);
×
1877
    m_addr = f.remap(m_addr, m_size, a, size, map_flags);
×
1878
    m_size = m_reservation_size = size;
×
1879
}
×
1880

1881
bool File::MapBase::try_reserve(const File& file, AccessMode a, size_t size, size_t offset,
1882
                                util::WriteObserver* observer)
1883
{
100,413✔
1884
#ifdef _WIN32
1885
    static_cast<void>(observer);
1886
    // unsupported for now
1887
    return false;
1888
#else
1889
    void* addr = ::mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
100,413✔
1890
    if (addr == MAP_FAILED)
100,413✔
1891
        return false;
×
1892
    m_addr = addr;
100,413✔
1893
    REALM_ASSERT(m_size == 0);
100,413✔
1894
    m_access_mode = a;
100,413✔
1895
    m_reservation_size = size;
100,413✔
1896
    m_fd = file.get_descriptor();
100,413✔
1897
    m_offset = offset;
100,413✔
1898
#if REALM_ENABLE_ENCRYPTION
100,413✔
1899
    if (file.m_encryption_key) {
100,413✔
1900
        m_encrypted_mapping =
3,387✔
1901
            util::reserve_mapping(addr, {m_fd, file.get_path(), a, file.m_encryption_key.get()}, offset);
3,387✔
1902
        if (observer) {
3,387✔
1903
            m_encrypted_mapping->set_observer(observer);
3,381✔
1904
        }
3,381✔
1905
    }
3,387✔
1906
#else
1907
    static_cast<void>(observer);
1908
#endif
1909
#endif
100,413✔
1910
    return true;
100,413✔
1911
}
100,413✔
1912

1913
bool File::MapBase::try_extend_to(size_t size) noexcept
1914
{
120,366✔
1915
    if (size > m_reservation_size) {
120,366✔
1916
        return false;
×
1917
    }
×
1918
    // return false;
1919
#ifndef _WIN32
120,366✔
1920
    char* extension_start_addr = (char*)m_addr + m_size;
120,366✔
1921
    size_t extension_size = size - m_size;
120,366✔
1922
    size_t extension_start_offset = m_offset + m_size;
120,366✔
1923
#if REALM_ENABLE_ENCRYPTION
120,366✔
1924
    if (m_encrypted_mapping) {
120,366✔
1925
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
4,026✔
1926
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
4,026✔
1927
        if (got_addr == MAP_FAILED)
4,026✔
1928
            return false;
×
1929
        REALM_ASSERT(got_addr == extension_start_addr);
4,026✔
1930
        util::extend_encrypted_mapping(m_encrypted_mapping, m_addr, m_offset, m_size, size);
4,026✔
1931
        m_size = size;
4,026✔
1932
        return true;
4,026✔
1933
    }
4,026✔
1934
#endif
116,340✔
1935
    try {
116,340✔
1936
        void* got_addr = util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode,
116,340✔
1937
                                          extension_start_offset, nullptr);
116,340✔
1938
        if (got_addr == extension_start_addr) {
116,340✔
1939
            m_size = size;
116,256✔
1940
            return true;
116,256✔
1941
        }
116,256✔
1942
    }
116,340✔
1943
    catch (...) {
116,340✔
1944
        return false;
18✔
1945
    }
18✔
1946
#endif
×
1947
    return false;
66✔
1948
}
116,340✔
1949

1950
void File::MapBase::sync()
1951
{
1,388,214✔
1952
    REALM_ASSERT(m_addr);
1,388,214✔
1953

1954
    File::sync_map(m_fd, m_addr, m_size);
1,388,214✔
1955
}
1,388,214✔
1956

1957
void File::MapBase::flush()
1958
{
1,358,874✔
1959
    REALM_ASSERT(m_addr);
1,358,874✔
1960
#if REALM_ENABLE_ENCRYPTION
1,358,874✔
1961
    if (m_encrypted_mapping) {
1,358,874✔
1962
        realm::util::encryption_flush(m_encrypted_mapping);
4,869✔
1963
    }
4,869✔
1964
#endif
1,358,874✔
1965
}
1,358,874✔
1966

1967
std::time_t File::last_write_time(const std::string& path)
1968
{
891✔
1969
#if REALM_HAVE_STD_FILESYSTEM
1970
    auto time = std::filesystem::last_write_time(u8path(path));
1971

1972
    using namespace std::chrono;
1973
#if __cplusplus >= 202002L
1974
    auto system_time = clock_cast<system_clock>(time);
1975
#else
1976
    auto system_time =
1977
        time_point_cast<system_clock::duration>(time - decltype(time)::clock::now() + system_clock::now());
1978
#endif
1979
    return system_clock::to_time_t(system_time);
1980
#else
1981
    struct stat statbuf;
891✔
1982
    if (::stat(path.c_str(), &statbuf) != 0) {
891✔
1983
        throw SystemError(errno, "stat() failed");
×
1984
    }
×
1985
    return statbuf.st_mtime;
891✔
1986
#endif
891✔
1987
}
891✔
1988

1989
File::SizeType File::get_free_space(const std::string& path)
1990
{
144✔
1991
#if REALM_HAVE_STD_FILESYSTEM
1992
    return std::filesystem::space(u8path(path)).available;
1993
#else
1994
    struct statvfs stat;
144✔
1995
    if (statvfs(path.c_str(), &stat) != 0) {
144✔
1996
        throw SystemError(errno, "statvfs() failed");
×
1997
    }
×
1998
    return SizeType(stat.f_bavail) * stat.f_bsize;
144✔
1999
#endif
144✔
2000
}
144✔
2001

2002
#if REALM_HAVE_STD_FILESYSTEM
2003

2004
DirScanner::DirScanner(const std::string& path, bool allow_missing)
2005
{
2006
    try {
2007
        m_iterator = std::filesystem::directory_iterator(u8path(path));
2008
    }
2009
    catch (const std::filesystem::filesystem_error& e) {
2010
        if (e.code() != std::errc::no_such_file_or_directory || !allow_missing)
2011
            throw;
2012
    }
2013
}
2014

2015
DirScanner::~DirScanner() = default;
2016

2017
bool DirScanner::next(std::string& name)
2018
{
2019
    const std::filesystem::directory_iterator end;
2020
    if (m_iterator == end)
2021
        return false;
2022
    name = m_iterator->path().filename().u8string();
2023
    m_iterator++;
2024
    return true;
2025
}
2026

2027
#elif !defined(_WIN32)
2028

2029
DirScanner::DirScanner(const std::string& path, bool allow_missing)
2030
{
123,636✔
2031
    m_dirp = opendir(path.c_str());
123,636✔
2032
    if (!m_dirp) {
123,636✔
2033
        int err = errno; // Eliminate any risk of clobbering
7,155✔
2034
        if (allow_missing && err == ENOENT)
7,155✔
2035
            return;
7,155✔
2036

2037
        std::string msg = format_errno("opendir() failed: %1", err);
×
2038
        switch (err) {
×
2039
            case EACCES:
×
2040
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
2041
            case ENOENT:
×
2042
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
2043
            default:
×
2044
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
2045
        }
×
2046
    }
×
2047
}
123,636✔
2048

2049
DirScanner::~DirScanner() noexcept
2050
{
123,633✔
2051
    if (m_dirp) {
123,633✔
2052
        int r = closedir(m_dirp);
116,481✔
2053
        REALM_ASSERT_RELEASE(r == 0);
116,481✔
2054
    }
116,481✔
2055
}
123,633✔
2056

2057
bool DirScanner::next(std::string& name)
2058
{
340,140✔
2059
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
2060
    !defined(__EMSCRIPTEN__)
2061
#error "readdir() is not known to be thread-safe on this platform"
2062
#endif
2063

2064
    if (!m_dirp)
340,140✔
2065
        return false;
7,152✔
2066

2067
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
2068
// in 32-bits.
2069
#if REALM_HAVE_READDIR64
113,763✔
2070
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
227,685✔
2071
#else
2072
#define REALM_READDIR(...) readdir(__VA_ARGS__)
338,265✔
2073
#endif
219,225✔
2074

2075
    for (;;) {
565,950✔
2076
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
565,950✔
2077
        DirentPtr dirent;
565,950✔
2078
        do {
565,950✔
2079
            // readdir() signals both errors and end-of-stream by returning a
2080
            // null pointer. To distinguish between end-of-stream and errors,
2081
            // the manpage recommends setting errno specifically to 0 before
2082
            // calling it...
2083
            errno = 0;
565,950✔
2084

2085
            dirent = REALM_READDIR(m_dirp);
565,950✔
2086
        } while (!dirent && errno == EAGAIN);
565,950✔
2087

2088
        if (!dirent) {
565,950✔
2089
            if (errno != 0)
116,280✔
2090
                throw SystemError(errno, "readdir() failed");
×
2091
            return false; // End of stream
116,280✔
2092
        }
116,280✔
2093
        const char* name_1 = dirent->d_name;
449,670✔
2094
        std::string name_2 = name_1;
449,670✔
2095
        if (name_2 != "." && name_2 != "..") {
449,670✔
2096
            name = name_2;
216,708✔
2097
            return true;
216,708✔
2098
        }
216,708✔
2099
    }
449,670✔
2100
}
332,988✔
2101

2102
#else
2103

2104
DirScanner::DirScanner(const std::string&, bool)
2105
{
2106
    throw NotImplemented();
2107
}
2108

2109
DirScanner::~DirScanner() noexcept {}
2110

2111
bool DirScanner::next(std::string&)
2112
{
2113
    return false;
2114
}
2115

2116
#endif
2117

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