The Pedigree Project 0.1
Ext2Xattr-writeback.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/utilities/Vector.h"
6#include "pedigree/kernel/utilities/assert.h"
7
8#include "Ext2Filesystem.h"
9#include "Ext2Node.h"
10#include "Ext2Xattr.h"
11#include "ext2.h"
12
13Ext2Filesystem::AttributeRetirement::~AttributeRetirement() {
14 if (buffer)
15 filesystem->unpinBlock(block);
16 if (provisional)
17 filesystem->releaseBlockLocked(block);
18}
19
20XattrStatus Ext2Filesystem::attributeFormatStatus() const {
22 return XattrStatus::IoError;
23 // The external Ext2 layout does not describe Ext3/4 journals, checksums, or EA inodes.
24 if (LITTLE_TO_HOST32(m_pSuperblock->s_rev_level) != 1 ||
25 (LITTLE_TO_HOST32(m_pSuperblock->s_feature_compat) & 4) ||
26 (LITTLE_TO_HOST32(m_pSuperblock->s_feature_incompat) & ~uint32_t(2)) ||
27 (LITTLE_TO_HOST32(m_pSuperblock->s_feature_ro_compat) & ~uint32_t(7)))
28 return XattrStatus::Unsupported;
29 return XattrStatus::Success;
30}
31
32XattrStatus Ext2Filesystem::readAttributeBlockLocked(Inode* inode, AttributeRetirement& plan) {
33 plan.filesystem = this;
34 plan.block = LITTLE_TO_HOST32(inode->i_file_acl);
35 if (!plan.block)
36 return XattrStatus::Success;
37 const uint32_t first = LITTLE_TO_HOST32(m_pSuperblock->s_first_data_block);
38 const uint32_t perGroup = LITTLE_TO_HOST32(m_pSuperblock->s_blocks_per_group);
39 if (plan.block <= first || plan.block >= LITTLE_TO_HOST32(m_pSuperblock->s_blocks_count) ||
40 !perGroup || (plan.block - first) / perGroup >= m_nGroupDescriptors)
41 return XattrStatus::IoError;
42 // Never interpret filesystem allocation metadata as a writable EA payload.
43 const uint32_t descriptorEnd =
44 first + 1 + (m_nGroupDescriptors * sizeof(GroupDesc) + m_BlockSize - 1) / m_BlockSize;
45 if (plan.block < descriptorEnd)
46 return XattrStatus::IoError;
47 const uint32_t tableBlocks =
48 (static_cast<uint64_t>(LITTLE_TO_HOST32(m_pSuperblock->s_inodes_per_group)) * m_InodeSize +
49 m_BlockSize - 1) /
51 for (size_t n = 0; n < m_nGroupDescriptors; ++n) {
52 const auto* descriptor = m_pGroupDescriptors[n];
53 const uint32_t table = LITTLE_TO_HOST32(descriptor->bg_inode_table);
54 if (plan.block == LITTLE_TO_HOST32(descriptor->bg_block_bitmap) ||
55 plan.block == LITTLE_TO_HOST32(descriptor->bg_inode_bitmap) ||
56 (plan.block >= table && plan.block - table < tableBlocks))
57 return XattrStatus::IoError;
58 }
59 plan.buffer = readBlock(plan.block);
60 if (!plan.buffer)
61 return XattrStatus::IoError;
62 return Ext2Ea::validate(reinterpret_cast<void*>(plan.buffer), m_BlockSize);
63}
64
65XattrStatus Ext2Filesystem::prepareAttributeRetirementLocked(Inode* inode,
66 AttributeRetirement& plan) {
67 auto status = readAttributeBlockLocked(inode, plan);
68 if (status != XattrStatus::Success || !plan.block)
69 return status;
70 if (!prepareBlockReleaseLocked(plan.block))
71 return XattrStatus::IoError;
72 const uint32_t relative = plan.block - LITTLE_TO_HOST32(m_pSuperblock->s_first_data_block);
73 const uint32_t perGroup = LITTLE_TO_HOST32(m_pSuperblock->s_blocks_per_group);
74 const uint32_t index = relative % perGroup;
75 const auto& bitmap = m_pBlockBitmaps[relative / perGroup];
76 const auto* byte = reinterpret_cast<const uint8_t*>(bitmap[(index / 8) / m_BlockSize]);
77 if (!(byte[(index / 8) % m_BlockSize] & (1U << (index % 8))))
78 return XattrStatus::IoError;
79 return XattrStatus::Success;
80}
81
82XattrStatus Ext2Filesystem::reserveAttributeWritesLocked(size_t additional) {
83 if (additional > MaximumAttributeWrites)
84 return XattrStatus::NoSpace;
85 if (m_AttributeWriteCount + additional > MaximumAttributeWrites && !flushAttributeWritesLocked())
86 return XattrStatus::IoError;
87 return XattrStatus::Success;
88}
89
90void Ext2Filesystem::recordAttributeWriteLocked(uint64_t location, AttributeWriteKind kind,
91 bool adoptPin) {
92 for (size_t n = 0; n < m_AttributeWriteCount; ++n) {
93 if (m_AttributeWrites[n].location != location)
94 continue;
95 if (adoptPin) {
96 if (m_AttributeWrites[n].ownsPin)
97 m_pDisk->unpin(location);
98 else
99 m_AttributeWrites[n].ownsPin = true;
100 }
101 return;
102 }
103 assert(m_AttributeWriteCount < MaximumAttributeWrites);
104 m_AttributeWrites[m_AttributeWriteCount++] = {location, kind, adoptPin};
105}
106
107void Ext2Filesystem::recordAttributeAllocationLocked(uint32_t block) {
108 const uint32_t first = LITTLE_TO_HOST32(m_pSuperblock->s_first_data_block);
109 const uint32_t perGroup = LITTLE_TO_HOST32(m_pSuperblock->s_blocks_per_group);
110 const uint32_t group = (block - first) / perGroup;
111 const uint32_t index = (block - first) % perGroup;
112 const uint32_t bitmap =
113 LITTLE_TO_HOST32(m_pGroupDescriptors[group]->bg_block_bitmap) + (index / 8) / m_BlockSize;
114 recordAttributeWriteLocked(static_cast<uint64_t>(bitmap) * m_BlockSize,
115 AttributeWriteKind::Allocation);
116 const uint32_t descriptor = first + 1 + (group * sizeof(GroupDesc)) / m_BlockSize;
117 recordAttributeWriteLocked(static_cast<uint64_t>(descriptor) * m_BlockSize,
118 AttributeWriteKind::Allocation);
119 recordAttributeWriteLocked(1024, AttributeWriteKind::Allocation);
120}
121
122void Ext2Filesystem::recordAttributeInodeLocked(uint32_t inode) {
123 const uint32_t perGroup = LITTLE_TO_HOST32(m_pSuperblock->s_inodes_per_group);
124 const uint32_t group = (inode - 1) / perGroup, index = (inode - 1) % perGroup;
125 const uint32_t block = LITTLE_TO_HOST32(m_pGroupDescriptors[group]->bg_inode_table) +
126 (static_cast<uint64_t>(index) * m_InodeSize) / m_BlockSize;
127 recordAttributeWriteLocked(static_cast<uint64_t>(block) * m_BlockSize, AttributeWriteKind::Inode);
128}
129
130void Ext2Filesystem::forgetAttributeBlockLocked(uint32_t block) {
131 const uint64_t location = static_cast<uint64_t>(block) * m_BlockSize;
132 for (size_t n = 0; n < m_AttributeWriteCount; ++n) {
133 if (m_AttributeWrites[n].location != location)
134 continue;
135 assert(m_AttributeWrites[n].kind == AttributeWriteKind::Payload);
136 if (m_AttributeWrites[n].ownsPin)
137 m_pDisk->unpin(location);
138 m_AttributeWrites[n] = m_AttributeWrites[--m_AttributeWriteCount];
139 return;
140 }
141}
142
143bool Ext2Filesystem::flushAttributeWritesLocked() {
144 const AttributeWriteKind order[] = {AttributeWriteKind::Payload, AttributeWriteKind::Allocation,
145 AttributeWriteKind::Inode};
146 for (auto kind : order) {
147 for (size_t n = 0; n < m_AttributeWriteCount; ++n) {
148 if (m_AttributeWrites[n].kind == kind && !m_pDisk->sync(m_AttributeWrites[n].location, false))
149 return false;
150 }
151 }
152 for (size_t n = 0; n < m_AttributeWriteCount; ++n) {
153 if (m_AttributeWrites[n].ownsPin)
154 m_pDisk->unpin(m_AttributeWrites[n].location);
155 }
156 m_AttributeWriteCount = 0;
157 return true;
158}
159
160void Ext2Filesystem::drainAttributeWrites() {
162 if (flushAttributeWritesLocked())
163 return;
164 ERROR("Ext2: attribute metadata writeback failed at filesystem teardown");
165 for (size_t n = 0; n < m_AttributeWriteCount; ++n) {
166 if (m_AttributeWrites[n].ownsPin)
167 m_pDisk->unpin(m_AttributeWrites[n].location);
168 }
169 m_AttributeWriteCount = 0;
170}
171
172XattrStatus Ext2Filesystem::allocateAttributeBlockLocked(uint32_t inode,
173 AttributeRetirement& plan) {
174 Vector<uint32_t> block;
175 if (!block.tryReserve(1))
176 return XattrStatus::NoMemory;
177 const uint32_t perGroup = LITTLE_TO_HOST32(m_pSuperblock->s_inodes_per_group);
178 if (!inode || !perGroup || !m_nGroupDescriptors)
179 return XattrStatus::IoError;
180 const size_t start = ((inode - 1) / perGroup) % m_nGroupDescriptors;
181 for (size_t n = 0; n < m_nGroupDescriptors; ++n) {
182 const size_t group = (start + n) % m_nGroupDescriptors;
183 if (!LITTLE_TO_HOST16(m_pGroupDescriptors[group]->bg_free_blocks_count))
184 continue;
185 if (!ensureFreeBlockBitmapLoaded(group))
186 return XattrStatus::IoError;
187 if (!findFreeBlocksInGroup(group, 1, block))
188 continue;
189 plan.filesystem = this;
190 plan.block = block[0];
191 plan.buffer = readBlock(plan.block);
192 if (!plan.buffer) {
193 releaseBlockLocked(plan.block);
194 plan.block = 0;
195 return XattrStatus::IoError;
196 }
197 plan.provisional = true;
198 return XattrStatus::Success;
199 }
200 return XattrStatus::NoSpace;
201}
202
203void Ext2Filesystem::commitAttributeRetirementLocked(AttributeRetirement& plan) {
204 if (!plan.block)
205 return;
206 auto* header = reinterpret_cast<Ext2Ea::Header*>(plan.buffer);
207 const uint32_t references = LITTLE_TO_HOST32(header->references);
208 assert(references);
209 if (references == 1) {
210 forgetAttributeBlockLocked(plan.block);
211 releaseBlockLocked(plan.block);
212 recordAttributeAllocationLocked(plan.block);
213 } else {
214 header->references = HOST_TO_LITTLE32(references - 1);
215 writeBlock(plan.block);
216 recordAttributeWriteLocked(static_cast<uint64_t>(plan.block) * m_BlockSize,
217 AttributeWriteKind::Payload, true);
218 plan.buffer = 0;
219 }
220}
virtual bool sync(uint64_t location, bool async)
Definition Disk.cc:358
virtual void unpin(uint64_t location)=0
size_t m_nGroupDescriptors
GroupDesc ** m_pGroupDescriptors
void writeBlock(uint32_t block)
uintptr_t readBlock(uint32_t block)
Vector< size_t > * m_pBlockBitmaps
void releaseBlockLocked(uint32_t block, uint32_t inode=0)
size_t findFreeBlocksInGroup(uint32_t group, size_t maxCount, Vector< uint32_t > &blocks)
Superblock * m_pSuperblock
Disk * m_pDisk
Definition Filesystem.h:180
A vector / dynamic array.
Definition Vector.h:33
#define assert(x)
Definition assert.h:39
Definition ext2.h:152