use crate::attribute_bits::AttributeBits;
use crate::display_bytes::DisplayBytes;
use crate::error::*;
use crate::opt::Opt;
use crate::table::Table;
use crate::types::PartitionTypeGUID;
use crate::uuid::{convert_str_to_array, generate_random_uuid, Uuid};
use count_zeroes::CountZeroes;
#[cfg(target_os = "linux")]
use gptman::linux::reread_partition_table;
use gptman::{GPTPartitionEntry, GPT};
use linefeed::{DefaultTerminal, Signal, Terminal};
use std::fs;
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::{Path, PathBuf};
const REFRESH_INTERVAL: std::time::Duration = std::time::Duration::from_secs(1);
macro_rules! ask_with_default {
($ask:expr, $parser:expr, $prompt:expr, $default:expr) => {
loop {
let input = $ask(&format!("{} (default {}):", $prompt, $default))?;
if input == "" {
break Ok($default);
} else {
match $parser(&input) {
Err(err) => {
println!("{}", err);
}
x => break x,
}
}
}
};
}
pub fn execute<F>(full_command: &str, opt: &Opt, len: u64, gpt: &mut GPT, ask: &F) -> Result<bool>
where
F: Fn(&str) -> Result<String>,
{
let mut it = full_command.split(' ');
let command = it.next().unwrap();
let args = it.collect::<Vec<_>>();
match command {
"m" => help(),
"p" | "P" => {
let disk_order = command == "P";
if args.is_empty() {
print(opt, &opt.device, gpt, len, disk_order)?;
} else {
for path in args {
match open_and_print(opt, path.as_ref(), disk_order) {
Ok(()) => {}
Err(err) => println!("could not open {:?}: {}", path, err),
}
}
}
}
"n" => add_partition(gpt, ask)?,
"d" => delete_partition(gpt, ask)?,
"f" => fix_partitions_order(gpt),
"w" => {
write(gpt, opt)?;
return Ok(true);
}
"t" => change_type(gpt, ask)?,
"u" => change_partition_guid(gpt, ask)?,
"i" => change_disk_guid(gpt, ask)?,
"L" => change_partition_name(gpt, ask)?,
"A" => toggle_legacy_bootable(gpt, ask)?,
"B" => toggle_no_block_io(gpt, ask)?,
"R" => toggle_required(gpt, ask)?,
"S" => toggle_attributes(gpt, ask)?,
"r" => resize_partition(gpt, ask)?,
"c" => copy_partition(gpt, &opt.device, ask)?,
"D" => print_raw_data(gpt, &opt.device)?,
"a" => change_alignment(gpt, ask)?,
"Z" => randomize(gpt),
"s" => swap_partition_index(gpt, ask)?,
"C" => copy_all_partitions(gpt, &opt.device, ask)?,
"z" => count_zeroes(gpt, &opt.device, ask)?,
x => println!("{}: unknown command", x),
}
Ok(false)
}
fn help() {
println!("\nHelp:\n");
println!(" a change partition alignment");
println!(" A toggle the legacy BIOS bootable flag");
println!(" B toggle the no block IO protocol flag");
println!(" c copy a partition from another device (or the same)");
println!(" C copy all partitions from another device (or the same)");
println!(" d delete a partition");
println!(" D print the raw data of the disklabel from the device");
println!(" f fix partitions order");
println!(" i change disk GUID");
println!(" L change partition name");
println!(" n add a new partition");
println!(" p print the partition table (in order of the array)");
println!(" P print the partition table (in order of the disk)");
println!(" r resize a partition");
println!(" R toggle the required partition flag");
println!(" s swap partition indexes");
println!(" S toggle the GUID specific bits");
println!(" t change a partition type");
println!(" u change partition UUID");
println!(" z check how empty a partition physically is (number of empty blocks)");
println!(" Z randomize disk GUID and all partition's GUID");
println!();
println!(" q exit without saving");
println!(" w write table to disk and exit");
println!();
}
fn base_path(path: &Path) -> String {
let mut base_path = path.display().to_string();
if base_path.ends_with(char::is_numeric) {
base_path += "p";
}
base_path
}
fn open_and_print(opt: &Opt, path: &Path, disk_order: bool) -> Result<()> {
let mut f = fs::File::open(path)?;
let len = f.seek(SeekFrom::End(0))?;
let gpt = GPT::find_from(&mut f)?;
print(opt, path, &gpt, len, disk_order)
}
fn ask_free_slot<F>(gpt: &GPT, ask: &F) -> Result<u32>
where
F: Fn(&str) -> Result<String>,
{
let default_i = gpt
.iter()
.filter(|(_, x)| x.is_unused())
.map(|(i, _)| i)
.next()
.ok_or("no available slot")?;
let i = ask_with_default!(
ask,
|x| match u32::from_str_radix(x, 10) {
Ok(i) => {
if i <= 128 && i > 0 {
Ok(i)
} else {
Err("the partition index must be between 1 and 128")
}
}
Err(_) => Err("could not parse integer"),
},
"Enter free partition number",
default_i
)?;
if gpt[i].is_used() {
println!("WARNING: partition {} is going to be overwritten", i);
}
Ok(i)
}
fn ask_used_slot<F>(gpt: &GPT, ask: &F) -> Result<u32>
where
F: Fn(&str) -> Result<String>,
{
let default_i = gpt
.iter()
.filter(|(_, x)| x.is_used())
.map(|(i, _)| i)
.last()
.ok_or("no partition found")?;
let i = loop {
match ask_with_default!(
ask,
|x| match u32::from_str_radix(x, 10) {
Ok(i) => {
if i <= 128 && i > 0 {
Ok(i)
} else {
Err("the partition index must be between 1 and 128")
}
}
Err(_) => Err("could not parse integer"),
},
"Enter used partition number",
default_i
)? {
i if gpt[i].is_unused() => println!("Partition number {} is not used", i),
i => break i,
}
};
Ok(i)
}
fn ask_partition<F>(ask: &F, prompt: &str) -> Result<u32>
where
F: Fn(&str) -> Result<String>,
{
Ok(loop {
match u32::from_str_radix(ask(prompt)?.as_ref(), 10) {
Err(err) => println!("{}", err),
Ok(i) if i > 128 || i == 0 => println!("the partition index must be between 1 and 128"),
Ok(i) => break i,
}
})
}
fn ask_starting_lba<F>(gpt: &GPT, ask: &F, size: u64) -> Result<u64>
where
F: Fn(&str) -> Result<String>,
{
let optimal_lba = gpt
.find_optimal_place(size)
.ok_or("not enough space on device")?;
let first_lba = gpt.find_first_place(size).unwrap();
let last_lba = gpt.find_last_place(size).unwrap();
let starting_lba = ask_with_default!(
ask,
|x| match x {
">" => Ok(last_lba),
"<" => Ok(first_lba),
"^" => Ok(optimal_lba),
x => parse_lba(gpt, x, first_lba, last_lba),
},
&format!(
"Partition starting LBA (< {}, > {}, sectors, kib, mib, kb, mb, ...)",
first_lba, last_lba
),
optimal_lba
)?;
Ok(starting_lba)
}
fn parse_lba(gpt: &GPT, value: &str, min: u64, max: u64) -> Result<u64> {
let n = value.trim_end_matches(char::is_alphabetic).parse::<u64>()?;
let unit = (*value)
.to_uppercase()
.as_str()
.trim_start_matches(char::is_numeric)
.to_string();
let result = match unit.as_str() {
"KIB" => (n * 1024 - 1) / gpt.sector_size + 1,
"MIB" => (n * 1024_u64.pow(2) - 1) / gpt.sector_size + 1,
"GIB" => (n * 1024_u64.pow(3) - 1) / gpt.sector_size + 1,
"TIB" => (n * 1024_u64.pow(4) - 1) / gpt.sector_size + 1,
"PIB" => (n * 1024_u64.pow(5) - 1) / gpt.sector_size + 1,
"EIB" => (n * 1024_u64.pow(6) - 1) / gpt.sector_size + 1,
"ZIB" => (n * 1024_u64.pow(7) - 1) / gpt.sector_size + 1,
"YIB" => (n * 1024_u64.pow(8) - 1) / gpt.sector_size + 1,
"KB" => (n * 1000 - 1) / gpt.sector_size + 1,
"MB" => (n * 1000_u64.pow(2) - 1) / gpt.sector_size + 1,
"GB" => (n * 1000_u64.pow(3) - 1) / gpt.sector_size + 1,
"TB" => (n * 1000_u64.pow(4) - 1) / gpt.sector_size + 1,
"PB" => (n * 1000_u64.pow(5) - 1) / gpt.sector_size + 1,
"EB" => (n * 1000_u64.pow(6) - 1) / gpt.sector_size + 1,
"ZB" => (n * 1000_u64.pow(7) - 1) / gpt.sector_size + 1,
"YB" => (n * 1000_u64.pow(8) - 1) / gpt.sector_size + 1,
"" => value.parse::<u64>()?,
x => return Err(Error::new(&format!("Invalid unit: {}", x))),
};
let aligned_up = ((result - 1) / gpt.align + 1) * gpt.align;
if aligned_up < min {
Err("The value is too small".into())
} else if aligned_up > max {
Err("The value is too big".into())
} else {
Ok(aligned_up)
}
}
#[test]
fn test_parse_lba() {
use std::io;
let ss = 2;
let data = vec![0; 2 * 2048 * 10];
let mut cur = io::Cursor::new(data);
let mut gpt = GPT::new_from(&mut cur, ss, [0; 16]).unwrap();
gpt.align = 1;
assert_eq!(parse_lba(&gpt, "1", 1, 1), Ok(1));
assert_eq!(parse_lba(&gpt, "42", 1, 42), Ok(42));
assert!(parse_lba(&gpt, "42", 1, 1).is_err());
assert!(parse_lba(&gpt, "1", 2, 1).is_err());
assert!(parse_lba(&gpt, "1dl", 1, u64::max_value()).is_err());
assert_eq!(parse_lba(&gpt, "1kib", 1, u64::max_value()), Ok(1024 / ss));
assert_eq!(
parse_lba(&gpt, "5kib", 1, u64::max_value()),
Ok(5 * 1024 / ss)
);
assert_eq!(
parse_lba(&gpt, "1mib", 1, u64::max_value()),
Ok(1024 * 1024 / ss)
);
assert_eq!(
parse_lba(&gpt, "1gib", 1, u64::max_value()),
Ok(1024_u64.pow(3) / ss)
);
assert_eq!(parse_lba(&gpt, "1kb", 1, u64::max_value()), Ok(1000 / ss));
assert_eq!(
parse_lba(&gpt, "1mb", 1, u64::max_value()),
Ok(1_000_000 / ss)
);
assert_eq!(
parse_lba(&gpt, "1gb", 1, u64::max_value()),
Ok(1_000_000_000 / ss)
);
gpt.align = 5;
assert_eq!(parse_lba(&gpt, "1", 1, u64::max_value()), Ok(5));
assert_eq!(parse_lba(&gpt, "42", 1, u64::max_value()), Ok(45));
assert_eq!(
parse_lba(&gpt, "1kib", 1, u64::max_value()),
Ok(1024 / ss / 5 * 5 + 5)
);
assert_eq!(
parse_lba(&gpt, "4kib", 1, u64::max_value()),
Ok(4 * 1024 / ss / 5 * 5 + 5)
);
assert_eq!(
parse_lba(&gpt, "5kib", 1, u64::max_value()),
Ok(5 * 1024 / ss)
);
gpt.sector_size = 4096;
gpt.align = 1;
assert_eq!(parse_lba(&gpt, "1kib", 1, u64::max_value()), Ok(1));
}
fn print_bytes<R>(reader: &mut R, limit: usize) -> Result<()>
where
R: Read + Seek,
{
let mut bytes_read = 0;
let mut pos = reader.seek(SeekFrom::Current(0))?;
let mut skipping = false;
while bytes_read < limit {
let mut data = vec![0; 16.min(limit - bytes_read)];
let len = reader.read(&mut data)?;
pos += len as u64;
bytes_read += len;
if data == [0; 16] {
if !skipping {
skipping = true;
println!("*");
}
continue;
} else {
skipping = false;
}
print!("{:08x} ", pos);
let mut it = data.iter().take(len);
for b in it.by_ref().take(8) {
print!("{:02x} ", b);
}
for b in it.by_ref() {
print!(" {:02x}", b);
}
println!();
}
Ok(())
}
pub fn print(opt: &Opt, path: &Path, gpt: &GPT, len: u64, disk_order: bool) -> Result<()> {
use crate::opt::Column;
let usable = gpt.header.last_usable_lba - gpt.header.first_usable_lba + 1;
println!("Sector size: {} bytes", gpt.sector_size);
println!(
"Partition alignment: {} ({} bytes)",
gpt.align,
gpt.align * gpt.sector_size
);
println!("Disk size: {} ({} bytes)", DisplayBytes::new(len), len);
println!(
"Usable sectors: {}-{} ({} sectors)",
gpt.header.first_usable_lba, gpt.header.last_usable_lba, usable,
);
println!(
"Free sectors: {}",
gpt.find_free_sectors()
.iter()
.map(|(i, l)| format!(
"{}-{} ({})",
i,
i + l - 1,
DisplayBytes::new(l * gpt.sector_size),
))
.collect::<Vec<_>>()
.join(", "),
);
println!(
"Usable space: {} ({} bytes)",
DisplayBytes::new(usable * gpt.sector_size),
usable * gpt.sector_size,
);
println!("Disk identifier: {}", gpt.header.disk_guid.display_uuid());
println!();
let misaligned = gpt
.iter()
.filter(|(_, x)| x.is_used() && x.starting_lba % gpt.align != 0)
.map(|(i, _)| format!("{}", i))
.collect::<Vec<_>>();
if !misaligned.is_empty() {
println!(
"WARNING: some partitions are not aligned: {}\n",
misaligned.join(", ")
);
}
let mut table = Table::new(opt.columns.len());
for column in opt.columns.iter() {
match column {
Column::Device => table.add_cell("Device"),
Column::Start => table.add_cell_rtl("Start"),
Column::End => table.add_cell_rtl("End"),
Column::Sectors => table.add_cell_rtl("Sectors"),
Column::Size => table.add_cell_rtl("Size"),
Column::Type => table.add_cell("Type"),
Column::Guid => table.add_cell("GUID"),
Column::Attributes => table.add_cell("Attributes"),
Column::Name => table.add_cell("Name"),
}
}
let base_path = base_path(path);
let mut partitions: Vec<_> = gpt.iter().filter(|(_, x)| x.is_used()).collect();
if disk_order {
partitions.sort_by_key(|(_, x)| x.starting_lba);
}
for (i, p) in partitions {
for column in opt.columns.iter() {
match column {
Column::Device => table.add_cell(&format!("{}{}", base_path, i)),
Column::Start => table.add_cell_rtl(&format!("{}", p.starting_lba)),
Column::End => table.add_cell_rtl(&format!("{}", p.ending_lba)),
Column::Sectors => table.add_cell_rtl(&format!("{}", p.size()?)),
Column::Size => table.add_cell_rtl(
&DisplayBytes::new_padded(p.size()? * gpt.sector_size).to_string(),
),
Column::Type => {
table.add_cell(p.partition_type_guid.display_partition_type_guid().as_str())
}
Column::Guid => table.add_cell(p.unique_partition_guid.display_uuid().as_str()),
Column::Attributes => table.add_cell(
p.attribute_bits
.display_attribute_bits(p.partition_type_guid)
.as_str(),
),
Column::Name => table.add_cell(p.partition_name.as_str()),
}
}
}
print!("{}", table);
Ok(())
}
fn add_partition<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let max_size: u64 = gpt.get_maximum_partition_size()?;
let default_unique_partition_guid = generate_random_uuid();
let i = ask_free_slot(gpt, ask)?;
let size = ask_with_default!(
ask,
|x| parse_lba(gpt, x, 1, max_size),
"Partition size (sectors, kib, mib, kb, mb, ...)",
max_size
)?;
if size == 0 {
return Err("The size must be at least 1 sector".into());
}
let partition_type_guid = ask_partition_type_guid(ask)?;
let starting_lba = ask_starting_lba(gpt, ask, size)?;
let partition_name = ask("Partition name:")?.as_str().into();
let unique_partition_guid = match ask("Partition GUID (default: random):")?.as_ref() {
"" => default_unique_partition_guid,
x => convert_str_to_array(x)?,
};
gpt[i] = GPTPartitionEntry {
starting_lba,
ending_lba: starting_lba + size - 1,
attribute_bits: 0,
partition_name,
partition_type_guid,
unique_partition_guid,
};
Ok(())
}
fn fix_partitions_order(gpt: &mut GPT) {
gpt.sort();
}
fn write(gpt: &mut GPT, opt: &Opt) -> Result<()> {
let mut f = fs::OpenOptions::new().write(true).open(&opt.device)?;
gpt.write_into(&mut f)?;
if opt.init {
GPT::write_protective_mbr_into(&mut f, gpt.sector_size)?;
println!("protective MBR has been written");
}
#[cfg(target_os = "linux")]
{
if let Err(err) = reread_partition_table(&mut f) {
println!("rereading partition table failed: {}", err);
}
}
Ok(())
}
fn change_type<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
gpt[i].partition_type_guid = ask_partition_type_guid(ask)?;
Ok(())
}
fn ask_partition_type_guid<F>(ask: &F) -> Result<[u8; 16]>
where
F: Fn(&str) -> Result<String>,
{
use crate::types::TYPE_MAP;
let mut categories: Vec<_> = TYPE_MAP.keys().collect();
categories.sort_by_key(|&a| a.to_ascii_lowercase());
loop {
match ask("Partition type GUID (type L to list all types):")?.as_ref() {
"" => {}
"q" => break,
"L" => {
loop {
println!("Category:");
for (i, cat) in categories.iter().enumerate() {
println!("{:2} => {}", i + 1, cat);
}
match ask("Choose category (q to go back):")?.as_ref() {
"" => {}
"q" => break,
i => loop {
if let Some(types_map) = i
.parse::<usize>()
.ok()
.and_then(|x| categories.get(x - 1))
.and_then(|x| TYPE_MAP.get(*x))
{
let mut types: Vec<_> = types_map.iter().collect();
types.sort_by(|a, b| a.1.cmp(b.1));
let types: Vec<(usize, &(&[u8; 16], &&str))> =
types.iter().enumerate().collect();
println!("Partition types:");
for (i, (guid, name)) in types.iter() {
println!("{:2} => {}: {}", i + 1, guid.display_uuid(), name);
}
match ask("Choose partition type (q to go back):")?.as_ref() {
"" => {}
"q" => break,
i => {
if let Some(arr) = i.parse::<usize>().ok().and_then(|x| {
types.get(x - 1).map(|(_, (arr, _))| **arr)
}) {
return Ok(arr);
}
}
}
}
},
}
}
}
x => match convert_str_to_array(x) {
Ok(arr) => return Ok(arr),
Err(err) => {
println!("{}", err);
}
},
}
}
Err(Error::new("aborted."))
}
fn change_partition_guid<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let default_unique_partition_guid = generate_random_uuid();
let i = ask_used_slot(gpt, ask)?;
let unique_partition_guid = match ask("Partition GUID (default: random):")?.as_ref() {
"" => default_unique_partition_guid,
x => convert_str_to_array(x)?,
};
gpt[i].unique_partition_guid = unique_partition_guid;
Ok(())
}
fn change_disk_guid<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let default_disk_guid = generate_random_uuid();
let disk_guid = match ask("Disk GUID (default: random):")?.as_ref() {
"" => default_disk_guid,
x => convert_str_to_array(x)?,
};
gpt.header.disk_guid = disk_guid;
Ok(())
}
fn change_partition_name<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
let partition_name = ask("Partition name:")?.as_str().into();
gpt[i].partition_name = partition_name;
Ok(())
}
fn toggle_legacy_bootable<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
gpt[i].attribute_bits ^= 0b100;
Ok(())
}
fn toggle_no_block_io<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
gpt[i].attribute_bits ^= 0b10;
Ok(())
}
fn toggle_required<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
gpt[i].attribute_bits ^= 0b1;
Ok(())
}
fn toggle_attributes<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
let attributes = loop {
match ask("Enter GUID specific bits (48-63):")?.as_str() {
"" => return Ok(()),
s => {
let attributes = s
.split(',')
.map(|x| u64::from_str_radix(x, 10))
.collect::<Vec<_>>();
if let Some(attr) = attributes.iter().find(|x| x.is_err()) {
println!("{}", attr.as_ref().unwrap_err());
} else if let Some(attr) = attributes
.iter()
.map(|x| x.as_ref().unwrap())
.find(|x| **x < 48 || **x > 63)
{
println!("invalid attribute: {}", attr);
} else {
#[allow(clippy::redundant_closure)]
break attributes.into_iter().map(|x| x.unwrap());
}
}
}
};
for x in attributes {
gpt[i].attribute_bits ^= 1 << x;
}
Ok(())
}
fn resize_partition<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
let free_sectors = gpt.find_free_sectors();
let max_size = {
let align = gpt.align;
let p = &mut gpt[i];
(p.size()?
+ free_sectors
.iter()
.skip_while(|(m, _)| *m < p.starting_lba)
.take(1)
.find(|(m, _)| *m == ((p.ending_lba + 1) / align) * align)
.map(|(_, n)| n)
.unwrap_or(&0))
/ align
* align
};
let size = ask_with_default!(
ask,
|x| parse_lba(gpt, x, 1, max_size),
"Partition size",
max_size
)?;
gpt[i].ending_lba = gpt[i].starting_lba + size - 1;
Ok(())
}
fn copy_partition<F>(dst_gpt: &mut GPT, dst_path: &Path, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let src_path: PathBuf =
match ask(&format!("From disk (default {}):", dst_path.display()))?.as_str() {
"" => dst_path.to_path_buf(),
x => x.into(),
};
let src_gpt = GPT::find_from(&mut fs::File::open(src_path)?)?;
let src_i = ask_used_slot(&src_gpt, ask)?;
let dst_i = ask_free_slot(dst_gpt, ask)?;
let size_in_bytes = src_gpt[src_i].size()? * src_gpt.sector_size;
if size_in_bytes % dst_gpt.sector_size != 0 {
return Err(Error::new(&format!(
"Partition size {} is not aligned to sector size {}",
size_in_bytes, dst_gpt.sector_size
)));
}
let size = size_in_bytes / dst_gpt.sector_size;
let starting_lba = ask_starting_lba(dst_gpt, ask, size)?;
dst_gpt[dst_i] = src_gpt[src_i].clone();
dst_gpt[dst_i].starting_lba = starting_lba;
dst_gpt[dst_i].ending_lba = starting_lba + size - 1;
Ok(())
}
fn print_raw_data(gpt: &GPT, path: &Path) -> Result<()> {
let mut f = fs::File::open(path)?;
print_table(&mut f, "First sector", 0, gpt.sector_size as u32)?;
print_table(
&mut f,
"GPT header",
gpt.header.primary_lba * gpt.sector_size,
gpt.header.header_size,
)?;
print_table(
&mut f,
"GPT entries",
gpt.header.partition_entry_lba * gpt.sector_size,
gpt.header.number_of_partition_entries * gpt.header.size_of_partition_entry,
)?;
Ok(())
}
fn print_table<R>(reader: &mut R, label: &str, offset: u64, size: u32) -> Result<()>
where
R: Read + Seek,
{
println!("{}: offset = {}, size = {}", label, offset, size);
reader.seek(SeekFrom::Start(offset))?;
print_bytes(reader, size as usize)?;
println!();
Ok(())
}
fn change_alignment<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
gpt.align = ask_with_default!(
ask,
|x| u64::from_str_radix(x, 10),
"Partition alignment",
gpt.align
)?;
Ok(())
}
fn randomize(gpt: &mut GPT) {
gpt.header.disk_guid = generate_random_uuid();
for (_, p) in gpt.iter_mut() {
p.unique_partition_guid = generate_random_uuid();
}
}
fn swap_partition_index<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i1 = ask_partition(ask, "Enter first partition number:")?;
let i2 = ask_partition(ask, "Enter second partition number:")?;
let p1 = gpt[i1].clone();
gpt[i1] = gpt[i2].clone();
gpt[i2] = p1;
Ok(())
}
fn copy_all_partitions<F>(dst_gpt: &mut GPT, dst_path: &Path, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let src_path: PathBuf =
match ask(&format!("From disk (default {}):", dst_path.display()))?.as_str() {
"" => dst_path.to_path_buf(),
x => x.into(),
};
let src_gpt = GPT::find_from(&mut fs::File::open(src_path)?)?;
for (src_i, p) in src_gpt.iter().filter(|(_, x)| x.is_used()) {
let size_in_bytes = p.size()? * src_gpt.sector_size;
if size_in_bytes % dst_gpt.sector_size != 0 {
return Err(Error::new(&format!(
"Partition size {} is not aligned to sector size {}",
size_in_bytes, dst_gpt.sector_size
)));
}
let size = size_in_bytes / dst_gpt.sector_size;
println!(
"Copy partition {} of {} sectors ({}):",
src_i,
size,
DisplayBytes::new(size_in_bytes)
);
let dst_i = ask_free_slot(dst_gpt, ask)?;
let starting_lba = ask_starting_lba(dst_gpt, ask, size)?;
dst_gpt[dst_i] = p.clone();
dst_gpt[dst_i].starting_lba = starting_lba;
dst_gpt[dst_i].ending_lba = starting_lba + size - 1;
}
Ok(())
}
fn delete_partition<F>(gpt: &mut GPT, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
gpt.remove(i)?;
Ok(())
}
fn count_zeroes<F>(gpt: &mut GPT, path: &Path, ask: &F) -> Result<()>
where
F: Fn(&str) -> Result<String>,
{
let i = ask_used_slot(gpt, ask)?;
let base_path = base_path(path);
let partition_path = format!("{}{}", base_path, i);
let mut f = fs::File::open(partition_path)?;
let len = f.seek(std::io::SeekFrom::End(0))?;
f.seek(std::io::SeekFrom::Start(0))?;
let stdout = std::io::stdout();
let mut stdout_lock = stdout.lock();
let mut t1 = std::time::Instant::now();
let t2 = std::time::Instant::now();
let progress_len = DisplayBytes::new(len);
macro_rules! display_progress {
($zeroes:expr, $count:expr) => {{
let _ = write!(
stdout_lock,
"\u{001b}[2K\r{}/{} zeroes: {} ({:.2}%) speed: {}/s",
DisplayBytes::new($count),
progress_len,
DisplayBytes::new($zeroes),
$zeroes as f64 / $count as f64 * 100.0,
DisplayBytes::new($count.checked_div(t2.elapsed().as_secs()).unwrap_or(0)),
);
let _ = stdout_lock.flush();
}};
}
let mut res = Ok(());
let terminal = DefaultTerminal::new()?;
let mut terminal_lock = terminal.lock_read();
let prev_terminal_state = terminal_lock.prepare(true, Signal::Interrupt.into())?;
let (zeroes, count) = f.count_zeroes(|zeroes: u64, count: u64| {
if t1.elapsed() >= REFRESH_INTERVAL {
display_progress!(zeroes, count);
t1 = std::time::Instant::now();
match terminal_lock.wait_for_input(Some(std::time::Duration::ZERO)) {
Err(err) => {
res = Err(err.into());
false
}
Ok(true) => {
res = Err("Interrupted!".into());
false
}
Ok(false) => true,
}
} else {
true
}
})?;
display_progress!(zeroes, count);
let _ = writeln!(stdout_lock);
let mut sink = Vec::new();
let _ = terminal_lock.read(&mut sink);
terminal_lock.restore(prev_terminal_state)?;
res
}