use crate::{RunContext, Runnable, util::open_path};
use anyhow::{Context, Result};
use btrfs_uapi::subvolume::{subvolume_info_by_id, subvolume_list};
use clap::Parser;
use std::{os::unix::io::AsFd, path::PathBuf, thread, time::Duration};
#[derive(Parser, Debug)]
pub struct SubvolumeSyncCommand {
path: PathBuf,
subvolids: Vec<u64>,
#[clap(short = 's', long, value_name = "SECONDS")]
sleep: Option<u64>,
}
impl Runnable for SubvolumeSyncCommand {
fn run(&self, _ctx: &RunContext) -> Result<()> {
let file = open_path(&self.path)?;
let interval = Duration::from_secs(self.sleep.unwrap_or(1));
let mut ids: Vec<u64> = if self.subvolids.is_empty() {
let items = subvolume_list(file.as_fd())
.with_context(|| "failed to list subvolumes")?;
items
.iter()
.filter(|item| item.parent_id == 0)
.map(|item| item.root_id)
.collect()
} else {
self.subvolids.clone()
};
if ids.is_empty() {
return Ok(());
}
let total = ids.len();
let mut done = 0usize;
loop {
let mut all_gone = true;
for id in &mut ids {
if *id == 0 {
continue;
}
match subvolume_info_by_id(file.as_fd(), *id) {
Ok(_) => {
all_gone = false;
}
Err(nix::errno::Errno::ENOENT) => {
done += 1;
eprintln!("Subvolume id {id} is gone ({done}/{total})");
*id = 0;
}
Err(e) => {
anyhow::bail!("failed to query subvolume {id}: {e}");
}
}
}
if all_gone {
break;
}
thread::sleep(interval);
}
Ok(())
}
}