[thin_explore] Explore devices tree, including path support.

This commit is contained in:
Joe Thornber 2020-10-15 11:53:09 +01:00
parent c42b623e39
commit f60ae770c2
4 changed files with 206 additions and 46 deletions

View File

@ -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<btree::NodeHeader> {
let b = engine.read(loc)?;
unpack(&b.get_data()).map_err(|_| anyhow!("couldn't unpack btree header"))
}
*/
fn read_node<V: Unpack>(engine: &dyn IoEngine, loc: u64) -> Result<btree::Node<V>> {
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<X: Adjacent, Y: Adjacent> 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<V: Adjacent + Sized + Copy>(keys: &[u64], values: &[V]) -> Vec<((u64,
adjacent_runs(pairs)
}
//------------------------------------
struct NodeWidget<'a, V: Unpack + Adjacent + Clone> {
title: String,
node: &'a btree::Node<V>,
@ -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<Action>;
}
//------------------------------------
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<Action> {
None
fn input(&mut self, k: Key) -> Option<Action> {
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<Action> {
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<DeviceDetail>,
nr_entries: usize,
state: ListState,
}
impl DeviceDetailPanel {
fn new(node: btree::Node<DeviceDetail>) -> 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<Action> {
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<Action> {
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<u64>,
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::<DeviceDetail>(engine, b)?;
panels.push(Box::new(DeviceDetailPanel::new(node)));
}
PushTopLevel(b) => {
let node = read_node::<u64>(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<Vec<u64>>) -> 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<Box<dyn Panel>> = 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<Vec<u64>>) -> Result<()> {
}
} else {
let sb = read_superblock(&engine, 0)?;
panels.push(Box::new(SBPanel { sb: sb.clone() }));
let node = read_node::<u64>(&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()

View File

@ -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<W: WriteBytesExt>(&self, w: &mut W) -> anyhow::Result<()> {
// csum needs to be calculated right for the whole metadata block.
w.write_u32::<LittleEndian>(0)?;
let flags;
if self.is_leaf {
flags = LEAF_NODE;
} else {
flags = INTERNAL_NODE;
}
w.write_u32::<LittleEndian>(flags)?;
w.write_u64::<LittleEndian>(self.block)?;
w.write_u32::<LittleEndian>(self.nr_entries)?;
w.write_u32::<LittleEndian>(self.max_entries)?;
w.write_u32::<LittleEndian>(self.value_size)?;
w.write_u32::<LittleEndian>(0)?;
Ok(())
}
}
#[derive(Clone)]
pub enum Node<V: Unpack> {
Internal {
@ -1020,15 +1041,14 @@ impl<V: Unpack + Copy> NodeVisitor<V> for ValueCollector<V> {
}
pub fn btree_to_map<V: Unpack + Copy>(
_path: &mut Vec<u64>,
path: &mut Vec<u64>,
engine: Arc<dyn IoEngine + Send + Sync>,
ignore_non_fatal: bool,
root: u64,
) -> Result<BTreeMap<u64, V>> {
let walker = BTreeWalker::new(engine, ignore_non_fatal);
let visitor = ValueCollector::<V>::new();
let mut path = Vec::new();
walker.walk(&mut path, &visitor, root)?;
walker.walk(path, &visitor, root)?;
Ok(visitor.values.into_inner().unwrap())
}

View File

@ -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::<DeviceDetail>(&mut path, engine.clone(), false, sb.details_root)?;
let devs = btree_to_map::<DeviceDetail>(&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<IndexEntry> = 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,
)?;

View File

@ -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