Merge branch '2020-08-13-thin-check-rewrite'
This commit is contained in:
commit
0372e689e5
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -704,6 +704,7 @@ name = "thinp"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
|
"atty",
|
||||||
"base64",
|
"base64",
|
||||||
"byteorder",
|
"byteorder",
|
||||||
"clap",
|
"clap",
|
||||||
|
@ -6,6 +6,7 @@ edition = "2018"
|
|||||||
license = "GPL3"
|
license = "GPL3"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
atty = "0.2"
|
||||||
anyhow = "1.0"
|
anyhow = "1.0"
|
||||||
base64 = "0.12"
|
base64 = "0.12"
|
||||||
byteorder = "1.3"
|
byteorder = "1.3"
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
extern crate clap;
|
extern crate clap;
|
||||||
extern crate thinp;
|
extern crate thinp;
|
||||||
|
|
||||||
|
use atty::Stream;
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::process;
|
use std::process;
|
||||||
use thinp::file_utils;
|
|
||||||
use thinp::thin::check::{check, ThinCheckOptions};
|
|
||||||
|
|
||||||
use std::process::exit;
|
use std::process::exit;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use thinp::file_utils;
|
||||||
|
use thinp::report::*;
|
||||||
|
use thinp::thin::check::{check, ThinCheckOptions};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let parser = App::new("thin_check")
|
let parser = App::new("thin_check")
|
||||||
@ -17,26 +19,27 @@ fn main() {
|
|||||||
Arg::with_name("QUIET")
|
Arg::with_name("QUIET")
|
||||||
.help("Suppress output messages, return only exit code.")
|
.help("Suppress output messages, return only exit code.")
|
||||||
.short("q")
|
.short("q")
|
||||||
.long("quiet")
|
.long("quiet"),
|
||||||
.value_name("QUIET"),
|
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("SB_ONLY")
|
Arg::with_name("SB_ONLY")
|
||||||
.help("Only check the superblock.")
|
.help("Only check the superblock.")
|
||||||
.long("super-block-only")
|
.long("super-block-only")
|
||||||
.value_name("SB_ONLY"),
|
.value_name("SB_ONLY"),
|
||||||
|
) .arg(
|
||||||
|
Arg::with_name("AUTO_REPAIR")
|
||||||
|
.help("Auto repair trivial issues.")
|
||||||
|
.long("auto-repair"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("ignore-non-fatal-errors")
|
Arg::with_name("IGNORE_NON_FATAL")
|
||||||
.help("Only return a non-zero exit code if a fatal error is found.")
|
.help("Only return a non-zero exit code if a fatal error is found.")
|
||||||
.long("ignore-non-fatal-errors")
|
.long("ignore-non-fatal-errors"),
|
||||||
.value_name("IGNORE_NON_FATAL"),
|
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("clear-needs-check-flag")
|
Arg::with_name("CLEAR_NEEDS_CHECK")
|
||||||
.help("Clears the 'needs_check' flag in the superblock")
|
.help("Clears the 'needs_check' flag in the superblock")
|
||||||
.long("clear-needs-check")
|
.long("clear-needs-check"),
|
||||||
.value_name("CLEAR_NEEDS_CHECK"),
|
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("OVERRIDE_MAPPING_ROOT")
|
Arg::with_name("OVERRIDE_MAPPING_ROOT")
|
||||||
@ -61,9 +64,7 @@ fn main() {
|
|||||||
.arg(
|
.arg(
|
||||||
Arg::with_name("SYNC_IO")
|
Arg::with_name("SYNC_IO")
|
||||||
.help("Force use of synchronous io")
|
.help("Force use of synchronous io")
|
||||||
.long("sync-io")
|
.long("sync-io"),
|
||||||
.value_name("SYNC_IO")
|
|
||||||
.takes_value(false),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
let matches = parser.get_matches();
|
let matches = parser.get_matches();
|
||||||
@ -74,13 +75,26 @@ fn main() {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let report;
|
||||||
|
|
||||||
|
if matches.is_present("QUIET") {
|
||||||
|
report = std::sync::Arc::new(mk_quiet_report());
|
||||||
|
} else if atty::is(Stream::Stdout) {
|
||||||
|
report = std::sync::Arc::new(mk_progress_bar_report());
|
||||||
|
} else {
|
||||||
|
report = Arc::new(mk_simple_report());
|
||||||
|
}
|
||||||
|
|
||||||
let opts = ThinCheckOptions {
|
let opts = ThinCheckOptions {
|
||||||
dev: &input_file,
|
dev: &input_file,
|
||||||
async_io: !matches.is_present("SYNC_IO"),
|
async_io: !matches.is_present("SYNC_IO"),
|
||||||
|
ignore_non_fatal: matches.is_present("IGNORE_NON_FATAL"),
|
||||||
|
auto_repair: matches.is_present("AUTO_REPAIR"),
|
||||||
|
report,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Err(reason) = check(&opts) {
|
if let Err(reason) = check(opts) {
|
||||||
println!("Application error: {}", reason);
|
println!("{}", reason);
|
||||||
process::exit(1);
|
process::exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use byteorder::{LittleEndian, ReadBytesExt};
|
use anyhow::{anyhow, Result};
|
||||||
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
|
||||||
use crc32c::crc32c;
|
use crc32c::crc32c;
|
||||||
|
|
||||||
use std::io::Cursor;
|
use std::io::Cursor;
|
||||||
@ -44,3 +45,22 @@ pub fn metadata_block_type(buf: &[u8]) -> BT {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn write_checksum(buf: &mut [u8], kind: BT) -> Result<()> {
|
||||||
|
if buf.len() != BLOCK_SIZE as usize {
|
||||||
|
return Err(anyhow!("block is wrong size"));
|
||||||
|
}
|
||||||
|
|
||||||
|
use BT::*;
|
||||||
|
let salt = match kind {
|
||||||
|
SUPERBLOCK => SUPERBLOCK_CSUM_XOR,
|
||||||
|
NODE => BTREE_CSUM_XOR,
|
||||||
|
BITMAP => BITMAP_CSUM_XOR,
|
||||||
|
INDEX => INDEX_CSUM_XOR,
|
||||||
|
UNKNOWN => {return Err(anyhow!("Invalid block type"));}
|
||||||
|
};
|
||||||
|
|
||||||
|
let csum = checksum(buf) ^ salt;
|
||||||
|
let mut out = std::io::Cursor::new(buf);
|
||||||
|
out.write_u32::<LittleEndian>(csum)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
116
src/io_engine.rs
116
src/io_engine.rs
@ -4,11 +4,11 @@ use io_uring::IoUring;
|
|||||||
use std::alloc::{alloc, dealloc, Layout};
|
use std::alloc::{alloc, dealloc, Layout};
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::fs::OpenOptions;
|
use std::fs::OpenOptions;
|
||||||
use std::io::{self, Read, Seek};
|
use std::io::{self, Read, Seek, Write};
|
||||||
use std::os::unix::fs::OpenOptionsExt;
|
use std::os::unix::fs::OpenOptionsExt;
|
||||||
use std::os::unix::io::{AsRawFd, RawFd};
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::{Arc, Mutex, Condvar};
|
use std::sync::{Arc, Condvar, Mutex};
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
@ -50,7 +50,9 @@ unsafe impl Send for Block {}
|
|||||||
pub trait IoEngine {
|
pub trait IoEngine {
|
||||||
fn get_nr_blocks(&self) -> u64;
|
fn get_nr_blocks(&self) -> u64;
|
||||||
fn read(&self, block: &mut Block) -> Result<()>;
|
fn read(&self, block: &mut Block) -> Result<()>;
|
||||||
fn read_many(&self, blocks: &mut Vec<Block>) -> Result<()>;
|
fn read_many(&self, blocks: &mut [Block]) -> Result<()>;
|
||||||
|
fn write(&self, block: &Block) -> Result<()>;
|
||||||
|
fn write_many(&self, blocks: &[Block]) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_nr_blocks(path: &Path) -> io::Result<u64> {
|
fn get_nr_blocks(path: &Path) -> io::Result<u64> {
|
||||||
@ -67,20 +69,20 @@ pub struct SyncIoEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SyncIoEngine {
|
impl SyncIoEngine {
|
||||||
fn open_file(path: &Path) -> Result<File> {
|
fn open_file(path: &Path, writeable: bool) -> Result<File> {
|
||||||
let file = OpenOptions::new()
|
let file = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(false)
|
.write(writeable)
|
||||||
.custom_flags(libc::O_DIRECT)
|
.custom_flags(libc::O_DIRECT)
|
||||||
.open(path)?;
|
.open(path)?;
|
||||||
|
|
||||||
Ok(file)
|
Ok(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(path: &Path, nr_files: usize) -> Result<SyncIoEngine> {
|
pub fn new(path: &Path, nr_files: usize, writeable: bool) -> Result<SyncIoEngine> {
|
||||||
let mut files = Vec::new();
|
let mut files = Vec::new();
|
||||||
for _n in 0..nr_files {
|
for _n in 0..nr_files {
|
||||||
files.push(SyncIoEngine::open_file(path)?);
|
files.push(SyncIoEngine::open_file(path, writeable)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(SyncIoEngine {
|
Ok(SyncIoEngine {
|
||||||
@ -120,7 +122,7 @@ impl IoEngine for SyncIoEngine {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_many(&self, blocks: &mut Vec<Block>) -> Result<()> {
|
fn read_many(&self, blocks: &mut [Block]) -> Result<()> {
|
||||||
let mut input = self.get();
|
let mut input = self.get();
|
||||||
for b in blocks {
|
for b in blocks {
|
||||||
input.seek(io::SeekFrom::Start(b.loc * BLOCK_SIZE as u64))?;
|
input.seek(io::SeekFrom::Start(b.loc * BLOCK_SIZE as u64))?;
|
||||||
@ -130,6 +132,26 @@ impl IoEngine for SyncIoEngine {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write(&self, b: &Block) -> Result<()> {
|
||||||
|
let mut input = self.get();
|
||||||
|
input.seek(io::SeekFrom::Start(b.loc * BLOCK_SIZE as u64))?;
|
||||||
|
input.write_all(&b.get_data())?;
|
||||||
|
self.put(input);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_many(&self, blocks: &[Block]) -> Result<()> {
|
||||||
|
let mut input = self.get();
|
||||||
|
for b in blocks {
|
||||||
|
input.seek(io::SeekFrom::Start(b.loc * BLOCK_SIZE as u64))?;
|
||||||
|
input.write_all(&b.get_data())?;
|
||||||
|
}
|
||||||
|
self.put(input);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
@ -147,10 +169,10 @@ pub struct AsyncIoEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AsyncIoEngine {
|
impl AsyncIoEngine {
|
||||||
pub fn new(path: &Path, queue_len: u32) -> Result<AsyncIoEngine> {
|
pub fn new(path: &Path, queue_len: u32, writeable: bool) -> Result<AsyncIoEngine> {
|
||||||
let input = OpenOptions::new()
|
let input = OpenOptions::new()
|
||||||
.read(true)
|
.read(true)
|
||||||
.write(false)
|
.write(writeable)
|
||||||
.custom_flags(libc::O_DIRECT)
|
.custom_flags(libc::O_DIRECT)
|
||||||
.open(path)?;
|
.open(path)?;
|
||||||
|
|
||||||
@ -165,6 +187,7 @@ impl AsyncIoEngine {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: refactor next two fns
|
||||||
fn read_many_(&self, blocks: &mut [Block]) -> Result<()> {
|
fn read_many_(&self, blocks: &mut [Block]) -> Result<()> {
|
||||||
let mut inner = self.inner.lock().unwrap();
|
let mut inner = self.inner.lock().unwrap();
|
||||||
let count = blocks.len();
|
let count = blocks.len();
|
||||||
@ -195,6 +218,37 @@ impl AsyncIoEngine {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write_many_(&self, blocks: &[Block]) -> Result<()> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
let count = blocks.len();
|
||||||
|
let fd = types::Target::Fd(inner.input.as_raw_fd());
|
||||||
|
|
||||||
|
for b in blocks.iter() {
|
||||||
|
let write_e = opcode::Write::new(fd, b.data, BLOCK_SIZE as u32)
|
||||||
|
.offset(b.loc as i64 * BLOCK_SIZE as i64);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut queue = inner.ring.submission().available();
|
||||||
|
queue
|
||||||
|
.push(write_e.build().user_data(1))
|
||||||
|
.ok()
|
||||||
|
.expect("queue is full");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.ring.submit_and_wait(count)?;
|
||||||
|
|
||||||
|
let cqes = inner.ring.completion().available().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// FIXME: return proper errors
|
||||||
|
assert_eq!(cqes.len(), count);
|
||||||
|
for c in &cqes {
|
||||||
|
assert_eq!(c.result(), BLOCK_SIZE as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for AsyncIoEngine {
|
impl Clone for AsyncIoEngine {
|
||||||
@ -245,7 +299,7 @@ impl IoEngine for AsyncIoEngine {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_many(&self, blocks: &mut Vec<Block>) -> Result<()> {
|
fn read_many(&self, blocks: &mut [Block]) -> Result<()> {
|
||||||
let inner = self.inner.lock().unwrap();
|
let inner = self.inner.lock().unwrap();
|
||||||
let queue_len = inner.queue_len as usize;
|
let queue_len = inner.queue_len as usize;
|
||||||
drop(inner);
|
drop(inner);
|
||||||
@ -258,6 +312,46 @@ impl IoEngine for AsyncIoEngine {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn write(&self, b: &Block) -> Result<()> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
let fd = types::Target::Fd(inner.input.as_raw_fd());
|
||||||
|
let write_e = opcode::Write::new(fd, b.data, BLOCK_SIZE as u32)
|
||||||
|
.offset(b.loc as i64 * BLOCK_SIZE as i64);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let mut queue = inner.ring.submission().available();
|
||||||
|
queue
|
||||||
|
.push(write_e.build().user_data(1))
|
||||||
|
.ok()
|
||||||
|
.expect("queue is full");
|
||||||
|
}
|
||||||
|
|
||||||
|
inner.ring.submit_and_wait(1)?;
|
||||||
|
|
||||||
|
let cqes = inner.ring.completion().available().collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// FIXME: return proper errors
|
||||||
|
assert_eq!(cqes.len(), 1);
|
||||||
|
assert_eq!(cqes[0].user_data(), 1);
|
||||||
|
assert_eq!(cqes[0].result(), BLOCK_SIZE as i32);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_many(&self, blocks: &[Block]) -> Result<()> {
|
||||||
|
let inner = self.inner.lock().unwrap();
|
||||||
|
let queue_len = inner.queue_len as usize;
|
||||||
|
drop(inner);
|
||||||
|
|
||||||
|
let mut done = 0;
|
||||||
|
while done != blocks.len() {
|
||||||
|
let len = usize::min(blocks.len() - done, queue_len);
|
||||||
|
self.write_many_(&blocks[done..(done + len)])?;
|
||||||
|
done += len;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
@ -15,12 +15,13 @@ extern crate quickcheck;
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
extern crate quickcheck_macros;
|
extern crate quickcheck_macros;
|
||||||
|
|
||||||
pub mod io_engine;
|
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod checksum;
|
pub mod checksum;
|
||||||
pub mod file_utils;
|
pub mod file_utils;
|
||||||
|
pub mod io_engine;
|
||||||
pub mod pack;
|
pub mod pack;
|
||||||
pub mod pdata;
|
pub mod pdata;
|
||||||
|
pub mod report;
|
||||||
pub mod shrink;
|
pub mod shrink;
|
||||||
pub mod thin;
|
pub mod thin;
|
||||||
pub mod version;
|
pub mod version;
|
||||||
|
@ -2,9 +2,10 @@ use anyhow::{anyhow, Result};
|
|||||||
use fixedbitset::FixedBitSet;
|
use fixedbitset::FixedBitSet;
|
||||||
use nom::{multi::count, number::complete::*, IResult};
|
use nom::{multi::count, number::complete::*, IResult};
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
|
|
||||||
use crate::io_engine::*;
|
use crate::io_engine::*;
|
||||||
use crate::pdata::unpack::Unpack;
|
use crate::pdata::unpack::{Pack, Unpack};
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
@ -78,7 +79,7 @@ impl Unpack for IndexEntry {
|
|||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
const MAX_METADATA_BITMAPS: usize = 255;
|
pub const MAX_METADATA_BITMAPS: usize = 255;
|
||||||
|
|
||||||
pub struct MetadataIndex {
|
pub struct MetadataIndex {
|
||||||
pub indexes: Vec<IndexEntry>,
|
pub indexes: Vec<IndexEntry>,
|
||||||
@ -129,6 +130,15 @@ impl Unpack for BitmapHeader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pack for BitmapHeader {
|
||||||
|
fn pack<W: WriteBytesExt>(&self, out: &mut W) -> Result<()> {
|
||||||
|
out.write_u32::<LittleEndian>(self.csum)?;
|
||||||
|
out.write_u32::<LittleEndian>(self.not_used)?;
|
||||||
|
out.write_u64::<LittleEndian>(self.blocknr)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq)]
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
pub enum BitmapEntry {
|
pub enum BitmapEntry {
|
||||||
Small(u8),
|
Small(u8),
|
||||||
@ -175,6 +185,40 @@ impl Unpack for Bitmap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pack for Bitmap {
|
||||||
|
fn pack<W: WriteBytesExt>(&self, out: &mut W) -> Result<()> {
|
||||||
|
use BitmapEntry::*;
|
||||||
|
BitmapHeader::pack(&self.header, out)?;
|
||||||
|
|
||||||
|
for chunk in self.entries.chunks(32) {
|
||||||
|
let mut w = 0u64;
|
||||||
|
for e in chunk {
|
||||||
|
w >>= 2;
|
||||||
|
match e {
|
||||||
|
Small(0) => {
|
||||||
|
},
|
||||||
|
Small(1) => {
|
||||||
|
w |= 0x2 << 62;
|
||||||
|
},
|
||||||
|
Small(2) => {
|
||||||
|
w |= 0x1 << 62;
|
||||||
|
},
|
||||||
|
Small(_) => {
|
||||||
|
return Err(anyhow!("Bad small value in bitmap entry"));
|
||||||
|
},
|
||||||
|
Overflow => {
|
||||||
|
w |= 0x3 << 62;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u64::pack(&w, out)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
pub trait SpaceMap {
|
pub trait SpaceMap {
|
||||||
@ -184,6 +228,8 @@ pub trait SpaceMap {
|
|||||||
fn inc(&mut self, begin: u64, len: u64) -> Result<()>;
|
fn inc(&mut self, begin: u64, len: u64) -> Result<()>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type ASpaceMap = Arc<Mutex<dyn SpaceMap + Sync + Send>>;
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
pub struct CoreSpaceMap<T> {
|
pub struct CoreSpaceMap<T> {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use nom::{number::complete::*, IResult};
|
use nom::{number::complete::*, IResult};
|
||||||
|
use byteorder::{LittleEndian, WriteBytesExt};
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
@ -20,6 +21,12 @@ pub fn unpack<U: Unpack>(data: &[u8]) -> Result<U> {
|
|||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
|
pub trait Pack {
|
||||||
|
fn pack<W: WriteBytesExt>(&self, data: &mut W) -> Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
impl Unpack for u64 {
|
impl Unpack for u64 {
|
||||||
fn disk_size() -> u32 {
|
fn disk_size() -> u32 {
|
||||||
8
|
8
|
||||||
@ -30,6 +37,13 @@ impl Unpack for u64 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pack for u64 {
|
||||||
|
fn pack<W: WriteBytesExt>(&self, out: &mut W) -> Result<()> {
|
||||||
|
out.write_u64::<LittleEndian>(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Unpack for u32 {
|
impl Unpack for u32 {
|
||||||
fn disk_size() -> u32 {
|
fn disk_size() -> u32 {
|
||||||
4
|
4
|
||||||
@ -40,4 +54,11 @@ impl Unpack for u32 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Pack for u32 {
|
||||||
|
fn pack<W: WriteBytesExt>(&self, out: &mut W) -> Result<()> {
|
||||||
|
out.write_u32::<LittleEndian>(*self)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
202
src/report.rs
Normal file
202
src/report.rs
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
use indicatif::{ProgressBar, ProgressStyle};
|
||||||
|
use std::sync::Mutex;
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub enum ReportOutcome {
|
||||||
|
Success,
|
||||||
|
NonFatal,
|
||||||
|
Fatal,
|
||||||
|
}
|
||||||
|
|
||||||
|
use ReportOutcome::*;
|
||||||
|
|
||||||
|
impl ReportOutcome {
|
||||||
|
pub fn combine(lhs: &ReportOutcome, rhs: &ReportOutcome) -> ReportOutcome {
|
||||||
|
match (lhs, rhs) {
|
||||||
|
(Success, rhs) => rhs.clone(),
|
||||||
|
(lhs, Success) => lhs.clone(),
|
||||||
|
(Fatal, _) => Fatal,
|
||||||
|
(_, Fatal) => Fatal,
|
||||||
|
(_, _) => NonFatal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Report {
|
||||||
|
outcome: Mutex<ReportOutcome>,
|
||||||
|
inner: Mutex<Box<dyn ReportInner + Send>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
trait ReportInner {
|
||||||
|
fn set_title(&mut self, txt: &str);
|
||||||
|
fn set_sub_title(&mut self, txt: &str);
|
||||||
|
fn progress(&mut self, percent: u8);
|
||||||
|
fn log(&mut self, txt: &str);
|
||||||
|
fn complete(&mut self);
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Report {
|
||||||
|
fn new(inner: Box<dyn ReportInner + Send>) -> Report {
|
||||||
|
Report {
|
||||||
|
outcome: Mutex::new(Success),
|
||||||
|
inner: Mutex::new(inner),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_outcome(&self, rhs: ReportOutcome) {
|
||||||
|
let mut lhs = self.outcome.lock().unwrap();
|
||||||
|
*lhs = ReportOutcome::combine(&lhs, &rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_title(&self, txt: &str) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.set_title(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_sub_title(&self, txt: &str) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.set_sub_title(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn progress(&self, percent: u8) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.progress(percent)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn info(&self, txt: &str) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.log(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn non_fatal(&self, txt: &str) {
|
||||||
|
self.update_outcome(NonFatal);
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.log(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn fatal(&self, txt: &str) {
|
||||||
|
self.update_outcome(Fatal);
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.log(txt)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn complete(&mut self) {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.complete();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_outcome(&self) -> ReportOutcome {
|
||||||
|
let outcome = self.outcome.lock().unwrap();
|
||||||
|
outcome.clone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
struct PBInner {
|
||||||
|
title: String,
|
||||||
|
bar: ProgressBar,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReportInner for PBInner {
|
||||||
|
fn set_title(&mut self, txt: &str) {
|
||||||
|
self.title = txt.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_sub_title(&mut self, txt: &str) {
|
||||||
|
//let mut fmt = "".to_string(); //Checking thin metadata".to_string(); //self.title.clone();
|
||||||
|
let mut fmt = "Checking thin metadata [{bar:40}] Remaining {eta}, ".to_string();
|
||||||
|
fmt.push_str(&txt);
|
||||||
|
self.bar.set_style(
|
||||||
|
ProgressStyle::default_bar()
|
||||||
|
.template(&fmt)
|
||||||
|
.progress_chars("=> "),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn progress(&mut self, percent: u8) {
|
||||||
|
self.bar.set_position(percent as u64);
|
||||||
|
self.bar.tick();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&mut self, txt: &str) {
|
||||||
|
self.bar.println(txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete(&mut self) {
|
||||||
|
self.bar.finish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mk_progress_bar_report() -> Report {
|
||||||
|
Report::new(Box::new(PBInner {
|
||||||
|
title: "".to_string(),
|
||||||
|
bar: ProgressBar::new(100),
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
struct SimpleInner {
|
||||||
|
last_progress: std::time::SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleInner {
|
||||||
|
fn new() -> SimpleInner {
|
||||||
|
SimpleInner {
|
||||||
|
last_progress: std::time::SystemTime::now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReportInner for SimpleInner {
|
||||||
|
fn set_title(&mut self, txt: &str) {
|
||||||
|
println!("{}", txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_sub_title(&mut self, txt: &str) {
|
||||||
|
println!("{}", txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn progress(&mut self, percent: u8) {
|
||||||
|
let elapsed = self.last_progress.elapsed().unwrap();
|
||||||
|
if elapsed > std::time::Duration::from_secs(5) {
|
||||||
|
println!("Progress: {}%", percent);
|
||||||
|
self.last_progress = std::time::SystemTime::now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn log(&mut self, txt: &str) {
|
||||||
|
eprintln!("{}", txt);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn complete(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mk_simple_report() -> Report {
|
||||||
|
Report::new(Box::new(SimpleInner::new()))
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
||||||
|
|
||||||
|
struct QuietInner {}
|
||||||
|
|
||||||
|
impl ReportInner for QuietInner {
|
||||||
|
fn set_title(&mut self, _txt: &str) {}
|
||||||
|
|
||||||
|
fn set_sub_title(&mut self, _txt: &str) {}
|
||||||
|
|
||||||
|
fn progress(&mut self, _percent: u8) {}
|
||||||
|
|
||||||
|
fn log(&mut self, _txt: &str) {}
|
||||||
|
|
||||||
|
fn complete(&mut self) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mk_quiet_report() -> Report {
|
||||||
|
Report::new(Box::new(QuietInner {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
//------------------------------------------
|
@ -1,11 +1,10 @@
|
|||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use indicatif::{ProgressBar, ProgressStyle};
|
|
||||||
use nom::{number::complete::*, IResult};
|
use nom::{number::complete::*, IResult};
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
use std::io::Cursor;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError};
|
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::{thread, time};
|
use std::thread::{self, JoinHandle};
|
||||||
use threadpool::ThreadPool;
|
use threadpool::ThreadPool;
|
||||||
|
|
||||||
use crate::checksum;
|
use crate::checksum;
|
||||||
@ -13,6 +12,7 @@ use crate::io_engine::{AsyncIoEngine, Block, IoEngine, SyncIoEngine};
|
|||||||
use crate::pdata::btree::{btree_to_map, btree_to_map_with_sm, BTreeWalker, Node, NodeVisitor};
|
use crate::pdata::btree::{btree_to_map, btree_to_map_with_sm, BTreeWalker, Node, NodeVisitor};
|
||||||
use crate::pdata::space_map::*;
|
use crate::pdata::space_map::*;
|
||||||
use crate::pdata::unpack::*;
|
use crate::pdata::unpack::*;
|
||||||
|
use crate::report::*;
|
||||||
use crate::thin::superblock::*;
|
use crate::thin::superblock::*;
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
@ -44,7 +44,7 @@ impl Unpack for BlockTime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct BottomLevelVisitor {
|
struct BottomLevelVisitor {
|
||||||
data_sm: Arc<Mutex<dyn SpaceMap + Send>>,
|
data_sm: ASpaceMap,
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
@ -156,148 +156,23 @@ impl<'a> NodeVisitor<u32> for OverflowChecker<'a> {
|
|||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
struct ReportOptions {}
|
struct BitmapLeak {
|
||||||
|
blocknr: u64, // blocknr for the first entry in the bitmap
|
||||||
#[derive(Clone)]
|
loc: u64, // location of the bitmap
|
||||||
enum ReportOutcome {
|
|
||||||
Success,
|
|
||||||
NonFatal,
|
|
||||||
Fatal,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
use ReportOutcome::*;
|
// This checks the space map and returns any leak blocks for auto-repair to process.
|
||||||
|
|
||||||
impl ReportOutcome {
|
|
||||||
fn combine(lhs: &ReportOutcome, rhs: &ReportOutcome) -> ReportOutcome {
|
|
||||||
match (lhs, rhs) {
|
|
||||||
(Success, rhs) => rhs.clone(),
|
|
||||||
(lhs, Success) => lhs.clone(),
|
|
||||||
(Fatal, _) => Fatal,
|
|
||||||
(_, Fatal) => Fatal,
|
|
||||||
(_, _) => NonFatal,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum ReportCmd {
|
|
||||||
Log(String),
|
|
||||||
Complete,
|
|
||||||
Title(String),
|
|
||||||
}
|
|
||||||
|
|
||||||
struct Report {
|
|
||||||
opts: ReportOptions,
|
|
||||||
outcome: ReportOutcome,
|
|
||||||
tx: Sender<ReportCmd>,
|
|
||||||
tid: thread::JoinHandle<()>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Report {
|
|
||||||
fn new(
|
|
||||||
opts: ReportOptions,
|
|
||||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
|
||||||
total_allocated: u64,
|
|
||||||
) -> Result<Report> {
|
|
||||||
let (tx, rx) = channel();
|
|
||||||
let tid = thread::spawn(move || report_thread(sm, total_allocated, rx));
|
|
||||||
Ok(Report {
|
|
||||||
opts,
|
|
||||||
outcome: ReportOutcome::Success,
|
|
||||||
tx,
|
|
||||||
tid,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn info<I: Into<String>>(&mut self, txt: I) -> Result<()> {
|
|
||||||
self.tx.send(ReportCmd::Log(txt.into()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_outcome(&mut self, rhs: ReportOutcome) {
|
|
||||||
self.outcome = ReportOutcome::combine(&self.outcome, &rhs);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn non_fatal<I: Into<String>>(&mut self, txt: I) -> Result<()> {
|
|
||||||
self.add_outcome(NonFatal);
|
|
||||||
self.tx.send(ReportCmd::Log(txt.into()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fatal<I: Into<String>>(&mut self, txt: I) -> Result<()> {
|
|
||||||
self.add_outcome(Fatal);
|
|
||||||
self.tx.send(ReportCmd::Log(txt.into()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete(self) -> Result<()> {
|
|
||||||
self.tx.send(ReportCmd::Complete)?;
|
|
||||||
self.tid.join();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_title(&mut self, txt: &str) -> Result<()> {
|
|
||||||
self.tx.send(ReportCmd::Title(txt.to_string()))?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn report_thread(
|
|
||||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
|
||||||
total_allocated: u64,
|
|
||||||
rx: Receiver<ReportCmd>,
|
|
||||||
) {
|
|
||||||
let interval = time::Duration::from_millis(250);
|
|
||||||
let bar = ProgressBar::new(total_allocated);
|
|
||||||
loop {
|
|
||||||
loop {
|
|
||||||
match rx.try_recv() {
|
|
||||||
Ok(ReportCmd::Log(txt)) => {
|
|
||||||
bar.println(txt);
|
|
||||||
}
|
|
||||||
Ok(ReportCmd::Complete) => {
|
|
||||||
bar.finish();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Ok(ReportCmd::Title(txt)) => {
|
|
||||||
let mut fmt = "Checking thin metadata [{bar:40}] Remaining {eta}, ".to_string();
|
|
||||||
fmt.push_str(&txt);
|
|
||||||
bar.set_style(
|
|
||||||
ProgressStyle::default_bar()
|
|
||||||
.template(&fmt)
|
|
||||||
.progress_chars("=> "),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Err(TryRecvError::Disconnected) => {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Err(TryRecvError::Empty) => {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let sm = sm.lock().unwrap();
|
|
||||||
let nr_allocated = sm.get_nr_allocated().unwrap();
|
|
||||||
drop(sm);
|
|
||||||
|
|
||||||
bar.set_position(nr_allocated);
|
|
||||||
bar.tick();
|
|
||||||
|
|
||||||
thread::sleep(interval);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//------------------------------------------
|
|
||||||
|
|
||||||
fn check_space_map(
|
fn check_space_map(
|
||||||
|
ctx: &Context,
|
||||||
kind: &str,
|
kind: &str,
|
||||||
engine: Arc<dyn IoEngine + Send + Sync>,
|
|
||||||
bar: &mut Report,
|
|
||||||
entries: Vec<IndexEntry>,
|
entries: Vec<IndexEntry>,
|
||||||
metadata_sm: Option<Arc<Mutex<dyn SpaceMap + Send + Sync>>>,
|
metadata_sm: Option<ASpaceMap>,
|
||||||
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
sm: ASpaceMap,
|
||||||
root: SMRoot,
|
root: SMRoot,
|
||||||
) -> Result<()> {
|
) -> Result<Vec<BitmapLeak>> {
|
||||||
|
let report = ctx.report.clone();
|
||||||
|
let engine = ctx.engine.clone();
|
||||||
|
|
||||||
let sm = sm.lock().unwrap();
|
let sm = sm.lock().unwrap();
|
||||||
|
|
||||||
// overflow btree
|
// overflow btree
|
||||||
@ -321,19 +196,21 @@ fn check_space_map(
|
|||||||
engine.read_many(&mut blocks)?;
|
engine.read_many(&mut blocks)?;
|
||||||
|
|
||||||
let mut leaks = 0;
|
let mut leaks = 0;
|
||||||
let mut fail = false;
|
|
||||||
let mut blocknr = 0;
|
let mut blocknr = 0;
|
||||||
|
let mut bitmap_leaks = Vec::new();
|
||||||
for n in 0..entries.len() {
|
for n in 0..entries.len() {
|
||||||
let b = &blocks[n];
|
let b = &blocks[n];
|
||||||
if checksum::metadata_block_type(&b.get_data()) != checksum::BT::BITMAP {
|
if checksum::metadata_block_type(&b.get_data()) != checksum::BT::BITMAP {
|
||||||
return Err(anyhow!(
|
report.fatal(&format!(
|
||||||
"Index entry points to block ({}) that isn't a bitmap",
|
"Index entry points to block ({}) that isn't a bitmap",
|
||||||
b.loc
|
b.loc
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let bitmap = unpack::<Bitmap>(b.get_data())?;
|
let bitmap = unpack::<Bitmap>(b.get_data())?;
|
||||||
for e in bitmap.entries {
|
let first_blocknr = blocknr;
|
||||||
|
let mut contains_leak = false;
|
||||||
|
for e in bitmap.entries.iter() {
|
||||||
if blocknr >= root.nr_blocks {
|
if blocknr >= root.nr_blocks {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -341,43 +218,84 @@ fn check_space_map(
|
|||||||
match e {
|
match e {
|
||||||
BitmapEntry::Small(actual) => {
|
BitmapEntry::Small(actual) => {
|
||||||
let expected = sm.get(blocknr)?;
|
let expected = sm.get(blocknr)?;
|
||||||
if actual == 1 && expected == 0 {
|
if *actual == 1 && expected == 0 {
|
||||||
leaks += 1;
|
leaks += 1;
|
||||||
} else if actual != expected as u8 {
|
contains_leak = true;
|
||||||
bar.fatal(format!("Bad reference count for {} block {}. Expected {}, but space map contains {}.",
|
} else if *actual != expected as u8 {
|
||||||
kind, blocknr, expected, actual))?;
|
report.fatal(&format!("Bad reference count for {} block {}. Expected {}, but space map contains {}.",
|
||||||
fail = true;
|
kind, blocknr, expected, actual));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
BitmapEntry::Overflow => {
|
BitmapEntry::Overflow => {
|
||||||
let expected = sm.get(blocknr)?;
|
let expected = sm.get(blocknr)?;
|
||||||
if expected < 3 {
|
if expected < 3 {
|
||||||
bar.fatal(format!("Bad reference count for {} block {}. Expected {}, but space map says it's >= 3.",
|
report.fatal(&format!("Bad reference count for {} block {}. Expected {}, but space map says it's >= 3.",
|
||||||
kind, blocknr, expected))?;
|
kind, blocknr, expected));
|
||||||
fail = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
blocknr += 1;
|
blocknr += 1;
|
||||||
}
|
}
|
||||||
|
if contains_leak {
|
||||||
|
bitmap_leaks.push(BitmapLeak {
|
||||||
|
blocknr: first_blocknr,
|
||||||
|
loc: b.loc,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if leaks > 0 {
|
if leaks > 0 {
|
||||||
bar.non_fatal(format!(
|
report.non_fatal(&format!("{} {} blocks have leaked.", leaks, kind));
|
||||||
"{} {} blocks have leaked. Use --auto-repair to fix.",
|
|
||||||
leaks, kind
|
|
||||||
))?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if fail {
|
Ok(bitmap_leaks)
|
||||||
return Err(anyhow!("Inconsistent data space map"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This assumes the only errors in the space map are leaks. Entries should just be
|
||||||
|
// those that contain leaks.
|
||||||
|
fn repair_space_map(ctx: &Context, entries: Vec<BitmapLeak>, sm: ASpaceMap) -> Result<()> {
|
||||||
|
let engine = ctx.engine.clone();
|
||||||
|
|
||||||
|
let sm = sm.lock().unwrap();
|
||||||
|
|
||||||
|
let mut blocks = Vec::new();
|
||||||
|
for i in &entries {
|
||||||
|
blocks.push(Block::new(i.loc));
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: we should do this in batches
|
||||||
|
engine.read_many(&mut blocks)?;
|
||||||
|
|
||||||
|
for (be, b) in entries.iter().zip(blocks.iter()) {
|
||||||
|
let mut blocknr = be.blocknr;
|
||||||
|
let mut bitmap = unpack::<Bitmap>(b.get_data())?;
|
||||||
|
for e in bitmap.entries.iter_mut() {
|
||||||
|
if blocknr >= sm.get_nr_blocks()? {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let BitmapEntry::Small(actual) = e {
|
||||||
|
let expected = sm.get(blocknr)?;
|
||||||
|
if *actual == 1 && expected == 0 {
|
||||||
|
*e = BitmapEntry::Small(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blocknr += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = Cursor::new(b.get_data());
|
||||||
|
bitmap.pack(&mut out)?;
|
||||||
|
checksum::write_checksum(b.get_data(), checksum::BT::BITMAP)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
engine.write_many(&blocks)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
fn inc_entries(sm: &Arc<Mutex<dyn SpaceMap + Sync + Send>>, entries: &[IndexEntry]) -> Result<()> {
|
fn inc_entries(sm: &ASpaceMap, entries: &[IndexEntry]) -> Result<()> {
|
||||||
let mut sm = sm.lock().unwrap();
|
let mut sm = sm.lock().unwrap();
|
||||||
for ie in entries {
|
for ie in entries {
|
||||||
sm.inc(ie.blocknr, 1)?;
|
sm.inc(ie.blocknr, 1)?;
|
||||||
@ -385,6 +303,12 @@ fn inc_entries(sm: &Arc<Mutex<dyn SpaceMap + Sync + Send>>, entries: &[IndexEntr
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn inc_superblock(sm: &ASpaceMap) -> Result<()> {
|
||||||
|
let mut sm = sm.lock().unwrap();
|
||||||
|
sm.inc(SUPERBLOCK_LOCATION, 1)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
//------------------------------------------
|
//------------------------------------------
|
||||||
|
|
||||||
const MAX_CONCURRENT_IO: u32 = 1024;
|
const MAX_CONCURRENT_IO: u32 = 1024;
|
||||||
@ -392,70 +316,67 @@ const MAX_CONCURRENT_IO: u32 = 1024;
|
|||||||
pub struct ThinCheckOptions<'a> {
|
pub struct ThinCheckOptions<'a> {
|
||||||
pub dev: &'a Path,
|
pub dev: &'a Path,
|
||||||
pub async_io: bool,
|
pub async_io: bool,
|
||||||
|
pub ignore_non_fatal: bool,
|
||||||
|
pub auto_repair: bool,
|
||||||
|
pub report: Arc<Report>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check(opts: &ThinCheckOptions) -> Result<()> {
|
fn spawn_progress_thread(
|
||||||
let engine: Arc<dyn IoEngine + Send + Sync>;
|
sm: Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
|
nr_allocated_metadata: u64,
|
||||||
|
report: Arc<Report>,
|
||||||
|
) -> Result<(JoinHandle<()>, Arc<Mutex<bool>>)> {
|
||||||
|
let tid;
|
||||||
|
let stop_progress = Arc::new(Mutex::new(false));
|
||||||
|
|
||||||
let nr_threads;
|
|
||||||
if opts.async_io {
|
|
||||||
nr_threads = std::cmp::min(4, num_cpus::get());
|
|
||||||
engine = Arc::new(AsyncIoEngine::new(opts.dev, MAX_CONCURRENT_IO)?);
|
|
||||||
} else {
|
|
||||||
nr_threads = num_cpus::get() * 2;
|
|
||||||
engine = Arc::new(SyncIoEngine::new(opts.dev, nr_threads)?);
|
|
||||||
}
|
|
||||||
|
|
||||||
// superblock
|
|
||||||
let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?;
|
|
||||||
|
|
||||||
let nr_allocated_metadata;
|
|
||||||
{
|
{
|
||||||
let root = unpack::<SMRoot>(&sb.metadata_sm_root[0..])?;
|
let stop_progress = stop_progress.clone();
|
||||||
nr_allocated_metadata = root.nr_allocated;
|
tid = thread::spawn(move || {
|
||||||
}
|
let interval = std::time::Duration::from_millis(250);
|
||||||
|
loop {
|
||||||
// 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>(engine.clone(), false, sb.details_root)?;
|
|
||||||
let nr_devs = devs.len();
|
|
||||||
let metadata_sm = core_sm(engine.get_nr_blocks(), nr_devs as u32);
|
|
||||||
let opts = ReportOptions {};
|
|
||||||
let mut report = Report::new(opts, metadata_sm.clone(), nr_allocated_metadata)?;
|
|
||||||
|
|
||||||
report.set_title("device details tree")?;
|
|
||||||
let _devs = btree_to_map_with_sm::<DeviceDetail>(
|
|
||||||
engine.clone(),
|
|
||||||
metadata_sm.clone(),
|
|
||||||
false,
|
|
||||||
sb.details_root,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// increment superblock
|
|
||||||
{
|
{
|
||||||
let mut sm = metadata_sm.lock().unwrap();
|
let stop_progress = stop_progress.lock().unwrap();
|
||||||
sm.inc(SUPERBLOCK_LOCATION, 1)?;
|
if *stop_progress {
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// mapping top level
|
let sm = sm.lock().unwrap();
|
||||||
let roots = btree_to_map_with_sm::<u64>(engine.clone(), metadata_sm.clone(), false, sb.mapping_root)?;
|
let mut n = sm.get_nr_allocated().unwrap();
|
||||||
|
drop(sm);
|
||||||
|
|
||||||
|
n *= 100;
|
||||||
|
n /= nr_allocated_metadata;
|
||||||
|
|
||||||
|
let _r = report.progress(n as u8);
|
||||||
|
thread::sleep(interval);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok((tid, stop_progress))
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Context {
|
||||||
|
report: Arc<Report>,
|
||||||
|
engine: Arc<dyn IoEngine + Send + Sync>,
|
||||||
|
pool: ThreadPool,
|
||||||
|
}
|
||||||
|
|
||||||
// Check the mappings filling in the data_sm as we go.
|
// Check the mappings filling in the data_sm as we go.
|
||||||
report.set_title("mapping tree")?;
|
fn check_mapping_bottom_level(
|
||||||
let data_sm;
|
ctx: &Context,
|
||||||
{
|
metadata_sm: &Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
// FIXME: with a thread pool we need to return errors another way.
|
data_sm: &Arc<Mutex<dyn SpaceMap + Send + Sync>>,
|
||||||
let nr_workers = nr_threads;
|
roots: &BTreeMap<u64, u64>,
|
||||||
let pool = ThreadPool::new(nr_workers);
|
) -> Result<()> {
|
||||||
|
ctx.report.set_sub_title("mapping tree");
|
||||||
let root = unpack::<SMRoot>(&sb.data_sm_root[0..])?;
|
|
||||||
data_sm = core_sm(root.nr_blocks, nr_devs as u32);
|
|
||||||
|
|
||||||
for (_thin_id, root) in roots {
|
for (_thin_id, root) in roots {
|
||||||
let mut w = BTreeWalker::new_with_sm(engine.clone(), metadata_sm.clone(), false)?;
|
let mut w = BTreeWalker::new_with_sm(ctx.engine.clone(), metadata_sm.clone(), false)?;
|
||||||
let data_sm = data_sm.clone();
|
let data_sm = data_sm.clone();
|
||||||
pool.execute(move || {
|
let root = *root;
|
||||||
|
ctx.pool.execute(move || {
|
||||||
let mut v = BottomLevelVisitor { data_sm };
|
let mut v = BottomLevelVisitor { data_sm };
|
||||||
|
|
||||||
// FIXME: return error
|
// FIXME: return error
|
||||||
@ -464,17 +385,94 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> {
|
|||||||
eprintln!("walk failed {:?}", e);
|
eprintln!("walk failed {:?}", e);
|
||||||
std::process::abort();
|
std::process::abort();
|
||||||
}
|
}
|
||||||
Ok(_result) => {
|
Ok(_result) => {}
|
||||||
//eprintln!("checked thin_dev {} -> {:?}", thin_id, result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
ctx.pool.join();
|
||||||
|
|
||||||
pool.join();
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
report.set_title("data space map")?;
|
fn mk_context(opts: &ThinCheckOptions) -> Result<Context> {
|
||||||
|
let engine: Arc<dyn IoEngine + Send + Sync>;
|
||||||
|
let nr_threads;
|
||||||
|
|
||||||
|
if opts.async_io {
|
||||||
|
nr_threads = std::cmp::min(4, num_cpus::get());
|
||||||
|
engine = Arc::new(AsyncIoEngine::new(opts.dev, MAX_CONCURRENT_IO, opts.auto_repair)?);
|
||||||
|
} else {
|
||||||
|
nr_threads = num_cpus::get() * 2;
|
||||||
|
engine = Arc::new(SyncIoEngine::new(opts.dev, nr_threads, opts.auto_repair)?);
|
||||||
|
}
|
||||||
|
let pool = ThreadPool::new(nr_threads);
|
||||||
|
|
||||||
|
Ok(Context {
|
||||||
|
report: opts.report.clone(),
|
||||||
|
engine,
|
||||||
|
pool,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bail_out(ctx: &Context, task: &str) -> Result<()> {
|
||||||
|
use ReportOutcome::*;
|
||||||
|
|
||||||
|
match ctx.report.get_outcome() {
|
||||||
|
Fatal => Err(anyhow!(format!(
|
||||||
|
"Check of {} failed, ending check early.",
|
||||||
|
task
|
||||||
|
))),
|
||||||
|
_ => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn check(opts: ThinCheckOptions) -> Result<()> {
|
||||||
|
let ctx = mk_context(&opts)?;
|
||||||
|
|
||||||
|
// FIXME: temporarily get these out
|
||||||
|
let report = &ctx.report;
|
||||||
|
let engine = &ctx.engine;
|
||||||
|
|
||||||
|
report.set_title("Checking thin metadata");
|
||||||
|
|
||||||
|
// superblock
|
||||||
|
let sb = read_superblock(engine.as_ref(), SUPERBLOCK_LOCATION)?;
|
||||||
|
let metadata_root = unpack::<SMRoot>(&sb.metadata_sm_root[0..])?;
|
||||||
|
|
||||||
|
// 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>(engine.clone(), false, 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)?;
|
||||||
|
|
||||||
|
report.set_sub_title("device details tree");
|
||||||
|
let _devs = btree_to_map_with_sm::<DeviceDetail>(
|
||||||
|
engine.clone(),
|
||||||
|
metadata_sm.clone(),
|
||||||
|
false,
|
||||||
|
sb.details_root,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let (tid, stop_progress) = spawn_progress_thread(
|
||||||
|
metadata_sm.clone(),
|
||||||
|
metadata_root.nr_allocated,
|
||||||
|
report.clone(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// mapping top level
|
||||||
|
let roots =
|
||||||
|
btree_to_map_with_sm::<u64>(engine.clone(), metadata_sm.clone(), false, sb.mapping_root)?;
|
||||||
|
|
||||||
|
// mapping bottom level
|
||||||
|
report.set_sub_title("mapping tree");
|
||||||
|
let root = unpack::<SMRoot>(&sb.data_sm_root[0..])?;
|
||||||
|
let data_sm = core_sm(root.nr_blocks, nr_devs as u32);
|
||||||
|
check_mapping_bottom_level(&ctx, &metadata_sm, &data_sm, &roots)?;
|
||||||
|
bail_out(&ctx, "mapping tree")?;
|
||||||
|
|
||||||
|
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..])?;
|
||||||
|
|
||||||
let entries = btree_to_map_with_sm::<IndexEntry>(
|
let entries = btree_to_map_with_sm::<IndexEntry>(
|
||||||
@ -486,17 +484,17 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> {
|
|||||||
let entries: Vec<IndexEntry> = entries.values().cloned().collect();
|
let entries: Vec<IndexEntry> = entries.values().cloned().collect();
|
||||||
inc_entries(&metadata_sm, &entries[0..])?;
|
inc_entries(&metadata_sm, &entries[0..])?;
|
||||||
|
|
||||||
check_space_map(
|
let data_leaks = check_space_map(
|
||||||
|
&ctx,
|
||||||
"data",
|
"data",
|
||||||
engine.clone(),
|
|
||||||
&mut report,
|
|
||||||
entries,
|
entries,
|
||||||
Some(metadata_sm.clone()),
|
Some(metadata_sm.clone()),
|
||||||
data_sm.clone(),
|
data_sm.clone(),
|
||||||
root,
|
root,
|
||||||
)?;
|
)?;
|
||||||
|
bail_out(&ctx, "data space map")?;
|
||||||
|
|
||||||
report.set_title("metadata space map")?;
|
report.set_sub_title("metadata space map");
|
||||||
let root = unpack::<SMRoot>(&sb.metadata_sm_root[0..])?;
|
let root = unpack::<SMRoot>(&sb.metadata_sm_root[0..])?;
|
||||||
let mut b = Block::new(root.bitmap_root);
|
let mut b = Block::new(root.bitmap_root);
|
||||||
engine.read(&mut b)?;
|
engine.read(&mut b)?;
|
||||||
@ -521,17 +519,29 @@ pub fn check(opts: &ThinCheckOptions) -> Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Now the counts should be correct and we can check it.
|
// Now the counts should be correct and we can check it.
|
||||||
check_space_map(
|
let metadata_leaks = check_space_map(&ctx, "metadata", entries, None, metadata_sm.clone(), root)?;
|
||||||
"metadata",
|
|
||||||
engine.clone(),
|
if opts.auto_repair {
|
||||||
&mut report,
|
if data_leaks.len() > 0 {
|
||||||
entries,
|
ctx.report.info("Repairing data leaks.");
|
||||||
None,
|
repair_space_map(&ctx, data_leaks, data_sm.clone());
|
||||||
metadata_sm.clone(),
|
}
|
||||||
root,
|
|
||||||
)?;
|
if metadata_leaks.len() > 0 {
|
||||||
|
ctx.report.info("Repairing metadata leaks.");
|
||||||
|
repair_space_map(&ctx, metadata_leaks, metadata_sm.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Completing consumes the report.
|
||||||
|
{
|
||||||
|
let mut stop_progress = stop_progress.lock().unwrap();
|
||||||
|
*stop_progress = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
tid.join();
|
||||||
|
bail_out(&ctx, "metadata space map")?;
|
||||||
|
|
||||||
report.complete()?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -295,7 +295,7 @@ pub fn generate_metadata_leaks(md: &PathBuf, nr_blocks: u64, expected: u32, actu
|
|||||||
pub fn get_needs_check(md: &PathBuf) -> Result<bool> {
|
pub fn get_needs_check(md: &PathBuf) -> Result<bool> {
|
||||||
use thinp::thin::superblock::*;
|
use thinp::thin::superblock::*;
|
||||||
|
|
||||||
let engine = SyncIoEngine::new(&md, 1)?;
|
let engine = SyncIoEngine::new(&md, 1, false)?;
|
||||||
let sb = read_superblock(&engine, SUPERBLOCK_LOCATION)?;
|
let sb = read_superblock(&engine, SUPERBLOCK_LOCATION)?;
|
||||||
Ok(sb.flags.needs_check)
|
Ok(sb.flags.needs_check)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user