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

realm / realm-core / 2213

10 Apr 2024 11:21PM UTC coverage: 91.792% (-0.8%) from 92.623%
2213

push

Evergreen

web-flow
Add missing availability checks for SecCopyErrorMessageString (#7577)

This requires iOS 11.3 and we currently target iOS 11.

94842 of 175770 branches covered (53.96%)

7 of 22 new or added lines in 2 files covered. (31.82%)

1861 existing lines in 82 files now uncovered.

242866 of 264583 relevant lines covered (91.79%)

5593111.45 hits per line

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

81.55
/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
{
210,288✔
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)
210,288✔
204
        return true;
73,269✔
205

64,644✔
206
    int err = errno; // Eliminate any risk of clobbering
137,019✔
207
    if (err == EEXIST)
137,019✔
208
        return false;
137,013✔
209

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

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
{
219✔
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
9✔
239
    size_t pos = path.find_first_of("/\\");
219✔
240
    if (pos == std::string::npos)
219✔
241
        return;
×
242
    pos += 1;
219✔
243

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

263

264
void remove_dir(const std::string& path)
265
{
10,140✔
266
    if (try_remove_dir(path)) // Throws
10,140✔
267
        return;
10,122✔
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
{
113,100✔
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)
113,100✔
283
        return true;
112,833✔
284

126✔
285
    int err = errno; // Eliminate any risk of clobbering
267✔
286
    if (err == ENOENT)
267✔
287
        return false;
264✔
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
{
102,933✔
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
    {
102,933✔
314
        bool allow_missing = true;
102,933✔
315
        DirScanner ds{path, allow_missing}; // Throws
102,933✔
316
        std::string name;
102,933✔
317
        while (ds.next(name)) {                              // Throws
290,241✔
318
            std::string subpath = File::resolve(name, path); // Throws
187,308✔
319
            if (File::is_dir(subpath)) {                     // Throws
187,308✔
320
                try_remove_dir_recursive(subpath);           // Throws
59,811✔
321
            }
59,811✔
322
            else {
127,497✔
323
                File::remove(subpath); // Throws
127,497✔
324
            }
127,497✔
325
        }
187,308✔
326
    }
102,933✔
327
    return try_remove_dir(path); // Throws
102,933✔
328
#endif
102,933✔
329
}
102,933✔
330

331

332
std::string make_temp_dir()
333
{
43,233✔
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,306✔
358
#if REALM_ANDROID
359
    std::string buffer = "/data/local/tmp/realm_XXXXXX";
360
#else
361
    char* tmp_dir_env = getenv("TMPDIR");
43,233✔
362
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
43,233✔
363
    if (!buffer.empty() && buffer.back() != '/') {
43,233✔
364
        buffer += "/";
43,233✔
365
    }
43,233✔
366
    buffer += "realm_XXXXXX";
43,233✔
367
#endif
43,233✔
368

21,306✔
369
    if (mkdtemp(buffer.data()) == 0) {
43,233✔
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,233✔
374
#endif
43,233✔
375
}
43,233✔
376

377
std::string make_temp_file(const char* prefix)
378
{
38,280✔
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

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

411
size_t page_size()
412
{
3,886,299✔
413
    return cached_page_size;
3,886,299✔
414
}
3,886,299✔
415

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

131,286✔
422
#ifdef _WIN32 // Windows version
423

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

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

486
#else // POSIX version
487

131,286✔
488
    int flags2 = 0;
455,637✔
489
    switch (a) {
455,637✔
490
        case access_ReadOnly:
2,883✔
491
            flags2 = O_RDONLY;
2,883✔
492
            break;
2,883✔
493
        case access_ReadWrite:
452,787✔
494
            flags2 = O_RDWR;
452,787✔
495
            break;
452,787✔
496
    }
455,670✔
497
    switch (c) {
455,670✔
498
        case create_Auto:
426,837✔
499
            flags2 |= O_CREAT;
426,837✔
500
            break;
426,837✔
501
        case create_Never:
27,999✔
502
            break;
27,999✔
503
        case create_Must:
828✔
504
            flags2 |= O_CREAT | O_EXCL;
828✔
505
            break;
828✔
506
    }
455,649✔
507
    if (flags & flag_Trunc)
455,649✔
508
        flags2 |= O_TRUNC;
558✔
509
    if (flags & flag_Append)
455,649✔
510
        flags2 |= O_APPEND;
183,120✔
511
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
455,649✔
512
    if (0 <= fd) {
455,649✔
513
        m_fd = fd;
455,517✔
514
        m_have_lock = false;
455,517✔
515
        if (success)
455,517✔
516
            *success = true;
204✔
517
        return;
455,517✔
518
    }
455,517✔
519

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

66✔
549
#endif
102✔
550
}
102✔
551

552

553
void File::close() noexcept
554
{
883,569✔
555
#ifdef _WIN32 // Windows version
556

557
    if (!m_fd)
558
        return;
559
    if (m_have_lock)
560
        unlock();
561

562
    BOOL r = CloseHandle(m_fd);
563
    REALM_ASSERT_RELEASE(r);
564
    m_fd = nullptr;
565

566
#else // POSIX version
567

252,138✔
568
    if (m_fd < 0)
883,569✔
569
        return;
428,118✔
570
    if (m_have_lock)
455,451✔
571
        unlock();
153✔
572
    int r = ::close(m_fd);
455,451✔
573
    REALM_ASSERT_RELEASE(r == 0);
455,451✔
574
    m_fd = -1;
455,451✔
575

131,208✔
576
#endif
455,451✔
577
}
455,451✔
578

579
void File::close_static(FileDesc fd)
580
{
1,581✔
581
#ifdef _WIN32
582
    if (!fd)
583
        return;
584

585
    if (!CloseHandle(fd))
586
        throw std::system_error(GetLastError(), std::system_category(),
587
                                "CloseHandle() failed from File::close_static()");
588
#else
589
    if (fd < 0)
1,581✔
UNCOV
590
        return;
×
591

912✔
592
    int ret = -1;
1,581✔
593
    do {
1,581✔
594
        ret = ::close(fd);
1,581✔
595
    } while (ret == -1 && errno == EINTR);
1,581!
596

912✔
597
    if (ret != 0) {
1,581✔
UNCOV
598
        int err = errno; // Eliminate any risk of clobbering
×
UNCOV
599
        if (err == EBADF || err == EIO)
×
UNCOV
600
            throw SystemError(err, "File::close_static() failed");
×
601
    }
×
602
#endif
1,581✔
603
}
1,581✔
604

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

624
error:
625
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
626
    throw SystemError(int(err), "ReadFile() failed");
627

628
#else // POSIX version
629

262,926✔
630
    char* const data_0 = data;
502,719✔
631
    while (0 < size) {
1,004,553✔
632
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
263,013✔
633
        size_t n = std::min(size, size_t(SSIZE_MAX));
502,809✔
634
        ssize_t r = ::read(fd, data, n);
502,809✔
635
        if (r == 0)
502,809✔
636
            break;
975✔
637
        if (r < 0)
501,834✔
638
            goto error; // LCOV_EXCL_LINE
639
        REALM_ASSERT_RELEASE(size_t(r) <= n);
501,834✔
640
        size -= size_t(r);
501,834✔
641
        data += size_t(r);
501,834✔
642
    }
501,834✔
643
    return data - data_0;
502,719✔
644

UNCOV
645
error:
×
646
    // LCOV_EXCL_START
UNCOV
647
    throw SystemError(errno, "read() failed");
×
648
// LCOV_EXCL_STOP
262,926✔
649
#endif
502,719✔
650
}
502,719✔
651

652

653
size_t File::read(char* data, size_t size)
654
{
1,266✔
655
    REALM_ASSERT_RELEASE(is_attached());
1,266✔
656

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

861✔
669
    return read_static(m_fd, data, size);
1,266✔
670
}
1,266✔
671

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

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

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

719
#endif
×
UNCOV
720
}
×
721

722
void File::write(const char* data, size_t size)
723
{
44,958✔
724
    REALM_ASSERT_RELEASE(is_attached());
44,958✔
725

22,749✔
726
    if (m_encryption_key) {
44,958✔
727
        uint64_t pos_original = get_file_pos(m_fd);
855✔
728
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
855✔
729
        size_t pos = size_t(pos_original);
855✔
730
        Map<char> write_map(*this, access_ReadWrite, static_cast<size_t>(pos + size));
855✔
731
        realm::util::encryption_read_barrier(write_map, pos, size);
855✔
732
        memcpy(write_map.get_addr() + pos, data, size);
855✔
733
        realm::util::encryption_write_barrier(write_map, pos, size);
855✔
734
        uint64_t cur = get_file_pos(m_fd);
855✔
735
        seek(cur + size);
855✔
736
        return;
855✔
737
    }
855✔
738

22,167✔
739
    write_static(m_fd, data, size);
44,103✔
740
}
44,103✔
741

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

753
    return uint64_t(res.QuadPart);
754
#else
755
    auto pos = lseek(fd, 0, SEEK_CUR);
716,319✔
756
    if (pos < 0) {
716,319✔
UNCOV
757
        throw SystemError(errno, "lseek() failed");
×
UNCOV
758
    }
×
759
    return uint64_t(pos);
716,319✔
760
#endif
716,319✔
761
}
716,319✔
762

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

769
File::SizeType File::get_size_static(FileDesc fd)
770
{
1,765,149✔
771
#ifdef _WIN32
772
    LARGE_INTEGER large_int;
773
    if (GetFileSizeEx(fd, &large_int)) {
774
        File::SizeType size;
775
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
776
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
777

778
        return size;
779
    }
780
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
781

782
#else // POSIX version
783

909,264✔
784
    struct stat statbuf;
1,765,149✔
785
    if (::fstat(fd, &statbuf) == 0) {
1,765,416✔
786
        SizeType size;
1,765,416✔
787
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,765,416✔
UNCOV
788
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
789

909,408✔
790
        return size;
1,765,416✔
791
    }
1,765,416✔
792
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
793

2,147,483,647✔
794
#endif
4,294,967,294✔
795
}
4,294,967,294✔
796

797
File::SizeType File::get_size() const
798
{
1,762,074✔
799
    REALM_ASSERT_RELEASE(is_attached());
1,762,074✔
800
    File::SizeType size = get_size_static(m_fd);
1,762,074✔
801

907,605✔
802
    if (m_encryption_key) {
1,762,074✔
803
        File::SizeType ret_size = encrypted_size_to_data_size(size);
3,975✔
804
        return ret_size;
3,975✔
805
    }
3,975✔
806
    else
1,758,099✔
807
        return size;
1,758,099✔
808
}
1,762,074✔
809

810

811
void File::resize(SizeType size)
812
{
272,097✔
813
    REALM_ASSERT_RELEASE(is_attached());
272,097✔
814

43,983✔
815
#ifdef _WIN32 // Windows version
816

817
    // Save file position
818
    SizeType p = get_file_pos(m_fd);
819

820
    if (m_encryption_key)
821
        size = data_size_to_encrypted_size(size);
822

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

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

837
    // Restore file position
838
    seek(p);
839

840
#else // POSIX version
841

43,983✔
842
    if (m_encryption_key)
272,097✔
843
        size = data_size_to_encrypted_size(size);
42✔
844

43,983✔
845
    off_t size2;
272,097✔
846
    if (int_cast_with_overflow_detect(size, size2))
272,097✔
UNCOV
847
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
848

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

43,983✔
860
#endif
272,097✔
861
}
272,097✔
862

863

864
void File::prealloc(size_t size)
865
{
164,874✔
866
    REALM_ASSERT_RELEASE(is_attached());
164,874✔
867

86,868✔
868
    if (size <= to_size_t(get_size())) {
164,874✔
869
        return;
38,325✔
870
    }
38,325✔
871

68,139✔
872
    size_t new_size = size;
126,549✔
873
    if (m_encryption_key) {
126,549✔
874
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
648✔
875
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
648✔
876
        if (new_size < size) {
648✔
UNCOV
877
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow: data_size_to_encrypted_size(" +
×
UNCOV
878
                                                           realm::util::to_string(size) +
×
UNCOV
879
                                                           ") == " + realm::util::to_string(new_size));
×
880
        }
×
881
    }
126,549✔
882

68,139✔
883
    auto manually_consume_space = [&]() {
126,549✔
UNCOV
884
        constexpr size_t chunk_size = 4096;
×
UNCOV
885
        int64_t original_size = get_size_static(m_fd); // raw size
×
UNCOV
886
        seek(original_size);
×
887
        size_t num_bytes = size_t(new_size - original_size);
×
888
        std::string zeros(chunk_size, '\0');
×
889
        while (num_bytes > 0) {
×
890
            size_t t = num_bytes > chunk_size ? chunk_size : num_bytes;
×
891
            write_static(m_fd, zeros.c_str(), t);
×
892
            num_bytes -= t;
×
893
        }
×
894
    };
×
895

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

68,139✔
913
#if REALM_HAVE_POSIX_FALLOCATE
68,139✔
914
    // Mostly Linux only
68,139✔
915
    if (!prealloc_if_supported(0, new_size)) {
68,139✔
916
        consume_space_interlocked();
917
    }
918
#else // Non-atomic fallback
919
#if REALM_PLATFORM_APPLE
58,410✔
920
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
921
    // ftruncate().
922

923
    struct stat statbuf;
58,410✔
924
    if (::fstat(m_fd, &statbuf) != 0) {
58,410✔
925
        int err = errno;
926
        throw SystemError(err, "fstat() inside prealloc() failed");
927
    }
928

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

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

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

967
    int ret = 0;
58,410✔
968

969
    do {
58,410✔
970
        ret = ftruncate(m_fd, new_size);
58,410✔
971
    } while (ret == -1 && errno == EINTR);
58,410!
972

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

981
    consume_space_interlocked();
982

983
#else
984
#error Please check if/how your OS supports file preallocation
985
#endif
986

987
#endif // REALM_HAVE_POSIX_FALLOCATE
58,410✔
988
}
126,549✔
989

990

991
bool File::prealloc_if_supported(SizeType offset, size_t size)
992
{
68,139✔
993
    REALM_ASSERT_RELEASE(is_attached());
68,139!
994

68,139✔
995
#if REALM_HAVE_POSIX_FALLOCATE
68,139✔
996

68,139✔
997
    REALM_ASSERT_RELEASE(is_prealloc_supported());
68,139✔
998

68,139✔
999
    if (size == 0) {
68,139✔
1000
        // calling posix_fallocate with a size of 0 will cause a return of EINVAL
1001
        // since this is a meaningless operation anyway, we just return early here
1002
        return true;
1003
    }
1004

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

68,139✔
1012
    if (REALM_LIKELY(status == 0)) {
68,142✔
1013
        return true;
68,142✔
1014
    }
68,142✔
1015

2,147,483,647✔
1016
    if (status == EINVAL || status == EPERM || status == EOPNOTSUPP) {
2,147,483,647✔
1017
        return false; // Retry with non-atomic version
1018
    }
1019

2,147,483,647✔
1020
    auto msg = format_errno("posix_fallocate() failed: %1", status);
2,147,483,647✔
1021
    if (status == ENOSPC || status == EDQUOT) {
2,147,483,647✔
1022
        throw OutOfDiskSpace(msg);
1023
    }
1024
    throw SystemError(status, msg);
2,147,483,647✔
1025

2,147,483,647✔
1026
    // FIXME: OS X does not have any version of fallocate, but see
2,147,483,647✔
1027
    // http://stackoverflow.com/questions/11497567/fallocate-command-equivalent-in-os-x
2,147,483,647✔
1028

2,147,483,647✔
1029
    // FIXME: On Windows one could use a call to CreateFileMapping()
2,147,483,647✔
1030
    // since it will grow the file if necessary, but never shrink it,
2,147,483,647✔
1031
    // just like posix_fallocate(). The advantage would be that it
2,147,483,647✔
1032
    // then becomes an atomic operation (probably).
2,147,483,647✔
1033

2,147,483,647✔
1034
#else
1035

1036
    static_cast<void>(offset);
1037
    static_cast<void>(size);
1038

1039
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1040

1041
#endif
1042
    return false;
2,147,483,647✔
1043
}
2,147,483,647✔
1044

1045

1046
bool File::is_prealloc_supported()
1047
{
68,139✔
1048
#if REALM_HAVE_POSIX_FALLOCATE
68,139✔
1049
    return true;
68,139✔
1050
#else
1051
    return false;
1052
#endif
1053
}
68,139✔
1054

1055
void File::seek(SizeType position)
1056
{
39,696✔
1057
    REALM_ASSERT_RELEASE(is_attached());
39,696✔
1058
    seek_static(m_fd, position);
39,696✔
1059
}
39,696✔
1060

1061
void File::seek_static(FileDesc fd, SizeType position)
1062
{
1,468,911✔
1063
#ifdef _WIN32 // Windows version
1064

1065
    LARGE_INTEGER large_int;
1066
    if (int_cast_with_overflow_detect(position, large_int.QuadPart))
1067
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1068

1069
    if (!SetFilePointerEx(fd, large_int, 0, FILE_BEGIN))
1070
        throw SystemError(GetLastError(), "SetFilePointerEx() failed");
1071

1072
#else // POSIX version
1073

750,159✔
1074
    off_t position2;
1,468,911✔
1075
    if (int_cast_with_overflow_detect(position, position2))
1,468,911✔
UNCOV
1076
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1077

750,159✔
1078
    if (0 <= ::lseek(fd, position2, SEEK_SET))
1,468,911✔
1079
        return;
1,468,914✔
1080
    throw SystemError(errno, "lseek() failed");
2,147,483,647✔
1081

1082
#endif
2,147,483,647✔
1083
}
2,147,483,647✔
1084

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

501✔
1093
#if defined _WIN32 // Windows version
1094

1095
    if (FlushFileBuffers(m_fd))
1096
        return;
1097
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1098

1099
#elif REALM_PLATFORM_APPLE
1100

1101
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
309✔
1102
        return;
309✔
1103
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1104

1105
#else // POSIX version
1106

501✔
1107
    if (::fsync(m_fd) == 0)
501✔
1108
        return;
501✔
1109
    throw SystemError(errno, "fsync() failed");
1110

1111
#endif
UNCOV
1112
}
×
1113

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

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

1139
bool File::rw_lock(bool exclusive, bool non_blocking)
1140
{
359,058✔
1141
    // exclusive blocking rw locks not implemented for emulation
171,864✔
1142
    REALM_ASSERT(!exclusive || non_blocking);
359,058✔
1143

171,864✔
1144
#ifndef REALM_FILELOCK_EMULATION
359,058✔
1145
    return lock(exclusive, non_blocking);
359,058✔
1146
#else
1147
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1148

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

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

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

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

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

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

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

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

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

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

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

1237
bool File::lock(bool exclusive, bool non_blocking)
1238
{
3,895,737✔
1239
    REALM_ASSERT_RELEASE(is_attached());
3,895,737✔
1240
    REALM_ASSERT_RELEASE(!m_have_lock);
3,895,737✔
1241

171,867✔
1242
#ifdef _WIN32 // Windows version
1243

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

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

1299
void File::unlock() noexcept
1300
{
3,788,190✔
1301
    if (!m_have_lock)
3,788,190✔
UNCOV
1302
        return;
×
1303

121,287✔
1304
#ifdef _WIN32 // Windows version
1305
    OVERLAPPED overlapped;
1306
    overlapped.hEvent = 0;
1307
    overlapped.OffsetHigh = 0;
1308
    overlapped.Offset = 0;
1309
    overlapped.Pointer = 0;
1310
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1311
    REALM_ASSERT_RELEASE(r);
1312
#else
1313
    _unlock(m_fd);
3,788,190✔
1314
#endif
3,788,190✔
1315
    m_have_lock = false;
3,788,190✔
1316
}
3,788,190✔
1317

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

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

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

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

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

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

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

1412
#endif // REALM_ENABLE_ENCRYPTION
1413

1414
void File::unmap(void* addr, size_t size) noexcept
UNCOV
1415
{
×
UNCOV
1416
    realm::util::munmap(addr, size);
×
UNCOV
1417
}
×
1418

1419

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

1426

1427
void File::sync_map(FileDesc fd, void* addr, size_t size)
1428
{
1,359,366✔
1429
    realm::util::msync(fd, addr, size);
1,359,366✔
1430
}
1,359,366✔
1431

1432

1433
bool File::exists(const std::string& path)
1434
{
683,547✔
1435
#if REALM_HAVE_STD_FILESYSTEM
1436
    return std::filesystem::exists(u8path(path));
1437
#else // POSIX
1438
    if (::access(path.c_str(), F_OK) == 0)
683,547✔
1439
        return true;
30,618✔
1440
    int err = errno; // Eliminate any risk of clobbering
652,929✔
1441
    switch (err) {
652,929✔
1442
        case EACCES:
356,961✔
1443
        case ENOENT:
652,908✔
1444
        case ENOTDIR:
652,908✔
1445
            return false;
652,908✔
1446
    }
24✔
1447
    throw SystemError(err, "access() failed");
24✔
1448
#endif
24✔
1449
}
24✔
1450

1451

1452
bool File::is_dir(const std::string& path)
1453
{
225,237✔
1454
#if REALM_HAVE_STD_FILESYSTEM
1455
    return std::filesystem::is_directory(u8path(path));
1456
#elif !defined(_WIN32)
1457
    struct stat statbuf;
162,576✔
1458
    if (::stat(path.c_str(), &statbuf) == 0)
225,237✔
1459
        return S_ISDIR(statbuf.st_mode);
218,721✔
1460
    int err = errno; // Eliminate any risk of clobbering
6,516✔
1461
    switch (err) {
6,516✔
1462
        case EACCES:
3,261✔
1463
        case ENOENT:
6,516✔
1464
        case ENOTDIR:
6,516✔
1465
            return false;
6,516✔
UNCOV
1466
    }
×
UNCOV
1467
    throw SystemError(err, "stat() failed");
×
1468
#else
1469
    static_cast<void>(path);
1470
    throw NotImplemented();
1471
#endif
UNCOV
1472
}
×
1473

1474

1475
void File::remove(const std::string& path)
1476
{
169,647✔
1477
    if (try_remove(path))
169,647✔
1478
        return;
169,632✔
1479
    int err = ENOENT;
15✔
1480
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
15✔
1481
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
15✔
1482
}
15✔
1483

1484

1485
bool File::try_remove(const std::string& path)
1486
{
228,237✔
1487
#if REALM_HAVE_STD_FILESYSTEM
1488
    std::error_code error;
1489
    bool result = std::filesystem::remove(u8path(path), error);
1490
    throwIfFileError(error, path);
1491
    return result;
1492
#else // POSIX
1493
    if (::unlink(path.c_str()) == 0)
228,237✔
1494
        return true;
190,245✔
1495

18,978✔
1496
    int err = errno; // Eliminate any risk of clobbering
37,992✔
1497
    if (err == ENOENT)
37,992✔
1498
        return false;
37,965✔
1499

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

1516

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

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

1550

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

192✔
1568
    File origin_file{origin_path, mode_Read};  // Throws
204✔
1569
    File target_file;
204✔
1570
    bool did_create = false;
204✔
1571
    target_file.open(target_path, did_create); // Throws
204✔
1572
    if (!did_create && !overwrite_existing) {
204✔
1573
        return false;
6✔
1574
    }
6✔
1575

186✔
1576
    size_t buffer_size = 4096;
198✔
1577
    auto buffer = std::make_unique<char[]>(buffer_size); // Throws
198✔
1578
    for (;;) {
576✔
1579
        size_t n = origin_file.read(buffer.get(), buffer_size); // Throws
576✔
1580
        target_file.write(buffer.get(), n);                     // Throws
576✔
1581
        if (n < buffer_size)
576✔
1582
            break;
198✔
1583
    }
576✔
1584

186✔
1585
    return true;
198✔
1586
#endif
198✔
1587
}
198✔
1588

1589

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

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

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

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

912✔
1631
    if (fd_duped == -1) {
1,581✔
UNCOV
1632
        int err = errno; // Eliminate any risk of clobbering
×
UNCOV
1633
        throw std::system_error(err, std::system_category(), "dup() failed");
×
UNCOV
1634
    }
×
1635
#endif // conditonal on _WIN32
1,581✔
1636
    return fd_duped;
1,581✔
1637
}
1,581✔
1638

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

1656
FileDesc File::get_descriptor() const
1657
{
96,012✔
1658
    return m_fd;
96,012✔
1659
}
96,012✔
1660

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

1670
    if (fileHandle == INVALID_HANDLE_VALUE) {
1671
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1672
            return none;
1673
        }
1674
        throw SystemError(GetLastError(), "CreateFileW failed");
1675
    }
1676

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

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

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

1733
std::string File::get_path() const
1734
{
354✔
1735
    return m_path;
354✔
1736
}
354✔
1737

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

61,956✔
1771
        if (path_2.size() < 2 || path_2[1] != '.')
61,956✔
1772
            break;
61,956✔
1773
        if (path_2.size())
61,956✔
1774
    }
61,956✔
1775
    */
61,956✔
1776
    return base_dir_2 + path_2;
223,791✔
1777
#endif
223,791✔
1778
}
223,791✔
1779

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

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

1800

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

1819
const char* File::get_encryption_key() const
1820
{
118,701✔
1821
    return m_encryption_key.get();
118,701✔
1822
}
118,701✔
1823

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

1843

1844
void File::MapBase::unmap() noexcept
1845
{
3,218,352✔
1846
    if (!m_addr)
3,218,352✔
1847
        return;
1,739,919✔
1848
    REALM_ASSERT(m_reservation_size);
1,478,433✔
1849
#if REALM_ENABLE_ENCRYPTION
1,478,433✔
1850
    if (m_encrypted_mapping) {
1,478,433✔
1851
        m_encrypted_mapping = nullptr;
3,072✔
1852
        util::remove_encrypted_mapping(m_addr, m_size);
3,072✔
1853
    }
3,072✔
1854
#endif
1,478,433✔
1855
    ::munmap(m_addr, m_reservation_size);
1,478,433✔
1856
    m_addr = nullptr;
1,478,433✔
1857
    m_size = 0;
1,478,433✔
1858
    m_reservation_size = 0;
1,478,433✔
1859
}
1,478,433✔
1860

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

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

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

1937
void File::MapBase::sync()
1938
{
1,359,423✔
1939
    REALM_ASSERT(m_addr);
1,359,423✔
1940

707,652✔
1941
    File::sync_map(m_fd, m_addr, m_size);
1,359,423✔
1942
}
1,359,423✔
1943

1944
void File::MapBase::flush()
1945
{
1,331,355✔
1946
    REALM_ASSERT(m_addr);
1,331,355✔
1947
#if REALM_ENABLE_ENCRYPTION
1,331,355✔
1948
    if (m_encrypted_mapping) {
1,331,355✔
1949
        realm::util::encryption_flush(m_encrypted_mapping);
1,125✔
1950
    }
1,125✔
1951
#endif
1,331,355✔
1952
}
1,331,355✔
1953

1954
std::time_t File::last_write_time(const std::string& path)
1955
{
882✔
1956
#if REALM_HAVE_STD_FILESYSTEM
1957
    auto time = std::filesystem::last_write_time(u8path(path));
1958

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

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

1989
#if REALM_HAVE_STD_FILESYSTEM
1990

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

2002
DirScanner::~DirScanner() = default;
2003

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

2014
#elif !defined(_WIN32)
2015

2016
DirScanner::DirScanner(const std::string& path, bool allow_missing)
2017
{
122,274✔
2018
    m_dirp = opendir(path.c_str());
122,274✔
2019
    if (!m_dirp) {
122,274✔
2020
        int err = errno; // Eliminate any risk of clobbering
6,741✔
2021
        if (allow_missing && err == ENOENT)
6,741✔
2022
            return;
6,741✔
2023

UNCOV
2024
        std::string msg = format_errno("opendir() failed: %1", err);
×
UNCOV
2025
        switch (err) {
×
UNCOV
2026
            case EACCES:
×
UNCOV
2027
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
UNCOV
2028
            case ENOENT:
×
2029
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
2030
            default:
×
2031
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
2032
        }
×
2033
    }
×
2034
}
122,274✔
2035

2036
DirScanner::~DirScanner() noexcept
2037
{
122,274✔
2038
    if (m_dirp) {
122,274✔
2039
        int r = closedir(m_dirp);
115,533✔
2040
        REALM_ASSERT_RELEASE(r == 0);
115,533✔
2041
    }
115,533✔
2042
}
122,274✔
2043

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

116,397✔
2051
    if (!m_dirp)
336,987✔
2052
        return false;
6,741✔
2053

113,031✔
2054
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
113,031✔
2055
// in 32-bits.
113,031✔
2056
#if REALM_HAVE_READDIR64
113,031✔
2057
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
225,999✔
2058
#else
2059
#define REALM_READDIR(...) readdir(__VA_ARGS__)
335,313✔
2060
#endif
217,215✔
2061

113,031✔
2062
    for (;;) {
561,312✔
2063
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
561,312✔
2064
        DirentPtr dirent;
561,312✔
2065
        do {
561,312✔
2066
            // readdir() signals both errors and end-of-stream by returning a
225,999✔
2067
            // null pointer. To distinguish between end-of-stream and errors,
225,999✔
2068
            // the manpage recommends setting errno specifically to 0 before
225,999✔
2069
            // calling it...
225,999✔
2070
            errno = 0;
561,312✔
2071

225,999✔
2072
            dirent = REALM_READDIR(m_dirp);
561,312✔
2073
        } while (!dirent && errno == EAGAIN);
561,312✔
2074

225,999✔
2075
        if (!dirent) {
561,312✔
2076
            if (errno != 0)
115,332✔
UNCOV
2077
                throw SystemError(errno, "readdir() failed");
×
2078
            return false; // End of stream
115,332✔
2079
        }
115,332✔
2080
        const char* name_1 = dirent->d_name;
445,980✔
2081
        std::string name_2 = name_1;
445,980✔
2082
        if (name_2 != "." && name_2 != "..") {
445,980✔
2083
            name = name_2;
214,911✔
2084
            return true;
214,911✔
2085
        }
214,911✔
2086
    }
445,980✔
2087
}
330,246✔
2088

2089
#else
2090

2091
DirScanner::DirScanner(const std::string&, bool)
2092
{
2093
    throw NotImplemented();
2094
}
2095

2096
DirScanner::~DirScanner() noexcept {}
2097

2098
bool DirScanner::next(std::string&)
2099
{
2100
    return false;
2101
}
2102

2103
#endif
2104

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