diff --git a/src/bin/thin_explore.rs b/src/bin/thin_explore.rs index efcb1e6..8fd6ddb 100644 --- a/src/bin/thin_explore.rs +++ b/src/bin/thin_explore.rs @@ -18,12 +18,12 @@ use termion::input::TermRead; use termion::raw::IntoRawMode; use tui::{ - backend::{TermionBackend}, + backend::TermionBackend, buffer::Buffer, layout::{Constraint, Direction, Layout, Rect}, style::{Color, Modifier, Style}, terminal::Frame, - text::{Span}, + text::Span, widgets::{Block, Borders, List, ListItem, ListState, Row, StatefulWidget, Table, Widget}, Terminal, }; @@ -32,6 +32,7 @@ use thinp::io_engine::*; use thinp::pdata::btree; use thinp::pdata::unpack::*; use thinp::thin::block_time::*; +use thinp::thin::device_detail::*; use thinp::thin::superblock::*; //------------------------------------ @@ -141,13 +142,20 @@ fn ls_previous(ls: &mut ListState) { //------------------------------------ -struct SBWidget { - sb: Superblock, +struct SBWidget<'a> { + sb: &'a Superblock, } -impl Widget for SBWidget { - fn render(self, area: Rect, buf: &mut Buffer) { - let sb = &self.sb; +impl<'a> StatefulWidget for SBWidget<'a> { + type State = ListState; + + fn render(self, area: Rect, buf: &mut Buffer, state: &mut ListState) { + let chunks = Layout::default() + .direction(Direction::Vertical) + .constraints([Constraint::Min(10), Constraint::Percentage(80)].as_ref()) + .split(area); + + let sb = self.sb; let flags = ["flags".to_string(), format!("{}", sb.flags)]; let block = ["block".to_string(), format!("{}", sb.block)]; let uuid = ["uuid".to_string(), format!("-")]; @@ -198,7 +206,22 @@ impl Widget for SBWidget { .style(Style::default().fg(Color::White)) .column_spacing(1); - Widget::render(table, area, buf); + Widget::render(table, chunks[0], buf); + + let items = vec![ + ListItem::new(Span::raw(format!("Device tree"))), + ListItem::new(Span::raw(format!("Mapping tree"))) + ]; + + let items = List::new(items) + .block(Block::default().borders(Borders::ALL).title("Entries")) + .highlight_style( + Style::default() + .bg(Color::LightGreen) + .add_modifier(Modifier::BOLD), + ); + + StatefulWidget::render(items, chunks[1], buf, state); } } @@ -245,13 +268,6 @@ impl<'a> Widget for HeaderWidget<'a> { } } -/* -fn read_node_header(engine: &dyn IoEngine, loc: u64) -> Result { - let b = engine.read(loc)?; - unpack(&b.get_data()).map_err(|_| anyhow!("couldn't unpack btree header")) -} -*/ - fn read_node(engine: &dyn IoEngine, loc: u64) -> Result> { let b = engine.read(loc)?; let path = Vec::new(); @@ -284,6 +300,12 @@ impl Adjacent for BlockTime { } } +impl Adjacent for DeviceDetail { + fn adjacent(&self, rhs: &Self) -> bool { + false + } +} + impl Adjacent for (X, Y) { fn adjacent(&self, rhs: &Self) -> bool { self.0.adjacent(&rhs.0) && self.1.adjacent(&rhs.1) @@ -328,6 +350,8 @@ fn mk_runs(keys: &[u64], values: &[V]) -> Vec<((u64, adjacent_runs(pairs) } +//------------------------------------ + struct NodeWidget<'a, V: Unpack + Adjacent + Clone> { title: String, node: &'a btree::Node, @@ -420,6 +444,7 @@ impl<'a, V: Unpack + fmt::Display + Adjacent + Copy> StatefulWidget for NodeWidg //------------------------------------ enum Action { + PushDeviceDetail(u64), PushTopLevel(u64), PushBottomLevel(u32, u64), PopPanel, @@ -435,32 +460,133 @@ trait Panel { fn path_action(&mut self, child: u64) -> Option; } +//------------------------------------ + struct SBPanel { sb: Superblock, + state: ListState, +} + +impl SBPanel { + fn new(sb: Superblock) -> SBPanel { + let mut state = ListState::default(); + state.select(Some(0)); + + SBPanel { sb, state } + } } impl Panel for SBPanel { fn render(&mut self, area: Rect, f: &mut Frame_) { - // FIXME: get rid of clone - let w = SBWidget { - sb: self.sb.clone(), - }; - f.render_widget(w, area); + let w = SBWidget { sb: &self.sb }; + f.render_stateful_widget(w, area, &mut self.state); } - fn input(&mut self, _k: Key) -> Option { - None + fn input(&mut self, k: Key) -> Option { + match k { + Key::Char('j') | Key::Down => { + ls_next(&mut self.state, 2); + None + } + Key::Char('k') | Key::Up => { + ls_previous(&mut self.state); + None + } + Key::Char('l') | Key::Right => { + if self.state.selected().unwrap() == 0 { + Some(PushDeviceDetail(self.sb.details_root)) + } else { + Some(PushTopLevel(self.sb.mapping_root)) + } + }, + Key::Char('h') | Key::Left => Some(PopPanel), + _ => None, + } } fn path_action(&mut self, child: u64) -> Option { if child == self.sb.mapping_root { Some(PushTopLevel(child)) + } else if child == self.sb.details_root { + Some(PushDeviceDetail(child)) } else { None } } } +//------------------------------------ + +struct DeviceDetailPanel { + node: btree::Node, + nr_entries: usize, + state: ListState, +} + +impl DeviceDetailPanel { + fn new(node: btree::Node) -> DeviceDetailPanel { + let nr_entries = node.get_header().nr_entries as usize; + let mut state = ListState::default(); + state.select(Some(0)); + + DeviceDetailPanel { + node, + nr_entries, + state, + } + } +} + +impl Panel for DeviceDetailPanel { + fn render(&mut self, area: Rect, f: &mut Frame_) { + let w = NodeWidget { + title: "Device Details".to_string(), + node: &self.node, + }; + + f.render_stateful_widget(w, area, &mut self.state); + } + + fn input(&mut self, k: Key) -> Option { + match k { + Key::Char('j') | Key::Down => { + ls_next(&mut self.state, self.nr_entries); + None + } + Key::Char('k') | Key::Up => { + ls_previous(&mut self.state); + None + } + Key::Char('l') | Key::Right => match &self.node { + btree::Node::Internal { values, .. } => { + Some(PushDeviceDetail(values[self.state.selected().unwrap()])) + } + btree::Node::Leaf { values, keys, .. } => None, + }, + Key::Char('h') | Key::Left => Some(PopPanel), + _ => None, + } + } + + fn path_action(&mut self, child: u64) -> Option { + match &self.node { + btree::Node::Internal { values, .. } => { + for i in 0..values.len() { + if values[i] == child { + self.state.select(Some(i)); + return Some(PushDeviceDetail(child)); + } + } + + return None; + } + btree::Node::Leaf { .. } => None, + } + } +} + +//------------------------------------ + struct TopLevelPanel { node: btree::Node, nr_entries: usize, @@ -485,7 +611,7 @@ impl Panel for TopLevelPanel { fn render(&mut self, area: Rect, f: &mut Frame_) { let w = NodeWidget { title: "Top Level".to_string(), - node: &self.node, // FIXME: get rid of clone + node: &self.node, }; f.render_stateful_widget(w, area, &mut self.state); @@ -606,7 +732,7 @@ impl Panel for BottomLevelPanel { return Some(PushBottomLevel(self.thin_id, child)); } } - + return None; } btree::Node::Leaf { .. } => None, @@ -622,6 +748,10 @@ fn perform_action( action: Action, ) -> Result<()> { match action { + PushDeviceDetail(b) => { + let node = read_node::(engine, b)?; + panels.push(Box::new(DeviceDetailPanel::new(node))); + } PushTopLevel(b) => { let node = read_node::(engine, b)?; panels.push(Box::new(TopLevelPanel::new(node))); @@ -631,7 +761,7 @@ fn perform_action( panels.push(Box::new(BottomLevelPanel::new(thin_id, node))); } PopPanel => { - if panels.len() > 2 { + if panels.len() > 1 { panels.pop(); } } @@ -640,21 +770,15 @@ fn perform_action( } fn explore(path: &Path, node_path: Option>) -> Result<()> { - let stdout = io::stdout(); - let mut stdout = stdout.lock().into_raw_mode()?; - write!(stdout, "{}", termion::clear::All)?; - - let backend = TermionBackend::new(stdout); - let mut terminal = Terminal::new(backend)?; - let engine = SyncIoEngine::new(&path, 1, false)?; let mut panels: Vec> = Vec::new(); if let Some(path) = node_path { + eprintln!("using path: {:?}", path); assert_eq!(path[0], 0); let sb = read_superblock(&engine, path[0])?; - panels.push(Box::new(SBPanel { sb })); + panels.push(Box::new(SBPanel::new(sb))); for b in &path[1..] { let action = panels.last_mut().unwrap().path_action(*b); if let Some(action) = action { @@ -665,14 +789,17 @@ fn explore(path: &Path, node_path: Option>) -> Result<()> { } } else { let sb = read_superblock(&engine, 0)?; - panels.push(Box::new(SBPanel { sb: sb.clone() })); - - let node = read_node::(&engine, sb.mapping_root)?; - panels.push(Box::new(TopLevelPanel::new(node))); + panels.push(Box::new(SBPanel::new(sb.clone()))); } let events = Events::new(); + let stdout = io::stdout(); + let mut stdout = stdout.lock().into_raw_mode()?; + write!(stdout, "{}", termion::clear::All)?; + let backend = TermionBackend::new(stdout); + let mut terminal = Terminal::new(backend)?; + 'main: loop { let render_panels = |f: &mut Frame_| { let chunks = Layout::default() diff --git a/src/pdata/btree.rs b/src/pdata/btree.rs index 36f6205..f1cfb17 100644 --- a/src/pdata/btree.rs +++ b/src/pdata/btree.rs @@ -1,5 +1,5 @@ use anyhow::anyhow; -use byteorder::{ReadBytesExt, WriteBytesExt}; +use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use data_encoding::BASE64; use nom::{number::complete::*, IResult}; use std::collections::BTreeMap; @@ -418,6 +418,27 @@ impl Unpack for NodeHeader { } } +impl Pack for NodeHeader { + fn pack(&self, w: &mut W) -> anyhow::Result<()> { + // csum needs to be calculated right for the whole metadata block. + w.write_u32::(0)?; + + let flags; + if self.is_leaf { + flags = LEAF_NODE; + } else { + flags = INTERNAL_NODE; + } + w.write_u32::(flags)?; + w.write_u64::(self.block)?; + w.write_u32::(self.nr_entries)?; + w.write_u32::(self.max_entries)?; + w.write_u32::(self.value_size)?; + w.write_u32::(0)?; + Ok(()) + } +} + #[derive(Clone)] pub enum Node { Internal { @@ -1020,15 +1041,14 @@ impl NodeVisitor for ValueCollector { } pub fn btree_to_map( - _path: &mut Vec, + path: &mut Vec, engine: Arc, ignore_non_fatal: bool, root: u64, ) -> Result> { let walker = BTreeWalker::new(engine, ignore_non_fatal); let visitor = ValueCollector::::new(); - let mut path = Vec::new(); - walker.walk(&mut path, &visitor, root)?; + walker.walk(path, &visitor, root)?; Ok(visitor.values.into_inner().unwrap()) } diff --git a/src/thin/check.rs b/src/thin/check.rs index e95b81b..08f7ff9 100644 --- a/src/thin/check.rs +++ b/src/thin/check.rs @@ -467,7 +467,7 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { // Device details. We read this once to get the number of thin devices, and hence the // maximum metadata ref count. Then create metadata space map, and reread to increment // the ref counts for that metadata. - let devs = btree_to_map::(&mut path, engine.clone(), false, sb.details_root)?; + let devs = btree_to_map::(&mut path, engine.clone(), opts.ignore_non_fatal, sb.details_root)?; let nr_devs = devs.len(); let metadata_sm = core_sm(engine.get_nr_blocks(), nr_devs as u32); inc_superblock(&metadata_sm)?; @@ -477,7 +477,7 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { &mut path, engine.clone(), metadata_sm.clone(), - false, + opts.ignore_non_fatal, sb.details_root, )?; @@ -493,7 +493,7 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { &mut path, engine.clone(), metadata_sm.clone(), - false, + opts.ignore_non_fatal, sb.mapping_root, )?; @@ -514,7 +514,7 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { &mut path, engine.clone(), metadata_sm.clone(), - false, + opts.ignore_non_fatal, root.bitmap_root, )?; let entries: Vec = entries.values().cloned().collect(); @@ -556,7 +556,7 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> { &mut path, engine.clone(), metadata_sm.clone(), - false, + opts.ignore_non_fatal, root.ref_count_root, )?; diff --git a/src/thin/device_detail.rs b/src/thin/device_detail.rs index 332fbf7..68fc21a 100644 --- a/src/thin/device_detail.rs +++ b/src/thin/device_detail.rs @@ -1,3 +1,5 @@ +use std::fmt; + use crate::pdata::unpack::*; use nom::{number::complete::*, IResult}; @@ -11,6 +13,17 @@ pub struct DeviceDetail { pub snapshotted_time: u32, } +impl fmt::Display for DeviceDetail { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "mapped = {}, trans = {}, create = {}, snap = {}", + self.mapped_blocks, + self.transaction_id, + self.creation_time, + self.snapshotted_time); + Ok(()) + } +} + impl Unpack for DeviceDetail { fn disk_size() -> u32 { 24