1use std::{os::unix::io::AsRawFd, path::PathBuf};
2
3use bitflags::bitflags;
4
5pub mod description;
6pub use description::DatasetKind;
7
8pub mod delegating;
9pub use delegating::DelegatingZfsEngine;
10pub mod open3;
11pub use open3::ZfsOpen3;
12
13pub mod lzc;
14use crate::zfs::properties::{AclInheritMode, AclMode};
15pub use lzc::ZfsLzc;
16use std::collections::HashMap;
17
18pub mod properties;
19pub use properties::{
20 CacheMode, CanMount, Checksum, Compression, Copies, FilesystemProperties, Properties, SnapDir,
21 VolumeProperties,
22};
23
24mod pathext;
25pub use pathext::PathExt;
26
27pub static DATASET_NAME_MAX_LENGTH: usize = 255;
28
29mod errors;
30
31pub use errors::{Error, ErrorKind, Result, ValidationError, ValidationResult};
32
33#[derive(Clone, PartialEq, Eq, Debug)]
36pub enum DestroyTiming {
37 RightNow,
40 Defer,
43}
44
45impl DestroyTiming {
46 pub fn as_c_uint(&self) -> std::os::raw::c_uint {
47 match self {
48 DestroyTiming::Defer => 1,
49 DestroyTiming::RightNow => 0,
50 }
51 }
52}
53
54pub struct BookmarkRequest {
55 pub snapshot: PathBuf,
56 pub bookmark: PathBuf,
57}
58
59impl BookmarkRequest {
60 pub fn new(snapshot: PathBuf, bookmark: PathBuf) -> Self {
61 BookmarkRequest { snapshot, bookmark }
62 }
63}
64
65bitflags! {
66 #[derive(Default)]
67 pub struct SendFlags: u32 {
68 const LZC_SEND_FLAG_EMBED_DATA = 1 << 0;
69 const LZC_SEND_FLAG_LARGE_BLOCK = 1 << 1;
70 const LZC_SEND_FLAG_COMPRESS = 1 << 2;
71 const LZC_SEND_FLAG_RAW = 1 << 3;
72 const LZC_SEND_FLAG_SAVED = 1 << 4;
73 }
74}
75pub trait ZfsEngine {
76 #[cfg_attr(tarpaulin, skip)]
81 fn exists<N: Into<PathBuf>>(&self, _name: N) -> Result<bool> {
82 Err(Error::Unimplemented)
83 }
84
85 #[cfg_attr(tarpaulin, skip)]
87 fn create(&self, _request: CreateDatasetRequest) -> Result<()> {
88 Err(Error::Unimplemented)
89 }
90
91 #[cfg_attr(tarpaulin, skip)]
93 fn snapshot(
94 &self,
95 _snapshots: &[PathBuf],
96 _user_properties: Option<HashMap<String, String>>,
97 ) -> Result<()> {
98 Err(Error::Unimplemented)
99 }
100
101 #[cfg_attr(tarpaulin, skip)]
103 fn bookmark(&self, _snapshots: &[BookmarkRequest]) -> Result<()> {
104 Err(Error::Unimplemented)
105 }
106
107 #[cfg_attr(tarpaulin, skip)]
110 fn destroy<N: Into<PathBuf>>(&self, _name: N) -> Result<()> {
111 Err(Error::Unimplemented)
112 }
113
114 #[cfg_attr(tarpaulin, skip)]
116 fn destroy_snapshots(&self, _snapshots: &[PathBuf], _timing: DestroyTiming) -> Result<()> {
117 Err(Error::Unimplemented)
118 }
119
120 #[cfg_attr(tarpaulin, skip)]
122 fn destroy_bookmarks(&self, _bookmarks: &[PathBuf]) -> Result<()> {
123 Err(Error::Unimplemented)
124 }
125
126 #[cfg_attr(tarpaulin, skip)]
127 fn list<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<(DatasetKind, PathBuf)>> {
128 Err(Error::Unimplemented)
129 }
130 #[cfg_attr(tarpaulin, skip)]
131 fn list_filesystems<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<PathBuf>> {
132 Err(Error::Unimplemented)
133 }
134 #[cfg_attr(tarpaulin, skip)]
135 fn list_snapshots<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<PathBuf>> {
136 Err(Error::Unimplemented)
137 }
138 #[cfg_attr(tarpaulin, skip)]
139 fn list_bookmarks<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<PathBuf>> {
140 Err(Error::Unimplemented)
141 }
142 #[cfg_attr(tarpaulin, skip)]
143 fn list_volumes<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<PathBuf>> {
144 Err(Error::Unimplemented)
145 }
146 #[cfg_attr(tarpaulin, skip)]
148 fn read_properties<N: Into<PathBuf>>(&self, _path: N) -> Result<Properties> {
149 Err(Error::Unimplemented)
150 }
151
152 #[cfg_attr(tarpaulin, skip)]
154 fn send_full<N: Into<PathBuf>, FD: AsRawFd>(
155 &self,
156 _path: N,
157 _fd: FD,
158 _flags: SendFlags,
159 ) -> Result<()> {
160 Err(Error::Unimplemented)
161 }
162
163 #[cfg_attr(tarpaulin, skip)]
165 fn send_incremental<N: Into<PathBuf>, F: Into<PathBuf>, FD: AsRawFd>(
166 &self,
167 _path: N,
168 _from: F,
169 _fd: FD,
170 _flags: SendFlags,
171 ) -> Result<()> {
172 Err(Error::Unimplemented)
173 }
174
175 #[cfg_attr(tarpaulin, skip)]
177 fn run_channel_program<N: Into<PathBuf>>(
178 &self,
179 _pool: N,
180 _program: &str,
181 _instr_limit: u64,
182 _mem_limit: u64,
183 _sync: bool,
184 _args: libnv::nvpair::NvList,
185 ) -> Result<libnv::nvpair::NvList> {
186 Err(Error::Unimplemented)
187 }
188}
189
190#[derive(Default, Builder, Debug, Clone, Getters)]
191#[builder(setter(into))]
192#[get = "pub"]
193pub struct CreateDatasetRequest {
196 name: PathBuf,
198 kind: DatasetKind,
200 #[builder(default)]
208 user_properties: Option<HashMap<String, String>>,
209
210 #[builder(default)]
214 acl_inherit: Option<AclInheritMode>,
215 #[builder(default)]
217 acl_mode: Option<AclMode>,
218 #[builder(default)]
220 atime: Option<bool>,
221 #[builder(default)]
223 can_mount: CanMount,
224 #[builder(default)]
226 checksum: Option<Checksum>,
227 #[builder(default)]
229 compression: Option<Compression>,
230 #[builder(default)]
237 copies: Option<Copies>,
238 #[builder(default)]
240 devices: Option<bool>,
241 #[builder(default)]
244 exec: Option<bool>,
245 #[builder(default)]
247 mount_point: Option<PathBuf>,
248 #[builder(default)]
250 primary_cache: Option<CacheMode>,
251 #[builder(default)]
253 quota: Option<u64>,
254 #[builder(default)]
256 readonly: Option<bool>,
257 #[builder(default)]
261 record_size: Option<u64>,
262 #[builder(default)]
266 ref_quota: Option<u64>,
267 #[builder(default)]
270 ref_reservation: Option<u64>,
271 #[builder(default)]
273 reservation: Option<u64>,
274 #[builder(default)]
276 secondary_cache: Option<CacheMode>,
277 #[builder(default)]
279 setuid: Option<bool>,
280 #[builder(default)]
282 snap_dir: Option<SnapDir>,
283 #[builder(default)]
285 volume_size: Option<u64>,
286 #[builder(default)]
291 volume_block_size: Option<u64>,
292 #[builder(default)]
294 xattr: Option<bool>,
295}
296
297impl CreateDatasetRequest {
298 pub fn builder() -> CreateDatasetRequestBuilder {
299 CreateDatasetRequestBuilder::default()
300 }
301
302 pub fn validate(&self) -> Result<()> {
303 let mut errors = Vec::new();
304
305 if let Err(e) = validators::validate_name(self.name()) {
306 errors.push(e);
307 }
308
309 if errors.is_empty() {
310 Ok(())
311 } else {
312 Err(errors.into())
313 }
314 }
315}
316
317pub(crate) mod validators {
318 use crate::zfs::{errors::ValidationResult, ValidationError, DATASET_NAME_MAX_LENGTH};
319 use std::path::Path;
320
321 pub fn validate_name<P: AsRef<Path>>(dataset: P) -> ValidationResult {
322 _validate_name(dataset.as_ref())
323 }
324
325 pub fn _validate_name(dataset: &Path) -> ValidationResult {
326 let name = dataset.to_string_lossy();
327 if name.ends_with('/') {
328 return Err(ValidationError::MissingName(dataset.to_owned()));
329 }
330 if dataset.has_root() {
331 return Err(ValidationError::MissingPool(dataset.to_owned()));
332 }
333 dataset
334 .file_name()
335 .ok_or_else(|| ValidationError::MissingName(dataset.to_owned()))
336 .and_then(|name| {
337 if name.len() > DATASET_NAME_MAX_LENGTH {
338 return Err(ValidationError::NameTooLong(dataset.to_owned()));
339 }
340 Ok(())
341 })
342 }
343}
344
345#[cfg(test)]
346mod test {
347 use super::{CreateDatasetRequest, DatasetKind, Error, ErrorKind, ValidationError};
348 use std::path::PathBuf;
349
350 #[test]
351 fn test_error_ds_not_found() {
352 let stderr = b"cannot open 's/asd/asd': dataset does not exist";
353
354 let err = Error::from_stderr(stderr);
355 assert_eq!(Error::DatasetNotFound(PathBuf::from("s/asd/asd")), err);
356 assert_eq!(ErrorKind::DatasetNotFound, err.kind());
357 }
358
359 #[test]
360 fn test_error_rubbish() {
361 let stderr = b"there is no way there is an error like this";
362 let stderr_string = String::from_utf8_lossy(stderr).to_string();
363
364 let err = Error::from_stderr(stderr);
365 assert_eq!(Error::UnknownSoFar(stderr_string), err);
366 assert_eq!(ErrorKind::Unknown, err.kind());
367 }
368
369 #[test]
370 fn test_name_validator() {
371 let path = PathBuf::from("z/asd/");
372 let request = CreateDatasetRequest::builder()
373 .name(path.clone())
374 .kind(DatasetKind::Filesystem)
375 .build()
376 .unwrap();
377
378 let result = request.validate().unwrap_err();
379 let expected = Error::from(vec![ValidationError::MissingName(path.clone())]);
380 assert_eq!(expected, result);
381
382 let path = PathBuf::from("z/asd/jnmgyfklueiodyfryvopvyfidvdgxqxsesjmqeoevdgmzsqmesuqzqoxhjfltmsvltdyiilgkvklinlfhaanfqisdazjpfmwttnuosdfijickudhwegburxsoesvunamysaigtagymxcyfeyqiqphtalmbkskrjdndbbcjqiiwucsxzezqmvpzmkylrojumtvatfvrpfkxubfujyioyylmffvrvtfetnzghkwaqzxkqmialkaaekotuhgiivwvbsoqqa");
383 let request = CreateDatasetRequest::builder()
384 .name(path.clone())
385 .kind(DatasetKind::Filesystem)
386 .build()
387 .unwrap();
388
389 let result = request.validate().unwrap_err();
390 let expected = Error::from(vec![ValidationError::NameTooLong(path.clone())]);
391 assert_eq!(expected, result);
392 }
393}