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

realm / realm-core / 2539

02 Aug 2024 08:26PM UTC coverage: 91.124% (+0.04%) from 91.088%
2539

push

Evergreen

web-flow
Fix a test failure when the default log level is too low (#7943)

This test was relying on the default log level being high enough that the
expected messages would actually be logged.

102788 of 181570 branches covered (56.61%)

1 of 1 new or added line in 1 file covered. (100.0%)

24 existing lines in 9 files now uncovered.

216879 of 238004 relevant lines covered (91.12%)

5621251.38 hits per line

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

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

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

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

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

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

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

51
using namespace realm::util;
52

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

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

81
#ifdef _WIN32
82

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

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

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

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

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

125
    HANDLE handle = INVALID_HANDLE_VALUE;
126
};
127

128
#endif
129

130
#if REALM_HAVE_STD_FILESYSTEM
131
#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
{
214,944✔
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)
214,944✔
208
        return true;
76,461✔
209

210
    int err = errno; // Eliminate any risk of clobbering
138,483✔
211
    if (err == EEXIST)
138,483✔
212
        return false;
138,474✔
213

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

216
    switch (err) {
9✔
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
    }
9✔
223
#endif
9✔
224
}
9✔
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,788✔
270
    if (try_remove_dir(path)) // Throws
10,788✔
271
        return;
10,770✔
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,788✔
276

277

278
bool try_remove_dir(const std::string& path)
279
{
117,384✔
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,384✔
287
        return true;
117,105✔
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,572✔
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,572✔
318
        bool allow_missing = true;
106,572✔
319
        DirScanner ds{path, allow_missing}; // Throws
106,572✔
320
        std::string name;
106,572✔
321
        while (ds.next(name)) {                              // Throws
300,573✔
322
            std::string subpath = File::resolve(name, path); // Throws
194,001✔
323
            if (File::is_dir(subpath)) {                     // Throws
194,001✔
324
                try_remove_dir_recursive(subpath);           // Throws
61,833✔
325
            }
61,833✔
326
            else {
132,168✔
327
                File::remove(subpath); // Throws
132,168✔
328
            }
132,168✔
329
        }
194,001✔
330
    }
106,572✔
331
    return try_remove_dir(path); // Throws
106,572✔
332
#endif
106,572✔
333
}
106,572✔
334

335

336
std::string make_temp_dir()
337
{
44,880✔
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,880✔
366
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
44,880✔
367
    if (!buffer.empty() && buffer.back() != '/') {
44,880✔
368
        buffer += "/";
44,880✔
369
    }
44,880✔
370
    buffer += "realm_XXXXXX";
44,880✔
371
#endif
44,880✔
372

373
    if (mkdtemp(buffer.data()) == 0) {
44,880✔
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,880✔
378
#endif
44,880✔
379
}
44,880✔
380

381
std::string make_temp_file(const char* prefix)
382
{
38,412✔
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,412✔
400
    std::string base_dir = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
38,412✔
401
    if (!base_dir.empty() && base_dir[base_dir.length() - 1] != '/') {
38,412✔
402
        base_dir += '/';
38,412✔
403
    }
38,412✔
404
#endif
38,412✔
405
    std::string filename = util::format("%1%2_XXXXXX", base_dir, prefix);
38,412✔
406
    auto fd = mkstemp(filename.data());
38,412✔
407
    if (fd == -1) {
38,412✔
408
        throw std::system_error(errno, std::system_category(), "mkstemp() failed"); // LCOV_EXCL_LINE
409
    }
×
410
    close(fd);
38,412✔
411
    return filename;
38,412✔
412
#endif
38,412✔
413
}
38,412✔
414

415
size_t page_size()
416
{
26,451,876✔
417
    static constexpr size_t c_min_supported_page_size = 4096;
26,451,876✔
418
    static size_t page_size = [] {
26,451,876✔
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,451,876✔
432
}
26,451,876✔
433

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

440
File::~File() noexcept
441
{
359,397✔
442
    close();
359,397✔
443
}
359,397✔
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
{
406,623✔
473
    REALM_ASSERT_RELEASE(!is_attached());
406,623✔
474
    m_path = path; // for error reporting and debugging
406,623✔
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;
406,623✔
542
    switch (a) {
406,623✔
543
        case access_ReadOnly:
2,967✔
544
            flags2 = O_RDONLY;
2,967✔
545
            break;
2,967✔
546
        case access_ReadWrite:
403,650✔
547
            flags2 = O_RDWR;
403,650✔
548
            break;
403,650✔
549
    }
406,623✔
550
    switch (c) {
406,617✔
551
        case create_Auto:
377,277✔
552
            flags2 |= O_CREAT;
377,277✔
553
            break;
377,277✔
554
        case create_Never:
28,506✔
555
            break;
28,506✔
556
        case create_Must:
840✔
557
            flags2 |= O_CREAT | O_EXCL;
840✔
558
            break;
840✔
559
    }
406,617✔
560
    if (flags & flag_Trunc)
406,617✔
561
        flags2 |= O_TRUNC;
654✔
562
    if (flags & flag_Append)
406,617✔
563
        flags2 |= O_APPEND;
151,674✔
564
    int fd = ::open(m_path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
406,617✔
565
    if (0 <= fd) {
406,617✔
566
        m_fd = fd;
406,509✔
567
        m_have_lock = false;
406,509✔
568
        if (success)
406,509✔
569
            *success = true;
204✔
570
        return;
406,509✔
571
    }
406,509✔
572

573
    int err = errno; // Eliminate any risk of clobbering
108✔
574
    if (success && err == EEXIST && c == create_Must) {
108✔
575
        *success = false;
30✔
576
        return;
30✔
577
    }
30✔
578
    if (success && err == ENOENT && c == create_Never) {
78!
579
        *success = false;
×
580
        return;
×
581
    }
×
582
    std::string msg = format_errno("Failed to open file at path '%2': %1", err, path);
78✔
583
    switch (err) {
78✔
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:
36✔
599
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
600
    }
78✔
601

602
#endif
78✔
603
}
78✔
604

605

606
void File::close() noexcept
607
{
762,228✔
608
    m_encryption.reset();
762,228✔
609
    if (m_fd == invalid_fd)
762,228✔
610
        return;
355,740✔
611
    if (m_have_lock)
406,488✔
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);
406,488✔
618
    REALM_ASSERT_RELEASE(r == 0);
406,488✔
619
#endif
406,488✔
620

621
    m_fd = invalid_fd;
406,488✔
622
}
406,488✔
623

624
size_t File::read_static(FileDesc fd, SizeType pos, char* data, size_t size)
625
{
879,717✔
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,717✔
652
    while (0 < size) {
1,759,233✔
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,822✔
655
        ssize_t r = pread(fd, data, n, pos);
879,822✔
656
        if (r == 0)
879,822✔
657
            break;
306✔
658
        if (r < 0)
879,516✔
659
            goto error; // LCOV_EXCL_LINE
660
        REALM_ASSERT_RELEASE(size_t(r) <= n);
879,516✔
661
        size -= size_t(r);
879,516✔
662
        data += size_t(r);
879,516✔
663
        pos += r;
879,516✔
664
    }
879,516✔
665
    return data - data_0;
879,717✔
666

667
error:
×
668
    // LCOV_EXCL_START
669
    throw SystemError(errno, "read() failed");
×
670
// LCOV_EXCL_STOP
671
#endif
879,717✔
672
}
879,717✔
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,426✔
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,248,735✔
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,309✔
720
        ssize_t r = pwrite(fd, data, n, pos);
624,309✔
721
        if (r < 0)
624,309✔
722
            goto error; // LCOV_EXCL_LINE
723
        REALM_ASSERT_RELEASE(r != 0);
624,309✔
724
        REALM_ASSERT_RELEASE(size_t(r) <= n);
624,309✔
725
        size -= size_t(r);
624,309✔
726
        data += size_t(r);
624,309✔
727
        pos += off_t(r);
624,309✔
728
    }
624,309✔
729
    return;
624,426✔
730

731
error:
624,426✔
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,438✔
746
    REALM_ASSERT_RELEASE(is_attached());
54,438✔
747

748
    if (m_encryption) {
54,438✔
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,568✔
757
}
53,568✔
758

759
File::SizeType File::get_file_pos()
760
{
6,864✔
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,864✔
773
    if (pos < 0) {
6,864✔
774
        throw SystemError(errno, "lseek() failed");
×
775
    }
×
776
    return SizeType(pos);
6,864✔
777
#endif
6,864✔
778
}
6,864✔
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,752,438✔
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,752,438✔
802
    if (::fstat(fd, &statbuf) == 0) {
1,752,519✔
803
        SizeType size;
1,752,519✔
804
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,752,519✔
805
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
806

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

811
#endif
1,752,438✔
812
}
1,752,438✔
813

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

819
    if (m_encryption) {
1,751,931✔
820
        return encrypted_size_to_data_size(size);
3,999✔
821
    }
3,999✔
822
    return size;
1,747,932✔
823
}
1,751,931✔
824

825

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

830
    if (m_encryption)
219,846✔
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;
219,846✔
846
    if (int_cast_with_overflow_detect(size, size2))
219,846✔
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) {
219,846✔
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
219,846✔
861
}
219,846✔
862

863

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

871
    SizeType new_size = size;
109,332✔
872
    if (m_encryption) {
109,332✔
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 = [&]() {
109,332✔
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
60,426✔
892
    // Mostly Linux only
893
    if (!prealloc_if_supported(0, new_size)) {
60,426✔
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,906✔
901
    if (::fstat(m_fd, &statbuf) != 0) {
48,906✔
902
        int err = errno;
903
        throw SystemError(err, "fstat() inside prealloc() failed");
904
    }
905

906
    SizeType allocated_size;
48,906✔
907
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
48,906✔
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,906✔
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,906✔
921
        off_t to_allocate = static_cast<off_t>(new_size - statbuf.st_size);
48,870✔
922
        fstore_t store = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, to_allocate, 0};
48,870✔
923
        int ret = 0;
48,870✔
924
        do {
48,870✔
925
            ret = fcntl(m_fd, F_PREALLOCATE, &store);
48,870✔
926
        } while (ret == -1 && errno == EINTR);
48,870!
927
        if (ret == -1) {
48,870✔
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,870✔
942

943
    int ret = 0;
48,906✔
944

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

949
    if (ret != 0) {
48,906✔
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
}
109,332✔
961

962

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

967
#if REALM_HAVE_POSIX_FALLOCATE
60,438✔
968

969
    REALM_ASSERT_RELEASE(is_prealloc_supported());
60,438✔
970

971
    if (size == 0) {
60,438✔
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;
60,438✔
980
    do {
60,438✔
981
        status = ::posix_fallocate(m_fd, offset, size);
60,438✔
982
    } while (status == EINTR);
60,438✔
983

984
    if (REALM_LIKELY(status == 0)) {
60,438✔
985
        return true;
60,438✔
986
    }
60,438✔
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;
×
UNCOV
1015
}
×
1016

1017

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

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

1033
void File::seek_static(FileDesc fd, SizeType position)
1034
{
6,870✔
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,870✔
1047
    if (int_cast_with_overflow_detect(position, position2))
6,870✔
1048
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1049

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

1054
#endif
6,870✔
1055
}
6,870✔
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
{
777✔
1063
    REALM_ASSERT_RELEASE(is_attached());
777✔
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)
474✔
1080
        return;
474✔
1081
    throw SystemError(errno, "fsync() failed");
1082

1083
#endif
474✔
1084
}
777✔
1085

1086
void File::barrier()
1087
{
336✔
1088
#if REALM_PLATFORM_APPLE
168✔
1089
    if (::fcntl(m_fd, F_BARRIERFSYNC) == 0)
168✔
1090
        return;
168✔
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();
168✔
1095
}
168✔
1096

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

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

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

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

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

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

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

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

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

1209
bool File::lock(bool exclusive, bool non_blocking)
1210
{
4,185,543✔
1211
    REALM_ASSERT_RELEASE(is_attached());
4,185,543✔
1212
    REALM_ASSERT_RELEASE(!m_have_lock);
4,185,543✔
1213

1214
#ifdef _WIN32 // Windows version
1215

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

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

1271
void File::unlock() noexcept
1272
{
3,991,482✔
1273
    if (!m_have_lock)
3,991,482✔
1274
        return;
×
1275

1276
#ifdef _WIN32 // Windows version
1277
    OVERLAPPED overlapped;
1278
    overlapped.hEvent = 0;
1279
    overlapped.OffsetHigh = 0;
1280
    overlapped.Offset = 0;
1281
    overlapped.Pointer = 0;
1282
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1283
    REALM_ASSERT_RELEASE(r);
1284
#else
1285
    _unlock(m_fd);
3,991,482✔
1286
#endif
3,991,482✔
1287
    m_have_lock = false;
3,991,482✔
1288
}
3,991,482✔
1289

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

1318
bool File::exists(const std::string& path)
1319
{
601,539✔
1320
#if REALM_HAVE_STD_FILESYSTEM
1321
    return std::filesystem::exists(u8path(path));
1322
#else // POSIX
1323
    if (::access(path.c_str(), F_OK) == 0)
601,539✔
1324
        return true;
31,266✔
1325
    int err = errno; // Eliminate any risk of clobbering
570,273✔
1326
    switch (err) {
570,273✔
1327
        case EACCES:
✔
1328
        case ENOENT:
570,246✔
1329
        case ENOTDIR:
570,246✔
1330
            return false;
570,246✔
1331
    }
570,273✔
1332
    throw SystemError(err, "access() failed");
24✔
1333
#endif
570,273✔
1334
}
570,273✔
1335

1336

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

1359

1360
void File::remove(const std::string& path)
1361
{
176,244✔
1362
    if (try_remove(path))
176,244✔
1363
        return;
176,229✔
1364
    int err = ENOENT;
15✔
1365
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
15✔
1366
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
15✔
1367
}
176,244✔
1368

1369

1370
bool File::try_remove(const std::string& path)
1371
{
238,392✔
1372
#if REALM_HAVE_STD_FILESYSTEM
1373
    std::error_code error;
1374
    bool result = std::filesystem::remove(u8path(path), error);
1375
    throwIfFileError(error, path);
1376
    return result;
1377
#else // POSIX
1378
    if (::unlink(path.c_str()) == 0)
238,392✔
1379
        return true;
198,450✔
1380

1381
    int err = errno; // Eliminate any risk of clobbering
39,942✔
1382
    if (err == ENOENT)
39,942✔
1383
        return false;
39,897✔
1384

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

1401

1402
void File::move(const std::string& old_path, const std::string& new_path)
1403
{
312✔
1404
#if REALM_HAVE_STD_FILESYSTEM
1405
    std::error_code error;
1406
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1407

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

1435

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

1453
    File origin_file{origin_path, mode_Read}; // Throws
204✔
1454
    File target_file;
204✔
1455
    bool did_create = false;
204✔
1456
    target_file.open(target_path, did_create); // Throws
204✔
1457
    if (!did_create && !overwrite_existing) {
204✔
1458
        return false;
6✔
1459
    }
6✔
1460

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

1472
    return true;
198✔
1473
#endif
204✔
1474
}
204✔
1475

1476

1477
bool File::is_same_file_static(FileDesc f1, FileDesc f2, const std::string& path1, const std::string& path2)
1478
{
24✔
1479
    return get_unique_id(f1, path1) == get_unique_id(f2, path2);
24✔
1480
}
24✔
1481

1482
bool File::is_same_file(const File& f) const
1483
{
24✔
1484
    REALM_ASSERT_RELEASE(is_attached());
24✔
1485
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1486
    return is_same_file_static(m_fd, f.m_fd, m_path, f.m_path);
24✔
1487
}
24✔
1488

1489
FileDesc File::dup_file_desc(FileDesc fd)
1490
{
×
1491
    FileDesc fd_duped;
×
1492
#ifdef _WIN32
1493
    if (!DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &fd_duped, 0, FALSE, DUPLICATE_SAME_ACCESS))
1494
        throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
1495
#else
1496
    fd_duped = dup(fd);
×
1497

1498
    if (fd_duped == -1) {
×
1499
        int err = errno; // Eliminate any risk of clobbering
×
1500
        throw std::system_error(err, std::system_category(), "dup() failed");
×
1501
    }
×
1502
#endif // conditonal on _WIN32
×
1503
    return fd_duped;
×
1504
}
×
1505

1506
FileDesc File::get_descriptor() const
1507
{
75,729✔
1508
    return m_fd;
75,729✔
1509
}
75,729✔
1510

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

1520
    if (fileHandle == INVALID_HANDLE_VALUE) {
1521
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1522
            return none;
1523
        }
1524
        throw SystemError(GetLastError(), "CreateFileW failed");
1525
    }
1526

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

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

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

1583
std::string File::get_path() const
1584
{
30✔
1585
    return m_path;
30✔
1586
}
30✔
1587

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

1621
        if (path_2.size() < 2 || path_2[1] != '.')
1622
            break;
1623
        if (path_2.size())
1624
    }
1625
    */
1626
    return base_dir_2 + path_2;
232,254✔
1627
#endif
232,260✔
1628
}
232,260✔
1629

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

1645
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1646
{
6✔
1647
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1648
}
6✔
1649

1650

1651
void File::set_encryption_key(const char* key)
1652
{
76,167✔
1653
#if REALM_ENABLE_ENCRYPTION
76,167✔
1654
    if (key) {
76,167✔
1655
        m_encryption = std::make_unique<util::EncryptedFile>(key, m_fd);
609✔
1656
    }
609✔
1657
    else {
75,558✔
1658
        m_encryption.reset();
75,558✔
1659
    }
75,558✔
1660
#else
1661
    if (key) {
1662
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1663
    }
1664
#endif
1665
}
76,167✔
1666

1667
EncryptedFile* File::get_encryption() const noexcept
1668
{
3,016,410✔
1669
#if REALM_ENABLE_ENCRYPTION
3,016,410✔
1670
    return m_encryption.get();
3,016,410✔
1671
#else
1672
    return nullptr;
1673
#endif
1674
}
3,016,410✔
1675

1676
File::MapBase::MapBase() noexcept = default;
1,475,769✔
1677
File::MapBase::~MapBase() noexcept
1678
{
1,627,203✔
1679
    unmap();
1,627,203✔
1680
}
1,627,203✔
1681

1682
File::MapBase::MapBase(MapBase&& other) noexcept
1683
{
151,251✔
1684
    *this = std::move(other);
151,251✔
1685
}
151,251✔
1686

1687
File::MapBase& File::MapBase::operator=(MapBase&& other) noexcept
1688
{
151,902✔
1689
    REALM_ASSERT(this != &other);
151,902✔
1690
    if (m_addr)
151,902✔
1691
        unmap();
×
1692
    m_addr = std::exchange(other.m_addr, nullptr);
151,902✔
1693
    m_size = std::exchange(other.m_size, 0);
151,902✔
1694
    m_access_mode = other.m_access_mode;
151,902✔
1695
    m_reservation_size = std::exchange(other.m_reservation_size, 0);
151,902✔
1696
    m_offset = std::exchange(other.m_offset, 0);
151,902✔
1697
    m_fd = std::exchange(other.m_fd, invalid_fd);
151,902✔
1698
#if REALM_ENABLE_ENCRYPTION
151,902✔
1699
    m_encrypted_mapping = std::move(other.m_encrypted_mapping);
151,902✔
1700
#endif
151,902✔
1701
    return *this;
151,902✔
1702
}
151,902✔
1703

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

1723

1724
void File::MapBase::unmap() noexcept
1725
{
2,834,208✔
1726
    if (!m_addr)
2,834,208✔
1727
        return;
1,436,883✔
1728
    REALM_ASSERT(m_reservation_size);
1,397,325✔
1729
#if REALM_ENABLE_ENCRYPTION
1,397,325✔
1730
    m_encrypted_mapping = nullptr;
1,397,325✔
1731
#endif
1,397,325✔
1732
    munmap(m_addr, m_reservation_size);
1,397,325✔
1733
    m_addr = nullptr;
1,397,325✔
1734
    m_size = 0;
1,397,325✔
1735
    m_reservation_size = 0;
1,397,325✔
1736
}
1,397,325✔
1737

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

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

1805
void File::MapBase::sync()
1806
{
1,407,378✔
1807
    REALM_ASSERT(m_addr);
1,407,378✔
1808
#if REALM_ENABLE_ENCRYPTION
1,407,378✔
1809
    if (m_encrypted_mapping) {
1,407,378✔
1810
        m_encrypted_mapping->sync();
1,620✔
1811
        return;
1,620✔
1812
    }
1,620✔
1813
#endif
1,405,758✔
1814

1815
    realm::util::msync(m_fd, m_addr, m_size);
1,405,758✔
1816
}
1,405,758✔
1817

1818
void File::MapBase::flush(bool skip_validate)
1819
{
1,566,264✔
1820
    REALM_ASSERT(m_addr);
1,566,264✔
1821
#if REALM_ENABLE_ENCRYPTION
1,566,264✔
1822
    if (m_encrypted_mapping) {
1,566,264✔
1823
        m_encrypted_mapping->flush(skip_validate);
142,650✔
1824
    }
142,650✔
1825
#else
1826
    static_cast<void>(skip_validate);
1827
#endif
1828
}
1,566,264✔
1829

1830
std::time_t File::last_write_time(const std::string& path)
1831
{
792✔
1832
#if REALM_HAVE_STD_FILESYSTEM
1833
    auto time = std::filesystem::last_write_time(u8path(path));
1834

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

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

1865
#if REALM_HAVE_STD_FILESYSTEM
1866

1867
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1868
{
1869
    std::error_code ec;
1870
    m_iterator = std::filesystem::directory_iterator(u8path(path), ec);
1871
    if (ec && (ec != std::errc::no_such_file_or_directory || !allow_missing))
1872
        throw std::filesystem::filesystem_error("directory_iterator::directory_iterator", u8path(path), ec);
1873
}
1874

1875
DirScanner::~DirScanner() = default;
1876

1877
bool DirScanner::next(std::string& name)
1878
{
1879
    const std::filesystem::directory_iterator end;
1880
    if (m_iterator == end)
1881
        return false;
1882
    name = path_to_string(m_iterator->path().filename());
1883
    m_iterator++;
1884
    return true;
1885
}
1886

1887
#elif !defined(_WIN32)
1888

1889
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1890
{
127,119✔
1891
    m_dirp = opendir(path.c_str());
127,119✔
1892
    if (!m_dirp) {
127,119✔
1893
        int err = errno; // Eliminate any risk of clobbering
7,239✔
1894
        if (allow_missing && err == ENOENT)
7,239✔
1895
            return;
7,239✔
1896

1897
        std::string msg = format_errno("opendir() failed: %1", err);
×
1898
        switch (err) {
×
1899
            case EACCES:
×
1900
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
1901
            case ENOENT:
×
1902
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
1903
            default:
×
1904
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
1905
        }
×
1906
    }
×
1907
}
127,119✔
1908

1909
DirScanner::~DirScanner() noexcept
1910
{
127,119✔
1911
    if (m_dirp) {
127,119✔
1912
        int r = closedir(m_dirp);
119,880✔
1913
        REALM_ASSERT_RELEASE(r == 0);
119,880✔
1914
    }
119,880✔
1915
}
127,119✔
1916

1917
bool DirScanner::next(std::string& name)
1918
{
349,986✔
1919
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
1920
    !defined(__EMSCRIPTEN__)
1921
#error "readdir() is not known to be thread-safe on this platform"
1922
#endif
1923

1924
    if (!m_dirp)
349,986✔
1925
        return false;
7,239✔
1926

1927
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
1928
// in 32-bits.
1929
#if REALM_HAVE_READDIR64
117,147✔
1930
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
234,459✔
1931
#else
1932
#define REALM_READDIR(...) readdir(__VA_ARGS__)
348,048✔
1933
#endif
225,600✔
1934

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

1945
            dirent = REALM_READDIR(m_dirp);
582,507✔
1946
        } while (!dirent && errno == EAGAIN);
582,507✔
1947

1948
        if (!dirent) {
582,507✔
1949
            if (errno != 0)
119,676✔
1950
                throw SystemError(errno, "readdir() failed");
×
1951
            return false; // End of stream
119,676✔
1952
        }
119,676✔
1953
        const char* name_1 = dirent->d_name;
462,831✔
1954
        std::string name_2 = name_1;
462,831✔
1955
        if (name_2 != "." && name_2 != "..") {
462,831✔
1956
            name = name_2;
223,071✔
1957
            return true;
223,071✔
1958
        }
223,071✔
1959
    }
462,831✔
1960
}
342,747✔
1961

1962
#else
1963

1964
DirScanner::DirScanner(const std::string&, bool)
1965
{
1966
    throw NotImplemented();
1967
}
1968

1969
DirScanner::~DirScanner() noexcept {}
1970

1971
bool DirScanner::next(std::string&)
1972
{
1973
    return false;
1974
}
1975

1976
#endif
1977

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