1use super::*;
2use vfsi_core::internal::ManyResults;
3
4#[doc(hidden)]
9pub struct DirPageCursor(Box<dyn std::any::Any + Send>);
10
11impl DirPageCursor {
12 pub fn new<T: std::any::Any + Send>(state: T) -> Self {
13 Self(Box::new(state))
14 }
15
16 pub fn is<T: std::any::Any + Send>(&self) -> bool {
17 self.0.is::<T>()
18 }
19
20 pub fn into_state<T: std::any::Any + Send>(self) -> VfResult<T> {
21 self.0
22 .downcast::<T>()
23 .map(|state| *state)
24 .map_err(|_| VfError::client(0, ERR_INVAL))
25 }
26}
27
28#[doc(hidden)]
31pub type BackendDirectoryPage = (
32 Vec<VfAttrs>,
33 Option<DirPageCursor>,
34 Vec<Option<DirPageCursor>>,
35);
36
37#[doc(hidden)]
39pub type DirectoryPage = (
40 crate::DirectoryListing,
41 Option<DirPageCursor>,
42 Vec<(std::path::PathBuf, DirPageCursor)>,
43);
44
45#[derive(Clone, Copy, Debug, PartialEq, Eq)]
47pub struct ReadAllOptions {
48 max_total_bytes: usize,
49}
50
51impl ReadAllOptions {
52 pub const fn new() -> Self {
53 Self {
54 max_total_bytes: DEFAULT_READ_ALLV_MAX_TOTAL_BYTES,
55 }
56 }
57
58 pub const fn max_total_bytes(mut self, bytes: usize) -> Self {
60 self.max_total_bytes = bytes;
61 self
62 }
63
64 pub const fn total_byte_limit(self) -> usize {
65 self.max_total_bytes
66 }
67}
68
69impl Default for ReadAllOptions {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75fn contract_error(
76 operation: &str,
77 index: Option<usize>,
78 detail: impl std::fmt::Display,
79) -> VfError {
80 VfError::transport(
81 index,
82 format!("{operation} backend contract violation: {detail}"),
83 )
84}
85
86fn take_single_result<T>(operation: &str, mut results: Vec<T>) -> VfResult<T> {
87 if results.len() != 1 {
88 return Err(contract_error(
89 operation,
90 None,
91 format!("returned {} results for one request", results.len()),
92 ));
93 }
94 Ok(results.pop().expect("validated one result"))
95}
96
97pub(crate) fn validate_read_results(
98 operation: &str,
99 requests: &[ReadOp],
100 results: &[ReadResult],
101) -> VfResult<()> {
102 if results.len() != requests.len() {
103 return Err(contract_error(
104 operation,
105 None,
106 format!(
107 "returned {} results for {} requests",
108 results.len(),
109 requests.len()
110 ),
111 ));
112 }
113 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
114 if result.file != request.file {
115 return Err(contract_error(
116 operation,
117 Some(index),
118 "result file does not match request",
119 ));
120 }
121 if result.data.len() > request.length {
122 return Err(contract_error(
123 operation,
124 Some(index),
125 format!(
126 "returned {} bytes for a {}-byte read",
127 result.data.len(),
128 request.length
129 ),
130 ));
131 }
132 if let VfOffset::At(expected) = request.offset
133 && result.offset != expected
134 {
135 return Err(contract_error(
136 operation,
137 Some(index),
138 format!("result offset {} does not match {expected}", result.offset),
139 ));
140 }
141 if request.length != 0 && result.data.is_empty() && !result.eof {
142 return Err(contract_error(
143 operation,
144 Some(index),
145 "read made no progress without reporting EOF",
146 ));
147 }
148 }
149 Ok(())
150}
151
152pub(crate) fn validate_read_into_results(
153 operation: &str,
154 requests: &[ReadOp],
155 results: &[ReadIntoResult],
156) -> VfResult<()> {
157 if results.len() != requests.len() {
158 return Err(contract_error(
159 operation,
160 None,
161 format!(
162 "returned {} results for {} requests",
163 results.len(),
164 requests.len()
165 ),
166 ));
167 }
168 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
169 if result.file != request.file {
170 return Err(contract_error(
171 operation,
172 Some(index),
173 "result file does not match request",
174 ));
175 }
176 if result.read > request.length {
177 return Err(contract_error(
178 operation,
179 Some(index),
180 format!(
181 "returned {} bytes for a {}-byte read",
182 result.read, request.length
183 ),
184 ));
185 }
186 if let VfOffset::At(expected) = request.offset
187 && result.offset != expected
188 {
189 return Err(contract_error(
190 operation,
191 Some(index),
192 format!("result offset {} does not match {expected}", result.offset),
193 ));
194 }
195 if request.length != 0 && result.read == 0 && !result.eof {
196 return Err(contract_error(
197 operation,
198 Some(index),
199 "read made no progress without reporting EOF",
200 ));
201 }
202 }
203 Ok(())
204}
205
206pub(crate) fn validate_write_results(
207 operation: &str,
208 requests: &[WriteOpRef<'_>],
209 results: &[WriteResult],
210) -> VfResult<()> {
211 if results.len() != requests.len() {
212 return Err(contract_error(
213 operation,
214 None,
215 format!(
216 "returned {} results for {} requests",
217 results.len(),
218 requests.len()
219 ),
220 ));
221 }
222 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
223 if result.file != *request.file {
224 return Err(contract_error(
225 operation,
226 Some(index),
227 "result file does not match request",
228 ));
229 }
230 if result.written > request.data.len() {
231 return Err(contract_error(
232 operation,
233 Some(index),
234 format!(
235 "reported {} bytes written for a {}-byte write",
236 result.written,
237 request.data.len()
238 ),
239 ));
240 }
241 if let VfOffset::At(expected) = request.offset
242 && result.offset != expected
243 {
244 return Err(contract_error(
245 operation,
246 Some(index),
247 format!("result offset {} does not match {expected}", result.offset),
248 ));
249 }
250 }
251 Ok(())
252}
253
254pub trait VecFs {
269 fn close_deferred(&mut self, file: &VfFile) -> VfResult<()> {
272 self.close(file)
273 }
274 fn take_notifications(&mut self) -> Vec<Box<dyn FnOnce() + Send>> {
277 Vec::new()
278 }
279 #[deprecated(note = "use the NFS backend's NfsExtensions trait")]
281 fn nfs_minorversion(&self) -> Option<u32> {
282 None
283 }
284
285 #[deprecated(note = "use the SMB backend's SmbExtensions trait")]
288 fn smb_dialect(&self) -> Option<u16> {
289 None
290 }
291
292 fn capabilities(&self) -> u64 {
295 0
296 }
297
298 fn typed_capabilities(&self) -> Capabilities {
300 Capabilities::from_bits_retain(self.capabilities())
301 }
302
303 fn abs_path(&self, path: &Path) -> PathBuf;
314
315 fn open_by_path(
319 &mut self,
320 base: VfPathBase,
321 pathname: &Path,
322 flags: i32,
323 mode: u32,
324 ) -> VfResult<VfFile>;
325
326 fn close(&mut self, tcf: &VfFile) -> VfResult<()>;
328
329 fn sync_data(&mut self, tcf: &VfFile) -> VfResult<()>;
332
333 fn sync_all(&mut self, tcf: &VfFile) -> VfResult<()> {
335 self.sync_data(tcf)
336 }
337
338 fn chdir(&mut self, path: &Path) -> VfResult<()>;
340
341 fn getcwd(&self) -> PathBuf;
343
344 fn readv(&mut self, reads: &[ReadOp]) -> VfResult<Vec<ReadResult>>;
347
348 fn readv_into(
352 &mut self,
353 reads: &[ReadOp],
354 buffers: &mut [&mut [u8]],
355 ) -> VfResult<Vec<ReadIntoResult>> {
356 if reads.len() != buffers.len() {
357 return Err(VfError::client(0, ERR_INVAL));
358 }
359 for (index, (request, buffer)) in reads.iter().zip(buffers.iter()).enumerate() {
360 if request.length != buffer.len() {
361 return Err(VfError::client(index, ERR_INVAL));
362 }
363 }
364 let results = self.readv(reads)?;
365 validate_read_results("readv_into", reads, &results)?;
366 Ok(results
367 .into_iter()
368 .zip(buffers.iter_mut())
369 .map(|(result, buffer)| {
370 buffer[..result.data.len()].copy_from_slice(&result.data);
371 ReadIntoResult {
372 file: result.file,
373 offset: result.offset,
374 read: result.data.len(),
375 eof: result.eof,
376 }
377 })
378 .collect())
379 }
380
381 fn read_allv(&mut self, files: &[VfFile]) -> VfResult<Vec<Vec<u8>>> {
385 self.read_allv_with_options(files, ReadAllOptions::default())
386 }
387
388 fn read_allv_with_options(
390 &mut self,
391 files: &[VfFile],
392 options: ReadAllOptions,
393 ) -> VfResult<Vec<Vec<u8>>> {
394 let mut out: Vec<Vec<u8>> = files.iter().map(|_| Vec::new()).collect();
395 let mut total = 0usize;
396 let mut limit_error = None;
397 let stream_budget = options
398 .total_byte_limit()
399 .clamp(1, DEFAULT_READ_ALLV_MAX_TOTAL_BYTES);
400 let chunk_size = stream_budget.min(1024 * 1024);
401 self.read_streamv(
402 files,
403 chunk_size,
404 stream_budget,
405 &mut |index, _, data, _| {
406 let Some(next_total) = total.checked_add(data.len()) else {
407 limit_error = Some(index);
408 return false;
409 };
410 if next_total > options.total_byte_limit() {
411 limit_error = Some(index);
412 return false;
413 }
414 out[index].extend_from_slice(data);
415 total = next_total;
416 true
417 },
418 )?;
419 if let Some(index) = limit_error {
420 return Err(VfError::failure(index, libc::EFBIG as u32));
421 }
422 Ok(out)
423 }
424
425 fn writev(&mut self, writes: &[WriteOp]) -> VfResult<Vec<WriteResult>>;
428
429 fn writev_borrowed(&mut self, writes: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>> {
433 let owned: Vec<WriteOp> = writes
434 .iter()
435 .map(|write| WriteOp {
436 file: write.file.clone(),
437 offset: write.offset,
438 data: write.data.to_vec(),
439 creation: write.creation,
440 truncate: write.truncate,
441 })
442 .collect();
443 self.writev(&owned)
444 }
445
446 fn fseek(&mut self, tcf: &VfFile, offset: i64, whence: SeekFrom) -> VfResult<i64>;
452
453 fn getattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
456
457 fn lgetattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
460
461 fn setattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
467
468 fn lsetattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
473
474 fn listdir(
481 &mut self,
482 dir: &Path,
483 masks: AttrMask,
484 max_count: usize,
485 recursive: bool,
486 ) -> VfResult<Vec<VfAttrs>>;
487
488 fn listdir_page(
496 &mut self,
497 dir: &Path,
498 masks: AttrMask,
499 cursor: Option<DirPageCursor>,
500 page_size: usize,
501 max_entries: usize,
502 ) -> VfResult<(Vec<VfAttrs>, Option<DirPageCursor>)> {
503 if page_size == 0 {
504 return Err(VfError::client(0, ERR_INVAL));
505 }
506 let mut remaining = match cursor {
507 Some(cursor) => cursor.into_state::<std::vec::IntoIter<VfAttrs>>()?,
508 None => self.listdir(dir, masks, max_entries, false)?.into_iter(),
509 };
510 let page: Vec<_> = remaining.by_ref().take(page_size).collect();
511 let next = if remaining.len() == 0 {
512 None
513 } else {
514 Some(DirPageCursor::new(remaining))
515 };
516 Ok((page, next))
517 }
518
519 fn directory_page_batch_size(&self) -> usize {
521 1
522 }
523
524 fn listdir_pages(
526 &mut self,
527 dirs: &[&Path],
528 masks: AttrMask,
529 cursors: Vec<Option<DirPageCursor>>,
530 page_size: usize,
531 max_entries: usize,
532 ) -> VfResult<Vec<BackendDirectoryPage>> {
533 if dirs.len() != cursors.len() || page_size == 0 {
534 return Err(VfError::client(0, ERR_INVAL));
535 }
536 dirs.iter()
537 .zip(cursors)
538 .enumerate()
539 .map(|(index, (dir, cursor))| {
540 self.listdir_page(dir, masks, cursor, page_size, max_entries)
541 .map(|(entries, next)| {
542 let children = (0..entries.len()).map(|_| None).collect();
543 (entries, next, children)
544 })
545 .map_err(|error| error.with_index(index))
546 })
547 .collect()
548 }
549
550 fn walk(
558 &mut self,
559 root: &Path,
560 masks: AttrMask,
561 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
562 ) -> VfResult<Vec<WalkEntry>> {
563 self.walk_with_options(root, masks, WalkOptions::default(), sort)
564 }
565
566 fn walk_with_options(
568 &mut self,
569 root: &Path,
570 masks: AttrMask,
571 options: WalkOptions,
572 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
573 ) -> VfResult<Vec<WalkEntry>> {
574 let mut out = Vec::new();
578 let mut stack = vec![(root.to_path_buf(), 0usize)];
579 let mut entry_count = 0usize;
580 let mut path_bytes = 0usize;
581 while let Some((dir, depth)) = stack.pop() {
582 let remaining = options.entry_limit().saturating_sub(entry_count);
583 let request_count = remaining.saturating_add(1);
584 let mut entries = self.listdir(&dir, masks, request_count, false)?;
585 if entries.len() > remaining {
586 return Err(
587 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
588 );
589 }
590 for entry in &entries {
591 let bytes = entry.file.path().map_or(0, |path| path.as_os_str().len());
592 path_bytes = path_bytes.checked_add(bytes).ok_or_else(|| {
593 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
594 })?;
595 if path_bytes > options.path_byte_limit() {
596 return Err(VfError::failure(entry_count, libc::EFBIG as u32)
597 .with_context("walk", &dir));
598 }
599 entry_count += 1;
600 }
601 sort(dir.as_path(), &mut entries);
602 let subdirs: Vec<PathBuf> = entries
603 .iter()
604 .filter(|e| e.ftype == VfType::Directory)
605 .filter_map(|e| e.file.path().map(|p| p.to_path_buf()))
606 .collect();
607 if !subdirs.is_empty()
608 && depth >= options.depth_limit()
609 && !options.truncates_at_depth_limit()
610 {
611 return Err(
612 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
613 );
614 }
615 if depth < options.depth_limit() {
616 for s in subdirs.into_iter().rev() {
617 stack.push((s, depth + 1));
618 }
619 }
620 out.push(WalkEntry { path: dir, entries });
621 }
622 Ok(out)
623 }
624
625 fn renamev(&mut self, pairs: &[(VfFile, VfFile)]) -> VfRes;
627
628 fn removev(&mut self, files: &[VfFile]) -> VfRes;
630
631 fn mkdirv(&mut self, dirs: &[VfAttrs]) -> VfRes;
633
634 fn symlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
636
637 fn readlinkv(&mut self, paths: &[&Path]) -> VfResult<Vec<Vec<u8>>>;
639
640 fn hardlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
642
643 fn dupv(&mut self, pairs: &[ExtentPair]) -> VfRes;
646
647 fn lcopyv(&mut self, pairs: &[ExtentPair]) -> VfRes;
651
652 fn write_adb(&mut self, patterns: &[Adb]) -> VfResult<Vec<usize>>;
655
656 fn read_streamv(
665 &mut self,
666 files: &[VfFile],
667 chunk_size: usize,
668 memory_limit: usize,
669 cb: &mut ReadStreamCallback<'_>,
670 ) -> VfRes {
671 use std::collections::VecDeque;
672
673 if chunk_size == 0 || memory_limit == 0 {
674 return Err(VfError::failure(0, ERR_INVAL));
675 }
676 let mut pending: VecDeque<usize> = (0..files.len()).collect();
677 let mut offsets = vec![0u64; files.len()];
678 while !pending.is_empty() {
679 let mut budget = memory_limit;
680 let mut batch_indices = Vec::new();
681 let mut reads = Vec::new();
682 while budget > 0 && !pending.is_empty() {
683 let index = pending.pop_front().expect("pending was non-empty");
684 let length = chunk_size.min(budget);
685 reads.push(ReadOp::at(files[index].clone(), offsets[index], length));
686 batch_indices.push(index);
687 budget -= length;
688 }
689 let results = self.readv(&reads).map_err(|error| {
690 error.index().map_or(error.clone(), |local_index| {
691 batch_indices
692 .get(local_index)
693 .copied()
694 .map_or(error.clone(), |index| error.with_index(index))
695 })
696 })?;
697 validate_read_results("read_streamv", &reads, &results).map_err(|error| {
698 error.index().map_or(error.clone(), |local_index| {
699 batch_indices
700 .get(local_index)
701 .copied()
702 .map_or(error.clone(), |index| error.with_index(index))
703 })
704 })?;
705 for (batch_index, result) in results.into_iter().enumerate() {
706 let index = batch_indices[batch_index];
707 let offset = offsets[index];
708 let eof = result.eof;
709 offsets[index] = offset
710 .checked_add(result.data.len() as u64)
711 .ok_or_else(|| VfError::failure(index, libc::EOVERFLOW as u32))?;
712 if !cb(index, offset, &result.data, eof) {
713 return Ok(());
714 }
715 if !eof {
716 pending.push_back(index);
717 }
718 }
719 }
720 Ok(())
721 }
722
723 fn rm(&mut self, objs: &[&Path], recursive: bool) -> VfRes {
740 self.rm_with_options(objs, recursive, RemoveOptions::default())
741 }
742
743 fn rm_with_options(
752 &mut self,
753 objs: &[&Path],
754 recursive: bool,
755 options: RemoveOptions,
756 ) -> VfRes {
757 if options != RemoveOptions::default() {
758 return Err(VfError::unsupported(0));
759 }
760 if objs.is_empty() {
761 return Ok(());
762 }
763 if !recursive {
764 let files: Vec<VfFile> = objs.iter().map(|p| VfFile::from_os_path(p)).collect();
765 return self.removev(&files);
766 }
767
768 for (root_index, root) in objs.iter().enumerate() {
769 self.before_remove_type(root_index)?;
770 let mut root_attrs = VfAttrs {
772 file: VfFile::from_os_path(root),
773 masks: AttrMask::default(),
774 ..VfAttrs::default()
775 };
776 self.lgetattrsv(std::slice::from_mut(&mut root_attrs))
777 .map_err(|error| error.map_index(|_| root_index))?;
778 if root_attrs.ftype != VfType::Directory {
779 self.removev(&[VfFile::from_os_path(root)])
780 .map_err(|error| error.map_index(|_| root_index))?;
781 continue;
782 }
783
784 let mut dir_levels: Vec<Vec<PathBuf>> = Vec::new();
787 let mut frontier: Vec<PathBuf> = vec![root.to_path_buf()];
788 while !frontier.is_empty() {
789 let refs: Vec<&Path> = frontier.iter().map(PathBuf::as_path).collect();
790 let mut per_dir: Vec<Vec<VfAttrs>> = (0..refs.len()).map(|_| Vec::new()).collect();
791 {
792 let index_of: std::collections::HashMap<&Path, usize> =
793 refs.iter().enumerate().map(|(i, p)| (*p, i)).collect();
794 let mut cb = |attrs: &VfAttrs, dir: &Path| {
795 if let Some(&slot) = index_of.get(dir) {
796 per_dir[slot].push(attrs.clone());
797 }
798 true
799 };
800 self.listdirv(&refs, AttrMask::default(), 0, false, &mut cb)
801 .map_err(|error| error.map_index(|_| root_index))?;
802 }
803
804 let mut next: Vec<PathBuf> = Vec::new();
805 for entries in per_dir {
806 let mut files: Vec<VfFile> = Vec::new();
807 for attrs in entries {
808 if attrs.ftype == VfType::Directory {
809 if let Some(path) = attrs.file.path() {
810 next.push(path.to_path_buf());
811 }
812 } else {
813 files.push(attrs.file);
814 }
815 }
816 if !files.is_empty() {
817 self.removev(&files)
818 .map_err(|error| error.map_index(|_| root_index))?;
819 }
820 }
821 dir_levels.push(frontier);
822 frontier = next;
823 }
824
825 for level in dir_levels.iter().rev() {
826 let dirs: Vec<VfFile> = level
827 .iter()
828 .map(|path| VfFile::from_os_path(path))
829 .collect();
830 self.removev(&dirs)
831 .map_err(|error| error.map_index(|_| root_index))?;
832 }
833 }
834 Ok(())
835 }
836
837 fn open_dir(&mut self, path: &Path) -> VfResult<VfDir> {
849 let mut attrs = VfAttrs {
850 file: VfFile::from_os_path(path),
851 masks: AttrMask::default(),
852 ..VfAttrs::default()
853 };
854 self.lgetattrsv(std::slice::from_mut(&mut attrs))?;
855 if attrs.ftype != VfType::Directory {
856 return Err(VfError::failure(0, ERR_NOTDIR));
857 }
858 Ok(VfDir::Path(path.to_path_buf()))
859 }
860
861 fn rm_dir_contents(&mut self, dir: &VfDir) -> VfRes {
865 self.rm_dir_contents_with_options(dir, RemoveOptions::default())
866 }
867
868 fn rm_dir_contents_with_options(&mut self, dir: &VfDir, options: RemoveOptions) -> VfRes {
870 match dir {
871 VfDir::Path(path) => self.rm_contents_with_options(path, options),
872 _ => Err(VfError::unsupported(0)),
873 }
874 }
875
876 fn close_dir(&mut self, _dir: &VfDir) -> VfResult<()> {
878 Ok(())
879 }
880
881 fn rm_contents(&mut self, dir: &Path) -> VfRes {
887 self.rm_contents_with_options(dir, RemoveOptions::default())
888 }
889
890 fn rm_contents_with_options(&mut self, dir: &Path, options: RemoveOptions) -> VfRes {
892 if options != RemoveOptions::default() {
893 return Err(VfError::unsupported(0));
894 }
895 let entries = self
896 .listdir(dir, AttrMask::default(), 0, false)
897 .map_err(|error| error.with_index(0))?;
898 let mut files: Vec<VfFile> = Vec::new();
899 let mut dirs: Vec<PathBuf> = Vec::new();
900 for attrs in entries {
901 if attrs.ftype == VfType::Directory {
902 if let Some(path) = attrs.file.path() {
903 dirs.push(path.to_path_buf());
904 }
905 } else {
906 files.push(attrs.file);
907 }
908 }
909 if !files.is_empty() {
910 self.removev(&files).map_err(|error| error.with_index(0))?;
911 }
912 for sub in dirs {
913 self.rm(&[sub.as_path()], true)
914 .map_err(|error| error.with_index(0))?;
915 }
916 Ok(())
917 }
918
919 fn ensure_empty_dir(&mut self, dir: &Path) -> VfRes {
925 match self.mkdir(dir, 0o777) {
926 Ok(()) => Ok(()),
927 Err(error) if error.err_no() == ERR_EXIST => {
928 let mut attrs = VfAttrs {
929 file: VfFile::from_os_path(dir),
930 masks: AttrMask::default(),
931 ..VfAttrs::default()
932 };
933 self.lgetattrsv(std::slice::from_mut(&mut attrs))?;
934 if attrs.ftype != VfType::Directory {
935 return Err(VfError::failure(0, ERR_NOTDIR));
936 }
937 self.rm_contents(dir)
938 }
939 Err(error) => Err(error),
940 }
941 }
942
943 fn cp_recursive(
945 &mut self,
946 src_dir: &Path,
947 dst: &Path,
948 symlinks: bool,
949 use_server_side_copy: bool,
950 ) -> VfRes;
951
952 fn vf_path(&self, file: &VfFile) -> VfResult<PathBuf> {
961 match file {
962 VfFile::Path {
963 base: VfPathBase::Abs,
964 path,
965 } => Ok(self.abs_path(&Path::new("/").join(path))),
966 VfFile::Path {
967 base: VfPathBase::Cwd,
968 path,
969 }
970 | VfFile::CwdPath(path) => Ok(self.abs_path(path)),
971 VfFile::Cwd => Ok(self.abs_path(Path::new(""))),
972 VfFile::Descriptor(_) | VfFile::Saved => Err(VfError::failure(0, ERR_INVAL)),
973 _ => Err(VfError::failure(0, ERR_INVAL)),
974 }
975 }
976
977 fn open(&mut self, pathname: &Path, flags: i32, mode: u32) -> VfResult<VfFile> {
979 self.open_by_path(VfPathBase::Cwd, pathname, flags, mode)
980 }
981
982 fn read(&mut self, file: &VfFile, offset: u64, length: usize) -> VfResult<Vec<u8>> {
984 let requests = [ReadOp::at(file.clone(), offset, length)];
985 let mut results = self.readv(&requests)?;
986 validate_read_results("read", &requests, &results)?;
987 Ok(results.pop().expect("validated one result").data)
988 }
989
990 fn write(&mut self, file: &VfFile, offset: u64, data: &[u8]) -> VfResult<usize> {
992 let owned = WriteOp::at(file.clone(), offset, data.to_vec());
993 let requests = [WriteOpRef {
994 file: &owned.file,
995 offset: owned.offset,
996 data: &owned.data,
997 creation: owned.creation,
998 truncate: owned.truncate,
999 }];
1000 let mut results = self.writev(std::slice::from_ref(&owned))?;
1001 validate_write_results("write", &requests, &results)?;
1002 Ok(results.pop().expect("validated one result").written)
1003 }
1004
1005 #[doc(hidden)]
1011 fn open_many(
1012 &mut self,
1013 paths: &[&Path],
1014 flags: &[i32],
1015 modes: &[u32],
1016 ) -> VfResult<ManyResults<VfFile>> {
1017 if paths.len() != flags.len() || paths.len() != modes.len() {
1018 return Err(VfError::failure(0, ERR_INVAL));
1019 }
1020 let mut results = Vec::with_capacity(paths.len());
1021 for ((path, flag), mode) in paths.iter().zip(flags).zip(modes) {
1022 match self.open(path, *flag, *mode) {
1023 Ok(file) => results.push(Ok(file)),
1024 Err(error) => {
1025 results.push(Err(error));
1026 break;
1027 }
1028 }
1029 }
1030 Ok(ManyResults::new(paths.len(), results))
1031 }
1032
1033 #[doc(hidden)]
1036 fn before_open_cleanup(&mut self, _index: usize, _file: &VfFile) -> VfResult<()> {
1037 Ok(())
1038 }
1039
1040 #[doc(hidden)]
1042 fn before_remove_type(&mut self, _index: usize) -> VfResult<()> {
1043 Ok(())
1044 }
1045
1046 fn openv(&mut self, paths: &[&Path], flags: &[i32], modes: &[u32]) -> VfResult<Vec<VfFile>> {
1050 let results = self.open_many(paths, flags, modes)?;
1051 results.try_collect_with_cleanup(paths.len(), |index, file| {
1052 let injected = self.before_open_cleanup(index, file);
1053 let closed = self.close(file);
1054 injected.and(closed)
1055 })
1056 }
1057
1058 fn openv_simple(&mut self, paths: &[&Path], flags: i32, mode: u32) -> VfResult<Vec<VfFile>> {
1061 let flags_v = vec![flags; paths.len()];
1062 let modes_v = vec![mode; paths.len()];
1063 self.openv(paths, &flags_v, &modes_v)
1064 }
1065
1066 fn closev(&mut self, files: &[VfFile]) -> VfRes {
1068 for (i, f) in files.iter().enumerate() {
1069 self.close(f).map_err(|e| e.with_index(i))?;
1070 }
1071 Ok(())
1072 }
1073
1074 fn stat(&mut self, path: &Path) -> VfResult<VfAttrs> {
1076 let mut a = VfAttrs {
1077 file: VfFile::from_os_path(path),
1078 masks: AttrMask::stat(),
1079 ..VfAttrs::default()
1080 };
1081 self.getattrsv(std::slice::from_mut(&mut a))?;
1082 Ok(a)
1083 }
1084
1085 fn lstat(&mut self, path: &Path) -> VfResult<VfAttrs> {
1087 let mut a = VfAttrs {
1088 file: VfFile::from_os_path(path),
1089 masks: AttrMask::stat(),
1090 ..VfAttrs::default()
1091 };
1092 self.lgetattrsv(std::slice::from_mut(&mut a))?;
1093 Ok(a)
1094 }
1095
1096 fn fstat(&mut self, tcf: &VfFile) -> VfResult<VfAttrs> {
1098 let mut a = VfAttrs {
1099 file: tcf.clone(),
1100 masks: AttrMask::stat(),
1101 ..VfAttrs::default()
1102 };
1103 self.getattrsv(std::slice::from_mut(&mut a))?;
1104 Ok(a)
1105 }
1106
1107 fn exists(&mut self, path: &Path) -> VfResult<bool> {
1111 match self.lstat(path) {
1112 Ok(_) => Ok(true),
1113 Err(e) if e.err_no() == ERR_NOENT => Ok(false),
1114 Err(e) => Err(e),
1115 }
1116 }
1117
1118 fn file_type(&mut self, path: &Path) -> VfResult<VfType> {
1121 Ok(self.lstat(path)?.ftype)
1122 }
1123
1124 fn listdirv(
1130 &mut self,
1131 dirs: &[&Path],
1132 masks: AttrMask,
1133 max_entries: usize,
1134 recursive: bool,
1135 cb: &mut dyn FnMut(&VfAttrs, &Path) -> bool,
1136 ) -> VfRes {
1137 let mut count = 0usize;
1138 for (i, d) in dirs.iter().enumerate() {
1139 if max_entries != 0 && count >= max_entries {
1140 break;
1141 }
1142 let remaining = if max_entries == 0 {
1143 0
1144 } else {
1145 max_entries - count
1146 };
1147 if !recursive {
1148 let mut stopped = false;
1149 self.visit_dir(d, masks, remaining, &mut |entry| {
1150 count += 1;
1151 if !cb(entry, d) {
1152 stopped = true;
1153 return false;
1154 }
1155 true
1156 })
1157 .map_err(|e| e.with_index(i))?;
1158 if stopped {
1159 return Ok(());
1160 }
1161 continue;
1162 }
1163 let entries = self
1164 .listdir(d, masks, remaining, true)
1165 .map_err(|e| e.with_index(i))?;
1166 for e in &entries {
1167 let entry_dir = if recursive {
1171 e.file.path().and_then(Path::parent).unwrap_or(d)
1172 } else {
1173 d
1174 };
1175 if !cb(e, entry_dir) {
1176 return Ok(());
1177 }
1178 count += 1;
1179 }
1180 }
1181 Ok(())
1182 }
1183
1184 fn visit_dir(
1191 &mut self,
1192 dir: &Path,
1193 masks: AttrMask,
1194 max_entries: usize,
1195 cb: &mut dyn FnMut(&VfAttrs) -> bool,
1196 ) -> VfRes {
1197 let entries = self.listdir(dir, masks, max_entries, false)?;
1198 for entry in &entries {
1199 if !cb(entry) {
1200 break;
1201 }
1202 }
1203 Ok(())
1204 }
1205
1206 fn unlink(&mut self, pathname: &Path) -> VfResult<()> {
1208 self.removev(&[VfFile::from_os_path(pathname)])
1209 }
1210
1211 fn unlinkv(&mut self, pathnames: &[&Path]) -> VfRes {
1213 let files: Vec<VfFile> = pathnames.iter().map(|p| VfFile::from_os_path(p)).collect();
1214 self.removev(&files)
1215 }
1216
1217 fn mkdir(&mut self, path: &Path, mode: u32) -> VfResult<()> {
1219 let a = VfAttrs {
1220 file: VfFile::from_os_path(path),
1221 masks: AttrMask::MODE,
1222 mode,
1223 ..VfAttrs::default()
1224 };
1225 self.mkdirv(std::slice::from_ref(&a))
1226 }
1227
1228 fn symlink(&mut self, oldpath: &Path, newpath: &Path) -> VfResult<()> {
1230 self.symlinkv(
1231 std::slice::from_ref(&oldpath),
1232 std::slice::from_ref(&newpath),
1233 )
1234 }
1235
1236 fn readlink(&mut self, path: &Path) -> VfResult<Vec<u8>> {
1238 take_single_result("readlink", self.readlinkv(std::slice::from_ref(&path))?)
1239 }
1240
1241 fn ldupv(&mut self, pairs: &[ExtentPair]) -> VfRes {
1245 self.dupv(pairs)
1246 }
1247
1248 fn copyv(&mut self, pairs: &[ExtentPair]) -> VfRes {
1253 self.dupv(pairs)
1254 }
1255
1256 fn ensure_dir(&mut self, dir: &Path, mode: u32) -> VfResult<()> {
1262 use std::path::Component;
1263 let mut so_far = PathBuf::new();
1264 for comp in self.abs_path(dir).components() {
1265 if let Component::Normal(part) = comp {
1266 so_far.push(part);
1267 let full = Path::new("/").join(&so_far);
1268 match self.mkdir(&full, mode) {
1269 Ok(()) => {}
1270 Err(e) if e.err_no() == ERR_EXIST => {}
1271 Err(e) => return Err(e),
1272 }
1273 }
1274 }
1275 Ok(())
1276 }
1277}
1278
1279pub trait VecFsExt: VecFs {
1286 fn stat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
1287 self.stat(path.as_ref())
1288 }
1289
1290 fn lstat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
1291 self.lstat(path.as_ref())
1292 }
1293
1294 fn exists_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<bool> {
1295 self.exists(path.as_ref())
1296 }
1297
1298 fn file_type_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfType> {
1299 self.file_type(path.as_ref())
1300 }
1301
1302 fn open_path<P: AsRef<Path>>(&mut self, path: P, flags: i32, mode: u32) -> VfResult<VfFile> {
1303 self.open(path.as_ref(), flags, mode)
1304 }
1305
1306 fn mkdir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
1307 self.mkdir(path.as_ref(), mode)
1308 }
1309
1310 fn ensure_dir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
1311 self.ensure_dir(path.as_ref(), mode)
1312 }
1313
1314 fn chdir_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
1315 self.chdir(path.as_ref())
1316 }
1317
1318 fn unlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
1319 self.unlink(path.as_ref())
1320 }
1321
1322 fn readlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<Vec<u8>> {
1323 self.readlink(path.as_ref())
1324 }
1325
1326 fn symlink_path<P, Q>(&mut self, oldpath: P, newpath: Q) -> VfResult<()>
1327 where
1328 P: AsRef<Path>,
1329 Q: AsRef<Path>,
1330 {
1331 self.symlink(oldpath.as_ref(), newpath.as_ref())
1332 }
1333
1334 fn listdir_path<P: AsRef<Path>>(
1335 &mut self,
1336 path: P,
1337 masks: AttrMask,
1338 max_count: usize,
1339 recursive: bool,
1340 ) -> VfResult<Vec<VfAttrs>> {
1341 self.listdir(path.as_ref(), masks, max_count, recursive)
1342 }
1343
1344 fn walk_path<P: AsRef<Path>>(
1345 &mut self,
1346 root: P,
1347 masks: AttrMask,
1348 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
1349 ) -> VfResult<Vec<WalkEntry>> {
1350 self.walk(root.as_ref(), masks, sort)
1351 }
1352
1353 fn openv_paths<P: AsRef<Path>>(
1354 &mut self,
1355 paths: &[P],
1356 flags: &[i32],
1357 modes: &[u32],
1358 ) -> VfResult<Vec<VfFile>> {
1359 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1360 self.openv(&refs, flags, modes)
1361 }
1362
1363 fn openv_simple_paths<P: AsRef<Path>>(
1364 &mut self,
1365 paths: &[P],
1366 flags: i32,
1367 mode: u32,
1368 ) -> VfResult<Vec<VfFile>> {
1369 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1370 self.openv_simple(&refs, flags, mode)
1371 }
1372
1373 fn unlinkv_paths<P: AsRef<Path>>(&mut self, paths: &[P]) -> VfRes {
1374 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1375 self.unlinkv(&refs)
1376 }
1377
1378 fn rm_paths<P: AsRef<Path>>(&mut self, paths: &[P], recursive: bool) -> VfRes {
1379 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1380 self.rm(&refs, recursive)
1381 }
1382
1383 fn rm_recursive_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1384 self.rm(&[path.as_ref()], true)
1385 }
1386
1387 fn rm_contents_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1388 self.rm_contents(path.as_ref())
1389 }
1390
1391 fn ensure_empty_dir_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1392 self.ensure_empty_dir(path.as_ref())
1393 }
1394}
1395
1396impl<T: VecFs + ?Sized> VecFsExt for T {}
1397
1398pub fn rm_recursive(fs: &mut impl VecFs, dir: &Path) -> VfRes {
1400 fs.rm(&[dir], true)
1401}
1402
1403#[cfg(test)]
1404mod contract_tests {
1405 use super::*;
1406
1407 fn assert_contract_error(error: VfError, index: Option<usize>, detail: &str) {
1408 assert_eq!(error.domain(), vfsi_core::ErrorDomain::Transport);
1409 assert_eq!(error.index(), index);
1410 assert_eq!(error.status(), None);
1411 let message = error.to_string();
1412 assert!(message.contains("backend contract violation"), "{message}");
1413 assert!(message.contains(detail), "{message}");
1414 }
1415
1416 fn request() -> ReadOp {
1417 ReadOp::at(VfFile::from_path("/file"), 0, 1)
1418 }
1419
1420 fn result() -> ReadResult {
1421 ReadResult {
1422 file: VfFile::from_path("/file"),
1423 offset: 0,
1424 data: vec![1],
1425 eof: true,
1426 }
1427 }
1428
1429 #[test]
1430 fn read_result_cardinality_is_checked_before_indexing() {
1431 let requests = [request(), request()];
1432 validate_read_results("test", &requests, &[result(), result()]).unwrap();
1433 validate_read_results("test", &[], &[]).unwrap();
1434 assert_contract_error(
1435 validate_read_results("test", &requests, &[result()]).unwrap_err(),
1436 None,
1437 "returned 1 results for 2 requests",
1438 );
1439 assert_contract_error(
1440 validate_read_results("test", &requests[..1], &[result(), result()]).unwrap_err(),
1441 None,
1442 "returned 2 results for 1 requests",
1443 );
1444 }
1445
1446 #[test]
1447 fn read_result_identity_offset_progress_and_size_are_checked() {
1448 let request = request();
1449 validate_read_results("test", std::slice::from_ref(&request), &[result()]).unwrap();
1450 let larger = ReadOp::at(VfFile::from_path("/file"), 0, 4);
1451 validate_read_results(
1452 "test",
1453 &[larger],
1454 &[ReadResult {
1455 eof: false,
1456 ..result()
1457 }],
1458 )
1459 .unwrap();
1460 validate_read_results(
1461 "test",
1462 std::slice::from_ref(&request),
1463 &[ReadResult {
1464 data: Vec::new(),
1465 eof: true,
1466 ..result()
1467 }],
1468 )
1469 .unwrap();
1470 for malformed in [
1471 ReadResult {
1472 file: VfFile::from_path("/other"),
1473 ..result()
1474 },
1475 ReadResult {
1476 offset: 1,
1477 ..result()
1478 },
1479 ReadResult {
1480 data: vec![1, 2],
1481 ..result()
1482 },
1483 ReadResult {
1484 data: Vec::new(),
1485 eof: false,
1486 ..result()
1487 },
1488 ] {
1489 assert_contract_error(
1492 validate_read_results(
1493 "test",
1494 &[request.clone(), request.clone()],
1495 &[result(), malformed],
1496 )
1497 .unwrap_err(),
1498 Some(1),
1499 "test",
1500 );
1501 }
1502 }
1503
1504 #[test]
1505 fn write_result_identity_offset_size_and_cardinality_are_checked() {
1506 let file = VfFile::from_path("/file");
1507 let request = WriteOpRef::new(&file, VfOffset::At(0), b"x");
1508 let valid = WriteResult {
1509 file: file.clone(),
1510 offset: 0,
1511 written: 1,
1512 stable: true,
1513 };
1514 validate_write_results("test", &[request], std::slice::from_ref(&valid)).unwrap();
1515 validate_write_results(
1518 "test",
1519 &[request],
1520 &[WriteResult {
1521 written: 0,
1522 ..valid.clone()
1523 }],
1524 )
1525 .unwrap();
1526 validate_write_results("test", &[], &[]).unwrap();
1527 assert_contract_error(
1528 validate_write_results("test", &[request], &[]).unwrap_err(),
1529 None,
1530 "returned 0 results",
1531 );
1532 assert_contract_error(
1533 validate_write_results("test", &[request], &[valid.clone(), valid.clone()])
1534 .unwrap_err(),
1535 None,
1536 "returned 2 results",
1537 );
1538 for malformed in [
1539 WriteResult {
1540 file: VfFile::from_path("/other"),
1541 ..valid.clone()
1542 },
1543 WriteResult {
1544 offset: 1,
1545 ..valid.clone()
1546 },
1547 WriteResult {
1548 written: 2,
1549 ..valid.clone()
1550 },
1551 ] {
1552 assert_contract_error(
1553 validate_write_results("test", &[request, request], &[valid.clone(), malformed])
1554 .unwrap_err(),
1555 Some(1),
1556 "test",
1557 );
1558 }
1559 }
1560
1561 #[test]
1562 fn scalar_result_cardinality_is_checked_without_panicking() {
1563 assert_contract_error(
1564 take_single_result::<u8>("readlink", Vec::new()).unwrap_err(),
1565 None,
1566 "readlink",
1567 );
1568 assert_contract_error(
1569 take_single_result("readlink", vec![1u8, 2]).unwrap_err(),
1570 None,
1571 "readlink",
1572 );
1573 assert_eq!(take_single_result("readlink", vec![7u8]).unwrap(), 7);
1574 }
1575}
1576
1577#[cfg(test)]
1578mod walk_depth_tests {
1579 use super::{DepthLimit, WalkOptions};
1580 #[test]
1581 fn depth_limit_is_one_byte_and_preserves_optional_inheritance() {
1582 const UNLIMITED: DepthLimit = DepthLimit::new(201);
1583 assert_eq!(std::mem::size_of::<DepthLimit>(), 1);
1584 assert_eq!(std::mem::size_of::<Option<DepthLimit>>(), 1);
1585 assert!(UNLIMITED.is_unlimited());
1586 for depth in 0..=200 {
1587 let limit = DepthLimit::new(depth);
1588 assert!(!limit.is_unlimited());
1589 assert_eq!(limit.get(), depth);
1590 }
1591 for depth in [201, 255, 256, 500, usize::MAX - 1, usize::MAX] {
1592 assert_eq!(DepthLimit::new(depth), DepthLimit::unlimited());
1593 }
1594 assert_ne!(None, Some(DepthLimit::new(0)));
1595 assert_ne!(None, Some(DepthLimit::unlimited()));
1596 }
1597 #[test]
1598 fn depth_limits_normalize_without_wrapping_and_keep_other_options() {
1599 const UNLIMITED: WalkOptions = WalkOptions::new().max_depth(201);
1600 assert_eq!(UNLIMITED.depth_limit(), usize::MAX);
1601 for depth in 0..=200 {
1602 let options = WalkOptions::new()
1603 .max_depth(depth)
1604 .max_entries(7)
1605 .max_path_bytes(11)
1606 .truncate_at_max_depth(true);
1607 assert_eq!(options.depth_limit(), depth);
1608 assert_eq!(options.entry_limit(), 7);
1609 assert_eq!(options.path_byte_limit(), 11);
1610 assert!(options.truncates_at_depth_limit());
1611 }
1612 for depth in [201, 254, 255, 256, 500, usize::MAX - 1, usize::MAX] {
1613 let options = WalkOptions::new().max_depth(depth);
1614 assert_eq!(options.depth_limit(), usize::MAX);
1615 assert_eq!(options.max_depth(0).depth_limit(), 0);
1616 assert_eq!(options.max_depth(200).depth_limit(), 200);
1617 }
1618 assert_eq!(WalkOptions::unlimited().depth_limit(), usize::MAX);
1619 }
1620}