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
28uintptr_t FatFilesystem::fileIdentifier(uint32_t cluster, uint32_t offset) {
29 LockGuard<Mutex> registry(m_StateLock);
30 return fileIdentifierLocked(slotKey(cluster, offset));
31}
32
33uintptr_t FatFilesystem::fileIdentifierLocked(uint64_t slot) {
34 uintptr_t identifier = m_FileIdentifiers.lookup(slot);
35 if (!identifier) {
36 // Public IDs occupy the range above every valid FAT32 cluster number.
37 assert(m_NextFileIdentifier >= 0x10000000);
38 identifier = m_NextFileIdentifier++;
39 m_FileIdentifiers.insert(slot, identifier);
40 }
41 return identifier;
42}
43
44FatFile::State* FatFilesystem::acquireFileState(FatFile* file, uintptr_t inode, size_t size,
45 uint32_t cluster, uint32_t offset,
46 Time::Timestamp accessed, Time::Timestamp modified,
47 Time::Timestamp changed) {
48 LockGuard<Mutex> registry(m_StateLock);
49 const bool published = publishedSlot(cluster, offset);
50 FatFile::State* state = published ? m_FileStates.lookup(slotKey(cluster, offset)) : nullptr;
51 if (!state) {
52 state = new FatFile::State(this);
53 state->inode = inode;
54 state->size = size;
55 state->directoryCluster = cluster;
56 state->directoryOffset = offset;
57 state->accessed = accessed;
58 state->modified = modified;
59 state->changed = changed;
60 state->next = m_StateList;
61 m_StateList = state;
62 if (published) {
63 state->identifier = fileIdentifierLocked(slotKey(cluster, offset));
64 m_FileStates.insert(slotKey(cluster, offset), state);
65 state->registered = true;
66 }
67 }
68 return state;
69}
70
71void FatFilesystem::releaseFileState(FatFile* file) {
72 auto* state = file->m_State;
73 LockGuard<Mutex> data(state->dataLock);
74 // Keep failed writeback retryable even when the last descriptor closes.
75 const bool written = state->cache.fill.syncAll(FatFile::checkedBatchCallback, state);
76 if (m_pDisk && !syncFileMetadata(file))
77 WARNING("FAT: retaining pending metadata after close");
78 if (!written)
79 WARNING("FAT: retaining dirty file pages after close");
80 bool retire = false;
81 {
82 LockGuard<Mutex> guard(m_FileMutationLock);
83 LockGuard<Mutex> registry(m_StateLock);
84 FatFile** alias = &state->aliases;
85 while (*alias && *alias != file)
86 alias = &(*alias)->m_NextAlias;
87 if (*alias)
88 *alias = file->m_NextAlias;
89 retire = state->unlinked && !state->aliases;
90 if (retire)
91 state->retiring = true;
92 }
93 if (retire) {
94 // Callback storage survives the terminal drain; the old allocation cannot
95 // be reused while a callback can still write its pages.
96 state->cache.fill.shutdown();
97 retireNode(file);
98 state->inode = 0;
99 }
100}
101
102void FatFilesystem::publishSize(File* file, size_t size) {
103 if (file->isDirectory() || file->isSymlink()) {
104 file->setSize(size);
105 return;
106 }
107 auto* regular = static_cast<FatFile*>(file);
108 LockGuard<Mutex> registry(m_StateLock);
109 regular->m_State->size = size;
110 regular->File::setSize(size);
111 for (FatFile* alias = regular->m_State->aliases; alias; alias = alias->m_NextAlias)
112 alias->File::setSize(size);
113}
114
115bool FatFilesystem::isNodeUnlinked(File* file) const {
116 if (file->isDirectory())
117 return static_cast<FatDirectory*>(file)->m_Unlinked;
118 if (file->isSymlink())
119 return static_cast<FatSymlink*>(file)->m_Unlinked;
120 return static_cast<FatFile*>(file)->m_State->unlinked;
121}
122
123void FatFilesystem::unlinkNode(File* file) {
124 if (file->isDirectory() || file->isSymlink()) {
125 if (file->isDirectory()) {
126 auto* node = static_cast<FatDirectory*>(file);
127 discardPendingAttributes(node->getDirCluster(), node->getDirOffset());
128 } else {
129 auto* node = static_cast<FatSymlink*>(file);
130 discardPendingAttributes(node->getDirCluster(), node->getDirOffset());
131 }
132 unlinkNonFileNode(file);
133 return;
134 }
135 LockGuard<Mutex> registry(m_StateLock);
136 auto* state = static_cast<FatFile*>(file)->m_State;
137 state->unlinked = true;
138 if (state->registered) {
139 m_FileStates.remove(slotKey(state->directoryCluster, state->directoryOffset));
140 m_FileIdentifiers.remove(slotKey(state->directoryCluster, state->directoryOffset));
141 state->registered = false;
142 }
143}
144
145void FatFilesystem::moveNode(File* file, uint32_t cluster, uint32_t offset) {
146 if (file->isDirectory() || file->isSymlink()) {
147 if (file->isDirectory()) {
148 auto* node = static_cast<FatDirectory*>(file);
149 movePendingAttributes(node->getDirCluster(), node->getDirOffset(), cluster, offset);
150 } else {
151 auto* node = static_cast<FatSymlink*>(file);
152 movePendingAttributes(node->getDirCluster(), node->getDirOffset(), cluster, offset);
153 }
154 moveNonFileNode(file, cluster, offset);
155 } else {
156 LockGuard<Mutex> registry(m_StateLock);
157 auto* state = static_cast<FatFile*>(file)->m_State;
158 if (state->registered) {
159 m_FileStates.remove(slotKey(state->directoryCluster, state->directoryOffset));
160 m_FileIdentifiers.remove(slotKey(state->directoryCluster, state->directoryOffset));
161 }
162 state->directoryCluster = cluster;
163 state->directoryOffset = offset;
164 if (!state->identifier)
165 state->identifier = fileIdentifierLocked(slotKey(cluster, offset));
166 else
167 m_FileIdentifiers.insert(slotKey(cluster, offset), state->identifier);
168 m_FileStates.insert(slotKey(cluster, offset), state);
169 state->registered = true;
170 }
171}
172
173void FatFilesystem::retireNode(File* file) {
174 if (m_bReadOnly)
175 return;
176 LockGuard<Mutex> guard(m_FileMutationLock);
177 if (!file->isDirectory() && !file->isSymlink()) {
178 auto* regular = static_cast<FatFile*>(file);
179 for (uint32_t cluster : regular->m_RetiredClusters) {
180 if (!setClusterEntry(cluster, 0))
181 m_IoFailed = true;
182 }
183 regular->m_RetiredClusters.clear();
184 }
185 if (!releaseClusterChain(file->getInode(), false)) {
186 m_IoFailed = true;
187 ERROR("FAT: orphan allocation reclamation needs a FAT retry");
188 }
189}
190
191Filesystem::SyncStatus FatFilesystem::sync() {
192 TerminationDeferral lifetime;
193 if (!m_pDisk)
194 return SyncStatus::IoError;
195 if (m_bReadOnly)
196 return m_IoFailed ? SyncStatus::IoError : SyncStatus::Success;
198 {
199 LockGuard<Mutex> registry(m_StateLock);
200 for (auto* state = m_StateList; state; state = state->next) {
201 if (!states.tryReserve(states.count() + 1))
202 return SyncStatus::NoMemory;
203 states.pushBack(state);
204 }
205 }
206 bool succeeded = true;
207 for (auto* state : states) {
208 LockGuard<Mutex> data(state->dataLock);
209 if (state->retiring)
210 continue;
211 FatFile file(*state);
212 succeeded = state->cache.fill.syncAll(FatFile::checkedBatchCallback, state) && succeeded;
213 succeeded = syncFileMetadata(&file) && succeeded;
214 }
215 LockGuard<Mutex> guard(m_FileMutationLock);
216 succeeded = syncPendingAttributes() && succeeded;
217 succeeded = syncFat() && succeeded;
218 succeeded = m_pDisk->syncAll() && succeeded;
219 return succeeded ? SyncStatus::Success : SyncStatus::IoError;
220}
221
222Filesystem::SyncStatus FatFilesystem::shutdown() {
223 TerminationDeferral lifetime;
224 if (m_ShutdownComplete)
225 return SyncStatus::Success;
226 auto status = sync();
227 if (status != SyncStatus::Success)
228 return status;
229 delete m_pRoot;
230 m_pRoot = nullptr;
231 if (!drainFileStates(true))
232 return SyncStatus::IoError;
233 // Closing aliases may release orphan clusters or queue directory attributes.
234 status = sync();
235 if (status != SyncStatus::Success || m_IoFailed || !m_MountedClean)
236 return status == SyncStatus::Success ? SyncStatus::IoError : status;
237 m_ShutdownComplete = true;
238 return SyncStatus::Success;
239}
240
241bool FatFilesystem::drainFileStates(bool checked) {
242 bool succeeded = true;
243 while (m_StateList) {
244 auto* state = m_StateList;
245 if (checked && state->aliases)
246 return false;
247 if (!state->retiring && m_pDisk) {
248 FatFile file(*state);
249 if (!state->cache.fill.syncAll(FatFile::checkedBatchCallback, state)) {
250 succeeded = false;
251 ERROR("FAT: dirty pages remain during unmount");
252 }
253 succeeded = syncFileMetadata(&file) && succeeded;
254 }
255 if (checked && (!succeeded || !state->cache.fill.shutdown()))
256 return false;
257 m_StateList = state->next;
258 delete state;
259 }
260 m_FileStates.clear();
261 m_FileIdentifiers.clear();
262 return succeeded;
263}
MUST_USE_RESULT bool syncAll()
Definition Cache.cc:1608
bool shutdown()
Definition Cache.cc:654
bool sync() override
Definition FatFile.cc:168
SyncStatus sync() override
SyncStatus shutdown() override
Definition File.h:74
virtual bool isSymlink()
Definition File.cc:717
virtual bool isDirectory()
Definition File.cc:721
A vector / dynamic array.
Definition Vector.h:33
#define assert(x)
Definition assert.h:39
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