use crate::{RunContext, Runnable, util::open_path};
use anyhow::{Context, Result};
use btrfs_disk::items::{FileExtentBody, FileExtentItem, FileExtentType};
use btrfs_uapi::{
filesystem::sync,
inode::ino_paths,
raw::BTRFS_EXTENT_DATA_KEY,
subvolume::subvolume_info,
tree_search::{SearchFilter, tree_search},
};
use clap::Parser;
use std::{os::unix::io::AsFd, path::PathBuf};
#[derive(Parser, Debug)]
pub struct SubvolumeFindNewCommand {
path: PathBuf,
last_gen: u64,
}
impl Runnable for SubvolumeFindNewCommand {
fn run(&self, _ctx: &RunContext) -> Result<()> {
let file = open_path(&self.path)?;
sync(file.as_fd()).with_context(|| {
format!("failed to sync '{}'", self.path.display())
})?;
let info = subvolume_info(file.as_fd()).with_context(|| {
format!(
"failed to get subvolume info for '{}'",
self.path.display()
)
})?;
let max_gen = info.generation;
let mut key = SearchFilter::for_type(0, BTRFS_EXTENT_DATA_KEY);
key.min_transid = self.last_gen;
let mut cache_ino: u64 = 0;
let mut cache_name: Option<String> = None;
tree_search(file.as_fd(), key, |hdr, data| {
let Some(fe) = FileExtentItem::parse(data) else {
return Ok(());
};
if fe.generation < self.last_gen {
return Ok(());
}
let compressed =
!matches!(fe.compression, btrfs_disk::items::CompressionType::None);
let (disk_start, disk_offset, len) = match &fe.body {
FileExtentBody::Regular {
disk_bytenr,
num_bytes,
offset,
..
} => (*disk_bytenr, *offset, *num_bytes),
FileExtentBody::Inline { inline_size } => {
(0, 0, *inline_size as u64)
}
};
let name = if hdr.objectid == cache_ino {
cache_name.as_deref().unwrap_or("unknown")
} else {
let resolved = match ino_paths(file.as_fd(), hdr.objectid) {
Ok(paths) if !paths.is_empty() => {
Some(paths.into_iter().next().unwrap())
}
_ => None,
};
cache_ino = hdr.objectid;
cache_name = resolved;
cache_name.as_deref().unwrap_or("unknown")
};
let mut flags = String::new();
if compressed {
flags.push_str("COMPRESS");
}
if fe.extent_type == FileExtentType::Prealloc {
if !flags.is_empty() {
flags.push('|');
}
flags.push_str("PREALLOC");
}
if fe.extent_type == FileExtentType::Inline {
if !flags.is_empty() {
flags.push('|');
}
flags.push_str("INLINE");
}
if flags.is_empty() {
flags.push_str("NONE");
}
println!(
"inode {} file offset {} len {} disk start {} offset {} gen {} flags {flags} {name}",
hdr.objectid, hdr.offset, len, disk_start, disk_offset, fe.generation,
);
Ok(())
})
.with_context(|| format!("tree search failed for '{}'", self.path.display()))?;
println!("transid marker was {max_gen}");
Ok(())
}
}