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

realm / realm-core / 2609

09 Sep 2024 11:58AM UTC coverage: 91.117% (-0.009%) from 91.126%
2609

push

Evergreen

jedelbo
Bump baasaas version

102802 of 181478 branches covered (56.65%)

217211 of 238388 relevant lines covered (91.12%)

6203598.7 hits per line

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

84.84
/src/realm/util/file.cpp
1
/*************************************************************************
2
 *
3
 * Copyright 2016 Realm Inc.
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 * http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 **************************************************************************/
18

19
#include <realm/util/file.hpp>
20

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

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

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

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

51
using namespace realm::util;
52

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

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

81
#ifdef _WIN32
82

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

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

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

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

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

125
    HANDLE handle = INVALID_HANDLE_VALUE;
126
};
127

128
#endif
129

130
#if REALM_HAVE_STD_FILESYSTEM
131
#if __cplusplus < 202002L
132
using std::filesystem::u8path;
133
inline std::string path_to_string(const std::filesystem::path& path)
134
{
135
    return path.u8string();
136
}
137
#else
138
inline std::string path_to_string(const std::filesystem::path& path)
139
{
140
    return reinterpret_cast<const char*>(path.u8string().c_str());
141
}
142
inline auto u8path(const std::string& str)
143
{
144
    return std::filesystem::path(reinterpret_cast<const char8_t*>(str.c_str()));
145
};
146
#endif
147

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

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

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

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

180
} // anonymous namespace
181

182

183
namespace realm::util {
184
namespace {
185

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

196
} // anonymous namespace
197

198

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

210
    int err = errno; // Eliminate any risk of clobbering
130,797✔
211
    if (err == EEXIST)
130,797✔
212
        return false;
130,791✔
213

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

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

226

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

234

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

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

267

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

277

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

289
    int err = errno; // Eliminate any risk of clobbering
279✔
290
    if (err == ENOENT)
279✔
291
        return false;
273✔
292

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

308

309
bool try_remove_dir_recursive(const std::string& path)
310
{
106,560✔
311
#if REALM_HAVE_STD_FILESYSTEM
312
    std::error_code error;
313
    auto removed_count = std::filesystem::remove_all(u8path(path), error);
314
    throwIfFileError(error, path);
315
    return removed_count > 0;
316
#else
317
    {
106,560✔
318
        bool allow_missing = true;
106,560✔
319
        DirScanner ds{path, allow_missing}; // Throws
106,560✔
320
        std::string name;
106,560✔
321
        while (ds.next(name)) {                              // Throws
300,591✔
322
            std::string subpath = File::resolve(name, path); // Throws
194,031✔
323
            if (File::is_dir(subpath)) {                     // Throws
194,031✔
324
                try_remove_dir_recursive(subpath);           // Throws
61,797✔
325
            }
61,797✔
326
            else {
132,234✔
327
                File::remove(subpath); // Throws
132,234✔
328
            }
132,234✔
329
        }
194,031✔
330
    }
106,560✔
331
    return try_remove_dir(path); // Throws
106,560✔
332
#endif
106,560✔
333
}
106,560✔
334

335

336
std::string make_temp_dir()
337
{
44,916✔
338
#ifdef _WIN32 // Windows version
339
    std::filesystem::path temp = std::filesystem::temp_directory_path();
340

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

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

360
#else // POSIX.1-2008 version
361

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

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

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

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

392
    return path_to_string(std::filesystem::path(buffer));
393

394
#else // POSIX.1-2008 version
395

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

415
size_t page_size()
416
{
26,440,428✔
417
    static constexpr size_t c_min_supported_page_size = 4096;
26,440,428✔
418
    static size_t page_size = [] {
26,440,428✔
419
#ifdef _WIN32
420
        SYSTEM_INFO sysinfo;
421
        GetNativeSystemInfo(&sysinfo);
422
        // DWORD size = sysinfo.dwPageSize;
423
        // On windows we use the allocation granularity instead
424
        DWORD size = sysinfo.dwAllocationGranularity;
425
#else
426
        long size = sysconf(_SC_PAGESIZE);
24✔
427
#endif
24✔
428
        REALM_ASSERT(size > 0 && size % c_min_supported_page_size == 0);
24✔
429
        return static_cast<size_t>(size);
24✔
430
    }();
24✔
431
    return page_size;
26,440,428✔
432
}
26,440,428✔
433

434
File::File() = default;
356,838✔
435
File::File(std::string_view path, Mode m)
436
{
2,859✔
437
    open(path, m);
2,859✔
438
}
2,859✔
439

440
File::~File() noexcept
441
{
359,697✔
442
    close();
359,697✔
443
}
359,697✔
444

445
File::File(File&& f) noexcept
446
{
12✔
447
    m_fd = std::exchange(f.m_fd, invalid_fd);
12✔
448
#ifdef REALM_FILELOCK_EMULATION
449
    m_pipe_fd = std::exchange(f.m_pipe_fd, invalid_fd);
450
    m_has_exclusive_lock = std::exchange(f.m_has_exclusive_lock, false);
451
#endif
452
    m_have_lock = std::exchange(f.m_have_lock, false);
12✔
453
    m_encryption = std::move(f.m_encryption);
12✔
454
}
12✔
455

456
File& File::operator=(File&& f) noexcept
457
{
6✔
458
    close();
6✔
459

460
    m_fd = std::exchange(f.m_fd, invalid_fd);
6✔
461
#ifdef REALM_FILELOCK_EMULATION
462
    m_pipe_fd = std::exchange(f.m_pipe_fd, invalid_fd);
463
    m_has_exclusive_lock = std::exchange(f.m_has_exclusive_lock, false);
464
#endif
465
    m_have_lock = std::exchange(f.m_have_lock, false);
6✔
466
    m_encryption = std::move(f.m_encryption);
6✔
467
    return *this;
6✔
468
}
6✔
469

470

471
void File::open_internal(std::string_view path, AccessMode a, CreateMode c, int flags, bool* success)
472
{
399,186✔
473
    REALM_ASSERT_RELEASE(!is_attached());
399,186✔
474
    m_path = path; // for error reporting and debugging
399,186✔
475

476
#ifdef _WIN32 // Windows version
477

478
    DWORD desired_access = GENERIC_READ;
479
    switch (a) {
480
        case access_ReadOnly:
481
            break;
482
        case access_ReadWrite:
483
            if (flags & flag_Append) {
484
                desired_access = FILE_APPEND_DATA;
485
            }
486
            else {
487
                desired_access |= GENERIC_WRITE;
488
            }
489
            break;
490
    }
491
    // FIXME: Should probably be zero if we are called on behalf of a
492
    // Group instance that is not managed by a SharedGroup instance,
493
    // since in this case concurrenct access is prohibited anyway.
494
    DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
495
    DWORD creation_disposition = 0;
496
    switch (c) {
497
        case create_Auto:
498
            creation_disposition = flags & flag_Trunc ? CREATE_ALWAYS : OPEN_ALWAYS;
499
            break;
500
        case create_Never:
501
            creation_disposition = flags & flag_Trunc ? TRUNCATE_EXISTING : OPEN_EXISTING;
502
            break;
503
        case create_Must:
504
            creation_disposition = CREATE_NEW;
505
            break;
506
    }
507
    DWORD flags_and_attributes = 0;
508
    m_fd = CreateFile2(u8path(m_path).c_str(), desired_access, share_mode, creation_disposition, nullptr);
509
    if (m_fd != INVALID_HANDLE_VALUE) {
510
        m_have_lock = false;
511
        if (success)
512
            *success = true;
513
        return;
514
    }
515

516
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
517
    if (success && err == ERROR_FILE_EXISTS && c == create_Must) {
518
        *success = false;
519
        return;
520
    }
521
    if (success && err == ERROR_FILE_NOT_FOUND && c == create_Never) {
522
        *success = false;
523
        return;
524
    }
525
    std::string msg = get_last_error_msg("CreateFile() failed: ", err);
526
    switch (err) {
527
        case ERROR_SHARING_VIOLATION:
528
        case ERROR_ACCESS_DENIED:
529
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, m_path, int(err));
530
        case ERROR_FILE_NOT_FOUND:
531
        case ERROR_PATH_NOT_FOUND:
532
            throw FileAccessError(ErrorCodes::FileNotFound, msg, m_path, int(err));
533
        case ERROR_FILE_EXISTS:
534
            throw Exists(msg, m_path);
535
        default:
536
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, m_path, int(err));
537
    }
538

539
#else // POSIX version
540

541
    int flags2 = 0;
399,186✔
542
    switch (a) {
399,186✔
543
        case access_ReadOnly:
2,967✔
544
            flags2 = O_RDONLY;
2,967✔
545
            break;
2,967✔
546
        case access_ReadWrite:
396,222✔
547
            flags2 = O_RDWR;
396,222✔
548
            break;
396,222✔
549
    }
399,186✔
550
    switch (c) {
399,159✔
551
        case create_Auto:
369,723✔
552
            flags2 |= O_CREAT;
369,723✔
553
            break;
369,723✔
554
        case create_Never:
28,593✔
555
            break;
28,593✔
556
        case create_Must:
852✔
557
            flags2 |= O_CREAT | O_EXCL;
852✔
558
            break;
852✔
559
    }
399,159✔
560
    if (flags & flag_Trunc)
399,165✔
561
        flags2 |= O_TRUNC;
666✔
562
    if (flags & flag_Append)
399,165✔
563
        flags2 |= O_APPEND;
151,770✔
564
    int fd = ::open(m_path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
399,165✔
565
    if (0 <= fd) {
399,165✔
566
        m_fd = fd;
399,060✔
567
        m_have_lock = false;
399,060✔
568
        if (success)
399,060✔
569
            *success = true;
204✔
570
        return;
399,060✔
571
    }
399,060✔
572

573
    int err = errno; // Eliminate any risk of clobbering
105✔
574
    if (success && err == EEXIST && c == create_Must) {
105✔
575
        *success = false;
30✔
576
        return;
30✔
577
    }
30✔
578
    if (success && err == ENOENT && c == create_Never) {
75!
579
        *success = false;
×
580
        return;
×
581
    }
×
582
    std::string msg = format_errno("Failed to open file at path '%2': %1", err, path);
75✔
583
    switch (err) {
75✔
584
        case EACCES:
6✔
585
        case EPERM:
6✔
586
        case EROFS:
6✔
587
        case ETXTBSY:
6✔
588
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
589
        case ENOENT:
30✔
590
            if (c != create_Never)
30✔
591
                msg = util::format("Failed to open file at path '%1': parent directory does not exist", path);
6✔
592
            throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
30✔
593
        case EEXIST:
36✔
594
            throw Exists(msg, m_path);
36✔
595
        case ENOTDIR:
✔
596
            msg = format("Failed to open file at path '%1': parent path is not a directory", path);
×
597
            [[fallthrough]];
×
598
        default:
42✔
599
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
600
    }
75✔
601

602
#endif
75✔
603
}
75✔
604

605

606
void File::close() noexcept
607
{
755,037✔
608
    m_encryption.reset();
755,037✔
609
    if (m_fd == invalid_fd)
755,037✔
610
        return;
356,022✔
611
    if (m_have_lock)
399,015✔
612
        unlock();
159✔
613
#ifdef _WIN32 // Windows version
614
    BOOL r = CloseHandle(m_fd);
615
    REALM_ASSERT_RELEASE(r);
616
#else // POSIX version
617
    int r = ::close(m_fd);
399,015✔
618
    REALM_ASSERT_RELEASE(r == 0);
399,015✔
619
#endif
399,015✔
620

621
    m_fd = invalid_fd;
399,015✔
622
}
399,015✔
623

624
size_t File::read_static(FileDesc fd, SizeType pos, char* data, size_t size)
625
{
879,849✔
626
#ifdef _WIN32 // Windows version
627
    char* const data_0 = data;
628
    while (0 < size) {
629
        DWORD n = std::numeric_limits<DWORD>::max();
630
        if (int_less_than(size, n))
631
            n = static_cast<DWORD>(size);
632
        DWORD r = 0;
633
        OVERLAPPED o{};
634
        o.Offset = static_cast<DWORD>(pos);
635
        o.OffsetHigh = static_cast<DWORD>(pos >> 32);
636
        if (!ReadFile(fd, data, n, &r, &o)) {
637
            DWORD err = GetLastError();
638
            if (err == ERROR_HANDLE_EOF)
639
                break;
640
            throw SystemError(int(err), "ReadFile() failed");
641
        }
642
        REALM_ASSERT_RELEASE(r > 0 && r <= n);
643
        size -= size_t(r);
644
        data += size_t(r);
645
        pos += r;
646
    }
647
    return data - data_0;
648

649
#else // POSIX version
650

651
    char* const data_0 = data;
879,849✔
652
    while (0 < size) {
1,759,491✔
653
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
654
        size_t n = std::min(size, size_t(SSIZE_MAX));
879,948✔
655
        ssize_t r = pread(fd, data, n, pos);
879,948✔
656
        if (r == 0)
879,948✔
657
            break;
306✔
658
        if (r < 0)
879,642✔
659
            goto error; // LCOV_EXCL_LINE
660
        REALM_ASSERT_RELEASE(size_t(r) <= n);
879,642✔
661
        size -= size_t(r);
879,642✔
662
        data += size_t(r);
879,642✔
663
        pos += r;
879,642✔
664
    }
879,642✔
665
    return data - data_0;
879,849✔
666

667
error:
×
668
    // LCOV_EXCL_START
669
    throw SystemError(errno, "read() failed");
×
670
// LCOV_EXCL_STOP
671
#endif
879,849✔
672
}
879,849✔
673

674

675
size_t File::read(SizeType pos, char* data, size_t size)
676
{
7,032✔
677
    REALM_ASSERT_RELEASE(is_attached());
7,032✔
678

679
    if (m_encryption) {
7,032✔
680
        Map<char> read_map(*this, pos, access_ReadOnly, size);
×
681
        util::encryption_read_barrier(read_map, 0, size);
×
682
        memcpy(data, read_map.get_addr(), size);
×
683
        return size;
×
684
    }
×
685

686
    return read_static(m_fd, pos, data, size);
7,032✔
687
}
7,032✔
688

689
void File::write_static(FileDesc fd, SizeType pos, const char* data, size_t size)
690
{
624,585✔
691
#ifdef _WIN32
692
    while (0 < size) {
693
        DWORD n = std::numeric_limits<DWORD>::max();
694
        if (int_less_than(size, n))
695
            n = static_cast<DWORD>(size);
696
        DWORD r = 0;
697
        OVERLAPPED o{};
698
        o.Offset = static_cast<DWORD>(pos);
699
        o.OffsetHigh = static_cast<DWORD>(pos >> 32);
700
        if (!WriteFile(fd, data, n, &r, &o))
701
            goto error;
702
        REALM_ASSERT_RELEASE(r == n); // Partial writes are not possible.
703
        size -= size_t(r);
704
        data += size_t(r);
705
        pos += r;
706
    }
707
    return;
708

709
error:
710
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
711
    if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
712
        std::string msg = get_last_error_msg("WriteFile() failed: ", err);
713
        throw OutOfDiskSpace(msg);
714
    }
715
    throw SystemError(err, "WriteFile() failed");
716
#else
717
    while (0 < size) {
1,249,053✔
718
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
719
        size_t n = std::min(size, size_t(SSIZE_MAX));
624,468✔
720
        ssize_t r = pwrite(fd, data, n, pos);
624,468✔
721
        if (r < 0)
624,468✔
722
            goto error; // LCOV_EXCL_LINE
723
        REALM_ASSERT_RELEASE(r != 0);
624,468✔
724
        REALM_ASSERT_RELEASE(size_t(r) <= n);
624,468✔
725
        size -= size_t(r);
624,468✔
726
        data += size_t(r);
624,468✔
727
        pos += off_t(r);
624,468✔
728
    }
624,468✔
729
    return;
624,585✔
730

731
error:
624,585✔
732
    // LCOV_EXCL_START
733
    int err = errno; // Eliminate any risk of clobbering
×
734
    auto msg = format_errno("write() failed: %1", err);
×
735
    if (err == ENOSPC || err == EDQUOT) {
×
736
        throw OutOfDiskSpace(msg);
×
737
    }
×
738
    throw SystemError(err, msg);
×
739
    // LCOV_EXCL_STOP
740

741
#endif
×
742
}
×
743

744
void File::write(SizeType pos, const char* data, size_t size)
745
{
54,510✔
746
    REALM_ASSERT_RELEASE(is_attached());
54,510✔
747

748
    if (m_encryption) {
54,510✔
749
        Map<char> write_map(*this, pos, access_ReadWrite, size);
870✔
750
        util::encryption_read_barrier(write_map, 0, size);
870✔
751
        memcpy(write_map.get_addr(), data, size);
870✔
752
        realm::util::encryption_write_barrier(write_map, 0, size);
870✔
753
        return;
870✔
754
    }
870✔
755

756
    write_static(m_fd, pos, data, size);
53,640✔
757
}
53,640✔
758

759
File::SizeType File::get_file_pos()
760
{
6,876✔
761
#ifdef _WIN32
762
    LONG high_dword = 0;
763
    LARGE_INTEGER li;
764
    LARGE_INTEGER res;
765
    li.QuadPart = 0;
766
    bool ok = SetFilePointerEx(m_fd, li, &res, FILE_CURRENT);
767
    if (!ok)
768
        throw SystemError(GetLastError(), "SetFilePointer() failed");
769

770
    return SizeType(res.QuadPart);
771
#else
772
    auto pos = lseek(m_fd, 0, SEEK_CUR);
6,876✔
773
    if (pos < 0) {
6,876✔
774
        throw SystemError(errno, "lseek() failed");
×
775
    }
×
776
    return SizeType(pos);
6,876✔
777
#endif
6,876✔
778
}
6,876✔
779

780
File::SizeType File::get_size_static(const std::string& path)
781
{
1,353✔
782
    File f(path);
1,353✔
783
    return f.get_size();
1,353✔
784
}
1,353✔
785

786
File::SizeType File::get_size_static(FileDesc fd)
787
{
1,734,561✔
788
#ifdef _WIN32
789
    LARGE_INTEGER large_int;
790
    if (GetFileSizeEx(fd, &large_int)) {
791
        File::SizeType size;
792
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
793
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
794

795
        return size;
796
    }
797
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
798

799
#else // POSIX version
800

801
    struct stat statbuf;
1,734,561✔
802
    if (::fstat(fd, &statbuf) == 0) {
1,734,762✔
803
        SizeType size;
1,734,762✔
804
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,734,762✔
805
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
806

807
        return size;
1,734,762✔
808
    }
1,734,762✔
809
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
810

811
#endif
1,734,561✔
812
}
1,734,561✔
813

814
File::SizeType File::get_size() const
815
{
1,734,075✔
816
    REALM_ASSERT_RELEASE(is_attached());
1,734,075✔
817
    File::SizeType size = get_size_static(m_fd);
1,734,075✔
818

819
    if (m_encryption) {
1,734,075✔
820
        return encrypted_size_to_data_size(size);
3,987✔
821
    }
3,987✔
822
    return size;
1,730,088✔
823
}
1,734,075✔
824

825

826
void File::resize(SizeType size)
827
{
220,050✔
828
    REALM_ASSERT_RELEASE(is_attached());
220,050✔
829

830
    if (m_encryption)
220,050✔
831
        size = data_size_to_encrypted_size(size);
150✔
832

833
#ifdef _WIN32 // Windows version
834
    FILE_END_OF_FILE_INFO info;
835
    info.EndOfFile.QuadPart = size;
836
    if (!SetFileInformationByHandle(m_fd, FileEndOfFileInfo, &info, sizeof(info))) {
837
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
838
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
839
            throw OutOfDiskSpace(get_last_error_msg("SetFileInformationByHandle() failed: ", err));
840
        }
841
        throw SystemError(int(err), "SetFileInformationByHandle() failed");
842
    }
843
#else // POSIX version
844

845
    off_t size2;
220,050✔
846
    if (int_cast_with_overflow_detect(size, size2))
220,050✔
847
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
848

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

860
#endif
220,050✔
861
}
220,050✔
862

863

864
void File::prealloc(SizeType size)
865
{
148,317✔
866
    REALM_ASSERT_RELEASE(is_attached());
148,317✔
867
    if (size <= get_size()) {
148,317✔
868
        return;
40,125✔
869
    }
40,125✔
870

871
    SizeType new_size = size;
108,192✔
872
    if (m_encryption) {
108,192✔
873
        new_size = data_size_to_encrypted_size(size);
645✔
874
        REALM_ASSERT(new_size > size);
645✔
875
        REALM_ASSERT(size == encrypted_size_to_data_size(new_size));
645✔
876
    }
645✔
877

878
    auto manually_consume_space = [&]() {
108,192✔
879
        constexpr uint16_t chunk_size = 4096;
×
880
        SizeType write_pos = get_size_static(m_fd); // raw size
×
881
        SizeType num_bytes = new_size - write_pos;
×
882
        std::string zeros(chunk_size, '\0');
×
883
        while (num_bytes > 0) {
×
884
            uint16_t t = uint16_t(std::min<SizeType>(num_bytes, chunk_size));
×
885
            write_static(m_fd, write_pos, zeros.c_str(), t);
×
886
            num_bytes -= t;
×
887
            write_pos += t;
×
888
        }
×
889
    };
×
890

891
#if REALM_HAVE_POSIX_FALLOCATE
59,280✔
892
    // Mostly Linux only
893
    if (!prealloc_if_supported(0, new_size)) {
59,280✔
894
        manually_consume_space();
895
    }
896
#elif REALM_PLATFORM_APPLE // Non-atomic fallback
897
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
898
    // ftruncate().
899

900
    struct stat statbuf;
48,912✔
901
    if (::fstat(m_fd, &statbuf) != 0) {
48,912✔
902
        int err = errno;
903
        throw SystemError(err, "fstat() inside prealloc() failed");
904
    }
905

906
    SizeType allocated_size;
48,912✔
907
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
48,912✔
908
        throw RuntimeError(ErrorCodes::RangeError,
909
                           util::format("Overflow on block conversion to SizeType %1", statbuf.st_blocks));
910
    }
911
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
48,912✔
912
        throw RuntimeError(ErrorCodes::RangeError,
913
                           util::format("Overflow computing existing file space allocation blocks: %1 block size %2",
914
                                        allocated_size, S_BLKSIZE));
915
    }
916

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

943
    int ret = 0;
48,912✔
944

945
    do {
48,912✔
946
        ret = ftruncate(m_fd, new_size);
48,912✔
947
    } while (ret == -1 && errno == EINTR);
48,912!
948

949
    if (ret != 0) {
48,912✔
950
        int err = errno;
951
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
952
        // so this is some other runtime error and not OutOfDiskSpace
953
        throw SystemError(err, "ftruncate() inside prealloc() failed");
954
    }
955
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
956
    manually_consume_space();
957
#else
958
#error Please check if/how your OS supports file preallocation
959
#endif // REALM_HAVE_POSIX_FALLOCATE
960
}
108,192✔
961

962

963
bool File::prealloc_if_supported(SizeType offset, SizeType size)
964
{
59,280✔
965
    REALM_ASSERT_RELEASE(is_attached());
59,280✔
966

967
#if REALM_HAVE_POSIX_FALLOCATE
59,280✔
968

969
    REALM_ASSERT_RELEASE(is_prealloc_supported());
59,280✔
970

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

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

984
    if (REALM_LIKELY(status == 0)) {
59,280✔
985
        return true;
59,280✔
986
    }
59,280✔
987

988
    if (status == EINVAL || status == EPERM || status == EOPNOTSUPP) {
×
989
        return false; // Retry with non-atomic version
990
    }
991

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

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

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

1006
#else
1007

1008
    static_cast<void>(offset);
1009
    static_cast<void>(size);
1010

1011
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1012

1013
#endif
1014
    return false;
×
1015
}
×
1016

1017

1018
bool File::is_prealloc_supported()
1019
{
59,280✔
1020
#if REALM_HAVE_POSIX_FALLOCATE
59,280✔
1021
    return true;
59,280✔
1022
#else
1023
    return false;
1024
#endif
1025
}
59,280✔
1026

1027
void File::seek(SizeType position)
1028
{
6,882✔
1029
    REALM_ASSERT_RELEASE(is_attached());
6,882✔
1030
    seek_static(m_fd, position);
6,882✔
1031
}
6,882✔
1032

1033
void File::seek_static(FileDesc fd, SizeType position)
1034
{
6,882✔
1035
#ifdef _WIN32 // Windows version
1036

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

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

1044
#else // POSIX version
1045

1046
    off_t position2;
6,882✔
1047
    if (int_cast_with_overflow_detect(position, position2))
6,882✔
1048
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1049

1050
    if (0 <= ::lseek(fd, position2, SEEK_SET))
6,882✔
1051
        return;
6,882✔
1052
    throw SystemError(errno, "lseek() failed");
×
1053

1054
#endif
6,882✔
1055
}
6,882✔
1056

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

1065
#if defined _WIN32 // Windows version
1066

1067
    if (FlushFileBuffers(m_fd))
1068
        return;
1069
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1070

1071
#elif REALM_PLATFORM_APPLE
1072

1073
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
303✔
1074
        return;
303✔
1075
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1076

1077
#else // POSIX version
1078

1079
    if (::fsync(m_fd) == 0)
480✔
1080
        return;
480✔
1081
    throw SystemError(errno, "fsync() failed");
1082

1083
#endif
480✔
1084
}
783✔
1085

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

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

1111
bool File::rw_lock(bool exclusive, bool non_blocking)
1112
{
366,639✔
1113
    // exclusive blocking rw locks not implemented for emulation
1114
    REALM_ASSERT(!exclusive || non_blocking);
366,639✔
1115

1116
#ifndef REALM_FILELOCK_EMULATION
366,639✔
1117
    return lock(exclusive, non_blocking);
366,639✔
1118
#else
1119
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1120

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

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

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

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

1155
    auto report_err = [this](int err, const char* msg) {
1156
        auto message = util::format("%1: %2, path = '%3'", msg, std::system_category().message(err), m_fifo_path);
1157
        throw RuntimeError(ErrorCodes::FileOperationFailed, message); // LCOV_EXCL_LINE
1158
    };
1159

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

1175
            // Otherwise we got an unexpected error
1176
            report_err(err, "opening lock fifo for writing failed");
1177
        }
1178

1179
        if (err == ENOENT) {
1180
            // The fifo doesn't exist and we're opening in shared mode, so we
1181
            // need to create it.
1182
            if (!m_fifo_dir_path.empty())
1183
                try_make_dir(m_fifo_dir_path);
1184
            status = mkfifo(m_fifo_path.c_str(), 0666);
1185
            if (status != 0)
1186
                report_err(errno, "creating lock fifo for reading failed");
1187

1188
            // Try again to open the fifo now that it exists
1189
            fd = ::open(m_fifo_path.c_str(), mode);
1190
            err = errno;
1191
        }
1192

1193
        if (fd == -1)
1194
            report_err(err, "opening lock fifo for reading failed");
1195
    }
1196

1197
    // We successfully opened the pipe. If we're trying to acquire an exclusive
1198
    // lock that means there's a reader (aka a shared lock) and we've failed.
1199
    // Release the exclusive lock and back out.
1200
    if (exclusive) {
1201
        ::close(fd);
1202
        return false;
1203
    }
1204

1205
    // We're in shared mode, so opening the fifo means we've successfully acquired
1206
    // a shared lock and are done.
1207
    ulg.release();
1208
    rw_unlock();
1209
    m_pipe_fd = fd;
1210
    return true;
1211
#endif // REALM_FILELOCK_EMULATION
1212
}
366,639✔
1213

1214
bool File::lock(bool exclusive, bool non_blocking)
1215
{
4,105,419✔
1216
    REALM_ASSERT_RELEASE(is_attached());
4,105,419✔
1217
    REALM_ASSERT_RELEASE(!m_have_lock);
4,105,419✔
1218

1219
#ifdef _WIN32 // Windows version
1220

1221
    // Under Windows a file lock must be explicitely released before
1222
    // the file is closed. It will eventually be released by the
1223
    // system, but there is no guarantees on the timing.
1224

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

1276
void File::unlock() noexcept
1277
{
3,964,863✔
1278
    if (!m_have_lock)
3,964,863✔
1279
        return;
×
1280

1281
#ifdef _WIN32 // Windows version
1282
    OVERLAPPED overlapped;
1283
    overlapped.hEvent = 0;
1284
    overlapped.OffsetHigh = 0;
1285
    overlapped.Offset = 0;
1286
    overlapped.Pointer = 0;
1287
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1288
    REALM_ASSERT_RELEASE(r);
1289
#else
1290
    _unlock(m_fd);
3,964,863✔
1291
#endif
3,964,863✔
1292
    m_have_lock = false;
3,964,863✔
1293
}
3,964,863✔
1294

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

1323
bool File::exists(const std::string& path)
1324
{
724,275✔
1325
#if REALM_HAVE_STD_FILESYSTEM
1326
    return std::filesystem::exists(u8path(path));
1327
#else // POSIX
1328
    if (::access(path.c_str(), F_OK) == 0)
724,275✔
1329
        return true;
31,251✔
1330
    int err = errno; // Eliminate any risk of clobbering
693,024✔
1331
    switch (err) {
693,024✔
1332
        case EACCES:
✔
1333
        case ENOENT:
693,003✔
1334
        case ENOTDIR:
693,003✔
1335
            return false;
693,003✔
1336
    }
693,024✔
1337
    throw SystemError(err, "access() failed");
24✔
1338
#endif
693,024✔
1339
}
693,024✔
1340

1341

1342
bool File::is_dir(const std::string& path)
1343
{
234,588✔
1344
#if REALM_HAVE_STD_FILESYSTEM
1345
    return std::filesystem::is_directory(u8path(path));
1346
#elif !defined(_WIN32)
1347
    struct stat statbuf;
1348
    if (::stat(path.c_str(), &statbuf) == 0)
234,588✔
1349
        return S_ISDIR(statbuf.st_mode);
227,553✔
1350
    int err = errno; // Eliminate any risk of clobbering
7,035✔
1351
    switch (err) {
7,035✔
1352
        case EACCES:
✔
1353
        case ENOENT:
7,035✔
1354
        case ENOTDIR:
7,035✔
1355
            return false;
7,035✔
1356
    }
7,035✔
1357
    throw SystemError(err, "stat() failed");
×
1358
#else
1359
    static_cast<void>(path);
1360
    throw NotImplemented();
1361
#endif
1362
}
7,035✔
1363

1364

1365
void File::remove(const std::string& path)
1366
{
176,385✔
1367
    if (try_remove(path))
176,385✔
1368
        return;
176,373✔
1369
    int err = ENOENT;
12✔
1370
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
12✔
1371
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
12✔
1372
}
176,385✔
1373

1374

1375
bool File::try_remove(const std::string& path)
1376
{
238,701✔
1377
#if REALM_HAVE_STD_FILESYSTEM
1378
    std::error_code error;
1379
    bool result = std::filesystem::remove(u8path(path), error);
1380
    throwIfFileError(error, path);
1381
    return result;
1382
#else // POSIX
1383
    if (::unlink(path.c_str()) == 0)
238,701✔
1384
        return true;
198,681✔
1385

1386
    int err = errno; // Eliminate any risk of clobbering
40,020✔
1387
    if (err == ENOENT)
40,020✔
1388
        return false;
39,969✔
1389

1390
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
51✔
1391
    switch (err) {
51✔
1392
        case EACCES:
✔
1393
        case EROFS:
✔
1394
        case ETXTBSY:
✔
1395
        case EBUSY:
✔
1396
        case EPERM:
21✔
1397
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
21✔
1398
        case ENOENT:
✔
1399
            return false;
×
1400
        default:
30✔
1401
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
30✔
1402
    }
51✔
1403
#endif
51✔
1404
}
51✔
1405

1406

1407
void File::move(const std::string& old_path, const std::string& new_path)
1408
{
312✔
1409
#if REALM_HAVE_STD_FILESYSTEM
1410
    std::error_code error;
1411
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1412

1413
    if (error == std::errc::no_such_file_or_directory) {
1414
        throw FileAccessError(ErrorCodes::FileNotFound, error.message(), old_path);
1415
    }
1416
    throwIfFileError(error, old_path);
1417
#else
1418
    int r = rename(old_path.c_str(), new_path.c_str());
312✔
1419
    if (r == 0)
312✔
1420
        return;
312✔
1421
    int err = errno; // Eliminate any risk of clobbering
×
1422
    std::string msg = format_errno("Failed to rename file from '%2' to '%3': %1", err, old_path, new_path);
×
1423
    switch (err) {
×
1424
        case EACCES:
×
1425
        case EROFS:
×
1426
        case ETXTBSY:
×
1427
        case EBUSY:
×
1428
        case EPERM:
×
1429
        case EEXIST:
×
1430
        case ENOTEMPTY:
×
1431
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, old_path, err);
×
1432
        case ENOENT:
×
1433
            throw FileAccessError(ErrorCodes::FileNotFound, msg, old_path, err);
×
1434
        default:
×
1435
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, old_path, err);
×
1436
    }
×
1437
#endif
×
1438
}
×
1439

1440

1441
bool File::copy(const std::string& origin_path, const std::string& target_path, bool overwrite_existing)
1442
{
390✔
1443
#if REALM_HAVE_STD_FILESYSTEM
1444
    auto options = overwrite_existing ? std::filesystem::copy_options::overwrite_existing
1445
                                      : std::filesystem::copy_options::skip_existing;
1446
    return std::filesystem::copy_file(u8path(origin_path), u8path(target_path), options); // Throws
1447
#else
1448
#if REALM_PLATFORM_APPLE
198✔
1449
    // Try to use clonefile and fall back to manual copying if it fails
1450
    if (clonefile(origin_path.c_str(), target_path.c_str(), 0) == 0) {
198✔
1451
        return true;
180✔
1452
    }
180✔
1453
    if (errno == EEXIST && !overwrite_existing) {
18✔
1454
        return false;
6✔
1455
    }
6✔
1456
#endif
12✔
1457

1458
    File origin_file{origin_path, mode_Read}; // Throws
204✔
1459
    File target_file;
204✔
1460
    bool did_create = false;
204✔
1461
    target_file.open(target_path, did_create); // Throws
204✔
1462
    if (!did_create && !overwrite_existing) {
204✔
1463
        return false;
6✔
1464
    }
6✔
1465

1466
    size_t buffer_size = 4096;
198✔
1467
    off_t pos = 0;
198✔
1468
    auto buffer = std::make_unique<char[]>(buffer_size); // Throws
198✔
1469
    for (;;) {
576✔
1470
        size_t n = origin_file.read(pos, buffer.get(), buffer_size); // Throws
576✔
1471
        target_file.write(pos, buffer.get(), n);                     // Throws
576✔
1472
        pos += n;
576✔
1473
        if (n < buffer_size)
576✔
1474
            break;
198✔
1475
    }
576✔
1476

1477
    return true;
198✔
1478
#endif
204✔
1479
}
204✔
1480

1481

1482
bool File::is_same_file_static(FileDesc f1, FileDesc f2, const std::string& path1, const std::string& path2)
1483
{
24✔
1484
    return get_unique_id(f1, path1) == get_unique_id(f2, path2);
24✔
1485
}
24✔
1486

1487
bool File::is_same_file(const File& f) const
1488
{
24✔
1489
    REALM_ASSERT_RELEASE(is_attached());
24✔
1490
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1491
    return is_same_file_static(m_fd, f.m_fd, m_path, f.m_path);
24✔
1492
}
24✔
1493

1494
FileDesc File::dup_file_desc(FileDesc fd)
1495
{
×
1496
    FileDesc fd_duped;
×
1497
#ifdef _WIN32
1498
    if (!DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &fd_duped, 0, FALSE, DUPLICATE_SAME_ACCESS))
1499
        throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
1500
#else
1501
    fd_duped = dup(fd);
×
1502

1503
    if (fd_duped == -1) {
×
1504
        int err = errno; // Eliminate any risk of clobbering
×
1505
        throw std::system_error(err, std::system_category(), "dup() failed");
×
1506
    }
×
1507
#endif // conditonal on _WIN32
×
1508
    return fd_duped;
×
1509
}
×
1510

1511
FileDesc File::get_descriptor() const
1512
{
75,792✔
1513
    return m_fd;
75,792✔
1514
}
75,792✔
1515

1516
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1517
{
216✔
1518
#ifdef _WIN32 // Windows version
1519
    // CreateFile2 with creationDisposition OPEN_EXISTING will return a file handle only if the file exists
1520
    // otherwise it will raise ERROR_FILE_NOT_FOUND. This call will never create a new file.
1521
    WindowsFileHandleHolder fileHandle(::CreateFile2(u8path(path).c_str(), FILE_READ_ATTRIBUTES,
1522
                                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1523
                                                     OPEN_EXISTING, nullptr));
1524

1525
    if (fileHandle == INVALID_HANDLE_VALUE) {
1526
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1527
            return none;
1528
        }
1529
        throw SystemError(GetLastError(), "CreateFileW failed");
1530
    }
1531

1532
    return get_unique_id(fileHandle, path);
1533
#else // POSIX version
1534
    struct stat statbuf;
216✔
1535
    if (::stat(path.c_str(), &statbuf) == 0) {
216✔
1536
        if (statbuf.st_size == 0) {
216✔
1537
            // On exFAT systems the inode and device are not populated correctly until the file
1538
            // has been allocated some space. The uid can also be reassigned if the file is
1539
            // truncated to zero. This has led to bugs where a unique id returned here was
1540
            // reused by different files. The following check ensures that this is not
1541
            // happening anywhere else in future code.
1542
            return none;
×
1543
        }
×
1544
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
216✔
1545
    }
216✔
1546
    int err = errno; // Eliminate any risk of clobbering
×
1547
    // File doesn't exist
1548
    if (err == ENOENT)
×
1549
        return none;
×
1550
    throw SystemError(err, format_errno("fstat() failed: %1 for '%2'", err, path));
×
1551
#endif
×
1552
}
×
1553

1554
File::UniqueID File::get_unique_id(FileDesc file, const std::string& debug_path)
1555
{
48✔
1556
#ifdef _WIN32 // Windows version
1557
    REALM_ASSERT(file != nullptr);
1558
    File::UniqueID ret;
1559
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1560
        throw std::system_error(GetLastError(), std::system_category(),
1561
                                util::format("GetFileInformationByHandleEx() failed for '%1'", debug_path));
1562
    }
1563

1564
    return ret;
1565
#else // POSIX version
1566
    REALM_ASSERT(file >= 0);
48✔
1567
    struct stat statbuf;
48✔
1568
    if (::fstat(file, &statbuf) == 0) {
48✔
1569
        // On exFAT systems the inode and device are not populated correctly until the file
1570
        // has been allocated some space. The uid can also be reassigned if the file is
1571
        // truncated to zero. This has led to bugs where a unique id returned here was
1572
        // reused by different files. The following check ensures that this is not
1573
        // happening anywhere else in future code.
1574
        if (statbuf.st_size == 0) {
48✔
1575
            throw FileAccessError(
×
1576
                ErrorCodes::FileOperationFailed,
×
1577
                util::format("Attempt to get unique id on an empty file. This could be due to an external "
×
1578
                             "process modifying Realm files. '%1'",
×
1579
                             debug_path),
×
1580
                debug_path);
×
1581
        }
×
1582
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
48✔
1583
    }
48✔
1584
    throw std::system_error(errno, std::system_category(), util::format("fstat() failed for '%1'", debug_path));
×
1585
#endif
48✔
1586
}
48✔
1587

1588
std::string File::get_path() const
1589
{
30✔
1590
    return m_path;
30✔
1591
}
30✔
1592

1593
std::string File::resolve(const std::string& path, const std::string& base_dir)
1594
{
232,935✔
1595
#if REALM_HAVE_STD_FILESYSTEM
1596
    return path_to_string((u8path(base_dir) / u8path(path)).lexically_normal());
1597
#else
1598
    char dir_sep = '/';
232,935✔
1599
    std::string path_2 = path;
232,935✔
1600
    std::string base_dir_2 = base_dir;
232,935✔
1601
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
232,935✔
1602
    if (is_absolute)
232,935✔
1603
        return path_2;
6✔
1604
    if (path_2.empty())
232,929✔
1605
        path_2 = ".";
6✔
1606
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
232,929✔
1607
        base_dir_2.push_back(dir_sep);
231,342✔
1608
    /*
1609
    // Abbreviate
1610
    for (;;) {
1611
        if (base_dir_2.empty()) {
1612
            if (path_2.empty())
1613
                return "./";
1614
            return path_2;
1615
        }
1616
        if (path_2 == ".") {
1617
            remove_trailing_dir_seps(base_dir_2);
1618
            return base_dir_2;
1619
        }
1620
        if (has_prefix(path_2, "./")) {
1621
            remove_trailing_dir_seps(base_dir_2);
1622
            // drop dot
1623
            // transfer slashes
1624
        }
1625

1626
        if (path_2.size() < 2 || path_2[1] != '.')
1627
            break;
1628
        if (path_2.size())
1629
    }
1630
    */
1631
    return base_dir_2 + path_2;
232,929✔
1632
#endif
232,935✔
1633
}
232,935✔
1634

1635
std::string File::parent_dir(const std::string& path)
1636
{
90✔
1637
#if REALM_HAVE_STD_FILESYSTEM
1638
    return path_to_string(u8path(path).parent_path()); // Throws
1639
#else
1640
    auto is_sep = [](char c) -> bool {
1,200✔
1641
        return c == '/' || c == '\\';
1,200✔
1642
    };
1,200✔
1643
    auto it = std::find_if(path.rbegin(), path.rend(), is_sep);
90✔
1644
    while (it != path.rend() && is_sep(*it))
192✔
1645
        ++it;
102✔
1646
    return path.substr(0, path.rend() - it);
90✔
1647
#endif
90✔
1648
}
90✔
1649

1650
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1651
{
6✔
1652
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1653
}
6✔
1654

1655

1656
void File::set_encryption_key(const char* key)
1657
{
76,242✔
1658
#if REALM_ENABLE_ENCRYPTION
76,242✔
1659
    if (key) {
76,242✔
1660
        m_encryption = std::make_unique<util::EncryptedFile>(key, m_fd);
591✔
1661
    }
591✔
1662
    else {
75,651✔
1663
        m_encryption.reset();
75,651✔
1664
    }
75,651✔
1665
#else
1666
    if (key) {
1667
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1668
    }
1669
#endif
1670
}
76,242✔
1671

1672
EncryptedFile* File::get_encryption() const noexcept
1673
{
3,085,290✔
1674
#if REALM_ENABLE_ENCRYPTION
3,085,290✔
1675
    return m_encryption.get();
3,085,290✔
1676
#else
1677
    return nullptr;
1678
#endif
1679
}
3,085,290✔
1680

1681
File::MapBase::MapBase() noexcept = default;
1,470,744✔
1682
File::MapBase::~MapBase() noexcept
1683
{
1,622,325✔
1684
    unmap();
1,622,325✔
1685
}
1,622,325✔
1686

1687
File::MapBase::MapBase(MapBase&& other) noexcept
1688
{
151,380✔
1689
    *this = std::move(other);
151,380✔
1690
}
151,380✔
1691

1692
File::MapBase& File::MapBase::operator=(MapBase&& other) noexcept
1693
{
152,034✔
1694
    REALM_ASSERT(this != &other);
152,034✔
1695
    if (m_addr)
152,034✔
1696
        unmap();
×
1697
    m_addr = std::exchange(other.m_addr, nullptr);
152,034✔
1698
    m_size = std::exchange(other.m_size, 0);
152,034✔
1699
    m_access_mode = other.m_access_mode;
152,034✔
1700
    m_reservation_size = std::exchange(other.m_reservation_size, 0);
152,034✔
1701
    m_offset = std::exchange(other.m_offset, 0);
152,034✔
1702
    m_fd = std::exchange(other.m_fd, invalid_fd);
152,034✔
1703
#if REALM_ENABLE_ENCRYPTION
152,034✔
1704
    m_encrypted_mapping = std::move(other.m_encrypted_mapping);
152,034✔
1705
#endif
152,034✔
1706
    return *this;
152,034✔
1707
}
152,034✔
1708

1709
void File::MapBase::map(const File& f, AccessMode a, size_t size, SizeType offset, util::WriteObserver* observer)
1710
{
1,311,690✔
1711
    REALM_ASSERT(!m_addr);
1,311,690✔
1712
#if REALM_ENABLE_ENCRYPTION
1,311,690✔
1713
    m_addr = mmap({f.m_fd, a, f.m_encryption.get()}, size, offset, m_encrypted_mapping);
1,311,690✔
1714
    if (observer && m_encrypted_mapping) {
1,311,690✔
1715
        m_encrypted_mapping->set_observer(observer);
387✔
1716
    }
387✔
1717
#else
1718
    std::unique_ptr<util::EncryptedFileMapping> dummy_encrypted_mapping;
1719
    m_addr = mmap({f.m_fd, a, nullptr}, size, offset, dummy_encrypted_mapping);
1720
    static_cast<void>(observer);
1721
#endif
1722
    m_size = m_reservation_size = size;
1,311,690✔
1723
    m_fd = f.m_fd;
1,311,690✔
1724
    m_offset = offset;
1,311,690✔
1725
    m_access_mode = a;
1,311,690✔
1726
}
1,311,690✔
1727

1728

1729
void File::MapBase::unmap() noexcept
1730
{
2,819,076✔
1731
    if (!m_addr)
2,819,076✔
1732
        return;
1,431,867✔
1733
    REALM_ASSERT(m_reservation_size);
1,387,209✔
1734
#if REALM_ENABLE_ENCRYPTION
1,387,209✔
1735
    m_encrypted_mapping = nullptr;
1,387,209✔
1736
#endif
1,387,209✔
1737
    munmap(m_addr, m_reservation_size);
1,387,209✔
1738
    m_addr = nullptr;
1,387,209✔
1739
    m_size = 0;
1,387,209✔
1740
    m_reservation_size = 0;
1,387,209✔
1741
}
1,387,209✔
1742

1743
bool File::MapBase::try_reserve(const File& file, AccessMode a, size_t size, SizeType offset,
1744
                                util::WriteObserver* observer)
1745
{
75,354✔
1746
#ifdef _WIN32
1747
    static_cast<void>(observer);
1748
    // unsupported for now
1749
    return false;
1750
#else
1751
    void* addr = ::mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
75,354✔
1752
    if (addr == MAP_FAILED)
75,354✔
1753
        return false;
×
1754
    m_addr = addr;
75,354✔
1755
    REALM_ASSERT(m_size == 0);
75,354✔
1756
    m_access_mode = a;
75,354✔
1757
    m_reservation_size = size;
75,354✔
1758
    m_fd = file.get_descriptor();
75,354✔
1759
    m_offset = offset;
75,354✔
1760
#if REALM_ENABLE_ENCRYPTION
75,354✔
1761
    if (file.m_encryption) {
75,354✔
1762
        m_encrypted_mapping = util::reserve_mapping(addr, {m_fd, a, file.m_encryption.get()}, offset);
285✔
1763
        if (observer) {
285✔
1764
            m_encrypted_mapping->set_observer(observer);
279✔
1765
        }
279✔
1766
    }
285✔
1767
#else
1768
    static_cast<void>(observer);
1769
#endif
1770
#endif
75,354✔
1771
    return true;
75,354✔
1772
}
75,354✔
1773

1774
bool File::MapBase::try_extend_to(size_t size) noexcept
1775
{
95,487✔
1776
    if (size > m_reservation_size) {
95,487✔
1777
        return false;
×
1778
    }
×
1779
#ifndef _WIN32
95,487✔
1780
    char* extension_start_addr = (char*)m_addr + m_size;
95,487✔
1781
    size_t extension_size = size - m_size;
95,487✔
1782
    size_t extension_start_offset = static_cast<size_t>(m_offset) + m_size;
95,487✔
1783
#if REALM_ENABLE_ENCRYPTION
95,487✔
1784
    if (m_encrypted_mapping) {
95,487✔
1785
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
318✔
1786
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
318✔
1787
        if (got_addr == MAP_FAILED)
318✔
1788
            return false;
×
1789
        REALM_ASSERT(got_addr == extension_start_addr);
318✔
1790
        m_size = size;
318✔
1791
        m_encrypted_mapping->extend_to(m_offset, size);
318✔
1792
        return true;
318✔
1793
    }
318✔
1794
#endif
95,169✔
1795
    try {
95,169✔
1796
        void* got_addr =
95,169✔
1797
            util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode, extension_start_offset);
95,169✔
1798
        if (got_addr == extension_start_addr) {
95,169✔
1799
            m_size = size;
95,091✔
1800
            return true;
95,091✔
1801
        }
95,091✔
1802
    }
95,169✔
1803
    catch (...) {
95,169✔
1804
        return false;
18✔
1805
    }
18✔
1806
#endif
×
1807
    return false;
60✔
1808
}
95,169✔
1809

1810
void File::MapBase::sync()
1811
{
1,403,361✔
1812
    REALM_ASSERT(m_addr);
1,403,361✔
1813
#if REALM_ENABLE_ENCRYPTION
1,403,361✔
1814
    if (m_encrypted_mapping) {
1,403,361✔
1815
        m_encrypted_mapping->sync();
1,656✔
1816
        return;
1,656✔
1817
    }
1,656✔
1818
#endif
1,401,705✔
1819

1820
    realm::util::msync(m_fd, m_addr, m_size);
1,401,705✔
1821
}
1,401,705✔
1822

1823
void File::MapBase::flush(bool skip_validate)
1824
{
1,563,633✔
1825
    REALM_ASSERT(m_addr);
1,563,633✔
1826
#if REALM_ENABLE_ENCRYPTION
1,563,633✔
1827
    if (m_encrypted_mapping) {
1,563,633✔
1828
        m_encrypted_mapping->flush(skip_validate);
142,686✔
1829
    }
142,686✔
1830
#else
1831
    static_cast<void>(skip_validate);
1832
#endif
1833
}
1,563,633✔
1834

1835
std::time_t File::last_write_time(const std::string& path)
1836
{
792✔
1837
#if REALM_HAVE_STD_FILESYSTEM
1838
    auto time = std::filesystem::last_write_time(u8path(path));
1839

1840
    using namespace std::chrono;
1841
#if __cplusplus >= 202002L
1842
    auto system_time = clock_cast<system_clock>(time);
1843
#else
1844
    auto system_time =
1845
        time_point_cast<system_clock::duration>(time - decltype(time)::clock::now() + system_clock::now());
1846
#endif
1847
    return system_clock::to_time_t(system_time);
1848
#else
1849
    struct stat statbuf;
792✔
1850
    if (::stat(path.c_str(), &statbuf) != 0) {
792✔
1851
        throw SystemError(errno, "stat() failed");
×
1852
    }
×
1853
    return statbuf.st_mtime;
792✔
1854
#endif
792✔
1855
}
792✔
1856

1857
File::SizeType File::get_free_space(const std::string& path)
1858
{
144✔
1859
#if REALM_HAVE_STD_FILESYSTEM
1860
    return std::filesystem::space(u8path(path)).available;
1861
#else
1862
    struct statvfs stat;
144✔
1863
    if (statvfs(path.c_str(), &stat) != 0) {
144✔
1864
        throw SystemError(errno, "statvfs() failed");
×
1865
    }
×
1866
    return SizeType(stat.f_bavail) * stat.f_bsize;
144✔
1867
#endif
144✔
1868
}
144✔
1869

1870
#if REALM_HAVE_STD_FILESYSTEM
1871

1872
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1873
{
1874
    std::error_code ec;
1875
    m_iterator = std::filesystem::directory_iterator(u8path(path), ec);
1876
    if (ec && (ec != std::errc::no_such_file_or_directory || !allow_missing))
1877
        throw std::filesystem::filesystem_error("directory_iterator::directory_iterator", u8path(path), ec);
1878
}
1879

1880
DirScanner::~DirScanner() = default;
1881

1882
bool DirScanner::next(std::string& name)
1883
{
1884
    const std::filesystem::directory_iterator end;
1885
    if (m_iterator == end)
1886
        return false;
1887
    name = path_to_string(m_iterator->path().filename());
1888
    m_iterator++;
1889
    return true;
1890
}
1891

1892
#elif !defined(_WIN32)
1893

1894
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1895
{
127,206✔
1896
    m_dirp = opendir(path.c_str());
127,206✔
1897
    if (!m_dirp) {
127,206✔
1898
        int err = errno; // Eliminate any risk of clobbering
7,269✔
1899
        if (allow_missing && err == ENOENT)
7,269✔
1900
            return;
7,269✔
1901

1902
        std::string msg = format_errno("opendir() failed: %1", err);
×
1903
        switch (err) {
×
1904
            case EACCES:
×
1905
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
1906
            case ENOENT:
×
1907
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
1908
            default:
×
1909
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
1910
        }
×
1911
    }
×
1912
}
127,206✔
1913

1914
DirScanner::~DirScanner() noexcept
1915
{
127,206✔
1916
    if (m_dirp) {
127,206✔
1917
        int r = closedir(m_dirp);
119,937✔
1918
        REALM_ASSERT_RELEASE(r == 0);
119,937✔
1919
    }
119,937✔
1920
}
127,206✔
1921

1922
bool DirScanner::next(std::string& name)
1923
{
350,268✔
1924
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
1925
    !defined(__EMSCRIPTEN__)
1926
#error "readdir() is not known to be thread-safe on this platform"
1927
#endif
1928

1929
    if (!m_dirp)
350,268✔
1930
        return false;
7,269✔
1931

1932
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
1933
// in 32-bits.
1934
#if REALM_HAVE_READDIR64
117,171✔
1935
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
234,537✔
1936
#else
1937
#define REALM_READDIR(...) readdir(__VA_ARGS__)
348,336✔
1938
#endif
225,828✔
1939

1940
    for (;;) {
582,873✔
1941
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
582,873✔
1942
        DirentPtr dirent;
582,873✔
1943
        do {
582,873✔
1944
            // readdir() signals both errors and end-of-stream by returning a
1945
            // null pointer. To distinguish between end-of-stream and errors,
1946
            // the manpage recommends setting errno specifically to 0 before
1947
            // calling it...
1948
            errno = 0;
582,873✔
1949

1950
            dirent = REALM_READDIR(m_dirp);
582,873✔
1951
        } while (!dirent && errno == EAGAIN);
582,873✔
1952

1953
        if (!dirent) {
582,873✔
1954
            if (errno != 0)
119,709✔
1955
                throw SystemError(errno, "readdir() failed");
×
1956
            return false; // End of stream
119,709✔
1957
        }
119,709✔
1958
        const char* name_1 = dirent->d_name;
463,164✔
1959
        std::string name_2 = name_1;
463,164✔
1960
        if (name_2 != "." && name_2 != "..") {
463,164✔
1961
            name = name_2;
223,290✔
1962
            return true;
223,290✔
1963
        }
223,290✔
1964
    }
463,164✔
1965
}
342,999✔
1966

1967
#else
1968

1969
DirScanner::DirScanner(const std::string&, bool)
1970
{
1971
    throw NotImplemented();
1972
}
1973

1974
DirScanner::~DirScanner() noexcept {}
1975

1976
bool DirScanner::next(std::string&)
1977
{
1978
    return false;
1979
}
1980

1981
#endif
1982

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