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

realm / realm-core / 1658

10 Sep 2023 11:58PM UTC coverage: 91.222% (-0.04%) from 91.263%
1658

push

Evergreen

GitHub
Merge pull request #6938 from realm/tg/tls-error-reporting

95840 of 175760 branches covered (0.0%)

142 of 146 new or added lines in 7 files covered. (97.26%)

290 existing lines in 22 files now uncovered.

233460 of 255926 relevant lines covered (91.22%)

6997123.82 hits per line

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

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

19
#include <climits>
20
#include <limits>
21
#include <algorithm>
22
#include <vector>
23

24
#include <cerrno>
25
#include <cstring>
26
#include <cstddef>
27
#include <cstdio>
28
#include <cstdlib>
29
#include <iostream>
30
#include <fcntl.h>
31

32
#ifndef _WIN32
33
#include <unistd.h>
34
#include <sys/mman.h>
35
#include <sys/file.h> // BSD / Linux flock()
36
#include <sys/statvfs.h>
37
#endif
38

39
#include <realm/exceptions.hpp>
40
#include <realm/unicode.hpp>
41
#include <realm/util/errno.hpp>
42
#include <realm/util/file_mapper.hpp>
43
#include <realm/util/safe_int_ops.hpp>
44
#include <realm/util/features.h>
45
#include <realm/util/file.hpp>
46

47
using namespace realm::util;
48

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

65
// This variable exists such that page_size() can return the page size without having to make any system calls.
66
// It could also have been a static local variable, but Valgrind/Helgrind gives a false error on that.
67
size_t cached_page_size = get_page_size();
68

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

90
#ifdef _WIN32
91

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

110
struct WindowsFileHandleHolder {
111
    WindowsFileHandleHolder() = default;
112
    explicit WindowsFileHandleHolder(HANDLE h)
113
        : handle(h)
114
    {
115
    }
116

117
    WindowsFileHandleHolder(WindowsFileHandleHolder&&) = delete;
118
    WindowsFileHandleHolder(const WindowsFileHandleHolder&) = delete;
119
    WindowsFileHandleHolder& operator=(WindowsFileHandleHolder&&) = delete;
120
    WindowsFileHandleHolder& operator=(const WindowsFileHandleHolder&) = delete;
121

122
    operator HANDLE() const noexcept
123
    {
124
        return handle;
125
    }
126

127
    ~WindowsFileHandleHolder()
128
    {
129
        if (handle != INVALID_HANDLE_VALUE) {
130
            ::CloseHandle(handle);
131
        }
132
    }
133

134
    HANDLE handle = INVALID_HANDLE_VALUE;
135
};
136

137
#endif
138

139
#if REALM_HAVE_STD_FILESYSTEM
140
using std::filesystem::u8path;
141

142
void throwIfCreateDirectoryError(std::error_code error, const std::string& path)
143
{
144
    if (!error)
145
        return;
146

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

157
void throwIfFileError(std::error_code error, const std::string& path)
158
{
159
    if (!error)
160
        return;
161

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

174
} // anonymous namespace
175

176

177
namespace realm::util {
178
namespace {
179

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

190
} // anonymous namespace
191

192

193
bool try_make_dir(const std::string& path)
194
{
243,459✔
195
#if REALM_HAVE_STD_FILESYSTEM
196
    std::error_code error;
197
    bool result = std::filesystem::create_directory(u8path(path), error);
198
    throwIfCreateDirectoryError(error, path);
199
    return result;
200
#else // POSIX
201
    if (::mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
243,459✔
202
        return true;
113,916✔
203

64,185✔
204
    int err = errno; // Eliminate any risk of clobbering
129,543✔
205
    if (err == EEXIST)
129,543✔
206
        return false;
129,540✔
207

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

3✔
210
    switch (err) {
6✔
211
        case EACCES:
3✔
212
        case EROFS:
6✔
213
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
214
        default:
3✔
215
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
×
216
    }
6✔
217
#endif
6✔
218
}
6✔
219

220

221
void make_dir(const std::string& path)
222
{
168✔
223
    if (try_make_dir(path)) // Throws
168✔
224
        return;
150✔
225
    throw Exists(format_errno("Failed to create directory at '%2': %1", EEXIST, path), path);
18✔
226
}
18✔
227

228

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

242
    while (pos < path.size()) {
2,010✔
243
        auto sep = path.find_first_of("/\\", pos);
1,809✔
244
        char c = 0;
1,809✔
245
        if (sep < path.size()) {
1,809✔
246
            c = path[sep];
1,809✔
247
            path[sep] = 0;
1,809✔
248
        }
1,809✔
249
        try_make_dir(path);
1,809✔
250
        if (sep < path.size())
1,809✔
251
            path[sep] = c;
1,809✔
252
        pos = sep + 1;
1,809✔
253
    }
1,809✔
254
#endif
201✔
255
}
201✔
256

257

258
void remove_dir(const std::string& path)
259
{
10,266✔
260
    if (try_remove_dir(path)) // Throws
10,266✔
261
        return;
10,248✔
262
    int err = ENOENT;
18✔
263
    std::string msg = format_errno("Failed to remove directory '%2': %1", err, path);
18✔
264
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
18✔
265
}
18✔
266

267

268
bool try_remove_dir(const std::string& path)
269
{
138,396✔
270
#if REALM_HAVE_STD_FILESYSTEM
271
    std::error_code error;
272
    bool result = std::filesystem::remove(u8path(path), error);
273
    throwIfFileError(error, path);
274
    return result;
275
#else // POSIX
276
    if (::rmdir(path.c_str()) == 0)
138,396✔
277
        return true;
138,135✔
278

132✔
279
    int err = errno; // Eliminate any risk of clobbering
261✔
280
    if (err == ENOENT)
261✔
281
        return false;
258✔
282

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

298

299
bool try_remove_dir_recursive(const std::string& path)
300
{
128,124✔
301
#if REALM_HAVE_STD_FILESYSTEM
302
    std::error_code error;
303
    auto removed_count = std::filesystem::remove_all(u8path(path), error);
304
    throwIfFileError(error, path);
305
    return removed_count > 0;
306
#else
307
    {
128,124✔
308
        bool allow_missing = true;
128,124✔
309
        DirScanner ds{path, allow_missing}; // Throws
128,124✔
310
        std::string name;
128,124✔
311
        while (ds.next(name)) {                              // Throws
370,827✔
312
            std::string subpath = File::resolve(name, path); // Throws
242,703✔
313
            if (File::is_dir(subpath)) {                     // Throws
242,703✔
314
                try_remove_dir_recursive(subpath);           // Throws
76,416✔
315
            }
76,416✔
316
            else {
166,287✔
317
                File::remove(subpath); // Throws
166,287✔
318
            }
166,287✔
319
        }
242,703✔
320
    }
128,124✔
321
    return try_remove_dir(path); // Throws
128,124✔
322
#endif
128,124✔
323
}
128,124✔
324

325

326
std::string make_temp_dir()
327
{
49,893✔
328
#ifdef _WIN32 // Windows version
329
    std::filesystem::path temp = std::filesystem::temp_directory_path();
330

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

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

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

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

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

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

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

382
    return std::filesystem::path(buffer).u8string();
383

384
#else // POSIX.1-2008 version
385

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

408
size_t page_size()
409
{
4,061,904✔
410
    return cached_page_size;
4,061,904✔
411
}
4,061,904✔
412

413
void File::open_internal(const std::string& path, AccessMode a, CreateMode c, int flags, bool* success)
414
{
543,012✔
415
    REALM_ASSERT_RELEASE(!is_attached());
543,012✔
416
    m_path = path; // for error reporting and debugging
543,012✔
417

160,326✔
418
#ifdef _WIN32 // Windows version
419

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

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

482
#else // POSIX version
483

160,326✔
484
    int flags2 = 0;
543,012✔
485
    switch (a) {
543,012✔
486
        case access_ReadOnly:
3,198✔
487
            flags2 = O_RDONLY;
3,198✔
488
            break;
3,198✔
489
        case access_ReadWrite:
539,817✔
490
            flags2 = O_RDWR;
539,817✔
491
            break;
539,817✔
492
    }
543,015✔
493
    switch (c) {
543,015✔
494
        case create_Auto:
515,793✔
495
            flags2 |= O_CREAT;
515,793✔
496
            break;
515,793✔
497
        case create_Never:
26,757✔
498
            break;
26,757✔
499
        case create_Must:
468✔
500
            flags2 |= O_CREAT | O_EXCL;
468✔
501
            break;
468✔
502
    }
543,015✔
503
    if (flags & flag_Trunc)
543,015✔
504
        flags2 |= O_TRUNC;
218,529✔
505
    if (flags & flag_Append)
543,015✔
506
        flags2 |= O_APPEND;
414✔
507
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
543,015✔
508
    if (0 <= fd) {
543,015✔
509
        m_fd = fd;
542,901✔
510
        if (success)
542,901✔
511
            *success = true;
×
512
        return;
542,901✔
513
    }
542,901✔
514

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

60✔
544
#endif
114✔
545
}
114✔
546

547

548
void File::close() noexcept
549
{
1,103,781✔
550
#ifdef _WIN32 // Windows version
551

552
    if (!m_fd)
553
        return;
554
    if (m_have_lock)
555
        unlock();
556

557
    BOOL r = CloseHandle(m_fd);
558
    REALM_ASSERT_RELEASE(r);
559
    m_fd = nullptr;
560

561
#else // POSIX version
562

329,499✔
563
    if (m_fd < 0)
1,103,781✔
564
        return;
560,901✔
565
    unlock();
542,880✔
566
    int r = ::close(m_fd);
542,880✔
567
    REALM_ASSERT_RELEASE(r == 0);
542,880✔
568
    m_fd = -1;
542,880✔
569

160,266✔
570
#endif
542,880✔
571
}
542,880✔
572

573
size_t File::read_static(FileDesc fd, char* data, size_t size)
574
{
500,064✔
575
#ifdef _WIN32 // Windows version
576
    char* const data_0 = data;
577
    while (0 < size) {
578
        DWORD n = std::numeric_limits<DWORD>::max();
579
        if (int_less_than(size, n))
580
            n = static_cast<DWORD>(size);
581
        DWORD r = 0;
582
        if (!ReadFile(fd, data, n, &r, 0))
583
            goto error;
584
        if (r == 0)
585
            break;
586
        REALM_ASSERT_RELEASE(r <= n);
587
        size -= size_t(r);
588
        data += size_t(r);
589
    }
590
    return data - data_0;
591

592
error:
593
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
594
    throw SystemError(int(err), "ReadFile() failed");
595

596
#else // POSIX version
597

261,549✔
598
    char* const data_0 = data;
500,064✔
599
    while (0 < size) {
998,760✔
600
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
261,654✔
601
        size_t n = std::min(size, size_t(SSIZE_MAX));
500,271✔
602
        ssize_t r = ::read(fd, data, n);
500,271✔
603
        if (r == 0)
500,271✔
604
            break;
1,575✔
605
        if (r < 0)
498,696✔
606
            goto error; // LCOV_EXCL_LINE
×
607
        REALM_ASSERT_RELEASE(size_t(r) <= n);
498,696✔
608
        size -= size_t(r);
498,696✔
609
        data += size_t(r);
498,696✔
610
    }
498,696✔
611
    return data - data_0;
500,064✔
612

613
error:
×
614
    // LCOV_EXCL_START
615
    throw SystemError(errno, "read() failed");
×
616
// LCOV_EXCL_STOP
261,549✔
617
#endif
500,064✔
618
}
500,064✔
619

620

621
size_t File::read(char* data, size_t size)
622
{
2,085✔
623
    REALM_ASSERT_RELEASE(is_attached());
2,085✔
624

927✔
625
    if (m_encryption_key) {
2,085✔
626
        uint64_t pos_original = File::get_file_pos(m_fd);
×
627
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
×
628
        size_t pos = size_t(pos_original);
×
629
        Map<char> read_map(*this, access_ReadOnly, static_cast<size_t>(pos + size));
×
630
        realm::util::encryption_read_barrier(read_map, pos, size);
×
631
        memcpy(data, read_map.get_addr() + pos, size);
×
632
        uint64_t cur = File::get_file_pos(m_fd);
×
633
        seek_static(m_fd, cur + size);
×
634
        return read_map.get_size() - pos;
×
635
    }
×
636

927✔
637
    return read_static(m_fd, data, size);
2,085✔
638
}
2,085✔
639

640
void File::write_static(FileDesc fd, const char* data, size_t size)
641
{
270,963✔
642
#ifdef _WIN32
643
    while (0 < size) {
644
        DWORD n = std::numeric_limits<DWORD>::max();
645
        if (int_less_than(size, n))
646
            n = static_cast<DWORD>(size);
647
        DWORD r = 0;
648
        if (!WriteFile(fd, data, n, &r, 0))
649
            goto error;
650
        REALM_ASSERT_RELEASE(r == n); // Partial writes are not possible.
651
        size -= size_t(r);
652
        data += size_t(r);
653
    }
654
    return;
655

656
error:
657
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
658
    if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
659
        std::string msg = get_last_error_msg("WriteFile() failed: ", err);
660
        throw OutOfDiskSpace(msg);
661
    }
662
    throw SystemError(err, "WriteFile() failed");
663
#else
664
    while (0 < size) {
541,596✔
665
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
129,825✔
666
        size_t n = std::min(size, size_t(SSIZE_MAX));
270,633✔
667
        ssize_t r = ::write(fd, data, n);
270,633✔
668
        if (r < 0)
270,633✔
669
            goto error; // LCOV_EXCL_LINE
×
670
        REALM_ASSERT_RELEASE(r != 0);
270,633✔
671
        REALM_ASSERT_RELEASE(size_t(r) <= n);
270,633✔
672
        size -= size_t(r);
270,633✔
673
        data += size_t(r);
270,633✔
674
    }
270,633✔
675
    return;
270,963✔
676

677
error:
×
678
    // LCOV_EXCL_START
679
    int err = errno; // Eliminate any risk of clobbering
×
680
    auto msg = format_errno("write() failed: %1", err);
×
681
    if (err == ENOSPC || err == EDQUOT) {
×
682
        throw OutOfDiskSpace(msg);
×
683
    }
×
684
    throw SystemError(err, msg);
×
685
    // LCOV_EXCL_STOP
686

687
#endif
×
688
}
×
689

690
void File::write(const char* data, size_t size)
691
{
57,063✔
692
    REALM_ASSERT_RELEASE(is_attached());
57,063✔
693

27,747✔
694
    if (m_encryption_key) {
57,063✔
695
        uint64_t pos_original = get_file_pos(m_fd);
1,464✔
696
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
1,464✔
697
        size_t pos = size_t(pos_original);
1,464✔
698
        Map<char> write_map(*this, access_ReadWrite, static_cast<size_t>(pos + size));
1,464✔
699
        realm::util::encryption_read_barrier(write_map, pos, size);
1,464✔
700
        memcpy(write_map.get_addr() + pos, data, size);
1,464✔
701
        realm::util::encryption_write_barrier(write_map, pos, size);
1,464✔
702
        uint64_t cur = get_file_pos(m_fd);
1,464✔
703
        seek(cur + size);
1,464✔
704
        return;
1,464✔
705
    }
1,464✔
706

27,168✔
707
    write_static(m_fd, data, size);
55,599✔
708
}
55,599✔
709

710
uint64_t File::get_file_pos(FileDesc fd)
711
{
716,271✔
712
#ifdef _WIN32
713
    LONG high_dword = 0;
714
    LARGE_INTEGER li;
715
    LARGE_INTEGER res;
716
    li.QuadPart = 0;
717
    bool ok = SetFilePointerEx(fd, li, &res, FILE_CURRENT);
718
    if (!ok)
719
        throw SystemError(GetLastError(), "SetFilePointer() failed");
720

721
    return uint64_t(res.QuadPart);
722
#else
723
    auto pos = lseek(fd, 0, SEEK_CUR);
716,271✔
724
    if (pos < 0) {
716,271✔
725
        throw SystemError(errno, "lseek() failed");
×
726
    }
×
727
    return uint64_t(pos);
716,271✔
728
#endif
716,271✔
729
}
716,271✔
730

731
File::SizeType File::get_size_static(const std::string& path)
732
{
1,401✔
733
    File f(path);
1,401✔
734
    return f.get_size();
1,401✔
735
}
1,401✔
736

737
File::SizeType File::get_size_static(FileDesc fd)
738
{
2,419,161✔
739
#ifdef _WIN32
740
    LARGE_INTEGER large_int;
741
    if (GetFileSizeEx(fd, &large_int)) {
742
        File::SizeType size;
743
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
744
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
745

746
        return size;
747
    }
748
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
749

750
#else // POSIX version
751

1,238,079✔
752
    struct stat statbuf;
2,419,161✔
753
    if (::fstat(fd, &statbuf) == 0) {
2,419,230✔
754
        SizeType size;
2,419,230✔
755
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
2,419,230✔
756
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
757

1,238,106✔
758
        return size;
2,419,230✔
759
    }
2,419,230✔
760
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
761

2,147,483,647✔
762
#endif
4,294,967,294✔
763
}
4,294,967,294✔
764

765
File::SizeType File::get_size() const
766
{
2,416,155✔
767
    REALM_ASSERT_RELEASE(is_attached());
2,416,155✔
768
    File::SizeType size = get_size_static(m_fd);
2,416,155✔
769

1,236,681✔
770
    if (m_encryption_key) {
2,416,155✔
771
        File::SizeType ret_size = encrypted_size_to_data_size(size);
3,378✔
772
        return ret_size;
3,378✔
773
    }
3,378✔
774
    else
2,412,777✔
775
        return size;
2,412,777✔
776
}
2,416,155✔
777

778

779
void File::resize(SizeType size)
780
{
335,322✔
781
    REALM_ASSERT_RELEASE(is_attached());
335,322✔
782

58,167✔
783
#ifdef _WIN32 // Windows version
784

785
    // Save file position
786
    SizeType p = get_file_pos(m_fd);
787

788
    if (m_encryption_key)
789
        size = data_size_to_encrypted_size(size);
790

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

796
    if (!SetEndOfFile(m_fd)) {
797
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
798
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
799
            std::string msg = get_last_error_msg("SetEndOfFile() failed: ", err);
800
            throw OutOfDiskSpace(msg);
801
        }
802
        throw SystemError(int(err), "SetEndOfFile() failed");
803
    }
804

805
    // Restore file position
806
    seek(p);
807

808
#else // POSIX version
809

58,167✔
810
    if (m_encryption_key)
335,322✔
811
        size = data_size_to_encrypted_size(size);
×
812

58,167✔
813
    off_t size2;
335,322✔
814
    if (int_cast_with_overflow_detect(size, size2))
335,322✔
815
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
816

58,167✔
817
    // POSIX specifies that introduced bytes read as zero. This is not
58,167✔
818
    // required by File::resize().
58,167✔
819
    if (::ftruncate(m_fd, size2) != 0) {
335,322✔
820
        int err = errno; // Eliminate any risk of clobbering
×
821
        auto msg = format_errno("ftruncate() failed: %1", err);
×
822
        if (err == ENOSPC || err == EDQUOT) {
×
823
            throw OutOfDiskSpace(msg);
×
824
        }
×
825
        throw SystemError(err, msg);
×
826
    }
×
827

58,167✔
828
#endif
335,322✔
829
}
335,322✔
830

831

832
void File::prealloc(size_t size)
833
{
239,652✔
834
    REALM_ASSERT_RELEASE(is_attached());
239,652✔
835

126,348✔
836
    if (size <= to_size_t(get_size())) {
239,652✔
837
        return;
48,189✔
838
    }
48,189✔
839

102,765✔
840
    size_t new_size = size;
191,463✔
841
    if (m_encryption_key) {
191,463✔
842
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
525✔
843
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
525✔
844
        if (new_size < size) {
525✔
845
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow: data_size_to_encrypted_size(" +
×
846
                                                           realm::util::to_string(size) +
×
847
                                                           ") == " + realm::util::to_string(new_size));
×
848
        }
×
849
    }
191,463✔
850

102,765✔
851
    auto manually_consume_space = [&]() {
191,463✔
852
        constexpr size_t chunk_size = 4096;
×
853
        int64_t original_size = get_size_static(m_fd); // raw size
×
854
        seek(original_size);
×
855
        size_t num_bytes = size_t(new_size - original_size);
×
856
        std::string zeros(chunk_size, '\0');
×
857
        while (num_bytes > 0) {
×
858
            size_t t = num_bytes > chunk_size ? chunk_size : num_bytes;
×
859
            write_static(m_fd, zeros.c_str(), t);
×
860
            num_bytes -= t;
×
861
        }
×
862
    };
×
863

102,765✔
864
    auto consume_space_interlocked = [&] {
102,765✔
865
#if REALM_ENABLE_ENCRYPTION
×
866
        if (m_encryption_key) {
×
867
            // We need to prevent concurrent calls to lseek from the encryption layer
868
            // while we're writing to the file to extend it. Otherwise an intervening
869
            // lseek may redirect the writing process, causing file corruption.
870
            UniqueLock lck(util::mapping_mutex);
×
871
            manually_consume_space();
×
872
        }
×
873
        else {
×
874
            manually_consume_space();
×
875
        }
×
876
#else
877
        manually_consume_space();
878
#endif
879
    };
×
880

102,765✔
881
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version
102,765✔
882
    // Mostly Linux only
102,765✔
883
    if (!prealloc_if_supported(0, new_size)) {
102,765✔
884
        consume_space_interlocked();
885
    }
886
#else // Non-atomic fallback
887
#if REALM_PLATFORM_APPLE
88,698✔
888
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
889
    // ftruncate().
890

891
    struct stat statbuf;
88,698✔
892
    if (::fstat(m_fd, &statbuf) != 0) {
88,698✔
893
        int err = errno;
894
        throw SystemError(err, "fstat() inside prealloc() failed");
895
    }
896

897
    size_t allocated_size;
88,698✔
898
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
88,698✔
899
        throw RuntimeError(ErrorCodes::RangeError,
900
                           "Overflow on block conversion to size_t " + realm::util::to_string(statbuf.st_blocks));
901
    }
902
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
88,698✔
903
        throw RuntimeError(ErrorCodes::RangeError, "Overflow computing existing file space allocation blocks: " +
904
                                                       realm::util::to_string(allocated_size) +
905
                                                       " block size: " + realm::util::to_string(S_BLKSIZE));
906
    }
907

908
    // Only attempt to preallocate space if there's not already sufficient free space in the file.
909
    // APFS would fail with EINVAL if we attempted it, and HFS+ would preallocate extra space unnecessarily.
910
    // See <https://github.com/realm/realm-core/issues/3005> for details.
911
    if (new_size > allocated_size) {
88,698✔
912

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

935
    int ret = 0;
88,698✔
936

937
    do {
88,698✔
938
        ret = ftruncate(m_fd, new_size);
88,698✔
939
    } while (ret == -1 && errno == EINTR);
88,698!
940

941
    if (ret != 0) {
88,698✔
942
        int err = errno;
943
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
944
        // so this is some other runtime error and not OutOfDiskSpace
945
        throw SystemError(err, "ftruncate() inside prealloc() failed");
946
    }
947
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
948

949
    consume_space_interlocked();
950

951
#else
952
#error Please check if/how your OS supports file preallocation
953
#endif
954

955
#endif // !(_POSIX_C_SOURCE >= 200112L)
88,698✔
956
}
191,463✔
957

958

959
bool File::prealloc_if_supported(SizeType offset, size_t size)
960
{
102,765✔
961
    REALM_ASSERT_RELEASE(is_attached());
102,765!
962

102,765✔
963
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version
102,765✔
964

102,765✔
965
    REALM_ASSERT_RELEASE(is_prealloc_supported());
102,765✔
966

102,765✔
967
    if (size == 0) {
102,765✔
968
        // calling posix_fallocate with a size of 0 will cause a return of EINVAL
969
        // since this is a meaningless operation anyway, we just return early here
970
        return true;
971
    }
972

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

102,765✔
980
    if (REALM_LIKELY(status == 0)) {
102,765✔
981
        return true;
102,765✔
982
    }
102,765✔
983

984
    if (status == EINVAL || status == EPERM) {
985
        return false; // Retry with non-atomic version
986
    }
987

988
    auto msg = format_errno("posix_fallocate() failed: %1", status);
989
    if (status == ENOSPC || status == EDQUOT) {
990
        throw OutOfDiskSpace(msg);
991
    }
992
    throw SystemError(status, msg);
993

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

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

1002
#else
1003

1004
    static_cast<void>(offset);
1005
    static_cast<void>(size);
1006

1007
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1008

1009
#endif
1010
    return false;
×
1011
}
×
1012

1013

1014
bool File::is_prealloc_supported()
1015
{
102,765✔
1016
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version
102,765✔
1017
    return true;
102,765✔
1018
#else
1019
    return false;
1020
#endif
1021
}
102,765✔
1022

1023
void File::seek(SizeType position)
1024
{
1,536✔
1025
    REALM_ASSERT_RELEASE(is_attached());
1,536✔
1026
    seek_static(m_fd, position);
1,536✔
1027
}
1,536✔
1028

1029
void File::seek_static(FileDesc fd, SizeType position)
1030
{
1,428,222✔
1031
#ifdef _WIN32 // Windows version
1032

1033
    LARGE_INTEGER large_int;
1034
    if (int_cast_with_overflow_detect(position, large_int.QuadPart))
1035
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1036

1037
    if (!SetFilePointerEx(fd, large_int, 0, FILE_BEGIN))
1038
        throw SystemError(GetLastError(), "SetFilePointerEx() failed");
1039

1040
#else // POSIX version
1041

727,503✔
1042
    off_t position2;
1,428,222✔
1043
    if (int_cast_with_overflow_detect(position, position2))
1,428,222✔
1044
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1045

727,503✔
1046
    if (0 <= ::lseek(fd, position2, SEEK_SET))
1,428,222✔
1047
        return;
1,428,222✔
1048
    throw SystemError(errno, "lseek() failed");
×
1049

1050
#endif
×
1051
}
×
1052

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

546✔
1061
#if defined _WIN32 // Windows version
1062

1063
    if (FlushFileBuffers(m_fd))
1064
        return;
1065
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1066

1067
#elif REALM_PLATFORM_APPLE
1068

1069
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
312✔
1070
        return;
312✔
1071
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1072

1073
#else // POSIX version
1074

546✔
1075
    if (::fsync(m_fd) == 0)
546✔
1076
        return;
546✔
1077
    throw SystemError(errno, "fsync() failed");
1078

1079
#endif
1080
}
×
1081

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

1093
#ifndef _WIN32
1094
// little helper
1095
static void _unlock(int m_fd)
1096
{
7,913,589✔
1097
    int r;
7,913,589✔
1098
    do {
7,913,589✔
1099
        r = flock(m_fd, LOCK_UN);
7,913,589✔
1100
    } while (r != 0 && errno == EINTR);
7,913,589!
1101
    REALM_ASSERT_RELEASE_EX(r == 0 && "File::unlock()", r, errno);
7,913,589✔
1102
}
7,913,589✔
1103
#endif
1104

1105
bool File::rw_lock(bool exclusive, bool non_blocking)
1106
{
365,286✔
1107
    // exclusive blocking rw locks not implemented for emulation
181,032✔
1108
    REALM_ASSERT(!exclusive || non_blocking);
365,286✔
1109

181,032✔
1110
#ifndef REALM_FILELOCK_EMULATION
365,286✔
1111
    return lock(exclusive, non_blocking);
365,286✔
1112
#else
1113
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1114

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

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

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

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

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

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

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

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

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

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

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

1203
bool File::lock(bool exclusive, bool non_blocking)
1204
{
7,430,688✔
1205
    REALM_ASSERT_RELEASE(is_attached());
7,430,688✔
1206

181,032✔
1207
#ifdef _WIN32 // Windows version
1208
    REALM_ASSERT_RELEASE(!m_have_lock);
1209

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

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

1263
void File::unlock() noexcept
1264
{
7,914,213✔
1265
#ifdef _WIN32 // Windows version
1266
    if (!m_have_lock)
1267
        return;
1268

1269
    OVERLAPPED overlapped;
1270
    overlapped.hEvent = 0;
1271
    overlapped.OffsetHigh = 0;
1272
    overlapped.Offset = 0;
1273
    overlapped.Pointer = 0;
1274
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1275

1276
    REALM_ASSERT_RELEASE(r);
1277
    m_have_lock = false;
1278
#else
1279
    // The Linux man page for flock() does not state explicitely that
311,697✔
1280
    // unlocking is idempotent, however, we will assume it since there
311,697✔
1281
    // is no mention of the error that would be reported if a
311,697✔
1282
    // non-locked file were unlocked.
311,697✔
1283
    _unlock(m_fd);
7,914,213✔
1284
#endif
7,914,213✔
1285
}
7,914,213✔
1286

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

1315
void* File::map(AccessMode a, size_t size, int /*map_flags*/, size_t offset) const
1316
{
6✔
1317
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset);
6✔
1318
}
6✔
1319

1320
void* File::map_fixed(AccessMode a, void* address, size_t size, int /* map_flags */, size_t offset) const
1321
{
×
1322
    if (m_encryption_key.get()) {
×
1323
        // encryption enabled - this is not supported - see explanation in alloc_slab.cpp
1324
        REALM_ASSERT(false);
×
1325
    }
×
1326
#ifdef _WIN32
1327
    // windows, no encryption - this is not supported, see explanation in alloc_slab.cpp,
1328
    // above the method 'update_reader_view()'
1329
    REALM_ASSERT(false);
1330
    return nullptr;
1331
#else
1332
    // unencrypted - mmap part of already reserved space
1333
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, m_encryption_key.get());
×
1334
#endif
×
1335
}
×
1336

1337
void* File::map_reserve(AccessMode a, size_t size, size_t offset) const
1338
{
×
1339
    static_cast<void>(a); // FIXME: Consider removing this argument
×
1340
    return realm::util::mmap_reserve(m_fd, size, offset);
×
1341
}
×
1342

1343
#if REALM_ENABLE_ENCRYPTION
1344
void* File::map(AccessMode a, size_t size, EncryptedFileMapping*& mapping, int /*map_flags*/, size_t offset) const
1345
{
1,946,685✔
1346
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
1,946,685✔
1347
}
1,946,685✔
1348

1349
void* File::map_fixed(AccessMode a, void* address, size_t size, EncryptedFileMapping* mapping, int /* map_flags */,
1350
                      size_t offset) const
1351
{
×
1352
    if (m_encryption_key.get()) {
×
1353
        // encryption enabled - we shouldn't be here, all memory was allocated by reserve
1354
        REALM_ASSERT_RELEASE(false);
×
1355
    }
×
1356
#ifndef _WIN32
×
1357
    // no encryption. On Unixes, map relevant part of reserved virtual address range
1358
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, nullptr, mapping);
×
1359
#else
1360
    // no encryption - unsupported on windows
1361
    REALM_ASSERT(false);
1362
    return nullptr;
1363
#endif
1364
}
×
1365

1366
void* File::map_reserve(AccessMode a, size_t size, size_t offset, EncryptedFileMapping*& mapping) const
1367
{
×
1368
    if (m_encryption_key.get()) {
×
1369
        // encrypted file - just mmap it, the encryption layer handles if the mapping extends beyond eof
1370
        return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
×
1371
    }
×
1372
#ifndef _WIN32
×
1373
    // not encrypted, do a proper reservation on Unixes'
1374
    return realm::util::mmap_reserve({m_fd, m_path, a, nullptr}, size, offset, mapping);
×
1375
#else
1376
    // on windows, this is a no-op
1377
    return nullptr;
1378
#endif
1379
}
×
1380

1381
#endif // REALM_ENABLE_ENCRYPTION
1382

1383
void File::unmap(void* addr, size_t size) noexcept
1384
{
×
1385
    realm::util::munmap(addr, size);
×
1386
}
×
1387

1388

1389
void* File::remap(void* old_addr, size_t old_size, AccessMode a, size_t new_size, int /*map_flags*/,
1390
                  size_t file_offset) const
1391
{
×
1392
    return realm::util::mremap({m_fd, m_path, a, m_encryption_key.get()}, file_offset, old_addr, old_size, new_size);
×
1393
}
×
1394

1395

1396
void File::sync_map(FileDesc fd, void* addr, size_t size)
1397
{
1,308,849✔
1398
    realm::util::msync(fd, addr, size);
1,308,849✔
1399
}
1,308,849✔
1400

1401

1402
bool File::exists(const std::string& path)
1403
{
6,350,868✔
1404
#if REALM_HAVE_STD_FILESYSTEM
1405
    return std::filesystem::exists(u8path(path));
1406
#else // POSIX
1407
    if (::access(path.c_str(), F_OK) == 0)
6,350,868✔
1408
        return true;
29,685✔
1409
    int err = errno; // Eliminate any risk of clobbering
6,321,183✔
1410
    switch (err) {
6,321,183✔
1411
        case EACCES:
5,502,522✔
1412
        case ENOENT:
6,321,162✔
1413
        case ENOTDIR:
6,321,162✔
1414
            return false;
6,321,162✔
1415
    }
30✔
1416
    throw SystemError(err, "access() failed");
30✔
1417
#endif
30✔
1418
}
30✔
1419

1420

1421
bool File::is_dir(const std::string& path)
1422
{
286,860✔
1423
#if REALM_HAVE_STD_FILESYSTEM
1424
    return std::filesystem::is_directory(u8path(path));
1425
#elif !defined(_WIN32)
1426
    struct stat statbuf;
206,313✔
1427
    if (::stat(path.c_str(), &statbuf) == 0)
286,860✔
1428
        return S_ISDIR(statbuf.st_mode);
278,700✔
1429
    int err = errno; // Eliminate any risk of clobbering
8,160✔
1430
    switch (err) {
8,160✔
1431
        case EACCES:
4,086✔
1432
        case ENOENT:
8,163✔
1433
        case ENOTDIR:
8,163✔
1434
            return false;
8,163✔
1435
    }
×
1436
    throw SystemError(err, "stat() failed");
×
1437
#else
1438
    static_cast<void>(path);
1439
    throw NotImplemented();
1440
#endif
1441
}
×
1442

1443

1444
void File::remove(const std::string& path)
1445
{
210,069✔
1446
    if (try_remove(path))
210,069✔
1447
        return;
210,030✔
1448
    int err = ENOENT;
39✔
1449
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
39✔
1450
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
39✔
1451
}
39✔
1452

1453

1454
bool File::try_remove(const std::string& path)
1455
{
272,607✔
1456
#if REALM_HAVE_STD_FILESYSTEM
1457
    std::error_code error;
1458
    bool result = std::filesystem::remove(u8path(path), error);
1459
    throwIfFileError(error, path);
1460
    return result;
1461
#else // POSIX
1462
    if (::unlink(path.c_str()) == 0)
272,607✔
1463
        return true;
233,823✔
1464

19,392✔
1465
    int err = errno; // Eliminate any risk of clobbering
38,784✔
1466
    if (err == ENOENT)
38,784✔
1467
        return false;
38,760✔
1468

21✔
1469
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
45✔
1470
    switch (err) {
45✔
1471
        case EACCES:
6✔
1472
        case EROFS:
6✔
1473
        case ETXTBSY:
6✔
1474
        case EBUSY:
6✔
1475
        case EPERM:
18✔
1476
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
18✔
1477
        case ENOENT:
3✔
1478
            return false;
×
1479
        default:
27✔
1480
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
27✔
1481
    }
45✔
1482
#endif
45✔
1483
}
45✔
1484

1485

1486
void File::move(const std::string& old_path, const std::string& new_path)
1487
{
342✔
1488
#if REALM_HAVE_STD_FILESYSTEM
1489
    std::error_code error;
1490
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1491

1492
    if (error == std::errc::no_such_file_or_directory) {
1493
        throw FileAccessError(ErrorCodes::FileNotFound, error.message(), old_path);
1494
    }
1495
    throwIfFileError(error, old_path);
1496
#else
1497
    int r = rename(old_path.c_str(), new_path.c_str());
342✔
1498
    if (r == 0)
342✔
1499
        return;
342✔
1500
    int err = errno; // Eliminate any risk of clobbering
×
1501
    std::string msg = format_errno("Failed to rename file from '%2' to '%3': %1", err, old_path, new_path);
×
1502
    switch (err) {
×
1503
        case EACCES:
×
1504
        case EROFS:
×
1505
        case ETXTBSY:
×
1506
        case EBUSY:
×
1507
        case EPERM:
×
1508
        case EEXIST:
×
1509
        case ENOTEMPTY:
×
1510
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, old_path, err);
×
1511
        case ENOENT:
×
1512
            throw FileAccessError(ErrorCodes::FileNotFound, msg, old_path, err);
×
1513
        default:
×
1514
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, old_path, err);
×
1515
    }
×
1516
#endif
×
1517
}
×
1518

1519

1520
void File::copy(const std::string& origin_path, const std::string& target_path)
1521
{
528✔
1522
#if REALM_HAVE_STD_FILESYSTEM
1523
    std::filesystem::copy_file(u8path(origin_path), u8path(target_path),
1524
                               std::filesystem::copy_options::overwrite_existing); // Throws
1525
#else
1526
    File origin_file{origin_path, mode_Read};  // Throws
528✔
1527
    File target_file{target_path, mode_Write}; // Throws
528✔
1528
    size_t buffer_size = 4096;
528✔
1529
    std::unique_ptr<char[]> buffer = std::make_unique<char[]>(buffer_size); // Throws
528✔
1530
    for (;;) {
1,395✔
1531
        size_t n = origin_file.read(buffer.get(), buffer_size); // Throws
1,395✔
1532
        target_file.write(buffer.get(), n);                     // Throws
1,395✔
1533
        if (n < buffer_size)
1,395✔
1534
            break;
528✔
1535
    }
1,395✔
1536
#endif
528✔
1537
}
528✔
1538

1539

1540
bool File::compare(const std::string& path_1, const std::string& path_2)
1541
{
×
1542
    File file_1{path_1}; // Throws
×
1543
    File file_2{path_2}; // Throws
×
1544
    size_t buffer_size = 4096;
×
1545
    std::unique_ptr<char[]> buffer_1 = std::make_unique<char[]>(buffer_size); // Throws
×
1546
    std::unique_ptr<char[]> buffer_2 = std::make_unique<char[]>(buffer_size); // Throws
×
1547
    for (;;) {
×
1548
        size_t n_1 = file_1.read(buffer_1.get(), buffer_size); // Throws
×
1549
        size_t n_2 = file_2.read(buffer_2.get(), buffer_size); // Throws
×
1550
        if (n_1 != n_2)
×
1551
            return false;
×
1552
        if (!std::equal(buffer_1.get(), buffer_1.get() + n_1, buffer_2.get()))
×
1553
            return false;
×
1554
        if (n_1 < buffer_size)
×
1555
            break;
×
1556
    }
×
1557
    return true;
×
1558
}
×
1559

1560
bool File::is_same_file_static(FileDesc f1, FileDesc f2)
1561
{
24✔
1562
    return get_unique_id(f1) == get_unique_id(f2);
24✔
1563
}
24✔
1564

1565
bool File::is_same_file(const File& f) const
1566
{
24✔
1567
    REALM_ASSERT_RELEASE(is_attached());
24✔
1568
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1569
    return is_same_file_static(m_fd, f.m_fd);
24✔
1570
}
24✔
1571

1572
File::UniqueID File::get_unique_id() const
1573
{
361,920✔
1574
    REALM_ASSERT_RELEASE(is_attached());
361,920✔
1575
    return File::get_unique_id(m_fd);
361,920✔
1576
}
361,920✔
1577

1578
FileDesc File::get_descriptor() const
1579
{
144,963✔
1580
    return m_fd;
144,963✔
1581
}
144,963✔
1582

1583
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1584
{
257,388✔
1585
#ifdef _WIN32 // Windows version
1586
    // CreateFile2 with creationDisposition OPEN_EXISTING will return a file handle only if the file exists
1587
    // otherwise it will raise ERROR_FILE_NOT_FOUND. This call will never create a new file.
1588
    WindowsFileHandleHolder fileHandle(::CreateFile2(u8path(path).c_str(), FILE_READ_ATTRIBUTES,
1589
                                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1590
                                                     OPEN_EXISTING, nullptr));
1591

1592
    if (fileHandle == INVALID_HANDLE_VALUE) {
1593
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1594
            return none;
1595
        }
1596
        throw SystemError(GetLastError(), "CreateFileW failed");
1597
    }
1598

1599
    return get_unique_id(fileHandle);
1600
#else // POSIX version
1601
    struct stat statbuf;
257,388✔
1602
    if (::stat(path.c_str(), &statbuf) == 0) {
257,388✔
1603
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
182,250✔
1604
    }
182,250✔
1605
    int err = errno; // Eliminate any risk of clobbering
75,138✔
1606
    // File doesn't exist
3✔
1607
    if (err == ENOENT)
75,138✔
1608
        return none;
75,138✔
1609
    throw SystemError(err, format_errno("fstat() failed: %1", err));
×
1610
#endif
×
1611
}
×
1612

1613
File::UniqueID File::get_unique_id(FileDesc file)
1614
{
361,968✔
1615
#ifdef _WIN32 // Windows version
1616
    REALM_ASSERT(file != nullptr);
1617
    File::UniqueID ret;
1618
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1619
        throw std::system_error(GetLastError(), std::system_category(), "GetFileInformationByHandleEx() failed");
1620
    }
1621

1622
    return ret;
1623
#else // POSIX version
1624
    REALM_ASSERT(file >= 0);
361,968✔
1625
    struct stat statbuf;
361,968✔
1626
    if (::fstat(file, &statbuf) == 0) {
361,968✔
1627
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
361,968✔
1628
    }
361,968✔
UNCOV
1629
    throw std::system_error(errno, std::system_category(), "fstat() failed");
×
UNCOV
1630
#endif
×
UNCOV
1631
}
×
1632

1633
std::string File::get_path() const
1634
{
279✔
1635
    return m_path;
279✔
1636
}
279✔
1637

1638
std::string File::resolve(const std::string& path, const std::string& base_dir)
1639
{
281,976✔
1640
#if REALM_HAVE_STD_FILESYSTEM
1641
    return (u8path(base_dir) / u8path(path)).lexically_normal().u8string();
1642
#else
1643
    char dir_sep = '/';
281,976✔
1644
    std::string path_2 = path;
281,976✔
1645
    std::string base_dir_2 = base_dir;
281,976✔
1646
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
281,976✔
1647
    if (is_absolute)
281,976✔
1648
        return path_2;
6✔
1649
    if (path_2.empty())
281,970✔
1650
        path_2 = ".";
6✔
1651
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
281,970✔
1652
        base_dir_2.push_back(dir_sep);
268,431✔
1653
    /*
78,102✔
1654
    // Abbreviate
78,102✔
1655
    for (;;) {
78,102✔
1656
        if (base_dir_2.empty()) {
78,102✔
1657
            if (path_2.empty())
78,102✔
1658
                return "./";
78,102✔
1659
            return path_2;
78,102✔
1660
        }
78,102✔
1661
        if (path_2 == ".") {
78,102✔
1662
            remove_trailing_dir_seps(base_dir_2);
78,102✔
1663
            return base_dir_2;
78,102✔
1664
        }
78,102✔
1665
        if (has_prefix(path_2, "./")) {
78,102✔
1666
            remove_trailing_dir_seps(base_dir_2);
78,102✔
1667
            // drop dot
78,102✔
1668
            // transfer slashes
78,102✔
1669
        }
78,102✔
1670

78,102✔
1671
        if (path_2.size() < 2 || path_2[1] != '.')
78,102✔
1672
            break;
78,102✔
1673
        if (path_2.size())
78,102✔
1674
    }
78,102✔
1675
    */
78,102✔
1676
    return base_dir_2 + path_2;
281,970✔
1677
#endif
281,970✔
1678
}
281,970✔
1679

1680
std::string File::parent_dir(const std::string& path)
1681
{
78✔
1682
#if REALM_HAVE_STD_FILESYSTEM
1683
    return u8path(path).parent_path().u8string(); // Throws
1684
#else
1685
    auto is_sep = [](char c) -> bool {
1,119✔
1686
        return c == '/' || c == '\\';
1,119✔
1687
    };
1,119✔
1688
    auto it = std::find_if(path.rbegin(), path.rend(), is_sep);
78✔
1689
    while (it != path.rend() && is_sep(*it))
168✔
1690
        ++it;
90✔
1691
    return path.substr(0, path.rend() - it);
78✔
1692
#endif
78✔
1693
}
78✔
1694

1695
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1696
{
6✔
1697
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1698
}
6✔
1699

1700

1701
void File::set_encryption_key(const char* key)
1702
{
145,359✔
1703
#if REALM_ENABLE_ENCRYPTION
145,359✔
1704
    if (key) {
145,359✔
1705
        auto buffer = std::make_unique<char[]>(64);
339✔
1706
        memcpy(buffer.get(), key, 64);
339✔
1707
        m_encryption_key = std::move(buffer);
339✔
1708
    }
339✔
1709
    else {
145,020✔
1710
        m_encryption_key.reset();
145,020✔
1711
    }
145,020✔
1712
#else
1713
    if (key) {
1714
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1715
    }
1716
#endif
1717
}
145,359✔
1718

1719
const char* File::get_encryption_key() const
1720
{
138✔
1721
    return m_encryption_key.get();
138✔
1722
}
138✔
1723

1724
void File::MapBase::map(const File& f, AccessMode a, size_t size, int map_flags, size_t offset,
1725
                        util::WriteObserver* observer)
1726
{
1,946,685✔
1727
    REALM_ASSERT(!m_addr);
1,946,685✔
1728
#if REALM_ENABLE_ENCRYPTION
1,946,685✔
1729
    m_addr = f.map(a, size, m_encrypted_mapping, map_flags, offset);
1,946,685✔
1730
    if (observer && m_encrypted_mapping) {
1,946,685✔
1731
        m_encrypted_mapping->set_observer(observer);
396✔
1732
    }
396✔
1733
#else
1734
    m_addr = f.map(a, size, map_flags, offset);
1735
    static_cast<void>(observer);
1736
#endif
1737
    m_size = m_reservation_size = size;
1,946,685✔
1738
    m_fd = f.m_fd;
1,946,685✔
1739
    m_offset = offset;
1,946,685✔
1740
    m_access_mode = a;
1,946,685✔
1741
}
1,946,685✔
1742

1743

1744
void File::MapBase::unmap() noexcept
1745
{
4,550,586✔
1746
    if (!m_addr)
4,550,586✔
1747
        return;
2,459,178✔
1748
    REALM_ASSERT(m_reservation_size);
2,091,408✔
1749
#if REALM_ENABLE_ENCRYPTION
2,091,408✔
1750
    if (m_encrypted_mapping) {
2,091,408✔
1751
        m_encrypted_mapping = nullptr;
2,994✔
1752
        util::remove_encrypted_mapping(m_addr, m_size);
2,994✔
1753
    }
2,994✔
1754
#endif
2,091,408✔
1755
    ::munmap(m_addr, m_reservation_size);
2,091,408✔
1756
    m_addr = nullptr;
2,091,408✔
1757
    m_size = 0;
2,091,408✔
1758
    m_reservation_size = 0;
2,091,408✔
1759
}
2,091,408✔
1760

1761
void File::MapBase::remap(const File& f, AccessMode a, size_t size, int map_flags)
1762
{
×
1763
    REALM_ASSERT(m_addr);
×
1764
    m_addr = f.remap(m_addr, m_size, a, size, map_flags);
×
1765
    m_size = m_reservation_size = size;
×
1766
}
×
1767

1768
bool File::MapBase::try_reserve(const File& file, AccessMode a, size_t size, size_t offset,
1769
                                util::WriteObserver* observer)
1770
{
144,897✔
1771
#ifdef _WIN32
1772
    static_cast<void>(observer);
1773
    // unsupported for now
1774
    return false;
1775
#else
1776
    void* addr = ::mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
144,897✔
1777
    if (addr == MAP_FAILED)
144,897✔
1778
        return false;
71,076✔
1779
    m_addr = addr;
144,897✔
1780
    REALM_ASSERT(m_size == 0);
144,897✔
1781
    m_access_mode = a;
144,897✔
1782
    m_reservation_size = size;
144,897✔
1783
    m_fd = file.get_descriptor();
144,897✔
1784
    m_offset = offset;
144,897✔
1785
#if REALM_ENABLE_ENCRYPTION
144,897✔
1786
    if (file.m_encryption_key) {
144,897✔
1787
        m_encrypted_mapping =
273✔
1788
            util::reserve_mapping(addr, {m_fd, file.get_path(), a, file.m_encryption_key.get()}, offset);
273✔
1789
        if (observer) {
273✔
1790
            m_encrypted_mapping->set_observer(observer);
267✔
1791
        }
267✔
1792
    }
273✔
1793
#else
1794
    static_cast<void>(observer);
1795
#endif
1796
#endif
144,897✔
1797
    return true;
144,897✔
1798
}
144,897✔
1799

1800
bool File::MapBase::try_extend_to(size_t size) noexcept
1801
{
164,013✔
1802
    if (size > m_reservation_size) {
164,013✔
1803
        return false;
×
1804
    }
×
1805
    // return false;
88,689✔
1806
#ifndef _WIN32
164,013✔
1807
    char* extension_start_addr = (char*)m_addr + m_size;
164,013✔
1808
    size_t extension_size = size - m_size;
164,013✔
1809
    size_t extension_start_offset = m_offset + m_size;
164,013✔
1810
#if REALM_ENABLE_ENCRYPTION
164,013✔
1811
    if (m_encrypted_mapping) {
164,013✔
1812
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
318✔
1813
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
318✔
1814
        if (got_addr == MAP_FAILED)
318✔
1815
            return false;
180✔
1816
        REALM_ASSERT(got_addr == extension_start_addr);
318✔
1817
        util::extend_encrypted_mapping(m_encrypted_mapping, m_addr, m_offset, m_size, size);
318✔
1818
        m_size = size;
318✔
1819
        return true;
318✔
1820
    }
318✔
1821
#endif
163,695✔
1822
    try {
163,695✔
1823
        void* got_addr = util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode,
163,695✔
1824
                                          extension_start_offset, nullptr);
163,695✔
1825
        if (got_addr == extension_start_addr) {
163,695✔
1826
            m_size = size;
163,611✔
1827
            return true;
163,611✔
1828
        }
163,611✔
1829
    }
18✔
1830
    catch (...) {
18✔
1831
        return false;
18✔
1832
    }
18✔
1833
#endif
66✔
1834
    return false;
66✔
1835
}
66✔
1836

1837
void File::MapBase::sync()
1838
{
1,308,867✔
1839
    REALM_ASSERT(m_addr);
1,308,867✔
1840

667,068✔
1841
    File::sync_map(m_fd, m_addr, m_size);
1,308,867✔
1842
}
1,308,867✔
1843

1844
void File::MapBase::flush()
1845
{
2,466,396✔
1846
    REALM_ASSERT(m_addr);
2,466,396✔
1847
#if REALM_ENABLE_ENCRYPTION
2,466,396✔
1848
    if (m_encrypted_mapping) {
2,466,396✔
1849
        realm::util::encryption_flush(m_encrypted_mapping);
606✔
1850
    }
606✔
1851
#endif
2,466,396✔
1852
}
2,466,396✔
1853

1854
std::time_t File::last_write_time(const std::string& path)
1855
{
948✔
1856
#if REALM_HAVE_STD_FILESYSTEM
1857
    auto time = std::filesystem::last_write_time(u8path(path));
1858

1859
    using namespace std::chrono;
1860
#if __cplusplus >= 202002L
1861
    auto system_time = clock_cast<system_clock>(time);
1862
#else
1863
    auto system_time =
1864
        time_point_cast<system_clock::duration>(time - decltype(time)::clock::now() + system_clock::now());
1865
#endif
1866
    return system_clock::to_time_t(system_time);
1867
#else
1868
    struct stat statbuf;
948✔
1869
    if (::stat(path.c_str(), &statbuf) != 0) {
948✔
1870
        throw SystemError(errno, "stat() failed");
×
1871
    }
×
1872
    return statbuf.st_mtime;
948✔
1873
#endif
948✔
1874
}
948✔
1875

1876
File::SizeType File::get_free_space(const std::string& path)
1877
{
186✔
1878
#if REALM_HAVE_STD_FILESYSTEM
1879
    return std::filesystem::space(u8path(path)).available;
1880
#else
1881
    struct statvfs stat;
186✔
1882
    if (statvfs(path.c_str(), &stat) != 0) {
186✔
1883
        throw SystemError(errno, "statvfs() failed");
×
1884
    }
×
1885
    return SizeType(stat.f_bavail) * stat.f_bsize;
186✔
1886
#endif
186✔
1887
}
186✔
1888

1889
#if REALM_HAVE_STD_FILESYSTEM
1890

1891
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1892
{
1893
    try {
1894
        m_iterator = std::filesystem::directory_iterator(u8path(path));
1895
    }
1896
    catch (const std::filesystem::filesystem_error& e) {
1897
        if (e.code() != std::errc::no_such_file_or_directory || !allow_missing)
1898
            throw;
1899
    }
1900
}
1901

1902
DirScanner::~DirScanner() = default;
1903

1904
bool DirScanner::next(std::string& name)
1905
{
1906
    const std::filesystem::directory_iterator end;
1907
    if (m_iterator == end)
1908
        return false;
1909
    name = m_iterator->path().filename().u8string();
1910
    m_iterator++;
1911
    return true;
1912
}
1913

1914
#elif !defined(_WIN32)
1915

1916
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1917
{
159,201✔
1918
    m_dirp = opendir(path.c_str());
159,201✔
1919
    if (!m_dirp) {
159,201✔
1920
        int err = errno; // Eliminate any risk of clobbering
8,373✔
1921
        if (allow_missing && err == ENOENT)
8,373✔
1922
            return;
8,373✔
1923

UNCOV
1924
        std::string msg = format_errno("opendir() failed: %1", err);
×
UNCOV
1925
        switch (err) {
×
1926
            case EACCES:
×
1927
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
1928
            case ENOENT:
×
1929
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
1930
            default:
×
1931
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
UNCOV
1932
        }
×
UNCOV
1933
    }
×
1934
}
159,201✔
1935

1936
DirScanner::~DirScanner() noexcept
1937
{
159,198✔
1938
    if (m_dirp) {
159,198✔
1939
        int r = closedir(m_dirp);
150,825✔
1940
        REALM_ASSERT_RELEASE(r == 0);
150,825✔
1941
    }
150,825✔
1942
}
159,198✔
1943

1944
bool DirScanner::next(std::string& name)
1945
{
431,883✔
1946
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
1947
    !defined(__EMSCRIPTEN__)
1948
#error "readdir() is not known to be thread-safe on this platform"
1949
#endif
1950

150,528✔
1951
    if (!m_dirp)
431,883✔
1952
        return false;
8,373✔
1953

146,337✔
1954
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
146,337✔
1955
// in 32-bits.
146,337✔
1956
#if REALM_HAVE_READDIR64
146,337✔
1957
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
293,826✔
1958
#else
1959
#define REALM_READDIR(...) readdir(__VA_ARGS__)
431,334✔
1960
#endif
277,173✔
1961

146,337✔
1962
    for (;;) {
725,160✔
1963
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
725,160✔
1964
        DirentPtr dirent;
725,160✔
1965
        do {
725,160✔
1966
            // readdir() signals both errors and end-of-stream by returning a
293,826✔
1967
            // null pointer. To distinguish between end-of-stream and errors,
293,826✔
1968
            // the manpage recommends setting errno specifically to 0 before
293,826✔
1969
            // calling it...
293,826✔
1970
            errno = 0;
725,160✔
1971

293,826✔
1972
            dirent = REALM_READDIR(m_dirp);
725,160✔
1973
        } while (!dirent && errno == EAGAIN);
725,160✔
1974

293,826✔
1975
        if (!dirent) {
725,160✔
1976
            if (errno != 0)
150,756✔
1977
                throw SystemError(errno, "readdir() failed");
×
1978
            return false; // End of stream
150,756✔
1979
        }
150,756✔
1980
        const char* name_1 = dirent->d_name;
574,404✔
1981
        std::string name_2 = name_1;
574,404✔
1982
        if (name_2 != "." && name_2 != "..") {
574,404✔
1983
            name = name_2;
272,757✔
1984
            return true;
272,757✔
1985
        }
272,757✔
1986
    }
574,404✔
1987
}
423,510✔
1988

1989
#else
1990

1991
DirScanner::DirScanner(const std::string&, bool)
1992
{
1993
    throw NotImplemented();
1994
}
1995

1996
DirScanner::~DirScanner() noexcept {}
1997

1998
bool DirScanner::next(std::string&)
1999
{
2000
    return false;
2001
}
2002

2003
#endif
2004

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