1use super::*;
2use vfsi_core::internal::ManyResults;
3
4pub const DEFAULT_READ_MAX_BYTES: usize = 16 * 1024 * 1024;
6
7pub const DEFAULT_READ_ALLV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
9
10pub const DEFAULT_DIRECTORY_MAX_ENTRIES: usize = 100_000;
12
13pub const DEFAULT_DIRECTORY_MAX_PATH_BYTES: usize = 16 * 1024 * 1024;
15
16pub const DEFAULT_WALK_MAX_DEPTH: usize = 128;
18
19#[derive(Clone, Copy, Debug, PartialEq, Eq)]
21pub struct ReadDirOptions {
22 max_entries: usize,
23 max_path_bytes: usize,
24}
25
26impl ReadDirOptions {
27 pub const fn new() -> Self {
28 Self {
29 max_entries: DEFAULT_DIRECTORY_MAX_ENTRIES,
30 max_path_bytes: DEFAULT_DIRECTORY_MAX_PATH_BYTES,
31 }
32 }
33
34 pub const fn unlimited() -> Self {
36 Self {
37 max_entries: usize::MAX,
38 max_path_bytes: usize::MAX,
39 }
40 }
41
42 pub const fn max_entries(mut self, entries: usize) -> Self {
43 self.max_entries = entries;
44 self
45 }
46
47 pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
48 self.max_path_bytes = bytes;
49 self
50 }
51
52 pub const fn entry_limit(self) -> usize {
53 self.max_entries
54 }
55
56 pub const fn path_byte_limit(self) -> usize {
57 self.max_path_bytes
58 }
59}
60
61impl Default for ReadDirOptions {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67#[derive(Clone, Copy, Debug, PartialEq, Eq)]
69pub struct WalkOptions {
70 directory: ReadDirOptions,
71 max_depth: usize,
72}
73
74impl WalkOptions {
75 pub const fn new() -> Self {
76 Self {
77 directory: ReadDirOptions::new(),
78 max_depth: DEFAULT_WALK_MAX_DEPTH,
79 }
80 }
81
82 pub const fn unlimited() -> Self {
84 Self {
85 directory: ReadDirOptions::unlimited(),
86 max_depth: usize::MAX,
87 }
88 }
89
90 pub const fn max_entries(mut self, entries: usize) -> Self {
91 self.directory = self.directory.max_entries(entries);
92 self
93 }
94
95 pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
96 self.directory = self.directory.max_path_bytes(bytes);
97 self
98 }
99
100 pub const fn max_depth(mut self, depth: usize) -> Self {
101 self.max_depth = depth;
102 self
103 }
104
105 pub const fn entry_limit(self) -> usize {
106 self.directory.entry_limit()
107 }
108
109 pub const fn path_byte_limit(self) -> usize {
110 self.directory.path_byte_limit()
111 }
112
113 pub const fn depth_limit(self) -> usize {
114 self.max_depth
115 }
116}
117
118impl Default for WalkOptions {
119 fn default() -> Self {
120 Self::new()
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq, Eq)]
126pub struct ReadAllOptions {
127 max_total_bytes: usize,
128}
129
130impl ReadAllOptions {
131 pub const fn new() -> Self {
132 Self {
133 max_total_bytes: DEFAULT_READ_ALLV_MAX_TOTAL_BYTES,
134 }
135 }
136
137 pub const fn max_total_bytes(mut self, bytes: usize) -> Self {
139 self.max_total_bytes = bytes;
140 self
141 }
142
143 pub const fn total_byte_limit(self) -> usize {
144 self.max_total_bytes
145 }
146}
147
148impl Default for ReadAllOptions {
149 fn default() -> Self {
150 Self::new()
151 }
152}
153
154fn contract_error(
155 operation: &str,
156 index: Option<usize>,
157 detail: impl std::fmt::Display,
158) -> VfError {
159 VfError::transport(
160 index,
161 format!("{operation} backend contract violation: {detail}"),
162 )
163}
164
165fn take_single_result<T>(operation: &str, mut results: Vec<T>) -> VfResult<T> {
166 if results.len() != 1 {
167 return Err(contract_error(
168 operation,
169 None,
170 format!("returned {} results for one request", results.len()),
171 ));
172 }
173 Ok(results.pop().expect("validated one result"))
174}
175
176pub(crate) fn validate_read_results(
177 operation: &str,
178 requests: &[ReadOp],
179 results: &[ReadResult],
180) -> VfResult<()> {
181 if results.len() != requests.len() {
182 return Err(contract_error(
183 operation,
184 None,
185 format!(
186 "returned {} results for {} requests",
187 results.len(),
188 requests.len()
189 ),
190 ));
191 }
192 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
193 if result.file != request.file {
194 return Err(contract_error(
195 operation,
196 Some(index),
197 "result file does not match request",
198 ));
199 }
200 if result.data.len() > request.length {
201 return Err(contract_error(
202 operation,
203 Some(index),
204 format!(
205 "returned {} bytes for a {}-byte read",
206 result.data.len(),
207 request.length
208 ),
209 ));
210 }
211 if let VfOffset::At(expected) = request.offset
212 && result.offset != expected
213 {
214 return Err(contract_error(
215 operation,
216 Some(index),
217 format!("result offset {} does not match {expected}", result.offset),
218 ));
219 }
220 if request.length != 0 && result.data.is_empty() && !result.eof {
221 return Err(contract_error(
222 operation,
223 Some(index),
224 "read made no progress without reporting EOF",
225 ));
226 }
227 }
228 Ok(())
229}
230
231pub(crate) fn validate_write_results(
232 operation: &str,
233 requests: &[WriteOpRef<'_>],
234 results: &[WriteResult],
235) -> VfResult<()> {
236 if results.len() != requests.len() {
237 return Err(contract_error(
238 operation,
239 None,
240 format!(
241 "returned {} results for {} requests",
242 results.len(),
243 requests.len()
244 ),
245 ));
246 }
247 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
248 if result.file != *request.file {
249 return Err(contract_error(
250 operation,
251 Some(index),
252 "result file does not match request",
253 ));
254 }
255 if result.written > request.data.len() {
256 return Err(contract_error(
257 operation,
258 Some(index),
259 format!(
260 "reported {} bytes written for a {}-byte write",
261 result.written,
262 request.data.len()
263 ),
264 ));
265 }
266 if let VfOffset::At(expected) = request.offset
267 && result.offset != expected
268 {
269 return Err(contract_error(
270 operation,
271 Some(index),
272 format!("result offset {} does not match {expected}", result.offset),
273 ));
274 }
275 }
276 Ok(())
277}
278
279pub trait VecFs {
294 #[deprecated(note = "use the NFS backend's NfsExtensions trait")]
296 fn nfs_minorversion(&self) -> Option<u32> {
297 None
298 }
299
300 #[deprecated(note = "use the SMB backend's SmbExtensions trait")]
303 fn smb_dialect(&self) -> Option<u16> {
304 None
305 }
306
307 fn capabilities(&self) -> u64 {
310 0
311 }
312
313 fn typed_capabilities(&self) -> Capabilities {
315 Capabilities::from_bits_retain(self.capabilities())
316 }
317
318 fn abs_path(&self, path: &Path) -> PathBuf;
326
327 fn open_by_path(
331 &mut self,
332 base: VfPathBase,
333 pathname: &Path,
334 flags: i32,
335 mode: u32,
336 ) -> VfResult<VfFile>;
337
338 fn close(&mut self, tcf: &VfFile) -> VfResult<()>;
340
341 fn sync_data(&mut self, tcf: &VfFile) -> VfResult<()>;
344
345 fn sync_all(&mut self, tcf: &VfFile) -> VfResult<()> {
347 self.sync_data(tcf)
348 }
349
350 fn chdir(&mut self, path: &Path) -> VfResult<()>;
352
353 fn getcwd(&self) -> PathBuf;
355
356 fn readv(&mut self, reads: &[ReadOp]) -> VfResult<Vec<ReadResult>>;
359
360 fn read_allv(&mut self, files: &[VfFile]) -> VfResult<Vec<Vec<u8>>> {
364 self.read_allv_with_options(files, ReadAllOptions::default())
365 }
366
367 fn read_allv_with_options(
369 &mut self,
370 files: &[VfFile],
371 options: ReadAllOptions,
372 ) -> VfResult<Vec<Vec<u8>>> {
373 let mut out: Vec<Vec<u8>> = files.iter().map(|_| Vec::new()).collect();
374 let mut total = 0usize;
375 let mut limit_error = None;
376 let stream_budget = options
377 .total_byte_limit()
378 .clamp(1, DEFAULT_READ_ALLV_MAX_TOTAL_BYTES);
379 let chunk_size = stream_budget.min(1024 * 1024);
380 self.read_streamv(
381 files,
382 chunk_size,
383 stream_budget,
384 &mut |index, _, data, _| {
385 let Some(next_total) = total.checked_add(data.len()) else {
386 limit_error = Some(index);
387 return false;
388 };
389 if next_total > options.total_byte_limit() {
390 limit_error = Some(index);
391 return false;
392 }
393 out[index].extend_from_slice(data);
394 total = next_total;
395 true
396 },
397 )?;
398 if let Some(index) = limit_error {
399 return Err(VfError::failure(index, libc::EFBIG as u32));
400 }
401 Ok(out)
402 }
403
404 fn writev(&mut self, writes: &[WriteOp]) -> VfResult<Vec<WriteResult>>;
407
408 fn writev_borrowed(&mut self, writes: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>> {
412 let owned: Vec<WriteOp> = writes
413 .iter()
414 .map(|write| WriteOp {
415 file: write.file.clone(),
416 offset: write.offset,
417 data: write.data.to_vec(),
418 creation: write.creation,
419 truncate: write.truncate,
420 })
421 .collect();
422 self.writev(&owned)
423 }
424
425 fn fseek(&mut self, tcf: &VfFile, offset: i64, whence: SeekFrom) -> VfResult<i64>;
431
432 fn getattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
435
436 fn lgetattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
439
440 fn setattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
446
447 fn lsetattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
452
453 fn listdir(
460 &mut self,
461 dir: &Path,
462 masks: AttrMask,
463 max_count: usize,
464 recursive: bool,
465 ) -> VfResult<Vec<VfAttrs>>;
466
467 fn walk(
475 &mut self,
476 root: &Path,
477 masks: AttrMask,
478 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
479 ) -> VfResult<Vec<WalkEntry>> {
480 self.walk_with_options(root, masks, WalkOptions::default(), sort)
481 }
482
483 fn walk_with_options(
485 &mut self,
486 root: &Path,
487 masks: AttrMask,
488 options: WalkOptions,
489 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
490 ) -> VfResult<Vec<WalkEntry>> {
491 let mut out = Vec::new();
495 let mut stack = vec![(root.to_path_buf(), 0usize)];
496 let mut entry_count = 0usize;
497 let mut path_bytes = 0usize;
498 while let Some((dir, depth)) = stack.pop() {
499 let remaining = options.entry_limit().saturating_sub(entry_count);
500 let request_count = remaining.saturating_add(1);
501 let mut entries = self.listdir(&dir, masks, request_count, false)?;
502 if entries.len() > remaining {
503 return Err(
504 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
505 );
506 }
507 for entry in &entries {
508 let bytes = entry.file.path().map_or(0, |path| path.as_os_str().len());
509 path_bytes = path_bytes.checked_add(bytes).ok_or_else(|| {
510 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
511 })?;
512 if path_bytes > options.path_byte_limit() {
513 return Err(VfError::failure(entry_count, libc::EFBIG as u32)
514 .with_context("walk", &dir));
515 }
516 entry_count += 1;
517 }
518 sort(dir.as_path(), &mut entries);
519 let subdirs: Vec<PathBuf> = entries
520 .iter()
521 .filter(|e| e.ftype == VfType::Directory)
522 .filter_map(|e| e.file.path().map(|p| p.to_path_buf()))
523 .collect();
524 if !subdirs.is_empty() && depth >= options.depth_limit() {
525 return Err(
526 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
527 );
528 }
529 for s in subdirs.into_iter().rev() {
530 stack.push((s, depth + 1));
531 }
532 out.push(WalkEntry { path: dir, entries });
533 }
534 Ok(out)
535 }
536
537 fn renamev(&mut self, pairs: &[(VfFile, VfFile)]) -> VfRes;
539
540 fn removev(&mut self, files: &[VfFile]) -> VfRes;
542
543 fn mkdirv(&mut self, dirs: &[VfAttrs]) -> VfRes;
545
546 fn symlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
548
549 fn readlinkv(&mut self, paths: &[&Path]) -> VfResult<Vec<Vec<u8>>>;
551
552 fn hardlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
554
555 fn dupv(&mut self, pairs: &[ExtentPair]) -> VfRes;
558
559 fn lcopyv(&mut self, pairs: &[ExtentPair]) -> VfRes;
563
564 fn write_adb(&mut self, patterns: &[Adb]) -> VfResult<Vec<usize>>;
567
568 fn read_streamv(
577 &mut self,
578 files: &[VfFile],
579 chunk_size: usize,
580 memory_limit: usize,
581 cb: &mut ReadStreamCallback<'_>,
582 ) -> VfRes {
583 use std::collections::VecDeque;
584
585 if chunk_size == 0 || memory_limit == 0 {
586 return Err(VfError::failure(0, ERR_INVAL));
587 }
588 let mut pending: VecDeque<usize> = (0..files.len()).collect();
589 let mut offsets = vec![0u64; files.len()];
590 while !pending.is_empty() {
591 let mut budget = memory_limit;
592 let mut batch_indices = Vec::new();
593 let mut reads = Vec::new();
594 while budget > 0 && !pending.is_empty() {
595 let index = pending.pop_front().expect("pending was non-empty");
596 let length = chunk_size.min(budget);
597 reads.push(ReadOp::at(files[index].clone(), offsets[index], length));
598 batch_indices.push(index);
599 budget -= length;
600 }
601 let results = self.readv(&reads).map_err(|error| {
602 error.index_opt().map_or(error.clone(), |local_index| {
603 batch_indices
604 .get(local_index)
605 .copied()
606 .map_or(error.clone(), |index| error.with_index(index))
607 })
608 })?;
609 validate_read_results("read_streamv", &reads, &results).map_err(|error| {
610 error.index_opt().map_or(error.clone(), |local_index| {
611 batch_indices
612 .get(local_index)
613 .copied()
614 .map_or(error.clone(), |index| error.with_index(index))
615 })
616 })?;
617 for (batch_index, result) in results.into_iter().enumerate() {
618 let index = batch_indices[batch_index];
619 let offset = offsets[index];
620 let eof = result.eof;
621 offsets[index] = offset
622 .checked_add(result.data.len() as u64)
623 .ok_or_else(|| VfError::failure(index, libc::EOVERFLOW as u32))?;
624 if !cb(index, offset, &result.data, eof) {
625 return Ok(());
626 }
627 if !eof {
628 pending.push_back(index);
629 }
630 }
631 }
632 Ok(())
633 }
634
635 fn rm(&mut self, objs: &[&Path], recursive: bool) -> VfRes;
637
638 fn cp_recursive(
640 &mut self,
641 src_dir: &Path,
642 dst: &Path,
643 symlinks: bool,
644 use_server_side_copy: bool,
645 ) -> VfRes;
646
647 fn vf_path(&self, file: &VfFile) -> VfResult<PathBuf> {
656 match file {
657 VfFile::Path {
658 base: VfPathBase::Abs,
659 path,
660 } => Ok(self.abs_path(&Path::new("/").join(path))),
661 VfFile::Path {
662 base: VfPathBase::Cwd,
663 path,
664 }
665 | VfFile::CwdPath(path) => Ok(self.abs_path(path)),
666 VfFile::Cwd => Ok(self.abs_path(Path::new(""))),
667 VfFile::Descriptor(_) | VfFile::Saved => Err(VfError::failure(0, ERR_INVAL)),
668 _ => Err(VfError::failure(0, ERR_INVAL)),
669 }
670 }
671
672 fn open(&mut self, pathname: &Path, flags: i32, mode: u32) -> VfResult<VfFile> {
674 self.open_by_path(VfPathBase::Cwd, pathname, flags, mode)
675 }
676
677 fn read(&mut self, file: &VfFile, offset: u64, length: usize) -> VfResult<Vec<u8>> {
679 let requests = [ReadOp::at(file.clone(), offset, length)];
680 let mut results = self.readv(&requests)?;
681 validate_read_results("read", &requests, &results)?;
682 Ok(results.pop().expect("validated one result").data)
683 }
684
685 fn write(&mut self, file: &VfFile, offset: u64, data: &[u8]) -> VfResult<usize> {
687 let owned = WriteOp::at(file.clone(), offset, data.to_vec());
688 let requests = [WriteOpRef {
689 file: &owned.file,
690 offset: owned.offset,
691 data: &owned.data,
692 creation: owned.creation,
693 truncate: owned.truncate,
694 }];
695 let mut results = self.writev(std::slice::from_ref(&owned))?;
696 validate_write_results("write", &requests, &results)?;
697 Ok(results.pop().expect("validated one result").written)
698 }
699
700 #[doc(hidden)]
706 fn open_many(
707 &mut self,
708 paths: &[&Path],
709 flags: &[i32],
710 modes: &[u32],
711 ) -> VfResult<ManyResults<VfFile>> {
712 if paths.len() != flags.len() || paths.len() != modes.len() {
713 return Err(VfError::failure(0, ERR_INVAL));
714 }
715 let mut results = Vec::with_capacity(paths.len());
716 for ((path, flag), mode) in paths.iter().zip(flags).zip(modes) {
717 match self.open(path, *flag, *mode) {
718 Ok(file) => results.push(Ok(file)),
719 Err(error) => {
720 results.push(Err(error));
721 break;
722 }
723 }
724 }
725 Ok(ManyResults::new(paths.len(), results))
726 }
727
728 #[doc(hidden)]
731 fn before_open_cleanup(&mut self, _index: usize, _file: &VfFile) -> VfResult<()> {
732 Ok(())
733 }
734
735 fn openv(&mut self, paths: &[&Path], flags: &[i32], modes: &[u32]) -> VfResult<Vec<VfFile>> {
739 let results = self.open_many(paths, flags, modes)?;
740 results.try_collect_with_cleanup(paths.len(), |index, file| {
741 let injected = self.before_open_cleanup(index, file);
742 let closed = self.close(file);
743 injected.and(closed)
744 })
745 }
746
747 fn openv_simple(&mut self, paths: &[&Path], flags: i32, mode: u32) -> VfResult<Vec<VfFile>> {
750 let flags_v = vec![flags; paths.len()];
751 let modes_v = vec![mode; paths.len()];
752 self.openv(paths, &flags_v, &modes_v)
753 }
754
755 fn closev(&mut self, files: &[VfFile]) -> VfRes {
757 for (i, f) in files.iter().enumerate() {
758 self.close(f).map_err(|e| e.with_index(i))?;
759 }
760 Ok(())
761 }
762
763 fn stat(&mut self, path: &Path) -> VfResult<VfAttrs> {
765 let mut a = VfAttrs {
766 file: VfFile::from_os_path(path),
767 masks: AttrMask::stat(),
768 ..VfAttrs::default()
769 };
770 self.getattrsv(std::slice::from_mut(&mut a))?;
771 Ok(a)
772 }
773
774 fn lstat(&mut self, path: &Path) -> VfResult<VfAttrs> {
776 let mut a = VfAttrs {
777 file: VfFile::from_os_path(path),
778 masks: AttrMask::stat(),
779 ..VfAttrs::default()
780 };
781 self.lgetattrsv(std::slice::from_mut(&mut a))?;
782 Ok(a)
783 }
784
785 fn fstat(&mut self, tcf: &VfFile) -> VfResult<VfAttrs> {
787 let mut a = VfAttrs {
788 file: tcf.clone(),
789 masks: AttrMask::stat(),
790 ..VfAttrs::default()
791 };
792 self.getattrsv(std::slice::from_mut(&mut a))?;
793 Ok(a)
794 }
795
796 fn exists(&mut self, path: &Path) -> VfResult<bool> {
800 match self.lstat(path) {
801 Ok(_) => Ok(true),
802 Err(e) if e.err_no() == ERR_NOENT => Ok(false),
803 Err(e) => Err(e),
804 }
805 }
806
807 fn file_type(&mut self, path: &Path) -> VfResult<VfType> {
810 Ok(self.lstat(path)?.ftype)
811 }
812
813 fn listdirv(
816 &mut self,
817 dirs: &[&Path],
818 masks: AttrMask,
819 max_entries: usize,
820 recursive: bool,
821 cb: &mut dyn FnMut(&VfAttrs, &Path) -> bool,
822 ) -> VfRes {
823 for (i, d) in dirs.iter().enumerate() {
824 let entries = self
825 .listdir(d, masks, max_entries, recursive)
826 .map_err(|e| e.with_index(i))?;
827 for e in &entries {
828 let entry_dir = if recursive {
832 e.file.path().and_then(Path::parent).unwrap_or(d)
833 } else {
834 d
835 };
836 if !cb(e, entry_dir) {
837 return Ok(());
838 }
839 }
840 }
841 Ok(())
842 }
843
844 fn unlink(&mut self, pathname: &Path) -> VfResult<()> {
846 self.removev(&[VfFile::from_os_path(pathname)])
847 }
848
849 fn unlinkv(&mut self, pathnames: &[&Path]) -> VfRes {
851 let files: Vec<VfFile> = pathnames.iter().map(|p| VfFile::from_os_path(p)).collect();
852 self.removev(&files)
853 }
854
855 fn mkdir(&mut self, path: &Path, mode: u32) -> VfResult<()> {
857 let a = VfAttrs {
858 file: VfFile::from_os_path(path),
859 masks: AttrMask::MODE,
860 mode,
861 ..VfAttrs::default()
862 };
863 self.mkdirv(std::slice::from_ref(&a))
864 }
865
866 fn symlink(&mut self, oldpath: &Path, newpath: &Path) -> VfResult<()> {
868 self.symlinkv(
869 std::slice::from_ref(&oldpath),
870 std::slice::from_ref(&newpath),
871 )
872 }
873
874 fn readlink(&mut self, path: &Path) -> VfResult<Vec<u8>> {
876 take_single_result("readlink", self.readlinkv(std::slice::from_ref(&path))?)
877 }
878
879 fn ldupv(&mut self, pairs: &[ExtentPair]) -> VfRes {
883 self.dupv(pairs)
884 }
885
886 fn copyv(&mut self, pairs: &[ExtentPair]) -> VfRes {
891 self.dupv(pairs)
892 }
893
894 fn ensure_dir(&mut self, dir: &Path, mode: u32) -> VfResult<()> {
900 use std::path::Component;
901 let mut so_far = PathBuf::new();
902 for comp in self.abs_path(dir).components() {
903 if let Component::Normal(part) = comp {
904 so_far.push(part);
905 let full = Path::new("/").join(&so_far);
906 match self.mkdir(&full, mode) {
907 Ok(()) => {}
908 Err(e) if e.err_no() == ERR_EXIST => {}
909 Err(e) => return Err(e),
910 }
911 }
912 }
913 Ok(())
914 }
915}
916
917pub trait VecFsExt: VecFs {
924 fn stat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
925 self.stat(path.as_ref())
926 }
927
928 fn lstat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
929 self.lstat(path.as_ref())
930 }
931
932 fn exists_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<bool> {
933 self.exists(path.as_ref())
934 }
935
936 fn file_type_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfType> {
937 self.file_type(path.as_ref())
938 }
939
940 fn open_path<P: AsRef<Path>>(&mut self, path: P, flags: i32, mode: u32) -> VfResult<VfFile> {
941 self.open(path.as_ref(), flags, mode)
942 }
943
944 fn mkdir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
945 self.mkdir(path.as_ref(), mode)
946 }
947
948 fn ensure_dir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
949 self.ensure_dir(path.as_ref(), mode)
950 }
951
952 fn chdir_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
953 self.chdir(path.as_ref())
954 }
955
956 fn unlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
957 self.unlink(path.as_ref())
958 }
959
960 fn readlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<Vec<u8>> {
961 self.readlink(path.as_ref())
962 }
963
964 fn symlink_path<P, Q>(&mut self, oldpath: P, newpath: Q) -> VfResult<()>
965 where
966 P: AsRef<Path>,
967 Q: AsRef<Path>,
968 {
969 self.symlink(oldpath.as_ref(), newpath.as_ref())
970 }
971
972 fn listdir_path<P: AsRef<Path>>(
973 &mut self,
974 path: P,
975 masks: AttrMask,
976 max_count: usize,
977 recursive: bool,
978 ) -> VfResult<Vec<VfAttrs>> {
979 self.listdir(path.as_ref(), masks, max_count, recursive)
980 }
981
982 fn walk_path<P: AsRef<Path>>(
983 &mut self,
984 root: P,
985 masks: AttrMask,
986 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
987 ) -> VfResult<Vec<WalkEntry>> {
988 self.walk(root.as_ref(), masks, sort)
989 }
990
991 fn openv_paths<P: AsRef<Path>>(
992 &mut self,
993 paths: &[P],
994 flags: &[i32],
995 modes: &[u32],
996 ) -> VfResult<Vec<VfFile>> {
997 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
998 self.openv(&refs, flags, modes)
999 }
1000
1001 fn openv_simple_paths<P: AsRef<Path>>(
1002 &mut self,
1003 paths: &[P],
1004 flags: i32,
1005 mode: u32,
1006 ) -> VfResult<Vec<VfFile>> {
1007 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1008 self.openv_simple(&refs, flags, mode)
1009 }
1010
1011 fn unlinkv_paths<P: AsRef<Path>>(&mut self, paths: &[P]) -> VfRes {
1012 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1013 self.unlinkv(&refs)
1014 }
1015
1016 fn rm_paths<P: AsRef<Path>>(&mut self, paths: &[P], recursive: bool) -> VfRes {
1017 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1018 self.rm(&refs, recursive)
1019 }
1020
1021 fn rm_recursive_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1022 self.rm(&[path.as_ref()], true)
1023 }
1024}
1025
1026impl<T: VecFs + ?Sized> VecFsExt for T {}
1027
1028pub fn rm_recursive(fs: &mut impl VecFs, dir: &Path) -> VfRes {
1030 fs.rm(&[dir], true)
1031}
1032
1033#[cfg(test)]
1034mod contract_tests {
1035 use super::*;
1036
1037 fn request() -> ReadOp {
1038 ReadOp::at(VfFile::from_path("/file"), 0, 1)
1039 }
1040
1041 fn result() -> ReadResult {
1042 ReadResult {
1043 file: VfFile::from_path("/file"),
1044 offset: 0,
1045 data: vec![1],
1046 eof: true,
1047 }
1048 }
1049
1050 #[test]
1051 fn read_result_cardinality_is_checked_before_indexing() {
1052 let requests = [request(), request()];
1053 assert!(validate_read_results("test", &requests, &[result()]).is_err());
1054 assert!(validate_read_results("test", &requests[..1], &[result(), result()]).is_err());
1055 }
1056
1057 #[test]
1058 fn read_result_identity_offset_progress_and_size_are_checked() {
1059 let request = request();
1060 for malformed in [
1061 ReadResult {
1062 file: VfFile::from_path("/other"),
1063 ..result()
1064 },
1065 ReadResult {
1066 offset: 1,
1067 ..result()
1068 },
1069 ReadResult {
1070 data: vec![1, 2],
1071 ..result()
1072 },
1073 ReadResult {
1074 data: Vec::new(),
1075 eof: false,
1076 ..result()
1077 },
1078 ] {
1079 assert!(
1080 validate_read_results("test", std::slice::from_ref(&request), &[malformed])
1081 .is_err()
1082 );
1083 }
1084 }
1085
1086 #[test]
1087 fn write_result_identity_offset_size_and_cardinality_are_checked() {
1088 let file = VfFile::from_path("/file");
1089 let request = WriteOpRef::new(&file, VfOffset::At(0), b"x");
1090 let valid = WriteResult {
1091 file: file.clone(),
1092 offset: 0,
1093 written: 1,
1094 stable: true,
1095 };
1096 assert!(validate_write_results("test", &[request], &[]).is_err());
1097 for malformed in [
1098 WriteResult {
1099 file: VfFile::from_path("/other"),
1100 ..valid.clone()
1101 },
1102 WriteResult {
1103 offset: 1,
1104 ..valid.clone()
1105 },
1106 WriteResult {
1107 written: 2,
1108 ..valid.clone()
1109 },
1110 ] {
1111 assert!(validate_write_results("test", &[request], &[malformed]).is_err());
1112 }
1113 }
1114
1115 #[test]
1116 fn scalar_result_cardinality_is_checked_without_panicking() {
1117 assert!(take_single_result::<u8>("readlink", Vec::new()).is_err());
1118 assert!(take_single_result("readlink", vec![1u8, 2]).is_err());
1119 assert_eq!(take_single_result("readlink", vec![7u8]).unwrap(), 7);
1120 }
1121}