GCC Code Coverage Report


ernie/
Coverage:
low: ≥ 0%
medium: ≥ 75.0%
high: ≥ 90.0%
Lines:
45 / 50
90.0%
Branches:
51 / 102
50.0%
Functions:
6 / 6
100.0%

util/file.cpp
Line Branch Exec Source
1 // SPDX-License-Identifier: BSD-3-Clause
2 /// @file file.cpp
3 /// @brief Whole-file reads, file lookups, and read-only mapping on POSIX and Windows.
4 #include "ernie/util/file.h"
5
6 #include "ernie/util/text.h"
7
8 #include <fstream>
9 #include <iterator>
10 #include <stdexcept>
11 #include <system_error>
12
13 #ifdef _WIN32
14 #ifndef NOMINMAX
15 #define NOMINMAX
16 #endif
17 #include <windows.h>
18 #else
19 #include <fcntl.h>
20 #include <sys/mman.h>
21 #include <sys/stat.h>
22 #include <unistd.h>
23 #endif
24
25 namespace ernie {
26
27 namespace fs = std::filesystem;
28
29 31 std::string ReadTextFile(const fs::path& path) {
30 31 std::ifstream file(path, std::ios::binary);
31
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 27 times.
31 if (!file) {
32
3/6
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
8 throw std::runtime_error("Cannot read " + path.string());
33 }
34
1/2
✓ Branch 1 taken 27 times.
✗ Branch 2 not taken.
54 return {std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
35 31 }
36
37 13 void WriteTextFile(const fs::path& path, std::string_view text) {
38 13 WriteBinaryFile(path, std::as_bytes(std::span(text)));
39 13 }
40
41 20 void WriteBinaryFile(const fs::path& path, std::span<const std::byte> bytes) {
42
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 if (path.has_parent_path()) {
43
1/2
✓ Branch 2 taken 20 times.
✗ Branch 3 not taken.
40 fs::create_directories(path.parent_path());
44 }
45 20 std::ofstream file(path, std::ios::binary);
46
2/4
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✓ Branch 4 taken 20 times.
20 if (!file.write(reinterpret_cast<const char*>(bytes.data()),
47
1/2
✓ Branch 1 taken 20 times.
✗ Branch 2 not taken.
20 static_cast<std::streamsize>(bytes.size()))) {
48
0/6
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ throw std::runtime_error("Cannot write " + path.string());
49 }
50 20 }
51
52 13 fs::path FindFileAnyCase(const fs::path& directory, const std::string& name) {
53
1/2
✓ Branch 2 taken 13 times.
✗ Branch 3 not taken.
13 const fs::path exact = directory / name;
54
3/4
✓ Branch 1 taken 13 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 7 times.
✓ Branch 4 taken 6 times.
13 if (fs::exists(exact)) {
55
1/2
✓ Branch 1 taken 7 times.
✗ Branch 2 not taken.
7 return exact;
56 }
57
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 const std::string wanted = ToLower(name);
58
1/2
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
6 std::error_code error;
59
11/14
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 5 times.
✓ Branch 4 taken 1 time.
✓ Branch 5 taken 5 times.
✓ Branch 6 taken 1 time.
✓ Branch 7 taken 5 times.
✓ Branch 8 taken 1 time.
✓ Branch 11 taken 49 times.
✗ Branch 12 not taken.
✓ Branch 13 taken 52 times.
✓ Branch 14 taken 3 times.
✗ Branch 15 not taken.
✓ Branch 16 taken 3 times.
76 for (const fs::directory_entry& entry : fs::directory_iterator(directory, error)) {
60
4/8
✓ Branch 1 taken 52 times.
✗ Branch 2 not taken.
✓ Branch 4 taken 52 times.
✗ Branch 5 not taken.
✓ Branch 9 taken 3 times.
✓ Branch 10 taken 49 times.
✗ Branch 11 not taken.
✗ Branch 12 not taken.
156 if (ToLower(entry.path().filename().string()) == wanted) {
61
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 return entry.path();
62 }
63
5/10
✗ Branch 0 not taken.
✓ Branch 1 taken 3 times.
✓ Branch 2 taken 2 times.
✓ Branch 3 taken 1 time.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
12 }
64
1/2
✓ Branch 1 taken 3 times.
✗ Branch 2 not taken.
3 return exact;
65 19 }
66
67 #ifdef _WIN32
68
69 MappedFile::MappedFile(const fs::path& path) {
70 // Others may delete or replace the file while it is mapped, as on POSIX systems: caches evict
71 // and rewrite entries that live textures still map, which keep their views until closed.
72 file_ = CreateFileW(path.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_DELETE, nullptr,
73 OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
74 if (file_ == INVALID_HANDLE_VALUE) {
75 file_ = nullptr;
76 throw std::runtime_error("Cannot open " + path.string());
77 }
78 LARGE_INTEGER size{};
79 GetFileSizeEx(file_, &size);
80 size_ = static_cast<std::size_t>(size.QuadPart);
81 if (size_ == 0) {
82 return;
83 }
84 mapping_ = CreateFileMappingW(file_, nullptr, PAGE_READONLY, 0, 0, nullptr);
85 if (mapping_ != nullptr) {
86 data_ = MapViewOfFile(mapping_, FILE_MAP_READ, 0, 0, 0);
87 }
88 if (data_ == nullptr) {
89 if (mapping_ != nullptr) {
90 CloseHandle(mapping_);
91 }
92 CloseHandle(file_);
93 throw std::runtime_error("Cannot map " + path.string());
94 }
95 }
96
97 MappedFile::~MappedFile() {
98 if (data_ != nullptr) {
99 UnmapViewOfFile(data_);
100 }
101 if (mapping_ != nullptr) {
102 CloseHandle(mapping_);
103 }
104 if (file_ != nullptr) {
105 CloseHandle(file_);
106 }
107 }
108
109 #else
110
111 95 MappedFile::MappedFile(const fs::path& path) {
112 95 const int fd = open(path.c_str(), O_RDONLY | O_CLOEXEC);
113
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 91 times.
95 if (fd < 0) {
114
3/6
✓ Branch 2 taken 4 times.
✗ Branch 3 not taken.
✓ Branch 5 taken 4 times.
✗ Branch 6 not taken.
✓ Branch 8 taken 4 times.
✗ Branch 9 not taken.
8 throw std::runtime_error("Cannot open " + path.string());
115 }
116 91 struct stat info{};
117
1/2
✗ Branch 1 not taken.
✓ Branch 2 taken 91 times.
91 if (fstat(fd, &info) != 0) {
118 ✗ close(fd);
119
0/6
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ throw std::runtime_error("Cannot read " + path.string());
120 }
121 91 size_ = static_cast<std::size_t>(info.st_size);
122
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 1 time.
91 if (size_ > 0) {
123 90 void* data = mmap(nullptr, size_, PROT_READ, MAP_PRIVATE, fd, 0);
124
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 90 times.
90 if (data == MAP_FAILED) {
125 ✗ close(fd);
126
0/6
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 5 not taken.
✗ Branch 6 not taken.
✗ Branch 8 not taken.
✗ Branch 9 not taken.
✗ throw std::runtime_error("Cannot map " + path.string());
127 }
128 90 data_ = data;
129 }
130 // The mapping keeps the file; the descriptor is not needed.
131 91 close(fd);
132 91 }
133
134 91 MappedFile::~MappedFile() {
135
2/2
✓ Branch 0 taken 90 times.
✓ Branch 1 taken 1 time.
91 if (data_ != nullptr) {
136 90 munmap(data_, size_);
137 }
138 91 }
139
140 #endif
141
142 } // namespace ernie
143