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 clap::Parser;
19use driver::{btrfs, external, zfs};
20use ovr_ruc::{cmd, *};
21use std::{fmt, result::Result as StdResult, str::FromStr};
22
23pub const CAP_MAX: u64 = 4096;
25
26pub const STEP_CNT: usize = 10;
28
29pub const ENV_VAR_BTM_VOLUME: &str = "BTM_VOLUME";
31
32#[derive(Clone, Debug, Parser)]
34pub struct BtmCfg {
35 #[clap(long)]
37 pub enable: bool,
38 #[clap(short, long, default_value_t = 10)]
40 pub itv: u64,
41 #[clap(short, long, default_value_t = 100)]
43 pub cap: u64,
44 #[clap(short, long, default_value_t = SnapMode::Zfs)]
46 pub mode: SnapMode,
47 #[clap(short, long, default_value_t = SnapAlgo::Fair)]
49 pub algo: SnapAlgo,
50 #[clap(short = 'p', long, default_value_t = String::from("zfs/data"))]
52 pub volume: String,
53}
54
55impl Default for BtmCfg {
56 fn default() -> Self {
57 BtmCfg {
58 enable: false,
59 itv: 10,
60 cap: 100,
61 mode: SnapMode::Zfs,
62 algo: SnapAlgo::Fair,
63 volume: "zfs/data".to_owned(),
64 }
65 }
66}
67
68impl BtmCfg {
69 #[inline(always)]
71 pub fn new() -> Self {
72 Self::new_enabled()
73 }
74
75 #[inline(always)]
76 fn new_enabled() -> Self {
77 BtmCfg {
78 enable: true,
79 ..Self::default()
80 }
81 }
82
83 #[inline(always)]
85 pub fn new_client_hdr() -> Self {
86 Self::new_enabled()
87 }
88
89 #[inline(always)]
91 pub fn snapshot(&self, idx: u64) -> Result<()> {
92 alt!(!self.enable, return Ok(()));
93
94 nix::unistd::sync();
96
97 match self.mode {
98 SnapMode::Zfs => zfs::gen_snapshot(self, idx).c(d!()),
99 SnapMode::Btrfs => btrfs::gen_snapshot(self, idx).c(d!()),
100 SnapMode::External => external::gen_snapshot(self, idx).c(d!()),
101 }
102 }
103
104 #[inline(always)]
106 pub fn rollback(&self, idx: Option<u64>, strict: bool) -> Result<()> {
107 match self.mode {
108 SnapMode::Zfs => zfs::rollback(self, idx, strict).c(d!()),
109 SnapMode::Btrfs => btrfs::rollback(self, idx, strict).c(d!()),
110 SnapMode::External => Err(eg!("please use `btm` tool in `External` mode")),
111 }
112 }
113
114 #[inline(always)]
116 pub fn get_sorted_snapshots(&self) -> Result<Vec<u64>> {
117 match self.mode {
118 SnapMode::Zfs => zfs::sorted_snapshots(self).c(d!()),
119 SnapMode::Btrfs => btrfs::sorted_snapshots(self).c(d!()),
120 SnapMode::External => Err(eg!("please use `btm` tool in `External` mode")),
121 }
122 }
123
124 #[inline(always)]
127 pub fn guess_mode(volume: &str) -> Result<SnapMode> {
128 zfs::check(volume)
129 .c(d!())
130 .map(|_| SnapMode::Zfs)
131 .or_else(|e| btrfs::check(volume).c(d!(e)).map(|_| SnapMode::Btrfs))
132 }
133
134 #[inline(always)]
135 fn get_cap(&self) -> u64 {
136 alt!(self.cap > CAP_MAX, CAP_MAX, self.cap)
137 }
138
139 pub fn list_snapshots(&self) -> Result<()> {
141 println!("Available snapshots are listed below:");
142 self.get_sorted_snapshots().c(d!()).map(|list| {
143 list.into_iter().rev().for_each(|h| {
144 println!(" {}", h);
145 })
146 })
147 }
148
149 pub fn clean_snapshots(&self) -> Result<()> {
151 self.get_sorted_snapshots().c(d!()).map(|list| {
152 list.into_iter().rev().for_each(|height| {
153 let cmd = match self.mode {
154 SnapMode::Btrfs => {
155 format!("btrfs subvolume delete {}@{}", &self.volume, height)
156 }
157 SnapMode::Zfs => format!("zfs destroy {}@{}", &self.volume, height),
158 _ => pnk!(Err(eg!("Unsupported deriver"))),
159 };
160 info_omit!(cmd::exec_output(&cmd));
161 });
162 })
163 }
164}
165
166#[derive(Clone, Copy, Debug)]
197pub enum SnapMode {
198 Zfs,
204 Btrfs,
207 External,
210}
211
212impl SnapMode {
213 #[inline(always)]
214 #[allow(missing_docs)]
215 pub fn from_string(m: &str) -> Result<Self> {
216 match m.to_lowercase().as_str() {
217 "zfs" => Ok(Self::Zfs),
218 "btrfs" => Ok(Self::Btrfs),
219 "external" => Ok(Self::External),
220 _ => Err(eg!()),
221 }
222 }
223}
224
225impl Default for SnapMode {
226 fn default() -> Self {
227 Self::Zfs
228 }
229}
230
231impl fmt::Display for SnapMode {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 let contents = match self {
234 Self::Zfs => "Zfs",
235 Self::Btrfs => "Btrfs",
236 Self::External => "External",
237 };
238 write!(f, "{}", contents)
239 }
240}
241
242impl FromStr for SnapMode {
243 type Err = String;
244 fn from_str(s: &str) -> StdResult<Self, Self::Err> {
245 Self::from_string(s).c(d!()).map_err(|e| e.to_string())
246 }
247}
248
249#[derive(Clone, Copy, Debug)]
251pub enum SnapAlgo {
252 Fair,
254 Fade,
256}
257
258impl SnapAlgo {
259 #[inline(always)]
260 #[allow(missing_docs)]
261 pub fn from_string(m: &str) -> Result<Self> {
262 match m.to_lowercase().as_str() {
263 "fair" => Ok(Self::Fair),
264 "fade" => Ok(Self::Fade),
265 _ => Err(eg!()),
266 }
267 }
268}
269
270impl Default for SnapAlgo {
271 fn default() -> Self {
272 Self::Fair
273 }
274}
275
276impl fmt::Display for SnapAlgo {
277 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278 let contents = match self {
279 Self::Fair => "Fair",
280 Self::Fade => "Fade",
281 };
282 write!(f, "{}", contents)
283 }
284}
285
286impl FromStr for SnapAlgo {
287 type Err = String;
288 fn from_str(s: &str) -> StdResult<Self, Self::Err> {
289 Self::from_string(s).c(d!()).map_err(|e| e.to_string())
290 }
291}