The Pedigree Project 0.1
Ext2Quota.cc
1/* Copyright (c) 2026, Pedigree Developers. */
2#include "pedigree/kernel/utilities/assert.h"
3
4#include "Ext2Quota.h"
5
6Ext2QuotaLedger::~Ext2QuotaLedger() {
7 for (auto it = m_Inodes.begin(); it != m_Inodes.end(); ++it)
8 delete it.value();
9}
10
11Ext2QuotaLedger::InodeCharge* Ext2QuotaLedger::find(uint32_t inode) const {
12 return m_Inodes.lookup(inode);
13}
14
15QuotaStatus Ext2QuotaLedger::prepareCharge(uint32_t uid, uint32_t gid, uint64_t bytes,
16 uint64_t inodes, bool enforce) {
17 const uint32_t ids[] = {uid, gid};
18 for (size_t type = 0; type < 2; ++type) {
19 if (!m_Enabled[type])
20 continue;
21 if (ids[type] > m_MaximumId[type])
22 return QuotaStatus::Overflow;
23 auto* entry = m_Tables[type].prepare(ids[type]);
24 if (!entry)
25 return QuotaStatus::NoMemory;
26 const auto status = QuotaTable::canCharge(*entry, bytes, inodes, enforce);
27 if (status != QuotaStatus::Success)
28 return status;
29 }
30 return QuotaStatus::Success;
31}
32
33void Ext2QuotaLedger::charge(uint32_t uid, uint32_t gid, uint64_t bytes, uint64_t inodes) {
34 const uint32_t ids[] = {uid, gid};
35 for (size_t type = 0; type < 2; ++type) {
36 if (m_Enabled[type])
37 QuotaTable::charge(*m_Tables[type].find(ids[type]), bytes, inodes);
38 }
39}
40
41void Ext2QuotaLedger::refund(uint32_t uid, uint32_t gid, uint64_t bytes, uint64_t inodes) {
42 const uint32_t ids[] = {uid, gid};
43 for (size_t type = 0; type < 2; ++type) {
44 if (m_Enabled[type])
45 QuotaTable::refund(*m_Tables[type].find(ids[type]), bytes, inodes);
46 }
47}
48
49QuotaStatus Ext2QuotaLedger::track(uint32_t inode, uint32_t uid, uint32_t gid, uint64_t bytes) {
50 if (find(inode))
51 return QuotaStatus::Success;
52 const auto status = prepareCharge(uid, gid, bytes, 1, false);
53 if (status != QuotaStatus::Success)
54 return status;
55 auto* entry = new InodeCharge;
56 if (!entry)
57 return QuotaStatus::NoMemory;
58 entry->uid = uid;
59 entry->gid = gid;
60 entry->bytes = bytes;
61 if (!m_Inodes.tryInsert(inode, entry)) {
62 delete entry;
63 return QuotaStatus::NoMemory;
64 }
65 charge(uid, gid, bytes, 1);
66 return QuotaStatus::Success;
67}
68
69QuotaStatus Ext2QuotaLedger::create(uint32_t inode, uint32_t uid, uint32_t gid) {
70 assert(!find(inode));
71 const auto status = prepareCharge(uid, gid, 0, 1, true);
72 return status == QuotaStatus::Success ? track(inode, uid, gid, 0) : status;
73}
74
75QuotaStatus Ext2QuotaLedger::reserve(uint32_t inode, uint64_t bytes) {
76 auto* entry = find(inode);
77 assert(entry);
78 if (bytes > ~uint64_t(0) - entry->bytes)
79 return QuotaStatus::Overflow;
80 if (!entry->exempt) {
81 const auto status = prepareCharge(entry->uid, entry->gid, bytes, 0, true);
82 if (status != QuotaStatus::Success)
83 return status;
84 charge(entry->uid, entry->gid, bytes, 0);
85 }
86 entry->bytes += bytes;
87 return QuotaStatus::Success;
88}
89
90void Ext2QuotaLedger::refund(uint32_t inode, uint64_t bytes) {
91 auto* entry = find(inode);
92 // Before the first enable, an untouched inode needs no overlay: trim updates
93 // i_blocks and releases the same blocks under this allocation lock.
94 if (!entry)
95 return;
96 assert(bytes <= entry->bytes);
97 if (!entry->exempt)
98 refund(entry->uid, entry->gid, bytes, 0);
99 entry->bytes -= bytes;
100}
101
102void Ext2QuotaLedger::forget(uint32_t inode) {
103 auto* entry = find(inode);
104 if (!entry)
105 return;
106 if (!entry->exempt)
107 refund(entry->uid, entry->gid, entry->bytes, 1);
108 m_Inodes.remove(inode);
109 delete entry;
110}
111
112QuotaStatus Ext2QuotaLedger::transfer(uint32_t inode, uint32_t uid, uint32_t gid) {
113 auto* entry = find(inode);
114 assert(entry);
115 const uint32_t oldIds[] = {entry->uid, entry->gid}, newIds[] = {uid, gid};
116 if (!entry->exempt) {
117 for (size_t type = 0; type < 2; ++type) {
118 if (!m_Enabled[type] || oldIds[type] == newIds[type])
119 continue;
120 if (newIds[type] > m_MaximumId[type])
121 return QuotaStatus::Overflow;
122 auto* destination = m_Tables[type].prepare(newIds[type]);
123 if (!destination)
124 return QuotaStatus::NoMemory;
125 const auto status = QuotaTable::canCharge(*destination, entry->bytes, 1, true);
126 if (status != QuotaStatus::Success)
127 return status;
128 }
129 for (size_t type = 0; type < 2; ++type) {
130 if (!m_Enabled[type] || oldIds[type] == newIds[type])
131 continue;
132 QuotaTable::refund(*m_Tables[type].find(oldIds[type]), entry->bytes, 1);
133 QuotaTable::charge(*m_Tables[type].find(newIds[type]), entry->bytes, 1);
134 }
135 }
136 entry->uid = uid;
137 entry->gid = gid;
138 return QuotaStatus::Success;
139}
140
141QuotaStatus Ext2QuotaLedger::exempt(uint32_t inode, bool exempt) {
142 auto* entry = find(inode);
143 assert(entry);
144 if (entry->exempt == exempt)
145 return QuotaStatus::Success;
146 if (exempt) {
147 refund(entry->uid, entry->gid, entry->bytes, 1);
148 } else {
149 // Enable retains even exempt owners' empty accounts, so ending an
150 // exemption cannot fail after quota-file protection has been published.
151 charge(entry->uid, entry->gid, entry->bytes, 1);
152 }
153 entry->exempt = exempt;
154 return QuotaStatus::Success;
155}
156
157QuotaStatus Ext2QuotaLedger::enable(QuotaType type, QuotaTable& loaded, uint32_t maximumId) {
158 const size_t index = static_cast<size_t>(type);
159 if (m_Enabled[index])
160 return QuotaStatus::Busy;
161 for (auto& entry : loaded.entries()) {
162 entry.record.currentSpace = 0;
163 entry.record.currentInodes = 0;
164 entry.dirty = true;
165 }
166 for (auto it = m_Inodes.begin(); it != m_Inodes.end(); ++it) {
167 const auto& usage = *it.value();
168 if ((index ? usage.gid : usage.uid) > maximumId)
169 return QuotaStatus::Overflow;
170 auto* destination = loaded.prepare(index ? usage.gid : usage.uid);
171 if (!destination)
172 return QuotaStatus::NoMemory;
173 if (usage.exempt)
174 continue;
175 const auto status = QuotaTable::canCharge(*destination, usage.bytes, 1, false);
176 if (status != QuotaStatus::Success)
177 return status;
178 QuotaTable::charge(*destination, usage.bytes, 1);
179 }
180 m_Tables[index].swap(loaded);
181 m_MaximumId[index] = maximumId;
182 m_Enabled[index] = true;
183 return QuotaStatus::Success;
184}
185
186void Ext2QuotaLedger::disable(QuotaType type) {
187 const size_t index = static_cast<size_t>(type);
188 m_Enabled[index] = false;
189 m_Tables[index].clear();
190}
191
192bool Ext2QuotaLedger::enabled(QuotaType type) const {
193 return m_Enabled[static_cast<size_t>(type)];
194}
195
196QuotaTable& Ext2QuotaLedger::table(QuotaType type) {
197 return m_Tables[static_cast<size_t>(type)];
198}
#define assert(x)
Definition assert.h:39