1#![cfg(target_os = "linux")]
10#![deny(warnings)]
11#![deny(missing_docs)]
12
13mod api;
14mod driver;
15
16pub use api::server::run_daemon;
17
18use driver::{btrfs, external, zfs};
19use ruc::{cmd, *};
20use std::{fmt, result::Result as StdResult, str::FromStr};
21
22pub const CAP_MAX: u64 = 4096;
24
25pub const STEP_CNT: usize = 10;
28
29#[derive(Clone, Debug)]
31pub struct BtmCfg {
32 pub itv: u64,
34 pub cap: u64,
36 pub cap_clean_kept: usize,
38 pub mode: SnapMode,
40 pub algo: SnapAlgo,
42 pub volume: String,
44}
45
46impl BtmCfg {
47 fn check(&self) -> Result<()> {
49 self.itv.checked_pow(STEP_CNT as u32).c(d!()).map(|_| ())
50 }
51
52 #[inline(always)]
54 pub fn new(volume: &str, mode: Option<&str>) -> Result<Self> {
55 let mode = if let Some(m) = mode {
56 SnapMode::from_str(m).map_err(|e| eg!(e))?
57 } else {
58 SnapMode::guess(volume).c(d!())?
59 };
60 Ok(Self {
61 itv: 10,
62 cap: 100,
63 cap_clean_kept: 0,
64 mode,
65 algo: SnapAlgo::Fair,
66 volume: volume.to_owned(),
67 })
68 }
69
70 #[inline(always)]
72 pub fn snapshot(&self, idx: u64) -> Result<()> {
73 nix::unistd::sync();
75
76 match self.mode {
77 SnapMode::Zfs => zfs::gen_snapshot(self, idx).c(d!()),
78 SnapMode::Btrfs => btrfs::gen_snapshot(self, idx).c(d!()),
79 SnapMode::External => external::gen_snapshot(self, idx).c(d!()),
80 }
81 }
82
83 #[inline(always)]
85 pub fn rollback(&self, idx: Option<i128>, strict: bool) -> Result<()> {
86 match self.mode {
87 SnapMode::Zfs => zfs::rollback(self, idx, strict).c(d!()),
88 SnapMode::Btrfs => btrfs::rollback(self, idx, strict).c(d!()),
89 SnapMode::External => Err(eg!("please use the `btm` tool in `External` mode")),
90 }
91 }
92
93 #[inline(always)]
95 pub fn get_sorted_snapshots(&self) -> Result<Vec<u64>> {
96 match self.mode {
97 SnapMode::Zfs => zfs::sorted_snapshots(self).c(d!()),
98 SnapMode::Btrfs => btrfs::sorted_snapshots(self).c(d!()),
99 SnapMode::External => Err(eg!("please use `btm` tool in `External` mode")),
100 }
101 }
102
103 #[inline(always)]
104 fn get_cap(&self) -> u64 {
105 alt!(self.cap > CAP_MAX, CAP_MAX, self.cap)
106 }
107
108 pub fn list_snapshots(&self) -> Result<()> {
110 println!("Available snapshots are listed below:");
111 self.get_sorted_snapshots().c(d!()).map(|list| {
112 list.into_iter().rev().for_each(|h| {
113 println!(" {}", h);
114 })
115 })
116 }
117
118 pub fn clean_snapshots(&self) -> Result<()> {
120 self.get_sorted_snapshots().c(d!()).map(|list| {
121 list.into_iter()
122 .skip(self.cap_clean_kept)
123 .rev()
124 .for_each(|height| {
125 let cmd = match self.mode {
126 SnapMode::Btrfs => {
127 format!("btrfs subvolume delete {}@{}", &self.volume, height)
128 }
129 SnapMode::Zfs => format!("zfs destroy {}@{}", &self.volume, height),
130 _ => pnk!(Err(eg!("Unsupported deriver"))),
131 };
132 info_omit!(cmd::exec_output(&cmd));
133 });
134 })
135 }
136}
137
138#[derive(Clone, Copy, Debug)]
169pub enum SnapMode {
170 Zfs,
176 Btrfs,
179 External,
181}
182
183impl SnapMode {
184 #[inline(always)]
185 #[allow(missing_docs)]
186 pub fn from_string(m: &str) -> Result<Self> {
187 match m.to_lowercase().as_str() {
188 "zfs" => Ok(Self::Zfs),
189 "btrfs" => Ok(Self::Btrfs),
190 "external" => Ok(Self::External),
191 _ => Err(eg!()),
192 }
193 }
194
195 pub fn guess(volume: &str) -> Result<Self> {
200 zfs::check(volume)
201 .c(d!())
202 .map(|_| SnapMode::Zfs)
203 .or_else(|e| btrfs::check(volume).c(d!(e)).map(|_| SnapMode::Btrfs))
204 }
205}
206
207impl Default for SnapMode {
208 fn default() -> Self {
209 Self::External
210 }
211}
212
213impl fmt::Display for SnapMode {
214 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
215 let contents = match self {
216 Self::Zfs => "Zfs",
217 Self::Btrfs => "Btrfs",
218 Self::External => "External",
219 };
220 write!(f, "{}", contents)
221 }
222}
223
224impl FromStr for SnapMode {
225 type Err = String;
226 fn from_str(s: &str) -> StdResult<Self, Self::Err> {
227 Self::from_string(s).c(d!()).map_err(|e| e.to_string())
228 }
229}
230
231#[derive(Clone, Copy, Debug)]
233pub enum SnapAlgo {
234 Fair,
236 Fade,
238}
239
240impl SnapAlgo {
241 #[inline(always)]
242 #[allow(missing_docs)]
243 pub fn from_string(m: &str) -> Result<Self> {
244 match m.to_lowercase().as_str() {
245 "fair" => Ok(Self::Fair),
246 "fade" => Ok(Self::Fade),
247 _ => Err(eg!()),
248 }
249 }
250}
251
252impl Default for SnapAlgo {
253 fn default() -> Self {
254 Self::Fair
255 }
256}
257
258impl fmt::Display for SnapAlgo {
259 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
260 let contents = match self {
261 Self::Fair => "Fair",
262 Self::Fade => "Fade",
263 };
264 write!(f, "{}", contents)
265 }
266}
267
268impl FromStr for SnapAlgo {
269 type Err = String;
270 fn from_str(s: &str) -> StdResult<Self, Self::Err> {
271 Self::from_string(s).c(d!()).map_err(|e| e.to_string())
272 }
273}