The Pedigree Project 0.1
rawfs-contract-regressions.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted.
6 */
7
8#include "pedigree/kernel/Atomic.h"
9#include "pedigree/kernel/Log.h"
10#include "pedigree/kernel/TargetInfo.h"
11#include "pedigree/kernel/machine/Disk.h"
12#include "pedigree/kernel/utilities/utility.h"
13
14#include "modules/system/rawfs/RawFs.h"
15#include "modules/system/rawfs/RawFsDir.h"
16#include "modules/system/rawfs/RawFsFile.h"
17
18namespace {
19class TrackingDisk final : public Disk {
20 public:
21 static constexpr size_t PageSize = TargetInfo::getPageSize();
22 static constexpr size_t DataSize = (2 * PageSize) + 512;
23 static constexpr size_t PageSlots = 4;
24
25 TrackingDisk()
26 : m_ReadCount(0),
27 m_UnpinCount(0),
28 m_WriteCount(0),
29 m_SyncCount(0),
30 m_WriteSuccess(1),
31 m_SyncSuccess(1),
32 m_DataAllocation(new uint8_t[DataSize + PageSize - 1]),
33 m_Data(reinterpret_cast<uint8_t*>(
34 (reinterpret_cast<uintptr_t>(m_DataAllocation) + PageSize - 1) & ~(PageSize - 1))),
35 m_PageAllocation(new uint8_t[(PageSlots * PageSize) + PageSize - 1]),
36 m_PageData(reinterpret_cast<uint8_t*>(
37 (reinterpret_cast<uintptr_t>(m_PageAllocation) + PageSize - 1) & ~(PageSize - 1))),
38 m_AlignmentPoint(0),
39 m_BalanceError(false) {
40 for (size_t i = 0; i < DataSize; ++i) {
41 m_Data[i] = static_cast<uint8_t>((i * 29) ^ (i >> 2));
42 }
43 ByteSet(m_ReadLocations, 0, sizeof(m_ReadLocations));
44 ByteSet(m_UnpinLocations, 0, sizeof(m_UnpinLocations));
45 ByteSet(m_PageReferences, 0, sizeof(m_PageReferences));
46 ByteSet(m_PageKeys, 0xFF, sizeof(m_PageKeys));
47 }
48
49 ~TrackingDisk() override {
50 delete[] m_PageAllocation;
51 delete[] m_DataAllocation;
52 }
53
54 BufferView read(uint64_t location) override {
55 const uint64_t page = pageLocation(location);
56 if (location >= DataSize || page >= DataSize) {
57 return BufferView();
58 }
59
60 const size_t slot = findPage(page, true);
61 if (slot == PageSlots) {
62 return BufferView();
63 }
64
65 if (m_ReadCount < 8) {
66 m_ReadLocations[m_ReadCount] = location;
67 }
68 ++m_ReadCount;
69 ++m_PageReferences[slot];
70 const size_t validLength = PageSize < (DataSize - page) ? PageSize : (DataSize - page);
71 return BufferView(m_PageData + (slot * PageSize) + (location - page),
72 validLength - (location - page));
73 }
74
75 size_t getSize() const override {
76 return DataSize;
77 }
78
79 bool writeFrom(uint64_t location, const void* buffer, size_t length) override {
80 ++m_WriteCount;
81 if (!m_WriteSuccess || !buffer || location > DataSize || length > DataSize - location)
82 return false;
83 MemoryCopy(m_Data + location, buffer, length);
84 return true;
85 }
86
87 bool syncData() override {
88 ++m_SyncCount;
89 return m_SyncSuccess;
90 }
91
92 size_t getBlockSize() const override {
93 // A readahead extent is not a lifetime guarantee for every page in it.
94 return 65536;
95 }
96
97 bool pin(uint64_t location) override {
98 const size_t slot = findPage(pageLocation(location), false);
99 if (slot == PageSlots) {
100 return false;
101 }
102 ++m_PageReferences[slot];
103 return true;
104 }
105
106 void unpin(uint64_t location) override {
107 if (m_UnpinCount < 8) {
108 m_UnpinLocations[m_UnpinCount] = location;
109 }
110 ++m_UnpinCount;
111
112 const size_t slot = findPage(pageLocation(location), false);
113 if (slot == PageSlots || !m_PageReferences[slot]) {
114 m_BalanceError = true;
115 return;
116 }
117 --m_PageReferences[slot];
118 }
119
120 void align(uint64_t location) override {
121 m_AlignmentPoint = location;
122 }
123
124 bool balanced() const {
125 if (m_BalanceError) {
126 return false;
127 }
128 for (size_t i = 0; i < PageSlots; ++i) {
129 if (m_PageReferences[i]) {
130 return false;
131 }
132 }
133 return true;
134 }
135
136 const uint8_t* data() const {
137 return m_Data;
138 }
139
140 size_t m_ReadCount;
141 size_t m_UnpinCount;
142 Atomic<size_t> m_WriteCount;
143 Atomic<size_t> m_SyncCount;
144 Atomic<size_t> m_WriteSuccess;
145 Atomic<size_t> m_SyncSuccess;
146 uint64_t m_ReadLocations[8];
147 uint64_t m_UnpinLocations[8];
148
149 private:
150 uint64_t pageLocation(uint64_t location) const {
151 const uint64_t alignPoint = m_AlignmentPoint <= location ? m_AlignmentPoint : 0;
152 return location - ((location - alignPoint) % PageSize);
153 }
154
155 size_t findPage(uint64_t page, bool create) {
156 size_t freeSlot = PageSlots;
157 for (size_t i = 0; i < PageSlots; ++i) {
158 if (m_PageKeys[i] == page) {
159 return i;
160 }
161 if (freeSlot == PageSlots && m_PageKeys[i] == ~uint64_t(0)) {
162 freeSlot = i;
163 }
164 }
165
166 if (!create || freeSlot == PageSlots) {
167 return PageSlots;
168 }
169
170 m_PageKeys[freeSlot] = page;
171 uint8_t* destination = m_PageData + (freeSlot * PageSize);
172 ByteSet(destination, 0, PageSize);
173 const size_t validLength = PageSize < (DataSize - page) ? PageSize : (DataSize - page);
174 MemoryCopy(destination, m_Data + page, validLength);
175 return freeSlot;
176 }
177
178 uint8_t* m_DataAllocation;
179 uint8_t* m_Data;
180 uint8_t* m_PageAllocation;
181 uint8_t* m_PageData;
182 uint64_t m_PageKeys[PageSlots];
183 size_t m_PageReferences[PageSlots];
184 uint64_t m_AlignmentPoint;
185 bool m_BalanceError;
186};
187
188bool rawFsNativePageOwnership() {
189 constexpr size_t Start = 512;
190 constexpr size_t TransferSize = TrackingDisk::PageSize;
191
192 TrackingDisk disk;
193 RawFs filesystem;
194 RawFsFile file(String("raw-disk"), &filesystem, nullptr, &disk);
195 uint8_t* destination = new uint8_t[TransferSize];
196
197 const uint64_t bytesRead =
198 file.read(Start, TransferSize, reinterpret_cast<uintptr_t>(destination));
199 const bool bytesMatch = !MemoryCompare(destination, disk.data() + Start, TransferSize);
200 delete[] destination;
201
202 const bool splitAtPage = disk.m_ReadCount == 2 && disk.m_ReadLocations[0] == 0 &&
203 disk.m_ReadLocations[1] == TrackingDisk::PageSize;
204 const bool balanced = disk.m_UnpinCount == 2 && disk.m_UnpinLocations[0] == 0 &&
205 disk.m_UnpinLocations[1] == TrackingDisk::PageSize && disk.balanced();
206
207 const bool passed = bytesRead == TransferSize && bytesMatch && splitAtPage && balanced;
208 if (passed) {
209 NOTICE("HOSTED-WAIT-TEST: PASS rawfs-native-page-ownership");
210 } else {
211 ERROR(
212 "HOSTED-WAIT-TEST: FAIL rawfs-native-page-ownership: "
213 "one raw read crossed an unowned native page or leaked its "
214 "reference");
215 }
216 return passed;
217}
218
219bool rawFsParentAlignmentIsolation() {
220 constexpr size_t Start = TrackingDisk::PageSize;
221 constexpr size_t TransferSize = TrackingDisk::PageSize;
222
223 TrackingDisk disk;
224 disk.align(512);
225 RawFs filesystem;
226 RawFsFile file(String("aligned-raw-disk"), &filesystem, nullptr, &disk);
227 uint8_t* destination = new uint8_t[TransferSize];
228
229 const uint64_t bytesRead =
230 file.read(Start, TransferSize, reinterpret_cast<uintptr_t>(destination));
231 const bool bytesMatch = !MemoryCompare(destination, disk.data() + Start, TransferSize);
232 delete[] destination;
233
234 const bool splitAtParentPage = disk.m_ReadCount == 2 && disk.m_ReadLocations[0] == Start &&
235 disk.m_ReadLocations[1] == (Start + 512);
236 const bool balanced = disk.m_UnpinCount == 2 && disk.m_UnpinLocations[0] == Start &&
237 disk.m_UnpinLocations[1] == (Start + 512) && disk.balanced();
238
239 const bool passed = bytesRead == TransferSize && bytesMatch && splitAtParentPage && balanced;
240 if (passed) {
241 NOTICE("HOSTED-WAIT-TEST: PASS rawfs-parent-alignment-isolation");
242 } else {
243 ERROR(
244 "HOSTED-WAIT-TEST: FAIL rawfs-parent-alignment-isolation: "
245 "a parent alignment point exposed bytes beyond one owned page");
246 }
247 return passed;
248}
249
250bool filePastEofDoesNotRead() {
251 TrackingDisk disk;
252 RawFs filesystem;
253 RawFsFile file(String("raw-disk"), &filesystem, nullptr, &disk);
254 uint8_t destination = 0;
255
256 const uint64_t bytesRead =
257 file.read(TrackingDisk::DataSize + 1, 1, reinterpret_cast<uintptr_t>(&destination));
258 const uintptr_t block = file.readBlock(TrackingDisk::DataSize + 1);
259 const bool pinned = file.pinBlock(TrackingDisk::DataSize + 1);
260 const bool passed =
261 !bytesRead && !block && !pinned && !disk.m_ReadCount && !disk.m_UnpinCount && disk.balanced();
262 if (passed) {
263 NOTICE("HOSTED-WAIT-TEST: PASS file-past-eof-no-read");
264 } else {
265 ERROR(
266 "HOSTED-WAIT-TEST: FAIL file-past-eof-no-read: "
267 "an out-of-range read reached the backing object");
268 }
269 return passed;
270}
271
272bool rawFsTerminalPage() {
273 constexpr uint64_t Start = 2 * TrackingDisk::PageSize;
274 constexpr size_t TransferSize = 512;
275
276 TrackingDisk disk;
277 RawFs filesystem;
278 RawFsFile file(String("raw-disk"), &filesystem, nullptr, &disk);
279 uint8_t destination[TransferSize] = {};
280
281 const uint64_t bytesRead =
282 file.read(Start, TransferSize, reinterpret_cast<uintptr_t>(destination));
283 const bool bytesMatch = !MemoryCompare(destination, disk.data() + Start, TransferSize);
284
285 const uintptr_t cached = file.readBlock(Start);
286 bool zeroFilled = cached != 0;
287 for (size_t i = TransferSize; cached && i < TrackingDisk::PageSize; ++i) {
288 if (reinterpret_cast<const uint8_t*>(cached)[i]) {
289 zeroFilled = false;
290 break;
291 }
292 }
293 if (cached) {
294 file.unpinBlock(Start);
295 }
296
297 const bool passed = bytesRead == TransferSize && bytesMatch && zeroFilled &&
298 disk.m_ReadCount == 1 && disk.m_UnpinCount == 1 && disk.balanced();
299 if (passed) {
300 NOTICE("HOSTED-WAIT-TEST: PASS rawfs-terminal-page");
301 } else {
302 ERROR(
303 "HOSTED-WAIT-TEST: FAIL rawfs-terminal-page: terminal bytes were rejected, exposed past "
304 "EOF, or leaked a backing reference");
305 }
306 return passed;
307}
308
309bool rawFsCheckedWriteback() {
310 TrackingDisk disk;
311 RawFs filesystem;
312 auto* root = static_cast<RawFsDir*>(filesystem.getRoot());
313 auto* directory = new RawFsDir(String("nested"), &filesystem, root);
314 root->addEntry(directory);
315 auto* file = new RawFsFile(String("raw-disk"), &filesystem, directory, &disk);
316 directory->addEntry(file);
317 constexpr uint64_t location = 2 * TrackingDisk::PageSize;
318 uint8_t replacement[512];
319 ByteSet(replacement, 0x6d, sizeof(replacement));
320 disk.m_WriteSuccess = 0;
321 const bool accepted =
322 file->write(location, sizeof(replacement), reinterpret_cast<uintptr_t>(replacement)) ==
323 sizeof(replacement);
324 const bool writeFailed = filesystem.sync() == Filesystem::SyncStatus::IoError;
325 disk.m_WriteSuccess = 1;
326 disk.m_SyncSuccess = 0;
327 const bool flushFailed = filesystem.sync() == Filesystem::SyncStatus::IoError;
328 disk.m_SyncSuccess = 1;
329 const bool recovered = filesystem.sync() == Filesystem::SyncStatus::Success;
330 const bool durable = !MemoryCompare(disk.data() + location, replacement, sizeof(replacement));
331 const bool closed = filesystem.shutdown() == Filesystem::SyncStatus::Success &&
332 filesystem.shutdown() == Filesystem::SyncStatus::Success;
333 const bool passed = accepted && writeFailed && flushFailed && recovered && durable && closed &&
334 disk.m_WriteCount && disk.m_SyncCount && disk.balanced();
335 if (passed)
336 NOTICE("HOSTED-WAIT-TEST: PASS rawfs-checked-writeback");
337 else
338 ERROR("HOSTED-WAIT-TEST: FAIL rawfs-checked-writeback: write/flush failure or retry was lost");
339 return passed;
340}
341} // namespace
342
343EXPORTED_PUBLIC bool runHostedRawFsContractRegressions() {
344 return rawFsNativePageOwnership() && rawFsParentAlignmentIsolation() &&
345 filePastEofDoesNotRead() && rawFsTerminalPage() && rawFsCheckedWriteback();
346}
Definition Disk.h:35
virtual void align(uint64_t location)
Sets the page boundary alignment after a specific location on the disk.
Definition Disk.cc:342
virtual BufferView read(uint64_t location)
Definition Disk.cc:163
virtual size_t getSize() const
Gets the size of the disk.
Definition Disk.cc:344
virtual void unpin(uint64_t location)=0
virtual MUST_USE_RESULT bool pin(uint64_t location)=0
Pins a cache page.
virtual size_t getBlockSize() const
Gets the preferred I/O extent of the disk.
Definition Disk.cc:348
virtual MUST_USE_RESULT bool syncData()
Definition Disk.cc:299
Definition RawFs.h:33
virtual SyncStatus shutdown()
Definition RawFs.cc:54
virtual SyncStatus sync()
Definition RawFs.cc:50
virtual File * getRoot() const
Definition RawFs.cc:58
static constexpr size_t getPageSize() noexcept
Definition TargetInfo.h:40