use super::*;
use vfsi_core::internal::ManyResults;
pub const DEFAULT_READ_MAX_BYTES: usize = 16 * 1024 * 1024;
pub const DEFAULT_READ_ALLV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
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,
}
impl WalkOptions {
pub const fn new() -> Self {
Self {
directory: ReadDirOptions::new(),
max_depth: DEFAULT_WALK_MAX_DEPTH,
}
}
pub const fn unlimited() -> Self {
Self {
directory: ReadDirOptions::unlimited(),
max_depth: usize::MAX,
}
}
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 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
}
}
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_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 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() {
return Err(
VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
);
}
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;
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(())
}
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)
}
}
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);
}
}