add: version 1
This commit is contained in:
+229
@@ -0,0 +1,229 @@
|
||||
use std::fmt::Write;
|
||||
use std::{
|
||||
fmt::Display,
|
||||
path::{Path, PathBuf},
|
||||
process::Child,
|
||||
};
|
||||
|
||||
use anyhow::{Result, anyhow, bail};
|
||||
|
||||
use crate::cmd::zfs::{
|
||||
pool_exists, zfs_list_main_datasets, zfs_list_snapshots, zfs_receive, zfs_send_absolute,
|
||||
zfs_send_incremental, zpool_import,
|
||||
};
|
||||
use crate::io::read_vars_from_file;
|
||||
|
||||
pub struct Pool {
|
||||
name: String,
|
||||
pub datasets: Vec<Dataset>,
|
||||
}
|
||||
|
||||
impl Pool {
|
||||
pub fn new(arg: &str) -> Result<Self> {
|
||||
let result = Self {
|
||||
name: arg.to_string(),
|
||||
datasets: Vec::new(),
|
||||
};
|
||||
|
||||
if !result.exists() {
|
||||
bail!("pool does not exist")
|
||||
}
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn exists(&self) -> bool {
|
||||
pool_exists(&self.name)
|
||||
}
|
||||
|
||||
pub fn send_absolute(&self, dataset_to_send: &Dataset, snapshot: &Snapshot) -> Result<Child> {
|
||||
assert!(self.datasets.contains(dataset_to_send));
|
||||
|
||||
let mut qualified_name = dataset_to_send.0.to_str().unwrap().to_string();
|
||||
qualified_name.push('@');
|
||||
qualified_name.push_str(&snapshot.0);
|
||||
|
||||
let handle = zfs_send_absolute(&qualified_name)?;
|
||||
Ok(handle)
|
||||
}
|
||||
|
||||
pub fn send_incremental(
|
||||
&self,
|
||||
dataset_to_send: &Dataset,
|
||||
common_snapshot: &Snapshot,
|
||||
recent_snapshot: &Snapshot,
|
||||
) -> Result<Child> {
|
||||
let mut from = String::new();
|
||||
let mut to = String::new();
|
||||
write!(from, "@{common_snapshot}")?;
|
||||
write!(to, "{dataset_to_send}@{recent_snapshot}")?;
|
||||
zfs_send_incremental(&from, &to)
|
||||
}
|
||||
|
||||
pub fn receive(&self, handle: Child) -> Result<()> {
|
||||
zfs_receive(&self.name, handle)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_datasets_from_file(&mut self, filename: &str) -> Result<()> {
|
||||
self.datasets = Dataset::from_file(&PathBuf::from(filename), &self.name)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_datasets_from_system(&mut self) -> Result<()> {
|
||||
let datasets = zfs_list_main_datasets(&self.name)?;
|
||||
|
||||
self.datasets = datasets
|
||||
.iter()
|
||||
.map(PathBuf::from)
|
||||
.map(Dataset::new)
|
||||
.skip(1)
|
||||
.collect(); // first one is the Pool
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn contains(&self, dataset: &Dataset) -> bool {
|
||||
self.datasets.contains(dataset)
|
||||
}
|
||||
|
||||
pub fn get_matching_dataset(&self, dataset_from_other_pool: &Dataset) -> Option<Dataset> {
|
||||
let mut required_name = PathBuf::new();
|
||||
required_name.push(&self.name);
|
||||
dataset_from_other_pool
|
||||
.0
|
||||
.iter()
|
||||
.skip(1)
|
||||
.for_each(|element| required_name.push(element));
|
||||
let matching_dataset = Dataset::new(required_name);
|
||||
if self.contains(&matching_dataset) {
|
||||
Some(matching_dataset)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn import(name: &str) -> Result<Pool> {
|
||||
zpool_import(name)?;
|
||||
let result = Pool::new(name)?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
/// Full path of a ZFS dataset.
|
||||
///
|
||||
/// E.g. if the name of the pool is `tank`, with the elements `ROOT` and `default` below it, this
|
||||
/// struct would contain `tank/ROOT/default`.
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Dataset(PathBuf);
|
||||
|
||||
impl Dataset {
|
||||
fn new(path: PathBuf) -> Self {
|
||||
Self(path)
|
||||
}
|
||||
|
||||
fn from_file(filename: &Path, pool: &str) -> Result<Vec<Self>> {
|
||||
let datasets = read_vars_from_file(filename.to_str().unwrap())?;
|
||||
Ok(datasets
|
||||
.into_iter()
|
||||
.map(|ds| {
|
||||
let mut pb = PathBuf::new();
|
||||
pb.push(pool);
|
||||
pb.push(ds);
|
||||
Self::new(pb)
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn find_snapshots_like_tag(&self, snapshot_tag: &str) -> Result<Vec<Snapshot>> {
|
||||
let snapshots = zfs_list_snapshots(self.0.to_str().unwrap())?;
|
||||
|
||||
let mut result = vec![];
|
||||
|
||||
for item in snapshots {
|
||||
let snapshot_name = item.split('@').next_back().unwrap();
|
||||
if snapshot_name.contains(snapshot_tag) {
|
||||
result.push(Snapshot(snapshot_name.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
pub fn find_last_snapshot_with_tag(&self, snapshot_tag: &str) -> Result<Snapshot> {
|
||||
let mut snapshots = self.find_snapshots_like_tag(snapshot_tag)?;
|
||||
snapshots.sort_unstable();
|
||||
let result = snapshots
|
||||
.last()
|
||||
.cloned()
|
||||
.ok_or(anyhow!("could not find any snapshots"))?;
|
||||
Ok(result)
|
||||
}
|
||||
|
||||
fn get_snapshots(&self) -> Result<Vec<Snapshot>> {
|
||||
let snapshots_string = zfs_list_snapshots(self.0.to_str().unwrap())?;
|
||||
let snapshots = snapshots_string
|
||||
.into_iter()
|
||||
// TODO should snapshot just take a string?
|
||||
.map(|s| Snapshot::new(&s))
|
||||
.collect();
|
||||
Ok(snapshots)
|
||||
}
|
||||
|
||||
pub fn change_pool_name(&self, new_pool: &Pool) -> Self {
|
||||
let new_pool = new_pool.name.as_str();
|
||||
let mut pathbuf = PathBuf::new();
|
||||
pathbuf.push(new_pool);
|
||||
self.0
|
||||
.iter()
|
||||
.skip(1)
|
||||
.for_each(|element| pathbuf.push(element));
|
||||
|
||||
Self(pathbuf)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Dataset {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0.to_str().unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, PartialOrd, Ord, Eq)]
|
||||
/// A ZFS snapshot.
|
||||
///
|
||||
/// If the absolute ZFS path were "zpool/dataset@snapshot", this would contain "snapshot".
|
||||
pub struct Snapshot(String);
|
||||
|
||||
impl Snapshot {
|
||||
fn new(name: &str) -> Self {
|
||||
Self(name.to_string())
|
||||
}
|
||||
|
||||
pub fn find_last_common_snapshot_with_tag(
|
||||
ds1: &Dataset,
|
||||
ds2: &Dataset,
|
||||
tag: &str,
|
||||
) -> Option<Self> {
|
||||
let sn1 = ds1.get_snapshots().ok()?;
|
||||
let sn2 = ds2.get_snapshots().ok()?;
|
||||
let mut common = vec![];
|
||||
|
||||
for snapshot in sn1 {
|
||||
if !snapshot.0.contains(tag) {
|
||||
continue;
|
||||
}
|
||||
if sn2.contains(&snapshot) {
|
||||
common.push(snapshot);
|
||||
}
|
||||
}
|
||||
|
||||
common.sort();
|
||||
common.last().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Snapshot {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user