Skip to content

Commit ba08d3c

Browse files
committed
partitioning: Add basic writer that tracks partition ids
Signed-off-by: Ikey Doherty <[email protected]>
1 parent 7febdc6 commit ba08d3c

File tree

3 files changed

+236
-19
lines changed

3 files changed

+236
-19
lines changed

crates/partitioning/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,5 @@ pub use gpt;
1010

1111
pub mod planner;
1212
pub mod strategy;
13+
14+
pub mod writer;

crates/partitioning/src/planner.rs

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,9 @@ pub enum PlanError {
3939
#[derive(Debug, Clone)]
4040
pub enum Change {
4141
/// Add a new partition
42-
AddPartition { start: u64, end: u64 },
42+
AddPartition { start: u64, end: u64, partition_id: u32 },
4343
/// Delete an existing partition
44-
DeletePartition { original_index: usize },
44+
DeletePartition { original_index: usize, partition_id: u32 },
4545
}
4646

4747
/// A disk partitioning planner.
@@ -55,6 +55,12 @@ pub struct Planner {
5555
changes: VecDeque<Change>,
5656
/// Original partition layout for reference
5757
original_regions: Vec<Region>,
58+
/// Track original partition IDs
59+
original_partition_ids: Vec<u32>,
60+
/// Next available partition ID for new partitions
61+
next_partition_id: u32,
62+
63+
wipe_disk: bool,
5864
}
5965

6066
/// A contiguous region of disk space defined by absolute start and end positions
@@ -189,16 +195,24 @@ impl Change {
189195
/// Get a human readable description of this change
190196
pub fn describe(&self, disk_size: u64) -> String {
191197
match self {
192-
Change::AddPartition { start, end } => {
198+
Change::AddPartition {
199+
start,
200+
end,
201+
partition_id,
202+
} => {
193203
format!(
194-
"Add new partition: {} ({} at {})",
204+
"Add new partition #{}: {} ({} at {})",
205+
partition_id,
195206
format_size(end - start),
196207
Region::new(*start, *end).describe(disk_size),
197208
format_position(*start, disk_size)
198209
)
199210
}
200-
Change::DeletePartition { original_index } => {
201-
format!("Delete partition #{}", original_index + 1)
211+
Change::DeletePartition {
212+
original_index,
213+
partition_id,
214+
} => {
215+
format!("Delete partition #{} (index {})", partition_id, original_index + 1)
202216
}
203217
}
204218
}
@@ -209,18 +223,25 @@ impl Planner {
209223
pub fn new(device: &BlockDevice) -> Self {
210224
debug!("Creating new partition planner for device of size {}", device.size());
211225

212-
// Extract original regions from device
213-
let original_regions = device
214-
.partitions()
215-
.iter()
216-
.map(|p| Region::new(p.start, p.end))
217-
.collect();
226+
// Extract original regions and partition IDs from device
227+
let mut original_regions = Vec::new();
228+
let mut original_partition_ids = Vec::new();
229+
let mut max_id = 0u32;
230+
231+
for part in device.partitions() {
232+
original_regions.push(Region::new(part.start, part.end));
233+
original_partition_ids.push(part.number);
234+
max_id = max_id.max(part.number);
235+
}
218236

219237
Self {
220238
usable_start: 0,
221239
usable_end: device.size(),
222240
changes: VecDeque::new(),
223241
original_regions,
242+
original_partition_ids,
243+
next_partition_id: max_id + 1,
244+
wipe_disk: false,
224245
}
225246
}
226247

@@ -262,7 +283,11 @@ impl Planner {
262283

263284
// First pass: collect indices to delete
264285
for change in &self.changes {
265-
if let Change::DeletePartition { original_index } = change {
286+
if let Change::DeletePartition {
287+
original_index,
288+
partition_id: _,
289+
} = change
290+
{
266291
deleted_indices.push(*original_index);
267292
}
268293
}
@@ -276,8 +301,13 @@ impl Planner {
276301

277302
// Second pass: add new partitions
278303
for change in &self.changes {
279-
if let Change::AddPartition { start, end } = change {
280-
debug!("Adding partition {}..{}", start, end);
304+
if let Change::AddPartition {
305+
start,
306+
end,
307+
partition_id,
308+
} = change
309+
{
310+
debug!("Adding partition {}..{} (ID: {})", start, end, partition_id);
281311
layout.push(Region {
282312
start: *start,
283313
end: *end,
@@ -358,10 +388,12 @@ impl Planner {
358388
}
359389
}
360390

361-
debug!("Adding new partition to change queue");
391+
let partition_id = self.allocate_partition_id();
392+
debug!("Adding new partition with ID {} to change queue", partition_id);
362393
self.changes.push_back(Change::AddPartition {
363394
start: aligned_start,
364395
end: aligned_end,
396+
partition_id,
365397
});
366398
Ok(())
367399
}
@@ -378,9 +410,18 @@ impl Planner {
378410
});
379411
}
380412

381-
debug!("Adding partition deletion to change queue");
382-
self.changes
383-
.push_back(Change::DeletePartition { original_index: index });
413+
let partition_id = self
414+
.get_original_partition_id(index)
415+
.ok_or(PlanError::RegionOutOfBounds {
416+
start: self.usable_start,
417+
end: self.usable_size(),
418+
})?;
419+
420+
debug!("Adding deletion of partition ID {} to change queue", partition_id);
421+
self.changes.push_back(Change::DeletePartition {
422+
original_index: index,
423+
partition_id,
424+
});
384425
Ok(())
385426
}
386427

@@ -425,8 +466,24 @@ impl Planner {
425466
debug!("Planning to create new GPT partition table");
426467
self.changes.clear(); // Clear any existing changes
427468
self.original_regions.clear(); // Clear original partitions
469+
self.wipe_disk = true;
428470
Ok(())
429471
}
472+
473+
pub fn wipe_disk(&self) -> bool {
474+
self.wipe_disk
475+
}
476+
/// Get the next available partition ID and increment the counter
477+
pub fn allocate_partition_id(&mut self) -> u32 {
478+
let id = self.next_partition_id;
479+
self.next_partition_id += 1;
480+
id
481+
}
482+
483+
/// Get the original partition ID for a given index
484+
pub fn get_original_partition_id(&self, index: usize) -> Option<u32> {
485+
self.original_partition_ids.get(index).copied()
486+
}
430487
}
431488

432489
#[cfg(test)]

crates/partitioning/src/writer.rs

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
// SPDX-FileCopyrightText: Copyright © 2025 Serpent OS Developers
2+
// SPDX-FileCopyrightText: Copyright © 2025 AerynOS Developers
3+
//
4+
// SPDX-License-Identifier: MPL-2.0
5+
6+
use std::fs;
7+
8+
use disks::BlockDevice;
9+
use gpt::{mbr, partition_types, GptConfig};
10+
use thiserror::Error;
11+
12+
use crate::planner::{Change, Planner};
13+
14+
/// Errors that can occur when writing changes to disk
15+
#[derive(Debug, Error)]
16+
pub enum WriteError {
17+
/// Device size has changed since the plan was created
18+
#[error("Device size changed since planning")]
19+
DeviceSizeChanged,
20+
21+
/// A partition ID was used multiple times
22+
#[error("Duplicate partition ID: {0}")]
23+
DuplicatePartitionId(u32),
24+
25+
/// Error from GPT library
26+
#[error("GPT error: {0}")]
27+
Gpt(#[from] gpt::GptError),
28+
29+
/// Error from MBR handling
30+
#[error("GPT error: {0}")]
31+
Mbr(#[from] gpt::mbr::MBRError),
32+
33+
/// Underlying I/O error
34+
#[error("I/O error: {0}")]
35+
IoError(#[from] std::io::Error),
36+
}
37+
38+
/// A writer that applies the layouts from the Planner to the disk.
39+
pub struct DiskWriter<'a> {
40+
/// The block device to write to
41+
pub device: &'a BlockDevice,
42+
/// The planner containing the changes to apply
43+
pub planner: &'a Planner,
44+
}
45+
46+
impl<'a> DiskWriter<'a> {
47+
/// Create a new DiskWriter.
48+
pub fn new(device: &'a BlockDevice, planner: &'a Planner) -> Self {
49+
Self { device, planner }
50+
}
51+
52+
/// Simulate changes without writing to disk
53+
pub fn simulate(&self) -> Result<(), WriteError> {
54+
let mut device = fs::OpenOptions::new()
55+
.read(true)
56+
.write(false)
57+
.open(self.device.device())?;
58+
self.validate_changes(&device)?;
59+
self.apply_changes(&mut device, false)?;
60+
Ok(())
61+
}
62+
63+
/// Actually write changes to disk
64+
pub fn write(&self) -> Result<(), WriteError> {
65+
let mut device = fs::OpenOptions::new()
66+
.read(true)
67+
.write(true)
68+
.open(self.device.device())?;
69+
70+
self.validate_changes(&device)?;
71+
self.apply_changes(&mut device, true)?;
72+
Ok(())
73+
}
74+
75+
/// Validate all planned changes before applying them by checking:
76+
/// - Device size matches the planned size
77+
/// - No duplicate partition IDs exist
78+
fn validate_changes(&self, device: &fs::File) -> Result<(), WriteError> {
79+
// Verify device size matches what we planned for
80+
let metadata = device.metadata()?;
81+
if metadata.len() != self.device.size() {
82+
return Err(WriteError::DeviceSizeChanged);
83+
}
84+
85+
// Verify partition IDs don't conflict
86+
let mut used_ids = std::collections::HashSet::new();
87+
for change in self.planner.changes() {
88+
match change {
89+
Change::AddPartition { partition_id, .. } => {
90+
if !used_ids.insert(*partition_id) {
91+
return Err(WriteError::DuplicatePartitionId(*partition_id));
92+
}
93+
}
94+
Change::DeletePartition { partition_id, .. } => {
95+
used_ids.remove(partition_id);
96+
}
97+
}
98+
}
99+
100+
Ok(())
101+
}
102+
103+
/// Apply the changes to disk by:
104+
/// - Creating or opening the GPT table
105+
/// - Applying each change in sequence
106+
fn apply_changes(&self, device: &mut fs::File, writable: bool) -> Result<(), WriteError> {
107+
let mut gpt_table = if self.planner.wipe_disk() {
108+
if writable {
109+
let mbr = mbr::ProtectiveMBR::with_lb_size(
110+
u32::try_from((self.device.size() / 512) - 1).unwrap_or(0xFF_FF_FF_FF),
111+
);
112+
mbr.overwrite_lba0(device)?;
113+
}
114+
115+
GptConfig::default()
116+
.writable(writable)
117+
.logical_block_size(gpt::disk::LogicalBlockSize::Lb512)
118+
.create_from_device(device, None)?
119+
} else {
120+
GptConfig::default().writable(writable).open_from_device(device)?
121+
};
122+
123+
let _layout = self.planner.current_layout();
124+
let changes = self.planner.changes();
125+
126+
for change in changes {
127+
match change {
128+
Change::DeletePartition {
129+
partition_id,
130+
original_index,
131+
} => {
132+
if let Some(id) = gpt_table.remove_partition(*partition_id) {
133+
println!(
134+
"Deleted partition {} (index {}): {:?}",
135+
partition_id, original_index, id
136+
);
137+
}
138+
}
139+
Change::AddPartition {
140+
start,
141+
end,
142+
partition_id,
143+
} => {
144+
let start_lba = *start / 512;
145+
let size_lba = (*end - *start) / 512;
146+
let part_type = partition_types::BASIC;
147+
148+
let id = gpt_table.add_partition_at("", *partition_id, start_lba, size_lba, part_type, 0)?;
149+
println!("Added partition {}: {:?}", partition_id, id);
150+
}
151+
}
152+
}
153+
154+
eprintln!("GPT is now: {gpt_table:?}");
155+
156+
Ok(())
157+
}
158+
}

0 commit comments

Comments
 (0)