Skip to content

Commit a05d75f

Browse files
authored
Check partition for zeroes (#73)
1 parent 0467316 commit a05d75f

File tree

5 files changed

+171
-22
lines changed

5 files changed

+171
-22
lines changed

Cargo.lock

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ unicode-width = { version = "0.1.8", optional = true }
3434
linefeed = { version = "0.6.0", optional = true }
3535
rand = { version = "0.8", optional = true }
3636
structopt = { version = "0.3", optional = true }
37+
count-zeroes = "0.2.0"
3738

3839
[features]
3940
default = [ "cli" ]

src/cli/commands.rs

Lines changed: 91 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,20 @@
11
use crate::attribute_bits::AttributeBits;
2+
use crate::display_bytes::DisplayBytes;
23
use crate::error::*;
34
use crate::opt::Opt;
45
use crate::table::Table;
56
use crate::types::PartitionTypeGUID;
67
use crate::uuid::{convert_str_to_array, generate_random_uuid, Uuid};
8+
use count_zeroes::CountZeroes;
79
#[cfg(target_os = "linux")]
810
use gptman::linux::reread_partition_table;
911
use gptman::{GPTPartitionEntry, GPT};
12+
use linefeed::{DefaultTerminal, Signal, Terminal};
1013
use std::fs;
11-
use std::io::{Read, Seek, SeekFrom};
14+
use std::io::{Read, Seek, SeekFrom, Write};
1215
use std::path::{Path, PathBuf};
1316

14-
const BYTE_UNITS: &[&str] = &["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
15-
16-
fn format_bytes(value: u64) -> String {
17-
BYTE_UNITS
18-
.iter()
19-
.enumerate()
20-
.map(|(i, u)| (value / 1000_u64.pow(i as u32 + 1), u))
21-
.take_while(|(i, _)| *i > 10)
22-
.map(|(i, u)| format!("{} {}", i, u))
23-
.last()
24-
.unwrap_or_else(|| format!("{} B ", value))
25-
}
17+
const REFRESH_INTERVAL: std::time::Duration = std::time::Duration::from_secs(1);
2618

2719
macro_rules! ask_with_default {
2820
($ask:expr, $parser:expr, $prompt:expr, $default:expr) => {
@@ -89,6 +81,7 @@ where
8981
"Z" => randomize(gpt),
9082
"s" => swap_partition_index(gpt, ask)?,
9183
"C" => copy_all_partitions(gpt, &opt.device, ask)?,
84+
"z" => count_zeroes(gpt, &opt.device, ask)?,
9285
x => println!("{}: unknown command", x),
9386
}
9487

@@ -116,13 +109,22 @@ fn help() {
116109
println!(" S toggle the GUID specific bits");
117110
println!(" t change a partition type");
118111
println!(" u change partition UUID");
112+
println!(" z check how empty a partition physically is (number of empty blocks)");
119113
println!(" Z randomize disk GUID and all partition's GUID");
120114
println!();
121115
println!(" q exit without saving");
122116
println!(" w write table to disk and exit");
123117
println!();
124118
}
125119

120+
fn base_path(path: &Path) -> String {
121+
let mut base_path = path.display().to_string();
122+
if base_path.ends_with(char::is_numeric) {
123+
base_path += "p";
124+
}
125+
base_path
126+
}
127+
126128
fn open_and_print(opt: &Opt, path: &Path, disk_order: bool) -> Result<()> {
127129
let mut f = fs::File::open(path)?;
128130
let len = f.seek(SeekFrom::End(0))?;
@@ -389,7 +391,7 @@ pub fn print(opt: &Opt, path: &Path, gpt: &GPT, len: u64, disk_order: bool) -> R
389391
gpt.align,
390392
gpt.align * gpt.sector_size
391393
);
392-
println!("Disk size: {} ({} bytes)", format_bytes(len), len);
394+
println!("Disk size: {} ({} bytes)", DisplayBytes::new(len), len);
393395
println!(
394396
"Usable sectors: {}-{} ({} sectors)",
395397
gpt.header.first_usable_lba, gpt.header.last_usable_lba, usable,
@@ -402,14 +404,14 @@ pub fn print(opt: &Opt, path: &Path, gpt: &GPT, len: u64, disk_order: bool) -> R
402404
"{}-{} ({})",
403405
i,
404406
i + l - 1,
405-
format_bytes(l * gpt.sector_size).trim()
407+
DisplayBytes::new(l * gpt.sector_size),
406408
))
407409
.collect::<Vec<_>>()
408410
.join(", "),
409411
);
410412
println!(
411413
"Usable space: {} ({} bytes)",
412-
format_bytes(usable * gpt.sector_size),
414+
DisplayBytes::new(usable * gpt.sector_size),
413415
usable * gpt.sector_size,
414416
);
415417
println!("Disk identifier: {}", gpt.header.disk_guid.display_uuid());
@@ -441,10 +443,7 @@ pub fn print(opt: &Opt, path: &Path, gpt: &GPT, len: u64, disk_order: bool) -> R
441443
Column::Name => table.add_cell("Name"),
442444
}
443445
}
444-
let mut base_path = path.display().to_string();
445-
if base_path.ends_with(char::is_numeric) {
446-
base_path += "p";
447-
}
446+
let base_path = base_path(path);
448447

449448
let mut partitions: Vec<_> = gpt.iter().filter(|(_, x)| x.is_used()).collect();
450449

@@ -459,7 +458,9 @@ pub fn print(opt: &Opt, path: &Path, gpt: &GPT, len: u64, disk_order: bool) -> R
459458
Column::Start => table.add_cell_rtl(&format!("{}", p.starting_lba)),
460459
Column::End => table.add_cell_rtl(&format!("{}", p.ending_lba)),
461460
Column::Sectors => table.add_cell_rtl(&format!("{}", p.size()?)),
462-
Column::Size => table.add_cell_rtl(&format_bytes(p.size()? * gpt.sector_size)),
461+
Column::Size => table.add_cell_rtl(
462+
&DisplayBytes::new_padded(p.size()? * gpt.sector_size).to_string(),
463+
),
463464
Column::Type => {
464465
table.add_cell(p.partition_type_guid.display_partition_type_guid().as_str())
465466
}
@@ -899,7 +900,7 @@ where
899900
"Copy partition {} of {} sectors ({}):",
900901
src_i,
901902
size,
902-
format_bytes(size_in_bytes)
903+
DisplayBytes::new(size_in_bytes)
903904
);
904905
let dst_i = ask_free_slot(dst_gpt, ask)?;
905906
let starting_lba = ask_starting_lba(dst_gpt, ask, size)?;
@@ -922,3 +923,71 @@ where
922923

923924
Ok(())
924925
}
926+
927+
fn count_zeroes<F>(gpt: &mut GPT, path: &Path, ask: &F) -> Result<()>
928+
where
929+
F: Fn(&str) -> Result<String>,
930+
{
931+
let i = ask_used_slot(gpt, ask)?;
932+
let base_path = base_path(path);
933+
let partition_path = format!("{}{}", base_path, i);
934+
935+
let mut f = fs::File::open(partition_path)?;
936+
let len = f.seek(std::io::SeekFrom::End(0))?;
937+
f.seek(std::io::SeekFrom::Start(0))?;
938+
let stdout = std::io::stdout();
939+
let mut stdout_lock = stdout.lock();
940+
let mut t1 = std::time::Instant::now();
941+
let t2 = std::time::Instant::now();
942+
let progress_len = DisplayBytes::new(len);
943+
944+
macro_rules! display_progress {
945+
($zeroes:expr, $count:expr) => {{
946+
let _ = write!(
947+
stdout_lock,
948+
"\u{001b}[2K\r{}/{} zeroes: {} ({:.2}%) speed: {}/s",
949+
DisplayBytes::new($count),
950+
progress_len,
951+
DisplayBytes::new($zeroes),
952+
$zeroes as f64 / $count as f64 * 100.0,
953+
DisplayBytes::new($count.checked_div(t2.elapsed().as_secs()).unwrap_or(0)),
954+
);
955+
let _ = stdout_lock.flush();
956+
}};
957+
}
958+
959+
let mut res = Ok(());
960+
let terminal = DefaultTerminal::new()?;
961+
let mut terminal_lock = terminal.lock_read();
962+
let prev_terminal_state = terminal_lock.prepare(true, Signal::Interrupt.into())?;
963+
964+
let (zeroes, count) = f.count_zeroes(|zeroes: u64, count: u64| {
965+
if t1.elapsed() >= REFRESH_INTERVAL {
966+
display_progress!(zeroes, count);
967+
t1 = std::time::Instant::now();
968+
969+
match terminal_lock.wait_for_input(Some(std::time::Duration::ZERO)) {
970+
Err(err) => {
971+
res = Err(err.into());
972+
false
973+
}
974+
Ok(true) => {
975+
res = Err("Interrupted!".into());
976+
false
977+
}
978+
Ok(false) => true,
979+
}
980+
} else {
981+
true
982+
}
983+
})?;
984+
985+
display_progress!(zeroes, count);
986+
let _ = writeln!(stdout_lock);
987+
988+
let mut sink = Vec::new();
989+
let _ = terminal_lock.read(&mut sink);
990+
terminal_lock.restore(prev_terminal_state)?;
991+
992+
res
993+
}

src/cli/display_bytes.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
use std::fmt;
2+
3+
const BYTE_UNITS: &[&str] = &["kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
4+
5+
pub struct DisplayBytes {
6+
unit: Option<&'static str>,
7+
value: f64,
8+
padded: bool,
9+
}
10+
11+
impl fmt::Display for DisplayBytes {
12+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
13+
if let Some(unit) = self.unit {
14+
write!(f, "{:.2} {}", self.value, unit)
15+
} else if self.padded {
16+
write!(f, "{:.0} B ", self.value)
17+
} else {
18+
write!(f, "{:.0} B", self.value)
19+
}
20+
}
21+
}
22+
23+
impl DisplayBytes {
24+
pub fn new(value: u64) -> Self {
25+
let value = value as f64;
26+
27+
if let Some((value, unit)) = BYTE_UNITS
28+
.iter()
29+
.enumerate()
30+
.map(|(i, u)| (value / 1000_f64.powf(i as f64 + 1.0), u))
31+
.take_while(|(i, _)| *i > 1.0)
32+
.last()
33+
{
34+
Self {
35+
unit: Some(unit),
36+
value,
37+
padded: false,
38+
}
39+
} else {
40+
Self {
41+
unit: None,
42+
value,
43+
padded: false,
44+
}
45+
}
46+
}
47+
48+
pub fn new_padded(value: u64) -> Self {
49+
let value = value as f64;
50+
51+
if let Some((value, unit)) = BYTE_UNITS
52+
.iter()
53+
.enumerate()
54+
.map(|(i, u)| (value / 1000_f64.powf(i as f64 + 1.0), u))
55+
.take_while(|(i, _)| *i > 1.0)
56+
.last()
57+
{
58+
Self {
59+
unit: Some(unit),
60+
value,
61+
padded: true,
62+
}
63+
} else {
64+
Self {
65+
unit: None,
66+
value,
67+
padded: true,
68+
}
69+
}
70+
}
71+
}

src/cli/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
mod attribute_bits;
44
mod commands;
5+
mod display_bytes;
56
mod error;
67
mod opt;
78
mod table;

0 commit comments

Comments
 (0)