fractal_btrfs_wrappers/
lib.rs

1use anyhow::{anyhow, Error, Result};
2use lazy_static::lazy_static;
3use log::*;
4use regex::Regex;
5use std::path::{Path, PathBuf};
6use std::process::Stdio;
7use tokio::process::{Child, Command};
8
9/// Create a new, empty BTRFS subvolume at `path`.
10///
11/// This assumes that the parent directory of `path` is a BTRFS volume.
12pub async fn btrfs_subvolume_create(path: &Path) -> Result<()> {
13    let mut command = Command::new("btrfs");
14    command.arg("subvolume").arg("create").arg(path);
15    debug!("Creating BTRFS subvolume: {:?}", command);
16    let output = command.output().await.unwrap();
17    if !output.status.success() {
18        return Err(anyhow!("Error creating BTRFS subvolume: {output:?}"));
19    }
20    Ok(())
21}
22
23/// Take a snapshot of the BTRFS subvolume `path` at the new folder `snapshot`. Optionally,
24/// mark the snapshot as read-only.
25pub async fn btrfs_subvolume_snapshot(path: &Path, snapshot: &Path, readonly: bool) -> Result<()> {
26    let mut command = Command::new("btrfs");
27    command.arg("subvolume").arg("snapshot");
28    if readonly {
29        command.arg("-r");
30    }
31    command.arg(path).arg(snapshot);
32    debug!("Snapshotting BTRFS volume: {:?}", command);
33    let output = command.output().await.unwrap();
34    if !output.status.success() {
35        return Err(anyhow!("Error snapshotting subvolume: {:?}", output));
36    }
37    Ok(())
38}
39
40/// Delete a BTRFS subvolume.
41pub async fn btrfs_subvolume_delete(path: &Path) -> Result<()> {
42    let mut command = Command::new("btrfs");
43    command.arg("subvolume").arg("delete").arg(path);
44    debug!("Deleting BTRFS volume: {:?}", command);
45    let output = command.output().await.unwrap();
46    if !output.status.success() {
47        return Err(anyhow!("Error deleting subvolume: {:?}", output));
48    }
49    Ok(())
50}
51
52/// Parsed output of the `btrfs subvolume show` command.
53pub struct BtrfsSubvolumeShow {
54    /// Current generation number of this subvolume.
55    pub generation: u64,
56}
57
58/// Runs `btrfs subvolume show` and parses the output.
59pub async fn btrfs_subvolume_show(path: &Path) -> Result<BtrfsSubvolumeShow, Error> {
60    lazy_static! {
61        static ref GENERATION: Regex = Regex::new(r"Generation:\s+(\d+)").unwrap();
62    }
63
64    let mut command = Command::new("btrfs");
65    command.arg("subvolume").arg("show").arg(path);
66    debug!("Getting BTRFS subvolume info: {command:?}");
67
68    let output = command.output().await?;
69
70    if !output.status.success() {
71        return Err(anyhow!("Error getting subvolume info: {:?}", output));
72    }
73
74    let stdout = String::from_utf8(output.stdout)?;
75    let generation: u64 = GENERATION
76        .captures(&stdout)
77        .unwrap()
78        .get(1)
79        .unwrap()
80        .as_str()
81        .parse()?;
82
83    Ok(BtrfsSubvolumeShow { generation })
84}
85
86/// Start a BTRFS send process, returning a handle to it's standard output.
87pub async fn btrfs_send(path: &Path, parent: Option<&Path>) -> std::io::Result<Child> {
88    let mut command = Command::new("btrfs");
89    command.arg("send");
90
91    if let Some(parent) = parent {
92        command.arg("-p").arg(parent);
93    }
94
95    command.arg(path);
96    debug!("Sending BTRFS snapshot: {:?}", command);
97
98    command.stdout(Stdio::piped());
99    command.stderr(Stdio::piped());
100    command.spawn()
101}
102
103/// Start a BTRFS receive process, returning a handle to it's standard output.
104pub async fn btrfs_receive(path: &Path) -> std::io::Result<Child> {
105    let mut command = Command::new("btrfs");
106    command.arg("receive");
107    command.arg(path);
108    debug!("Receiving BTRFS snapshot: {:?}", command);
109
110    command.stdin(Stdio::piped());
111    command.spawn()
112}
113
114/// Format a block device as a BTRFS filesystem.
115///
116/// This will overwrite the block device at `path`.
117pub async fn mkfs_btrfs(path: &Path) -> Result<()> {
118    let mut command = Command::new("mkfs.btrfs");
119    command.arg(path);
120    debug!("Creating BTRFS filesystem: {:?}", command);
121    let output = command.output().await?;
122    if !output.status.success() {
123        Err(anyhow!("Error creating BTRFS filesystem: {:?}", output))
124    } else {
125        Ok(())
126    }
127}
128
129/// Unmount path.
130pub async fn umount(path: &Path) -> Result<()> {
131    let mut command = Command::new("umount");
132    command.arg(path);
133    debug!("Unmounting filesystem: {:?}", command);
134    let output = command.output().await?;
135    if !output.status.success() {
136        Err(anyhow!("Error unmounting filesystem: {:?}", output))
137    } else {
138        Ok(())
139    }
140}
141
142/// Options for mounting a BTRFS filesystem.
143pub struct MountOptions {
144    /// Path to block device holding the filesystem.
145    pub block_device: PathBuf,
146    /// Folder at which the filesystem should be mounted.
147    pub mount_target: PathBuf,
148}
149
150/// Mount a BTRFS filesystem at a specified path.
151pub async fn mount_btrfs(options: MountOptions) -> Result<()> {
152    let mut command = Command::new("mount");
153    command
154        .arg("-t")
155        .arg("btrfs")
156        .arg(options.block_device)
157        .arg(options.mount_target);
158    debug!("Mounting BTRFS filesystem: {:?}", command);
159    let output = command.output().await?;
160    if !output.status.success() {
161        Err(anyhow!("Error mounting BTRFS filesystem: {:?}", output))
162    } else {
163        Ok(())
164    }
165}