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

realm / realm-core / jorgen.edelbo_402

21 Aug 2024 11:10AM UTC coverage: 91.054% (-0.03%) from 91.085%
jorgen.edelbo_402

Pull #7803

Evergreen

jedelbo
Small fix to Table::typed_write

When writing the realm to a new file from a write transaction,
the Table may be COW so that the top ref is changed. So don't
use the ref that is present in the group when the operation starts.
Pull Request #7803: Feature/string compression

103494 of 181580 branches covered (57.0%)

1929 of 1999 new or added lines in 46 files covered. (96.5%)

695 existing lines in 51 files now uncovered.

220142 of 241772 relevant lines covered (91.05%)

7344461.76 hits per line

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

84.84
/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/encrypted_file_mapping.hpp>
24
#include <realm/util/file_mapper.hpp>
25

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

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

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

51
using namespace realm::util;
52

53
#ifndef _WIN32
54
// All mainstream platforms other than Windows migrated to 64-bit off_t many
55
// years ago. Supporting 32-bit off_t is possible, but not currently implemented.
56
static_assert(sizeof(off_t) == 8 || sizeof(size_t) == 4);
57
#endif
58

59
namespace {
60
bool for_each_helper(const std::string& path, const std::string& dir, realm::util::File::ForEachHandler& handler)
61
{
30✔
62
    using File = realm::util::File;
30✔
63
    realm::util::DirScanner ds{path}; // Throws
30✔
64
    std::string name;
30✔
65
    while (ds.next(name)) {                              // Throws
90✔
66
        std::string subpath = File::resolve(name, path); // Throws
60✔
67
        bool go_on;
60✔
68
        if (File::is_dir(subpath)) {                           // Throws
60✔
69
            std::string subdir = File::resolve(name, dir);     // Throws
24✔
70
            go_on = for_each_helper(subpath, subdir, handler); // Throws
24✔
71
        }
24✔
72
        else {
36✔
73
            go_on = handler(name, dir); // Throws
36✔
74
        }
36✔
75
        if (!go_on)
60✔
76
            return false;
×
77
    }
60✔
78
    return true;
30✔
79
}
30✔
80

81
#ifdef _WIN32
82

83
std::string get_last_error_msg(const char* prefix, DWORD err)
84
{
85
    std::string buffer;
86
    buffer.append(prefix);
87
    size_t offset = buffer.size();
88
    size_t max_msg_size = 1024;
89
    buffer.resize(offset + max_msg_size);
90
    DWORD flags = FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS;
91
    DWORD language_id = MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT);
92
    DWORD size =
93
        FormatMessageA(flags, 0, err, language_id, buffer.data() + offset, static_cast<DWORD>(max_msg_size), 0);
94
    if (0 < size)
95
        return buffer;
96
    buffer.resize(offset);
97
    buffer.append("Unknown error");
98
    return buffer;
99
}
100

101
struct WindowsFileHandleHolder {
102
    WindowsFileHandleHolder() = default;
103
    explicit WindowsFileHandleHolder(HANDLE h)
104
        : handle(h)
105
    {
106
    }
107

108
    WindowsFileHandleHolder(WindowsFileHandleHolder&&) = delete;
109
    WindowsFileHandleHolder(const WindowsFileHandleHolder&) = delete;
110
    WindowsFileHandleHolder& operator=(WindowsFileHandleHolder&&) = delete;
111
    WindowsFileHandleHolder& operator=(const WindowsFileHandleHolder&) = delete;
112

113
    operator HANDLE() const noexcept
114
    {
115
        return handle;
116
    }
117

118
    ~WindowsFileHandleHolder()
119
    {
120
        if (handle != INVALID_HANDLE_VALUE) {
121
            ::CloseHandle(handle);
122
        }
123
    }
124

125
    HANDLE handle = INVALID_HANDLE_VALUE;
126
};
127

128
#endif
129

130
#if REALM_HAVE_STD_FILESYSTEM
131
#if __cplusplus < 202002L
132
using std::filesystem::u8path;
133
inline std::string path_to_string(const std::filesystem::path& path)
134
{
135
    return path.u8string();
136
}
137
#else
138
inline std::string path_to_string(const std::filesystem::path& path)
139
{
140
    return reinterpret_cast<const char*>(path.u8string().c_str());
141
}
142
inline auto u8path(const std::string& str)
143
{
144
    return std::filesystem::path(reinterpret_cast<const char8_t*>(str.c_str()));
145
};
146
#endif
147

148
void throwIfCreateDirectoryError(std::error_code error, const std::string& path)
149
{
150
    if (!error)
151
        return;
152

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

163
void throwIfFileError(std::error_code error, const std::string& path)
164
{
165
    if (!error)
166
        return;
167

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

180
} // anonymous namespace
181

182

183
namespace realm::util {
184
namespace {
185

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

196
} // anonymous namespace
197

198

199
bool try_make_dir(const std::string& path)
200
{
205,026✔
201
#if REALM_HAVE_STD_FILESYSTEM
202
    std::error_code error;
203
    bool result = std::filesystem::create_directory(u8path(path), error);
204
    throwIfCreateDirectoryError(error, path);
205
    return result;
206
#else // POSIX
207
    if (::mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == 0)
205,026✔
208
        return true;
76,572✔
209

210
    int err = errno; // Eliminate any risk of clobbering
128,454✔
211
    if (err == EEXIST)
128,454✔
212
        return false;
128,460✔
213

214
    auto msg = format_errno("Failed to create directory at '%2': %1", err, path);
2,147,483,647✔
215

216
    switch (err) {
2,147,483,647✔
217
        case EACCES:
3✔
218
        case EROFS:
6✔
219
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
220
        default:
✔
221
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
222
    }
2,147,483,647✔
223
#endif
2,147,483,647✔
224
}
2,147,483,647✔
225

226

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

234

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

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

267

268
void remove_dir(const std::string& path)
269
{
10,896✔
270
    if (try_remove_dir(path)) // Throws
10,896✔
271
        return;
10,878✔
272
    int err = ENOENT;
18✔
273
    std::string msg = format_errno("Failed to remove directory '%2': %1", err, path);
18✔
274
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
18✔
275
}
10,896✔
276

277

278
bool try_remove_dir(const std::string& path)
279
{
117,504✔
280
#if REALM_HAVE_STD_FILESYSTEM
281
    std::error_code error;
282
    bool result = std::filesystem::remove(u8path(path), error);
283
    throwIfFileError(error, path);
284
    return result;
285
#else // POSIX
286
    if (::rmdir(path.c_str()) == 0)
117,504✔
287
        return true;
117,219✔
288

289
    int err = errno; // Eliminate any risk of clobbering
285✔
290
    if (err == ENOENT)
285✔
291
        return false;
279✔
292

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

308

309
bool try_remove_dir_recursive(const std::string& path)
310
{
106,578✔
311
#if REALM_HAVE_STD_FILESYSTEM
312
    std::error_code error;
313
    auto removed_count = std::filesystem::remove_all(u8path(path), error);
314
    throwIfFileError(error, path);
315
    return removed_count > 0;
316
#else
317
    {
106,578✔
318
        bool allow_missing = true;
106,578✔
319
        DirScanner ds{path, allow_missing}; // Throws
106,578✔
320
        std::string name;
106,578✔
321
        while (ds.next(name)) {                              // Throws
300,609✔
322
            std::string subpath = File::resolve(name, path); // Throws
194,031✔
323
            if (File::is_dir(subpath)) {                     // Throws
194,031✔
324
                try_remove_dir_recursive(subpath);           // Throws
61,839✔
325
            }
61,839✔
326
            else {
132,192✔
327
                File::remove(subpath); // Throws
132,192✔
328
            }
132,192✔
329
        }
194,031✔
330
    }
106,578✔
331
    return try_remove_dir(path); // Throws
106,578✔
332
#endif
106,578✔
333
}
106,578✔
334

335

336
std::string make_temp_dir()
337
{
44,880✔
338
#ifdef _WIN32 // Windows version
339
    std::filesystem::path temp = std::filesystem::temp_directory_path();
340

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

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

360
#else // POSIX.1-2008 version
361

362
#if REALM_ANDROID
363
    std::string buffer = "/data/local/tmp/realm_XXXXXX";
364
#else
365
    char* tmp_dir_env = getenv("TMPDIR");
44,880✔
366
    std::string buffer = tmp_dir_env ? tmp_dir_env : std::string(P_tmpdir);
44,880✔
367
    if (!buffer.empty() && buffer.back() != '/') {
44,880✔
368
        buffer += "/";
44,880✔
369
    }
44,880✔
370
    buffer += "realm_XXXXXX";
44,880✔
371
#endif
44,880✔
372

373
    if (mkdtemp(buffer.data()) == 0) {
44,880✔
374
        int err = errno;
×
375
        throw SystemError(err, util::format("Failed to create temporary directory: %1", err)); // LCOV_EXCL_LINE
376
    }
×
377
    return buffer;
44,880✔
378
#endif
44,880✔
379
}
44,880✔
380

381
std::string make_temp_file(const char* prefix)
382
{
38,412✔
383
#ifdef _WIN32 // Windows version
384
    std::filesystem::path temp = std::filesystem::temp_directory_path();
385

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

392
    return path_to_string(std::filesystem::path(buffer));
393

394
#else // POSIX.1-2008 version
395

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

415
size_t page_size()
416
{
26,446,056✔
417
    static constexpr size_t c_min_supported_page_size = 4096;
26,446,056✔
418
    static size_t page_size = [] {
26,446,056✔
419
#ifdef _WIN32
420
        SYSTEM_INFO sysinfo;
421
        GetNativeSystemInfo(&sysinfo);
422
        // DWORD size = sysinfo.dwPageSize;
423
        // On windows we use the allocation granularity instead
424
        DWORD size = sysinfo.dwAllocationGranularity;
425
#else
426
        long size = sysconf(_SC_PAGESIZE);
24✔
427
#endif
24✔
428
        REALM_ASSERT(size > 0 && size % c_min_supported_page_size == 0);
24✔
429
        return static_cast<size_t>(size);
24✔
430
    }();
24✔
431
    return page_size;
26,446,056✔
432
}
26,446,056✔
433

434
File::File() = default;
356,940✔
435
File::File(std::string_view path, Mode m)
436
{
2,847✔
437
    open(path, m);
2,847✔
438
}
2,847✔
439

440
File::~File() noexcept
441
{
359,883✔
442
    close();
359,883✔
443
}
359,883✔
444

445
File::File(File&& f) noexcept
446
{
12✔
447
    m_fd = std::exchange(f.m_fd, invalid_fd);
12✔
448
#ifdef REALM_FILELOCK_EMULATION
449
    m_pipe_fd = std::exchange(f.m_pipe_fd, invalid_fd);
450
    m_has_exclusive_lock = std::exchange(f.m_has_exclusive_lock, false);
451
#endif
452
    m_have_lock = std::exchange(f.m_have_lock, false);
12✔
453
    m_encryption = std::move(f.m_encryption);
12✔
454
}
12✔
455

456
File& File::operator=(File&& f) noexcept
457
{
6✔
458
    close();
6✔
459

460
    m_fd = std::exchange(f.m_fd, invalid_fd);
6✔
461
#ifdef REALM_FILELOCK_EMULATION
462
    m_pipe_fd = std::exchange(f.m_pipe_fd, invalid_fd);
463
    m_has_exclusive_lock = std::exchange(f.m_has_exclusive_lock, false);
464
#endif
465
    m_have_lock = std::exchange(f.m_have_lock, false);
6✔
466
    m_encryption = std::move(f.m_encryption);
6✔
467
    return *this;
6✔
468
}
6✔
469

470

471
void File::open_internal(std::string_view path, AccessMode a, CreateMode c, int flags, bool* success)
472
{
396,966✔
473
    REALM_ASSERT_RELEASE(!is_attached());
396,966✔
474
    m_path = path; // for error reporting and debugging
396,966✔
475

476
#ifdef _WIN32 // Windows version
477

478
    DWORD desired_access = GENERIC_READ;
479
    switch (a) {
480
        case access_ReadOnly:
481
            break;
482
        case access_ReadWrite:
483
            if (flags & flag_Append) {
484
                desired_access = FILE_APPEND_DATA;
485
            }
486
            else {
487
                desired_access |= GENERIC_WRITE;
488
            }
489
            break;
490
    }
491
    // FIXME: Should probably be zero if we are called on behalf of a
492
    // Group instance that is not managed by a SharedGroup instance,
493
    // since in this case concurrenct access is prohibited anyway.
494
    DWORD share_mode = FILE_SHARE_READ | FILE_SHARE_WRITE;
495
    DWORD creation_disposition = 0;
496
    switch (c) {
497
        case create_Auto:
498
            creation_disposition = flags & flag_Trunc ? CREATE_ALWAYS : OPEN_ALWAYS;
499
            break;
500
        case create_Never:
501
            creation_disposition = flags & flag_Trunc ? TRUNCATE_EXISTING : OPEN_EXISTING;
502
            break;
503
        case create_Must:
504
            creation_disposition = CREATE_NEW;
505
            break;
506
    }
507
    DWORD flags_and_attributes = 0;
508
    m_fd = CreateFile2(u8path(m_path).c_str(), desired_access, share_mode, creation_disposition, nullptr);
509
    if (m_fd != INVALID_HANDLE_VALUE) {
510
        m_have_lock = false;
511
        if (success)
512
            *success = true;
513
        return;
514
    }
515

516
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
517
    if (success && err == ERROR_FILE_EXISTS && c == create_Must) {
518
        *success = false;
519
        return;
520
    }
521
    if (success && err == ERROR_FILE_NOT_FOUND && c == create_Never) {
522
        *success = false;
523
        return;
524
    }
525
    std::string msg = get_last_error_msg("CreateFile() failed: ", err);
526
    switch (err) {
527
        case ERROR_SHARING_VIOLATION:
528
        case ERROR_ACCESS_DENIED:
529
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, m_path, int(err));
530
        case ERROR_FILE_NOT_FOUND:
531
        case ERROR_PATH_NOT_FOUND:
532
            throw FileAccessError(ErrorCodes::FileNotFound, msg, m_path, int(err));
533
        case ERROR_FILE_EXISTS:
534
            throw Exists(msg, m_path);
535
        default:
536
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, m_path, int(err));
537
    }
538

539
#else // POSIX version
540

541
    int flags2 = 0;
396,966✔
542
    switch (a) {
396,966✔
543
        case access_ReadOnly:
2,991✔
544
            flags2 = O_RDONLY;
2,991✔
545
            break;
2,991✔
546
        case access_ReadWrite:
394,026✔
547
            flags2 = O_RDWR;
394,026✔
548
            break;
394,026✔
549
    }
396,966✔
550
    switch (c) {
397,011✔
551
        case create_Auto:
365,571✔
552
            flags2 |= O_CREAT;
365,571✔
553
            break;
365,571✔
554
        case create_Never:
30,582✔
555
            break;
30,582✔
556
        case create_Must:
870✔
557
            flags2 |= O_CREAT | O_EXCL;
870✔
558
            break;
870✔
559
    }
397,011✔
560
    if (flags & flag_Trunc)
397,017✔
561
        flags2 |= O_TRUNC;
654✔
562
    if (flags & flag_Append)
397,017✔
563
        flags2 |= O_APPEND;
151,743✔
564
    int fd = ::open(m_path.c_str(), flags2, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
397,017✔
565
    if (0 <= fd) {
397,017✔
566
        m_fd = fd;
396,933✔
567
        m_have_lock = false;
396,933✔
568
        if (success)
396,933✔
569
            *success = true;
204✔
570
        return;
396,933✔
571
    }
396,933✔
572

573
    int err = errno; // Eliminate any risk of clobbering
84✔
574
    if (success && err == EEXIST && c == create_Must) {
84✔
575
        *success = false;
30✔
576
        return;
30✔
577
    }
30✔
578
    if (success && err == ENOENT && c == create_Never) {
54!
579
        *success = false;
×
580
        return;
×
581
    }
×
582
    std::string msg = format_errno("Failed to open file at path '%2': %1", err, path);
54✔
583
    switch (err) {
54✔
584
        case EACCES:
6✔
585
        case EPERM:
6✔
586
        case EROFS:
6✔
587
        case ETXTBSY:
6✔
588
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
6✔
589
        case ENOENT:
30✔
590
            if (c != create_Never)
30✔
591
                msg = util::format("Failed to open file at path '%1': parent directory does not exist", path);
6✔
592
            throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
30✔
593
        case EEXIST:
36✔
594
            throw Exists(msg, m_path);
36✔
595
        case ENOTDIR:
✔
596
            msg = format("Failed to open file at path '%1': parent path is not a directory", path);
×
597
            [[fallthrough]];
×
598
        default:
36✔
599
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err); // LCOV_EXCL_LINE
600
    }
54✔
601

602
#endif
54✔
603
}
54✔
604

605

606
void File::close() noexcept
607
{
753,054✔
608
    m_encryption.reset();
753,054✔
609
    if (m_fd == invalid_fd)
753,054✔
610
        return;
356,118✔
611
    if (m_have_lock)
396,936✔
612
        unlock();
159✔
613
#ifdef _WIN32 // Windows version
614
    BOOL r = CloseHandle(m_fd);
615
    REALM_ASSERT_RELEASE(r);
616
#else // POSIX version
617
    int r = ::close(m_fd);
396,936✔
618
    REALM_ASSERT_RELEASE(r == 0);
396,936✔
619
#endif
396,936✔
620

621
    m_fd = invalid_fd;
396,936✔
622
}
396,936✔
623

624
size_t File::read_static(FileDesc fd, SizeType pos, char* data, size_t size)
625
{
473,991✔
626
#ifdef _WIN32 // Windows version
627
    char* const data_0 = data;
628
    while (0 < size) {
629
        DWORD n = std::numeric_limits<DWORD>::max();
630
        if (int_less_than(size, n))
631
            n = static_cast<DWORD>(size);
632
        DWORD r = 0;
633
        OVERLAPPED o{};
634
        o.Offset = static_cast<DWORD>(pos);
635
        o.OffsetHigh = static_cast<DWORD>(pos >> 32);
636
        if (!ReadFile(fd, data, n, &r, &o)) {
637
            DWORD err = GetLastError();
638
            if (err == ERROR_HANDLE_EOF)
639
                break;
640
            throw SystemError(int(err), "ReadFile() failed");
641
        }
642
        REALM_ASSERT_RELEASE(r > 0 && r <= n);
643
        size -= size_t(r);
644
        data += size_t(r);
645
        pos += r;
646
    }
647
    return data - data_0;
648

649
#else // POSIX version
650

651
    char* const data_0 = data;
473,991✔
652
    while (0 < size) {
947,769✔
653
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
654
        size_t n = std::min(size, size_t(SSIZE_MAX));
474,096✔
655
        ssize_t r = pread(fd, data, n, pos);
474,096✔
656
        if (r == 0)
474,096✔
657
            break;
318✔
658
        if (r < 0)
473,778✔
659
            goto error; // LCOV_EXCL_LINE
660
        REALM_ASSERT_RELEASE(size_t(r) <= n);
473,778✔
661
        size -= size_t(r);
473,778✔
662
        data += size_t(r);
473,778✔
663
        pos += r;
473,778✔
664
    }
473,778✔
665
    return data - data_0;
473,991✔
666

667
error:
×
668
    // LCOV_EXCL_START
669
    throw SystemError(errno, "read() failed");
×
670
// LCOV_EXCL_STOP
671
#endif
473,991✔
672
}
473,991✔
673

674

675
size_t File::read(SizeType pos, char* data, size_t size)
676
{
7,035✔
677
    REALM_ASSERT_RELEASE(is_attached());
7,035✔
678

679
    if (m_encryption) {
7,035✔
680
        Map<char> read_map(*this, pos, access_ReadOnly, size);
×
681
        util::encryption_read_barrier(read_map, 0, size);
×
682
        memcpy(data, read_map.get_addr(), size);
×
683
        return size;
×
684
    }
×
685

686
    return read_static(m_fd, pos, data, size);
7,035✔
687
}
7,035✔
688

689
void File::write_static(FileDesc fd, SizeType pos, const char* data, size_t size)
690
{
423,687✔
691
#ifdef _WIN32
692
    while (0 < size) {
693
        DWORD n = std::numeric_limits<DWORD>::max();
694
        if (int_less_than(size, n))
695
            n = static_cast<DWORD>(size);
696
        DWORD r = 0;
697
        OVERLAPPED o{};
698
        o.Offset = static_cast<DWORD>(pos);
699
        o.OffsetHigh = static_cast<DWORD>(pos >> 32);
700
        if (!WriteFile(fd, data, n, &r, &o))
701
            goto error;
702
        REALM_ASSERT_RELEASE(r == n); // Partial writes are not possible.
703
        size -= size_t(r);
704
        data += size_t(r);
705
        pos += r;
706
    }
707
    return;
708

709
error:
710
    DWORD err = GetLastError(); // Eliminate any risk of clobbering
711
    if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
712
        std::string msg = get_last_error_msg("WriteFile() failed: ", err);
713
        throw OutOfDiskSpace(msg);
714
    }
715
    throw SystemError(err, "WriteFile() failed");
716
#else
717
    while (0 < size) {
847,257✔
718
        // POSIX requires that 'n' is less than or equal to SSIZE_MAX
719
        size_t n = std::min(size, size_t(SSIZE_MAX));
423,570✔
720
        ssize_t r = pwrite(fd, data, n, pos);
423,570✔
721
        if (r < 0)
423,570✔
722
            goto error; // LCOV_EXCL_LINE
723
        REALM_ASSERT_RELEASE(r != 0);
423,570✔
724
        REALM_ASSERT_RELEASE(size_t(r) <= n);
423,570✔
725
        size -= size_t(r);
423,570✔
726
        data += size_t(r);
423,570✔
727
        pos += off_t(r);
423,570✔
728
    }
423,570✔
729
    return;
423,687✔
730

731
error:
423,687✔
732
    // LCOV_EXCL_START
733
    int err = errno; // Eliminate any risk of clobbering
×
734
    auto msg = format_errno("write() failed: %1", err);
×
735
    if (err == ENOSPC || err == EDQUOT) {
×
736
        throw OutOfDiskSpace(msg);
×
737
    }
×
738
    throw SystemError(err, msg);
×
739
    // LCOV_EXCL_STOP
740

741
#endif
×
742
}
×
743

744
void File::write(SizeType pos, const char* data, size_t size)
745
{
52,341✔
746
    REALM_ASSERT_RELEASE(is_attached());
52,341✔
747

748
    if (m_encryption) {
52,341✔
749
        Map<char> write_map(*this, pos, access_ReadWrite, size);
651✔
750
        util::encryption_read_barrier(write_map, 0, size);
651✔
751
        memcpy(write_map.get_addr(), data, size);
651✔
752
        realm::util::encryption_write_barrier(write_map, 0, size);
651✔
753
        return;
651✔
754
    }
651✔
755

756
    write_static(m_fd, pos, data, size);
51,690✔
757
}
51,690✔
758

759
File::SizeType File::get_file_pos()
760
{
4,650✔
761
#ifdef _WIN32
762
    LONG high_dword = 0;
763
    LARGE_INTEGER li;
764
    LARGE_INTEGER res;
765
    li.QuadPart = 0;
766
    bool ok = SetFilePointerEx(m_fd, li, &res, FILE_CURRENT);
767
    if (!ok)
768
        throw SystemError(GetLastError(), "SetFilePointer() failed");
769

770
    return SizeType(res.QuadPart);
771
#else
772
    auto pos = lseek(m_fd, 0, SEEK_CUR);
4,650✔
773
    if (pos < 0) {
4,650✔
774
        throw SystemError(errno, "lseek() failed");
×
775
    }
×
776
    return SizeType(pos);
4,650✔
777
#endif
4,650✔
778
}
4,650✔
779

780
File::SizeType File::get_size_static(const std::string& path)
781
{
1,353✔
782
    File f(path);
1,353✔
783
    return f.get_size();
1,353✔
784
}
1,353✔
785

786
File::SizeType File::get_size_static(FileDesc fd)
787
{
1,746,909✔
788
#ifdef _WIN32
789
    LARGE_INTEGER large_int;
790
    if (GetFileSizeEx(fd, &large_int)) {
791
        File::SizeType size;
792
        if (int_cast_with_overflow_detect(large_int.QuadPart, size))
793
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
794

795
        return size;
796
    }
797
    throw SystemError(GetLastError(), "GetFileSizeEx() failed");
798

799
#else // POSIX version
800

801
    struct stat statbuf;
1,746,909✔
802
    if (::fstat(fd, &statbuf) == 0) {
1,747,020✔
803
        SizeType size;
1,747,020✔
804
        if (int_cast_with_overflow_detect(statbuf.st_size, size))
1,747,020✔
805
            throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
806

807
        return size;
1,747,020✔
808
    }
1,747,020✔
809
    throw SystemError(errno, "fstat() failed");
4,294,967,294✔
810

811
#endif
1,746,909✔
812
}
1,746,909✔
813

814
File::SizeType File::get_size() const
815
{
1,746,414✔
816
    REALM_ASSERT_RELEASE(is_attached());
1,746,414✔
817
    File::SizeType size = get_size_static(m_fd);
1,746,414✔
818

819
    if (m_encryption) {
1,746,414✔
820
        return encrypted_size_to_data_size(size);
2,577✔
821
    }
2,577✔
822
    return size;
1,743,837✔
823
}
1,746,414✔
824

825

826
void File::resize(SizeType size)
827
{
220,119✔
828
    REALM_ASSERT_RELEASE(is_attached());
220,119✔
829

830
    if (m_encryption)
220,119✔
831
        size = data_size_to_encrypted_size(size);
150✔
832

833
#ifdef _WIN32 // Windows version
834
    FILE_END_OF_FILE_INFO info;
835
    info.EndOfFile.QuadPart = size;
836
    if (!SetFileInformationByHandle(m_fd, FileEndOfFileInfo, &info, sizeof(info))) {
837
        DWORD err = GetLastError(); // Eliminate any risk of clobbering
838
        if (err == ERROR_HANDLE_DISK_FULL || err == ERROR_DISK_FULL) {
839
            throw OutOfDiskSpace(get_last_error_msg("SetFileInformationByHandle() failed: ", err));
840
        }
841
        throw SystemError(int(err), "SetFileInformationByHandle() failed");
842
    }
843
#else // POSIX version
844

845
    off_t size2;
220,119✔
846
    if (int_cast_with_overflow_detect(size, size2))
220,119✔
847
        throw RuntimeError(ErrorCodes::RangeError, "File size overflow");
×
848

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

860
#endif
220,119✔
861
}
220,119✔
862

863

864
void File::prealloc(SizeType size)
865
{
150,849✔
866
    REALM_ASSERT_RELEASE(is_attached());
150,849✔
867
    if (size <= get_size()) {
150,849✔
868
        return;
40,179✔
869
    }
40,179✔
870

871
    SizeType new_size = size;
110,670✔
872
    if (m_encryption) {
110,670✔
873
        new_size = data_size_to_encrypted_size(size);
255✔
874
        REALM_ASSERT(new_size > size);
255✔
875
        REALM_ASSERT(size == encrypted_size_to_data_size(new_size));
255✔
876
    }
255✔
877

878
    auto manually_consume_space = [&]() {
110,670✔
879
        constexpr uint16_t chunk_size = 4096;
×
880
        SizeType write_pos = get_size_static(m_fd); // raw size
×
881
        SizeType num_bytes = new_size - write_pos;
×
882
        std::string zeros(chunk_size, '\0');
×
883
        while (num_bytes > 0) {
×
884
            uint16_t t = uint16_t(std::min<SizeType>(num_bytes, chunk_size));
×
885
            write_static(m_fd, write_pos, zeros.c_str(), t);
×
886
            num_bytes -= t;
×
887
            write_pos += t;
×
888
        }
×
889
    };
×
890

891
#if REALM_HAVE_POSIX_FALLOCATE
62,163✔
892
    // Mostly Linux only
893
    if (!prealloc_if_supported(0, new_size)) {
62,163✔
894
        manually_consume_space();
895
    }
896
#elif REALM_PLATFORM_APPLE // Non-atomic fallback
897
    // posix_fallocate() is not supported on MacOS or iOS, so use a combination of fcntl(F_PREALLOCATE) and
898
    // ftruncate().
899

900
    struct stat statbuf;
48,507✔
901
    if (::fstat(m_fd, &statbuf) != 0) {
48,507✔
902
        int err = errno;
903
        throw SystemError(err, "fstat() inside prealloc() failed");
904
    }
905

906
    SizeType allocated_size;
48,507✔
907
    if (int_cast_with_overflow_detect(statbuf.st_blocks, allocated_size)) {
48,507✔
908
        throw RuntimeError(ErrorCodes::RangeError,
909
                           util::format("Overflow on block conversion to SizeType %1", statbuf.st_blocks));
910
    }
911
    if (int_multiply_with_overflow_detect(allocated_size, S_BLKSIZE)) {
48,507✔
912
        throw RuntimeError(ErrorCodes::RangeError,
913
                           util::format("Overflow computing existing file space allocation blocks: %1 block size %2",
914
                                        allocated_size, S_BLKSIZE));
915
    }
916

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

943
    int ret = 0;
48,507✔
944

945
    do {
48,507✔
946
        ret = ftruncate(m_fd, new_size);
48,507✔
947
    } while (ret == -1 && errno == EINTR);
48,507!
948

949
    if (ret != 0) {
48,507✔
950
        int err = errno;
951
        // by the definition of F_PREALLOCATE, a proceeding ftruncate will not fail due to out of disk space
952
        // so this is some other runtime error and not OutOfDiskSpace
953
        throw SystemError(err, "ftruncate() inside prealloc() failed");
954
    }
955
#elif REALM_ANDROID || defined(_WIN32) || defined(__EMSCRIPTEN__)
956
    manually_consume_space();
957
#else
958
#error Please check if/how your OS supports file preallocation
959
#endif // REALM_HAVE_POSIX_FALLOCATE
960
}
110,670✔
961

962

963
bool File::prealloc_if_supported(SizeType offset, SizeType size)
964
{
62,163✔
965
    REALM_ASSERT_RELEASE(is_attached());
62,163✔
966

967
#if REALM_HAVE_POSIX_FALLOCATE
62,163✔
968

969
    REALM_ASSERT_RELEASE(is_prealloc_supported());
62,163✔
970

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

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

984
    if (REALM_LIKELY(status == 0)) {
62,163✔
985
        return true;
62,163✔
986
    }
62,163✔
987

988
    if (status == EINVAL || status == EPERM || status == EOPNOTSUPP) {
×
989
        return false; // Retry with non-atomic version
990
    }
991

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

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

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

1006
#else
1007

1008
    static_cast<void>(offset);
1009
    static_cast<void>(size);
1010

1011
    REALM_ASSERT_RELEASE(!is_prealloc_supported());
×
1012

1013
#endif
1014
    return false;
×
UNCOV
1015
}
×
1016

1017

1018
bool File::is_prealloc_supported()
1019
{
62,163✔
1020
#if REALM_HAVE_POSIX_FALLOCATE
62,163✔
1021
    return true;
62,163✔
1022
#else
1023
    return false;
1024
#endif
1025
}
62,163✔
1026

1027
void File::seek(SizeType position)
1028
{
4,656✔
1029
    REALM_ASSERT_RELEASE(is_attached());
4,656✔
1030
    seek_static(m_fd, position);
4,656✔
1031
}
4,656✔
1032

1033
void File::seek_static(FileDesc fd, SizeType position)
1034
{
4,656✔
1035
#ifdef _WIN32 // Windows version
1036

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

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

1044
#else // POSIX version
1045

1046
    off_t position2;
4,656✔
1047
    if (int_cast_with_overflow_detect(position, position2))
4,656✔
1048
        throw RuntimeError(ErrorCodes::RangeError, "File position overflow");
×
1049

1050
    if (0 <= ::lseek(fd, position2, SEEK_SET))
4,656✔
1051
        return;
4,656✔
1052
    throw SystemError(errno, "lseek() failed");
×
1053

1054
#endif
4,656✔
1055
}
4,656✔
1056

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

1065
#if defined _WIN32 // Windows version
1066

1067
    if (FlushFileBuffers(m_fd))
1068
        return;
1069
    throw SystemError(GetLastError(), "FlushFileBuffers() failed");
1070

1071
#elif REALM_PLATFORM_APPLE
1072

1073
    if (::fcntl(m_fd, F_FULLFSYNC) == 0)
303✔
1074
        return;
303✔
1075
    throw SystemError(errno, "fcntl() with F_FULLSYNC failed");
1076

1077
#else // POSIX version
1078

1079
    if (::fsync(m_fd) == 0)
477✔
1080
        return;
477✔
1081
    throw SystemError(errno, "fsync() failed");
1082

1083
#endif
477✔
1084
}
780✔
1085

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

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

1111
bool File::rw_lock(bool exclusive, bool non_blocking)
1112
{
371,628✔
1113
    // exclusive blocking rw locks not implemented for emulation
1114
    REALM_ASSERT(!exclusive || non_blocking);
371,628✔
1115

1116
#ifndef REALM_FILELOCK_EMULATION
371,628✔
1117
    return lock(exclusive, non_blocking);
371,628✔
1118
#else
1119
    REALM_ASSERT(!m_has_exclusive_lock && !has_shared_lock());
1120

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

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

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

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

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

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

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

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

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

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

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

1209
bool File::lock(bool exclusive, bool non_blocking)
1210
{
4,198,935✔
1211
    REALM_ASSERT_RELEASE(is_attached());
4,198,935✔
1212
    REALM_ASSERT_RELEASE(!m_have_lock);
4,198,935✔
1213

1214
#ifdef _WIN32 // Windows version
1215

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

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

1271
void File::unlock() noexcept
1272
{
4,051,476✔
1273
    if (!m_have_lock)
4,051,476✔
1274
        return;
×
1275

1276
#ifdef _WIN32 // Windows version
1277
    OVERLAPPED overlapped;
1278
    overlapped.hEvent = 0;
1279
    overlapped.OffsetHigh = 0;
1280
    overlapped.Offset = 0;
1281
    overlapped.Pointer = 0;
1282
    BOOL r = UnlockFileEx(m_fd, 0, 1, 0, &overlapped);
1283
    REALM_ASSERT_RELEASE(r);
1284
#else
1285
    _unlock(m_fd);
4,051,476✔
1286
#endif
4,051,476✔
1287
    m_have_lock = false;
4,051,476✔
1288
}
4,051,476✔
1289

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

1318
bool File::exists(const std::string& path)
1319
{
599,982✔
1320
#if REALM_HAVE_STD_FILESYSTEM
1321
    return std::filesystem::exists(u8path(path));
1322
#else // POSIX
1323
    if (::access(path.c_str(), F_OK) == 0)
599,982✔
1324
        return true;
31,236✔
1325
    int err = errno; // Eliminate any risk of clobbering
568,746✔
1326
    switch (err) {
568,746✔
1327
        case EACCES:
✔
1328
        case ENOENT:
568,725✔
1329
        case ENOTDIR:
568,725✔
1330
            return false;
568,725✔
1331
    }
568,746✔
1332
    throw SystemError(err, "access() failed");
24✔
1333
#endif
568,746✔
1334
}
568,746✔
1335

1336

1337
bool File::is_dir(const std::string& path)
1338
{
234,897✔
1339
#if REALM_HAVE_STD_FILESYSTEM
1340
    return std::filesystem::is_directory(u8path(path));
1341
#elif !defined(_WIN32)
1342
    struct stat statbuf;
1343
    if (::stat(path.c_str(), &statbuf) == 0)
234,897✔
1344
        return S_ISDIR(statbuf.st_mode);
227,787✔
1345
    int err = errno; // Eliminate any risk of clobbering
7,110✔
1346
    switch (err) {
7,110✔
1347
        case EACCES:
✔
1348
        case ENOENT:
7,110✔
1349
        case ENOTDIR:
7,110✔
1350
            return false;
7,110✔
1351
    }
7,110✔
1352
    throw SystemError(err, "stat() failed");
×
1353
#else
1354
    static_cast<void>(path);
1355
    throw NotImplemented();
1356
#endif
1357
}
7,110✔
1358

1359

1360
void File::remove(const std::string& path)
1361
{
176,538✔
1362
    if (try_remove(path))
176,538✔
1363
        return;
176,523✔
1364
    int err = ENOENT;
15✔
1365
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
15✔
1366
    throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
15✔
1367
}
176,538✔
1368

1369

1370
bool File::try_remove(const std::string& path)
1371
{
239,172✔
1372
#if REALM_HAVE_STD_FILESYSTEM
1373
    std::error_code error;
1374
    bool result = std::filesystem::remove(u8path(path), error);
1375
    throwIfFileError(error, path);
1376
    return result;
1377
#else // POSIX
1378
    if (::unlink(path.c_str()) == 0)
239,172✔
1379
        return true;
198,975✔
1380

1381
    int err = errno; // Eliminate any risk of clobbering
40,197✔
1382
    if (err == ENOENT)
40,197✔
1383
        return false;
40,152✔
1384

1385
    std::string msg = format_errno("Failed to delete file at '%2': %1", err, path);
45✔
1386
    switch (err) {
45✔
1387
        case EACCES:
✔
1388
        case EROFS:
✔
1389
        case ETXTBSY:
✔
1390
        case EBUSY:
✔
1391
        case EPERM:
21✔
1392
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
21✔
1393
        case ENOENT:
✔
1394
            return false;
×
1395
        default:
24✔
1396
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
24✔
1397
    }
45✔
1398
#endif
45✔
1399
}
45✔
1400

1401

1402
void File::move(const std::string& old_path, const std::string& new_path)
1403
{
318✔
1404
#if REALM_HAVE_STD_FILESYSTEM
1405
    std::error_code error;
1406
    std::filesystem::rename(u8path(old_path), u8path(new_path), error);
1407

1408
    if (error == std::errc::no_such_file_or_directory) {
1409
        throw FileAccessError(ErrorCodes::FileNotFound, error.message(), old_path);
1410
    }
1411
    throwIfFileError(error, old_path);
1412
#else
1413
    int r = rename(old_path.c_str(), new_path.c_str());
318✔
1414
    if (r == 0)
318✔
1415
        return;
318✔
1416
    int err = errno; // Eliminate any risk of clobbering
×
1417
    std::string msg = format_errno("Failed to rename file from '%2' to '%3': %1", err, old_path, new_path);
×
1418
    switch (err) {
×
1419
        case EACCES:
×
1420
        case EROFS:
×
1421
        case ETXTBSY:
×
1422
        case EBUSY:
×
1423
        case EPERM:
×
1424
        case EEXIST:
×
1425
        case ENOTEMPTY:
×
1426
            throw FileAccessError(ErrorCodes::PermissionDenied, msg, old_path, err);
×
1427
        case ENOENT:
×
1428
            throw FileAccessError(ErrorCodes::FileNotFound, msg, old_path, err);
×
1429
        default:
×
1430
            throw FileAccessError(ErrorCodes::FileOperationFailed, msg, old_path, err);
×
1431
    }
×
1432
#endif
×
1433
}
×
1434

1435

1436
bool File::copy(const std::string& origin_path, const std::string& target_path, bool overwrite_existing)
1437
{
390✔
1438
#if REALM_HAVE_STD_FILESYSTEM
1439
    auto options = overwrite_existing ? std::filesystem::copy_options::overwrite_existing
1440
                                      : std::filesystem::copy_options::skip_existing;
1441
    return std::filesystem::copy_file(u8path(origin_path), u8path(target_path), options); // Throws
1442
#else
1443
#if REALM_PLATFORM_APPLE
198✔
1444
    // Try to use clonefile and fall back to manual copying if it fails
1445
    if (clonefile(origin_path.c_str(), target_path.c_str(), 0) == 0) {
198✔
1446
        return true;
180✔
1447
    }
180✔
1448
    if (errno == EEXIST && !overwrite_existing) {
18✔
1449
        return false;
6✔
1450
    }
6✔
1451
#endif
12✔
1452

1453
    File origin_file{origin_path, mode_Read}; // Throws
204✔
1454
    File target_file;
204✔
1455
    bool did_create = false;
204✔
1456
    target_file.open(target_path, did_create); // Throws
204✔
1457
    if (!did_create && !overwrite_existing) {
204✔
1458
        return false;
6✔
1459
    }
6✔
1460

1461
    size_t buffer_size = 4096;
198✔
1462
    off_t pos = 0;
198✔
1463
    auto buffer = std::make_unique<char[]>(buffer_size); // Throws
198✔
1464
    for (;;) {
579✔
1465
        size_t n = origin_file.read(pos, buffer.get(), buffer_size); // Throws
579✔
1466
        target_file.write(pos, buffer.get(), n);                     // Throws
579✔
1467
        pos += n;
579✔
1468
        if (n < buffer_size)
579✔
1469
            break;
198✔
1470
    }
579✔
1471

1472
    return true;
198✔
1473
#endif
204✔
1474
}
204✔
1475

1476

1477
bool File::is_same_file_static(FileDesc f1, FileDesc f2, const std::string& path1, const std::string& path2)
1478
{
24✔
1479
    return get_unique_id(f1, path1) == get_unique_id(f2, path2);
24✔
1480
}
24✔
1481

1482
bool File::is_same_file(const File& f) const
1483
{
24✔
1484
    REALM_ASSERT_RELEASE(is_attached());
24✔
1485
    REALM_ASSERT_RELEASE(f.is_attached());
24✔
1486
    return is_same_file_static(m_fd, f.m_fd, m_path, f.m_path);
24✔
1487
}
24✔
1488

1489
FileDesc File::dup_file_desc(FileDesc fd)
1490
{
×
1491
    FileDesc fd_duped;
×
1492
#ifdef _WIN32
1493
    if (!DuplicateHandle(GetCurrentProcess(), fd, GetCurrentProcess(), &fd_duped, 0, FALSE, DUPLICATE_SAME_ACCESS))
1494
        throw std::system_error(GetLastError(), std::system_category(), "DuplicateHandle() failed");
1495
#else
1496
    fd_duped = dup(fd);
×
1497

1498
    if (fd_duped == -1) {
×
1499
        int err = errno; // Eliminate any risk of clobbering
×
1500
        throw std::system_error(err, std::system_category(), "dup() failed");
×
1501
    }
×
1502
#endif // conditonal on _WIN32
×
1503
    return fd_duped;
×
1504
}
×
1505

1506
FileDesc File::get_descriptor() const
1507
{
75,906✔
1508
    return m_fd;
75,906✔
1509
}
75,906✔
1510

1511
std::optional<File::UniqueID> File::get_unique_id(const std::string& path)
1512
{
216✔
1513
#ifdef _WIN32 // Windows version
1514
    // CreateFile2 with creationDisposition OPEN_EXISTING will return a file handle only if the file exists
1515
    // otherwise it will raise ERROR_FILE_NOT_FOUND. This call will never create a new file.
1516
    WindowsFileHandleHolder fileHandle(::CreateFile2(u8path(path).c_str(), FILE_READ_ATTRIBUTES,
1517
                                                     FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE,
1518
                                                     OPEN_EXISTING, nullptr));
1519

1520
    if (fileHandle == INVALID_HANDLE_VALUE) {
1521
        if (GetLastError() == ERROR_FILE_NOT_FOUND) {
1522
            return none;
1523
        }
1524
        throw SystemError(GetLastError(), "CreateFileW failed");
1525
    }
1526

1527
    return get_unique_id(fileHandle, path);
1528
#else // POSIX version
1529
    struct stat statbuf;
216✔
1530
    if (::stat(path.c_str(), &statbuf) == 0) {
216✔
1531
        if (statbuf.st_size == 0) {
216✔
1532
            // On exFAT systems the inode and device are not populated correctly until the file
1533
            // has been allocated some space. The uid can also be reassigned if the file is
1534
            // truncated to zero. This has led to bugs where a unique id returned here was
1535
            // reused by different files. The following check ensures that this is not
1536
            // happening anywhere else in future code.
1537
            return none;
×
1538
        }
×
1539
        return File::UniqueID(statbuf.st_dev, statbuf.st_ino);
216✔
1540
    }
216✔
1541
    int err = errno; // Eliminate any risk of clobbering
×
1542
    // File doesn't exist
1543
    if (err == ENOENT)
×
1544
        return none;
×
1545
    throw SystemError(err, format_errno("fstat() failed: %1 for '%2'", err, path));
×
1546
#endif
×
1547
}
×
1548

1549
File::UniqueID File::get_unique_id(FileDesc file, const std::string& debug_path)
1550
{
48✔
1551
#ifdef _WIN32 // Windows version
1552
    REALM_ASSERT(file != nullptr);
1553
    File::UniqueID ret;
1554
    if (GetFileInformationByHandleEx(file, FileIdInfo, &ret.id_info, sizeof(ret.id_info)) == 0) {
1555
        throw std::system_error(GetLastError(), std::system_category(),
1556
                                util::format("GetFileInformationByHandleEx() failed for '%1'", debug_path));
1557
    }
1558

1559
    return ret;
1560
#else // POSIX version
1561
    REALM_ASSERT(file >= 0);
48✔
1562
    struct stat statbuf;
48✔
1563
    if (::fstat(file, &statbuf) == 0) {
48✔
1564
        // On exFAT systems the inode and device are not populated correctly until the file
1565
        // has been allocated some space. The uid can also be reassigned if the file is
1566
        // truncated to zero. This has led to bugs where a unique id returned here was
1567
        // reused by different files. The following check ensures that this is not
1568
        // happening anywhere else in future code.
1569
        if (statbuf.st_size == 0) {
48✔
1570
            throw FileAccessError(
×
1571
                ErrorCodes::FileOperationFailed,
×
1572
                util::format("Attempt to get unique id on an empty file. This could be due to an external "
×
1573
                             "process modifying Realm files. '%1'",
×
1574
                             debug_path),
×
1575
                debug_path);
×
1576
        }
×
1577
        return UniqueID(statbuf.st_dev, statbuf.st_ino);
48✔
1578
    }
48✔
1579
    throw std::system_error(errno, std::system_category(), util::format("fstat() failed for '%1'", debug_path));
×
1580
#endif
48✔
1581
}
48✔
1582

1583
std::string File::get_path() const
1584
{
30✔
1585
    return m_path;
30✔
1586
}
30✔
1587

1588
std::string File::resolve(const std::string& path, const std::string& base_dir)
1589
{
232,689✔
1590
#if REALM_HAVE_STD_FILESYSTEM
1591
    return path_to_string((u8path(base_dir) / u8path(path)).lexically_normal());
1592
#else
1593
    char dir_sep = '/';
232,689✔
1594
    std::string path_2 = path;
232,689✔
1595
    std::string base_dir_2 = base_dir;
232,689✔
1596
    bool is_absolute = (!path_2.empty() && path_2.front() == dir_sep);
232,689✔
1597
    if (is_absolute)
232,689✔
1598
        return path_2;
6✔
1599
    if (path_2.empty())
232,683✔
1600
        path_2 = ".";
6✔
1601
    if (!base_dir_2.empty() && base_dir_2.back() != dir_sep)
232,683✔
1602
        base_dir_2.push_back(dir_sep);
231,102✔
1603
    /*
1604
    // Abbreviate
1605
    for (;;) {
1606
        if (base_dir_2.empty()) {
1607
            if (path_2.empty())
1608
                return "./";
1609
            return path_2;
1610
        }
1611
        if (path_2 == ".") {
1612
            remove_trailing_dir_seps(base_dir_2);
1613
            return base_dir_2;
1614
        }
1615
        if (has_prefix(path_2, "./")) {
1616
            remove_trailing_dir_seps(base_dir_2);
1617
            // drop dot
1618
            // transfer slashes
1619
        }
1620

1621
        if (path_2.size() < 2 || path_2[1] != '.')
1622
            break;
1623
        if (path_2.size())
1624
    }
1625
    */
1626
    return base_dir_2 + path_2;
232,683✔
1627
#endif
232,689✔
1628
}
232,689✔
1629

1630
std::string File::parent_dir(const std::string& path)
1631
{
90✔
1632
#if REALM_HAVE_STD_FILESYSTEM
1633
    return path_to_string(u8path(path).parent_path()); // Throws
1634
#else
1635
    auto is_sep = [](char c) -> bool {
1,200✔
1636
        return c == '/' || c == '\\';
1,200✔
1637
    };
1,200✔
1638
    auto it = std::find_if(path.rbegin(), path.rend(), is_sep);
90✔
1639
    while (it != path.rend() && is_sep(*it))
192✔
1640
        ++it;
102✔
1641
    return path.substr(0, path.rend() - it);
90✔
1642
#endif
90✔
1643
}
90✔
1644

1645
bool File::for_each(const std::string& dir_path, ForEachHandler handler)
1646
{
6✔
1647
    return for_each_helper(dir_path, "", handler); // Throws
6✔
1648
}
6✔
1649

1650

1651
void File::set_encryption_key(const char* key)
1652
{
76,377✔
1653
#if REALM_ENABLE_ENCRYPTION
76,377✔
1654
    if (key) {
76,377✔
1655
        m_encryption = std::make_unique<util::EncryptedFile>(key, m_fd);
645✔
1656
    }
645✔
1657
    else {
75,732✔
1658
        m_encryption.reset();
75,732✔
1659
    }
75,732✔
1660
#else
1661
    if (key) {
1662
        throw LogicError(ErrorCodes::NotSupported, "Encryption not enabled");
1663
    }
1664
#endif
1665
}
76,377✔
1666

1667
EncryptedFile* File::get_encryption() const noexcept
1668
{
2,895,837✔
1669
#if REALM_ENABLE_ENCRYPTION
2,895,837✔
1670
    return m_encryption.get();
2,895,837✔
1671
#else
1672
    return nullptr;
1673
#endif
1674
}
2,895,837✔
1675

1676
File::MapBase::MapBase() noexcept = default;
1,477,338✔
1677
File::MapBase::~MapBase() noexcept
1678
{
1,629,006✔
1679
    unmap();
1,629,006✔
1680
}
1,629,006✔
1681

1682
File::MapBase::MapBase(MapBase&& other) noexcept
1683
{
151,548✔
1684
    *this = std::move(other);
151,548✔
1685
}
151,548✔
1686

1687
File::MapBase& File::MapBase::operator=(MapBase&& other) noexcept
1688
{
152,226✔
1689
    REALM_ASSERT(this != &other);
152,226✔
1690
    if (m_addr)
152,226✔
1691
        unmap();
×
1692
    m_addr = std::exchange(other.m_addr, nullptr);
152,226✔
1693
    m_size = std::exchange(other.m_size, 0);
152,226✔
1694
    m_access_mode = other.m_access_mode;
152,226✔
1695
    m_reservation_size = std::exchange(other.m_reservation_size, 0);
152,226✔
1696
    m_offset = std::exchange(other.m_offset, 0);
152,226✔
1697
    m_fd = std::exchange(other.m_fd, invalid_fd);
152,226✔
1698
#if REALM_ENABLE_ENCRYPTION
152,226✔
1699
    m_encrypted_mapping = std::move(other.m_encrypted_mapping);
152,226✔
1700
#endif
152,226✔
1701
    return *this;
152,226✔
1702
}
152,226✔
1703

1704
void File::MapBase::map(const File& f, AccessMode a, size_t size, SizeType offset, util::WriteObserver* observer)
1705
{
1,316,961✔
1706
    REALM_ASSERT(!m_addr);
1,316,961✔
1707
#if REALM_ENABLE_ENCRYPTION
1,316,961✔
1708
    m_addr = mmap({f.m_fd, a, f.m_encryption.get()}, size, offset, m_encrypted_mapping);
1,316,961✔
1709
    if (observer && m_encrypted_mapping) {
1,316,961✔
1710
        m_encrypted_mapping->set_observer(observer);
441✔
1711
    }
441✔
1712
#else
1713
    std::unique_ptr<util::EncryptedFileMapping> dummy_encrypted_mapping;
1714
    m_addr = mmap({f.m_fd, a, nullptr}, size, offset, dummy_encrypted_mapping);
1715
    static_cast<void>(observer);
1716
#endif
1717
    m_size = m_reservation_size = size;
1,316,961✔
1718
    m_fd = f.m_fd;
1,316,961✔
1719
    m_offset = offset;
1,316,961✔
1720
    m_access_mode = a;
1,316,961✔
1721
}
1,316,961✔
1722

1723

1724
void File::MapBase::unmap() noexcept
1725
{
2,831,013✔
1726
    if (!m_addr)
2,831,013✔
1727
        return;
1,438,506✔
1728
    REALM_ASSERT(m_reservation_size);
1,392,507✔
1729
#if REALM_ENABLE_ENCRYPTION
1,392,507✔
1730
    m_encrypted_mapping = nullptr;
1,392,507✔
1731
#endif
1,392,507✔
1732
    munmap(m_addr, m_reservation_size);
1,392,507✔
1733
    m_addr = nullptr;
1,392,507✔
1734
    m_size = 0;
1,392,507✔
1735
    m_reservation_size = 0;
1,392,507✔
1736
}
1,392,507✔
1737

1738
bool File::MapBase::try_reserve(const File& file, AccessMode a, size_t size, SizeType offset,
1739
                                util::WriteObserver* observer)
1740
{
75,468✔
1741
#ifdef _WIN32
1742
    static_cast<void>(observer);
1743
    // unsupported for now
1744
    return false;
1745
#else
1746
    void* addr = ::mmap(0, size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
75,468✔
1747
    if (addr == MAP_FAILED)
75,468✔
1748
        return false;
×
1749
    m_addr = addr;
75,468✔
1750
    REALM_ASSERT(m_size == 0);
75,468✔
1751
    m_access_mode = a;
75,468✔
1752
    m_reservation_size = size;
75,468✔
1753
    m_fd = file.get_descriptor();
75,468✔
1754
    m_offset = offset;
75,468✔
1755
#if REALM_ENABLE_ENCRYPTION
75,468✔
1756
    if (file.m_encryption) {
75,468✔
1757
        m_encrypted_mapping = util::reserve_mapping(addr, {m_fd, a, file.m_encryption.get()}, offset);
333✔
1758
        if (observer) {
333✔
1759
            m_encrypted_mapping->set_observer(observer);
327✔
1760
        }
327✔
1761
    }
333✔
1762
#else
1763
    static_cast<void>(observer);
1764
#endif
1765
#endif
75,468✔
1766
    return true;
75,468✔
1767
}
75,468✔
1768

1769
bool File::MapBase::try_extend_to(size_t size) noexcept
1770
{
99,255✔
1771
    if (size > m_reservation_size) {
99,255✔
1772
        return false;
×
1773
    }
×
1774
#ifndef _WIN32
99,255✔
1775
    char* extension_start_addr = (char*)m_addr + m_size;
99,255✔
1776
    size_t extension_size = size - m_size;
99,255✔
1777
    size_t extension_start_offset = static_cast<size_t>(m_offset) + m_size;
99,255✔
1778
#if REALM_ENABLE_ENCRYPTION
99,255✔
1779
    if (m_encrypted_mapping) {
99,255✔
1780
        void* got_addr = ::mmap(extension_start_addr, extension_size, PROT_READ | PROT_WRITE,
381✔
1781
                                MAP_ANON | MAP_PRIVATE | MAP_FIXED, -1, 0);
381✔
1782
        if (got_addr == MAP_FAILED)
381✔
1783
            return false;
×
1784
        REALM_ASSERT(got_addr == extension_start_addr);
381✔
1785
        m_size = size;
381✔
1786
        m_encrypted_mapping->extend_to(m_offset, size);
381✔
1787
        return true;
381✔
1788
    }
381✔
1789
#endif
98,874✔
1790
    try {
98,874✔
1791
        void* got_addr =
98,874✔
1792
            util::mmap_fixed(m_fd, extension_start_addr, extension_size, m_access_mode, extension_start_offset);
98,874✔
1793
        if (got_addr == extension_start_addr) {
98,874✔
1794
            m_size = size;
98,796✔
1795
            return true;
98,796✔
1796
        }
98,796✔
1797
    }
98,874✔
1798
    catch (...) {
98,874✔
1799
        return false;
18✔
1800
    }
18✔
1801
#endif
×
1802
    return false;
60✔
1803
}
98,874✔
1804

1805
void File::MapBase::sync()
1806
{
1,409,718✔
1807
    REALM_ASSERT(m_addr);
1,409,718✔
1808
#if REALM_ENABLE_ENCRYPTION
1,409,718✔
1809
    if (m_encrypted_mapping) {
1,409,718✔
1810
        m_encrypted_mapping->sync();
1,320✔
1811
        return;
1,320✔
1812
    }
1,320✔
1813
#endif
1,408,398✔
1814

1815
    realm::util::msync(m_fd, m_addr, m_size);
1,408,398✔
1816
}
1,408,398✔
1817

1818
void File::MapBase::flush(bool skip_validate)
1819
{
1,567,437✔
1820
    REALM_ASSERT(m_addr);
1,567,437✔
1821
#if REALM_ENABLE_ENCRYPTION
1,567,437✔
1822
    if (m_encrypted_mapping) {
1,567,437✔
1823
        m_encrypted_mapping->flush(skip_validate);
142,740✔
1824
    }
142,740✔
1825
#else
1826
    static_cast<void>(skip_validate);
1827
#endif
1828
}
1,567,437✔
1829

1830
std::time_t File::last_write_time(const std::string& path)
1831
{
792✔
1832
#if REALM_HAVE_STD_FILESYSTEM
1833
    auto time = std::filesystem::last_write_time(u8path(path));
1834

1835
    using namespace std::chrono;
1836
#if __cplusplus >= 202002L
1837
    auto system_time = clock_cast<system_clock>(time);
1838
#else
1839
    auto system_time =
1840
        time_point_cast<system_clock::duration>(time - decltype(time)::clock::now() + system_clock::now());
1841
#endif
1842
    return system_clock::to_time_t(system_time);
1843
#else
1844
    struct stat statbuf;
792✔
1845
    if (::stat(path.c_str(), &statbuf) != 0) {
792✔
1846
        throw SystemError(errno, "stat() failed");
×
1847
    }
×
1848
    return statbuf.st_mtime;
792✔
1849
#endif
792✔
1850
}
792✔
1851

1852
File::SizeType File::get_free_space(const std::string& path)
1853
{
144✔
1854
#if REALM_HAVE_STD_FILESYSTEM
1855
    return std::filesystem::space(u8path(path)).available;
1856
#else
1857
    struct statvfs stat;
144✔
1858
    if (statvfs(path.c_str(), &stat) != 0) {
144✔
1859
        throw SystemError(errno, "statvfs() failed");
×
1860
    }
×
1861
    return SizeType(stat.f_bavail) * stat.f_bsize;
144✔
1862
#endif
144✔
1863
}
144✔
1864

1865
#if REALM_HAVE_STD_FILESYSTEM
1866

1867
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1868
{
1869
    std::error_code ec;
1870
    m_iterator = std::filesystem::directory_iterator(u8path(path), ec);
1871
    if (ec && (ec != std::errc::no_such_file_or_directory || !allow_missing))
1872
        throw std::filesystem::filesystem_error("directory_iterator::directory_iterator", u8path(path), ec);
1873
}
1874

1875
DirScanner::~DirScanner() = default;
1876

1877
bool DirScanner::next(std::string& name)
1878
{
1879
    const std::filesystem::directory_iterator end;
1880
    if (m_iterator == end)
1881
        return false;
1882
    name = path_to_string(m_iterator->path().filename());
1883
    m_iterator++;
1884
    return true;
1885
}
1886

1887
#elif !defined(_WIN32)
1888

1889
DirScanner::DirScanner(const std::string& path, bool allow_missing)
1890
{
127,338✔
1891
    m_dirp = opendir(path.c_str());
127,338✔
1892
    if (!m_dirp) {
127,338✔
1893
        int err = errno; // Eliminate any risk of clobbering
7,353✔
1894
        if (allow_missing && err == ENOENT)
7,353✔
1895
            return;
7,353✔
1896

1897
        std::string msg = format_errno("opendir() failed: %1", err);
×
1898
        switch (err) {
×
1899
            case EACCES:
×
1900
                throw FileAccessError(ErrorCodes::PermissionDenied, msg, path, err);
×
1901
            case ENOENT:
×
1902
                throw FileAccessError(ErrorCodes::FileNotFound, msg, path, err);
×
1903
            default:
×
1904
                throw FileAccessError(ErrorCodes::FileOperationFailed, msg, path, err);
×
1905
        }
×
1906
    }
×
1907
}
127,338✔
1908

1909
DirScanner::~DirScanner() noexcept
1910
{
127,338✔
1911
    if (m_dirp) {
127,338✔
1912
        int r = closedir(m_dirp);
119,985✔
1913
        REALM_ASSERT_RELEASE(r == 0);
119,985✔
1914
    }
119,985✔
1915
}
127,338✔
1916

1917
bool DirScanner::next(std::string& name)
1918
{
350,553✔
1919
#if !defined(__linux__) && !REALM_PLATFORM_APPLE && !REALM_WINDOWS && !REALM_UWP && !REALM_ANDROID &&                \
1920
    !defined(__EMSCRIPTEN__)
1921
#error "readdir() is not known to be thread-safe on this platform"
1922
#endif
1923

1924
    if (!m_dirp)
350,553✔
1925
        return false;
7,353✔
1926

1927
// Use readdir64 if it is available. This is necessary to support filesystems that return dirent fields that don't fit
1928
// in 32-bits.
1929
#if REALM_HAVE_READDIR64
117,201✔
1930
#define REALM_READDIR(...) readdir64(__VA_ARGS__)
234,621✔
1931
#else
1932
#define REALM_READDIR(...) readdir(__VA_ARGS__)
348,549✔
1933
#endif
225,999✔
1934

1935
    for (;;) {
583,170✔
1936
        using DirentPtr = decltype(REALM_READDIR(m_dirp));
583,170✔
1937
        DirentPtr dirent;
583,170✔
1938
        do {
583,170✔
1939
            // readdir() signals both errors and end-of-stream by returning a
1940
            // null pointer. To distinguish between end-of-stream and errors,
1941
            // the manpage recommends setting errno specifically to 0 before
1942
            // calling it...
1943
            errno = 0;
583,170✔
1944

1945
            dirent = REALM_READDIR(m_dirp);
583,170✔
1946
        } while (!dirent && errno == EAGAIN);
583,170✔
1947

1948
        if (!dirent) {
583,170✔
1949
            if (errno != 0)
119,790✔
1950
                throw SystemError(errno, "readdir() failed");
×
1951
            return false; // End of stream
119,790✔
1952
        }
119,790✔
1953
        const char* name_1 = dirent->d_name;
463,380✔
1954
        std::string name_2 = name_1;
463,380✔
1955
        if (name_2 != "." && name_2 != "..") {
463,380✔
1956
            name = name_2;
223,410✔
1957
            return true;
223,410✔
1958
        }
223,410✔
1959
    }
463,380✔
1960
}
343,200✔
1961

1962
#else
1963

1964
DirScanner::DirScanner(const std::string&, bool)
1965
{
1966
    throw NotImplemented();
1967
}
1968

1969
DirScanner::~DirScanner() noexcept {}
1970

1971
bool DirScanner::next(std::string&)
1972
{
1973
    return false;
1974
}
1975

1976
#endif
1977

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

© 2025 Coveralls, Inc