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

realm / realm-core / thomas.goyne_275

09 Apr 2024 03:33AM UTC coverage: 92.608% (+0.5%) from 92.088%
thomas.goyne_275

Pull #7300

Evergreen

tgoyne
Extract some duplicated code in PushClient
Pull Request #7300: Rework sync user handling and metadata storage

102672 of 194970 branches covered (52.66%)

3165 of 3247 new or added lines in 46 files covered. (97.47%)

34 existing lines in 9 files now uncovered.

249420 of 269329 relevant lines covered (92.61%)

45087511.34 hits per line

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

92.75
/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
{
192✔
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);
192✔
62
#endif
192✔
63
    REALM_ASSERT(size > 0 && size % 4096 == 0);
192✔
64
    return static_cast<size_t>(size);
192✔
65
}
192✔
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
{
240✔
73
    using File = realm::util::File;
240✔
74
    realm::util::DirScanner ds{path}; // Throws
240✔
75
    std::string name;
240✔
76
    while (ds.next(name)) {                              // Throws
720✔
77
        std::string subpath = File::resolve(name, path); // Throws
480✔
78
        bool go_on;
480✔
79
        if (File::is_dir(subpath)) {                           // Throws
480✔
80
            std::string subdir = File::resolve(name, dir);     // Throws
192✔
81
            go_on = for_each_helper(subpath, subdir, handler); // Throws
192✔
82
        }
192✔
83
        else {
288✔
84
            go_on = handler(name, dir); // Throws
288✔
85
        }
288✔
86
        if (!go_on)
480✔
87
            return false;
×
88
    }
480✔
89
    return true;
240✔
90
}
240✔
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
    {
384✔
189
    }
384✔
190
};
191

192
} // anonymous namespace
193

194

195
bool try_make_dir(const std::string& path)
196
{
1,652,751✔
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)
1,652,751✔
204
        return true;
610,188✔
205

476,583✔
206
    int err = errno; // Eliminate any risk of clobbering
1,042,563✔
207
    if (err == EEXIST)
1,042,563✔
208
        return false;
1,042,542✔
209

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

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

222

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

230

231
void make_dir_recursive(std::string path)
232
{
1,626✔
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("/\\");
1,626✔
240
    if (pos == std::string::npos)
1,626✔
241
        return;
×
242
    pos += 1;
1,626✔
243

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

263

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

273

274
bool try_remove_dir(const std::string& path)
275
{
920,646✔
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)
920,646✔
283
        return true;
919,575✔
284

525✔
285
    int err = errno; // Eliminate any risk of clobbering
1,071✔
286
    if (err == ENOENT)
1,071✔
287
        return false;
1,047✔
288

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

304

305
bool try_remove_dir_recursive(const std::string& path)
306
{
843,876✔
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
    {
843,876✔
314
        bool allow_missing = true;
843,876✔
315
        DirScanner ds{path, allow_missing}; // Throws
843,876✔
316
        std::string name;
843,876✔
317
        while (ds.next(name)) {                              // Throws
2,277,474✔
318
            std::string subpath = File::resolve(name, path); // Throws
1,433,598✔
319
            if (File::is_dir(subpath)) {                     // Throws
1,433,598✔
320
                try_remove_dir_recursive(subpath);           // Throws
402,270✔
321
            }
402,270✔
322
            else {
1,031,328✔
323
                File::remove(subpath); // Throws
1,031,328✔
324
            }
1,031,328✔
325
        }
1,433,598✔
326
    }
843,876✔
327
    return try_remove_dir(path); // Throws
843,876✔
328
#endif
843,876✔
329
}
843,876✔
330

331

332
std::string make_temp_dir()
333
{
352,320✔
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

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

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

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

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

3,892,581✔
414
size_t page_size()
3,892,581✔
415
{
27,483,063✔
416
    return cached_page_size;
27,483,063✔
417
}
27,931,389✔
418

448,326✔
419
void File::open_internal(const std::string& path, AccessMode a, CreateMode c, int flags, bool* success)
448,326✔
420
{
3,482,178✔
421
    REALM_ASSERT_RELEASE(!is_attached());
3,161,742✔
422
    m_path = path; // for error reporting and debugging
3,033,852✔
423
    m_cached_unique_id = {};
3,033,852✔
424

860,664✔
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
    }
127,890✔
488

448,326✔
489
#else // POSIX version
448,326✔
490

863,547✔
491
    int flags2 = 0;
3,036,735✔
492
    switch (a) {
3,036,735✔
493
        case access_ReadOnly:
465,732✔
494
            flags2 = O_RDONLY;
465,732✔
495
            break;
465,732✔
496
        case access_ReadWrite:
3,462,024✔
497
            flags2 = O_RDWR;
3,462,024✔
498
            break;
3,431,721✔
499
    }
3,451,992✔
500
    switch (c) {
3,451,992✔
501
        case create_Auto:
2,858,916✔
502
            flags2 |= O_CREAT;
2,858,916✔
503
            break;
2,830,266✔
504
        case create_Never:
199,464✔
505
            break;
199,464✔
506
        case create_Must:
454,224✔
507
            flags2 |= O_CREAT | O_EXCL;
454,224✔
508
            break;
6,438✔
509
    }
3,482,139✔
510
    if (flags & flag_Trunc)
3,216,951✔
511
        flags2 |= O_TRUNC;
454,308✔
512
    if (flags & flag_Append)
3,482,139✔
513
        flags2 |= O_APPEND;
1,664,037✔
514
    int fd = ::open(path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
3,482,019✔
515
    if (0 <= fd) {
3,482,019✔
516
        m_fd = fd;
3,033,252✔
517
        m_have_lock = false;
3,481,272✔
518
        if (success)
3,481,272✔
519
            *success = true;
1,587✔
520
        return;
3,033,168✔
521
    }
3,033,168✔
522

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

438✔
552
#endif
4,294,967,894✔
553
}
4,294,967,894✔
554

876,309✔
555

556
void File::close() noexcept
557
{
5,877,861✔
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;
248,775✔
568

876,309✔
569
#else // POSIX version
428,190✔
570

2,113,155✔
571
    if (m_fd < 0)
5,878,014✔
572
        return;
3,293,571✔
573
    if (m_have_lock)
3,480,528✔
574
        unlock();
449,190✔
575
    int r = ::close(m_fd);
3,160,218✔
576
    REALM_ASSERT_RELEASE(r == 0);
3,480,528✔
577
    m_fd = -1;
3,480,528✔
578

860,121✔
579
#endif
3,032,409✔
580
}
3,034,005✔
581

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

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

7,716✔
595
    int ret = -1;
11,808!
596
    do {
11,181✔
597
        ret = ::close(fd);
11,808✔
598
    } while (ret == -1 && errno == EINTR);
10,212!
599

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

608
size_t File::read_static(FileDesc fd, char* data, size_t size)
609
{
3,507,300✔
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");
263,505✔
630

502,581✔
631
#else // POSIX version
1,004,277✔
632

2,101,926✔
633
    char* const data_0 = data;
4,009,971✔
634
    while (0 < size) {
7,510,950✔
635
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
2,341,614✔
636
        size_t n = std::min(size, size_t(SSIZE_MAX));
3,508,905✔
637
        ssize_t r = ::read(fd, data, n);
4,009,626✔
638
        if (r == 0)
3,507,930✔
639
            break;
508,647✔
640
        if (r < 0)
4,002,675✔
641
            goto error; // LCOV_EXCL_LINE
501,696✔
642
        REALM_ASSERT_RELEASE(size_t(r) <= n);
4,002,675✔
643
        size -= size_t(r);
4,003,560✔
644
        data += size_t(r);
3,500,979✔
645
    }
3,500,979✔
646
    return data - data_0;
3,507,300✔
647

648
error:
263,505✔
649
    // LCOV_EXCL_START
502,581✔
650
    throw SystemError(errno, "read() failed");
502,581✔
651
// LCOV_EXCL_STOP
1,838,334✔
652
#endif
3,507,300✔
653
}
3,507,300✔
654

1,266✔
655

1,266✔
656
size_t File::read(char* data, size_t size)
861✔
657
{
10,464✔
658
    REALM_ASSERT_RELEASE(is_attached());
9,198✔
659

6,363!
660
    if (m_encryption_key) {
9,198✔
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);
861✔
669
        return read_map.get_size() - pos;
1,266✔
670
    }
1,266✔
671

6,363✔
672
    return read_static(m_fd, data, size);
9,198✔
673
}
266,454✔
674

675
void File::write_static(FileDesc fd, const char* data, size_t size)
676
{
1,803,639✔
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
    }
514,395✔
697
    throw SystemError(err, "WriteFile() failed");
125,433✔
698
#else
257,139✔
699
    while (0 < size) {
3,863,472✔
700
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
1,136,499✔
701
        size_t n = std::min(size, size_t(SSIZE_MAX));
1,802,694✔
702
        ssize_t r = ::write(fd, data, n);
2,059,833✔
703
        if (r < 0)
2,059,833✔
704
            goto error; // LCOV_EXCL_LINE
257,139✔
705
        REALM_ASSERT_RELEASE(r != 0);
2,059,833✔
706
        REALM_ASSERT_RELEASE(size_t(r) <= n);
2,059,833✔
707
        size -= size_t(r);
2,059,950✔
708
        data += size_t(r);
1,802,694✔
709
    }
1,802,694✔
710
    return;
1,803,639✔
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
}
44,952✔
724

44,952✔
725
void File::write(const char* data, size_t size)
22,743✔
726
{
365,049✔
727
    REALM_ASSERT_RELEASE(is_attached());
320,955✔
728

162,978✔
729
    if (m_encryption_key) {
320,955✔
730
        uint64_t pos_original = get_file_pos(m_fd);
6,846✔
731
        REALM_ASSERT(!int_cast_has_overflow<size_t>(pos_original));
6,846✔
732
        size_t pos = size_t(pos_original);
6,846✔
733
        Map<char> write_map(*this, access_ReadWrite, static_cast<size_t>(pos + size));
6,846✔
734
        realm::util::encryption_read_barrier(write_map, pos, size);
6,846✔
735
        memcpy(write_map.get_addr() + pos, data, size);
6,846✔
736
        realm::util::encryption_write_barrier(write_map, pos, size);
6,846✔
737
        uint64_t cur = get_file_pos(m_fd);
6,846✔
738
        seek(cur + size);
28,146✔
739
        return;
50,082✔
740
    }
50,082✔
741

158,037✔
742
    write_static(m_fd, data, size);
314,109✔
743
}
1,030,302✔
744

745
uint64_t File::get_file_pos(FileDesc fd)
746
{
4,999,605✔
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

716,193✔
756
    return uint64_t(res.QuadPart);
716,193✔
757
#else
758
    auto pos = lseek(fd, 0, SEEK_CUR);
4,999,605✔
759
    if (pos < 0) {
5,715,798✔
760
        throw SystemError(errno, "lseek() failed");
716,193✔
761
    }
716,193✔
762
    return uint64_t(pos);
4,999,605✔
763
#endif
4,999,605✔
764
}
5,000,958✔
765

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

772
File::SizeType File::get_size_static(FileDesc fd)
773
{
12,273,564✔
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");
903,990✔
784

1,760,427✔
785
#else // POSIX version
1,760,787✔
786

8,044,035✔
787
    struct stat statbuf;
14,034,351✔
788
    if (::fstat(fd, &statbuf) == 0) {
12,275,466✔
789
        SizeType size;
13,179,672✔
790
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
14,036,253✔
791
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
1,760,787✔
792

4,301,251,481✔
793
        return size;
2,159,759,113✔
794
    }
4,307,242,760✔
795
    throw SystemError(errno, "fstat() failed");
34,359,738,352✔
796

15,032,385,529✔
797
#endif
30,064,771,058✔
798
}
30,066,528,428✔
799

1,757,370✔
800
File::SizeType File::get_size() const
1,757,370✔
801
{
13,155,963✔
802
    REALM_ASSERT_RELEASE(is_attached());
14,011,092✔
803
    File::SizeType size = get_size_static(m_fd);
12,257,757✔
804

6,276,225✔
805
    if (m_encryption_key) {
12,257,757✔
806
        File::SizeType ret_size = encrypted_size_to_data_size(size);
1,780,116✔
807
        return ret_size;
1,780,116✔
808
    }
1,784,151✔
809
    else
12,226,941✔
810
        return size;
12,226,941✔
811
}
12,253,722✔
812

271,893✔
813

271,893✔
814
void File::resize(SizeType size)
43,812✔
815
{
1,795,578✔
816
    REALM_ASSERT_RELEASE(is_attached());
1,795,578✔
817

286,161✔
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);
43,812✔
842

271,893✔
843
#else // POSIX version
42✔
844

329,973✔
845
    if (m_encryption_key)
2,067,471✔
846
        size = data_size_to_encrypted_size(size);
272,145✔
847

286,161✔
848
    off_t size2;
1,839,390✔
849
    if (int_cast_with_overflow_detect(size, size2))
1,839,390✔
850
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
43,812✔
851

558,054✔
852
    // POSIX specifies that introduced bytes read as zero. This is not
286,161✔
853
    // required by File::resize().
286,161✔
854
    if (::ftruncate(m_fd, size2) != 0) {
1,795,578!
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
        }
43,812✔
860
        throw SystemError(err, msg);
271,893✔
861
    }
271,893✔
862

286,161✔
863
#endif
1,795,578✔
864
}
1,795,578✔
865

164,628✔
866

164,628✔
867
void File::prealloc(size_t size)
86,676✔
868
{
1,284,987✔
869
    REALM_ASSERT_RELEASE(is_attached());
1,158,690✔
870

629,847✔
871
    if (size <= to_size_t(get_size())) {
1,188,312✔
872
        return;
397,917✔
873
    }
397,917✔
874

459,417✔
875
    size_t new_size = size;
849,390✔
876
    if (m_encryption_key) {
849,390✔
877
        new_size = static_cast<size_t>(data_size_to_encrypted_size(size));
4,539✔
878
        REALM_ASSERT(size == static_cast<size_t>(encrypted_size_to_data_size(new_size)));
4,539✔
879
        if (new_size < size) {
4,539✔
880
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow: data_size_to_encrypted_size(" +
×
881
                                                           realm::util::to_string(size) +
126,297✔
882
                                                           ") == " + realm::util::to_string(new_size));
67,953✔
883
        }
126,297✔
884
    }
848,739✔
885

458,766✔
886
    auto manually_consume_space = [&]() {
848,739✔
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;
67,953✔
896
        }
67,953✔
897
    };
×
898

458,766!
899
    auto consume_space_interlocked = [&] {
458,766✔
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();
67,953✔
913
#endif
67,953✔
914
    };
67,953✔
915

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

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

932
    size_t allocated_size;
389,973✔
933
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
389,973✔
934
        throw RuntimeError(ErrorCodes::RangeError,
58,344✔
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)) {
389,973✔
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.
58,344✔
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.
58,311✔
946
    if (new_size > allocated_size) {
448,284✔
947

58,311✔
948
        off_t to_allocate = static_cast<off_t>(new_size - statbuf.st_size);
448,032✔
949
        fstore_t store = {F_ALLOCATEALL, F_PEOFPOSMODE, 0, to_allocate, 0};
448,032✔
950
        int ret = 0;
448,032!
951
        do {
448,032✔
952
            ret = fcntl(m_fd, F_PREALLOCATE, &store);
389,721✔
953
        } while (ret == -1 && errno == EINTR);
389,721!
954
        if (ret == -1) {
389,721✔
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
58,311✔
966
            consume_space_interlocked();
967
        }
58,344✔
968
    }
389,721✔
969

58,344✔
970
    int ret = 0;
448,317✔
971

58,344!
972
    do {
389,973✔
973
        ret = ftruncate(m_fd, new_size);
448,317✔
974
    } while (ret == -1 && errno == EINTR);
389,973!
975

976
    if (ret != 0) {
389,973✔
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
58,344✔
988
#endif
126,297✔
989

990
#endif // REALM_HAVE_POSIX_FALLOCATE
389,973✔
991
}
848,739✔
992

67,950✔
993

67,950!
994
bool File::prealloc_if_supported(SizeType offset, size_t size)
67,950✔
995
{
526,734✔
996
    REALM_ASSERT_RELEASE(is_attached());
526,734!
997

526,734✔
998
#if REALM_HAVE_POSIX_FALLOCATE
526,734✔
999

526,734✔
1000
    REALM_ASSERT_RELEASE(is_prealloc_supported());
458,784✔
1001

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

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

526,734✔
1015
    if (REALM_LIKELY(status == 0)) {
458,784✔
1016
        return true;
458,781✔
1017
    }
458,781✔
1018

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

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

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

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

3✔
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;
3✔
1046
}
3✔
1047

67,950✔
1048

67,950✔
1049
bool File::is_prealloc_supported()
67,950✔
1050
{
458,781✔
1051
#if REALM_HAVE_POSIX_FALLOCATE
458,781✔
1052
    return true;
458,781✔
1053
#else
67,950✔
1054
    return false;
1055
#endif
1056
}
498,474✔
1057

39,693✔
1058
void File::seek(SizeType position)
39,693✔
1059
{
322,626✔
1060
    REALM_ASSERT_RELEASE(is_attached());
282,933✔
1061
    seek_static(m_fd, position);
282,933✔
1062
}
1,751,580✔
1063

1064
void File::seek_static(FileDesc fd, SizeType position)
1065
{
10,258,188✔
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");
751,614✔
1074

1,468,647✔
1075
#else // POSIX version
1,468,647✔
1076

5,247,840✔
1077
    off_t position2;
11,009,802✔
1078
    if (int_cast_with_overflow_detect(position, position2))
11,726,835✔
1079
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
1,468,647✔
1080

5,247,840✔
1081
    if (0 <= ::lseek(fd, position2, SEEK_SET))
10,258,188✔
1082
        return;
10,258,194✔
1083
    throw SystemError(errno, "lseek() failed");
6,442,450,944✔
1084

4,294,967,297✔
1085
#endif
6,442,450,944✔
1086
}
6,442,450,944✔
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
810✔
1091
// http://www.humboldt.co.uk/2009/03/fsync-across-platforms.html.
810✔
1092
void File::sync()
501✔
1093
{
5,880✔
1094
    REALM_ASSERT_RELEASE(is_attached());
5,880✔
1095

3,717✔
1096
#if defined _WIN32 // Windows version
1097

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

309✔
1102
#elif REALM_PLATFORM_APPLE
309✔
1103

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

501✔
1108
#else // POSIX version
501✔
1109

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

1114
#endif
1115
}
372✔
1116

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

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

165,018✔
1142
bool File::rw_lock(bool exclusive, bool non_blocking)
344,520✔
1143
{
2,602,305✔
1144
    // exclusive blocking rw locks not implemented for emulation
1,470,213✔
1145
    REALM_ASSERT(!exclusive || non_blocking);
2,781,807✔
1146

1,125,693✔
1147
#ifndef REALM_FILELOCK_EMULATION
2,437,287✔
1148
    return lock(exclusive, non_blocking);
2,437,287✔
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;
344,520✔
1236
    return true;
1237
#endif // REALM_FILELOCK_EMULATION
1238
}
6,420,405✔
1239

3,983,118✔
1240
bool File::lock(bool exclusive, bool non_blocking)
3,983,118✔
1241
{
28,196,277✔
1242
    REALM_ASSERT_RELEASE(is_attached());
28,031,253✔
1243
    REALM_ASSERT_RELEASE(!m_have_lock);
28,031,253✔
1244

1,125,714✔
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;
165,024✔
1267
    throw std::system_error(err, std::system_category(), "LockFileEx() failed");
165,024✔
1268
#else // _WIN32
165,024✔
1269
    // NOTE: It would probably have been more portable to use fcntl()
1,290,738✔
1270
    // based POSIX locks, however these locks are not recursive within
1,290,738✔
1271
    // a single process, and since a second attempt to acquire such a
1,290,738✔
1272
    // lock will always appear to succeed, one will easily suffer the
1,290,738✔
1273
    // 'spurious unlocking issue'. It remains to be determined whether
1,290,738✔
1274
    // this also applies across distinct threads inside a single
1,290,738✔
1275
    // process.
1,290,738✔
1276
    //
1,290,738✔
1277
    // To make matters worse, flock() may be a simple wrapper around
1,290,738✔
1278
    // fcntl() based locks on some systems. This is bad news, because
1,290,738✔
1279
    // the robustness of the Realm API relies in part by the
1,290,738✔
1280
    // assumption that a single process (even a single thread) can
1,290,738✔
1281
    // hold multiple overlapping independent shared locks on a single
1,290,738✔
1282
    // file as long as they are placed via distinct file descriptors.
1,290,738✔
1283
    //
5,108,832✔
1284
    // Fortunately, on both Linux and Darwin, flock() does not suffer
5,108,832✔
1285
    // from this 'spurious unlocking issue'.
1,390,779✔
1286
    int operation = exclusive ? LOCK_EX : LOCK_SH;
32,014,371✔
1287
    if (non_blocking)
32,014,371✔
1288
        operation |= LOCK_NB;
5,779,185✔
1289
    do {
31,914,699✔
1290
        if (flock(m_fd, operation) == 0) {
31,914,699✔
1291
            m_have_lock = true;
27,379,713✔
1292
            return true;
27,496,590✔
1293
        }
27,379,713✔
1294
    } while (errno == EINTR);
853,725✔
1295
    int err = errno; // Eliminate any risk of clobbering
4,296,505,835✔
1296
    if (err == EWOULDBLOCK)
4,295,718,506✔
1297
        return false;
4,295,741,477✔
1298
    throw SystemError(err, "flock() failed");
27,917,287,411✔
1299
#endif
27,917,287,411✔
1300
}
27,921,168,694✔
1301

3,881,283✔
1302
void File::unlock() noexcept
1303
{
27,379,401✔
1304
    if (!m_have_lock)
27,262,524✔
1305
        return;
1306

787,332✔
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);
3,881,283✔
1314
    REALM_ASSERT_RELEASE(r);
3,881,283✔
1315
#else
3,881,283✔
1316
    _unlock(m_fd);
31,143,807✔
1317
#endif
27,262,524✔
1318
    m_have_lock = false;
27,262,524✔
1319
}
27,504,447✔
1320

241,923✔
1321
void File::rw_unlock() noexcept
241,923✔
1322
{
1,662,303✔
1323
#ifndef REALM_FILELOCK_EMULATION
1,662,303✔
1324
    unlock();
1,662,303✔
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);
241,923✔
1345
    m_has_exclusive_lock = false;
1346
#endif // REALM_FILELOCK_EMULATION
1347
}
1,662,309✔
1348

6✔
1349
void* File::map(AccessMode a, size_t size, int /*map_flags*/, size_t offset) const
6✔
1350
{
42✔
1351
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset);
42✔
1352
}
42✔
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

1,378,953✔
1377
#if REALM_ENABLE_ENCRYPTION
1,378,953✔
1378
void* File::map(AccessMode a, size_t size, EncryptedFileMapping*& mapping, int /*map_flags*/, size_t offset) const
1,378,953✔
1379
{
9,574,731✔
1380
    return realm::util::mmap({m_fd, m_path, a, m_encryption_key.get()}, size, offset, mapping);
9,574,731✔
1381
}
9,574,731✔
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

1,365,774✔
1429

1,365,774✔
1430
void File::sync_map(FileDesc fd, void* addr, size_t size)
1,365,774✔
1431
{
9,769,734✔
1432
    realm::util::msync(fd, addr, size);
9,769,734✔
1433
}
9,769,734✔
1434

701,445✔
1435

1436
bool File::exists(const std::string& path)
1437
{
5,484,195✔
1438
#if REALM_HAVE_STD_FILESYSTEM
701,445✔
1439
    return std::filesystem::exists(u8path(path));
30,645✔
1440
#else // POSIX
670,800✔
1441
    if (::access(path.c_str(), F_OK) == 0)
6,154,995✔
1442
        return true;
601,647✔
1443
    int err = errno; // Eliminate any risk of clobbering
5,931,078✔
1444
    switch (err) {
5,931,078✔
1445
        case EACCES:
3,996,312✔
1446
        case ENOENT:
5,260,203✔
1447
        case ENOTDIR:
5,260,203✔
1448
            return false;
5,260,203✔
1449
    }
192✔
1450
    throw SystemError(err, "access() failed");
168✔
1451
#endif
168✔
1452
}
168✔
1453

225,186✔
1454

1455
bool File::is_dir(const std::string& path)
1456
{
1,513,344✔
1457
#if REALM_HAVE_STD_FILESYSTEM
162,534✔
1458
    return std::filesystem::is_directory(u8path(path));
225,186✔
1459
#elif !defined(_WIN32)
218,667✔
1460
    struct stat statbuf;
1,117,131✔
1461
    if (::stat(path.c_str(), &statbuf) == 0)
1,519,863✔
1462
        return S_ISDIR(statbuf.st_mode);
1,470,972✔
1463
    int err = errno; // Eliminate any risk of clobbering
52,152✔
1464
    switch (err) {
52,152✔
1465
        case EACCES:
29,343✔
1466
        case ENOENT:
45,627✔
1467
        case ENOTDIR:
45,627✔
1468
            return false;
45,627✔
1469
    }
1470
    throw SystemError(err, "stat() failed");
1471
#else
1472
    static_cast<void>(path);
1473
    throw NotImplemented();
1474
#endif
1475
}
1476

169,611✔
1477

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

228,192✔
1487

1488
bool File::try_remove(const std::string& path)
1489
{
1,615,380✔
1490
#if REALM_HAVE_STD_FILESYSTEM
1491
    std::error_code error;
1492
    bool result = std::filesystem::remove(u8path(path), error);
1493
    throwIfFileError(error, path);
228,192✔
1494
    return result;
190,203✔
1495
#else // POSIX
18,972✔
1496
    if (::unlink(path.c_str()) == 0)
1,653,369✔
1497
        return true;
1,388,457✔
1498

170,430✔
1499
    int err = errno; // Eliminate any risk of clobbering
264,924✔
1500
    if (err == ENOENT)
264,951✔
1501
        return false;
264,825✔
1502

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

300✔
1519

1520
void File::move(const std::string& old_path, const std::string& new_path)
1521
{
2,100✔
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
    }
300✔
1529
    throwIfFileError(error, old_path);
300✔
1530
#else
300✔
1531
    int r = rename(old_path.c_str(), new_path.c_str());
2,100✔
1532
    if (r == 0)
2,100✔
1533
        return;
2,100✔
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

390✔
1553

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

1,632✔
1571
    File origin_file{origin_path, mode_Read};  // Throws
1,716✔
1572
    File target_file;
1,716✔
1573
    bool did_create = false;
1,518✔
1574
    target_file.open(target_path, did_create); // Throws
1,518✔
1575
    if (!did_create && !overwrite_existing) {
1,698✔
1576
        return false;
198✔
1577
    }
198✔
1578

2,004✔
1579
    size_t buffer_size = 4096;
2,088✔
1580
    auto buffer = std::make_unique<char[]>(buffer_size); // Throws
2,088✔
1581
    for (;;) {
4,944✔
1582
        size_t n = origin_file.read(buffer.get(), buffer_size); // Throws
4,566✔
1583
        target_file.write(buffer.get(), n);                     // Throws
4,944✔
1584
        if (n < buffer_size)
4,554✔
1585
            break;
1,710✔
1586
    }
4,566✔
1587

1,626✔
1588
    return true;
1,512✔
1589
#endif
1,512✔
1590
}
1,512✔
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
}
24✔
1612

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

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

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

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

95,826✔
1642
File::UniqueID File::get_unique_id()
95,826✔
1643
{
723,816✔
1644
    REALM_ASSERT_RELEASE(is_attached());
723,804✔
1645
    File::UniqueID uid = File::get_unique_id(m_fd, m_path);
723,804✔
1646
    if (!m_cached_unique_id) {
723,816✔
1647
        m_cached_unique_id = std::make_optional(uid);
627,906✔
1648
    }
627,906✔
1649
    if (m_cached_unique_id != uid) {
627,990✔
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),
95,826✔
1654
                              m_path);
95,826✔
1655
    }
1656
    return uid;
627,990✔
1657
}
724,017✔
1658

96,027✔
1659
FileDesc File::get_descriptor() const
96,027✔
1660
{
629,457✔
1661
    return m_fd;
629,457✔
1662
}
629,685✔
1663

1664
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1665
{
1,596✔
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

228✔
1680
    return get_unique_id(fileHandle, path);
228✔
1681
#else // POSIX version
222✔
1682
    struct stat statbuf;
1,596✔
1683
    if (::stat(path.c_str(), &statbuf) == 0) {
1,596✔
1684
        if (statbuf.st_size == 0) {
1,554✔
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.
222✔
1690
            return none;
222✔
1691
        }
6✔
1692
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
1,557✔
1693
    }
1,560✔
1694
    int err = errno; // Eliminate any risk of clobbering
48✔
1695
    // File doesn't exist
21✔
1696
    if (err == ENOENT)
42✔
1697
        return none;
42✔
1698
    throw SystemError(err, format_errno("fstat() failed: %1 for '%2'", err, path));
1699
#endif
1700
}
98,997✔
1701

1702
File::UniqueID File::get_unique_id(FileDesc file, const std::string& debug_path)
1703
{
648,360✔
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

98,997✔
1712
    return ret;
98,997✔
1713
#else // POSIX version
98,997✔
1714
    REALM_ASSERT(file >= 0);
697,074✔
1715
    struct stat statbuf;
697,074✔
1716
    if (::fstat(file, &statbuf) == 0) {
697,074✔
1717
        // On exFAT systems the inode and device are not populated correctly until the file
367,458✔
1718
        // has been allocated some space. The uid can also be reassigned if the file is
367,458✔
1719
        // truncated to zero. This has led to bugs where a unique id returned here was
417,741✔
1720
        // reused by different files. The following check ensures that this is not
318,744✔
1721
        // happening anywhere else in future code.
318,744✔
1722
        if (statbuf.st_size == 0) {
648,360✔
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),
98,997✔
1728
                debug_path);
98,997✔
1729
        }
×
1730
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
648,360✔
1731
    }
648,360✔
1732
    throw std::system_error(errno, std::system_category(), util::format("fstat() failed for '%1'", debug_path));
1733
#endif
1734
}
360✔
1735

360✔
1736
std::string File::get_path() const
360✔
1737
{
2,217✔
1738
    return m_path;
2,217✔
1739
}
228,480✔
1740

1741
std::string File::resolve(const std::string& path, const std::string& base_dir)
1742
{
1,512,807✔
1743
#if REALM_HAVE_STD_FILESYSTEM
226,263✔
1744
    return (u8path(base_dir) / u8path(path)).lexically_normal().u8string();
226,263✔
1745
#else
226,263✔
1746
    char dir_sep = '/';
1,739,070✔
1747
    std::string path_2 = path;
1,739,070✔
1748
    std::string base_dir_2 = base_dir;
1,512,813✔
1749
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
1,739,067✔
1750
    if (is_absolute)
1,512,813✔
1751
        return path_2;
226,299✔
1752
    if (path_2.empty())
1,737,543✔
1753
        path_2 = ".";
62,154✔
1754
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
1,574,877✔
1755
        base_dir_2.push_back(dir_sep);
1,470,531✔
1756
    /*
458,517✔
1757
    // Abbreviate
458,517✔
1758
    for (;;) {
458,517✔
1759
        if (base_dir_2.empty()) {
458,517✔
1760
            if (path_2.empty())
458,517✔
1761
                return "./";
458,517✔
1762
            return path_2;
458,517✔
1763
        }
458,517✔
1764
        if (path_2 == ".") {
458,517✔
1765
            remove_trailing_dir_seps(base_dir_2);
458,517✔
1766
            return base_dir_2;
458,517✔
1767
        }
458,517✔
1768
        if (has_prefix(path_2, "./")) {
458,517✔
1769
            remove_trailing_dir_seps(base_dir_2);
458,517✔
1770
            // drop dot
458,517✔
1771
            // transfer slashes
458,517✔
1772
        }
458,517✔
1773

458,517✔
1774
        if (path_2.size() < 2 || path_2[1] != '.')
458,517✔
1775
            break;
458,517✔
1776
        if (path_2.size())
622,662✔
1777
    }
622,662✔
1778
    */
622,662✔
1779
    return base_dir_2 + path_2;
1,512,765✔
1780
#endif
1,512,765✔
1781
}
1,512,855✔
1782

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

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

96,606✔
1803

96,606✔
1804
void File::set_encryption_key(const char* key)
96,606✔
1805
{
633,702✔
1806
#if REALM_ENABLE_ENCRYPTION
633,702✔
1807
    if (key) {
633,702✔
1808
        auto buffer = std::make_unique<char[]>(64);
2,895✔
1809
        memcpy(buffer.get(), key, 64);
98,649✔
1810
        m_encryption_key = std::move(buffer);
98,649✔
1811
    }
98,649✔
1812
    else {
630,807✔
1813
        m_encryption_key.reset();
630,807✔
1814
    }
630,807✔
1815
#else
1816
    if (key) {
1817
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
96,606✔
1818
    }
1819
#endif
1820
}
752,001✔
1821

118,725✔
1822
const char* File::get_encryption_key() const
118,725✔
1823
{
740,739✔
1824
    return m_encryption_key.get();
740,739✔
1825
}
740,739✔
1826

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

3,219,378✔
1846

3,219,378✔
1847
void File::MapBase::unmap() noexcept
1,744,419✔
1848
{
23,643,438✔
1849
    if (!m_addr)
23,643,438✔
1850
        return;
13,440,072✔
1851
    REALM_ASSERT(m_reservation_size);
10,206,483✔
1852
#if REALM_ENABLE_ENCRYPTION
10,206,483✔
1853
    if (m_encrypted_mapping) {
10,206,483✔
1854
        m_encrypted_mapping = nullptr;
1,494,951✔
1855
        util::remove_encrypted_mapping(m_addr, m_size);
1,494,951✔
1856
    }
1,494,951✔
1857
#endif
11,678,325✔
1858
    ::munmap(m_addr, m_reservation_size);
11,678,325✔
1859
    m_addr = nullptr;
11,678,325✔
1860
    m_size = 0;
10,203,366✔
1861
    m_reservation_size = 0;
10,203,366✔
1862
}
10,203,366✔
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

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

115,239✔
1903
bool File::MapBase::try_extend_to(size_t size) noexcept
1904
{
764,988✔
1905
    if (size > m_reservation_size) {
827,181✔
1906
        return false;
115,239✔
1907
    }
115,239✔
1908
    // return false;
530,793✔
1909
#ifndef _WIN32
880,227✔
1910
    char* extension_start_addr = (char*)m_addr + m_size;
880,227✔
1911
    size_t extension_size = size - m_size;
880,227✔
1912
    size_t extension_start_offset = m_offset + m_size;
765,357✔
1913
#if REALM_ENABLE_ENCRYPTION
765,357✔
1914
    if (m_encrypted_mapping) {
765,357✔
1915
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
2,481✔
1916
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
2,649✔
1917
        if (got_addr == MAP_FAILED)
2,649✔
1918
            return false;
1,524✔
1919
        REALM_ASSERT(got_addr == extension_start_addr);
2,649✔
1920
        util::extend_encrypted_mapping(m_encrypted_mapping, m_addr, m_offset, m_size, size);
2,649✔
1921
        m_size = size;
117,150✔
1922
        return true;
117,150✔
1923
    }
117,150✔
1924
#endif
877,578✔
1925
    try {
877,578✔
1926
        void* got_addr = util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode,
877,494✔
1927
                                          extension_start_offset, nullptr);
877,494✔
1928
        if (got_addr == extension_start_addr) {
877,494✔
1929
            m_size = size;
762,165✔
1930
            return true;
762,165✔
1931
        }
762,165✔
1932
    }
144✔
1933
    catch (...) {
192✔
1934
        return false;
192✔
1935
    }
192✔
1936
#endif
462✔
1937
    return false;
462✔
1938
}
1,366,281✔
1939

1,365,819✔
1940
void File::MapBase::sync()
705,564✔
1941
{
11,135,823✔
1942
    REALM_ASSERT(m_addr);
11,135,823✔
1943

5,032,569✔
1944
    File::sync_map(m_fd, m_addr, m_size);
9,770,004✔
1945
}
11,107,890✔
1946

1,337,886✔
1947
void File::MapBase::flush()
1,337,886✔
1948
{
10,919,193✔
1949
    REALM_ASSERT(m_addr);
9,582,486✔
1950
#if REALM_ENABLE_ENCRYPTION
9,582,486✔
1951
    if (m_encrypted_mapping) {
10,919,193✔
1952
        realm::util::encryption_flush(m_encrypted_mapping);
1,345,275✔
1953
    }
7,389✔
1954
#endif
9,581,307✔
1955
}
9,582,192✔
1956

1957
std::time_t File::last_write_time(const std::string& path)
1958
{
6,201✔
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
885✔
1969
    return system_clock::to_time_t(system_time);
885✔
1970
#else
1971
    struct stat statbuf;
6,201✔
1972
    if (::stat(path.c_str(), &statbuf) != 0) {
7,086✔
1973
        throw SystemError(errno, "stat() failed");
885✔
1974
    }
885✔
1975
    return statbuf.st_mtime;
6,201✔
1976
#endif
6,201✔
1977
}
6,345✔
1978

1979
File::SizeType File::get_free_space(const std::string& path)
1980
{
1,008✔
1981
#if REALM_HAVE_STD_FILESYSTEM
144✔
1982
    return std::filesystem::space(u8path(path)).available;
144✔
1983
#else
1984
    struct statvfs stat;
1,008✔
1985
    if (statvfs(path.c_str(), &stat) != 0) {
1,152✔
1986
        throw SystemError(errno, "statvfs() failed");
144✔
1987
    }
144✔
1988
    return SizeType(stat.f_bavail) * stat.f_bsize;
1,008✔
1989
#endif
1,008✔
1990
}
1,008✔
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)
122,253✔
2018

122,253✔
2019
DirScanner::DirScanner(const std::string& path, bool allow_missing)
122,253✔
2020
{
879,954✔
2021
    m_dirp = opendir(path.c_str());
879,954✔
2022
    if (!m_dirp) {
879,954✔
2023
        int err = errno; // Eliminate any risk of clobbering
46,113✔
2024
        if (allow_missing && err == ENOENT)
46,116✔
2025
            return;
46,116✔
2026

2,147,483,647!
2027
        std::string msg = format_errno("opendir() failed: %1", err);
2,147,483,647✔
2028
        switch (err) {
2,147,483,647!
2029
            case EACCES:
×
2030
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
2031
            case ENOENT:
×
UNCOV
2032
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
UNCOV
2033
            default:
×
2034
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
122,253✔
2035
        }
2,147,483,647✔
2036
    }
2,147,483,647✔
2037
}
995,463✔
2038

122,250✔
2039
DirScanner::~DirScanner() noexcept
115,509✔
2040
{
988,716✔
2041
    if (m_dirp) {
988,716✔
2042
        int r = closedir(m_dirp);
949,341✔
2043
        REALM_ASSERT_RELEASE(r == 0);
827,091✔
2044
    }
827,091✔
2045
}
1,210,158✔
2046

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

898,143✔
2054
    if (!m_dirp)
2,423,211✔
2055
        return false;
159,120✔
2056

875,064✔
2057
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
988,020✔
2058
// in 32-bits.
762,060✔
2059
#if REALM_HAVE_READDIR64
1,097,331✔
2060
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
1,786,482✔
2061
#else
113,004✔
2062
#define REALM_READDIR(...) readdir(__VA_ARGS__)
2,910,204✔
2063
#endif
2,063,262✔
2064

1,323,291✔
2065
    for (;;) {
4,479,480✔
2066
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
4,144,209✔
2067
        DirentPtr dirent;
4,144,209✔
2068
        do {
4,144,209✔
2069
            // readdir() signals both errors and end-of-stream by returning a
1,795,236✔
2070
            // null pointer. To distinguish between end-of-stream and errors,
2,130,507✔
2071
            // the manpage recommends setting errno specifically to 0 before
1,795,236✔
2072
            // calling it...
2,130,507✔
2073
            errno = 0;
4,479,480✔
2074

1,795,236✔
2075
            dirent = REALM_READDIR(m_dirp);
4,479,480✔
2076
        } while (!dirent && errno == EAGAIN);
4,033,566✔
2077

1,569,276✔
2078
        if (!dirent) {
4,033,566✔
2079
            if (errno != 0)
942,051✔
2080
                throw SystemError(errno, "readdir() failed");
445,914✔
2081
            return false; // End of stream
1,272,648✔
2082
        }
1,272,648✔
2083
        const char* name_1 = dirent->d_name;
3,306,411✔
2084
        std::string name_2 = name_1;
3,306,411✔
2085
        if (name_2 != "." && name_2 != "..") {
3,306,411✔
2086
            name = name_2;
1,883,265✔
2087
            return true;
1,767,561✔
2088
        }
1,437,351✔
2089
    }
3,091,515✔
2090
}
2,264,091✔
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