[thin_explore] First code drop
This commit is contained in:
parent
5168621f02
commit
8493cf7081
62
Cargo.lock
generated
62
Cargo.lock
generated
@ -71,6 +71,12 @@ version = "1.3.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "cassowary"
|
||||||
|
version = "0.3.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cc"
|
name = "cc"
|
||||||
version = "1.0.59"
|
version = "1.0.59"
|
||||||
@ -426,6 +432,12 @@ version = "0.3.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a"
|
checksum = "17b02fc0ff9a9e4b35b3342880f48e896ebf69f2967921fe8646bf5b7125956a"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "numtoa"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "once_cell"
|
name = "once_cell"
|
||||||
version = "1.4.1"
|
version = "1.4.1"
|
||||||
@ -488,9 +500,9 @@ checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "proc-macro2"
|
name = "proc-macro2"
|
||||||
version = "1.0.19"
|
version = "1.0.20"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "04f5f085b5d71e2188cb8271e5da0161ad52c3f227a661a3c135fdf28e258b12"
|
checksum = "175c513d55719db99da20232b06cda8bab6b83ec2d04e3283edf0213c37c1a29"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
@ -583,6 +595,15 @@ version = "0.1.57"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "redox_termios"
|
||||||
|
version = "0.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76"
|
||||||
|
dependencies = [
|
||||||
|
"redox_syscall",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "regex"
|
name = "regex"
|
||||||
version = "1.3.9"
|
version = "1.3.9"
|
||||||
@ -646,9 +667,9 @@ checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "syn"
|
name = "syn"
|
||||||
version = "1.0.39"
|
version = "1.0.40"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9"
|
checksum = "963f7d3cc59b59b9325165add223142bbf1df27655d07789f109896d353d8350"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"proc-macro2",
|
"proc-macro2",
|
||||||
"quote",
|
"quote",
|
||||||
@ -679,6 +700,18 @@ dependencies = [
|
|||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "termion"
|
||||||
|
version = "1.5.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c22cec9d8978d906be5ac94bceb5a010d885c626c4c8855721a4dbd20e3ac905"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"numtoa",
|
||||||
|
"redox_syscall",
|
||||||
|
"redox_termios",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "termios"
|
name = "termios"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@ -725,8 +758,10 @@ dependencies = [
|
|||||||
"quickcheck_macros",
|
"quickcheck_macros",
|
||||||
"rand",
|
"rand",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"termion",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"threadpool",
|
"threadpool",
|
||||||
|
"tui",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -767,6 +802,25 @@ dependencies = [
|
|||||||
"num_cpus",
|
"num_cpus",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tui"
|
||||||
|
version = "0.10.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a977b0bb2e2033a6fef950f218f13622c3c34e59754b704ce3492dedab1dfe95"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"cassowary",
|
||||||
|
"termion",
|
||||||
|
"unicode-segmentation",
|
||||||
|
"unicode-width",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "unicode-segmentation"
|
||||||
|
version = "1.6.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "unicode-width"
|
name = "unicode-width"
|
||||||
version = "0.1.8"
|
version = "0.1.8"
|
||||||
|
@ -29,6 +29,8 @@ rand = "0.7"
|
|||||||
tempfile = "3.1"
|
tempfile = "3.1"
|
||||||
threadpool = "1.8"
|
threadpool = "1.8"
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
|
tui = "0.10"
|
||||||
|
termion = "1.5"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
json = "0.12"
|
json = "0.12"
|
||||||
|
555
src/bin/thin_explore.rs
Normal file
555
src/bin/thin_explore.rs
Normal file
@ -0,0 +1,555 @@
|
|||||||
|
extern crate clap;
|
||||||
|
|
||||||
|
use anyhow::{anyhow, Result};
|
||||||
|
use clap::{App, Arg};
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use termion::clear;
|
||||||
|
use termion::color;
|
||||||
|
use termion::event::Key;
|
||||||
|
use termion::input::TermRead;
|
||||||
|
use termion::raw::IntoRawMode;
|
||||||
|
|
||||||
|
use tui::{
|
||||||
|
backend::{Backend, TermionBackend},
|
||||||
|
buffer::Buffer,
|
||||||
|
layout::{Constraint, Direction, Layout, Rect},
|
||||||
|
style::{Color, Modifier, Style},
|
||||||
|
terminal::Frame,
|
||||||
|
text::{Span, Spans},
|
||||||
|
widgets::{Block, Borders, List, ListItem, ListState, Row, StatefulWidget, Table, Widget},
|
||||||
|
Terminal,
|
||||||
|
};
|
||||||
|
|
||||||
|
use thinp::io_engine::*;
|
||||||
|
use thinp::pdata::btree;
|
||||||
|
use thinp::pdata::unpack::*;
|
||||||
|
use thinp::thin::block_time::*;
|
||||||
|
use thinp::thin::superblock::*;
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
pub enum Event<I> {
|
||||||
|
Input(I),
|
||||||
|
Tick,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Events {
|
||||||
|
rx: mpsc::Receiver<Event<Key>>,
|
||||||
|
input_handle: thread::JoinHandle<()>,
|
||||||
|
ignore_exit_key: Arc<AtomicBool>,
|
||||||
|
tick_handle: thread::JoinHandle<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
pub struct Config {
|
||||||
|
pub exit_key: Key,
|
||||||
|
pub tick_rate: Duration,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Config {
|
||||||
|
Config {
|
||||||
|
exit_key: Key::Char('q'),
|
||||||
|
tick_rate: Duration::from_millis(250),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Events {
|
||||||
|
pub fn new() -> Events {
|
||||||
|
Events::with_config(Config::default())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_config(config: Config) -> Events {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let ignore_exit_key = Arc::new(AtomicBool::new(false));
|
||||||
|
let input_handle = {
|
||||||
|
let tx = tx.clone();
|
||||||
|
let ignore_exit_key = ignore_exit_key.clone();
|
||||||
|
thread::spawn(move || {
|
||||||
|
let stdin = io::stdin();
|
||||||
|
for evt in stdin.keys() {
|
||||||
|
if let Ok(key) = evt {
|
||||||
|
if let Err(err) = tx.send(Event::Input(key)) {
|
||||||
|
eprintln!("{}", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if !ignore_exit_key.load(Ordering::Relaxed) && key == config.exit_key {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
let tick_handle = {
|
||||||
|
thread::spawn(move || loop {
|
||||||
|
if tx.send(Event::Tick).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
thread::sleep(config.tick_rate);
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
|
Events {
|
||||||
|
rx,
|
||||||
|
ignore_exit_key,
|
||||||
|
input_handle,
|
||||||
|
tick_handle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn next(&self) -> Result<Event<Key>, mpsc::RecvError> {
|
||||||
|
self.rx.recv()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable_exit_key(&mut self) {
|
||||||
|
self.ignore_exit_key.store(true, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn enable_exit_key(&mut self) {
|
||||||
|
self.ignore_exit_key.store(false, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
fn ls_next(ls: &mut ListState, max: usize) {
|
||||||
|
let i = match ls.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i >= max - 1 {
|
||||||
|
max - 1
|
||||||
|
} else {
|
||||||
|
i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
ls.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ls_previous(ls: &mut ListState, max: usize) {
|
||||||
|
let i = match ls.selected() {
|
||||||
|
Some(i) => {
|
||||||
|
if i == 0 {
|
||||||
|
0
|
||||||
|
} else {
|
||||||
|
i - 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => 0,
|
||||||
|
};
|
||||||
|
ls.select(Some(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
struct SBWidget {
|
||||||
|
sb: Superblock,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Widget for SBWidget {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
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!("-")];
|
||||||
|
let version = ["version".to_string(), format!("{}", sb.version)];
|
||||||
|
let time = ["time".to_string(), format!("{}", sb.time)];
|
||||||
|
let transaction_id = [
|
||||||
|
"transaction_id".to_string(),
|
||||||
|
format!("{}", sb.transaction_id),
|
||||||
|
];
|
||||||
|
let metadata_snap = [
|
||||||
|
"metadata_snap".to_string(),
|
||||||
|
if sb.metadata_snap == 0 {
|
||||||
|
"-".to_string()
|
||||||
|
} else {
|
||||||
|
format!("{}", sb.metadata_snap)
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let mapping_root = ["mapping root".to_string(), format!("{}", sb.mapping_root)];
|
||||||
|
let details_root = ["details root".to_string(), format!("{}", sb.details_root)];
|
||||||
|
let data_block_size = [
|
||||||
|
"data block size".to_string(),
|
||||||
|
format!("{}k", sb.data_block_size * 2),
|
||||||
|
];
|
||||||
|
|
||||||
|
let row_style = Style::default().fg(Color::White);
|
||||||
|
let table = Table::new(
|
||||||
|
["Field", "Value"].iter(),
|
||||||
|
vec![
|
||||||
|
Row::Data(flags.iter()),
|
||||||
|
Row::Data(block.iter()),
|
||||||
|
Row::Data(uuid.iter()),
|
||||||
|
Row::Data(version.iter()),
|
||||||
|
Row::Data(time.iter()),
|
||||||
|
Row::Data(transaction_id.iter()),
|
||||||
|
Row::Data(metadata_snap.iter()),
|
||||||
|
Row::Data(mapping_root.iter()),
|
||||||
|
Row::Data(details_root.iter()),
|
||||||
|
Row::Data(data_block_size.iter()),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
.block(
|
||||||
|
Block::default()
|
||||||
|
.borders(Borders::ALL)
|
||||||
|
.title("Superblock".to_string()),
|
||||||
|
)
|
||||||
|
.header_style(Style::default().fg(Color::Yellow))
|
||||||
|
.widths(&[Constraint::Length(20), Constraint::Length(60)])
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.column_spacing(1);
|
||||||
|
|
||||||
|
Widget::render(table, area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
struct HeaderWidget<'a> {
|
||||||
|
title: String,
|
||||||
|
hdr: &'a btree::NodeHeader,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Widget for HeaderWidget<'a> {
|
||||||
|
fn render(self, area: Rect, buf: &mut Buffer) {
|
||||||
|
let hdr = &self.hdr;
|
||||||
|
let kind = [
|
||||||
|
"type".to_string(),
|
||||||
|
match hdr.is_leaf {
|
||||||
|
true => "LEAF".to_string(),
|
||||||
|
false => "INTERNAL".to_string(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
let nr_entries = ["nr_entries".to_string(), format!("{}", hdr.nr_entries)];
|
||||||
|
let max_entries = ["max_entries".to_string(), format!("{}", hdr.max_entries)];
|
||||||
|
let value_size = ["value size".to_string(), format!("{}", hdr.value_size)];
|
||||||
|
|
||||||
|
let row_style = Style::default().fg(Color::White);
|
||||||
|
let table = Table::new(
|
||||||
|
["Field", "Value"].iter(),
|
||||||
|
vec![
|
||||||
|
Row::Data(kind.iter()),
|
||||||
|
Row::Data(nr_entries.iter()),
|
||||||
|
Row::Data(max_entries.iter()),
|
||||||
|
Row::Data(value_size.iter()),
|
||||||
|
]
|
||||||
|
.into_iter(),
|
||||||
|
)
|
||||||
|
.block(Block::default().borders(Borders::ALL).title(self.title))
|
||||||
|
.header_style(Style::default().fg(Color::Yellow))
|
||||||
|
.widths(&[Constraint::Length(20), Constraint::Length(60)])
|
||||||
|
.style(Style::default().fg(Color::White))
|
||||||
|
.column_spacing(1);
|
||||||
|
|
||||||
|
Widget::render(table, area, buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
btree::unpack_node(&b.get_data(), true, false)
|
||||||
|
.map_err(|_| anyhow!("couldn't unpack btree node"))
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
struct NodeWidget<'a, V: Unpack> {
|
||||||
|
title: String,
|
||||||
|
node: &'a btree::Node<V>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, V: Unpack + fmt::Display> StatefulWidget for NodeWidget<'a, V> {
|
||||||
|
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 hdr = HeaderWidget {
|
||||||
|
title: self.title,
|
||||||
|
hdr: self.node.get_header(),
|
||||||
|
};
|
||||||
|
hdr.render(chunks[0], buf);
|
||||||
|
|
||||||
|
let mut items: Vec<ListItem> = Vec::new();
|
||||||
|
|
||||||
|
match self.node {
|
||||||
|
btree::Node::Internal { keys, values, .. } => {
|
||||||
|
for (k, v) in keys.iter().zip(values.iter()) {
|
||||||
|
items.push(ListItem::new(Span::raw(format!("{} -> {}", k, v))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btree::Node::Leaf { keys, values, .. } => {
|
||||||
|
for (k, v) in keys.iter().zip(values.iter()) {
|
||||||
|
items.push(ListItem::new(Span::raw(format!("{} -> {}", k, v))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
enum Action {
|
||||||
|
PushTopLevel(u64),
|
||||||
|
PushBottomLevel(u32, u64),
|
||||||
|
PopPanel,
|
||||||
|
}
|
||||||
|
|
||||||
|
type Frame_<'a, 'b> = Frame<'a, TermionBackend<termion::raw::RawTerminal<std::io::StdoutLock<'b>>>>;
|
||||||
|
|
||||||
|
trait Panel {
|
||||||
|
fn render(&mut self, area: Rect, f: &mut Frame_);
|
||||||
|
fn input(&mut self, k: Key) -> Option<Action>;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SBPanel {
|
||||||
|
sb: Superblock,
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn input(&mut self, k: Key) -> Option<Action> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct TopLevelPanel {
|
||||||
|
node: btree::Node<u64>,
|
||||||
|
nr_entries: usize,
|
||||||
|
state: ListState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TopLevelPanel {
|
||||||
|
fn new(node: btree::Node<u64>) -> TopLevelPanel {
|
||||||
|
let nr_entries = node.get_header().nr_entries as usize;
|
||||||
|
let mut state = ListState::default();
|
||||||
|
state.select(Some(0));
|
||||||
|
|
||||||
|
TopLevelPanel {
|
||||||
|
node,
|
||||||
|
nr_entries,
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
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, self.nr_entries);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Key::Char('l') | Key::Right => match &self.node {
|
||||||
|
btree::Node::Internal { values, .. } => {
|
||||||
|
Some(Action::PushTopLevel(values[self.state.selected().unwrap()]))
|
||||||
|
}
|
||||||
|
btree::Node::Leaf { values, keys, .. } => {
|
||||||
|
let index = self.state.selected().unwrap();
|
||||||
|
|
||||||
|
Some(Action::PushBottomLevel(keys[index] as u32, values[index]))
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Key::Char('h') | Key::Left => Some(Action::PopPanel),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct BottomLevelPanel {
|
||||||
|
thin_id: u32,
|
||||||
|
node: btree::Node<BlockTime>,
|
||||||
|
nr_entries: usize,
|
||||||
|
state: ListState,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BottomLevelPanel {
|
||||||
|
fn new(thin_id: u32, node: btree::Node<BlockTime>) -> BottomLevelPanel {
|
||||||
|
let nr_entries = node.get_header().nr_entries as usize;
|
||||||
|
let mut state = ListState::default();
|
||||||
|
state.select(Some(0));
|
||||||
|
|
||||||
|
BottomLevelPanel {
|
||||||
|
thin_id,
|
||||||
|
node,
|
||||||
|
nr_entries,
|
||||||
|
state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Panel for BottomLevelPanel {
|
||||||
|
fn render(&mut self, area: Rect, f: &mut Frame_) {
|
||||||
|
let w = NodeWidget {
|
||||||
|
title: format!("Thin dev #{}", self.thin_id),
|
||||||
|
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, self.nr_entries);
|
||||||
|
None
|
||||||
|
}
|
||||||
|
Key::Char('l') | Key::Right => match &self.node {
|
||||||
|
btree::Node::Internal { values, .. } => Some(Action::PushBottomLevel(
|
||||||
|
self.thin_id,
|
||||||
|
values[self.state.selected().unwrap()],
|
||||||
|
)),
|
||||||
|
_ => None,
|
||||||
|
},
|
||||||
|
|
||||||
|
Key::Char('h') | Key::Left => Some(Action::PopPanel),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
fn explore(path: &Path) -> 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 path = std::path::Path::new("bz1763895/meta.bin");
|
||||||
|
let engine = SyncIoEngine::new(&path, 1, false)?;
|
||||||
|
|
||||||
|
let mut panels: Vec<Box<dyn Panel>> = Vec::new();
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
let events = Events::new();
|
||||||
|
|
||||||
|
'main: loop {
|
||||||
|
let render_panels = |f: &mut Frame_| {
|
||||||
|
let chunks = Layout::default()
|
||||||
|
.direction(Direction::Horizontal)
|
||||||
|
.constraints([Constraint::Percentage(50), Constraint::Percentage(50)].as_ref())
|
||||||
|
.split(f.size());
|
||||||
|
|
||||||
|
let mut base = panels.len();
|
||||||
|
if base >= 2 {
|
||||||
|
base -= 2;
|
||||||
|
} else {
|
||||||
|
base = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
for i in base..panels.len() {
|
||||||
|
panels[i].render(chunks[i - base], f);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
terminal.draw(render_panels)?;
|
||||||
|
|
||||||
|
let last = panels.len() - 1;
|
||||||
|
let active_panel = &mut panels[last];
|
||||||
|
if let Event::Input(key) = events.next()? {
|
||||||
|
match key {
|
||||||
|
Key::Char('q') => break 'main,
|
||||||
|
_ => match active_panel.input(key) {
|
||||||
|
Some(Action::PushTopLevel(b)) => {
|
||||||
|
let node = read_node::<u64>(&engine, b)?;
|
||||||
|
panels.push(Box::new(TopLevelPanel::new(node)));
|
||||||
|
}
|
||||||
|
Some(Action::PushBottomLevel(thin_id, b)) => {
|
||||||
|
let node = read_node::<BlockTime>(&engine, b)?;
|
||||||
|
panels.push(Box::new(BottomLevelPanel::new(thin_id, node)));
|
||||||
|
}
|
||||||
|
Some(Action::PopPanel) => {
|
||||||
|
panels.pop();
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------
|
||||||
|
|
||||||
|
fn main() -> Result<()> {
|
||||||
|
let parser = App::new("thin_explore")
|
||||||
|
.version(thinp::version::TOOLS_VERSION)
|
||||||
|
.about("A text user interface for examining thin metadata.")
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("INPUT")
|
||||||
|
.help("Specify the input device to check")
|
||||||
|
.required(true)
|
||||||
|
.index(1),
|
||||||
|
);
|
||||||
|
|
||||||
|
let matches = parser.get_matches();
|
||||||
|
let input_file = Path::new(matches.value_of("INPUT").unwrap());
|
||||||
|
|
||||||
|
explore(&input_file)
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------
|
@ -1,7 +1,8 @@
|
|||||||
use anyhow::{anyhow, Result};
|
|
||||||
use nom::{number::complete::*, IResult};
|
use nom::{number::complete::*, IResult};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::fmt;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use thiserror::Error;
|
||||||
use threadpool::ThreadPool;
|
use threadpool::ThreadPool;
|
||||||
|
|
||||||
use crate::checksum;
|
use crate::checksum;
|
||||||
@ -13,39 +14,346 @@ use crate::pdata::unpack::*;
|
|||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub struct KeyRange {
|
||||||
|
start: Option<u64>,
|
||||||
|
end: Option<u64>, // This is the one-past-the-end value
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for KeyRange {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match (self.start, self.end) {
|
||||||
|
(None, None) => write!(f, "[..]"),
|
||||||
|
(None, Some(e)) => write!(f, "[..{}]", e),
|
||||||
|
(Some(s), None) => write!(f, "[{}..]", s),
|
||||||
|
(Some(s), Some(e)) => write!(f, "[{}..{}]", s, e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl KeyRange {
|
||||||
|
// None will be returned if either range would be zero length
|
||||||
|
fn split(&self, n: u64) -> Option<(KeyRange, KeyRange)> {
|
||||||
|
match (self.start, self.end) {
|
||||||
|
(None, None) => Some((
|
||||||
|
KeyRange {
|
||||||
|
start: None,
|
||||||
|
end: Some(n),
|
||||||
|
},
|
||||||
|
KeyRange {
|
||||||
|
start: Some(n),
|
||||||
|
end: None,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
(None, Some(e)) => {
|
||||||
|
if n < e {
|
||||||
|
Some((
|
||||||
|
KeyRange {
|
||||||
|
start: None,
|
||||||
|
end: Some(n),
|
||||||
|
},
|
||||||
|
KeyRange {
|
||||||
|
start: Some(n),
|
||||||
|
end: Some(e),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(s), None) => {
|
||||||
|
if s < n {
|
||||||
|
Some((
|
||||||
|
KeyRange {
|
||||||
|
start: Some(s),
|
||||||
|
end: Some(n),
|
||||||
|
},
|
||||||
|
KeyRange {
|
||||||
|
start: Some(n),
|
||||||
|
end: None,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(Some(s), Some(e)) => {
|
||||||
|
if s < n && n < e {
|
||||||
|
Some((
|
||||||
|
KeyRange {
|
||||||
|
start: Some(s),
|
||||||
|
end: Some(n),
|
||||||
|
},
|
||||||
|
KeyRange {
|
||||||
|
start: Some(n),
|
||||||
|
end: Some(e),
|
||||||
|
},
|
||||||
|
))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_split_range() {
|
||||||
|
struct Test(Option<u64>, Option<u64>, u64, Option<(KeyRange, KeyRange)>);
|
||||||
|
|
||||||
|
let tests = vec![
|
||||||
|
Test(
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
100,
|
||||||
|
Some((
|
||||||
|
KeyRange {
|
||||||
|
start: None,
|
||||||
|
end: Some(100),
|
||||||
|
},
|
||||||
|
KeyRange {
|
||||||
|
start: Some(100),
|
||||||
|
end: None,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Test(None, Some(100), 1000, None),
|
||||||
|
Test(
|
||||||
|
None,
|
||||||
|
Some(100),
|
||||||
|
50,
|
||||||
|
Some((
|
||||||
|
KeyRange {
|
||||||
|
start: None,
|
||||||
|
end: Some(50),
|
||||||
|
},
|
||||||
|
KeyRange {
|
||||||
|
start: Some(50),
|
||||||
|
end: Some(100),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Test(None, Some(100), 100, None),
|
||||||
|
Test(Some(100), None, 50, None),
|
||||||
|
Test(
|
||||||
|
Some(100),
|
||||||
|
None,
|
||||||
|
150,
|
||||||
|
Some((
|
||||||
|
KeyRange {
|
||||||
|
start: Some(100),
|
||||||
|
end: Some(150),
|
||||||
|
},
|
||||||
|
KeyRange {
|
||||||
|
start: Some(150),
|
||||||
|
end: None,
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Test(Some(100), Some(200), 50, None),
|
||||||
|
Test(Some(100), Some(200), 250, None),
|
||||||
|
Test(
|
||||||
|
Some(100),
|
||||||
|
Some(200),
|
||||||
|
150,
|
||||||
|
Some((
|
||||||
|
KeyRange {
|
||||||
|
start: Some(100),
|
||||||
|
end: Some(150),
|
||||||
|
},
|
||||||
|
KeyRange {
|
||||||
|
start: Some(150),
|
||||||
|
end: Some(200),
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
];
|
||||||
|
|
||||||
|
for Test(start, end, n, expected) in tests {
|
||||||
|
let kr = KeyRange { start, end };
|
||||||
|
let actual = kr.split(n);
|
||||||
|
assert_eq!(actual, expected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_one(kr: &KeyRange, k: u64) -> Result<(KeyRange, KeyRange)> {
|
||||||
|
match kr.split(k) {
|
||||||
|
None => {
|
||||||
|
return Err(node_err(&format!(
|
||||||
|
"couldn't split key range {} at {}",
|
||||||
|
kr, k
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
Some(pair) => Ok(pair),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_key_ranges_(kr: &KeyRange, keys: &[u64]) -> Result<Vec<KeyRange>> {
|
||||||
|
let mut krs = Vec::with_capacity(keys.len());
|
||||||
|
|
||||||
|
if keys.len() == 0 {
|
||||||
|
return Err(node_err("split_key_ranges: no keys present"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// The first key gives the lower bound
|
||||||
|
let mut kr = KeyRange {
|
||||||
|
start: Some(keys[0]),
|
||||||
|
end: kr.end,
|
||||||
|
};
|
||||||
|
|
||||||
|
for i in 1..keys.len() {
|
||||||
|
let (first, rest) = split_one(&kr, keys[i])?;
|
||||||
|
krs.push(first);
|
||||||
|
kr = rest;
|
||||||
|
}
|
||||||
|
|
||||||
|
krs.push(kr);
|
||||||
|
|
||||||
|
Ok(krs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn split_key_ranges(kr: &KeyRange, keys: &[u64]) -> Result<Vec<KeyRange>> {
|
||||||
|
let msg = format!("split: {:?} at {:?}", &kr, &keys);
|
||||||
|
let r = split_key_ranges_(kr, keys);
|
||||||
|
if r.is_err() {
|
||||||
|
eprintln!("{} -> {:?}", msg, &r);
|
||||||
|
}
|
||||||
|
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
const NODE_HEADER_SIZE: usize = 32;
|
const NODE_HEADER_SIZE: usize = 32;
|
||||||
|
|
||||||
|
#[derive(Error, Clone, Debug)]
|
||||||
|
pub enum BTreeError {
|
||||||
|
// #[error("io error")]
|
||||||
|
IoError, // (std::io::Error), // FIXME: we can't clone an io_error
|
||||||
|
|
||||||
|
// #[error("node error: {0}")]
|
||||||
|
NodeError(String),
|
||||||
|
|
||||||
|
// #[error("value error: {0}")]
|
||||||
|
ValueError(String),
|
||||||
|
|
||||||
|
// #[error("keys: {0:?}")]
|
||||||
|
KeyContext(KeyRange, Box<BTreeError>),
|
||||||
|
|
||||||
|
// #[error("aggregate: {0:?}")]
|
||||||
|
Aggregate(Vec<BTreeError>),
|
||||||
|
|
||||||
|
// #[error("{0:?}, {1}")]
|
||||||
|
Path(u64, Box<BTreeError>),
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_path(e: &BTreeError) -> (Vec<u64>, BTreeError) {
|
||||||
|
let mut path = Vec::new();
|
||||||
|
let mut e = e;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match e {
|
||||||
|
BTreeError::Path(b, next) => {
|
||||||
|
path.push(*b);
|
||||||
|
e = next.as_ref();
|
||||||
|
}
|
||||||
|
_ => return (path, e.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BTreeError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let (path, e) = extract_path(self);
|
||||||
|
match e {
|
||||||
|
BTreeError::IoError => write!(f, "io error, path{:?}", path),
|
||||||
|
BTreeError::NodeError(msg) => write!(f, "node error: {}, path{:?}", msg, path),
|
||||||
|
BTreeError::ValueError(msg) => write!(f, "value error: {}, path{:?}", msg, path),
|
||||||
|
BTreeError::KeyContext(kr, be) => {
|
||||||
|
write!(f, "{}, effecting keys {}, path{:?}", be, kr, path)
|
||||||
|
}
|
||||||
|
BTreeError::Aggregate(errs) => {
|
||||||
|
for e in errs {
|
||||||
|
write!(f, "{}", e)?
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
// Can't happen
|
||||||
|
BTreeError::Path(_, e) => write!(f, "{}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn node_err(msg: &str) -> BTreeError {
|
||||||
|
BTreeError::NodeError(msg.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn node_err_s(msg: String) -> BTreeError {
|
||||||
|
BTreeError::NodeError(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn io_err() -> BTreeError {
|
||||||
|
BTreeError::IoError
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn value_err(msg: String) -> BTreeError {
|
||||||
|
BTreeError::ValueError(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn aggregate_error(rs: Vec<BTreeError>) -> BTreeError {
|
||||||
|
BTreeError::Aggregate(rs)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BTreeError {
|
||||||
|
pub fn keys_context(self, keys: &KeyRange) -> BTreeError {
|
||||||
|
BTreeError::KeyContext(keys.clone(), Box::new(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type Result<T> = std::result::Result<T, BTreeError>;
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct NodeHeader {
|
pub struct NodeHeader {
|
||||||
is_leaf: bool,
|
pub block: u64,
|
||||||
nr_entries: u32,
|
pub is_leaf: bool,
|
||||||
max_entries: u32,
|
pub nr_entries: u32,
|
||||||
value_size: u32,
|
pub max_entries: u32,
|
||||||
|
pub value_size: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const INTERNAL_NODE: u32 = 1;
|
const INTERNAL_NODE: u32 = 1;
|
||||||
const LEAF_NODE: u32 = 2;
|
const LEAF_NODE: u32 = 2;
|
||||||
|
|
||||||
pub fn unpack_node_header(data: &[u8]) -> IResult<&[u8], NodeHeader> {
|
impl Unpack for NodeHeader {
|
||||||
let (i, _csum) = le_u32(data)?;
|
fn disk_size() -> u32 {
|
||||||
let (i, flags) = le_u32(i)?;
|
32
|
||||||
let (i, _block) = le_u64(i)?;
|
}
|
||||||
let (i, nr_entries) = le_u32(i)?;
|
|
||||||
let (i, max_entries) = le_u32(i)?;
|
|
||||||
let (i, value_size) = le_u32(i)?;
|
|
||||||
let (i, _padding) = le_u32(i)?;
|
|
||||||
|
|
||||||
Ok((
|
fn unpack(data: &[u8]) -> IResult<&[u8], NodeHeader> {
|
||||||
i,
|
let (i, _csum) = le_u32(data)?;
|
||||||
NodeHeader {
|
let (i, flags) = le_u32(i)?;
|
||||||
is_leaf: flags == LEAF_NODE,
|
let (i, block) = le_u64(i)?;
|
||||||
nr_entries,
|
let (i, nr_entries) = le_u32(i)?;
|
||||||
max_entries,
|
let (i, max_entries) = le_u32(i)?;
|
||||||
value_size,
|
let (i, value_size) = le_u32(i)?;
|
||||||
},
|
let (i, _padding) = le_u32(i)?;
|
||||||
))
|
|
||||||
|
Ok((
|
||||||
|
i,
|
||||||
|
NodeHeader {
|
||||||
|
block,
|
||||||
|
is_leaf: flags == LEAF_NODE,
|
||||||
|
nr_entries,
|
||||||
|
max_entries,
|
||||||
|
value_size,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum Node<V: Unpack> {
|
pub enum Node<V: Unpack> {
|
||||||
Internal {
|
Internal {
|
||||||
header: NodeHeader,
|
header: NodeHeader,
|
||||||
@ -59,17 +367,22 @@ pub enum Node<V: Unpack> {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn node_err<V>(msg: String) -> Result<V> {
|
impl<V: Unpack> Node<V> {
|
||||||
let msg = format!("btree node error: {}", msg);
|
pub fn get_header(&self) -> &NodeHeader {
|
||||||
Err(anyhow!(msg))
|
use Node::*;
|
||||||
|
match self {
|
||||||
|
Internal { header, .. } => header,
|
||||||
|
Leaf { header, .. } => header,
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_any<'a, V>(r: IResult<&'a [u8], V>) -> Result<(&'a [u8], V)> {
|
pub fn convert_result<'a, V>(r: IResult<&'a [u8], V>) -> Result<(&'a [u8], V)> {
|
||||||
if let Ok((i, v)) = r {
|
r.map_err(|_e| node_err("parse error"))
|
||||||
Ok((i, v))
|
}
|
||||||
} else {
|
|
||||||
Err(anyhow!("btree node error: parse error"))
|
pub fn convert_io_err<V>(r: std::io::Result<V>) -> Result<V> {
|
||||||
}
|
r.map_err(|_| io_err())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unpack_node<V: Unpack>(
|
pub fn unpack_node<V: Unpack>(
|
||||||
@ -79,45 +392,52 @@ pub fn unpack_node<V: Unpack>(
|
|||||||
) -> Result<Node<V>> {
|
) -> Result<Node<V>> {
|
||||||
use nom::multi::count;
|
use nom::multi::count;
|
||||||
|
|
||||||
let (i, header) = to_any(unpack_node_header(data))?;
|
let (i, header) =
|
||||||
|
NodeHeader::unpack(data).map_err(|_e| node_err("couldn't parse node header"))?;
|
||||||
|
|
||||||
if header.is_leaf && header.value_size != V::disk_size() {
|
if header.is_leaf && header.value_size != V::disk_size() {
|
||||||
return node_err(format!(
|
return Err(node_err_s(format!(
|
||||||
"value_size mismatch: expected {}, was {}",
|
"value_size mismatch: expected {}, was {}",
|
||||||
V::disk_size(),
|
V::disk_size(),
|
||||||
header.value_size
|
header.value_size
|
||||||
));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let elt_size = header.value_size + 8;
|
let elt_size = header.value_size + 8;
|
||||||
if elt_size as usize * header.max_entries as usize + NODE_HEADER_SIZE > BLOCK_SIZE {
|
if elt_size as usize * header.max_entries as usize + NODE_HEADER_SIZE > BLOCK_SIZE {
|
||||||
return node_err(format!("max_entries is too large ({})", header.max_entries));
|
return Err(node_err_s(format!(
|
||||||
|
"max_entries is too large ({})",
|
||||||
|
header.max_entries
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if header.nr_entries > header.max_entries {
|
if header.nr_entries > header.max_entries {
|
||||||
return node_err("nr_entries > max_entries".to_string());
|
return Err(node_err("nr_entries > max_entries"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ignore_non_fatal {
|
if !ignore_non_fatal {
|
||||||
if header.max_entries % 3 != 0 {
|
if header.max_entries % 3 != 0 {
|
||||||
return node_err("max_entries is not divisible by 3".to_string());
|
return Err(node_err("max_entries is not divisible by 3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !is_root {
|
if !is_root {
|
||||||
let min = header.max_entries / 3;
|
let min = header.max_entries / 3;
|
||||||
if header.nr_entries < min {
|
if header.nr_entries < min {
|
||||||
return node_err("too few entries".to_string());
|
return Err(node_err_s(format!(
|
||||||
|
"too few entries {}, expected at least {}",
|
||||||
|
header.nr_entries, min
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let (i, keys) = to_any(count(le_u64, header.nr_entries as usize)(i))?;
|
let (i, keys) = convert_result(count(le_u64, header.nr_entries as usize)(i))?;
|
||||||
|
|
||||||
let mut last = None;
|
let mut last = None;
|
||||||
for k in &keys {
|
for k in &keys {
|
||||||
if let Some(l) = last {
|
if let Some(l) = last {
|
||||||
if k <= l {
|
if k <= l {
|
||||||
return node_err("keys out of order".to_string());
|
return Err(node_err("keys out of order"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -125,10 +445,10 @@ pub fn unpack_node<V: Unpack>(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let nr_free = header.max_entries - header.nr_entries;
|
let nr_free = header.max_entries - header.nr_entries;
|
||||||
let (i, _padding) = to_any(count(le_u64, nr_free as usize)(i))?;
|
let (i, _padding) = convert_result(count(le_u64, nr_free as usize)(i))?;
|
||||||
|
|
||||||
if header.is_leaf {
|
if header.is_leaf {
|
||||||
let (_i, values) = to_any(count(V::unpack, header.nr_entries as usize)(i))?;
|
let (_i, values) = convert_result(count(V::unpack, header.nr_entries as usize)(i))?;
|
||||||
|
|
||||||
Ok(Node::Leaf {
|
Ok(Node::Leaf {
|
||||||
header,
|
header,
|
||||||
@ -136,7 +456,7 @@ pub fn unpack_node<V: Unpack>(
|
|||||||
values,
|
values,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
let (_i, values) = to_any(count(le_u64, header.nr_entries as usize)(i))?;
|
let (_i, values) = convert_result(count(le_u64, header.nr_entries as usize)(i))?;
|
||||||
Ok(Node::Internal {
|
Ok(Node::Internal {
|
||||||
header,
|
header,
|
||||||
keys,
|
keys,
|
||||||
@ -149,13 +469,15 @@ pub fn unpack_node<V: Unpack>(
|
|||||||
|
|
||||||
pub trait NodeVisitor<V: Unpack> {
|
pub trait NodeVisitor<V: Unpack> {
|
||||||
// &self is deliberately non mut to allow the walker to use multiple threads.
|
// &self is deliberately non mut to allow the walker to use multiple threads.
|
||||||
fn visit(&self, header: &NodeHeader, keys: &[u64], values: &[V]) -> Result<()>;
|
fn visit(&self, keys: &KeyRange, header: &NodeHeader, keys: &[u64], values: &[V])
|
||||||
|
-> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct BTreeWalker {
|
pub struct BTreeWalker {
|
||||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
|
fails: Arc<Mutex<BTreeMap<u64, BTreeError>>>,
|
||||||
ignore_non_fatal: bool,
|
ignore_non_fatal: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,6 +487,7 @@ impl BTreeWalker {
|
|||||||
let r: BTreeWalker = BTreeWalker {
|
let r: BTreeWalker = BTreeWalker {
|
||||||
engine,
|
engine,
|
||||||
sm: Arc::new(Mutex::new(RestrictedSpaceMap::new(nr_blocks as u64))),
|
sm: Arc::new(Mutex::new(RestrictedSpaceMap::new(nr_blocks as u64))),
|
||||||
|
fails: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
ignore_non_fatal,
|
ignore_non_fatal,
|
||||||
};
|
};
|
||||||
r
|
r
|
||||||
@ -177,53 +500,116 @@ impl BTreeWalker {
|
|||||||
) -> Result<BTreeWalker> {
|
) -> Result<BTreeWalker> {
|
||||||
{
|
{
|
||||||
let sm = sm.lock().unwrap();
|
let sm = sm.lock().unwrap();
|
||||||
assert_eq!(sm.get_nr_blocks()?, engine.get_nr_blocks());
|
assert_eq!(sm.get_nr_blocks().unwrap(), engine.get_nr_blocks());
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(BTreeWalker {
|
Ok(BTreeWalker {
|
||||||
engine,
|
engine,
|
||||||
sm,
|
sm,
|
||||||
|
fails: Arc::new(Mutex::new(BTreeMap::new())),
|
||||||
ignore_non_fatal,
|
ignore_non_fatal,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Atomically increments the ref count, and returns the _old_ count.
|
fn failed(&self, b: u64) -> Option<BTreeError> {
|
||||||
fn sm_inc(&self, b: u64) -> Result<u32> {
|
let fails = self.fails.lock().unwrap();
|
||||||
let mut sm = self.sm.lock().unwrap();
|
match fails.get(&b) {
|
||||||
let count = sm.get(b)?;
|
None => None,
|
||||||
sm.inc(b, 1)?;
|
Some(e) => Some(e.clone()),
|
||||||
Ok(count)
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_nodes<NV, V>(&self, visitor: &NV, bs: &[u64]) -> Result<()>
|
fn set_fail(&self, b: u64, err: BTreeError) {
|
||||||
|
// FIXME: should we monitor the size of fails, and abort if too many errors?
|
||||||
|
let mut fails = self.fails.lock().unwrap();
|
||||||
|
fails.insert(b, err);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atomically increments the ref count, and returns the _old_ count.
|
||||||
|
fn sm_inc(&self, b: u64) -> u32 {
|
||||||
|
let mut sm = self.sm.lock().unwrap();
|
||||||
|
let count = sm.get(b).unwrap();
|
||||||
|
sm.inc(b, 1).unwrap();
|
||||||
|
count
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build_aggregate(&self, b: u64, errs: Vec<BTreeError>) -> Result<()> {
|
||||||
|
match errs.len() {
|
||||||
|
0 => Ok(()),
|
||||||
|
1 => {
|
||||||
|
let e = errs[0].clone();
|
||||||
|
self.set_fail(b, e.clone());
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let e = aggregate_error(errs);
|
||||||
|
self.set_fail(b, e.clone());
|
||||||
|
Err(e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn walk_nodes<NV, V>(&self, visitor: &NV, kr: &[KeyRange], bs: &[u64]) -> Vec<BTreeError>
|
||||||
where
|
where
|
||||||
NV: NodeVisitor<V>,
|
NV: NodeVisitor<V>,
|
||||||
V: Unpack,
|
V: Unpack,
|
||||||
{
|
{
|
||||||
|
let mut errs: Vec<BTreeError> = Vec::new();
|
||||||
|
|
||||||
let mut blocks = Vec::with_capacity(bs.len());
|
let mut blocks = Vec::with_capacity(bs.len());
|
||||||
for b in bs {
|
for b in bs {
|
||||||
if self.sm_inc(*b)? == 0 {
|
if self.sm_inc(*b) == 0 {
|
||||||
|
// Node not yet seen
|
||||||
blocks.push(*b);
|
blocks.push(*b);
|
||||||
|
} else {
|
||||||
|
// This node has already been checked ...
|
||||||
|
match self.failed(*b) {
|
||||||
|
None => {
|
||||||
|
// ... it was clean so we can ignore.
|
||||||
|
}
|
||||||
|
Some(e) => {
|
||||||
|
// ... there was an error
|
||||||
|
errs.push(e.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let blocks = self.engine.read_many(&blocks[0..])?;
|
match self.engine.read_many(&blocks[0..]) {
|
||||||
|
Err(_) => {
|
||||||
|
// IO completely failed, error every block
|
||||||
|
for (i, b) in blocks.iter().enumerate() {
|
||||||
|
let e = io_err().keys_context(&kr[i]);
|
||||||
|
errs.push(e.clone());
|
||||||
|
self.set_fail(*b, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(rblocks) => {
|
||||||
|
let mut i = 0;
|
||||||
|
for rb in rblocks {
|
||||||
|
match rb {
|
||||||
|
Err(_) => {
|
||||||
|
let e = io_err().keys_context(&kr[i]);
|
||||||
|
errs.push(e.clone());
|
||||||
|
self.set_fail(blocks[i], e);
|
||||||
|
}
|
||||||
|
Ok(b) => match self.walk_node(visitor, &kr[i], &b, false) {
|
||||||
|
Err(e) => {
|
||||||
|
errs.push(e);
|
||||||
|
}
|
||||||
|
Ok(()) => {}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
for b in blocks {
|
i += 1;
|
||||||
match b {
|
}
|
||||||
Err(_e) => {
|
|
||||||
todo!();
|
|
||||||
},
|
|
||||||
Ok(b) => {
|
|
||||||
self.walk_node(visitor, &b, false)?;
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
errs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn walk_node<NV, V>(&self, visitor: &NV, b: &Block, is_root: bool) -> Result<()>
|
fn walk_node_<NV, V>(&self, visitor: &NV, kr: &KeyRange, b: &Block, is_root: bool) -> Result<()>
|
||||||
where
|
where
|
||||||
NV: NodeVisitor<V>,
|
NV: NodeVisitor<V>,
|
||||||
V: Unpack,
|
V: Unpack,
|
||||||
@ -232,51 +618,78 @@ impl BTreeWalker {
|
|||||||
|
|
||||||
let bt = checksum::metadata_block_type(b.get_data());
|
let bt = checksum::metadata_block_type(b.get_data());
|
||||||
if bt != checksum::BT::NODE {
|
if bt != checksum::BT::NODE {
|
||||||
return Err(anyhow!("checksum failed for node {}, {:?}", b.loc, bt));
|
return Err(
|
||||||
|
node_err_s(format!("checksum failed for node {}, {:?}", b.loc, bt))
|
||||||
|
.keys_context(kr),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = unpack_node::<V>(&b.get_data(), self.ignore_non_fatal, is_root)?;
|
let node = unpack_node::<V>(&b.get_data(), self.ignore_non_fatal, is_root)?;
|
||||||
|
|
||||||
match node {
|
match node {
|
||||||
Internal {
|
Internal { keys, values, .. } => {
|
||||||
header: _h,
|
let krs = split_key_ranges(&kr, &keys)?;
|
||||||
keys: _k,
|
let errs = self.walk_nodes(visitor, &krs, &values);
|
||||||
values,
|
return self.build_aggregate(b.loc, errs);
|
||||||
} => {
|
|
||||||
self.walk_nodes(visitor, &values)?;
|
|
||||||
}
|
}
|
||||||
Leaf {
|
Leaf {
|
||||||
header,
|
header,
|
||||||
keys,
|
keys,
|
||||||
values,
|
values,
|
||||||
} => {
|
} => {
|
||||||
visitor.visit(&header, &keys, &values)?;
|
if let Err(e) = visitor.visit(&kr, &header, &keys, &values) {
|
||||||
|
self.set_fail(b.loc, e.clone());
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn walk_node<NV, V>(&self, visitor: &NV, kr: &KeyRange, b: &Block, is_root: bool) -> Result<()>
|
||||||
|
where
|
||||||
|
NV: NodeVisitor<V>,
|
||||||
|
V: Unpack,
|
||||||
|
{
|
||||||
|
let r = self.walk_node_(visitor, kr, b, is_root);
|
||||||
|
let r = match r {
|
||||||
|
Err(e) => Err(BTreeError::Path(b.loc, Box::new(e))),
|
||||||
|
Ok(v) => Ok(v),
|
||||||
|
};
|
||||||
|
r
|
||||||
|
}
|
||||||
|
|
||||||
pub fn walk<NV, V>(&self, visitor: &NV, root: u64) -> Result<()>
|
pub fn walk<NV, V>(&self, visitor: &NV, root: u64) -> Result<()>
|
||||||
where
|
where
|
||||||
NV: NodeVisitor<V>,
|
NV: NodeVisitor<V>,
|
||||||
V: Unpack,
|
V: Unpack,
|
||||||
{
|
{
|
||||||
if self.sm_inc(root)? > 0 {
|
if self.sm_inc(root) > 0 {
|
||||||
Ok(())
|
if let Some(e) = self.failed(root) {
|
||||||
|
Err(e.clone())
|
||||||
|
} else {
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
let root = self.engine.read(root)?;
|
let root = self.engine.read(root).map_err(|_| io_err())?;
|
||||||
self.walk_node(visitor, &root, true)
|
let kr = KeyRange {
|
||||||
|
start: None,
|
||||||
|
end: None,
|
||||||
|
};
|
||||||
|
self.walk_node(visitor, &kr, &root, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//--------------------------------
|
//--------------------------------
|
||||||
|
|
||||||
|
/*
|
||||||
fn walk_node_threaded<NV, V>(
|
fn walk_node_threaded<NV, V>(
|
||||||
w: Arc<BTreeWalker>,
|
w: Arc<BTreeWalker>,
|
||||||
pool: &ThreadPool,
|
pool: &ThreadPool,
|
||||||
visitor: Arc<NV>,
|
visitor: Arc<NV>,
|
||||||
|
kr: &KeyRange,
|
||||||
b: &Block,
|
b: &Block,
|
||||||
is_root: bool,
|
is_root: bool,
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
@ -288,25 +701,25 @@ where
|
|||||||
|
|
||||||
let bt = checksum::metadata_block_type(b.get_data());
|
let bt = checksum::metadata_block_type(b.get_data());
|
||||||
if bt != checksum::BT::NODE {
|
if bt != checksum::BT::NODE {
|
||||||
return Err(anyhow!("checksum failed for node {}, {:?}", b.loc, bt));
|
return Err(node_err_s(format!(
|
||||||
|
"checksum failed for node {}, {:?}",
|
||||||
|
b.loc, bt
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
let node = unpack_node::<V>(&b.get_data(), w.ignore_non_fatal, is_root)?;
|
let node = unpack_node::<V>(&b.get_data(), w.ignore_non_fatal, is_root)?;
|
||||||
|
|
||||||
match node {
|
match node {
|
||||||
Internal {
|
Internal { keys, values, .. } => {
|
||||||
header: _h,
|
let krs = BTreeWalker::split_key_ranges(&kr, &keys)?;
|
||||||
keys: _k,
|
walk_nodes_threaded(w, pool, visitor, &krs, &values)?;
|
||||||
values,
|
|
||||||
} => {
|
|
||||||
walk_nodes_threaded(w, pool, visitor, &values)?;
|
|
||||||
}
|
}
|
||||||
Leaf {
|
Leaf {
|
||||||
header,
|
header,
|
||||||
keys,
|
keys,
|
||||||
values,
|
values,
|
||||||
} => {
|
} => {
|
||||||
visitor.visit(&header, &keys, &values)?;
|
visitor.visit(kr, &header, &keys, &values)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,6 +730,7 @@ fn walk_nodes_threaded<NV, V>(
|
|||||||
w: Arc<BTreeWalker>,
|
w: Arc<BTreeWalker>,
|
||||||
pool: &ThreadPool,
|
pool: &ThreadPool,
|
||||||
visitor: Arc<NV>,
|
visitor: Arc<NV>,
|
||||||
|
krs: &[KeyRange],
|
||||||
bs: &[u64],
|
bs: &[u64],
|
||||||
) -> Result<()>
|
) -> Result<()>
|
||||||
where
|
where
|
||||||
@ -330,22 +744,30 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let blocks = w.engine.read_many(&blocks[0..])?;
|
let rblocks = convert_io_err(w.engine.read_many(&blocks[0..]))?;
|
||||||
|
|
||||||
for b in blocks {
|
let mut i = 0;
|
||||||
|
for b in rblocks {
|
||||||
match b {
|
match b {
|
||||||
Err(_e) => {
|
Err(_) => {
|
||||||
todo!();
|
// FIXME: aggregate these
|
||||||
},
|
return Err(io_err());
|
||||||
|
}
|
||||||
Ok(b) => {
|
Ok(b) => {
|
||||||
let w = w.clone();
|
let w = w.clone();
|
||||||
let visitor = visitor.clone();
|
let visitor = visitor.clone();
|
||||||
|
let kr = krs[i].clone();
|
||||||
|
|
||||||
pool.execute(move || {
|
pool.execute(move || {
|
||||||
// FIXME: return result
|
let result = w.walk_node(visitor.as_ref(), &kr, &b, false);
|
||||||
w.walk_node(visitor.as_ref(), &b, false);
|
if result.is_err() {
|
||||||
|
todo!();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
i += 1;
|
||||||
}
|
}
|
||||||
pool.join();
|
pool.join();
|
||||||
|
|
||||||
@ -365,10 +787,28 @@ where
|
|||||||
if w.sm_inc(root)? > 0 {
|
if w.sm_inc(root)? > 0 {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
let root = w.engine.read(root)?;
|
let root = convert_io_err(w.engine.read(root))?;
|
||||||
walk_node_threaded(w, pool, visitor, &root, true)
|
let kr = KeyRange {
|
||||||
|
start: None,
|
||||||
|
end: None,
|
||||||
|
};
|
||||||
|
walk_node_threaded(w, pool, visitor, &kr, &root, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
*/
|
||||||
|
|
||||||
|
pub fn walk_threaded<NV, V>(
|
||||||
|
w: Arc<BTreeWalker>,
|
||||||
|
pool: &ThreadPool,
|
||||||
|
visitor: Arc<NV>,
|
||||||
|
root: u64,
|
||||||
|
) -> Result<()>
|
||||||
|
where
|
||||||
|
NV: NodeVisitor<V> + Send + Sync + 'static,
|
||||||
|
V: Unpack,
|
||||||
|
{
|
||||||
|
w.walk(visitor.as_ref(), root)
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
@ -385,7 +825,7 @@ impl<V> ValueCollector<V> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<V: Unpack + Clone> NodeVisitor<V> for ValueCollector<V> {
|
impl<V: Unpack + Clone> NodeVisitor<V> for ValueCollector<V> {
|
||||||
fn visit(&self, _h: &NodeHeader, keys: &[u64], values: &[V]) -> Result<()> {
|
fn visit(&self, _kr: &KeyRange, _h: &NodeHeader, keys: &[u64], values: &[V]) -> Result<()> {
|
||||||
let mut vals = self.values.lock().unwrap();
|
let mut vals = self.values.lock().unwrap();
|
||||||
for n in 0..keys.len() {
|
for n in 0..keys.len() {
|
||||||
vals.insert(keys[n], values[n].clone());
|
vals.insert(keys[n], values[n].clone());
|
||||||
@ -402,7 +842,6 @@ pub fn btree_to_map<V: Unpack + Clone>(
|
|||||||
) -> Result<BTreeMap<u64, V>> {
|
) -> Result<BTreeMap<u64, V>> {
|
||||||
let walker = BTreeWalker::new(engine, ignore_non_fatal);
|
let walker = BTreeWalker::new(engine, ignore_non_fatal);
|
||||||
let visitor = ValueCollector::<V>::new();
|
let visitor = ValueCollector::<V>::new();
|
||||||
|
|
||||||
walker.walk(&visitor, root)?;
|
walker.walk(&visitor, root)?;
|
||||||
Ok(visitor.values.into_inner().unwrap())
|
Ok(visitor.values.into_inner().unwrap())
|
||||||
}
|
}
|
||||||
|
40
src/thin/block_time.rs
Normal file
40
src/thin/block_time.rs
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
use nom::{number::complete::*, IResult};
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
use crate::pdata::unpack::*;
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct BlockTime {
|
||||||
|
pub block: u64,
|
||||||
|
pub time: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Unpack for BlockTime {
|
||||||
|
fn disk_size() -> u32 {
|
||||||
|
8
|
||||||
|
}
|
||||||
|
|
||||||
|
fn unpack(i: &[u8]) -> IResult<&[u8], BlockTime> {
|
||||||
|
let (i, n) = le_u64(i)?;
|
||||||
|
let block = n >> 24;
|
||||||
|
let time = n & ((1 << 24) - 1);
|
||||||
|
|
||||||
|
Ok((
|
||||||
|
i,
|
||||||
|
BlockTime {
|
||||||
|
block,
|
||||||
|
time: time as u32,
|
||||||
|
},
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for BlockTime {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "{} @ {}", self.block, self.time)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
@ -9,7 +9,7 @@ use threadpool::ThreadPool;
|
|||||||
|
|
||||||
use crate::checksum;
|
use crate::checksum;
|
||||||
use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine};
|
use crate::io_engine::{AsyncIoEngine, IoEngine, SyncIoEngine};
|
||||||
use crate::pdata::btree::*;
|
use crate::pdata::btree::{self, *};
|
||||||
use crate::pdata::space_map::*;
|
use crate::pdata::space_map::*;
|
||||||
use crate::pdata::unpack::*;
|
use crate::pdata::unpack::*;
|
||||||
use crate::report::*;
|
use crate::report::*;
|
||||||
@ -50,7 +50,7 @@ struct BottomLevelVisitor {
|
|||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
impl NodeVisitor<BlockTime> for BottomLevelVisitor {
|
impl NodeVisitor<BlockTime> for BottomLevelVisitor {
|
||||||
fn visit(&self, _h: &NodeHeader, _k: &[u64], values: &[BlockTime]) -> Result<()> {
|
fn visit(&self, _kr: &KeyRange, _h: &NodeHeader, _k: &[u64], values: &[BlockTime]) -> btree::Result<()> {
|
||||||
// FIXME: do other checks
|
// FIXME: do other checks
|
||||||
|
|
||||||
if values.len() == 0 {
|
if values.len() == 0 {
|
||||||
@ -67,13 +67,13 @@ impl NodeVisitor<BlockTime> for BottomLevelVisitor {
|
|||||||
if block == start + len {
|
if block == start + len {
|
||||||
len += 1;
|
len += 1;
|
||||||
} else {
|
} else {
|
||||||
data_sm.inc(start, len)?;
|
data_sm.inc(start, len).unwrap();
|
||||||
start = block;
|
start = block;
|
||||||
len = 1;
|
len = 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data_sm.inc(start, len)?;
|
data_sm.inc(start, len).unwrap();
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -124,14 +124,14 @@ impl<'a> OverflowChecker<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> NodeVisitor<u32> for OverflowChecker<'a> {
|
impl<'a> NodeVisitor<u32> for OverflowChecker<'a> {
|
||||||
fn visit(&self, _h: &NodeHeader, keys: &[u64], values: &[u32]) -> Result<()> {
|
fn visit(&self, _kr: &KeyRange, _h: &NodeHeader, keys: &[u64], values: &[u32]) -> btree::Result<()> {
|
||||||
for n in 0..keys.len() {
|
for n in 0..keys.len() {
|
||||||
let k = keys[n];
|
let k = keys[n];
|
||||||
let v = values[n];
|
let v = values[n];
|
||||||
let expected = self.data_sm.get(k)?;
|
let expected = self.data_sm.get(k).unwrap();
|
||||||
if expected != v {
|
if expected != v {
|
||||||
return Err(anyhow!("Bad reference count for data block {}. Expected {}, but space map contains {}.",
|
return Err(value_err(format!("Bad reference count for data block {}. Expected {}, but space map contains {}.",
|
||||||
k, expected, v));
|
k, expected, v)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -382,7 +382,7 @@ fn check_mapping_bottom_level(
|
|||||||
false,
|
false,
|
||||||
)?);
|
)?);
|
||||||
|
|
||||||
if roots.len() > 64 {
|
if roots.len() > 64000 {
|
||||||
ctx.report.info("spreading load across devices");
|
ctx.report.info("spreading load across devices");
|
||||||
for (_thin_id, root) in roots {
|
for (_thin_id, root) in roots {
|
||||||
let data_sm = data_sm.clone();
|
let data_sm = data_sm.clone();
|
||||||
@ -390,6 +390,7 @@ fn check_mapping_bottom_level(
|
|||||||
let v = BottomLevelVisitor { data_sm };
|
let v = BottomLevelVisitor { data_sm };
|
||||||
let w = w.clone();
|
let w = w.clone();
|
||||||
ctx.pool.execute(move || {
|
ctx.pool.execute(move || {
|
||||||
|
// FIXME: propogate errors + share fails.
|
||||||
let _r = w.walk(&v, root);
|
let _r = w.walk(&v, root);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -401,6 +402,7 @@ fn check_mapping_bottom_level(
|
|||||||
let data_sm = data_sm.clone();
|
let data_sm = data_sm.clone();
|
||||||
let root = *root;
|
let root = *root;
|
||||||
let v = Arc::new(BottomLevelVisitor { data_sm });
|
let v = Arc::new(BottomLevelVisitor { data_sm });
|
||||||
|
// FIXME: propogate errors + share fails.
|
||||||
walk_threaded(w, &ctx.pool, v, root)?
|
walk_threaded(w, &ctx.pool, v, root)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -480,15 +482,16 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// mapping top level
|
// mapping top level
|
||||||
|
report.set_sub_title("mapping tree");
|
||||||
let roots =
|
let roots =
|
||||||
btree_to_map_with_sm::<u64>(engine.clone(), metadata_sm.clone(), false, sb.mapping_root)?;
|
btree_to_map_with_sm::<u64>(engine.clone(), metadata_sm.clone(), false, sb.mapping_root)?;
|
||||||
|
|
||||||
// mapping bottom level
|
// mapping bottom level
|
||||||
report.set_sub_title("mapping tree");
|
|
||||||
let root = unpack::<SMRoot>(&sb.data_sm_root[0..])?;
|
let root = unpack::<SMRoot>(&sb.data_sm_root[0..])?;
|
||||||
let data_sm = core_sm(root.nr_blocks, nr_devs as u32);
|
let data_sm = core_sm(root.nr_blocks, nr_devs as u32);
|
||||||
check_mapping_bottom_level(&ctx, &metadata_sm, &data_sm, &roots)?;
|
check_mapping_bottom_level(&ctx, &metadata_sm, &data_sm, &roots)?;
|
||||||
bail_out(&ctx, "mapping tree")?;
|
bail_out(&ctx, "mapping tree")?;
|
||||||
|
eprintln!("checked mapping");
|
||||||
|
|
||||||
report.set_sub_title("data space map");
|
report.set_sub_title("data space map");
|
||||||
let root = unpack::<SMRoot>(&sb.data_sm_root[0..])?;
|
let root = unpack::<SMRoot>(&sb.data_sm_root[0..])?;
|
||||||
@ -556,7 +559,6 @@ pub fn check(opts: ThinCheckOptions) -> Result<()> {
|
|||||||
let mut stop_progress = stop_progress.lock().unwrap();
|
let mut stop_progress = stop_progress.lock().unwrap();
|
||||||
*stop_progress = true;
|
*stop_progress = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
tid.join();
|
tid.join();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
pub mod block_time;
|
||||||
pub mod superblock;
|
pub mod superblock;
|
||||||
pub mod check;
|
pub mod check;
|
||||||
pub mod xml;
|
pub mod xml;
|
||||||
|
@ -1,17 +1,27 @@
|
|||||||
use crate::io_engine::*;
|
use crate::io_engine::*;
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use nom::{bytes::complete::*, number::complete::*, IResult};
|
use nom::{bytes::complete::*, number::complete::*, IResult};
|
||||||
|
use std::fmt;
|
||||||
pub const SUPERBLOCK_LOCATION: u64 = 0;
|
pub const SUPERBLOCK_LOCATION: u64 = 0;
|
||||||
//const UUID_SIZE: usize = 16;
|
//const UUID_SIZE: usize = 16;
|
||||||
const SPACE_MAP_ROOT_SIZE: usize = 128;
|
const SPACE_MAP_ROOT_SIZE: usize = 128;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct SuperblockFlags {
|
pub struct SuperblockFlags {
|
||||||
pub needs_check: bool,
|
pub needs_check: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
impl fmt::Display for SuperblockFlags {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if self.needs_check {
|
||||||
|
write!(f, "NEEDS_CHECK")
|
||||||
|
} else {
|
||||||
|
write!(f, "-")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
pub struct Superblock {
|
pub struct Superblock {
|
||||||
pub flags: SuperblockFlags,
|
pub flags: SuperblockFlags,
|
||||||
pub block: u64,
|
pub block: u64,
|
||||||
@ -76,7 +86,9 @@ fn unpack(data: &[u8]) -> IResult<&[u8], Superblock> {
|
|||||||
Ok((
|
Ok((
|
||||||
i,
|
i,
|
||||||
Superblock {
|
Superblock {
|
||||||
flags: SuperblockFlags {needs_check: (flags & 0x1) != 0},
|
flags: SuperblockFlags {
|
||||||
|
needs_check: (flags & 0x1) != 0,
|
||||||
|
},
|
||||||
block,
|
block,
|
||||||
//uuid: uuid[0..UUID_SIZE],
|
//uuid: uuid[0..UUID_SIZE],
|
||||||
version,
|
version,
|
||||||
|
Loading…
Reference in New Issue
Block a user