use std::path::Path;
use tracing::debug;
use super::super::blockdev;
use crate::actions::canopy::backup::hold::{HeldCapture, HoldRecord};
pub async fn diverged_bytes(
record: &HoldRecord,
live: &Path,
pool: &blockdev::ThinPool,
) -> Option<u64> {
let HeldCapture::Lvm { vg, lv, .. } = &record.capture else {
return None;
};
let live_source = blockdev::findmnt("SOURCE", live).await?;
let live_lv = blockdev::lvs("lv_name", &live_source, false).await?;
let held_id = blockdev::lvs("thin_id", &format!("{vg}/{lv}"), false).await?;
let live_id = blockdev::lvs("thin_id", &format!("{vg}/{live_lv}"), false).await?;
let block_size: u64 = blockdev::lvs("chunk_size", &pool.qualified(), true)
.await?
.parse()
.ok()?;
let pool_dm = blockdev::dm_name(&pool.vg, &pool.pool);
let metadata = format!("/dev/mapper/{pool_dm}_tmeta");
blockdev::run("dmsetup", &["message", &pool_dm, "0", "reserve_metadata_snap"]).await?;
let delta = blockdev::capture(
"thin_delta",
&["--snap1", &held_id, "--snap2", &live_id, "-m", &metadata],
)
.await;
let _ = blockdev::run("dmsetup", &["message", &pool_dm, "0", "release_metadata_snap"]).await;
let blocks = differing_blocks(&delta?)?;
let bytes = blocks.saturating_mul(block_size);
debug!(blocks, bytes, "thin_delta sized the divergence from the capture");
Some(bytes)
}
fn differing_blocks(xml: &str) -> Option<u64> {
let mut total = 0u64;
let mut saw_any = false;
for line in xml.lines() {
let line = line.trim();
if !line.starts_with('<') {
continue;
}
let differing = line.starts_with("<left_only ")
|| line.starts_with("<right_only ")
|| line.starts_with("<different ");
saw_any |= differing || line.starts_with("<same ");
if !differing {
continue;
}
let Some(length) = attribute(line, "length") else {
continue;
};
total = total.saturating_add(length);
}
saw_any.then_some(total)
}
fn attribute(line: &str, name: &str) -> Option<u64> {
let at = line.find(&format!("{name}=\""))? + name.len() + 2;
let rest = &line[at..];
let end = rest.find('"')?;
rest[..end].parse().ok()
}
#[cfg(test)]
mod tests {
use super::*;
const DELTA: &str = r#"<superblock>
<diff left="1" right="2">
<same begin="0" length="1000"/>
<different begin="1000" length="20"/>
<left_only begin="1020" length="5"/>
<right_only begin="1025" length="3"/>
<same begin="1028" length="500"/>
</diff>
</superblock>
"#;
#[test]
fn counts_every_kind_of_difference_and_no_sameness() {
assert_eq!(differing_blocks(DELTA), Some(28));
}
#[test]
fn nothing_diverged_is_zero_rather_than_unknown() {
let xml = "<superblock>\n<diff left=\"1\" right=\"2\">\n<same begin=\"0\" length=\"1000\"/>\n</diff>\n</superblock>";
assert_eq!(differing_blocks(xml), Some(0));
}
#[test]
fn output_with_no_mappings_at_all_is_unknown() {
assert_eq!(differing_blocks("not xml at all"), None);
}
#[test]
fn reads_an_attribute_off_an_element() {
assert_eq!(attribute(r#"<different begin="10" length="20"/>"#, "length"), Some(20));
assert_eq!(attribute(r#"<different begin="10" length="20"/>"#, "begin"), Some(10));
assert_eq!(attribute("<different/>", "length"), None);
}
}