Added builder

This commit is contained in:
2024-12-18 19:23:12 -08:00
parent d42db82558
commit 8b3a1e658a
48 changed files with 1214 additions and 292 deletions

228
builder/src/cmd/disk.rs Normal file
View File

@ -0,0 +1,228 @@
use clap::Subcommand;
use std::fs::{File, OpenOptions};
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use crate::gpt::{GPTDisk, PartitionType};
use crate::util::parse_bytes_postfix;
#[derive(Subcommand, Debug)]
pub enum DiskCommand {
/// Create an empty file of the given size
Create {
/// The new file's size, in bytes.
/// May be followed by the usual SI prefixes:
/// - K, M, G, T for powers of 10
/// - Kib,Mib,Gib,Tib for powers of 1024
size: String,
},
/// Initialize partition table on the given disk
Init {},
/// Show GPT information.
///
/// By default, this command dumps GPT headers & partitons.
/// Use the flags below to compute other values.
#[command(alias = "i")]
Inspect {
/// Show index of first byte of partition
#[clap(long, group = "inspect-mode", value_name = "PARTITION")]
part_start: Option<u32>,
/// Show index of sector of partition
#[clap(long, group = "inspect-mode", value_name = "PARTITION")]
part_start_sector: Option<u32>,
},
/// Initialize partition table on the given disk
#[command(alias = "addpart")]
AddPartition {
/// The partition's name
name: String,
/// The partition's size, in bytes.
/// May be followed by the usual SI prefixes:
/// - K, M, G, T for powers of 10
/// - Kib,Mib,Gib,Tib for powers of 1024
size: String,
/// The type of this partition
partition_type: PartitionType,
},
/// Write a file to the given partition
#[command(alias = "writepart")]
WritePartition {
/// The index of the partition to write to
part_idx: u32,
/// The data to write
source: PathBuf,
},
/// Write a BIOS bootloader to this disk
SetBootCode {
/// The data to write.
/// This file must contain exactly 440 bytes.
source: PathBuf,
},
}
impl DiskCommand {
pub fn run(&self, file: &Path) {
match self {
Self::Inspect {
part_start,
part_start_sector,
} => {
let file = OpenOptions::new()
.create(false)
.append(false)
.write(false)
.read(true)
.open(file)
.unwrap();
let mut dw = GPTDisk::new(file);
if let Some(part) = part_start {
println!("{}", dw.get_partition(*part).unwrap().start());
} else if let Some(part) = part_start_sector {
assert!(dw.get_partition(*part).unwrap().start() % 512 == 0);
println!("{}", dw.get_partition(*part).unwrap().start() / 512);
} else {
let disk = dw.disk(false);
println!("Disk header: {:#?}", disk.primary_header());
println!("Partition layout: {:#?}", disk.partitions());
}
}
Self::Create { size } => {
let size = match parse_bytes_postfix(&size) {
Some(x) => x,
None => panic!("Bad byte string"),
};
if file.exists() {
panic!("file exists")
}
let mut file = File::create(file).unwrap();
let mut buffer = [0; 1024];
let buffer_len: u64 = buffer.len().try_into().unwrap();
let mut remaining_size = size;
while remaining_size > 0 {
let to_write = remaining_size.min(buffer_len);
let to_write_usize: usize = to_write.try_into().unwrap();
let buffer = &mut buffer[..to_write_usize];
file.write(buffer).unwrap();
remaining_size -= to_write;
}
}
Self::Init {} => {
let file = OpenOptions::new()
.create(false)
.append(false)
.write(true)
.read(true)
.open(file)
.unwrap();
let mut dw = GPTDisk::new(file);
dw.init();
}
Self::AddPartition {
name,
size,
partition_type,
} => {
let file = OpenOptions::new()
.create(false)
.append(false)
.write(true)
.read(true)
.open(file)
.unwrap();
let size = match parse_bytes_postfix(&size) {
Some(x) => x,
None => panic!("Bad byte string"),
};
let mut dw = GPTDisk::new(file);
let mut disk = dw.disk(true);
disk.add_partition(name, size, partition_type.into(), 0, None)
.expect("failed to write disk");
disk.write().unwrap();
}
Self::WritePartition { part_idx, source } => {
let file = OpenOptions::new()
.create(false)
.append(false)
.write(true)
.read(true)
.open(file)
.unwrap();
let mut dw = GPTDisk::new(file);
let mut part = match dw.get_partition(*part_idx) {
Some(part) => part,
None => panic!("no such partition"),
};
let mut source = std::fs::File::open(source).unwrap();
let src_size = source.metadata().unwrap().len();
if src_size > part.len() {
panic!("file too big");
}
let n = std::io::copy(&mut source, &mut part).unwrap();
println!("wrote {n} bytes");
}
Self::SetBootCode { source } => {
let file = OpenOptions::new()
.create(false)
.append(false)
.write(true)
.read(true)
.open(file)
.unwrap();
let mut src = OpenOptions::new()
.create(false)
.append(false)
.write(false)
.read(true)
.open(source)
.unwrap();
if src.metadata().unwrap().len() != 440 {
panic!(
"Bad MBR size {}, expected 440.",
src.metadata().unwrap().len()
)
}
let mut mbr = [0u8; 440];
src.read_exact(&mut mbr).unwrap();
let mut dw = GPTDisk::new(file);
dw.set_boot_code(mbr);
}
}
}
}

25
builder/src/cmd/mod.rs Normal file
View File

@ -0,0 +1,25 @@
mod disk;
use clap::Subcommand;
use std::path::PathBuf;
#[derive(Subcommand, Debug)]
pub enum Command {
/// Disk utilities
#[command(alias = "d")]
Disk {
/// The disk image to work on
file: PathBuf,
#[command(subcommand)]
command: disk::DiskCommand,
},
}
impl Command {
pub fn run(&self) {
match self {
Self::Disk { command, file } => command.run(file),
}
}
}

272
builder/src/gpt.rs Normal file
View File

@ -0,0 +1,272 @@
use gpt::{partition_types, DiskDevice, GptDisk};
use std::io::{Read, Seek, SeekFrom, Write};
use std::str::FromStr;
/// Convenience wrapper.
/// Represents a disk with a GPT partition table
pub struct GPTDisk<D: DiskDevice> {
device: D,
start_offset: u64,
}
impl<D: DiskDevice> GPTDisk<D> {
pub fn new(mut device: D) -> Self {
//TODO:error handling
let start_offset = device.stream_position().unwrap();
return Self {
device,
start_offset,
};
}
/// Re-initilaize this disk's GPT metadata.
/// This destroys data and creates a new, empty disk.
pub fn init(&mut self) {
let disk = gpt::GptConfig::new()
.writable(true)
.create_from_device(&mut self.device, None)
.expect("failed to open disk");
disk.write().unwrap();
}
/// Read this disk's partition table.
pub fn disk(&mut self, writable: bool) -> GptDisk<&mut D> {
self.device
.seek(SeekFrom::Start(self.start_offset))
.unwrap();
let disk = gpt::GptConfig::new()
.writable(writable)
.readonly_backup(!writable)
.open_from_device(&mut self.device)
.expect("failed to open disk");
return disk;
}
/// Get a handle to a partition on this disk
pub fn get_partition<'a>(&'a mut self, partition_idx: u32) -> Option<GPTPartition<'a, D>> {
let disk = self.disk(false);
let part = match disk.partitions().get(&partition_idx) {
Some(part) => part,
None => return None,
};
let lb_size = *disk.logical_block_size();
let start_byte = part.bytes_start(lb_size).unwrap();
let length = part.bytes_len(lb_size).unwrap();
return Some(GPTPartition {
disk: self,
start_byte,
length,
seek: 0,
});
}
/// Set this disk's boot code (first 440 bytes of MBR)
pub fn set_boot_code(&mut self, boot_code: [u8; 440]) {
let disk = self.disk(false);
let lbs = *disk.logical_block_size();
drop(disk);
self.device
.seek(SeekFrom::Start(self.start_offset))
.unwrap();
// Get or make MBR
let mut mbr = gpt::mbr::ProtectiveMBR::from_disk(&mut self.device, lbs).unwrap_or(
gpt::mbr::ProtectiveMBR::with_lb_size(lbs.as_u64().try_into().unwrap_or(0xFF_FF_FF_FF)),
);
mbr.set_bootcode(boot_code);
self.device.write_all(&mbr.to_bytes()).unwrap();
}
}
/// A view into a [`GPTDisk`]'s partition.
pub struct GPTPartition<'a, D: DiskDevice> {
/// The disk this partition is on
disk: &'a mut GPTDisk<D>,
/// The index of the first byte of this partition inside its GPTDisk
start_byte: u64,
/// The length of this partition, in bytes.
length: u64,
/// The position we are reading/writing at within this file
seek: u64,
}
impl<'a, D: DiskDevice> Seek for GPTPartition<'a, D> {
fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
match pos {
SeekFrom::Current(x) => {
if x.is_negative() {
let x: u64 = x.abs().try_into().unwrap();
if x > self.seek {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek past start",
));
}
self.seek -= x;
} else {
let x: u64 = x.try_into().unwrap();
if self.seek + x > self.length {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek past end",
));
}
self.seek += x;
}
}
SeekFrom::Start(x) => {
let x: u64 = x.try_into().unwrap();
if x > self.length {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek past end",
));
}
self.seek = x;
}
SeekFrom::End(x) => {
let x: u64 = x.try_into().unwrap();
if x > self.length {
return Err(std::io::Error::new(
std::io::ErrorKind::InvalidInput,
"Seek past start",
));
}
self.seek = self.length - x;
}
}
assert!(self.seek <= self.length);
return Ok(self.seek);
}
}
impl<'a, D: DiskDevice> Read for GPTPartition<'a, D> {
fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
// Save parent cursor & seek
// (Not necessary for now, we don't need to save the parent cursor)
// let initial_pos = self.disk.device.stream_position()?;
self.disk.device.seek(SeekFrom::Start(
self.disk.start_offset + self.start_byte + self.seek,
))?;
// Make sure we stay within this partition
let from_back = self.length - self.seek;
let len: u64 = buf.len().try_into().unwrap();
let len = len.min(from_back);
let len: usize = len.try_into().unwrap();
// Do the read
let n = self.disk.device.read(&mut buf[0..len])?;
// Update cursor
let n_u64: u64 = n.try_into().unwrap();
self.seek += n_u64;
assert!(n_u64 <= from_back);
assert!(self.seek <= self.length);
// Fix parent cursor
// self.disk.device.seek(SeekFrom::Start(initial_pos))?;
return Ok(n);
}
}
impl<'a, D: DiskDevice> Write for GPTPartition<'a, D> {
fn flush(&mut self) -> std::io::Result<()> {
self.disk.device.flush()
}
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
// Save parent cursor & seek
// (Not necessary for now, we don't need to save the parent cursor)
// let initial_pos = self.disk.device.stream_position()?;
self.disk.device.seek(SeekFrom::Start(
self.disk.start_offset + self.start_byte + self.seek,
))?;
// Make sure we stay within this partition
let from_back = self.length - self.seek;
let len: u64 = buf.len().try_into().unwrap();
let len = len.min(from_back);
let len: usize = len.try_into().unwrap();
// Do the write
let n = self.disk.device.write(&buf[0..len])?;
// Update cursor
let n_u64: u64 = n.try_into().unwrap();
self.seek += n_u64;
assert!(n_u64 <= from_back);
assert!(self.seek <= self.length);
// Fix parent cursor
// self.disk.device.seek(SeekFrom::Start(initial_pos))?;
return Ok(n);
}
}
impl<'a, D: DiskDevice> GPTPartition<'a, D> {
/// The index of the first byte in this partition
pub fn start(&self) -> u64 {
self.start_byte
}
/// The length of this partition, in bytes
pub fn len(&self) -> u64 {
self.length
}
}
#[derive(Debug, Clone, Copy)]
pub enum PartitionType {
BIOS,
EFI,
Linux,
}
impl FromStr for PartitionType {
type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"bios" => Self::BIOS,
"efi" => Self::EFI,
"linux" => Self::Linux,
_ => return Err("unknown partition type".into()),
})
}
}
impl From<&PartitionType> for partition_types::Type {
fn from(value: &PartitionType) -> Self {
match value {
PartitionType::BIOS => partition_types::BIOS,
PartitionType::EFI => partition_types::EFI,
PartitionType::Linux => partition_types::LINUX_FS,
}
}
}

49
builder/src/main.rs Normal file
View File

@ -0,0 +1,49 @@
use anstyle::{AnsiColor, Color, Style};
use clap::Parser;
mod cmd;
mod gpt;
mod util;
fn get_styles() -> clap::builder::Styles {
clap::builder::Styles::styled()
.usage(
Style::new()
.bold()
.fg_color(Some(Color::Ansi(AnsiColor::BrightBlue))),
)
.header(
Style::new()
.bold()
.fg_color(Some(Color::Ansi(AnsiColor::Green))),
)
.literal(Style::new().fg_color(Some(Color::Ansi(AnsiColor::BrightBlack))))
.invalid(Style::new().fg_color(Some(Color::Ansi(AnsiColor::Red))))
.error(
Style::new()
.bold()
.fg_color(Some(Color::Ansi(AnsiColor::Red))),
)
.valid(
Style::new()
.bold()
.underline()
.fg_color(Some(Color::Ansi(AnsiColor::Green))),
)
.placeholder(Style::new().fg_color(Some(Color::Ansi(AnsiColor::White))))
}
/// Redox disk utility
#[derive(Parser, Debug)]
#[command(version, about, long_about = None, styles=get_styles())]
struct Cli {
#[command(subcommand)]
command: cmd::Command,
}
/// Demonstrates how to create a new partition table without anything pre-existing
fn main() {
let cli = Cli::parse();
cli.command.run();
}

45
builder/src/util.rs Normal file
View File

@ -0,0 +1,45 @@
// Parse a byte string like `5M` or `8 Tib` into a
// quantity of bytes. Returns `None` if the input
// could not be parsed.
pub fn parse_bytes_postfix(string: &str) -> Option<u64> {
let mut lower = string.trim().to_lowercase();
// Turn Mb & Mib into M and Mi
if lower.ends_with("b") {
lower.pop();
}
let mut string = &lower[..];
let mut multiplier = 1;
let len = string.len();
if string.ends_with("k") {
string = &string[0..len - 1];
multiplier = 1000;
} else if lower.ends_with("m") {
string = &string[0..len - 1];
multiplier = 1000 * 1000;
} else if lower.ends_with("g") {
string = &string[0..len - 1];
multiplier = 1000 * 1000 * 1000;
} else if lower.ends_with("t") {
string = &string[0..len - 1];
multiplier = 1000 * 1000 * 1000 * 1000;
} else if lower.ends_with("ki") {
string = &string[0..len - 2];
multiplier = 1024;
} else if lower.ends_with("mi") {
string = &string[0..len - 2];
multiplier = 1024 * 1024;
} else if lower.ends_with("gi") {
string = &string[0..len - 2];
multiplier = 1024 * 1024 * 1024;
} else if lower.ends_with("ti") {
string = &string[0..len - 2];
multiplier = 1024 * 1024 * 1024 * 1024;
}
return match string.trim().parse::<u64>() {
Ok(x) => Some(multiplier * x),
Err(_) => None,
};
}