Coverage Report

Created: 2025-09-19 09:35

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