The Pedigree Project 0.1
Ext2Xattr-write.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/LockGuard.h"
3#include "pedigree/kernel/machine/Disk.h"
4#include "pedigree/kernel/utilities/Pointers.h"
5#include "pedigree/kernel/utilities/utility.h"
6
7#include "Ext2Directory.h"
8#include "Ext2File.h"
9#include "Ext2Filesystem.h"
10#include "Ext2Symlink.h"
11#include "Ext2Xattr.h"
12#include "ext2.h"
13
14namespace {
15XattrStatus quotaAttributeStatus(QuotaStatus status) {
16 switch (status) {
17 case QuotaStatus::Success:
18 return XattrStatus::Success;
19 case QuotaStatus::Limit:
20 return XattrStatus::Quota;
21 case QuotaStatus::Overflow:
22 return XattrStatus::Overflow;
23 case QuotaStatus::NoMemory:
24 return XattrStatus::NoMemory;
25 default:
26 return XattrStatus::IoError;
27 }
28}
29} // namespace
30
31XattrStatus Ext2Node::changeXattr(const StringView& name, const void* value, size_t length,
32 unsigned flags, bool remove) {
33 auto replacement = UniqueArray<uint8_t>::allocate(m_pExt2Fs->m_BlockSize);
34 if (!replacement)
35 return XattrStatus::NoMemory;
36 LockGuard<Mutex> inodeGuard(m_State->writebackLock);
37 LockGuard<Mutex> allocationGuard(m_pExt2Fs->m_WriteLock);
38 if (m_State->quotaFile)
39 return XattrStatus::Denied;
40 if (m_pExt2Fs->isReadOnly())
41 return XattrStatus::ReadOnly;
42 if (!m_State->allocationValid)
43 return XattrStatus::IoError;
44 const auto quotaStatus = m_pExt2Fs->prepareQuotaInodeLocked(m_InodeNumber);
45 if (quotaStatus != QuotaStatus::Success)
46 return quotaAttributeStatus(quotaStatus);
47 auto status = m_pExt2Fs->attributeFormatStatus();
48 if (status != XattrStatus::Success)
49 return status;
51 status = m_pExt2Fs->prepareAttributeRetirementLocked(m_pInode, old);
52 if (status != XattrStatus::Success)
53 return status;
54 bool empty = false;
55 status = Ext2Ea::rebuild(reinterpret_cast<const void*>(old.buffer), m_pExt2Fs->m_BlockSize, name,
56 value, length, flags, remove, replacement.get(), empty);
57 if (status != XattrStatus::Success)
58 return status;
59 uint32_t sectors = 0;
60 if (!encodeAllocation(m_State->allocatedDataBlocks, m_nMetadataBlocks, !empty,
61 m_pExt2Fs->m_BlockSize, sectors))
62 return XattrStatus::NoSpace;
63 if (!m_pExt2Fs->prepareInodeWrite(m_InodeNumber))
64 return XattrStatus::IoError;
65 // At most two payloads, two allocation groups, the superblock and inode change.
66 status = m_pExt2Fs->reserveAttributeWritesLocked(12);
67 if (status != XattrStatus::Success)
68 return status;
70 auto* destination = &old;
71 if (!empty &&
72 (!old.block ||
73 LITTLE_TO_HOST32(reinterpret_cast<const Ext2Ea::Header*>(old.buffer)->references) > 1)) {
74 status = m_pExt2Fs->allocateAttributeBlockLocked(m_InodeNumber, allocated);
75 if (status != XattrStatus::Success)
76 return status;
77 destination = &allocated;
78 }
79 const uint32_t newBlock = empty ? 0 : destination->block;
80 if (newBlock && !old.block) {
81 const auto charged = m_pExt2Fs->m_Quota.reserve(m_InodeNumber, m_pExt2Fs->m_BlockSize);
82 if (charged != QuotaStatus::Success)
83 return quotaAttributeStatus(charged);
84 }
85 if (!empty) {
86 MemoryCopy(reinterpret_cast<void*>(destination->buffer), replacement.get(),
87 m_pExt2Fs->m_BlockSize);
88 m_pExt2Fs->writeBlock(newBlock);
89 m_pExt2Fs->recordAttributeWriteLocked(static_cast<uint64_t>(newBlock) * m_pExt2Fs->m_BlockSize,
90 Ext2Filesystem::AttributeWriteKind::Payload, true);
91 destination->buffer = 0;
92 destination->provisional = false;
93 if (destination == &allocated)
94 m_pExt2Fs->recordAttributeAllocationLocked(newBlock);
95 }
96 m_pInode->i_file_acl = HOST_TO_LITTLE32(newBlock);
97 m_pInode->i_blocks = HOST_TO_LITTLE32(sectors);
98 m_pInode->i_ctime = HOST_TO_LITTLE32(Time::getTime());
99 m_pExt2Fs->m_pSuperblock->s_feature_compat |= HOST_TO_LITTLE32(EXT2_FEATURE_COMPAT_EXT_ATTR);
100 m_pExt2Fs->getDisk()->write(1024);
101 m_pExt2Fs->recordAttributeWriteLocked(1024, Ext2Filesystem::AttributeWriteKind::Allocation);
102 m_pExt2Fs->writeInode(m_InodeNumber);
103 m_pExt2Fs->recordAttributeInodeLocked(m_InodeNumber);
104 if (old.block && old.block != newBlock)
105 m_pExt2Fs->commitAttributeRetirementLocked(old);
106 if (old.block && !newBlock)
107 m_pExt2Fs->m_Quota.refund(m_InodeNumber, m_pExt2Fs->m_BlockSize);
108 return XattrStatus::Success;
109}
110
111XattrStatus Ext2Node::setXattr(const StringView& name, const void* value, size_t length,
112 unsigned flags) {
113 return changeXattr(name, value, length, flags, false);
114}
115XattrStatus Ext2Node::removeXattr(const StringView& name) {
116 return changeXattr(name, nullptr, 0, 0, true);
117}
118
119XattrStatus Ext2File::getExtendedAttribute(const StringView& name, void* output, size_t capacity,
120 size_t& required) {
121 return getXattr(name, output, capacity, required);
122}
123XattrStatus Ext2File::listExtendedAttributes(void* output, size_t capacity, size_t& required) {
124 return listXattrs(output, capacity, required);
125}
126XattrStatus Ext2File::setExtendedAttribute(const StringView& name, const void* value, size_t length,
127 unsigned flags) {
128 const auto status = setXattr(name, value, length, flags);
129 if (status == XattrStatus::Success)
130 publishEvent(FileEvents::Attributes);
131 return status;
132}
133XattrStatus Ext2File::removeExtendedAttribute(const StringView& name) {
134 const auto status = removeXattr(name);
135 if (status == XattrStatus::Success)
136 publishEvent(FileEvents::Attributes);
137 return status;
138}
139XattrStatus Ext2Directory::getExtendedAttribute(const StringView& name, void* output,
140 size_t capacity, size_t& required) {
141 return getXattr(name, output, capacity, required);
142}
143XattrStatus Ext2Directory::listExtendedAttributes(void* output, size_t capacity, size_t& required) {
144 return listXattrs(output, capacity, required);
145}
146XattrStatus Ext2Directory::setExtendedAttribute(const StringView& name, const void* value,
147 size_t length, unsigned flags) {
148 const auto status = setXattr(name, value, length, flags);
149 if (status == XattrStatus::Success)
150 publishEvent(FileEvents::Attributes);
151 return status;
152}
153XattrStatus Ext2Directory::removeExtendedAttribute(const StringView& name) {
154 const auto status = removeXattr(name);
155 if (status == XattrStatus::Success)
156 publishEvent(FileEvents::Attributes);
157 return status;
158}
159XattrStatus Ext2Symlink::getExtendedAttribute(const StringView& name, void* output, size_t capacity,
160 size_t& required) {
161 return getXattr(name, output, capacity, required);
162}
163XattrStatus Ext2Symlink::listExtendedAttributes(void* output, size_t capacity, size_t& required) {
164 return listXattrs(output, capacity, required);
165}
166XattrStatus Ext2Symlink::setExtendedAttribute(const StringView& name, const void* value,
167 size_t length, unsigned flags) {
168 const auto status = setXattr(name, value, length, flags);
169 if (status == XattrStatus::Success)
170 publishEvent(FileEvents::Attributes);
171 return status;
172}
173XattrStatus Ext2Symlink::removeExtendedAttribute(const StringView& name) {
174 const auto status = removeXattr(name);
175 if (status == XattrStatus::Success)
176 publishEvent(FileEvents::Attributes);
177 return status;
178}
virtual void write(uint64_t location)
Definition Disk.cc:340
XattrStatus getExtendedAttribute(const StringView &, void *, size_t, size_t &) override
virtual XattrStatus getExtendedAttribute(const StringView &, void *, size_t, size_t &) override
void writeBlock(uint32_t block)
Superblock * m_pSuperblock
void publishEvent(FileEventMask mask, const StringView &name=StringView(), bool targetIsDirectory=false)
Definition File.cc:778
Disk * getDisk()
Definition Filesystem.h:137
bool isReadOnly()
Definition Filesystem.h:142