use std::{os::unix::io::AsRawFd, path::PathBuf};
use bitflags::bitflags;
pub mod description;
pub use description::DatasetKind;
pub mod delegating;
pub use delegating::DelegatingZfsEngine;
pub mod open3;
pub use open3::ZfsOpen3;
pub mod lzc;
use crate::zfs::properties::{AclInheritMode, AclMode};
pub use lzc::ZfsLzc;
use std::collections::HashMap;
pub mod properties;
pub use properties::{CacheMode, CanMount, Checksum, Compression, Copies, FilesystemProperties,
Properties, SnapDir, VolumeProperties};
mod pathext;
pub use pathext::PathExt;
pub static DATASET_NAME_MAX_LENGTH: usize = 255;
mod errors;
pub use errors::{Error, ErrorKind, Result, ValidationError, ValidationResult};
#[derive(Clone, PartialEq, Eq, Debug)]
pub enum DestroyTiming {
RightNow,
Defer,
}
impl DestroyTiming {
pub fn as_c_uint(&self) -> std::os::raw::c_uint {
match self {
DestroyTiming::Defer => 1,
DestroyTiming::RightNow => 0,
}
}
}
pub struct BookmarkRequest {
pub snapshot: PathBuf,
pub bookmark: PathBuf,
}
impl BookmarkRequest {
pub fn new(snapshot: PathBuf, bookmark: PathBuf) -> Self {
BookmarkRequest { snapshot, bookmark }
}
}
bitflags! {
#[derive(Default)]
pub struct SendFlags: u32 {
const LZC_SEND_FLAG_EMBED_DATA = 1 << 0;
const LZC_SEND_FLAG_LARGE_BLOCK = 1 << 1;
const LZC_SEND_FLAG_COMPRESS = 1 << 2;
const LZC_SEND_FLAG_RAW = 1 << 3;
const LZC_SEND_FLAG_SAVED = 1 << 4;
}
}
pub trait ZfsEngine {
#[cfg_attr(tarpaulin, skip)]
fn exists<N: Into<PathBuf>>(&self, _name: N) -> Result<bool> { Err(Error::Unimplemented) }
#[cfg_attr(tarpaulin, skip)]
fn create(&self, _request: CreateDatasetRequest) -> Result<()> { Err(Error::Unimplemented) }
#[cfg_attr(tarpaulin, skip)]
fn snapshot(
&self,
_snapshots: &[PathBuf],
_user_properties: Option<HashMap<String, String>>,
) -> Result<()> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn bookmark(&self, _snapshots: &[BookmarkRequest]) -> Result<()> { Err(Error::Unimplemented) }
#[cfg_attr(tarpaulin, skip)]
fn destroy<N: Into<PathBuf>>(&self, _name: N) -> Result<()> { Err(Error::Unimplemented) }
#[cfg_attr(tarpaulin, skip)]
fn destroy_snapshots(&self, _snapshots: &[PathBuf], _timing: DestroyTiming) -> Result<()> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn destroy_bookmarks(&self, _bookmarks: &[PathBuf]) -> Result<()> { Err(Error::Unimplemented) }
#[cfg_attr(tarpaulin, skip)]
fn list<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<(DatasetKind, PathBuf)>> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn list_filesystems<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<PathBuf>> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn list_snapshots<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<PathBuf>> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn list_bookmarks<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<PathBuf>> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn list_volumes<N: Into<PathBuf>>(&self, _pool: N) -> Result<Vec<PathBuf>> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn read_properties<N: Into<PathBuf>>(&self, _path: N) -> Result<Properties> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn send_full<N: Into<PathBuf>, FD: AsRawFd>(
&self,
_path: N,
_fd: FD,
_flags: SendFlags,
) -> Result<()> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn send_incremental<N: Into<PathBuf>, F: Into<PathBuf>, FD: AsRawFd>(
&self,
_path: N,
_from: F,
_fd: FD,
_flags: SendFlags,
) -> Result<()> {
Err(Error::Unimplemented)
}
#[cfg_attr(tarpaulin, skip)]
fn run_channel_program<N: Into<PathBuf>>(
&self,
_pool: N,
_program: &str,
_instr_limit: u64,
_mem_limit: u64,
_sync: bool,
_args: libnv::nvpair::NvList,
) -> Result<libnv::nvpair::NvList> {
Err(Error::Unimplemented)
}
}
#[derive(Default, Builder, Debug, Clone, Getters)]
#[builder(setter(into))]
#[get = "pub"]
pub struct CreateDatasetRequest {
name: PathBuf,
kind: DatasetKind,
#[builder(default)]
user_properties: Option<HashMap<String, String>>,
#[builder(default)]
acl_inherit: Option<AclInheritMode>,
#[builder(default)]
acl_mode: Option<AclMode>,
#[builder(default)]
atime: Option<bool>,
#[builder(default)]
can_mount: CanMount,
#[builder(default)]
checksum: Option<Checksum>,
#[builder(default)]
compression: Option<Compression>,
#[builder(default)]
copies: Option<Copies>,
#[builder(default)]
devices: Option<bool>,
#[builder(default)]
exec: Option<bool>,
#[builder(default)]
mount_point: Option<PathBuf>,
#[builder(default)]
primary_cache: Option<CacheMode>,
#[builder(default)]
quota: Option<u64>,
#[builder(default)]
readonly: Option<bool>,
#[builder(default)]
record_size: Option<u64>,
#[builder(default)]
ref_quota: Option<u64>,
#[builder(default)]
ref_reservation: Option<u64>,
#[builder(default)]
reservation: Option<u64>,
#[builder(default)]
secondary_cache: Option<CacheMode>,
#[builder(default)]
setuid: Option<bool>,
#[builder(default)]
snap_dir: Option<SnapDir>,
#[builder(default)]
volume_size: Option<u64>,
#[builder(default)]
volume_block_size: Option<u64>,
#[builder(default)]
xattr: Option<bool>,
}
impl CreateDatasetRequest {
pub fn builder() -> CreateDatasetRequestBuilder { CreateDatasetRequestBuilder::default() }
pub fn validate(&self) -> Result<()> {
let mut errors = Vec::new();
if let Err(e) = validators::validate_name(self.name()) {
errors.push(e);
}
if errors.is_empty() {
Ok(())
} else {
Err(errors.into())
}
}
}
pub(crate) mod validators {
use crate::zfs::{errors::ValidationResult, ValidationError, DATASET_NAME_MAX_LENGTH};
use std::path::Path;
pub fn validate_name<P: AsRef<Path>>(dataset: P) -> ValidationResult {
_validate_name(dataset.as_ref())
}
pub fn _validate_name(dataset: &Path) -> ValidationResult {
let name = dataset.to_string_lossy();
if name.ends_with('/') {
return Err(ValidationError::MissingName(dataset.to_owned()));
}
if dataset.has_root() || dataset.components().count() < 2 {
return Err(ValidationError::MissingPool(dataset.to_owned()));
}
dataset
.file_name()
.ok_or_else(|| ValidationError::MissingName(dataset.to_owned()))
.and_then(|name| {
if name.len() > DATASET_NAME_MAX_LENGTH {
return Err(ValidationError::NameTooLong(dataset.to_owned()));
}
Ok(())
})
}
}
#[cfg(test)]
mod test {
use super::{CreateDatasetRequest, DatasetKind, Error, ErrorKind, ValidationError};
use std::path::PathBuf;
#[test]
fn test_error_ds_not_found() {
let stderr = b"cannot open 's/asd/asd': dataset does not exist";
let err = Error::from_stderr(stderr);
assert_eq!(Error::DatasetNotFound(PathBuf::from("s/asd/asd")), err);
assert_eq!(ErrorKind::DatasetNotFound, err.kind());
}
#[test]
fn test_error_rubbish() {
let stderr = b"there is no way there is an error like this";
let stderr_string = String::from_utf8_lossy(stderr).to_string();
let err = Error::from_stderr(stderr);
assert_eq!(Error::UnknownSoFar(stderr_string), err);
assert_eq!(ErrorKind::Unknown, err.kind());
}
#[test]
fn test_name_validator() {
let path = PathBuf::from("z/asd/");
let request = CreateDatasetRequest::builder()
.name(path.clone())
.kind(DatasetKind::Filesystem)
.build()
.unwrap();
let result = request.validate().unwrap_err();
let expected = Error::from(vec![ValidationError::MissingName(path.clone())]);
assert_eq!(expected, result);
let path = PathBuf::from("z/asd/jnmgyfklueiodyfryvopvyfidvdgxqxsesjmqeoevdgmzsqmesuqzqoxhjfltmsvltdyiilgkvklinlfhaanfqisdazjpfmwttnuosdfijickudhwegburxsoesvunamysaigtagymxcyfeyqiqphtalmbkskrjdndbbcjqiiwucsxzezqmvpzmkylrojumtvatfvrpfkxubfujyioyylmffvrvtfetnzghkwaqzxkqmialkaaekotuhgiivwvbsoqqa");
let request = CreateDatasetRequest::builder()
.name(path.clone())
.kind(DatasetKind::Filesystem)
.build()
.unwrap();
let result = request.validate().unwrap_err();
let expected = Error::from(vec![ValidationError::NameTooLong(path.clone())]);
assert_eq!(expected, result);
}
}