8#include "pedigree/kernel/Log.h"
9#include "pedigree/kernel/TargetInfo.h"
10#include "pedigree/kernel/machine/Disk.h"
11#include "pedigree/kernel/processor/PhysicalMemoryManager.h"
12#include "pedigree/kernel/utilities/StringView.h"
13#include "pedigree/kernel/utilities/utility.h"
15#include "modules/system/fat/FatFilesystem.h"
16#include "modules/system/vfs/VFS.h"
19class TrackingDisk final :
public Disk {
22 static constexpr size_t DataSize = 2 * PageSize;
28 m_Data(new uint8_t[DataSize]),
29 m_BalanceError(false) {
30 ByteSet(m_Data, 0xA5, DataSize);
31 ByteSet(m_ReadLocations, 0,
sizeof(m_ReadLocations));
32 ByteSet(m_WriteLocations, 0,
sizeof(m_WriteLocations));
33 ByteSet(m_UnpinLocations, 0,
sizeof(m_UnpinLocations));
34 ByteSet(m_PageReferences, 0,
sizeof(m_PageReferences));
37 ~TrackingDisk()
override {
42 if (location >= DataSize) {
46 if (m_ReadCount < 4) {
47 m_ReadLocations[m_ReadCount] = location;
50 ++m_PageReferences[location / PageSize];
51 return BufferView(m_Data + location, PageSize - (location % PageSize));
54 void write(uint64_t location)
override {
55 if (m_WriteCount < 4) {
56 m_WriteLocations[m_WriteCount] = location;
61 bool sync(uint64_t location,
bool)
override {
62 if (location >= DataSize) {
69 size_t getSize()
const override {
78 bool pin(uint64_t location)
override {
79 if (location >= DataSize) {
82 ++m_PageReferences[location / PageSize];
86 void unpin(uint64_t location)
override {
87 if (m_UnpinCount < 4) {
88 m_UnpinLocations[m_UnpinCount] = location;
92 size_t& references = m_PageReferences[location / PageSize];
94 m_BalanceError =
true;
100 bool balanced()
const {
101 return !m_BalanceError && !m_PageReferences[0] && !m_PageReferences[1];
111 uint64_t m_ReadLocations[4];
112 uint64_t m_WriteLocations[4];
113 uint64_t m_UnpinLocations[4];
117 size_t m_PageReferences[2];
123 void configure(
Disk* disk) {
125 m_Superblock.BPB_BytsPerSec = 512;
128 bool writeSectors(uint32_t sector,
size_t size, uintptr_t source) {
132 void configureRemoval(
Disk* disk, uintptr_t fatTable) {
136 m_ClusterCount = 254;
139 m_Superblock.BPB_BytsPerSec = 512;
140 m_Superblock.BPB_SecPerClus = 1;
141 m_FatCache.insert(0, fatTable);
144 bool removeForTest(
File* parent,
const String& name,
File* file) {
145 return remove(name.view(), parent, file);
151 ShortReadFatFilesystem() : m_ReadCalls(0) {}
153 uint64_t read(
File*, uint64_t, uint64_t size, uintptr_t buffer,
bool =
true)
override {
155 ByteSet(
reinterpret_cast<void*
>(buffer), 0x5A, size);
156 return m_ReadCalls == 1 ? size - 1 : size;
162class RemovalProbeFile final :
public File {
164 RemovalProbeFile(
const String& name,
Filesystem* filesystem,
File* parent,
size_t& destructions)
165 :
File(name, 0, 0, 0, 0, filesystem, 0, parent), m_Destructions(destructions) {}
167 ~RemovalProbeFile()
override {
172 size_t& m_Destructions;
175class RemovalDirectory final :
public Directory {
177 explicit RemovalDirectory(
Filesystem* filesystem)
178 :
Directory(
String(
"removal-root"), 0, 0, 0, 0, filesystem, 0, nullptr) {}
180 void publish(
const String& alias,
File* file) {
184 bool contains(
const String& alias)
const {
188 bool retire(
const String& alias,
File* file) {
193class RemovalFilesystem final :
public Filesystem {
195 RemovalFilesystem(
size_t& destructions,
bool selfRemoving)
199 m_Destructions(destructions),
200 m_SelfRemoving(selfRemoving),
201 m_SecondObservedPriorRetirement(true),
203 m_FirstAlias(
"cache-first"),
204 m_SecondAlias(
"cache-second"),
205 m_Label(
"directory-removal-test") {}
207 void configure(RemovalDirectory* root,
File* first,
File* second) {
227 if (m_RemoveCalls == 2 && m_Destructions != 1) {
228 m_SecondObservedPriorRetirement =
false;
231 return static_cast<RemovalDirectory*
>(parent)->retire(filename, file);
234 bool secondObservedPriorRetirement()
const {
235 return m_SecondObservedPriorRetirement;
238 size_t removeCalls()
const {
239 return m_RemoveCalls;
242 const String& firstAlias()
const {
246 const String& secondAlias()
const {
247 return m_SecondAlias;
264 RemovalDirectory* m_Root;
267 size_t& m_Destructions;
269 bool m_SecondObservedPriorRetirement;
270 size_t m_RemoveCalls;
279 bool clearInodeAfterRemove =
true)
281 m_Alias(
"fat-cache-alias"),
282 m_ClearInodeAfterRemove(clearInodeAfterRemove),
285 void publish(
File* file) {
292 if (m_ClearInodeAfterRemove) {
302 bool containsAlias()
const {
306 const String& alias()
const {
310 size_t removeCalls()
const {
311 return m_RemoveCalls;
316 bool m_ClearInodeAfterRemove;
317 size_t m_RemoveCalls;
320class RetainedFatFile final :
public FatFile {
323 :
FatFile(
String(
"fat-intrinsic-name"), 0, 0, 0, 2, filesystem, 0, 3, 0, parent),
324 m_Destructions(destructions) {}
326 ~RetainedFatFile()
override {
331 size_t& m_Destructions;
334bool fatSectorPageBoundary() {
335 constexpr size_t TransferSize = TrackingDisk::PageSize;
336 uint8_t* source =
new uint8_t[TransferSize];
337 for (
size_t i = 0; i < TransferSize; ++i) {
338 source[i] =
static_cast<uint8_t
>((i * 37) ^ (i >> 3));
342 FatFilesystemHarness filesystem;
343 filesystem.configure(&disk);
345 const bool wrote = filesystem.writeSectors(1, TransferSize,
reinterpret_cast<uintptr_t
>(source));
346 const bool bytesMatch = !MemoryCompare(disk.data() + 512, source, TransferSize);
349 const bool splitAtPage =
350 disk.m_ReadCount == 2 && disk.m_WriteCount == 2 && disk.m_ReadLocations[0] == 512 &&
351 disk.m_ReadLocations[1] == TrackingDisk::PageSize && disk.m_WriteLocations[0] == 512 &&
352 disk.m_WriteLocations[1] == TrackingDisk::PageSize;
353 const bool balanced = disk.m_UnpinCount == 2 && disk.m_UnpinLocations[0] == 512 &&
354 disk.m_UnpinLocations[1] == TrackingDisk::PageSize && disk.balanced();
356 const bool passed = wrote && bytesMatch && splitAtPage && balanced;
358 NOTICE(
"HOSTED-WAIT-TEST: PASS fat-sector-page-boundary");
361 "HOSTED-WAIT-TEST: FAIL fat-sector-page-boundary: "
362 "unaligned transfer crossed an unowned cache page or leaked a "
368bool fatShortReadPublication() {
369 ShortReadFatFilesystem filesystem;
370 FatFile file(
String(
"short-read"), 0, 0, 0, 1, &filesystem, 4096);
372 const uintptr_t failed = file.
readBlock(0);
373 const uintptr_t retried = file.
readBlock(0);
378 const bool passed = !failed && retried && filesystem.m_ReadCalls == 2;
380 NOTICE(
"HOSTED-WAIT-TEST: PASS fat-short-read-publication");
383 "HOSTED-WAIT-TEST: FAIL fat-short-read-publication: "
384 "a short read was published or its failed Editing page blocked "
390bool directoryEmptyRemovalOwnership() {
391 size_t destructions = 0;
392 RemovalFilesystem filesystem(destructions,
false);
393 RemovalDirectory directory(&filesystem);
394 RemovalProbeFile* first =
395 new RemovalProbeFile(
String(
"intrinsic-first"), &filesystem, &directory, destructions);
396 RemovalProbeFile* second =
397 new RemovalProbeFile(
String(
"intrinsic-second"), &filesystem, &directory, destructions);
398 filesystem.configure(&directory, first, second);
399 directory.publish(filesystem.firstAlias(), first);
400 directory.publish(filesystem.secondAlias(), second);
402 const bool emptied = directory.empty();
403 const bool retiresAsItGoes = emptied && filesystem.removeCalls() == 2 &&
404 filesystem.secondObservedPriorRetirement() && destructions == 2 &&
405 !directory.contains(filesystem.firstAlias()) &&
406 !directory.contains(filesystem.secondAlias());
408 bool selfRemovingSafe =
false;
409 if (retiresAsItGoes) {
410 size_t selfRemovingDestructions = 0;
411 RemovalFilesystem selfRemovingFilesystem(selfRemovingDestructions,
true);
412 RemovalDirectory selfRemovingDirectory(&selfRemovingFilesystem);
413 RemovalProbeFile* selfFirst =
414 new RemovalProbeFile(
String(
"self-intrinsic-first"), &selfRemovingFilesystem,
415 &selfRemovingDirectory, selfRemovingDestructions);
416 RemovalProbeFile* selfSecond =
417 new RemovalProbeFile(
String(
"self-intrinsic-second"), &selfRemovingFilesystem,
418 &selfRemovingDirectory, selfRemovingDestructions);
419 selfRemovingFilesystem.configure(&selfRemovingDirectory, selfFirst, selfSecond);
420 selfRemovingDirectory.publish(selfRemovingFilesystem.firstAlias(), selfFirst);
421 selfRemovingDirectory.publish(selfRemovingFilesystem.secondAlias(), selfSecond);
423 selfRemovingSafe = selfRemovingDirectory.empty() && selfRemovingFilesystem.removeCalls() == 2 &&
424 selfRemovingDestructions == 2 &&
425 !selfRemovingDirectory.contains(selfRemovingFilesystem.firstAlias()) &&
426 !selfRemovingDirectory.contains(selfRemovingFilesystem.secondAlias());
429 const bool passed = retiresAsItGoes && selfRemovingSafe;
431 NOTICE(
"HOSTED-WAIT-TEST: PASS directory-empty-removal-ownership");
434 "HOSTED-WAIT-TEST: FAIL directory-empty-removal-ownership: "
435 "removed entries remained alive or were retired twice");
440bool fatRemoveRetirementOrder() {
442 uint32_t fatTable[256] = {};
443 fatTable[1] = 0xFFFF;
445 FatFilesystemHarness filesystem;
446 filesystem.configureRemoval(&disk,
reinterpret_cast<uintptr_t
>(fatTable));
449 RemovalFatDirectory parent(&filesystem, info);
450 size_t destructions = 0;
451 RetainedFatFile* child =
new RetainedFatFile(&filesystem, &parent, destructions);
452 parent.publish(child);
455 const bool removed = retained && filesystem.removeForTest(&parent, parent.alias(), child);
456 const bool operationPassed =
457 removed && parent.removeCalls() == 1 && !destructions && !parent.containsAlias() &&
458 fatTable[1] == 0 && disk.m_ReadCount == 1 && disk.m_WriteCount == 1 &&
459 disk.m_UnpinCount == 1 && disk.m_ReadLocations[0] == 2048 &&
460 disk.m_WriteLocations[0] == 2048 && disk.m_UnpinLocations[0] == 2048 && disk.balanced();
462 bool emergencyWasFinal =
false;
465 if (emergencyWasFinal) {
467 }
else if (parent.containsAlias()) {
468 parent.removeAlias();
471 parent.removeAlias();
474 const bool passed = operationPassed && emergencyWasFinal && destructions == 1;
476 NOTICE(
"HOSTED-WAIT-TEST: PASS fat-remove-retirement-order");
479 "HOSTED-WAIT-TEST: FAIL fat-remove-retirement-order: "
480 "FAT removal read a child after retiring its final cache owner");
485bool fatRemoveCommitsBeforeClusterReclaim() {
487 uint32_t fatTable[256] = {};
489 FatFilesystemHarness filesystem;
490 filesystem.configureRemoval(&disk,
reinterpret_cast<uintptr_t
>(fatTable));
493 RemovalFatDirectory parent(&filesystem, info,
false);
494 size_t destructions = 0;
495 RetainedFatFile* child =
new RetainedFatFile(&filesystem, &parent, destructions);
496 parent.publish(child);
499 const bool removed = retained && filesystem.removeForTest(&parent, parent.alias(), child);
500 const bool operationPassed = removed && parent.removeCalls() == 1 && !destructions &&
501 !parent.containsAlias() && fatTable[1] == 0;
503 bool retainedLeaseWasFinal =
false;
506 if (retainedLeaseWasFinal) {
508 }
else if (parent.containsAlias()) {
509 parent.removeAlias();
512 parent.removeAlias();
515 const bool passed = operationPassed && retainedLeaseWasFinal && destructions == 1;
517 NOTICE(
"HOSTED-WAIT-TEST: PASS fat-remove-commits-before-cluster-reclaim");
520 "HOSTED-WAIT-TEST: FAIL fat-remove-commits-before-cluster-reclaim: "
521 "a failed cluster reclaim made a completed namespace removal fail");
527EXPORTED_PUBLIC
bool runHostedFatSectorRegressions() {
529 passed &= fatSectorPageBoundary();
530 passed &= fatShortReadPublication();
531 passed &= directoryEmptyRemovalOwnership();
532 passed &= fatRemoveRetirementOrder();
533 passed &= fatRemoveCommitsBeforeClusterReclaim();
bool addDirectoryEntry(const String &name, File *pTarget)
void remove(const HashedStringView &s)
File * lookup(const HashedStringView &s) const
bool removeDirectoryEntry(const HashedStringView &name, File *expected)
virtual bool sync(uint64_t location, bool async)
virtual BufferView read(uint64_t location)
virtual size_t getSize() const
Gets the size of the disk.
virtual void unpin(uint64_t location)=0
virtual void write(uint64_t location)
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.
virtual bool removeEntry(const String &filename, File *pFile)
bool writeSectorBlock(uint32_t sec, size_t size, uintptr_t buffer)
virtual uintptr_t readBlock(uint64_t location)
virtual void unpinBlock(uint64_t location)
virtual bool removeNode(File *parent, const String &filename, File *file)=0
virtual bool initialise(Disk *pDisk)=0
virtual const String & getVolumeLabel() const =0
bool createSymlink(const StringView &path, const String &value, File *pStartNode=0)
bool createDirectory(const StringView &path, uint32_t mask, File *pStartNode=0)
virtual File * getRoot() const =0
bool createFile(const StringView &path, uint32_t mask, File *pStartNode=0)
bool remove(const StringView &path, File *pStartNode=0)
static constexpr size_t getPageSize() noexcept
MUST_USE_RESULT bool retainTrackedFile(File *pFile)
bool untrackFile(File *pFile, bool destroy=true)