#![cfg(target_os = "linux")]
#![deny(warnings)]
#![deny(missing_docs)]
mod api;
mod driver;
pub use api::server::run_daemon;
use driver::{btrfs, external, zfs};
use ruc::{cmd, *};
use std::{fmt, result::Result as StdResult, str::FromStr};
pub const CAP_MAX: u64 = 4096;
pub const STEP_CNT: usize = 10;
#[derive(Clone, Debug)]
pub struct BtmCfg {
pub itv: u64,
pub cap: u64,
pub cap_clean_kept: usize,
pub mode: SnapMode,
pub algo: SnapAlgo,
pub volume: String,
}
impl BtmCfg {
fn check(&self) -> Result<()> {
self.itv.checked_pow(STEP_CNT as u32).c(d!()).map(|_| ())
}
#[inline(always)]
pub fn new(volume: &str, mode: Option<&str>) -> Result<Self> {
let mode = if let Some(m) = mode {
SnapMode::from_str(m).map_err(|e| eg!(e))?
} else {
SnapMode::guess(volume).c(d!())?
};
Ok(Self {
itv: 10,
cap: 100,
cap_clean_kept: 0,
mode,
algo: SnapAlgo::Fair,
volume: volume.to_owned(),
})
}
#[inline(always)]
pub fn snapshot(&self, idx: u64) -> Result<()> {
nix::unistd::sync();
match self.mode {
SnapMode::Zfs => zfs::gen_snapshot(self, idx).c(d!()),
SnapMode::Btrfs => btrfs::gen_snapshot(self, idx).c(d!()),
SnapMode::External => external::gen_snapshot(self, idx).c(d!()),
}
}
#[inline(always)]
pub fn rollback(&self, idx: Option<i128>, strict: bool) -> Result<()> {
match self.mode {
SnapMode::Zfs => zfs::rollback(self, idx, strict).c(d!()),
SnapMode::Btrfs => btrfs::rollback(self, idx, strict).c(d!()),
SnapMode::External => Err(eg!("please use the `btm` tool in `External` mode")),
}
}
#[inline(always)]
pub fn get_sorted_snapshots(&self) -> Result<Vec<u64>> {
match self.mode {
SnapMode::Zfs => zfs::sorted_snapshots(self).c(d!()),
SnapMode::Btrfs => btrfs::sorted_snapshots(self).c(d!()),
SnapMode::External => Err(eg!("please use `btm` tool in `External` mode")),
}
}
#[inline(always)]
fn get_cap(&self) -> u64 {
alt!(self.cap > CAP_MAX, CAP_MAX, self.cap)
}
pub fn list_snapshots(&self) -> Result<()> {
println!("Available snapshots are listed below:");
self.get_sorted_snapshots().c(d!()).map(|list| {
list.into_iter().rev().for_each(|h| {
println!(" {}", h);
})
})
}
pub fn clean_snapshots(&self) -> Result<()> {
self.get_sorted_snapshots().c(d!()).map(|list| {
list.into_iter()
.skip(self.cap_clean_kept)
.rev()
.for_each(|height| {
let cmd = match self.mode {
SnapMode::Btrfs => {
format!("btrfs subvolume delete {}@{}", &self.volume, height)
}
SnapMode::Zfs => format!("zfs destroy {}@{}", &self.volume, height),
_ => pnk!(Err(eg!("Unsupported deriver"))),
};
info_omit!(cmd::exec_output(&cmd));
});
})
}
}
#[derive(Clone, Copy, Debug)]
pub enum SnapMode {
Zfs,
Btrfs,
External,
}
impl SnapMode {
#[inline(always)]
#[allow(missing_docs)]
pub fn from_string(m: &str) -> Result<Self> {
match m.to_lowercase().as_str() {
"zfs" => Ok(Self::Zfs),
"btrfs" => Ok(Self::Btrfs),
"external" => Ok(Self::External),
_ => Err(eg!()),
}
}
pub fn guess(volume: &str) -> Result<Self> {
zfs::check(volume)
.c(d!())
.map(|_| SnapMode::Zfs)
.or_else(|e| btrfs::check(volume).c(d!(e)).map(|_| SnapMode::Btrfs))
}
}
impl Default for SnapMode {
fn default() -> Self {
Self::External
}
}
impl fmt::Display for SnapMode {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let contents = match self {
Self::Zfs => "Zfs",
Self::Btrfs => "Btrfs",
Self::External => "External",
};
write!(f, "{}", contents)
}
}
impl FromStr for SnapMode {
type Err = String;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
Self::from_string(s).c(d!()).map_err(|e| e.to_string())
}
}
#[derive(Clone, Copy, Debug)]
pub enum SnapAlgo {
Fair,
Fade,
}
impl SnapAlgo {
#[inline(always)]
#[allow(missing_docs)]
pub fn from_string(m: &str) -> Result<Self> {
match m.to_lowercase().as_str() {
"fair" => Ok(Self::Fair),
"fade" => Ok(Self::Fade),
_ => Err(eg!()),
}
}
}
impl Default for SnapAlgo {
fn default() -> Self {
Self::Fair
}
}
impl fmt::Display for SnapAlgo {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let contents = match self {
Self::Fair => "Fair",
Self::Fade => "Fade",
};
write!(f, "{}", contents)
}
}
impl FromStr for SnapAlgo {
type Err = String;
fn from_str(s: &str) -> StdResult<Self, Self::Err> {
Self::from_string(s).c(d!()).map_err(|e| e.to_string())
}
}