aboutsummaryrefslogtreecommitdiffstats
path: root/zone_zfs/src
diff options
context:
space:
mode:
Diffstat (limited to 'zone_zfs/src')
-rw-r--r--zone_zfs/src/error.rs5
-rw-r--r--zone_zfs/src/file_system.rs171
-rw-r--r--zone_zfs/src/lib.rs11
-rw-r--r--zone_zfs/src/snapshot.rs56
-rw-r--r--zone_zfs/src/zfs.rs42
5 files changed, 166 insertions, 119 deletions
diff --git a/zone_zfs/src/error.rs b/zone_zfs/src/error.rs
index bf32f14..872804b 100644
--- a/zone_zfs/src/error.rs
+++ b/zone_zfs/src/error.rs
@@ -3,7 +3,7 @@ pub type Result<T> = std::result::Result<T, Error>;
#[allow(clippy::large_enum_variant)]
#[derive(thiserror::Error, Debug)]
pub enum Error {
- #[error("ZFS error")]
+ #[error("ZFS error: {0:?}")]
ZFS(String),
#[error("Snapshot Error: {0:?}")]
@@ -20,4 +20,7 @@ pub enum Error {
#[error("Config Error: {0:?}")]
Config(#[from] figment::Error),
+
+ #[error(transparent)]
+ Builder(#[from] crate::file_system::FileSystemBuilderError),
}
diff --git a/zone_zfs/src/file_system.rs b/zone_zfs/src/file_system.rs
index c961277..5c747ce 100644
--- a/zone_zfs/src/file_system.rs
+++ b/zone_zfs/src/file_system.rs
@@ -1,5 +1,6 @@
use crate::{Error, Result, Snapshot};
use bytesize::ByteSize;
+use derive_builder::Builder;
use std::{
ffi::{OsStr, OsString},
fmt::Display,
@@ -7,97 +8,119 @@ use std::{
process::Command,
};
-macro_rules! opt [
- ($name:expr, $value:expr) => ({
+macro_rules! concat_opt [
+ ($($value:expr),+) => ({
let mut _temp = OsString::new();
- _temp.push($name);
- _temp.push("=");
- _temp.push($value);
+ $(_temp.push($value);)+
_temp
})
];
-#[derive(Debug, Default, Clone)]
+#[derive(Debug, Default, Clone, Builder)]
+#[builder(
+ build_fn(private, name = "build_file_system"),
+ derive(Debug),
+ setter(into),
+ default
+)]
pub struct FileSystem {
- pub(crate) value: OsString,
+ dataset: PathBuf,
mountpoint: PathBuf,
quota: ByteSize,
}
impl Display for FileSystem {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.value.to_string_lossy())
+ write!(f, "{}", self.dataset.to_string_lossy())
}
}
-impl TryFrom<OsString> for FileSystem {
- type Error = Error;
+impl FileSystemBuilder {
+ pub fn build(&self) -> Result<FileSystem> {
+ self.build_file_system()?.create()
+ }
+
+ pub fn build_from(&self, snapshot: &Snapshot) -> Result<FileSystem> {
+ self.build_file_system()?.create_from(snapshot)
+ }
- fn try_from(value: OsString) -> Result<Self> {
- Ok(FileSystem {
- value,
- ..FileSystem::default()
- })
+ pub(crate) fn to_file_system(&self) -> Result<FileSystem> {
+ self.build_file_system().map_err(Error::from)
}
}
-impl TryFrom<&str> for FileSystem {
- type Error = Error;
+impl FileSystem {
+ pub fn builder() -> FileSystemBuilder {
+ FileSystemBuilder::default()
+ }
- fn try_from(value: &str) -> Result<Self> {
- value.try_into()
+ /// Get a reference to the file system's dataset.
+ pub fn dataset(&self) -> &PathBuf {
+ &self.dataset
}
-}
-impl AsRef<OsStr> for FileSystem {
- fn as_ref(&self) -> &OsStr {
- self.value.as_ref()
+ /// Get a reference to the file system's mountpoint.
+ pub fn mountpoint(&self) -> &PathBuf {
+ &self.mountpoint
}
-}
-impl From<FileSystem> for PathBuf {
- fn from(val: FileSystem) -> Self {
- PathBuf::from(val.value)
+ /// Get the file system's quota.
+ pub fn quota(&self) -> ByteSize {
+ self.quota
}
-}
-impl From<FileSystem> for String {
- fn from(val: FileSystem) -> Self {
- val.value.to_string_lossy().to_string()
+ /// Get the value of the file system's `option` from ZFS
+ pub fn get_opt<T>(&self, option: T) -> Result<OsString>
+ where
+ T: AsRef<OsStr>,
+ {
+ Command::new("zfs")
+ .arg("get")
+ .arg("-H")
+ .arg("-o")
+ .arg("value,source")
+ .arg(&option)
+ .arg(&self.dataset)
+ .output()
+ .map(|o| String::from_utf8(o.stdout))?
+ .map(OsString::from)
+ .map_err(Error::from)
}
-}
-impl FileSystem {
- pub(super) fn set_opt<T, U>(&self, name: T, value: U) -> Result<&Self>
+ pub fn set_opt<T, U>(&self, option: T, value: U) -> Result<&Self>
where
T: AsRef<OsStr>,
U: AsRef<OsStr>,
{
Command::new("zfs")
.arg("set")
- .arg(opt!(&name, value))
- .arg(&self.value)
+ .arg(concat_opt!(&option, "=", value))
+ .arg(&self.dataset)
.status()?
.success()
.then(|| self)
.ok_or_else(|| {
- Error::FileSystem(format!("Failed to set {:?}: {:?}", name.as_ref(), self))
+ Error::FileSystem(format!("Failed to set {:?}: {:?}", option.as_ref(), self))
})
}
- pub(super) fn set_quota(&mut self, quota: ByteSize) -> Result<Self> {
+ pub fn set_quota(&mut self, quota: ByteSize) -> Result<Self> {
self.set_opt("quota", quota.to_string())?;
self.quota = quota;
Ok(self.to_owned())
}
- pub(super) fn set_mountpoint(&mut self, mountpoint: PathBuf) -> Result<Self> {
+ pub fn set_mountpoint(&mut self, mountpoint: PathBuf) -> Result<Self> {
self.set_opt("mountpoint", &mountpoint)?;
self.mountpoint = mountpoint;
Ok(self.to_owned())
}
- pub(super) fn get_snapshots(&self) -> Result<Vec<Snapshot>> {
+ pub fn get_mountpoint(&self) -> PathBuf {
+ self.mountpoint.to_owned()
+ }
+
+ pub fn get_snapshots(&self) -> Result<Vec<Snapshot>> {
Command::new("zfs")
.arg("list")
.arg("-H")
@@ -105,56 +128,76 @@ impl FileSystem {
.arg("name")
.arg("-t")
.arg("snapshot")
- .arg(self)
+ .arg(&self.dataset)
.output()
.map(|o| String::from_utf8(o.stdout))?
.map_err(Error::from)?
.split_whitespace()
- .map(Snapshot::try_from)
+ .filter_map(|s| Snapshot::try_from(s).ok())
+ .map(Ok)
.collect()
}
- pub(super) fn get_latest_snapshot(&self) -> Result<Option<Snapshot>> {
- Ok(self
- .get_snapshots()?
- .into_iter()
- .max_by_key(|s| s.timestamp))
+ pub fn get_latest_snapshot(&self) -> Result<Option<Snapshot>> {
+ todo!("implement using feature")
}
- pub fn mount(&self) -> Result<()> {
+ pub(crate) fn create(&self) -> Result<Self> {
Command::new("zfs")
- .arg("mount")
- .arg(&self.value)
+ .arg("create")
+ .arg("-o")
+ .arg(concat_opt!("mountpoint=", &self.mountpoint))
+ .arg(concat_opt!("quota=", self.quota.to_string()))
.status()?
.success()
- .then(|| ())
- .ok_or_else(|| Error::FileSystem(format!("Failed to mount: {:?}", self)))
+ .then(|| self.to_owned())
+ .ok_or_else(|| Error::FileSystem(format!("Failed to create file system: {:?}", self)))
}
- pub fn unmount(&self) -> Result<()> {
+ pub(crate) fn create_from(&self, snapshot: &Snapshot) -> Result<Self> {
Command::new("zfs")
- .arg("unmount")
- .arg(&self.value)
+ .arg("clone")
+ .arg(&snapshot.name())
+ .arg(&self.dataset)
.status()?
.success()
- .then(|| ())
- .ok_or_else(|| Error::FileSystem(format!("Failed to unmount: {:?}", self)))
+ .then(|| self.to_owned())
+ .ok_or_else(|| Error::Snapshot(format!("Failed to clone snapshot: {:?}", self)))
}
pub fn destroy(&self, force: bool) -> Result<()> {
- let mut args: Vec<&OsStr> = Vec::new();
- let f_arg = &OsString::from("-f");
- if force {
- args.push(f_arg);
- }
- args.push(&self.value);
+ let args = if force {
+ vec!["destroy", "-f"]
+ } else {
+ vec!["destroy"]
+ };
Command::new("zfs")
- .arg("destroy")
.args(args)
+ .arg(&self.dataset)
.status()?
.success()
.then(|| ())
.ok_or_else(|| Error::FileSystem(format!("Failed to destroy: {:?}", self)))
}
+
+ pub fn mount(&self) -> Result<()> {
+ Command::new("zfs")
+ .arg("mount")
+ .arg(&self.dataset)
+ .status()?
+ .success()
+ .then(|| ())
+ .ok_or_else(|| Error::FileSystem(format!("Failed to mount: {:?}", self)))
+ }
+
+ pub fn unmount(&self) -> Result<()> {
+ Command::new("zfs")
+ .arg("unmount")
+ .arg(&self.dataset)
+ .status()?
+ .success()
+ .then(|| ())
+ .ok_or_else(|| Error::FileSystem(format!("Failed to unmount: {:?}", self)))
+ }
}
diff --git a/zone_zfs/src/lib.rs b/zone_zfs/src/lib.rs
index fc23bf8..410f474 100644
--- a/zone_zfs/src/lib.rs
+++ b/zone_zfs/src/lib.rs
@@ -1,8 +1,9 @@
-pub use config::Config;
-pub use error::{Error, Result};
-pub use file_system::FileSystem;
-pub use snapshot::Snapshot;
-pub use zfs::ZFS;
+pub use crate::config::Config;
+pub use crate::error::{Error, Result};
+pub use crate::file_system::FileSystem;
+pub use crate::file_system::FileSystemBuilder;
+pub use crate::snapshot::Snapshot;
+pub use crate::zfs::ZFS;
mod config;
mod error;
diff --git a/zone_zfs/src/snapshot.rs b/zone_zfs/src/snapshot.rs
index e28182c..2be77a3 100644
--- a/zone_zfs/src/snapshot.rs
+++ b/zone_zfs/src/snapshot.rs
@@ -1,51 +1,39 @@
use crate::{Error, FileSystem, Result};
-use chrono::{DateTime, Utc};
-use std::{ffi::OsString, path::PathBuf, process::Command};
-use tracing::warn;
+use std::{ffi::OsString, path::PathBuf};
#[derive(Debug)]
pub struct Snapshot {
name: OsString,
- file_system: FileSystem,
- pub(crate) timestamp: DateTime<Utc>,
+ dataset: PathBuf,
}
impl TryFrom<&str> for Snapshot {
type Error = Error;
- fn try_from(value: &str) -> Result<Self> {
- match value.split('@').collect::<Vec<&str>>()[..] {
- [file_system, name] => Ok(Snapshot {
- file_system: FileSystem::try_from(file_system)?,
- name: file_system.into(),
- timestamp: name.parse().unwrap_or_else(|_err| {
- warn!(
- "Failed to parse timestamp from `{}`, using default value.",
- value
- );
- chrono::MIN_DATETIME
- }),
- }),
- _ => Err(Error::Snapshot(format!(
- "Failed to parse snapshot: {:?}",
- value
- ))),
- }
+ fn try_from(s: &str) -> Result<Self> {
+ s.split_once('@')
+ .map(|(d, n)| Snapshot {
+ name: n.into(),
+ dataset: d.into(),
+ })
+ .ok_or_else(|| Error::Snapshot(format!("Failed to parse snapshot: {:?}", s)))
}
}
impl Snapshot {
- pub fn clone_into_file_system(&self, new_fs: PathBuf) -> Result<FileSystem> {
- let mut file_system = self.file_system.clone();
- file_system.value.push(&new_fs);
+ pub fn clone_new(&self, path: PathBuf) -> Result<FileSystem> {
+ FileSystem::builder()
+ .dataset(&self.dataset.join(path))
+ .build_from(self.to_owned())
+ }
+
+ /// Get a reference to the snapshot's name.
+ pub fn name(&self) -> &OsString {
+ &self.name
+ }
- Command::new("zfs")
- .arg("clone")
- .arg(&self.name)
- .arg(&new_fs)
- .status()?
- .success()
- .then(|| file_system)
- .ok_or_else(|| Error::Snapshot(format!("Failed to clone snapshot: {:?}", self)))
+ /// Get a reference to the snapshot's dataset.
+ pub fn dataset(&self) -> &PathBuf {
+ &self.dataset
}
}
diff --git a/zone_zfs/src/zfs.rs b/zone_zfs/src/zfs.rs
index c820c1c..0ffba13 100644
--- a/zone_zfs/src/zfs.rs
+++ b/zone_zfs/src/zfs.rs
@@ -20,15 +20,11 @@ impl ZFS {
.map(|config| Self { config })
}
- pub fn clone_from_latest(&self, name: PathBuf, parent: PathBuf) -> Result<FileSystem> {
- Self::get_file_system(parent)?
- .get_latest_snapshot()?
- .ok_or_else(|| Error::Snapshot("No snapshot found".into()))?
- .clone_into_file_system(name)?
- .set_quota(self.config.quota)
+ pub fn clone_from_latest(&self, _name: PathBuf, _parent: PathBuf) -> Result<FileSystem> {
+ todo!("implement using feature")
}
- pub(super) fn get_file_systems() -> Result<Vec<FileSystem>> {
+ pub fn get_file_systems(&self) -> Result<Vec<FileSystem>> {
Command::new("zfs")
.arg("list")
.arg("-H")
@@ -36,16 +32,32 @@ impl ZFS {
.arg("name")
.output()
.map(|o| String::from_utf8(o.stdout))?
- .map_err(Error::from)?
- .split_whitespace()
- .map(FileSystem::try_from)
- .collect()
+ .map_err(Error::from)
+ .map(|s| {
+ s.lines()
+ .into_iter()
+ .filter_map(|l| l.split_once(" ").map(|s| (s.0.trim(), s.1.trim())))
+ .filter_map(|(dataset, mountpoint)| {
+ let mountpoint = if mountpoint == "none" {
+ PathBuf::from(mountpoint)
+ } else {
+ self.config.mountpoint.to_owned()
+ };
+
+ FileSystem::builder()
+ .mountpoint(mountpoint)
+ .dataset(dataset)
+ .to_file_system()
+ .ok()
+ })
+ .collect()
+ })
}
- fn get_file_system(name: PathBuf) -> Result<FileSystem> {
- Self::get_file_systems()?
+ pub fn get_file_system(&self, name: PathBuf) -> Result<FileSystem> {
+ self.get_file_systems()?
.into_iter()
- .find(|fs| PathBuf::from(&fs.value).ends_with(&name))
+ .find(|fs| fs.dataset().ends_with(&name))
.ok_or_else(|| Error::FileSystem("No file system found".into()))
}
}
@@ -55,6 +67,6 @@ mod tests {
#[test]
fn zfs_list() {
use super::*;
- assert!(ZFS::get_file_systems().is_ok());
+ assert!(ZFS::new().unwrap().get_file_systems().is_ok());
}
}