The Pedigree Project 0.1
Ext2Xattr.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "Ext2Xattr.h"
3#include "pedigree/kernel/LockGuard.h"
4#include "pedigree/kernel/panic.h"
5#include "pedigree/kernel/utilities/utility.h"
6
7#include "Ext2Filesystem.h"
8#include "Ext2Node.h"
9#include "ext2.h"
10
11namespace Ext2Ea {
12namespace {
13size_t align4(size_t size) {
14 return (size + 3) & ~size_t(3);
15}
16const Entry* entryAt(const void* block, size_t offset) {
17 return reinterpret_cast<const Entry*>(static_cast<const uint8_t*>(block) + offset);
18}
19int compare(unsigned index, const char* name, size_t length, const Entry& entry) {
20 if (index != entry.nameIndex)
21 return index < entry.nameIndex ? -1 : 1;
22 if (length != entry.nameLength)
23 return length < entry.nameLength ? -1 : 1;
24 const auto* other = reinterpret_cast<const uint8_t*>(&entry + 1);
25 for (size_t n = 0; n < length; ++n) {
26 const auto character = static_cast<uint8_t>(name[n]);
27 if (character != other[n])
28 return character < other[n] ? -1 : 1;
29 }
30 return 0;
31}
32bool last(const Entry* entry) {
33 uint32_t end;
34 MemoryCopy(&end, entry, sizeof(end));
35 return !end;
36}
37XattrStatus suffix(const StringView& name) {
38 if (!name.length() || name.length() > Xattr::MaximumNameLength)
39 return XattrStatus::Range;
40 for (size_t n = 0; n < name.length(); ++n) {
41 if (!name[n])
42 return XattrStatus::Invalid;
43 }
44 if (name.length() < 5 || !name.substring(0, 5).compare("user.", 5))
45 return XattrStatus::Unsupported;
46 return name.length() == 5 ? XattrStatus::Invalid : XattrStatus::Success;
47}
48uint32_t entryHash(const Entry& entry, const uint8_t* value) {
49 uint32_t hash = 0;
50 const auto* name = reinterpret_cast<const uint8_t*>(&entry + 1);
51 for (size_t n = 0; n < entry.nameLength; ++n)
52 hash = (hash << 5) ^ (hash >> 27) ^ name[n];
53 for (size_t n = 0; n < align4(LITTLE_TO_HOST32(entry.valueLength)); n += 4) {
54 uint32_t word;
55 MemoryCopy(&word, value + n, sizeof(word));
56 hash = (hash << 16) ^ (hash >> 16) ^ LITTLE_TO_HOST32(word);
57 }
58 return hash;
59}
60} // namespace
61
62XattrStatus validate(const void* block, size_t size) {
63 if (!block)
64 return XattrStatus::Success;
65 if (size < sizeof(Header) + 4 || size > 4096)
66 return XattrStatus::IoError;
67 const auto* header = static_cast<const Header*>(block);
68 if (LITTLE_TO_HOST32(header->magic) != Magic || LITTLE_TO_HOST32(header->blocks) != 1 ||
69 !LITTLE_TO_HOST32(header->references) || LITTLE_TO_HOST32(header->references) > 1024)
70 return XattrStatus::IoError;
71 for (uint32_t reserved : header->reserved) {
72 if (reserved)
73 return XattrStatus::Unsupported;
74 }
75 size_t cursor = sizeof(Header), valuesBegin = size;
76 const Entry* previous = nullptr;
77 while (cursor <= size - 4) {
78 const Entry* entry = entryAt(block, cursor);
79 if (last(entry))
80 return cursor + 4 <= valuesBegin ? XattrStatus::Success : XattrStatus::IoError;
81 if (size - cursor < sizeof(Entry))
82 return XattrStatus::IoError;
83 const size_t span = align4(sizeof(Entry) + entry->nameLength);
84 if (span > size - cursor - 4)
85 return XattrStatus::IoError;
86 if (!entry->nameIndex)
87 return XattrStatus::IoError;
88 if (entry->valueBlock)
89 return XattrStatus::Unsupported;
90 if (entry->nameIndex == User) {
91 if (!entry->nameLength || entry->nameLength > Xattr::MaximumNameLength - 5)
92 return XattrStatus::IoError;
93 const auto* name = reinterpret_cast<const uint8_t*>(entry + 1);
94 for (size_t n = 0; n < entry->nameLength; ++n) {
95 if (!name[n])
96 return XattrStatus::IoError;
97 }
98 }
99 if (previous && compare(previous->nameIndex, reinterpret_cast<const char*>(previous + 1),
100 previous->nameLength, *entry) >= 0)
101 return XattrStatus::IoError;
102 const size_t length = LITTLE_TO_HOST32(entry->valueLength);
103 const size_t offset = LITTLE_TO_HOST16(entry->valueOffset);
104 if (length > size || offset > size || length > size - offset ||
105 (length && ((offset & 3) || align4(length) > size - offset)))
106 return XattrStatus::IoError;
107 if (length) {
108 for (size_t prior = sizeof(Header); prior < cursor;) {
109 const auto* other = entryAt(block, prior);
110 const size_t otherLength = LITTLE_TO_HOST32(other->valueLength);
111 const size_t otherOffset = LITTLE_TO_HOST16(other->valueOffset);
112 if (otherLength && offset < otherOffset + align4(otherLength) &&
113 otherOffset < offset + align4(length))
114 return XattrStatus::IoError;
115 prior += align4(sizeof(Entry) + other->nameLength);
116 }
117 if (offset < valuesBegin)
118 valuesBegin = offset;
119 }
120 previous = entry;
121 cursor += span;
122 }
123 return XattrStatus::IoError;
124}
125
126XattrStatus get(const void* block, size_t size, const StringView& name, void* output,
127 size_t capacity, size_t& required) {
128 required = 0;
129 auto status = suffix(name);
130 if (status != XattrStatus::Success)
131 return status;
132 status = validate(block, size);
133 if (status != XattrStatus::Success || !block)
134 return status == XattrStatus::Success ? XattrStatus::Missing : status;
135 for (size_t cursor = sizeof(Header); !last(entryAt(block, cursor));) {
136 const Entry* entry = entryAt(block, cursor);
137 if (!compare(User, name.str() + 5, name.length() - 5, *entry)) {
138 required = LITTLE_TO_HOST32(entry->valueLength);
139 if (capacity && capacity < required)
140 return XattrStatus::Range;
141 if (capacity && required && !output)
142 return XattrStatus::Invalid;
143 if (capacity && required)
144 MemoryCopy(output,
145 static_cast<const uint8_t*>(block) + LITTLE_TO_HOST16(entry->valueOffset),
146 required);
147 return XattrStatus::Success;
148 }
149 cursor += align4(sizeof(Entry) + entry->nameLength);
150 }
151 return XattrStatus::Missing;
152}
153
154XattrStatus list(const void* block, size_t size, void* output, size_t capacity, size_t& required) {
155 required = 0;
156 if (capacity && !output)
157 return XattrStatus::Invalid;
158 auto status = validate(block, size);
159 if (status != XattrStatus::Success || !block)
160 return status;
161 for (size_t cursor = sizeof(Header); !last(entryAt(block, cursor));) {
162 const Entry* entry = entryAt(block, cursor);
163 if (entry->nameIndex == User)
164 required += 6 + entry->nameLength;
165 cursor += align4(sizeof(Entry) + entry->nameLength);
166 }
167 if (capacity && capacity < required)
168 return XattrStatus::Range;
169 if (!capacity)
170 return XattrStatus::Success;
171 auto* destination = static_cast<char*>(output);
172 for (size_t cursor = sizeof(Header); !last(entryAt(block, cursor));) {
173 const Entry* entry = entryAt(block, cursor);
174 if (entry->nameIndex == User) {
175 MemoryCopy(destination, "user.", 5);
176 MemoryCopy(destination + 5, entry + 1, entry->nameLength);
177 destination[5 + entry->nameLength] = 0;
178 destination += 6 + entry->nameLength;
179 }
180 cursor += align4(sizeof(Entry) + entry->nameLength);
181 }
182 return XattrStatus::Success;
183}
184
185XattrStatus rebuild(const void* oldBlock, size_t size, const StringView& name, const void* value,
186 size_t length, unsigned flags, bool remove, void* replacement, bool& empty) {
187 auto status = suffix(name);
188 if (status != XattrStatus::Success)
189 return status;
190 if (flags & ~(Xattr::Create | Xattr::Replace))
191 return XattrStatus::Invalid;
192 if (length > size)
193 return XattrStatus::Range;
194 if (!replacement || (length && !value))
195 return XattrStatus::Invalid;
196 status = validate(oldBlock, size);
197 if (status != XattrStatus::Success)
198 return status;
199 bool exists = false;
200 if (oldBlock) {
201 for (size_t cursor = sizeof(Header); !last(entryAt(oldBlock, cursor));) {
202 const auto* entry = entryAt(oldBlock, cursor);
203 exists |= !compare(User, name.str() + 5, name.length() - 5, *entry);
204 cursor += align4(sizeof(Entry) + entry->nameLength);
205 }
206 }
207 if (exists && (flags & Xattr::Create))
208 return XattrStatus::Exists;
209 if (!exists && (remove || (flags & Xattr::Replace)))
210 return XattrStatus::Missing;
211 ByteSet(replacement, 0, size);
212 auto* header = static_cast<Header*>(replacement);
213 header->magic = HOST_TO_LITTLE32(Magic);
214 header->blocks = header->references = HOST_TO_LITTLE32(1);
215 size_t cursor = sizeof(Header), valueEnd = size;
216 uint32_t hash = 0;
217 bool unshareable = false;
218 auto emit = [&](unsigned index, const char* entryName, size_t nameLength, const void* bytes,
219 size_t valueLength) {
220 const size_t entrySize = align4(sizeof(Entry) + nameLength), valueSize = align4(valueLength);
221 if (valueSize > valueEnd || cursor + entrySize + 4 > valueEnd - valueSize)
222 return false;
223 valueEnd -= valueSize;
224 auto* entry = reinterpret_cast<Entry*>(static_cast<uint8_t*>(replacement) + cursor);
225 entry->nameIndex = index;
226 entry->nameLength = nameLength;
227 entry->valueOffset = HOST_TO_LITTLE16(valueLength ? valueEnd : 0);
228 entry->valueLength = HOST_TO_LITTLE32(valueLength);
229 MemoryCopy(entry + 1, entryName, nameLength);
230 auto* destination = static_cast<uint8_t*>(replacement) + valueEnd;
231 if (valueLength)
232 MemoryCopy(destination, bytes, valueLength);
233 const uint32_t entryValueHash = entryHash(*entry, destination);
234 entry->hash = HOST_TO_LITTLE32(entryValueHash);
235 unshareable |= !entryValueHash;
236 hash = (hash << 16) ^ (hash >> 16) ^ entryValueHash;
237 cursor += entrySize;
238 return true;
239 };
240 bool inserted = remove;
241 if (oldBlock) {
242 for (size_t offset = sizeof(Header); !last(entryAt(oldBlock, offset));) {
243 const auto* entry = entryAt(oldBlock, offset);
244 const int order = compare(User, name.str() + 5, name.length() - 5, *entry);
245 if (!inserted && order <= 0) {
246 if (!emit(User, name.str() + 5, name.length() - 5, value, length))
247 return XattrStatus::NoSpace;
248 inserted = true;
249 }
250 if (order &&
251 !emit(entry->nameIndex, reinterpret_cast<const char*>(entry + 1), entry->nameLength,
252 static_cast<const uint8_t*>(oldBlock) + LITTLE_TO_HOST16(entry->valueOffset),
253 LITTLE_TO_HOST32(entry->valueLength)))
254 return XattrStatus::NoSpace;
255 offset += align4(sizeof(Entry) + entry->nameLength);
256 }
257 }
258 if (!inserted && !emit(User, name.str() + 5, name.length() - 5, value, length))
259 return XattrStatus::NoSpace;
260 header->hash = HOST_TO_LITTLE32(unshareable ? 0 : hash);
261 empty = cursor == sizeof(Header);
262 return XattrStatus::Success;
263}
264} // namespace Ext2Ea
265
266XattrStatus Ext2Node::getXattr(const StringView& name, void* output, size_t capacity,
267 size_t& required) {
268 OperationBarrier::Lease operation;
269 if (!m_pExt2Fs->tryAcquireOperation(operation)) {
270 return XattrStatus::IoError;
271 }
272 required = 0;
273 LockGuard<Mutex> inodeGuard(m_State->writebackLock);
274 LockGuard<Mutex> allocationGuard(m_pExt2Fs->m_WriteLock);
275 if (!m_State->allocationValid)
276 return XattrStatus::IoError;
277 auto status = m_pExt2Fs->attributeFormatStatus();
278 if (status != XattrStatus::Success)
279 return status;
281 status = m_pExt2Fs->readAttributeBlockLocked(m_pInode, block);
282 return status == XattrStatus::Success
283 ? Ext2Ea::get(reinterpret_cast<const void*>(block.buffer), m_pExt2Fs->m_BlockSize,
284 name, output, capacity, required)
285 : status;
286}
287XattrStatus Ext2Node::listXattrs(void* output, size_t capacity, size_t& required) {
288 OperationBarrier::Lease operation;
289 if (!m_pExt2Fs->tryAcquireOperation(operation)) {
290 return XattrStatus::IoError;
291 }
292 required = 0;
293 LockGuard<Mutex> inodeGuard(m_State->writebackLock);
294 LockGuard<Mutex> allocationGuard(m_pExt2Fs->m_WriteLock);
295 if (!m_State->allocationValid)
296 return XattrStatus::IoError;
297 auto status = m_pExt2Fs->attributeFormatStatus();
298 if (status != XattrStatus::Success)
299 return status;
301 status = m_pExt2Fs->readAttributeBlockLocked(m_pInode, block);
302 return status == XattrStatus::Success
303 ? Ext2Ea::list(reinterpret_cast<const void*>(block.buffer), m_pExt2Fs->m_BlockSize,
304 output, capacity, required)
305 : status;
306}
307
308bool Ext2Node::decodeAllocation(const Inode& inode, uint32_t blockSize, uint32_t& blocks,
309 bool& inlineSymlink) {
310 if (blockSize < 512 || blockSize > 4096 || (blockSize & (blockSize - 1)))
311 return false;
312 const uint32_t sectors = LITTLE_TO_HOST32(inode.i_blocks);
313 const uint32_t eaSectors = inode.i_file_acl ? blockSize / 512 : 0;
314 if (sectors < eaSectors || (sectors - eaSectors) % (blockSize / 512))
315 return false;
316 blocks = (sectors - eaSectors) / (blockSize / 512);
317 inlineSymlink = (LITTLE_TO_HOST16(inode.i_mode) & 0xf000) == EXT2_S_IFLNK && !blocks;
318 return !inlineSymlink || LITTLE_TO_HOST32(inode.i_size) <= sizeof(inode.i_block);
319}
320
321bool Ext2Node::encodeAllocation(uint32_t data, uint32_t indirect, bool hasEa, uint32_t blockSize,
322 uint32_t& sectors) {
323 const uint64_t count = (static_cast<uint64_t>(data) + indirect + hasEa) * (blockSize / 512);
324 if (count > 0xffffffffULL)
325 return false;
326 sectors = count;
327 return true;
328}
329
330bool Ext2Node::isInlineSymlink() const {
331 uint32_t blocks = 0;
332 bool inlineSymlink = false;
333 return decodeAllocation(*m_pInode, m_pExt2Fs->m_BlockSize, blocks, inlineSymlink) &&
334 inlineSymlink;
335}
336
337void Ext2Node::updateAllocatedSectorCount() {
338 uint32_t sectors;
339 const bool valid = encodeAllocation(m_State->allocatedDataBlocks, m_nMetadataBlocks,
340 m_pInode->i_file_acl != 0, m_pExt2Fs->m_BlockSize, sectors);
341 if (!valid) {
342 panic("Ext2: allocated sector count exceeds inode capacity");
343 }
344 m_pInode->i_blocks = HOST_TO_LITTLE32(sectors);
345}
Definition User.h:32
void EXPORTED_PUBLIC panic(const char *msg) NORETURN
Definition panic.cc:118
Definition ext2.h:152