142 lines
4.4 KiB
Rust
142 lines
4.4 KiB
Rust
use anyhow::{anyhow, Context, Result};
|
|
use duct::cmd;
|
|
use std::io::prelude::*;
|
|
|
|
#[derive(Debug, Default)]
|
|
pub struct Dataset {
|
|
pub path: String,
|
|
pub target_path: Option<String>,
|
|
pub mountpoint: Option<String>,
|
|
is_in_store: Option<bool>,
|
|
}
|
|
|
|
pub fn get_datasets() -> Result<Vec<Dataset>> {
|
|
let mut datasets = Vec::<Dataset>::new();
|
|
|
|
for line in std::io::BufReader::new(
|
|
cmd!(
|
|
"zfs",
|
|
"get",
|
|
"-t",
|
|
"filesystem",
|
|
"-Hpo",
|
|
"name,property,value",
|
|
"mountpoint,soft:target,soft:store"
|
|
)
|
|
.stdin_null()
|
|
.reader()
|
|
.context("Failed to list ZFS datasets")?,
|
|
)
|
|
.lines()
|
|
{
|
|
let l = line.context("Failed to read the list of ZFS datasets")?;
|
|
let (n, pv) = l.split_once('\t').ok_or(anyhow!("Weird ZFS output"))?;
|
|
let (p, v) = pv.split_once('\t').ok_or(anyhow!("Weird ZFS output"))?;
|
|
|
|
let (mut last_idx, _) = datasets.len().overflowing_sub(1);
|
|
if last_idx == usize::MAX || datasets[last_idx].path != n {
|
|
datasets.push(Dataset { path: n.to_owned(), ..Default::default() });
|
|
last_idx = datasets.len() - 1;
|
|
}
|
|
|
|
let ds = &mut datasets[last_idx];
|
|
match p {
|
|
"mountpoint" => ds.mountpoint = Some(v.to_owned()),
|
|
"soft:target" => ds.target_path = Some(v.to_owned()),
|
|
"soft:store" => ds.is_in_store = Some(v == "true"),
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
Ok(datasets)
|
|
}
|
|
|
|
type Store = (Dataset, Vec<Dataset>);
|
|
|
|
pub fn filter_stores(datasets: Vec<Dataset>) -> Result<Vec<Store>> {
|
|
let mut stores = datasets.into_iter().fold(Vec::<Store>::new(), |mut acc, ds| {
|
|
if ds.is_in_store == Some(true) {
|
|
if acc.last().map(|(l, _)| ds.path.starts_with(&l.path)) == Some(true) {
|
|
acc.last_mut().unwrap().1.push(ds);
|
|
} else {
|
|
acc.push((ds, Vec::new()));
|
|
}
|
|
}
|
|
acc
|
|
});
|
|
|
|
stores.sort_by_cached_key(|(s, _ds)| {
|
|
s.mountpoint
|
|
.as_ref()
|
|
.map(|p| p.chars().filter(|c| *c == '/').count())
|
|
.unwrap_or(usize::MAX)
|
|
});
|
|
|
|
Ok(stores)
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct WritableDataset(pub String);
|
|
|
|
impl WritableDataset {
|
|
pub fn from_scratch(under: &Dataset, name: &str, target_path: &str) -> Result<WritableDataset> {
|
|
let dspath = format!("{}/{}", under.path, name);
|
|
log::trace!("Creating dataset '{}' from scratch", dspath);
|
|
#[rustfmt::skip]
|
|
cmd!(
|
|
"zfs", "create",
|
|
"-o", "atime=off",
|
|
"-o", "acltype=off",
|
|
"-o", "canmount=noauto",
|
|
"-o", "devices=off",
|
|
"-o", "utf8only=on",
|
|
"-o", "xattr=sa",
|
|
"-o", "compression=zstd",
|
|
"-o", "checksum=sha256",
|
|
"-o", "readonly=off",
|
|
"-o", format!("soft:target={}", target_path),
|
|
&dspath,
|
|
)
|
|
.run()
|
|
.with_context(|| format!("Failed to create dataset '{}'", dspath))?;
|
|
Ok(WritableDataset(dspath))
|
|
}
|
|
|
|
pub fn from_snapshot(under: &Dataset, from: &Dataset, name: &str) -> Result<WritableDataset> {
|
|
let dspath = format!("{}/{}", under.path, name);
|
|
let snpath = format!("{}@S", from.path);
|
|
log::trace!("Creating dataset '{}' by cloning '{}'", dspath, snpath);
|
|
#[rustfmt::skip]
|
|
cmd!(
|
|
"zfs", "clone",
|
|
"-o", "atime=off",
|
|
"-o", "acltype=off",
|
|
"-o", "canmount=noauto",
|
|
"-o", "devices=off",
|
|
"-o", "xattr=sa",
|
|
"-o", "compression=zstd",
|
|
"-o", "checksum=sha256",
|
|
"-o", "readonly=off",
|
|
"-o", format!("soft:target={}", from.target_path.as_ref().unwrap()),
|
|
&snpath,
|
|
&dspath,
|
|
)
|
|
.run()
|
|
.with_context(|| format!("Failed to create dataset '{}' by cloning '{}'", dspath, snpath))?;
|
|
Ok(WritableDataset(dspath))
|
|
}
|
|
|
|
fn _commit(&self) -> std::io::Result<()> {
|
|
cmd!("zfs", "set", "canmount=on", "readonly=on", &self.0).run()?;
|
|
cmd!("zfs", "snapshot", format!("{}@S", &self.0)).run()?;
|
|
cmd!("zfs", "mount", &self.0).run()?;
|
|
Ok(())
|
|
}
|
|
|
|
pub fn commit(&self) -> Result<()> {
|
|
log::trace!("Committing dataset '{}'", self.0);
|
|
self._commit()
|
|
.with_context(|| format!("Failed to mount dataset '{}'", self.0))
|
|
}
|
|
}
|