The Pedigree Project 0.1
FatFilesystem-namespace.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/syscallError.h"
3
4#include "FatFilesystem.h"
5#include "FatSymlink.h"
6
8 public:
9 struct Portion {
10 uint32_t cluster;
11 size_t size;
12 uint8_t* before;
13 uint8_t* after;
14 };
15 struct Slot {
16 Portion* portion;
17 uint32_t offset;
18 Dir* entry() const {
19 return reinterpret_cast<Dir*>(portion->after + offset);
20 }
21 };
22 struct Contents {
23 FatDirectory* directory;
24 Vector<Slot> slots;
25 uint32_t tail = 0;
26 };
27
28 explicit NamespaceEdit(FatFilesystem* filesystem) : m_Filesystem(filesystem) {}
30 for (Portion* portion : m_Portions) {
31 delete[] portion->before;
32 delete[] portion->after;
33 delete portion;
34 }
35 }
36
37 bool load(FatDirectory* directory, Contents& contents) {
38 contents.directory = directory;
39 uint32_t cluster = directory->getInode();
40 for (size_t visited = 0; visited <= m_Filesystem->m_ClusterCount; ++visited) {
41 if ((cluster == 0 && m_Filesystem->m_Type == FAT32) ||
42 (cluster && (cluster < 2 || cluster >= m_Filesystem->m_ClusterCount + 2)))
43 return fail();
44 for (const Slot& slot : contents.slots) {
45 if (!slot.offset && slot.portion->cluster == cluster)
46 return fail();
47 }
48 Portion* portion = read(cluster);
49 if (!portion)
50 return false;
51 append(contents, portion);
52 if (!cluster && m_Filesystem->m_Type != FAT32)
53 return true;
54 const uint32_t next = m_Filesystem->getClusterEntry(cluster);
55 if (m_Filesystem->isEof(next))
56 return true;
57 cluster = next;
58 }
59 return fail();
60 }
61
62 bool locate(Contents& contents, const String& name, File* file, size_t& index) {
63 struct Identity {
64 StringView name;
65 File* file;
66 uint32_t cluster = 0;
67 uint32_t offset = 0;
68 bool found = false;
69 } identity{name.view(), file};
70 auto match = [](void* opaque, const ScannedEntry& entry, uint64_t, uint64_t) {
71 Identity& identity = *static_cast<Identity*>(opaque);
72 if (entry.name != identity.name)
73 return true;
74 const uint32_t cluster = LITTLE_TO_HOST16(entry.entry.DIR_FstClusLO) |
75 (uint32_t(LITTLE_TO_HOST16(entry.entry.DIR_FstClusHI)) << 16);
76 uint32_t expectedCluster = 0, expectedOffset = 0;
77 if (identity.file->isDirectory()) {
78 auto* file = static_cast<FatDirectory*>(identity.file);
79 expectedCluster = file->getDirCluster();
80 expectedOffset = file->getDirOffset();
81 } else if (identity.file->isSymlink()) {
82 auto* file = static_cast<FatSymlink*>(identity.file);
83 expectedCluster = file->getDirCluster();
84 expectedOffset = file->getDirOffset();
85 } else {
86 auto* file = static_cast<FatFile*>(identity.file);
87 expectedCluster = file->getDirCluster();
88 expectedOffset = file->getDirOffset();
89 }
90 identity.found = cluster == identity.file->getInode() &&
91 entry.directoryCluster == expectedCluster &&
92 entry.directoryOffset == expectedOffset;
93 identity.cluster = entry.directoryCluster;
94 identity.offset = entry.directoryOffset;
95 return false;
96 };
97 uint64_t cookie = 0;
98 const ReadStatus status = contents.directory->scanDirectory(cookie, match, &identity);
99 if (!identity.found) {
100 syscallError(status == ReadStatus::IoError ? Error::IoError : Error::DoesNotExist);
101 return false;
102 }
103 for (index = 0; index < contents.slots.count(); ++index) {
104 const Slot& slot = contents.slots[index];
105 if (slot.portion->cluster == identity.cluster && slot.offset == identity.offset)
106 return true;
107 }
108 return fail();
109 }
110
111 void erase(Contents& contents, size_t index) {
112 Dir* entry = contents.slots[index].entry();
113 const uint8_t checksum = nameChecksum(entry->DIR_Name);
114 entry->DIR_Name[0] = 0xE5;
115 for (size_t ordinal = 1; index && ordinal <= 20; ++ordinal) {
116 DirLongFilename* previous =
117 reinterpret_cast<DirLongFilename*>(contents.slots[--index].entry());
118 if ((previous->LDIR_Attr & ATTR_LONG_NAME_MASK) != ATTR_LONG_NAME ||
119 previous->LDIR_Chksum != checksum || (previous->LDIR_Ord & 0x1F) != ordinal ||
120 previous->LDIR_Type || previous->LDIR_FstClusLO)
121 break;
122 const bool last = previous->LDIR_Ord & 0x40;
123 previous->LDIR_Ord = 0xE5;
124 if (last)
125 break;
126 }
127 }
128
129 bool insert(Contents& contents, const String& name, const Dir& metadata, Slot& result) {
130 Vector<Dir> entries;
131 if (!contents.directory->encodeEntrySet(name, metadata, entries))
132 return false;
133 if (!uniqueShortName(contents, entries))
134 return false;
135 size_t index = 0, freeCount = 0;
136 bool pastEnd = false;
137 while (true) {
138 for (; index < contents.slots.count(); ++index) {
139 const uint8_t first = contents.slots[index].entry()->DIR_Name[0];
140 pastEnd = pastEnd || !first;
141 freeCount = pastEnd || first == 0xE5 ? freeCount + 1 : 0;
142 if (freeCount == entries.count()) {
143 const size_t begin = index + 1 - entries.count();
144 for (size_t i = 0; i < entries.count(); ++i)
145 *contents.slots[begin + i].entry() = entries[i];
146 if (pastEnd && index + 1 < contents.slots.count())
147 contents.slots[index + 1].entry()->DIR_Name[0] = 0;
148 result = contents.slots[index];
149 return true;
150 }
151 }
152 if (!grow(contents))
153 return false;
154 }
155 }
156
157 bool updateParent(Contents& contents, uint32_t oldParent, uint32_t newParent) {
158 if (contents.slots.count() < 2)
159 return fail();
160 Dir* entry = contents.slots[1].entry();
161 if (MemoryCompare(entry->DIR_Name, ".. ", 11) || !(entry->DIR_Attr & ATTR_DIRECTORY))
162 return fail();
163 const uint32_t recorded = LITTLE_TO_HOST16(entry->DIR_FstClusLO) |
164 (uint32_t(LITTLE_TO_HOST16(entry->DIR_FstClusHI)) << 16);
165 const uint32_t root = m_Filesystem->m_pRoot->getInode();
166 if (recorded != oldParent && !(oldParent == root && !recorded))
167 return fail();
168 if (newParent == root)
169 newParent = 0;
170 entry->DIR_FstClusLO = HOST_TO_LITTLE16(newParent & 0xFFFF);
171 entry->DIR_FstClusHI = HOST_TO_LITTLE16(newParent >> 16);
172 return true;
173 }
174
175 bool commit(const Contents* destination = nullptr) {
176 Vector<Portion*> ordered;
177 if (destination) {
178 for (const Slot& slot : destination->slots) {
179 if (!slot.offset)
180 ordered.pushBack(slot.portion);
181 }
182 }
183 for (Portion* portion : m_Portions) {
184 bool found = false;
185 for (Portion* existing : ordered)
186 found = found || existing == portion;
187 if (!found)
188 ordered.pushBack(portion);
189 }
190 for (size_t i = 0; i < ordered.count(); ++i) {
191 Portion* portion = ordered[i];
192 if (!MemoryCompare(portion->before, portion->after, portion->size))
193 continue;
194 if (!m_Filesystem->writeDirectoryPortion(portion->cluster, portion->after)) {
195 // Disk writeback may have accepted a prefix before reporting failure.
196 // Restore every attempted portion, including that failed write's cache.
197 for (size_t rollback = i + 1; rollback; --rollback) {
198 Portion* original = ordered[rollback - 1];
199 if (!m_Filesystem->writeDirectoryPortion(original->cluster, original->before)) {
200 m_Filesystem->m_IoFailed = true;
201 m_Filesystem->m_bReadOnly = true;
202 ERROR("FAT namespace rollback failed; filesystem is now read-only");
203 }
204 }
205 return fail();
206 }
207 }
208 return true;
209 }
210
211 private:
212 static bool fail() {
213 SYSCALL_ERROR(IoError);
214 return false;
215 }
216 static uint8_t nameChecksum(const uint8_t* name) {
217 uint8_t checksum = 0;
218 for (size_t i = 0; i < 11; ++i)
219 checksum = uint8_t(((checksum & 1) ? 0x80 : 0) + (checksum >> 1) + name[i]);
220 return checksum;
221 }
222 Portion* read(uint32_t cluster) {
223 for (Portion* portion : m_Portions) {
224 if (portion->cluster == cluster)
225 return portion;
226 }
227 const size_t size =
228 cluster ? m_Filesystem->m_BlockSize
229 : m_Filesystem->m_RootDirCount * m_Filesystem->m_Superblock.BPB_BytsPerSec;
230 if (!size || size % sizeof(Dir)) {
231 fail();
232 return nullptr;
233 }
234 auto* before = static_cast<uint8_t*>(m_Filesystem->readDirectoryPortion(cluster));
235 if (!before) {
236 fail();
237 return nullptr;
238 }
239 auto* after = new uint8_t[size];
240 MemoryCopy(after, before, size);
241 Portion* portion = new Portion{cluster, size, before, after};
242 m_Portions.pushBack(portion);
243 return portion;
244 }
245 void append(Contents& contents, Portion* portion) {
246 for (size_t offset = 0; offset < portion->size; offset += sizeof(Dir))
247 contents.slots.pushBack(Slot{portion, uint32_t(offset)});
248 contents.tail = portion->cluster;
249 }
250 bool grow(Contents& contents) {
251 if (!contents.tail) {
252 SYSCALL_ERROR(NoSpaceLeftOnDevice);
253 return false;
254 }
255 const uint32_t cluster = m_Filesystem->findFreeCluster();
256 if (!cluster)
257 return false;
258 auto* zero = new uint8_t[m_Filesystem->m_BlockSize];
259 ByteSet(zero, 0, m_Filesystem->m_BlockSize);
260 const bool cleared = m_Filesystem->writeCluster(cluster, reinterpret_cast<uintptr_t>(zero));
261 delete[] zero;
262 if (!cleared) {
263 m_Filesystem->releaseClusterChain(cluster, false);
264 return fail();
265 }
266 const uint32_t oldTail = m_Filesystem->getClusterEntry(contents.tail);
267 if (!m_Filesystem->setClusterEntry(contents.tail, cluster)) {
268 if (m_Filesystem->setClusterEntry(contents.tail, oldTail)) {
269 m_Filesystem->releaseClusterChain(cluster, false);
270 } else {
271 m_Filesystem->m_IoFailed = true;
272 m_Filesystem->m_bReadOnly = true;
273 ERROR("FAT directory extension rollback failed; filesystem is now read-only");
274 }
275 return fail();
276 }
277 // An empty extension is safe to retain if a later namespace write fails.
278 Portion* portion = read(cluster);
279 if (!portion)
280 return false;
281 append(contents, portion);
282 return true;
283 }
284 bool uniqueShortName(const Contents& contents, Vector<Dir>& entries) {
285 Dir& target = entries[entries.count() - 1];
286 uint8_t original[11];
287 MemoryCopy(original, target.DIR_Name, 11);
288 for (uint32_t suffix = 0; suffix < 1000000; ++suffix) {
289 bool occupied = false;
290 for (const Slot& slot : contents.slots) {
291 const Dir* entry = slot.entry();
292 if (!entry->DIR_Name[0])
293 break;
294 if (entry->DIR_Name[0] != 0xE5 &&
295 (entry->DIR_Attr & ATTR_LONG_NAME_MASK) != ATTR_LONG_NAME &&
296 !MemoryCompare(entry->DIR_Name, target.DIR_Name, 11)) {
297 occupied = true;
298 break;
299 }
300 }
301 if (!occupied) {
302 const uint8_t checksum = nameChecksum(target.DIR_Name);
303 for (size_t i = 0; i + 1 < entries.count(); ++i)
304 reinterpret_cast<DirLongFilename*>(&entries[i])->LDIR_Chksum = checksum;
305 return true;
306 }
307 char digits[7];
308 size_t length = 0;
309 for (uint32_t number = suffix + 1; number; number /= 10)
310 digits[length++] = '0' + number % 10;
311 MemoryCopy(target.DIR_Name, original, 11);
312 const size_t prefix = 7 - length;
313 for (size_t i = 0; i < prefix; ++i) {
314 if (target.DIR_Name[i] == ' ')
315 target.DIR_Name[i] = '_';
316 }
317 target.DIR_Name[prefix] = '~';
318 for (size_t i = 0; i < length; ++i)
319 target.DIR_Name[prefix + 1 + i] = digits[length - 1 - i];
320 }
321 SYSCALL_ERROR(NoSpaceLeftOnDevice);
322 return false;
323 }
324
325 FatFilesystem* m_Filesystem;
326 Vector<Portion*> m_Portions;
327};
328
329bool FatDirectory::addEntry(String filename, File* file, size_t type, bool publish) {
330 const bool special = filename == "." || filename == "..";
331 NameReservation reservation;
332 if (!special && publish && !reserveDirectoryEntry(HashedStringView(file->getName()), reservation))
333 return false;
335 auto* filesystem = static_cast<FatFilesystem*>(m_pFilesystem);
336 LockGuard<Mutex> fileGuard(filesystem->m_FileMutationLock);
337 if (filesystem->isReadOnly()) {
338 SYSCALL_ERROR(ReadOnlyFilesystem);
339 return false;
340 }
341 if (isDetached()) {
342 SYSCALL_ERROR(DoesNotExist);
343 return false;
344 }
345 struct ExistingName {
346 String name;
347 bool found = false;
348 } existing{file->getName()};
349 auto match = [](void* opaque, const ScannedEntry& entry, uint64_t, uint64_t) {
350 auto& existing = *static_cast<ExistingName*>(opaque);
351 existing.found = entry.name == existing.name;
352 return !existing.found;
353 };
354 uint64_t cookie = 0;
355 if (!special) {
356 const ReadStatus status = scanDirectory(cookie, match, &existing);
357 if (existing.found || status == ReadStatus::IoError) {
358 syscallError(existing.found ? Error::FileExists : Error::IoError);
359 return false;
360 }
361 }
362 NamespaceEdit edit(filesystem);
364 if (!edit.load(this, contents))
365 return false;
366 Dir metadata = {};
367 metadata.DIR_Attr = type ? ATTR_DIRECTORY : 0;
368 uint32_t cluster = file->getInode();
369 if (filename == ".." && filesystem->m_pRoot && cluster == filesystem->m_pRoot->getInode())
370 cluster = 0;
371 metadata.DIR_FstClusLO = HOST_TO_LITTLE16(cluster & 0xFFFF);
372 metadata.DIR_FstClusHI = HOST_TO_LITTLE16(cluster >> 16);
373 metadata.DIR_FileSize = HOST_TO_LITTLE32(type ? 0 : file->getSize());
374 filesystem->writeEntryAttributes(file, &metadata, true);
375 NamespaceEdit::Slot location;
376 if (!edit.insert(contents, filename, metadata, location) || !edit.commit())
377 return false;
378 filesystem->moveNode(file, location.portion->cluster, location.offset);
379 if (publish && !special) {
380 const bool published = addCachedDirectoryEntry(reservation, file);
381 assert(published);
382 publishEvent(FileEvents::Created, file->getName().view(), file->isDirectory());
383 reservation.complete(LookupStatus::Found);
384 }
385 return true;
386}
387
388bool FatDirectory::removeEntry(const String& name, File* file) {
390 auto* filesystem = static_cast<FatFilesystem*>(m_pFilesystem);
391 {
392 LockGuard<Mutex> fileGuard(filesystem->m_FileMutationLock);
393 if (filesystem->isReadOnly()) {
394 SYSCALL_ERROR(ReadOnlyFilesystem);
395 return false;
396 }
397 NamespaceEdit edit(filesystem);
399 size_t index = 0;
400 if (!edit.load(this, contents) || !edit.locate(contents, name, file, index))
401 return false;
402 edit.erase(contents, index);
403 if (!edit.commit())
404 return false;
405 filesystem->unlinkNode(file);
406 }
408 return true;
409}
410
411bool FatFilesystem::renameNode(Directory* oldParent, const String& oldName, File* source,
412 Directory* newParent, const String& newName, File* replaced) {
413 auto* oldDirectory = static_cast<FatDirectory*>(oldParent);
414 auto* newDirectory = static_cast<FatDirectory*>(newParent);
415 auto* moved = source->isDirectory() ? static_cast<FatDirectory*>(source) : nullptr;
416 auto* victim =
417 replaced && replaced->isDirectory() ? static_cast<FatDirectory*>(replaced) : nullptr;
418 const bool oldFirst =
419 reinterpret_cast<uintptr_t>(oldDirectory) < reinterpret_cast<uintptr_t>(newDirectory);
420 FatDirectory* first = oldFirst ? oldDirectory : newDirectory;
421 FatDirectory* second = oldFirst ? newDirectory : oldDirectory;
422 LockGuard<Mutex> firstGuard(first->m_Lock);
423 LockGuard<Mutex> secondGuard(second->m_Lock, second != first);
424 LockGuard<Mutex> movedGuard(moved ? moved->m_Lock : first->m_Lock, moved != nullptr);
425 LockGuard<Mutex> victimGuard(victim ? victim->m_Lock : first->m_Lock, victim != nullptr);
427 if (oldDirectory->isDetached() || newDirectory->isDetached() || (moved && moved->isDetached()) ||
428 (victim && victim->isDetached())) {
429 SYSCALL_ERROR(DoesNotExist);
430 return false;
431 }
432 using Edit = FatDirectory::NamespaceEdit;
433 Edit edit(this);
434 Edit::Contents oldContents, newContents, movedContents;
435 Edit::Contents* destination = oldDirectory == newDirectory ? &oldContents : &newContents;
436 if (!edit.load(oldDirectory, oldContents) ||
437 (destination == &newContents && !edit.load(newDirectory, newContents)))
438 return false;
439 size_t oldIndex = 0, replacedIndex = 0;
440 if (!edit.locate(oldContents, oldName, source, oldIndex) ||
441 (replaced && !edit.locate(*destination, newName, replaced, replacedIndex)))
442 return false;
443 Dir metadata = *oldContents.slots[oldIndex].entry();
444 metadata.DIR_FileSize = HOST_TO_LITTLE32(moved ? 0 : source->getSize());
445 metadata.DIR_FstClusLO = HOST_TO_LITTLE16(source->getInode() & 0xFFFF);
446 metadata.DIR_FstClusHI = HOST_TO_LITTLE16(source->getInode() >> 16);
447 edit.erase(oldContents, oldIndex);
448 if (replaced)
449 edit.erase(*destination, replacedIndex);
450 String diskName = newName;
451 if (source->isSymlink())
452 diskName += FatDirectory::symlinkSuffix();
453 Edit::Slot location;
454 if (!edit.insert(*destination, diskName, metadata, location))
455 return false;
456 if (moved && oldDirectory != newDirectory &&
457 (!edit.load(moved, movedContents) ||
458 !edit.updateParent(movedContents, oldDirectory->getInode(), newDirectory->getInode())))
459 return false;
460 if (!edit.commit(destination))
461 return false;
462 if (replaced)
463 unlinkNode(replaced);
464 moveNode(source, location.portion->cluster, location.offset);
465 return true;
466}
void invalidateDirectoryEntry(const HashedStringView &name)
Definition Directory.cc:858
bool isDetached() const
Definition Directory.h:179
bool addCachedDirectoryEntry(NameReservation &reservation, File *pTarget)
Definition Directory.cc:838
bool reserveDirectoryEntry(const HashedStringView &name, NameReservation &reservation)
Definition Directory.cc:609
virtual bool removeEntry(const String &filename, File *pFile)
virtual bool addEntry(String filename, File *pFile, size_t type, bool publish=true)
bool renameNode(Directory *oldParent, const String &oldName, File *source, Directory *newParent, const String &newName, File *replaced) override
uint32_t findFreeCluster(bool *persisted=nullptr)
uint32_t m_ClusterCount
bool writeCluster(uint32_t block, uintptr_t buffer)
Mutex m_FileMutationLock
uint32_t m_BlockSize
void * readDirectoryPortion(uint32_t clus) const
bool writeDirectoryPortion(uint32_t clus, void *p)
bool isEof(uint32_t cluster) const
uint32_t getClusterEntry(uint32_t cluster, bool bLock=true)
bool setClusterEntry(uint32_t cluster, uint32_t value, bool bLock=true, bool persist=true)
Superblock m_Superblock
bool releaseClusterChain(uint32_t clus, bool lockFile=true)
Definition File.h:74
String getName() const
Definition File.cc:670
virtual bool isSymlink()
Definition File.cc:688
virtual bool isDirectory()
Definition File.cc:692
void publishEvent(FileEventMask mask, const StringView &name=StringView(), bool targetIsDirectory=false)
Definition File.cc:749
bool m_bReadOnly
Definition Filesystem.h:178
StringView view() const
Definition String.cc:804
A vector / dynamic array.
Definition Vector.h:33
void pushBack(const T &value)
Definition Vector.h:275
size_t count() const
Definition Vector.h:270
Definition ext2.h:201