Added builder
This commit is contained in:
228
builder/src/cmd/disk.rs
Normal file
228
builder/src/cmd/disk.rs
Normal 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
25
builder/src/cmd/mod.rs
Normal 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
272
builder/src/gpt.rs
Normal 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
49
builder/src/main.rs
Normal 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
45
builder/src/util.rs
Normal 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,
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user