use crate::error::FsError;
use crate::error::FsErrorKind;
use crate::error::FsOperation;
use crate::error::FsResult;
use crate::metadata::FileSystemCapabilities;
use crate::metadata::FileSystemCapability;
use crate::metadata::FileSystemInfo;
use crate::metadata::FileSystemLimits;
use crate::metadata::SymlinkPolicy;
use crate::path::Path;
use crate::path::PathConstraints;
use crate::path::PathForm;
use crate::path::PathSemantics;
use crate::spi::ProviderOperation;
use crate::spi::ProviderProperties;
#[derive(Clone, Debug)]
pub struct FileSystemProperties {
info: FileSystemInfo,
capabilities: FileSystemCapabilities,
limits: FileSystemLimits,
path_constraints: PathConstraints,
symlink_policy: SymlinkPolicy,
}
impl FileSystemProperties {
pub(crate) fn from_provider(provider: &ProviderProperties) -> FsResult<Self> {
let mut capabilities = provider.declared_capabilities();
let operations = provider.operations();
let streamed_copy = operations.supports(ProviderOperation::Stat)
&& operations.supports(ProviderOperation::OpenReader)
&& operations.supports(ProviderOperation::OpenWriter)
&& capabilities.supports(FileSystemCapability::Read)
&& capabilities.supports(FileSystemCapability::Write);
if streamed_copy && !capabilities.supports(FileSystemCapability::Copy) {
capabilities = capabilities.with_conditional(FileSystemCapability::Copy);
}
Self::new(
provider.info().clone(),
capabilities,
*provider.limits(),
provider.path_constraints().clone(),
provider.symlink_policy(),
)
}
#[inline]
pub fn new(
info: FileSystemInfo,
capabilities: FileSystemCapabilities,
limits: FileSystemLimits,
path_constraints: PathConstraints,
symlink_policy: SymlinkPolicy,
) -> FsResult<Self> {
let properties = Self {
info,
capabilities,
limits,
path_constraints,
symlink_policy,
};
properties.validate()?;
Ok(properties)
}
#[inline]
#[must_use]
pub const fn info(&self) -> &FileSystemInfo {
&self.info
}
#[inline]
#[must_use]
pub const fn capabilities(&self) -> FileSystemCapabilities {
self.capabilities
}
#[inline]
#[must_use]
pub const fn limits(&self) -> &FileSystemLimits {
&self.limits
}
#[inline]
#[must_use]
pub const fn path_constraints(&self) -> &PathConstraints {
&self.path_constraints
}
#[inline]
#[must_use = "the filesystem symbolic-link policy must be used"]
pub const fn symlink_policy(&self) -> SymlinkPolicy {
self.symlink_policy
}
pub fn validate_path(&self, path: &Path, operation: FsOperation) -> FsResult<()> {
let result = if path.semantics() != self.info.path_semantics() {
Err(FsError::invalid_path(
operation,
"path semantics do not match this filesystem",
))
} else {
self.path_constraints
.validate(path)
.and_then(|()| self.limits.validate_path(path, self.info.path_semantics(), operation))
};
result.map_err(|error| {
error
.with_operation(operation)
.with_missing_context(path, None, self.info.provider_id())
})
}
pub(crate) fn validate(&self) -> FsResult<()> {
if self.info.provider_id().is_empty() || self.info.provider_id().chars().any(char::is_control) {
return Err(invalid_properties(
"provider id must be non-empty and contain no controls",
));
}
if let Some((_capability, _dependency)) = self.capabilities.missing_dependency() {
return Err(invalid_properties("advertised capability dependency is missing"));
}
if [
self.limits.max_path_text_bytes(),
self.limits.max_component_text_bytes(),
self.limits.max_read_range_bytes(),
self.limits.max_write_bytes(),
self.limits.max_list_page_entries(),
]
.into_iter()
.any(|limit| matches!(limit, crate::metadata::FileSystemLimit::Maximum(0)))
{
return Err(invalid_properties(
"finite filesystem limits must have a positive value",
));
}
if self.info.path_semantics() != PathSemantics::Hierarchical
&& self.path_constraints.form() == PathForm::Absolute
{
return Err(invalid_properties(
"literal path semantics cannot require hierarchical absolute paths",
));
}
Ok(())
}
}
fn invalid_properties(message: &'static str) -> FsError {
FsError::new(FsErrorKind::InvalidOptions, FsOperation::ValidateProperties, message)
}