Skip to content

Commit cdb4b42

Browse files
committed
partitioning: Fix units and sequential regions for partitions
Elininates last overlap bugs for new partitions Signed-off-by: Ikey Doherty <[email protected]>
1 parent 7b495d7 commit cdb4b42

File tree

2 files changed

+47
-54
lines changed

2 files changed

+47
-54
lines changed

crates/partitioning/src/planner.rs

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,12 @@ pub enum Change {
4545
}
4646

4747
/// A disk partitioning planner.
48+
#[derive(Debug, Clone)]
4849
pub struct Planner {
4950
/// First usable LBA position on disk in bytes
5051
usable_start: u64,
5152
/// Last usable LBA position on disk in bytes
5253
usable_end: u64,
53-
/// The original block device state that we're planning changes for
54-
device: BlockDevice,
5554
/// Stack of changes that can be undone
5655
changes: VecDeque<Change>,
5756
/// Original partition layout for reference
@@ -124,8 +123,8 @@ impl Region {
124123
///
125124
/// ```
126125
/// use partitioning::planner::format_size;
127-
/// assert_eq!(format_size(1500), "1.5KB");
128-
/// assert_eq!(format_size(1500000), "1.4MB");
126+
/// assert_eq!(format_size(1500), "1.5KiB");
127+
/// assert_eq!(format_size(1500000), "1.4MiB");
129128
/// ```
130129
pub fn format_size(size: u64) -> String {
131130
const KB: f64 = 1024.0;
@@ -135,13 +134,13 @@ pub fn format_size(size: u64) -> String {
135134

136135
let size = size as f64;
137136
if size >= TB {
138-
format!("{:.1}TB", size / TB)
137+
format!("{:.1}TiB", size / TB)
139138
} else if size >= GB {
140-
format!("{:.1}GB", size / GB)
139+
format!("{:.1}GiB", size / GB)
141140
} else if size >= MB {
142-
format!("{:.1}MB", size / MB)
141+
format!("{:.1}MiB", size / MB)
143142
} else if size >= KB {
144-
format!("{:.1}KB", size / KB)
143+
format!("{:.1}KiB", size / KB)
145144
} else {
146145
format!("{}B", size)
147146
}
@@ -207,7 +206,7 @@ impl Change {
207206

208207
impl Planner {
209208
/// Creates a new partitioning planner for the given disk.
210-
pub fn new(device: BlockDevice) -> Self {
209+
pub fn new(device: &BlockDevice) -> Self {
211210
debug!("Creating new partition planner for device of size {}", device.size());
212211

213212
// Extract original regions from device
@@ -220,7 +219,6 @@ impl Planner {
220219
Self {
221220
usable_start: 0,
222221
usable_end: device.size(),
223-
device,
224222
changes: VecDeque::new(),
225223
original_regions,
226224
}
@@ -350,8 +348,8 @@ impl Planner {
350348
for region in &current {
351349
if new_region.overlaps_with(region) {
352350
warn!(
353-
"Partition would overlap with existing partition at {}..{}",
354-
region.start, region.end
351+
"Partition would overlap with existing partition at {}..{} - attempted region {}..{}",
352+
region.start, region.end, new_region.start, new_region.end
355353
);
356354
return Err(PlanError::RegionOverlap {
357355
start: aligned_start,
@@ -412,11 +410,6 @@ impl Planner {
412410
&self.changes
413411
}
414412

415-
/// Get the original block device state
416-
pub fn original_device(&self) -> &BlockDevice {
417-
&self.device
418-
}
419-
420413
/// Get the size of the usable disk region in bytes
421414
pub fn usable_size(&self) -> u64 {
422415
self.usable_end - self.usable_start
@@ -469,7 +462,7 @@ mod tests {
469462
#[test]
470463
fn test_fresh_installation() {
471464
let disk = create_mock_disk();
472-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
465+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
473466

474467
// Create typical Linux partition layout with absolute positions
475468
// - 0 -> 512MB: EFI System Partition
@@ -491,7 +484,7 @@ mod tests {
491484
#[test]
492485
fn test_dual_boot_with_windows() {
493486
let disk = create_windows_disk();
494-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
487+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
495488

496489
// Available space starts after Windows partitions (~ 200.6GB)
497490
let start = 200 * GB + 616 * MB;
@@ -518,7 +511,7 @@ mod tests {
518511
disk.add_partition(512 * MB, 4 * GB + 512 * MB); // Swap: 512MB -> 4.5GB
519512
disk.add_partition(4 * GB + 512 * MB, 500 * GB); // Root: 4.5GB -> 500GB
520513

521-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
514+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
522515

523516
// Delete old Linux partitions
524517
assert!(planner.plan_delete_partition(1).is_ok()); // Delete swap
@@ -541,7 +534,7 @@ mod tests {
541534
#[test]
542535
fn test_region_validation() {
543536
let disk = create_mock_disk();
544-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
537+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
545538

546539
// Test out of bounds
547540
assert!(matches!(
@@ -560,7 +553,7 @@ mod tests {
560553
#[test]
561554
fn test_undo_operations() {
562555
let disk = create_mock_disk();
563-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
556+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
564557

565558
// Add some partitions
566559
assert!(planner.plan_add_partition(0, 100 * GB).is_ok());
@@ -582,7 +575,7 @@ mod tests {
582575
#[test]
583576
fn test_partition_boundaries() {
584577
let disk = create_mock_disk();
585-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
578+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
586579

587580
// Add first partition from 0 to 100GB
588581
assert!(planner.plan_add_partition(0, 100 * GB).is_ok());
@@ -609,7 +602,7 @@ mod tests {
609602
#[test]
610603
fn test_alignment() {
611604
let disk = create_mock_disk();
612-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
605+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
613606

614607
// Already aligned values should not be re-aligned
615608
let aligned_start = PARTITION_ALIGNMENT;

crates/partitioning/src/strategy.rs

Lines changed: 30 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ use crate::planner::{PlanError, Planner};
3131
use crate::planner::Region;
3232

3333
/// Strategy for allocating partitions
34-
#[derive(Debug)]
34+
#[derive(Debug, Clone)]
3535
pub enum AllocationStrategy {
3636
/// Initialize a clean partition layout using the entire disk.
3737
/// This will remove all existing partitions and create a new layout.
@@ -64,6 +64,7 @@ pub struct PartitionRequest {
6464
}
6565

6666
/// Handles planning partition layouts according to specific strategies
67+
#[derive(Debug, Clone)]
6768
pub struct Strategy {
6869
allocation: AllocationStrategy,
6970
requests: Vec<PartitionRequest>,
@@ -197,15 +198,7 @@ impl Strategy {
197198
});
198199
}
199200

200-
// Calculate distributable space
201-
let distributable = remaining - total_fixed - min_flexible;
202-
let per_flexible = if !flexible_requests.is_empty() {
203-
distributable / flexible_requests.len() as u64
204-
} else {
205-
0
206-
};
207-
208-
// First allocate fixed partitions
201+
// First pass: allocate exact size partitions
209202
for request in &self.requests {
210203
if let SizeRequirement::Exact(size) = request.size {
211204
planner.plan_add_partition(current, current + size)?;
@@ -214,28 +207,35 @@ impl Strategy {
214207
}
215208
}
216209

217-
// Then allocate flexible partitions with fair distribution
218-
for (_, min, max_opt) in &flexible_requests {
219-
let base = min + per_flexible;
220-
let size = if let Some(max) = max_opt { base.min(*max) } else { base };
210+
// Second pass: allocate flexible partitions
211+
let mut remaining_flexible = flexible_requests.len();
212+
for (_idx, min, max_opt) in &flexible_requests {
213+
remaining_flexible -= 1;
214+
215+
let size = if remaining_flexible == 0 {
216+
// Last flexible partition gets all remaining space
217+
let size = remaining;
218+
if let Some(max) = max_opt {
219+
size.min(*max).max(*min)
220+
} else {
221+
size.max(*min)
222+
}
223+
} else {
224+
// Other flexible partitions get fair share plus minimum
225+
let share = remaining / (remaining_flexible + 1) as u64;
226+
let size = min + share;
227+
if let Some(max) = max_opt {
228+
size.min(*max)
229+
} else {
230+
size
231+
}
232+
};
233+
221234
planner.plan_add_partition(current, current + size)?;
222235
current += size;
223236
remaining -= size;
224237
}
225238

226-
// Give any remaining space to the last flexible partition
227-
if remaining > 0 && !flexible_requests.is_empty() {
228-
planner.undo(); // Remove last partition
229-
let (_, min, max_opt) = flexible_requests.last().unwrap();
230-
let final_size = min + per_flexible + remaining;
231-
let final_size = if let Some(max) = max_opt {
232-
final_size.min(*max)
233-
} else {
234-
final_size
235-
};
236-
planner.plan_add_partition(current - per_flexible - min, current - per_flexible - min + final_size)?;
237-
}
238-
239239
Ok(())
240240
}
241241
}
@@ -313,7 +313,7 @@ mod tests {
313313
fn test_uefi_clean_install() {
314314
// Test case: Clean UEFI installation with separate /home
315315
let disk = create_test_disk();
316-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
316+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
317317
let mut strategy = Strategy::new(AllocationStrategy::InitializeWholeDisk);
318318

319319
// Standard UEFI layout with separate /home
@@ -347,7 +347,7 @@ mod tests {
347347
disk.add_partition(100 * MB, 116 * MB); // MSR
348348
disk.add_partition(116 * MB, 200 * GB); // Windows
349349

350-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
350+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
351351
let mut strategy = Strategy::new(AllocationStrategy::LargestFree);
352352

353353
// Standard Linux layout using remaining space
@@ -366,7 +366,7 @@ mod tests {
366366
fn test_minimal_server_install() {
367367
// Test case: Minimal server installation with single root partition
368368
let disk = create_test_disk();
369-
let mut planner = Planner::new(BlockDevice::mock_device(disk));
369+
let mut planner = Planner::new(&BlockDevice::mock_device(disk));
370370
let mut strategy = Strategy::new(AllocationStrategy::InitializeWholeDisk);
371371

372372
// Simple layout - just boot and root

0 commit comments

Comments
 (0)