use crate::{
chunk::{
self, ChunkTreeCache, ParityKind, ParityPlan, ParityRow,
StripePlacement, WritePlan,
},
raid56, raw,
superblock::{self, Superblock},
tree::{KeyType, TreeBlock},
};
use bytes::Buf;
use std::{
collections::BTreeMap,
io::{self, Read, Seek, SeekFrom, Write},
mem,
sync::Arc,
};
pub trait TreeBlockCache: Send + Sync {
fn get(&self, addr: u64) -> Option<Arc<TreeBlock>>;
fn put(&self, addr: u64, block: Arc<TreeBlock>);
fn invalidate(&self, addr: u64);
}
pub struct BlockReader<R> {
devices: BTreeMap<u64, R>,
nodesize: u32,
chunk_cache: ChunkTreeCache,
cache: Option<Arc<dyn TreeBlockCache>>,
}
impl<R> BlockReader<R> {
pub fn new(
handle: R,
devid: u64,
nodesize: u32,
chunk_cache: ChunkTreeCache,
) -> Self {
let mut devices = BTreeMap::new();
devices.insert(devid, handle);
Self {
devices,
nodesize,
chunk_cache,
cache: None,
}
}
pub fn set_cache(&mut self, cache: Option<Arc<dyn TreeBlockCache>>) {
self.cache = cache;
}
#[must_use]
pub fn with_cache(mut self, cache: Arc<dyn TreeBlockCache>) -> Self {
self.cache = Some(cache);
self
}
#[must_use]
pub fn new_multi(
devices: BTreeMap<u64, R>,
nodesize: u32,
chunk_cache: ChunkTreeCache,
) -> Self {
Self {
devices,
nodesize,
chunk_cache,
cache: None,
}
}
#[must_use]
pub fn devices(&self) -> &BTreeMap<u64, R> {
&self.devices
}
pub fn devices_mut(&mut self) -> &mut BTreeMap<u64, R> {
&mut self.devices
}
pub fn single_device_mut(&mut self) -> &mut R {
assert_eq!(
self.devices.len(),
1,
"single_device_mut: filesystem has {} devices, not 1",
self.devices.len(),
);
self.devices.values_mut().next().unwrap()
}
}
impl<R: Read + Seek> BlockReader<R> {
pub fn read_block(&mut self, logical: u64) -> io::Result<Vec<u8>> {
self.read_data(logical, self.nodesize as usize)
}
pub fn read_tree_block(
&mut self,
logical: u64,
) -> io::Result<Arc<TreeBlock>> {
if let Some(cache) = self.cache.as_ref() {
if let Some(hit) = cache.get(logical) {
return Ok(hit);
}
}
let buf = self.read_block(logical)?;
let block = Arc::new(TreeBlock::parse(&buf));
if let Some(cache) = self.cache.as_ref() {
cache.put(logical, Arc::clone(&block));
}
Ok(block)
}
#[must_use]
pub fn chunk_cache(&self) -> &ChunkTreeCache {
&self.chunk_cache
}
pub fn chunk_cache_mut(&mut self) -> &mut ChunkTreeCache {
&mut self.chunk_cache
}
#[must_use]
pub fn nodesize(&self) -> u32 {
self.nodesize
}
pub fn read_data(
&mut self,
logical: u64,
len: usize,
) -> io::Result<Vec<u8>> {
let placements =
self.chunk_cache.plan_read(logical, len).ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!(
"logical address {logical} not mapped or unsupported profile"
),
)
})?;
let mut buf = vec![0u8; len];
for p in placements {
let dev = self.device_handle_mut(p.devid)?;
dev.seek(SeekFrom::Start(p.physical))?;
dev.read_exact(&mut buf[p.buf_offset..p.buf_offset + p.len])?;
}
Ok(buf)
}
fn device_handle_mut(&mut self, devid: u64) -> io::Result<&mut R> {
self.devices.get_mut(&devid).ok_or_else(|| {
io::Error::new(
io::ErrorKind::NotFound,
format!(
"device {devid} not open (referenced by the chunk cache)"
),
)
})
}
}
impl<R: Read + Write + Seek> BlockReader<R> {
pub fn write_block(&mut self, logical: u64, buf: &[u8]) -> io::Result<()> {
let plan = self.chunk_cache.plan_write(logical, buf.len()).ok_or_else(
|| {
io::Error::new(
io::ErrorKind::NotFound,
format!(
"logical address {logical} not mapped or unsupported profile"
),
)
},
)?;
match plan {
WritePlan::Plain(placements) => {
self.execute_plain_plan(buf, &placements)
}
WritePlan::Parity(plan) => self.execute_parity_plan(buf, &plan),
}
}
fn execute_plain_plan(
&mut self,
buf: &[u8],
placements: &[StripePlacement],
) -> io::Result<()> {
for p in placements {
let dev = self.device_handle_mut(p.devid)?;
dev.seek(SeekFrom::Start(p.physical))?;
dev.write_all(&buf[p.buf_offset..p.buf_offset + p.len])?;
}
Ok(())
}
fn execute_parity_plan(
&mut self,
buf: &[u8],
plan: &ParityPlan,
) -> io::Result<()> {
let stripe_len = plan.stripe_len as usize;
for row in &plan.rows {
self.execute_parity_row(buf, stripe_len, row)?;
}
Ok(())
}
fn execute_parity_row(
&mut self,
buf: &[u8],
stripe_len: usize,
row: &ParityRow,
) -> io::Result<()> {
let mut scratch: Vec<Vec<u8>> =
Vec::with_capacity(row.data_columns.len());
for col in &row.data_columns {
let mut slot = vec![0u8; stripe_len];
let dev = self.device_handle_mut(col.devid)?;
dev.seek(SeekFrom::Start(col.physical))?;
dev.read_exact(&mut slot)?;
scratch.push(slot);
}
for (col, slot) in row.data_columns.iter().zip(scratch.iter_mut()) {
if let Some(ov) = &col.overlay {
let dst_start = ov.slot_offset as usize;
let dst_end = dst_start + ov.len as usize;
let src_start = ov.buf_offset;
let src_end = src_start + ov.len as usize;
slot[dst_start..dst_end]
.copy_from_slice(&buf[src_start..src_end]);
}
}
let stripe_refs: Vec<&[u8]> =
scratch.iter().map(Vec::as_slice).collect();
let want_q = row.parity_targets.iter().any(|t| t.kind == ParityKind::Q);
let (p_buf, q_buf) = if want_q {
let (p, q) = raid56::compute_p_q(&stripe_refs);
(p, Some(q))
} else {
(raid56::compute_p(&stripe_refs), None)
};
for col in &row.data_columns {
let Some(ov) = &col.overlay else { continue };
let dev = self.device_handle_mut(col.devid)?;
dev.seek(SeekFrom::Start(
col.physical + u64::from(ov.slot_offset),
))?;
let src_start = ov.buf_offset;
let src_end = src_start + ov.len as usize;
dev.write_all(&buf[src_start..src_end])?;
}
for target in &row.parity_targets {
let bytes = match target.kind {
ParityKind::P => &p_buf,
ParityKind::Q => {
q_buf.as_ref().expect("Q target without Q computation")
}
};
let dev = self.device_handle_mut(target.devid)?;
dev.seek(SeekFrom::Start(target.physical))?;
dev.write_all(bytes)?;
}
Ok(())
}
}
pub struct OpenFilesystem<R> {
pub reader: BlockReader<R>,
pub superblock: Superblock,
pub tree_roots: BTreeMap<u64, (u64, u64)>,
pub per_device_dev_items: BTreeMap<u64, crate::items::DeviceItem>,
}
pub fn filesystem_open<R: Read + Seek>(
reader: R,
) -> io::Result<OpenFilesystem<R>> {
filesystem_open_mirror(reader, 0)
}
pub fn filesystem_open_mirror<R: Read + Seek>(
reader: R,
mirror: u32,
) -> io::Result<OpenFilesystem<R>> {
let mut reader = reader;
let sb = superblock::read_superblock(&mut reader, mirror)?;
let primary_devid = sb.dev_item.devid;
let mut per_device_dev_items = BTreeMap::new();
per_device_dev_items.insert(primary_devid, sb.dev_item.clone());
let chunk_cache = chunk::seed_from_sys_chunk_array(
&sb.sys_chunk_array,
sb.sys_chunk_array_size,
);
let mut block_reader =
BlockReader::new(reader, primary_devid, sb.nodesize, chunk_cache);
read_chunk_tree(&mut block_reader, sb.chunk_root)?;
let tree_roots = read_root_tree(&mut block_reader, sb.root)?;
Ok(OpenFilesystem {
reader: block_reader,
superblock: sb,
tree_roots,
per_device_dev_items,
})
}
pub fn filesystem_open_multi<R: Read + Seek>(
devices: BTreeMap<u64, R>,
) -> io::Result<OpenFilesystem<R>> {
if devices.is_empty() {
return Err(io::Error::other(
"filesystem_open_multi: device map is empty",
));
}
let mut devices = devices;
let mut per_device_dev_items: BTreeMap<u64, crate::items::DeviceItem> =
BTreeMap::new();
let mut superblocks: BTreeMap<u64, Superblock> = BTreeMap::new();
for (&devid, dev) in &mut devices {
let sb = superblock::read_superblock(dev, 0)?;
if sb.dev_item.devid != devid {
return Err(io::Error::other(format!(
"device map key {devid} doesn't match superblock dev_item.devid {}",
sb.dev_item.devid,
)));
}
per_device_dev_items.insert(devid, sb.dev_item.clone());
superblocks.insert(devid, sb);
}
let primary_sb = superblocks.values().next().unwrap().clone();
for (devid, sb) in &superblocks {
if sb.fsid != primary_sb.fsid {
return Err(io::Error::other(format!(
"device {devid} fsid {} differs from primary fsid {}",
sb.fsid, primary_sb.fsid,
)));
}
}
let chunk_cache = chunk::seed_from_sys_chunk_array(
&primary_sb.sys_chunk_array,
primary_sb.sys_chunk_array_size,
);
let mut block_reader =
BlockReader::new_multi(devices, primary_sb.nodesize, chunk_cache);
read_chunk_tree(&mut block_reader, primary_sb.chunk_root)?;
let mut referenced: std::collections::BTreeSet<u64> =
std::collections::BTreeSet::new();
for mapping in block_reader.chunk_cache().iter() {
for stripe in &mapping.stripes {
referenced.insert(stripe.devid);
}
}
for devid in &referenced {
if !block_reader.devices().contains_key(devid) {
return Err(io::Error::other(format!(
"chunk tree references devid {devid} but no handle was provided"
)));
}
}
let tree_roots = read_root_tree(&mut block_reader, primary_sb.root)?;
Ok(OpenFilesystem {
reader: block_reader,
superblock: primary_sb,
tree_roots,
per_device_dev_items,
})
}
pub fn filesystem_open_with_cache<R: Read + Seek>(
reader: R,
mirror: u32,
chunk_cache: ChunkTreeCache,
) -> io::Result<OpenFilesystem<R>> {
let mut reader = reader;
let sb = superblock::read_superblock(&mut reader, mirror)?;
let primary_devid = sb.dev_item.devid;
let mut per_device_dev_items = BTreeMap::new();
per_device_dev_items.insert(primary_devid, sb.dev_item.clone());
let mut block_reader =
BlockReader::new(reader, primary_devid, sb.nodesize, chunk_cache);
let tree_roots = read_root_tree(&mut block_reader, sb.root)?;
Ok(OpenFilesystem {
reader: block_reader,
superblock: sb,
tree_roots,
per_device_dev_items,
})
}
pub fn read_chunk_tree<R: Read + Seek>(
reader: &mut BlockReader<R>,
root_logical: u64,
) -> io::Result<()> {
let block = reader.read_tree_block(root_logical)?;
match &*block {
TreeBlock::Leaf { items, data, .. } => {
for item in items {
if item.key.key_type != KeyType::ChunkItem {
continue;
}
let item_data = &data[mem::size_of::<raw::btrfs_header>()
+ item.offset as usize..];
if let Some((mapping, _)) =
chunk::parse_chunk_item(item_data, item.key.offset)
{
if reader.chunk_cache.lookup(mapping.logical).is_none() {
reader.chunk_cache.insert(mapping);
}
}
}
}
TreeBlock::Node { ptrs, .. } => {
for ptr in ptrs {
read_chunk_tree(reader, ptr.blockptr)?;
}
}
}
Ok(())
}
pub fn read_root_tree<R: Read + Seek>(
reader: &mut BlockReader<R>,
root_logical: u64,
) -> io::Result<BTreeMap<u64, (u64, u64)>> {
let mut tree_roots = BTreeMap::new();
collect_root_items(reader, root_logical, &mut tree_roots)?;
Ok(tree_roots)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Traversal {
Bfs,
Dfs,
}
pub fn tree_walk<R: Read + Seek>(
reader: &mut BlockReader<R>,
root_logical: u64,
traversal: Traversal,
visitor: &mut dyn FnMut(&TreeBlock),
) -> io::Result<()> {
match traversal {
Traversal::Bfs => tree_walk_bfs(reader, root_logical, visitor),
Traversal::Dfs => tree_walk_dfs(reader, root_logical, visitor),
}
}
fn tree_walk_dfs<R: Read + Seek>(
reader: &mut BlockReader<R>,
logical: u64,
visitor: &mut dyn FnMut(&TreeBlock),
) -> io::Result<()> {
let block = reader.read_tree_block(logical)?;
visitor(&block);
if let TreeBlock::Node { ptrs, .. } = &*block {
for ptr in ptrs {
tree_walk_dfs(reader, ptr.blockptr, visitor)?;
}
}
Ok(())
}
fn tree_walk_bfs<R: Read + Seek>(
reader: &mut BlockReader<R>,
root_logical: u64,
visitor: &mut dyn FnMut(&TreeBlock),
) -> io::Result<()> {
let root_block = reader.read_tree_block(root_logical)?;
let root_level = root_block.header().level;
visitor(&root_block);
let mut current_level_ptrs: Vec<u64> = match &*root_block {
TreeBlock::Node { ptrs, .. } => {
ptrs.iter().map(|p| p.blockptr).collect()
}
TreeBlock::Leaf { .. } => return Ok(()),
};
for _level in (0..root_level).rev() {
let mut next_level_ptrs = Vec::new();
for logical in ¤t_level_ptrs {
let block = reader.read_tree_block(*logical)?;
visitor(&block);
if let TreeBlock::Node { ptrs, .. } = &*block {
next_level_ptrs.extend(ptrs.iter().map(|p| p.blockptr));
}
}
current_level_ptrs = next_level_ptrs;
}
Ok(())
}
pub fn tree_walk_tolerant<R: Read + Seek>(
reader: &mut BlockReader<R>,
root_logical: u64,
visitor: &mut dyn FnMut(&[u8], &TreeBlock),
on_error: &mut dyn FnMut(u64, &io::Error),
) -> io::Result<()> {
let buf = reader.read_block(root_logical)?;
let block = TreeBlock::parse(&buf);
visitor(&buf, &block);
if let TreeBlock::Node { ptrs, .. } = &block {
for ptr in ptrs {
tree_walk_tolerant_dfs(reader, ptr.blockptr, visitor, on_error);
}
}
Ok(())
}
fn tree_walk_tolerant_dfs<R: Read + Seek>(
reader: &mut BlockReader<R>,
logical: u64,
visitor: &mut dyn FnMut(&[u8], &TreeBlock),
on_error: &mut dyn FnMut(u64, &io::Error),
) {
let buf = match reader.read_block(logical) {
Ok(b) => b,
Err(e) => {
on_error(logical, &e);
return;
}
};
let block = TreeBlock::parse(&buf);
visitor(&buf, &block);
if let TreeBlock::Node { ptrs, .. } = &block {
for ptr in ptrs {
tree_walk_tolerant_dfs(reader, ptr.blockptr, visitor, on_error);
}
}
}
pub fn tree_walk_mut<R: Read + Write + Seek>(
reader: &mut BlockReader<R>,
root_logical: u64,
csum_type: superblock::ChecksumType,
visitor: &mut dyn FnMut(&mut Vec<u8>, &TreeBlock) -> bool,
) -> io::Result<()> {
let mut buf = reader.read_block(root_logical)?;
let block = TreeBlock::parse(&buf);
let child_ptrs: Vec<u64> = if let TreeBlock::Node { ptrs, .. } = &block {
ptrs.iter().map(|p| p.blockptr).collect()
} else {
Vec::new()
};
if visitor(&mut buf, &block) {
crate::util::csum_tree_block(&mut buf, csum_type);
reader.write_block(root_logical, &buf)?;
}
for ptr in child_ptrs {
tree_walk_mut(reader, ptr, csum_type, visitor)?;
}
Ok(())
}
pub fn block_visit<R: Read + Seek>(
reader: &mut BlockReader<R>,
logical: u64,
follow: bool,
traversal: Traversal,
visitor: &mut dyn FnMut(&TreeBlock),
) -> io::Result<()> {
if follow {
tree_walk(reader, logical, traversal, visitor)
} else {
let block = reader.read_tree_block(logical)?;
visitor(&block);
Ok(())
}
}
#[derive(Debug, Clone)]
pub struct TreeStats {
pub total_nodes: u64,
pub total_bytes: u64,
pub total_inline: u64,
pub total_seeks: u64,
pub forward_seeks: u64,
pub backward_seeks: u64,
pub total_seek_len: u64,
pub max_seek_len: u64,
pub total_clusters: u64,
pub total_cluster_size: u64,
pub min_cluster_size: u64,
pub max_cluster_size: u64,
pub lowest_bytenr: u64,
pub highest_bytenr: u64,
pub node_counts: Vec<u64>,
pub levels: u8,
}
pub fn tree_stats_collect<R: Read + Seek>(
reader: &mut BlockReader<R>,
root_logical: u64,
find_inline: bool,
) -> io::Result<TreeStats> {
let root_block = reader.read_tree_block(root_logical)?;
let nodesize = u64::from(reader.nodesize());
let root_level = root_block.header().level;
let root_bytenr = root_block.header().bytenr;
let mut stats = TreeStats {
total_nodes: 0,
total_bytes: 0,
total_inline: 0,
total_seeks: 0,
forward_seeks: 0,
backward_seeks: 0,
total_seek_len: 0,
max_seek_len: 0,
total_clusters: 0,
total_cluster_size: 0,
min_cluster_size: u64::MAX,
max_cluster_size: nodesize,
lowest_bytenr: root_bytenr,
highest_bytenr: root_bytenr,
node_counts: vec![0u64; root_level as usize + 1],
levels: root_level + 1,
};
walk_stats(reader, &root_block, &mut stats, find_inline, nodesize)?;
Ok(stats)
}
fn walk_stats<R: Read + Seek>(
reader: &mut BlockReader<R>,
block: &TreeBlock,
stats: &mut TreeStats,
find_inline: bool,
nodesize: u64,
) -> io::Result<()> {
let level = block.header().level;
let bytenr = block.header().bytenr;
stats.total_nodes += 1;
stats.total_bytes += nodesize;
if (level as usize) < stats.node_counts.len() {
stats.node_counts[level as usize] += 1;
}
if bytenr < stats.lowest_bytenr {
stats.lowest_bytenr = bytenr;
}
if bytenr > stats.highest_bytenr {
stats.highest_bytenr = bytenr;
}
match block {
TreeBlock::Leaf { items, data, .. } => {
if find_inline {
let type_off =
mem::offset_of!(raw::btrfs_file_extent_item, type_);
let inline_hdr_size =
mem::offset_of!(raw::btrfs_file_extent_item, disk_bytenr);
let header_size = mem::size_of::<raw::btrfs_header>();
for item in items {
if item.key.key_type != KeyType::ExtentData {
continue;
}
let start = header_size + item.offset as usize;
if start + type_off >= data.len() {
continue;
}
if data[start + type_off] == 0
&& item.size as usize > inline_hdr_size
{
stats.total_inline +=
u64::from(item.size) - inline_hdr_size as u64;
}
}
}
}
TreeBlock::Node { ptrs, .. } => {
let mut last_block = bytenr;
let mut cluster_size = nodesize;
for ptr in ptrs {
let child = reader.read_tree_block(ptr.blockptr)?;
walk_stats(reader, &child, stats, find_inline, nodesize)?;
let cur = ptr.blockptr;
if last_block + nodesize == cur {
cluster_size += nodesize;
} else {
let distance = cur.abs_diff(last_block + nodesize);
stats.total_seeks += 1;
stats.total_seek_len += distance;
if distance > stats.max_seek_len {
stats.max_seek_len = distance;
}
if cur > last_block + nodesize {
stats.forward_seeks += 1;
} else {
stats.backward_seeks += 1;
}
if cluster_size != nodesize {
stats.total_clusters += 1;
stats.total_cluster_size += cluster_size;
if cluster_size < stats.min_cluster_size {
stats.min_cluster_size = cluster_size;
}
if cluster_size > stats.max_cluster_size {
stats.max_cluster_size = cluster_size;
}
}
cluster_size = nodesize;
}
last_block = cur;
}
}
}
Ok(())
}
fn collect_root_items<R: Read + Seek>(
reader: &mut BlockReader<R>,
logical: u64,
tree_roots: &mut BTreeMap<u64, (u64, u64)>,
) -> io::Result<()> {
let block = reader.read_tree_block(logical)?;
match &*block {
TreeBlock::Leaf { items, data, .. } => {
let header_size = mem::size_of::<raw::btrfs_header>();
let root_item_bytenr_offset = {
mem::offset_of!(raw::btrfs_root_item, bytenr)
};
for item in items {
if item.key.key_type != KeyType::RootItem {
continue;
}
let item_start = header_size + item.offset as usize;
if item_start + root_item_bytenr_offset + 8 > data.len() {
continue;
}
let mut buf = &data[item_start + root_item_bytenr_offset..];
let bytenr = buf.get_u64_le();
if bytenr != 0 {
tree_roots
.insert(item.key.objectid, (bytenr, item.key.offset));
}
}
}
TreeBlock::Node { ptrs, .. } => {
for ptr in ptrs {
collect_root_items(reader, ptr.blockptr, tree_roots)?;
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use crate::chunk::{ChunkMapping, Stripe};
use std::io::Cursor;
use uuid::Uuid;
fn make_device(len: usize) -> Cursor<Vec<u8>> {
Cursor::new(vec![0u8; len])
}
fn make_mapping(
logical: u64,
length: u64,
stripes: &[(u64, u64)],
) -> ChunkMapping {
let chunk_type = if stripes.len() == 1 {
0 } else {
let same_devid = stripes.iter().all(|s| s.0 == stripes[0].0);
if same_devid {
u64::from(raw::BTRFS_BLOCK_GROUP_DUP)
} else {
u64::from(match stripes.len() {
3 => raw::BTRFS_BLOCK_GROUP_RAID1C3,
4 => raw::BTRFS_BLOCK_GROUP_RAID1C4,
_ => raw::BTRFS_BLOCK_GROUP_RAID1,
})
}
};
ChunkMapping {
logical,
length,
stripe_len: 65536,
chunk_type,
num_stripes: stripes.len() as u16,
sub_stripes: 0,
stripes: stripes
.iter()
.map(|&(devid, offset)| Stripe {
devid,
offset,
dev_uuid: Uuid::nil(),
})
.collect(),
}
}
fn make_reader(
devices: &[(u64, usize)],
mappings: &[ChunkMapping],
) -> BlockReader<Cursor<Vec<u8>>> {
let mut handles = BTreeMap::new();
for &(devid, len) in devices {
handles.insert(devid, make_device(len));
}
let mut cache = ChunkTreeCache::default();
for m in mappings {
cache.insert(m.clone());
}
BlockReader::new_multi(handles, 4096, cache)
}
#[test]
fn read_block_routes_to_correct_devid() {
let mapping =
make_mapping(1_000_000, 4096, &[(2, 50_000), (1, 20_000)]);
let mut reader = make_reader(&[(1, 100_000), (2, 100_000)], &[mapping]);
reader.devices_mut().get_mut(&1).unwrap().get_mut()
[20_000..20_000 + 4096]
.fill(0xAA);
reader.devices_mut().get_mut(&2).unwrap().get_mut()
[50_000..50_000 + 4096]
.fill(0xBB);
let buf = reader.read_block(1_000_000).expect("read_block");
assert_eq!(buf.len(), 4096);
assert!(buf.iter().all(|&b| b == 0xBB), "expected all 0xBB");
}
#[test]
fn read_data_routes_to_correct_devid() {
let mapping = make_mapping(1_000_000, 4096, &[(5, 8000)]);
let mut reader = make_reader(&[(5, 100_000)], &[mapping]);
reader.devices_mut().get_mut(&5).unwrap().get_mut()[8000..8000 + 100]
.fill(0xCC);
let buf = reader.read_data(1_000_000, 100).expect("read_data");
assert_eq!(buf, vec![0xCC; 100]);
}
#[test]
fn write_block_fans_out_to_all_stripes() {
let mapping = make_mapping(2_000_000, 4096, &[(1, 1000), (2, 7000)]);
let mut reader = make_reader(&[(1, 100_000), (2, 100_000)], &[mapping]);
let payload = vec![0xDDu8; 4096];
reader
.write_block(2_000_000, &payload)
.expect("write_block");
let dev1 = reader.devices().get(&1).unwrap().get_ref();
let dev2 = reader.devices().get(&2).unwrap().get_ref();
assert_eq!(&dev1[1000..1000 + 4096], &payload[..]);
assert_eq!(&dev2[7000..7000 + 4096], &payload[..]);
assert!(dev1[..1000].iter().all(|&b| b == 0));
assert!(dev1[1000 + 4096..].iter().all(|&b| b == 0));
assert!(dev2[..7000].iter().all(|&b| b == 0));
assert!(dev2[7000 + 4096..].iter().all(|&b| b == 0));
}
#[test]
fn write_block_fans_out_to_dup_same_devid() {
let mapping = make_mapping(3_000_000, 4096, &[(1, 1000), (1, 50_000)]);
let mut reader = make_reader(&[(1, 100_000)], &[mapping]);
let payload = vec![0xEEu8; 4096];
reader
.write_block(3_000_000, &payload)
.expect("write_block");
let dev = reader.devices().get(&1).unwrap().get_ref();
assert_eq!(&dev[1000..1000 + 4096], &payload[..]);
assert_eq!(&dev[50_000..50_000 + 4096], &payload[..]);
}
#[test]
fn write_block_three_devices_raid1c3() {
let mapping = make_mapping(4_000_000, 4096, &[(1, 0), (2, 0), (3, 0)]);
let mut reader =
make_reader(&[(1, 8192), (2, 8192), (3, 8192)], &[mapping]);
let payload = vec![0xFFu8; 4096];
reader
.write_block(4_000_000, &payload)
.expect("write_block");
for &devid in &[1u64, 2, 3] {
let dev = reader.devices().get(&devid).unwrap().get_ref();
assert_eq!(
&dev[..4096],
&payload[..],
"devid {devid} mirror missing"
);
}
}
#[test]
fn read_block_missing_devid_errors() {
let mapping = make_mapping(5_000_000, 4096, &[(9, 0)]);
let mut reader = make_reader(&[(1, 8192), (2, 8192)], &[mapping]);
let err = reader.read_block(5_000_000).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::NotFound);
assert!(
err.to_string().contains("device 9"),
"expected error to mention devid 9, got: {err}"
);
}
#[test]
fn write_block_missing_devid_errors() {
let mapping = make_mapping(5_000_000, 4096, &[(1, 0), (9, 0)]);
let mut reader = make_reader(&[(1, 8192)], &[mapping]);
let err = reader.write_block(5_000_000, &[0u8; 4096]).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::NotFound);
assert!(err.to_string().contains("device 9"));
}
#[test]
fn read_block_unmapped_logical_errors() {
let mut reader = make_reader(&[(1, 8192)], &[]);
let err = reader.read_block(1_000_000).unwrap_err();
assert_eq!(err.kind(), io::ErrorKind::NotFound);
assert!(err.to_string().contains("not mapped"));
}
#[test]
fn new_single_inserts_under_supplied_devid() {
let cursor = make_device(8192);
let cache = ChunkTreeCache::default();
let reader = BlockReader::new(cursor, 7, 4096, cache);
assert_eq!(reader.devices().len(), 1);
assert!(reader.devices().contains_key(&7));
}
#[test]
fn new_multi_with_disjoint_devids() {
let mapping = make_mapping(0, 4096, &[(1, 100), (5, 200)]);
let mut reader = make_reader(&[(1, 8192), (5, 8192)], &[mapping]);
let payload = vec![0x77u8; 4096];
reader.write_block(0, &payload).expect("write_block");
let dev1 = reader.devices().get(&1).unwrap().get_ref();
let dev5 = reader.devices().get(&5).unwrap().get_ref();
assert_eq!(&dev1[100..100 + 4096], &payload[..]);
assert_eq!(&dev5[200..200 + 4096], &payload[..]);
}
#[test]
#[should_panic(expected = "filesystem has 2 devices")]
fn single_device_mut_panics_on_multi_device() {
let mapping = make_mapping(0, 4096, &[(1, 0), (2, 0)]);
let mut reader = make_reader(&[(1, 4096), (2, 4096)], &[mapping]);
let _ = reader.single_device_mut();
}
#[test]
fn single_device_mut_returns_handle_for_single_device() {
let mapping = make_mapping(0, 4096, &[(1, 0)]);
let mut reader = make_reader(&[(1, 8192)], &[mapping]);
reader.single_device_mut().get_mut()[42] = 0x99;
assert_eq!(reader.devices().get(&1).unwrap().get_ref()[42], 0x99);
}
}