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

realm / realm-core / 1771

20 Oct 2023 08:58AM UTC coverage: 91.567% (-0.009%) from 91.576%
1771

push

Evergreen

web-flow
Fix blocked DB::open on multiprocess access on exFAT filesystem (#6959)

Fix double file lock and DB::open being blocked with multiple concurrent realm access on fat32/exfat file systems.

When file is truncated on fat32/exfat its uid is available for other files. With multiple processes opening and truncating the same set of files could lead to the situation when within one process get_unique_id will return the same value for different files in timing is right. This breaks proper initialization of static data for interprocess mutexes, so that subsequent locks will hang by trying to lock essentially the same file twice.

94304 of 173552 branches covered (0.0%)

59 of 82 new or added lines in 5 files covered. (71.95%)

53 existing lines in 13 files now uncovered.

230544 of 251776 relevant lines covered (91.57%)

6594884.0 hits per line

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

81.25
/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
{
271,968✔
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)
271,968✔
202
        return true;
114,450✔
203

75,003✔
204
    int err = errno; // Eliminate any risk of clobbering
157,518✔
205
    if (err == EEXIST)
157,518✔
206
        return false;
157,518✔
207

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

3✔
210
    switch (err) {
3✔
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
    }
3✔
217
#endif
3✔
218
}
3✔
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,200✔
260
    if (try_remove_dir(path)) // Throws
10,200✔
261
        return;
10,182✔
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,831✔
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,831✔
277
        return true;
138,711✔
278

60✔
279
    int err = errno; // Eliminate any risk of clobbering
120✔
280
    if (err == ENOENT)
120✔
281
        return false;
117✔
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,613✔
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,613✔
308
        bool allow_missing = true;
128,613✔
309
        DirScanner ds{path, allow_missing}; // Throws
128,613✔
310
        std::string name;
128,613✔
311
        while (ds.next(name)) {                              // Throws
372,375✔
312
            std::string subpath = File::resolve(name, path); // Throws
243,762✔
313
            if (File::is_dir(subpath)) {                     // Throws
243,762✔
314
                try_remove_dir_recursive(subpath);           // Throws
76,797✔
315
            }
76,797✔
316
            else {
166,965✔
317
                File::remove(subpath); // Throws
166,965✔
318
            }
166,965✔
319
        }
243,762✔
320
    }
128,613✔
321
    return try_remove_dir(path); // Throws
128,613✔
322
#endif
128,613✔
323
}
128,613✔
324

325

326
std::string make_temp_dir()
327
{
50,079✔
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,729✔
352
#if REALM_ANDROID
353
    std::string buffer = "/data/local/tmp/realm_XXXXXX";
354
#else
355
    char* tmp_dir_env = getenv("TMPDIR");
50,079✔
356
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
50,079✔
357
    if (!buffer.empty() && buffer.back() != '/') {
50,079✔
358
        buffer += "/";
50,079✔
359
    }
50,079✔
360
    buffer += "realm_XXXXXX";
50,079✔
361
#endif
50,079✔
362

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

371
std::string make_temp_file(const char* prefix)
372
{
37,932✔
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,932✔
390
    std::string base_dir = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
37,932✔
391
    if (!base_dir.empty() && base_dir[base_dir.length() - 1] != '/') {
37,932✔
392
        base_dir = base_dir + "/";
37,932✔
393
    }
37,932✔
394
#endif
37,932✔
395
    std::string tmp = base_dir + prefix + std::string("_XXXXXX") + std::string("\0", 1);
37,932✔
396
    std::unique_ptr<char[]> buffer = std::make_unique<char[]>(tmp.size()); // Throws
37,932✔
397
    memcpy(buffer.get(), tmp.c_str(), tmp.size());
37,932✔
398
    char* filename = buffer.get();
37,932✔
399
    auto fd = mkstemp(filename);
37,932✔
400
    if (fd == -1) {
37,932✔
401
        throw std::system_error(errno, std::system_category(), "mkstemp() failed"); // LCOV_EXCL_LINE
×
402
    }
×
403
    close(fd);
37,932✔
404
    return std::string(filename);
37,932✔
405
#endif
37,932✔
406
}
37,932✔
407

408
size_t page_size()
409
{
8,827,053✔
410
    return cached_page_size;
8,827,053✔
411
}
8,827,053✔
412

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

168,612✔
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

168,612✔
484
    int flags2 = 0;
557,736✔
485
    switch (a) {
557,736✔
486
        case access_ReadOnly:
3,204✔
487
            flags2 = O_RDONLY;
3,204✔
488
            break;
3,204✔
489
        case access_ReadWrite:
554,547✔
490
            flags2 = O_RDWR;
554,547✔
491
            break;
554,547✔
492
    }
557,751✔
493
    switch (c) {
557,751✔
494
        case create_Auto:
530,682✔
495
            flags2 |= O_CREAT;
530,682✔
496
            break;
530,682✔
497
        case create_Never:
26,598✔
498
            break;
26,598✔
499
        case create_Must:
468✔
500
            flags2 |= O_CREAT | O_EXCL;
468✔
501
            break;
468✔
502
    }
557,748✔
503
    if (flags & flag_Trunc)
557,748✔
504
        flags2 |= O_TRUNC;
1,380✔
505
    if (flags & flag_Append)
557,748✔
506
        flags2 |= O_APPEND;
209,559✔
507
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
557,748✔
508
    if (0 <= fd) {
557,748✔
509
        m_fd = fd;
557,631✔
510
        m_have_lock = false;
557,631✔
511
        if (success)
557,631✔
512
            *success = true;
×
513
        return;
557,631✔
514
    }
557,631✔
515

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

60✔
545
#endif
117✔
546
}
117✔
547

548

549
void File::close() noexcept
550
{
1,099,914✔
551
#ifdef _WIN32 // Windows version
552

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

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

562
#else // POSIX version
563

332,661✔
564
    if (m_fd < 0)
1,099,914✔
565
        return;
542,313✔
566
    if (m_have_lock)
557,601✔
567
        unlock();
657✔
568
    int r = ::close(m_fd);
557,601✔
569
    REALM_ASSERT_RELEASE(r == 0);
557,601✔
570
    m_fd = -1;
557,601✔
571

168,546✔
572
#endif
557,601✔
573
}
557,601✔
574

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

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

598
#else // POSIX version
599

262,623✔
600
    char* const data_0 = data;
507,414✔
601
    while (0 < size) {
1,013,466✔
602
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
262,731✔
603
        size_t n = std::min(size, size_t(SSIZE_MAX));
507,627✔
604
        ssize_t r = ::read(fd, data, n);
507,627✔
605
        if (r == 0)
507,627✔
606
            break;
1,575✔
607
        if (r < 0)
506,052✔
608
            goto error; // LCOV_EXCL_LINE
×
609
        REALM_ASSERT_RELEASE(size_t(r) <= n);
506,052✔
610
        size -= size_t(r);
506,052✔
611
        data += size_t(r);
506,052✔
612
    }
506,052✔
613
    return data - data_0;
507,414✔
614

615
error:
×
616
    // LCOV_EXCL_START
617
    throw SystemError(errno, "read() failed");
×
618
// LCOV_EXCL_STOP
262,623✔
619
#endif
507,414✔
620
}
507,414✔
621

622

623
size_t File::read(char* data, size_t size)
624
{
2,103✔
625
    REALM_ASSERT_RELEASE(is_attached());
2,103✔
626

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

936✔
639
    return read_static(m_fd, data, size);
2,103✔
640
}
2,103✔
641

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

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

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

689
#endif
×
690
}
×
691

692
void File::write(const char* data, size_t size)
693
{
57,159✔
694
    REALM_ASSERT_RELEASE(is_attached());
57,159✔
695

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

27,216✔
709
    write_static(m_fd, data, size);
55,698✔
710
}
55,698✔
711

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

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

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

739
File::SizeType File::get_size_static(FileDesc fd)
740
{
3,622,647✔
741
#ifdef _WIN32
742
    LARGE_INTEGER large_int;
743
    if (GetFileSizeEx(fd, &large_int)) {
744
        File::SizeType size;
745
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
746
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
747

748
        return size;
749
    }
750
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
751

752
#else // POSIX version
753

1,843,419✔
754
    struct stat statbuf;
3,622,647✔
755
    if (::fstat(fd, &statbuf) == 0) {
3,622,884✔
756
        SizeType size;
3,622,884✔
757
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
3,622,884✔
758
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
759

1,843,536✔
760
        return size;
3,622,884✔
761
    }
3,622,884✔
762
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
763

2,147,483,647✔
764
#endif
4,294,967,294✔
765
}
4,294,967,294✔
766

767
File::SizeType File::get_size() const
768
{
3,619,236✔
769
    REALM_ASSERT_RELEASE(is_attached());
3,619,236✔
770
    File::SizeType size = get_size_static(m_fd);
3,619,236✔
771

1,841,859✔
772
    if (m_encryption_key) {
3,619,236✔
773
        File::SizeType ret_size = encrypted_size_to_data_size(size);
3,729✔
774
        return ret_size;
3,729✔
775
    }
3,729✔
776
    else
3,615,507✔
777
        return size;
3,615,507✔
778
}
3,619,236✔
779

780

781
void File::resize(SizeType size)
782
{
321,669✔
783
    REALM_ASSERT_RELEASE(is_attached());
321,669✔
784

55,248✔
785
#ifdef _WIN32 // Windows version
786

787
    // Save file position
788
    SizeType p = get_file_pos(m_fd);
789

790
    if (m_encryption_key)
791
        size = data_size_to_encrypted_size(size);
792

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

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

807
    // Restore file position
808
    seek(p);
809

810
#else // POSIX version
811

55,248✔
812
    if (m_encryption_key)
321,669✔
813
        size = data_size_to_encrypted_size(size);
×
814

55,248✔
815
    off_t size2;
321,669✔
816
    if (int_cast_with_overflow_detect(size, size2))
321,669✔
817
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
818

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

55,248✔
830
#endif
321,669✔
831
}
321,669✔
832

833

834
void File::prealloc(size_t size)
835
{
234,378✔
836
    REALM_ASSERT_RELEASE(is_attached());
234,378✔
837

123,630✔
838
    if (size <= to_size_t(get_size())) {
234,378✔
839
        return;
48,243✔
840
    }
48,243✔
841

100,017✔
842
    size_t new_size = size;
186,135✔
843
    if (m_encryption_key) {
186,135✔
844
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
513✔
845
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
513✔
846
        if (new_size < size) {
513✔
847
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow: data_size_to_encrypted_size(" +
×
848
                                                           realm::util::to_string(size) +
×
849
                                                           ") == " + realm::util::to_string(new_size));
×
850
        }
×
851
    }
186,135✔
852

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

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

100,017✔
883
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version
100,017✔
884
    // Mostly Linux only
100,017✔
885
    if (!prealloc_if_supported(0, new_size)) {
100,017✔
886
        consume_space_interlocked();
887
    }
888
#else // Non-atomic fallback
889
#if REALM_PLATFORM_APPLE
86,118✔
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;
86,118✔
894
    if (::fstat(m_fd, &statbuf) != 0) {
86,118✔
895
        int err = errno;
896
        throw SystemError(err, "fstat() inside prealloc() failed");
897
    }
898

899
    size_t allocated_size;
86,118✔
900
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
86,118✔
901
        throw RuntimeError(ErrorCodes::RangeError,
902
                           "Overflow on block conversion to size_t " + realm::util::to_string(statbuf.st_blocks));
903
    }
904
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
86,118✔
905
        throw RuntimeError(ErrorCodes::RangeError, "Overflow computing existing file space allocation blocks: " +
906
                                                       realm::util::to_string(allocated_size) +
907
                                                       " block size: " + realm::util::to_string(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) {
86,118✔
914

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

937
    int ret = 0;
86,118✔
938

939
    do {
86,118✔
940
        ret = ftruncate(m_fd, new_size);
86,118✔
941
    } while (ret == -1 && errno == EINTR);
86,118!
942

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

951
    consume_space_interlocked();
952

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

957
#endif // !(_POSIX_C_SOURCE >= 200112L)
86,118✔
958
}
186,135✔
959

960

961
bool File::prealloc_if_supported(SizeType offset, size_t size)
962
{
100,017✔
963
    REALM_ASSERT_RELEASE(is_attached());
100,017!
964

100,017✔
965
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 200112L // POSIX.1-2001 version
100,017✔
966

100,017✔
967
    REALM_ASSERT_RELEASE(is_prealloc_supported());
100,017✔
968

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

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

100,017✔
982
    if (REALM_LIKELY(status == 0)) {
100,017✔
983
        return true;
100,017✔
984
    }
100,017✔
985

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

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

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

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

1004
#else
1005

1006
    static_cast<void>(offset);
1007
    static_cast<void>(size);
1008

1009
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1010

1011
#endif
UNCOV
1012
    return false;
×
UNCOV
1013
}
×
1014

1015

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

1025
void File::seek(SizeType position)
1026
{
1,533✔
1027
    REALM_ASSERT_RELEASE(is_attached());
1,533✔
1028
    seek_static(m_fd, position);
1,533✔
1029
}
1,533✔
1030

1031
void File::seek_static(FileDesc fd, SizeType position)
1032
{
1,447,491✔
1033
#ifdef _WIN32 // Windows version
1034

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

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

1042
#else // POSIX version
1043

730,254✔
1044
    off_t position2;
1,447,491✔
1045
    if (int_cast_with_overflow_detect(position, position2))
1,447,491✔
1046
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1047

730,254✔
1048
    if (0 <= ::lseek(fd, position2, SEEK_SET))
1,447,491✔
1049
        return;
1,447,491✔
1050
    throw SystemError(errno, "lseek() failed");
×
1051

1052
#endif
×
1053
}
×
1054

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

537✔
1063
#if defined _WIN32 // Windows version
1064

1065
    if (FlushFileBuffers(m_fd))
1066
        return;
1067
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1068

1069
#elif REALM_PLATFORM_APPLE
1070

1071
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
315✔
1072
        return;
315✔
1073
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1074

1075
#else // POSIX version
1076

537✔
1077
    if (::fsync(m_fd) == 0)
537✔
1078
        return;
537✔
1079
    throw SystemError(errno, "fsync() failed");
1080

1081
#endif
1082
}
×
1083

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

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

1109
bool File::rw_lock(bool exclusive, bool non_blocking)
1110
{
421,494✔
1111
    // exclusive blocking rw locks not implemented for emulation
202,719✔
1112
    REALM_ASSERT(!exclusive || non_blocking);
421,494✔
1113

202,719✔
1114
#ifndef REALM_FILELOCK_EMULATION
421,494✔
1115
    return lock(exclusive, non_blocking);
421,494✔
1116
#else
1117
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1118

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

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

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

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

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

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

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

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

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

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

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

1207
bool File::lock(bool exclusive, bool non_blocking)
1208
{
7,572,351✔
1209
    REALM_ASSERT_RELEASE(is_attached());
7,572,351✔
1210
    REALM_ASSERT_RELEASE(!m_have_lock);
7,572,351✔
1211

202,719✔
1212
#ifdef _WIN32 // Windows version
1213

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

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

1269
void File::unlock() noexcept
1270
{
7,479,999✔
1271
    if (!m_have_lock)
7,479,999✔
1272
        return;
×
1273

159,615✔
1274
#ifdef _WIN32 // Windows version
1275
    OVERLAPPED overlapped;
1276
    overlapped.hEvent = 0;
1277
    overlapped.OffsetHigh = 0;
1278
    overlapped.Offset = 0;
1279
    overlapped.Pointer = 0;
1280
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1281
    REALM_ASSERT_RELEASE(r);
1282
#else
1283
    _unlock(m_fd);
7,479,999✔
1284
#endif
7,479,999✔
1285
    m_have_lock = false;
7,479,999✔
1286
}
7,479,999✔
1287

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

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

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

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

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

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

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

1382
#endif // REALM_ENABLE_ENCRYPTION
1383

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

1389

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

1396

1397
void File::sync_map(FileDesc fd, void* addr, size_t size)
1398
{
3,701,370✔
1399
    realm::util::msync(fd, addr, size);
3,701,370✔
1400
}
3,701,370✔
1401

1402

1403
bool File::exists(const std::string& path)
1404
{
7,115,499✔
1405
#if REALM_HAVE_STD_FILESYSTEM
1406
    return std::filesystem::exists(u8path(path));
1407
#else // POSIX
1408
    if (::access(path.c_str(), F_OK) == 0)
7,115,499✔
1409
        return true;
29,889✔
1410
    int err = errno; // Eliminate any risk of clobbering
7,085,610✔
1411
    switch (err) {
7,085,610✔
1412
        case EACCES:
6,297,573✔
1413
        case ENOENT:
7,085,583✔
1414
        case ENOTDIR:
7,085,583✔
1415
            return false;
7,085,583✔
1416
    }
24✔
1417
    throw SystemError(err, "access() failed");
24✔
1418
#endif
24✔
1419
}
24✔
1420

1421

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

1444

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

1454

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

19,242✔
1466
    int err = errno; // Eliminate any risk of clobbering
38,475✔
1467
    if (err == ENOENT)
38,475✔
1468
        return false;
38,457✔
1469

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

1486

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

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

1520

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

1540

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

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

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

1573
File::UniqueID File::get_unique_id() const
1574
{
348,600✔
1575
    REALM_ASSERT_RELEASE(is_attached());
348,600✔
1576
    return File::get_unique_id(m_fd);
348,600✔
1577
}
348,600✔
1578

1579
FileDesc File::get_descriptor() const
1580
{
139,665✔
1581
    return m_fd;
139,665✔
1582
}
139,665✔
1583

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

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

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

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

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

1634
std::string File::get_path() const
1635
{
40,146✔
1636
    return m_path;
40,146✔
1637
}
40,146✔
1638

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

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

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

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

1701

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

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

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

1744

1745
void File::MapBase::unmap() noexcept
1746
{
6,884,319✔
1747
    if (!m_addr)
6,884,319✔
1748
        return;
3,617,589✔
1749
    REALM_ASSERT(m_reservation_size);
3,266,730✔
1750
#if REALM_ENABLE_ENCRYPTION
3,266,730✔
1751
    if (m_encrypted_mapping) {
3,266,730✔
1752
        m_encrypted_mapping = nullptr;
3,411✔
1753
        util::remove_encrypted_mapping(m_addr, m_size);
3,411✔
1754
    }
3,411✔
1755
#endif
3,266,730✔
1756
    ::munmap(m_addr, m_reservation_size);
3,266,730✔
1757
    m_addr = nullptr;
3,266,730✔
1758
    m_size = 0;
3,266,730✔
1759
    m_reservation_size = 0;
3,266,730✔
1760
}
3,266,730✔
1761

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

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

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

1838
void File::MapBase::sync()
1839
{
3,701,415✔
1840
    REALM_ASSERT(m_addr);
3,701,415✔
1841

1,875,489✔
1842
    File::sync_map(m_fd, m_addr, m_size);
3,701,415✔
1843
}
3,701,415✔
1844

1845
void File::MapBase::flush()
1846
{
3,670,710✔
1847
    REALM_ASSERT(m_addr);
3,670,710✔
1848
#if REALM_ENABLE_ENCRYPTION
3,670,710✔
1849
    if (m_encrypted_mapping) {
3,670,710✔
1850
        realm::util::encryption_flush(m_encrypted_mapping);
1,053✔
1851
    }
1,053✔
1852
#endif
3,670,710✔
1853
}
3,670,710✔
1854

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

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

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

1890
#if REALM_HAVE_STD_FILESYSTEM
1891

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

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

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

1915
#elif !defined(_WIN32)
1916

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

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

1937
DirScanner::~DirScanner() noexcept
1938
{
159,627✔
1939
    if (m_dirp) {
159,627✔
1940
        int r = closedir(m_dirp);
151,479✔
1941
        REALM_ASSERT_RELEASE(r == 0);
151,479✔
1942
    }
151,479✔
1943
}
159,627✔
1944

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

151,077✔
1952
    if (!m_dirp)
433,281✔
1953
        return false;
8,148✔
1954

147,000✔
1955
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
147,000✔
1956
// in 32-bits.
147,000✔
1957
#if REALM_HAVE_READDIR64
147,000✔
1958
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
295,149✔
1959
#else
1960
#define REALM_READDIR(...) readdir(__VA_ARGS__)
432,945✔
1961
#endif
278,133✔
1962

147,000✔
1963
    for (;;) {
728,094✔
1964
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
728,094✔
1965
        DirentPtr dirent;
728,094✔
1966
        do {
728,094✔
1967
            // readdir() signals both errors and end-of-stream by returning a
295,149✔
1968
            // null pointer. To distinguish between end-of-stream and errors,
295,149✔
1969
            // the manpage recommends setting errno specifically to 0 before
295,149✔
1970
            // calling it...
295,149✔
1971
            errno = 0;
728,094✔
1972

295,149✔
1973
            dirent = REALM_READDIR(m_dirp);
728,094✔
1974
        } while (!dirent && errno == EAGAIN);
728,094✔
1975

295,149✔
1976
        if (!dirent) {
728,094✔
1977
            if (errno != 0)
151,422✔
1978
                throw SystemError(errno, "readdir() failed");
×
1979
            return false; // End of stream
151,422✔
1980
        }
151,422✔
1981
        const char* name_1 = dirent->d_name;
576,672✔
1982
        std::string name_2 = name_1;
576,672✔
1983
        if (name_2 != "." && name_2 != "..") {
576,672✔
1984
            name = name_2;
273,711✔
1985
            return true;
273,711✔
1986
        }
273,711✔
1987
    }
576,672✔
1988
}
425,133✔
1989

1990
#else
1991

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

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

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

2004
#endif
2005

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