#![cfg_attr(not(any(feature = "std", test)), no_std)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
extern crate alloc;
macro_rules! vendored {
($($m:ident),+ $(,)?) => {
$(
#[allow(
clippy::all,
clippy::pedantic,
clippy::nursery,
clippy::restriction,
missing_docs,
unused,
unreachable_pub,
elided_lifetimes_in_paths,
// The vendored subtree gates its SIMD paths behind feature names
// (sha256-avx2, fletcher4-avx512f, …) that lamzfs does not
// declare — those accelerators require `unsafe`, which the crate
// forbids, so the gates are permanently-false and only the scalar
// path compiles. Silence the resulting unknown-feature lint here.
unexpected_cfgs
)]
mod $m;
)+
};
}
vendored!(checksum, compression, phys, util);
mod block_read;
mod cksum;
mod compress;
mod dataset;
mod error;
mod file;
mod pool;
mod vdev;
mod walk;
use alloc::{string::String, vec::Vec};
pub use block_read::{BlockRead, PoolMember};
pub use error::{Error, LabelReason, Location};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EntryKind {
Regular,
Directory,
Symlink,
Other,
}
#[derive(Debug, Clone)]
pub struct DirEntry {
pub name: String,
pub kind: EntryKind,
pub object_id: u64,
}
fn dirent_kind(value: u64) -> EntryKind {
match value >> 60 {
4 => EntryKind::Directory,
8 => EntryKind::Regular,
10 => EntryKind::Symlink,
_ => EntryKind::Other,
}
}
const DIRENT_OBJ_MASK: u64 = (1 << 48) - 1;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Stat {
pub kind: EntryKind,
pub size: u64,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PoolId {
pub guid: u64,
pub name: String,
}
pub fn peek_pool_id<R: BlockRead>(
member: &mut PoolMember<R>,
) -> core::result::Result<PoolId, Error> {
let (guid, name) = pool::peek_pool_id(member)?;
Ok(PoolId { guid, name })
}
pub struct Zfs<R: BlockRead> {
members: Vec<PoolMember<R>>,
pool: pool::ImportedPool,
}
impl<R: BlockRead> Zfs<R> {
pub fn import(mut members: Vec<PoolMember<R>>) -> core::result::Result<Self, Error> {
let pool = pool::import(&mut members)?;
Ok(Self { members, pool })
}
pub fn pool_guid(&self) -> u64 {
self.pool.pool_guid
}
pub fn pool_name(&self) -> &str {
&self.pool.pool_name
}
pub fn txg(&self) -> u64 {
self.pool.uberblock.txg
}
pub fn member_count(&self) -> usize {
self.members.len()
}
pub fn child_datasets(&mut self, parent: &[&str]) -> core::result::Result<Vec<String>, Error> {
dataset::child_dataset_names(
&mut self.members,
&self.pool.topology,
&self.pool.mos_dnode,
self.pool.order,
parent,
)
}
pub fn datasets(&mut self) -> core::result::Result<Vec<Vec<String>>, Error> {
let mut out: Vec<Vec<String>> = alloc::vec![Vec::new()];
let mut queue: Vec<Vec<String>> = alloc::vec![Vec::new()];
while let Some(parent) = queue.pop() {
if out.len() >= MAX_DATASETS || parent.len() >= MAX_DATASET_DEPTH {
continue;
}
let parent_ref: Vec<&str> = parent.iter().map(String::as_str).collect();
let Ok(children) = self.child_datasets(&parent_ref) else {
continue;
};
for child in children {
if out.len() >= MAX_DATASETS {
break;
}
let mut path = parent.clone();
path.push(child);
out.push(path.clone());
queue.push(path);
}
}
Ok(out)
}
pub fn read_dir(
&mut self,
dataset_path: &[&str],
dir_path: &[&str],
) -> core::result::Result<Vec<DirEntry>, Error> {
let order = self.pool.order;
let ds = self.open_dataset(dataset_path)?;
let dir_obj = if dir_path.is_empty() {
dataset::root_dir_obj(&mut self.members, &self.pool.topology, &ds, order)?
} else {
let (obj, value) = dataset::resolve_path(
&mut self.members,
&self.pool.topology,
&ds,
order,
dir_path,
)?;
if dirent_kind(value) != EntryKind::Directory {
return Err(Error::NotADirectory);
}
obj
};
let entries =
dataset::list_dir(&mut self.members, &self.pool.topology, &ds, dir_obj, order)?;
Ok(entries
.into_iter()
.map(|(name, value)| DirEntry {
name,
kind: dirent_kind(value),
object_id: value & DIRENT_OBJ_MASK,
})
.collect())
}
pub fn stat(
&mut self,
dataset_path: &[&str],
path: &[&str],
) -> core::result::Result<Stat, Error> {
let order = self.pool.order;
let ds = self.open_dataset(dataset_path)?;
if path.is_empty() {
return Ok(Stat {
kind: EntryKind::Directory,
size: 0,
});
}
let (obj, value) =
dataset::resolve_path(&mut self.members, &self.pool.topology, &ds, order, path)?;
let kind = dirent_kind(value);
let size = if kind == EntryKind::Regular {
let dnode = walk::read_object_dnode(
&mut self.members,
&self.pool.topology,
&ds.meta_dnode,
obj,
order,
)?;
dataset::sa_file_size(dnode.bonus_used(), order)?
} else {
0
};
Ok(Stat { kind, size })
}
pub fn exists(
&mut self,
dataset_path: &[&str],
path: &[&str],
) -> core::result::Result<bool, Error> {
match self.stat(dataset_path, path) {
Ok(_) => Ok(true),
Err(Error::NotFound { .. }) => Ok(false),
Err(e) => Err(e),
}
}
pub fn read(
&mut self,
dataset_path: &[&str],
file_path: &[&str],
) -> core::result::Result<Vec<u8>, Error> {
let order = self.pool.order;
let ds = self.open_dataset(dataset_path)?;
let (dnode, size) = self.regular_file(&ds, file_path)?;
if size > MAX_FILE_BYTES {
return Err(Error::FileTooLarge {
size,
max: MAX_FILE_BYTES,
});
}
let size = usize::try_from(size).map_err(|_| Error::FileTooLarge {
size,
max: MAX_FILE_BYTES,
})?;
file::read_dnode_range(
&mut self.members,
&self.pool.topology,
&dnode,
0,
size,
order,
)
}
pub fn read_at(
&mut self,
dataset_path: &[&str],
file_path: &[&str],
offset: u64,
len: usize,
) -> core::result::Result<Vec<u8>, Error> {
let order = self.pool.order;
let ds = self.open_dataset(dataset_path)?;
let (dnode, size) = self.regular_file(&ds, file_path)?;
if offset >= size {
return Ok(Vec::new());
}
let want = (len as u64).min(size - offset);
if want > MAX_FILE_BYTES {
return Err(Error::FileTooLarge {
size: want,
max: MAX_FILE_BYTES,
});
}
let want = usize::try_from(want).map_err(|_| Error::FileTooLarge {
size: want,
max: MAX_FILE_BYTES,
})?;
file::read_dnode_range(
&mut self.members,
&self.pool.topology,
&dnode,
offset,
want,
order,
)
}
fn open_dataset(
&mut self,
dataset_path: &[&str],
) -> core::result::Result<dataset::Dataset, Error> {
dataset::open_dataset(
&mut self.members,
&self.pool.topology,
&self.pool.mos_dnode,
self.pool.order,
dataset_path,
)
}
fn regular_file(
&mut self,
ds: &dataset::Dataset,
file_path: &[&str],
) -> core::result::Result<(crate::phys::Dnode, u64), Error> {
let order = self.pool.order;
let (obj, value) =
dataset::resolve_path(&mut self.members, &self.pool.topology, ds, order, file_path)?;
if dirent_kind(value) != EntryKind::Regular {
return Err(Error::NotARegularFile);
}
let dnode = walk::read_object_dnode(
&mut self.members,
&self.pool.topology,
&ds.meta_dnode,
obj,
order,
)?;
let size = dataset::sa_file_size(dnode.bonus_used(), order)?;
Ok((dnode, size))
}
}
pub const MAX_FILE_BYTES: u64 = 256 * 1024 * 1024;
pub const MAX_DATASETS: usize = 256;
pub const MAX_DATASET_DEPTH: usize = 16;