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