diff options
author | Toby Vincent <tobyv13@gmail.com> | 2022-02-22 22:19:57 -0600 |
---|---|---|
committer | Toby Vincent <tobyv13@gmail.com> | 2022-02-22 22:19:57 -0600 |
commit | f4d54562e5c4f7c61faadfe13701286cce1e3d05 (patch) | |
tree | f0056da257964b8169a8e149cf660e3069222dd5 /zone_zfs/src/file_system.rs | |
parent | 1ea68976f452a35b7aa20119c3eeb8ae14de107e (diff) |
refactor(zfs): use builder pattern for FileSystem
Refactor FileSystem to use a builder pattern to create a zfs dataset
Fix #26
Diffstat (limited to 'zone_zfs/src/file_system.rs')
-rw-r--r-- | zone_zfs/src/file_system.rs | 171 |
1 files changed, 107 insertions, 64 deletions
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))) + } } |