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

realm / realm-core / 2085

01 Mar 2024 12:26PM UTC coverage: 90.926% (-0.001%) from 90.927%
2085

push

Evergreen

jedelbo
Avoid doing unneeded logger work in Replication

Most of the replication log statements do some work including memory
allocations which are then thrown away if the log level it too high, so always
check the log level first. A few places don't actually benefit from this, but
it's easier to consistently check the log level every time.

93986 of 173116 branches covered (54.29%)

63 of 100 new or added lines in 2 files covered. (63.0%)

114 existing lines in 17 files now uncovered.

238379 of 262169 relevant lines covered (90.93%)

6007877.32 hits per line

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

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

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

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

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

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

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

49
using namespace realm::util;
50

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

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

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

92
#ifdef _WIN32
93

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

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

119
    WindowsFileHandleHolder(WindowsFileHandleHolder&&) = delete;
120
    WindowsFileHandleHolder(const WindowsFileHandleHolder&) = delete;
121
    WindowsFileHandleHolder& operator=(WindowsFileHandleHolder&&) = delete;
122
    WindowsFileHandleHolder& operator=(const WindowsFileHandleHolder&) = delete;
123

124
    operator HANDLE() const noexcept
125
    {
126
        return handle;
127
    }
128

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

136
    HANDLE handle = INVALID_HANDLE_VALUE;
137
};
138

139
#endif
140

141
#if REALM_HAVE_STD_FILESYSTEM
142
using std::filesystem::u8path;
143

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

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

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

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

176
} // anonymous namespace
177

178

179
namespace realm::util {
180
namespace {
181

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

192
} // anonymous namespace
193

194

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

72,054✔
206
    int err = errno; // Eliminate any risk of clobbering
143,976✔
207
    if (err == EEXIST)
143,976✔
208
        return false;
143,973✔
209

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

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

222

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

230

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

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

263

264
void remove_dir(const std::string& path)
265
{
8,565✔
266
    if (try_remove_dir(path)) // Throws
8,565✔
267
        return;
8,547✔
268
    int err = ENOENT;
18✔
269
    std::string msg = format_errno("Failed to remove directory '%2': %1", err, path);
18✔
270
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
18✔
271
}
18✔
272

273

274
bool try_remove_dir(const std::string& path)
275
{
111,798✔
276
#if REALM_HAVE_STD_FILESYSTEM
277
    std::error_code error;
278
    bool result = std::filesystem::remove(u8path(path), error);
279
    throwIfFileError(error, path);
280
    return result;
281
#else // POSIX
282
    if (::rmdir(path.c_str()) == 0)
111,798✔
283
        return true;
111,684✔
284

57✔
285
    int err = errno; // Eliminate any risk of clobbering
114✔
286
    if (err == ENOENT)
114✔
287
        return false;
111✔
288

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

304

305
bool try_remove_dir_recursive(const std::string& path)
306
{
103,206✔
307
#if REALM_HAVE_STD_FILESYSTEM
308
    std::error_code error;
309
    auto removed_count = std::filesystem::remove_all(u8path(path), error);
310
    throwIfFileError(error, path);
311
    return removed_count > 0;
312
#else
313
    {
103,206✔
314
        bool allow_missing = true;
103,206✔
315
        DirScanner ds{path, allow_missing}; // Throws
103,206✔
316
        std::string name;
103,206✔
317
        while (ds.next(name)) {                              // Throws
276,843✔
318
            std::string subpath = File::resolve(name, path); // Throws
173,637✔
319
            if (File::is_dir(subpath)) {                     // Throws
173,637✔
320
                try_remove_dir_recursive(subpath);           // Throws
47,640✔
321
            }
47,640✔
322
            else {
125,997✔
323
                File::remove(subpath); // Throws
125,997✔
324
            }
125,997✔
325
        }
173,637✔
326
    }
103,206✔
327
    return try_remove_dir(path); // Throws
103,206✔
328
#endif
103,206✔
329
}
103,206✔
330

331

332
std::string make_temp_dir()
333
{
43,101✔
334
#ifdef _WIN32 // Windows version
335
    std::filesystem::path temp = std::filesystem::temp_directory_path();
336

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

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

356
#else // POSIX.1-2008 version
357

21,240✔
358
#if REALM_ANDROID
359
    std::string buffer = "/data/local/tmp/realm_XXXXXX";
360
#else
361
    char* tmp_dir_env = getenv("TMPDIR");
43,101✔
362
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
43,101✔
363
    if (!buffer.empty() && buffer.back() != '/') {
43,101✔
364
        buffer += "/";
43,101✔
365
    }
43,101✔
366
    buffer += "realm_XXXXXX";
43,101✔
367
#endif
43,101✔
368

21,240✔
369
    if (mkdtemp(buffer.data()) == 0) {
43,101✔
370
        int err = errno;
×
371
        throw SystemError(err, util::format("Failed to create temporary directory: %1", err)); // LCOV_EXCL_LINE
372
    }
×
373
    return buffer;
43,101✔
374
#endif
43,101✔
375
}
43,101✔
376

377
std::string make_temp_file(const char* prefix)
378
{
38,139✔
379
#ifdef _WIN32 // Windows version
380
    std::filesystem::path temp = std::filesystem::temp_directory_path();
381

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

388
    return std::filesystem::path(buffer).u8string();
389

390
#else // POSIX.1-2008 version
391

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

414
size_t page_size()
415
{
3,852,039✔
416
    return cached_page_size;
3,852,039✔
417
}
3,852,039✔
418

419
void File::open_internal(const std::string& path, AccessMode a, CreateMode c, int flags, bool* success)
420
{
395,064✔
421
    REALM_ASSERT_RELEASE(!is_attached());
395,064✔
422
    m_path = path; // for error reporting and debugging
395,064✔
423
    m_cached_unique_id = {};
395,064✔
424

133,611✔
425
#ifdef _WIN32 // Windows version
426

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

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

489
#else // POSIX version
490

133,611✔
491
    int flags2 = 0;
395,064✔
492
    switch (a) {
395,064✔
493
        case access_ReadOnly:
2,895✔
494
            flags2 = O_RDONLY;
2,895✔
495
            break;
2,895✔
496
        case access_ReadWrite:
392,169✔
497
            flags2 = O_RDWR;
392,169✔
498
            break;
392,169✔
499
    }
395,064✔
500
    switch (c) {
395,064✔
501
        case create_Auto:
363,912✔
502
            flags2 |= O_CREAT;
363,912✔
503
            break;
363,912✔
504
        case create_Never:
30,312✔
505
            break;
30,312✔
506
        case create_Must:
840✔
507
            flags2 |= O_CREAT | O_EXCL;
840✔
508
            break;
840✔
509
    }
395,067✔
510
    if (flags & flag_Trunc)
395,067✔
511
        flags2 |= O_TRUNC;
840✔
512
    if (flags & flag_Append)
395,067✔
513
        flags2 |= O_APPEND;
125,988✔
514
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
395,067✔
515
    if (0 <= fd) {
395,067✔
516
        m_fd = fd;
394,905✔
517
        m_have_lock = false;
394,905✔
518
        if (success)
394,905✔
519
            *success = true;
216✔
520
        return;
394,905✔
521
    }
394,905✔
522

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

78✔
552
#endif
138✔
553
}
138✔
554

555

556
void File::close() noexcept
557
{
748,737✔
558
#ifdef _WIN32 // Windows version
559

560
    if (!m_fd)
561
        return;
562
    if (m_have_lock)
563
        unlock();
564

565
    BOOL r = CloseHandle(m_fd);
566
    REALM_ASSERT_RELEASE(r);
567
    m_fd = nullptr;
568

569
#else // POSIX version
570

245,952✔
571
    if (m_fd < 0)
748,737✔
572
        return;
353,835✔
573
    if (m_have_lock)
394,902✔
574
        unlock();
153✔
575
    int r = ::close(m_fd);
394,902✔
576
    REALM_ASSERT_RELEASE(r == 0);
394,902✔
577
    m_fd = -1;
394,902✔
578

133,536✔
579
#endif
394,902✔
580
}
394,902✔
581

582
void File::close_static(FileDesc fd)
583
{
1,944✔
584
#ifdef _WIN32
585
    if (!fd)
586
        return;
587

588
    if (!CloseHandle(fd))
589
        throw std::system_error(GetLastError(), std::system_category(),
590
                                "CloseHandle() failed from File::close_static()");
591
#else
592
    if (fd < 0)
1,944✔
593
        return;
×
594

852✔
595
    int ret = -1;
1,944✔
596
    do {
1,944✔
597
        ret = ::close(fd);
1,944✔
598
    } while (ret == -1 && errno == EINTR);
1,944!
599

852✔
600
    if (ret != 0) {
1,944✔
601
        int err = errno; // Eliminate any risk of clobbering
×
602
        if (err == EBADF || err == EIO)
×
603
            throw SystemError(err, "File::close_static() failed");
×
604
    }
×
605
#endif
1,944✔
606
}
1,944✔
607

608
size_t File::read_static(FileDesc fd, char* data, size_t size)
609
{
504,807✔
610
#ifdef _WIN32 // Windows version
611
    char* const data_0 = data;
612
    while (0 < size) {
613
        DWORD n = std::numeric_limits<DWORD>::max();
614
        if (int_less_than(size, n))
615
            n = static_cast<DWORD>(size);
616
        DWORD r = 0;
617
        if (!ReadFile(fd, data, n, &r, 0))
618
            goto error;
619
        if (r == 0)
620
            break;
621
        REALM_ASSERT_RELEASE(r <= n);
622
        size -= size_t(r);
623
        data += size_t(r);
624
    }
625
    return data - data_0;
626

627
error:
628
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
629
    throw SystemError(int(err), "ReadFile() failed");
630

631
#else // POSIX version
632

262,710✔
633
    char* const data_0 = data;
504,807✔
634
    while (0 < size) {
1,008,711✔
635
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
262,797✔
636
        size_t n = std::min(size, size_t(SSIZE_MAX));
504,897✔
637
        ssize_t r = ::read(fd, data, n);
504,897✔
638
        if (r == 0)
504,897✔
639
            break;
993✔
640
        if (r < 0)
503,904✔
641
            goto error; // LCOV_EXCL_LINE
642
        REALM_ASSERT_RELEASE(size_t(r) <= n);
503,904✔
643
        size -= size_t(r);
503,904✔
644
        data += size_t(r);
503,904✔
645
    }
503,904✔
646
    return data - data_0;
504,807✔
647

648
error:
×
649
    // LCOV_EXCL_START
650
    throw SystemError(errno, "read() failed");
×
651
// LCOV_EXCL_STOP
262,710✔
652
#endif
504,807✔
653
}
504,807✔
654

655

656
size_t File::read(char* data, size_t size)
657
{
1,314✔
658
    REALM_ASSERT_RELEASE(is_attached());
1,314✔
659

909✔
660
    if (m_encryption_key) {
1,314✔
661
        uint64_t pos_original = File::get_file_pos(m_fd);
×
662
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
×
663
        size_t pos = size_t(pos_original);
×
664
        Map<char> read_map(*this, access_ReadOnly, static_cast<size_t>(pos + size));
×
665
        realm::util::encryption_read_barrier(read_map, pos, size);
×
666
        memcpy(data, read_map.get_addr() + pos, size);
×
667
        uint64_t cur = File::get_file_pos(m_fd);
×
668
        seek_static(m_fd, cur + size);
×
669
        return read_map.get_size() - pos;
×
670
    }
×
671

909✔
672
    return read_static(m_fd, data, size);
1,314✔
673
}
1,314✔
674

675
void File::write_static(FileDesc fd, const char* data, size_t size)
676
{
260,937✔
677
#ifdef _WIN32
678
    while (0 < size) {
679
        DWORD n = std::numeric_limits<DWORD>::max();
680
        if (int_less_than(size, n))
681
            n = static_cast<DWORD>(size);
682
        DWORD r = 0;
683
        if (!WriteFile(fd, data, n, &r, 0))
684
            goto error;
685
        REALM_ASSERT_RELEASE(r == n); // Partial writes are not possible.
686
        size -= size_t(r);
687
        data += size_t(r);
688
    }
689
    return;
690

691
error:
692
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
693
    if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
694
        std::string msg = get_last_error_msg("WriteFile() failed: ", err);
695
        throw OutOfDiskSpace(msg);
696
    }
697
    throw SystemError(err, "WriteFile() failed");
698
#else
699
    while (0 < size) {
521,739✔
700
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
124,782✔
701
        size_t n = std::min(size, size_t(SSIZE_MAX));
260,802✔
702
        ssize_t r = ::write(fd, data, n);
260,802✔
703
        if (r < 0)
260,802✔
704
            goto error; // LCOV_EXCL_LINE
705
        REALM_ASSERT_RELEASE(r != 0);
260,802✔
706
        REALM_ASSERT_RELEASE(size_t(r) <= n);
260,802✔
707
        size -= size_t(r);
260,802✔
708
        data += size_t(r);
260,802✔
709
    }
260,802✔
710
    return;
260,937✔
711

712
error:
×
713
    // LCOV_EXCL_START
714
    int err = errno; // Eliminate any risk of clobbering
×
715
    auto msg = format_errno("write() failed: %1", err);
×
716
    if (err == ENOSPC || err == EDQUOT) {
×
717
        throw OutOfDiskSpace(msg);
×
718
    }
×
719
    throw SystemError(err, msg);
×
720
    // LCOV_EXCL_STOP
721

722
#endif
×
723
}
×
724

725
void File::write(const char* data, size_t size)
726
{
45,285✔
727
    REALM_ASSERT_RELEASE(is_attached());
45,285✔
728

22,305✔
729
    if (m_encryption_key) {
45,285✔
730
        uint64_t pos_original = get_file_pos(m_fd);
1,458✔
731
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
1,458✔
732
        size_t pos = size_t(pos_original);
1,458✔
733
        Map<char> write_map(*this, access_ReadWrite, static_cast<size_t>(pos + size));
1,458✔
734
        realm::util::encryption_read_barrier(write_map, pos, size);
1,458✔
735
        memcpy(write_map.get_addr() + pos, data, size);
1,458✔
736
        realm::util::encryption_write_barrier(write_map, pos, size);
1,458✔
737
        uint64_t cur = get_file_pos(m_fd);
1,458✔
738
        seek(cur + size);
1,458✔
739
        return;
1,458✔
740
    }
1,458✔
741

21,729✔
742
    write_static(m_fd, data, size);
43,827✔
743
}
43,827✔
744

745
uint64_t File::get_file_pos(FileDesc fd)
746
{
723,519✔
747
#ifdef _WIN32
748
    LONG high_dword = 0;
749
    LARGE_INTEGER li;
750
    LARGE_INTEGER res;
751
    li.QuadPart = 0;
752
    bool ok = SetFilePointerEx(fd, li, &res, FILE_CURRENT);
753
    if (!ok)
754
        throw SystemError(GetLastError(), "SetFilePointer() failed");
755

756
    return uint64_t(res.QuadPart);
757
#else
758
    auto pos = lseek(fd, 0, SEEK_CUR);
723,519✔
759
    if (pos < 0) {
723,519✔
760
        throw SystemError(errno, "lseek() failed");
×
761
    }
×
762
    return uint64_t(pos);
723,519✔
763
#endif
723,519✔
764
}
723,519✔
765

766
File::SizeType File::get_size_static(const std::string& path)
767
{
1,353✔
768
    File f(path);
1,353✔
769
    return f.get_size();
1,353✔
770
}
1,353✔
771

772
File::SizeType File::get_size_static(FileDesc fd)
773
{
1,724,958✔
774
#ifdef _WIN32
775
    LARGE_INTEGER large_int;
776
    if (GetFileSizeEx(fd, &large_int)) {
777
        File::SizeType size;
778
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
779
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
780

781
        return size;
782
    }
783
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
784

785
#else // POSIX version
786

885,852✔
787
    struct stat statbuf;
1,724,958✔
788
    if (::fstat(fd, &statbuf) == 0) {
1,725,171✔
789
        SizeType size;
1,725,171✔
790
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,725,171✔
791
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
792

885,951✔
793
        return size;
1,725,171✔
794
    }
1,725,171✔
795
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
796

2,147,483,647✔
797
#endif
4,294,967,294✔
798
}
4,294,967,294✔
799

800
File::SizeType File::get_size() const
801
{
1,721,682✔
802
    REALM_ASSERT_RELEASE(is_attached());
1,721,682✔
803
    File::SizeType size = get_size_static(m_fd);
1,721,682✔
804

884,268✔
805
    if (m_encryption_key) {
1,721,682✔
806
        File::SizeType ret_size = encrypted_size_to_data_size(size);
3,645✔
807
        return ret_size;
3,645✔
808
    }
3,645✔
809
    else
1,718,037✔
810
        return size;
1,718,037✔
811
}
1,721,682✔
812

813

814
void File::resize(SizeType size)
815
{
183,969✔
816
    REALM_ASSERT_RELEASE(is_attached());
183,969✔
817

28,599✔
818
#ifdef _WIN32 // Windows version
819

820
    // Save file position
821
    SizeType p = get_file_pos(m_fd);
822

823
    if (m_encryption_key)
824
        size = data_size_to_encrypted_size(size);
825

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

831
    if (!SetEndOfFile(m_fd)) {
832
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
833
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
834
            std::string msg = get_last_error_msg("SetEndOfFile() failed: ", err);
835
            throw OutOfDiskSpace(msg);
836
        }
837
        throw SystemError(int(err), "SetEndOfFile() failed");
838
    }
839

840
    // Restore file position
841
    seek(p);
842

843
#else // POSIX version
844

28,599✔
845
    if (m_encryption_key)
183,969✔
846
        size = data_size_to_encrypted_size(size);
18✔
847

28,599✔
848
    off_t size2;
183,969✔
849
    if (int_cast_with_overflow_detect(size, size2))
183,969✔
850
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
851

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

28,599✔
863
#endif
183,969✔
864
}
183,969✔
865

866

867
void File::prealloc(size_t size)
868
{
152,739✔
869
    REALM_ASSERT_RELEASE(is_attached());
152,739✔
870

80,916✔
871
    if (size <= to_size_t(get_size())) {
152,739✔
872
        return;
37,119✔
873
    }
37,119✔
874

62,784✔
875
    size_t new_size = size;
115,620✔
876
    if (m_encryption_key) {
115,620✔
877
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
630✔
878
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
630✔
879
        if (new_size < size) {
630✔
880
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow: data_size_to_encrypted_size(" +
×
881
                                                           realm::util::to_string(size) +
×
882
                                                           ") == " + realm::util::to_string(new_size));
×
883
        }
×
884
    }
115,620✔
885

62,784✔
886
    auto manually_consume_space = [&]() {
115,620✔
887
        constexpr size_t chunk_size = 4096;
×
888
        int64_t original_size = get_size_static(m_fd); // raw size
×
889
        seek(original_size);
×
890
        size_t num_bytes = size_t(new_size - original_size);
×
891
        std::string zeros(chunk_size, '\0');
×
892
        while (num_bytes > 0) {
×
893
            size_t t = num_bytes > chunk_size ? chunk_size : num_bytes;
×
894
            write_static(m_fd, zeros.c_str(), t);
×
895
            num_bytes -= t;
×
896
        }
×
897
    };
×
898

62,784✔
899
    auto consume_space_interlocked = [&] {
62,784✔
900
#if REALM_ENABLE_ENCRYPTION
×
901
        if (m_encryption_key) {
×
902
            // We need to prevent concurrent calls to lseek from the encryption layer
903
            // while we're writing to the file to extend it. Otherwise an intervening
904
            // lseek may redirect the writing process, causing file corruption.
905
            UniqueLock lck(util::mapping_mutex);
×
906
            manually_consume_space();
×
907
        }
×
908
        else {
×
909
            manually_consume_space();
×
910
        }
×
911
#else
912
        manually_consume_space();
913
#endif
914
    };
×
915

62,784✔
916
#if REALM_HAVE_POSIX_FALLOCATE
62,784✔
917
    // Mostly Linux only
62,784✔
918
    if (!prealloc_if_supported(0, new_size)) {
62,784✔
919
        consume_space_interlocked();
920
    }
921
#else // Non-atomic fallback
922
#if REALM_PLATFORM_APPLE
52,836✔
923
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
924
    // ftruncate().
925

926
    struct stat statbuf;
52,836✔
927
    if (::fstat(m_fd, &statbuf) != 0) {
52,836✔
928
        int err = errno;
929
        throw SystemError(err, "fstat() inside prealloc() failed");
930
    }
931

932
    size_t allocated_size;
52,836✔
933
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
52,836✔
934
        throw RuntimeError(ErrorCodes::RangeError,
935
                           "Overflow on block conversion to size_t " + realm::util::to_string(statbuf.st_blocks));
936
    }
937
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
52,836✔
938
        throw RuntimeError(ErrorCodes::RangeError, "Overflow computing existing file space allocation blocks: " +
939
                                                       realm::util::to_string(allocated_size) +
940
                                                       " block size: " + realm::util::to_string(S_BLKSIZE));
941
    }
942

943
    // Only attempt to preallocate space if there's not already sufficient free space in the file.
944
    // APFS would fail with EINVAL if we attempted it, and HFS+ would preallocate extra space unnecessarily.
945
    // See <https://github.com/realm/realm-core/issues/3005> for details.
946
    if (new_size > allocated_size) {
52,836✔
947

948
        off_t to_allocate = static_cast<off_t>(new_size - statbuf.st_size);
52,800✔
949
        fstore_t store = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, to_allocate, 0};
52,800✔
950
        int ret = 0;
52,800✔
951
        do {
52,800✔
952
            ret = fcntl(m_fd, F_PREALLOCATE, &store);
52,800✔
953
        } while (ret == -1 && errno == EINTR);
52,800!
954
        if (ret == -1) {
52,800✔
955
            // Consider fcntl() as an optimization on Apple devices, where if it fails,
956
            // we fall back to manually consuming space which is slower but may succeed in some
957
            // cases where fcntl fails. Some known cases are:
958
            // 1) There's a timing sensitive bug on APFS which causes fcntl to sometimes throw EINVAL.
959
            // This might not be the case, but we'll fall back and attempt to manually allocate all the requested
960
            // space. Worst case, this might also fail, but there is also a chance it will succeed. We don't
961
            // call this in the first place because using fcntl(F_PREALLOCATE) will be faster if it works (it has
962
            // been reliable on HSF+).
963
            // 2) fcntl will fail with ENOTSUP on non-supported file systems such as ExFAT. In this case
964
            // the fallback should succeed.
965
            // 3) if there is some other error such as no space left (ENOSPC) we will expect to fail again later
966
            consume_space_interlocked();
967
        }
968
    }
52,800✔
969

970
    int ret = 0;
52,836✔
971

972
    do {
52,836✔
973
        ret = ftruncate(m_fd, new_size);
52,836✔
974
    } while (ret == -1 && errno == EINTR);
52,836!
975

976
    if (ret != 0) {
52,836✔
977
        int err = errno;
978
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
979
        // so this is some other runtime error and not OutOfDiskSpace
980
        throw SystemError(err, "ftruncate() inside prealloc() failed");
981
    }
982
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
983

984
    consume_space_interlocked();
985

986
#else
987
#error Please check if/how your OS supports file preallocation
988
#endif
989

990
#endif // REALM_HAVE_POSIX_FALLOCATE
52,836✔
991
}
115,620✔
992

993

994
bool File::prealloc_if_supported(SizeType offset, size_t size)
995
{
62,784✔
996
    REALM_ASSERT_RELEASE(is_attached());
62,784!
997

62,784✔
998
#if REALM_HAVE_POSIX_FALLOCATE
62,784✔
999

62,784✔
1000
    REALM_ASSERT_RELEASE(is_prealloc_supported());
62,784✔
1001

62,784✔
1002
    if (size == 0) {
62,784✔
1003
        // calling posix_fallocate with a size of 0 will cause a return of EINVAL
1004
        // since this is a meaningless operation anyway, we just return early here
1005
        return true;
1006
    }
1007

62,784✔
1008
    // posix_fallocate() does not set errno, it returns the error (if any) or zero.
62,784✔
1009
    // It is also possible for it to be interrupted by EINTR according to some man pages (ex fedora 24)
62,784✔
1010
    int status;
62,784✔
1011
    do {
62,784✔
1012
        status = ::posix_fallocate(m_fd, offset, size);
62,784✔
1013
    } while (status == EINTR);
62,784✔
1014

62,784✔
1015
    if (REALM_LIKELY(status == 0)) {
62,784✔
1016
        return true;
62,784✔
1017
    }
62,784✔
1018

1019
    if (status == EINVAL || status == EPERM || status == EOPNOTSUPP) {
1020
        return false; // Retry with non-atomic version
1021
    }
1022

1023
    auto msg = format_errno("posix_fallocate() failed: %1", status);
1024
    if (status == ENOSPC || status == EDQUOT) {
1025
        throw OutOfDiskSpace(msg);
1026
    }
1027
    throw SystemError(status, msg);
1028

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

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

1037
#else
1038

1039
    static_cast<void>(offset);
1040
    static_cast<void>(size);
1041

1042
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1043

1044
#endif
UNCOV
1045
    return false;
×
UNCOV
1046
}
×
1047

1048

1049
bool File::is_prealloc_supported()
1050
{
62,784✔
1051
#if REALM_HAVE_POSIX_FALLOCATE
62,784✔
1052
    return true;
62,784✔
1053
#else
1054
    return false;
1055
#endif
1056
}
62,784✔
1057

1058
void File::seek(SizeType position)
1059
{
39,333✔
1060
    REALM_ASSERT_RELEASE(is_attached());
39,333✔
1061
    seek_static(m_fd, position);
39,333✔
1062
}
39,333✔
1063

1064
void File::seek_static(FileDesc fd, SizeType position)
1065
{
1,480,539✔
1066
#ifdef _WIN32 // Windows version
1067

1068
    LARGE_INTEGER large_int;
1069
    if (int_cast_with_overflow_detect(position, large_int.QuadPart))
1070
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1071

1072
    if (!SetFilePointerEx(fd, large_int, 0, FILE_BEGIN))
1073
        throw SystemError(GetLastError(), "SetFilePointerEx() failed");
1074

1075
#else // POSIX version
1076

749,046✔
1077
    off_t position2;
1,480,539✔
1078
    if (int_cast_with_overflow_detect(position, position2))
1,480,539✔
1079
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1080

749,046✔
1081
    if (0 <= ::lseek(fd, position2, SEEK_SET))
1,480,539✔
1082
        return;
1,480,539✔
1083
    throw SystemError(errno, "lseek() failed");
×
1084

1085
#endif
×
1086
}
×
1087

1088
// FIXME: The current implementation may not guarantee that data is
1089
// actually written to disk. POSIX is rather vague on what fsync() has
1090
// to do unless _POSIX_SYNCHRONIZED_IO is defined. See also
1091
// http://www.humboldt.co.uk/2009/03/fsync-across-platforms.html.
1092
void File::sync()
1093
{
840✔
1094
    REALM_ASSERT_RELEASE(is_attached());
840✔
1095

531✔
1096
#if defined _WIN32 // Windows version
1097

1098
    if (FlushFileBuffers(m_fd))
1099
        return;
1100
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1101

1102
#elif REALM_PLATFORM_APPLE
1103

1104
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
309✔
1105
        return;
309✔
1106
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1107

1108
#else // POSIX version
1109

531✔
1110
    if (::fsync(m_fd) == 0)
531✔
1111
        return;
531✔
1112
    throw SystemError(errno, "fsync() failed");
1113

1114
#endif
1115
}
×
1116

1117
void File::barrier()
1118
{
432✔
1119
#if REALM_PLATFORM_APPLE
216✔
1120
    if (::fcntl(m_fd, F_BARRIERFSYNC) == 0)
216✔
1121
        return;
216✔
1122
        // If fcntl fails, we fallback to full sync.
1123
        // This is known to occur on exFAT which does not support F_BARRIERSYNC.
1124
#endif
1125
    sync();
216✔
1126
}
216✔
1127

1128
#ifndef _WIN32
1129
// little helper
1130
static void _unlock(int m_fd)
1131
{
3,754,410✔
1132
    int r;
3,754,410✔
1133
    do {
3,754,410✔
1134
        r = flock(m_fd, LOCK_UN);
3,754,410✔
1135
    } while (r != 0 && errno == EINTR);
3,754,410!
1136
    if (r) {
3,754,410✔
1137
        throw SystemError(errno, "File::unlock() has failed");
×
1138
    }
×
1139
}
3,754,410✔
1140
#endif
1141

1142
bool File::rw_lock(bool exclusive, bool non_blocking)
1143
{
369,216✔
1144
    // exclusive blocking rw locks not implemented for emulation
184,650✔
1145
    REALM_ASSERT(!exclusive || non_blocking);
369,216✔
1146

184,650✔
1147
#ifndef REALM_FILELOCK_EMULATION
369,216✔
1148
    return lock(exclusive, non_blocking);
369,216✔
1149
#else
1150
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1151

1152
    // First obtain an exclusive lock on the file proper
1153
    int operation = LOCK_EX;
1154
    if (non_blocking)
1155
        operation |= LOCK_NB;
1156
    int status;
1157
    do {
1158
        status = flock(m_fd, operation);
1159
    } while (status != 0 && errno == EINTR);
1160
    if (status != 0 && errno == EWOULDBLOCK)
1161
        return false;
1162
    if (status != 0)
1163
        throw SystemError(errno, "flock() failed");
1164
    m_has_exclusive_lock = true;
1165

1166
    // Every path through this function except for successfully acquiring an
1167
    // exclusive lock needs to release the flock() before returning.
1168
    UnlockGuard ulg(*this);
1169

1170
    // now use a named pipe to emulate locking in conjunction with using exclusive lock
1171
    // on the file proper.
1172
    // exclusively locked: we can't sucessfully write to the pipe.
1173
    //                     AND we continue to hold the exclusive lock.
1174
    //                     (unlock must lift the exclusive lock).
1175
    // shared locked: we CAN succesfully write to the pipe. We open the pipe for reading
1176
    //                before releasing the exclusive lock.
1177
    //                (unlock must close the pipe for reading)
1178
    REALM_ASSERT_RELEASE(m_pipe_fd == -1);
1179
    if (m_fifo_path.empty())
1180
        m_fifo_path = m_path + ".fifo";
1181

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

1186
    // Optimistically try to open the fifo. This may fail due to the fifo not
1187
    // existing, but since it usually exists this is faster than trying to create
1188
    // it first.
1189
    int fd = ::open(m_fifo_path.c_str(), mode);
1190
    if (fd == -1) {
1191
        int err = errno;
1192
        if (exclusive) {
1193
            if (err == ENOENT || err == ENXIO) {
1194
                // If the fifo either doesn't exist or there's no readers with the
1195
                // other end of the pipe open (ENXIO) then we have an exclusive lock
1196
                // and are done.
1197
                ulg.release();
1198
                return true;
1199
            }
1200

1201
            // Otherwise we got an unexpected error
1202
            throw std::system_error(err, std::system_category(), "opening lock fifo for writing failed");
1203
        }
1204

1205
        if (err == ENOENT) {
1206
            // The fifo doesn't exist and we're opening in shared mode, so we
1207
            // need to create it.
1208
            if (!m_fifo_dir_path.empty())
1209
                try_make_dir(m_fifo_dir_path);
1210
            status = mkfifo(m_fifo_path.c_str(), 0666);
1211
            if (status != 0)
1212
                throw std::system_error(errno, std::system_category(), "creating lock fifo for reading failed");
1213

1214
            // Try again to open the fifo now that it exists
1215
            fd = ::open(m_fifo_path.c_str(), mode);
1216
            err = errno;
1217
        }
1218

1219
        if (fd == -1)
1220
            throw std::system_error(err, std::system_category(), "opening lock fifo for reading failed");
1221
    }
1222

1223
    // We successfully opened the pipe. If we're trying to acquire an exclusive
1224
    // lock that means there's a reader (aka a shared lock) and we've failed.
1225
    // Release the exclusive lock and back out.
1226
    if (exclusive) {
1227
        ::close(fd);
1228
        return false;
1229
    }
1230

1231
    // We're in shared mode, so opening the fifo means we've successfully acquired
1232
    // a shared lock and are done.
1233
    ulg.release();
1234
    rw_unlock();
1235
    m_pipe_fd = fd;
1236
    return true;
1237
#endif // REALM_FILELOCK_EMULATION
1238
}
369,216✔
1239

1240
bool File::lock(bool exclusive, bool non_blocking)
1241
{
3,877,554✔
1242
    REALM_ASSERT_RELEASE(is_attached());
3,877,554✔
1243
    REALM_ASSERT_RELEASE(!m_have_lock);
3,877,554✔
1244

184,656✔
1245
#ifdef _WIN32 // Windows version
1246

1247
    // Under Windows a file lock must be explicitely released before
1248
    // the file is closed. It will eventually be released by the
1249
    // system, but there is no guarantees on the timing.
1250

1251
    DWORD flags = 0;
1252
    if (exclusive)
1253
        flags |= LOCKFILE_EXCLUSIVE_LOCK;
1254
    if (non_blocking)
1255
        flags |= LOCKFILE_FAIL_IMMEDIATELY;
1256
    OVERLAPPED overlapped;
1257
    memset(&overlapped, 0, sizeof overlapped);
1258
    overlapped.Offset = 0;     // Just for clarity
1259
    overlapped.OffsetHigh = 0; // Just for clarity
1260
    if (LockFileEx(m_fd, flags, 0, 1, 0, &overlapped)) {
1261
        m_have_lock = true;
1262
        return true;
1263
    }
1264
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
1265
    if (err == ERROR_LOCK_VIOLATION)
1266
        return false;
1267
    throw std::system_error(err, std::system_category(), "LockFileEx() failed");
1268
#else // _WIN32
1269
    // NOTE: It would probably have been more portable to use fcntl()
184,656✔
1270
    // based POSIX locks, however these locks are not recursive within
184,656✔
1271
    // a single process, and since a second attempt to acquire such a
184,656✔
1272
    // lock will always appear to succeed, one will easily suffer the
184,656✔
1273
    // 'spurious unlocking issue'. It remains to be determined whether
184,656✔
1274
    // this also applies across distinct threads inside a single
184,656✔
1275
    // process.
184,656✔
1276
    //
184,656✔
1277
    // To make matters worse, flock() may be a simple wrapper around
184,656✔
1278
    // fcntl() based locks on some systems. This is bad news, because
184,656✔
1279
    // the robustness of the Realm API relies in part by the
184,656✔
1280
    // assumption that a single process (even a single thread) can
184,656✔
1281
    // hold multiple overlapping independent shared locks on a single
184,656✔
1282
    // file as long as they are placed via distinct file descriptors.
184,656✔
1283
    //
184,656✔
1284
    // Fortunately, on both Linux and Darwin, flock() does not suffer
184,656✔
1285
    // from this 'spurious unlocking issue'.
184,656✔
1286
    int operation = exclusive ? LOCK_EX : LOCK_SH;
3,877,554✔
1287
    if (non_blocking)
3,877,554✔
1288
        operation |= LOCK_NB;
279,921✔
1289
    do {
3,877,554✔
1290
        if (flock(m_fd, operation) == 0) {
3,877,554✔
1291
            m_have_lock = true;
3,756,498✔
1292
            return true;
3,756,498✔
1293
        }
3,756,498✔
1294
    } while (errno == EINTR);
121,056✔
1295
    int err = errno; // Eliminate any risk of clobbering
243,354✔
1296
    if (err == EWOULDBLOCK)
121,056✔
1297
        return false;
123,390✔
1298
    throw SystemError(err, "flock() failed");
4,294,967,294✔
1299
#endif
4,294,967,294✔
1300
}
4,294,967,294✔
1301

1302
void File::unlock() noexcept
1303
{
3,754,506✔
1304
    if (!m_have_lock)
3,754,506✔
1305
        return;
×
1306

122,298✔
1307
#ifdef _WIN32 // Windows version
1308
    OVERLAPPED overlapped;
1309
    overlapped.hEvent = 0;
1310
    overlapped.OffsetHigh = 0;
1311
    overlapped.Offset = 0;
1312
    overlapped.Pointer = 0;
1313
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1314
    REALM_ASSERT_RELEASE(r);
1315
#else
1316
    _unlock(m_fd);
3,754,506✔
1317
#endif
3,754,506✔
1318
    m_have_lock = false;
3,754,506✔
1319
}
3,754,506✔
1320

1321
void File::rw_unlock() noexcept
1322
{
245,760✔
1323
#ifndef REALM_FILELOCK_EMULATION
245,760✔
1324
    unlock();
245,760✔
1325
#else
1326
    // Coming here with an exclusive lock, we must release that lock.
1327
    // Coming here with a shared lock, we must close the pipe that we have opened for reading.
1328
    //   - we have to do that under the protection of a proper exclusive lock to serialize
1329
    //     with anybody trying to obtain a lock concurrently.
1330
    if (has_shared_lock()) {
1331
        // shared lock. We need to reacquire the exclusive lock on the file
1332
        int status;
1333
        do {
1334
            status = flock(m_fd, LOCK_EX);
1335
        } while (status != 0 && errno == EINTR);
1336
        REALM_ASSERT(status == 0);
1337
        // close the pipe (== release the shared lock)
1338
        ::close(m_pipe_fd);
1339
        m_pipe_fd = -1;
1340
    }
1341
    else {
1342
        REALM_ASSERT(m_has_exclusive_lock);
1343
    }
1344
    _unlock(m_fd);
1345
    m_has_exclusive_lock = false;
1346
#endif // REALM_FILELOCK_EMULATION
1347
}
245,760✔
1348

1349
void* File::map(AccessMode a, size_t size, int /*map_flags*/, size_t offset) const
1350
{
6✔
1351
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset);
6✔
1352
}
6✔
1353

1354
void* File::map_fixed(AccessMode a, void* address, size_t size, int /* map_flags */, size_t offset) const
1355
{
×
1356
    if (m_encryption_key.get()) {
×
1357
        // encryption enabled - this is not supported - see explanation in alloc_slab.cpp
1358
        REALM_ASSERT(false);
×
1359
    }
×
1360
#ifdef _WIN32
1361
    // windows, no encryption - this is not supported, see explanation in alloc_slab.cpp,
1362
    // above the method 'update_reader_view()'
1363
    REALM_ASSERT(false);
1364
    return nullptr;
1365
#else
1366
    // unencrypted - mmap part of already reserved space
1367
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, m_encryption_key.get());
×
1368
#endif
×
1369
}
×
1370

1371
void* File::map_reserve(AccessMode a, size_t size, size_t offset) const
1372
{
×
1373
    static_cast<void>(a); // FIXME: Consider removing this argument
×
1374
    return realm::util::mmap_reserve(m_fd, size, offset);
×
1375
}
×
1376

1377
#if REALM_ENABLE_ENCRYPTION
1378
void* File::map(AccessMode a, size_t size, EncryptedFileMapping*& mapping, int /*map_flags*/, size_t offset) const
1379
{
1,328,277✔
1380
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
1,328,277✔
1381
}
1,328,277✔
1382

1383
void* File::map_fixed(AccessMode a, void* address, size_t size, EncryptedFileMapping* mapping, int /* map_flags */,
1384
                      size_t offset) const
1385
{
×
1386
    if (m_encryption_key.get()) {
×
1387
        // encryption enabled - we shouldn't be here, all memory was allocated by reserve
1388
        REALM_ASSERT_RELEASE(false);
×
1389
    }
×
1390
#ifndef _WIN32
×
1391
    // no encryption. On Unixes, map relevant part of reserved virtual address range
1392
    return realm::util::mmap_fixed(m_fd, address, size, a, offset, nullptr, mapping);
×
1393
#else
1394
    // no encryption - unsupported on windows
1395
    REALM_ASSERT(false);
1396
    return nullptr;
1397
#endif
1398
}
×
1399

1400
void* File::map_reserve(AccessMode a, size_t size, size_t offset, EncryptedFileMapping*& mapping) const
1401
{
×
1402
    if (m_encryption_key.get()) {
×
1403
        // encrypted file - just mmap it, the encryption layer handles if the mapping extends beyond eof
1404
        return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
×
1405
    }
×
1406
#ifndef _WIN32
×
1407
    // not encrypted, do a proper reservation on Unixes'
1408
    return realm::util::mmap_reserve({m_fd, m_path, a, nullptr}, size, offset, mapping);
×
1409
#else
1410
    // on windows, this is a no-op
1411
    return nullptr;
1412
#endif
1413
}
×
1414

1415
#endif // REALM_ENABLE_ENCRYPTION
1416

1417
void File::unmap(void* addr, size_t size) noexcept
1418
{
×
1419
    realm::util::munmap(addr, size);
×
1420
}
×
1421

1422

1423
void* File::remap(void* old_addr, size_t old_size, AccessMode a, size_t new_size, int /*map_flags*/,
1424
                  size_t file_offset) const
1425
{
×
1426
    return realm::util::mremap({m_fd, m_path, a, m_encryption_key.get()}, file_offset, old_addr, old_size, new_size);
×
1427
}
×
1428

1429

1430
void File::sync_map(FileDesc fd, void* addr, size_t size)
1431
{
1,374,618✔
1432
    realm::util::msync(fd, addr, size);
1,374,618✔
1433
}
1,374,618✔
1434

1435

1436
bool File::exists(const std::string& path)
1437
{
634,392✔
1438
#if REALM_HAVE_STD_FILESYSTEM
1439
    return std::filesystem::exists(u8path(path));
1440
#else // POSIX
1441
    if (::access(path.c_str(), F_OK) == 0)
634,392✔
1442
        return true;
31,272✔
1443
    int err = errno; // Eliminate any risk of clobbering
603,120✔
1444
    switch (err) {
603,120✔
1445
        case EACCES:
338,649✔
1446
        case ENOENT:
603,126✔
1447
        case ENOTDIR:
603,126✔
1448
            return false;
603,126✔
1449
    }
24✔
1450
    throw SystemError(err, "access() failed");
24✔
1451
#endif
24✔
1452
}
24✔
1453

1454

1455
bool File::is_dir(const std::string& path)
1456
{
207,672✔
1457
#if REALM_HAVE_STD_FILESYSTEM
1458
    return std::filesystem::is_directory(u8path(path));
1459
#elif !defined(_WIN32)
1460
    struct stat statbuf;
152,325✔
1461
    if (::stat(path.c_str(), &statbuf) == 0)
207,672✔
1462
        return S_ISDIR(statbuf.st_mode);
202,035✔
1463
    int err = errno; // Eliminate any risk of clobbering
5,637✔
1464
    switch (err) {
5,637✔
1465
        case EACCES:
2,820✔
1466
        case ENOENT:
5,637✔
1467
        case ENOTDIR:
5,637✔
1468
            return false;
5,637✔
1469
    }
×
1470
    throw SystemError(err, "stat() failed");
×
1471
#else
1472
    static_cast<void>(path);
1473
    throw NotImplemented();
1474
#endif
1475
}
×
1476

1477

1478
void File::remove(const std::string& path)
1479
{
165,705✔
1480
    if (try_remove(path))
165,705✔
1481
        return;
165,690✔
1482
    int err = ENOENT;
15✔
1483
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
15✔
1484
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
15✔
1485
}
15✔
1486

1487

1488
bool File::try_remove(const std::string& path)
1489
{
220,725✔
1490
#if REALM_HAVE_STD_FILESYSTEM
1491
    std::error_code error;
1492
    bool result = std::filesystem::remove(u8path(path), error);
1493
    throwIfFileError(error, path);
1494
    return result;
1495
#else // POSIX
1496
    if (::unlink(path.c_str()) == 0)
220,725✔
1497
        return true;
185,157✔
1498

17,784✔
1499
    int err = errno; // Eliminate any risk of clobbering
35,568✔
1500
    if (err == ENOENT)
35,568✔
1501
        return false;
35,547✔
1502

21✔
1503
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
42✔
1504
    switch (err) {
42✔
1505
        case EACCES:
6✔
1506
        case EROFS:
6✔
1507
        case ETXTBSY:
6✔
1508
        case EBUSY:
6✔
1509
        case EPERM:
18✔
1510
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
18✔
1511
        case ENOENT:
3✔
1512
            return false;
×
1513
        default:
24✔
1514
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
24✔
1515
    }
42✔
1516
#endif
42✔
1517
}
42✔
1518

1519

1520
void File::move(const std::string& old_path, const std::string& new_path)
1521
{
300✔
1522
#if REALM_HAVE_STD_FILESYSTEM
1523
    std::error_code error;
1524
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1525

1526
    if (error == std::errc::no_such_file_or_directory) {
1527
        throw FileAccessError(ErrorCodes::FileNotFound, error.message(), old_path);
1528
    }
1529
    throwIfFileError(error, old_path);
1530
#else
1531
    int r = rename(old_path.c_str(), new_path.c_str());
300✔
1532
    if (r == 0)
300✔
1533
        return;
300✔
1534
    int err = errno; // Eliminate any risk of clobbering
×
1535
    std::string msg = format_errno("Failed to rename file from '%2' to '%3': %1", err, old_path, new_path);
×
1536
    switch (err) {
×
1537
        case EACCES:
×
1538
        case EROFS:
×
1539
        case ETXTBSY:
×
1540
        case EBUSY:
×
1541
        case EPERM:
×
1542
        case EEXIST:
×
1543
        case ENOTEMPTY:
×
1544
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, old_path, err);
×
1545
        case ENOENT:
×
1546
            throw FileAccessError(ErrorCodes::FileNotFound, msg, old_path, err);
×
1547
        default:
×
1548
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, old_path, err);
×
1549
    }
×
1550
#endif
×
1551
}
×
1552

1553

1554
bool File::copy(const std::string& origin_path, const std::string& target_path, bool overwrite_existing)
1555
{
408✔
1556
#if REALM_HAVE_STD_FILESYSTEM
1557
    auto options = overwrite_existing ? std::filesystem::copy_options::overwrite_existing
1558
                                      : std::filesystem::copy_options::skip_existing;
1559
    return std::filesystem::copy_file(u8path(origin_path), u8path(target_path), options); // Throws
1560
#else
1561
#if REALM_PLATFORM_APPLE
204✔
1562
    // Try to use clonefile and fall back to manual copying if it fails
1563
    if (clonefile(origin_path.c_str(), target_path.c_str(), 0) == 0) {
204✔
1564
        return true;
192✔
1565
    }
192✔
1566
    if (errno == EEXIST && !overwrite_existing) {
12✔
1567
        return false;
1568
    }
1569
#endif
12✔
1570

204✔
1571
    File origin_file{origin_path, mode_Read};  // Throws
216✔
1572
    File target_file;
216✔
1573
    bool did_create = false;
216✔
1574
    target_file.open(target_path, did_create); // Throws
216✔
1575
    if (!did_create && !overwrite_existing) {
216✔
1576
        return false;
×
1577
    }
×
1578

204✔
1579
    size_t buffer_size = 4096;
216✔
1580
    auto buffer = std::make_unique<char[]>(buffer_size); // Throws
216✔
1581
    for (;;) {
624✔
1582
        size_t n = origin_file.read(buffer.get(), buffer_size); // Throws
624✔
1583
        target_file.write(buffer.get(), n);                     // Throws
624✔
1584
        if (n < buffer_size)
624✔
1585
            break;
216✔
1586
    }
624✔
1587

204✔
1588
    return true;
216✔
1589
#endif
216✔
1590
}
216✔
1591

1592

1593
bool File::compare(const std::string& path_1, const std::string& path_2)
1594
{
×
1595
    File file_1{path_1}; // Throws
×
1596
    File file_2{path_2}; // Throws
×
1597
    size_t buffer_size = 4096;
×
1598
    std::unique_ptr<char[]> buffer_1 = std::make_unique<char[]>(buffer_size); // Throws
×
1599
    std::unique_ptr<char[]> buffer_2 = std::make_unique<char[]>(buffer_size); // Throws
×
1600
    for (;;) {
×
1601
        size_t n_1 = file_1.read(buffer_1.get(), buffer_size); // Throws
×
1602
        size_t n_2 = file_2.read(buffer_2.get(), buffer_size); // Throws
×
1603
        if (n_1 != n_2)
×
1604
            return false;
×
1605
        if (!std::equal(buffer_1.get(), buffer_1.get() + n_1, buffer_2.get()))
×
1606
            return false;
×
1607
        if (n_1 < buffer_size)
×
1608
            break;
×
1609
    }
×
1610
    return true;
×
1611
}
×
1612

1613
bool File::is_same_file_static(FileDesc f1, FileDesc f2, const std::string& path1, const std::string& path2)
1614
{
24✔
1615
    return get_unique_id(f1, path1) == get_unique_id(f2, path2);
24✔
1616
}
24✔
1617

1618
bool File::is_same_file(const File& f) const
1619
{
24✔
1620
    REALM_ASSERT_RELEASE(is_attached());
24✔
1621
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1622
    return is_same_file_static(m_fd, f.m_fd, m_path, f.m_path);
24✔
1623
}
24✔
1624

1625
FileDesc File::dup_file_desc(FileDesc fd)
1626
{
1,944✔
1627
    FileDesc fd_duped;
1,944✔
1628
#ifdef _WIN32
1629
    if (!DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &fd_duped, 0, FALSE, DUPLICATE_SAME_ACCESS))
1630
        throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
1631
#else
1632
    fd_duped = dup(fd);
1,944✔
1633

852✔
1634
    if (fd_duped == -1) {
1,944✔
1635
        int err = errno; // Eliminate any risk of clobbering
×
1636
        throw std::system_error(err, std::system_category(), "dup() failed");
×
1637
    }
×
1638
#endif // conditonal on _WIN32
1,944✔
1639
    return fd_duped;
1,944✔
1640
}
1,944✔
1641

1642
File::UniqueID File::get_unique_id()
1643
{
212,727✔
1644
    REALM_ASSERT_RELEASE(is_attached());
212,727✔
1645
    File::UniqueID uid = File::get_unique_id(m_fd, m_path);
212,727✔
1646
    if (!m_cached_unique_id) {
212,727✔
1647
        m_cached_unique_id = std::make_optional(uid);
212,715✔
1648
    }
212,715✔
1649
    if (m_cached_unique_id != uid) {
212,727✔
1650
        throw FileAccessError(ErrorCodes::FileOperationFailed,
×
1651
                              util::format("The unique id of this Realm file has changed unexpectedly, this could be "
×
1652
                                           "due to modifications by an external process '%1'",
×
1653
                                           m_path),
×
1654
                              m_path);
×
1655
    }
×
1656
    return uid;
212,727✔
1657
}
212,727✔
1658

1659
FileDesc File::get_descriptor() const
1660
{
87,279✔
1661
    return m_fd;
87,279✔
1662
}
87,279✔
1663

1664
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1665
{
169,548✔
1666
#ifdef _WIN32 // Windows version
1667
    // CreateFile2 with creationDisposition OPEN_EXISTING will return a file handle only if the file exists
1668
    // otherwise it will raise ERROR_FILE_NOT_FOUND. This call will never create a new file.
1669
    WindowsFileHandleHolder fileHandle(::CreateFile2(u8path(path).c_str(), FILE_READ_ATTRIBUTES,
1670
                                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1671
                                                     OPEN_EXISTING, nullptr));
1672

1673
    if (fileHandle == INVALID_HANDLE_VALUE) {
1674
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1675
            return none;
1676
        }
1677
        throw SystemError(GetLastError(), "CreateFileW failed");
1678
    }
1679

1680
    return get_unique_id(fileHandle, path);
1681
#else // POSIX version
1682
    struct stat statbuf;
169,548✔
1683
    if (::stat(path.c_str(), &statbuf) == 0) {
169,548✔
1684
        if (statbuf.st_size == 0) {
111,132✔
1685
            // On exFAT systems the inode and device are not populated correctly until the file
1686
            // has been allocated some space. The uid can also be reassigned if the file is
1687
            // truncated to zero. This has led to bugs where a unique id returned here was
1688
            // reused by different files. The following check ensures that this is not
1689
            // happening anywhere else in future code.
1690
            return none;
38,133✔
1691
        }
38,133✔
1692
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
72,999✔
1693
    }
72,999✔
1694
    int err = errno; // Eliminate any risk of clobbering
58,416✔
1695
    // File doesn't exist
3✔
1696
    if (err == ENOENT)
58,416✔
1697
        return none;
58,416✔
1698
    throw SystemError(err, format_errno("fstat() failed: %1 for '%2'", err, path));
×
1699
#endif
×
1700
}
×
1701

1702
File::UniqueID File::get_unique_id(FileDesc file, const std::string& debug_path)
1703
{
216,072✔
1704
#ifdef _WIN32 // Windows version
1705
    REALM_ASSERT(file != nullptr);
1706
    File::UniqueID ret;
1707
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1708
        throw std::system_error(GetLastError(), std::system_category(),
1709
                                util::format("GetFileInformationByHandleEx() failed for '%1'", debug_path));
1710
    }
1711

1712
    return ret;
1713
#else // POSIX version
1714
    REALM_ASSERT(file >= 0);
216,072✔
1715
    struct stat statbuf;
216,072✔
1716
    if (::fstat(file, &statbuf) == 0) {
216,072✔
1717
        // On exFAT systems the inode and device are not populated correctly until the file
44,256✔
1718
        // has been allocated some space. The uid can also be reassigned if the file is
44,256✔
1719
        // truncated to zero. This has led to bugs where a unique id returned here was
44,256✔
1720
        // reused by different files. The following check ensures that this is not
44,256✔
1721
        // happening anywhere else in future code.
44,256✔
1722
        if (statbuf.st_size == 0) {
216,072✔
1723
            throw FileAccessError(
×
1724
                ErrorCodes::FileOperationFailed,
×
1725
                util::format("Attempt to get unique id on an empty file. This could be due to an external "
×
1726
                             "process modifying Realm files. '%1'",
×
1727
                             debug_path),
×
1728
                debug_path);
×
1729
        }
×
1730
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
216,072✔
1731
    }
216,072✔
1732
    throw std::system_error(errno, std::system_category(), util::format("fstat() failed for '%1'", debug_path));
×
1733
#endif
×
1734
}
×
1735

1736
std::string File::get_path() const
1737
{
43,920✔
1738
    return m_path;
43,920✔
1739
}
43,920✔
1740

1741
std::string File::resolve(const std::string& path, const std::string& base_dir)
1742
{
210,324✔
1743
#if REALM_HAVE_STD_FILESYSTEM
1744
    return (u8path(base_dir) / u8path(path)).lexically_normal().u8string();
1745
#else
1746
    char dir_sep = '/';
210,324✔
1747
    std::string path_2 = path;
210,324✔
1748
    std::string base_dir_2 = base_dir;
210,324✔
1749
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
210,324✔
1750
    if (is_absolute)
210,324✔
1751
        return path_2;
6✔
1752
    if (path_2.empty())
210,318✔
1753
        path_2 = ".";
6✔
1754
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
210,318✔
1755
        base_dir_2.push_back(dir_sep);
195,792✔
1756
    /*
55,521✔
1757
    // Abbreviate
55,521✔
1758
    for (;;) {
55,521✔
1759
        if (base_dir_2.empty()) {
55,521✔
1760
            if (path_2.empty())
55,521✔
1761
                return "./";
55,521✔
1762
            return path_2;
55,521✔
1763
        }
55,521✔
1764
        if (path_2 == ".") {
55,521✔
1765
            remove_trailing_dir_seps(base_dir_2);
55,521✔
1766
            return base_dir_2;
55,521✔
1767
        }
55,521✔
1768
        if (has_prefix(path_2, "./")) {
55,521✔
1769
            remove_trailing_dir_seps(base_dir_2);
55,521✔
1770
            // drop dot
55,521✔
1771
            // transfer slashes
55,521✔
1772
        }
55,521✔
1773

55,521✔
1774
        if (path_2.size() < 2 || path_2[1] != '.')
55,521✔
1775
            break;
55,521✔
1776
        if (path_2.size())
55,521✔
1777
    }
55,521✔
1778
    */
55,521✔
1779
    return base_dir_2 + path_2;
210,318✔
1780
#endif
210,318✔
1781
}
210,318✔
1782

1783
std::string File::parent_dir(const std::string& path)
1784
{
78✔
1785
#if REALM_HAVE_STD_FILESYSTEM
1786
    return u8path(path).parent_path().u8string(); // Throws
1787
#else
1788
    auto is_sep = [](char c) -> bool {
1,119✔
1789
        return c == '/' || c == '\\';
1,119✔
1790
    };
1,119✔
1791
    auto it = std::find_if(path.rbegin(), path.rend(), is_sep);
78✔
1792
    while (it != path.rend() && is_sep(*it))
168✔
1793
        ++it;
90✔
1794
    return path.substr(0, path.rend() - it);
78✔
1795
#endif
78✔
1796
}
78✔
1797

1798
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1799
{
6✔
1800
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1801
}
6✔
1802

1803

1804
void File::set_encryption_key(const char* key)
1805
{
87,831✔
1806
#if REALM_ENABLE_ENCRYPTION
87,831✔
1807
    if (key) {
87,831✔
1808
        auto buffer = std::make_unique<char[]>(64);
318✔
1809
        memcpy(buffer.get(), key, 64);
318✔
1810
        m_encryption_key = std::move(buffer);
318✔
1811
    }
318✔
1812
    else {
87,513✔
1813
        m_encryption_key.reset();
87,513✔
1814
    }
87,513✔
1815
#else
1816
    if (key) {
1817
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1818
    }
1819
#endif
1820
}
87,831✔
1821

1822
const char* File::get_encryption_key() const
1823
{
138✔
1824
    return m_encryption_key.get();
138✔
1825
}
138✔
1826

1827
void File::MapBase::map(const File& f, AccessMode a, size_t size, int map_flags, size_t offset,
1828
                        util::WriteObserver* observer)
1829
{
1,328,304✔
1830
    REALM_ASSERT(!m_addr);
1,328,304✔
1831
#if REALM_ENABLE_ENCRYPTION
1,328,304✔
1832
    m_addr = f.map(a, size, m_encrypted_mapping, map_flags, offset);
1,328,304✔
1833
    if (observer && m_encrypted_mapping) {
1,328,304✔
1834
        m_encrypted_mapping->set_observer(observer);
369✔
1835
    }
369✔
1836
#else
1837
    m_addr = f.map(a, size, map_flags, offset);
1838
    static_cast<void>(observer);
1839
#endif
1840
    m_size = m_reservation_size = size;
1,328,304✔
1841
    m_fd = f.m_fd;
1,328,304✔
1842
    m_offset = offset;
1,328,304✔
1843
    m_access_mode = a;
1,328,304✔
1844
}
1,328,304✔
1845

1846

1847
void File::MapBase::unmap() noexcept
1848
{
3,074,208✔
1849
    if (!m_addr)
3,074,208✔
1850
        return;
1,658,478✔
1851
    REALM_ASSERT(m_reservation_size);
1,415,730✔
1852
#if REALM_ENABLE_ENCRYPTION
1,415,730✔
1853
    if (m_encrypted_mapping) {
1,415,730✔
1854
        m_encrypted_mapping = nullptr;
3,291✔
1855
        util::remove_encrypted_mapping(m_addr, m_size);
3,291✔
1856
    }
3,291✔
1857
#endif
1,415,730✔
1858
    ::munmap(m_addr, m_reservation_size);
1,415,730✔
1859
    m_addr = nullptr;
1,415,730✔
1860
    m_size = 0;
1,415,730✔
1861
    m_reservation_size = 0;
1,415,730✔
1862
}
1,415,730✔
1863

1864
void File::MapBase::remap(const File& f, AccessMode a, size_t size, int map_flags)
1865
{
×
1866
    REALM_ASSERT(m_addr);
×
1867
    m_addr = f.remap(m_addr, m_size, a, size, map_flags);
×
1868
    m_size = m_reservation_size = size;
×
1869
}
×
1870

1871
bool File::MapBase::try_reserve(const File& file, AccessMode a, size_t size, size_t offset,
1872
                                util::WriteObserver* observer)
1873
{
87,213✔
1874
#ifdef _WIN32
1875
    static_cast<void>(observer);
1876
    // unsupported for now
1877
    return false;
1878
#else
1879
    void* addr = ::mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
87,213✔
1880
    if (addr == MAP_FAILED)
87,213✔
1881
        return false;
42,729✔
1882
    m_addr = addr;
87,213✔
1883
    REALM_ASSERT(m_size == 0);
87,213✔
1884
    m_access_mode = a;
87,213✔
1885
    m_reservation_size = size;
87,213✔
1886
    m_fd = file.get_descriptor();
87,213✔
1887
    m_offset = offset;
87,213✔
1888
#if REALM_ENABLE_ENCRYPTION
87,213✔
1889
    if (file.m_encryption_key) {
87,213✔
1890
        m_encrypted_mapping =
252✔
1891
            util::reserve_mapping(addr, {m_fd, file.get_path(), a, file.m_encryption_key.get()}, offset);
252✔
1892
        if (observer) {
252✔
1893
            m_encrypted_mapping->set_observer(observer);
246✔
1894
        }
246✔
1895
    }
252✔
1896
#else
1897
    static_cast<void>(observer);
1898
#endif
1899
#endif
87,213✔
1900
    return true;
87,213✔
1901
}
87,213✔
1902

1903
bool File::MapBase::try_extend_to(size_t size) noexcept
1904
{
105,732✔
1905
    if (size > m_reservation_size) {
105,732✔
1906
        return false;
×
1907
    }
×
1908
    // return false;
57,540✔
1909
#ifndef _WIN32
105,732✔
1910
    char* extension_start_addr = (char*)m_addr + m_size;
105,732✔
1911
    size_t extension_size = size - m_size;
105,732✔
1912
    size_t extension_start_offset = m_offset + m_size;
105,732✔
1913
#if REALM_ENABLE_ENCRYPTION
105,732✔
1914
    if (m_encrypted_mapping) {
105,732✔
1915
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
291✔
1916
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
291✔
1917
        if (got_addr == MAP_FAILED)
291✔
1918
            return false;
168✔
1919
        REALM_ASSERT(got_addr == extension_start_addr);
291✔
1920
        util::extend_encrypted_mapping(m_encrypted_mapping, m_addr, m_offset, m_size, size);
291✔
1921
        m_size = size;
291✔
1922
        return true;
291✔
1923
    }
291✔
1924
#endif
105,441✔
1925
    try {
105,441✔
1926
        void* got_addr = util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode,
105,441✔
1927
                                          extension_start_offset, nullptr);
105,441✔
1928
        if (got_addr == extension_start_addr) {
105,441✔
1929
            m_size = size;
105,360✔
1930
            return true;
105,360✔
1931
        }
105,360✔
1932
    }
18✔
1933
    catch (...) {
18✔
1934
        return false;
18✔
1935
    }
18✔
1936
#endif
66✔
1937
    return false;
66✔
1938
}
66✔
1939

1940
void File::MapBase::sync()
1941
{
1,374,681✔
1942
    REALM_ASSERT(m_addr);
1,374,681✔
1943

701,919✔
1944
    File::sync_map(m_fd, m_addr, m_size);
1,374,681✔
1945
}
1,374,681✔
1946

1947
void File::MapBase::flush()
1948
{
1,348,689✔
1949
    REALM_ASSERT(m_addr);
1,348,689✔
1950
#if REALM_ENABLE_ENCRYPTION
1,348,689✔
1951
    if (m_encrypted_mapping) {
1,348,689✔
1952
        realm::util::encryption_flush(m_encrypted_mapping);
999✔
1953
    }
999✔
1954
#endif
1,348,689✔
1955
}
1,348,689✔
1956

1957
std::time_t File::last_write_time(const std::string& path)
1958
{
885✔
1959
#if REALM_HAVE_STD_FILESYSTEM
1960
    auto time = std::filesystem::last_write_time(u8path(path));
1961

1962
    using namespace std::chrono;
1963
#if __cplusplus >= 202002L
1964
    auto system_time = clock_cast<system_clock>(time);
1965
#else
1966
    auto system_time =
1967
        time_point_cast<system_clock::duration>(time - decltype(time)::clock::now() + system_clock::now());
1968
#endif
1969
    return system_clock::to_time_t(system_time);
1970
#else
1971
    struct stat statbuf;
885✔
1972
    if (::stat(path.c_str(), &statbuf) != 0) {
885✔
1973
        throw SystemError(errno, "stat() failed");
×
1974
    }
×
1975
    return statbuf.st_mtime;
885✔
1976
#endif
885✔
1977
}
885✔
1978

1979
File::SizeType File::get_free_space(const std::string& path)
1980
{
144✔
1981
#if REALM_HAVE_STD_FILESYSTEM
1982
    return std::filesystem::space(u8path(path)).available;
1983
#else
1984
    struct statvfs stat;
144✔
1985
    if (statvfs(path.c_str(), &stat) != 0) {
144✔
1986
        throw SystemError(errno, "statvfs() failed");
×
1987
    }
×
1988
    return SizeType(stat.f_bavail) * stat.f_bsize;
144✔
1989
#endif
144✔
1990
}
144✔
1991

1992
#if REALM_HAVE_STD_FILESYSTEM
1993

1994
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1995
{
1996
    try {
1997
        m_iterator = std::filesystem::directory_iterator(u8path(path));
1998
    }
1999
    catch (const std::filesystem::filesystem_error& e) {
2000
        if (e.code() != std::errc::no_such_file_or_directory || !allow_missing)
2001
            throw;
2002
    }
2003
}
2004

2005
DirScanner::~DirScanner() = default;
2006

2007
bool DirScanner::next(std::string& name)
2008
{
2009
    const std::filesystem::directory_iterator end;
2010
    if (m_iterator == end)
2011
        return false;
2012
    name = m_iterator->path().filename().u8string();
2013
    m_iterator++;
2014
    return true;
2015
}
2016

2017
#elif !defined(_WIN32)
2018

2019
DirScanner::DirScanner(const std::string& path, bool allow_missing)
2020
{
120,270✔
2021
    m_dirp = opendir(path.c_str());
120,270✔
2022
    if (!m_dirp) {
120,270✔
2023
        int err = errno; // Eliminate any risk of clobbering
5,706✔
2024
        if (allow_missing && err == ENOENT)
5,706✔
2025
            return;
5,706✔
2026

2027
        std::string msg = format_errno("opendir() failed: %1", err);
×
2028
        switch (err) {
×
2029
            case EACCES:
×
2030
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
2031
            case ENOENT:
×
2032
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
2033
            default:
×
2034
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
2035
        }
×
2036
    }
×
2037
}
120,270✔
2038

2039
DirScanner::~DirScanner() noexcept
2040
{
120,270✔
2041
    if (m_dirp) {
120,270✔
2042
        int r = closedir(m_dirp);
114,564✔
2043
        REALM_ASSERT_RELEASE(r == 0);
114,564✔
2044
    }
114,564✔
2045
}
120,270✔
2046

2047
bool DirScanner::next(std::string& name)
2048
{
318,792✔
2049
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
2050
    !defined(__EMSCRIPTEN__)
2051
#error "readdir() is not known to be thread-safe on this platform"
2052
#endif
2053

108,624✔
2054
    if (!m_dirp)
318,792✔
2055
        return false;
5,706✔
2056

105,768✔
2057
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
105,768✔
2058
// in 32-bits.
105,768✔
2059
#if REALM_HAVE_READDIR64
105,768✔
2060
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
217,491✔
2061
#else
2062
#define REALM_READDIR(...) readdir(__VA_ARGS__)
324,717✔
2063
#endif
207,318✔
2064

105,768✔
2065
    for (;;) {
542,208✔
2066
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
542,208✔
2067
        DirentPtr dirent;
542,208✔
2068
        do {
542,208✔
2069
            // readdir() signals both errors and end-of-stream by returning a
217,491✔
2070
            // null pointer. To distinguish between end-of-stream and errors,
217,491✔
2071
            // the manpage recommends setting errno specifically to 0 before
217,491✔
2072
            // calling it...
217,491✔
2073
            errno = 0;
542,208✔
2074

217,491✔
2075
            dirent = REALM_READDIR(m_dirp);
542,208✔
2076
        } while (!dirent && errno == EAGAIN);
542,208✔
2077

217,491✔
2078
        if (!dirent) {
542,208✔
2079
            if (errno != 0)
114,513✔
2080
                throw SystemError(errno, "readdir() failed");
×
2081
            return false; // End of stream
114,513✔
2082
        }
114,513✔
2083
        const char* name_1 = dirent->d_name;
427,695✔
2084
        std::string name_2 = name_1;
427,695✔
2085
        if (name_2 != "." && name_2 != "..") {
427,695✔
2086
            name = name_2;
198,573✔
2087
            return true;
198,573✔
2088
        }
198,573✔
2089
    }
427,695✔
2090
}
313,086✔
2091

2092
#else
2093

2094
DirScanner::DirScanner(const std::string&, bool)
2095
{
2096
    throw NotImplemented();
2097
}
2098

2099
DirScanner::~DirScanner() noexcept {}
2100

2101
bool DirScanner::next(std::string&)
2102
{
2103
    return false;
2104
}
2105

2106
#endif
2107

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