diff --git a/Cargo.toml b/Cargo.toml index c3058d8..668303b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,38 +1,6 @@ -[package] -name = "wbtools" -version = "1.1.0" -authors = ["Akira Hayakawa "] - -[dependencies] -crc = "3" -byteorder = "1" -clap = { version = "4", features = ["derive"] } -anyhow = "1" - -[[bin]] -name = "wbcheck" -path = "src/wbcheck.rs" - -[[bin]] -name = "wbcreate" -path = "src/wbcreate.rs" - -[[bin]] -name = "wbdump" -path = "src/wbdump.rs" - -[[bin]] -name = "wbmeta" -path = "src/wbmeta.rs" - -[[bin]] -name = "wbremove" -path = "src/wbremove.rs" - -[[bin]] -name = "wbstatus" -path = "src/wbstatus.rs" - -[lib] -name = "lib" -path = "src/lib.rs" +[workspace] +resolver = "2" +members = [ + "wbtools", + "writeboost-cli" +] \ No newline at end of file diff --git a/README.md b/README.md index 618bacc..4d0d0db 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ Rustup is the best way to install Rust toolset. https://github.com/rust-lang-nursery/rustup.rs -Then `cargo install` to install the commands. +Then `cargo install --path writeboost-cli` to install **writeboost-cli**. +(For older version, `cargo install --path wbtools`) ## Video @@ -19,46 +20,46 @@ https://www.youtube.com/watch?v=QAXbmr2Rf30 ## Commands (Lexicographical order) -### wbcheck +### check Check if the log was written successfully. -* `wbcheck /dev/cache 1` checks the segment ID 1. Returns 0 on success. +* `writeboost-cli check /dev/cache 1` checks the segment ID 1. Returns 0 on success. -### wbcreate +### create Create a WB device (wrapping dmsetup create) -* `wbcreate wbdev /dev/backing /dev/cache --reformat` creates a WB device with reformatting -* `wbcreate wbdev /dev/backing /dev/cache --reformat --read_cache_threshold=4 --writeback_threshold=70` +* `writeboost-cli create wbdev /dev/backing /dev/cache --reformat` creates a WB device with reformatting +* `writeboost-cli create wbdev /dev/backing /dev/cache --reformat --read_cache_threshold=4 --writeback_threshold=70` creates a WB device with some settings -### wbdump +### dump Dump the data blocks corresponding to the specified metablock ID. -* `wbdump /dev/cache 126` dumps the data block of metablock index 126 +* `writeboost-cli dump /dev/cache 126` dumps the data block of metablock index 126 -### wbmeta +### meta Look into the metadata in the cache device. -* `wbmeta /dev/cache 0` dumps the superblock metadata (ID 0 is special) -* `wbmeta /dev/cache 1` dumps the metadata of segment ID 1 +* `writeboost-cli meta /dev/cache 0` dumps the superblock metadata (ID 0 is special) +* `writeboost-cli meta /dev/cache 1` dumps the metadata of segment ID 1 ### wbremove Remove a WB device -* `wbremove wbdev` removes a WB device after flushing data in RAM buffer and then writing back +* `writeboost-cli remove wbdev` removes a WB device after flushing data in RAM buffer and then writing back all cache blocks. This is the way Dmirty Smirnov's writeboost script suggests. (Recommended) -* `wbremove wbdev --nowriteback` remove a WB device without writing back all cache blocks. +* `writeboost-cli remove wbdev --nowriteback` remove a WB device without writing back all cache blocks. ### wbstatus Pretty-print the status line. -* `dmsetup status wbdev | wbstatus` +* `dmsetup status wbdev | writeboost-cli status` ## Author diff --git a/wbtools/Cargo.toml b/wbtools/Cargo.toml new file mode 100644 index 0000000..c3058d8 --- /dev/null +++ b/wbtools/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "wbtools" +version = "1.1.0" +authors = ["Akira Hayakawa "] + +[dependencies] +crc = "3" +byteorder = "1" +clap = { version = "4", features = ["derive"] } +anyhow = "1" + +[[bin]] +name = "wbcheck" +path = "src/wbcheck.rs" + +[[bin]] +name = "wbcreate" +path = "src/wbcreate.rs" + +[[bin]] +name = "wbdump" +path = "src/wbdump.rs" + +[[bin]] +name = "wbmeta" +path = "src/wbmeta.rs" + +[[bin]] +name = "wbremove" +path = "src/wbremove.rs" + +[[bin]] +name = "wbstatus" +path = "src/wbstatus.rs" + +[lib] +name = "lib" +path = "src/lib.rs" diff --git a/data/sample.cache.226 b/wbtools/data/sample.cache.226 similarity index 100% rename from data/sample.cache.226 rename to wbtools/data/sample.cache.226 diff --git a/data/sample.status.226 b/wbtools/data/sample.status.226 similarity index 100% rename from data/sample.status.226 rename to wbtools/data/sample.status.226 diff --git a/data/sample.table.226 b/wbtools/data/sample.table.226 similarity index 100% rename from data/sample.table.226 rename to wbtools/data/sample.table.226 diff --git a/data/sys_dev.0 b/wbtools/data/sys_dev.0 similarity index 100% rename from data/sys_dev.0 rename to wbtools/data/sys_dev.0 diff --git a/src/lib.rs b/wbtools/src/lib.rs similarity index 100% rename from src/lib.rs rename to wbtools/src/lib.rs diff --git a/src/wbcheck.rs b/wbtools/src/wbcheck.rs similarity index 100% rename from src/wbcheck.rs rename to wbtools/src/wbcheck.rs diff --git a/src/wbcreate.rs b/wbtools/src/wbcreate.rs similarity index 100% rename from src/wbcreate.rs rename to wbtools/src/wbcreate.rs diff --git a/src/wbdump.rs b/wbtools/src/wbdump.rs similarity index 100% rename from src/wbdump.rs rename to wbtools/src/wbdump.rs diff --git a/src/wbmeta.rs b/wbtools/src/wbmeta.rs similarity index 100% rename from src/wbmeta.rs rename to wbtools/src/wbmeta.rs diff --git a/src/wbremove.rs b/wbtools/src/wbremove.rs similarity index 100% rename from src/wbremove.rs rename to wbtools/src/wbremove.rs diff --git a/src/wbstatus.rs b/wbtools/src/wbstatus.rs similarity index 100% rename from src/wbstatus.rs rename to wbtools/src/wbstatus.rs diff --git a/writeboost-cli/Cargo.toml b/writeboost-cli/Cargo.toml new file mode 100644 index 0000000..8309c04 --- /dev/null +++ b/writeboost-cli/Cargo.toml @@ -0,0 +1,11 @@ +[package] +name = "writeboost-cli" +version = "2.0.0" +authors = ["Akira Hayakawa "] +edition = "2024" + +[dependencies] +anyhow = "1" +byteorder = "1" +clap = { version = "4", features = ["derive"] } +crc = "3" \ No newline at end of file diff --git a/writeboost-cli/data/sample.cache.226 b/writeboost-cli/data/sample.cache.226 new file mode 100644 index 0000000..00e4a58 Binary files /dev/null and b/writeboost-cli/data/sample.cache.226 differ diff --git a/writeboost-cli/data/sample.status.226 b/writeboost-cli/data/sample.status.226 new file mode 100644 index 0000000..fd41ea7 --- /dev/null +++ b/writeboost-cli/data/sample.status.226 @@ -0,0 +1 @@ +0 204800 writeboost 2 762 6 73 73 67 391 12 224 0 0 15 9 1 0 15 6567 0 0 1 0 15 0 11 10 writeback_threshold 0 nr_cur_batched_writeback 1 sync_data_interval 0 update_sb_record_interval 0 read_cache_threshold 0 diff --git a/writeboost-cli/data/sample.table.226 b/writeboost-cli/data/sample.table.226 new file mode 100644 index 0000000..262516c --- /dev/null +++ b/writeboost-cli/data/sample.table.226 @@ -0,0 +1 @@ +0 204800 writeboost 251:0 251:3 diff --git a/writeboost-cli/data/sys_dev.0 b/writeboost-cli/data/sys_dev.0 new file mode 100644 index 0000000..cb196e4 --- /dev/null +++ b/writeboost-cli/data/sys_dev.0 @@ -0,0 +1,5 @@ +MAJOR=252 +MINOR=1 +DEVNAME=vda1 +DEVTYPE=partition +PARTN=1 diff --git a/writeboost-cli/src/main.rs b/writeboost-cli/src/main.rs new file mode 100644 index 0000000..eb386e5 --- /dev/null +++ b/writeboost-cli/src/main.rs @@ -0,0 +1,35 @@ +use clap::{Args, Parser, Subcommand}; + +mod sub; + +mod utils; +use utils::*; + +#[derive(Subcommand)] +enum Sub { + Check(sub::check::CommandArgs), + Create(sub::create::CommandArgs), + Remove(sub::remove::CommandArgs), + Dump(sub::dump::CommandArgs), + Meta(sub::meta::CommandArgs), + Status(sub::status::CommandArgs), +} + +#[derive(Parser)] +struct CommandArgs { + #[clap(subcommand)] + sub: Sub, +} + +fn main() { + let args = CommandArgs::parse(); + + match args.sub { + Sub::Check(args) => sub::check::run(args), + Sub::Create(args) => sub::create::run(args), + Sub::Remove(args) => sub::remove::run(args), + Sub::Dump(args) => sub::dump::run(args), + Sub::Meta(args) => sub::meta::run(args), + Sub::Status(args) => sub::status::run(args), + } +} diff --git a/writeboost-cli/src/sub/check/mod.rs b/writeboost-cli/src/sub/check/mod.rs new file mode 100644 index 0000000..7934e4e --- /dev/null +++ b/writeboost-cli/src/sub/check/mod.rs @@ -0,0 +1,67 @@ +use super::*; + +use std::fs::File; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; + +use crc::{CRC_32_ISCSI, Crc}; +pub const CASTAGNOLI: Crc = Crc::::new(&CRC_32_ISCSI); +fn checksum(data: &[u8]) -> u32 { + CASTAGNOLI.checksum(data) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_checksum() { + let buf = vec![0; 4096 - 512]; + assert_eq!(checksum(&buf), 143703573); + } +} + +#[derive(Args)] +#[command(about = "Check if the segment is broken")] +pub struct CommandArgs { + #[arg(help = "Path to the cache device")] + cachedev: String, + #[arg(help = "Segment id")] + segid: i32, +} + +pub fn run(args: CommandArgs) { + let devname: String = args.cachedev; + let id = args.segid; + let cache_dev = CacheDevice::new(devname.to_owned()); + + let mut f = File::open(&devname).expect(&format!("Device {} not found", &devname)); + + let start_byte: u64 = (cache_dev.calc_segment_start(id) as u64) << 9; + f.seek(SeekFrom::Start(start_byte)).unwrap(); + + let header = { + let mut buf = vec![0; 512]; + f.read(&mut buf).unwrap(); + SegmentHeader::from_buf(&buf) + }; + + if header.uninitialized() { + std::process::exit(0); + } + + let computed = { + let size: usize = (4096 - 512) + ((header.length as usize) << 12); + let mut buf = vec![0; size]; + f.read(&mut buf).unwrap(); + checksum(&buf) + }; + + if computed != header.checksum { + panic!( + "Checksum is broken. computed={}, expected={}", + computed, header.checksum + ); + } +} diff --git a/writeboost-cli/src/sub/create/mod.rs b/writeboost-cli/src/sub/create/mod.rs new file mode 100644 index 0000000..cdb82df --- /dev/null +++ b/writeboost-cli/src/sub/create/mod.rs @@ -0,0 +1,104 @@ +use super::*; + +use std::process::Command; + +#[derive(Args)] +#[command(about = "Create a writeboost device")] +#[command(author, version)] +#[clap(rename_all = "snake_case")] +pub struct CommandArgs { + #[arg(help = "Name of the writeboost device")] + lvname: String, + #[arg(help = "Path to the backing device")] + backingdev: String, + #[arg(help = "Path to the cache device")] + cachedev: String, + #[arg( + help = "Reformat the cache device. This cleans up all existing cache blocks", + long + )] + reformat: bool, + #[arg(long)] + write_around_mode: bool, + #[arg(long, value_name = "INT")] + writeback_threshold: Option, + #[arg(long, value_name = "INT")] + nr_max_batched_writeback: Option, + #[arg(long, value_name = "INT")] + update_sb_record_interval: Option, + #[arg(long, value_name = "INT")] + sync_data_interval: Option, + #[arg(long, value_name = "INT")] + read_cache_threshold: Option, +} + +pub fn run(args: CommandArgs) { + let wbname = args.lvname; + let backing_dev = { + let name = args.backingdev; + BlockDevice::new(name) + }; + let cache_dev_name = args.cachedev; + + if args.reformat { + let status = Command::new("dd") + .arg("if=/dev/zero") + .arg(format!("of={}", cache_dev_name)) + .arg("bs=512") + .arg("count=1") + .status() + .expect("Failed to zero out the cache device"); + assert!(status.success()); + } + + let mut optionals: Vec = Vec::new(); + if args.write_around_mode { + optionals.push("write_around_mode".to_string()); + optionals.push("1".to_string()); + } + if let Some(v) = args.writeback_threshold { + optionals.push("writeback_threshold".to_string()); + optionals.push(v.to_string()); + } + if let Some(v) = args.nr_max_batched_writeback { + optionals.push("nr_max_batched_writeback".to_string()); + optionals.push(v.to_string()); + } + if let Some(v) = args.update_sb_record_interval { + optionals.push("update_sb_record_interval".to_string()); + optionals.push(v.to_string()); + } + if let Some(v) = args.sync_data_interval { + optionals.push("sync_data_interval".to_string()); + optionals.push(v.to_string()); + } + if let Some(v) = args.read_cache_threshold { + optionals.push("read_cache_threshold".to_string()); + optionals.push(v.to_string()); + } + + let n = optionals.len(); + + let optionals_table = if n == 0 { + "".to_string() + } else { + format!(" {} {}", n, optionals.join(" ")) + }; + + let table = format!( + "0 {} writeboost {} {}{}", + backing_dev.size(), + backing_dev.name(), + cache_dev_name, + optionals_table + ); + + let status = Command::new("dmsetup") + .arg("create") + .arg(wbname) + .arg("--table") + .arg(table) + .status() + .expect("Failed to execute dmsetup create"); + assert!(status.success()); +} diff --git a/writeboost-cli/src/sub/dump/mod.rs b/writeboost-cli/src/sub/dump/mod.rs new file mode 100644 index 0000000..6ef03d0 --- /dev/null +++ b/writeboost-cli/src/sub/dump/mod.rs @@ -0,0 +1,43 @@ +use super::*; + +#[derive(Args)] +#[command(about = "Dump a cache block")] +#[command(author, version)] +pub struct CommandArgs { + #[arg(help = "Path to the cache device")] + cachedev: String, + #[arg(help = "Metablock index")] + mbidx: i32, + #[arg(long, help = "MBIDX is relative to this SEGID (default is 1)")] + #[arg(default_value_t = 1)] + segid: i32, +} + +pub fn run(args: CommandArgs) { + let mb_idx: i32 = args.mbidx; + let cache_dev = { + let devname = args.cachedev; + CacheDevice::new(devname.to_owned()) + }; + + let mut base_id = args.segid; + + base_id += mb_idx / 127; + let idx_inseg = mb_idx % 127; + let start_byte = (cache_dev.calc_segment_start(base_id) << 9) + ((1 + idx_inseg) << 12); + + use std::process::Command; + let output = Command::new("od") + .arg(format!("-j{}", start_byte)) + .arg("-N4096") + .arg("-Ax") + .arg(&cache_dev.dev.name()) + .output() + .expect("failed to execute od") + .stdout; + let output = String::from_utf8(output) + .expect("invalid utf8 output") + .to_string(); + let output = output.trim(); + println!("{}", output); +} diff --git a/writeboost-cli/src/sub/meta/mod.rs b/writeboost-cli/src/sub/meta/mod.rs new file mode 100644 index 0000000..daa319a --- /dev/null +++ b/writeboost-cli/src/sub/meta/mod.rs @@ -0,0 +1,70 @@ +use super::*; + +use std::fs::File; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; + +#[derive(Args)] +#[command(about = "Dump a segment header")] +#[command(author, version)] +pub struct CommandArgs { + #[arg(help = "Path to the cache device")] + cachedev: String, + #[arg(help = "Segment id. 0 means the superblock")] + segid: i32, +} + +pub fn run(args: CommandArgs) { + let devname: String = args.cachedev; + let id: i32 = args.segid; + + let cache_dev = CacheDevice::new(devname.to_owned()); + + if id == 0 { + // superblock + let mut buf = [0; 512]; + let mut f = File::open(&devname).unwrap(); + f.read(&mut buf).unwrap(); + let sup_header = SuperBlockHeader::from_buf(&buf); + + let s = if sup_header.magic == 0x57427374 { + "formatted" + } else { + "unformatted" + }; + + println!("[superblock header]"); + println!("magic = {} ({})", sup_header.magic, s); + + f.seek(SeekFrom::Start((1u64 << 20) - 512)).unwrap(); + f.read(&mut buf).unwrap(); + let sup_record = SuperBlockRecord::from_buf(&buf); + + println!("[superblock record]"); + println!( + "last writeback id = {}", + sup_record.last_writeback_segment_id + ); + } else { + // header + let mut buf = [0; 4096]; + let mut f = File::open(&devname).unwrap(); + let start_byte: u64 = (cache_dev.calc_segment_start(id) as u64) << 9; + f.seek(SeekFrom::Start(start_byte)).unwrap(); + f.read(&mut buf).unwrap(); + let (header, metablocks) = Segment::from_buf(&buf); + + println!("[segment header]"); + println!("id = {}", header.id); + println!("checksumx = {}", header.checksum); + println!("length = {}", header.length); + + for (i, metablock) in metablocks.iter().enumerate() { + println!( + "[{}] sector={}, dirty_bits={}", + i, metablock.sector, metablock.dirty_bits + ); + } + } +} diff --git a/writeboost-cli/src/sub/mod.rs b/writeboost-cli/src/sub/mod.rs new file mode 100644 index 0000000..cd4def1 --- /dev/null +++ b/writeboost-cli/src/sub/mod.rs @@ -0,0 +1,8 @@ +use super::*; + +pub mod check; +pub mod create; +pub mod dump; +pub mod meta; +pub mod remove; +pub mod status; diff --git a/writeboost-cli/src/sub/remove/mod.rs b/writeboost-cli/src/sub/remove/mod.rs new file mode 100644 index 0000000..061362f --- /dev/null +++ b/writeboost-cli/src/sub/remove/mod.rs @@ -0,0 +1,75 @@ +use super::*; + +use std::process::Command; + +#[derive(Args)] +#[command(about = "Remove a writeboost device")] +#[command(author, version)] +pub struct CommandArgs { + #[arg(help = "Name of the writeboost device")] + lvname: String, + #[arg(long, help = "Don't flush RAM buffer to cache device before removing")] + noflush: bool, + #[arg( + long, + help = "Don't write back dirty caches to the backing device before removing" + )] + nowriteback: bool, +} + +pub fn run(args: CommandArgs) { + let wbname = args.lvname; + + let do_flush = !args.noflush; + if do_flush { + let status = Command::new("dmsetup") + .arg("suspend") + .arg(&wbname) + .status() + .expect("Failed to flush transient data"); + assert!(status.success()); + + let status = Command::new("dmsetup") + .arg("resume") + .arg(&wbname) + .status() + .expect("Failed to flush transient data"); + assert!(status.success()); + } + + let do_writeback = !args.nowriteback; + if do_writeback { + let status = Command::new("dmsetup") + .arg("message") + .arg(&wbname) + .arg("0") + .arg("drop_caches") + .status() + .expect("Failed to drop caches"); + assert!(status.success()); + } + + let cache_dev_name = WBDev::new(wbname.to_string()) + .table() + .cache_dev + .sys_dev_table() + .get("DEVNAME"); + + let status = Command::new("dmsetup") + .arg("remove") + .arg(&wbname) + .status() + .expect("Failed to execute dmsetup remove"); + assert!(status.success()); + + if do_writeback { + let status = Command::new("dd") + .arg("if=/dev/zero") + .arg(format!("of=/dev/{}", cache_dev_name)) + .arg("bs=512") + .arg("count=1") + .status() + .expect("Failed to zero out the cache device"); + assert!(status.success()); + } +} diff --git a/writeboost-cli/src/sub/status/mod.rs b/writeboost-cli/src/sub/status/mod.rs new file mode 100644 index 0000000..0402eb3 --- /dev/null +++ b/writeboost-cli/src/sub/status/mod.rs @@ -0,0 +1,58 @@ +use super::*; + +use std::io::{self, Read}; + +fn print_status(s: &str) { + let toks: Vec<&str> = { s.split_whitespace().collect() }; + + println!("cursor pos = {}", toks[3]); + println!("# of cache blocks = {}", toks[4]); + println!("# of segments = {}", toks[5]); + println!("current id = {}", toks[6]); + println!("last flushed id = {}", toks[7]); + println!("last writeback id = {}", toks[8]); + println!("# of dirty cache blocks = {}", toks[9]); + println!("# of partial flushes = {}", toks[26]); + + println!("write? hit? on_buffer? fullsize?"); + for i in 0..16 { + let b = |bit: usize| { + if (i & (1 << bit)) > 0 { 1 } else { 0 } + }; + let v = toks[10 + i]; + println!( + "{} {} {} {} {}", + b(3), + b(2), + b(1), + b(0), + v + ); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_print_status() { + let mut buf = String::new(); + let mut f = std::fs::File::open("data/sample.status.226").unwrap(); + f.read_to_string(&mut buf).unwrap(); + print_status(&buf); + } +} + +#[derive(Args)] +#[command(about = "Pretty-print the dmsetup status output")] +#[command(override_usage = "dmsetup status wbdev | wbstatus")] +#[command(author, version)] +pub struct CommandArgs; + +pub fn run(_: CommandArgs) { + let mut buf = String::new(); + io::stdin().read_to_string(&mut buf).unwrap(); + + print_status(&buf); +} diff --git a/writeboost-cli/src/utils.rs b/writeboost-cli/src/utils.rs new file mode 100644 index 0000000..4f82146 --- /dev/null +++ b/writeboost-cli/src/utils.rs @@ -0,0 +1,249 @@ +use super::*; + +use byteorder::{LittleEndian, ReadBytesExt}; +use std::collections::HashMap; +use std::fs::File; +use std::io::Cursor; +use std::io::Read; +use std::io::Seek; +use std::io::SeekFrom; +use std::process::Command; + +pub struct BlockDevice { + name: String, +} + +impl BlockDevice { + pub fn new(name_: String) -> Self { + BlockDevice { name: name_ } + } + + pub fn name(&self) -> String { + self.name.to_owned() + } + + pub fn size(&self) -> i64 { + use std::str::FromStr; + let output: Vec = Command::new("blockdev") + .arg("--getsz") + .arg(&self.name()) + .output() + .expect(&format!("Failed to get the size of {}", self.name())) + .stdout; + let output = String::from_utf8(output) + .expect("Invalid utf8 output") + .to_string(); + let output = output.trim_end(); + i64::from_str(output).expect("Couldn't parse as i64") + } +} + +pub struct CacheDevice { + pub dev: BlockDevice, +} + +impl CacheDevice { + pub fn new(name: String) -> Self { + CacheDevice { + dev: BlockDevice::new(name), + } + } + + fn nr_segments(&self) -> i32 { + ((self.dev.size() - (1 << 11)) / (1 << 10)) as i32 + } + + pub fn calc_segment_start(&self, id: i32) -> i32 { + let idx = (id - 1) % self.nr_segments(); + (1 << 11) + (idx * (1 << 10)) + } +} + +pub struct SegmentHeader { + pub id: u64, + pub checksum: u32, + pub length: u8, +} + +impl SegmentHeader { + pub fn from_buf(data: &[u8]) -> SegmentHeader { + let mut rdr = Cursor::new(data); + let id_ = rdr.read_u64::().unwrap(); + let checksum_ = rdr.read_u32::().unwrap(); + let length_ = rdr.read_u8().unwrap(); + SegmentHeader { + id: id_, + checksum: checksum_, + length: length_, + } + } + + pub fn uninitialized(&self) -> bool { + self.id == 0 + } +} + +pub struct Metablock { + pub sector: u64, + pub dirty_bits: u8, +} + +pub struct Segment; + +impl Segment { + pub fn from_buf(buf: &[u8]) -> (SegmentHeader, Vec) { + let seg = SegmentHeader::from_buf(buf); + + let mut metablocks = Vec::new(); + let mut rdr = Cursor::new(buf); + rdr.seek(SeekFrom::Start(512)).unwrap(); + for _ in 0..seg.length { + let sector_ = rdr.read_u64::().unwrap(); + let dirty_bits_ = rdr.read_u8().unwrap(); + let metablock = Metablock { + sector: sector_, + dirty_bits: dirty_bits_, + }; + metablocks.push(metablock); + let padding = 16 - (8 + 1); + rdr.seek(SeekFrom::Current(padding)).unwrap(); + } + (seg, metablocks) + } +} + +pub struct SuperBlockHeader { + pub magic: u32, +} + +impl SuperBlockHeader { + pub fn from_buf(data: &[u8]) -> SuperBlockHeader { + let mut rdr = Cursor::new(data); + let magic_ = rdr.read_u32::().unwrap(); + SuperBlockHeader { magic: magic_ } + } +} + +pub struct SuperBlockRecord { + pub last_writeback_segment_id: u64, +} + +impl SuperBlockRecord { + pub fn from_buf(data: &[u8]) -> SuperBlockRecord { + let mut rdr = Cursor::new(data); + let last_writeback_segment_id_ = rdr.read_u64::().unwrap(); + SuperBlockRecord { + last_writeback_segment_id: last_writeback_segment_id_, + } + } +} + +#[derive(Debug)] +pub struct SysDevTable { + map: HashMap, +} + +impl SysDevTable { + pub fn from_file(path: &str) -> SysDevTable { + let mut f = File::open(path).unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + let it = s.lines().map(|line| { + let v: Vec<&str> = line.split("=").collect(); + (v[0].to_string(), v[1].to_string()) + }); + let mut m = HashMap::new(); + for (k, v) in it { + m.insert(k, v); + } + SysDevTable { map: m } + } + + pub fn get(&self, name: &str) -> String { + self.map[name].to_string() + } +} + +pub struct BlockNumber { + value: String, +} + +impl BlockNumber { + pub fn unwrap(&self) -> String { + self.value.to_owned() + } + + pub fn sys_dev_table(&self) -> SysDevTable { + let path = format!("/sys/dev/block/{}/uevent", self.value); + SysDevTable::from_file(&path) + } +} + +pub struct DMTable { + pub backing_dev: BlockNumber, + pub cache_dev: BlockNumber, +} + +impl DMTable { + pub fn parse(line: String) -> DMTable { + let line: Vec = line + .split(" ") + .filter(|x| x != &"") + .map(|x| x.to_string()) + .collect(); + DMTable { + backing_dev: BlockNumber { + value: line[3].clone(), + }, + cache_dev: BlockNumber { + value: line[4].clone(), + }, + } + } +} + +pub struct WBDev { + name: String, +} + +impl WBDev { + pub fn new(name_: String) -> WBDev { + WBDev { name: name_ } + } + + pub fn table(&self) -> DMTable { + let output = Command::new("dmsetup") + .arg("table") + .arg(&self.name) + .output() + .expect("Fail to dmsetup table") + .stdout; + let output = String::from_utf8(output) + .expect("Invalid utf8 output") + .to_string(); + let output = output.trim().to_string(); + DMTable::parse(output) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_read_sys_dev_file() { + let t = SysDevTable::from_file("data/sys_dev.0"); + assert_eq!(t.get("DEVNAME"), "vda1"); + } + + #[test] + fn test_dmtable_parse() { + let mut s = String::new(); + let mut f = File::open("data/sample.table.226").unwrap(); + f.read_to_string(&mut s).unwrap(); + let t = DMTable::parse(s.trim().to_string()); + println!("{}", s.clone()); + assert_eq!(t.backing_dev.unwrap(), "251:0"); + assert_eq!(t.cache_dev.unwrap(), "251:3"); + } +}