The Pedigree Project 0.1
FatFilesystem-state.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/machine/Disk.h"
3#include "pedigree/kernel/process/TerminationDeferral.h"
4
5#include "FatFilesystem.h"
6#include "FatSymlink.h"
7
8namespace {
9uint64_t slotKey(uint32_t cluster, uint32_t offset) {
10 return (uint64_t(cluster) << 32) | offset;
11}
12bool publishedSlot(uint32_t cluster, uint32_t offset) {
13 return cluster != 0xdeadbeef && offset != 0xbeefdead;
14}
15} // namespace
16
17FatFile::State::State(FatFilesystem* owner) : filesystem(owner) {
18 cache.fill.setDirtyTracking(Cache::DirtyTracking::Explicit);
19 cache.fill.setCallback(checkedWriteCallback, this);
20 cache.fill.setBackgroundWriteback(checkedBatchCallback);
21}
22
23FatFile::State::~State() {
24 if (!cache.fill.shutdown())
25 ERROR("FAT: unable to drain file cache during unmount");
26}
27
28bool FatFilesystem::deviceRemoved(bool deviceAvailable) {
29 if (!m_pDisk) {
30 return true;
31 }
32 if (!beginDeviceRemoval(deviceAvailable)) {
33 return false;
34 }
35 m_bReadOnly = true;
36 for (auto* state = m_StateList; state; state = state->next) {
37 LockGuard<Mutex> data(state->dataLock);
38 if (!state->cache.fill.shutdown(Cache::ShutdownMode::DiscardDeferred)) {
39 return false;
40 }
41 state->retiring = true;
42 state->metadataDirty = false;
43 state->trimPending = false;
44 state->retiredClusters.clear();
45 }
46 {
47 LockGuard<Mutex> guard(m_FileMutationLock);
48 clearPendingAttributes();
49 LockGuard<UnlikelyLock> fat(m_FatLock);
50 for (auto it = m_FatCache.begin(); it != m_FatCache.end(); ++it) {
51 delete[] reinterpret_cast<uint8_t*>(it.value());
52 }
53 m_FatCache.clear();
54 m_DirtyFatSectors.clear();
55 }
57}
58
59uintptr_t FatFilesystem::fileIdentifier(uint32_t cluster, uint32_t offset) {
60 LockGuard<Mutex> registry(m_StateLock);
61 return fileIdentifierLocked(slotKey(cluster, offset));
62}
63
64uintptr_t FatFilesystem::fileIdentifierLocked(uint64_t slot) {
65 uintptr_t identifier = m_FileIdentifiers.lookup(slot);
66 if (!identifier) {
67 // Public IDs occupy the range above every valid FAT32 cluster number.
68 assert(m_NextFileIdentifier >= 0x10000000);
69 identifier = m_NextFileIdentifier++;
70 m_FileIdentifiers.insert(slot, identifier);
71 }
72 return identifier;
73}
74
75FatFile::State* FatFilesystem::acquireFileState(FatFile* file, uintptr_t inode, size_t size,
76 uint32_t cluster, uint32_t offset,
77 Time::Timestamp accessed, Time::Timestamp modified,
78 Time::Timestamp changed) {
79 LockGuard<Mutex> registry(m_StateLock);
80 const bool published = publishedSlot(cluster, offset);
81 FatFile::State* state = published ? m_FileStates.lookup(slotKey(cluster, offset)) : nullptr;
82 if (!state) {
83 state = new FatFile::State(this);
84 state->inode = inode;
85 state->size = size;
86 state->directoryCluster = cluster;
87 state->directoryOffset = offset;
88 state->accessed = accessed;
89 state->modified = modified;
90 state->changed = changed;
91 state->next = m_StateList;
92 m_StateList = state;
93 if (published) {
94 state->identifier = fileIdentifierLocked(slotKey(cluster, offset));
95 m_FileStates.insert(slotKey(cluster, offset), state);
96 state->registered = true;
97 }
98 }
99 return state;
100}
101
102void FatFilesystem::releaseFileState(FatFile* file) {
103 auto* state = file->m_State;
104 LockGuard<Mutex> data(state->dataLock);
105 // Keep failed writeback retryable even when the last descriptor closes.
106 OperationBarrier::Lease operation;
107 const bool available = tryAcquireOperation(operation);
108 const bool written =
109 !available || state->cache.fill.syncAll(FatFile::checkedBatchCallback, state);
110 if (available && m_pDisk && !syncFileMetadata(file)) {
111 WARNING("FAT: retaining pending metadata after close");
112 }
113 if (!written)
114 WARNING("FAT: retaining dirty file pages after close");
115 bool retire = false;
116 {
117 LockGuard<Mutex> guard(m_FileMutationLock);
118 LockGuard<Mutex> registry(m_StateLock);
119 FatFile** alias = &state->aliases;
120 while (*alias && *alias != file)
121 alias = &(*alias)->m_NextAlias;
122 if (*alias)
123 *alias = file->m_NextAlias;
124 retire = state->unlinked && !state->aliases;
125 if (retire)
126 state->retiring = true;
127 }
128 if (retire && available) {
129 // Callback storage survives the terminal drain; the old allocation cannot
130 // be reused while a callback can still write its pages.
131 state->cache.fill.shutdown();
132 retireNode(file);
133 state->inode = 0;
134 }
135}
136
137void FatFilesystem::publishSize(File* file, size_t size) {
138 if (file->isDirectory() || file->isSymlink()) {
139 file->setSize(size);
140 return;
141 }
142 auto* regular = static_cast<FatFile*>(file);
143 LockGuard<Mutex> registry(m_StateLock);
144 regular->m_State->size = size;
145 regular->File::setSize(size);
146 for (FatFile* alias = regular->m_State->aliases; alias; alias = alias->m_NextAlias)
147 alias->File::setSize(size);
148}
149
150bool FatFilesystem::isNodeUnlinked(File* file) const {
151 if (file->isDirectory())
152 return static_cast<FatDirectory*>(file)->m_Unlinked;
153 if (file->isSymlink())
154 return static_cast<FatSymlink*>(file)->m_Unlinked;
155 return static_cast<FatFile*>(file)->m_State->unlinked;
156}
157
158void FatFilesystem::unlinkNode(File* file) {
159 if (file->isDirectory() || file->isSymlink()) {
160 if (file->isDirectory()) {
161 auto* node = static_cast<FatDirectory*>(file);
162 discardPendingAttributes(node->getDirCluster(), node->getDirOffset());
163 } else {
164 auto* node = static_cast<FatSymlink*>(file);
165 discardPendingAttributes(node->getDirCluster(), node->getDirOffset());
166 }
167 unlinkNonFileNode(file);
168 return;
169 }
170 LockGuard<Mutex> registry(m_StateLock);
171 auto* state = static_cast<FatFile*>(file)->m_State;
172 state->unlinked = true;
173 if (state->registered) {
174 m_FileStates.remove(slotKey(state->directoryCluster, state->directoryOffset));
175 m_FileIdentifiers.remove(slotKey(state->directoryCluster, state->directoryOffset));
176 state->registered = false;
177 }
178}
179
180void FatFilesystem::moveNode(File* file, uint32_t cluster, uint32_t offset) {
181 if (file->isDirectory() || file->isSymlink()) {
182 if (file->isDirectory()) {
183 auto* node = static_cast<FatDirectory*>(file);
184 movePendingAttributes(node->getDirCluster(), node->getDirOffset(), cluster, offset);
185 } else {
186 auto* node = static_cast<FatSymlink*>(file);
187 movePendingAttributes(node->getDirCluster(), node->getDirOffset(), cluster, offset);
188 }
189 moveNonFileNode(file, cluster, offset);
190 } else {
191 LockGuard<Mutex> registry(m_StateLock);
192 auto* state = static_cast<FatFile*>(file)->m_State;
193 if (state->registered) {
194 m_FileStates.remove(slotKey(state->directoryCluster, state->directoryOffset));
195 m_FileIdentifiers.remove(slotKey(state->directoryCluster, state->directoryOffset));
196 }
197 state->directoryCluster = cluster;
198 state->directoryOffset = offset;
199 if (!state->identifier)
200 state->identifier = fileIdentifierLocked(slotKey(cluster, offset));
201 else
202 m_FileIdentifiers.insert(slotKey(cluster, offset), state->identifier);
203 m_FileStates.insert(slotKey(cluster, offset), state);
204 state->registered = true;
205 }
206}
207
208void FatFilesystem::retireNode(File* file) {
209 if (m_bReadOnly)
210 return;
211 LockGuard<Mutex> guard(m_FileMutationLock);
212 if (!file->isDirectory() && !file->isSymlink()) {
213 auto* regular = static_cast<FatFile*>(file);
214 for (uint32_t cluster : regular->m_RetiredClusters) {
215 if (!setClusterEntry(cluster, 0))
216 m_IoFailed = true;
217 }
218 regular->m_RetiredClusters.clear();
219 }
220 if (!releaseClusterChain(file->getInode(), false)) {
221 m_IoFailed = true;
222 ERROR("FAT: orphan allocation reclamation needs a FAT retry");
223 }
224}
225
226Filesystem::SyncStatus FatFilesystem::sync() {
227 OperationBarrier::Lease operation;
228 if (!tryAcquireOperation(operation)) {
229 return SyncStatus::IoError;
230 }
231 TerminationDeferral lifetime;
232 if (!m_pDisk)
233 return SyncStatus::IoError;
234 if (m_bReadOnly)
235 return m_IoFailed ? SyncStatus::IoError : SyncStatus::Success;
237 {
238 LockGuard<Mutex> registry(m_StateLock);
239 for (auto* state = m_StateList; state; state = state->next) {
240 if (!states.tryReserve(states.count() + 1))
241 return SyncStatus::NoMemory;
242 states.pushBack(state);
243 }
244 }
245 bool succeeded = true;
246 for (auto* state : states) {
247 LockGuard<Mutex> data(state->dataLock);
248 if (state->retiring)
249 continue;
250 FatFile file(*state);
251 succeeded = state->cache.fill.syncAll(FatFile::checkedBatchCallback, state) && succeeded;
252 succeeded = syncFileMetadata(&file) && succeeded;
253 }
254 LockGuard<Mutex> guard(m_FileMutationLock);
255 succeeded = syncPendingAttributes() && succeeded;
256 succeeded = syncFat() && succeeded;
257 succeeded = m_pDisk->syncAll() && succeeded;
258 return succeeded ? SyncStatus::Success : SyncStatus::IoError;
259}
260
261Filesystem::SyncStatus FatFilesystem::shutdown() {
262 OperationBarrier::Lease operation;
263 if (!tryAcquireOperation(operation)) {
264 return SyncStatus::IoError;
265 }
266 TerminationDeferral lifetime;
267 if (m_ShutdownComplete)
268 return SyncStatus::Success;
269 auto status = sync();
270 if (status != SyncStatus::Success)
271 return status;
272 delete m_pRoot;
273 m_pRoot = nullptr;
274 if (!drainFileStates(true))
275 return SyncStatus::IoError;
276 // Closing aliases may release orphan clusters or queue directory attributes.
277 status = sync();
278 // Pre-existing dirty/error bits remain for fsck; only this mount's failures
279 // prevent completion after its writes have drained.
280 if (status != SyncStatus::Success || m_IoFailed) {
281 return status == SyncStatus::Success ? SyncStatus::IoError : status;
282 }
283 m_ShutdownComplete = true;
284 return SyncStatus::Success;
285}
286
287bool FatFilesystem::drainFileStates(bool checked) {
288 bool succeeded = true;
289 while (m_StateList) {
290 auto* state = m_StateList;
291 if (checked && state->aliases)
292 return false;
293 if (!state->retiring && m_pDisk) {
294 FatFile file(*state);
295 if (!state->cache.fill.syncAll(FatFile::checkedBatchCallback, state)) {
296 succeeded = false;
297 ERROR("FAT: dirty pages remain during unmount");
298 }
299 succeeded = syncFileMetadata(&file) && succeeded;
300 }
301 if (checked && (!succeeded || !state->cache.fill.shutdown()))
302 return false;
303 m_StateList = state->next;
304 delete state;
305 }
306 m_FileStates.clear();
307 m_FileIdentifiers.clear();
308 return succeeded;
309}
bool shutdown(ShutdownMode mode=ShutdownMode::WriteBack)
Definition Cache.cc:663
MUST_USE_RESULT bool syncAll()
Definition Cache.cc:1687
bool sync() override
Definition FatFile.cc:168
bool deviceRemoved(bool deviceAvailable=false) override
SyncStatus sync() override
SyncStatus shutdown() override
Definition File.h:75
virtual bool isSymlink()
Definition File.cc:800
virtual bool isDirectory()
Definition File.cc:804
virtual bool deviceRemoved(bool deviceAvailable=false)
Definition Filesystem.cc:92
A vector / dynamic array.
Definition Vector.h:33
void pushBack(const T &value)
Definition Vector.h:275
void clear(bool freeMem=false)
Definition Vector.h:378
size_t count() const
Definition Vector.h:270