1use super::*;
2use vfsi_core::internal::ManyResults;
3
4pub const DEFAULT_READ_MAX_BYTES: usize = 16 * 1024 * 1024;
6
7pub const DEFAULT_READ_STREAM_CHUNK_BYTES: usize = 1024 * 1024;
9
10pub const DEFAULT_READ_ALLV_MAX_TOTAL_BYTES: usize = DEFAULT_READ_MAX_BYTES;
12
13#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub struct ReadStreamOptions {
16 chunk_size: usize,
17}
18
19impl ReadStreamOptions {
20 pub const fn new() -> Self {
21 Self {
22 chunk_size: DEFAULT_READ_STREAM_CHUNK_BYTES,
23 }
24 }
25
26 pub const fn chunk_size(mut self, bytes: usize) -> Self {
31 self.chunk_size = bytes;
32 self
33 }
34
35 pub const fn chunk_size_bytes(self) -> usize {
36 self.chunk_size
37 }
38}
39
40impl Default for ReadStreamOptions {
41 fn default() -> Self {
42 Self::new()
43 }
44}
45
46#[doc(hidden)]
51pub struct DirPageCursor(Box<dyn std::any::Any + Send>);
52
53impl DirPageCursor {
54 pub fn new<T: std::any::Any + Send>(state: T) -> Self {
55 Self(Box::new(state))
56 }
57
58 pub fn into_state<T: std::any::Any + Send>(self) -> VfResult<T> {
59 self.0
60 .downcast::<T>()
61 .map(|state| *state)
62 .map_err(|_| VfError::client(0, ERR_INVAL))
63 }
64}
65
66pub const DEFAULT_DIRECTORY_MAX_ENTRIES: usize = 100_000;
68
69pub const DEFAULT_DIRECTORY_MAX_PATH_BYTES: usize = 16 * 1024 * 1024;
71
72pub const DEFAULT_WALK_MAX_DEPTH: usize = 128;
74
75#[derive(Clone, Copy, Debug, PartialEq, Eq)]
77pub struct ReadDirOptions {
78 max_entries: usize,
79 max_path_bytes: usize,
80}
81
82impl ReadDirOptions {
83 pub const fn new() -> Self {
84 Self {
85 max_entries: DEFAULT_DIRECTORY_MAX_ENTRIES,
86 max_path_bytes: DEFAULT_DIRECTORY_MAX_PATH_BYTES,
87 }
88 }
89
90 pub const fn unlimited() -> Self {
92 Self {
93 max_entries: usize::MAX,
94 max_path_bytes: usize::MAX,
95 }
96 }
97
98 pub const fn max_entries(mut self, entries: usize) -> Self {
99 self.max_entries = entries;
100 self
101 }
102
103 pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
104 self.max_path_bytes = bytes;
105 self
106 }
107
108 pub const fn entry_limit(self) -> usize {
109 self.max_entries
110 }
111
112 pub const fn path_byte_limit(self) -> usize {
113 self.max_path_bytes
114 }
115}
116
117impl Default for ReadDirOptions {
118 fn default() -> Self {
119 Self::new()
120 }
121}
122
123#[derive(Clone, Copy, Debug, PartialEq, Eq)]
125pub struct WalkOptions {
126 directory: ReadDirOptions,
127 max_depth: usize,
128 truncate_at_max_depth: bool,
129}
130
131impl WalkOptions {
132 pub const fn new() -> Self {
133 Self {
134 directory: ReadDirOptions::new(),
135 max_depth: DEFAULT_WALK_MAX_DEPTH,
136 truncate_at_max_depth: false,
137 }
138 }
139
140 pub const fn unlimited() -> Self {
142 Self {
143 directory: ReadDirOptions::unlimited(),
144 max_depth: usize::MAX,
145 truncate_at_max_depth: false,
146 }
147 }
148
149 pub const fn max_entries(mut self, entries: usize) -> Self {
150 self.directory = self.directory.max_entries(entries);
151 self
152 }
153
154 pub const fn max_path_bytes(mut self, bytes: usize) -> Self {
155 self.directory = self.directory.max_path_bytes(bytes);
156 self
157 }
158
159 pub const fn max_depth(mut self, depth: usize) -> Self {
160 self.max_depth = depth;
161 self
162 }
163
164 pub const fn truncate_at_max_depth(mut self, truncate: bool) -> Self {
167 self.truncate_at_max_depth = truncate;
168 self
169 }
170
171 pub const fn entry_limit(self) -> usize {
172 self.directory.entry_limit()
173 }
174
175 pub const fn path_byte_limit(self) -> usize {
176 self.directory.path_byte_limit()
177 }
178
179 pub const fn depth_limit(self) -> usize {
180 self.max_depth
181 }
182
183 pub const fn truncates_at_depth_limit(self) -> bool {
184 self.truncate_at_max_depth
185 }
186}
187
188impl Default for WalkOptions {
189 fn default() -> Self {
190 Self::new()
191 }
192}
193
194#[derive(Clone, Copy, Debug, PartialEq, Eq)]
196pub struct ReadAllOptions {
197 max_total_bytes: usize,
198}
199
200impl ReadAllOptions {
201 pub const fn new() -> Self {
202 Self {
203 max_total_bytes: DEFAULT_READ_ALLV_MAX_TOTAL_BYTES,
204 }
205 }
206
207 pub const fn max_total_bytes(mut self, bytes: usize) -> Self {
209 self.max_total_bytes = bytes;
210 self
211 }
212
213 pub const fn total_byte_limit(self) -> usize {
214 self.max_total_bytes
215 }
216}
217
218impl Default for ReadAllOptions {
219 fn default() -> Self {
220 Self::new()
221 }
222}
223
224fn contract_error(
225 operation: &str,
226 index: Option<usize>,
227 detail: impl std::fmt::Display,
228) -> VfError {
229 VfError::transport(
230 index,
231 format!("{operation} backend contract violation: {detail}"),
232 )
233}
234
235fn take_single_result<T>(operation: &str, mut results: Vec<T>) -> VfResult<T> {
236 if results.len() != 1 {
237 return Err(contract_error(
238 operation,
239 None,
240 format!("returned {} results for one request", results.len()),
241 ));
242 }
243 Ok(results.pop().expect("validated one result"))
244}
245
246pub(crate) fn validate_read_results(
247 operation: &str,
248 requests: &[ReadOp],
249 results: &[ReadResult],
250) -> VfResult<()> {
251 if results.len() != requests.len() {
252 return Err(contract_error(
253 operation,
254 None,
255 format!(
256 "returned {} results for {} requests",
257 results.len(),
258 requests.len()
259 ),
260 ));
261 }
262 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
263 if result.file != request.file {
264 return Err(contract_error(
265 operation,
266 Some(index),
267 "result file does not match request",
268 ));
269 }
270 if result.data.len() > request.length {
271 return Err(contract_error(
272 operation,
273 Some(index),
274 format!(
275 "returned {} bytes for a {}-byte read",
276 result.data.len(),
277 request.length
278 ),
279 ));
280 }
281 if let VfOffset::At(expected) = request.offset
282 && result.offset != expected
283 {
284 return Err(contract_error(
285 operation,
286 Some(index),
287 format!("result offset {} does not match {expected}", result.offset),
288 ));
289 }
290 if request.length != 0 && result.data.is_empty() && !result.eof {
291 return Err(contract_error(
292 operation,
293 Some(index),
294 "read made no progress without reporting EOF",
295 ));
296 }
297 }
298 Ok(())
299}
300
301pub(crate) fn validate_read_into_results(
302 operation: &str,
303 requests: &[ReadOp],
304 results: &[ReadIntoResult],
305) -> VfResult<()> {
306 if results.len() != requests.len() {
307 return Err(contract_error(
308 operation,
309 None,
310 format!(
311 "returned {} results for {} requests",
312 results.len(),
313 requests.len()
314 ),
315 ));
316 }
317 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
318 if result.file != request.file {
319 return Err(contract_error(
320 operation,
321 Some(index),
322 "result file does not match request",
323 ));
324 }
325 if result.read > request.length {
326 return Err(contract_error(
327 operation,
328 Some(index),
329 format!(
330 "returned {} bytes for a {}-byte read",
331 result.read, request.length
332 ),
333 ));
334 }
335 if let VfOffset::At(expected) = request.offset
336 && result.offset != expected
337 {
338 return Err(contract_error(
339 operation,
340 Some(index),
341 format!("result offset {} does not match {expected}", result.offset),
342 ));
343 }
344 if request.length != 0 && result.read == 0 && !result.eof {
345 return Err(contract_error(
346 operation,
347 Some(index),
348 "read made no progress without reporting EOF",
349 ));
350 }
351 }
352 Ok(())
353}
354
355pub(crate) fn validate_write_results(
356 operation: &str,
357 requests: &[WriteOpRef<'_>],
358 results: &[WriteResult],
359) -> VfResult<()> {
360 if results.len() != requests.len() {
361 return Err(contract_error(
362 operation,
363 None,
364 format!(
365 "returned {} results for {} requests",
366 results.len(),
367 requests.len()
368 ),
369 ));
370 }
371 for (index, (request, result)) in requests.iter().zip(results).enumerate() {
372 if result.file != *request.file {
373 return Err(contract_error(
374 operation,
375 Some(index),
376 "result file does not match request",
377 ));
378 }
379 if result.written > request.data.len() {
380 return Err(contract_error(
381 operation,
382 Some(index),
383 format!(
384 "reported {} bytes written for a {}-byte write",
385 result.written,
386 request.data.len()
387 ),
388 ));
389 }
390 if let VfOffset::At(expected) = request.offset
391 && result.offset != expected
392 {
393 return Err(contract_error(
394 operation,
395 Some(index),
396 format!("result offset {} does not match {expected}", result.offset),
397 ));
398 }
399 }
400 Ok(())
401}
402
403pub trait VecFs {
418 #[deprecated(note = "use the NFS backend's NfsExtensions trait")]
420 fn nfs_minorversion(&self) -> Option<u32> {
421 None
422 }
423
424 #[deprecated(note = "use the SMB backend's SmbExtensions trait")]
427 fn smb_dialect(&self) -> Option<u16> {
428 None
429 }
430
431 fn capabilities(&self) -> u64 {
434 0
435 }
436
437 fn typed_capabilities(&self) -> Capabilities {
439 Capabilities::from_bits_retain(self.capabilities())
440 }
441
442 fn abs_path(&self, path: &Path) -> PathBuf;
453
454 fn open_by_path(
458 &mut self,
459 base: VfPathBase,
460 pathname: &Path,
461 flags: i32,
462 mode: u32,
463 ) -> VfResult<VfFile>;
464
465 fn close(&mut self, tcf: &VfFile) -> VfResult<()>;
467
468 fn sync_data(&mut self, tcf: &VfFile) -> VfResult<()>;
471
472 fn sync_all(&mut self, tcf: &VfFile) -> VfResult<()> {
474 self.sync_data(tcf)
475 }
476
477 fn chdir(&mut self, path: &Path) -> VfResult<()>;
479
480 fn getcwd(&self) -> PathBuf;
482
483 fn readv(&mut self, reads: &[ReadOp]) -> VfResult<Vec<ReadResult>>;
486
487 fn readv_into(
491 &mut self,
492 reads: &[ReadOp],
493 buffers: &mut [&mut [u8]],
494 ) -> VfResult<Vec<ReadIntoResult>> {
495 if reads.len() != buffers.len() {
496 return Err(VfError::client(0, ERR_INVAL));
497 }
498 for (index, (request, buffer)) in reads.iter().zip(buffers.iter()).enumerate() {
499 if request.length != buffer.len() {
500 return Err(VfError::client(index, ERR_INVAL));
501 }
502 }
503 let results = self.readv(reads)?;
504 validate_read_results("readv_into", reads, &results)?;
505 Ok(results
506 .into_iter()
507 .zip(buffers.iter_mut())
508 .map(|(result, buffer)| {
509 buffer[..result.data.len()].copy_from_slice(&result.data);
510 ReadIntoResult {
511 file: result.file,
512 offset: result.offset,
513 read: result.data.len(),
514 eof: result.eof,
515 }
516 })
517 .collect())
518 }
519
520 fn read_allv(&mut self, files: &[VfFile]) -> VfResult<Vec<Vec<u8>>> {
524 self.read_allv_with_options(files, ReadAllOptions::default())
525 }
526
527 fn read_allv_with_options(
529 &mut self,
530 files: &[VfFile],
531 options: ReadAllOptions,
532 ) -> VfResult<Vec<Vec<u8>>> {
533 let mut out: Vec<Vec<u8>> = files.iter().map(|_| Vec::new()).collect();
534 let mut total = 0usize;
535 let mut limit_error = None;
536 let stream_budget = options
537 .total_byte_limit()
538 .clamp(1, DEFAULT_READ_ALLV_MAX_TOTAL_BYTES);
539 let chunk_size = stream_budget.min(1024 * 1024);
540 self.read_streamv(
541 files,
542 chunk_size,
543 stream_budget,
544 &mut |index, _, data, _| {
545 let Some(next_total) = total.checked_add(data.len()) else {
546 limit_error = Some(index);
547 return false;
548 };
549 if next_total > options.total_byte_limit() {
550 limit_error = Some(index);
551 return false;
552 }
553 out[index].extend_from_slice(data);
554 total = next_total;
555 true
556 },
557 )?;
558 if let Some(index) = limit_error {
559 return Err(VfError::failure(index, libc::EFBIG as u32));
560 }
561 Ok(out)
562 }
563
564 fn writev(&mut self, writes: &[WriteOp]) -> VfResult<Vec<WriteResult>>;
567
568 fn writev_borrowed(&mut self, writes: &[WriteOpRef<'_>]) -> VfResult<Vec<WriteResult>> {
572 let owned: Vec<WriteOp> = writes
573 .iter()
574 .map(|write| WriteOp {
575 file: write.file.clone(),
576 offset: write.offset,
577 data: write.data.to_vec(),
578 creation: write.creation,
579 truncate: write.truncate,
580 })
581 .collect();
582 self.writev(&owned)
583 }
584
585 fn fseek(&mut self, tcf: &VfFile, offset: i64, whence: SeekFrom) -> VfResult<i64>;
591
592 fn getattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
595
596 fn lgetattrsv(&mut self, attrs: &mut [VfAttrs]) -> VfRes;
599
600 fn setattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
606
607 fn lsetattrsv(&mut self, attrs: &[VfAttrs]) -> VfRes;
612
613 fn listdir(
620 &mut self,
621 dir: &Path,
622 masks: AttrMask,
623 max_count: usize,
624 recursive: bool,
625 ) -> VfResult<Vec<VfAttrs>>;
626
627 fn listdir_page(
635 &mut self,
636 dir: &Path,
637 masks: AttrMask,
638 cursor: Option<DirPageCursor>,
639 page_size: usize,
640 max_entries: usize,
641 ) -> VfResult<(Vec<VfAttrs>, Option<DirPageCursor>)> {
642 if page_size == 0 {
643 return Err(VfError::client(0, ERR_INVAL));
644 }
645 let mut remaining = match cursor {
646 Some(cursor) => cursor.into_state::<std::vec::IntoIter<VfAttrs>>()?,
647 None => self.listdir(dir, masks, max_entries, false)?.into_iter(),
648 };
649 let page: Vec<_> = remaining.by_ref().take(page_size).collect();
650 let next = if remaining.len() == 0 {
651 None
652 } else {
653 Some(DirPageCursor::new(remaining))
654 };
655 Ok((page, next))
656 }
657
658 fn walk(
666 &mut self,
667 root: &Path,
668 masks: AttrMask,
669 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
670 ) -> VfResult<Vec<WalkEntry>> {
671 self.walk_with_options(root, masks, WalkOptions::default(), sort)
672 }
673
674 fn walk_with_options(
676 &mut self,
677 root: &Path,
678 masks: AttrMask,
679 options: WalkOptions,
680 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
681 ) -> VfResult<Vec<WalkEntry>> {
682 let mut out = Vec::new();
686 let mut stack = vec![(root.to_path_buf(), 0usize)];
687 let mut entry_count = 0usize;
688 let mut path_bytes = 0usize;
689 while let Some((dir, depth)) = stack.pop() {
690 let remaining = options.entry_limit().saturating_sub(entry_count);
691 let request_count = remaining.saturating_add(1);
692 let mut entries = self.listdir(&dir, masks, request_count, false)?;
693 if entries.len() > remaining {
694 return Err(
695 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
696 );
697 }
698 for entry in &entries {
699 let bytes = entry.file.path().map_or(0, |path| path.as_os_str().len());
700 path_bytes = path_bytes.checked_add(bytes).ok_or_else(|| {
701 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
702 })?;
703 if path_bytes > options.path_byte_limit() {
704 return Err(VfError::failure(entry_count, libc::EFBIG as u32)
705 .with_context("walk", &dir));
706 }
707 entry_count += 1;
708 }
709 sort(dir.as_path(), &mut entries);
710 let subdirs: Vec<PathBuf> = entries
711 .iter()
712 .filter(|e| e.ftype == VfType::Directory)
713 .filter_map(|e| e.file.path().map(|p| p.to_path_buf()))
714 .collect();
715 if !subdirs.is_empty()
716 && depth >= options.depth_limit()
717 && !options.truncates_at_depth_limit()
718 {
719 return Err(
720 VfError::failure(entry_count, libc::EFBIG as u32).with_context("walk", &dir)
721 );
722 }
723 if depth < options.depth_limit() {
724 for s in subdirs.into_iter().rev() {
725 stack.push((s, depth + 1));
726 }
727 }
728 out.push(WalkEntry { path: dir, entries });
729 }
730 Ok(out)
731 }
732
733 fn renamev(&mut self, pairs: &[(VfFile, VfFile)]) -> VfRes;
735
736 fn removev(&mut self, files: &[VfFile]) -> VfRes;
738
739 fn mkdirv(&mut self, dirs: &[VfAttrs]) -> VfRes;
741
742 fn symlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
744
745 fn readlinkv(&mut self, paths: &[&Path]) -> VfResult<Vec<Vec<u8>>>;
747
748 fn hardlinkv(&mut self, oldpaths: &[&Path], newpaths: &[&Path]) -> VfRes;
750
751 fn dupv(&mut self, pairs: &[ExtentPair]) -> VfRes;
754
755 fn lcopyv(&mut self, pairs: &[ExtentPair]) -> VfRes;
759
760 fn write_adb(&mut self, patterns: &[Adb]) -> VfResult<Vec<usize>>;
763
764 fn read_streamv(
773 &mut self,
774 files: &[VfFile],
775 chunk_size: usize,
776 memory_limit: usize,
777 cb: &mut ReadStreamCallback<'_>,
778 ) -> VfRes {
779 use std::collections::VecDeque;
780
781 if chunk_size == 0 || memory_limit == 0 {
782 return Err(VfError::failure(0, ERR_INVAL));
783 }
784 let mut pending: VecDeque<usize> = (0..files.len()).collect();
785 let mut offsets = vec![0u64; files.len()];
786 while !pending.is_empty() {
787 let mut budget = memory_limit;
788 let mut batch_indices = Vec::new();
789 let mut reads = Vec::new();
790 while budget > 0 && !pending.is_empty() {
791 let index = pending.pop_front().expect("pending was non-empty");
792 let length = chunk_size.min(budget);
793 reads.push(ReadOp::at(files[index].clone(), offsets[index], length));
794 batch_indices.push(index);
795 budget -= length;
796 }
797 let results = self.readv(&reads).map_err(|error| {
798 error.index().map_or(error.clone(), |local_index| {
799 batch_indices
800 .get(local_index)
801 .copied()
802 .map_or(error.clone(), |index| error.with_index(index))
803 })
804 })?;
805 validate_read_results("read_streamv", &reads, &results).map_err(|error| {
806 error.index().map_or(error.clone(), |local_index| {
807 batch_indices
808 .get(local_index)
809 .copied()
810 .map_or(error.clone(), |index| error.with_index(index))
811 })
812 })?;
813 for (batch_index, result) in results.into_iter().enumerate() {
814 let index = batch_indices[batch_index];
815 let offset = offsets[index];
816 let eof = result.eof;
817 offsets[index] = offset
818 .checked_add(result.data.len() as u64)
819 .ok_or_else(|| VfError::failure(index, libc::EOVERFLOW as u32))?;
820 if !cb(index, offset, &result.data, eof) {
821 return Ok(());
822 }
823 if !eof {
824 pending.push_back(index);
825 }
826 }
827 }
828 Ok(())
829 }
830
831 fn rm(&mut self, objs: &[&Path], recursive: bool) -> VfRes {
848 self.rm_with_options(objs, recursive, RemoveOptions::default())
849 }
850
851 fn rm_with_options(
860 &mut self,
861 objs: &[&Path],
862 recursive: bool,
863 options: RemoveOptions,
864 ) -> VfRes {
865 if options != RemoveOptions::default() {
866 return Err(VfError::unsupported(0));
867 }
868 if objs.is_empty() {
869 return Ok(());
870 }
871 if !recursive {
872 let files: Vec<VfFile> = objs.iter().map(|p| VfFile::from_os_path(p)).collect();
873 return self.removev(&files);
874 }
875
876 for (root_index, root) in objs.iter().enumerate() {
877 self.before_remove_type(root_index)?;
878 let mut root_attrs = VfAttrs {
880 file: VfFile::from_os_path(root),
881 masks: AttrMask::default(),
882 ..VfAttrs::default()
883 };
884 self.lgetattrsv(std::slice::from_mut(&mut root_attrs))
885 .map_err(|error| error.map_index(|_| root_index))?;
886 if root_attrs.ftype != VfType::Directory {
887 self.removev(&[VfFile::from_os_path(root)])
888 .map_err(|error| error.map_index(|_| root_index))?;
889 continue;
890 }
891
892 let mut dir_levels: Vec<Vec<PathBuf>> = Vec::new();
895 let mut frontier: Vec<PathBuf> = vec![root.to_path_buf()];
896 while !frontier.is_empty() {
897 let refs: Vec<&Path> = frontier.iter().map(PathBuf::as_path).collect();
898 let mut per_dir: Vec<Vec<VfAttrs>> = (0..refs.len()).map(|_| Vec::new()).collect();
899 {
900 let index_of: std::collections::HashMap<&Path, usize> =
901 refs.iter().enumerate().map(|(i, p)| (*p, i)).collect();
902 let mut cb = |attrs: &VfAttrs, dir: &Path| {
903 if let Some(&slot) = index_of.get(dir) {
904 per_dir[slot].push(attrs.clone());
905 }
906 true
907 };
908 self.listdirv(&refs, AttrMask::default(), 0, false, &mut cb)
909 .map_err(|error| error.map_index(|_| root_index))?;
910 }
911
912 let mut next: Vec<PathBuf> = Vec::new();
913 for entries in per_dir {
914 let mut files: Vec<VfFile> = Vec::new();
915 for attrs in entries {
916 if attrs.ftype == VfType::Directory {
917 if let Some(path) = attrs.file.path() {
918 next.push(path.to_path_buf());
919 }
920 } else {
921 files.push(attrs.file);
922 }
923 }
924 if !files.is_empty() {
925 self.removev(&files)
926 .map_err(|error| error.map_index(|_| root_index))?;
927 }
928 }
929 dir_levels.push(frontier);
930 frontier = next;
931 }
932
933 for level in dir_levels.iter().rev() {
934 let dirs: Vec<VfFile> = level
935 .iter()
936 .map(|path| VfFile::from_os_path(path))
937 .collect();
938 self.removev(&dirs)
939 .map_err(|error| error.map_index(|_| root_index))?;
940 }
941 }
942 Ok(())
943 }
944
945 fn open_dir(&mut self, path: &Path) -> VfResult<VfDir> {
957 let mut attrs = VfAttrs {
958 file: VfFile::from_os_path(path),
959 masks: AttrMask::default(),
960 ..VfAttrs::default()
961 };
962 self.lgetattrsv(std::slice::from_mut(&mut attrs))?;
963 if attrs.ftype != VfType::Directory {
964 return Err(VfError::failure(0, ERR_NOTDIR));
965 }
966 Ok(VfDir::Path(path.to_path_buf()))
967 }
968
969 fn rm_dir_contents(&mut self, dir: &VfDir) -> VfRes {
973 self.rm_dir_contents_with_options(dir, RemoveOptions::default())
974 }
975
976 fn rm_dir_contents_with_options(&mut self, dir: &VfDir, options: RemoveOptions) -> VfRes {
978 match dir {
979 VfDir::Path(path) => self.rm_contents_with_options(path, options),
980 _ => Err(VfError::unsupported(0)),
981 }
982 }
983
984 fn close_dir(&mut self, _dir: &VfDir) -> VfResult<()> {
986 Ok(())
987 }
988
989 fn rm_contents(&mut self, dir: &Path) -> VfRes {
995 self.rm_contents_with_options(dir, RemoveOptions::default())
996 }
997
998 fn rm_contents_with_options(&mut self, dir: &Path, options: RemoveOptions) -> VfRes {
1000 if options != RemoveOptions::default() {
1001 return Err(VfError::unsupported(0));
1002 }
1003 let entries = self
1004 .listdir(dir, AttrMask::default(), 0, false)
1005 .map_err(|error| error.with_index(0))?;
1006 let mut files: Vec<VfFile> = Vec::new();
1007 let mut dirs: Vec<PathBuf> = Vec::new();
1008 for attrs in entries {
1009 if attrs.ftype == VfType::Directory {
1010 if let Some(path) = attrs.file.path() {
1011 dirs.push(path.to_path_buf());
1012 }
1013 } else {
1014 files.push(attrs.file);
1015 }
1016 }
1017 if !files.is_empty() {
1018 self.removev(&files).map_err(|error| error.with_index(0))?;
1019 }
1020 for sub in dirs {
1021 self.rm(&[sub.as_path()], true)
1022 .map_err(|error| error.with_index(0))?;
1023 }
1024 Ok(())
1025 }
1026
1027 fn ensure_empty_dir(&mut self, dir: &Path) -> VfRes {
1033 match self.mkdir(dir, 0o777) {
1034 Ok(()) => Ok(()),
1035 Err(error) if error.err_no() == ERR_EXIST => {
1036 let mut attrs = VfAttrs {
1037 file: VfFile::from_os_path(dir),
1038 masks: AttrMask::default(),
1039 ..VfAttrs::default()
1040 };
1041 self.lgetattrsv(std::slice::from_mut(&mut attrs))?;
1042 if attrs.ftype != VfType::Directory {
1043 return Err(VfError::failure(0, ERR_NOTDIR));
1044 }
1045 self.rm_contents(dir)
1046 }
1047 Err(error) => Err(error),
1048 }
1049 }
1050
1051 fn cp_recursive(
1053 &mut self,
1054 src_dir: &Path,
1055 dst: &Path,
1056 symlinks: bool,
1057 use_server_side_copy: bool,
1058 ) -> VfRes;
1059
1060 fn vf_path(&self, file: &VfFile) -> VfResult<PathBuf> {
1069 match file {
1070 VfFile::Path {
1071 base: VfPathBase::Abs,
1072 path,
1073 } => Ok(self.abs_path(&Path::new("/").join(path))),
1074 VfFile::Path {
1075 base: VfPathBase::Cwd,
1076 path,
1077 }
1078 | VfFile::CwdPath(path) => Ok(self.abs_path(path)),
1079 VfFile::Cwd => Ok(self.abs_path(Path::new(""))),
1080 VfFile::Descriptor(_) | VfFile::Saved => Err(VfError::failure(0, ERR_INVAL)),
1081 _ => Err(VfError::failure(0, ERR_INVAL)),
1082 }
1083 }
1084
1085 fn open(&mut self, pathname: &Path, flags: i32, mode: u32) -> VfResult<VfFile> {
1087 self.open_by_path(VfPathBase::Cwd, pathname, flags, mode)
1088 }
1089
1090 fn read(&mut self, file: &VfFile, offset: u64, length: usize) -> VfResult<Vec<u8>> {
1092 let requests = [ReadOp::at(file.clone(), offset, length)];
1093 let mut results = self.readv(&requests)?;
1094 validate_read_results("read", &requests, &results)?;
1095 Ok(results.pop().expect("validated one result").data)
1096 }
1097
1098 fn write(&mut self, file: &VfFile, offset: u64, data: &[u8]) -> VfResult<usize> {
1100 let owned = WriteOp::at(file.clone(), offset, data.to_vec());
1101 let requests = [WriteOpRef {
1102 file: &owned.file,
1103 offset: owned.offset,
1104 data: &owned.data,
1105 creation: owned.creation,
1106 truncate: owned.truncate,
1107 }];
1108 let mut results = self.writev(std::slice::from_ref(&owned))?;
1109 validate_write_results("write", &requests, &results)?;
1110 Ok(results.pop().expect("validated one result").written)
1111 }
1112
1113 #[doc(hidden)]
1119 fn open_many(
1120 &mut self,
1121 paths: &[&Path],
1122 flags: &[i32],
1123 modes: &[u32],
1124 ) -> VfResult<ManyResults<VfFile>> {
1125 if paths.len() != flags.len() || paths.len() != modes.len() {
1126 return Err(VfError::failure(0, ERR_INVAL));
1127 }
1128 let mut results = Vec::with_capacity(paths.len());
1129 for ((path, flag), mode) in paths.iter().zip(flags).zip(modes) {
1130 match self.open(path, *flag, *mode) {
1131 Ok(file) => results.push(Ok(file)),
1132 Err(error) => {
1133 results.push(Err(error));
1134 break;
1135 }
1136 }
1137 }
1138 Ok(ManyResults::new(paths.len(), results))
1139 }
1140
1141 #[doc(hidden)]
1144 fn before_open_cleanup(&mut self, _index: usize, _file: &VfFile) -> VfResult<()> {
1145 Ok(())
1146 }
1147
1148 #[doc(hidden)]
1150 fn before_remove_type(&mut self, _index: usize) -> VfResult<()> {
1151 Ok(())
1152 }
1153
1154 fn openv(&mut self, paths: &[&Path], flags: &[i32], modes: &[u32]) -> VfResult<Vec<VfFile>> {
1158 let results = self.open_many(paths, flags, modes)?;
1159 results.try_collect_with_cleanup(paths.len(), |index, file| {
1160 let injected = self.before_open_cleanup(index, file);
1161 let closed = self.close(file);
1162 injected.and(closed)
1163 })
1164 }
1165
1166 fn openv_simple(&mut self, paths: &[&Path], flags: i32, mode: u32) -> VfResult<Vec<VfFile>> {
1169 let flags_v = vec![flags; paths.len()];
1170 let modes_v = vec![mode; paths.len()];
1171 self.openv(paths, &flags_v, &modes_v)
1172 }
1173
1174 fn closev(&mut self, files: &[VfFile]) -> VfRes {
1176 for (i, f) in files.iter().enumerate() {
1177 self.close(f).map_err(|e| e.with_index(i))?;
1178 }
1179 Ok(())
1180 }
1181
1182 fn stat(&mut self, path: &Path) -> VfResult<VfAttrs> {
1184 let mut a = VfAttrs {
1185 file: VfFile::from_os_path(path),
1186 masks: AttrMask::stat(),
1187 ..VfAttrs::default()
1188 };
1189 self.getattrsv(std::slice::from_mut(&mut a))?;
1190 Ok(a)
1191 }
1192
1193 fn lstat(&mut self, path: &Path) -> VfResult<VfAttrs> {
1195 let mut a = VfAttrs {
1196 file: VfFile::from_os_path(path),
1197 masks: AttrMask::stat(),
1198 ..VfAttrs::default()
1199 };
1200 self.lgetattrsv(std::slice::from_mut(&mut a))?;
1201 Ok(a)
1202 }
1203
1204 fn fstat(&mut self, tcf: &VfFile) -> VfResult<VfAttrs> {
1206 let mut a = VfAttrs {
1207 file: tcf.clone(),
1208 masks: AttrMask::stat(),
1209 ..VfAttrs::default()
1210 };
1211 self.getattrsv(std::slice::from_mut(&mut a))?;
1212 Ok(a)
1213 }
1214
1215 fn exists(&mut self, path: &Path) -> VfResult<bool> {
1219 match self.lstat(path) {
1220 Ok(_) => Ok(true),
1221 Err(e) if e.err_no() == ERR_NOENT => Ok(false),
1222 Err(e) => Err(e),
1223 }
1224 }
1225
1226 fn file_type(&mut self, path: &Path) -> VfResult<VfType> {
1229 Ok(self.lstat(path)?.ftype)
1230 }
1231
1232 fn listdirv(
1238 &mut self,
1239 dirs: &[&Path],
1240 masks: AttrMask,
1241 max_entries: usize,
1242 recursive: bool,
1243 cb: &mut dyn FnMut(&VfAttrs, &Path) -> bool,
1244 ) -> VfRes {
1245 let mut count = 0usize;
1246 for (i, d) in dirs.iter().enumerate() {
1247 if max_entries != 0 && count >= max_entries {
1248 break;
1249 }
1250 let remaining = if max_entries == 0 {
1251 0
1252 } else {
1253 max_entries - count
1254 };
1255 if !recursive {
1256 let mut stopped = false;
1257 self.visit_dir(d, masks, remaining, &mut |entry| {
1258 count += 1;
1259 if !cb(entry, d) {
1260 stopped = true;
1261 return false;
1262 }
1263 true
1264 })
1265 .map_err(|e| e.with_index(i))?;
1266 if stopped {
1267 return Ok(());
1268 }
1269 continue;
1270 }
1271 let entries = self
1272 .listdir(d, masks, remaining, true)
1273 .map_err(|e| e.with_index(i))?;
1274 for e in &entries {
1275 let entry_dir = if recursive {
1279 e.file.path().and_then(Path::parent).unwrap_or(d)
1280 } else {
1281 d
1282 };
1283 if !cb(e, entry_dir) {
1284 return Ok(());
1285 }
1286 count += 1;
1287 }
1288 }
1289 Ok(())
1290 }
1291
1292 fn visit_dir(
1299 &mut self,
1300 dir: &Path,
1301 masks: AttrMask,
1302 max_entries: usize,
1303 cb: &mut dyn FnMut(&VfAttrs) -> bool,
1304 ) -> VfRes {
1305 let entries = self.listdir(dir, masks, max_entries, false)?;
1306 for entry in &entries {
1307 if !cb(entry) {
1308 break;
1309 }
1310 }
1311 Ok(())
1312 }
1313
1314 fn unlink(&mut self, pathname: &Path) -> VfResult<()> {
1316 self.removev(&[VfFile::from_os_path(pathname)])
1317 }
1318
1319 fn unlinkv(&mut self, pathnames: &[&Path]) -> VfRes {
1321 let files: Vec<VfFile> = pathnames.iter().map(|p| VfFile::from_os_path(p)).collect();
1322 self.removev(&files)
1323 }
1324
1325 fn mkdir(&mut self, path: &Path, mode: u32) -> VfResult<()> {
1327 let a = VfAttrs {
1328 file: VfFile::from_os_path(path),
1329 masks: AttrMask::MODE,
1330 mode,
1331 ..VfAttrs::default()
1332 };
1333 self.mkdirv(std::slice::from_ref(&a))
1334 }
1335
1336 fn symlink(&mut self, oldpath: &Path, newpath: &Path) -> VfResult<()> {
1338 self.symlinkv(
1339 std::slice::from_ref(&oldpath),
1340 std::slice::from_ref(&newpath),
1341 )
1342 }
1343
1344 fn readlink(&mut self, path: &Path) -> VfResult<Vec<u8>> {
1346 take_single_result("readlink", self.readlinkv(std::slice::from_ref(&path))?)
1347 }
1348
1349 fn ldupv(&mut self, pairs: &[ExtentPair]) -> VfRes {
1353 self.dupv(pairs)
1354 }
1355
1356 fn copyv(&mut self, pairs: &[ExtentPair]) -> VfRes {
1361 self.dupv(pairs)
1362 }
1363
1364 fn ensure_dir(&mut self, dir: &Path, mode: u32) -> VfResult<()> {
1370 use std::path::Component;
1371 let mut so_far = PathBuf::new();
1372 for comp in self.abs_path(dir).components() {
1373 if let Component::Normal(part) = comp {
1374 so_far.push(part);
1375 let full = Path::new("/").join(&so_far);
1376 match self.mkdir(&full, mode) {
1377 Ok(()) => {}
1378 Err(e) if e.err_no() == ERR_EXIST => {}
1379 Err(e) => return Err(e),
1380 }
1381 }
1382 }
1383 Ok(())
1384 }
1385}
1386
1387pub trait VecFsExt: VecFs {
1394 fn stat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
1395 self.stat(path.as_ref())
1396 }
1397
1398 fn lstat_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfAttrs> {
1399 self.lstat(path.as_ref())
1400 }
1401
1402 fn exists_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<bool> {
1403 self.exists(path.as_ref())
1404 }
1405
1406 fn file_type_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<VfType> {
1407 self.file_type(path.as_ref())
1408 }
1409
1410 fn open_path<P: AsRef<Path>>(&mut self, path: P, flags: i32, mode: u32) -> VfResult<VfFile> {
1411 self.open(path.as_ref(), flags, mode)
1412 }
1413
1414 fn mkdir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
1415 self.mkdir(path.as_ref(), mode)
1416 }
1417
1418 fn ensure_dir_path<P: AsRef<Path>>(&mut self, path: P, mode: u32) -> VfResult<()> {
1419 self.ensure_dir(path.as_ref(), mode)
1420 }
1421
1422 fn chdir_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
1423 self.chdir(path.as_ref())
1424 }
1425
1426 fn unlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<()> {
1427 self.unlink(path.as_ref())
1428 }
1429
1430 fn readlink_path<P: AsRef<Path>>(&mut self, path: P) -> VfResult<Vec<u8>> {
1431 self.readlink(path.as_ref())
1432 }
1433
1434 fn symlink_path<P, Q>(&mut self, oldpath: P, newpath: Q) -> VfResult<()>
1435 where
1436 P: AsRef<Path>,
1437 Q: AsRef<Path>,
1438 {
1439 self.symlink(oldpath.as_ref(), newpath.as_ref())
1440 }
1441
1442 fn listdir_path<P: AsRef<Path>>(
1443 &mut self,
1444 path: P,
1445 masks: AttrMask,
1446 max_count: usize,
1447 recursive: bool,
1448 ) -> VfResult<Vec<VfAttrs>> {
1449 self.listdir(path.as_ref(), masks, max_count, recursive)
1450 }
1451
1452 fn walk_path<P: AsRef<Path>>(
1453 &mut self,
1454 root: P,
1455 masks: AttrMask,
1456 sort: &mut dyn FnMut(&Path, &mut Vec<VfAttrs>),
1457 ) -> VfResult<Vec<WalkEntry>> {
1458 self.walk(root.as_ref(), masks, sort)
1459 }
1460
1461 fn openv_paths<P: AsRef<Path>>(
1462 &mut self,
1463 paths: &[P],
1464 flags: &[i32],
1465 modes: &[u32],
1466 ) -> VfResult<Vec<VfFile>> {
1467 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1468 self.openv(&refs, flags, modes)
1469 }
1470
1471 fn openv_simple_paths<P: AsRef<Path>>(
1472 &mut self,
1473 paths: &[P],
1474 flags: i32,
1475 mode: u32,
1476 ) -> VfResult<Vec<VfFile>> {
1477 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1478 self.openv_simple(&refs, flags, mode)
1479 }
1480
1481 fn unlinkv_paths<P: AsRef<Path>>(&mut self, paths: &[P]) -> VfRes {
1482 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1483 self.unlinkv(&refs)
1484 }
1485
1486 fn rm_paths<P: AsRef<Path>>(&mut self, paths: &[P], recursive: bool) -> VfRes {
1487 let refs: Vec<&Path> = paths.iter().map(|p| p.as_ref()).collect();
1488 self.rm(&refs, recursive)
1489 }
1490
1491 fn rm_recursive_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1492 self.rm(&[path.as_ref()], true)
1493 }
1494
1495 fn rm_contents_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1496 self.rm_contents(path.as_ref())
1497 }
1498
1499 fn ensure_empty_dir_path<P: AsRef<Path>>(&mut self, path: P) -> VfRes {
1500 self.ensure_empty_dir(path.as_ref())
1501 }
1502}
1503
1504impl<T: VecFs + ?Sized> VecFsExt for T {}
1505
1506pub fn rm_recursive(fs: &mut impl VecFs, dir: &Path) -> VfRes {
1508 fs.rm(&[dir], true)
1509}
1510
1511#[cfg(test)]
1512mod contract_tests {
1513 use super::*;
1514
1515 fn assert_contract_error(error: VfError, index: Option<usize>, detail: &str) {
1516 assert_eq!(error.domain(), vfsi_core::ErrorDomain::Transport);
1517 assert_eq!(error.index(), index);
1518 assert_eq!(error.status(), None);
1519 let message = error.to_string();
1520 assert!(message.contains("backend contract violation"), "{message}");
1521 assert!(message.contains(detail), "{message}");
1522 }
1523
1524 fn request() -> ReadOp {
1525 ReadOp::at(VfFile::from_path("/file"), 0, 1)
1526 }
1527
1528 fn result() -> ReadResult {
1529 ReadResult {
1530 file: VfFile::from_path("/file"),
1531 offset: 0,
1532 data: vec![1],
1533 eof: true,
1534 }
1535 }
1536
1537 #[test]
1538 fn read_result_cardinality_is_checked_before_indexing() {
1539 let requests = [request(), request()];
1540 validate_read_results("test", &requests, &[result(), result()]).unwrap();
1541 validate_read_results("test", &[], &[]).unwrap();
1542 assert_contract_error(
1543 validate_read_results("test", &requests, &[result()]).unwrap_err(),
1544 None,
1545 "returned 1 results for 2 requests",
1546 );
1547 assert_contract_error(
1548 validate_read_results("test", &requests[..1], &[result(), result()]).unwrap_err(),
1549 None,
1550 "returned 2 results for 1 requests",
1551 );
1552 }
1553
1554 #[test]
1555 fn read_result_identity_offset_progress_and_size_are_checked() {
1556 let request = request();
1557 validate_read_results("test", std::slice::from_ref(&request), &[result()]).unwrap();
1558 let larger = ReadOp::at(VfFile::from_path("/file"), 0, 4);
1559 validate_read_results(
1560 "test",
1561 &[larger],
1562 &[ReadResult {
1563 eof: false,
1564 ..result()
1565 }],
1566 )
1567 .unwrap();
1568 validate_read_results(
1569 "test",
1570 std::slice::from_ref(&request),
1571 &[ReadResult {
1572 data: Vec::new(),
1573 eof: true,
1574 ..result()
1575 }],
1576 )
1577 .unwrap();
1578 for malformed in [
1579 ReadResult {
1580 file: VfFile::from_path("/other"),
1581 ..result()
1582 },
1583 ReadResult {
1584 offset: 1,
1585 ..result()
1586 },
1587 ReadResult {
1588 data: vec![1, 2],
1589 ..result()
1590 },
1591 ReadResult {
1592 data: Vec::new(),
1593 eof: false,
1594 ..result()
1595 },
1596 ] {
1597 assert_contract_error(
1600 validate_read_results(
1601 "test",
1602 &[request.clone(), request.clone()],
1603 &[result(), malformed],
1604 )
1605 .unwrap_err(),
1606 Some(1),
1607 "test",
1608 );
1609 }
1610 }
1611
1612 #[test]
1613 fn write_result_identity_offset_size_and_cardinality_are_checked() {
1614 let file = VfFile::from_path("/file");
1615 let request = WriteOpRef::new(&file, VfOffset::At(0), b"x");
1616 let valid = WriteResult {
1617 file: file.clone(),
1618 offset: 0,
1619 written: 1,
1620 stable: true,
1621 };
1622 validate_write_results("test", &[request], std::slice::from_ref(&valid)).unwrap();
1623 validate_write_results(
1626 "test",
1627 &[request],
1628 &[WriteResult {
1629 written: 0,
1630 ..valid.clone()
1631 }],
1632 )
1633 .unwrap();
1634 validate_write_results("test", &[], &[]).unwrap();
1635 assert_contract_error(
1636 validate_write_results("test", &[request], &[]).unwrap_err(),
1637 None,
1638 "returned 0 results",
1639 );
1640 assert_contract_error(
1641 validate_write_results("test", &[request], &[valid.clone(), valid.clone()])
1642 .unwrap_err(),
1643 None,
1644 "returned 2 results",
1645 );
1646 for malformed in [
1647 WriteResult {
1648 file: VfFile::from_path("/other"),
1649 ..valid.clone()
1650 },
1651 WriteResult {
1652 offset: 1,
1653 ..valid.clone()
1654 },
1655 WriteResult {
1656 written: 2,
1657 ..valid.clone()
1658 },
1659 ] {
1660 assert_contract_error(
1661 validate_write_results("test", &[request, request], &[valid.clone(), malformed])
1662 .unwrap_err(),
1663 Some(1),
1664 "test",
1665 );
1666 }
1667 }
1668
1669 #[test]
1670 fn scalar_result_cardinality_is_checked_without_panicking() {
1671 assert_contract_error(
1672 take_single_result::<u8>("readlink", Vec::new()).unwrap_err(),
1673 None,
1674 "readlink",
1675 );
1676 assert_contract_error(
1677 take_single_result("readlink", vec![1u8, 2]).unwrap_err(),
1678 None,
1679 "readlink",
1680 );
1681 assert_eq!(take_single_result("readlink", vec![7u8]).unwrap(), 7);
1682 }
1683}