btrfs_cli/subvolume/
sync.rs1use crate::{Format, Runnable, util::open_path};
2use anyhow::{Context, Result};
3use btrfs_uapi::subvolume::{subvolume_info_by_id, subvolume_list};
4use clap::Parser;
5use std::{os::unix::io::AsFd, path::PathBuf, thread, time::Duration};
6
7#[derive(Parser, Debug)]
14pub struct SubvolumeSyncCommand {
15 path: PathBuf,
17
18 subvolids: Vec<u64>,
20
21 #[clap(short = 's', long, value_name = "SECONDS")]
23 sleep: Option<u64>,
24}
25
26impl Runnable for SubvolumeSyncCommand {
27 fn run(&self, _format: Format, _dry_run: bool) -> Result<()> {
28 let file = open_path(&self.path)?;
29
30 let interval = Duration::from_secs(self.sleep.unwrap_or(1));
31
32 let mut ids: Vec<u64> = if self.subvolids.is_empty() {
33 let items = subvolume_list(file.as_fd())
36 .with_context(|| "failed to list subvolumes")?;
37 items
38 .iter()
39 .filter(|item| item.parent_id == 0)
40 .map(|item| item.root_id)
41 .collect()
42 } else {
43 self.subvolids.clone()
44 };
45
46 if ids.is_empty() {
47 return Ok(());
48 }
49
50 let total = ids.len();
51 let mut done = 0usize;
52
53 loop {
54 let mut all_gone = true;
55
56 for id in &mut ids {
57 if *id == 0 {
58 continue;
59 }
60 match subvolume_info_by_id(file.as_fd(), *id) {
61 Ok(_) => {
62 all_gone = false;
63 }
64 Err(nix::errno::Errno::ENOENT) => {
65 done += 1;
66 eprintln!("Subvolume id {id} is gone ({done}/{total})");
67 *id = 0;
68 }
69 Err(e) => {
70 anyhow::bail!("failed to query subvolume {id}: {e}");
71 }
72 }
73 }
74
75 if all_gone {
76 break;
77 }
78
79 thread::sleep(interval);
80 }
81
82 Ok(())
83 }
84}