1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
use crate::constants::*;
use crate::error::{FeoxError, Result};
use std::collections::BTreeMap;
/// Represents a contiguous range of free sectors on disk
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FreeSpace {
pub start: u64, // Starting sector
pub size: u64, // Size in sectors
}
/// Free space manager using dual RB-trees for efficient allocation and coalescing
/// Matches the kernel implementation's design for correctness
pub struct FreeSpaceManager {
/// Tree sorted by (size, start) for best-fit allocation
/// Using composite key ensures uniqueness
by_size: BTreeMap<(u64, u64), FreeSpace>,
/// Tree sorted by start address for efficient merging
by_start: BTreeMap<u64, FreeSpace>,
/// Total free space in bytes
total_free: u64,
/// Device size in bytes (for validation)
device_size: u64,
/// Fragmentation percentage (0-100)
fragmentation_percent: u32,
}
impl FreeSpaceManager {
/// Create a new free space manager
pub fn new() -> Self {
Self {
by_size: BTreeMap::new(),
by_start: BTreeMap::new(),
total_free: 0,
device_size: 0,
fragmentation_percent: 0,
}
}
/// Initialize with device size and initial free space
pub fn initialize(&mut self, device_size: u64) -> Result<()> {
self.device_size = device_size;
// Reserve first 16 sectors for metadata (matching FEOX_DATA_START_BLOCK)
let metadata_sectors = 16;
let total_sectors = device_size / FEOX_BLOCK_SIZE as u64;
if total_sectors <= metadata_sectors {
return Err(FeoxError::InvalidDevice);
}
// Add all space after metadata as free
let free_sectors = total_sectors - metadata_sectors;
self.insert_free_space(FreeSpace {
start: metadata_sectors,
size: free_sectors,
})?;
Ok(())
}
/// Set device size for bounds checking (used when rebuilding from scan)
pub fn set_device_size(&mut self, device_size: u64) {
self.device_size = device_size;
}
/// Allocate sectors using best-fit algorithm
pub fn allocate_sectors(&mut self, sectors_needed: u64) -> Result<u64> {
if sectors_needed == 0 {
return Err(FeoxError::InvalidArgument);
}
// Find best-fit (smallest space that fits)
let mut best_fit = None;
// Search from smallest size upward
for ((size, start), space) in &self.by_size {
if *size >= sectors_needed {
best_fit = Some((*size, *start, space.clone()));
break; // First fit is best fit since we're sorted by size
}
}
if let Some((size, start, space)) = best_fit {
// Validate the space
if !self.is_valid_free_space(&space) {
return Err(FeoxError::CorruptedData);
}
// Remove from both trees
self.by_size.remove(&(size, start));
self.by_start.remove(&space.start);
let allocated_start = space.start;
// Handle remaining space if any
if space.size > sectors_needed {
let remaining = FreeSpace {
start: space.start + sectors_needed,
size: space.size - sectors_needed,
};
// Insert remaining space back
if let Err(e) = self.insert_free_space(remaining) {
// Try to restore original space on error
let _ = self.insert_free_space(space);
return Err(e);
}
}
self.total_free -= sectors_needed * FEOX_BLOCK_SIZE as u64;
self.update_fragmentation();
Ok(allocated_start)
} else {
Err(FeoxError::OutOfSpace)
}
}
/// Release sectors back to free space pool with coalescing
pub fn release_sectors(&mut self, start: u64, count: u64) -> Result<()> {
if start == 0 || count == 0 {
return Err(FeoxError::InvalidArgument);
}
// Validate bounds
if !self.is_valid_sector_range(start, count) {
return Err(FeoxError::InvalidArgument);
}
// Try to merge with adjacent spaces
let merged = self.try_merge_spaces(start, count)?;
// Insert the merged space (this will update total_free)
self.insert_free_space(merged)?;
self.update_fragmentation();
Ok(())
}
/// Try to merge with adjacent free spaces
fn try_merge_spaces(&mut self, start: u64, size: u64) -> Result<FreeSpace> {
let end = start + size;
let mut merged_start = start;
let mut merged_size = size;
// Find predecessor (space ending at our start)
let mut prev = None;
for (&s, space) in self.by_start.range(..start).rev().take(1) {
if s + space.size == start {
prev = Some(space.clone());
}
}
// Find successor (space starting at our end)
let next = self.by_start.get(&end).cloned();
// Merge with predecessor if found
if let Some(prev_space) = prev {
// Remove from both trees
self.by_size.remove(&(prev_space.size, prev_space.start));
self.by_start.remove(&prev_space.start);
// Subtract the removed space from total_free (will be re-added when inserting merged)
self.total_free -= prev_space.size * FEOX_BLOCK_SIZE as u64;
merged_start = prev_space.start;
merged_size += prev_space.size;
}
// Merge with successor if found
if let Some(next_space) = next {
// Remove from both trees
self.by_size.remove(&(next_space.size, next_space.start));
self.by_start.remove(&next_space.start);
// Subtract the removed space from total_free (will be re-added when inserting merged)
self.total_free -= next_space.size * FEOX_BLOCK_SIZE as u64;
merged_size += next_space.size;
}
Ok(FreeSpace {
start: merged_start,
size: merged_size,
})
}
/// Insert a free space into both trees
fn insert_free_space(&mut self, space: FreeSpace) -> Result<()> {
if space.size == 0 {
return Err(FeoxError::InvalidArgument);
}
// Validate the space
if !self.is_valid_free_space(&space) {
return Err(FeoxError::InvalidArgument);
}
// Check for duplicates
if self.by_start.contains_key(&space.start) {
return Err(FeoxError::DuplicateKey);
}
// Update total free space
self.total_free += space.size * FEOX_BLOCK_SIZE as u64;
// Insert into both trees
self.by_size
.insert((space.size, space.start), space.clone());
self.by_start.insert(space.start, space);
Ok(())
}
/// Check if a free space is valid
fn is_valid_free_space(&self, space: &FreeSpace) -> bool {
// Never allow sector 0 (reserved for metadata)
if space.start == 0 {
return false;
}
// Check device bounds
if self.device_size > 0 {
let device_sectors = self.device_size / FEOX_BLOCK_SIZE as u64;
if space.start >= device_sectors {
return false;
}
if space.start + space.size > device_sectors {
return false;
}
}
true
}
/// Check if a sector range is valid
fn is_valid_sector_range(&self, start: u64, count: u64) -> bool {
if start == 0 || count == 0 {
return false;
}
if self.device_size > 0 {
let device_sectors = self.device_size / FEOX_BLOCK_SIZE as u64;
if start >= device_sectors {
return false;
}
if start + count > device_sectors {
return false;
}
}
true
}
/// Update fragmentation metric
fn update_fragmentation(&mut self) {
if self.total_free == 0 {
self.fragmentation_percent = 0;
return;
}
let num_chunks = self.by_start.len() as u64;
if num_chunks <= 1 {
self.fragmentation_percent = 0;
return;
}
// Find largest free chunk
let largest = self
.by_size
.iter()
.next_back()
.map(|(_, space)| space.size * FEOX_BLOCK_SIZE as u64)
.unwrap_or(0);
// Calculate fragmentation as percentage of free space not in largest chunk
if self.total_free > 0 {
let fragmented = self.total_free - largest;
self.fragmentation_percent = ((fragmented * 100) / self.total_free) as u32;
}
}
/// Get total free space in bytes
pub fn get_total_free(&self) -> u64 {
self.total_free
}
/// Get fragmentation percentage
pub fn get_fragmentation(&self) -> u32 {
self.fragmentation_percent
}
/// Get number of free chunks
pub fn get_free_chunks_count(&self) -> usize {
self.by_start.len()
}
/// Get largest free chunk in bytes
pub fn get_largest_free_chunk(&self) -> u64 {
self.by_size
.iter()
.next_back()
.map(|(_, space)| space.size * FEOX_BLOCK_SIZE as u64)
.unwrap_or(0)
}
}
impl Default for FreeSpaceManager {
fn default() -> Self {
Self::new()
}
}