Skip to main content

btrfs_cli/subvolume/
sync.rs

1use 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/// Wait until given subvolume(s) are completely removed from the filesystem
10///
11/// Wait until given subvolume(s) are completely removed from the filesystem
12/// after deletion. If no subvolume id is given, wait until all current
13/// deletion requests are completed, but do not wait for subvolumes deleted
14/// meanwhile. The status of subvolume ids is checked periodically.
15#[derive(Parser, Debug)]
16pub struct SubvolumeSyncCommand {
17    /// Path to the btrfs filesystem mount point
18    path: PathBuf,
19
20    /// One or more subvolume IDs to wait for (waits for all pending if omitted)
21    subvolids: Vec<u64>,
22
23    /// Sleep N seconds between checks (default: 1)
24    #[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            // No IDs given: find all deleted subvolumes (those with parent_id == 0
38            // in subvolume_list, meaning no ROOT_BACKREF — pending deletion).
39            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}