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) {
34 if (!m_pExt2Fs->tryAcquireOperation(operation)) {
35 return XattrStatus::IoError;
36 }
37 auto replacement = UniqueArray<uint8_t>::allocate(m_pExt2Fs->m_BlockSize);
38 if (!replacement)
39 return XattrStatus::NoMemory;
40 LockGuard<Mutex> inodeGuard(m_State->writebackLock);
41 LockGuard<Mutex> allocationGuard(m_pExt2Fs->m_WriteLock);
42 if (m_State->quotaFile)
43 return XattrStatus::Denied;
44 if (m_pExt2Fs->isReadOnly())
45 return XattrStatus::ReadOnly;
46 if (!m_State->allocationValid)
47 return XattrStatus::IoError;
48 const auto quotaStatus = m_pExt2Fs->prepareQuotaInodeLocked(m_InodeNumber);
49 if (quotaStatus != QuotaStatus::Success)
50 return quotaAttributeStatus(quotaStatus);
51 auto status = m_pExt2Fs->attributeFormatStatus();
52 if (status != XattrStatus::Success)
53 return status;
55 status = m_pExt2Fs->prepareAttributeRetirementLocked(m_pInode, old);
56 if (status != XattrStatus::Success)
57 return status;
58 bool empty = false;
59 status = Ext2Ea::rebuild(reinterpret_cast<const void*>(old.buffer), m_pExt2Fs->m_BlockSize, name,
60 value, length, flags, remove, replacement.get(), empty);
61 if (status != XattrStatus::Success)
62 return status;
63 uint32_t sectors = 0;
64 if (!encodeAllocation(m_State->allocatedDataBlocks, m_nMetadataBlocks, !empty,
65 m_pExt2Fs->m_BlockSize, sectors))
66 return XattrStatus::NoSpace;
67 if (!m_pExt2Fs->prepareInodeWrite(m_InodeNumber))
68 return XattrStatus::IoError;
69 // At most two payloads, two allocation groups, the superblock and inode change.
70 status = m_pExt2Fs->reserveAttributeWritesLocked(12);
71 if (status != XattrStatus::Success)
72 return status;
74 auto* destination = &old;
75 if (!empty &&
76 (!old.block ||
77 LITTLE_TO_HOST32(reinterpret_cast<const Ext2Ea::Header*>(old.buffer)->references) > 1)) {
78 status = m_pExt2Fs->allocateAttributeBlockLocked(m_InodeNumber, allocated);
79 if (status != XattrStatus::Success)
80 return status;
81 destination = &allocated;
82 }
83 const uint32_t newBlock = empty ? 0 : destination->block;
84 if (newBlock && !old.block) {
85 const auto charged = m_pExt2Fs->m_Quota.reserve(m_InodeNumber, m_pExt2Fs->m_BlockSize);
86 if (charged != QuotaStatus::Success)
87 return quotaAttributeStatus(charged);
88 }
89 if (!empty) {
90 MemoryCopy(reinterpret_cast<void*>(destination->buffer), replacement.get(),
91 m_pExt2Fs->m_BlockSize);
92 m_pExt2Fs->writeBlock(newBlock);
93 m_pExt2Fs->recordAttributeWriteLocked(static_cast<uint64_t>(newBlock) * m_pExt2Fs->m_BlockSize,
94 Ext2Filesystem::AttributeWriteKind::Payload, true);
95 destination->buffer = 0;
96 destination->provisional = false;
97 if (destination == &allocated)
98 m_pExt2Fs->recordAttributeAllocationLocked(newBlock);
99 }
100 m_pInode->i_file_acl = HOST_TO_LITTLE32(newBlock);
101 m_pInode->i_blocks = HOST_TO_LITTLE32(sectors);
102 m_pInode->i_ctime = HOST_TO_LITTLE32(Time::getTime());
103 m_pExt2Fs->m_pSuperblock->s_feature_compat |= HOST_TO_LITTLE32(EXT2_FEATURE_COMPAT_EXT_ATTR);
104 m_pExt2Fs->getDisk()->write(1024);
105 m_pExt2Fs->recordAttributeWriteLocked(1024, Ext2Filesystem::AttributeWriteKind::Allocation);
106 m_pExt2Fs->writeInode(m_InodeNumber);
107 m_pExt2Fs->recordAttributeInodeLocked(m_InodeNumber);
108 if (old.block && old.block != newBlock)
109 m_pExt2Fs->commitAttributeRetirementLocked(old);
110 if (old.block && !newBlock)
111 m_pExt2Fs->m_Quota.refund(m_InodeNumber, m_pExt2Fs->m_BlockSize);
112 return XattrStatus::Success;
113}
114
115XattrStatus Ext2Node::setXattr(const StringView& name, const void* value, size_t length,
116 unsigned flags) {
117 return changeXattr(name, value, length, flags, false);
118}
119XattrStatus Ext2Node::removeXattr(const StringView& name) {
120 return changeXattr(name, nullptr, 0, 0, true);
121}
122
123XattrStatus Ext2File::getExtendedAttribute(const StringView& name, void* output, size_t capacity,
124 size_t& required) {
125 return getXattr(name, output, capacity, required);
126}
127XattrStatus Ext2File::listExtendedAttributes(void* output, size_t capacity, size_t& required) {
128 return listXattrs(output, capacity, required);
129}
130XattrStatus Ext2File::setExtendedAttribute(const StringView& name, const void* value, size_t length,
131 unsigned flags) {
132 const auto status = setXattr(name, value, length, flags);
133 if (status == XattrStatus::Success)
134 publishEvent(FileEvents::Attributes);
135 return status;
136}
137XattrStatus Ext2File::removeExtendedAttribute(const StringView& name) {
138 const auto status = removeXattr(name);
139 if (status == XattrStatus::Success)
140 publishEvent(FileEvents::Attributes);
141 return status;
142}
143XattrStatus Ext2Directory::getExtendedAttribute(const StringView& name, void* output,
144 size_t capacity, size_t& required) {
145 return getXattr(name, output, capacity, required);
146}
147XattrStatus Ext2Directory::listExtendedAttributes(void* output, size_t capacity, size_t& required) {
148 return listXattrs(output, capacity, required);
149}
150XattrStatus Ext2Directory::setExtendedAttribute(const StringView& name, const void* value,
151 size_t length, unsigned flags) {
152 const auto status = setXattr(name, value, length, flags);
153 if (status == XattrStatus::Success)
154 publishEvent(FileEvents::Attributes);
155 return status;
156}
157XattrStatus Ext2Directory::removeExtendedAttribute(const StringView& name) {
158 const auto status = removeXattr(name);
159 if (status == XattrStatus::Success)
160 publishEvent(FileEvents::Attributes);
161 return status;
162}
163XattrStatus Ext2Symlink::getExtendedAttribute(const StringView& name, void* output, size_t capacity,
164 size_t& required) {
165 return getXattr(name, output, capacity, required);
166}
167XattrStatus Ext2Symlink::listExtendedAttributes(void* output, size_t capacity, size_t& required) {
168 return listXattrs(output, capacity, required);
169}
170XattrStatus Ext2Symlink::setExtendedAttribute(const StringView& name, const void* value,
171 size_t length, unsigned flags) {
172 const auto status = setXattr(name, value, length, flags);
173 if (status == XattrStatus::Success)
174 publishEvent(FileEvents::Attributes);
175 return status;
176}
177XattrStatus Ext2Symlink::removeExtendedAttribute(const StringView& name) {
178 const auto status = removeXattr(name);
179 if (status == XattrStatus::Success)
180 publishEvent(FileEvents::Attributes);
181 return status;
182}
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:861
Disk * getDisk()
Definition Filesystem.h:145
bool isReadOnly()
Definition Filesystem.h:150