The Pedigree Project 0.1
Ext2Filesystem-sync.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/Log.h"
4#include "pedigree/kernel/machine/Disk.h"
5#include "pedigree/kernel/process/TerminationDeferral.h"
6#include "pedigree/kernel/utilities/Vector.h"
7#include "pedigree/kernel/utilities/assert.h"
8
9#include "Ext2Directory.h"
10#include "Ext2File.h"
11#include "Ext2Filesystem.h"
12#include "Ext2Node.h"
13#include "ext2.h"
14
15bool Ext2Filesystem::beginWritableMount() {
16 if (checkOptionalFeature(0x4) || checkRequiredFeature(~size_t(0x2)) ||
17 checkReadOnlyFeature(~size_t(0x3))) {
18 ERROR("Ext2: unsupported filesystem features prevent a writable mount");
19 return false;
20 }
21 m_MountState = LITTLE_TO_HOST16(m_pSuperblock->s_state);
22 m_pSuperblock->s_state = HOST_TO_LITTLE16(m_MountState & ~EXT2_STATE_CLEAN);
23 m_pDisk->write(1024ULL);
24#if !CRIPPLE_HDD || defined(EXT2_STANDALONE)
25 if (!m_pDisk->sync(1024ULL, false)) {
26 ERROR("Ext2: could not persist the writable mount state");
27 return false;
28 }
29#endif
30 return true;
31}
32
33Filesystem::SyncStatus Ext2Filesystem::shutdown() {
35 if (!tryAcquireOperation(operation)) {
36 return SyncStatus::IoError;
37 }
38 TerminationDeferral lifetime;
39 if (m_ShutdownComplete)
40 return SyncStatus::Success;
41 if (m_bReadOnly)
42 return sync();
44 return SyncStatus::IoError;
45
46 auto status = sync();
47 if (status != SyncStatus::Success)
48 return status;
49 if (!closeQuotaFiles(false))
50 return SyncStatus::IoError;
51 delete m_pRoot;
52 m_pRoot = nullptr;
53 // Releasing directory aliases can retire orphan allocations and quota owners.
54 // All of those writes, and callback retirement, precede the clean marker.
55 status = sync();
56 if (status != SyncStatus::Success)
57 return status;
58 for (auto it = m_InodeStates.begin(); it != m_InodeStates.end(); ++it) {
59 auto* state = it.value();
60 if (state->references || state->pageLoans || state->syncReferences || state->orphan)
61 return SyncStatus::IoError;
62 if (state->cache && !state->cache->fill.shutdown())
63 return SyncStatus::IoError;
64 }
65 for (auto it = m_InodeStates.begin(); it != m_InodeStates.end(); ++it)
66 delete it.value();
67 m_InodeStates.clear();
68 if (!m_pDisk->syncAll() || m_TeardownFailed)
69 return SyncStatus::IoError;
70 // This driver cannot certify journal recovery or unknown incompatible formats.
71 if (checkOptionalFeature(0x4) || checkRequiredFeature(~size_t(0x2)) ||
72 checkReadOnlyFeature(~size_t(0x3)))
73 return SyncStatus::Unsupported;
74
75 // Draining this mount's writes cannot certify pre-existing filesystem damage.
76 // Leave the unchecked marker in place, but do not prevent an orderly shutdown.
77 if (m_MountState != EXT2_STATE_CLEAN) {
78 m_ShutdownComplete = true;
79 return SyncStatus::Success;
80 }
81
82 m_pSuperblock->s_state = HOST_TO_LITTLE16(m_MountState);
83 m_pDisk->write(1024ULL);
84 if (!m_pDisk->sync(1024ULL, false) || !m_pDisk->syncAll()) {
85 m_pSuperblock->s_state = HOST_TO_LITTLE16(m_MountState & ~EXT2_STATE_CLEAN);
86 m_pDisk->write(1024ULL);
87 if (!m_pDisk->sync(1024ULL, false))
88 ERROR("Ext2: could not restore the unchecked state after shutdown I/O failure");
89 return SyncStatus::IoError;
90 }
91 m_ShutdownComplete = true;
92 return SyncStatus::Success;
93}
94
96 public:
97 explicit SyncSnapshot(Ext2Filesystem& filesystem) : filesystem(filesystem) {}
99 for (const auto& entry : entries)
100 filesystem.releaseSyncState(entry.inode, entry.state);
101 }
102
103 struct Entry {
104 uint32_t inode;
105 Ext2InodeState* state;
106 };
107 Ext2Filesystem& filesystem;
108 Vector<Entry> entries;
109};
110
111void Ext2Filesystem::releaseSyncState(uint32_t inode, Ext2InodeState* state) {
112 assert(__atomic_load_n(&state->syncReferences, __ATOMIC_RELAXED));
113 __atomic_sub_fetch(&state->syncReferences, size_t(1), __ATOMIC_RELEASE);
114 // The last admission must provide wipe() a node when final unlink raced sync.
115 Ext2Node admission(inode, state->metadata, this, *state);
116}
117
118Filesystem::SyncStatus Ext2Filesystem::sync() {
119 OperationBarrier::Lease operation;
120 if (!tryAcquireOperation(operation)) {
121 return SyncStatus::IoError;
122 }
123 TerminationDeferral lifetime;
124 if (m_bReadOnly)
125 return SyncStatus::Success;
126 if (!m_pDisk || !m_pSuperblock || !m_BlockSize)
127 return SyncStatus::IoError;
128
129 const auto quotaStatus = flushQuotas();
130
131 SyncSnapshot snapshot(*this);
132 for (;;) {
133 size_t required;
134 {
135 LockGuard<Mutex> registry(m_InodeStateLock);
136 required = m_InodeStates.count();
137 if (required <= snapshot.entries.size()) {
138 for (auto it = m_InodeStates.begin(); it != m_InodeStates.end(); ++it) {
139 Ext2InodeState* state = it.value();
140 // Dormant identities without a cache have discarded their block maps.
141 // Pinning those would suppress the next opener's mapping reload.
142 if (!state->references && !state->cache)
143 continue;
144 if (state->references == ~size_t(0))
145 return SyncStatus::NoMemory;
146 ++state->references;
147 __atomic_add_fetch(&state->syncReferences, size_t(1), __ATOMIC_RELEASE);
148 snapshot.entries.pushBack({it.key(), state});
149 }
150 break;
151 }
152 }
153 // Allocation can re-enter filesystem caches under memory pressure.
154 if (!snapshot.entries.tryReserve(required, false))
155 return SyncStatus::NoMemory;
156 }
157
158 bool succeeded = quotaStatus == QuotaStatus::Success;
159 for (const auto& entry : snapshot.entries) {
160 LockGuard<Mutex> data(entry.state->dataLock);
161 if (!entry.state->allocationValid)
162 succeeded = false;
163 if (entry.state->cache)
164 succeeded =
165 entry.state->cache->fill.syncAll(Ext2File::sharedFillBatchCallback, entry.state) &&
166 succeeded;
167 }
168
169 {
170#if THREADS || defined(STANDALONE_MUTEXES)
171 LockGuard<Mutex> allocation(m_WriteLock);
172#endif
173 // A whole-disk drain must not bypass failed xattr dependency ordering.
174 if (!flushAttributeWritesLocked())
175 return SyncStatus::IoError;
176 succeeded = m_pDisk->syncAll() && succeeded;
177 }
178 return succeeded ? SyncStatus::Success
179 : quotaStatus == QuotaStatus::NoMemory ? SyncStatus::NoMemory
180 : SyncStatus::IoError;
181}
virtual bool sync(uint64_t location, bool async)
Definition Disk.cc:358
virtual void write(uint64_t location)
Definition Disk.cc:340
virtual MUST_USE_RESULT bool syncAll()
Definition Disk.cc:375
virtual SyncStatus sync()
virtual SyncStatus shutdown()
Superblock * m_pSuperblock
Disk * m_pDisk
Definition Filesystem.h:188
bool m_bReadOnly
Definition Filesystem.h:186
Iterator begin()
Definition Tree.h:402
void clear()
Definition Tree.h:383
Iterator end()
Definition Tree.h:427
size_t count() const
Definition Tree.h:142
A vector / dynamic array.
Definition Vector.h:33