use super::{ReconstructionResult, ScanResult};
use anyhow::{Context, Result};
use btrfs_disk::{
chunk::{ChunkTreeCache, chunk_item_bytes},
items::BlockGroupFlags,
raw,
tree::{DiskKey, KeyType},
};
use btrfs_transaction::{
Filesystem, Transaction,
buffer::ITEM_SIZE,
items,
path::BtrfsPath,
search::{self, SearchIntent},
};
use std::fs::File;
const CHUNK_TREE_OBJECTID: u64 = raw::BTRFS_CHUNK_TREE_OBJECTID as u64;
pub fn apply_chunk_tree(
file: File,
scan: &ScanResult,
result: &ReconstructionResult,
) -> Result<()> {
let chunk_cache = build_chunk_cache(result);
let sectorsize = scan.nodesize.min(4096);
let system_chunks: Vec<(u64, Vec<u8>)> = result
.chunks
.iter()
.filter(|c| c.chunk.chunk_type.contains(BlockGroupFlags::SYSTEM))
.map(|c| {
let mapping = c.chunk.to_mapping(c.logical);
let bytes = chunk_item_bytes(&mapping, sectorsize);
(c.logical, bytes)
})
.collect();
let mut fs = Filesystem::open_with_chunk_cache(file, 0, chunk_cache)
.context("failed to open filesystem with reconstructed chunk cache")?;
let mut trans =
Transaction::start(&mut fs).context("failed to start transaction")?;
trans
.rebuild_chunk_tree(&mut fs, &system_chunks)
.context("failed to create new chunk tree")?;
for dev in &result.devices {
let key = DiskKey {
objectid: u64::from(raw::BTRFS_DEV_ITEMS_OBJECTID),
key_type: KeyType::DeviceItem,
offset: dev.devid,
};
let mut data = Vec::new();
dev.device.write_bytes(&mut data);
insert_in_chunk_tree(&mut trans, &mut fs, &key, &data)?;
}
for chunk in &result.chunks {
let key = DiskKey {
objectid: u64::from(raw::BTRFS_FIRST_CHUNK_TREE_OBJECTID),
key_type: KeyType::ChunkItem,
offset: chunk.logical,
};
let mapping = chunk.chunk.to_mapping(chunk.logical);
let data = chunk_item_bytes(&mapping, sectorsize);
insert_in_chunk_tree(&mut trans, &mut fs, &key, &data)?;
}
trans
.commit(&mut fs)
.context("failed to commit chunk tree rebuild")?;
fs.sync().context("failed to sync filesystem")?;
Ok(())
}
fn build_chunk_cache(result: &ReconstructionResult) -> ChunkTreeCache {
let mut cache = ChunkTreeCache::default();
for chunk in &result.chunks {
let mapping = chunk.chunk.to_mapping(chunk.logical);
cache.insert(mapping);
}
cache
}
fn insert_in_chunk_tree(
trans: &mut Transaction<File>,
fs: &mut Filesystem<File>,
key: &DiskKey,
data: &[u8],
) -> Result<()> {
let mut path = BtrfsPath::new();
#[allow(clippy::cast_possible_truncation)]
let found = search::search_slot(
Some(&mut *trans),
fs,
CHUNK_TREE_OBJECTID,
key,
&mut path,
SearchIntent::Insert((ITEM_SIZE + data.len()) as u32),
true,
)?;
if found {
path.release();
anyhow::bail!("duplicate key {key:?} in chunk tree");
}
let leaf = path.nodes[0]
.as_mut()
.ok_or_else(|| anyhow::anyhow!("no leaf in path after search"))?;
items::insert_item(leaf, path.slots[0], key, data)?;
fs.mark_dirty(leaf);
path.release();
Ok(())
}