The Pedigree Project 0.1
Ext2Resize.cc
1/*
2 * Copyright (c) 2026, Pedigree Developers
3 *
4 * Permission to use, copy, modify, and distribute this software for any
5 * purpose with or without fee is hereby granted, provided that the above
6 * copyright notice and this permission notice appear in all copies.
7 */
8
9#include "pedigree/kernel/LockGuard.h"
10#include "pedigree/kernel/Log.h"
11#include "pedigree/kernel/syscallError.h"
12#include "pedigree/kernel/utilities/new"
13#include "pedigree/kernel/utilities/utility.h"
14
15#include "Ext2File.h"
16#include "Ext2Filesystem.h"
17#include "Ext2Node.h"
18#include "ext2.h"
19
20namespace {
21template <typename T>
22bool appendPrepared(Vector<T>& values, const T& value) {
23 if (values.count() == values.size() &&
24 !values.tryReserve(values.size() ? values.size() * 2 : 16)) {
25 SYSCALL_ERROR(OutOfMemory);
26 return false;
27 }
28 values.pushBack(value);
29 return true;
30}
31} // namespace
32
33Ext2Node::TrimPlan::TrimPlan(Ext2Filesystem& filesystem) : filesystem(filesystem) {}
34
35Ext2Node::TrimPlan::~TrimPlan() {
36 for (const MappingPage& page : pages) {
37 if (page.buffer)
38 filesystem.unpinBlock(page.block);
39 }
40}
41
42bool Ext2Node::collectMappingPages(uint32_t block, unsigned depth, size_t first, size_t span,
43 Vector<MappingPage>& pages) {
44 if (!block)
45 return true;
46 const uintptr_t buffer = m_pExt2Fs->readBlock(block);
47 if (!buffer) {
48 SYSCALL_ERROR(IoError);
49 return false;
50 }
51 if (!appendPrepared(pages, MappingPage{block, buffer, first, span, depth})) {
52 m_pExt2Fs->unpinBlock(block);
53 return false;
54 }
55 if (depth == 1)
56 return true;
57 const size_t entries = m_pExt2Fs->m_BlockSize / sizeof(uint32_t);
58 const size_t childSpan = span / entries;
59 const uint32_t* children = reinterpret_cast<const uint32_t*>(buffer);
60 for (size_t i = 0; i < entries; ++i) {
61 if (!collectMappingPages(LITTLE_TO_HOST32(children[i]), depth - 1, first + i * childSpan,
62 childSpan, pages))
63 return false;
64 }
65 return true;
66}
67
68bool Ext2Node::prepareTrim(size_t keep, TrimPlan& plan, bool allocationLockHeld) {
69 if (!m_State->allocationValid) {
70 SYSCALL_ERROR(IoError);
71 return false;
72 }
73 plan.keep = keep;
74 const size_t entries = m_pExt2Fs->m_BlockSize / sizeof(uint32_t);
75 if (!collectMappingPages(LITTLE_TO_HOST32(m_pInode->i_block[12]), 1, 12, entries, plan.pages) ||
76 !collectMappingPages(LITTLE_TO_HOST32(m_pInode->i_block[13]), 2, 12 + entries,
77 entries * entries, plan.pages) ||
78 !collectMappingPages(LITTLE_TO_HOST32(m_pInode->i_block[14]), 3,
79 12 + entries + entries * entries, entries * entries * entries,
80 plan.pages))
81 return false;
82 for (size_t i = 0; i < 12; ++i) {
83 const uint32_t block = LITTLE_TO_HOST32(m_pInode->i_block[i]);
84 if (block) {
85 if (i >= keep) {
86 if (!appendPrepared(plan.retiredData, block))
87 return false;
88 } else {
89 ++plan.retainedData;
90 }
91 }
92 }
93 for (const MappingPage& page : plan.pages) {
94 if (page.depth != 1)
95 continue;
96 const uint32_t* children = reinterpret_cast<const uint32_t*>(page.buffer);
97 for (size_t i = 0; i < entries; ++i) {
98 const uint32_t block = LITTLE_TO_HOST32(children[i]);
99 if (block) {
100 if (page.first + i >= keep) {
101 if (!appendPrepared(plan.retiredData, block))
102 return false;
103 } else {
104 ++plan.retainedData;
105 }
106 }
107 }
108 }
109 LockGuard<Mutex> allocationGuard(m_pExt2Fs->m_WriteLock, !allocationLockHeld);
110 if (!m_pExt2Fs->prepareInodeWrite(getInodeNumber()))
111 return false;
112 for (uint32_t block : plan.retiredData) {
113 if (!m_pExt2Fs->prepareBlockReleaseLocked(block))
114 return false;
115 }
116 for (const MappingPage& page : plan.pages) {
117 if (page.first >= keep && !m_pExt2Fs->prepareBlockReleaseLocked(page.block))
118 return false;
119 }
120 return true;
121}
122
123void Ext2Node::commitTrim(TrimPlan& plan, bool allocationLockHeld) {
124 const size_t keep = plan.keep;
125 const size_t entries = m_pExt2Fs->m_BlockSize / sizeof(uint32_t);
126 LockGuard<Mutex> allocationGuard(m_pExt2Fs->m_WriteLock, !allocationLockHeld);
127 // Every read, metadata pin, and retirement journal allocation has succeeded.
128 for (size_t i = keep; i < 12; ++i)
129 m_pInode->i_block[i] = 0;
130 const size_t firstIndices[3] = {12, 12 + entries, 12 + entries + entries * entries};
131 for (size_t i = 0; i < 3; ++i) {
132 if (firstIndices[i] >= keep)
133 m_pInode->i_block[12 + i] = 0;
134 }
135 size_t retainedMetadata = 0;
136 for (const MappingPage& page : plan.pages) {
137 uint32_t* children = reinterpret_cast<uint32_t*>(page.buffer);
138 const size_t childSpan = page.span / entries;
139 for (size_t i = 0; i < entries; ++i) {
140 if (page.first + i * childSpan >= keep)
141 children[i] = 0;
142 }
143 if (page.first < keep) {
144 ++retainedMetadata;
145 m_pExt2Fs->writeBlock(page.block);
146 }
147 }
148 while (m_Blocks.count() > keep)
149 m_Blocks.popBack();
150 m_nMetadataBlocks = retainedMetadata;
151 m_State->allocatedDataBlocks = plan.retainedData;
152 updateAllocatedSectorCount();
153 m_pExt2Fs->writeInode(getInodeNumber());
154 for (uint32_t block : plan.retiredData)
155 m_pExt2Fs->releaseBlockLocked(block, m_InodeNumber);
156 for (MappingPage& page : plan.pages) {
157 m_pExt2Fs->unpinBlock(page.block);
158 page.buffer = 0;
159 if (page.first >= keep)
160 m_pExt2Fs->releaseBlockLocked(page.block, m_InodeNumber);
161 }
162}
163
164bool Ext2Node::trimToBlocks(size_t keep, bool allocationLockHeld) {
165 TrimPlan plan(*m_pExt2Fs);
166 if (!prepareTrim(keep, plan, allocationLockHeld))
167 return false;
168 commitTrim(plan, allocationLockHeld);
169 return true;
170}
171
173 public:
174 DataShrinkPlan(Ext2Node& node, size_t size)
175 : node(node), size(size), trim(*node.m_pExt2Fs), tail(0), tailBlock(0) {}
176 ~DataShrinkPlan() override {
177 if (tail)
178 node.m_pExt2Fs->unpinBlock(tailBlock);
179 }
180 void commit() override {
181 LockGuard<Mutex> guard(node.m_State->writebackLock);
182 node.commitTrim(trim);
183 if (tail) {
184 const size_t within = size % node.m_pExt2Fs->m_BlockSize;
185 ByteSet(reinterpret_cast<void*>(tail + within), 0, node.m_pExt2Fs->m_BlockSize - within);
186 node.m_pExt2Fs->writeBlock(tailBlock);
187 }
188 node.m_nSize = size;
189 node.m_pInode->i_size = HOST_TO_LITTLE32(size);
190 node.m_pExt2Fs->writeInode(node.getInodeNumber());
191 for (Ext2File* alias : node.m_State->files)
192 alias->setSize(size);
193 }
194 Ext2Node& node;
195 size_t size;
196 TrimPlan trim;
197 uintptr_t tail;
198 uint32_t tailBlock;
199};
200
201bool Ext2Node::prepareDataShrink(size_t size, UniquePointer<File::PreparedShrink>& prepared) {
202 DataShrinkPlan* plan = new DataShrinkPlan(*this, size);
204 if (!plan) {
205 SYSCALL_ERROR(OutOfMemory);
206 return false;
207 }
208 const size_t blockSize = m_pExt2Fs->m_BlockSize;
209 if (size % blockSize) {
210 const size_t index = size / blockSize;
211 if (index >= m_Blocks.count() || !ensureBlockLoaded(index)) {
212 SYSCALL_ERROR(IoError);
213 return false;
214 }
215 plan->tailBlock = m_Blocks[index];
216 if (plan->tailBlock) {
217 plan->tail = m_pExt2Fs->readBlock(plan->tailBlock);
218 if (!plan->tail) {
219 SYSCALL_ERROR(IoError);
220 return false;
221 }
222 }
223 }
224 if (!prepareTrim(size / blockSize + (size % blockSize != 0), plan->trim))
225 return false;
226 prepared = pedigree_std::move(owner);
227 return true;
228}
229
230bool Ext2Node::zeroRange(size_t start, size_t end) {
231 const size_t blockSize = m_pExt2Fs->m_BlockSize;
232 while (start < end) {
233 const size_t block = start / blockSize;
234 const size_t within = start % blockSize;
235 const size_t amount = (end - start < blockSize - within) ? end - start : blockSize - within;
236 if (block >= m_Blocks.count() || !ensureBlockLoaded(block)) {
237 SYSCALL_ERROR(IoError);
238 return false;
239 }
240 if (m_Blocks[block]) {
241 const uintptr_t buffer = m_pExt2Fs->readBlock(m_Blocks[block]);
242 if (!buffer) {
243 SYSCALL_ERROR(IoError);
244 return false;
245 }
246 ByteSet(reinterpret_cast<void*>(buffer + within), 0, amount);
247 m_pExt2Fs->writeBlock(m_Blocks[block]);
248 m_pExt2Fs->unpinBlock(m_Blocks[block]);
249 }
250 start += amount;
251 }
252 return true;
253}
254
255bool Ext2Node::resizeData(size_t size) {
256 if (size > 0xffffffffULL) {
257 SYSCALL_ERROR(FileTooLarge);
258 return false;
259 }
260 if (size > m_nSize) {
261 return ensureLargeEnough(size, 0, 0);
262 }
263 const size_t blockSize = m_pExt2Fs->m_BlockSize;
264 const size_t keep = size / blockSize + (size % blockSize != 0);
265 // Preserve the old visible bytes if any backing read fails during preflight.
266 uintptr_t tail = 0;
267 uint32_t tailBlock = 0;
268 if (size % blockSize) {
269 if (!ensureBlockLoaded(size / blockSize)) {
270 SYSCALL_ERROR(IoError);
271 return false;
272 }
273 tailBlock = m_Blocks[size / blockSize];
274 if (tailBlock) {
275 tail = m_pExt2Fs->readBlock(tailBlock);
276 if (!tail) {
277 SYSCALL_ERROR(IoError);
278 return false;
279 }
280 }
281 }
282 if (!trimToBlocks(keep)) {
283 if (tail) {
284 m_pExt2Fs->unpinBlock(tailBlock);
285 }
286 return false;
287 }
288 if (tail) {
289 const size_t within = size % blockSize;
290 ByteSet(reinterpret_cast<void*>(tail + within), 0, blockSize - within);
291 m_pExt2Fs->writeBlock(tailBlock);
292 m_pExt2Fs->unpinBlock(tailBlock);
293 }
294 m_nSize = size;
295 m_pInode->i_size = HOST_TO_LITTLE32(size);
296 m_pExt2Fs->writeInode(getInodeNumber());
297 return true;
298}
void writeBlock(uint32_t block)
uintptr_t readBlock(uint32_t block)
void releaseBlockLocked(uint32_t block, uint32_t inode=0)
void commit() override
bool ensureLargeEnough(size_t size, uint64_t location, uint64_t opsize, bool onlyBlocks=false, bool nozeroblocks=false)
Definition Ext2Node.cc:254
static UniquePointer< T > adopt(T *pointer)
Definition Pointers.h:101
A vector / dynamic array.
Definition Vector.h:33
T popBack()
Definition Vector.h:303
void pushBack(const T &value)
Definition Vector.h:275
size_t size() const
Definition Vector.h:265
size_t count() const
Definition Vector.h:270