#![forbid(unsafe_code)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[repr(u8)]
pub enum Feature {
Chunk4K = 1,
Chunk16K = 2,
Chunk256K = 3,
EntropyRef = 4,
Palette = 5,
Permutation = 6,
Encrypted = 7,
ExtentDeltaIndex = 8,
OptimizerRewrite = 9,
SequenceRans = 10,
SparseBlock64 = 11,
SequenceDict = 12,
SequenceSharedDict = 13,
SequenceDeep = 14,
MutationLog = 15,
}
impl Feature {
pub const fn set(self) -> FeatureSetKind {
match self {
Feature::Chunk4K
| Feature::Chunk16K
| Feature::Chunk256K
| Feature::EntropyRef
| Feature::Palette
| Feature::Permutation
| Feature::SequenceRans
| Feature::SparseBlock64
| Feature::SequenceDict
| Feature::SequenceSharedDict
| Feature::SequenceDeep
| Feature::MutationLog => FeatureSetKind::Incompat,
Feature::Encrypted => FeatureSetKind::RoCompat,
Feature::ExtentDeltaIndex | Feature::OptimizerRewrite => FeatureSetKind::Compat,
}
}
pub const fn mask(self) -> u64 {
1u64 << (self as u8 - 1)
}
pub const ALL: [Feature; 15] = [
Feature::Chunk4K,
Feature::Chunk16K,
Feature::Chunk256K,
Feature::EntropyRef,
Feature::Palette,
Feature::Permutation,
Feature::Encrypted,
Feature::ExtentDeltaIndex,
Feature::OptimizerRewrite,
Feature::SequenceRans,
Feature::SparseBlock64,
Feature::SequenceDict,
Feature::SequenceSharedDict,
Feature::SequenceDeep,
Feature::MutationLog,
];
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FeatureSetKind {
Compat,
RoCompat,
Incompat,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct FeatureBits {
pub compat: u64,
pub ro_compat: u64,
pub incompat: u64,
}
impl FeatureBits {
pub const fn empty() -> Self {
Self {
compat: 0,
ro_compat: 0,
incompat: 0,
}
}
pub fn enable(&mut self, f: Feature) {
match f.set() {
FeatureSetKind::Compat => self.compat |= f.mask(),
FeatureSetKind::RoCompat => self.ro_compat |= f.mask(),
FeatureSetKind::Incompat => self.incompat |= f.mask(),
}
}
pub fn has(&self, f: Feature) -> bool {
match f.set() {
FeatureSetKind::Compat => self.compat & f.mask() != 0,
FeatureSetKind::RoCompat => self.ro_compat & f.mask() != 0,
FeatureSetKind::Incompat => self.incompat & f.mask() != 0,
}
}
pub fn for_representations(
&self,
has_entropy_ref: bool,
has_palette: bool,
has_permutation: bool,
) -> Self {
let mut out = *self;
if has_entropy_ref {
out.enable(Feature::EntropyRef);
}
if has_palette {
out.enable(Feature::Palette);
}
if has_permutation {
out.enable(Feature::Permutation);
}
out
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccessMode {
ReadOnly,
ReadWrite,
}
impl AccessMode {
pub const fn name(self) -> &'static str {
match self {
Self::ReadOnly => "read-only",
Self::ReadWrite => "read-write",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CompatibilityError {
pub format_major: u16,
pub format_minor: u16,
pub unknown_incompat: u64,
pub unknown_ro_compat: u64,
pub unknown_compat: u64,
pub access: AccessMode,
pub remediation: String,
}
impl CompatibilityError {
pub fn unknown_any(&self) -> u64 {
self.unknown_incompat | self.unknown_ro_compat | self.unknown_compat
}
}
impl std::fmt::Display for CompatibilityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"store format {}.{} carries unknown feature bits \
(incompat 0x{:016x}, ro_compat 0x{:016x}, compat 0x{:016x}); \
{} access refused: {}",
self.format_major,
self.format_minor,
self.unknown_incompat,
self.unknown_ro_compat,
self.unknown_compat,
self.access.name(),
self.remediation
)
}
}
impl std::error::Error for CompatibilityError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Compatibility {
Ok,
ReadOnlyOnly(CompatibilityError),
Refused(CompatibilityError),
}
fn supported_incompat_mask() -> u64 {
Feature::Chunk4K.mask()
| Feature::Chunk16K.mask()
| Feature::Chunk256K.mask()
| Feature::EntropyRef.mask()
| Feature::Palette.mask()
| Feature::Permutation.mask()
| Feature::SequenceRans.mask()
| Feature::SparseBlock64.mask()
| Feature::SequenceDict.mask()
| Feature::SequenceSharedDict.mask()
| Feature::SequenceDeep.mask()
| Feature::MutationLog.mask()
}
fn supported_ro_compat_mask() -> u64 {
0
}
pub fn check(on_disk: FeatureBits, want_write: bool) -> Compatibility {
check_with_version(
on_disk,
want_write,
crate::format::version::FORMAT_MAJOR,
crate::format::version::FORMAT_MINOR,
)
}
pub fn check_with_version(
on_disk: FeatureBits,
want_write: bool,
format_major: u16,
format_minor: u16,
) -> Compatibility {
let unknown_incompat = on_disk.incompat & !supported_incompat_mask();
if unknown_incompat != 0 {
return Compatibility::Refused(CompatibilityError {
format_major,
format_minor,
unknown_incompat,
unknown_ro_compat: 0,
unknown_compat: 0,
access: AccessMode::ReadWrite,
remediation: "this store was written by a newer EntropyFS with \
incompatible on-disk features; upgrade the tool (never \
force-open a store with unknown incompat bits)"
.to_string(),
});
}
let unknown_ro_compat = on_disk.ro_compat & !supported_ro_compat_mask();
if unknown_ro_compat != 0 {
let err = CompatibilityError {
format_major,
format_minor,
unknown_incompat: 0,
unknown_ro_compat,
unknown_compat: on_disk.compat & !supported_compat_mask(),
access: AccessMode::ReadOnly,
remediation: "mount (or open) the store READ-ONLY: read-only access \
is safe because the unknown ro_compat features only \
affect writes; upgrade the tool to open it read-write"
.to_string(),
};
return if want_write {
let mut e = err;
e.access = AccessMode::ReadWrite;
Compatibility::Refused(e)
} else {
Compatibility::ReadOnlyOnly(err)
};
}
Compatibility::Ok
}
fn supported_compat_mask() -> u64 {
Feature::ExtentDeltaIndex.mask() | Feature::OptimizerRewrite.mask()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn feature_sets() {
let mut f = FeatureBits::empty();
assert!(!f.has(Feature::Palette));
f.enable(Feature::Palette);
assert!(f.has(Feature::Palette));
assert_eq!(
f.incompat & Feature::Palette.mask(),
Feature::Palette.mask()
);
f.enable(Feature::ExtentDeltaIndex);
assert!(f.has(Feature::ExtentDeltaIndex));
assert_eq!(f.compat, Feature::ExtentDeltaIndex.mask());
}
#[test]
fn all_features_are_in_one_set() {
for f in Feature::ALL {
assert!(matches!(
f.set(),
FeatureSetKind::Compat | FeatureSetKind::RoCompat | FeatureSetKind::Incompat
));
assert!(f.mask() != 0);
}
}
#[test]
fn compat_checks() {
let mut f = FeatureBits::empty();
f.incompat = 1u64 << 63;
assert!(matches!(check(f, true), Compatibility::Refused(_)));
assert!(matches!(check(f, false), Compatibility::Refused(_)));
let mut f = FeatureBits::empty();
f.enable(Feature::Palette);
f.enable(Feature::EntropyRef);
assert_eq!(check(f, true), Compatibility::Ok);
let mut f = FeatureBits::empty();
f.enable(Feature::Encrypted);
assert!(matches!(check(f, true), Compatibility::Refused(_)));
let ro = check(f, false);
assert!(matches!(ro, Compatibility::ReadOnlyOnly(_)));
if let Compatibility::ReadOnlyOnly(e) = ro {
assert_eq!(e.unknown_ro_compat, Feature::Encrypted.mask());
assert_eq!(e.access, AccessMode::ReadOnly);
assert_eq!(e.format_major, crate::format::version::FORMAT_MAJOR);
}
let mut f = FeatureBits::empty();
f.compat = 1u64 << 62;
assert_eq!(check(f, true), Compatibility::Ok);
}
#[test]
fn check_with_version_reports_superblock_version() {
let mut f = FeatureBits::empty();
f.incompat = 1u64 << 63;
if let Compatibility::Refused(e) = check_with_version(f, true, 1, 7) {
assert_eq!(e.format_major, 1);
assert_eq!(e.format_minor, 7);
} else {
panic!("expected refusal");
}
}
#[test]
fn compat_error_display_is_structured_not_parsed() {
let mut f = FeatureBits::empty();
f.incompat = 1u64 << 63;
if let Compatibility::Refused(e) = check(f, true) {
let s = e.to_string();
assert!(s.contains("0x8000000000000000"));
assert!(s.contains("refused"));
} else {
panic!("expected refusal");
}
}
}