use super::*;
use vfsi_core::internal::ManyResults;
pub const DEFAULT_READ_MAX_BYTES: usize = 16 * 1024 * 1024;
pub const DEFAULT_READ_STREAM_CHUNK_BYTES: usize = 1024 * 1024;
pub const DEFAULT_READ_ALLV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReadStreamOptions {
chunk_size: usize,
}
impl ReadStreamOptions {
pub const fn new() -> Self {
Self {
chunk_size: DEFAULT_READ_STREAM_CHUNK_BYTES,
}
}
pub const fn chunk_size(mut self, bytes: usize) -> Self {
self.chunk_size = bytes;
self
}
pub const fn chunk_size_bytes(self) -> usize {
self.chunk_size
}
}
impl Default for ReadStreamOptions {
fn default() -> Self {
Self::new()
}
}
pub const DEFAULT_DIRECTORY_MAX_ENTRIES: usize = 100_000;
pub const DEFAULT_DIRECTORY_MAX_PATH_BYTES: usize = 16 * 1024 * 1024;
pub const DEFAULT_WALK_MAX_DEPTH: usize = 128;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReadDirOptions {
max_entries: usize,
max_path_bytes: usize,
}
impl ReadDirOptions {
pub const fn new() -> Self {
Self {
max_entries: DEFAULT_DIRECTORY_MAX_ENTRIES,
max_path_bytes: DEFAULT_DIRECTORY_MAX_PATH_BYTES,
}
}
pub const fn unlimited() -> Self {
Self {
max_entries: usize::MAX,
max_path_bytes: usize::MAX,
}
}
pub const fn max_entries(mut self, entries: usize) -> Self {
self.max_entries = entries;
self
}
pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
self.max_path_bytes = bytes;
self
}
pub const fn entry_limit(self) -> usize {
self.max_entries
}
pub const fn path_byte_limit(self) -> usize {
self.max_path_bytes
}
}
impl Default for ReadDirOptions {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct WalkOptions {
directory: ReadDirOptions,
max_depth: usize,
truncate_at_max_depth: bool,
}
impl WalkOptions {
pub const fn new() -> Self {
Self {
directory: ReadDirOptions::new(),
max_depth: DEFAULT_WALK_MAX_DEPTH,
truncate_at_max_depth: false,
}
}
pub const fn unlimited() -> Self {
Self {
directory: ReadDirOptions::unlimited(),
max_depth: usize::MAX,
truncate_at_max_depth: false,
}
}
pub const fn max_entries(mut self, entries: usize) -> Self {
self.directory = self.directory.max_entries(entries);
self
}
pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
self.directory = self.directory.max_path_bytes(bytes);
self
}
pub const fn max_depth(mut self, depth: usize) -> Self {
self.max_depth = depth;
self
}
pub const fn truncate_at_max_depth(mut self, truncate: bool) -> Self {
self.truncate_at_max_depth = truncate;
self
}
pub const fn entry_limit(self) -> usize {
self.directory.entry_limit()
}
pub const fn path_byte_limit(self) -> usize {
self.directory.path_byte_limit()
}
pub const fn depth_limit(self) -> usize {
self.max_depth
}
pub const fn truncates_at_depth_limit(self) -> bool {
self.truncate_at_max_depth
}
}
impl Default for WalkOptions {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct ReadAllOptions {
max_total_bytes: usize,
}
impl ReadAllOptions {
pub const fn new() -> Self {
Self {
max_total_bytes: DEFAULT_READ_ALLV_MAX_TOTAL_BYTES,
}
}
pub const fn max_total_bytes(mut self, bytes: usize) -> Self {
self.max_total_bytes = bytes;
self
}
pub const fn total_byte_limit(self) -> usize {
self.max_total_bytes
}
}
impl Default for ReadAllOptions {
fn default() -> Self {
Self::new()
}
}
fn contract_error(
operation: &str,
index: Option<usize>,
detail: impl std::fmt::Display,
) -> VfError {
VfError::transport(
index,
format!("{operation} backend contract violation: {detail}"),
)
}
fn take_single_result<T>(operation: &str, mut results: Vec<T>) -> VfResult<T> {
if results.len() != 1 {
return Err(contract_error(
operation,
None,
format!("returned {} results for one request", results.len()),
));
}
Ok(results.pop().expect("validated one result"))
}
pub(crate) fn validate_read_results(
operation: &str,
requests: &[ReadOp],
results: &[ReadResult],
) -> VfResult<()> {
if results.len() != requests.len() {
return Err(contract_error(
operation,
None,
format!(
"returned {} results for {} requests",
results.len(),
requests.len()
),
));
}
for (index, (request, result)) in requests.iter().zip(results).enumerate() {
if result.file != request.file {
return Err(contract_error(
operation,
Some(index),
"result file does not match request",
));
}
if result.data.len() > request.length {
return Err(contract_error(
operation,
Some(index),
format!(
"returned {} bytes for a {}-byte read",
result.data.len(),
request.length
),
));
}
if let VfOffset::At(expected) = request.offset
&& result.offset != expected
{
return Err(contract_error(
operation,
Some(index),
format!("result offset {} does not match {expected}", result.offset),
));
}
if request.length != 0 && result.data.is_empty() && !result.eof {
return Err(contract_error(
operation,
Some(index),
"read made no progress without reporting EOF",
));
}
}
Ok(())
}
pub(crate) fn validate_read_into_results(
operation: &str,
requests: &[ReadOp],
results: &[ReadIntoResult],
) -> VfResult<()> {
if results.len() != requests.len() {
return Err(contract_error(
operation,
None,
format!(
"returned {} results for {} requests",
results.len(),
requests.len()
),
));
}
for (index, (request, result)) in requests.iter().zip(results).enumerate() {
if result.file != request.file {
return Err(contract_error(
operation,
Some(index),
"result file does not match request",
));
}
if result.read > request.length {
return Err(contract_error(
operation,
Some(index),
format!(
"returned {} bytes for a {}-byte read",
result.read, request.length
),
));
}
if let VfOffset::At(expected) = request.offset
&& result.offset != expected
{
return Err(contract_error(
operation,
Some(index),
format!("result offset {} does not match {expected}", result.offset),
));
}
if request.length != 0 && result.read == 0 && !result.eof {
return Err(contract_error(
operation,
Some(index),
"read made no progress without reporting EOF",
));
}
}
Ok(())
}
pub(crate) fn validate_write_results(
operation: &str,
requests: &[WriteOpRef<'_>],
results: &[WriteResult],
) -> VfResult<()> {
if results.len() != requests.len() {
return Err(contract_error(
operation,
None,
format!(
"returned {} results for {} requests",
results.len(),
requests.len()
),
));
}
for (index, (request, result)) in requests.iter().zip(results).enumerate() {
if result.file != *request.file {
return Err(contract_error(
operation,
Some(index),
"result file does not match request",
));
}
if result.written > request.data.len() {
return Err(contract_error(
operation,
Some(index),
format!(
"reported {} bytes written for a {}-byte write",
result.written,
request.data.len()
),
));
}
if let VfOffset::At(expected) = request.offset
&& result.offset != expected
{
return Err(contract_error(
operation,
Some(index),
format!("result offset {} does not match {expected}", result.offset),
));
}
}
Ok(())
}
pub trait VecFs {
#[deprecated(note = "use the NFS backend's NfsExtensions trait")]
fn nfs_minorversion(&self) -> Option<u32> {
None
}
#[deprecated(note = "use the SMB backend's SmbExtensions trait")]
fn smb_dialect(&self) -> Option<u16> {
None
}
fn capabilities(&self) -> u64 {
0
}
fn typed_capabilities(&self) -> Capabilities {
Capabilities::from_bits_retain(self.capabilities())
}
fn abs_path(&self, path: &Path) -> PathBuf;
fn open_by_path(
&mut self,
base: VfPathBase,
pathname: &Path,
flags: i32,
mode: u32,
) -> VfResult<VfFile>;
fn close(&mut self, tcf: &VfFile) -> VfResult<()>;
fn sync_data(&mut self, tcf: &VfFile) -> VfResult<()>;
fn sync_all(&mut self, tcf: &VfFile) -> VfResult<()> {
self.sync_data(tcf)
}
fn chdir(&mut self, path: &Path) -> VfResult<()>;
fn getcwd(&self) -> PathBuf;
fn readv(&mut self, reads: &[ReadOp]) -> VfResult<Vec<ReadResult>>;
fn readv_into(
&mut self,
reads: &[ReadOp],
buffers: &mut [&mut [u8]],
) -> VfResult<Vec<ReadIntoResult>> {
if reads.len() != buffers.len() {
return Err(VfError::client(0, ERR_INVAL));
}
for (index, (request, buffer)) in reads.iter().zip(buffers.iter()).enumerate() {
if request.length != buffer.len() {
return Err(VfError::client(index, ERR_INVAL));
}
}
let results = self.readv(reads)?;
validate_read_results("readv_into", reads, &results)?;
Ok(results
.into_iter()
.zip(buffers.iter_mut())
.map(|(result, buffer)| {
buffer[..result.data.len()].copy_from_slice(&result.data);
ReadIntoResult {
file: result.file,
offset: result.offset,
read: result.data.len(),
eof: result.eof,
}
})
.collect())
}
fn read_allv(&mut self, files: &[VfFile]) -> VfResult<Vec<Vec<u8>>> {
self.read_allv_with_options(files, ReadAllOptions::default())
}
fn read_allv_with_options(
&mut self,
files: &[VfFile],
options: ReadAllOptions,
) -> VfResult<Vec<Vec<u8>>> {
let mut out: Vec<Vec<u8>> = files.iter().map(|_| Vec::new()).collect();
let mut total = 0usize;
let mut limit_error = None;
let stream_budget = options
.total_byte_limit()
.clamp(1, DEFAULT_READ_ALLV_MAX_TOTAL_BYTES);
let chunk_size = stream_budget.min(1024 * 1024);
self.read_streamv(
files,
chunk_size,
stream_budget,
&mut |index, _, data, _| {
let Some(next_total) = total.checked_add(data.len()) else {
limit_error = Some(index);
return false;
};
if next_total > options.total_byte_limit() {
limit_error = Some(index);
return false;
}
out[index].extend_from_slice(data);
total = next_total;
true
},
)?;
if let Some(index) = limit_error {
return Err(VfError::failure(index, libc::EFBIG as u32));
}
Ok(out)
}
fn writev(&mut self, writes: &[WriteOp]) -> VfResult<Vec<WriteResult>>;
fn writev_borrowed(&mut self, writes: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>> {
let owned: Vec<WriteOp> = writes
.iter()
.map(|write| WriteOp {
file: write.file.clone(),
offset: write.offset,
data: write.data.to_vec(),
creation: write.creation,
truncate: write.truncate,
})
.collect();
self.writev(&owned)
}
fn fseek(&mut self, tcf: &VfFile, offset: i64, whence: SeekFrom) -> VfResult<i64>;
fn getattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
fn lgetattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
fn setattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
fn lsetattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
fn listdir(
&mut self,
dir: &Path,
masks: AttrMask,
max_count: usize,
recursive: bool,
) -> VfResult<Vec<VfAttrs>>;
fn walk(
&mut self,
root: &Path,
masks: AttrMask,
sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
) -> VfResult<Vec<WalkEntry>> {
self.walk_with_options(root, masks, WalkOptions::default(), sort)
}
fn walk_with_options(
&mut self,
root: &Path,
masks: AttrMask,
options: WalkOptions,
sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
) -> VfResult<Vec<WalkEntry>> {
let mut out = Vec::new();
let mut stack = vec![(root.to_path_buf(), 0usize)];
let mut entry_count = 0usize;
let mut path_bytes = 0usize;
while let Some((dir, depth)) = stack.pop() {
let remaining = options.entry_limit().saturating_sub(entry_count);
let request_count = remaining.saturating_add(1);
let mut entries = self.listdir(&dir, masks, request_count, false)?;
if entries.len() > remaining {
return Err(
VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
);
}
for entry in &entries {
let bytes = entry.file.path().map_or(0, |path| path.as_os_str().len());
path_bytes = path_bytes.checked_add(bytes).ok_or_else(|| {
VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
})?;
if path_bytes > options.path_byte_limit() {
return Err(VfError::failure(entry_count, libc::EFBIG as u32)
.with_context("walk", &dir));
}
entry_count += 1;
}
sort(dir.as_path(), &mut entries);
let subdirs: Vec<PathBuf> = entries
.iter()
.filter(|e| e.ftype == VfType::Directory)
.filter_map(|e| e.file.path().map(|p| p.to_path_buf()))
.collect();
if !subdirs.is_empty()
&& depth >= options.depth_limit()
&& !options.truncates_at_depth_limit()
{
return Err(
VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
);
}
if depth < options.depth_limit() {
for s in subdirs.into_iter().rev() {
stack.push((s, depth + 1));
}
}
out.push(WalkEntry { path: dir, entries });
}
Ok(out)
}
fn renamev(&mut self, pairs: &[(VfFile, VfFile)]) -> VfRes;
fn removev(&mut self, files: &[VfFile]) -> VfRes;
fn mkdirv(&mut self, dirs: &[VfAttrs]) -> VfRes;
fn symlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
fn readlinkv(&mut self, paths: &[&Path]) -> VfResult<Vec<Vec<u8>>>;
fn hardlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
fn dupv(&mut self, pairs: &[ExtentPair]) -> VfRes;
fn lcopyv(&mut self, pairs: &[ExtentPair]) -> VfRes;
fn write_adb(&mut self, patterns: &[Adb]) -> VfResult<Vec<usize>>;
fn read_streamv(
&mut self,
files: &[VfFile],
chunk_size: usize,
memory_limit: usize,
cb: &mut ReadStreamCallback<'_>,
) -> VfRes {
use std::collections::VecDeque;
if chunk_size == 0 || memory_limit == 0 {
return Err(VfError::failure(0, ERR_INVAL));
}
let mut pending: VecDeque<usize> = (0..files.len()).collect();
let mut offsets = vec![0u64; files.len()];
while !pending.is_empty() {
let mut budget = memory_limit;
let mut batch_indices = Vec::new();
let mut reads = Vec::new();
while budget > 0 && !pending.is_empty() {
let index = pending.pop_front().expect("pending was non-empty");
let length = chunk_size.min(budget);
reads.push(ReadOp::at(files[index].clone(), offsets[index], length));
batch_indices.push(index);
budget -= length;
}
let results = self.readv(&reads).map_err(|error| {
error.index_opt().map_or(error.clone(), |local_index| {
batch_indices
.get(local_index)
.copied()
.map_or(error.clone(), |index| error.with_index(index))
})
})?;
validate_read_results("read_streamv", &reads, &results).map_err(|error| {
error.index_opt().map_or(error.clone(), |local_index| {
batch_indices
.get(local_index)
.copied()
.map_or(error.clone(), |index| error.with_index(index))
})
})?;
for (batch_index, result) in results.into_iter().enumerate() {
let index = batch_indices[batch_index];
let offset = offsets[index];
let eof = result.eof;
offsets[index] = offset
.checked_add(result.data.len() as u64)
.ok_or_else(|| VfError::failure(index, libc::EOVERFLOW as u32))?;
if !cb(index, offset, &result.data, eof) {
return Ok(());
}
if !eof {
pending.push_back(index);
}
}
}
Ok(())
}
fn rm(&mut self, objs: &[&Path], recursive: bool) -> VfRes {
self.rm_with_options(objs, recursive, RemoveOptions::default())
}
fn rm_with_options(
&mut self,
objs: &[&Path],
recursive: bool,
options: RemoveOptions,
) -> VfRes {
if options != RemoveOptions::default() {
return Err(VfError::unsupported(0));
}
if objs.is_empty() {
return Ok(());
}
if !recursive {
let files: Vec<VfFile> = objs.iter().map(|p| VfFile::from_os_path(p)).collect();
return self.removev(&files);
}
for (root_index, root) in objs.iter().enumerate() {
self.before_remove_type(root_index)?;
let mut root_attrs = VfAttrs {
file: VfFile::from_os_path(root),
masks: AttrMask::default(),
..VfAttrs::default()
};
self.lgetattrsv(std::slice::from_mut(&mut root_attrs))
.map_err(|error| error.map_index(|_| root_index))?;
if root_attrs.ftype != VfType::Directory {
self.removev(&[VfFile::from_os_path(root)])
.map_err(|error| error.map_index(|_| root_index))?;
continue;
}
let mut dir_levels: Vec<Vec<PathBuf>> = Vec::new();
let mut frontier: Vec<PathBuf> = vec![root.to_path_buf()];
while !frontier.is_empty() {
let refs: Vec<&Path> = frontier.iter().map(PathBuf::as_path).collect();
let mut per_dir: Vec<Vec<VfAttrs>> = (0..refs.len()).map(|_| Vec::new()).collect();
{
let index_of: std::collections::HashMap<&Path, usize> =
refs.iter().enumerate().map(|(i, p)| (*p, i)).collect();
let mut cb = |attrs: &VfAttrs, dir: &Path| {
if let Some(&slot) = index_of.get(dir) {
per_dir[slot].push(attrs.clone());
}
true
};
self.listdirv(&refs, AttrMask::default(), 0, false, &mut cb)
.map_err(|error| error.map_index(|_| root_index))?;
}
let mut next: Vec<PathBuf> = Vec::new();
for entries in per_dir {
let mut files: Vec<VfFile> = Vec::new();
for attrs in entries {
if attrs.ftype == VfType::Directory {
if let Some(path) = attrs.file.path() {
next.push(path.to_path_buf());
}
} else {
files.push(attrs.file);
}
}
if !files.is_empty() {
self.removev(&files)
.map_err(|error| error.map_index(|_| root_index))?;
}
}
dir_levels.push(frontier);
frontier = next;
}
for level in dir_levels.iter().rev() {
let dirs: Vec<VfFile> = level
.iter()
.map(|path| VfFile::from_os_path(path))
.collect();
self.removev(&dirs)
.map_err(|error| error.map_index(|_| root_index))?;
}
}
Ok(())
}
fn open_dir(&mut self, path: &Path) -> VfResult<VfDir> {
let mut attrs = VfAttrs {
file: VfFile::from_os_path(path),
masks: AttrMask::default(),
..VfAttrs::default()
};
self.lgetattrsv(std::slice::from_mut(&mut attrs))?;
if attrs.ftype != VfType::Directory {
return Err(VfError::failure(0, ERR_NOTDIR));
}
Ok(VfDir::Path(path.to_path_buf()))
}
fn rm_dir_contents(&mut self, dir: &VfDir) -> VfRes {
self.rm_dir_contents_with_options(dir, RemoveOptions::default())
}
fn rm_dir_contents_with_options(&mut self, dir: &VfDir, options: RemoveOptions) -> VfRes {
match dir {
VfDir::Path(path) => self.rm_contents_with_options(path, options),
_ => Err(VfError::unsupported(0)),
}
}
fn close_dir(&mut self, _dir: &VfDir) -> VfResult<()> {
Ok(())
}
fn rm_contents(&mut self, dir: &Path) -> VfRes {
self.rm_contents_with_options(dir, RemoveOptions::default())
}
fn rm_contents_with_options(&mut self, dir: &Path, options: RemoveOptions) -> VfRes {
if options != RemoveOptions::default() {
return Err(VfError::unsupported(0));
}
let entries = self
.listdir(dir, AttrMask::default(), 0, false)
.map_err(|error| error.with_index(0))?;
let mut files: Vec<VfFile> = Vec::new();
let mut dirs: Vec<PathBuf> = Vec::new();
for attrs in entries {
if attrs.ftype == VfType::Directory {
if let Some(path) = attrs.file.path() {
dirs.push(path.to_path_buf());
}
} else {
files.push(attrs.file);
}
}
if !files.is_empty() {
self.removev(&files).map_err(|error| error.with_index(0))?;
}
for sub in dirs {
self.rm(&[sub.as_path()], true)
.map_err(|error| error.with_index(0))?;
}
Ok(())
}
fn ensure_empty_dir(&mut self, dir: &Path) -> VfRes {
match self.mkdir(dir, 0o777) {
Ok(()) => Ok(()),
Err(error) if error.err_no() == ERR_EXIST => {
let mut attrs = VfAttrs {
file: VfFile::from_os_path(dir),
masks: AttrMask::default(),
..VfAttrs::default()
};
self.lgetattrsv(std::slice::from_mut(&mut attrs))?;
if attrs.ftype != VfType::Directory {
return Err(VfError::failure(0, ERR_NOTDIR));
}
self.rm_contents(dir)
}
Err(error) => Err(error),
}
}
fn cp_recursive(
&mut self,
src_dir: &Path,
dst: &Path,
symlinks: bool,
use_server_side_copy: bool,
) -> VfRes;
fn vf_path(&self, file: &VfFile) -> VfResult<PathBuf> {
match file {
VfFile::Path {
base: VfPathBase::Abs,
path,
} => Ok(self.abs_path(&Path::new("/").join(path))),
VfFile::Path {
base: VfPathBase::Cwd,
path,
}
| VfFile::CwdPath(path) => Ok(self.abs_path(path)),
VfFile::Cwd => Ok(self.abs_path(Path::new(""))),
VfFile::Descriptor(_) | VfFile::Saved => Err(VfError::failure(0, ERR_INVAL)),
_ => Err(VfError::failure(0, ERR_INVAL)),
}
}
fn open(&mut self, pathname: &Path, flags: i32, mode: u32) -> VfResult<VfFile> {
self.open_by_path(VfPathBase::Cwd, pathname, flags, mode)
}
fn read(&mut self, file: &VfFile, offset: u64, length: usize) -> VfResult<Vec<u8>> {
let requests = [ReadOp::at(file.clone(), offset, length)];
let mut results = self.readv(&requests)?;
validate_read_results("read", &requests, &results)?;
Ok(results.pop().expect("validated one result").data)
}
fn write(&mut self, file: &VfFile, offset: u64, data: &[u8]) -> VfResult<usize> {
let owned = WriteOp::at(file.clone(), offset, data.to_vec());
let requests = [WriteOpRef {
file: &owned.file,
offset: owned.offset,
data: &owned.data,
creation: owned.creation,
truncate: owned.truncate,
}];
let mut results = self.writev(std::slice::from_ref(&owned))?;
validate_write_results("write", &requests, &results)?;
Ok(results.pop().expect("validated one result").written)
}
#[doc(hidden)]
fn open_many(
&mut self,
paths: &[&Path],
flags: &[i32],
modes: &[u32],
) -> VfResult<ManyResults<VfFile>> {
if paths.len() != flags.len() || paths.len() != modes.len() {
return Err(VfError::failure(0, ERR_INVAL));
}
let mut results = Vec::with_capacity(paths.len());
for ((path, flag), mode) in paths.iter().zip(flags).zip(modes) {
match self.open(path, *flag, *mode) {
Ok(file) => results.push(Ok(file)),
Err(error) => {
results.push(Err(error));
break;
}
}
}
Ok(ManyResults::new(paths.len(), results))
}
#[doc(hidden)]
fn before_open_cleanup(&mut self, _index: usize, _file: &VfFile) -> VfResult<()> {
Ok(())
}
#[doc(hidden)]
fn before_remove_type(&mut self, _index: usize) -> VfResult<()> {
Ok(())
}
fn openv(&mut self, paths: &[&Path], flags: &[i32], modes: &[u32]) -> VfResult<Vec<VfFile>> {
let results = self.open_many(paths, flags, modes)?;
results.try_collect_with_cleanup(paths.len(), |index, file| {
let injected = self.before_open_cleanup(index, file);
let closed = self.close(file);
injected.and(closed)
})
}
fn openv_simple(&mut self, paths: &[&Path], flags: i32, mode: u32) -> VfResult<Vec<VfFile>> {
let flags_v = vec![flags; paths.len()];
let modes_v = vec![mode; paths.len()];
self.openv(paths, &flags_v, &modes_v)
}
fn closev(&mut self, files: &[VfFile]) -> VfRes {
for (i, f) in files.iter().enumerate() {
self.close(f).map_err(|e| e.with_index(i))?;
}
Ok(())
}
fn stat(&mut self, path: &Path) -> VfResult<VfAttrs> {
let mut a = VfAttrs {
file: VfFile::from_os_path(path),
masks: AttrMask::stat(),
..VfAttrs::default()
};
self.getattrsv(std::slice::from_mut(&mut a))?;
Ok(a)
}
fn lstat(&mut self, path: &Path) -> VfResult<VfAttrs> {
let mut a = VfAttrs {
file: VfFile::from_os_path(path),
masks: AttrMask::stat(),
..VfAttrs::default()
};
self.lgetattrsv(std::slice::from_mut(&mut a))?;
Ok(a)
}
fn fstat(&mut self, tcf: &VfFile) -> VfResult<VfAttrs> {
let mut a = VfAttrs {
file: tcf.clone(),
masks: AttrMask::stat(),
..VfAttrs::default()
};
self.getattrsv(std::slice::from_mut(&mut a))?;
Ok(a)
}
fn exists(&mut self, path: &Path) -> VfResult<bool> {
match self.lstat(path) {
Ok(_) => Ok(true),
Err(e) if e.err_no() == ERR_NOENT => Ok(false),
Err(e) => Err(e),
}
}
fn file_type(&mut self, path: &Path) -> VfResult<VfType> {
Ok(self.lstat(path)?.ftype)
}
fn listdirv(
&mut self,
dirs: &[&Path],
masks: AttrMask,
max_entries: usize,
recursive: bool,
cb: &mut dyn FnMut(&VfAttrs, &Path) -> bool,
) -> VfRes {
for (i, d) in dirs.iter().enumerate() {
let entries = self
.listdir(d, masks, max_entries, recursive)
.map_err(|e| e.with_index(i))?;
for e in &entries {
let entry_dir = if recursive {
e.file.path().and_then(Path::parent).unwrap_or(d)
} else {
d
};
if !cb(e, entry_dir) {
return Ok(());
}
}
}
Ok(())
}
fn unlink(&mut self, pathname: &Path) -> VfResult<()> {
self.removev(&[VfFile::from_os_path(pathname)])
}
fn unlinkv(&mut self, pathnames: &[&Path]) -> VfRes {
let files: Vec<VfFile> = pathnames.iter().map(|p| VfFile::from_os_path(p)).collect();
self.removev(&files)
}
fn mkdir(&mut self, path: &Path, mode: u32) -> VfResult<()> {
let a = VfAttrs {
file: VfFile::from_os_path(path),
masks: AttrMask::MODE,
mode,
..VfAttrs::default()
};
self.mkdirv(std::slice::from_ref(&a))
}
fn symlink(&mut self, oldpath: &Path, newpath: &Path) -> VfResult<()> {
self.symlinkv(
std::slice::from_ref(&oldpath),
std::slice::from_ref(&newpath),
)
}
fn readlink(&mut self, path: &Path) -> VfResult<Vec<u8>> {
take_single_result("readlink", self.readlinkv(std::slice::from_ref(&path))?)
}
fn ldupv(&mut self, pairs: &[ExtentPair]) -> VfRes {
self.dupv(pairs)
}
fn copyv(&mut self, pairs: &[ExtentPair]) -> VfRes {
self.dupv(pairs)
}
fn ensure_dir(&mut self, dir: &Path, mode: u32) -> VfResult<()> {
use std::path::Component;
let mut so_far = PathBuf::new();
for comp in self.abs_path(dir).components() {
if let Component::Normal(part) = comp {
so_far.push(part);
let full = Path::new("/").join(&so_far);
match self.mkdir(&full, mode) {
Ok(()) => {}
Err(e) if e.err_no() == ERR_EXIST => {}
Err(e) => return Err(e),
}
}
}
Ok(())
}
}
pub trait VecFsExt: VecFs {
fn stat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
self.stat(path.as_ref())
}
fn lstat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
self.lstat(path.as_ref())
}
fn exists_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<bool> {
self.exists(path.as_ref())
}
fn file_type_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfType> {
self.file_type(path.as_ref())
}
fn open_path<P: AsRef<Path>>(&mut self, path: P, flags: i32, mode: u32) -> VfResult<VfFile> {
self.open(path.as_ref(), flags, mode)
}
fn mkdir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
self.mkdir(path.as_ref(), mode)
}
fn ensure_dir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
self.ensure_dir(path.as_ref(), mode)
}
fn chdir_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
self.chdir(path.as_ref())
}
fn unlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
self.unlink(path.as_ref())
}
fn readlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<Vec<u8>> {
self.readlink(path.as_ref())
}
fn symlink_path<P, Q>(&mut self, oldpath: P, newpath: Q) -> VfResult<()>
where
P: AsRef<Path>,
Q: AsRef<Path>,
{
self.symlink(oldpath.as_ref(), newpath.as_ref())
}
fn listdir_path<P: AsRef<Path>>(
&mut self,
path: P,
masks: AttrMask,
max_count: usize,
recursive: bool,
) -> VfResult<Vec<VfAttrs>> {
self.listdir(path.as_ref(), masks, max_count, recursive)
}
fn walk_path<P: AsRef<Path>>(
&mut self,
root: P,
masks: AttrMask,
sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
) -> VfResult<Vec<WalkEntry>> {
self.walk(root.as_ref(), masks, sort)
}
fn openv_paths<P: AsRef<Path>>(
&mut self,
paths: &[P],
flags: &[i32],
modes: &[u32],
) -> VfResult<Vec<VfFile>> {
let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
self.openv(&refs, flags, modes)
}
fn openv_simple_paths<P: AsRef<Path>>(
&mut self,
paths: &[P],
flags: i32,
mode: u32,
) -> VfResult<Vec<VfFile>> {
let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
self.openv_simple(&refs, flags, mode)
}
fn unlinkv_paths<P: AsRef<Path>>(&mut self, paths: &[P]) -> VfRes {
let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
self.unlinkv(&refs)
}
fn rm_paths<P: AsRef<Path>>(&mut self, paths: &[P], recursive: bool) -> VfRes {
let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
self.rm(&refs, recursive)
}
fn rm_recursive_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
self.rm(&[path.as_ref()], true)
}
fn rm_contents_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
self.rm_contents(path.as_ref())
}
fn ensure_empty_dir_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
self.ensure_empty_dir(path.as_ref())
}
}
impl<T: VecFs + ?Sized> VecFsExt for T {}
pub fn rm_recursive(fs: &mut impl VecFs, dir: &Path) -> VfRes {
fs.rm(&[dir], true)
}
#[cfg(test)]
mod contract_tests {
use super::*;
fn request() -> ReadOp {
ReadOp::at(VfFile::from_path("/file"), 0, 1)
}
fn result() -> ReadResult {
ReadResult {
file: VfFile::from_path("/file"),
offset: 0,
data: vec![1],
eof: true,
}
}
#[test]
fn read_result_cardinality_is_checked_before_indexing() {
let requests = [request(), request()];
assert!(validate_read_results("test", &requests, &[result()]).is_err());
assert!(validate_read_results("test", &requests[..1], &[result(), result()]).is_err());
}
#[test]
fn read_result_identity_offset_progress_and_size_are_checked() {
let request = request();
for malformed in [
ReadResult {
file: VfFile::from_path("/other"),
..result()
},
ReadResult {
offset: 1,
..result()
},
ReadResult {
data: vec![1, 2],
..result()
},
ReadResult {
data: Vec::new(),
eof: false,
..result()
},
] {
assert!(
validate_read_results("test", std::slice::from_ref(&request), &[malformed])
.is_err()
);
}
}
#[test]
fn write_result_identity_offset_size_and_cardinality_are_checked() {
let file = VfFile::from_path("/file");
let request = WriteOpRef::new(&file, VfOffset::At(0), b"x");
let valid = WriteResult {
file: file.clone(),
offset: 0,
written: 1,
stable: true,
};
assert!(validate_write_results("test", &[request], &[]).is_err());
for malformed in [
WriteResult {
file: VfFile::from_path("/other"),
..valid.clone()
},
WriteResult {
offset: 1,
..valid.clone()
},
WriteResult {
written: 2,
..valid.clone()
},
] {
assert!(validate_write_results("test", &[request], &[malformed]).is_err());
}
}
#[test]
fn scalar_result_cardinality_is_checked_without_panicking() {
assert!(take_single_result::<u8>("readlink", Vec::new()).is_err());
assert!(take_single_result("readlink", vec![1u8, 2]).is_err());
assert_eq!(take_single_result("readlink", vec![7u8]).unwrap(), 7);
}
}