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

realm / realm-core / 2092

02 Mar 2024 12:43AM UTC coverage: 90.936% (+0.02%) from 90.92%
2092

push

Evergreen

web-flow
Use updated curl on evergreen windows hosts (#7409)

93932 of 173116 branches covered (54.26%)

238423 of 262189 relevant lines covered (90.94%)

5915346.18 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
{
209,280✔
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)
209,280✔
204
        return true;
73,842✔
205

72,189✔
206
    int err = errno; // Eliminate any risk of clobbering
135,438✔
207
    if (err == EEXIST)
135,438✔
208
        return false;
135,435✔
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,801✔
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,801✔
283
        return true;
111,684✔
284

57✔
285
    int err = errno; // Eliminate any risk of clobbering
117✔
286
    if (err == ENOENT)
117✔
287
        return false;
114✔
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,209✔
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,209✔
314
        bool allow_missing = true;
103,209✔
315
        DirScanner ds{path, allow_missing}; // Throws
103,209✔
316
        std::string name;
103,209✔
317
        while (ds.next(name)) {                              // Throws
276,849✔
318
            std::string subpath = File::resolve(name, path); // Throws
173,640✔
319
            if (File::is_dir(subpath)) {                     // Throws
173,640✔
320
                try_remove_dir_recursive(subpath);           // Throws
47,643✔
321
            }
47,643✔
322
            else {
125,997✔
323
                File::remove(subpath); // Throws
125,997✔
324
            }
125,997✔
325
        }
173,640✔
326
    }
103,209✔
327
    return try_remove_dir(path); // Throws
103,209✔
328
#endif
103,209✔
329
}
103,209✔
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,864,078✔
416
    return cached_page_size;
3,864,078✔
417
}
3,864,078✔
418

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

133,704✔
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,704✔
491
    int flags2 = 0;
386,583✔
492
    switch (a) {
386,583✔
493
        case access_ReadOnly:
2,895✔
494
            flags2 = O_RDONLY;
2,895✔
495
            break;
2,895✔
496
        case access_ReadWrite:
383,703✔
497
            flags2 = O_RDWR;
383,703✔
498
            break;
383,703✔
499
    }
386,598✔
500
    switch (c) {
386,598✔
501
        case create_Auto:
355,935✔
502
            flags2 |= O_CREAT;
355,935✔
503
            break;
355,935✔
504
        case create_Never:
29,826✔
505
            break;
29,826✔
506
        case create_Must:
840✔
507
            flags2 |= O_CREAT | O_EXCL;
840✔
508
            break;
840✔
509
    }
386,598✔
510
    if (flags & flag_Trunc)
386,598✔
511
        flags2 |= O_TRUNC;
840✔
512
    if (flags & flag_Append)
386,598✔
513
        flags2 |= O_APPEND;
126,048✔
514
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
386,598✔
515
    if (0 <= fd) {
386,598✔
516
        m_fd = fd;
386,451✔
517
        m_have_lock = false;
386,451✔
518
        if (success)
386,451✔
519
            *success = true;
216✔
520
        return;
386,451✔
521
    }
386,451✔
522

84✔
523
    int err = errno; // Eliminate any risk of clobbering
147✔
524
    if (success && err == EEXIST && c == create_Must) {
147✔
525
        *success = false;
24✔
526
        return;
24✔
527
    }
24✔
528
    if (success && err == ENOENT && c == create_Never) {
123!
529
        *success = false;
×
530
        return;
×
531
    }
×
532
    std::string msg = format_errno("Failed to open file at path '%2': %1", err, path);
123✔
533
    switch (err) {
123✔
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
    }
123✔
551

72✔
552
#endif
123✔
553
}
123✔
554

555

556
void File::close() noexcept
557
{
740,352✔
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

246,003✔
571
    if (m_fd < 0)
740,352✔
572
        return;
353,919✔
573
    if (m_have_lock)
386,433✔
574
        unlock();
153✔
575
    int r = ::close(m_fd);
386,433✔
576
    REALM_ASSERT_RELEASE(r == 0);
386,433✔
577
    m_fd = -1;
386,433✔
578

133,635✔
579
#endif
386,433✔
580
}
386,433✔
581

582
void File::close_static(FileDesc fd)
583
{
1,971✔
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,971✔
593
        return;
×
594

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

840✔
600
    if (ret != 0) {
1,971✔
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,971✔
606
}
1,971✔
607

608
size_t File::read_static(FileDesc fd, char* data, size_t size)
609
{
504,864✔
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,614✔
633
    char* const data_0 = data;
504,864✔
634
    while (0 < size) {
1,008,825✔
635
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
262,701✔
636
        size_t n = std::min(size, size_t(SSIZE_MAX));
504,954✔
637
        ssize_t r = ::read(fd, data, n);
504,954✔
638
        if (r == 0)
504,954✔
639
            break;
993✔
640
        if (r < 0)
503,961✔
641
            goto error; // LCOV_EXCL_LINE
642
        REALM_ASSERT_RELEASE(size_t(r) <= n);
503,961✔
643
        size -= size_t(r);
503,961✔
644
        data += size_t(r);
503,961✔
645
    }
503,961✔
646
    return data - data_0;
504,864✔
647

648
error:
×
649
    // LCOV_EXCL_START
650
    throw SystemError(errno, "read() failed");
×
651
// LCOV_EXCL_STOP
262,614✔
652
#endif
504,864✔
653
}
504,864✔
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,883✔
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,631✔
700
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
124,770✔
701
        size_t n = std::min(size, size_t(SSIZE_MAX));
260,748✔
702
        ssize_t r = ::write(fd, data, n);
260,748✔
703
        if (r < 0)
260,748✔
704
            goto error; // LCOV_EXCL_LINE
705
        REALM_ASSERT_RELEASE(r != 0);
260,748✔
706
        REALM_ASSERT_RELEASE(size_t(r) <= n);
260,748✔
707
        size -= size_t(r);
260,748✔
708
        data += size_t(r);
260,748✔
709
    }
260,748✔
710
    return;
260,883✔
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,294✔
727
    REALM_ASSERT_RELEASE(is_attached());
45,294✔
728

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

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

745
uint64_t File::get_file_pos(FileDesc fd)
746
{
723,522✔
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,522✔
759
    if (pos < 0) {
723,522✔
760
        throw SystemError(errno, "lseek() failed");
×
761
    }
×
762
    return uint64_t(pos);
723,522✔
763
#endif
723,522✔
764
}
723,522✔
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,720,155✔
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

890,913✔
787
    struct stat statbuf;
1,720,155✔
788
    if (::fstat(fd, &statbuf) == 0) {
1,720,422✔
789
        SizeType size;
1,720,422✔
790
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,720,422✔
791
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
792

891,084✔
793
        return size;
1,720,422✔
794
    }
1,720,422✔
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,716,867✔
802
    REALM_ASSERT_RELEASE(is_attached());
1,716,867✔
803
    File::SizeType size = get_size_static(m_fd);
1,716,867✔
804

889,383✔
805
    if (m_encryption_key) {
1,716,867✔
806
        File::SizeType ret_size = encrypted_size_to_data_size(size);
3,669✔
807
        return ret_size;
3,669✔
808
    }
3,669✔
809
    else
1,713,198✔
810
        return size;
1,713,198✔
811
}
1,716,867✔
812

813

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

28,476✔
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,476✔
845
    if (m_encryption_key)
183,981✔
846
        size = data_size_to_encrypted_size(size);
18✔
847

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

28,476✔
852
    // POSIX specifies that introduced bytes read as zero. This is not
28,476✔
853
    // required by File::resize().
28,476✔
854
    if (::ftruncate(m_fd, size2) != 0) {
183,981✔
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,476✔
863
#endif
183,981✔
864
}
183,981✔
865

866

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

80,775✔
871
    if (size <= to_size_t(get_size())) {
152,682✔
872
        return;
37,128✔
873
    }
37,128✔
874

62,640✔
875
    size_t new_size = size;
115,554✔
876
    if (m_encryption_key) {
115,554✔
877
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
633✔
878
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
633✔
879
        if (new_size < size) {
633✔
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,554✔
885

62,640✔
886
    auto manually_consume_space = [&]() {
115,554✔
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,640✔
899
    auto consume_space_interlocked = [&] {
62,640✔
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,640✔
916
#if REALM_HAVE_POSIX_FALLOCATE
62,640✔
917
    // Mostly Linux only
62,640✔
918
    if (!prealloc_if_supported(0, new_size)) {
62,640✔
919
        consume_space_interlocked();
920
    }
921
#else // Non-atomic fallback
922
#if REALM_PLATFORM_APPLE
52,914✔
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,914✔
927
    if (::fstat(m_fd, &statbuf) != 0) {
52,914✔
928
        int err = errno;
929
        throw SystemError(err, "fstat() inside prealloc() failed");
930
    }
931

932
    size_t allocated_size;
52,914✔
933
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
52,914✔
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,914✔
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,914✔
947

948
        off_t to_allocate = static_cast<off_t>(new_size - statbuf.st_size);
52,878✔
949
        fstore_t store = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, to_allocate, 0};
52,878✔
950
        int ret = 0;
52,878✔
951
        do {
52,878✔
952
            ret = fcntl(m_fd, F_PREALLOCATE, &store);
52,878✔
953
        } while (ret == -1 && errno == EINTR);
52,878!
954
        if (ret == -1) {
52,878✔
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,878✔
969

970
    int ret = 0;
52,914✔
971

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

976
    if (ret != 0) {
52,914✔
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,914✔
991
}
115,554✔
992

993

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

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

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

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

62,640✔
1015
    if (REALM_LIKELY(status == 0)) {
62,640✔
1016
        return true;
62,640✔
1017
    }
62,640✔
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
1045
    return false;
×
1046
}
×
1047

1048

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

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

1064
void File::seek_static(FileDesc fd, SizeType position)
1065
{
1,480,545✔
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

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

748,830✔
1081
    if (0 <= ::lseek(fd, position2, SEEK_SET))
1,480,545✔
1082
        return;
1,480,545✔
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,742,554✔
1132
    int r;
3,742,554✔
1133
    do {
3,742,554✔
1134
        r = flock(m_fd, LOCK_UN);
3,742,554✔
1135
    } while (r != 0 && errno == EINTR);
3,742,554!
1136
    if (r) {
3,742,554✔
1137
        throw SystemError(errno, "File::unlock() has failed");
×
1138
    }
×
1139
}
3,742,554✔
1140
#endif
1141

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

184,953✔
1147
#ifndef REALM_FILELOCK_EMULATION
351,660✔
1148
    return lock(exclusive, non_blocking);
351,660✔
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
}
351,660✔
1239

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

184,956✔
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,956✔
1270
    // based POSIX locks, however these locks are not recursive within
184,956✔
1271
    // a single process, and since a second attempt to acquire such a
184,956✔
1272
    // lock will always appear to succeed, one will easily suffer the
184,956✔
1273
    // 'spurious unlocking issue'. It remains to be determined whether
184,956✔
1274
    // this also applies across distinct threads inside a single
184,956✔
1275
    // process.
184,956✔
1276
    //
184,956✔
1277
    // To make matters worse, flock() may be a simple wrapper around
184,956✔
1278
    // fcntl() based locks on some systems. This is bad news, because
184,956✔
1279
    // the robustness of the Realm API relies in part by the
184,956✔
1280
    // assumption that a single process (even a single thread) can
184,956✔
1281
    // hold multiple overlapping independent shared locks on a single
184,956✔
1282
    // file as long as they are placed via distinct file descriptors.
184,956✔
1283
    //
184,956✔
1284
    // Fortunately, on both Linux and Darwin, flock() does not suffer
184,956✔
1285
    // from this 'spurious unlocking issue'.
184,956✔
1286
    int operation = exclusive ? LOCK_EX : LOCK_SH;
3,856,296✔
1287
    if (non_blocking)
3,856,296✔
1288
        operation |= LOCK_NB;
262,215✔
1289
    do {
3,856,296✔
1290
        if (flock(m_fd, operation) == 0) {
3,856,296✔
1291
            m_have_lock = true;
3,744,996✔
1292
            return true;
3,744,996✔
1293
        }
3,744,996✔
1294
    } while (errno == EINTR);
111,300✔
1295
    int err = errno; // Eliminate any risk of clobbering
234,105✔
1296
    if (err == EWOULDBLOCK)
111,300✔
1297
        return false;
113,766✔
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,742,740✔
1304
    if (!m_have_lock)
3,742,740✔
1305
        return;
×
1306

122,805✔
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,742,740✔
1317
#endif
3,742,740✔
1318
    m_have_lock = false;
3,742,740✔
1319
}
3,742,740✔
1320

1321
void File::rw_unlock() noexcept
1322
{
237,777✔
1323
#ifndef REALM_FILELOCK_EMULATION
237,777✔
1324
    unlock();
237,777✔
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
}
237,777✔
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,883✔
1380
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
1,328,883✔
1381
}
1,328,883✔
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,379,616✔
1432
    realm::util::msync(fd, addr, size);
1,379,616✔
1433
}
1,379,616✔
1434

1435

1436
bool File::exists(const std::string& path)
1437
{
630,825✔
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)
630,825✔
1442
        return true;
31,275✔
1443
    int err = errno; // Eliminate any risk of clobbering
599,550✔
1444
    switch (err) {
599,550✔
1445
        case EACCES:
334,962✔
1446
        case ENOENT:
599,529✔
1447
        case ENOTDIR:
599,529✔
1448
            return false;
599,529✔
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,038✔
1463
    int err = errno; // Eliminate any risk of clobbering
5,634✔
1464
    switch (err) {
5,634✔
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,693✔
1482
    int err = ENOENT;
12✔
1483
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
12✔
1484
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
12✔
1485
}
12✔
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,160✔
1498

17,784✔
1499
    int err = errno; // Eliminate any risk of clobbering
35,565✔
1500
    if (err == ENOENT)
35,565✔
1501
        return false;
35,544✔
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,971✔
1627
    FileDesc fd_duped;
1,971✔
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,971✔
1633

840✔
1634
    if (fd_duped == -1) {
1,971✔
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,971✔
1639
    return fd_duped;
1,971✔
1640
}
1,971✔
1641

1642
File::UniqueID File::get_unique_id()
1643
{
212,799✔
1644
    REALM_ASSERT_RELEASE(is_attached());
212,799✔
1645
    File::UniqueID uid = File::get_unique_id(m_fd, m_path);
212,799✔
1646
    if (!m_cached_unique_id) {
212,799✔
1647
        m_cached_unique_id = std::make_optional(uid);
212,787✔
1648
    }
212,787✔
1649
    if (m_cached_unique_id != uid) {
212,799✔
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,799✔
1657
}
212,799✔
1658

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

1664
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1665
{
169,656✔
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,656✔
1683
    if (::stat(path.c_str(), &statbuf) == 0) {
169,656✔
1684
        if (statbuf.st_size == 0) {
111,213✔
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);
73,080✔
1693
    }
73,080✔
1694
    int err = errno; // Eliminate any risk of clobbering
58,443✔
1695
    // File doesn't exist
3✔
1696
    if (err == ENOENT)
58,443✔
1697
        return none;
58,443✔
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,162✔
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,162✔
1715
    struct stat statbuf;
216,162✔
1716
    if (::fstat(file, &statbuf) == 0) {
216,162✔
1717
        // On exFAT systems the inode and device are not populated correctly until the file
44,199✔
1718
        // has been allocated some space. The uid can also be reassigned if the file is
44,199✔
1719
        // truncated to zero. This has led to bugs where a unique id returned here was
44,199✔
1720
        // reused by different files. The following check ensures that this is not
44,199✔
1721
        // happening anywhere else in future code.
44,199✔
1722
        if (statbuf.st_size == 0) {
216,162✔
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,162✔
1731
    }
216,162✔
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,980✔
1738
    return m_path;
43,980✔
1739
}
43,980✔
1740

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

55,566✔
1774
        if (path_2.size() < 2 || path_2[1] != '.')
55,566✔
1775
            break;
55,566✔
1776
        if (path_2.size())
55,566✔
1777
    }
55,566✔
1778
    */
55,566✔
1779
    return base_dir_2 + path_2;
210,531✔
1780
#endif
210,531✔
1781
}
210,531✔
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,837✔
1806
#if REALM_ENABLE_ENCRYPTION
87,837✔
1807
    if (key) {
87,837✔
1808
        auto buffer = std::make_unique<char[]>(64);
330✔
1809
        memcpy(buffer.get(), key, 64);
330✔
1810
        m_encryption_key = std::move(buffer);
330✔
1811
    }
330✔
1812
    else {
87,507✔
1813
        m_encryption_key.reset();
87,507✔
1814
    }
87,507✔
1815
#else
1816
    if (key) {
1817
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1818
    }
1819
#endif
1820
}
87,837✔
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,907✔
1830
    REALM_ASSERT(!m_addr);
1,328,907✔
1831
#if REALM_ENABLE_ENCRYPTION
1,328,907✔
1832
    m_addr = f.map(a, size, m_encrypted_mapping, map_flags, offset);
1,328,907✔
1833
    if (observer && m_encrypted_mapping) {
1,328,907✔
1834
        m_encrypted_mapping->set_observer(observer);
384✔
1835
    }
384✔
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,907✔
1841
    m_fd = f.m_fd;
1,328,907✔
1842
    m_offset = offset;
1,328,907✔
1843
    m_access_mode = a;
1,328,907✔
1844
}
1,328,907✔
1845

1846

1847
void File::MapBase::unmap() noexcept
1848
{
3,077,766✔
1849
    if (!m_addr)
3,077,766✔
1850
        return;
1,661,754✔
1851
    REALM_ASSERT(m_reservation_size);
1,416,012✔
1852
#if REALM_ENABLE_ENCRYPTION
1,416,012✔
1853
    if (m_encrypted_mapping) {
1,416,012✔
1854
        m_encrypted_mapping = nullptr;
3,309✔
1855
        util::remove_encrypted_mapping(m_addr, m_size);
3,309✔
1856
    }
3,309✔
1857
#endif
1,416,012✔
1858
    ::munmap(m_addr, m_reservation_size);
1,416,012✔
1859
    m_addr = nullptr;
1,416,012✔
1860
    m_size = 0;
1,416,012✔
1861
    m_reservation_size = 0;
1,416,012✔
1862
}
1,416,012✔
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,222✔
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,222✔
1880
    if (addr == MAP_FAILED)
87,222✔
1881
        return false;
42,705✔
1882
    m_addr = addr;
87,222✔
1883
    REALM_ASSERT(m_size == 0);
87,222✔
1884
    m_access_mode = a;
87,222✔
1885
    m_reservation_size = size;
87,222✔
1886
    m_fd = file.get_descriptor();
87,222✔
1887
    m_offset = offset;
87,222✔
1888
#if REALM_ENABLE_ENCRYPTION
87,222✔
1889
    if (file.m_encryption_key) {
87,222✔
1890
        m_encrypted_mapping =
264✔
1891
            util::reserve_mapping(addr, {m_fd, file.get_path(), a, file.m_encryption_key.get()}, offset);
264✔
1892
        if (observer) {
264✔
1893
            m_encrypted_mapping->set_observer(observer);
258✔
1894
        }
258✔
1895
    }
264✔
1896
#else
1897
    static_cast<void>(observer);
1898
#endif
1899
#endif
87,222✔
1900
    return true;
87,222✔
1901
}
87,222✔
1902

1903
bool File::MapBase::try_extend_to(size_t size) noexcept
1904
{
105,717✔
1905
    if (size > m_reservation_size) {
105,717✔
1906
        return false;
×
1907
    }
×
1908
    // return false;
57,522✔
1909
#ifndef _WIN32
105,717✔
1910
    char* extension_start_addr = (char*)m_addr + m_size;
105,717✔
1911
    size_t extension_size = size - m_size;
105,717✔
1912
    size_t extension_start_offset = m_offset + m_size;
105,717✔
1913
#if REALM_ENABLE_ENCRYPTION
105,717✔
1914
    if (m_encrypted_mapping) {
105,717✔
1915
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
303✔
1916
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
303✔
1917
        if (got_addr == MAP_FAILED)
303✔
1918
            return false;
162✔
1919
        REALM_ASSERT(got_addr == extension_start_addr);
303✔
1920
        util::extend_encrypted_mapping(m_encrypted_mapping, m_addr, m_offset, m_size, size);
303✔
1921
        m_size = size;
303✔
1922
        return true;
303✔
1923
    }
303✔
1924
#endif
105,414✔
1925
    try {
105,414✔
1926
        void* got_addr = util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode,
105,414✔
1927
                                          extension_start_offset, nullptr);
105,414✔
1928
        if (got_addr == extension_start_addr) {
105,414✔
1929
            m_size = size;
105,333✔
1930
            return true;
105,333✔
1931
        }
105,333✔
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,379,592✔
1942
    REALM_ASSERT(m_addr);
1,379,592✔
1943

709,593✔
1944
    File::sync_map(m_fd, m_addr, m_size);
1,379,592✔
1945
}
1,379,592✔
1946

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

1957
std::time_t File::last_write_time(const std::string& path)
1958
{
894✔
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;
894✔
1972
    if (::stat(path.c_str(), &statbuf) != 0) {
894✔
1973
        throw SystemError(errno, "stat() failed");
×
1974
    }
×
1975
    return statbuf.st_mtime;
894✔
1976
#endif
894✔
1977
}
894✔
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,273✔
2021
    m_dirp = opendir(path.c_str());
120,273✔
2022
    if (!m_dirp) {
120,273✔
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,273✔
2038

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

2047
bool DirScanner::next(std::string& name)
2048
{
318,723✔
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,723✔
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,494✔
2061
#else
2062
#define REALM_READDIR(...) readdir(__VA_ARGS__)
324,651✔
2063
#endif
207,249✔
2064

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

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

217,494✔
2078
        if (!dirent) {
542,145✔
2079
            if (errno != 0)
114,516✔
2080
                throw SystemError(errno, "readdir() failed");
×
2081
            return false; // End of stream
114,516✔
2082
        }
114,516✔
2083
        const char* name_1 = dirent->d_name;
427,629✔
2084
        std::string name_2 = name_1;
427,629✔
2085
        if (name_2 != "." && name_2 != "..") {
427,629✔
2086
            name = name_2;
198,501✔
2087
            return true;
198,501✔
2088
        }
198,501✔
2089
    }
427,629✔
2090
}
313,017✔
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