Skip to main content

btrfs_cli/subvolume/
sync.rs

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