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

realm / realm-core / kenneth.geisshirt_16

26 Jun 2024 10:02AM UTC coverage: 90.961% (-0.02%) from 90.977%
kenneth.geisshirt_16

Pull #7841

Evergreen

kneth
if and not ifdef
Pull Request #7841: Building for Windows using C++20

102172 of 180382 branches covered (56.64%)

214709 of 236046 relevant lines covered (90.96%)

5762111.59 hits per line

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

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

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

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

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

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

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

51
using namespace realm::util;
52

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

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

81
#ifdef _WIN32
82

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

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

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

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

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

125
    HANDLE handle = INVALID_HANDLE_VALUE;
126
};
127

128
#endif
129

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

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

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

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

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

165
} // anonymous namespace
166

167

168
namespace realm::util {
169
namespace {
170

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

181
} // anonymous namespace
182

183

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

195
    int err = errno; // Eliminate any risk of clobbering
137,619✔
196
    if (err == EEXIST)
137,619✔
197
        return false;
137,613✔
198

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

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

211

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

219

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

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

252

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

262

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

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

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

293

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

320

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

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

336
        std::error_code error;
337
        std::filesystem::create_directory(path, error);
338
        if (error && error != std::errc::file_exists) {
339
            throw SystemError(error, util::format("Failed to create temporary directory: %1", error.message()));
340
        }
341
        break;
342
    }
343
#if __cplusplus < 202002L
344
    return path.u8string();
345
#else
346
    return path.string();
347
#endif
348

349
#else // POSIX.1-2008 version
350

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

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

370
std::string make_temp_file(const char* prefix)
371
{
38,388✔
372
#ifdef _WIN32 // Windows version
373
    std::filesystem::path temp = std::filesystem::temp_directory_path();
374

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

381
#if __cplusplus < 202002L
382
    return std::filesystem::path(buffer).u8string();
383
#else
384
    return std::filesystem::path(buffer).string();
385
#endif
386

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

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

408
size_t page_size()
409
{
26,615,691✔
410
    static constexpr size_t c_min_supported_page_size = 4096;
26,615,691✔
411
    static size_t page_size = [] {
26,615,691✔
412
#ifdef _WIN32
413
        SYSTEM_INFO sysinfo;
414
        GetNativeSystemInfo(&sysinfo);
415
        // DWORD size = sysinfo.dwPageSize;
416
        // On windows we use the allocation granularity instead
417
        DWORD size = sysinfo.dwAllocationGranularity;
418
#else
419
        long size = sysconf(_SC_PAGESIZE);
24✔
420
#endif
24✔
421
        REALM_ASSERT(size > 0 && size % c_min_supported_page_size == 0);
24✔
422
        return static_cast<size_t>(size);
24✔
423
    }();
24✔
424
    return page_size;
26,615,691✔
425
}
26,615,691✔
426

427
File::File() = default;
437,985✔
428
File::File(std::string_view path, Mode m)
429
{
2,847✔
430
    open(path, m);
2,847✔
431
}
2,847✔
432

433
File::~File() noexcept
434
{
440,856✔
435
    close();
440,856✔
436
}
440,856✔
437

438
File::File(File&& f) noexcept
439
{
12✔
440
    m_fd = std::exchange(f.m_fd, invalid_fd);
12✔
441
#ifdef REALM_FILELOCK_EMULATION
442
    m_pipe_fd = std::exchange(f.m_pipe_fd, invalid_fd);
443
    m_has_exclusive_lock = std::exchange(f.m_has_exclusive_lock, false);
444
#endif
445
    m_have_lock = std::exchange(f.m_have_lock, false);
12✔
446
    m_encryption = std::move(f.m_encryption);
12✔
447
}
12✔
448

449
File& File::operator=(File&& f) noexcept
450
{
6✔
451
    close();
6✔
452

453
    m_fd = std::exchange(f.m_fd, invalid_fd);
6✔
454
#ifdef REALM_FILELOCK_EMULATION
455
    m_pipe_fd = std::exchange(f.m_pipe_fd, invalid_fd);
456
    m_has_exclusive_lock = std::exchange(f.m_has_exclusive_lock, false);
457
#endif
458
    m_have_lock = std::exchange(f.m_have_lock, false);
6✔
459
    m_encryption = std::move(f.m_encryption);
6✔
460
    return *this;
6✔
461
}
6✔
462

463

464
void File::open_internal(std::string_view path, AccessMode a, CreateMode c, int flags, bool* success)
465
{
463,827✔
466
    REALM_ASSERT_RELEASE(!is_attached());
463,827✔
467
    m_path = path; // for error reporting and debugging
463,827✔
468

469
#ifdef _WIN32 // Windows version
470

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

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

532
#else // POSIX version
533

534
    int flags2 = 0;
463,827✔
535
    switch (a) {
463,827✔
536
        case access_ReadOnly:
2,967✔
537
            flags2 = O_RDONLY;
2,967✔
538
            break;
2,967✔
539
        case access_ReadWrite:
460,923✔
540
            flags2 = O_RDWR;
460,923✔
541
            break;
460,923✔
542
    }
463,827✔
543
    switch (c) {
463,890✔
544
        case create_Auto:
434,091✔
545
            flags2 |= O_CREAT;
434,091✔
546
            break;
434,091✔
547
        case create_Never:
28,965✔
548
            break;
28,965✔
549
        case create_Must:
828✔
550
            flags2 |= O_CREAT | O_EXCL;
828✔
551
            break;
828✔
552
    }
463,890✔
553
    if (flags & flag_Trunc)
463,875✔
554
        flags2 |= O_TRUNC;
654✔
555
    if (flags & flag_Append)
463,875✔
556
        flags2 |= O_APPEND;
187,035✔
557
    int fd = ::open(m_path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
463,875✔
558
    if (0 <= fd) {
463,875✔
559
        m_fd = fd;
463,740✔
560
        m_have_lock = false;
463,740✔
561
        if (success)
463,740✔
562
            *success = true;
204✔
563
        return;
463,740✔
564
    }
463,740✔
565

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

595
#endif
105✔
596
}
105✔
597

598

599
void File::close() noexcept
600
{
900,873✔
601
    m_encryption.reset();
900,873✔
602
    if (m_fd == invalid_fd)
900,873✔
603
        return;
437,211✔
604
    if (m_have_lock)
463,662✔
605
        unlock();
153✔
606
#ifdef _WIN32 // Windows version
607
    BOOL r = CloseHandle(m_fd);
608
    REALM_ASSERT_RELEASE(r);
609
#else // POSIX version
610
    int r = ::close(m_fd);
463,662✔
611
    REALM_ASSERT_RELEASE(r == 0);
463,662✔
612
#endif
463,662✔
613

614
    m_fd = invalid_fd;
463,662✔
615
}
463,662✔
616

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

642
#else // POSIX version
643

644
    char* const data_0 = data;
880,035✔
645
    while (0 < size) {
1,759,872✔
646
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
647
        size_t n = std::min(size, size_t(SSIZE_MAX));
880,131✔
648
        ssize_t r = pread(fd, data, n, pos);
880,131✔
649
        if (r == 0)
880,131✔
650
            break;
294✔
651
        if (r < 0)
879,837✔
652
            goto error; // LCOV_EXCL_LINE
653
        REALM_ASSERT_RELEASE(size_t(r) <= n);
879,837✔
654
        size -= size_t(r);
879,837✔
655
        data += size_t(r);
879,837✔
656
        pos += r;
879,837✔
657
    }
879,837✔
658
    return data - data_0;
880,035✔
659

660
error:
×
661
    // LCOV_EXCL_START
662
    throw SystemError(errno, "read() failed");
×
663
// LCOV_EXCL_STOP
664
#endif
880,035✔
665
}
880,035✔
666

667

668
size_t File::read(SizeType pos, char* data, size_t size)
669
{
7,032✔
670
    REALM_ASSERT_RELEASE(is_attached());
7,032✔
671

672
    if (m_encryption) {
7,032✔
673
        Map<char> read_map(*this, pos, access_ReadOnly, size);
×
674
        util::encryption_read_barrier(read_map, 0, size);
×
675
        memcpy(data, read_map.get_addr(), size);
×
676
        return size;
×
677
    }
×
678

679
    return read_static(m_fd, pos, data, size);
7,032✔
680
}
7,032✔
681

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

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

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

734
#endif
×
735
}
×
736

737
void File::write(SizeType pos, const char* data, size_t size)
738
{
53,970✔
739
    REALM_ASSERT_RELEASE(is_attached());
53,970✔
740

741
    if (m_encryption) {
53,970✔
742
        Map<char> write_map(*this, pos, access_ReadWrite, size);
858✔
743
        util::encryption_read_barrier(write_map, 0, size);
858✔
744
        memcpy(write_map.get_addr(), data, size);
858✔
745
        realm::util::encryption_write_barrier(write_map, 0, size);
858✔
746
        return;
858✔
747
    }
858✔
748

749
    write_static(m_fd, pos, data, size);
53,112✔
750
}
53,112✔
751

752
File::SizeType File::get_file_pos()
753
{
6,852✔
754
#ifdef _WIN32
755
    LONG high_dword = 0;
756
    LARGE_INTEGER li;
757
    LARGE_INTEGER res;
758
    li.QuadPart = 0;
759
    bool ok = SetFilePointerEx(m_fd, li, &res, FILE_CURRENT);
760
    if (!ok)
761
        throw SystemError(GetLastError(), "SetFilePointer() failed");
762

763
    return SizeType(res.QuadPart);
764
#else
765
    auto pos = lseek(m_fd, 0, SEEK_CUR);
6,852✔
766
    if (pos < 0) {
6,852✔
767
        throw SystemError(errno, "lseek() failed");
×
768
    }
×
769
    return SizeType(pos);
6,852✔
770
#endif
6,852✔
771
}
6,852✔
772

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

779
File::SizeType File::get_size_static(FileDesc fd)
780
{
1,852,077✔
781
#ifdef _WIN32
782
    LARGE_INTEGER large_int;
783
    if (GetFileSizeEx(fd, &large_int)) {
784
        File::SizeType size;
785
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
786
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
787

788
        return size;
789
    }
790
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
791

792
#else // POSIX version
793

794
    struct stat statbuf;
1,852,077✔
795
    if (::fstat(fd, &statbuf) == 0) {
1,852,167✔
796
        SizeType size;
1,852,167✔
797
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,852,167✔
798
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
799

800
        return size;
1,852,167✔
801
    }
1,852,167✔
802
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
803

804
#endif
1,852,077✔
805
}
1,852,077✔
806

807
File::SizeType File::get_size() const
808
{
1,851,609✔
809
    REALM_ASSERT_RELEASE(is_attached());
1,851,609✔
810
    File::SizeType size = get_size_static(m_fd);
1,851,609✔
811

812
    if (m_encryption) {
1,851,609✔
813
        return encrypted_size_to_data_size(size);
4,083✔
814
    }
4,083✔
815
    return size;
1,847,526✔
816
}
1,851,609✔
817

818

819
void File::resize(SizeType size)
820
{
278,541✔
821
    REALM_ASSERT_RELEASE(is_attached());
278,541✔
822

823
    if (m_encryption)
278,541✔
824
        size = data_size_to_encrypted_size(size);
150✔
825

826
#ifdef _WIN32 // Windows version
827
    FILE_END_OF_FILE_INFO info;
828
    info.EndOfFile.QuadPart = size;
829
    if (!SetFileInformationByHandle(m_fd, FileEndOfFileInfo, &info, sizeof(info))) {
830
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
831
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
832
            throw OutOfDiskSpace(get_last_error_msg("SetFileInformationByHandle() failed: ", err));
833
        }
834
        throw SystemError(int(err), "SetFileInformationByHandle() failed");
835
    }
836
#else // POSIX version
837

838
    off_t size2;
278,541✔
839
    if (int_cast_with_overflow_detect(size, size2))
278,541✔
840
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
841

842
    // POSIX specifies that introduced bytes read as zero. This is not
843
    // required by File::resize().
844
    if (::ftruncate(m_fd, size2) != 0) {
278,541✔
845
        int err = errno; // Eliminate any risk of clobbering
×
846
        auto msg = format_errno("ftruncate() failed: %1", err);
×
847
        if (err == ENOSPC || err == EDQUOT) {
×
848
            throw OutOfDiskSpace(msg);
×
849
        }
×
850
        throw SystemError(err, msg);
×
851
    }
×
852

853
#endif
278,541✔
854
}
278,541✔
855

856

857
void File::prealloc(SizeType size)
858
{
170,688✔
859
    REALM_ASSERT_RELEASE(is_attached());
170,688✔
860
    if (size <= get_size()) {
170,688✔
861
        return;
39,567✔
862
    }
39,567✔
863

864
    SizeType new_size = size;
131,121✔
865
    if (m_encryption) {
131,121✔
866
        new_size = data_size_to_encrypted_size(size);
645✔
867
        REALM_ASSERT(new_size > size);
645✔
868
        REALM_ASSERT(size == encrypted_size_to_data_size(new_size));
645✔
869
    }
645✔
870

871
    auto manually_consume_space = [&]() {
131,121✔
872
        constexpr uint16_t chunk_size = 4096;
×
873
        SizeType write_pos = get_size_static(m_fd); // raw size
×
874
        SizeType num_bytes = new_size - write_pos;
×
875
        std::string zeros(chunk_size, '\0');
×
876
        while (num_bytes > 0) {
×
877
            uint16_t t = uint16_t(std::min<SizeType>(num_bytes, chunk_size));
×
878
            write_static(m_fd, write_pos, zeros.c_str(), t);
×
879
            num_bytes -= t;
×
880
            write_pos += t;
×
881
        }
×
882
    };
×
883

884
#if REALM_HAVE_POSIX_FALLOCATE
71,136✔
885
    // Mostly Linux only
886
    if (!prealloc_if_supported(0, new_size)) {
71,136✔
887
        manually_consume_space();
888
    }
889
#elif REALM_PLATFORM_APPLE // Non-atomic fallback
890
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
891
    // ftruncate().
892

893
    struct stat statbuf;
59,985✔
894
    if (::fstat(m_fd, &statbuf) != 0) {
59,985✔
895
        int err = errno;
896
        throw SystemError(err, "fstat() inside prealloc() failed");
897
    }
898

899
    SizeType allocated_size;
59,985✔
900
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
59,985✔
901
        throw RuntimeError(ErrorCodes::RangeError,
902
                           util::format("Overflow on block conversion to SizeType %1", statbuf.st_blocks));
903
    }
904
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
59,985✔
905
        throw RuntimeError(ErrorCodes::RangeError,
906
                           util::format("Overflow computing existing file space allocation blocks: %1 block size %2",
907
                                        allocated_size, S_BLKSIZE));
908
    }
909

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

936
    int ret = 0;
59,985✔
937

938
    do {
59,985✔
939
        ret = ftruncate(m_fd, new_size);
59,985✔
940
    } while (ret == -1 && errno == EINTR);
59,985!
941

942
    if (ret != 0) {
59,985✔
943
        int err = errno;
944
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
945
        // so this is some other runtime error and not OutOfDiskSpace
946
        throw SystemError(err, "ftruncate() inside prealloc() failed");
947
    }
948
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
949
    manually_consume_space();
950
#else
951
#error Please check if/how your OS supports file preallocation
952
#endif // REALM_HAVE_POSIX_FALLOCATE
953
}
131,121✔
954

955

956
bool File::prealloc_if_supported(SizeType offset, SizeType size)
957
{
71,136✔
958
    REALM_ASSERT_RELEASE(is_attached());
71,136✔
959

960
#if REALM_HAVE_POSIX_FALLOCATE
71,136✔
961

962
    REALM_ASSERT_RELEASE(is_prealloc_supported());
71,136✔
963

964
    if (size == 0) {
71,136✔
965
        // calling posix_fallocate with a size of 0 will cause a return of EINVAL
966
        // since this is a meaningless operation anyway, we just return early here
967
        return true;
968
    }
969

970
    // posix_fallocate() does not set errno, it returns the error (if any) or zero.
971
    // It is also possible for it to be interrupted by EINTR according to some man pages (ex fedora 24)
972
    int status;
71,136✔
973
    do {
71,136✔
974
        status = ::posix_fallocate(m_fd, offset, size);
71,136✔
975
    } while (status == EINTR);
71,136✔
976

977
    if (REALM_LIKELY(status == 0)) {
71,136✔
978
        return true;
71,136✔
979
    }
71,136✔
980

981
    if (status == EINVAL || status == EPERM || status == EOPNOTSUPP) {
×
982
        return false; // Retry with non-atomic version
983
    }
984

985
    auto msg = format_errno("posix_fallocate() failed: %1", status);
986
    if (status == ENOSPC || status == EDQUOT) {
×
987
        throw OutOfDiskSpace(msg);
988
    }
989
    throw SystemError(status, msg);
990

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

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

999
#else
1000

1001
    static_cast<void>(offset);
1002
    static_cast<void>(size);
1003

1004
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1005

1006
#endif
1007
    return false;
×
1008
}
×
1009

1010

1011
bool File::is_prealloc_supported()
1012
{
71,136✔
1013
#if REALM_HAVE_POSIX_FALLOCATE
71,136✔
1014
    return true;
71,136✔
1015
#else
1016
    return false;
1017
#endif
1018
}
71,136✔
1019

1020
void File::seek(SizeType position)
1021
{
6,858✔
1022
    REALM_ASSERT_RELEASE(is_attached());
6,858✔
1023
    seek_static(m_fd, position);
6,858✔
1024
}
6,858✔
1025

1026
void File::seek_static(FileDesc fd, SizeType position)
1027
{
6,858✔
1028
#ifdef _WIN32 // Windows version
1029

1030
    LARGE_INTEGER large_int;
1031
    if (int_cast_with_overflow_detect(position, large_int.QuadPart))
1032
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1033

1034
    if (!SetFilePointerEx(fd, large_int, 0, FILE_BEGIN))
1035
        throw SystemError(GetLastError(), "SetFilePointerEx() failed");
1036

1037
#else // POSIX version
1038

1039
    off_t position2;
6,858✔
1040
    if (int_cast_with_overflow_detect(position, position2))
6,858✔
1041
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1042

1043
    if (0 <= ::lseek(fd, position2, SEEK_SET))
6,858✔
1044
        return;
6,858✔
1045
    throw SystemError(errno, "lseek() failed");
×
1046

1047
#endif
6,858✔
1048
}
6,858✔
1049

1050
// FIXME: The current implementation may not guarantee that data is
1051
// actually written to disk. POSIX is rather vague on what fsync() has
1052
// to do unless _POSIX_SYNCHRONIZED_IO is defined. See also
1053
// http://www.humboldt.co.uk/2009/03/fsync-across-platforms.html.
1054
void File::sync()
1055
{
783✔
1056
    REALM_ASSERT_RELEASE(is_attached());
783✔
1057

1058
#if defined _WIN32 // Windows version
1059

1060
    if (FlushFileBuffers(m_fd))
1061
        return;
1062
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1063

1064
#elif REALM_PLATFORM_APPLE
1065

1066
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
303✔
1067
        return;
303✔
1068
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1069

1070
#else // POSIX version
1071

1072
    if (::fsync(m_fd) == 0)
480✔
1073
        return;
480✔
1074
    throw SystemError(errno, "fsync() failed");
1075

1076
#endif
480✔
1077
}
783✔
1078

1079
void File::barrier()
1080
{
348✔
1081
#if REALM_PLATFORM_APPLE
174✔
1082
    if (::fcntl(m_fd, F_BARRIERFSYNC) == 0)
174✔
1083
        return;
174✔
1084
        // If fcntl fails, we fallback to full sync.
1085
        // This is known to occur on exFAT which does not support F_BARRIERSYNC.
1086
#endif
1087
    sync();
174✔
1088
}
174✔
1089

1090
#ifndef _WIN32
1091
// little helper
1092
static void _unlock(int m_fd)
1093
{
3,988,077✔
1094
    int r;
3,988,077✔
1095
    do {
3,988,077✔
1096
        r = flock(m_fd, LOCK_UN);
3,988,077✔
1097
    } while (r != 0 && errno == EINTR);
3,988,077!
1098
    if (r) {
3,988,077✔
1099
        throw SystemError(errno, "File::unlock() has failed");
×
1100
    }
×
1101
}
3,988,077✔
1102
#endif
1103

1104
bool File::rw_lock(bool exclusive, bool non_blocking)
1105
{
387,561✔
1106
    // exclusive blocking rw locks not implemented for emulation
1107
    REALM_ASSERT(!exclusive || non_blocking);
387,561✔
1108

1109
#ifndef REALM_FILELOCK_EMULATION
387,561✔
1110
    return lock(exclusive, non_blocking);
387,561✔
1111
#else
1112
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1113

1114
    // First obtain an exclusive lock on the file proper
1115
    int operation = LOCK_EX;
1116
    if (non_blocking)
1117
        operation |= LOCK_NB;
1118
    int status;
1119
    do {
1120
        status = flock(m_fd, operation);
1121
    } while (status != 0 && errno == EINTR);
1122
    if (status != 0 && errno == EWOULDBLOCK)
1123
        return false;
1124
    if (status != 0)
1125
        throw SystemError(errno, "flock() failed");
1126
    m_has_exclusive_lock = true;
1127

1128
    // Every path through this function except for successfully acquiring an
1129
    // exclusive lock needs to release the flock() before returning.
1130
    UnlockGuard ulg(*this);
1131

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

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

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

1163
            // Otherwise we got an unexpected error
1164
            throw std::system_error(err, std::system_category(), "opening lock fifo for writing failed");
1165
        }
1166

1167
        if (err == ENOENT) {
1168
            // The fifo doesn't exist and we're opening in shared mode, so we
1169
            // need to create it.
1170
            if (!m_fifo_dir_path.empty())
1171
                try_make_dir(m_fifo_dir_path);
1172
            status = mkfifo(m_fifo_path.c_str(), 0666);
1173
            if (status != 0)
1174
                throw std::system_error(errno, std::system_category(), "creating lock fifo for reading failed");
1175

1176
            // Try again to open the fifo now that it exists
1177
            fd = ::open(m_fifo_path.c_str(), mode);
1178
            err = errno;
1179
        }
1180

1181
        if (fd == -1)
1182
            throw std::system_error(err, std::system_category(), "opening lock fifo for reading failed");
1183
    }
1184

1185
    // We successfully opened the pipe. If we're trying to acquire an exclusive
1186
    // lock that means there's a reader (aka a shared lock) and we've failed.
1187
    // Release the exclusive lock and back out.
1188
    if (exclusive) {
1189
        ::close(fd);
1190
        return false;
1191
    }
1192

1193
    // We're in shared mode, so opening the fifo means we've successfully acquired
1194
    // a shared lock and are done.
1195
    ulg.release();
1196
    rw_unlock();
1197
    m_pipe_fd = fd;
1198
    return true;
1199
#endif // REALM_FILELOCK_EMULATION
1200
}
387,561✔
1201

1202
bool File::lock(bool exclusive, bool non_blocking)
1203
{
4,119,831✔
1204
    REALM_ASSERT_RELEASE(is_attached());
4,119,831✔
1205
    REALM_ASSERT_RELEASE(!m_have_lock);
4,119,831✔
1206

1207
#ifdef _WIN32 // Windows version
1208

1209
    // Under Windows a file lock must be explicitely released before
1210
    // the file is closed. It will eventually be released by the
1211
    // system, but there is no guarantees on the timing.
1212

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

1264
void File::unlock() noexcept
1265
{
3,988,317✔
1266
    if (!m_have_lock)
3,988,317✔
1267
        return;
×
1268

1269
#ifdef _WIN32 // Windows version
1270
    OVERLAPPED overlapped;
1271
    overlapped.hEvent = 0;
1272
    overlapped.OffsetHigh = 0;
1273
    overlapped.Offset = 0;
1274
    overlapped.Pointer = 0;
1275
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1276
    REALM_ASSERT_RELEASE(r);
1277
#else
1278
    _unlock(m_fd);
3,988,317✔
1279
#endif
3,988,317✔
1280
    m_have_lock = false;
3,988,317✔
1281
}
3,988,317✔
1282

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

1311
bool File::exists(const std::string& path)
1312
{
934,644✔
1313
#if REALM_HAVE_STD_FILESYSTEM
1314
    return std::filesystem::exists(u8path(path));
1315
#else // POSIX
1316
    if (::access(path.c_str(), F_OK) == 0)
934,644✔
1317
        return true;
30,756✔
1318
    int err = errno; // Eliminate any risk of clobbering
903,888✔
1319
    switch (err) {
903,888✔
1320
        case EACCES:
✔
1321
        case ENOENT:
903,873✔
1322
        case ENOTDIR:
903,873✔
1323
            return false;
903,873✔
1324
    }
903,888✔
1325
    throw SystemError(err, "access() failed");
24✔
1326
#endif
903,888✔
1327
}
903,888✔
1328

1329

1330
bool File::is_dir(const std::string& path)
1331
{
231,924✔
1332
#if REALM_HAVE_STD_FILESYSTEM
1333
    return std::filesystem::is_directory(u8path(path));
1334
#elif !defined(_WIN32)
1335
    struct stat statbuf;
1336
    if (::stat(path.c_str(), &statbuf) == 0)
231,924✔
1337
        return S_ISDIR(statbuf.st_mode);
224,955✔
1338
    int err = errno; // Eliminate any risk of clobbering
6,969✔
1339
    switch (err) {
6,969✔
1340
        case EACCES:
✔
1341
        case ENOENT:
6,969✔
1342
        case ENOTDIR:
6,969✔
1343
            return false;
6,969✔
1344
    }
6,969✔
1345
    throw SystemError(err, "stat() failed");
×
1346
#else
1347
    static_cast<void>(path);
1348
    throw NotImplemented();
1349
#endif
1350
}
6,969✔
1351

1352

1353
void File::remove(const std::string& path)
1354
{
174,477✔
1355
    if (try_remove(path))
174,477✔
1356
        return;
174,462✔
1357
    int err = ENOENT;
15✔
1358
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
15✔
1359
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
15✔
1360
}
174,477✔
1361

1362

1363
bool File::try_remove(const std::string& path)
1364
{
235,920✔
1365
#if REALM_HAVE_STD_FILESYSTEM
1366
    std::error_code error;
1367
    bool result = std::filesystem::remove(u8path(path), error);
1368
    throwIfFileError(error, path);
1369
    return result;
1370
#else // POSIX
1371
    if (::unlink(path.c_str()) == 0)
235,920✔
1372
        return true;
196,221✔
1373

1374
    int err = errno; // Eliminate any risk of clobbering
39,699✔
1375
    if (err == ENOENT)
39,699✔
1376
        return false;
39,657✔
1377

1378
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
42✔
1379
    switch (err) {
42✔
1380
        case EACCES:
✔
1381
        case EROFS:
✔
1382
        case ETXTBSY:
✔
1383
        case EBUSY:
✔
1384
        case EPERM:
21✔
1385
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
21✔
1386
        case ENOENT:
✔
1387
            return false;
×
1388
        default:
24✔
1389
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
24✔
1390
    }
42✔
1391
#endif
42✔
1392
}
42✔
1393

1394

1395
void File::move(const std::string& old_path, const std::string& new_path)
1396
{
300✔
1397
#if REALM_HAVE_STD_FILESYSTEM
1398
    std::error_code error;
1399
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1400

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

1428

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

1446
    File origin_file{origin_path, mode_Read}; // Throws
204✔
1447
    File target_file;
204✔
1448
    bool did_create = false;
204✔
1449
    target_file.open(target_path, did_create); // Throws
204✔
1450
    if (!did_create && !overwrite_existing) {
204✔
1451
        return false;
6✔
1452
    }
6✔
1453

1454
    size_t buffer_size = 4096;
198✔
1455
    off_t pos = 0;
198✔
1456
    auto buffer = std::make_unique<char[]>(buffer_size); // Throws
198✔
1457
    for (;;) {
576✔
1458
        size_t n = origin_file.read(pos, buffer.get(), buffer_size); // Throws
576✔
1459
        target_file.write(pos, buffer.get(), n);                     // Throws
576✔
1460
        pos += n;
576✔
1461
        if (n < buffer_size)
576✔
1462
            break;
198✔
1463
    }
576✔
1464

1465
    return true;
198✔
1466
#endif
204✔
1467
}
204✔
1468

1469

1470
bool File::is_same_file_static(FileDesc f1, FileDesc f2, const std::string& path1, const std::string& path2)
1471
{
24✔
1472
    return get_unique_id(f1, path1) == get_unique_id(f2, path2);
24✔
1473
}
24✔
1474

1475
bool File::is_same_file(const File& f) const
1476
{
24✔
1477
    REALM_ASSERT_RELEASE(is_attached());
24✔
1478
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1479
    return is_same_file_static(m_fd, f.m_fd, m_path, f.m_path);
24✔
1480
}
24✔
1481

1482
FileDesc File::dup_file_desc(FileDesc fd)
1483
{
×
1484
    FileDesc fd_duped;
×
1485
#ifdef _WIN32
1486
    if (!DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &fd_duped, 0, FALSE, DUPLICATE_SAME_ACCESS))
1487
        throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
1488
#else
1489
    fd_duped = dup(fd);
×
1490

1491
    if (fd_duped == -1) {
×
1492
        int err = errno; // Eliminate any risk of clobbering
×
1493
        throw std::system_error(err, std::system_category(), "dup() failed");
×
1494
    }
×
1495
#endif // conditonal on _WIN32
×
1496
    return fd_duped;
×
1497
}
×
1498

1499
FileDesc File::get_descriptor() const
1500
{
98,862✔
1501
    return m_fd;
98,862✔
1502
}
98,862✔
1503

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

1513
    if (fileHandle == INVALID_HANDLE_VALUE) {
1514
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1515
            return none;
1516
        }
1517
        throw SystemError(GetLastError(), "CreateFileW failed");
1518
    }
1519

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

1542
File::UniqueID File::get_unique_id(FileDesc file, const std::string& debug_path)
1543
{
48✔
1544
#ifdef _WIN32 // Windows version
1545
    REALM_ASSERT(file != nullptr);
1546
    File::UniqueID ret;
1547
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1548
        throw std::system_error(GetLastError(), std::system_category(),
1549
                                util::format("GetFileInformationByHandleEx() failed for '%1'", debug_path));
1550
    }
1551

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

1576
std::string File::get_path() const
1577
{
30✔
1578
    return m_path;
30✔
1579
}
30✔
1580

1581
std::string File::resolve(const std::string& path, const std::string& base_dir)
1582
{
229,884✔
1583
#if REALM_HAVE_STD_FILESYSTEM
1584
#if __cplusplus < 202002L
1585
    return (u8path(base_dir) / u8path(path)).lexically_normal().u8string();
1586
#else
1587
    return (u8path(base_dir) / u8path(path)).lexically_normal().string();
1588
#endif
1589
#else
1590
    char dir_sep = '/';
229,884✔
1591
    std::string path_2 = path;
229,884✔
1592
    std::string base_dir_2 = base_dir;
229,884✔
1593
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
229,884✔
1594
    if (is_absolute)
229,884✔
1595
        return path_2;
6✔
1596
    if (path_2.empty())
229,878✔
1597
        path_2 = ".";
6✔
1598
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
229,878✔
1599
        base_dir_2.push_back(dir_sep);
228,300✔
1600
    /*
1601
    // Abbreviate
1602
    for (;;) {
1603
        if (base_dir_2.empty()) {
1604
            if (path_2.empty())
1605
                return "./";
1606
            return path_2;
1607
        }
1608
        if (path_2 == ".") {
1609
            remove_trailing_dir_seps(base_dir_2);
1610
            return base_dir_2;
1611
        }
1612
        if (has_prefix(path_2, "./")) {
1613
            remove_trailing_dir_seps(base_dir_2);
1614
            // drop dot
1615
            // transfer slashes
1616
        }
1617

1618
        if (path_2.size() < 2 || path_2[1] != '.')
1619
            break;
1620
        if (path_2.size())
1621
    }
1622
    */
1623
    return base_dir_2 + path_2;
229,878✔
1624
#endif
229,884✔
1625
}
229,884✔
1626

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

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

1651

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

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

1677
File::MapBase::MapBase() noexcept = default;
1,654,278✔
1678
File::MapBase::~MapBase() noexcept
1679
{
1,851,834✔
1680
    unmap();
1,851,834✔
1681
}
1,851,834✔
1682

1683
File::MapBase::MapBase(MapBase&& other) noexcept
1684
{
197,520✔
1685
    *this = std::move(other);
197,520✔
1686
}
197,520✔
1687

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

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

1724

1725
void File::MapBase::unmap() noexcept
1726
{
3,109,785✔
1727
    if (!m_addr)
3,109,785✔
1728
        return;
1,591,641✔
1729
    REALM_ASSERT(m_reservation_size);
1,518,144✔
1730
#if REALM_ENABLE_ENCRYPTION
1,518,144✔
1731
    m_encrypted_mapping = nullptr;
1,518,144✔
1732
#endif
1,518,144✔
1733
    munmap(m_addr, m_reservation_size);
1,518,144✔
1734
    m_addr = nullptr;
1,518,144✔
1735
    m_size = 0;
1,518,144✔
1736
    m_reservation_size = 0;
1,518,144✔
1737
}
1,518,144✔
1738

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

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

1806
void File::MapBase::sync()
1807
{
1,394,727✔
1808
    REALM_ASSERT(m_addr);
1,394,727✔
1809
#if REALM_ENABLE_ENCRYPTION
1,394,727✔
1810
    if (m_encrypted_mapping) {
1,394,727✔
1811
        m_encrypted_mapping->sync();
1,659✔
1812
        return;
1,659✔
1813
    }
1,659✔
1814
#endif
1,393,068✔
1815

1816
    realm::util::msync(m_fd, m_addr, m_size);
1,393,068✔
1817
}
1,393,068✔
1818

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

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

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

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

1866
#if REALM_HAVE_STD_FILESYSTEM
1867

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

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

1878
bool DirScanner::next(std::string& name)
1879
{
1880
    const std::filesystem::directory_iterator end;
1881
    if (m_iterator == end)
1882
        return false;
1883
#if __cplusplus < 202002L
1884
    name = m_iterator->path().filename().u8string();
1885
#else
1886
    name = m_iterator->path().filename().string();
1887
#endif
1888
    m_iterator++;
1889
    return true;
1890
}
1891

1892
#elif !defined(_WIN32)
1893

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

1902
        std::string msg = format_errno("opendir() failed: %1", err);
2,147,483,647✔
1903
        switch (err) {
2,147,483,647✔
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
        }
2,147,483,647✔
1911
    }
2,147,483,647✔
1912
}
126,006✔
1913

1914
DirScanner::~DirScanner() noexcept
1915
{
126,003✔
1916
    if (m_dirp) {
126,003✔
1917
        int r = closedir(m_dirp);
118,809✔
1918
        REALM_ASSERT_RELEASE(r == 0);
118,809✔
1919
    }
118,809✔
1920
}
126,003✔
1921

1922
bool DirScanner::next(std::string& name)
1923
{
346,476✔
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)
346,476✔
1930
        return false;
7,194✔
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
115,962✔
1935
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
232,212✔
1936
#else
1937
#define REALM_READDIR(...) readdir(__VA_ARGS__)
344,688✔
1938
#endif
223,320✔
1939

1940
    for (;;) {
576,900✔
1941
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
576,900✔
1942
        DirentPtr dirent;
576,900✔
1943
        do {
576,900✔
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;
576,900✔
1949

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

1953
        if (!dirent) {
576,900✔
1954
            if (errno != 0)
118,611✔
1955
                throw SystemError(errno, "readdir() failed");
×
1956
            return false; // End of stream
118,611✔
1957
        }
118,611✔
1958
        const char* name_1 = dirent->d_name;
458,289✔
1959
        std::string name_2 = name_1;
458,289✔
1960
        if (name_2 != "." && name_2 != "..") {
458,289✔
1961
            name = name_2;
220,671✔
1962
            return true;
220,671✔
1963
        }
220,671✔
1964
    }
458,289✔
1965
}
339,282✔
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

© 2026 Coveralls, Inc