Skip to main content

libzetta/zfs/
mod.rs

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/// Whether to mark busy snapshots for deferred destruction rather than immediately failing if can't
34/// be destroyed right now.
35#[derive(Clone, PartialEq, Eq, Debug)]
36pub enum DestroyTiming {
37    /// If a snapshot has user holds or clones, destroy operation will fail and none of the
38    /// snapshots will be destroyed.
39    RightNow,
40    /// If a snapshot has user holds or clones, it will be marked for deferred destruction, and
41    /// will be destroyed when the last hold or clone is removed/destroyed.
42    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    /// Check if a dataset (a filesystem, or a volume, or a snapshot with the given name exists.
77    ///
78    /// NOTE: Can't be used to check for existence of bookmarks.
79    ///  * `name` - The dataset name to check.
80    #[cfg_attr(tarpaulin, skip)]
81    fn exists<N: Into<PathBuf>>(&self, _name: N) -> Result<bool> {
82        Err(Error::Unimplemented)
83    }
84
85    /// Create a new dataset.
86    #[cfg_attr(tarpaulin, skip)]
87    fn create(&self, _request: CreateDatasetRequest) -> Result<()> {
88        Err(Error::Unimplemented)
89    }
90
91    /// Create snapshots as one atomic operation.
92    #[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    /// Create bookmarks as one atomic operation.
102    #[cfg_attr(tarpaulin, skip)]
103    fn bookmark(&self, _snapshots: &[BookmarkRequest]) -> Result<()> {
104        Err(Error::Unimplemented)
105    }
106
107    /// Deletes the dataset
108    /// Deletes the dataset
109    #[cfg_attr(tarpaulin, skip)]
110    fn destroy<N: Into<PathBuf>>(&self, _name: N) -> Result<()> {
111        Err(Error::Unimplemented)
112    }
113
114    /// Delete snapshots as one atomic operation
115    #[cfg_attr(tarpaulin, skip)]
116    fn destroy_snapshots(&self, _snapshots: &[PathBuf], _timing: DestroyTiming) -> Result<()> {
117        Err(Error::Unimplemented)
118    }
119
120    /// Delete bookmarks as one atomic operation
121    #[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    /// Read all properties of filesystem/volume/snapshot/bookmark.
147    #[cfg_attr(tarpaulin, skip)]
148    fn read_properties<N: Into<PathBuf>>(&self, _path: N) -> Result<Properties> {
149        Err(Error::Unimplemented)
150    }
151
152    /// Send a full snapshot to a specified file descriptor.
153    #[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    /// Send an incremental snapshot to a specified file descriptor.
164    #[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    /// Run a channel program
176    #[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"]
193/// Consumer friendly builder for NvPair. Use this to create your datasets. Some properties only
194/// work on filesystems, some only on volumes.
195pub struct CreateDatasetRequest {
196    /// Name of the dataset. First crumb of path is name of zpool.
197    name: PathBuf,
198    /// Filesystem or Volume.
199    kind: DatasetKind,
200    /// Optional user defined properties. User property names must conform to the following
201    /// characteristics:
202    ///
203    ///  - Contain a colon (':') character to distinguish them from native properties.
204    ///  - Contain lowercase letters, numbers, and the following punctuation characters: ':',
205    ///    '+','.', '_'.
206    ///  - Maximum user property name is 256 characters.
207    #[builder(default)]
208    user_properties: Option<HashMap<String, String>>,
209
210    //
211    // the rest is zfs native properties
212    /// Controls how ACL entries inherited when files and directories created.
213    #[builder(default)]
214    acl_inherit: Option<AclInheritMode>,
215    /// Controls how an ACL entry modified during a `chmod` operation.
216    #[builder(default)]
217    acl_mode: Option<AclMode>,
218    /// Controls whether the access time for files updated when they are read.
219    #[builder(default)]
220    atime: Option<bool>,
221    /// Controls whether a file system can be mounted.
222    #[builder(default)]
223    can_mount: CanMount,
224    /// Controls the checksum used to verify data integrity.
225    #[builder(default)]
226    checksum: Option<Checksum>,
227    /// Enables or disables compression for a dataset.
228    #[builder(default)]
229    compression: Option<Compression>,
230    /// Sets the number of copies of user data per file system. Available values are 1, 2, or 3.
231    /// These copies are in addition to any pool-level redundancy. Disk space used by multiple
232    /// copies of user data charged to the corresponding file and dataset, and counts against
233    /// quotas and reservations. In addition, the used property updated when multiple copies
234    /// enabled. Consider setting this property when the file system created because changing this
235    /// property on an existing file system only affects newly written data.
236    #[builder(default)]
237    copies: Option<Copies>,
238    /// Controls whether device files in a file system can be opened.
239    #[builder(default)]
240    devices: Option<bool>,
241    /// Controls whether programs in a file system allowed to be executed. Also, when set to
242    /// `false`, `mmap(2)` calls with `PROT_EXEC` disallowed.
243    #[builder(default)]
244    exec: Option<bool>,
245    /// Controls the mount point used for this file system.
246    #[builder(default)]
247    mount_point: Option<PathBuf>,
248    /// Controls what is cached in the primary cache (ARC).
249    #[builder(default)]
250    primary_cache: Option<CacheMode>,
251    /// Limits the amount of disk space a dataset and its descendants can consume.
252    #[builder(default)]
253    quota: Option<u64>,
254    /// Controls whether a dataset can be modified.
255    #[builder(default)]
256    readonly: Option<bool>,
257    /// Specifies a suggested block size for files in a file system in bytes. The size specified
258    /// must be a power of two greater than or equal to 512 and less than or equal to 128 KiB.
259    /// If the large_blocks feature is enabled on the pool, the size may be up to 1 MiB.
260    #[builder(default)]
261    record_size: Option<u64>,
262    /// Sets the amount of disk space a dataset can consume. This property enforces a hard limit on
263    /// the amount of space used. This hard limit does not include disk space used by descendents,
264    /// such as snapshots and clones.
265    #[builder(default)]
266    ref_quota: Option<u64>,
267    /// Sets the minimum amount of disk space is guaranteed to a dataset, not including
268    /// descendants, such as snapshots and clones.
269    #[builder(default)]
270    ref_reservation: Option<u64>,
271    /// Sets the minimum amount of disk space guaranteed to a dataset and its descendants.
272    #[builder(default)]
273    reservation: Option<u64>,
274    /// Controls what is cached in the secondary cache (L2ARC).
275    #[builder(default)]
276    secondary_cache: Option<CacheMode>,
277    /// Controls whether the `setuid` bit is honored in a file system.
278    #[builder(default)]
279    setuid: Option<bool>,
280    /// Controls whether the .zfs directory is hidden or visible in the root of the file system
281    #[builder(default)]
282    snap_dir: Option<SnapDir>,
283    /// For volumes, specifies the logical size of the volume.
284    #[builder(default)]
285    volume_size: Option<u64>,
286    /// For volumes, specifies the block size of the volume in bytes. The block size cannot be
287    /// changed after the volume has been written, so set the block size at volume creation time.
288    /// The default block size for volumes is 8 KB. Any power of 2 from 512 bytes to 128 KB is
289    /// valid.
290    #[builder(default)]
291    volume_block_size: Option<u64>,
292    /// Indicates whether extended attributes are enabled or disabled.
293    #[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}